feat(privatevpn): native port forwarding support (#2285)
This commit is contained in:
81
internal/provider/privatevpn/portforward.go
Normal file
81
internal/provider/privatevpn/portforward.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package privatevpn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/provider/common"
|
||||
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
regexPort = regexp.MustCompile(`[1-9][0-9]{0,4}`)
|
||||
)
|
||||
|
||||
var (
|
||||
ErrPortForwardedNotFound = errors.New("port forwarded not found")
|
||||
)
|
||||
|
||||
// PortForward obtains a VPN server side port forwarded from the PrivateVPN API.
|
||||
// It returns 0 if all ports are to forwarded on a dedicated server IP.
|
||||
func (p *Provider) PortForward(ctx context.Context, objects utils.PortForwardObjects) (
|
||||
ports []uint16, err error) {
|
||||
url := "https://connect.pvdatanet.com/v3/Api/port?ip[]=" + objects.InternalIP.String()
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating HTTP request: %w", err)
|
||||
}
|
||||
|
||||
response, err := objects.Client.Do(request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sending HTTP request: %w", err)
|
||||
}
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%w: %d %s", common.ErrHTTPStatusCodeNotOK,
|
||||
response.StatusCode, response.Status)
|
||||
}
|
||||
|
||||
defer response.Body.Close()
|
||||
|
||||
bytes, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading response body: %w", err)
|
||||
}
|
||||
|
||||
var data struct {
|
||||
Status string `json:"status"`
|
||||
Supported bool `json:"supported"`
|
||||
}
|
||||
err = json.Unmarshal(bytes, &data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decoding JSON response: %w; data is: %s",
|
||||
err, string(bytes))
|
||||
} else if !data.Supported {
|
||||
return nil, fmt.Errorf("%w for this VPN server", common.ErrPortForwardNotSupported)
|
||||
}
|
||||
|
||||
portString := regexPort.FindString(data.Status)
|
||||
if portString == "" {
|
||||
return nil, fmt.Errorf("%w: in status %q", ErrPortForwardedNotFound, data.Status)
|
||||
}
|
||||
|
||||
const base, bitSize = 10, 16
|
||||
portUint64, err := strconv.ParseUint(portString, base, bitSize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing port: %w", err)
|
||||
}
|
||||
return []uint16{uint16(portUint64)}, nil
|
||||
}
|
||||
|
||||
func (p *Provider) KeepPortForward(ctx context.Context,
|
||||
_ utils.PortForwardObjects) (err error) {
|
||||
<-ctx.Done()
|
||||
return ctx.Err()
|
||||
}
|
||||
Reference in New Issue
Block a user