VPNSP value custom for OpenVPN custom config files (#621)

- Retro-compatibility: `OPENVPN_CUSTOM_CONFIG` set implies `VPNSP=custom`
- Change: `up` and `down` options are not filtered out
- Change: `OPENVPN_INTERFACE` overrides the network interface defined in the configuration file
- Change: `PORT` overrides any port found in the configuration file
- Feat: config file is read when building the OpenVPN configuration, so it's effectively reloaded on VPN restarts
- Feat: extract values from custom file at start to log out valid settings
- Maint: `internal/openvpn/extract` package instead of `internal/openvpn/custom` package
- Maint: All providers' `BuildConf` method return an error
- Maint: rename `CustomConfig` to `ConfFile` in Settings structures
This commit is contained in:
Quentin McGaw
2021-09-13 11:30:14 -04:00
committed by GitHub
parent 11af6c10f1
commit f807f756eb
43 changed files with 328 additions and 296 deletions

View File

@@ -34,7 +34,11 @@ func (c *CLI) OpenvpnConfig(logger logging.Logger, env params.Interface) error {
if err != nil {
return err
}
lines := providerConf.BuildConf(connection, allSettings.VPN.OpenVPN)
lines, err := providerConf.BuildConf(connection, allSettings.VPN.OpenVPN)
if err != nil {
return err
}
fmt.Println(strings.Join(lines, "\n"))
return nil
}

View File

@@ -0,0 +1,53 @@
package configuration
import (
"errors"
"fmt"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/golibs/params"
)
var (
errCustomNotSupported = errors.New("custom provider is not supported")
errCustomExtractFromFile = errors.New("cannot extract configuration from file")
)
func (settings *Provider) readCustom(r reader, vpnType string) (err error) {
settings.Name = constants.Custom
if vpnType != constants.OpenVPN {
return fmt.Errorf("%w: for VPN type %s", errCustomNotSupported, vpnType)
}
return settings.readCustomOpenVPN(r)
}
func (settings *Provider) readCustomOpenVPN(r reader) (err error) {
configFile, err := r.env.Get("OPENVPN_CUSTOM_CONFIG", params.CaseSensitiveValue(), params.Compulsory())
if err != nil {
return fmt.Errorf("environment variable OPENVPN_CUSTOM_CONFIG: %w", err)
}
settings.ServerSelection.OpenVPN.ConfFile = configFile
// For display and consistency purposes only,
// these values are not actually used since the file is re-read
// before each OpenVPN start.
_, connection, err := r.ovpnExt.Data(configFile)
if err != nil {
return fmt.Errorf("%w: %s", errCustomExtractFromFile, err)
}
settings.ServerSelection.OpenVPN.TCP = connection.Protocol == constants.TCP
return nil
}
func (settings *OpenVPN) readCustom(r reader) (err error) {
settings.ConfFile, err = r.env.Path("OPENVPN_CUSTOM_CONFIG",
params.Compulsory(), params.CaseSensitiveValue())
if err != nil {
return fmt.Errorf("environment variable OPENVPN_CUSTOM_CONFIG: %w", err)
}
return nil
}

View File

@@ -21,7 +21,7 @@ type OpenVPN struct {
Root bool `json:"run_as_root"`
Cipher string `json:"cipher"`
Auth string `json:"auth"`
Config string `json:"custom_config"`
ConfFile string `json:"conf_file"`
Version string `json:"version"`
ClientCrt string `json:"-"` // Cyberghost
ClientKey string `json:"-"` // Cyberghost, VPNUnlimited
@@ -59,8 +59,8 @@ func (settings *OpenVPN) lines() (lines []string) {
lines = append(lines, indent+lastIndent+"Custom auth algorithm: "+settings.Auth)
}
if len(settings.Config) > 0 {
lines = append(lines, indent+lastIndent+"Custom configuration: "+settings.Config)
if settings.ConfFile != "" {
lines = append(lines, indent+lastIndent+"Configuration file: "+settings.ConfFile)
}
if settings.ClientKey != "" {
@@ -83,13 +83,14 @@ func (settings *OpenVPN) lines() (lines []string) {
}
func (settings *OpenVPN) read(r reader, serviceProvider string) (err error) {
settings.Config, err = r.env.Get("OPENVPN_CUSTOM_CONFIG", params.CaseSensitiveValue())
if err != nil {
return fmt.Errorf("environment variable OPENVPN_CUSTOM_CONFIG: %w", err)
credentialsRequired := false
switch serviceProvider {
case constants.Custom:
case constants.VPNUnlimited:
default:
credentialsRequired = true
}
credentialsRequired := settings.Config == "" && serviceProvider != constants.VPNUnlimited
settings.User, err = r.getFromEnvOrSecretFile("OPENVPN_USER", credentialsRequired, []string{"USER"})
if err != nil {
return fmt.Errorf("environment variable OPENVPN_USER: %w", err)
@@ -159,6 +160,8 @@ func (settings *OpenVPN) read(r reader, serviceProvider string) (err error) {
}
switch serviceProvider {
case constants.Custom:
err = settings.readCustom(r) // read OPENVPN_CUSTOM_CONFIG
case constants.Cyberghost:
err = settings.readCyberghost(r)
case constants.PrivateInternetAccess:

View File

@@ -25,7 +25,7 @@ func Test_OpenVPN_JSON(t *testing.T) {
"run_as_root": true,
"cipher": "",
"auth": "",
"custom_config": "",
"conf_file": "",
"version": "",
"encryption_preset": "",
"ipv6": false,

View File

@@ -49,6 +49,8 @@ func (settings *Provider) read(r reader, vpnType string) error {
}
switch settings.Name {
case constants.Custom:
err = settings.readCustom(r, vpnType)
case constants.Cyberghost:
err = settings.readCyberghost(r)
case constants.Fastestvpn:
@@ -99,6 +101,7 @@ func (settings *Provider) readVPNServiceProvider(r reader, vpnType string) (err
switch vpnType {
case constants.OpenVPN:
allowedVPNServiceProviders = []string{
constants.Custom,
"cyberghost", "fastestvpn", "hidemyass", "ipvanish", "ivpn", "mullvad", "nordvpn",
"privado", "pia", "private internet access", "privatevpn", "protonvpn",
"purevpn", "surfshark", "torguard", constants.VPNUnlimited, "vyprvpn", "windscribe"}
@@ -115,6 +118,11 @@ func (settings *Provider) readVPNServiceProvider(r reader, vpnType string) (err
if vpnsp == "pia" { // retro compatibility
vpnsp = "private internet access"
}
if settings.isOpenVPNCustomConfig(r.env) { // retro compatibility
vpnsp = constants.Custom
}
settings.Name = vpnsp
return nil
@@ -199,3 +207,12 @@ func portsToString(ports []uint16) string {
}
return strings.Join(slice, ", ")
}
// isOpenVPNCustomConfig is for retro compatibility to set VPNSP=custom
// if OPENVPN_CUSTOM_CONFIG is set.
func (settings Provider) isOpenVPNCustomConfig(env params.Interface) (ok bool) {
s, _ := env.Get("VPN_TYPE")
isOpenVPN := s == constants.OpenVPN
s, _ = env.Get("OPENVPN_CUSTOM_CONFIG")
return isOpenVPN && s != ""
}

View File

@@ -8,6 +8,7 @@ import (
"strings"
"github.com/qdm12/gluetun/internal/models"
ovpnextract "github.com/qdm12/gluetun/internal/openvpn/extract"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/golibs/params"
"github.com/qdm12/golibs/verification"
@@ -18,6 +19,7 @@ type reader struct {
env params.Interface
logger logging.Logger
regex verification.Regex
ovpnExt ovpnextract.Interface
}
func newReader(env params.Interface,
@@ -27,6 +29,7 @@ func newReader(env params.Interface,
env: env,
logger: logger,
regex: verification.NewRegex(),
ovpnExt: ovpnextract.New(),
}
}

View File

@@ -51,22 +51,22 @@ func (r *reader) getFromEnvOrSecretFile(envKey string, compulsory bool, retroKey
file, fileErr := os.OpenFile(filepath, os.O_RDONLY, 0)
if os.IsNotExist(fileErr) {
if compulsory {
return "", envErr
return "", fmt.Errorf("environment variable %s: %w", envKey, envErr)
}
return "", nil
} else if fileErr != nil {
return "", fmt.Errorf("%w: %s", ErrReadSecretFile, fileErr)
return "", fmt.Errorf("%w: %s: %s", ErrReadSecretFile, filepath, fileErr)
}
b, err := io.ReadAll(file)
if err != nil {
return "", fmt.Errorf("%w: %s", ErrReadSecretFile, err)
return "", fmt.Errorf("%w: %s: %s", ErrReadSecretFile, filepath, err)
}
value = string(b)
value = cleanSuffix(value)
if compulsory && value == "" {
return "", ErrSecretFileIsEmpty
return "", fmt.Errorf("%s: %w", filepath, ErrSecretFileIsEmpty)
}
return value, nil

View File

@@ -106,6 +106,7 @@ func (selection ServerSelection) toLines() (lines []string) {
}
type OpenVPNSelection struct {
ConfFile string `json:"conf_file"` // Custom configuration file path
TCP bool `json:"tcp"` // UDP if TCP is false
CustomPort uint16 `json:"custom_port"` // HideMyAss, Mullvad, PIA, ProtonVPN, Windscribe
EncPreset string `json:"encryption_preset"` // PIA - needed to get the port number
@@ -114,6 +115,10 @@ type OpenVPNSelection struct {
func (settings *OpenVPNSelection) lines() (lines []string) {
lines = append(lines, lastIndent+"OpenVPN selection:")
if settings.ConfFile != "" {
lines = append(lines, indent+lastIndent+"Custom configuration file: "+settings.ConfFile)
}
lines = append(lines, indent+lastIndent+"Protocol: "+protoToString(settings.TCP))
if settings.CustomPort != 0 {

View File

@@ -58,11 +58,9 @@ func (settings *VPN) read(r reader) (err error) {
}
settings.Type = vpnType
if !settings.isOpenVPNCustomConfig(r.env) {
if err := settings.Provider.read(r, vpnType); err != nil {
return fmt.Errorf("%w: %s", errReadProviderSettings, err)
}
}
switch settings.Type {
case constants.OpenVPN:
@@ -79,19 +77,3 @@ func (settings *VPN) read(r reader) (err error) {
return nil
}
func (settings VPN) isOpenVPNCustomConfig(env params.Interface) (ok bool) {
if settings.Type != constants.OpenVPN {
return false
}
s, err := env.Get("OPENVPN_CUSTOM_CONFIG")
return err == nil && s != ""
}
func (settings VPN) VPNInterface() (intf string) {
if settings.Type == constants.Wireguard {
return settings.Wireguard.Interface
}
// OpenVPN
return settings.OpenVPN.Interface
}

View File

@@ -6,6 +6,9 @@ const (
)
const (
// Custom is the VPN provider name for custom
// VPN configurations.
Custom = "custom"
// Cyberghost is a VPN provider.
Cyberghost = "cyberghost"
// Fastestvpn is a VPN provider.

View File

@@ -1,35 +0,0 @@
package custom
import (
"errors"
"fmt"
"github.com/qdm12/gluetun/internal/configuration"
"github.com/qdm12/gluetun/internal/models"
)
var (
ErrReadCustomConfig = errors.New("cannot read custom configuration file")
ErrExtractConnection = errors.New("cannot extract connection from custom configuration file")
)
func BuildConfig(settings configuration.OpenVPN) (
lines []string, connection models.Connection, intf string, err error) {
lines, err = readCustomConfigLines(settings.Config)
if err != nil {
return nil, connection, "", fmt.Errorf("%w: %s", ErrReadCustomConfig, err)
}
connection, intf, err = extractDataFromLines(lines)
if err != nil {
return nil, connection, "", fmt.Errorf("%w: %s", ErrExtractConnection, err)
}
if intf == "" {
intf = settings.Interface
}
lines = modifyCustomConfig(lines, settings, connection, intf)
return lines, connection, intf, nil
}

View File

@@ -1,67 +0,0 @@
package custom
import (
"net"
"os"
"testing"
"github.com/qdm12/gluetun/internal/configuration"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_BuildConfig(t *testing.T) {
t.Parallel()
file, err := os.CreateTemp("", "")
require.NoError(t, err)
defer removeFile(t, file.Name())
defer file.Close()
_, err = file.WriteString("remote 1.9.8.7\nkeep me\ncipher remove")
require.NoError(t, err)
err = file.Close()
require.NoError(t, err)
settings := configuration.OpenVPN{
Cipher: "cipher",
MSSFix: 999,
Config: file.Name(),
Interface: "tun0",
}
lines, connection, intf, err := BuildConfig(settings)
assert.NoError(t, err)
expectedLines := []string{
"keep me",
"proto udp",
"remote 1.9.8.7 1194",
"dev tun0",
"mute-replay-warnings",
"auth-nocache",
"pull-filter ignore \"auth-token\"",
"auth-retry nointeract",
"suppress-timestamps",
"verb 0",
"data-ciphers-fallback cipher",
"data-ciphers cipher",
"mssfix 999",
"pull-filter ignore \"route-ipv6\"",
"pull-filter ignore \"ifconfig-ipv6\"",
"user ",
}
assert.Equal(t, expectedLines, lines)
expectedConnection := models.Connection{
IP: net.IPv4(1, 9, 8, 7),
Port: 1194,
Protocol: constants.UDP,
}
assert.Equal(t, expectedConnection, connection)
assert.Equal(t, "tun0", intf)
}

View File

@@ -0,0 +1,29 @@
package extract
import (
"errors"
"fmt"
"github.com/qdm12/gluetun/internal/models"
)
var (
ErrRead = errors.New("cannot read file")
ErrExtractConnection = errors.New("cannot extract connection from file")
)
// Data extracts the lines and connection from the OpenVPN configuration file.
func (e *Extractor) Data(filepath string) (lines []string,
connection models.Connection, err error) {
lines, err = readCustomConfigLines(filepath)
if err != nil {
return nil, connection, fmt.Errorf("%w: %s", ErrRead, err)
}
connection, err = extractDataFromLines(lines)
if err != nil {
return nil, connection, fmt.Errorf("%w: %s", ErrExtractConnection, err)
}
return lines, connection, nil
}

View File

@@ -1,4 +1,4 @@
package custom
package extract
import (
"errors"
@@ -16,23 +16,22 @@ var (
)
func extractDataFromLines(lines []string) (
connection models.Connection, intf string, err error) {
connection models.Connection, err error) {
for i, line := range lines {
ip, port, protocol, intfFound, err := extractDataFromLine(line)
ip, port, protocol, err := extractDataFromLine(line)
if err != nil {
return connection, "", fmt.Errorf("on line %d: %w", i+1, err)
return connection, fmt.Errorf("on line %d: %w", i+1, err)
}
intf = intfFound
connection.UpdateEmptyWith(ip, port, protocol)
if connection.Protocol != "" && connection.IP != nil && intf != "" {
if connection.Protocol != "" && connection.IP != nil {
break
}
}
if connection.IP == nil {
return connection, "", errRemoteLineNotFound
return connection, errRemoteLineNotFound
}
if connection.Protocol == "" {
@@ -46,41 +45,33 @@ func extractDataFromLines(lines []string) (
}
}
return connection, intf, nil
return connection, nil
}
var (
errExtractProto = errors.New("failed extracting protocol from proto line")
errExtractRemote = errors.New("failed extracting from remote line")
errExtractDev = errors.New("failed extracting network interface from dev line")
)
func extractDataFromLine(line string) (
ip net.IP, port uint16, protocol, intf string, err error) {
ip net.IP, port uint16, protocol string, err error) {
switch {
case strings.HasPrefix(line, "proto "):
protocol, err = extractProto(line)
if err != nil {
return nil, 0, "", "", fmt.Errorf("%w: %s", errExtractProto, err)
return nil, 0, "", fmt.Errorf("%w: %s", errExtractProto, err)
}
return nil, 0, protocol, "", nil
return nil, 0, protocol, nil
case strings.HasPrefix(line, "remote "):
ip, port, protocol, err = extractRemote(line)
if err != nil {
return nil, 0, "", "", fmt.Errorf("%w: %s", errExtractRemote, err)
return nil, 0, "", fmt.Errorf("%w: %s", errExtractRemote, err)
}
return ip, port, protocol, "", nil
case strings.HasPrefix(line, "dev "):
intf, err = extractInterfaceFromLine(line)
if err != nil {
return nil, 0, "", "", fmt.Errorf("%w: %s", errExtractDev, err)
}
return nil, 0, "", intf, nil
return ip, port, protocol, nil
}
return nil, 0, "", "", nil
return nil, 0, "", nil
}
var (
@@ -147,16 +138,3 @@ func extractRemote(line string) (ip net.IP, port uint16,
return ip, port, protocol, nil
}
var (
errDevLineFieldsCount = errors.New("dev line has not 2 fields as expected")
)
func extractInterfaceFromLine(line string) (intf string, err error) {
fields := strings.Fields(line)
if len(fields) != 2 { //nolint:gomnd
return "", fmt.Errorf("%w: %s", errDevLineFieldsCount, line)
}
return fields[1], nil
}

View File

@@ -1,4 +1,4 @@
package custom
package extract
import (
"errors"
@@ -17,7 +17,6 @@ func Test_extractDataFromLines(t *testing.T) {
testCases := map[string]struct {
lines []string
connection models.Connection
intf string
err error
}{
"success": {
@@ -27,7 +26,6 @@ func Test_extractDataFromLines(t *testing.T) {
Port: 1194,
Protocol: constants.TCP,
},
intf: "tun6",
},
"extraction error": {
lines: []string{"bla bla", "proto bad", "remote 1.2.3.4 1194 tcp"},
@@ -71,7 +69,7 @@ func Test_extractDataFromLines(t *testing.T) {
t.Run(name, func(t *testing.T) {
t.Parallel()
connection, intf, err := extractDataFromLines(testCase.lines)
connection, err := extractDataFromLines(testCase.lines)
if testCase.err != nil {
require.Error(t, err)
@@ -81,7 +79,6 @@ func Test_extractDataFromLines(t *testing.T) {
}
assert.Equal(t, testCase.connection, connection)
assert.Equal(t, testCase.intf, intf)
})
}
}
@@ -94,7 +91,6 @@ func Test_extractDataFromLine(t *testing.T) {
ip net.IP
port uint16
protocol string
intf string
isErr error
}{
"irrelevant line": {
@@ -108,14 +104,6 @@ func Test_extractDataFromLine(t *testing.T) {
line: "proto tcp",
protocol: constants.TCP,
},
"extract intf error": {
line: "dev ",
isErr: errExtractDev,
},
"extract intf success": {
line: "dev tun3",
intf: "tun3",
},
"extract remote error": {
line: "remote bad",
isErr: errExtractRemote,
@@ -133,7 +121,7 @@ func Test_extractDataFromLine(t *testing.T) {
t.Run(name, func(t *testing.T) {
t.Parallel()
ip, port, protocol, intf, err := extractDataFromLine(testCase.line)
ip, port, protocol, err := extractDataFromLine(testCase.line)
if testCase.isErr != nil {
assert.ErrorIs(t, err, testCase.isErr)
@@ -144,7 +132,6 @@ func Test_extractDataFromLine(t *testing.T) {
assert.Equal(t, testCase.ip, ip)
assert.Equal(t, testCase.port, port)
assert.Equal(t, testCase.protocol, protocol)
assert.Equal(t, testCase.intf, intf)
})
}
}
@@ -273,44 +260,3 @@ func Test_extractRemote(t *testing.T) {
})
}
}
func Test_extractInterface(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
line string
intf string
err error
}{
"found": {
line: "dev tun3",
intf: "tun3",
},
"not enough fields": {
line: "dev ",
err: errors.New("dev line has not 2 fields as expected: dev "),
},
"too many fields": {
line: "dev one two",
err: errors.New("dev line has not 2 fields as expected: dev one two"),
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
intf, err := extractInterfaceFromLine(testCase.line)
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.intf, intf)
})
}
}

View File

@@ -0,0 +1,18 @@
package extract
import (
"github.com/qdm12/gluetun/internal/models"
)
var _ Interface = (*Extractor)(nil)
type Interface interface {
Data(filepath string) (lines []string,
connection models.Connection, err error)
}
type Extractor struct{}
func New() *Extractor {
return new(Extractor)
}

View File

@@ -1,4 +1,4 @@
package custom
package extract
import (
"os"

View File

@@ -1,4 +1,4 @@
package custom
package extract
import (
"io"

View File

@@ -1,4 +1,4 @@
package custom
package extract
import (
"os"

View File

@@ -0,0 +1,38 @@
package custom
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 (
ErrVPNTypeNotSupported = errors.New("VPN type not supported for custom provider")
ErrExtractConnection = errors.New("cannot extract connection")
)
// GetConnection gets the connection from the OpenVPN configuration file.
func (p *Provider) GetConnection(selection configuration.ServerSelection) (
connection models.Connection, err error) {
if selection.VPN != constants.OpenVPN {
return connection, fmt.Errorf("%w: %s", ErrVPNTypeNotSupported, selection.VPN)
}
_, connection, err = p.extractor.Data(selection.OpenVPN.ConfFile)
if err != nil {
return connection, fmt.Errorf("%w: %s", ErrExtractConnection, err)
}
connection.Port = getPort(connection.Port, selection)
return connection, nil
}
// Port found is overridden by custom port set with `PORT` or `WIREGUARD_PORT`.
func getPort(foundPort uint16, selection configuration.ServerSelection) (port uint16) {
return utils.GetPort(selection, foundPort, foundPort, foundPort)
}

View File

@@ -1,6 +1,8 @@
package custom
import (
"errors"
"fmt"
"strconv"
"strings"
@@ -10,13 +12,27 @@ import (
"github.com/qdm12/gluetun/internal/provider/utils"
)
func modifyCustomConfig(lines []string, settings configuration.OpenVPN,
connection models.Connection, intf string) (modified []string) {
var ErrExtractData = errors.New("failed extracting information from custom configuration file")
func (p *Provider) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) {
lines, _, err = p.extractor.Data(settings.ConfFile)
if err != nil {
return nil, fmt.Errorf("%w: %s", ErrExtractData, err)
}
lines = modifyConfig(lines, connection, settings)
return lines, nil
}
func modifyConfig(lines []string, connection models.Connection,
settings configuration.OpenVPN) (modified []string) {
// Remove some lines
for _, line := range lines {
switch {
case strings.HasPrefix(line, "up "),
strings.HasPrefix(line, "down "),
case
line == "",
strings.HasPrefix(line, "verb "),
strings.HasPrefix(line, "auth-user-pass "),
strings.HasPrefix(line, "user "),
@@ -36,7 +52,7 @@ func modifyCustomConfig(lines []string, settings configuration.OpenVPN,
// Add values
modified = append(modified, connection.OpenVPNProtoLine())
modified = append(modified, connection.OpenVPNRemoteLine())
modified = append(modified, "dev "+intf)
modified = append(modified, "dev "+settings.Interface)
modified = append(modified, "mute-replay-warnings")
modified = append(modified, "auth-nocache")
modified = append(modified, "pull-filter ignore \"auth-token\"") // prevent auth failed loop
@@ -63,5 +79,23 @@ func modifyCustomConfig(lines []string, settings configuration.OpenVPN,
modified = append(modified, "user "+settings.ProcUser)
}
return modified
modified = append(modified, "") // trailing line
return uniqueLines(modified)
}
func uniqueLines(lines []string) (unique []string) {
seen := make(map[string]struct{}, len(lines))
unique = make([]string, 0, len(lines))
for _, line := range lines {
_, ok := seen[line]
if ok {
continue
}
seen[line] = struct{}{}
unique = append(unique, line)
}
return unique
}

View File

@@ -10,14 +10,13 @@ import (
"github.com/stretchr/testify/assert"
)
func Test_modifyCustomConfig(t *testing.T) {
func Test_modifyConfig(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
lines []string
settings configuration.OpenVPN
connection models.Connection
intf string
modified []string
}{
"mixed": {
@@ -26,6 +25,7 @@ func Test_modifyCustomConfig(t *testing.T) {
"proto tcp",
"remote 5.5.5.5",
"cipher bla",
"",
"tun-ipv6",
"keep me here",
"auth bla",
@@ -36,14 +36,15 @@ func Test_modifyCustomConfig(t *testing.T) {
Auth: "auth",
MSSFix: 1000,
ProcUser: "procuser",
Interface: "tun3",
},
connection: models.Connection{
IP: net.IPv4(1, 2, 3, 4),
Port: 1194,
Protocol: constants.UDP,
},
intf: "tun3",
modified: []string{
"up bla",
"keep me here",
"proto udp",
"remote 1.2.3.4 1194",
@@ -62,6 +63,7 @@ func Test_modifyCustomConfig(t *testing.T) {
"pull-filter ignore \"route-ipv6\"",
"pull-filter ignore \"ifconfig-ipv6\"",
"user procuser",
"",
},
},
}
@@ -71,8 +73,8 @@ func Test_modifyCustomConfig(t *testing.T) {
t.Run(name, func(t *testing.T) {
t.Parallel()
modified := modifyCustomConfig(testCase.lines,
testCase.settings, testCase.connection, testCase.intf)
modified := modifyConfig(testCase.lines,
testCase.connection, testCase.settings)
assert.Equal(t, testCase.modified, modified)
})

View File

@@ -0,0 +1,19 @@
package custom
import (
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/openvpn/extract"
"github.com/qdm12/gluetun/internal/provider/utils"
)
type Provider struct {
extractor extract.Interface
utils.NoPortForwarder
}
func New() *Provider {
return &Provider{
extractor: extract.New(),
NoPortForwarder: utils.NewNoPortForwarding(constants.Custom),
}
}

View File

@@ -11,7 +11,7 @@ import (
)
func (c *Cyberghost) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string) {
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES256cbc
}
@@ -87,5 +87,5 @@ func (c *Cyberghost) BuildConf(connection models.Connection,
lines = append(lines, "")
return lines
return lines, nil
}

View File

@@ -10,7 +10,7 @@ import (
)
func (f *Fastestvpn) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string) {
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES256cbc
}
@@ -76,5 +76,5 @@ func (f *Fastestvpn) BuildConf(connection models.Connection,
lines = append(lines, "")
return lines
return lines, nil
}

View File

@@ -10,7 +10,7 @@ import (
)
func (h *HideMyAss) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string) {
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES256cbc
}
@@ -75,5 +75,5 @@ func (h *HideMyAss) BuildConf(connection models.Connection,
lines = append(lines, "")
return lines
return lines, nil
}

View File

@@ -10,7 +10,7 @@ import (
)
func (i *Ipvanish) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string) {
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES256cbc
}
@@ -68,5 +68,5 @@ func (i *Ipvanish) BuildConf(connection models.Connection,
lines = append(lines, "")
return lines
return lines, nil
}

View File

@@ -11,7 +11,7 @@ import (
)
func (i *Ivpn) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string) {
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES256cbc
}
@@ -77,5 +77,5 @@ func (i *Ivpn) BuildConf(connection models.Connection,
lines = append(lines, "")
return lines
return lines, nil
}

View File

@@ -10,7 +10,7 @@ import (
)
func (m *Mullvad) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string) {
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES256cbc
}
@@ -83,5 +83,5 @@ func (m *Mullvad) BuildConf(connection models.Connection,
lines = append(lines, "")
return lines
return lines, nil
}

View File

@@ -10,7 +10,7 @@ import (
)
func (n *Nordvpn) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string) {
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES256cbc
}
@@ -81,5 +81,5 @@ func (n *Nordvpn) BuildConf(connection models.Connection,
lines = append(lines, "")
return lines
return lines, nil
}

View File

@@ -10,7 +10,7 @@ import (
)
func (p *Privado) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string) {
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES256cbc
}
@@ -70,5 +70,5 @@ func (p *Privado) BuildConf(connection models.Connection,
lines = append(lines, "")
return lines
return lines, nil
}

View File

@@ -10,7 +10,7 @@ import (
)
func (p *PIA) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string) {
settings configuration.OpenVPN) (lines []string, err error) {
var defaultCipher, defaultAuth, X509CRL, certificate string
switch settings.EncPreset {
case constants.PIAEncryptionPresetNormal:
@@ -93,5 +93,5 @@ func (p *PIA) BuildConf(connection models.Connection,
lines = append(lines, "")
return lines
return lines, nil
}

View File

@@ -10,7 +10,7 @@ import (
)
func (p *Privatevpn) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string) {
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES128gcm
}
@@ -73,5 +73,5 @@ func (p *Privatevpn) BuildConf(connection models.Connection,
lines = append(lines, "")
return lines
return lines, nil
}

View File

@@ -10,7 +10,7 @@ import (
)
func (p *Protonvpn) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string) {
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES256cbc
}
@@ -80,5 +80,5 @@ func (p *Protonvpn) BuildConf(connection models.Connection,
lines = append(lines, "")
return lines
return lines, nil
}

View File

@@ -11,6 +11,7 @@ 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/custom"
"github.com/qdm12/gluetun/internal/provider/cyberghost"
"github.com/qdm12/gluetun/internal/provider/fastestvpn"
"github.com/qdm12/gluetun/internal/provider/hidemyass"
@@ -34,7 +35,7 @@ import (
// Provider contains methods to read and modify the openvpn configuration to connect as a client.
type Provider interface {
GetConnection(selection configuration.ServerSelection) (connection models.Connection, err error)
BuildConf(connection models.Connection, settings configuration.OpenVPN) (lines []string)
BuildConf(connection models.Connection, settings configuration.OpenVPN) (lines []string, err error)
PortForwarder
}
@@ -50,6 +51,8 @@ type PortForwarder interface {
func New(provider string, allServers models.AllServers, timeNow func() time.Time) Provider {
randSource := rand.NewSource(timeNow().UnixNano())
switch provider {
case constants.Custom:
return custom.New()
case constants.Cyberghost:
return cyberghost.New(allServers.Cyberghost.Servers, randSource)
case constants.Fastestvpn:

View File

@@ -10,7 +10,7 @@ import (
)
func (p *Purevpn) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string) {
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES256gcm
}
@@ -84,5 +84,5 @@ func (p *Purevpn) BuildConf(connection models.Connection,
lines = append(lines, "")
return lines
return lines, nil
}

View File

@@ -10,7 +10,7 @@ import (
)
func (s *Surfshark) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string) {
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES256gcm
}
@@ -78,5 +78,5 @@ func (s *Surfshark) BuildConf(connection models.Connection,
lines = append(lines, "")
return lines
return lines, nil
}

View File

@@ -10,7 +10,7 @@ import (
)
func (t *Torguard) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string) {
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES256gcm
}
@@ -84,5 +84,5 @@ func (t *Torguard) BuildConf(connection models.Connection,
lines = append(lines, "")
return lines
return lines, nil
}

View File

@@ -10,7 +10,7 @@ import (
)
func (p *Provider) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string) {
settings configuration.OpenVPN) (lines []string, err error) {
lines = []string{
"client",
"dev " + settings.Interface,
@@ -71,5 +71,5 @@ func (p *Provider) BuildConf(connection models.Connection,
lines = append(lines, "")
return lines
return lines, nil
}

View File

@@ -10,7 +10,7 @@ import (
)
func (v *Vyprvpn) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string) {
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES256cbc
}
@@ -65,5 +65,5 @@ func (v *Vyprvpn) BuildConf(connection models.Connection,
lines = append(lines, "")
return lines
return lines, nil
}

View File

@@ -11,7 +11,7 @@ import (
)
func (w *Windscribe) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string) {
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES256cbc
}
@@ -81,5 +81,5 @@ func (w *Windscribe) BuildConf(connection models.Connection,
lines = append(lines, "")
return lines
return lines, nil
}

View File

@@ -7,15 +7,14 @@ import (
"github.com/qdm12/gluetun/internal/configuration"
"github.com/qdm12/gluetun/internal/firewall"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/openvpn"
"github.com/qdm12/gluetun/internal/openvpn/custom"
"github.com/qdm12/gluetun/internal/provider"
"github.com/qdm12/golibs/command"
"github.com/qdm12/golibs/logging"
)
var (
errServerConn = errors.New("failed finding a valid server connection")
errBuildConfig = errors.New("failed building configuration")
errWriteConfig = errors.New("failed writing configuration to file")
errWriteAuth = errors.New("failed writing auth to file")
@@ -28,18 +27,12 @@ func setupOpenVPN(ctx context.Context, fw firewall.VPNConnectionSetter,
openvpnConf openvpn.Interface, providerConf provider.Provider,
settings configuration.VPN, starter command.Starter, logger logging.Logger) (
runner vpnRunner, serverName string, err error) {
var connection models.Connection
var netInterface string
var lines []string
if settings.OpenVPN.Config == "" {
netInterface = settings.OpenVPN.Interface
connection, err = providerConf.GetConnection(settings.Provider.ServerSelection)
if err == nil {
lines = providerConf.BuildConf(connection, settings.OpenVPN)
}
} else {
lines, connection, netInterface, err = custom.BuildConfig(settings.OpenVPN)
connection, err := providerConf.GetConnection(settings.Provider.ServerSelection)
if err != nil {
return nil, "", fmt.Errorf("%w: %s", errServerConn, err)
}
lines, err := providerConf.BuildConf(connection, settings.OpenVPN)
if err != nil {
return nil, "", fmt.Errorf("%w: %s", errBuildConfig, err)
}
@@ -55,7 +48,7 @@ func setupOpenVPN(ctx context.Context, fw firewall.VPNConnectionSetter,
}
}
if err := fw.SetVPNConnection(ctx, connection, netInterface); err != nil {
if err := fw.SetVPNConnection(ctx, connection, settings.OpenVPN.Interface); err != nil {
return nil, "", fmt.Errorf("%w: %s", errFirewall, err)
}

View File

@@ -28,6 +28,7 @@
- Gluetun entire logs available at control server, maybe in structured format
- Authentication with the control server
- Get announcement from Github file
- Support multiple connections in custom ovpn
## Gluetun V4
@@ -47,4 +48,7 @@
- Change `VPNSP` to `VPN_SERVICE_PROVIDER`
- Change `REGION` (etc.) to `SERVER_REGIONS`
- Remove `PUBLICIP_FILE`
- Remove retro-compatibility where OPENVPN_CONFIG != "" implies VPNSP = "custom"
and set `OPENVPN_CUSTOM_CONFIG` default to `/gluetun/custom.ovpn`
- Split servers.json and compress it
- Use relative paths everywhere instead of absolute