mirror of
https://github.com/qdm12/gluetun.git
synced 2026-05-09 20:29:23 +02:00
chore: do not use sentinel errors when unneeded
- main reason being it's a burden to always define sentinel errors at global scope, wrap them with `%w` instead of using a string directly - only use sentinel errors when it has to be checked using `errors.Is` - replace all usage of these sentinel errors in `fmt.Errorf` with direct strings that were in the sentinel error - exclude the sentinel error definition requirement from .golangci.yml - update unit tests to use ContainersError instead of ErrorIs so it stays as a "not a change detector test" without requiring a sentinel error
This commit is contained in:
@@ -6,8 +6,6 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/provider/common"
|
||||
)
|
||||
|
||||
type apiData struct {
|
||||
@@ -48,8 +46,8 @@ func fetchAPI(ctx context.Context, client *http.Client) (
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
_ = response.Body.Close()
|
||||
return data, fmt.Errorf("%w: %d %s",
|
||||
common.ErrHTTPStatusCodeNotOK, response.StatusCode, response.Status)
|
||||
return data, fmt.Errorf("HTTP status code not OK: %d %s",
|
||||
response.StatusCode, response.Status)
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(response.Body)
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
package common
|
||||
|
||||
import "errors"
|
||||
|
||||
var ErrPortForwardNotSupported = errors.New("port forwarding not supported")
|
||||
@@ -10,10 +10,8 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotEnoughServers = errors.New("not enough servers found")
|
||||
ErrHTTPStatusCodeNotOK = errors.New("HTTP status code not OK")
|
||||
ErrIPFetcherUnsupported = errors.New("IP fetcher not supported")
|
||||
ErrCredentialsMissing = errors.New("credentials missing")
|
||||
ErrNotEnoughServers = errors.New("not enough servers found")
|
||||
ErrCredentialsMissing = errors.New("credentials are missing")
|
||||
)
|
||||
|
||||
type Fetcher interface {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package custom
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
@@ -10,8 +9,6 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
var ErrVPNTypeNotSupported = errors.New("VPN type not supported for custom provider")
|
||||
|
||||
// GetConnection gets the connection from the OpenVPN configuration file.
|
||||
func (p *Provider) GetConnection(selection settings.ServerSelection, _ bool) (
|
||||
connection models.Connection, err error,
|
||||
@@ -22,7 +19,7 @@ func (p *Provider) GetConnection(selection settings.ServerSelection, _ bool) (
|
||||
case vpn.Wireguard, vpn.AmneziaWg:
|
||||
return getWireguardConnection(selection), nil
|
||||
default:
|
||||
return connection, fmt.Errorf("%w: %s", ErrVPNTypeNotSupported, selection.VPN)
|
||||
return connection, fmt.Errorf("VPN type not supported for custom provider: %s", selection.VPN)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package custom
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -12,8 +11,6 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||
)
|
||||
|
||||
var ErrExtractData = errors.New("failed extracting information from custom configuration file")
|
||||
|
||||
func (p *Provider) OpenVPNConfig(connection models.Connection,
|
||||
settings settings.OpenVPN, ipv6Supported bool,
|
||||
) (lines []string) {
|
||||
|
||||
@@ -3,13 +3,10 @@ package updater
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var errHTTPStatusCodeNotOK = errors.New("HTTP status code not OK")
|
||||
|
||||
type apiData struct {
|
||||
Servers []apiServer `json:"servers"`
|
||||
}
|
||||
@@ -42,8 +39,8 @@ func fetchAPI(ctx context.Context, client *http.Client) (
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
_ = response.Body.Close()
|
||||
return data, fmt.Errorf("%w: %d %s",
|
||||
errHTTPStatusCodeNotOK, response.StatusCode, response.Status)
|
||||
return data, fmt.Errorf("HTTP status code not OK: %d %s",
|
||||
response.StatusCode, response.Status)
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(response.Body)
|
||||
|
||||
@@ -21,21 +21,17 @@ func Test_Provider_GetConnection(t *testing.T) {
|
||||
|
||||
const provider = providers.Expressvpn
|
||||
|
||||
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
|
||||
panicMessage string
|
||||
}{
|
||||
"error": {
|
||||
storageErr: errTest,
|
||||
errWrapped: errTest,
|
||||
storageErr: errors.New("test error"),
|
||||
errMessage: "filtering servers: test error",
|
||||
},
|
||||
"default OpenVPN TCP port": {
|
||||
@@ -100,9 +96,10 @@ func Test_Provider_GetConnection(t *testing.T) {
|
||||
|
||||
connection, err := provider.GetConnection(testCase.selection, testCase.ipv6Supported)
|
||||
|
||||
assert.ErrorIs(t, err, testCase.errWrapped)
|
||||
if testCase.errWrapped != nil {
|
||||
if testCase.errMessage != "" {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
assert.Equal(t, testCase.connection, connection)
|
||||
|
||||
@@ -3,14 +3,11 @@ package updater
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/provider/common"
|
||||
)
|
||||
|
||||
type apiServer struct {
|
||||
@@ -19,8 +16,6 @@ type apiServer struct {
|
||||
hostname string
|
||||
}
|
||||
|
||||
var ErrDataMalformed = errors.New("data is malformed")
|
||||
|
||||
const apiURL = "https://support.fastestvpn.com/wp-admin/admin-ajax.php"
|
||||
|
||||
// The API URL and requests are shamelessly taken from network operations
|
||||
@@ -49,7 +44,7 @@ func fetchAPIServers(ctx context.Context, client *http.Client, protocol string)
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
_ = response.Body.Close()
|
||||
return nil, fmt.Errorf("%w: %d", common.ErrHTTPStatusCodeNotOK, response.StatusCode)
|
||||
return nil, fmt.Errorf("HTTP status code not OK: %d", response.StatusCode)
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(response.Body)
|
||||
@@ -79,8 +74,8 @@ func fetchAPIServers(ctx context.Context, client *http.Client, protocol string)
|
||||
for i := range numberOfTDBlocks {
|
||||
tdBlock := getNextTDBlock(trBlock)
|
||||
if tdBlock == nil {
|
||||
return nil, fmt.Errorf("%w: expected 3 <td> blocks in <tr> block %q",
|
||||
ErrDataMalformed, string(trBlock))
|
||||
return nil, fmt.Errorf("data is malformed: expected 3 <td> blocks in <tr> block %q",
|
||||
string(trBlock))
|
||||
}
|
||||
trBlock = trBlock[len(tdBlock):]
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/provider/common"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -21,8 +20,6 @@ func (f roundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
func Test_fechAPIServers(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
errTest := errors.New("test error")
|
||||
|
||||
testCases := map[string]struct {
|
||||
ctx context.Context
|
||||
protocol string
|
||||
@@ -31,7 +28,6 @@ func Test_fechAPIServers(t *testing.T) {
|
||||
responseBody io.ReadCloser
|
||||
transportErr error
|
||||
servers []apiServer
|
||||
errWrapped error
|
||||
errMessage string
|
||||
}{
|
||||
"transport_error": {
|
||||
@@ -39,8 +35,7 @@ func Test_fechAPIServers(t *testing.T) {
|
||||
protocol: "tcp",
|
||||
requestBody: "action=vpn_servers&protocol=tcp",
|
||||
responseStatus: http.StatusOK,
|
||||
transportErr: errTest,
|
||||
errWrapped: errTest,
|
||||
transportErr: errors.New("test error"),
|
||||
errMessage: `sending request: Post ` +
|
||||
`"https://support.fastestvpn.com/wp-admin/admin-ajax.php": ` +
|
||||
`test error`,
|
||||
@@ -50,7 +45,6 @@ func Test_fechAPIServers(t *testing.T) {
|
||||
protocol: "tcp",
|
||||
requestBody: "action=vpn_servers&protocol=tcp",
|
||||
responseStatus: http.StatusNotFound,
|
||||
errWrapped: common.ErrHTTPStatusCodeNotOK,
|
||||
errMessage: "HTTP status code not OK: 404",
|
||||
},
|
||||
"empty_data": {
|
||||
@@ -110,9 +104,10 @@ func Test_fechAPIServers(t *testing.T) {
|
||||
|
||||
entries, err := fetchAPIServers(testCase.ctx, client, testCase.protocol)
|
||||
|
||||
assert.ErrorIs(t, err, testCase.errWrapped)
|
||||
if testCase.errWrapped != nil {
|
||||
if testCase.errMessage != "" {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, testCase.servers, entries)
|
||||
})
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var errNotOvpnExt = errors.New("filename does not have the openvpn file extension")
|
||||
|
||||
func parseFilename(fileName string) (
|
||||
region string, err error,
|
||||
) {
|
||||
const suffix = ".ovpn"
|
||||
if !strings.HasSuffix(fileName, suffix) {
|
||||
return "", fmt.Errorf("%w: %s", errNotOvpnExt, fileName)
|
||||
return "", fmt.Errorf("filename does not have the openvpn file extension: %s", fileName)
|
||||
}
|
||||
|
||||
region = strings.TrimSuffix(fileName, suffix)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -9,8 +8,6 @@ import (
|
||||
"golang.org/x/text/cases"
|
||||
)
|
||||
|
||||
var errCountryCodeUnknown = errors.New("country code is unknown")
|
||||
|
||||
func parseFilename(fileName, hostname string, titleCaser cases.Caser) (
|
||||
country, city string, err error,
|
||||
) {
|
||||
@@ -28,7 +25,7 @@ func parseFilename(fileName, hostname string, titleCaser cases.Caser) (
|
||||
countryCode := strings.ToLower(parts[0])
|
||||
country, ok := countryCodes[countryCode]
|
||||
if !ok {
|
||||
return "", "", fmt.Errorf("%w: %s", errCountryCodeUnknown, countryCode)
|
||||
return "", "", fmt.Errorf("country code is unknown: %s", countryCode)
|
||||
}
|
||||
country = titleCaser.String(country)
|
||||
|
||||
|
||||
@@ -22,20 +22,16 @@ func Test_Provider_GetConnection(t *testing.T) {
|
||||
|
||||
const provider = providers.Ivpn
|
||||
|
||||
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,
|
||||
storageErr: errors.New("test error"),
|
||||
errMessage: "filtering servers: test error",
|
||||
},
|
||||
"default OpenVPN TCP port": {
|
||||
@@ -104,9 +100,10 @@ func Test_Provider_GetConnection(t *testing.T) {
|
||||
|
||||
connection, err := provider.GetConnection(testCase.selection, testCase.ipv6Supported)
|
||||
|
||||
assert.ErrorIs(t, err, testCase.errWrapped)
|
||||
if testCase.errWrapped != nil {
|
||||
if testCase.errMessage != "" {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
assert.Equal(t, testCase.connection, connection)
|
||||
|
||||
@@ -3,13 +3,10 @@ package updater
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var errHTTPStatusCodeNotOK = errors.New("HTTP status code not OK")
|
||||
|
||||
type apiData struct {
|
||||
Servers []apiServer `json:"servers"`
|
||||
}
|
||||
@@ -45,8 +42,8 @@ func fetchAPI(ctx context.Context, client *http.Client) (
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
_ = response.Body.Close()
|
||||
return data, fmt.Errorf("%w: %d %s",
|
||||
errHTTPStatusCodeNotOK, response.StatusCode, response.Status)
|
||||
return data, fmt.Errorf("HTTP status code not OK: %d %s",
|
||||
response.StatusCode, response.Status)
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(response.Body)
|
||||
|
||||
@@ -22,20 +22,16 @@ func Test_Provider_GetConnection(t *testing.T) {
|
||||
|
||||
const provider = providers.Mullvad
|
||||
|
||||
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,
|
||||
storageErr: errors.New("test error"),
|
||||
errMessage: "filtering servers: test error",
|
||||
},
|
||||
"default Wireguard port": {
|
||||
@@ -70,9 +66,10 @@ func Test_Provider_GetConnection(t *testing.T) {
|
||||
|
||||
connection, err := provider.GetConnection(testCase.selection, testCase.ipv6Supported)
|
||||
|
||||
assert.ErrorIs(t, err, testCase.errWrapped)
|
||||
if testCase.errWrapped != nil {
|
||||
if testCase.errMessage != "" {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
assert.Equal(t, testCase.connection, connection)
|
||||
|
||||
@@ -3,16 +3,10 @@ package updater
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrHTTPStatusCodeNotOK = errors.New("HTTP status code not OK")
|
||||
ErrDecodeResponseBody = errors.New("failed decoding response body")
|
||||
)
|
||||
|
||||
type serverData struct {
|
||||
Hostname string `json:"hostname"`
|
||||
Country string `json:"country_name"`
|
||||
@@ -41,13 +35,12 @@ func fetchAPI(ctx context.Context, client *http.Client) (data []serverData, err
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%w: %d %s", ErrHTTPStatusCodeNotOK,
|
||||
response.StatusCode, response.Status)
|
||||
return nil, fmt.Errorf("HTTP status code not OK: %d %s", response.StatusCode, response.Status)
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(response.Body)
|
||||
if err := decoder.Decode(&data); err != nil {
|
||||
return nil, fmt.Errorf("%w: %s", ErrDecodeResponseBody, err)
|
||||
return nil, fmt.Errorf("failed decoding response body: %s", err)
|
||||
}
|
||||
|
||||
if err := response.Body.Close(); err != nil {
|
||||
|
||||
@@ -12,20 +12,13 @@ import (
|
||||
|
||||
type hostToServer map[string]models.Server
|
||||
|
||||
var (
|
||||
ErrNoIP = errors.New("no IP address for VPN server")
|
||||
ErrIPIsNotV4 = errors.New("IP address is not IPv4")
|
||||
ErrIPIsNotV6 = errors.New("IP address is not IPv6")
|
||||
ErrVPNTypeNotSupported = errors.New("VPN type not supported")
|
||||
)
|
||||
|
||||
func (hts hostToServer) add(data serverData) (err error) {
|
||||
if !data.Active {
|
||||
return nil
|
||||
}
|
||||
|
||||
if data.IPv4 == "" && data.IPv6 == "" {
|
||||
return fmt.Errorf("%w", ErrNoIP)
|
||||
return errors.New("no IP address for VPN server")
|
||||
}
|
||||
|
||||
server, ok := hts[data.Hostname]
|
||||
@@ -40,7 +33,7 @@ func (hts hostToServer) add(data serverData) (err error) {
|
||||
// ignore bridge servers
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("%w: %s", ErrVPNTypeNotSupported, data.Type)
|
||||
return fmt.Errorf("VPN type not supported: %s", data.Type)
|
||||
}
|
||||
|
||||
if data.IPv4 != "" {
|
||||
@@ -48,7 +41,7 @@ func (hts hostToServer) add(data serverData) (err error) {
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing IPv4 address: %w", err)
|
||||
} else if !ipv4.Is4() {
|
||||
return fmt.Errorf("%w: %s", ErrIPIsNotV4, data.IPv4)
|
||||
return fmt.Errorf("IP address is not IPv4: %s", data.IPv4)
|
||||
}
|
||||
server.IPs = append(server.IPs, ipv4)
|
||||
}
|
||||
@@ -58,7 +51,7 @@ func (hts hostToServer) add(data serverData) (err error) {
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing IPv6 address: %w", err)
|
||||
} else if !ipv6.Is6() {
|
||||
return fmt.Errorf("%w: %s", ErrIPIsNotV6, data.IPv6)
|
||||
return fmt.Errorf("IP address is not IPv6: %s", data.IPv6)
|
||||
}
|
||||
server.IPs = append(server.IPs, ipv6)
|
||||
}
|
||||
|
||||
@@ -3,13 +3,10 @@ package updater
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var ErrHTTPStatusCodeNotOK = errors.New("HTTP status code not OK")
|
||||
|
||||
func fetchAPI(ctx context.Context, client *http.Client,
|
||||
limit uint,
|
||||
) (data serversData, err error) {
|
||||
@@ -28,7 +25,7 @@ func fetchAPI(ctx context.Context, client *http.Client,
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return serversData{}, fmt.Errorf("%w: %s", ErrHTTPStatusCodeNotOK, response.Status)
|
||||
return serversData{}, fmt.Errorf("HTTP status code not OK: %s", response.Status)
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(response.Body)
|
||||
|
||||
@@ -168,11 +168,6 @@ func (s *serverData) ips() (ips []netip.Addr) {
|
||||
return ips
|
||||
}
|
||||
|
||||
var (
|
||||
ErrWireguardPublicKeyMalformed = errors.New("wireguard public key is malformed")
|
||||
ErrWireguardPublicKeyNotFound = errors.New("wireguard public key not found")
|
||||
)
|
||||
|
||||
// wireguardPublicKey returns the Wireguard public key for the server.
|
||||
func (s *serverData) wireguardPublicKey(technologies map[uint32]technologyData) (
|
||||
wgPubKey string, err error,
|
||||
@@ -189,11 +184,10 @@ func (s *serverData) wireguardPublicKey(technologies map[uint32]technologyData)
|
||||
wgPubKey = metadata.Value
|
||||
_, err = base64.StdEncoding.DecodeString(wgPubKey)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%w: %s cannot be decoded: %s",
|
||||
ErrWireguardPublicKeyMalformed, wgPubKey, err)
|
||||
return "", fmt.Errorf("wireguard public key is malformed: %s cannot be decoded: %s", wgPubKey, err)
|
||||
}
|
||||
return metadata.Value, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("%w", ErrWireguardPublicKeyNotFound)
|
||||
return "", errors.New("wireguard public key not found")
|
||||
}
|
||||
|
||||
@@ -7,10 +7,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoIDInServerName = errors.New("no ID in server name")
|
||||
ErrInvalidIDInServerName = errors.New("invalid ID in server name")
|
||||
)
|
||||
var ErrNoIDInServerName = errors.New("no ID in server name")
|
||||
|
||||
func parseServerName(serverName string) (number uint16, err error) {
|
||||
i := strings.IndexRune(serverName, '#')
|
||||
@@ -21,7 +18,7 @@ func parseServerName(serverName string) (number uint16, err error) {
|
||||
idString := serverName[i+1:]
|
||||
idUint64, err := strconv.ParseUint(idString, 10, 16)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("%w: %s", ErrInvalidIDInServerName, serverName)
|
||||
return 0, fmt.Errorf("invalid ID in server name: %s", serverName)
|
||||
}
|
||||
|
||||
number = uint16(idUint64)
|
||||
|
||||
@@ -11,8 +11,6 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/provider/common"
|
||||
)
|
||||
|
||||
var ErrNotIPv4 = errors.New("IP address is not IPv4")
|
||||
|
||||
func (u *Updater) FetchServers(ctx context.Context, minServers int) (
|
||||
servers []models.Server, err error,
|
||||
) {
|
||||
|
||||
@@ -21,8 +21,6 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||
)
|
||||
|
||||
var ErrServerNameNotFound = errors.New("server name not found in servers")
|
||||
|
||||
// PortForward obtains a VPN server side port forwarded from PIA.
|
||||
func (p *Provider) PortForward(ctx context.Context,
|
||||
objects utils.PortForwardObjects,
|
||||
@@ -42,7 +40,7 @@ func (p *Provider) PortForward(ctx context.Context,
|
||||
logger := objects.Logger
|
||||
|
||||
if !objects.CanPortForward {
|
||||
return nil, fmt.Errorf("%w: for server %s", ErrServerNameNotFound, serverName)
|
||||
return nil, fmt.Errorf("server name %s not found in servers", serverName)
|
||||
}
|
||||
|
||||
privateIPClient, err := newHTTPClient(serverName)
|
||||
@@ -91,8 +89,6 @@ func (p *Provider) PortForward(ctx context.Context,
|
||||
return map[uint16]uint16{data.Port: data.Port}, nil
|
||||
}
|
||||
|
||||
var ErrPortForwardedExpired = errors.New("port forwarded data expired")
|
||||
|
||||
func (p *Provider) KeepPortForward(ctx context.Context,
|
||||
objects utils.PortForwardObjects,
|
||||
) (err error) {
|
||||
@@ -136,14 +132,12 @@ func (p *Provider) KeepPortForward(ctx context.Context,
|
||||
}
|
||||
keepAliveTimer.Reset(keepAlivePeriod)
|
||||
case <-expiryTimer.C:
|
||||
return fmt.Errorf("%w: on %s", ErrPortForwardedExpired,
|
||||
return fmt.Errorf("port forwarded data expired on %s",
|
||||
data.Expiration.Format(time.RFC1123))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var errAPIIPNotFound = errors.New("API IP address not found")
|
||||
|
||||
func findAPIIP(ctx context.Context, client *http.Client, gateway netip.Addr) (
|
||||
apiIP netip.Addr, err error,
|
||||
) {
|
||||
@@ -188,7 +182,7 @@ func findAPIIP(ctx context.Context, client *http.Client, gateway netip.Addr) (
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errAPIIPNotFound, errors.Join(errs...))
|
||||
return netip.Addr{}, fmt.Errorf("API IP address not found: %w", errors.Join(errs...))
|
||||
}
|
||||
|
||||
func refreshPIAPortForwardData(ctx context.Context, client, privateIPClient *http.Client,
|
||||
@@ -290,8 +284,6 @@ func packPayload(port uint16, token string, expiration time.Time) (payload strin
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
var errEmptyToken = errors.New("token received is empty")
|
||||
|
||||
func fetchToken(ctx context.Context, client *http.Client,
|
||||
username, password string,
|
||||
) (token string, err error) {
|
||||
@@ -340,7 +332,7 @@ func fetchToken(ctx context.Context, client *http.Client,
|
||||
}
|
||||
|
||||
if result.Token == "" {
|
||||
return "", errEmptyToken
|
||||
return "", errors.New("token received is empty")
|
||||
}
|
||||
return result.Token, nil
|
||||
}
|
||||
@@ -391,7 +383,7 @@ func fetchPortForwardData(ctx context.Context, client *http.Client, apiIP netip.
|
||||
}
|
||||
|
||||
if data.Status != "OK" {
|
||||
return 0, "", expiration, fmt.Errorf("%w: status is: %s", ErrBadResponse, data.Status)
|
||||
return 0, "", expiration, fmt.Errorf("bad response received with status %s", data.Status)
|
||||
}
|
||||
|
||||
port, _, expiration, err = unpackPayload(data.Payload)
|
||||
@@ -401,8 +393,6 @@ func fetchPortForwardData(ctx context.Context, client *http.Client, apiIP netip.
|
||||
return port, data.Signature, expiration, err
|
||||
}
|
||||
|
||||
var ErrBadResponse = errors.New("bad response received")
|
||||
|
||||
func bindPort(ctx context.Context, client *http.Client, apiIPAddress netip.Addr, data piaPortForwardData) (err error) {
|
||||
// Define a timeout since the default client has a large timeout and we don't
|
||||
// want to wait too long.
|
||||
@@ -455,7 +445,7 @@ func bindPort(ctx context.Context, client *http.Client, apiIPAddress netip.Addr,
|
||||
}
|
||||
|
||||
if responseData.Status != "OK" {
|
||||
return fmt.Errorf("%w: %s: %s", ErrBadResponse, responseData.Status, responseData.Message)
|
||||
return fmt.Errorf("bad response received with status %q and message %q", responseData.Status, responseData.Message)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -464,7 +454,7 @@ func bindPort(ctx context.Context, client *http.Client, apiIPAddress netip.Addr,
|
||||
// replaceInErr is used to remove sensitive information from errors.
|
||||
func replaceInErr(err error, substitutions map[string]string) error {
|
||||
s := replaceInString(err.Error(), substitutions)
|
||||
return errors.New(s) //nolint:err113
|
||||
return errors.New(s)
|
||||
}
|
||||
|
||||
// replaceInString is used to remove sensitive information.
|
||||
@@ -475,8 +465,6 @@ func replaceInString(s string, substitutions map[string]string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
var ErrHTTPStatusCodeNotOK = errors.New("HTTP status code is not OK")
|
||||
|
||||
func makeNOKStatusError(response *http.Response, substitutions map[string]string) (err error) {
|
||||
url := response.Request.URL.String()
|
||||
url = replaceInString(url, substitutions)
|
||||
@@ -487,7 +475,6 @@ func makeNOKStatusError(response *http.Response, substitutions map[string]string
|
||||
shortenMessage = strings.ReplaceAll(shortenMessage, " ", " ")
|
||||
shortenMessage = replaceInString(shortenMessage, substitutions)
|
||||
|
||||
return fmt.Errorf("%w: %s: %d %s: response received: %s",
|
||||
ErrHTTPStatusCodeNotOK, url, response.StatusCode,
|
||||
response.Status, shortenMessage)
|
||||
return fmt.Errorf("HTTP status code not OK: %s: %d %s: response received: %s",
|
||||
url, response.StatusCode, response.Status, shortenMessage)
|
||||
}
|
||||
|
||||
@@ -4,15 +4,12 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
var ErrHTTPStatusCodeNotOK = errors.New("HTTP status code not OK")
|
||||
|
||||
type apiData struct {
|
||||
Regions []regionData `json:"regions"`
|
||||
}
|
||||
@@ -50,7 +47,7 @@ func fetchAPI(ctx context.Context, client *http.Client) (
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return data, fmt.Errorf("%w: %d %s", ErrHTTPStatusCodeNotOK,
|
||||
return data, fmt.Errorf("HTTP status code not OK: %d %s",
|
||||
response.StatusCode, response.Status)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,14 +11,11 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"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) (
|
||||
@@ -42,8 +39,7 @@ func (p *Provider) PortForward(ctx context.Context, objects utils.PortForwardObj
|
||||
}
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%w: %d %s", common.ErrHTTPStatusCodeNotOK,
|
||||
response.StatusCode, response.Status)
|
||||
return nil, fmt.Errorf("HTTP status code not OK: %d %s", response.StatusCode, response.Status)
|
||||
}
|
||||
|
||||
defer response.Body.Close()
|
||||
@@ -62,12 +58,12 @@ func (p *Provider) PortForward(ctx context.Context, objects utils.PortForwardObj
|
||||
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)
|
||||
return nil, errors.New("port forwarding not supported for this VPN server")
|
||||
}
|
||||
|
||||
portString := regexPort.FindString(data.Status)
|
||||
if portString == "" {
|
||||
return nil, fmt.Errorf("%w: in status %q", ErrPortForwardedNotFound, data.Status)
|
||||
return nil, fmt.Errorf("port forwarded not found in status %q", data.Status)
|
||||
}
|
||||
|
||||
const base, bitSize = 10, 16
|
||||
|
||||
@@ -22,8 +22,6 @@ func (s roundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
func Test_Provider_PortForward(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
errTest := errors.New("test error")
|
||||
|
||||
canceledCtx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
@@ -59,7 +57,7 @@ func Test_Provider_PortForward(t *testing.T) {
|
||||
assert.Equal(t,
|
||||
"https://connect.pvdatanet.com/v3/Api/port?ip[]=10.10.10.10",
|
||||
r.URL.String())
|
||||
return nil, errTest
|
||||
return nil, errors.New("test error")
|
||||
}),
|
||||
},
|
||||
},
|
||||
@@ -156,7 +154,7 @@ func Test_Provider_PortForward(t *testing.T) {
|
||||
}),
|
||||
},
|
||||
},
|
||||
errMessage: "port forwarded not found: in status \"no port here\"",
|
||||
errMessage: "port forwarded not found in status \"no port here\"",
|
||||
},
|
||||
"port_too_big": {
|
||||
ctx: context.Background(),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -9,12 +8,6 @@ import (
|
||||
|
||||
var trailingNumber = regexp.MustCompile(` [0-9]+$`)
|
||||
|
||||
var (
|
||||
errBadPrefix = errors.New("bad prefix in file name")
|
||||
errBadSuffix = errors.New("bad suffix in file name")
|
||||
errNotEnoughParts = errors.New("not enough parts in file name")
|
||||
)
|
||||
|
||||
func parseFilename(fileName string) (
|
||||
countryCode, city string, err error,
|
||||
) {
|
||||
@@ -22,7 +15,7 @@ func parseFilename(fileName string) (
|
||||
|
||||
const prefix = "PrivateVPN-"
|
||||
if !strings.HasPrefix(fileName, prefix) {
|
||||
return "", "", fmt.Errorf("%w: %s", errBadPrefix, fileName)
|
||||
return "", "", fmt.Errorf("bad prefix in file name %s", fileName)
|
||||
}
|
||||
s := strings.TrimPrefix(fileName, prefix)
|
||||
|
||||
@@ -34,7 +27,7 @@ func parseFilename(fileName string) (
|
||||
case strings.HasSuffix(fileName, udpSuffix):
|
||||
s = strings.TrimSuffix(s, udpSuffix)
|
||||
default:
|
||||
return "", "", fmt.Errorf("%w: %s", errBadSuffix, fileName)
|
||||
return "", "", fmt.Errorf("bad suffix in file name %s", fileName)
|
||||
}
|
||||
|
||||
s = trailingNumber.ReplaceAllString(s, "")
|
||||
@@ -42,7 +35,7 @@ func parseFilename(fileName string) (
|
||||
parts := strings.Split(s, "-")
|
||||
const minParts = 2
|
||||
if len(parts) < minParts {
|
||||
return "", "", fmt.Errorf("%w: %s", errNotEnoughParts, fileName)
|
||||
return "", "", fmt.Errorf("not enough parts in file name %s", fileName)
|
||||
}
|
||||
countryCode, city = parts[0], parts[1]
|
||||
|
||||
|
||||
@@ -12,8 +12,6 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||
)
|
||||
|
||||
var ErrServerPortForwardNotSupported = errors.New("server does not support port forwarding")
|
||||
|
||||
const nonSymmetricPortStart uint16 = 56789
|
||||
|
||||
// PortForward obtains a VPN server side port forwarded from ProtonVPN gateway.
|
||||
@@ -21,7 +19,7 @@ func (p *Provider) PortForward(ctx context.Context, objects utils.PortForwardObj
|
||||
internalToExternalPorts map[uint16]uint16, err error,
|
||||
) {
|
||||
if !objects.CanPortForward {
|
||||
return nil, fmt.Errorf("%w", ErrServerPortForwardNotSupported)
|
||||
return nil, errors.New("server does not support port forwarding")
|
||||
}
|
||||
|
||||
client := natpmp.New()
|
||||
@@ -104,11 +102,6 @@ func checkExternalPorts(logger utils.Logger, udpPort, tcpPort uint16) {
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInternalPortChanged = errors.New("internal port changed")
|
||||
ErrExternalPortChanged = errors.New("external port changed")
|
||||
)
|
||||
|
||||
func (p *Provider) KeepPortForward(ctx context.Context,
|
||||
objects utils.PortForwardObjects,
|
||||
) (err error) {
|
||||
@@ -135,11 +128,10 @@ func (p *Provider) KeepPortForward(ctx context.Context,
|
||||
}
|
||||
checkLifetime(logger, networkProtocol, lifetime, assignedLiftetime)
|
||||
if externalPort != assignedExternalPort {
|
||||
return fmt.Errorf("%w: %d changed to %d",
|
||||
ErrExternalPortChanged, externalPort, assignedExternalPort)
|
||||
return fmt.Errorf("external port changed from %d to %d", externalPort, assignedExternalPort)
|
||||
} else if internalPort != assignedInternalPort {
|
||||
return fmt.Errorf("%w: %d (for external port %d) changed to %d",
|
||||
ErrInternalPortChanged, internalPort, externalPort, assignedInternalPort)
|
||||
return fmt.Errorf("internal port changed from %d (for external port %d) to %d",
|
||||
internalPort, externalPort, assignedInternalPort)
|
||||
}
|
||||
}
|
||||
objects.Logger.Debug(fmt.Sprintf("port forwarded %d maintained", externalPort))
|
||||
|
||||
@@ -60,8 +60,6 @@ func newAPIClient(ctx context.Context, httpClient *http.Client) (client *apiClie
|
||||
}, nil
|
||||
}
|
||||
|
||||
var ErrCodeNotSuccess = errors.New("response code is not success")
|
||||
|
||||
// setHeaders sets the minimal necessary headers for Proton API requests
|
||||
// to succeed without being blocked by their "security" measures.
|
||||
// See for example [getMostRecentStableTag] on how the app version must
|
||||
@@ -126,8 +124,6 @@ func (c *apiClient) authenticate(ctx context.Context, email, password string,
|
||||
return authCookie, nil
|
||||
}
|
||||
|
||||
var ErrSessionIDNotFound = errors.New("session ID not found in cookies")
|
||||
|
||||
func (c *apiClient) getSessionID(ctx context.Context) (sessionID string, err error) {
|
||||
const url = "https://account.proton.me/vpn"
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
@@ -150,11 +146,9 @@ func (c *apiClient) getSessionID(ctx context.Context) (sessionID string, err err
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("%w", ErrSessionIDNotFound)
|
||||
return "", errors.New("session ID not found in cookies")
|
||||
}
|
||||
|
||||
var ErrDataFieldMissing = errors.New("data field missing in response")
|
||||
|
||||
func (c *apiClient) getUnauthSession(ctx context.Context, sessionID string) (
|
||||
tokenType, accessToken, refreshToken, uid string, err error,
|
||||
) {
|
||||
@@ -198,24 +192,22 @@ func (c *apiClient) getUnauthSession(ctx context.Context, sessionID string) (
|
||||
const successCode = 1000
|
||||
switch {
|
||||
case data.Code != successCode:
|
||||
return "", "", "", "", fmt.Errorf("%w: expected %d got %d",
|
||||
ErrCodeNotSuccess, successCode, data.Code)
|
||||
return "", "", "", "", fmt.Errorf("response code %d is not expected success code %d",
|
||||
data.Code, successCode)
|
||||
case data.AccessToken == "":
|
||||
return "", "", "", "", fmt.Errorf("%w: access token is empty", ErrDataFieldMissing)
|
||||
return "", "", "", "", errors.New("access token is empty in response")
|
||||
case data.RefreshToken == "":
|
||||
return "", "", "", "", fmt.Errorf("%w: refresh token is empty", ErrDataFieldMissing)
|
||||
return "", "", "", "", errors.New("refresh token is empty in response")
|
||||
case data.TokenType == "":
|
||||
return "", "", "", "", fmt.Errorf("%w: token type is empty", ErrDataFieldMissing)
|
||||
return "", "", "", "", errors.New("token type is empty in response")
|
||||
case data.UID == "":
|
||||
return "", "", "", "", fmt.Errorf("%w: UID is empty", ErrDataFieldMissing)
|
||||
return "", "", "", "", errors.New("UID is empty in response")
|
||||
}
|
||||
// Ignore Scopes and LocalID fields, we don't use them.
|
||||
|
||||
return data.TokenType, data.AccessToken, data.RefreshToken, data.UID, nil
|
||||
}
|
||||
|
||||
var ErrUIDMismatch = errors.New("UID in response does not match request UID")
|
||||
|
||||
func (c *apiClient) cookieToken(ctx context.Context, sessionID, tokenType, accessToken,
|
||||
refreshToken, uid string,
|
||||
) (cookieToken string, err error) {
|
||||
@@ -282,11 +274,11 @@ func (c *apiClient) cookieToken(ctx context.Context, sessionID, tokenType, acces
|
||||
const successCode = 1000
|
||||
switch {
|
||||
case cookies.Code != successCode:
|
||||
return "", fmt.Errorf("%w: expected %d got %d",
|
||||
ErrCodeNotSuccess, successCode, cookies.Code)
|
||||
return "", fmt.Errorf("response code %d is not expected success code %d",
|
||||
cookies.Code, successCode)
|
||||
case cookies.UID != requestBody.UID:
|
||||
return "", fmt.Errorf("%w: expected %s got %s",
|
||||
ErrUIDMismatch, requestBody.UID, cookies.UID)
|
||||
return "", fmt.Errorf("UID %s in response does not match request UID %s",
|
||||
cookies.UID, requestBody.UID)
|
||||
}
|
||||
// Ignore LocalID and RefreshCounter fields, we don't use them.
|
||||
|
||||
@@ -296,11 +288,9 @@ func (c *apiClient) cookieToken(ctx context.Context, sessionID, tokenType, acces
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("%w", ErrAuthCookieNotFound)
|
||||
return "", errors.New("auth cookie not found")
|
||||
}
|
||||
|
||||
var ErrUsernameDoesNotExist = errors.New("username does not exist")
|
||||
|
||||
// authInfo fetches SRP parameters for the account.
|
||||
func (c *apiClient) authInfo(ctx context.Context, email string, unauthCookie cookie) (
|
||||
username, modulusPGPClearSigned, serverEphemeralBase64, saltBase64, srpSessionHex string,
|
||||
@@ -358,20 +348,20 @@ func (c *apiClient) authInfo(ctx context.Context, email string, unauthCookie coo
|
||||
const successCode = 1000
|
||||
switch {
|
||||
case info.Code != successCode:
|
||||
return "", "", "", "", "", 0, fmt.Errorf("%w: expected %d got %d",
|
||||
ErrCodeNotSuccess, successCode, info.Code)
|
||||
return "", "", "", "", "", 0, fmt.Errorf("response code %d is not expected success code %d",
|
||||
info.Code, successCode)
|
||||
case info.Modulus == "":
|
||||
return "", "", "", "", "", 0, fmt.Errorf("%w: modulus is empty", ErrDataFieldMissing)
|
||||
return "", "", "", "", "", 0, errors.New("modulus is empty in response")
|
||||
case info.ServerEphemeral == "":
|
||||
return "", "", "", "", "", 0, fmt.Errorf("%w: server ephemeral is empty", ErrDataFieldMissing)
|
||||
return "", "", "", "", "", 0, errors.New("server ephemeral is empty in response")
|
||||
case info.Salt == "":
|
||||
return "", "", "", "", "", 0, fmt.Errorf("%w (salt data field is empty)", ErrUsernameDoesNotExist)
|
||||
return "", "", "", "", "", 0, errors.New("salt is empty in response")
|
||||
case info.SRPSession == "":
|
||||
return "", "", "", "", "", 0, fmt.Errorf("%w: SRP session is empty", ErrDataFieldMissing)
|
||||
return "", "", "", "", "", 0, errors.New("SRP session is empty in response")
|
||||
case info.Username == "":
|
||||
return "", "", "", "", "", 0, fmt.Errorf("%w: username is empty", ErrDataFieldMissing)
|
||||
return "", "", "", "", "", 0, errors.New("username is empty in response")
|
||||
case info.Version == nil:
|
||||
return "", "", "", "", "", 0, fmt.Errorf("%w: version is missing", ErrDataFieldMissing)
|
||||
return "", "", "", "", "", 0, errors.New("version is missing in response")
|
||||
}
|
||||
|
||||
version = int(*info.Version) //nolint:gosec
|
||||
@@ -399,13 +389,7 @@ func (c *cookie) String() string {
|
||||
return s
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrServerProofNotValid indicates the M2 from the server didn't match the expected proof.
|
||||
ErrServerProofNotValid = errors.New("server proof from server is not valid")
|
||||
ErrVPNScopeNotFound = errors.New("VPN scope not found in scopes")
|
||||
ErrTwoFANotSupported = errors.New("two factor authentication not supported in this client")
|
||||
ErrAuthCookieNotFound = errors.New("auth cookie not found")
|
||||
)
|
||||
// ErrServerProofNotValid indicates the M2 from the server didn't match the expected proof.
|
||||
|
||||
// auth performs the SRP proof submission (and optionally TOTP) to obtain tokens.
|
||||
func (c *apiClient) auth(ctx context.Context, unauthCookie cookie,
|
||||
@@ -495,22 +479,22 @@ func (c *apiClient) auth(ctx context.Context, unauthCookie cookie,
|
||||
return cookie{}, fmt.Errorf("decoding server proof: %w", err)
|
||||
}
|
||||
if !bytes.Equal(m2, proofs.ExpectedServerProof) {
|
||||
return cookie{}, fmt.Errorf("%w: expected %x got %x",
|
||||
ErrServerProofNotValid, proofs.ExpectedServerProof, m2)
|
||||
return cookie{}, fmt.Errorf("server proof from server %x is not expected proof %x",
|
||||
m2, proofs.ExpectedServerProof)
|
||||
}
|
||||
|
||||
const successCode = 1000
|
||||
switch {
|
||||
case auth.Code != successCode:
|
||||
return cookie{}, fmt.Errorf("%w: expected %d got %d",
|
||||
ErrCodeNotSuccess, successCode, auth.Code)
|
||||
return cookie{}, fmt.Errorf("response code %d is not expected success code %d",
|
||||
auth.Code, successCode)
|
||||
case auth.UID != unauthCookie.uid:
|
||||
return cookie{}, fmt.Errorf("%w: expected %s got %s",
|
||||
ErrUIDMismatch, unauthCookie.uid, auth.UID)
|
||||
return cookie{}, fmt.Errorf("UID %s in response does not match request UID %s",
|
||||
auth.UID, unauthCookie.uid)
|
||||
case auth.TwoFactor != 0:
|
||||
return cookie{}, fmt.Errorf("%w", ErrTwoFANotSupported)
|
||||
return cookie{}, errors.New("two factor authentication not supported in this client")
|
||||
case !slices.Contains(auth.Scopes, "vpn"):
|
||||
return cookie{}, fmt.Errorf("%w: in %v", ErrVPNScopeNotFound, auth.Scopes)
|
||||
return cookie{}, fmt.Errorf("VPN scope not found in scopes %v", auth.Scopes)
|
||||
}
|
||||
|
||||
for _, setCookieHeader := range response.Header.Values("Set-Cookie") {
|
||||
@@ -524,8 +508,7 @@ func (c *apiClient) auth(ctx context.Context, unauthCookie cookie,
|
||||
}
|
||||
}
|
||||
|
||||
return cookie{}, fmt.Errorf("%w: in HTTP headers %s",
|
||||
ErrAuthCookieNotFound, httpHeadersToString(response.Header))
|
||||
return cookie{}, fmt.Errorf("auth cookie not found in HTTP headers %s", httpHeadersToString(response.Header))
|
||||
}
|
||||
|
||||
// generateLettersDigits mimicing Proton's own random string generator:
|
||||
@@ -611,8 +594,6 @@ func (c *apiClient) fetchServers(ctx context.Context, cookie cookie) (
|
||||
return data, nil
|
||||
}
|
||||
|
||||
var ErrHTTPStatusCodeNotOK = errors.New("HTTP status code not OK")
|
||||
|
||||
func buildError(httpCode int, body []byte) error {
|
||||
prettyCode := http.StatusText(httpCode)
|
||||
var protonError struct {
|
||||
@@ -624,8 +605,8 @@ func buildError(httpCode int, body []byte) error {
|
||||
decoder.DisallowUnknownFields()
|
||||
err := decoder.Decode(&protonError)
|
||||
if err != nil || protonError.Error == nil || protonError.Code == nil {
|
||||
return fmt.Errorf("%w: %s: %s",
|
||||
ErrHTTPStatusCodeNotOK, prettyCode, body)
|
||||
return fmt.Errorf("HTTP status code not OK: %s: %s",
|
||||
prettyCode, body)
|
||||
}
|
||||
|
||||
details := make([]string, 0, len(protonError.Details))
|
||||
@@ -633,6 +614,6 @@ func buildError(httpCode int, body []byte) error {
|
||||
details = append(details, fmt.Sprintf("%s: %s", key, value))
|
||||
}
|
||||
|
||||
return fmt.Errorf("%w: %s: %s (code %d with details: %s)",
|
||||
ErrHTTPStatusCodeNotOK, prettyCode, *protonError.Error, *protonError.Code, strings.Join(details, ", "))
|
||||
return fmt.Errorf("HTTP status code not OK: %s: %s (code %d with details: %s)",
|
||||
prettyCode, *protonError.Error, *protonError.Code, strings.Join(details, ", "))
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func getMostRecentStableTag(ctx context.Context, client *http.Client) (version s
|
||||
}
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("%w: %s: %s", ErrHTTPStatusCodeNotOK, response.Status, data)
|
||||
return "", fmt.Errorf("HTTP status code not OK: %s: %s", response.Status, data)
|
||||
}
|
||||
|
||||
var tags []struct {
|
||||
|
||||
@@ -17,7 +17,7 @@ func (u *Updater) FetchServers(ctx context.Context, minServers int) (
|
||||
servers []models.Server, err error,
|
||||
) {
|
||||
if !u.ipFetcher.CanFetchAnyIP() {
|
||||
return nil, fmt.Errorf("%w: %s", common.ErrIPFetcherUnsupported, u.ipFetcher.String())
|
||||
return nil, fmt.Errorf("IP fetcher %s does not support fetching any IP", u.ipFetcher.String())
|
||||
}
|
||||
|
||||
const url = "https://d11a57lttb2ffq.cloudfront.net/heartbleed/router/Recommended-CA2.zip"
|
||||
|
||||
@@ -3,7 +3,6 @@ package updater
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
@@ -38,8 +37,6 @@ func addServersFromAPI(ctx context.Context, client *http.Client,
|
||||
return nil
|
||||
}
|
||||
|
||||
var ErrHTTPStatusCodeNotOK = errors.New("HTTP status code not OK")
|
||||
|
||||
type serverData struct {
|
||||
Host string `json:"connectionName"`
|
||||
Region string `json:"region"`
|
||||
@@ -66,7 +63,7 @@ func fetchAPI(ctx context.Context, client *http.Client) (
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%w: %d %s", ErrHTTPStatusCodeNotOK,
|
||||
return nil, fmt.Errorf("HTTP status code not OK: %d %s",
|
||||
response.StatusCode, response.Status)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/provider/surfshark/servers"
|
||||
)
|
||||
|
||||
var errHostnameNotFound = errors.New("hostname not found in hostname to location mapping")
|
||||
|
||||
func getHostInformation(host string, hostnameToLocation map[string]servers.ServerLocation) (
|
||||
data servers.ServerLocation, err error,
|
||||
) {
|
||||
locationData, ok := hostnameToLocation[host]
|
||||
if !ok {
|
||||
return locationData, fmt.Errorf("%w: %s", errHostnameNotFound, host)
|
||||
return locationData, fmt.Errorf("hostname %s not found in hostname to location mapping", host)
|
||||
}
|
||||
|
||||
return locationData, nil
|
||||
|
||||
@@ -19,8 +19,6 @@ import (
|
||||
func Test_GetConnection(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
errTest := errors.New("test error")
|
||||
|
||||
testCases := map[string]struct {
|
||||
provider string
|
||||
filteredServers []models.Server
|
||||
@@ -30,12 +28,10 @@ func Test_GetConnection(t *testing.T) {
|
||||
ipv6Supported bool
|
||||
randSource rand.Source
|
||||
connection models.Connection
|
||||
errWrapped error
|
||||
errMessage string
|
||||
}{
|
||||
"storage filter error": {
|
||||
filterError: errTest,
|
||||
errWrapped: errTest,
|
||||
filterError: errors.New("test error"),
|
||||
errMessage: "filtering servers: test error",
|
||||
},
|
||||
"server without IPs": {
|
||||
@@ -50,7 +46,6 @@ func Test_GetConnection(t *testing.T) {
|
||||
OpenVPNUDPPort: 1,
|
||||
WireguardPort: 1,
|
||||
},
|
||||
errWrapped: ErrNoConnectionToPickFrom,
|
||||
errMessage: "no connection to pick from",
|
||||
},
|
||||
"OpenVPN server with hostname": {
|
||||
@@ -199,9 +194,10 @@ func Test_GetConnection(t *testing.T) {
|
||||
testCase.randSource)
|
||||
|
||||
assert.Equal(t, testCase.connection, connection)
|
||||
assert.ErrorIs(t, err, testCase.errWrapped)
|
||||
if testCase.errWrapped != nil {
|
||||
if testCase.errMessage != "" {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
@@ -18,10 +17,8 @@ func NewNoFetcher(providerName string) *NoFetcher {
|
||||
}
|
||||
}
|
||||
|
||||
var ErrFetcherNotSupported = errors.New("fetching of servers is not supported")
|
||||
|
||||
func (n *NoFetcher) FetchServers(context.Context, int) (
|
||||
servers []models.Server, err error,
|
||||
) {
|
||||
return nil, fmt.Errorf("%w: for %s", ErrFetcherNotSupported, n.providerName)
|
||||
return nil, fmt.Errorf("fetching of servers is not supported for %s", n.providerName)
|
||||
}
|
||||
|
||||
@@ -11,8 +11,6 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
var ErrNoConnectionToPickFrom = errors.New("no connection to pick from")
|
||||
|
||||
// pickConnection picks a connection from a pool of connections.
|
||||
// If the VPN protocol is Wireguard and the target IP is set,
|
||||
// it finds the connection corresponding to this target IP.
|
||||
@@ -23,7 +21,7 @@ func pickConnection(connections []models.Connection,
|
||||
connection models.Connection, err error,
|
||||
) {
|
||||
if len(connections) == 0 {
|
||||
return connection, ErrNoConnectionToPickFrom
|
||||
return connection, errors.New("no connection to pick from")
|
||||
}
|
||||
|
||||
var targetIP netip.Addr
|
||||
@@ -56,8 +54,6 @@ func pickRandomConnection(connections []models.Connection,
|
||||
return connections[rand.New(source).Intn(len(connections))] //nolint:gosec
|
||||
}
|
||||
|
||||
var errTargetIPNotFound = errors.New("target IP address not found")
|
||||
|
||||
func getTargetIPConnection(connections []models.Connection,
|
||||
targetIP netip.Addr,
|
||||
) (connection models.Connection, err error) {
|
||||
@@ -66,6 +62,6 @@ func getTargetIPConnection(connections []models.Connection,
|
||||
return connection, nil
|
||||
}
|
||||
}
|
||||
return connection, fmt.Errorf("%w: in %d filtered connections",
|
||||
errTargetIPNotFound, len(connections))
|
||||
return connection, fmt.Errorf("target IP address not found: in %d filtered connections",
|
||||
len(connections))
|
||||
}
|
||||
|
||||
@@ -33,8 +33,6 @@ func fetchServers(ctx context.Context, client *http.Client,
|
||||
return servers, nil
|
||||
}
|
||||
|
||||
var ErrHTMLServersDivNotFound = errors.New("HTML servers container div not found")
|
||||
|
||||
const divString = "div"
|
||||
|
||||
func parseHTML(rootNode *html.Node) (servers []models.Server,
|
||||
@@ -43,7 +41,7 @@ func parseHTML(rootNode *html.Node) (servers []models.Server,
|
||||
// Find div container for all servers, searching with BFS.
|
||||
serversDiv := findServersDiv(rootNode)
|
||||
if serversDiv == nil {
|
||||
return nil, nil, htmlutils.WrapError(ErrHTMLServersDivNotFound, rootNode)
|
||||
return nil, nil, htmlutils.WrapError(errors.New("HTML servers container div not found"), rootNode)
|
||||
}
|
||||
|
||||
for countryNode := serversDiv.FirstChild; countryNode != nil; countryNode = countryNode.NextSibling {
|
||||
|
||||
@@ -31,12 +31,10 @@ func Test_fetchServers(t *testing.T) {
|
||||
responseStatus int
|
||||
responseBody io.ReadCloser
|
||||
servers []models.Server
|
||||
errWrapped error
|
||||
errMessage string
|
||||
}{
|
||||
"context canceled": {
|
||||
ctx: canceledCtx,
|
||||
errWrapped: context.Canceled,
|
||||
errMessage: `fetching HTML code: Get "https://www.vpnsecure.me/vpn-locations/": context canceled`,
|
||||
},
|
||||
"success": {
|
||||
@@ -105,9 +103,10 @@ func Test_fetchServers(t *testing.T) {
|
||||
|
||||
servers, err := fetchServers(testCase.ctx, client, warner)
|
||||
|
||||
assert.ErrorIs(t, err, testCase.errWrapped)
|
||||
if testCase.errWrapped != nil {
|
||||
if testCase.errMessage != "" {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, testCase.servers, servers)
|
||||
})
|
||||
@@ -121,12 +120,10 @@ func Test_parseHTML(t *testing.T) {
|
||||
rootNode *html.Node
|
||||
servers []models.Server
|
||||
warnings []string
|
||||
errWrapped error
|
||||
errMessage string
|
||||
}{
|
||||
"empty html": {
|
||||
rootNode: parseTestHTML(t, ""),
|
||||
errWrapped: ErrHTMLServersDivNotFound,
|
||||
errMessage: `HTML servers container div not found: in HTML code: <html><head></head><body></body></html>`,
|
||||
},
|
||||
"test data": {
|
||||
@@ -223,9 +220,10 @@ func Test_parseHTML(t *testing.T) {
|
||||
|
||||
assert.Equal(t, testCase.servers, servers)
|
||||
assert.Equal(t, testCase.warnings, warnings)
|
||||
assert.ErrorIs(t, err, testCase.errWrapped)
|
||||
if testCase.errWrapped != nil {
|
||||
if testCase.errMessage != "" {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var errNotOvpnExt = errors.New("filename does not have the openvpn file extension")
|
||||
|
||||
func parseFilename(fileName string) (
|
||||
region string, err error,
|
||||
) {
|
||||
const suffix = ".ovpn"
|
||||
if !strings.HasSuffix(fileName, suffix) {
|
||||
return "", fmt.Errorf("%w: %s", errNotOvpnExt, fileName)
|
||||
return "", fmt.Errorf("filename does not have the openvpn file extension: %s", fileName)
|
||||
}
|
||||
|
||||
region = strings.TrimSuffix(fileName, suffix)
|
||||
|
||||
@@ -22,21 +22,17 @@ func Test_Provider_GetConnection(t *testing.T) {
|
||||
|
||||
const provider = providers.Windscribe
|
||||
|
||||
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
|
||||
panicMessage string
|
||||
}{
|
||||
"error": {
|
||||
storageErr: errTest,
|
||||
errWrapped: errTest,
|
||||
storageErr: errors.New("test error"),
|
||||
errMessage: "filtering servers: test error",
|
||||
},
|
||||
"default OpenVPN TCP port": {
|
||||
@@ -111,9 +107,10 @@ func Test_Provider_GetConnection(t *testing.T) {
|
||||
|
||||
connection, err := provider.GetConnection(testCase.selection, testCase.ipv6Supported)
|
||||
|
||||
assert.ErrorIs(t, err, testCase.errWrapped)
|
||||
if testCase.errWrapped != nil {
|
||||
if testCase.errMessage != "" {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
assert.Equal(t, testCase.connection, connection)
|
||||
|
||||
@@ -3,7 +3,6 @@ package updater
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
@@ -11,8 +10,6 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var ErrHTTPStatusCodeNotOK = errors.New("HTTP status code not OK")
|
||||
|
||||
type apiData struct {
|
||||
Data []regionData `json:"data"`
|
||||
}
|
||||
@@ -55,7 +52,7 @@ func fetchAPI(ctx context.Context, client *http.Client) (
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return data, fmt.Errorf("%w: %d %s", ErrHTTPStatusCodeNotOK,
|
||||
return data, fmt.Errorf("HTTP status code not OK: %d %s",
|
||||
response.StatusCode, response.Status)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"sort"
|
||||
@@ -12,8 +11,6 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/provider/common"
|
||||
)
|
||||
|
||||
var ErrNoWireguardKey = errors.New("no wireguard public key found")
|
||||
|
||||
func (u *Updater) FetchServers(ctx context.Context, minServers int) (
|
||||
servers []models.Server, err error,
|
||||
) {
|
||||
@@ -51,7 +48,7 @@ func (u *Updater) FetchServers(ctx context.Context, minServers int) (
|
||||
if !node.IP3.IsValid() { // Wireguard + Stealth
|
||||
continue
|
||||
} else if wgPubKey == "" {
|
||||
return nil, fmt.Errorf("%w: for node %s", ErrNoWireguardKey, node.Hostname)
|
||||
return nil, fmt.Errorf("no wireguard public key found: for node %s", node.Hostname)
|
||||
}
|
||||
|
||||
server.VPN = vpn.Wireguard
|
||||
|
||||
Reference in New Issue
Block a user