PIA nextgen portforward (#242)

* Split provider/pia.go in piav3.go and piav4.go
* Change port forwarding signature
* Enable port forwarding parameter for PIA v4
* Fix VPN gateway IP obtention
* Setup HTTP client for TLS with custom cert
* Error message for regions not supporting pf
This commit is contained in:
Quentin McGaw
2020-10-12 10:55:08 -04:00
committed by GitHub
parent fbecbc1c82
commit ec157f102b
25 changed files with 763 additions and 202 deletions

View File

@@ -1,12 +1,17 @@
package provider
import (
"context"
"fmt"
"net"
"net/http"
"strings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/firewall"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/network"
"github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
)
type cyberghost struct {
@@ -135,6 +140,8 @@ func (c *cyberghost) BuildConf(connections []models.OpenVPNConnection, verbosity
return lines
}
func (c *cyberghost) GetPortForward(client network.Client) (port uint16, err error) {
func (c *cyberghost) PortForward(ctx context.Context, client *http.Client,
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
syncState func(port uint16) (pfFilepath models.Filepath)) {
panic("port forwarding is not supported for cyberghost")
}

View File

@@ -1,12 +1,17 @@
package provider
import (
"context"
"fmt"
"net"
"net/http"
"strings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/firewall"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/network"
"github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
)
type mullvad struct {
@@ -134,6 +139,8 @@ func (m *mullvad) BuildConf(connections []models.OpenVPNConnection, verbosity, u
return lines
}
func (m *mullvad) GetPortForward(client network.Client) (port uint16, err error) {
func (m *mullvad) PortForward(ctx context.Context, client *http.Client,
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
syncState func(port uint16) (pfFilepath models.Filepath)) {
panic("port forwarding is not supported for mullvad")
}

View File

@@ -1,12 +1,17 @@
package provider
import (
"context"
"fmt"
"net"
"net/http"
"strings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/firewall"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/network"
"github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
)
type nordvpn struct {
@@ -142,6 +147,8 @@ func (n *nordvpn) BuildConf(connections []models.OpenVPNConnection, verbosity, u
return lines
}
func (n *nordvpn) GetPortForward(client network.Client) (port uint16, err error) {
func (n *nordvpn) PortForward(ctx context.Context, client *http.Client,
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
syncState func(port uint16) (pfFilepath models.Filepath)) {
panic("port forwarding is not supported for nordvpn")
}

View File

@@ -1,35 +1,18 @@
package provider
import (
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"strings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/crypto/random"
"github.com/qdm12/golibs/network"
)
type pia struct {
random random.Random
servers []models.PIAServer
}
func newPrivateInternetAccess(servers []models.PIAServer) *pia {
return &pia{
random: random.NewRandom(),
servers: servers,
}
}
func (p *pia) filterServers(region string) (servers []models.PIAServer) {
func filterPIAServers(servers []models.PIAServer, region string) (filtered []models.PIAServer) {
if len(region) == 0 {
return p.servers
return servers
}
for _, server := range p.servers {
for _, server := range servers {
if strings.EqualFold(server.Region, region) {
return []models.PIAServer{server}
}
@@ -37,8 +20,8 @@ func (p *pia) filterServers(region string) (servers []models.PIAServer) {
return nil
}
func (p *pia) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) {
servers := p.filterServers(selection.Region)
func getPIAOpenVPNConnections(allServers []models.PIAServer, selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) {
servers := filterPIAServers(allServers, selection.Region)
if len(servers) == 0 {
return nil, fmt.Errorf("no server found for region %q", selection.Region)
}
@@ -87,7 +70,7 @@ func (p *pia) GetOpenVPNConnections(selection models.ServerSelection) (connectio
return connections, nil
}
func (p *pia) BuildConf(connections []models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
func buildPIAConf(connections []models.OpenVPNConnection, verbosity int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
var X509CRL, certificate string
if extras.EncryptionPreset == constants.PIAEncryptionPresetNormal {
if len(cipher) == 0 {
@@ -161,28 +144,3 @@ func (p *pia) BuildConf(connections []models.OpenVPNConnection, verbosity, uid,
}...)
return lines
}
func (p *pia) GetPortForward(client network.Client) (port uint16, err error) {
b, err := p.random.GenerateRandomBytes(32)
if err != nil {
return 0, err
}
clientID := hex.EncodeToString(b)
url := fmt.Sprintf("%s/?client_id=%s", constants.PIAPortForwardURL, clientID)
content, status, err := client.GetContent(url) // TODO add ctx
switch {
case err != nil:
return 0, err
case status != http.StatusOK:
return 0, fmt.Errorf("status is %d for %s; does your PIA server support port forwarding?", status, url)
case len(content) == 0:
return 0, fmt.Errorf("port forwarding is already activated on this connection, has expired, or you are not connected to a PIA region that supports port forwarding")
}
body := struct {
Port uint16 `json:"port"`
}{}
if err := json.Unmarshal(content, &body); err != nil {
return 0, fmt.Errorf("port forwarding response: %w", err)
}
return body.Port, nil
}

View File

@@ -0,0 +1,94 @@
package provider
import (
"context"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/firewall"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/crypto/random"
"github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
)
type piaV3 struct {
random random.Random
servers []models.PIAServer
}
func newPrivateInternetAccessV3(servers []models.PIAServer) *piaV3 {
return &piaV3{
random: random.NewRandom(),
servers: servers,
}
}
func (p *piaV3) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) {
return getPIAOpenVPNConnections(p.servers, selection)
}
func (p *piaV3) BuildConf(connections []models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
return buildPIAConf(connections, verbosity, root, cipher, auth, extras)
}
func (p *piaV3) PortForward(ctx context.Context, client *http.Client,
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
syncState func(port uint16) (pfFilepath models.Filepath)) {
b, err := p.random.GenerateRandomBytes(32)
if err != nil {
pfLogger.Error(err)
return
}
clientID := hex.EncodeToString(b)
url := fmt.Sprintf("%s/?client_id=%s", constants.PIAPortForwardURL, clientID)
response, err := client.Get(url) // TODO add ctx
if err != nil {
pfLogger.Error(err)
return
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
pfLogger.Error(fmt.Errorf("%s for %s; does your PIA server support port forwarding?", response.Status, url))
return
}
b, err = ioutil.ReadAll(response.Body)
if err != nil {
pfLogger.Error(err)
return
} else if len(b) == 0 {
pfLogger.Error(fmt.Errorf("port forwarding is already activated on this connection, has expired, or you are not connected to a PIA region that supports port forwarding"))
return
}
body := struct {
Port uint16 `json:"port"`
}{}
if err := json.Unmarshal(b, &body); err != nil {
pfLogger.Error(fmt.Errorf("port forwarding response: %w", err))
return
}
port := body.Port
filepath := syncState(port)
pfLogger.Info("Writing port to %s", filepath)
if err := fileManager.WriteToFile(
string(filepath), []byte(fmt.Sprintf("%d", port)),
files.Permissions(0666),
); err != nil {
pfLogger.Error(err)
}
if err := fw.SetAllowedPort(ctx, port, string(constants.TUN)); err != nil {
pfLogger.Error(err)
}
<-ctx.Done()
if err := fw.RemoveAllowedPort(ctx, port); err != nil {
pfLogger.Error(err)
}
}

412
internal/provider/piav4.go Normal file
View File

@@ -0,0 +1,412 @@
package provider
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"strings"
"time"
"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/files"
"github.com/qdm12/golibs/logging"
)
type piaV4 struct {
servers []models.PIAServer
timeNow func() time.Time
}
func newPrivateInternetAccessV4(servers []models.PIAServer) *piaV4 {
return &piaV4{
servers: servers,
timeNow: time.Now,
}
}
func (p *piaV4) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) {
return getPIAOpenVPNConnections(p.servers, selection)
}
func (p *piaV4) BuildConf(connections []models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
return buildPIAConf(connections, verbosity, root, cipher, auth, extras)
}
//nolint:gocognit
func (p *piaV4) PortForward(ctx context.Context, client *http.Client,
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
syncState func(port uint16) (pfFilepath models.Filepath)) {
if gateway == nil {
pfLogger.Error("aborting because: VPN gateway IP address was not found")
return
}
client, err := newPIAv4HTTPClient()
if err != nil {
pfLogger.Error("aborting because: %s", err)
return
}
defer pfLogger.Warn("loop exited")
data, err := readPIAPortForwardData(fileManager)
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(client, gateway, fileManager)
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 {
return bindPIAPort(client, gateway, data)
})
if ctx.Err() != nil {
return
}
filepath := syncState(data.Port)
pfLogger.Info("Writing port to %s", filepath)
if err := fileManager.WriteToFile(
string(filepath), []byte(fmt.Sprintf("%d", data.Port)),
files.Permissions(0666),
); err != nil {
pfLogger.Error(err)
}
if err := fw.SetAllowedPort(ctx, data.Port, string(constants.TUN)); err != nil {
pfLogger.Error(err)
}
expiryTimer := time.NewTimer(durationToExpiration)
defer expiryTimer.Stop()
const keepAlivePeriod = 15 * time.Minute
keepAliveTicker := time.NewTicker(keepAlivePeriod)
defer keepAliveTicker.Stop()
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)
}
return
case <-keepAliveTicker.C:
if err := bindPIAPort(client, gateway, data); err != nil {
pfLogger.Error(err)
}
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(client, gateway, fileManager)
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 := fileManager.WriteToFile(
string(filepath), []byte(fmt.Sprintf("%d", data.Port)),
files.Permissions(0666),
); err != nil {
pfLogger.Error(err)
}
if err := bindPIAPort(client, gateway, data); err != nil {
pfLogger.Error(err)
}
keepAliveTicker.Reset(keepAlivePeriod)
expiryTimer.Reset(durationToExpiration)
}
}
}
func newPIAv4HTTPClient() (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)
}
rootCAs := x509.NewCertPool()
rootCAs.AddCert(certificate)
TLSClientConfig := &tls.Config{
RootCAs: rootCAs,
MinVersion: tls.VersionTLS12,
InsecureSkipVerify: true, //nolint:gosec
} // TODO fix and remove InsecureSkipVerify
transport := http.Transport{
TLSClientConfig: TLSClientConfig,
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
const httpTimeout = 5 * time.Second
client = &http.Client{Transport: &transport, Timeout: httpTimeout}
return client, nil
}
func refreshPIAPortForwardData(client *http.Client, gateway net.IP, fileManager files.FileManager) (data piaPortForwardData, err error) {
data.Token, err = fetchPIAToken(fileManager, client)
if err != nil {
return data, fmt.Errorf("cannot obtain token: %w", err)
}
data.Port, data.Signature, data.Expiration, err = fetchPIAPortForwardData(client, gateway, data.Token)
if err != nil {
if strings.HasSuffix(err.Error(), "connection refused") {
return data, fmt.Errorf("cannot obtain port forwarding data: connection was refused, are you sure the region you are using supports port forwarding ;)")
}
return data, fmt.Errorf("cannot obtain port forwarding data: %w", err)
}
if err := writePIAPortForwardData(fileManager, 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(fileManager files.FileManager) (data piaPortForwardData, err error) {
const filepath = string(constants.PIAPortForward)
exists, err := fileManager.FileExists(filepath)
if err != nil {
return data, err
} else if !exists {
return data, nil
}
b, err := fileManager.ReadFile(filepath)
if err != nil {
return data, err
}
if err := json.Unmarshal(b, &data); err != nil {
return data, err
}
return data, nil
}
func writePIAPortForwardData(fileManager files.FileManager, data piaPortForwardData) (err error) {
b, err := json.Marshal(&data)
if err != nil {
return fmt.Errorf("cannot encode data: %w", err)
}
err = fileManager.WriteToFile(string(constants.PIAPortForward), b)
if err != nil {
return err
}
return nil
}
func unpackPIAPayload(payload string) (port uint16, token string, expiration time.Time, err error) {
b, err := base64.RawStdEncoding.DecodeString(payload)
if err != nil {
return 0, "", expiration, fmt.Errorf("cannot decode payload: %w", err)
}
var payloadData piaPayload
if err := json.Unmarshal(b, &payloadData); err != nil {
return 0, "", expiration, fmt.Errorf("cannot parse payload data: %w", 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.RawStdEncoding.EncodeToString(b)
return payload, nil
}
func fetchPIAToken(fileManager files.FileManager, client *http.Client) (token string, err error) {
username, password, err := getOpenvpnCredentials(fileManager)
if err != nil {
return "", fmt.Errorf("cannot get Openvpn credentials: %w", err)
}
url := url.URL{
Scheme: "https",
User: url.UserPassword(username, password),
Host: "10.0.0.1",
Path: "/authv3/generateToken",
}
request, err := http.NewRequest(http.MethodGet, url.String(), nil)
if err != nil {
return "", err
}
response, err := client.Do(request)
if err != nil {
return "", err
}
defer response.Body.Close()
b, err := ioutil.ReadAll(response.Body)
if response.StatusCode != http.StatusOK {
shortenMessage := string(b)
shortenMessage = strings.ReplaceAll(shortenMessage, "\n", "")
shortenMessage = strings.ReplaceAll(shortenMessage, " ", " ")
return "", fmt.Errorf("%s: response received: %q", response.Status, shortenMessage)
} else if err != nil {
return "", err
}
var result struct {
Token string `json:"token"`
}
if err := json.Unmarshal(b, &result); err != nil {
return "", err
} else if len(result.Token) == 0 {
return "", fmt.Errorf("token is empty")
}
return result.Token, nil
}
func getOpenvpnCredentials(fileManager files.FileManager) (username, password string, err error) {
authData, err := fileManager.ReadFile(string(constants.OpenVPNAuthConf))
if err != nil {
return "", "", fmt.Errorf("cannot read openvpn auth file: %w", err)
}
lines := strings.Split(string(authData), "\n")
if len(lines) < 2 {
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(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(),
}
response, err := client.Get(url.String())
if err != nil {
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)
}
b, err := ioutil.ReadAll(response.Body)
if err != nil {
return 0, "", expiration, fmt.Errorf("cannot obtain signature: %w", err)
}
var data struct {
Status string `json:"status"`
Payload string `json:"payload"`
Signature string `json:"signature"`
}
if err := json.Unmarshal(b, &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(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(),
}
response, err := client.Get(url.String())
if err != nil {
return fmt.Errorf("cannot bind port: %w", err)
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return fmt.Errorf("cannot bind port: %s", response.Status)
}
b, err := ioutil.ReadAll(response.Body)
if err != nil {
return fmt.Errorf("cannot bind port: %w", err)
}
var responseData struct {
Status string `json:"status"`
Message string `json:"message"`
}
if err := json.Unmarshal(b, &responseData); err != nil {
return fmt.Errorf("cannot bind port: %w", err)
} else if responseData.Status != "OK" {
return fmt.Errorf("response received from PIA: %s (%s)", responseData.Status, responseData.Message)
}
return nil
}

View File

@@ -1,24 +1,32 @@
package provider
import (
"context"
"net"
"net/http"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/firewall"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/network"
"github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
)
// Provider contains methods to read and modify the openvpn configuration to connect as a client
type Provider interface {
GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error)
BuildConf(connections []models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string)
GetPortForward(client network.Client) (port uint16, err error)
PortForward(ctx context.Context, client *http.Client,
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
syncState func(port uint16) (pfFilepath models.Filepath))
}
func New(provider models.VPNProvider, allServers models.AllServers) Provider {
switch provider {
case constants.PrivateInternetAccess:
return newPrivateInternetAccess(allServers.Pia.Servers)
return newPrivateInternetAccessV4(allServers.Pia.Servers)
case constants.PrivateInternetAccessOld:
return newPrivateInternetAccess(allServers.PiaOld.Servers)
return newPrivateInternetAccessV3(allServers.PiaOld.Servers)
case constants.Mullvad:
return newMullvad(allServers.Mullvad.Servers)
case constants.Windscribe:

View File

@@ -1,12 +1,17 @@
package provider
import (
"context"
"fmt"
"net"
"net/http"
"strings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/firewall"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/network"
"github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
)
type purevpn struct {
@@ -157,6 +162,8 @@ func (p *purevpn) BuildConf(connections []models.OpenVPNConnection, verbosity, u
return lines
}
func (p *purevpn) GetPortForward(client network.Client) (port uint16, err error) {
func (p *purevpn) PortForward(ctx context.Context, client *http.Client,
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
syncState func(port uint16) (pfFilepath models.Filepath)) {
panic("port forwarding is not supported for purevpn")
}

View File

@@ -1,12 +1,17 @@
package provider
import (
"context"
"fmt"
"net"
"net/http"
"strings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/firewall"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/network"
"github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
)
type surfshark struct {
@@ -135,6 +140,8 @@ func (s *surfshark) BuildConf(connections []models.OpenVPNConnection, verbosity,
return lines
}
func (s *surfshark) GetPortForward(client network.Client) (port uint16, err error) {
func (s *surfshark) PortForward(ctx context.Context, client *http.Client,
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
syncState func(port uint16) (pfFilepath models.Filepath)) {
panic("port forwarding is not supported for surfshark")
}

View File

@@ -0,0 +1,29 @@
package provider
import (
"context"
"time"
"github.com/qdm12/golibs/logging"
)
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
}
}
}

View File

@@ -1,12 +1,17 @@
package provider
import (
"context"
"fmt"
"net"
"net/http"
"strings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/firewall"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/network"
"github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
)
type vyprvpn struct {
@@ -121,6 +126,8 @@ func (v *vyprvpn) BuildConf(connections []models.OpenVPNConnection, verbosity, u
return lines
}
func (v *vyprvpn) GetPortForward(client network.Client) (port uint16, err error) {
func (v *vyprvpn) PortForward(ctx context.Context, client *http.Client,
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
syncState func(port uint16) (pfFilepath models.Filepath)) {
panic("port forwarding is not supported for vyprvpn")
}

View File

@@ -1,12 +1,17 @@
package provider
import (
"context"
"fmt"
"net"
"net/http"
"strings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/firewall"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/network"
"github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
)
type windscribe struct {
@@ -133,6 +138,8 @@ func (w *windscribe) BuildConf(connections []models.OpenVPNConnection, verbosity
return lines
}
func (w *windscribe) GetPortForward(client network.Client) (port uint16, err error) {
func (w *windscribe) PortForward(ctx context.Context, client *http.Client,
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
syncState func(port uint16) (pfFilepath models.Filepath)) {
panic("port forwarding is not supported for windscribe")
}