Maintenance: split each provider in a package
- Fix VyprVPN port - Fix missing Auth overrides
This commit is contained in:
@@ -4,3 +4,13 @@ const (
|
|||||||
TUN = "tun0"
|
TUN = "tun0"
|
||||||
TAP = "tap0"
|
TAP = "tap0"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
AES128cbc = "aes-128-cbc"
|
||||||
|
AES256cbc = "aes-256-cbc"
|
||||||
|
AES128gcm = "aes-128-gcm"
|
||||||
|
AES256gcm = "aes-256-gcm"
|
||||||
|
SHA1 = "sha1"
|
||||||
|
SHA256 = "sha256"
|
||||||
|
SHA512 = "sha512"
|
||||||
|
)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package models
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
type OpenVPNConnection struct {
|
type OpenVPNConnection struct {
|
||||||
@@ -15,3 +16,11 @@ func (o *OpenVPNConnection) Equal(other OpenVPNConnection) bool {
|
|||||||
return o.IP.Equal(other.IP) && o.Port == other.Port && o.Protocol == other.Protocol &&
|
return o.IP.Equal(other.IP) && o.Port == other.Port && o.Protocol == other.Protocol &&
|
||||||
o.Hostname == other.Hostname
|
o.Hostname == other.Hostname
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o OpenVPNConnection) RemoteLine() (line string) {
|
||||||
|
return "remote " + o.IP.String() + " " + strconv.Itoa(int(o.Port))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o OpenVPNConnection) ProtoLine() (line string) {
|
||||||
|
return "proto " + o.Protocol
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
package provider
|
|
||||||
|
|
||||||
const (
|
|
||||||
aes256cbc = "aes-256-cbc"
|
|
||||||
aes128gcm = "aes-128-gcm"
|
|
||||||
aes256gcm = "aes-256-gcm"
|
|
||||||
sha256 = "sha256"
|
|
||||||
)
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration"
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/gluetun/internal/firewall"
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
"github.com/qdm12/golibs/logging"
|
|
||||||
"github.com/qdm12/golibs/os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type cyberghost struct {
|
|
||||||
servers []models.CyberghostServer
|
|
||||||
randSource rand.Source
|
|
||||||
}
|
|
||||||
|
|
||||||
func newCyberghost(servers []models.CyberghostServer, timeNow timeNowFunc) *cyberghost {
|
|
||||||
return &cyberghost{
|
|
||||||
servers: servers,
|
|
||||||
randSource: rand.NewSource(timeNow().UnixNano()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *cyberghost) filterServers(regions, hostnames []string, group string) (servers []models.CyberghostServer) {
|
|
||||||
for _, server := range c.servers {
|
|
||||||
switch {
|
|
||||||
case group != "" && !strings.EqualFold(group, server.Group),
|
|
||||||
filterByPossibilities(server.Region, regions),
|
|
||||||
filterByPossibilities(server.Hostname, hostnames):
|
|
||||||
default:
|
|
||||||
servers = append(servers, server)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return servers
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *cyberghost) GetOpenVPNConnection(selection configuration.ServerSelection) (
|
|
||||||
connection models.OpenVPNConnection, err error) {
|
|
||||||
const httpsPort = 443
|
|
||||||
protocol := tcpBoolToProtocol(selection.TCP)
|
|
||||||
if selection.TargetIP != nil {
|
|
||||||
return models.OpenVPNConnection{IP: selection.TargetIP, Port: httpsPort, Protocol: protocol}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
servers := c.filterServers(selection.Regions, selection.Hostnames, selection.Group)
|
|
||||||
if len(servers) == 0 {
|
|
||||||
return connection,
|
|
||||||
fmt.Errorf("no server found for regions %s and group %q", commaJoin(selection.Regions), selection.Group)
|
|
||||||
}
|
|
||||||
|
|
||||||
var connections []models.OpenVPNConnection
|
|
||||||
for _, server := range servers {
|
|
||||||
for _, IP := range server.IPs {
|
|
||||||
connections = append(connections, models.OpenVPNConnection{IP: IP, Port: httpsPort, Protocol: protocol})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pickRandomConnection(connections, c.randSource), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *cyberghost) BuildConf(connection models.OpenVPNConnection,
|
|
||||||
username string, settings configuration.OpenVPN) (lines []string) {
|
|
||||||
if len(settings.Cipher) == 0 {
|
|
||||||
settings.Cipher = aes256cbc
|
|
||||||
}
|
|
||||||
if len(settings.Auth) == 0 {
|
|
||||||
settings.Auth = sha256
|
|
||||||
}
|
|
||||||
lines = []string{
|
|
||||||
"client",
|
|
||||||
"dev tun",
|
|
||||||
"nobind",
|
|
||||||
"persist-key",
|
|
||||||
"persist-tun",
|
|
||||||
"remote-cert-tls server",
|
|
||||||
"ping 10",
|
|
||||||
"ping-exit 60",
|
|
||||||
"ping-timer-rem",
|
|
||||||
"tls-exit",
|
|
||||||
|
|
||||||
// Cyberghost specific
|
|
||||||
// "redirect-gateway def1",
|
|
||||||
"ncp-disable",
|
|
||||||
"explicit-exit-notify 2",
|
|
||||||
"script-security 2",
|
|
||||||
"route-delay 5",
|
|
||||||
|
|
||||||
// Added constant values
|
|
||||||
"auth-nocache",
|
|
||||||
"mute-replay-warnings",
|
|
||||||
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
|
||||||
"auth-retry nointeract",
|
|
||||||
"suppress-timestamps",
|
|
||||||
|
|
||||||
// Modified variables
|
|
||||||
fmt.Sprintf("verb %d", settings.Verbosity),
|
|
||||||
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
|
||||||
fmt.Sprintf("proto %s", connection.Protocol),
|
|
||||||
fmt.Sprintf("remote %s %d", connection.IP, connection.Port),
|
|
||||||
"data-ciphers-fallback " + settings.Cipher,
|
|
||||||
"data-ciphers " + settings.Cipher,
|
|
||||||
fmt.Sprintf("auth %s", settings.Auth),
|
|
||||||
}
|
|
||||||
if strings.HasSuffix(settings.Cipher, "-gcm") {
|
|
||||||
lines = append(lines, "ncp-ciphers AES-256-GCM:AES-256-CBC:AES-128-GCM")
|
|
||||||
}
|
|
||||||
if !settings.Root {
|
|
||||||
lines = append(lines, "user "+username)
|
|
||||||
}
|
|
||||||
if settings.MSSFix > 0 {
|
|
||||||
lines = append(lines, "mssfix "+strconv.Itoa(int(settings.MSSFix)))
|
|
||||||
}
|
|
||||||
lines = append(lines, []string{
|
|
||||||
"<ca>",
|
|
||||||
"-----BEGIN CERTIFICATE-----",
|
|
||||||
constants.CyberghostCertificate,
|
|
||||||
"-----END CERTIFICATE-----",
|
|
||||||
"</ca>",
|
|
||||||
}...)
|
|
||||||
lines = append(lines, []string{
|
|
||||||
"<cert>",
|
|
||||||
"-----BEGIN CERTIFICATE-----",
|
|
||||||
settings.Provider.ExtraConfigOptions.ClientCertificate,
|
|
||||||
"-----END CERTIFICATE-----",
|
|
||||||
"</cert>",
|
|
||||||
}...)
|
|
||||||
lines = append(lines, []string{
|
|
||||||
"<key>",
|
|
||||||
"-----BEGIN PRIVATE KEY-----",
|
|
||||||
settings.Provider.ExtraConfigOptions.ClientKey,
|
|
||||||
"-----END PRIVATE KEY-----",
|
|
||||||
"</key>",
|
|
||||||
"",
|
|
||||||
}...)
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *cyberghost) PortForward(ctx context.Context, client *http.Client,
|
|
||||||
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
|
||||||
syncState func(port uint16) (pfFilepath string)) {
|
|
||||||
panic("port forwarding is not supported for cyberghost")
|
|
||||||
}
|
|
||||||
40
internal/provider/cyberghost/connection.go
Normal file
40
internal/provider/cyberghost/connection.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package cyberghost
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Cyberghost) GetOpenVPNConnection(selection configuration.ServerSelection) (
|
||||||
|
connection models.OpenVPNConnection, err error) {
|
||||||
|
const port = 443
|
||||||
|
protocol := constants.UDP
|
||||||
|
if selection.TCP {
|
||||||
|
protocol = constants.TCP
|
||||||
|
}
|
||||||
|
|
||||||
|
servers, err := c.filterServers(selection)
|
||||||
|
if err != nil {
|
||||||
|
return connection, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var connections []models.OpenVPNConnection
|
||||||
|
for _, server := range servers {
|
||||||
|
for _, IP := range server.IPs {
|
||||||
|
connection := models.OpenVPNConnection{
|
||||||
|
IP: IP,
|
||||||
|
Port: port,
|
||||||
|
Protocol: protocol,
|
||||||
|
}
|
||||||
|
connections = append(connections, connection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if selection.TargetIP != nil {
|
||||||
|
return utils.GetTargetIPConnection(connections, selection.TargetIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.PickRandomConnection(connections, c.randSource), nil
|
||||||
|
}
|
||||||
28
internal/provider/cyberghost/filter.go
Normal file
28
internal/provider/cyberghost/filter.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package cyberghost
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Cyberghost) filterServers(selection configuration.ServerSelection) (
|
||||||
|
servers []models.CyberghostServer, err error) {
|
||||||
|
for _, server := range c.servers {
|
||||||
|
switch {
|
||||||
|
case selection.Group != "" && !strings.EqualFold(selection.Group, server.Group), // TODO make CSV
|
||||||
|
utils.FilterByPossibilities(server.Region, selection.Regions),
|
||||||
|
utils.FilterByPossibilities(server.Hostname, selection.Hostnames):
|
||||||
|
default:
|
||||||
|
servers = append(servers, server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(servers) == 0 {
|
||||||
|
return nil, utils.NoServerFoundError(selection)
|
||||||
|
}
|
||||||
|
|
||||||
|
return servers, nil
|
||||||
|
}
|
||||||
@@ -1,22 +1,26 @@
|
|||||||
package provider
|
package cyberghost
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_cyberghost_filterServers(t *testing.T) {
|
func Test_Cyberghost_filterServers(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
servers []models.CyberghostServer
|
servers []models.CyberghostServer
|
||||||
regions []string
|
selection configuration.ServerSelection
|
||||||
hostnames []string
|
|
||||||
group string
|
|
||||||
filteredServers []models.CyberghostServer
|
filteredServers []models.CyberghostServer
|
||||||
|
err error
|
||||||
}{
|
}{
|
||||||
"no servers": {},
|
"no servers": {
|
||||||
|
err: errors.New("no server found: for protocol udp"),
|
||||||
|
},
|
||||||
"servers without filter": {
|
"servers without filter": {
|
||||||
servers: []models.CyberghostServer{
|
servers: []models.CyberghostServer{
|
||||||
{Region: "a", Group: "1"},
|
{Region: "a", Group: "1"},
|
||||||
@@ -38,7 +42,9 @@ func Test_cyberghost_filterServers(t *testing.T) {
|
|||||||
{Region: "c", Group: "2"},
|
{Region: "c", Group: "2"},
|
||||||
{Region: "d", Group: "2"},
|
{Region: "d", Group: "2"},
|
||||||
},
|
},
|
||||||
regions: []string{"a", "c"},
|
selection: configuration.ServerSelection{
|
||||||
|
Regions: []string{"a", "c"},
|
||||||
|
},
|
||||||
filteredServers: []models.CyberghostServer{
|
filteredServers: []models.CyberghostServer{
|
||||||
{Region: "a", Group: "1"},
|
{Region: "a", Group: "1"},
|
||||||
{Region: "c", Group: "2"},
|
{Region: "c", Group: "2"},
|
||||||
@@ -51,7 +57,9 @@ func Test_cyberghost_filterServers(t *testing.T) {
|
|||||||
{Region: "c", Group: "2"},
|
{Region: "c", Group: "2"},
|
||||||
{Region: "d", Group: "2"},
|
{Region: "d", Group: "2"},
|
||||||
},
|
},
|
||||||
group: "1",
|
selection: configuration.ServerSelection{
|
||||||
|
Group: "1",
|
||||||
|
},
|
||||||
filteredServers: []models.CyberghostServer{
|
filteredServers: []models.CyberghostServer{
|
||||||
{Region: "a", Group: "1"},
|
{Region: "a", Group: "1"},
|
||||||
{Region: "b", Group: "1"},
|
{Region: "b", Group: "1"},
|
||||||
@@ -64,8 +72,10 @@ func Test_cyberghost_filterServers(t *testing.T) {
|
|||||||
{Region: "c", Group: "2"},
|
{Region: "c", Group: "2"},
|
||||||
{Region: "d", Group: "2"},
|
{Region: "d", Group: "2"},
|
||||||
},
|
},
|
||||||
regions: []string{"a", "c"},
|
selection: configuration.ServerSelection{
|
||||||
group: "1",
|
Regions: []string{"a", "c"},
|
||||||
|
Group: "1",
|
||||||
|
},
|
||||||
filteredServers: []models.CyberghostServer{
|
filteredServers: []models.CyberghostServer{
|
||||||
{Region: "a", Group: "1"},
|
{Region: "a", Group: "1"},
|
||||||
},
|
},
|
||||||
@@ -76,7 +86,9 @@ func Test_cyberghost_filterServers(t *testing.T) {
|
|||||||
{Hostname: "b"},
|
{Hostname: "b"},
|
||||||
{Hostname: "c"},
|
{Hostname: "c"},
|
||||||
},
|
},
|
||||||
hostnames: []string{"a", "c"},
|
selection: configuration.ServerSelection{
|
||||||
|
Hostnames: []string{"a", "c"},
|
||||||
|
},
|
||||||
filteredServers: []models.CyberghostServer{
|
filteredServers: []models.CyberghostServer{
|
||||||
{Hostname: "a"},
|
{Hostname: "a"},
|
||||||
{Hostname: "c"},
|
{Hostname: "c"},
|
||||||
@@ -87,8 +99,16 @@ func Test_cyberghost_filterServers(t *testing.T) {
|
|||||||
testCase := testCase
|
testCase := testCase
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
c := &cyberghost{servers: testCase.servers}
|
c := &Cyberghost{servers: testCase.servers}
|
||||||
filteredServers := c.filterServers(testCase.regions, testCase.hostnames, testCase.group)
|
filteredServers, err := c.filterServers(testCase.selection)
|
||||||
|
|
||||||
|
if testCase.err != nil {
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Equal(t, testCase.err.Error(), err.Error())
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
assert.Equal(t, testCase.filteredServers, filteredServers)
|
assert.Equal(t, testCase.filteredServers, filteredServers)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
81
internal/provider/cyberghost/openvpnconf.go
Normal file
81
internal/provider/cyberghost/openvpnconf.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package cyberghost
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Cyberghost) BuildConf(connection models.OpenVPNConnection,
|
||||||
|
username string, settings configuration.OpenVPN) (lines []string) {
|
||||||
|
if settings.Cipher == "" {
|
||||||
|
settings.Cipher = constants.AES256cbc
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.Auth == "" {
|
||||||
|
settings.Auth = constants.SHA256
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = []string{
|
||||||
|
"client",
|
||||||
|
"dev tun",
|
||||||
|
"nobind",
|
||||||
|
"persist-key",
|
||||||
|
"persist-tun",
|
||||||
|
"remote-cert-tls server",
|
||||||
|
"ping 10",
|
||||||
|
"ping-exit 60",
|
||||||
|
"ping-timer-rem",
|
||||||
|
"tls-exit",
|
||||||
|
|
||||||
|
// Cyberghost specific
|
||||||
|
// "redirect-gateway def1",
|
||||||
|
"ncp-disable",
|
||||||
|
"explicit-exit-notify 2",
|
||||||
|
"script-security 2",
|
||||||
|
"route-delay 5",
|
||||||
|
|
||||||
|
// Added constant values
|
||||||
|
"auth-nocache",
|
||||||
|
"mute-replay-warnings",
|
||||||
|
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
||||||
|
"auth-retry nointeract",
|
||||||
|
"suppress-timestamps",
|
||||||
|
|
||||||
|
// Modified variables
|
||||||
|
"verb " + strconv.Itoa(settings.Verbosity),
|
||||||
|
"auth-user-pass " + constants.OpenVPNAuthConf,
|
||||||
|
connection.ProtoLine(),
|
||||||
|
connection.RemoteLine(),
|
||||||
|
"data-ciphers-fallback " + settings.Cipher,
|
||||||
|
"data-ciphers " + settings.Cipher,
|
||||||
|
"auth " + settings.Auth,
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasSuffix(settings.Cipher, "-gcm") {
|
||||||
|
lines = append(lines, "ncp-ciphers AES-256-GCM:AES-256-CBC:AES-128-GCM")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !settings.Root {
|
||||||
|
lines = append(lines, "user "+username)
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.MSSFix > 0 {
|
||||||
|
lines = append(lines, "mssfix "+strconv.Itoa(int(settings.MSSFix)))
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = append(lines, utils.WrapOpenvpnCA(
|
||||||
|
constants.CyberghostCertificate)...)
|
||||||
|
lines = append(lines, utils.WrapOpenvpnCert(
|
||||||
|
settings.Provider.ExtraConfigOptions.ClientCertificate)...)
|
||||||
|
lines = append(lines, utils.WrapOpenvpnKey(
|
||||||
|
settings.Provider.ExtraConfigOptions.ClientKey)...)
|
||||||
|
|
||||||
|
lines = append(lines, "")
|
||||||
|
|
||||||
|
return lines
|
||||||
|
}
|
||||||
17
internal/provider/cyberghost/portforward.go
Normal file
17
internal/provider/cyberghost/portforward.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package cyberghost
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/firewall"
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
|
"github.com/qdm12/golibs/os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Cyberghost) PortForward(ctx context.Context, client *http.Client,
|
||||||
|
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP,
|
||||||
|
fw firewall.Configurator, syncState func(port uint16) (pfFilepath string)) {
|
||||||
|
panic("port forwarding is not supported for Cyberghost")
|
||||||
|
}
|
||||||
19
internal/provider/cyberghost/provider.go
Normal file
19
internal/provider/cyberghost/provider.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package cyberghost
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Cyberghost struct {
|
||||||
|
servers []models.CyberghostServer
|
||||||
|
randSource rand.Source
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(servers []models.CyberghostServer, randSource rand.Source) *Cyberghost {
|
||||||
|
return &Cyberghost{
|
||||||
|
servers: servers,
|
||||||
|
randSource: randSource,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package provider
|
|
||||||
|
|
||||||
import "errors"
|
|
||||||
|
|
||||||
var ErrNoServerFound = errors.New("no server found")
|
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration"
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/gluetun/internal/firewall"
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
"github.com/qdm12/golibs/logging"
|
|
||||||
"github.com/qdm12/golibs/os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type fastestvpn struct {
|
|
||||||
servers []models.FastestvpnServer
|
|
||||||
randSource rand.Source
|
|
||||||
}
|
|
||||||
|
|
||||||
func newFastestvpn(servers []models.FastestvpnServer, timeNow timeNowFunc) *fastestvpn {
|
|
||||||
return &fastestvpn{
|
|
||||||
servers: servers,
|
|
||||||
randSource: rand.NewSource(timeNow().UnixNano()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fastestvpn) filterServers(countries, hostnames []string, tcp bool) (servers []models.FastestvpnServer) {
|
|
||||||
for _, server := range f.servers {
|
|
||||||
switch {
|
|
||||||
case filterByPossibilities(server.Country, countries):
|
|
||||||
case filterByPossibilities(server.Hostname, hostnames):
|
|
||||||
case tcp && !server.TCP:
|
|
||||||
case !tcp && !server.UDP:
|
|
||||||
default:
|
|
||||||
servers = append(servers, server)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return servers
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fastestvpn) notFoundErr(selection configuration.ServerSelection) error {
|
|
||||||
message := "no server found for protocol " + tcpBoolToProtocol(selection.TCP)
|
|
||||||
|
|
||||||
if len(selection.Hostnames) > 0 {
|
|
||||||
message += " + hostnames " + commaJoin(selection.Hostnames)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(selection.Countries) > 0 {
|
|
||||||
message += " + countries " + commaJoin(selection.Countries)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fastestvpn) GetOpenVPNConnection(selection configuration.ServerSelection) (
|
|
||||||
connection models.OpenVPNConnection, err error) {
|
|
||||||
var port uint16 = 4443
|
|
||||||
protocol := tcpBoolToProtocol(selection.TCP)
|
|
||||||
|
|
||||||
if selection.TargetIP != nil {
|
|
||||||
return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: protocol}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
servers := f.filterServers(selection.Countries, selection.Hostnames, selection.TCP)
|
|
||||||
if len(servers) == 0 {
|
|
||||||
return connection, f.notFoundErr(selection)
|
|
||||||
}
|
|
||||||
|
|
||||||
var connections []models.OpenVPNConnection
|
|
||||||
for _, server := range servers {
|
|
||||||
for _, IP := range server.IPs {
|
|
||||||
connection := models.OpenVPNConnection{
|
|
||||||
IP: IP,
|
|
||||||
Port: port,
|
|
||||||
Protocol: protocol,
|
|
||||||
}
|
|
||||||
connections = append(connections, connection)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pickRandomConnection(connections, f.randSource), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fastestvpn) BuildConf(connection models.OpenVPNConnection,
|
|
||||||
username string, settings configuration.OpenVPN) (lines []string) {
|
|
||||||
if len(settings.Cipher) == 0 {
|
|
||||||
settings.Cipher = aes256cbc
|
|
||||||
}
|
|
||||||
if len(settings.Auth) == 0 {
|
|
||||||
settings.Auth = sha256
|
|
||||||
}
|
|
||||||
if settings.MSSFix == 0 {
|
|
||||||
settings.MSSFix = 1450
|
|
||||||
}
|
|
||||||
|
|
||||||
lines = []string{
|
|
||||||
"client",
|
|
||||||
"dev tun",
|
|
||||||
"nobind",
|
|
||||||
"persist-key",
|
|
||||||
"ping 15",
|
|
||||||
"ping-exit 60",
|
|
||||||
"ping-timer-rem",
|
|
||||||
"tls-exit",
|
|
||||||
|
|
||||||
// Fastestvpn specific
|
|
||||||
"ping-restart 0",
|
|
||||||
"tls-client",
|
|
||||||
"tls-cipher TLS-DHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-CBC-SHA256:TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA:TLS-DHE-RSA-WITH-AES-256-CBC-SHA:TLS-RSA-WITH-CAMELLIA-256-CBC-SHA:TLS-RSA-WITH-AES-256-CBC-SHA", //nolint:lll
|
|
||||||
"comp-lzo",
|
|
||||||
"key-direction 1",
|
|
||||||
"tun-mtu 1500",
|
|
||||||
"tun-mtu-extra 32",
|
|
||||||
"mssfix " + strconv.Itoa(int(settings.MSSFix)), // defaults to 1450
|
|
||||||
|
|
||||||
// Added constant values
|
|
||||||
"auth-nocache",
|
|
||||||
"mute-replay-warnings",
|
|
||||||
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
|
||||||
"auth-retry nointeract",
|
|
||||||
"suppress-timestamps",
|
|
||||||
|
|
||||||
// Modified variables
|
|
||||||
"verb " + strconv.Itoa(settings.Verbosity),
|
|
||||||
"auth-user-pass " + constants.OpenVPNAuthConf,
|
|
||||||
"proto " + connection.Protocol,
|
|
||||||
"remote " + connection.IP.String() + " " + strconv.Itoa(int(connection.Port)),
|
|
||||||
"data-ciphers-fallback " + settings.Cipher,
|
|
||||||
"data-ciphers " + settings.Cipher,
|
|
||||||
"auth " + settings.Auth,
|
|
||||||
}
|
|
||||||
if !settings.Root {
|
|
||||||
lines = append(lines, "user "+username)
|
|
||||||
}
|
|
||||||
|
|
||||||
lines = append(lines, []string{
|
|
||||||
"<ca>",
|
|
||||||
"-----BEGIN CERTIFICATE-----",
|
|
||||||
constants.FastestvpnCertificate,
|
|
||||||
"-----END CERTIFICATE-----",
|
|
||||||
"</ca>",
|
|
||||||
}...)
|
|
||||||
|
|
||||||
lines = append(lines, []string{
|
|
||||||
"<tls-auth>",
|
|
||||||
"-----BEGIN OpenVPN Static key V1-----",
|
|
||||||
constants.FastestvpnOpenvpnStaticKeyV1,
|
|
||||||
"-----END OpenVPN Static key V1-----",
|
|
||||||
"</tls-auth>",
|
|
||||||
"",
|
|
||||||
}...)
|
|
||||||
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fastestvpn) PortForward(ctx context.Context, client *http.Client,
|
|
||||||
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
|
||||||
syncState func(port uint16) (pfFilepath string)) {
|
|
||||||
panic("port forwarding is not supported for fastestvpn")
|
|
||||||
}
|
|
||||||
40
internal/provider/fastestvpn/connection.go
Normal file
40
internal/provider/fastestvpn/connection.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package fastestvpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (f *Fastestvpn) GetOpenVPNConnection(selection configuration.ServerSelection) (
|
||||||
|
connection models.OpenVPNConnection, err error) {
|
||||||
|
const port = 4443
|
||||||
|
protocol := constants.UDP
|
||||||
|
if selection.TCP {
|
||||||
|
protocol = constants.TCP
|
||||||
|
}
|
||||||
|
|
||||||
|
servers, err := f.filterServers(selection)
|
||||||
|
if err != nil {
|
||||||
|
return connection, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var connections []models.OpenVPNConnection
|
||||||
|
for _, server := range servers {
|
||||||
|
for _, IP := range server.IPs {
|
||||||
|
connection := models.OpenVPNConnection{
|
||||||
|
IP: IP,
|
||||||
|
Port: port,
|
||||||
|
Protocol: protocol,
|
||||||
|
}
|
||||||
|
connections = append(connections, connection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if selection.TargetIP != nil {
|
||||||
|
return utils.GetTargetIPConnection(connections, selection.TargetIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.PickRandomConnection(connections, f.randSource), nil
|
||||||
|
}
|
||||||
28
internal/provider/fastestvpn/filter.go
Normal file
28
internal/provider/fastestvpn/filter.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package fastestvpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (f *Fastestvpn) filterServers(selection configuration.ServerSelection) (
|
||||||
|
servers []models.FastestvpnServer, err error) {
|
||||||
|
for _, server := range f.servers {
|
||||||
|
switch {
|
||||||
|
case
|
||||||
|
utils.FilterByPossibilities(server.Country, selection.Countries),
|
||||||
|
utils.FilterByPossibilities(server.Hostname, selection.Hostnames),
|
||||||
|
selection.TCP && !server.TCP,
|
||||||
|
!selection.TCP && !server.UDP:
|
||||||
|
default:
|
||||||
|
servers = append(servers, server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(servers) == 0 {
|
||||||
|
return nil, utils.NoServerFoundError(selection)
|
||||||
|
}
|
||||||
|
|
||||||
|
return servers, nil
|
||||||
|
}
|
||||||
73
internal/provider/fastestvpn/openvpnconf.go
Normal file
73
internal/provider/fastestvpn/openvpnconf.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package fastestvpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (f *Fastestvpn) BuildConf(connection models.OpenVPNConnection,
|
||||||
|
username string, settings configuration.OpenVPN) (lines []string) {
|
||||||
|
if settings.Cipher == "" {
|
||||||
|
settings.Cipher = constants.AES256cbc
|
||||||
|
}
|
||||||
|
if settings.Auth == "" {
|
||||||
|
settings.Auth = constants.SHA256
|
||||||
|
}
|
||||||
|
if settings.MSSFix == 0 {
|
||||||
|
settings.MSSFix = 1450
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = []string{
|
||||||
|
"client",
|
||||||
|
"dev tun",
|
||||||
|
"nobind",
|
||||||
|
"persist-key",
|
||||||
|
"ping 15",
|
||||||
|
"ping-exit 60",
|
||||||
|
"ping-timer-rem",
|
||||||
|
"tls-exit",
|
||||||
|
|
||||||
|
// Fastestvpn specific
|
||||||
|
"ping-restart 0",
|
||||||
|
"tls-client",
|
||||||
|
"tls-cipher TLS-DHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-CBC-SHA256:TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA:TLS-DHE-RSA-WITH-AES-256-CBC-SHA:TLS-RSA-WITH-CAMELLIA-256-CBC-SHA:TLS-RSA-WITH-AES-256-CBC-SHA", //nolint:lll
|
||||||
|
"comp-lzo",
|
||||||
|
"key-direction 1",
|
||||||
|
"tun-mtu 1500",
|
||||||
|
"tun-mtu-extra 32",
|
||||||
|
"mssfix " + strconv.Itoa(int(settings.MSSFix)), // defaults to 1450
|
||||||
|
|
||||||
|
// Added constant values
|
||||||
|
"auth-nocache",
|
||||||
|
"mute-replay-warnings",
|
||||||
|
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
||||||
|
"auth-retry nointeract",
|
||||||
|
"suppress-timestamps",
|
||||||
|
|
||||||
|
// Modified variables
|
||||||
|
"verb " + strconv.Itoa(settings.Verbosity),
|
||||||
|
"auth-user-pass " + constants.OpenVPNAuthConf,
|
||||||
|
connection.ProtoLine(),
|
||||||
|
connection.RemoteLine(),
|
||||||
|
"data-ciphers-fallback " + settings.Cipher,
|
||||||
|
"data-ciphers " + settings.Cipher,
|
||||||
|
"auth " + settings.Auth,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !settings.Root {
|
||||||
|
lines = append(lines, "user "+username)
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = append(lines, utils.WrapOpenvpnCA(
|
||||||
|
constants.FastestvpnCertificate)...)
|
||||||
|
lines = append(lines, utils.WrapOpenvpnTLSAuth(
|
||||||
|
constants.FastestvpnOpenvpnStaticKeyV1)...)
|
||||||
|
|
||||||
|
lines = append(lines, "")
|
||||||
|
|
||||||
|
return lines
|
||||||
|
}
|
||||||
17
internal/provider/fastestvpn/portforward.go
Normal file
17
internal/provider/fastestvpn/portforward.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package fastestvpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/firewall"
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
|
"github.com/qdm12/golibs/os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (f *Fastestvpn) PortForward(ctx context.Context, client *http.Client,
|
||||||
|
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP,
|
||||||
|
fw firewall.Configurator, syncState func(port uint16) (pfFilepath string)) {
|
||||||
|
panic("port forwarding is not supported for FastestVPN")
|
||||||
|
}
|
||||||
19
internal/provider/fastestvpn/provider.go
Normal file
19
internal/provider/fastestvpn/provider.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package fastestvpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Fastestvpn struct {
|
||||||
|
servers []models.FastestvpnServer
|
||||||
|
randSource rand.Source
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(servers []models.FastestvpnServer, randSource rand.Source) *Fastestvpn {
|
||||||
|
return &Fastestvpn{
|
||||||
|
servers: servers,
|
||||||
|
randSource: randSource,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,179 +0,0 @@
|
|||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration"
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/gluetun/internal/firewall"
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
"github.com/qdm12/golibs/logging"
|
|
||||||
"github.com/qdm12/golibs/os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type hideMyAss struct {
|
|
||||||
servers []models.HideMyAssServer
|
|
||||||
randSource rand.Source
|
|
||||||
}
|
|
||||||
|
|
||||||
func newHideMyAss(servers []models.HideMyAssServer, timeNow timeNowFunc) *hideMyAss {
|
|
||||||
return &hideMyAss{
|
|
||||||
servers: servers,
|
|
||||||
randSource: rand.NewSource(timeNow().UnixNano()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hideMyAss) filterServers(countries, cities, hostnames []string,
|
|
||||||
tcp bool) (servers []models.HideMyAssServer) {
|
|
||||||
for _, server := range h.servers {
|
|
||||||
switch {
|
|
||||||
case
|
|
||||||
filterByPossibilities(server.Country, countries),
|
|
||||||
filterByPossibilities(server.City, cities),
|
|
||||||
filterByPossibilities(server.Hostname, hostnames),
|
|
||||||
tcp && !server.TCP,
|
|
||||||
!tcp && !server.UDP:
|
|
||||||
default:
|
|
||||||
servers = append(servers, server)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return servers
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hideMyAss) notFoundErr(selection configuration.ServerSelection) error {
|
|
||||||
var filters []string
|
|
||||||
|
|
||||||
if len(selection.Countries) > 0 {
|
|
||||||
filters = append(filters, "countries "+commaJoin(selection.Countries))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(selection.Cities) > 0 {
|
|
||||||
filters = append(filters, "countries "+commaJoin(selection.Cities))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(selection.Hostnames) > 0 {
|
|
||||||
filters = append(filters, "countries "+commaJoin(selection.Hostnames))
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("%w for %s", ErrNoServerFound, strings.Join(filters, " + "))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hideMyAss) GetOpenVPNConnection(selection configuration.ServerSelection) (
|
|
||||||
connection models.OpenVPNConnection, err error) {
|
|
||||||
var defaultPort uint16 = 553
|
|
||||||
protocol := constants.UDP
|
|
||||||
if selection.TCP {
|
|
||||||
protocol = constants.TCP
|
|
||||||
defaultPort = 8080
|
|
||||||
}
|
|
||||||
port := defaultPort
|
|
||||||
if selection.CustomPort > 0 {
|
|
||||||
port = selection.CustomPort
|
|
||||||
}
|
|
||||||
|
|
||||||
if selection.TargetIP != nil {
|
|
||||||
return models.OpenVPNConnection{
|
|
||||||
IP: selection.TargetIP,
|
|
||||||
Port: port,
|
|
||||||
Protocol: protocol,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
servers := h.filterServers(selection.Countries, selection.Cities, selection.Hostnames, selection.TCP)
|
|
||||||
if len(servers) == 0 {
|
|
||||||
return models.OpenVPNConnection{}, h.notFoundErr(selection)
|
|
||||||
}
|
|
||||||
|
|
||||||
var connections []models.OpenVPNConnection
|
|
||||||
for _, server := range servers {
|
|
||||||
for _, IP := range server.IPs {
|
|
||||||
connections = append(connections, models.OpenVPNConnection{
|
|
||||||
IP: IP,
|
|
||||||
Port: port,
|
|
||||||
Protocol: protocol,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pickRandomConnection(connections, h.randSource), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hideMyAss) BuildConf(connection models.OpenVPNConnection,
|
|
||||||
username string, settings configuration.OpenVPN) (lines []string) {
|
|
||||||
if len(settings.Cipher) == 0 {
|
|
||||||
settings.Cipher = aes256cbc
|
|
||||||
}
|
|
||||||
|
|
||||||
lines = []string{
|
|
||||||
"client",
|
|
||||||
"dev tun",
|
|
||||||
"nobind",
|
|
||||||
"persist-key",
|
|
||||||
"ping 5",
|
|
||||||
"ping-exit 30",
|
|
||||||
"ping-timer-rem",
|
|
||||||
"tls-exit",
|
|
||||||
|
|
||||||
// HideMyAss specific
|
|
||||||
"remote-cert-tls server", // updated name of ns-cert-type
|
|
||||||
// "route-metric 1",
|
|
||||||
"comp-lzo yes",
|
|
||||||
"comp-noadapt",
|
|
||||||
|
|
||||||
// Added constant values
|
|
||||||
"mute-replay-warnings",
|
|
||||||
"auth-nocache",
|
|
||||||
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
|
||||||
"auth-retry nointeract",
|
|
||||||
"suppress-timestamps",
|
|
||||||
|
|
||||||
// Modified variables
|
|
||||||
"verb " + strconv.Itoa(settings.Verbosity),
|
|
||||||
"auth-user-pass " + constants.OpenVPNAuthConf,
|
|
||||||
"proto " + connection.Protocol,
|
|
||||||
"remote " + connection.IP.String() + strconv.Itoa(int(connection.Port)),
|
|
||||||
"data-ciphers-fallback " + settings.Cipher,
|
|
||||||
"data-ciphers " + settings.Cipher,
|
|
||||||
}
|
|
||||||
|
|
||||||
if !settings.Root {
|
|
||||||
lines = append(lines, "user "+username)
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.MSSFix > 0 {
|
|
||||||
lines = append(lines, "mssfix "+strconv.Itoa(int(settings.MSSFix)))
|
|
||||||
}
|
|
||||||
|
|
||||||
lines = append(lines, []string{
|
|
||||||
"<ca>",
|
|
||||||
"-----BEGIN CERTIFICATE-----",
|
|
||||||
constants.HideMyAssCA,
|
|
||||||
"-----END CERTIFICATE-----",
|
|
||||||
"</ca>",
|
|
||||||
"<cert>",
|
|
||||||
"-----BEGIN CERTIFICATE-----",
|
|
||||||
constants.HideMyAssCertificate,
|
|
||||||
"-----END CERTIFICATE-----",
|
|
||||||
"</cert>",
|
|
||||||
"<key>",
|
|
||||||
"-----BEGIN RSA PRIVATE KEY-----",
|
|
||||||
constants.HideMyAssRSAPrivateKey,
|
|
||||||
"-----END RSA PRIVATE KEY-----",
|
|
||||||
"</key>",
|
|
||||||
"",
|
|
||||||
}...)
|
|
||||||
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hideMyAss) PortForward(ctx context.Context, client *http.Client,
|
|
||||||
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
|
||||||
syncState func(port uint16) (pfFilepath string)) {
|
|
||||||
panic("port forwarding is not supported for hideMyAss")
|
|
||||||
}
|
|
||||||
45
internal/provider/hidemyass/connection.go
Normal file
45
internal/provider/hidemyass/connection.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package hidemyass
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *HideMyAss) GetOpenVPNConnection(selection configuration.ServerSelection) (
|
||||||
|
connection models.OpenVPNConnection, err error) {
|
||||||
|
var port uint16 = 553
|
||||||
|
protocol := constants.UDP
|
||||||
|
if selection.TCP {
|
||||||
|
protocol = constants.TCP
|
||||||
|
port = 8080
|
||||||
|
}
|
||||||
|
|
||||||
|
if selection.CustomPort > 0 {
|
||||||
|
port = selection.CustomPort
|
||||||
|
}
|
||||||
|
|
||||||
|
servers, err := h.filterServers(selection)
|
||||||
|
if err != nil {
|
||||||
|
return connection, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var connections []models.OpenVPNConnection
|
||||||
|
for _, server := range servers {
|
||||||
|
for _, IP := range server.IPs {
|
||||||
|
connection := models.OpenVPNConnection{
|
||||||
|
IP: IP,
|
||||||
|
Port: port,
|
||||||
|
Protocol: protocol,
|
||||||
|
}
|
||||||
|
connections = append(connections, connection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if selection.TargetIP != nil {
|
||||||
|
return utils.GetTargetIPConnection(connections, selection.TargetIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.PickRandomConnection(connections, h.randSource), nil
|
||||||
|
}
|
||||||
29
internal/provider/hidemyass/filter.go
Normal file
29
internal/provider/hidemyass/filter.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package hidemyass
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *HideMyAss) filterServers(selection configuration.ServerSelection) (
|
||||||
|
servers []models.HideMyAssServer, err error) {
|
||||||
|
for _, server := range h.servers {
|
||||||
|
switch {
|
||||||
|
case
|
||||||
|
utils.FilterByPossibilities(server.Country, selection.Countries),
|
||||||
|
utils.FilterByPossibilities(server.City, selection.Cities),
|
||||||
|
utils.FilterByPossibilities(server.Hostname, selection.Hostnames),
|
||||||
|
selection.TCP && !server.TCP,
|
||||||
|
!selection.TCP && !server.UDP:
|
||||||
|
default:
|
||||||
|
servers = append(servers, server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(servers) == 0 {
|
||||||
|
return nil, utils.NoServerFoundError(selection)
|
||||||
|
}
|
||||||
|
|
||||||
|
return servers, nil
|
||||||
|
}
|
||||||
72
internal/provider/hidemyass/openvpnconf.go
Normal file
72
internal/provider/hidemyass/openvpnconf.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package hidemyass
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *HideMyAss) BuildConf(connection models.OpenVPNConnection,
|
||||||
|
username string, settings configuration.OpenVPN) (lines []string) {
|
||||||
|
if settings.Cipher == "" {
|
||||||
|
settings.Cipher = constants.AES256cbc
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = []string{
|
||||||
|
"client",
|
||||||
|
"dev tun",
|
||||||
|
"nobind",
|
||||||
|
"persist-key",
|
||||||
|
"ping 5",
|
||||||
|
"ping-exit 30",
|
||||||
|
"ping-timer-rem",
|
||||||
|
"tls-exit",
|
||||||
|
|
||||||
|
// HideMyAss specific
|
||||||
|
"remote-cert-tls server", // updated name of ns-cert-type
|
||||||
|
// "route-metric 1",
|
||||||
|
"comp-lzo yes",
|
||||||
|
"comp-noadapt",
|
||||||
|
|
||||||
|
// Added constant values
|
||||||
|
"mute-replay-warnings",
|
||||||
|
"auth-nocache",
|
||||||
|
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
||||||
|
"auth-retry nointeract",
|
||||||
|
"suppress-timestamps",
|
||||||
|
|
||||||
|
// Modified variables
|
||||||
|
"verb " + strconv.Itoa(settings.Verbosity),
|
||||||
|
"auth-user-pass " + constants.OpenVPNAuthConf,
|
||||||
|
"proto " + connection.Protocol,
|
||||||
|
"remote " + connection.IP.String() + strconv.Itoa(int(connection.Port)),
|
||||||
|
"data-ciphers-fallback " + settings.Cipher,
|
||||||
|
"data-ciphers " + settings.Cipher,
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.Auth != "" {
|
||||||
|
lines = append(lines, "auth "+settings.Auth)
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.MSSFix > 0 {
|
||||||
|
lines = append(lines, "mssfix "+strconv.Itoa(int(settings.MSSFix)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !settings.Root {
|
||||||
|
lines = append(lines, "user "+username)
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = append(lines, utils.WrapOpenvpnCA(
|
||||||
|
constants.HideMyAssCA)...)
|
||||||
|
lines = append(lines, utils.WrapOpenvpnCert(
|
||||||
|
constants.HideMyAssCertificate)...)
|
||||||
|
lines = append(lines, utils.WrapOpenvpnRSAKey(
|
||||||
|
constants.HideMyAssRSAPrivateKey)...)
|
||||||
|
|
||||||
|
lines = append(lines, "")
|
||||||
|
|
||||||
|
return lines
|
||||||
|
}
|
||||||
17
internal/provider/hidemyass/portforward.go
Normal file
17
internal/provider/hidemyass/portforward.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package hidemyass
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/firewall"
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
|
"github.com/qdm12/golibs/os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (f *HideMyAss) PortForward(ctx context.Context, client *http.Client,
|
||||||
|
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP,
|
||||||
|
fw firewall.Configurator, syncState func(port uint16) (pfFilepath string)) {
|
||||||
|
panic("port forwarding is not supported for HideMyAss")
|
||||||
|
}
|
||||||
19
internal/provider/hidemyass/provider.go
Normal file
19
internal/provider/hidemyass/provider.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package hidemyass
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HideMyAss struct {
|
||||||
|
servers []models.HideMyAssServer
|
||||||
|
randSource rand.Source
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(servers []models.HideMyAssServer, randSource rand.Source) *HideMyAss {
|
||||||
|
return &HideMyAss{
|
||||||
|
servers: servers,
|
||||||
|
randSource: randSource,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration"
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/gluetun/internal/firewall"
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
"github.com/qdm12/golibs/logging"
|
|
||||||
"github.com/qdm12/golibs/os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type mullvad struct {
|
|
||||||
servers []models.MullvadServer
|
|
||||||
randSource rand.Source
|
|
||||||
}
|
|
||||||
|
|
||||||
func newMullvad(servers []models.MullvadServer, timeNow timeNowFunc) *mullvad {
|
|
||||||
return &mullvad{
|
|
||||||
servers: servers,
|
|
||||||
randSource: rand.NewSource(timeNow().UnixNano()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mullvad) filterServers(countries, cities, hostnames,
|
|
||||||
isps []string, owned bool) (servers []models.MullvadServer) {
|
|
||||||
for _, server := range m.servers {
|
|
||||||
switch {
|
|
||||||
case
|
|
||||||
filterByPossibilities(server.Country, countries),
|
|
||||||
filterByPossibilities(server.City, cities),
|
|
||||||
filterByPossibilities(server.Hostname, hostnames),
|
|
||||||
filterByPossibilities(server.ISP, isps),
|
|
||||||
owned && !server.Owned:
|
|
||||||
default:
|
|
||||||
servers = append(servers, server)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return servers
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mullvad) GetOpenVPNConnection(selection configuration.ServerSelection) (
|
|
||||||
connection models.OpenVPNConnection, err error) {
|
|
||||||
var defaultPort uint16 = 1194
|
|
||||||
protocol := constants.UDP
|
|
||||||
if selection.TCP {
|
|
||||||
defaultPort = 443
|
|
||||||
protocol = constants.TCP
|
|
||||||
}
|
|
||||||
port := defaultPort
|
|
||||||
if selection.CustomPort > 0 {
|
|
||||||
port = selection.CustomPort
|
|
||||||
}
|
|
||||||
|
|
||||||
if selection.TargetIP != nil {
|
|
||||||
return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: protocol}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
servers := m.filterServers(selection.Countries, selection.Cities,
|
|
||||||
selection.Hostnames, selection.ISPs, selection.Owned)
|
|
||||||
if len(servers) == 0 {
|
|
||||||
return connection, fmt.Errorf("no server found for countries %s, cities %s, ISPs %s and owned %t",
|
|
||||||
commaJoin(selection.Countries), commaJoin(selection.Cities), commaJoin(selection.ISPs), selection.Owned)
|
|
||||||
}
|
|
||||||
|
|
||||||
var connections []models.OpenVPNConnection
|
|
||||||
for _, server := range servers {
|
|
||||||
for _, IP := range server.IPs {
|
|
||||||
connections = append(connections, models.OpenVPNConnection{IP: IP, Port: port, Protocol: protocol})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pickRandomConnection(connections, m.randSource), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mullvad) BuildConf(connection models.OpenVPNConnection,
|
|
||||||
username string, settings configuration.OpenVPN) (lines []string) {
|
|
||||||
if len(settings.Cipher) == 0 {
|
|
||||||
settings.Cipher = aes256cbc
|
|
||||||
}
|
|
||||||
lines = []string{
|
|
||||||
"client",
|
|
||||||
"dev tun",
|
|
||||||
"nobind",
|
|
||||||
"persist-key",
|
|
||||||
"remote-cert-tls server",
|
|
||||||
"ping 10",
|
|
||||||
"ping-exit 60",
|
|
||||||
"ping-timer-rem",
|
|
||||||
"tls-exit",
|
|
||||||
|
|
||||||
// Mullvad specific
|
|
||||||
"sndbuf 524288",
|
|
||||||
"rcvbuf 524288",
|
|
||||||
"tls-cipher TLS-DHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-CBC-SHA",
|
|
||||||
"fast-io",
|
|
||||||
"script-security 2",
|
|
||||||
|
|
||||||
// Added constant values
|
|
||||||
"mute-replay-warnings",
|
|
||||||
"auth-nocache",
|
|
||||||
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
|
||||||
"auth-retry nointeract",
|
|
||||||
"suppress-timestamps",
|
|
||||||
|
|
||||||
// Modified variables
|
|
||||||
fmt.Sprintf("verb %d", settings.Verbosity),
|
|
||||||
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
|
||||||
fmt.Sprintf("proto %s", connection.Protocol),
|
|
||||||
fmt.Sprintf("remote %s %d", connection.IP, connection.Port),
|
|
||||||
"data-ciphers-fallback " + settings.Cipher,
|
|
||||||
"data-ciphers " + settings.Cipher,
|
|
||||||
}
|
|
||||||
if settings.Provider.ExtraConfigOptions.OpenVPNIPv6 {
|
|
||||||
lines = append(lines, "tun-ipv6")
|
|
||||||
} else {
|
|
||||||
lines = append(lines, `pull-filter ignore "route-ipv6"`)
|
|
||||||
lines = append(lines, `pull-filter ignore "ifconfig-ipv6"`)
|
|
||||||
}
|
|
||||||
if !settings.Root {
|
|
||||||
lines = append(lines, "user "+username)
|
|
||||||
}
|
|
||||||
if settings.MSSFix > 0 {
|
|
||||||
lines = append(lines, "mssfix "+strconv.Itoa(int(settings.MSSFix)))
|
|
||||||
}
|
|
||||||
lines = append(lines, []string{
|
|
||||||
"<ca>",
|
|
||||||
"-----BEGIN CERTIFICATE-----",
|
|
||||||
constants.MullvadCertificate,
|
|
||||||
"-----END CERTIFICATE-----",
|
|
||||||
"</ca>",
|
|
||||||
"",
|
|
||||||
}...)
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mullvad) PortForward(ctx context.Context, client *http.Client,
|
|
||||||
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
|
||||||
syncState func(port uint16) (pfFilepath string)) {
|
|
||||||
panic("port forwarding is not supported for mullvad")
|
|
||||||
}
|
|
||||||
45
internal/provider/mullvad/connection.go
Normal file
45
internal/provider/mullvad/connection.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package mullvad
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *Mullvad) GetOpenVPNConnection(selection configuration.ServerSelection) (
|
||||||
|
connection models.OpenVPNConnection, err error) {
|
||||||
|
var port uint16 = 1194
|
||||||
|
protocol := constants.UDP
|
||||||
|
if selection.TCP {
|
||||||
|
port = 443
|
||||||
|
protocol = constants.TCP
|
||||||
|
}
|
||||||
|
|
||||||
|
if selection.CustomPort > 0 {
|
||||||
|
port = selection.CustomPort
|
||||||
|
}
|
||||||
|
|
||||||
|
servers, err := m.filterServers(selection)
|
||||||
|
if err != nil {
|
||||||
|
return connection, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var connections []models.OpenVPNConnection
|
||||||
|
for _, server := range servers {
|
||||||
|
for _, IP := range server.IPs {
|
||||||
|
connection := models.OpenVPNConnection{
|
||||||
|
IP: IP,
|
||||||
|
Port: port,
|
||||||
|
Protocol: protocol,
|
||||||
|
}
|
||||||
|
connections = append(connections, connection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if selection.TargetIP != nil {
|
||||||
|
return utils.GetTargetIPConnection(connections, selection.TargetIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.PickRandomConnection(connections, m.randSource), nil
|
||||||
|
}
|
||||||
29
internal/provider/mullvad/filter.go
Normal file
29
internal/provider/mullvad/filter.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package mullvad
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *Mullvad) filterServers(selection configuration.ServerSelection) (
|
||||||
|
servers []models.MullvadServer, err error) {
|
||||||
|
for _, server := range m.servers {
|
||||||
|
switch {
|
||||||
|
case
|
||||||
|
utils.FilterByPossibilities(server.Country, selection.Countries),
|
||||||
|
utils.FilterByPossibilities(server.City, selection.Cities),
|
||||||
|
utils.FilterByPossibilities(server.ISP, selection.ISPs),
|
||||||
|
utils.FilterByPossibilities(server.Hostname, selection.Hostnames),
|
||||||
|
selection.Owned && !server.Owned:
|
||||||
|
default:
|
||||||
|
servers = append(servers, server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(servers) == 0 {
|
||||||
|
return nil, utils.NoServerFoundError(selection)
|
||||||
|
}
|
||||||
|
|
||||||
|
return servers, nil
|
||||||
|
}
|
||||||
77
internal/provider/mullvad/openvpnconf.go
Normal file
77
internal/provider/mullvad/openvpnconf.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package mullvad
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *Mullvad) BuildConf(connection models.OpenVPNConnection,
|
||||||
|
username string, settings configuration.OpenVPN) (lines []string) {
|
||||||
|
if settings.Cipher == "" {
|
||||||
|
settings.Cipher = constants.AES256cbc
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = []string{
|
||||||
|
"client",
|
||||||
|
"dev tun",
|
||||||
|
"nobind",
|
||||||
|
"persist-key",
|
||||||
|
"remote-cert-tls server",
|
||||||
|
"ping 10",
|
||||||
|
"ping-exit 60",
|
||||||
|
"ping-timer-rem",
|
||||||
|
"tls-exit",
|
||||||
|
|
||||||
|
// Mullvad specific
|
||||||
|
"sndbuf 524288",
|
||||||
|
"rcvbuf 524288",
|
||||||
|
"tls-cipher TLS-DHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-CBC-SHA",
|
||||||
|
"fast-io",
|
||||||
|
"script-security 2",
|
||||||
|
|
||||||
|
// Added constant values
|
||||||
|
"auth-nocache",
|
||||||
|
"mute-replay-warnings",
|
||||||
|
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
||||||
|
"auth-retry nointeract",
|
||||||
|
"suppress-timestamps",
|
||||||
|
|
||||||
|
// Modified variables
|
||||||
|
"verb " + strconv.Itoa(settings.Verbosity),
|
||||||
|
"auth-user-pass " + constants.OpenVPNAuthConf,
|
||||||
|
connection.ProtoLine(),
|
||||||
|
connection.RemoteLine(),
|
||||||
|
"data-ciphers-fallback " + settings.Cipher,
|
||||||
|
"data-ciphers " + settings.Cipher,
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.Auth != "" {
|
||||||
|
lines = append(lines, "auth "+settings.Auth)
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.Provider.ExtraConfigOptions.OpenVPNIPv6 {
|
||||||
|
lines = append(lines, "tun-ipv6")
|
||||||
|
} else {
|
||||||
|
lines = append(lines, `pull-filter ignore "route-ipv6"`)
|
||||||
|
lines = append(lines, `pull-filter ignore "ifconfig-ipv6"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !settings.Root {
|
||||||
|
lines = append(lines, "user "+username)
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.MSSFix > 0 {
|
||||||
|
lines = append(lines, "mssfix "+strconv.Itoa(int(settings.MSSFix)))
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = append(lines, utils.WrapOpenvpnCA(
|
||||||
|
constants.MullvadCertificate)...)
|
||||||
|
|
||||||
|
lines = append(lines, "")
|
||||||
|
|
||||||
|
return lines
|
||||||
|
}
|
||||||
17
internal/provider/mullvad/portforward.go
Normal file
17
internal/provider/mullvad/portforward.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package mullvad
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/firewall"
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
|
"github.com/qdm12/golibs/os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *Mullvad) PortForward(ctx context.Context, client *http.Client,
|
||||||
|
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP,
|
||||||
|
fw firewall.Configurator, syncState func(port uint16) (pfFilepath string)) {
|
||||||
|
panic("port forwarding logic is not needed for Mullvad")
|
||||||
|
}
|
||||||
19
internal/provider/mullvad/provider.go
Normal file
19
internal/provider/mullvad/provider.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package mullvad
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Mullvad struct {
|
||||||
|
servers []models.MullvadServer
|
||||||
|
randSource rand.Source
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(servers []models.MullvadServer, randSource rand.Source) *Mullvad {
|
||||||
|
return &Mullvad{
|
||||||
|
servers: servers,
|
||||||
|
randSource: randSource,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,184 +0,0 @@
|
|||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration"
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/gluetun/internal/firewall"
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
"github.com/qdm12/golibs/logging"
|
|
||||||
"github.com/qdm12/golibs/os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type nordvpn struct {
|
|
||||||
servers []models.NordvpnServer
|
|
||||||
randSource rand.Source
|
|
||||||
}
|
|
||||||
|
|
||||||
func newNordvpn(servers []models.NordvpnServer, timeNow timeNowFunc) *nordvpn {
|
|
||||||
return &nordvpn{
|
|
||||||
servers: servers,
|
|
||||||
randSource: rand.NewSource(timeNow().UnixNano()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *nordvpn) filterServers(regions, hostnames, names []string, numbers []uint16, tcp bool) (
|
|
||||||
servers []models.NordvpnServer) {
|
|
||||||
numbersStr := make([]string, len(numbers))
|
|
||||||
for i := range numbers {
|
|
||||||
numbersStr[i] = fmt.Sprintf("%d", numbers[i])
|
|
||||||
}
|
|
||||||
for _, server := range n.servers {
|
|
||||||
numberStr := fmt.Sprintf("%d", server.Number)
|
|
||||||
switch {
|
|
||||||
case
|
|
||||||
tcp && !server.TCP,
|
|
||||||
!tcp && !server.UDP,
|
|
||||||
filterByPossibilities(server.Region, regions),
|
|
||||||
filterByPossibilities(server.Hostname, hostnames),
|
|
||||||
filterByPossibilities(server.Name, names),
|
|
||||||
filterByPossibilities(numberStr, numbersStr):
|
|
||||||
default:
|
|
||||||
servers = append(servers, server)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return servers
|
|
||||||
}
|
|
||||||
|
|
||||||
var errNoServerFound = errors.New("no server found")
|
|
||||||
|
|
||||||
func (n *nordvpn) notFoundErr(selection configuration.ServerSelection) error {
|
|
||||||
message := "for protocol " + tcpBoolToProtocol(selection.TCP)
|
|
||||||
|
|
||||||
if len(selection.Regions) > 0 {
|
|
||||||
message += " + regions " + commaJoin(selection.Regions)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(selection.Hostnames) > 0 {
|
|
||||||
message += " + hostnames " + commaJoin(selection.Hostnames)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(selection.Names) > 0 {
|
|
||||||
message += " + names " + commaJoin(selection.Names)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(selection.Numbers) > 0 {
|
|
||||||
numbers := make([]string, len(selection.Numbers))
|
|
||||||
for i, n := range selection.Numbers {
|
|
||||||
numbers[i] = strconv.Itoa(int(n))
|
|
||||||
}
|
|
||||||
message += " + numbers " + commaJoin(numbers)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("%w: %s", errNoServerFound, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *nordvpn) GetOpenVPNConnection(selection configuration.ServerSelection) (
|
|
||||||
connection models.OpenVPNConnection, err error) {
|
|
||||||
var port uint16 = 1194
|
|
||||||
protocol := constants.UDP
|
|
||||||
if selection.TCP {
|
|
||||||
port = 443
|
|
||||||
protocol = constants.TCP
|
|
||||||
}
|
|
||||||
|
|
||||||
if selection.TargetIP != nil {
|
|
||||||
return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: protocol}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
servers := n.filterServers(selection.Regions, selection.Hostnames,
|
|
||||||
selection.Names, selection.Numbers, selection.TCP)
|
|
||||||
if len(servers) == 0 {
|
|
||||||
return connection, n.notFoundErr(selection)
|
|
||||||
}
|
|
||||||
|
|
||||||
connections := make([]models.OpenVPNConnection, len(servers))
|
|
||||||
for i := range servers {
|
|
||||||
connections[i] = models.OpenVPNConnection{IP: servers[i].IP, Port: port, Protocol: protocol}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pickRandomConnection(connections, n.randSource), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *nordvpn) BuildConf(connection models.OpenVPNConnection,
|
|
||||||
username string, settings configuration.OpenVPN) (lines []string) {
|
|
||||||
if len(settings.Cipher) == 0 {
|
|
||||||
settings.Cipher = aes256cbc
|
|
||||||
}
|
|
||||||
if len(settings.Auth) == 0 {
|
|
||||||
settings.Auth = "sha512"
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultMSSFix = 1450
|
|
||||||
if settings.MSSFix == 0 {
|
|
||||||
settings.MSSFix = defaultMSSFix
|
|
||||||
}
|
|
||||||
lines = []string{
|
|
||||||
"client",
|
|
||||||
"dev tun",
|
|
||||||
"nobind",
|
|
||||||
"persist-key",
|
|
||||||
"remote-cert-tls server",
|
|
||||||
"ping-timer-rem",
|
|
||||||
"tls-exit",
|
|
||||||
|
|
||||||
// Nordvpn specific
|
|
||||||
"tun-mtu 1500",
|
|
||||||
"tun-mtu-extra 32",
|
|
||||||
"mssfix " + strconv.Itoa(int(settings.MSSFix)),
|
|
||||||
"reneg-sec 0",
|
|
||||||
"comp-lzo no",
|
|
||||||
"fast-io",
|
|
||||||
"key-direction 1",
|
|
||||||
"ping 15",
|
|
||||||
"ping-restart 0",
|
|
||||||
|
|
||||||
// Added constant values
|
|
||||||
"auth-nocache",
|
|
||||||
"mute-replay-warnings",
|
|
||||||
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
|
||||||
"auth-retry nointeract",
|
|
||||||
"suppress-timestamps",
|
|
||||||
|
|
||||||
// Modified variables
|
|
||||||
fmt.Sprintf("verb %d", settings.Verbosity),
|
|
||||||
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
|
||||||
fmt.Sprintf("proto %s", connection.Protocol),
|
|
||||||
fmt.Sprintf("remote %s %d", connection.IP.String(), connection.Port),
|
|
||||||
"data-ciphers-fallback " + settings.Cipher,
|
|
||||||
"data-ciphers " + settings.Cipher,
|
|
||||||
fmt.Sprintf("auth %s", settings.Auth),
|
|
||||||
}
|
|
||||||
if !settings.Root {
|
|
||||||
lines = append(lines, "user "+username)
|
|
||||||
}
|
|
||||||
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) PortForward(ctx context.Context, client *http.Client,
|
|
||||||
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
|
||||||
syncState func(port uint16) (pfFilepath string)) {
|
|
||||||
panic("port forwarding is not supported for nordvpn")
|
|
||||||
}
|
|
||||||
39
internal/provider/nordvpn/connection.go
Normal file
39
internal/provider/nordvpn/connection.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package nordvpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (n *Nordvpn) GetOpenVPNConnection(selection configuration.ServerSelection) (
|
||||||
|
connection models.OpenVPNConnection, err error) {
|
||||||
|
var port uint16 = 1194
|
||||||
|
protocol := constants.UDP
|
||||||
|
if selection.TCP {
|
||||||
|
port = 443
|
||||||
|
protocol = constants.TCP
|
||||||
|
}
|
||||||
|
|
||||||
|
servers, err := n.filterServers(selection)
|
||||||
|
if err != nil {
|
||||||
|
return connection, err
|
||||||
|
}
|
||||||
|
|
||||||
|
connections := make([]models.OpenVPNConnection, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
connection := models.OpenVPNConnection{
|
||||||
|
IP: servers[i].IP,
|
||||||
|
Port: port,
|
||||||
|
Protocol: protocol,
|
||||||
|
}
|
||||||
|
connections[i] = connection
|
||||||
|
}
|
||||||
|
|
||||||
|
if selection.TargetIP != nil {
|
||||||
|
return utils.GetTargetIPConnection(connections, selection.TargetIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.PickRandomConnection(connections, n.randSource), nil
|
||||||
|
}
|
||||||
38
internal/provider/nordvpn/filter.go
Normal file
38
internal/provider/nordvpn/filter.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package nordvpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (n *Nordvpn) filterServers(selection configuration.ServerSelection) (
|
||||||
|
servers []models.NordvpnServer, err error) {
|
||||||
|
selectedNumbers := make([]string, len(selection.Numbers))
|
||||||
|
for i := range selection.Numbers {
|
||||||
|
selectedNumbers[i] = strconv.Itoa(int(selection.Numbers[i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, server := range n.servers {
|
||||||
|
serverNumber := strconv.Itoa(int(server.Number))
|
||||||
|
switch {
|
||||||
|
case
|
||||||
|
utils.FilterByPossibilities(server.Region, selection.Regions),
|
||||||
|
utils.FilterByPossibilities(server.Hostname, selection.Hostnames),
|
||||||
|
utils.FilterByPossibilities(server.Name, selection.Names),
|
||||||
|
utils.FilterByPossibilities(serverNumber, selectedNumbers),
|
||||||
|
selection.TCP && !server.TCP,
|
||||||
|
!selection.TCP && !server.UDP:
|
||||||
|
default:
|
||||||
|
servers = append(servers, server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(servers) == 0 {
|
||||||
|
return nil, utils.NoServerFoundError(selection)
|
||||||
|
}
|
||||||
|
|
||||||
|
return servers, nil
|
||||||
|
}
|
||||||
75
internal/provider/nordvpn/openvpnconf.go
Normal file
75
internal/provider/nordvpn/openvpnconf.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package nordvpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (n *Nordvpn) BuildConf(connection models.OpenVPNConnection,
|
||||||
|
username string, settings configuration.OpenVPN) (lines []string) {
|
||||||
|
if settings.Cipher == "" {
|
||||||
|
settings.Cipher = constants.AES256cbc
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.Auth == "" {
|
||||||
|
settings.Auth = constants.SHA512
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.MSSFix == 0 {
|
||||||
|
settings.MSSFix = 1450
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = []string{
|
||||||
|
"client",
|
||||||
|
"dev tun",
|
||||||
|
"nobind",
|
||||||
|
"persist-key",
|
||||||
|
"remote-cert-tls server",
|
||||||
|
"ping-timer-rem",
|
||||||
|
"tls-exit",
|
||||||
|
|
||||||
|
// Nordvpn specific
|
||||||
|
"tun-mtu 1500",
|
||||||
|
"tun-mtu-extra 32",
|
||||||
|
"mssfix " + strconv.Itoa(int(settings.MSSFix)),
|
||||||
|
"reneg-sec 0",
|
||||||
|
"comp-lzo no",
|
||||||
|
"fast-io",
|
||||||
|
"key-direction 1",
|
||||||
|
"ping 15",
|
||||||
|
"ping-restart 0",
|
||||||
|
|
||||||
|
// Added constant values
|
||||||
|
"auth-nocache",
|
||||||
|
"mute-replay-warnings",
|
||||||
|
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
||||||
|
"auth-retry nointeract",
|
||||||
|
"suppress-timestamps",
|
||||||
|
|
||||||
|
// Modified variables
|
||||||
|
"verb " + strconv.Itoa(settings.Verbosity),
|
||||||
|
"auth-user-pass " + constants.OpenVPNAuthConf,
|
||||||
|
connection.ProtoLine(),
|
||||||
|
connection.RemoteLine(),
|
||||||
|
"data-ciphers-fallback " + settings.Cipher,
|
||||||
|
"data-ciphers " + settings.Cipher,
|
||||||
|
"auth " + settings.Auth,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !settings.Root {
|
||||||
|
lines = append(lines, "user "+username)
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = append(lines, utils.WrapOpenvpnCA(
|
||||||
|
constants.NordvpnCertificate)...)
|
||||||
|
lines = append(lines, utils.WrapOpenvpnTLSAuth(
|
||||||
|
constants.NordvpnOpenvpnStaticKeyV1)...)
|
||||||
|
|
||||||
|
lines = append(lines, "")
|
||||||
|
|
||||||
|
return lines
|
||||||
|
}
|
||||||
17
internal/provider/nordvpn/portforward.go
Normal file
17
internal/provider/nordvpn/portforward.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package nordvpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/firewall"
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
|
"github.com/qdm12/golibs/os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (n *Nordvpn) PortForward(ctx context.Context, client *http.Client,
|
||||||
|
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP,
|
||||||
|
fw firewall.Configurator, syncState func(port uint16) (pfFilepath string)) {
|
||||||
|
panic("port forwarding is not supported for NordVPN")
|
||||||
|
}
|
||||||
19
internal/provider/nordvpn/provider.go
Normal file
19
internal/provider/nordvpn/provider.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package nordvpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Nordvpn struct {
|
||||||
|
servers []models.NordvpnServer
|
||||||
|
randSource rand.Source
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(servers []models.NordvpnServer, randSource rand.Source) *Nordvpn {
|
||||||
|
return &Nordvpn{
|
||||||
|
servers: servers,
|
||||||
|
randSource: randSource,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,680 +0,0 @@
|
|||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"math/rand"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration"
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/gluetun/internal/firewall"
|
|
||||||
gluetunLog "github.com/qdm12/gluetun/internal/logging"
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
"github.com/qdm12/golibs/logging"
|
|
||||||
"github.com/qdm12/golibs/os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type pia struct {
|
|
||||||
servers []models.PIAServer
|
|
||||||
timeNow timeNowFunc
|
|
||||||
randSource rand.Source
|
|
||||||
activeServer models.PIAServer
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPrivateInternetAccess(servers []models.PIAServer, timeNow timeNowFunc) *pia {
|
|
||||||
return &pia{
|
|
||||||
servers: servers,
|
|
||||||
timeNow: timeNow,
|
|
||||||
randSource: rand.NewSource(timeNow().UnixNano()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrInvalidPort = errors.New("invalid port number")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (p *pia) getPort(selection configuration.ServerSelection) (port uint16, err error) {
|
|
||||||
if selection.CustomPort == 0 {
|
|
||||||
if selection.TCP {
|
|
||||||
switch selection.EncryptionPreset {
|
|
||||||
case constants.PIAEncryptionPresetNormal:
|
|
||||||
port = 502
|
|
||||||
case constants.PIAEncryptionPresetStrong:
|
|
||||||
port = 501
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch selection.EncryptionPreset {
|
|
||||||
case constants.PIAEncryptionPresetNormal:
|
|
||||||
port = 1198
|
|
||||||
case constants.PIAEncryptionPresetStrong:
|
|
||||||
port = 1197
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return port, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
port = selection.CustomPort
|
|
||||||
if selection.TCP {
|
|
||||||
switch port {
|
|
||||||
case 80, 110, 443: //nolint:gomnd
|
|
||||||
return port, nil
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("%w: %d for protocol TCP", ErrInvalidPort, port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch port {
|
|
||||||
case 53, 1194, 1197, 1198, 8080, 9201: //nolint:gomnd
|
|
||||||
return port, nil
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("%w: %d for protocol UDP", ErrInvalidPort, port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pia) notFoundErr(regions, hostnames, names []string, tcp bool) error {
|
|
||||||
message := "for protocol " + tcpBoolToProtocol(tcp)
|
|
||||||
|
|
||||||
if len(regions) > 0 {
|
|
||||||
message += " + regions " + commaJoin(regions)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(hostnames) > 0 {
|
|
||||||
message += " + hostnames " + commaJoin(hostnames)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(names) > 0 {
|
|
||||||
message += " + names " + commaJoin(names)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("%w: %s", errNoServerFound, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pia) GetOpenVPNConnection(selection configuration.ServerSelection) (
|
|
||||||
connection models.OpenVPNConnection, err error) {
|
|
||||||
port, err := p.getPort(selection)
|
|
||||||
if err != nil {
|
|
||||||
return connection, err
|
|
||||||
}
|
|
||||||
|
|
||||||
protocol := tcpBoolToProtocol(selection.TCP)
|
|
||||||
|
|
||||||
servers := p.servers
|
|
||||||
if selection.TargetIP != nil {
|
|
||||||
connection = models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: protocol}
|
|
||||||
} else {
|
|
||||||
servers := p.filterServers(selection.Regions, selection.Hostnames,
|
|
||||||
selection.Names, selection.TCP)
|
|
||||||
if len(servers) == 0 {
|
|
||||||
return connection, p.notFoundErr(selection.Regions, selection.Hostnames,
|
|
||||||
selection.Names, selection.TCP)
|
|
||||||
}
|
|
||||||
|
|
||||||
var connections []models.OpenVPNConnection
|
|
||||||
for _, server := range servers {
|
|
||||||
for _, ip := range server.IPs {
|
|
||||||
connection := models.OpenVPNConnection{
|
|
||||||
IP: ip,
|
|
||||||
Port: port,
|
|
||||||
Protocol: protocol,
|
|
||||||
}
|
|
||||||
connections = append(connections, connection)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
connection = pickRandomConnection(connections, p.randSource)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reverse lookup server from picked connection
|
|
||||||
found := false
|
|
||||||
for _, server := range servers {
|
|
||||||
for _, ip := range server.IPs {
|
|
||||||
if connection.IP.Equal(ip) {
|
|
||||||
p.activeServer = server
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if found {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return connection, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pia) BuildConf(connection models.OpenVPNConnection,
|
|
||||||
username string, settings configuration.OpenVPN) (lines []string) {
|
|
||||||
var X509CRL, certificate string
|
|
||||||
var defaultCipher, defaultAuth string
|
|
||||||
if settings.Provider.ExtraConfigOptions.EncryptionPreset == constants.PIAEncryptionPresetNormal {
|
|
||||||
defaultCipher = "aes-128-cbc"
|
|
||||||
defaultAuth = "sha1"
|
|
||||||
X509CRL = constants.PiaX509CRLNormal
|
|
||||||
certificate = constants.PIACertificateNormal
|
|
||||||
} else { // strong encryption
|
|
||||||
defaultCipher = aes256cbc
|
|
||||||
defaultAuth = "sha256"
|
|
||||||
X509CRL = constants.PiaX509CRLStrong
|
|
||||||
certificate = constants.PIACertificateStrong
|
|
||||||
}
|
|
||||||
if len(settings.Cipher) == 0 {
|
|
||||||
settings.Cipher = defaultCipher
|
|
||||||
}
|
|
||||||
if len(settings.Auth) == 0 {
|
|
||||||
settings.Auth = defaultAuth
|
|
||||||
}
|
|
||||||
lines = []string{
|
|
||||||
"client",
|
|
||||||
"dev tun",
|
|
||||||
"nobind",
|
|
||||||
"persist-key",
|
|
||||||
"remote-cert-tls server",
|
|
||||||
|
|
||||||
// PIA specific
|
|
||||||
"reneg-sec 0",
|
|
||||||
"disable-occ",
|
|
||||||
"compress", // allow PIA server to choose the compression to use
|
|
||||||
|
|
||||||
// Added constant values
|
|
||||||
"auth-nocache",
|
|
||||||
"mute-replay-warnings",
|
|
||||||
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
|
||||||
"auth-retry nointeract",
|
|
||||||
"suppress-timestamps",
|
|
||||||
|
|
||||||
// Modified variables
|
|
||||||
fmt.Sprintf("verb %d", settings.Verbosity),
|
|
||||||
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
|
||||||
fmt.Sprintf("proto %s", connection.Protocol),
|
|
||||||
fmt.Sprintf("remote %s %d", connection.IP, connection.Port),
|
|
||||||
"data-ciphers-fallback " + settings.Cipher,
|
|
||||||
"data-ciphers " + settings.Cipher,
|
|
||||||
fmt.Sprintf("auth %s", settings.Auth),
|
|
||||||
}
|
|
||||||
if strings.HasSuffix(settings.Cipher, "-gcm") {
|
|
||||||
lines = append(lines, "ncp-disable")
|
|
||||||
}
|
|
||||||
if !settings.Root {
|
|
||||||
lines = append(lines, "user "+username)
|
|
||||||
}
|
|
||||||
if settings.MSSFix > 0 {
|
|
||||||
lines = append(lines, "mssfix "+strconv.Itoa(int(settings.MSSFix)))
|
|
||||||
}
|
|
||||||
lines = append(lines, []string{
|
|
||||||
"<crl-verify>",
|
|
||||||
"-----BEGIN X509 CRL-----",
|
|
||||||
X509CRL,
|
|
||||||
"-----END X509 CRL-----",
|
|
||||||
"</crl-verify>",
|
|
||||||
}...)
|
|
||||||
lines = append(lines, []string{
|
|
||||||
"<ca>",
|
|
||||||
"-----BEGIN CERTIFICATE-----",
|
|
||||||
certificate,
|
|
||||||
"-----END CERTIFICATE-----",
|
|
||||||
"</ca>",
|
|
||||||
"",
|
|
||||||
}...)
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint:gocognit
|
|
||||||
func (p *pia) PortForward(ctx context.Context, client *http.Client,
|
|
||||||
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
|
||||||
syncState func(port uint16) (pfFilepath string)) {
|
|
||||||
commonName := p.activeServer.ServerName
|
|
||||||
if !p.activeServer.PortForward {
|
|
||||||
pfLogger.Error("The server %s (region %s) does not support port forwarding",
|
|
||||||
commonName, p.activeServer.Region)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if gateway == nil {
|
|
||||||
pfLogger.Error("aborting because: VPN gateway IP address was not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
privateIPClient, err := newPIAHTTPClient(commonName)
|
|
||||||
if err != nil {
|
|
||||||
pfLogger.Error("aborting because: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer pfLogger.Warn("loop exited")
|
|
||||||
data, err := readPIAPortForwardData(openFile)
|
|
||||||
if err != nil {
|
|
||||||
pfLogger.Error(err)
|
|
||||||
}
|
|
||||||
dataFound := data.Port > 0
|
|
||||||
durationToExpiration := data.Expiration.Sub(p.timeNow())
|
|
||||||
expired := durationToExpiration <= 0
|
|
||||||
|
|
||||||
if dataFound {
|
|
||||||
pfLogger.Info("Found persistent forwarded port data for port %d", data.Port)
|
|
||||||
if expired {
|
|
||||||
pfLogger.Warn("Forwarded port data expired on %s, getting another one", data.Expiration.Format(time.RFC1123))
|
|
||||||
} else {
|
|
||||||
pfLogger.Info("Forwarded port data expires in %s", gluetunLog.FormatDuration(durationToExpiration))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !dataFound || expired {
|
|
||||||
tryUntilSuccessful(ctx, pfLogger, func() error {
|
|
||||||
data, err = refreshPIAPortForwardData(ctx, client, privateIPClient, gateway, openFile)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if ctx.Err() != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
durationToExpiration = data.Expiration.Sub(p.timeNow())
|
|
||||||
}
|
|
||||||
pfLogger.Info("Port forwarded is %d expiring in %s", data.Port, gluetunLog.FormatDuration(durationToExpiration))
|
|
||||||
|
|
||||||
// First time binding
|
|
||||||
tryUntilSuccessful(ctx, pfLogger, func() error {
|
|
||||||
if err := bindPIAPort(ctx, privateIPClient, gateway, data); err != nil {
|
|
||||||
return fmt.Errorf("cannot bind port: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if ctx.Err() != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
filepath := syncState(data.Port)
|
|
||||||
pfLogger.Info("Writing port to %s", filepath)
|
|
||||||
if err := writePortForwardedToFile(openFile, filepath, data.Port); err != nil {
|
|
||||||
pfLogger.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := fw.SetAllowedPort(ctx, data.Port, string(constants.TUN)); err != nil {
|
|
||||||
pfLogger.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expiryTimer := time.NewTimer(durationToExpiration)
|
|
||||||
const keepAlivePeriod = 15 * time.Minute
|
|
||||||
// Timer behaving as a ticker
|
|
||||||
keepAliveTimer := time.NewTimer(keepAlivePeriod)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
removeCtx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
||||||
defer cancel()
|
|
||||||
if err := fw.RemoveAllowedPort(removeCtx, data.Port); err != nil {
|
|
||||||
pfLogger.Error(err)
|
|
||||||
}
|
|
||||||
if !keepAliveTimer.Stop() {
|
|
||||||
<-keepAliveTimer.C
|
|
||||||
}
|
|
||||||
if !expiryTimer.Stop() {
|
|
||||||
<-expiryTimer.C
|
|
||||||
}
|
|
||||||
return
|
|
||||||
case <-keepAliveTimer.C:
|
|
||||||
if err := bindPIAPort(ctx, privateIPClient, gateway, data); err != nil {
|
|
||||||
pfLogger.Error("cannot bind port: " + err.Error())
|
|
||||||
}
|
|
||||||
keepAliveTimer.Reset(keepAlivePeriod)
|
|
||||||
case <-expiryTimer.C:
|
|
||||||
pfLogger.Warn("Forward port has expired on %s, getting another one", data.Expiration.Format(time.RFC1123))
|
|
||||||
oldPort := data.Port
|
|
||||||
for {
|
|
||||||
data, err = refreshPIAPortForwardData(ctx, client, privateIPClient, gateway, openFile)
|
|
||||||
if err != nil {
|
|
||||||
pfLogger.Error(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
durationToExpiration := data.Expiration.Sub(p.timeNow())
|
|
||||||
pfLogger.Info("Port forwarded is %d expiring in %s", data.Port, gluetunLog.FormatDuration(durationToExpiration))
|
|
||||||
if err := fw.RemoveAllowedPort(ctx, oldPort); err != nil {
|
|
||||||
pfLogger.Error(err)
|
|
||||||
}
|
|
||||||
if err := fw.SetAllowedPort(ctx, data.Port, string(constants.TUN)); err != nil {
|
|
||||||
pfLogger.Error(err)
|
|
||||||
}
|
|
||||||
filepath := syncState(data.Port)
|
|
||||||
pfLogger.Info("Writing port to %s", filepath)
|
|
||||||
if err := writePortForwardedToFile(openFile, filepath, data.Port); err != nil {
|
|
||||||
pfLogger.Error(err)
|
|
||||||
}
|
|
||||||
if err := bindPIAPort(ctx, privateIPClient, gateway, data); err != nil {
|
|
||||||
pfLogger.Error("cannot bind port: " + err.Error())
|
|
||||||
}
|
|
||||||
if !keepAliveTimer.Stop() {
|
|
||||||
<-keepAliveTimer.C
|
|
||||||
}
|
|
||||||
keepAliveTimer.Reset(keepAlivePeriod)
|
|
||||||
expiryTimer.Reset(durationToExpiration)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pia) filterServers(regions, hostnames, names []string, tcp bool) (
|
|
||||||
filtered []models.PIAServer) {
|
|
||||||
for _, server := range p.servers {
|
|
||||||
switch {
|
|
||||||
case filterByPossibilities(server.Region, regions),
|
|
||||||
filterByPossibilities(server.Hostname, hostnames),
|
|
||||||
filterByPossibilities(server.ServerName, names),
|
|
||||||
tcp && !server.TCP,
|
|
||||||
!tcp && !server.UDP:
|
|
||||||
default:
|
|
||||||
filtered = append(filtered, server)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return filtered
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPIAHTTPClient(serverName string) (client *http.Client, err error) {
|
|
||||||
certificateBytes, err := base64.StdEncoding.DecodeString(constants.PIACertificateStrong)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("cannot decode PIA root certificate: %w", err)
|
|
||||||
}
|
|
||||||
certificate, err := x509.ParseCertificate(certificateBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("cannot parse PIA root certificate: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint:gomnd
|
|
||||||
transport := &http.Transport{
|
|
||||||
// Settings taken from http.DefaultTransport
|
|
||||||
Proxy: http.ProxyFromEnvironment,
|
|
||||||
DialContext: (&net.Dialer{
|
|
||||||
Timeout: 30 * time.Second,
|
|
||||||
KeepAlive: 30 * time.Second,
|
|
||||||
}).DialContext,
|
|
||||||
ForceAttemptHTTP2: true,
|
|
||||||
MaxIdleConns: 100,
|
|
||||||
IdleConnTimeout: 90 * time.Second,
|
|
||||||
TLSHandshakeTimeout: 10 * time.Second,
|
|
||||||
ExpectContinueTimeout: 1 * time.Second,
|
|
||||||
}
|
|
||||||
rootCAs := x509.NewCertPool()
|
|
||||||
rootCAs.AddCert(certificate)
|
|
||||||
transport.TLSClientConfig = &tls.Config{
|
|
||||||
RootCAs: rootCAs,
|
|
||||||
MinVersion: tls.VersionTLS12,
|
|
||||||
ServerName: serverName,
|
|
||||||
}
|
|
||||||
|
|
||||||
const httpTimeout = 30 * time.Second
|
|
||||||
return &http.Client{
|
|
||||||
Transport: transport,
|
|
||||||
Timeout: httpTimeout,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func refreshPIAPortForwardData(ctx context.Context, client, privateIPClient *http.Client,
|
|
||||||
gateway net.IP, openFile os.OpenFileFunc) (data piaPortForwardData, err error) {
|
|
||||||
data.Token, err = fetchPIAToken(ctx, openFile, client)
|
|
||||||
if err != nil {
|
|
||||||
return data, fmt.Errorf("cannot obtain token: %w", err)
|
|
||||||
}
|
|
||||||
data.Port, data.Signature, data.Expiration, err = fetchPIAPortForwardData(ctx, privateIPClient, gateway, data.Token)
|
|
||||||
if err != nil {
|
|
||||||
return data, fmt.Errorf("cannot obtain port forwarding data: %w", err)
|
|
||||||
}
|
|
||||||
if err := writePIAPortForwardData(openFile, data); err != nil {
|
|
||||||
return data, fmt.Errorf("cannot persist port forwarding information to file: %w", err)
|
|
||||||
}
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type piaPayload struct {
|
|
||||||
Token string `json:"token"`
|
|
||||||
Port uint16 `json:"port"`
|
|
||||||
Expiration time.Time `json:"expires_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type piaPortForwardData struct {
|
|
||||||
Port uint16 `json:"port"`
|
|
||||||
Token string `json:"token"`
|
|
||||||
Signature string `json:"signature"`
|
|
||||||
Expiration time.Time `json:"expires_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func readPIAPortForwardData(openFile os.OpenFileFunc) (data piaPortForwardData, err error) {
|
|
||||||
file, err := openFile(constants.PIAPortForward, os.O_RDONLY, 0)
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return data, nil
|
|
||||||
} else if err != nil {
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
|
|
||||||
decoder := json.NewDecoder(file)
|
|
||||||
err = decoder.Decode(&data)
|
|
||||||
if err != nil {
|
|
||||||
_ = file.Close()
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
return data, file.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func writePIAPortForwardData(openFile os.OpenFileFunc, data piaPortForwardData) (err error) {
|
|
||||||
file, err := openFile(constants.PIAPortForward,
|
|
||||||
os.O_CREATE|os.O_TRUNC|os.O_WRONLY,
|
|
||||||
0644)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
encoder := json.NewEncoder(file)
|
|
||||||
err = encoder.Encode(data)
|
|
||||||
if err != nil {
|
|
||||||
_ = file.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return file.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func unpackPIAPayload(payload string) (port uint16, token string, expiration time.Time, err error) {
|
|
||||||
b, err := base64.StdEncoding.DecodeString(payload)
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", expiration,
|
|
||||||
fmt.Errorf("cannot decode payload: payload is %q: %w", payload, err)
|
|
||||||
}
|
|
||||||
var payloadData piaPayload
|
|
||||||
if err := json.Unmarshal(b, &payloadData); err != nil {
|
|
||||||
return 0, "", expiration,
|
|
||||||
fmt.Errorf("cannot parse payload data: data is %q: %w", string(b), err)
|
|
||||||
}
|
|
||||||
return payloadData.Port, payloadData.Token, payloadData.Expiration, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func packPIAPayload(port uint16, token string, expiration time.Time) (payload string, err error) {
|
|
||||||
payloadData := piaPayload{
|
|
||||||
Token: token,
|
|
||||||
Port: port,
|
|
||||||
Expiration: expiration,
|
|
||||||
}
|
|
||||||
b, err := json.Marshal(&payloadData)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("cannot serialize payload data: %w", err)
|
|
||||||
}
|
|
||||||
payload = base64.StdEncoding.EncodeToString(b)
|
|
||||||
return payload, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchPIAToken(ctx context.Context, openFile os.OpenFileFunc,
|
|
||||||
client *http.Client) (token string, err error) {
|
|
||||||
username, password, err := getOpenvpnCredentials(openFile)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("cannot get Openvpn credentials: %w", err)
|
|
||||||
}
|
|
||||||
url := url.URL{
|
|
||||||
Scheme: "https",
|
|
||||||
User: url.UserPassword(username, password),
|
|
||||||
Host: "privateinternetaccess.com",
|
|
||||||
Path: "/gtoken/generateToken",
|
|
||||||
}
|
|
||||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url.String(), nil)
|
|
||||||
if err != nil {
|
|
||||||
return "", replaceInErr(err, map[string]string{
|
|
||||||
username: "<username>", password: "<password>"})
|
|
||||||
}
|
|
||||||
response, err := client.Do(request)
|
|
||||||
if err != nil {
|
|
||||||
return "", replaceInErr(err, map[string]string{
|
|
||||||
username: "<username>", password: "<password>"})
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
if response.StatusCode != http.StatusOK {
|
|
||||||
b, _ := ioutil.ReadAll(response.Body)
|
|
||||||
shortenMessage := string(b)
|
|
||||||
shortenMessage = strings.ReplaceAll(shortenMessage, "\n", "")
|
|
||||||
shortenMessage = strings.ReplaceAll(shortenMessage, " ", " ")
|
|
||||||
return "", fmt.Errorf("%s: response received: %q", response.Status, shortenMessage)
|
|
||||||
}
|
|
||||||
decoder := json.NewDecoder(response.Body)
|
|
||||||
var result struct {
|
|
||||||
Token string `json:"token"`
|
|
||||||
}
|
|
||||||
if err := decoder.Decode(&result); err != nil {
|
|
||||||
return "", err
|
|
||||||
} else if len(result.Token) == 0 {
|
|
||||||
return "", fmt.Errorf("token is empty")
|
|
||||||
}
|
|
||||||
return result.Token, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getOpenvpnCredentials(openFile os.OpenFileFunc) (username, password string, err error) {
|
|
||||||
file, err := openFile(constants.OpenVPNAuthConf, os.O_RDONLY, 0)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", fmt.Errorf("cannot read openvpn auth file: %s", err)
|
|
||||||
}
|
|
||||||
authData, err := ioutil.ReadAll(file)
|
|
||||||
if err != nil {
|
|
||||||
_ = file.Close()
|
|
||||||
return "", "", fmt.Errorf("cannot read openvpn auth file: %s", err)
|
|
||||||
}
|
|
||||||
if err := file.Close(); err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
lines := strings.Split(string(authData), "\n")
|
|
||||||
const minLines = 2
|
|
||||||
if len(lines) < minLines {
|
|
||||||
return "", "", fmt.Errorf("not enough lines (%d) in openvpn auth file", len(lines))
|
|
||||||
}
|
|
||||||
username, password = lines[0], lines[1]
|
|
||||||
return username, password, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchPIAPortForwardData(ctx context.Context, client *http.Client, gateway net.IP, token string) (
|
|
||||||
port uint16, signature string, expiration time.Time, err error) {
|
|
||||||
queryParams := url.Values{}
|
|
||||||
queryParams.Add("token", token)
|
|
||||||
url := url.URL{
|
|
||||||
Scheme: "https",
|
|
||||||
Host: net.JoinHostPort(gateway.String(), "19999"),
|
|
||||||
Path: "/getSignature",
|
|
||||||
RawQuery: queryParams.Encode(),
|
|
||||||
}
|
|
||||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url.String(), nil)
|
|
||||||
if err != nil {
|
|
||||||
err = replaceInErr(err, map[string]string{token: "<token>"})
|
|
||||||
return 0, "", expiration, fmt.Errorf("cannot obtain signature: %w", err)
|
|
||||||
}
|
|
||||||
response, err := client.Do(request)
|
|
||||||
if err != nil {
|
|
||||||
err = replaceInErr(err, map[string]string{token: "<token>"})
|
|
||||||
return 0, "", expiration, fmt.Errorf("cannot obtain signature: %w", err)
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
if response.StatusCode != http.StatusOK {
|
|
||||||
return 0, "", expiration, fmt.Errorf("cannot obtain signature: %s", response.Status)
|
|
||||||
}
|
|
||||||
decoder := json.NewDecoder(response.Body)
|
|
||||||
var data struct {
|
|
||||||
Status string `json:"status"`
|
|
||||||
Payload string `json:"payload"`
|
|
||||||
Signature string `json:"signature"`
|
|
||||||
}
|
|
||||||
if err := decoder.Decode(&data); err != nil {
|
|
||||||
return 0, "", expiration, fmt.Errorf("cannot decode received data: %w", err)
|
|
||||||
} else if data.Status != "OK" {
|
|
||||||
return 0, "", expiration, fmt.Errorf("response received from PIA has status %s", data.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
port, _, expiration, err = unpackPIAPayload(data.Payload)
|
|
||||||
return port, data.Signature, expiration, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func bindPIAPort(ctx context.Context, client *http.Client, gateway net.IP, data piaPortForwardData) (err error) {
|
|
||||||
payload, err := packPIAPayload(data.Port, data.Token, data.Expiration)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
queryParams := url.Values{}
|
|
||||||
queryParams.Add("payload", payload)
|
|
||||||
queryParams.Add("signature", data.Signature)
|
|
||||||
url := url.URL{
|
|
||||||
Scheme: "https",
|
|
||||||
Host: net.JoinHostPort(gateway.String(), "19999"),
|
|
||||||
Path: "/bindPort",
|
|
||||||
RawQuery: queryParams.Encode(),
|
|
||||||
}
|
|
||||||
|
|
||||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url.String(), nil)
|
|
||||||
if err != nil {
|
|
||||||
return replaceInErr(err, map[string]string{
|
|
||||||
payload: "<payload>",
|
|
||||||
data.Signature: "<signature>",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
response, err := client.Do(request)
|
|
||||||
if err != nil {
|
|
||||||
return replaceInErr(err, map[string]string{
|
|
||||||
payload: "<payload>",
|
|
||||||
data.Signature: "<signature>",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
if response.StatusCode != http.StatusOK {
|
|
||||||
return fmt.Errorf("cannot bind port: %s", response.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
decoder := json.NewDecoder(response.Body)
|
|
||||||
var responseData struct {
|
|
||||||
Status string `json:"status"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
if err := decoder.Decode(&responseData); err != nil {
|
|
||||||
return err
|
|
||||||
} else if responseData.Status != "OK" {
|
|
||||||
return fmt.Errorf("response received from PIA: %s (%s)", responseData.Status, responseData.Message)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func writePortForwardedToFile(openFile os.OpenFileFunc,
|
|
||||||
filepath string, port uint16) (err error) {
|
|
||||||
file, err := openFile(filepath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = file.Write([]byte(fmt.Sprintf("%d", port)))
|
|
||||||
if err != nil {
|
|
||||||
_ = file.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return file.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// replaceInErr is used to remove sensitive information from logs.
|
|
||||||
func replaceInErr(err error, substitutions map[string]string) error {
|
|
||||||
s := err.Error()
|
|
||||||
for old, new := range substitutions {
|
|
||||||
s = strings.ReplaceAll(s, old, new)
|
|
||||||
}
|
|
||||||
return errors.New(s)
|
|
||||||
}
|
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration"
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/gluetun/internal/firewall"
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
"github.com/qdm12/golibs/logging"
|
|
||||||
"github.com/qdm12/golibs/os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type privado struct {
|
|
||||||
servers []models.PrivadoServer
|
|
||||||
randSource rand.Source
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPrivado(servers []models.PrivadoServer, timeNow timeNowFunc) *privado {
|
|
||||||
return &privado{
|
|
||||||
servers: servers,
|
|
||||||
randSource: rand.NewSource(timeNow().UnixNano()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *privado) filterServers(countries, regions, cities, hostnames []string) (servers []models.PrivadoServer) {
|
|
||||||
for _, server := range p.servers {
|
|
||||||
switch {
|
|
||||||
case filterByPossibilities(server.Country, countries),
|
|
||||||
filterByPossibilities(server.Region, regions),
|
|
||||||
filterByPossibilities(server.City, cities),
|
|
||||||
filterByPossibilities(server.Hostname, hostnames):
|
|
||||||
default:
|
|
||||||
servers = append(servers, server)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return servers
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *privado) notFoundErr(countries, regions, cities, hostnames []string) error {
|
|
||||||
var message string
|
|
||||||
|
|
||||||
if len(countries) > 0 {
|
|
||||||
message += " + countries " + commaJoin(countries)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(regions) > 0 {
|
|
||||||
message += " + regions " + commaJoin(regions)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cities) > 0 {
|
|
||||||
message += " + cities " + commaJoin(cities)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(hostnames) > 0 {
|
|
||||||
message += " + hostnames " + commaJoin(hostnames)
|
|
||||||
}
|
|
||||||
|
|
||||||
message = "for " + strings.TrimPrefix(message, " +")
|
|
||||||
|
|
||||||
return fmt.Errorf("%w: %s", errNoServerFound, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrProtocolUnsupported = errors.New("network protocol is not supported")
|
|
||||||
|
|
||||||
func (p *privado) GetOpenVPNConnection(selection configuration.ServerSelection) (
|
|
||||||
connection models.OpenVPNConnection, err error) {
|
|
||||||
var port uint16 = 1194
|
|
||||||
const protocol = constants.UDP
|
|
||||||
if selection.TCP {
|
|
||||||
return connection, fmt.Errorf("%w: TCP for provider Privado", ErrProtocolUnsupported)
|
|
||||||
}
|
|
||||||
|
|
||||||
if selection.TargetIP != nil {
|
|
||||||
return models.OpenVPNConnection{
|
|
||||||
IP: selection.TargetIP,
|
|
||||||
Port: port,
|
|
||||||
Protocol: protocol,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
servers := p.filterServers(selection.Countries, selection.Regions,
|
|
||||||
selection.Cities, selection.Hostnames)
|
|
||||||
if len(servers) == 0 {
|
|
||||||
return connection, p.notFoundErr(selection.Countries,
|
|
||||||
selection.Regions, selection.Cities, selection.Hostnames)
|
|
||||||
}
|
|
||||||
|
|
||||||
connections := make([]models.OpenVPNConnection, len(servers))
|
|
||||||
for i := range servers {
|
|
||||||
connection := models.OpenVPNConnection{
|
|
||||||
IP: servers[i].IP,
|
|
||||||
Port: port,
|
|
||||||
Protocol: protocol,
|
|
||||||
Hostname: servers[i].Hostname,
|
|
||||||
}
|
|
||||||
connections[i] = connection
|
|
||||||
}
|
|
||||||
|
|
||||||
return pickRandomConnection(connections, p.randSource), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *privado) BuildConf(connection models.OpenVPNConnection,
|
|
||||||
username string, settings configuration.OpenVPN) (lines []string) {
|
|
||||||
if len(settings.Cipher) == 0 {
|
|
||||||
settings.Cipher = aes256cbc
|
|
||||||
}
|
|
||||||
if len(settings.Auth) == 0 {
|
|
||||||
settings.Auth = sha256
|
|
||||||
}
|
|
||||||
lines = []string{
|
|
||||||
"client",
|
|
||||||
"dev tun",
|
|
||||||
"nobind",
|
|
||||||
"persist-key",
|
|
||||||
"ping 10",
|
|
||||||
"ping-exit 60",
|
|
||||||
"ping-timer-rem",
|
|
||||||
"tls-exit",
|
|
||||||
|
|
||||||
// Privado specific
|
|
||||||
"tls-cipher TLS-DHE-RSA-WITH-AES-256-CBC-SHA:TLS-DHE-DSS-WITH-AES-256-CBC-SHA:TLS-RSA-WITH-AES-256-CBC-SHA",
|
|
||||||
fmt.Sprintf("verify-x509-name %s name", connection.Hostname),
|
|
||||||
|
|
||||||
// Added constant values
|
|
||||||
"auth-nocache",
|
|
||||||
"mute-replay-warnings",
|
|
||||||
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
|
||||||
"auth-retry nointeract",
|
|
||||||
"suppress-timestamps",
|
|
||||||
|
|
||||||
// Modified variables
|
|
||||||
fmt.Sprintf("verb %d", settings.Verbosity),
|
|
||||||
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
|
||||||
fmt.Sprintf("proto %s", connection.Protocol),
|
|
||||||
fmt.Sprintf("remote %s %d", connection.IP, connection.Port),
|
|
||||||
"data-ciphers-fallback " + settings.Cipher,
|
|
||||||
"data-ciphers " + settings.Cipher,
|
|
||||||
fmt.Sprintf("auth %s", settings.Auth),
|
|
||||||
}
|
|
||||||
if !settings.Root {
|
|
||||||
lines = append(lines, "user "+username)
|
|
||||||
}
|
|
||||||
if settings.MSSFix > 0 {
|
|
||||||
lines = append(lines, "mssfix "+strconv.Itoa(int(settings.MSSFix)))
|
|
||||||
}
|
|
||||||
lines = append(lines, []string{
|
|
||||||
"<ca>",
|
|
||||||
"-----BEGIN CERTIFICATE-----",
|
|
||||||
constants.PrivadoCertificate,
|
|
||||||
"-----END CERTIFICATE-----",
|
|
||||||
"</ca>",
|
|
||||||
}...)
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *privado) PortForward(ctx context.Context, client *http.Client,
|
|
||||||
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
|
||||||
syncState func(port uint16) (pfFilepath string)) {
|
|
||||||
panic("port forwarding is not supported for privado")
|
|
||||||
}
|
|
||||||
44
internal/provider/privado/connection.go
Normal file
44
internal/provider/privado/connection.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package privado
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrProtocolUnsupported = errors.New("network protocol is not supported")
|
||||||
|
|
||||||
|
func (p *Privado) GetOpenVPNConnection(selection configuration.ServerSelection) (
|
||||||
|
connection models.OpenVPNConnection, err error) {
|
||||||
|
const port = 1194
|
||||||
|
const protocol = constants.UDP
|
||||||
|
if selection.TCP {
|
||||||
|
return connection, fmt.Errorf("%w: TCP for provider Privado", ErrProtocolUnsupported)
|
||||||
|
}
|
||||||
|
|
||||||
|
servers, err := p.filterServers(selection)
|
||||||
|
if err != nil {
|
||||||
|
return connection, err
|
||||||
|
}
|
||||||
|
|
||||||
|
connections := make([]models.OpenVPNConnection, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
connection := models.OpenVPNConnection{
|
||||||
|
IP: servers[i].IP,
|
||||||
|
Port: port,
|
||||||
|
Protocol: protocol,
|
||||||
|
Hostname: servers[i].Hostname,
|
||||||
|
}
|
||||||
|
connections[i] = connection
|
||||||
|
}
|
||||||
|
|
||||||
|
if selection.TargetIP != nil {
|
||||||
|
return utils.GetTargetIPConnection(connections, selection.TargetIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.PickRandomConnection(connections, p.randSource), nil
|
||||||
|
}
|
||||||
28
internal/provider/privado/filter.go
Normal file
28
internal/provider/privado/filter.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package privado
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *Privado) filterServers(selection configuration.ServerSelection) (
|
||||||
|
servers []models.PrivadoServer, err error) {
|
||||||
|
for _, server := range p.servers {
|
||||||
|
switch {
|
||||||
|
case
|
||||||
|
utils.FilterByPossibilities(server.Country, selection.Countries),
|
||||||
|
utils.FilterByPossibilities(server.Region, selection.Regions),
|
||||||
|
utils.FilterByPossibilities(server.City, selection.Cities),
|
||||||
|
utils.FilterByPossibilities(server.Hostname, selection.Hostnames):
|
||||||
|
default:
|
||||||
|
servers = append(servers, server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(servers) == 0 {
|
||||||
|
return nil, utils.NoServerFoundError(selection)
|
||||||
|
}
|
||||||
|
|
||||||
|
return servers, nil
|
||||||
|
}
|
||||||
67
internal/provider/privado/openvpnconf.go
Normal file
67
internal/provider/privado/openvpnconf.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package privado
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *Privado) BuildConf(connection models.OpenVPNConnection,
|
||||||
|
username string, settings configuration.OpenVPN) (lines []string) {
|
||||||
|
if settings.Cipher == "" {
|
||||||
|
settings.Cipher = constants.AES256cbc
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.Auth == "" {
|
||||||
|
settings.Auth = constants.SHA256
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = []string{
|
||||||
|
"client",
|
||||||
|
"dev tun",
|
||||||
|
"nobind",
|
||||||
|
"persist-key",
|
||||||
|
"ping 10",
|
||||||
|
"ping-exit 60",
|
||||||
|
"ping-timer-rem",
|
||||||
|
"tls-exit",
|
||||||
|
|
||||||
|
// Privado specific
|
||||||
|
"tls-cipher TLS-DHE-RSA-WITH-AES-256-CBC-SHA:TLS-DHE-DSS-WITH-AES-256-CBC-SHA:TLS-RSA-WITH-AES-256-CBC-SHA",
|
||||||
|
"verify-x509-name " + connection.Hostname + " name",
|
||||||
|
|
||||||
|
// Added constant values
|
||||||
|
"auth-nocache",
|
||||||
|
"mute-replay-warnings",
|
||||||
|
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
||||||
|
"auth-retry nointeract",
|
||||||
|
"suppress-timestamps",
|
||||||
|
|
||||||
|
// Modified variables
|
||||||
|
"verb " + strconv.Itoa(settings.Verbosity),
|
||||||
|
"auth-user-pass " + constants.OpenVPNAuthConf,
|
||||||
|
connection.ProtoLine(),
|
||||||
|
connection.RemoteLine(),
|
||||||
|
"data-ciphers-fallback " + settings.Cipher,
|
||||||
|
"data-ciphers " + settings.Cipher,
|
||||||
|
"auth " + settings.Auth,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !settings.Root {
|
||||||
|
lines = append(lines, "user "+username)
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.MSSFix > 0 {
|
||||||
|
lines = append(lines, "mssfix "+strconv.Itoa(int(settings.MSSFix)))
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = append(lines, utils.WrapOpenvpnCA(
|
||||||
|
constants.PrivadoCertificate)...)
|
||||||
|
|
||||||
|
lines = append(lines, "")
|
||||||
|
|
||||||
|
return lines
|
||||||
|
}
|
||||||
17
internal/provider/privado/portforward.go
Normal file
17
internal/provider/privado/portforward.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package privado
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/firewall"
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
|
"github.com/qdm12/golibs/os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *Privado) PortForward(ctx context.Context, client *http.Client,
|
||||||
|
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP,
|
||||||
|
fw firewall.Configurator, syncState func(port uint16) (pfFilepath string)) {
|
||||||
|
panic("port forwarding is not supported for Privado")
|
||||||
|
}
|
||||||
19
internal/provider/privado/provider.go
Normal file
19
internal/provider/privado/provider.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package privado
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Privado struct {
|
||||||
|
servers []models.PrivadoServer
|
||||||
|
randSource rand.Source
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(servers []models.PrivadoServer, randSource rand.Source) *Privado {
|
||||||
|
return &Privado{
|
||||||
|
servers: servers,
|
||||||
|
randSource: randSource,
|
||||||
|
}
|
||||||
|
}
|
||||||
65
internal/provider/privateinternetaccess/connection.go
Normal file
65
internal/provider/privateinternetaccess/connection.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package privateinternetaccess
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *PIA) GetOpenVPNConnection(selection configuration.ServerSelection) (
|
||||||
|
connection models.OpenVPNConnection, err error) {
|
||||||
|
protocol := constants.UDP
|
||||||
|
if selection.TCP {
|
||||||
|
protocol = constants.TCP
|
||||||
|
}
|
||||||
|
|
||||||
|
port, err := getPort(selection.TCP, selection.EncryptionPreset, selection.CustomPort)
|
||||||
|
if err != nil {
|
||||||
|
return connection, err
|
||||||
|
}
|
||||||
|
|
||||||
|
servers, err := p.filterServers(selection)
|
||||||
|
if err != nil {
|
||||||
|
return connection, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var connections []models.OpenVPNConnection
|
||||||
|
for _, server := range servers {
|
||||||
|
for _, IP := range server.IPs {
|
||||||
|
connection := models.OpenVPNConnection{
|
||||||
|
IP: IP,
|
||||||
|
Port: port,
|
||||||
|
Protocol: protocol,
|
||||||
|
}
|
||||||
|
connections = append(connections, connection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if selection.TargetIP != nil {
|
||||||
|
connection, err = utils.GetTargetIPConnection(connections, selection.TargetIP)
|
||||||
|
} else {
|
||||||
|
connection, err = utils.PickRandomConnection(connections, p.randSource), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return connection, err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.activeServer = findActiveServer(servers, connection)
|
||||||
|
|
||||||
|
return connection, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findActiveServer(servers []models.PIAServer,
|
||||||
|
connection models.OpenVPNConnection) (activeServer models.PIAServer) {
|
||||||
|
// Reverse lookup server using the randomly picked connection
|
||||||
|
for _, server := range servers {
|
||||||
|
for _, ip := range server.IPs {
|
||||||
|
if connection.IP.Equal(ip) {
|
||||||
|
return server
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return activeServer
|
||||||
|
}
|
||||||
29
internal/provider/privateinternetaccess/filter.go
Normal file
29
internal/provider/privateinternetaccess/filter.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package privateinternetaccess
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *PIA) filterServers(selection configuration.ServerSelection) (
|
||||||
|
servers []models.PIAServer, err error) {
|
||||||
|
for _, server := range p.servers {
|
||||||
|
switch {
|
||||||
|
case
|
||||||
|
utils.FilterByPossibilities(server.Region, selection.Regions),
|
||||||
|
utils.FilterByPossibilities(server.Hostname, selection.Hostnames),
|
||||||
|
utils.FilterByPossibilities(server.ServerName, selection.Names),
|
||||||
|
selection.TCP && !server.TCP,
|
||||||
|
!selection.TCP && !server.UDP:
|
||||||
|
default:
|
||||||
|
servers = append(servers, server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(servers) == 0 {
|
||||||
|
return nil, utils.NoServerFoundError(selection)
|
||||||
|
}
|
||||||
|
|
||||||
|
return servers, nil
|
||||||
|
}
|
||||||
57
internal/provider/privateinternetaccess/httpclient.go
Normal file
57
internal/provider/privateinternetaccess/httpclient.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package privateinternetaccess
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrParseCertificate = errors.New("cannot parse X509 certificate")
|
||||||
|
)
|
||||||
|
|
||||||
|
func newHTTPClient(serverName string) (client *http.Client, err error) {
|
||||||
|
certificateBytes, err := base64.StdEncoding.DecodeString(constants.PIACertificateStrong)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: %s", ErrParseCertificate, err)
|
||||||
|
}
|
||||||
|
certificate, err := x509.ParseCertificate(certificateBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: %s", ErrParseCertificate, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:gomnd
|
||||||
|
transport := &http.Transport{
|
||||||
|
// Settings taken from http.DefaultTransport
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
DialContext: (&net.Dialer{
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
KeepAlive: 30 * time.Second,
|
||||||
|
}).DialContext,
|
||||||
|
ForceAttemptHTTP2: true,
|
||||||
|
MaxIdleConns: 100,
|
||||||
|
IdleConnTimeout: 90 * time.Second,
|
||||||
|
TLSHandshakeTimeout: 10 * time.Second,
|
||||||
|
ExpectContinueTimeout: 1 * time.Second,
|
||||||
|
}
|
||||||
|
rootCAs := x509.NewCertPool()
|
||||||
|
rootCAs.AddCert(certificate)
|
||||||
|
transport.TLSClientConfig = &tls.Config{
|
||||||
|
RootCAs: rootCAs,
|
||||||
|
MinVersion: tls.VersionTLS12,
|
||||||
|
ServerName: serverName,
|
||||||
|
}
|
||||||
|
|
||||||
|
const httpTimeout = 30 * time.Second
|
||||||
|
return &http.Client{
|
||||||
|
Transport: transport,
|
||||||
|
Timeout: httpTimeout,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
83
internal/provider/privateinternetaccess/openvpnconf.go
Normal file
83
internal/provider/privateinternetaccess/openvpnconf.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package privateinternetaccess
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *PIA) BuildConf(connection models.OpenVPNConnection,
|
||||||
|
username string, settings configuration.OpenVPN) (lines []string) {
|
||||||
|
var defaultCipher, defaultAuth, X509CRL, certificate string
|
||||||
|
if settings.Provider.ExtraConfigOptions.EncryptionPreset == constants.PIAEncryptionPresetNormal {
|
||||||
|
defaultCipher = constants.AES128cbc
|
||||||
|
defaultAuth = constants.SHA1
|
||||||
|
X509CRL = constants.PiaX509CRLNormal
|
||||||
|
certificate = constants.PIACertificateNormal
|
||||||
|
} else { // strong encryption
|
||||||
|
defaultCipher = constants.AES256cbc
|
||||||
|
defaultAuth = constants.SHA256
|
||||||
|
X509CRL = constants.PiaX509CRLStrong
|
||||||
|
certificate = constants.PIACertificateStrong
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.Cipher == "" {
|
||||||
|
settings.Cipher = defaultCipher
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.Auth == "" {
|
||||||
|
settings.Auth = defaultAuth
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = []string{
|
||||||
|
"client",
|
||||||
|
"dev tun",
|
||||||
|
"nobind",
|
||||||
|
"persist-key",
|
||||||
|
"remote-cert-tls server",
|
||||||
|
|
||||||
|
// PIA specific
|
||||||
|
"reneg-sec 0",
|
||||||
|
"disable-occ",
|
||||||
|
"compress", // allow PIA server to choose the compression to use
|
||||||
|
|
||||||
|
// Added constant values
|
||||||
|
"auth-nocache",
|
||||||
|
"mute-replay-warnings",
|
||||||
|
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
||||||
|
"auth-retry nointeract",
|
||||||
|
"suppress-timestamps",
|
||||||
|
|
||||||
|
// Modified variables
|
||||||
|
"verb " + strconv.Itoa(settings.Verbosity),
|
||||||
|
"auth-user-pass " + constants.OpenVPNAuthConf,
|
||||||
|
connection.ProtoLine(),
|
||||||
|
connection.RemoteLine(),
|
||||||
|
"data-ciphers-fallback " + settings.Cipher,
|
||||||
|
"data-ciphers " + settings.Cipher,
|
||||||
|
"auth " + settings.Auth,
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasSuffix(settings.Cipher, "-gcm") {
|
||||||
|
lines = append(lines, "ncp-disable")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !settings.Root {
|
||||||
|
lines = append(lines, "user "+username)
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.MSSFix > 0 {
|
||||||
|
lines = append(lines, "mssfix "+strconv.Itoa(int(settings.MSSFix)))
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = append(lines, utils.WrapOpenvpnCA(certificate)...)
|
||||||
|
lines = append(lines, utils.WrapOpenvpnCRLVerify(X509CRL)...)
|
||||||
|
|
||||||
|
lines = append(lines, "")
|
||||||
|
|
||||||
|
return lines
|
||||||
|
}
|
||||||
59
internal/provider/privateinternetaccess/port.go
Normal file
59
internal/provider/privateinternetaccess/port.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package privateinternetaccess
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getPort(tcp bool, encryptionPreset string, customPort uint16) (
|
||||||
|
port uint16, err error) {
|
||||||
|
if customPort == 0 {
|
||||||
|
return getDefaultPort(tcp, encryptionPreset), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := checkPort(customPort, tcp); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return customPort, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultPort(tcp bool, encryptionPreset string) (port uint16) {
|
||||||
|
if tcp {
|
||||||
|
switch encryptionPreset {
|
||||||
|
case constants.PIAEncryptionPresetNormal:
|
||||||
|
port = 502
|
||||||
|
case constants.PIAEncryptionPresetStrong:
|
||||||
|
port = 501
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch encryptionPreset {
|
||||||
|
case constants.PIAEncryptionPresetNormal:
|
||||||
|
port = 1198
|
||||||
|
case constants.PIAEncryptionPresetStrong:
|
||||||
|
port = 1197
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return port
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrInvalidPort = errors.New("invalid port number")
|
||||||
|
|
||||||
|
func checkPort(port uint16, tcp bool) (err error) {
|
||||||
|
if tcp {
|
||||||
|
switch port {
|
||||||
|
case 80, 110, 443: //nolint:gomnd
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%w: %d for protocol TCP", ErrInvalidPort, port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch port {
|
||||||
|
case 53, 1194, 1197, 1198, 8080, 9201: //nolint:gomnd
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%w: %d for protocol UDP", ErrInvalidPort, port)
|
||||||
|
}
|
||||||
|
}
|
||||||
508
internal/provider/privateinternetaccess/portforward.go
Normal file
508
internal/provider/privateinternetaccess/portforward.go
Normal file
@@ -0,0 +1,508 @@
|
|||||||
|
package privateinternetaccess
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/firewall"
|
||||||
|
format "github.com/qdm12/gluetun/internal/logging"
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
|
"github.com/qdm12/golibs/os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrBindPort = errors.New("cannot bind port")
|
||||||
|
)
|
||||||
|
|
||||||
|
//nolint:gocognit
|
||||||
|
func (p *PIA) PortForward(ctx context.Context, client *http.Client,
|
||||||
|
openFile os.OpenFileFunc, logger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
||||||
|
syncState func(port uint16) (pfFilepath string)) {
|
||||||
|
defer logger.Warn("loop exited")
|
||||||
|
|
||||||
|
commonName := p.activeServer.ServerName
|
||||||
|
if !p.activeServer.PortForward {
|
||||||
|
logger.Error("The server " + commonName +
|
||||||
|
" (region " + p.activeServer.Region + ") does not support port forwarding")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if gateway == nil {
|
||||||
|
logger.Error("aborting because: VPN gateway IP address was not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
privateIPClient, err := newHTTPClient(commonName)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("aborting because: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := readPIAPortForwardData(openFile)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
}
|
||||||
|
dataFound := data.Port > 0
|
||||||
|
durationToExpiration := data.Expiration.Sub(p.timeNow())
|
||||||
|
expired := durationToExpiration <= 0
|
||||||
|
|
||||||
|
if dataFound {
|
||||||
|
logger.Info("Found persistent forwarded port data for port " + strconv.Itoa(int(data.Port)))
|
||||||
|
if expired {
|
||||||
|
logger.Warn("Forwarded port data expired on " +
|
||||||
|
data.Expiration.Format(time.RFC1123) + ", getting another one")
|
||||||
|
} else {
|
||||||
|
logger.Info("Forwarded port data expires in " + format.FormatDuration(durationToExpiration))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !dataFound || expired {
|
||||||
|
tryUntilSuccessful(ctx, logger, func() error {
|
||||||
|
data, err = refreshPIAPortForwardData(ctx, client, privateIPClient, gateway, openFile)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
durationToExpiration = data.Expiration.Sub(p.timeNow())
|
||||||
|
}
|
||||||
|
logger.Info("Port forwarded is " + strconv.Itoa(int(data.Port)) +
|
||||||
|
" expiring in " + format.FormatDuration(durationToExpiration))
|
||||||
|
|
||||||
|
// First time binding
|
||||||
|
tryUntilSuccessful(ctx, logger, func() error {
|
||||||
|
if err := bindPort(ctx, privateIPClient, gateway, data); err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrBindPort, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filepath := syncState(data.Port)
|
||||||
|
logger.Info("Writing port to " + filepath)
|
||||||
|
if err := writePortForwardedToFile(openFile, filepath, data.Port); err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fw.SetAllowedPort(ctx, data.Port, string(constants.TUN)); err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expiryTimer := time.NewTimer(durationToExpiration)
|
||||||
|
const keepAlivePeriod = 15 * time.Minute
|
||||||
|
// Timer behaving as a ticker
|
||||||
|
keepAliveTimer := time.NewTimer(keepAlivePeriod)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
removeCtx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||||
|
defer cancel()
|
||||||
|
if err := fw.RemoveAllowedPort(removeCtx, data.Port); err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
}
|
||||||
|
if !keepAliveTimer.Stop() {
|
||||||
|
<-keepAliveTimer.C
|
||||||
|
}
|
||||||
|
if !expiryTimer.Stop() {
|
||||||
|
<-expiryTimer.C
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case <-keepAliveTimer.C:
|
||||||
|
if err := bindPort(ctx, privateIPClient, gateway, data); err != nil {
|
||||||
|
logger.Error("cannot bind port: " + err.Error())
|
||||||
|
}
|
||||||
|
keepAliveTimer.Reset(keepAlivePeriod)
|
||||||
|
case <-expiryTimer.C:
|
||||||
|
logger.Warn("Forward port has expired on " +
|
||||||
|
data.Expiration.Format(time.RFC1123) + ", getting another one")
|
||||||
|
oldPort := data.Port
|
||||||
|
for {
|
||||||
|
data, err = refreshPIAPortForwardData(ctx, client, privateIPClient, gateway, openFile)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
durationToExpiration := data.Expiration.Sub(p.timeNow())
|
||||||
|
logger.Info("Port forwarded is " + strconv.Itoa(int(data.Port)) +
|
||||||
|
" expiring in " + format.FormatDuration(durationToExpiration))
|
||||||
|
if err := fw.RemoveAllowedPort(ctx, oldPort); err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
}
|
||||||
|
if err := fw.SetAllowedPort(ctx, data.Port, string(constants.TUN)); err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
}
|
||||||
|
filepath := syncState(data.Port)
|
||||||
|
logger.Info("Writing port to " + filepath)
|
||||||
|
if err := writePortForwardedToFile(openFile, filepath, data.Port); err != nil {
|
||||||
|
logger.Error("Cannot write port forward data to file: " + err.Error())
|
||||||
|
}
|
||||||
|
if err := bindPort(ctx, privateIPClient, gateway, data); err != nil {
|
||||||
|
logger.Error("Cannot bind port: " + err.Error())
|
||||||
|
}
|
||||||
|
if !keepAliveTimer.Stop() {
|
||||||
|
<-keepAliveTimer.C
|
||||||
|
}
|
||||||
|
keepAliveTimer.Reset(keepAlivePeriod)
|
||||||
|
expiryTimer.Reset(durationToExpiration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrFetchToken = errors.New("cannot fetch token")
|
||||||
|
ErrFetchPortForwarding = errors.New("cannot fetch port forwarding data")
|
||||||
|
ErrPersistPortForwarding = errors.New("cannot persist port forwarding data")
|
||||||
|
)
|
||||||
|
|
||||||
|
func refreshPIAPortForwardData(ctx context.Context, client, privateIPClient *http.Client,
|
||||||
|
gateway net.IP, openFile os.OpenFileFunc) (data piaPortForwardData, err error) {
|
||||||
|
data.Token, err = fetchToken(ctx, openFile, client)
|
||||||
|
if err != nil {
|
||||||
|
return data, fmt.Errorf("%w: %s", ErrFetchToken, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data.Port, data.Signature, data.Expiration, err = fetchPortForwardData(ctx, privateIPClient, gateway, data.Token)
|
||||||
|
if err != nil {
|
||||||
|
return data, fmt.Errorf("%w: %s", ErrFetchPortForwarding, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := writePIAPortForwardData(openFile, data); err != nil {
|
||||||
|
return data, fmt.Errorf("%w: %s", ErrPersistPortForwarding, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type piaPayload struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
Port uint16 `json:"port"`
|
||||||
|
Expiration time.Time `json:"expires_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type piaPortForwardData struct {
|
||||||
|
Port uint16 `json:"port"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
Signature string `json:"signature"`
|
||||||
|
Expiration time.Time `json:"expires_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func readPIAPortForwardData(openFile os.OpenFileFunc) (data piaPortForwardData, err error) {
|
||||||
|
file, err := openFile(constants.PIAPortForward, os.O_RDONLY, 0)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return data, nil
|
||||||
|
} else if err != nil {
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder := json.NewDecoder(file)
|
||||||
|
if err := decoder.Decode(&data); err != nil {
|
||||||
|
_ = file.Close()
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, file.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func writePIAPortForwardData(openFile os.OpenFileFunc, data piaPortForwardData) (err error) {
|
||||||
|
file, err := openFile(constants.PIAPortForward, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder := json.NewEncoder(file)
|
||||||
|
|
||||||
|
if err := encoder.Encode(data); err != nil {
|
||||||
|
_ = file.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return file.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func unpackPayload(payload string) (port uint16, token string, expiration time.Time, err error) {
|
||||||
|
b, err := base64.StdEncoding.DecodeString(payload)
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", expiration,
|
||||||
|
fmt.Errorf("%w: for payload: %s", err, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
var payloadData piaPayload
|
||||||
|
if err := json.Unmarshal(b, &payloadData); err != nil {
|
||||||
|
return 0, "", expiration,
|
||||||
|
fmt.Errorf("%w: for data: %s", err, string(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
return payloadData.Port, payloadData.Token, payloadData.Expiration, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func packPayload(port uint16, token string, expiration time.Time) (payload string, err error) {
|
||||||
|
payloadData := piaPayload{
|
||||||
|
Token: token,
|
||||||
|
Port: port,
|
||||||
|
Expiration: expiration,
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(&payloadData)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
payload = base64.StdEncoding.EncodeToString(b)
|
||||||
|
return payload, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
errGetCredentials = errors.New("cannot get username and password")
|
||||||
|
errEmptyToken = errors.New("token received is empty")
|
||||||
|
)
|
||||||
|
|
||||||
|
func fetchToken(ctx context.Context, openFile os.OpenFileFunc,
|
||||||
|
client *http.Client) (token string, err error) {
|
||||||
|
username, password, err := getOpenvpnCredentials(openFile)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("%w: %s", errGetCredentials, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
errSubstitutions := map[string]string{
|
||||||
|
username: "<username>",
|
||||||
|
password: "<password>",
|
||||||
|
}
|
||||||
|
|
||||||
|
url := url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
User: url.UserPassword(username, password),
|
||||||
|
Host: "privateinternetaccess.com",
|
||||||
|
Path: "/gtoken/generateToken",
|
||||||
|
}
|
||||||
|
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", replaceInErr(err, errSubstitutions)
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
return "", replaceInErr(err, errSubstitutions)
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
if response.StatusCode != http.StatusOK {
|
||||||
|
return "", makeNOKStatusError(response, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder := json.NewDecoder(response.Body)
|
||||||
|
var result struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
|
if err := decoder.Decode(&result); err != nil {
|
||||||
|
return "", fmt.Errorf("%w: %s", ErrUnmarshalResponse, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Token == "" {
|
||||||
|
return "", errEmptyToken
|
||||||
|
}
|
||||||
|
return result.Token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
errAuthFileRead = errors.New("cannot read OpenVPN authentication file")
|
||||||
|
errAuthFileMalformed = errors.New("authentication file is malformed")
|
||||||
|
)
|
||||||
|
|
||||||
|
func getOpenvpnCredentials(openFile os.OpenFileFunc) (username, password string, err error) {
|
||||||
|
file, err := openFile(constants.OpenVPNAuthConf, os.O_RDONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("%w: %s", errAuthFileRead, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
authData, err := ioutil.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
_ = file.Close()
|
||||||
|
return "", "", fmt.Errorf("%w: %s", errAuthFileRead, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := file.Close(); err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.Split(string(authData), "\n")
|
||||||
|
const minLines = 2
|
||||||
|
if len(lines) < minLines {
|
||||||
|
return "", "", fmt.Errorf("%w: only %d lines exist", errAuthFileMalformed, len(lines))
|
||||||
|
}
|
||||||
|
|
||||||
|
username, password = lines[0], lines[1]
|
||||||
|
return username, password, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
errGetSignaturePayload = errors.New("cannot obtain signature payload")
|
||||||
|
errUnpackPayload = errors.New("cannot unpack payload data")
|
||||||
|
)
|
||||||
|
|
||||||
|
func fetchPortForwardData(ctx context.Context, client *http.Client, gateway net.IP, token string) (
|
||||||
|
port uint16, signature string, expiration time.Time, err error) {
|
||||||
|
errSubstitutions := map[string]string{token: "<token>"}
|
||||||
|
|
||||||
|
queryParams := new(url.Values)
|
||||||
|
queryParams.Add("token", token)
|
||||||
|
url := url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: net.JoinHostPort(gateway.String(), "19999"),
|
||||||
|
Path: "/getSignature",
|
||||||
|
RawQuery: queryParams.Encode(),
|
||||||
|
}
|
||||||
|
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
err = replaceInErr(err, errSubstitutions)
|
||||||
|
return 0, "", expiration, fmt.Errorf("%w: %s", errGetSignaturePayload, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
err = replaceInErr(err, errSubstitutions)
|
||||||
|
return 0, "", expiration, fmt.Errorf("%w: %s", errGetSignaturePayload, err)
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
if response.StatusCode != http.StatusOK {
|
||||||
|
return 0, "", expiration, makeNOKStatusError(response, errSubstitutions)
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder := json.NewDecoder(response.Body)
|
||||||
|
var data struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Payload string `json:"payload"`
|
||||||
|
Signature string `json:"signature"`
|
||||||
|
}
|
||||||
|
if err := decoder.Decode(&data); err != nil {
|
||||||
|
return 0, "", expiration, fmt.Errorf("%w: %s", ErrUnmarshalResponse, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.Status != "OK" {
|
||||||
|
return 0, "", expiration, fmt.Errorf("%w: status is: %s", ErrBadResponse, data.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
port, _, expiration, err = unpackPayload(data.Payload)
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", expiration, fmt.Errorf("%w: %s", errUnpackPayload, err)
|
||||||
|
}
|
||||||
|
return port, data.Signature, expiration, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrSerializePayload = errors.New("cannot serialize payload")
|
||||||
|
ErrUnmarshalResponse = errors.New("cannot unmarshal response")
|
||||||
|
ErrBadResponse = errors.New("bad response received")
|
||||||
|
)
|
||||||
|
|
||||||
|
func bindPort(ctx context.Context, client *http.Client, gateway net.IP, data piaPortForwardData) (err error) {
|
||||||
|
payload, err := packPayload(data.Port, data.Token, data.Expiration)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrSerializePayload, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
queryParams := new(url.Values)
|
||||||
|
queryParams.Add("payload", payload)
|
||||||
|
queryParams.Add("signature", data.Signature)
|
||||||
|
url := url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: net.JoinHostPort(gateway.String(), "19999"),
|
||||||
|
Path: "/bindPort",
|
||||||
|
RawQuery: queryParams.Encode(),
|
||||||
|
}
|
||||||
|
|
||||||
|
errSubstitutions := map[string]string{
|
||||||
|
payload: "<payload>",
|
||||||
|
data.Signature: "<signature>",
|
||||||
|
}
|
||||||
|
|
||||||
|
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return replaceInErr(err, errSubstitutions)
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
return replaceInErr(err, errSubstitutions)
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
if response.StatusCode != http.StatusOK {
|
||||||
|
return makeNOKStatusError(response, errSubstitutions)
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder := json.NewDecoder(response.Body)
|
||||||
|
var responseData struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
if err := decoder.Decode(&responseData); err != nil {
|
||||||
|
return fmt.Errorf("%w: from %s: %s", ErrUnmarshalResponse, url.String(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if responseData.Status != "OK" {
|
||||||
|
return fmt.Errorf("%w: %s: %s", ErrBadResponse, responseData.Status, responseData.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writePortForwardedToFile(openFile os.OpenFileFunc,
|
||||||
|
filepath string, port uint16) (err error) {
|
||||||
|
file, err := openFile(filepath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = file.Write([]byte(fmt.Sprintf("%d", port)))
|
||||||
|
if err != nil {
|
||||||
|
_ = file.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return file.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// replaceInErr is used to remove sensitive information from errors.
|
||||||
|
func replaceInErr(err error, substitutions map[string]string) error {
|
||||||
|
s := replaceInString(err.Error(), substitutions)
|
||||||
|
return errors.New(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// replaceInString is used to remove sensitive information.
|
||||||
|
func replaceInString(s string, substitutions map[string]string) string {
|
||||||
|
for old, new := range substitutions {
|
||||||
|
s = strings.ReplaceAll(s, old, new)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrHTTPStatusCodeNotOK = errors.New("HTTP status code is not OK")
|
||||||
|
|
||||||
|
func makeNOKStatusError(response *http.Response, substitutions map[string]string) (err error) {
|
||||||
|
url := response.Request.URL.String()
|
||||||
|
url = replaceInString(url, substitutions)
|
||||||
|
|
||||||
|
b, _ := ioutil.ReadAll(response.Body)
|
||||||
|
shortenMessage := string(b)
|
||||||
|
shortenMessage = strings.ReplaceAll(shortenMessage, "\n", "")
|
||||||
|
shortenMessage = strings.ReplaceAll(shortenMessage, " ", " ")
|
||||||
|
shortenMessage = replaceInString(shortenMessage, substitutions)
|
||||||
|
|
||||||
|
return fmt.Errorf("%w: %s: %s: response received: %s",
|
||||||
|
ErrHTTPStatusCodeNotOK, url, response.Status, shortenMessage)
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package provider
|
package privateinternetaccess
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_newPIAHTTPClient(t *testing.T) {
|
func Test_newHTTPClient(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
const serverName = "testserver"
|
const serverName = "testserver"
|
||||||
@@ -35,7 +35,7 @@ func Test_newPIAHTTPClient(t *testing.T) {
|
|||||||
ServerName: serverName,
|
ServerName: serverName,
|
||||||
}
|
}
|
||||||
|
|
||||||
piaClient, err := newPIAHTTPClient(serverName)
|
piaClient, err := newHTTPClient(serverName)
|
||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ func Test_newPIAHTTPClient(t *testing.T) {
|
|||||||
assert.Equal(t, expectedPIATransportTLSConfig, piaTransport.TLSClientConfig)
|
assert.Equal(t, expectedPIATransportTLSConfig, piaTransport.TLSClientConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_unpackPIAPayload(t *testing.T) {
|
func Test_unpackPayload(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
const exampleToken = "token"
|
const exampleToken = "token"
|
||||||
@@ -70,11 +70,11 @@ func Test_unpackPIAPayload(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"invalid base64 payload": {
|
"invalid base64 payload": {
|
||||||
payload: "invalid",
|
payload: "invalid",
|
||||||
err: errors.New(`cannot decode payload: payload is "invalid": illegal base64 data at input byte 4`),
|
err: errors.New("illegal base64 data at input byte 4: for payload: invalid"),
|
||||||
},
|
},
|
||||||
"invalid json payload": {
|
"invalid json payload": {
|
||||||
payload: base64.StdEncoding.EncodeToString([]byte{1}),
|
payload: base64.StdEncoding.EncodeToString([]byte{1}),
|
||||||
err: errors.New(`cannot parse payload data: data is "\x01": invalid character '\x01' looking for beginning of value`), //nolint:lll
|
err: errors.New("invalid character '\\x01' looking for beginning of value: for data: \x01"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ func Test_unpackPIAPayload(t *testing.T) {
|
|||||||
testCase := testCase
|
testCase := testCase
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
port, token, expiration, err := unpackPIAPayload(testCase.payload)
|
port, token, expiration, err := unpackPayload(testCase.payload)
|
||||||
|
|
||||||
if testCase.err != nil {
|
if testCase.err != nil {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -112,3 +112,32 @@ func makePIAPayload(t *testing.T, token string, port uint16, expiration time.Tim
|
|||||||
|
|
||||||
return base64.StdEncoding.EncodeToString(b)
|
return base64.StdEncoding.EncodeToString(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_replaceInString(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
s string
|
||||||
|
substitutions map[string]string
|
||||||
|
result string
|
||||||
|
}{
|
||||||
|
"empty": {},
|
||||||
|
"multiple replacements": {
|
||||||
|
s: "https://test.com/username/password/",
|
||||||
|
substitutions: map[string]string{
|
||||||
|
"username": "xxx",
|
||||||
|
"password": "yyy",
|
||||||
|
},
|
||||||
|
result: "https://test.com/xxx/yyy/",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
result := replaceInString(testCase.s, testCase.substitutions)
|
||||||
|
assert.Equal(t, testCase.result, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
23
internal/provider/privateinternetaccess/provider.go
Normal file
23
internal/provider/privateinternetaccess/provider.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package privateinternetaccess
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PIA struct {
|
||||||
|
servers []models.PIAServer
|
||||||
|
randSource rand.Source
|
||||||
|
timeNow func() time.Time
|
||||||
|
activeServer models.PIAServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(servers []models.PIAServer, randSource rand.Source, timeNow func() time.Time) *PIA {
|
||||||
|
return &PIA{
|
||||||
|
servers: servers,
|
||||||
|
timeNow: timeNow,
|
||||||
|
randSource: randSource,
|
||||||
|
}
|
||||||
|
}
|
||||||
31
internal/provider/privateinternetaccess/try.go
Normal file
31
internal/provider/privateinternetaccess/try.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package privateinternetaccess
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
func tryUntilSuccessful(ctx context.Context, logger logging.Logger, fn func() error) {
|
||||||
|
const initialRetryPeriod = 5 * time.Second
|
||||||
|
retryPeriod := initialRetryPeriod
|
||||||
|
for {
|
||||||
|
err := fn()
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
logger.Error(err)
|
||||||
|
logger.Info("Trying again in " + retryPeriod.String())
|
||||||
|
timer := time.NewTimer(retryPeriod)
|
||||||
|
select {
|
||||||
|
case <-timer.C:
|
||||||
|
case <-ctx.Done():
|
||||||
|
if !timer.Stop() {
|
||||||
|
<-timer.C
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
retryPeriod *= 2
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration"
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/gluetun/internal/firewall"
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
"github.com/qdm12/golibs/logging"
|
|
||||||
"github.com/qdm12/golibs/os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type privatevpn struct {
|
|
||||||
servers []models.PrivatevpnServer
|
|
||||||
randSource rand.Source
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPrivatevpn(servers []models.PrivatevpnServer, timeNow timeNowFunc) *privatevpn {
|
|
||||||
return &privatevpn{
|
|
||||||
servers: servers,
|
|
||||||
randSource: rand.NewSource(timeNow().UnixNano()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *privatevpn) filterServers(countries, cities, hostnames []string) (servers []models.PrivatevpnServer) {
|
|
||||||
for _, server := range p.servers {
|
|
||||||
switch {
|
|
||||||
case
|
|
||||||
filterByPossibilities(server.Country, countries),
|
|
||||||
filterByPossibilities(server.City, cities),
|
|
||||||
filterByPossibilities(server.Hostname, hostnames):
|
|
||||||
default:
|
|
||||||
servers = append(servers, server)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return servers
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *privatevpn) notFoundErr(selection configuration.ServerSelection) error {
|
|
||||||
message := "no server found for protocol " + tcpBoolToProtocol(selection.TCP)
|
|
||||||
|
|
||||||
if len(selection.Countries) > 0 {
|
|
||||||
message += " + countries " + commaJoin(selection.Countries)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(selection.Cities) > 0 {
|
|
||||||
message += " + cities " + commaJoin(selection.Cities)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(selection.Hostnames) > 0 {
|
|
||||||
message += " + hostnames " + commaJoin(selection.Hostnames)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *privatevpn) GetOpenVPNConnection(selection configuration.ServerSelection) (
|
|
||||||
connection models.OpenVPNConnection, err error) {
|
|
||||||
var port uint16
|
|
||||||
protocol := constants.TCP
|
|
||||||
if selection.TCP {
|
|
||||||
port = 443
|
|
||||||
} else {
|
|
||||||
protocol = constants.UDP
|
|
||||||
port = 1194
|
|
||||||
}
|
|
||||||
|
|
||||||
if selection.TargetIP != nil {
|
|
||||||
return models.OpenVPNConnection{
|
|
||||||
IP: selection.TargetIP,
|
|
||||||
Port: port,
|
|
||||||
Protocol: protocol,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
servers := p.filterServers(selection.Countries, selection.Cities, selection.Hostnames)
|
|
||||||
if len(servers) == 0 {
|
|
||||||
return connection, p.notFoundErr(selection)
|
|
||||||
}
|
|
||||||
|
|
||||||
var connections []models.OpenVPNConnection
|
|
||||||
for _, server := range servers {
|
|
||||||
for _, ip := range server.IPs {
|
|
||||||
connection := models.OpenVPNConnection{
|
|
||||||
IP: ip,
|
|
||||||
Port: port,
|
|
||||||
Protocol: protocol,
|
|
||||||
}
|
|
||||||
connections = append(connections, connection)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pickRandomConnection(connections, p.randSource), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *privatevpn) BuildConf(connection models.OpenVPNConnection,
|
|
||||||
username string, settings configuration.OpenVPN) (lines []string) {
|
|
||||||
if len(settings.Cipher) == 0 {
|
|
||||||
settings.Cipher = aes128gcm
|
|
||||||
}
|
|
||||||
if len(settings.Auth) == 0 {
|
|
||||||
settings.Auth = sha256
|
|
||||||
}
|
|
||||||
|
|
||||||
lines = []string{
|
|
||||||
"client",
|
|
||||||
"dev tun",
|
|
||||||
"nobind",
|
|
||||||
"persist-key",
|
|
||||||
"remote-cert-tls server",
|
|
||||||
"tls-exit",
|
|
||||||
|
|
||||||
// Privatevpn specific
|
|
||||||
"comp-lzo",
|
|
||||||
"tun-ipv6",
|
|
||||||
|
|
||||||
// Added constant values
|
|
||||||
"auth-nocache",
|
|
||||||
"mute-replay-warnings",
|
|
||||||
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
|
||||||
"pull-filter ignore \"block-outside-dns\"",
|
|
||||||
"auth-retry nointeract",
|
|
||||||
"suppress-timestamps",
|
|
||||||
|
|
||||||
// Modified variables
|
|
||||||
fmt.Sprintf("verb %d", settings.Verbosity),
|
|
||||||
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
|
||||||
fmt.Sprintf("proto %s", connection.Protocol),
|
|
||||||
fmt.Sprintf("remote %s %d", connection.IP, connection.Port),
|
|
||||||
"data-ciphers-fallback " + settings.Cipher,
|
|
||||||
"data-ciphers " + settings.Cipher,
|
|
||||||
fmt.Sprintf("auth %s", settings.Auth),
|
|
||||||
}
|
|
||||||
if connection.Protocol == constants.UDP {
|
|
||||||
lines = append(lines, "key-direction 1")
|
|
||||||
}
|
|
||||||
if !settings.Root {
|
|
||||||
lines = append(lines, "user "+username)
|
|
||||||
}
|
|
||||||
if settings.MSSFix > 0 {
|
|
||||||
line := "mssfix " + strconv.Itoa(int(settings.MSSFix))
|
|
||||||
lines = append(lines, line)
|
|
||||||
}
|
|
||||||
lines = append(lines, []string{
|
|
||||||
"<ca>",
|
|
||||||
"-----BEGIN CERTIFICATE-----",
|
|
||||||
constants.PrivatevpnCertificate,
|
|
||||||
"-----END CERTIFICATE-----",
|
|
||||||
"</ca>",
|
|
||||||
}...)
|
|
||||||
lines = append(lines, []string{
|
|
||||||
"<tls-crypt>",
|
|
||||||
"-----BEGIN OpenVPN Static key V1-----",
|
|
||||||
constants.PrivatevpnOpenvpnStaticKeyV1,
|
|
||||||
"-----END OpenVPN Static key V1-----",
|
|
||||||
"</tls-crypt>",
|
|
||||||
"",
|
|
||||||
}...)
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *privatevpn) PortForward(ctx context.Context, client *http.Client,
|
|
||||||
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
|
||||||
syncState func(port uint16) (pfFilepath string)) {
|
|
||||||
panic("port forwarding is not supported for privatevpn")
|
|
||||||
}
|
|
||||||
41
internal/provider/privatevpn/connection.go
Normal file
41
internal/provider/privatevpn/connection.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package privatevpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *Privatevpn) GetOpenVPNConnection(selection configuration.ServerSelection) (
|
||||||
|
connection models.OpenVPNConnection, err error) {
|
||||||
|
protocol := constants.UDP
|
||||||
|
var port uint16 = 1194
|
||||||
|
if selection.TCP {
|
||||||
|
protocol = constants.TCP
|
||||||
|
port = 443
|
||||||
|
}
|
||||||
|
|
||||||
|
servers, err := p.filterServers(selection)
|
||||||
|
if err != nil {
|
||||||
|
return connection, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var connections []models.OpenVPNConnection
|
||||||
|
for _, server := range servers {
|
||||||
|
for _, ip := range server.IPs {
|
||||||
|
connection := models.OpenVPNConnection{
|
||||||
|
IP: ip,
|
||||||
|
Port: port,
|
||||||
|
Protocol: protocol,
|
||||||
|
}
|
||||||
|
connections = append(connections, connection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if selection.TargetIP != nil {
|
||||||
|
return utils.GetTargetIPConnection(connections, selection.TargetIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.PickRandomConnection(connections, p.randSource), nil
|
||||||
|
}
|
||||||
27
internal/provider/privatevpn/filter.go
Normal file
27
internal/provider/privatevpn/filter.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package privatevpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *Privatevpn) filterServers(selection configuration.ServerSelection) (
|
||||||
|
servers []models.PrivatevpnServer, err error) {
|
||||||
|
for _, server := range p.servers {
|
||||||
|
switch {
|
||||||
|
case
|
||||||
|
utils.FilterByPossibilities(server.Country, selection.Countries),
|
||||||
|
utils.FilterByPossibilities(server.City, selection.Cities),
|
||||||
|
utils.FilterByPossibilities(server.Hostname, selection.Hostnames):
|
||||||
|
default:
|
||||||
|
servers = append(servers, server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(servers) == 0 {
|
||||||
|
return nil, utils.NoServerFoundError(selection)
|
||||||
|
}
|
||||||
|
|
||||||
|
return servers, nil
|
||||||
|
}
|
||||||
71
internal/provider/privatevpn/openvpnconf.go
Normal file
71
internal/provider/privatevpn/openvpnconf.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package privatevpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *Privatevpn) BuildConf(connection models.OpenVPNConnection,
|
||||||
|
username string, settings configuration.OpenVPN) (lines []string) {
|
||||||
|
if settings.Cipher == "" {
|
||||||
|
settings.Cipher = constants.AES128gcm
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.Auth == "" {
|
||||||
|
settings.Auth = constants.SHA256
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = []string{
|
||||||
|
"client",
|
||||||
|
"dev tun",
|
||||||
|
"nobind",
|
||||||
|
"persist-key",
|
||||||
|
"remote-cert-tls server",
|
||||||
|
"tls-exit",
|
||||||
|
|
||||||
|
// Privatevpn specific
|
||||||
|
"comp-lzo",
|
||||||
|
"tun-ipv6",
|
||||||
|
|
||||||
|
// Added constant values
|
||||||
|
"auth-nocache",
|
||||||
|
"mute-replay-warnings",
|
||||||
|
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
||||||
|
"auth-retry nointeract",
|
||||||
|
"suppress-timestamps",
|
||||||
|
|
||||||
|
// Modified variables
|
||||||
|
"verb " + strconv.Itoa(settings.Verbosity),
|
||||||
|
"auth-user-pass " + constants.OpenVPNAuthConf,
|
||||||
|
connection.ProtoLine(),
|
||||||
|
connection.RemoteLine(),
|
||||||
|
"data-ciphers-fallback " + settings.Cipher,
|
||||||
|
"data-ciphers " + settings.Cipher,
|
||||||
|
"auth " + settings.Auth,
|
||||||
|
}
|
||||||
|
|
||||||
|
if connection.Protocol == constants.UDP {
|
||||||
|
lines = append(lines, "key-direction 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !settings.Root {
|
||||||
|
lines = append(lines, "user "+username)
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.MSSFix > 0 {
|
||||||
|
lines = append(lines, "mssfix "+strconv.Itoa(int(settings.MSSFix)))
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = append(lines, utils.WrapOpenvpnCA(
|
||||||
|
constants.PrivatevpnCertificate)...)
|
||||||
|
lines = append(lines, utils.WrapOpenvpnTLSCrypt(
|
||||||
|
constants.PrivatevpnOpenvpnStaticKeyV1)...)
|
||||||
|
|
||||||
|
lines = append(lines, "")
|
||||||
|
|
||||||
|
return lines
|
||||||
|
}
|
||||||
17
internal/provider/privatevpn/portforward.go
Normal file
17
internal/provider/privatevpn/portforward.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package privatevpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/firewall"
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
|
"github.com/qdm12/golibs/os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *Privatevpn) PortForward(ctx context.Context, client *http.Client,
|
||||||
|
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP,
|
||||||
|
fw firewall.Configurator, syncState func(port uint16) (pfFilepath string)) {
|
||||||
|
panic("port forwarding is not supported for PrivateVPN")
|
||||||
|
}
|
||||||
19
internal/provider/privatevpn/provider.go
Normal file
19
internal/provider/privatevpn/provider.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package privatevpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Privatevpn struct {
|
||||||
|
servers []models.PrivatevpnServer
|
||||||
|
randSource rand.Source
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(servers []models.PrivatevpnServer, randSource rand.Source) *Privatevpn {
|
||||||
|
return &Privatevpn{
|
||||||
|
servers: servers,
|
||||||
|
randSource: randSource,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,210 +0,0 @@
|
|||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration"
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/gluetun/internal/firewall"
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
"github.com/qdm12/golibs/logging"
|
|
||||||
"github.com/qdm12/golibs/os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type protonvpn struct {
|
|
||||||
servers []models.ProtonvpnServer
|
|
||||||
randSource rand.Source
|
|
||||||
}
|
|
||||||
|
|
||||||
func newProtonvpn(servers []models.ProtonvpnServer, timeNow timeNowFunc) *protonvpn {
|
|
||||||
return &protonvpn{
|
|
||||||
servers: servers,
|
|
||||||
randSource: rand.NewSource(timeNow().UnixNano()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *protonvpn) GetOpenVPNConnection(selection configuration.ServerSelection) (
|
|
||||||
connection models.OpenVPNConnection, err error) {
|
|
||||||
port, err := p.getPort(selection)
|
|
||||||
if err != nil {
|
|
||||||
return connection, err
|
|
||||||
}
|
|
||||||
|
|
||||||
protocol := tcpBoolToProtocol(selection.TCP)
|
|
||||||
|
|
||||||
if selection.TargetIP != nil {
|
|
||||||
return models.OpenVPNConnection{
|
|
||||||
IP: selection.TargetIP,
|
|
||||||
Port: port,
|
|
||||||
Protocol: protocol,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
servers := p.filterServers(selection.Countries, selection.Regions,
|
|
||||||
selection.Cities, selection.Names, selection.Hostnames)
|
|
||||||
if len(servers) == 0 {
|
|
||||||
return connection, p.notFoundErr(selection)
|
|
||||||
}
|
|
||||||
|
|
||||||
connections := make([]models.OpenVPNConnection, len(servers))
|
|
||||||
for i := range servers {
|
|
||||||
connections[i] = models.OpenVPNConnection{
|
|
||||||
IP: servers[i].EntryIP,
|
|
||||||
Port: port,
|
|
||||||
Protocol: protocol,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pickRandomConnection(connections, p.randSource), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *protonvpn) BuildConf(connection models.OpenVPNConnection,
|
|
||||||
username string, settings configuration.OpenVPN) (lines []string) {
|
|
||||||
if len(settings.Cipher) == 0 {
|
|
||||||
settings.Cipher = aes256cbc
|
|
||||||
}
|
|
||||||
if len(settings.Auth) == 0 {
|
|
||||||
settings.Auth = "SHA512"
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultMSSFix = 1450
|
|
||||||
if settings.MSSFix == 0 {
|
|
||||||
settings.MSSFix = defaultMSSFix
|
|
||||||
}
|
|
||||||
|
|
||||||
lines = []string{
|
|
||||||
"client",
|
|
||||||
"dev tun",
|
|
||||||
"nobind",
|
|
||||||
"persist-key",
|
|
||||||
"remote-cert-tls server",
|
|
||||||
"tls-exit",
|
|
||||||
|
|
||||||
// Protonvpn specific
|
|
||||||
"tun-mtu 1500",
|
|
||||||
"tun-mtu-extra 32",
|
|
||||||
"mssfix " + strconv.Itoa(int(settings.MSSFix)),
|
|
||||||
"reneg-sec 0",
|
|
||||||
"fast-io",
|
|
||||||
"key-direction 1",
|
|
||||||
"pull",
|
|
||||||
"comp-lzo no",
|
|
||||||
|
|
||||||
// Added constant values
|
|
||||||
"auth-nocache",
|
|
||||||
"mute-replay-warnings",
|
|
||||||
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
|
||||||
"pull-filter ignore \"block-outside-dns\"",
|
|
||||||
`pull-filter ignore "ping-restart"`,
|
|
||||||
"auth-retry nointeract",
|
|
||||||
"suppress-timestamps",
|
|
||||||
|
|
||||||
// Modified variables
|
|
||||||
"verb " + strconv.Itoa(settings.Verbosity),
|
|
||||||
"auth-user-pass " + constants.OpenVPNAuthConf,
|
|
||||||
"proto " + connection.Protocol,
|
|
||||||
"remote " + connection.IP.String() + " " + strconv.Itoa(int(connection.Port)),
|
|
||||||
"cipher " + settings.Cipher,
|
|
||||||
"auth " + settings.Auth,
|
|
||||||
}
|
|
||||||
if !settings.Root {
|
|
||||||
lines = append(lines, "user "+username)
|
|
||||||
}
|
|
||||||
lines = append(lines, []string{
|
|
||||||
"<ca>",
|
|
||||||
"-----BEGIN CERTIFICATE-----",
|
|
||||||
constants.ProtonvpnCertificate,
|
|
||||||
"-----END CERTIFICATE-----",
|
|
||||||
"</ca>",
|
|
||||||
}...)
|
|
||||||
lines = append(lines, []string{
|
|
||||||
"<tls-auth>",
|
|
||||||
"-----BEGIN OpenVPN Static key V1-----",
|
|
||||||
constants.ProtonvpnOpenvpnStaticKeyV1,
|
|
||||||
"-----END OpenVPN Static key V1-----",
|
|
||||||
"</tls-auth>",
|
|
||||||
"",
|
|
||||||
}...)
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *protonvpn) PortForward(ctx context.Context, client *http.Client,
|
|
||||||
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
|
||||||
syncState func(port uint16) (pfFilepath string)) {
|
|
||||||
panic("port forwarding is not supported for protonvpn")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *protonvpn) getPort(selection configuration.ServerSelection) (port uint16, err error) {
|
|
||||||
if selection.CustomPort == 0 {
|
|
||||||
if selection.TCP {
|
|
||||||
const defaultTCPPort = 443
|
|
||||||
return defaultTCPPort, nil
|
|
||||||
}
|
|
||||||
const defaultUDPPort = 1194
|
|
||||||
return defaultUDPPort, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
port = selection.CustomPort
|
|
||||||
if selection.TCP {
|
|
||||||
switch port {
|
|
||||||
case 443, 5995, 8443: //nolint:gomnd
|
|
||||||
return port, nil
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("%w: %d for protocol TCP", ErrInvalidPort, port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch port {
|
|
||||||
case 80, 443, 1194, 4569, 5060: //nolint:gomnd
|
|
||||||
return port, nil
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("%w: %d for protocol UDP", ErrInvalidPort, port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *protonvpn) filterServers(countries, regions, cities, names, hostnames []string) (
|
|
||||||
servers []models.ProtonvpnServer) {
|
|
||||||
for _, server := range p.servers {
|
|
||||||
switch {
|
|
||||||
case
|
|
||||||
filterByPossibilities(server.Country, countries),
|
|
||||||
filterByPossibilities(server.Region, regions),
|
|
||||||
filterByPossibilities(server.City, cities),
|
|
||||||
filterByPossibilities(server.Name, names),
|
|
||||||
filterByPossibilities(server.Hostname, hostnames):
|
|
||||||
default:
|
|
||||||
servers = append(servers, server)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return servers
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *protonvpn) notFoundErr(selection configuration.ServerSelection) error {
|
|
||||||
message := "no server found for protocol " + tcpBoolToProtocol(selection.TCP)
|
|
||||||
|
|
||||||
if len(selection.Countries) > 0 {
|
|
||||||
message += " + countries " + commaJoin(selection.Countries)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(selection.Regions) > 0 {
|
|
||||||
message += " + regions " + commaJoin(selection.Regions)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(selection.Cities) > 0 {
|
|
||||||
message += " + cities " + commaJoin(selection.Cities)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(selection.Names) > 0 {
|
|
||||||
message += " + names " + commaJoin(selection.Names)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(selection.Hostnames) > 0 {
|
|
||||||
message += " + hostnames " + commaJoin(selection.Hostnames)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf(message)
|
|
||||||
}
|
|
||||||
41
internal/provider/protonvpn/connection.go
Normal file
41
internal/provider/protonvpn/connection.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package protonvpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *Protonvpn) GetOpenVPNConnection(selection configuration.ServerSelection) (
|
||||||
|
connection models.OpenVPNConnection, err error) {
|
||||||
|
protocol := constants.UDP
|
||||||
|
if selection.TCP {
|
||||||
|
protocol = constants.TCP
|
||||||
|
}
|
||||||
|
|
||||||
|
port, err := getPort(selection.TCP, selection.CustomPort)
|
||||||
|
if err != nil {
|
||||||
|
return connection, err
|
||||||
|
}
|
||||||
|
|
||||||
|
servers, err := p.filterServers(selection)
|
||||||
|
if err != nil {
|
||||||
|
return connection, err
|
||||||
|
}
|
||||||
|
|
||||||
|
connections := make([]models.OpenVPNConnection, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
connections[i] = models.OpenVPNConnection{
|
||||||
|
IP: servers[i].EntryIP,
|
||||||
|
Port: port,
|
||||||
|
Protocol: protocol,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if selection.TargetIP != nil {
|
||||||
|
return utils.GetTargetIPConnection(connections, selection.TargetIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.PickRandomConnection(connections, p.randSource), nil
|
||||||
|
}
|
||||||
29
internal/provider/protonvpn/filter.go
Normal file
29
internal/provider/protonvpn/filter.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package protonvpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *Protonvpn) filterServers(selection configuration.ServerSelection) (
|
||||||
|
servers []models.ProtonvpnServer, err error) {
|
||||||
|
for _, server := range p.servers {
|
||||||
|
switch {
|
||||||
|
case
|
||||||
|
utils.FilterByPossibilities(server.Country, selection.Countries),
|
||||||
|
utils.FilterByPossibilities(server.Region, selection.Regions),
|
||||||
|
utils.FilterByPossibilities(server.City, selection.Cities),
|
||||||
|
utils.FilterByPossibilities(server.Hostname, selection.Hostnames),
|
||||||
|
utils.FilterByPossibilities(server.Name, selection.Names):
|
||||||
|
default:
|
||||||
|
servers = append(servers, server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(servers) == 0 {
|
||||||
|
return nil, utils.NoServerFoundError(selection)
|
||||||
|
}
|
||||||
|
|
||||||
|
return servers, nil
|
||||||
|
}
|
||||||
74
internal/provider/protonvpn/openvpnconf.go
Normal file
74
internal/provider/protonvpn/openvpnconf.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package protonvpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *Protonvpn) BuildConf(connection models.OpenVPNConnection,
|
||||||
|
username string, settings configuration.OpenVPN) (lines []string) {
|
||||||
|
if settings.Cipher == "" {
|
||||||
|
settings.Cipher = constants.AES256cbc
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.Auth == "" {
|
||||||
|
settings.Auth = constants.SHA512
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultMSSFix = 1450
|
||||||
|
if settings.MSSFix == 0 {
|
||||||
|
settings.MSSFix = defaultMSSFix
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = []string{
|
||||||
|
"client",
|
||||||
|
"dev tun",
|
||||||
|
"nobind",
|
||||||
|
"persist-key",
|
||||||
|
"remote-cert-tls server",
|
||||||
|
"tls-exit",
|
||||||
|
|
||||||
|
// Protonvpn specific
|
||||||
|
"tun-mtu 1500",
|
||||||
|
"tun-mtu-extra 32",
|
||||||
|
"mssfix " + strconv.Itoa(int(settings.MSSFix)),
|
||||||
|
"reneg-sec 0",
|
||||||
|
"fast-io",
|
||||||
|
"key-direction 1",
|
||||||
|
"pull",
|
||||||
|
"comp-lzo no",
|
||||||
|
|
||||||
|
// Added constant values
|
||||||
|
"auth-nocache",
|
||||||
|
"mute-replay-warnings",
|
||||||
|
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
||||||
|
"auth-retry nointeract",
|
||||||
|
"suppress-timestamps",
|
||||||
|
|
||||||
|
// Modified variables
|
||||||
|
"verb " + strconv.Itoa(settings.Verbosity),
|
||||||
|
"auth-user-pass " + constants.OpenVPNAuthConf,
|
||||||
|
connection.ProtoLine(),
|
||||||
|
connection.RemoteLine(),
|
||||||
|
"data-ciphers-fallback " + settings.Cipher,
|
||||||
|
"data-ciphers " + settings.Cipher,
|
||||||
|
"auth " + settings.Auth,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !settings.Root {
|
||||||
|
lines = append(lines, "user "+username)
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = append(lines, utils.WrapOpenvpnCA(
|
||||||
|
constants.ProtonvpnCertificate)...)
|
||||||
|
lines = append(lines, utils.WrapOpenvpnTLSAuth(
|
||||||
|
constants.ProtonvpnOpenvpnStaticKeyV1)...)
|
||||||
|
|
||||||
|
lines = append(lines, "")
|
||||||
|
|
||||||
|
return lines
|
||||||
|
}
|
||||||
41
internal/provider/protonvpn/port.go
Normal file
41
internal/provider/protonvpn/port.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package protonvpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getPort(tcp bool, customPort uint16) (port uint16, err error) {
|
||||||
|
if customPort == 0 {
|
||||||
|
const defaultTCPPort, defaultUDPPort = 443, 1194
|
||||||
|
if tcp {
|
||||||
|
return defaultTCPPort, nil
|
||||||
|
}
|
||||||
|
return defaultUDPPort, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := checkPort(customPort, tcp); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return customPort, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrInvalidPort = errors.New("invalid port number")
|
||||||
|
|
||||||
|
func checkPort(port uint16, tcp bool) (err error) {
|
||||||
|
if tcp {
|
||||||
|
switch port {
|
||||||
|
case 443, 5995, 8443: //nolint:gomnd
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%w: %d for protocol TCP", ErrInvalidPort, port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch port {
|
||||||
|
case 80, 443, 1194, 4569, 5060: //nolint:gomnd
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%w: %d for protocol UDP", ErrInvalidPort, port)
|
||||||
|
}
|
||||||
|
}
|
||||||
17
internal/provider/protonvpn/portforward.go
Normal file
17
internal/provider/protonvpn/portforward.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package protonvpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/firewall"
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
|
"github.com/qdm12/golibs/os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *Protonvpn) PortForward(ctx context.Context, client *http.Client,
|
||||||
|
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP,
|
||||||
|
fw firewall.Configurator, syncState func(port uint16) (pfFilepath string)) {
|
||||||
|
panic("port forwarding is not supported for ProtonVPN")
|
||||||
|
}
|
||||||
19
internal/provider/protonvpn/provider.go
Normal file
19
internal/provider/protonvpn/provider.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package protonvpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Protonvpn struct {
|
||||||
|
servers []models.ProtonvpnServer
|
||||||
|
randSource rand.Source
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(servers []models.ProtonvpnServer, randSource rand.Source) *Protonvpn {
|
||||||
|
return &Protonvpn{
|
||||||
|
servers: servers,
|
||||||
|
randSource: randSource,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,13 +3,29 @@ package provider
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration"
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/firewall"
|
"github.com/qdm12/gluetun/internal/firewall"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/cyberghost"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/fastestvpn"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/hidemyass"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/mullvad"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/nordvpn"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/privado"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/privatevpn"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/protonvpn"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/purevpn"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/surfshark"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/torguard"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/vyprvpn"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/windscribe"
|
||||||
"github.com/qdm12/golibs/logging"
|
"github.com/qdm12/golibs/logging"
|
||||||
"github.com/qdm12/golibs/os"
|
"github.com/qdm12/golibs/os"
|
||||||
)
|
)
|
||||||
@@ -23,36 +39,37 @@ type Provider interface {
|
|||||||
syncState func(port uint16) (pfFilepath string))
|
syncState func(port uint16) (pfFilepath string))
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(provider string, allServers models.AllServers, timeNow timeNowFunc) Provider {
|
func New(provider string, allServers models.AllServers, timeNow func() time.Time) Provider {
|
||||||
|
randSource := rand.NewSource(timeNow().UnixNano())
|
||||||
switch provider {
|
switch provider {
|
||||||
case constants.Cyberghost:
|
case constants.Cyberghost:
|
||||||
return newCyberghost(allServers.Cyberghost.Servers, timeNow)
|
return cyberghost.New(allServers.Cyberghost.Servers, randSource)
|
||||||
case constants.Fastestvpn:
|
case constants.Fastestvpn:
|
||||||
return newFastestvpn(allServers.Fastestvpn.Servers, timeNow)
|
return fastestvpn.New(allServers.Fastestvpn.Servers, randSource)
|
||||||
case constants.HideMyAss:
|
case constants.HideMyAss:
|
||||||
return newHideMyAss(allServers.HideMyAss.Servers, timeNow)
|
return hidemyass.New(allServers.HideMyAss.Servers, randSource)
|
||||||
case constants.Mullvad:
|
case constants.Mullvad:
|
||||||
return newMullvad(allServers.Mullvad.Servers, timeNow)
|
return mullvad.New(allServers.Mullvad.Servers, randSource)
|
||||||
case constants.Nordvpn:
|
case constants.Nordvpn:
|
||||||
return newNordvpn(allServers.Nordvpn.Servers, timeNow)
|
return nordvpn.New(allServers.Nordvpn.Servers, randSource)
|
||||||
case constants.Privado:
|
case constants.Privado:
|
||||||
return newPrivado(allServers.Privado.Servers, timeNow)
|
return privado.New(allServers.Privado.Servers, randSource)
|
||||||
case constants.PrivateInternetAccess:
|
case constants.PrivateInternetAccess:
|
||||||
return newPrivateInternetAccess(allServers.Pia.Servers, timeNow)
|
return privateinternetaccess.New(allServers.Pia.Servers, randSource, timeNow)
|
||||||
case constants.Privatevpn:
|
case constants.Privatevpn:
|
||||||
return newPrivatevpn(allServers.Privatevpn.Servers, timeNow)
|
return privatevpn.New(allServers.Privatevpn.Servers, randSource)
|
||||||
case constants.Protonvpn:
|
case constants.Protonvpn:
|
||||||
return newProtonvpn(allServers.Protonvpn.Servers, timeNow)
|
return protonvpn.New(allServers.Protonvpn.Servers, randSource)
|
||||||
case constants.Purevpn:
|
case constants.Purevpn:
|
||||||
return newPurevpn(allServers.Purevpn.Servers, timeNow)
|
return purevpn.New(allServers.Purevpn.Servers, randSource)
|
||||||
case constants.Surfshark:
|
case constants.Surfshark:
|
||||||
return newSurfshark(allServers.Surfshark.Servers, timeNow)
|
return surfshark.New(allServers.Surfshark.Servers, randSource)
|
||||||
case constants.Torguard:
|
case constants.Torguard:
|
||||||
return newTorguard(allServers.Torguard.Servers, timeNow)
|
return torguard.New(allServers.Torguard.Servers, randSource)
|
||||||
case constants.Vyprvpn:
|
case constants.Vyprvpn:
|
||||||
return newVyprvpn(allServers.Vyprvpn.Servers, timeNow)
|
return vyprvpn.New(allServers.Vyprvpn.Servers, randSource)
|
||||||
case constants.Windscribe:
|
case constants.Windscribe:
|
||||||
return newWindscribe(allServers.Windscribe.Servers, timeNow)
|
return windscribe.New(allServers.Windscribe.Servers, randSource)
|
||||||
default:
|
default:
|
||||||
return nil // should never occur
|
return nil // should never occur
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,167 +0,0 @@
|
|||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration"
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/gluetun/internal/firewall"
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
"github.com/qdm12/golibs/logging"
|
|
||||||
"github.com/qdm12/golibs/os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type purevpn struct {
|
|
||||||
servers []models.PurevpnServer
|
|
||||||
randSource rand.Source
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPurevpn(servers []models.PurevpnServer, timeNow timeNowFunc) *purevpn {
|
|
||||||
return &purevpn{
|
|
||||||
servers: servers,
|
|
||||||
randSource: rand.NewSource(timeNow().UnixNano()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *purevpn) filterServers(regions, countries, cities, hostnames []string,
|
|
||||||
tcp bool) (servers []models.PurevpnServer) {
|
|
||||||
for _, server := range p.servers {
|
|
||||||
switch {
|
|
||||||
case
|
|
||||||
filterByPossibilities(server.Region, regions),
|
|
||||||
filterByPossibilities(server.Country, countries),
|
|
||||||
filterByPossibilities(server.City, cities),
|
|
||||||
filterByPossibilities(server.Hostname, hostnames),
|
|
||||||
tcp && !server.TCP,
|
|
||||||
!tcp && !server.UDP:
|
|
||||||
default:
|
|
||||||
servers = append(servers, server)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return servers
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *purevpn) GetOpenVPNConnection(selection configuration.ServerSelection) (
|
|
||||||
connection models.OpenVPNConnection, err error) {
|
|
||||||
var port uint16 = 53
|
|
||||||
protocol := constants.UDP
|
|
||||||
if selection.TCP {
|
|
||||||
port = 80
|
|
||||||
protocol = constants.TCP
|
|
||||||
}
|
|
||||||
|
|
||||||
if selection.TargetIP != nil {
|
|
||||||
return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: protocol}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
servers := p.filterServers(selection.Regions, selection.Countries,
|
|
||||||
selection.Cities, selection.Hostnames, selection.TCP)
|
|
||||||
if len(servers) == 0 {
|
|
||||||
return connection, fmt.Errorf("no server found for regions %s, countries %s and cities %s",
|
|
||||||
commaJoin(selection.Regions), commaJoin(selection.Countries), commaJoin(selection.Cities))
|
|
||||||
}
|
|
||||||
|
|
||||||
var connections []models.OpenVPNConnection
|
|
||||||
for _, server := range servers {
|
|
||||||
for _, IP := range server.IPs {
|
|
||||||
connections = append(connections, models.OpenVPNConnection{IP: IP, Port: port, Protocol: protocol})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pickRandomConnection(connections, p.randSource), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *purevpn) BuildConf(connection models.OpenVPNConnection,
|
|
||||||
username string, settings configuration.OpenVPN) (lines []string) {
|
|
||||||
if len(settings.Cipher) == 0 {
|
|
||||||
settings.Cipher = aes256cbc
|
|
||||||
}
|
|
||||||
lines = []string{
|
|
||||||
"client",
|
|
||||||
"dev tun",
|
|
||||||
"nobind",
|
|
||||||
"persist-key",
|
|
||||||
"remote-cert-tls server",
|
|
||||||
"ping 10",
|
|
||||||
"ping-exit 60",
|
|
||||||
"ping-timer-rem",
|
|
||||||
"tls-exit",
|
|
||||||
|
|
||||||
// Purevpn specific
|
|
||||||
"key-direction 1",
|
|
||||||
"remote-cert-tls server",
|
|
||||||
"cipher AES-256-CBC",
|
|
||||||
"route-method exe",
|
|
||||||
"route-delay 0",
|
|
||||||
"script-security 2",
|
|
||||||
|
|
||||||
// Added constant values
|
|
||||||
"auth-nocache",
|
|
||||||
"mute-replay-warnings",
|
|
||||||
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
|
||||||
"auth-retry nointeract",
|
|
||||||
"suppress-timestamps",
|
|
||||||
|
|
||||||
// Modified variables
|
|
||||||
fmt.Sprintf("verb %d", settings.Verbosity),
|
|
||||||
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
|
||||||
fmt.Sprintf("proto %s", connection.Protocol),
|
|
||||||
fmt.Sprintf("remote %s %d", connection.IP.String(), connection.Port),
|
|
||||||
"data-ciphers-fallback " + settings.Cipher,
|
|
||||||
"data-ciphers " + settings.Cipher,
|
|
||||||
}
|
|
||||||
if !settings.Root {
|
|
||||||
lines = append(lines, "user "+username)
|
|
||||||
}
|
|
||||||
if settings.MSSFix > 0 {
|
|
||||||
lines = append(lines, "mssfix "+strconv.Itoa(int(settings.MSSFix)))
|
|
||||||
}
|
|
||||||
lines = append(lines, []string{
|
|
||||||
"<ca>",
|
|
||||||
"-----BEGIN CERTIFICATE-----",
|
|
||||||
constants.PurevpnCertificateAuthority,
|
|
||||||
"-----END CERTIFICATE-----",
|
|
||||||
"</ca>",
|
|
||||||
}...)
|
|
||||||
lines = append(lines, []string{
|
|
||||||
"<cert>",
|
|
||||||
"-----BEGIN CERTIFICATE-----",
|
|
||||||
constants.PurevpnCertificate,
|
|
||||||
"-----END CERTIFICATE-----",
|
|
||||||
"</cert>",
|
|
||||||
}...)
|
|
||||||
lines = append(lines, []string{
|
|
||||||
"<key>",
|
|
||||||
"-----BEGIN PRIVATE KEY-----",
|
|
||||||
constants.PurevpnKey,
|
|
||||||
"-----END PRIVATE KEY-----",
|
|
||||||
"</key>",
|
|
||||||
"",
|
|
||||||
}...)
|
|
||||||
lines = append(lines, []string{
|
|
||||||
"<tls-auth>",
|
|
||||||
"-----BEGIN OpenVPN Static key V1-----",
|
|
||||||
constants.PurevpnOpenvpnStaticKeyV1,
|
|
||||||
"-----END OpenVPN Static key V1-----",
|
|
||||||
"</tls-auth>",
|
|
||||||
"",
|
|
||||||
}...)
|
|
||||||
if len(settings.Auth) > 0 {
|
|
||||||
lines = append(lines, "auth "+settings.Auth)
|
|
||||||
}
|
|
||||||
if connection.Protocol == constants.UDP {
|
|
||||||
lines = append(lines, "explicit-exit-notify")
|
|
||||||
}
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *purevpn) PortForward(ctx context.Context, client *http.Client,
|
|
||||||
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
|
||||||
syncState func(port uint16) (pfFilepath string)) {
|
|
||||||
panic("port forwarding is not supported for purevpn")
|
|
||||||
}
|
|
||||||
41
internal/provider/purevpn/connection.go
Normal file
41
internal/provider/purevpn/connection.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package purevpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *Purevpn) GetOpenVPNConnection(selection configuration.ServerSelection) (
|
||||||
|
connection models.OpenVPNConnection, err error) {
|
||||||
|
protocol := constants.UDP
|
||||||
|
var port uint16 = 53
|
||||||
|
if selection.TCP {
|
||||||
|
protocol = constants.TCP
|
||||||
|
port = 80
|
||||||
|
}
|
||||||
|
|
||||||
|
servers, err := p.filterServers(selection)
|
||||||
|
if err != nil {
|
||||||
|
return connection, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var connections []models.OpenVPNConnection
|
||||||
|
for _, server := range servers {
|
||||||
|
for _, IP := range server.IPs {
|
||||||
|
connection := models.OpenVPNConnection{
|
||||||
|
IP: IP,
|
||||||
|
Port: port,
|
||||||
|
Protocol: protocol,
|
||||||
|
}
|
||||||
|
connections = append(connections, connection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if selection.TargetIP != nil {
|
||||||
|
return utils.GetTargetIPConnection(connections, selection.TargetIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.PickRandomConnection(connections, p.randSource), nil
|
||||||
|
}
|
||||||
30
internal/provider/purevpn/filter.go
Normal file
30
internal/provider/purevpn/filter.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package purevpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *Purevpn) filterServers(selection configuration.ServerSelection) (
|
||||||
|
servers []models.PurevpnServer, err error) {
|
||||||
|
for _, server := range p.servers {
|
||||||
|
switch {
|
||||||
|
case
|
||||||
|
utils.FilterByPossibilities(server.Region, selection.Regions),
|
||||||
|
utils.FilterByPossibilities(server.Country, selection.Countries),
|
||||||
|
utils.FilterByPossibilities(server.City, selection.Cities),
|
||||||
|
utils.FilterByPossibilities(server.Hostname, selection.Hostnames),
|
||||||
|
selection.TCP && !server.TCP,
|
||||||
|
!selection.TCP && !server.UDP:
|
||||||
|
default:
|
||||||
|
servers = append(servers, server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(servers) == 0 {
|
||||||
|
return nil, utils.NoServerFoundError(selection)
|
||||||
|
}
|
||||||
|
|
||||||
|
return servers, nil
|
||||||
|
}
|
||||||
81
internal/provider/purevpn/openvpnconf.go
Normal file
81
internal/provider/purevpn/openvpnconf.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package purevpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *Purevpn) BuildConf(connection models.OpenVPNConnection,
|
||||||
|
username string, settings configuration.OpenVPN) (lines []string) {
|
||||||
|
if settings.Cipher == "" {
|
||||||
|
settings.Cipher = constants.AES256cbc
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = []string{
|
||||||
|
"client",
|
||||||
|
"dev tun",
|
||||||
|
"nobind",
|
||||||
|
"persist-key",
|
||||||
|
"remote-cert-tls server",
|
||||||
|
"ping 10",
|
||||||
|
"ping-exit 60",
|
||||||
|
"ping-timer-rem",
|
||||||
|
"tls-exit",
|
||||||
|
|
||||||
|
// Purevpn specific
|
||||||
|
"key-direction 1",
|
||||||
|
"remote-cert-tls server",
|
||||||
|
"cipher AES-256-CBC",
|
||||||
|
"route-method exe",
|
||||||
|
"route-delay 0",
|
||||||
|
"script-security 2",
|
||||||
|
|
||||||
|
// Added constant values
|
||||||
|
"auth-nocache",
|
||||||
|
"mute-replay-warnings",
|
||||||
|
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
||||||
|
"auth-retry nointeract",
|
||||||
|
"suppress-timestamps",
|
||||||
|
|
||||||
|
// Modified variables
|
||||||
|
"verb " + strconv.Itoa(settings.Verbosity),
|
||||||
|
"auth-user-pass " + constants.OpenVPNAuthConf,
|
||||||
|
connection.ProtoLine(),
|
||||||
|
connection.RemoteLine(),
|
||||||
|
"data-ciphers-fallback " + settings.Cipher,
|
||||||
|
"data-ciphers " + settings.Cipher,
|
||||||
|
}
|
||||||
|
|
||||||
|
if connection.Protocol == constants.UDP {
|
||||||
|
lines = append(lines, "explicit-exit-notify")
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.Auth != "" {
|
||||||
|
lines = append(lines, "auth "+settings.Auth)
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.MSSFix > 0 {
|
||||||
|
lines = append(lines, "mssfix "+strconv.Itoa(int(settings.MSSFix)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !settings.Root {
|
||||||
|
lines = append(lines, "user "+username)
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = append(lines, utils.WrapOpenvpnCA(
|
||||||
|
constants.PurevpnCertificateAuthority)...)
|
||||||
|
lines = append(lines, utils.WrapOpenvpnCert(
|
||||||
|
constants.PurevpnCertificate)...)
|
||||||
|
lines = append(lines, utils.WrapOpenvpnKey(
|
||||||
|
constants.PurevpnKey)...)
|
||||||
|
lines = append(lines, utils.WrapOpenvpnTLSAuth(
|
||||||
|
constants.PurevpnOpenvpnStaticKeyV1)...)
|
||||||
|
|
||||||
|
lines = append(lines, "")
|
||||||
|
|
||||||
|
return lines
|
||||||
|
}
|
||||||
17
internal/provider/purevpn/portforward.go
Normal file
17
internal/provider/purevpn/portforward.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package purevpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/firewall"
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
|
"github.com/qdm12/golibs/os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *Purevpn) PortForward(ctx context.Context, client *http.Client,
|
||||||
|
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP,
|
||||||
|
fw firewall.Configurator, syncState func(port uint16) (pfFilepath string)) {
|
||||||
|
panic("port forwarding is not supported for PureVPN")
|
||||||
|
}
|
||||||
19
internal/provider/purevpn/provider.go
Normal file
19
internal/provider/purevpn/provider.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package purevpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Purevpn struct {
|
||||||
|
servers []models.PurevpnServer
|
||||||
|
randSource rand.Source
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(servers []models.PurevpnServer, randSource rand.Source) *Purevpn {
|
||||||
|
return &Purevpn{
|
||||||
|
servers: servers,
|
||||||
|
randSource: randSource,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration"
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/gluetun/internal/firewall"
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
"github.com/qdm12/golibs/logging"
|
|
||||||
"github.com/qdm12/golibs/os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type surfshark struct {
|
|
||||||
servers []models.SurfsharkServer
|
|
||||||
randSource rand.Source
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSurfshark(servers []models.SurfsharkServer, timeNow timeNowFunc) *surfshark {
|
|
||||||
return &surfshark{
|
|
||||||
servers: servers,
|
|
||||||
randSource: rand.NewSource(timeNow().UnixNano()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *surfshark) filterServers(regions, hostnames []string, tcp bool) (servers []models.SurfsharkServer) {
|
|
||||||
for _, server := range s.servers {
|
|
||||||
switch {
|
|
||||||
case
|
|
||||||
filterByPossibilities(server.Region, regions),
|
|
||||||
filterByPossibilities(server.Hostname, hostnames),
|
|
||||||
tcp && !server.TCP,
|
|
||||||
!tcp && !server.UDP:
|
|
||||||
default:
|
|
||||||
servers = append(servers, server)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return servers
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *surfshark) notFoundErr(selection configuration.ServerSelection) error {
|
|
||||||
message := "for protocol " + tcpBoolToProtocol(selection.TCP)
|
|
||||||
|
|
||||||
if len(selection.Countries) > 0 {
|
|
||||||
message += " + regions " + commaJoin(selection.Regions)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(selection.Hostnames) > 0 {
|
|
||||||
message += " + hostnames " + commaJoin(selection.Hostnames)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("%w: %s", errNoServerFound, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *surfshark) GetOpenVPNConnection(selection configuration.ServerSelection) (
|
|
||||||
connection models.OpenVPNConnection, err error) {
|
|
||||||
var port uint16 = 1194
|
|
||||||
protocol := constants.UDP
|
|
||||||
if selection.TCP {
|
|
||||||
port = 1443
|
|
||||||
protocol = constants.TCP
|
|
||||||
}
|
|
||||||
|
|
||||||
if selection.TargetIP != nil {
|
|
||||||
return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: protocol}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
servers := s.filterServers(selection.Regions, selection.Hostnames, selection.TCP)
|
|
||||||
if len(servers) == 0 {
|
|
||||||
return connection, s.notFoundErr(selection)
|
|
||||||
}
|
|
||||||
|
|
||||||
var connections []models.OpenVPNConnection
|
|
||||||
for _, server := range servers {
|
|
||||||
for _, IP := range server.IPs {
|
|
||||||
connections = append(connections, models.OpenVPNConnection{IP: IP, Port: port, Protocol: protocol})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if selection.TargetIP != nil {
|
|
||||||
return connection, fmt.Errorf("target IP %s not found in IP addresses", selection.TargetIP)
|
|
||||||
}
|
|
||||||
|
|
||||||
return pickRandomConnection(connections, s.randSource), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *surfshark) BuildConf(connection models.OpenVPNConnection,
|
|
||||||
username string, settings configuration.OpenVPN) (lines []string) {
|
|
||||||
if len(settings.Cipher) == 0 {
|
|
||||||
settings.Cipher = aes256gcm
|
|
||||||
}
|
|
||||||
if len(settings.Auth) == 0 {
|
|
||||||
settings.Auth = "SHA512"
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultMSSFix = 1450
|
|
||||||
if settings.MSSFix == 0 {
|
|
||||||
settings.MSSFix = defaultMSSFix
|
|
||||||
}
|
|
||||||
|
|
||||||
lines = []string{
|
|
||||||
"client",
|
|
||||||
"dev tun",
|
|
||||||
"nobind",
|
|
||||||
"persist-key",
|
|
||||||
"remote-cert-tls server",
|
|
||||||
"ping 15",
|
|
||||||
"ping-timer-rem",
|
|
||||||
"tls-exit",
|
|
||||||
|
|
||||||
// Surfshark specific
|
|
||||||
"tun-mtu 1500",
|
|
||||||
"tun-mtu-extra 32",
|
|
||||||
"mssfix " + strconv.Itoa(int(settings.MSSFix)),
|
|
||||||
"reneg-sec 0",
|
|
||||||
"fast-io",
|
|
||||||
"key-direction 1",
|
|
||||||
"script-security 2",
|
|
||||||
"ping-restart 0",
|
|
||||||
|
|
||||||
// Added constant values
|
|
||||||
"auth-nocache",
|
|
||||||
"mute-replay-warnings",
|
|
||||||
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
|
||||||
"pull-filter ignore \"block-outside-dns\"",
|
|
||||||
"auth-retry nointeract",
|
|
||||||
"suppress-timestamps",
|
|
||||||
|
|
||||||
// Modified variables
|
|
||||||
fmt.Sprintf("verb %d", settings.Verbosity),
|
|
||||||
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
|
||||||
fmt.Sprintf("proto %s", connection.Protocol),
|
|
||||||
fmt.Sprintf("remote %s %d", connection.IP, connection.Port),
|
|
||||||
"data-ciphers-fallback " + settings.Cipher,
|
|
||||||
"data-ciphers " + settings.Cipher,
|
|
||||||
fmt.Sprintf("auth %s", settings.Auth),
|
|
||||||
}
|
|
||||||
if !settings.Root {
|
|
||||||
lines = append(lines, "user "+username)
|
|
||||||
}
|
|
||||||
lines = append(lines, []string{
|
|
||||||
"<ca>",
|
|
||||||
"-----BEGIN CERTIFICATE-----",
|
|
||||||
constants.SurfsharkCertificate,
|
|
||||||
"-----END CERTIFICATE-----",
|
|
||||||
"</ca>",
|
|
||||||
}...)
|
|
||||||
lines = append(lines, []string{
|
|
||||||
"<tls-auth>",
|
|
||||||
"-----BEGIN OpenVPN Static key V1-----",
|
|
||||||
constants.SurfsharkOpenvpnStaticKeyV1,
|
|
||||||
"-----END OpenVPN Static key V1-----",
|
|
||||||
"</tls-auth>",
|
|
||||||
"",
|
|
||||||
}...)
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *surfshark) PortForward(ctx context.Context, client *http.Client,
|
|
||||||
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
|
||||||
syncState func(port uint16) (pfFilepath string)) {
|
|
||||||
panic("port forwarding is not supported for surfshark")
|
|
||||||
}
|
|
||||||
41
internal/provider/surfshark/connection.go
Normal file
41
internal/provider/surfshark/connection.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package surfshark
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Surfshark) GetOpenVPNConnection(selection configuration.ServerSelection) (
|
||||||
|
connection models.OpenVPNConnection, err error) {
|
||||||
|
protocol := constants.UDP
|
||||||
|
var port uint16 = 1194
|
||||||
|
if selection.TCP {
|
||||||
|
protocol = constants.TCP
|
||||||
|
port = 1443
|
||||||
|
}
|
||||||
|
|
||||||
|
servers, err := s.filterServers(selection)
|
||||||
|
if err != nil {
|
||||||
|
return connection, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var connections []models.OpenVPNConnection
|
||||||
|
for _, server := range servers {
|
||||||
|
for _, IP := range server.IPs {
|
||||||
|
connection := models.OpenVPNConnection{
|
||||||
|
IP: IP,
|
||||||
|
Port: port,
|
||||||
|
Protocol: protocol,
|
||||||
|
}
|
||||||
|
connections = append(connections, connection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if selection.TargetIP != nil {
|
||||||
|
return utils.GetTargetIPConnection(connections, selection.TargetIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.PickRandomConnection(connections, s.randSource), nil
|
||||||
|
}
|
||||||
28
internal/provider/surfshark/filter.go
Normal file
28
internal/provider/surfshark/filter.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package surfshark
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Surfshark) filterServers(selection configuration.ServerSelection) (
|
||||||
|
servers []models.SurfsharkServer, err error) {
|
||||||
|
for _, server := range s.servers {
|
||||||
|
switch {
|
||||||
|
case
|
||||||
|
utils.FilterByPossibilities(server.Region, selection.Regions),
|
||||||
|
utils.FilterByPossibilities(server.Hostname, selection.Hostnames),
|
||||||
|
selection.TCP && !server.TCP,
|
||||||
|
!selection.TCP && !server.UDP:
|
||||||
|
default:
|
||||||
|
servers = append(servers, server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(servers) == 0 {
|
||||||
|
return nil, utils.NoServerFoundError(selection)
|
||||||
|
}
|
||||||
|
|
||||||
|
return servers, nil
|
||||||
|
}
|
||||||
76
internal/provider/surfshark/openvpnconf.go
Normal file
76
internal/provider/surfshark/openvpnconf.go
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package surfshark
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Surfshark) BuildConf(connection models.OpenVPNConnection,
|
||||||
|
username string, settings configuration.OpenVPN) (lines []string) {
|
||||||
|
if settings.Cipher == "" {
|
||||||
|
settings.Cipher = constants.AES256gcm
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.Auth == "" {
|
||||||
|
settings.Auth = constants.SHA512
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultMSSFix = 1450
|
||||||
|
if settings.MSSFix == 0 {
|
||||||
|
settings.MSSFix = defaultMSSFix
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = []string{
|
||||||
|
"client",
|
||||||
|
"dev tun",
|
||||||
|
"nobind",
|
||||||
|
"persist-key",
|
||||||
|
"remote-cert-tls server",
|
||||||
|
"ping 15",
|
||||||
|
"ping-timer-rem",
|
||||||
|
"tls-exit",
|
||||||
|
|
||||||
|
// Surfshark specific
|
||||||
|
"tun-mtu 1500",
|
||||||
|
"tun-mtu-extra 32",
|
||||||
|
"mssfix " + strconv.Itoa(int(settings.MSSFix)),
|
||||||
|
"reneg-sec 0",
|
||||||
|
"fast-io",
|
||||||
|
"key-direction 1",
|
||||||
|
"script-security 2",
|
||||||
|
"ping-restart 0",
|
||||||
|
|
||||||
|
// Added constant values
|
||||||
|
"auth-nocache",
|
||||||
|
"mute-replay-warnings",
|
||||||
|
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
||||||
|
"auth-retry nointeract",
|
||||||
|
"suppress-timestamps",
|
||||||
|
|
||||||
|
// Modified variables
|
||||||
|
"verb " + strconv.Itoa(settings.Verbosity),
|
||||||
|
"auth-user-pass " + constants.OpenVPNAuthConf,
|
||||||
|
connection.ProtoLine(),
|
||||||
|
connection.RemoteLine(),
|
||||||
|
"data-ciphers-fallback " + settings.Cipher,
|
||||||
|
"data-ciphers " + settings.Cipher,
|
||||||
|
"auth " + settings.Auth,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !settings.Root {
|
||||||
|
lines = append(lines, "user "+username)
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = append(lines, utils.WrapOpenvpnCA(
|
||||||
|
constants.SurfsharkCertificate)...)
|
||||||
|
lines = append(lines, utils.WrapOpenvpnTLSAuth(
|
||||||
|
constants.SurfsharkOpenvpnStaticKeyV1)...)
|
||||||
|
|
||||||
|
lines = append(lines, "")
|
||||||
|
|
||||||
|
return lines
|
||||||
|
}
|
||||||
17
internal/provider/surfshark/portforward.go
Normal file
17
internal/provider/surfshark/portforward.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package surfshark
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/firewall"
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
|
"github.com/qdm12/golibs/os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Surfshark) PortForward(ctx context.Context, client *http.Client,
|
||||||
|
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP,
|
||||||
|
fw firewall.Configurator, syncState func(port uint16) (pfFilepath string)) {
|
||||||
|
panic("port forwarding is not supported for Surfshark")
|
||||||
|
}
|
||||||
19
internal/provider/surfshark/provider.go
Normal file
19
internal/provider/surfshark/provider.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package surfshark
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Surfshark struct {
|
||||||
|
servers []models.SurfsharkServer
|
||||||
|
randSource rand.Source
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(servers []models.SurfsharkServer, randSource rand.Source) *Surfshark {
|
||||||
|
return &Surfshark{
|
||||||
|
servers: servers,
|
||||||
|
randSource: randSource,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,184 +0,0 @@
|
|||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration"
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/gluetun/internal/firewall"
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
"github.com/qdm12/golibs/logging"
|
|
||||||
"github.com/qdm12/golibs/os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type torguard struct {
|
|
||||||
servers []models.TorguardServer
|
|
||||||
randSource rand.Source
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTorguard(servers []models.TorguardServer, timeNow timeNowFunc) *torguard {
|
|
||||||
return &torguard{
|
|
||||||
servers: servers,
|
|
||||||
randSource: rand.NewSource(timeNow().UnixNano()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *torguard) filterServers(countries, cities, hostnames []string,
|
|
||||||
tcp bool) (servers []models.TorguardServer) {
|
|
||||||
for _, server := range t.servers {
|
|
||||||
switch {
|
|
||||||
case
|
|
||||||
filterByPossibilities(server.Country, countries),
|
|
||||||
filterByPossibilities(server.City, cities),
|
|
||||||
filterByPossibilities(server.Hostname, hostnames),
|
|
||||||
tcp && !server.TCP,
|
|
||||||
!tcp && !server.UDP:
|
|
||||||
default:
|
|
||||||
servers = append(servers, server)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return servers
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *torguard) notFoundErr(selection configuration.ServerSelection) error {
|
|
||||||
message := "no server found for protocol " + tcpBoolToProtocol(selection.TCP)
|
|
||||||
|
|
||||||
if len(selection.Countries) > 0 {
|
|
||||||
message += " + countries " + commaJoin(selection.Countries)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(selection.Cities) > 0 {
|
|
||||||
message += " + cities " + commaJoin(selection.Cities)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(selection.Hostnames) > 0 {
|
|
||||||
message += " + hostnames " + commaJoin(selection.Hostnames)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *torguard) GetOpenVPNConnection(selection configuration.ServerSelection) (
|
|
||||||
connection models.OpenVPNConnection, err error) {
|
|
||||||
var port uint16 = 1912
|
|
||||||
if selection.CustomPort > 0 {
|
|
||||||
port = selection.CustomPort
|
|
||||||
}
|
|
||||||
|
|
||||||
protocol := tcpBoolToProtocol(selection.TCP)
|
|
||||||
|
|
||||||
if selection.TargetIP != nil {
|
|
||||||
return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: protocol}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
servers := t.filterServers(selection.Countries, selection.Cities,
|
|
||||||
selection.Hostnames, selection.TCP)
|
|
||||||
if len(servers) == 0 {
|
|
||||||
return connection, t.notFoundErr(selection)
|
|
||||||
}
|
|
||||||
|
|
||||||
var connections []models.OpenVPNConnection
|
|
||||||
for _, server := range servers {
|
|
||||||
for _, ip := range server.IPs {
|
|
||||||
connection := models.OpenVPNConnection{
|
|
||||||
IP: ip,
|
|
||||||
Port: port,
|
|
||||||
Protocol: protocol,
|
|
||||||
}
|
|
||||||
connections = append(connections, connection)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pickRandomConnection(connections, t.randSource), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *torguard) BuildConf(connection models.OpenVPNConnection,
|
|
||||||
username string, settings configuration.OpenVPN) (lines []string) {
|
|
||||||
if len(settings.Cipher) == 0 {
|
|
||||||
settings.Cipher = aes256gcm
|
|
||||||
}
|
|
||||||
if len(settings.Auth) == 0 {
|
|
||||||
settings.Auth = sha256
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultMSSFix = 1450
|
|
||||||
if settings.MSSFix == 0 {
|
|
||||||
settings.MSSFix = defaultMSSFix
|
|
||||||
}
|
|
||||||
|
|
||||||
lines = []string{
|
|
||||||
"client",
|
|
||||||
"dev tun",
|
|
||||||
"nobind",
|
|
||||||
"persist-key",
|
|
||||||
"remote-cert-tls server",
|
|
||||||
"tls-exit",
|
|
||||||
|
|
||||||
// Torguard specific
|
|
||||||
"tun-mtu 1500",
|
|
||||||
"tun-mtu-extra 32",
|
|
||||||
"mssfix " + strconv.Itoa(int(settings.MSSFix)),
|
|
||||||
"reneg-sec 0",
|
|
||||||
"fast-io",
|
|
||||||
"key-direction 1",
|
|
||||||
"script-security 2",
|
|
||||||
"ncp-disable",
|
|
||||||
"compress",
|
|
||||||
"keepalive 5 30",
|
|
||||||
"sndbuf 393216",
|
|
||||||
"rcvbuf 393216",
|
|
||||||
// "up /etc/openvpn/update-resolv-conf",
|
|
||||||
// "down /etc/openvpn/update-resolv-conf",
|
|
||||||
|
|
||||||
// Added constant values
|
|
||||||
"auth-nocache",
|
|
||||||
"mute-replay-warnings",
|
|
||||||
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
|
||||||
"pull-filter ignore \"block-outside-dns\"",
|
|
||||||
"auth-retry nointeract",
|
|
||||||
"suppress-timestamps",
|
|
||||||
|
|
||||||
// Modified variables
|
|
||||||
"verb " + strconv.Itoa(settings.Verbosity),
|
|
||||||
"auth-user-pass " + constants.OpenVPNAuthConf,
|
|
||||||
"proto " + connection.Protocol,
|
|
||||||
"remote " + connection.IP.String() + " " + strconv.Itoa(int(connection.Port)),
|
|
||||||
"data-ciphers-fallback " + settings.Cipher,
|
|
||||||
"data-ciphers " + settings.Cipher,
|
|
||||||
"auth " + settings.Auth,
|
|
||||||
}
|
|
||||||
|
|
||||||
if !settings.Root {
|
|
||||||
lines = append(lines, "user "+username)
|
|
||||||
}
|
|
||||||
|
|
||||||
lines = append(lines, []string{
|
|
||||||
"<ca>",
|
|
||||||
"-----BEGIN CERTIFICATE-----",
|
|
||||||
constants.TorguardCertificate,
|
|
||||||
"-----END CERTIFICATE-----",
|
|
||||||
"</ca>",
|
|
||||||
}...)
|
|
||||||
|
|
||||||
lines = append(lines, []string{
|
|
||||||
"<tls-auth>",
|
|
||||||
"-----BEGIN OpenVPN Static key V1-----",
|
|
||||||
constants.TorguardOpenvpnStaticKeyV1,
|
|
||||||
"-----END OpenVPN Static key V1-----",
|
|
||||||
"</tls-auth>",
|
|
||||||
"",
|
|
||||||
}...)
|
|
||||||
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *torguard) PortForward(ctx context.Context, client *http.Client,
|
|
||||||
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
|
||||||
syncState func(port uint16) (pfFilepath string)) {
|
|
||||||
panic("port forwarding is not supported for torguard")
|
|
||||||
}
|
|
||||||
44
internal/provider/torguard/connection.go
Normal file
44
internal/provider/torguard/connection.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package torguard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t *Torguard) GetOpenVPNConnection(selection configuration.ServerSelection) (
|
||||||
|
connection models.OpenVPNConnection, err error) {
|
||||||
|
protocol := constants.UDP
|
||||||
|
if selection.TCP {
|
||||||
|
protocol = constants.TCP
|
||||||
|
}
|
||||||
|
|
||||||
|
var port uint16 = 1912
|
||||||
|
if selection.CustomPort > 0 {
|
||||||
|
port = selection.CustomPort
|
||||||
|
}
|
||||||
|
|
||||||
|
servers, err := t.filterServers(selection)
|
||||||
|
if err != nil {
|
||||||
|
return connection, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var connections []models.OpenVPNConnection
|
||||||
|
for _, server := range servers {
|
||||||
|
for _, IP := range server.IPs {
|
||||||
|
connection := models.OpenVPNConnection{
|
||||||
|
IP: IP,
|
||||||
|
Port: port,
|
||||||
|
Protocol: protocol,
|
||||||
|
}
|
||||||
|
connections = append(connections, connection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if selection.TargetIP != nil {
|
||||||
|
return utils.GetTargetIPConnection(connections, selection.TargetIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.PickRandomConnection(connections, t.randSource), nil
|
||||||
|
}
|
||||||
29
internal/provider/torguard/filter.go
Normal file
29
internal/provider/torguard/filter.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package torguard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t *Torguard) filterServers(selection configuration.ServerSelection) (
|
||||||
|
servers []models.TorguardServer, err error) {
|
||||||
|
for _, server := range t.servers {
|
||||||
|
switch {
|
||||||
|
case
|
||||||
|
utils.FilterByPossibilities(server.Country, selection.Countries),
|
||||||
|
utils.FilterByPossibilities(server.City, selection.Cities),
|
||||||
|
utils.FilterByPossibilities(server.Hostname, selection.Hostnames),
|
||||||
|
selection.TCP && !server.TCP,
|
||||||
|
!selection.TCP && !server.UDP:
|
||||||
|
default:
|
||||||
|
servers = append(servers, server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(servers) == 0 {
|
||||||
|
return nil, utils.NoServerFoundError(selection)
|
||||||
|
}
|
||||||
|
|
||||||
|
return servers, nil
|
||||||
|
}
|
||||||
78
internal/provider/torguard/openvpnconf.go
Normal file
78
internal/provider/torguard/openvpnconf.go
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package torguard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t *Torguard) BuildConf(connection models.OpenVPNConnection,
|
||||||
|
username string, settings configuration.OpenVPN) (lines []string) {
|
||||||
|
if settings.Cipher == "" {
|
||||||
|
settings.Cipher = constants.AES256gcm
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.Auth == "" {
|
||||||
|
settings.Auth = constants.SHA256
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultMSSFix = 1450
|
||||||
|
if settings.MSSFix == 0 {
|
||||||
|
settings.MSSFix = defaultMSSFix
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = []string{
|
||||||
|
"client",
|
||||||
|
"dev tun",
|
||||||
|
"nobind",
|
||||||
|
"persist-key",
|
||||||
|
"remote-cert-tls server",
|
||||||
|
"tls-exit",
|
||||||
|
|
||||||
|
// Torguard specific
|
||||||
|
"tun-mtu 1500",
|
||||||
|
"tun-mtu-extra 32",
|
||||||
|
"mssfix " + strconv.Itoa(int(settings.MSSFix)),
|
||||||
|
"reneg-sec 0",
|
||||||
|
"fast-io",
|
||||||
|
"key-direction 1",
|
||||||
|
"script-security 2",
|
||||||
|
"ncp-disable",
|
||||||
|
"compress",
|
||||||
|
"keepalive 5 30",
|
||||||
|
"sndbuf 393216",
|
||||||
|
"rcvbuf 393216",
|
||||||
|
|
||||||
|
// Added constant values
|
||||||
|
"auth-nocache",
|
||||||
|
"mute-replay-warnings",
|
||||||
|
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
||||||
|
"auth-retry nointeract",
|
||||||
|
"suppress-timestamps",
|
||||||
|
|
||||||
|
// Modified variables
|
||||||
|
"verb " + strconv.Itoa(settings.Verbosity),
|
||||||
|
"auth-user-pass " + constants.OpenVPNAuthConf,
|
||||||
|
connection.ProtoLine(),
|
||||||
|
connection.RemoteLine(),
|
||||||
|
"data-ciphers-fallback " + settings.Cipher,
|
||||||
|
"data-ciphers " + settings.Cipher,
|
||||||
|
"auth " + settings.Auth,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !settings.Root {
|
||||||
|
lines = append(lines, "user "+username)
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = append(lines, utils.WrapOpenvpnCA(
|
||||||
|
constants.TorguardCertificate)...)
|
||||||
|
lines = append(lines, utils.WrapOpenvpnTLSAuth(
|
||||||
|
constants.TorguardOpenvpnStaticKeyV1)...)
|
||||||
|
|
||||||
|
lines = append(lines, "")
|
||||||
|
|
||||||
|
return lines
|
||||||
|
}
|
||||||
17
internal/provider/torguard/portforward.go
Normal file
17
internal/provider/torguard/portforward.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package torguard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/firewall"
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
|
"github.com/qdm12/golibs/os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t *Torguard) PortForward(ctx context.Context, client *http.Client,
|
||||||
|
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP,
|
||||||
|
fw firewall.Configurator, syncState func(port uint16) (pfFilepath string)) {
|
||||||
|
panic("port forwarding is not supported for Torguard")
|
||||||
|
}
|
||||||
19
internal/provider/torguard/provider.go
Normal file
19
internal/provider/torguard/provider.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package torguard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Torguard struct {
|
||||||
|
servers []models.TorguardServer
|
||||||
|
randSource rand.Source
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(servers []models.TorguardServer, randSource rand.Source) *Torguard {
|
||||||
|
return &Torguard{
|
||||||
|
servers: servers,
|
||||||
|
randSource: randSource,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"math/rand"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
"github.com/qdm12/golibs/logging"
|
|
||||||
)
|
|
||||||
|
|
||||||
type timeNowFunc func() time.Time
|
|
||||||
|
|
||||||
func tryUntilSuccessful(ctx context.Context, logger logging.Logger, fn func() error) {
|
|
||||||
const retryPeriod = 10 * time.Second
|
|
||||||
for {
|
|
||||||
err := fn()
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
logger.Error(err)
|
|
||||||
logger.Info("Trying again in %s", retryPeriod)
|
|
||||||
timer := time.NewTimer(retryPeriod)
|
|
||||||
select {
|
|
||||||
case <-timer.C:
|
|
||||||
case <-ctx.Done():
|
|
||||||
if !timer.Stop() {
|
|
||||||
<-timer.C
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func pickRandomConnection(connections []models.OpenVPNConnection, source rand.Source) models.OpenVPNConnection {
|
|
||||||
return connections[rand.New(source).Intn(len(connections))] //nolint:gosec
|
|
||||||
}
|
|
||||||
|
|
||||||
func filterByPossibilities(value string, possibilities []string) (filtered bool) {
|
|
||||||
if len(possibilities) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, possibility := range possibilities {
|
|
||||||
if strings.EqualFold(value, possibility) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func commaJoin(slice []string) string {
|
|
||||||
return strings.Join(slice, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
func tcpBoolToProtocol(tcp bool) (protocol string) {
|
|
||||||
if tcp {
|
|
||||||
return "tcp"
|
|
||||||
}
|
|
||||||
return "udp"
|
|
||||||
}
|
|
||||||
15
internal/provider/utils/filtering.go
Normal file
15
internal/provider/utils/filtering.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
func FilterByPossibilities(value string, possibilities []string) (filtered bool) {
|
||||||
|
if len(possibilities) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, possibility := range possibilities {
|
||||||
|
if strings.EqualFold(value, possibility) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
35
internal/provider/utils/filtering_test.go
Normal file
35
internal/provider/utils/filtering_test.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_FilterByPossibilities(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testCases := map[string]struct {
|
||||||
|
value string
|
||||||
|
possibilities []string
|
||||||
|
filtered bool
|
||||||
|
}{
|
||||||
|
"no possibilities": {},
|
||||||
|
"value not in possibilities": {
|
||||||
|
value: "c",
|
||||||
|
possibilities: []string{"a", "b"},
|
||||||
|
filtered: true,
|
||||||
|
},
|
||||||
|
"value in possibilities": {
|
||||||
|
value: "c",
|
||||||
|
possibilities: []string{"a", "b", "c"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
filtered := FilterByPossibilities(testCase.value, testCase.possibilities)
|
||||||
|
assert.Equal(t, testCase.filtered, filtered)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
119
internal/provider/utils/formatting.go
Normal file
119
internal/provider/utils/formatting.go
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
)
|
||||||
|
|
||||||
|
func commaJoin(slice []string) string {
|
||||||
|
return strings.Join(slice, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrNoServerFound = errors.New("no server found")
|
||||||
|
|
||||||
|
func NoServerFoundError(selection configuration.ServerSelection) (err error) {
|
||||||
|
var messageParts []string
|
||||||
|
|
||||||
|
protocol := constants.UDP
|
||||||
|
if selection.TCP {
|
||||||
|
protocol = constants.TCP
|
||||||
|
}
|
||||||
|
messageParts = append(messageParts, "protocol "+protocol)
|
||||||
|
|
||||||
|
if selection.Group != "" {
|
||||||
|
part := "group " + selection.Group
|
||||||
|
messageParts = append(messageParts, part)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(selection.Countries) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
part := "country " + selection.Countries[0]
|
||||||
|
messageParts = append(messageParts, part)
|
||||||
|
default:
|
||||||
|
part := "countries " + commaJoin(selection.Countries)
|
||||||
|
messageParts = append(messageParts, part)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(selection.Regions) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
part := "region " + selection.Regions[0]
|
||||||
|
messageParts = append(messageParts, part)
|
||||||
|
default:
|
||||||
|
part := "regions " + commaJoin(selection.Regions)
|
||||||
|
messageParts = append(messageParts, part)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(selection.Cities) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
part := "city " + selection.Cities[0]
|
||||||
|
messageParts = append(messageParts, part)
|
||||||
|
default:
|
||||||
|
part := "cities " + commaJoin(selection.Cities)
|
||||||
|
messageParts = append(messageParts, part)
|
||||||
|
}
|
||||||
|
|
||||||
|
if selection.Owned {
|
||||||
|
messageParts = append(messageParts, "owned servers only")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(selection.ISPs) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
part := "ISP " + selection.ISPs[0]
|
||||||
|
messageParts = append(messageParts, part)
|
||||||
|
default:
|
||||||
|
part := "ISPs " + commaJoin(selection.ISPs)
|
||||||
|
messageParts = append(messageParts, part)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(selection.Hostnames) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
part := "hostname " + selection.Hostnames[0]
|
||||||
|
messageParts = append(messageParts, part)
|
||||||
|
default:
|
||||||
|
part := "hostnames " + commaJoin(selection.Hostnames)
|
||||||
|
messageParts = append(messageParts, part)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(selection.Names) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
part := "name " + selection.Names[0]
|
||||||
|
messageParts = append(messageParts, part)
|
||||||
|
default:
|
||||||
|
part := "names " + commaJoin(selection.Names)
|
||||||
|
messageParts = append(messageParts, part)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(selection.Numbers) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
part := "server number " + strconv.Itoa(int(selection.Numbers[0]))
|
||||||
|
messageParts = append(messageParts, part)
|
||||||
|
default:
|
||||||
|
serverNumbers := make([]string, len(selection.Numbers))
|
||||||
|
for i := range selection.Numbers {
|
||||||
|
serverNumbers[i] = strconv.Itoa(int(selection.Numbers[i]))
|
||||||
|
}
|
||||||
|
part := "server numbers " + commaJoin(serverNumbers)
|
||||||
|
messageParts = append(messageParts, part)
|
||||||
|
}
|
||||||
|
|
||||||
|
if selection.EncryptionPreset != "" {
|
||||||
|
part := "encryption preset " + selection.EncryptionPreset
|
||||||
|
messageParts = append(messageParts, part)
|
||||||
|
}
|
||||||
|
|
||||||
|
message := "for " + strings.Join(messageParts, "; ")
|
||||||
|
|
||||||
|
return fmt.Errorf("%w: %s", ErrNoServerFound, message)
|
||||||
|
}
|
||||||
71
internal/provider/utils/openvpn.go
Normal file
71
internal/provider/utils/openvpn.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
func WrapOpenvpnCA(certificate string) (lines []string) {
|
||||||
|
return []string{
|
||||||
|
"<ca>",
|
||||||
|
"-----BEGIN CERTIFICATE-----",
|
||||||
|
certificate,
|
||||||
|
"-----END CERTIFICATE-----",
|
||||||
|
"</ca>",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WrapOpenvpnCert(clientCertificate string) (lines []string) {
|
||||||
|
return []string{
|
||||||
|
"<cert>",
|
||||||
|
"-----BEGIN CERTIFICATE-----",
|
||||||
|
clientCertificate,
|
||||||
|
"-----END CERTIFICATE-----",
|
||||||
|
"</cert>",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WrapOpenvpnCRLVerify(x509CRL string) (lines []string) {
|
||||||
|
return []string{
|
||||||
|
"<crl-verify>",
|
||||||
|
"-----BEGIN X509 CRL-----",
|
||||||
|
x509CRL,
|
||||||
|
"-----END X509 CRL-----",
|
||||||
|
"</crl-verify>",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WrapOpenvpnKey(clientKey string) (lines []string) {
|
||||||
|
return []string{
|
||||||
|
"<key>",
|
||||||
|
"-----BEGIN PRIVATE KEY-----",
|
||||||
|
clientKey,
|
||||||
|
"-----END PRIVATE KEY-----",
|
||||||
|
"</key>",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WrapOpenvpnRSAKey(rsaPrivateKey string) (lines []string) {
|
||||||
|
return []string{
|
||||||
|
"<key>",
|
||||||
|
"-----BEGIN RSA PRIVATE KEY-----",
|
||||||
|
rsaPrivateKey,
|
||||||
|
"-----END RSA PRIVATE KEY-----",
|
||||||
|
"</key>",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WrapOpenvpnTLSAuth(staticKeyV1 string) (lines []string) {
|
||||||
|
return []string{
|
||||||
|
"<tls-auth>",
|
||||||
|
"-----BEGIN OpenVPN Static key V1-----",
|
||||||
|
staticKeyV1,
|
||||||
|
"-----END OpenVPN Static key V1-----",
|
||||||
|
"</tls-auth>",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WrapOpenvpnTLSCrypt(staticKeyV1 string) (lines []string) {
|
||||||
|
return []string{
|
||||||
|
"<tls-crypt>",
|
||||||
|
"-----BEGIN OpenVPN Static key V1-----",
|
||||||
|
staticKeyV1,
|
||||||
|
"-----END OpenVPN Static key V1-----",
|
||||||
|
"</tls-crypt>",
|
||||||
|
}
|
||||||
|
}
|
||||||
12
internal/provider/utils/pick.go
Normal file
12
internal/provider/utils/pick.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PickRandomConnection(connections []models.OpenVPNConnection,
|
||||||
|
source rand.Source) models.OpenVPNConnection {
|
||||||
|
return connections[rand.New(source).Intn(len(connections))] //nolint:gosec
|
||||||
|
}
|
||||||
26
internal/provider/utils/pick_test.go
Normal file
26
internal/provider/utils/pick_test.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_PickRandomConnection(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
connections := []models.OpenVPNConnection{
|
||||||
|
{Port: 1}, {Port: 2}, {Port: 3}, {Port: 4},
|
||||||
|
}
|
||||||
|
source := rand.NewSource(0)
|
||||||
|
|
||||||
|
connection := PickRandomConnection(connections, source)
|
||||||
|
assert.Equal(t, models.OpenVPNConnection{Port: 3}, connection)
|
||||||
|
|
||||||
|
connection = PickRandomConnection(connections, source)
|
||||||
|
assert.Equal(t, models.OpenVPNConnection{Port: 3}, connection)
|
||||||
|
|
||||||
|
connection = PickRandomConnection(connections, source)
|
||||||
|
assert.Equal(t, models.OpenVPNConnection{Port: 2}, connection)
|
||||||
|
}
|
||||||
22
internal/provider/utils/targetip.go
Normal file
22
internal/provider/utils/targetip.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrTargetIPNotFound = errors.New("target IP address not found")
|
||||||
|
|
||||||
|
func GetTargetIPConnection(connections []models.OpenVPNConnection,
|
||||||
|
targetIP net.IP) (connection models.OpenVPNConnection, err error) {
|
||||||
|
for _, connection := range connections {
|
||||||
|
if targetIP.Equal(connection.IP) {
|
||||||
|
return connection, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return connection, fmt.Errorf("%w: in %d filtered connections",
|
||||||
|
ErrTargetIPNotFound, len(connections))
|
||||||
|
}
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/rand"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_pickRandomConnection(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
connections := []models.OpenVPNConnection{
|
|
||||||
{Port: 1}, {Port: 2}, {Port: 3}, {Port: 4},
|
|
||||||
}
|
|
||||||
source := rand.NewSource(0)
|
|
||||||
|
|
||||||
connection := pickRandomConnection(connections, source)
|
|
||||||
assert.Equal(t, models.OpenVPNConnection{Port: 3}, connection)
|
|
||||||
|
|
||||||
connection = pickRandomConnection(connections, source)
|
|
||||||
assert.Equal(t, models.OpenVPNConnection{Port: 3}, connection)
|
|
||||||
|
|
||||||
connection = pickRandomConnection(connections, source)
|
|
||||||
assert.Equal(t, models.OpenVPNConnection{Port: 2}, connection)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_filterByPossibilities(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
testCases := map[string]struct {
|
|
||||||
value string
|
|
||||||
possibilities []string
|
|
||||||
filtered bool
|
|
||||||
}{
|
|
||||||
"no possibilities": {},
|
|
||||||
"value not in possibilities": {
|
|
||||||
value: "c",
|
|
||||||
possibilities: []string{"a", "b"},
|
|
||||||
filtered: true,
|
|
||||||
},
|
|
||||||
"value in possibilities": {
|
|
||||||
value: "c",
|
|
||||||
possibilities: []string{"a", "b", "c"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, testCase := range testCases {
|
|
||||||
testCase := testCase
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
filtered := filterByPossibilities(testCase.value, testCase.possibilities)
|
|
||||||
assert.Equal(t, testCase.filtered, filtered)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration"
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/gluetun/internal/firewall"
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
"github.com/qdm12/golibs/logging"
|
|
||||||
"github.com/qdm12/golibs/os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type vyprvpn struct {
|
|
||||||
servers []models.VyprvpnServer
|
|
||||||
randSource rand.Source
|
|
||||||
}
|
|
||||||
|
|
||||||
func newVyprvpn(servers []models.VyprvpnServer, timeNow timeNowFunc) *vyprvpn {
|
|
||||||
return &vyprvpn{
|
|
||||||
servers: servers,
|
|
||||||
randSource: rand.NewSource(timeNow().UnixNano()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *vyprvpn) filterServers(regions, hostnames []string, tcp bool) (servers []models.VyprvpnServer) {
|
|
||||||
for _, server := range v.servers {
|
|
||||||
switch {
|
|
||||||
case
|
|
||||||
filterByPossibilities(server.Region, regions),
|
|
||||||
filterByPossibilities(server.Hostname, hostnames),
|
|
||||||
tcp && !server.TCP,
|
|
||||||
!tcp && !server.UDP:
|
|
||||||
default:
|
|
||||||
servers = append(servers, server)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return servers
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *vyprvpn) GetOpenVPNConnection(selection configuration.ServerSelection) (
|
|
||||||
connection models.OpenVPNConnection, err error) {
|
|
||||||
var port uint16
|
|
||||||
const protocol = constants.TCP
|
|
||||||
if selection.TCP {
|
|
||||||
return connection, fmt.Errorf("%w: TCP for provider VyprVPN",
|
|
||||||
ErrProtocolUnsupported)
|
|
||||||
}
|
|
||||||
|
|
||||||
if selection.TargetIP != nil {
|
|
||||||
return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: protocol}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
servers := v.filterServers(selection.Regions, selection.Hostnames, selection.TCP)
|
|
||||||
if len(servers) == 0 {
|
|
||||||
return connection, fmt.Errorf("no server found for region %s", commaJoin(selection.Regions))
|
|
||||||
}
|
|
||||||
|
|
||||||
var connections []models.OpenVPNConnection
|
|
||||||
for _, server := range servers {
|
|
||||||
for _, IP := range server.IPs {
|
|
||||||
connections = append(connections, models.OpenVPNConnection{IP: IP, Port: port, Protocol: protocol})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pickRandomConnection(connections, v.randSource), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *vyprvpn) BuildConf(connection models.OpenVPNConnection,
|
|
||||||
username string, settings configuration.OpenVPN) (lines []string) {
|
|
||||||
if len(settings.Cipher) == 0 {
|
|
||||||
settings.Cipher = aes256cbc
|
|
||||||
}
|
|
||||||
if len(settings.Auth) == 0 {
|
|
||||||
settings.Auth = "SHA256"
|
|
||||||
}
|
|
||||||
lines = []string{
|
|
||||||
"client",
|
|
||||||
"dev tun",
|
|
||||||
"nobind",
|
|
||||||
"persist-key",
|
|
||||||
"remote-cert-tls server",
|
|
||||||
"ping 10",
|
|
||||||
"ping-exit 60",
|
|
||||||
"ping-timer-rem",
|
|
||||||
"tls-exit",
|
|
||||||
|
|
||||||
// Vyprvpn specific
|
|
||||||
"comp-lzo",
|
|
||||||
// "verify-x509-name lu1.vyprvpn.com name",
|
|
||||||
"tls-cipher TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-CBC-SHA256:TLS-DHE-RSA-WITH-AES-256-CBC-SHA", //nolint:lll
|
|
||||||
|
|
||||||
// Added constant values
|
|
||||||
"auth-nocache",
|
|
||||||
"mute-replay-warnings",
|
|
||||||
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
|
||||||
"auth-retry nointeract",
|
|
||||||
"suppress-timestamps",
|
|
||||||
|
|
||||||
// Modified variables
|
|
||||||
fmt.Sprintf("verb %d", settings.Verbosity),
|
|
||||||
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
|
||||||
fmt.Sprintf("proto %s", connection.Protocol),
|
|
||||||
fmt.Sprintf("remote %s %d", connection.IP, connection.Port),
|
|
||||||
"data-ciphers-fallback " + settings.Cipher,
|
|
||||||
"data-ciphers " + settings.Cipher,
|
|
||||||
fmt.Sprintf("auth %s", settings.Auth),
|
|
||||||
}
|
|
||||||
if !settings.Root {
|
|
||||||
lines = append(lines, "user "+username)
|
|
||||||
}
|
|
||||||
if settings.MSSFix > 0 {
|
|
||||||
lines = append(lines, "mssfix "+strconv.Itoa(int(settings.MSSFix)))
|
|
||||||
}
|
|
||||||
lines = append(lines, []string{
|
|
||||||
"<ca>",
|
|
||||||
"-----BEGIN CERTIFICATE-----",
|
|
||||||
constants.VyprvpnCertificate,
|
|
||||||
"-----END CERTIFICATE-----",
|
|
||||||
"</ca>",
|
|
||||||
}...)
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *vyprvpn) PortForward(ctx context.Context, client *http.Client,
|
|
||||||
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
|
||||||
syncState func(port uint16) (pfFilepath string)) {
|
|
||||||
panic("port forwarding is not supported for vyprvpn")
|
|
||||||
}
|
|
||||||
45
internal/provider/vyprvpn/connection.go
Normal file
45
internal/provider/vyprvpn/connection.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package vyprvpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrProtocolUnsupported = errors.New("network protocol is not supported")
|
||||||
|
|
||||||
|
func (v *Vyprvpn) GetOpenVPNConnection(selection configuration.ServerSelection) (
|
||||||
|
connection models.OpenVPNConnection, err error) {
|
||||||
|
const port = 443
|
||||||
|
const protocol = constants.UDP
|
||||||
|
if selection.TCP {
|
||||||
|
return connection, fmt.Errorf("%w: TCP for provider VyprVPN", ErrProtocolUnsupported)
|
||||||
|
}
|
||||||
|
|
||||||
|
servers, err := v.filterServers(selection)
|
||||||
|
if err != nil {
|
||||||
|
return connection, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var connections []models.OpenVPNConnection
|
||||||
|
for _, server := range servers {
|
||||||
|
for _, IP := range server.IPs {
|
||||||
|
connection := models.OpenVPNConnection{
|
||||||
|
IP: IP,
|
||||||
|
Port: port,
|
||||||
|
Protocol: protocol,
|
||||||
|
}
|
||||||
|
connections = append(connections, connection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if selection.TargetIP != nil {
|
||||||
|
return utils.GetTargetIPConnection(connections, selection.TargetIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.PickRandomConnection(connections, v.randSource), nil
|
||||||
|
}
|
||||||
28
internal/provider/vyprvpn/filter.go
Normal file
28
internal/provider/vyprvpn/filter.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package vyprvpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (v *Vyprvpn) filterServers(selection configuration.ServerSelection) (
|
||||||
|
servers []models.VyprvpnServer, err error) {
|
||||||
|
for _, server := range v.servers {
|
||||||
|
switch {
|
||||||
|
case
|
||||||
|
utils.FilterByPossibilities(server.Region, selection.Regions),
|
||||||
|
utils.FilterByPossibilities(server.Hostname, selection.Hostnames),
|
||||||
|
selection.TCP && !server.TCP,
|
||||||
|
!selection.TCP && !server.UDP:
|
||||||
|
default:
|
||||||
|
servers = append(servers, server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(servers) == 0 {
|
||||||
|
return nil, utils.NoServerFoundError(selection)
|
||||||
|
}
|
||||||
|
|
||||||
|
return servers, nil
|
||||||
|
}
|
||||||
69
internal/provider/vyprvpn/openvpnconf.go
Normal file
69
internal/provider/vyprvpn/openvpnconf.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package vyprvpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (v *Vyprvpn) BuildConf(connection models.OpenVPNConnection,
|
||||||
|
username string, settings configuration.OpenVPN) (lines []string) {
|
||||||
|
if settings.Cipher == "" {
|
||||||
|
settings.Cipher = constants.AES256cbc
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.Auth == "" {
|
||||||
|
settings.Auth = constants.SHA256
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = []string{
|
||||||
|
"client",
|
||||||
|
"dev tun",
|
||||||
|
"nobind",
|
||||||
|
"persist-key",
|
||||||
|
"remote-cert-tls server",
|
||||||
|
"ping 10",
|
||||||
|
"ping-exit 60",
|
||||||
|
"ping-timer-rem",
|
||||||
|
"tls-exit",
|
||||||
|
|
||||||
|
// Vyprvpn specific
|
||||||
|
"comp-lzo",
|
||||||
|
// "verify-x509-name lu1.vyprvpn.com name",
|
||||||
|
"tls-cipher TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-CBC-SHA256:TLS-DHE-RSA-WITH-AES-256-CBC-SHA", //nolint:lll
|
||||||
|
|
||||||
|
// Added constant values
|
||||||
|
"auth-nocache",
|
||||||
|
"mute-replay-warnings",
|
||||||
|
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
||||||
|
"auth-retry nointeract",
|
||||||
|
"suppress-timestamps",
|
||||||
|
|
||||||
|
// Modified variables
|
||||||
|
"verb " + strconv.Itoa(settings.Verbosity),
|
||||||
|
"auth-user-pass " + constants.OpenVPNAuthConf,
|
||||||
|
connection.ProtoLine(),
|
||||||
|
connection.RemoteLine(),
|
||||||
|
"data-ciphers-fallback " + settings.Cipher,
|
||||||
|
"data-ciphers " + settings.Cipher,
|
||||||
|
"auth " + settings.Auth,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !settings.Root {
|
||||||
|
lines = append(lines, "user "+username)
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.MSSFix > 0 {
|
||||||
|
lines = append(lines, "mssfix "+strconv.Itoa(int(settings.MSSFix)))
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = append(lines, utils.WrapOpenvpnCA(
|
||||||
|
constants.VyprvpnCertificate)...)
|
||||||
|
|
||||||
|
lines = append(lines, "")
|
||||||
|
|
||||||
|
return lines
|
||||||
|
}
|
||||||
17
internal/provider/vyprvpn/portforward.go
Normal file
17
internal/provider/vyprvpn/portforward.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package vyprvpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/firewall"
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
|
"github.com/qdm12/golibs/os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (v *Vyprvpn) PortForward(ctx context.Context, clienv *http.Client,
|
||||||
|
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP,
|
||||||
|
fw firewall.Configurator, syncState func(port uint16) (pfFilepath string)) {
|
||||||
|
panic("port forwarding is not supported for Vyprvpn")
|
||||||
|
}
|
||||||
19
internal/provider/vyprvpn/provider.go
Normal file
19
internal/provider/vyprvpn/provider.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package vyprvpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Vyprvpn struct {
|
||||||
|
servers []models.VyprvpnServer
|
||||||
|
randSource rand.Source
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(servers []models.VyprvpnServer, randSource rand.Source) *Vyprvpn {
|
||||||
|
return &Vyprvpn{
|
||||||
|
servers: servers,
|
||||||
|
randSource: randSource,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration"
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/gluetun/internal/firewall"
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
"github.com/qdm12/golibs/logging"
|
|
||||||
"github.com/qdm12/golibs/os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type windscribe struct {
|
|
||||||
servers []models.WindscribeServer
|
|
||||||
randSource rand.Source
|
|
||||||
}
|
|
||||||
|
|
||||||
func newWindscribe(servers []models.WindscribeServer, timeNow timeNowFunc) *windscribe {
|
|
||||||
return &windscribe{
|
|
||||||
servers: servers,
|
|
||||||
randSource: rand.NewSource(timeNow().UnixNano()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *windscribe) filterServers(regions, cities, hostnames []string) (servers []models.WindscribeServer) {
|
|
||||||
for _, server := range w.servers {
|
|
||||||
switch {
|
|
||||||
case
|
|
||||||
filterByPossibilities(server.Region, regions),
|
|
||||||
filterByPossibilities(server.City, cities),
|
|
||||||
filterByPossibilities(server.Hostname, hostnames):
|
|
||||||
default:
|
|
||||||
servers = append(servers, server)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return servers
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint:lll
|
|
||||||
func (w *windscribe) GetOpenVPNConnection(selection configuration.ServerSelection) (connection models.OpenVPNConnection, err error) {
|
|
||||||
var port uint16 = 443
|
|
||||||
protocol := constants.UDP
|
|
||||||
if selection.TCP {
|
|
||||||
port = 1194
|
|
||||||
protocol = constants.TCP
|
|
||||||
}
|
|
||||||
|
|
||||||
if selection.CustomPort > 0 {
|
|
||||||
port = selection.CustomPort
|
|
||||||
}
|
|
||||||
|
|
||||||
if selection.TargetIP != nil {
|
|
||||||
return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: protocol}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
servers := w.filterServers(selection.Regions, selection.Cities, selection.Hostnames)
|
|
||||||
if len(servers) == 0 {
|
|
||||||
return connection, fmt.Errorf("no server found for region %s", commaJoin(selection.Regions))
|
|
||||||
}
|
|
||||||
|
|
||||||
var connections []models.OpenVPNConnection
|
|
||||||
for _, server := range servers {
|
|
||||||
for _, ip := range server.IPs {
|
|
||||||
connection := models.OpenVPNConnection{
|
|
||||||
IP: ip,
|
|
||||||
Port: port,
|
|
||||||
Protocol: protocol,
|
|
||||||
}
|
|
||||||
connections = append(connections, connection)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pickRandomConnection(connections, w.randSource), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *windscribe) BuildConf(connection models.OpenVPNConnection,
|
|
||||||
username string, settings configuration.OpenVPN) (lines []string) {
|
|
||||||
if len(settings.Cipher) == 0 {
|
|
||||||
settings.Cipher = aes256cbc
|
|
||||||
}
|
|
||||||
if len(settings.Auth) == 0 {
|
|
||||||
settings.Auth = "sha512"
|
|
||||||
}
|
|
||||||
lines = []string{
|
|
||||||
"client",
|
|
||||||
"dev tun",
|
|
||||||
"nobind",
|
|
||||||
"persist-key",
|
|
||||||
"remote-cert-tls server",
|
|
||||||
"ping 10",
|
|
||||||
"ping-exit 60",
|
|
||||||
"ping-timer-rem",
|
|
||||||
"tls-exit",
|
|
||||||
|
|
||||||
// Windscribe specific
|
|
||||||
"comp-lzo",
|
|
||||||
"key-direction 1",
|
|
||||||
"script-security 2",
|
|
||||||
"reneg-sec 0",
|
|
||||||
"ncp-disable",
|
|
||||||
|
|
||||||
// Added constant values
|
|
||||||
"auth-nocache",
|
|
||||||
"mute-replay-warnings",
|
|
||||||
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
|
||||||
"auth-retry nointeract",
|
|
||||||
"suppress-timestamps",
|
|
||||||
|
|
||||||
// Modified variables
|
|
||||||
fmt.Sprintf("verb %d", settings.Verbosity),
|
|
||||||
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
|
||||||
fmt.Sprintf("proto %s", connection.Protocol),
|
|
||||||
fmt.Sprintf("remote %s %d", connection.IP, connection.Port),
|
|
||||||
"data-ciphers-fallback " + settings.Cipher,
|
|
||||||
"data-ciphers " + settings.Cipher,
|
|
||||||
fmt.Sprintf("auth %s", settings.Auth),
|
|
||||||
}
|
|
||||||
if strings.HasSuffix(settings.Cipher, "-gcm") {
|
|
||||||
lines = append(lines, "ncp-ciphers AES-256-GCM:AES-256-CBC:AES-128-GCM")
|
|
||||||
}
|
|
||||||
if !settings.Root {
|
|
||||||
lines = append(lines, "user "+username)
|
|
||||||
}
|
|
||||||
if settings.MSSFix > 0 {
|
|
||||||
lines = append(lines, "mssfix "+strconv.Itoa(int(settings.MSSFix)))
|
|
||||||
}
|
|
||||||
lines = append(lines, []string{
|
|
||||||
"<ca>",
|
|
||||||
"-----BEGIN CERTIFICATE-----",
|
|
||||||
constants.WindscribeCertificate,
|
|
||||||
"-----END CERTIFICATE-----",
|
|
||||||
"</ca>",
|
|
||||||
}...)
|
|
||||||
lines = append(lines, []string{
|
|
||||||
"<tls-auth>",
|
|
||||||
"-----BEGIN OpenVPN Static key V1-----",
|
|
||||||
constants.WindscribeOpenvpnStaticKeyV1,
|
|
||||||
"-----END OpenVPN Static key V1-----",
|
|
||||||
"</tls-auth>",
|
|
||||||
"",
|
|
||||||
}...)
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *windscribe) PortForward(ctx context.Context, client *http.Client,
|
|
||||||
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
|
||||||
syncState func(port uint16) (pfFilepath string)) {
|
|
||||||
panic("port forwarding is not supported for windscribe")
|
|
||||||
}
|
|
||||||
45
internal/provider/windscribe/connection.go
Normal file
45
internal/provider/windscribe/connection.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package windscribe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (w *Windscribe) GetOpenVPNConnection(selection configuration.ServerSelection) (
|
||||||
|
connection models.OpenVPNConnection, err error) {
|
||||||
|
protocol := constants.UDP
|
||||||
|
var port uint16 = 443
|
||||||
|
if selection.TCP {
|
||||||
|
protocol = constants.TCP
|
||||||
|
port = 1194
|
||||||
|
}
|
||||||
|
|
||||||
|
if selection.CustomPort > 0 {
|
||||||
|
port = selection.CustomPort
|
||||||
|
}
|
||||||
|
|
||||||
|
servers, err := w.filterServers(selection)
|
||||||
|
if err != nil {
|
||||||
|
return connection, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var connections []models.OpenVPNConnection
|
||||||
|
for _, server := range servers {
|
||||||
|
for _, IP := range server.IPs {
|
||||||
|
connection := models.OpenVPNConnection{
|
||||||
|
IP: IP,
|
||||||
|
Port: port,
|
||||||
|
Protocol: protocol,
|
||||||
|
}
|
||||||
|
connections = append(connections, connection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if selection.TargetIP != nil {
|
||||||
|
return utils.GetTargetIPConnection(connections, selection.TargetIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.PickRandomConnection(connections, w.randSource), nil
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user