Skip to content

Commit

Permalink
initial code
Browse files Browse the repository at this point in the history
  • Loading branch information
qdm12 committed Oct 23, 2024
1 parent 8567522 commit f21ff7b
Show file tree
Hide file tree
Showing 27 changed files with 6,017 additions and 14 deletions.
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/bug.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ body:
- IVPN
- Mullvad
- NordVPN
- OVPN
- Privado
- Private Internet Access
- PrivateVPN
Expand Down
2 changes: 2 additions & 0 deletions .github/labels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@
color: "cfe8d4"
- name: "☁️ NordVPN"
color: "cfe8d4"
- name: "☁️ OVPN"
color: "cfe8d4"
- name: "☁️ Perfect Privacy"
color: "cfe8d4"
- name: "☁️ PIA"
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ ENV VPN_SERVICE_PROVIDER=pia \
# # ProtonVPN only:
SECURE_CORE_ONLY= \
TOR_ONLY= \
# # Surfshark only:
# # Surfshark and ovpn only:
MULTIHOP_ONLY= \
# # VPN Secure only:
PREMIUM_ONLY= \
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ Lightweight swiss-knife-like VPN client to multiple VPN service providers
## Features

- Based on Alpine 3.20 for a small Docker image of 35.6MB
- Supports: **AirVPN**, **Cyberghost**, **ExpressVPN**, **FastestVPN**, **Giganews**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **SlickVPN**, **Surfshark**, **TorGuard**, **VPNSecure.me**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers
- Supports: **AirVPN**, **Cyberghost**, **ExpressVPN**, **FastestVPN**, **Giganews**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Ovpn**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **SlickVPN**, **Surfshark**, **TorGuard**, **VPNSecure.me**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers
- Supports OpenVPN for all providers listed
- Supports Wireguard both kernelspace and userspace
- For **AirVPN**, **FastestVPN**, **Ivpn**, **Mullvad**, **NordVPN**, **Perfect privacy**, **ProtonVPN**, **Surfshark** and **Windscribe**
- For **AirVPN**, **FastestVPN**, **Ivpn**, **Mullvad**, **NordVPN**, **Ovpn**, **Perfect privacy**, **ProtonVPN**, **Surfshark** and **Windscribe**
- For **Cyberghost**, **Private Internet Access**, **PrivateVPN**, **PureVPN**, **Torguard**, **VPN Unlimited**, **VyprVPN** and **WeVPN** using [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md)
- For custom Wireguard configurations using [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md)
- More in progress, see [#134](https://github.com/qdm12/gluetun/issues/134)
Expand Down
2 changes: 1 addition & 1 deletion internal/configuration/settings/openvpnselection.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
switch vpnProvider {
// no restriction on port
case providers.Custom, providers.Cyberghost, providers.HideMyAss,
providers.Privatevpn, providers.Torguard:
providers.Ovpn, providers.Privatevpn, providers.Torguard:
// no custom port allowed
case providers.Expressvpn, providers.Fastestvpn,
providers.Giganews, providers.Ipvanish, providers.Nordvpn,
Expand Down
1 change: 1 addition & 0 deletions internal/configuration/settings/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func (p *Provider) validate(vpnType string, filterChoicesGetter FilterChoicesGet
providers.Ivpn,
providers.Mullvad,
providers.Nordvpn,
providers.Ovpn,
providers.Protonvpn,
providers.Surfshark,
providers.Windscribe,
Expand Down
1 change: 1 addition & 0 deletions internal/configuration/settings/wireguard.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error)
providers.Ivpn,
providers.Mullvad,
providers.Nordvpn,
providers.Ovpn,
providers.Protonvpn,
providers.Surfshark,
providers.Windscribe,
Expand Down
18 changes: 12 additions & 6 deletions internal/configuration/settings/wireguardselection.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"net/netip"

"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
Expand All @@ -21,7 +22,7 @@ type WireguardSelection struct {
// in the internal state.
EndpointIP netip.Addr `json:"endpoint_ip"`
// EndpointPort is a the server port to use for the VPN server.
// It is optional for VPN providers IVPN, Mullvad, Surfshark
// It is optional for VPN providers IVPN, Mullvad, Ovpn, Surfshark
// and Windscribe, and compulsory for the others.
// When optional, it can be set to 0 to indicate not use
// a custom endpoint port. It cannot be nil in the internal
Expand All @@ -39,8 +40,9 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
// Validate EndpointIP
switch vpnProvider {
case providers.Airvpn, providers.Fastestvpn, providers.Ivpn,
providers.Mullvad, providers.Nordvpn, providers.Protonvpn,
providers.Surfshark, providers.Windscribe:
providers.Mullvad, providers.Nordvpn, providers.Ovpn,
providers.Protonvpn, providers.Surfshark,
providers.Windscribe:
// endpoint IP addresses are baked in
case providers.Custom:
if !w.EndpointIP.IsValid() || w.EndpointIP.IsUnspecified() {
Expand All @@ -62,12 +64,16 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
if *w.EndpointPort != 0 {
return fmt.Errorf("%w", ErrWireguardEndpointPortSet)
}
case providers.Airvpn, providers.Ivpn, providers.Mullvad, providers.Windscribe:
case providers.Airvpn, providers.Ivpn, providers.Mullvad,
providers.Ovpn, providers.Windscribe:
// EndpointPort is optional and can be 0
if *w.EndpointPort == 0 {
break // no custom endpoint port set
}
if vpnProvider == providers.Mullvad {
if helpers.IsOneOf(vpnProvider,
providers.Mullvad,
providers.Ovpn,
) {
break // no restriction on custom endpoint port value
}
var allowed []uint16
Expand All @@ -92,7 +98,7 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
// Validate PublicKey
switch vpnProvider {
case providers.Fastestvpn, providers.Ivpn, providers.Mullvad,
providers.Surfshark, providers.Windscribe:
providers.Ovpn, providers.Surfshark, providers.Windscribe:
// public keys are baked in
case providers.Custom:
if w.PublicKey == "" {
Expand Down
2 changes: 2 additions & 0 deletions internal/constants/providers/providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const (
Ivpn = "ivpn"
Mullvad = "mullvad"
Nordvpn = "nordvpn"
Ovpn = "ovpn"
Perfectprivacy = "perfect privacy"
Privado = "privado"
PrivateInternetAccess = "private internet access"
Expand Down Expand Up @@ -44,6 +45,7 @@ func All() []string {
Ivpn,
Mullvad,
Nordvpn,
Ovpn,
Perfectprivacy,
Privado,
PrivateInternetAccess,
Expand Down
2 changes: 2 additions & 0 deletions internal/models/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ type Server struct {
PortForward bool `json:"port_forward,omitempty"`
Keep bool `json:"keep,omitempty"`
IPs []netip.Addr `json:"ips,omitempty"`
PortsTCP []uint16 `json:"ports_tcp,omitempty"`
PortsUDP []uint16 `json:"ports_udp,omitempty"`
}

var (
Expand Down
15 changes: 15 additions & 0 deletions internal/provider/ovpn/connection.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package ovpn

import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
)

func (p *Provider) GetConnection(selection settings.ServerSelection, ipv6Supported bool) (
connection models.Connection, err error,
) {
defaults := utils.NewConnectionDefaults(443, 1194, 9929) //nolint:mnd
return utils.GetConnection(p.Name(),
p.storage, selection, defaults, ipv6Supported, p.randSource)
}
128 changes: 128 additions & 0 deletions internal/provider/ovpn/connection_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package ovpn

import (
"errors"
"math/rand"
"net/http"
"net/netip"
"testing"

"github.com/golang/mock/gomock"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/common"
"github.com/stretchr/testify/assert"
)

func Test_Provider_GetConnection(t *testing.T) {
t.Parallel()

const provider = providers.Ovpn

errTest := errors.New("test error")

testCases := map[string]struct {
filteredServers []models.Server
storageErr error
selection settings.ServerSelection
ipv6Supported bool
connection models.Connection
errWrapped error
errMessage string
}{
"error": {
storageErr: errTest,
errWrapped: errTest,
errMessage: "filtering servers: test error",
},
"default_openvpn_tcp_port": {
filteredServers: []models.Server{
{IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}},
},
selection: settings.ServerSelection{
OpenVPN: settings.OpenVPNSelection{
Protocol: constants.TCP,
},
}.WithDefaults(provider),
connection: models.Connection{
Type: vpn.OpenVPN,
IP: netip.AddrFrom4([4]byte{1, 1, 1, 1}),
Port: 443,
Protocol: constants.TCP,
},
},
"default_openvpn_udp_port": {
filteredServers: []models.Server{
{IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}},
},
selection: settings.ServerSelection{
OpenVPN: settings.OpenVPNSelection{
Protocol: constants.UDP,
},
}.WithDefaults(provider),
connection: models.Connection{
Type: vpn.OpenVPN,
IP: netip.AddrFrom4([4]byte{1, 1, 1, 1}),
Port: 1194,
Protocol: constants.UDP,
},
},
"default_wireguard_port": {
filteredServers: []models.Server{
{IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}, WgPubKey: "x"},
},
selection: settings.ServerSelection{
VPN: vpn.Wireguard,
}.WithDefaults(provider),
connection: models.Connection{
Type: vpn.Wireguard,
IP: netip.AddrFrom4([4]byte{1, 1, 1, 1}),
Port: 9929,
Protocol: constants.UDP,
PubKey: "x",
},
},
"default_multihop_port": {
filteredServers: []models.Server{
{IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}, WgPubKey: "x", PortsUDP: []uint16{30044}},
},
selection: settings.ServerSelection{
VPN: vpn.Wireguard,
}.WithDefaults(provider),
connection: models.Connection{
Type: vpn.Wireguard,
IP: netip.AddrFrom4([4]byte{1, 1, 1, 1}),
Port: 30044,
Protocol: constants.UDP,
PubKey: "x",
},
},
}

for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)

storage := common.NewMockStorage(ctrl)
storage.EXPECT().FilterServers(provider, testCase.selection).
Return(testCase.filteredServers, testCase.storageErr)
randSource := rand.NewSource(0)

client := (*http.Client)(nil)
provider := New(storage, randSource, client)

connection, err := provider.GetConnection(testCase.selection, testCase.ipv6Supported)

assert.ErrorIs(t, err, testCase.errWrapped)
if testCase.errWrapped != nil {
assert.EqualError(t, err, testCase.errMessage)
}

assert.Equal(t, testCase.connection, connection)
})
}
}
23 changes: 23 additions & 0 deletions internal/provider/ovpn/openvpnconf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package ovpn

import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/openvpn"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
)

func (p *Provider) OpenVPNConfig(connection models.Connection,
settings settings.OpenVPN, ipv6Supported bool,
) (lines []string) {
providerSettings := utils.OpenVPNProviderSettings{
AuthUserPass: true,
Ciphers: []string{
openvpn.Chacha20Poly1305,
openvpn.AES256gcm,
openvpn.AES256cbc,
openvpn.AES128gcm,
},
}
return utils.OpenVPNConfig(providerSettings, connection, settings, ipv6Supported)
}
30 changes: 30 additions & 0 deletions internal/provider/ovpn/provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package ovpn

import (
"math/rand"
"net/http"

"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/provider/common"
"github.com/qdm12/gluetun/internal/provider/ovpn/updater"
)

type Provider struct {
storage common.Storage
randSource rand.Source
common.Fetcher
}

func New(storage common.Storage, randSource rand.Source,
client *http.Client,
) *Provider {
return &Provider{
storage: storage,
randSource: randSource,
Fetcher: updater.New(client),
}
}

func (p *Provider) Name() string {
return providers.Ovpn
}
Loading

0 comments on commit f21ff7b

Please sign in to comment.