feat(perfectprivacy): port forwarding support (#2378)
This commit is contained in:
@@ -60,7 +60,7 @@ Lightweight swiss-knife-like VPN client to multiple VPN service providers
|
|||||||
- Supports: **AirVPN**, **Cyberghost**, **ExpressVPN**, **FastestVPN**, **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**, **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 OpenVPN for all providers listed
|
- Supports OpenVPN for all providers listed
|
||||||
- Supports Wireguard both kernelspace and userspace
|
- Supports Wireguard both kernelspace and userspace
|
||||||
- For **AirVPN**, **Ivpn**, **Mullvad**, **NordVPN**, **Surfshark** and **Windscribe**
|
- For **AirVPN**, **Ivpn**, **Mullvad**, **NordVPN**, **Perfect privacy**, **Surfshark** and **Windscribe**
|
||||||
- For **ProtonVPN**, **PureVPN**, **Torguard**, **VPN Unlimited** and **WeVPN** using [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md)
|
- For **ProtonVPN**, **PureVPN**, **Torguard**, **VPN Unlimited** 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)
|
- 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)
|
- More in progress, see [#134](https://github.com/qdm12/gluetun/issues/134)
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ func (p PortForwarding) Validate(vpnProvider string) (err error) {
|
|||||||
providerSelected = *p.Provider
|
providerSelected = *p.Provider
|
||||||
}
|
}
|
||||||
validProviders := []string{
|
validProviders := []string{
|
||||||
|
providers.Perfectprivacy,
|
||||||
providers.PrivateInternetAccess,
|
providers.PrivateInternetAccess,
|
||||||
providers.Protonvpn,
|
providers.Protonvpn,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ type Service interface {
|
|||||||
|
|
||||||
type Routing interface {
|
type Routing interface {
|
||||||
VPNLocalGatewayIP(vpnInterface string) (gateway netip.Addr, err error)
|
VPNLocalGatewayIP(vpnInterface string) (gateway netip.Addr, err error)
|
||||||
|
AssignedIP(interfaceName string, family int) (ip netip.Addr, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type PortAllower interface {
|
type PortAllower interface {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ type PortAllower interface {
|
|||||||
|
|
||||||
type Routing interface {
|
type Routing interface {
|
||||||
VPNLocalGatewayIP(vpnInterface string) (gateway netip.Addr, err error)
|
VPNLocalGatewayIP(vpnInterface string) (gateway netip.Addr, err error)
|
||||||
|
AssignedIP(interfaceName string, family int) (ip netip.Addr, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Logger interface {
|
type Logger interface {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/netlink"
|
||||||
"github.com/qdm12/gluetun/internal/provider/utils"
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -22,9 +23,19 @@ func (s *Service) Start(ctx context.Context) (runError <-chan error, err error)
|
|||||||
return nil, fmt.Errorf("getting VPN local gateway IP: %w", err)
|
return nil, fmt.Errorf("getting VPN local gateway IP: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
family := netlink.FamilyV4
|
||||||
|
if gateway.Is6() {
|
||||||
|
family = netlink.FamilyV6
|
||||||
|
}
|
||||||
|
internalIP, err := s.routing.AssignedIP(s.settings.Interface, family)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting VPN assigned IP address: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
obj := utils.PortForwardObjects{
|
obj := utils.PortForwardObjects{
|
||||||
Logger: s.logger,
|
Logger: s.logger,
|
||||||
Gateway: gateway,
|
Gateway: gateway,
|
||||||
|
InternalIP: internalIP,
|
||||||
Client: s.client,
|
Client: s.client,
|
||||||
ServerName: s.settings.ServerName,
|
ServerName: s.settings.ServerName,
|
||||||
CanPortForward: s.settings.CanPortForward,
|
CanPortForward: s.settings.CanPortForward,
|
||||||
|
|||||||
43
internal/provider/perfectprivacy/portforward.go
Normal file
43
internal/provider/perfectprivacy/portforward.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package perfectprivacy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PortForward calculates and returns the VPN server side ports forwarded.
|
||||||
|
func (p *Provider) PortForward(_ context.Context,
|
||||||
|
objects utils.PortForwardObjects) (ports []uint16, err error) {
|
||||||
|
if !objects.InternalIP.IsValid() {
|
||||||
|
panic("internal ip is not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
return internalIPToPorts(objects.InternalIP), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) KeepPortForward(ctx context.Context,
|
||||||
|
_ utils.PortForwardObjects) (err error) {
|
||||||
|
<-ctx.Done()
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// See https://www.perfect-privacy.com/en/faq section
|
||||||
|
// How are the default forwarding ports being calculated?
|
||||||
|
func internalIPToPorts(internalIP netip.Addr) (ports []uint16) {
|
||||||
|
internalIPBytes := internalIP.AsSlice()
|
||||||
|
// Convert the internal IP address to a bit string
|
||||||
|
// and keep only the last 12 bits
|
||||||
|
last16Bits := internalIPBytes[len(internalIPBytes)-2:]
|
||||||
|
last12Bits := []byte{
|
||||||
|
last16Bits[0] & 0b00001111, // only keep 4 bits
|
||||||
|
last16Bits[1],
|
||||||
|
}
|
||||||
|
basePort := uint16(last12Bits[0])<<8 + uint16(last12Bits[1]) //nolint:gomnd
|
||||||
|
return []uint16{
|
||||||
|
10000 + basePort,
|
||||||
|
20000 + basePort,
|
||||||
|
30000 + basePort,
|
||||||
|
}
|
||||||
|
}
|
||||||
33
internal/provider/perfectprivacy/portforward_test.go
Normal file
33
internal/provider/perfectprivacy/portforward_test.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package perfectprivacy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_internalIPToPorts(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
internalIP netip.Addr
|
||||||
|
ports []uint16
|
||||||
|
}{
|
||||||
|
"example_case": {
|
||||||
|
internalIP: netip.AddrFrom4([4]byte{10, 0, 203, 88}),
|
||||||
|
ports: []uint16{12904, 22904, 32904},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ports := internalIPToPorts(testCase.internalIP)
|
||||||
|
|
||||||
|
assert.Equal(t, testCase.ports, ports)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,8 @@ type PortForwardObjects struct {
|
|||||||
// Gateway is the VPN gateway IP address, used by Private Internet Access
|
// Gateway is the VPN gateway IP address, used by Private Internet Access
|
||||||
// and ProtonVPN.
|
// and ProtonVPN.
|
||||||
Gateway netip.Addr
|
Gateway netip.Addr
|
||||||
|
// InternalIP is the VPN internal IP address assigned, used by Perfect Privacy.
|
||||||
|
InternalIP netip.Addr
|
||||||
// Client is used to query the VPN gateway for Private Internet Access.
|
// Client is used to query the VPN gateway for Private Internet Access.
|
||||||
Client *http.Client
|
Client *http.Client
|
||||||
// ServerName is used by Private Internet Access for port forwarding.
|
// ServerName is used by Private Internet Access for port forwarding.
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ func (r *Routing) DefaultRoutes() (defaultRoutes []DefaultRoute, err error) {
|
|||||||
if route.Gw.Is4() {
|
if route.Gw.Is4() {
|
||||||
family = netlink.FamilyV4
|
family = netlink.FamilyV4
|
||||||
}
|
}
|
||||||
defaultRoute.AssignedIP, err = r.assignedIP(defaultRoute.NetInterface, family)
|
defaultRoute.AssignedIP, err = r.AssignedIP(defaultRoute.NetInterface, family)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("getting assigned IP of %s: %w", defaultRoute.NetInterface, err)
|
return nil, fmt.Errorf("getting assigned IP of %s: %w", defaultRoute.NetInterface, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func ipMatchesFamily(ip netip.Addr, family int) bool {
|
|||||||
(family == netlink.FamilyV6 && ip.Is6())
|
(family == netlink.FamilyV6 && ip.Is6())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Routing) assignedIP(interfaceName string, family int) (ip netip.Addr, err error) {
|
func (r *Routing) AssignedIP(interfaceName string, family int) (ip netip.Addr, err error) {
|
||||||
iface, err := net.InterfaceByName(interfaceName)
|
iface, err := net.InterfaceByName(interfaceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ip, fmt.Errorf("network interface %s not found: %w", interfaceName, err)
|
return ip, fmt.Errorf("network interface %s not found: %w", interfaceName, err)
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ func (r *Routing) LocalNetworks() (localNetworks []LocalNetwork, err error) {
|
|||||||
if localNet.IPNet.Addr().Is4() {
|
if localNet.IPNet.Addr().Is4() {
|
||||||
family = netlink.FamilyV4
|
family = netlink.FamilyV4
|
||||||
}
|
}
|
||||||
ip, err := r.assignedIP(localNet.InterfaceName, family)
|
ip, err := r.AssignedIP(localNet.InterfaceName, family)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return localNetworks, err
|
return localNetworks, err
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user