mirror of
https://github.com/Alexey71/opera-proxy.git
synced 2026-05-15 07:01:00 +00:00
cleanup
This commit is contained in:
+52
-17
@@ -2,8 +2,26 @@ name: build
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
workflow_dispatch:
|
||||||
- 'v*'
|
inputs:
|
||||||
|
create_release:
|
||||||
|
description: 'Create release? (yes/no)'
|
||||||
|
required: true
|
||||||
|
default: 'no'
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- 'yes'
|
||||||
|
- 'no'
|
||||||
|
tag_name:
|
||||||
|
description: 'Release tag name (e.g., v1.2.3)'
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
type: string
|
||||||
|
release_name:
|
||||||
|
description: 'Release name (optional, defaults to tag name)'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: ''
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -12,31 +30,48 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
-
|
- name: Checkout
|
||||||
name: Checkout
|
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
-
|
|
||||||
name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v6
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version: 'stable'
|
go-version: "stable"
|
||||||
-
|
|
||||||
name: Read tag
|
- name: Build
|
||||||
id: tag
|
|
||||||
run: echo "tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
|
||||||
-
|
|
||||||
name: Build
|
|
||||||
run: >-
|
run: >-
|
||||||
make -j $(nproc) allplus
|
make -j $(nproc) allplus
|
||||||
NDK_CC_ARM64="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang"
|
NDK_CC_ARM64="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang"
|
||||||
NDK_CC_ARM="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi21-clang"
|
NDK_CC_ARM="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi21-clang"
|
||||||
VERSION=${{steps.tag.outputs.tag}}
|
VERSION=v1.17.1
|
||||||
-
|
|
||||||
name: Release
|
- name: Upload artifact for arm64
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
path: bin/opera-proxy.android-arm64
|
||||||
|
archive: false
|
||||||
|
|
||||||
|
- name: Upload artifact for win amd64
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
path: bin/opera-proxy.windows-amd64.exe
|
||||||
|
archive: false
|
||||||
|
|
||||||
|
- name: Upload all artifact
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
path: bin/*
|
||||||
|
|
||||||
|
- name: Create Release
|
||||||
|
if: github.event_name == 'workflow_dispatch' && github.event.inputs.create_release == 'yes'
|
||||||
uses: softprops/action-gh-release@v3
|
uses: softprops/action-gh-release@v3
|
||||||
with:
|
with:
|
||||||
files: bin/*
|
tag_name: ${{ github.event.inputs.tag_name }}
|
||||||
|
name: ${{ github.event.inputs.release_name || github.event.inputs.tag_name }}
|
||||||
|
files: bin/**/*
|
||||||
fail_on_unmatched_files: true
|
fail_on_unmatched_files: true
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
|
draft: false
|
||||||
|
prerelease: false
|
||||||
@@ -120,9 +120,6 @@ type CLIArgs struct {
|
|||||||
proxy string
|
proxy string
|
||||||
apiLogin string
|
apiLogin string
|
||||||
apiPassword string
|
apiPassword string
|
||||||
// randomAPICreds: when true, ignore --api-login/--api-password and
|
|
||||||
// generate a fresh cryptographically-random credential pair on every run.
|
|
||||||
randomAPICreds bool
|
|
||||||
apiAddress string
|
apiAddress string
|
||||||
apiClientType string
|
apiClientType string
|
||||||
apiClientVersion string
|
apiClientVersion string
|
||||||
@@ -168,7 +165,7 @@ func parse_args() *CLIArgs {
|
|||||||
flag.IntVar(&args.verbosity, "verbosity", 20, "logging verbosity "+
|
flag.IntVar(&args.verbosity, "verbosity", 20, "logging verbosity "+
|
||||||
"(10 - debug, 20 - info, 30 - warning, 40 - error, 50 - critical)")
|
"(10 - debug, 20 - info, 30 - warning, 40 - error, 50 - critical)")
|
||||||
flag.DurationVar(&args.timeout, "timeout", DEFAULT_TIMEOUT,
|
flag.DurationVar(&args.timeout, "timeout", DEFAULT_TIMEOUT,
|
||||||
"timeout for network operations (default raised to 30s for better API reliability)")
|
"timeout for network operations")
|
||||||
flag.BoolVar(&args.showVersion, "version", false, "show program version and exit")
|
flag.BoolVar(&args.showVersion, "version", false, "show program version and exit")
|
||||||
flag.StringVar(&args.proxy, "proxy", "", "sets base proxy to use for all dial-outs. "+
|
flag.StringVar(&args.proxy, "proxy", "", "sets base proxy to use for all dial-outs. "+
|
||||||
"Format: <http|https|socks5|socks5h>://[login:password@]host[:port] "+
|
"Format: <http|https|socks5|socks5h>://[login:password@]host[:port] "+
|
||||||
@@ -176,13 +173,8 @@ func parse_args() *CLIArgs {
|
|||||||
flag.StringVar(&args.apiClientVersion, "api-client-version", se.DefaultSESettings.ClientVersion, "client version reported to SurfEasy API")
|
flag.StringVar(&args.apiClientVersion, "api-client-version", se.DefaultSESettings.ClientVersion, "client version reported to SurfEasy API")
|
||||||
flag.StringVar(&args.apiClientType, "api-client-type", se.DefaultSESettings.ClientType, "client type reported to SurfEasy API")
|
flag.StringVar(&args.apiClientType, "api-client-type", se.DefaultSESettings.ClientType, "client type reported to SurfEasy API")
|
||||||
flag.StringVar(&args.apiUserAgent, "api-user-agent", se.DefaultSESettings.UserAgent, "user agent reported to SurfEasy API")
|
flag.StringVar(&args.apiUserAgent, "api-user-agent", se.DefaultSESettings.UserAgent, "user agent reported to SurfEasy API")
|
||||||
flag.StringVar(&args.apiLogin, "api-login", "se0316",
|
flag.StringVar(&args.apiLogin, "api-login", "se0316", "SurfEasy API login")
|
||||||
"SurfEasy API login (ignored when -random-api-creds is set)")
|
flag.StringVar(&args.apiPassword, "api-password", "SILrMEPBmJuhomxWkfm3JalqHX2Eheg1YhlEZiMh8II", "SurfEasy API password")
|
||||||
flag.StringVar(&args.apiPassword, "api-password", "SILrMEPBmJuhomxWkfm3JalqHX2Eheg1YhlEZiMh8II",
|
|
||||||
"SurfEasy API password (ignored when -random-api-creds is set)")
|
|
||||||
flag.BoolVar(&args.randomAPICreds, "random-api-creds", false,
|
|
||||||
"generate a random API login/password pair on every run instead of using -api-login/-api-password; "+
|
|
||||||
"reduces fingerprinting and avoids credential-reuse bans")
|
|
||||||
flag.StringVar(&args.apiAddress, "api-address", "", fmt.Sprintf("override IP address of %s", API_DOMAIN))
|
flag.StringVar(&args.apiAddress, "api-address", "", fmt.Sprintf("override IP address of %s", API_DOMAIN))
|
||||||
flag.StringVar(&args.apiProxy, "api-proxy", "", "additional proxy server used to access SurfEasy API")
|
flag.StringVar(&args.apiProxy, "api-proxy", "", "additional proxy server used to access SurfEasy API")
|
||||||
flag.Var(args.bootstrapDNS, "bootstrap-dns",
|
flag.Var(args.bootstrapDNS, "bootstrap-dns",
|
||||||
@@ -198,7 +190,7 @@ func parse_args() *CLIArgs {
|
|||||||
flag.StringVar(&args.overrideProxyAddress, "override-proxy-address", "", "use fixed proxy address instead of server address returned by SurfEasy API")
|
flag.StringVar(&args.overrideProxyAddress, "override-proxy-address", "", "use fixed proxy address instead of server address returned by SurfEasy API")
|
||||||
flag.Var(&args.serverSelection, "server-selection", "server selection policy (first/random/fastest)")
|
flag.Var(&args.serverSelection, "server-selection", "server selection policy (first/random/fastest)")
|
||||||
flag.DurationVar(&args.serverSelectionTimeout, "server-selection-timeout", DEFAULT_SERVER_SELECTION_TIMEOUT,
|
flag.DurationVar(&args.serverSelectionTimeout, "server-selection-timeout", DEFAULT_SERVER_SELECTION_TIMEOUT,
|
||||||
"timeout given for server selection function to produce result (default raised to 60s)")
|
"timeout given for server selection function to produce result")
|
||||||
flag.StringVar(&args.serverSelectionTestURL, "server-selection-test-url",
|
flag.StringVar(&args.serverSelectionTestURL, "server-selection-test-url",
|
||||||
"https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js",
|
"https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js",
|
||||||
"URL used for download benchmark by fastest server selection policy")
|
"URL used for download benchmark by fastest server selection policy")
|
||||||
@@ -241,6 +233,40 @@ func buildAPITransport(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// buildCAPool constructs the x509 cert pool used for all TLS verification.
|
||||||
|
// When -cafile is given, only that file is loaded (useful for custom/corporate CAs).
|
||||||
|
// Otherwise the bundled Mozilla NSS root store is used, which includes all
|
||||||
|
// major roots and supports AddCertWithConstraint for name-constrained CAs —
|
||||||
|
// strictly better than a plain PEM file.
|
||||||
|
func buildCAPool(caFile string, logger *clog.CondLogger) (*x509.CertPool, int) {
|
||||||
|
pool := x509.NewCertPool()
|
||||||
|
if caFile != "" {
|
||||||
|
certs, err := os.ReadFile(caFile)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Can't load CA file: %v", err)
|
||||||
|
return nil, 15
|
||||||
|
}
|
||||||
|
if ok := pool.AppendCertsFromPEM(certs); !ok {
|
||||||
|
logger.Error("Can't load certificates from CA file")
|
||||||
|
return nil, 15
|
||||||
|
}
|
||||||
|
return pool, 0
|
||||||
|
}
|
||||||
|
for c := range bundle.Roots() {
|
||||||
|
cert, err := x509.ParseCertificate(c.Certificate)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Unable to parse bundled certificate: %v", err)
|
||||||
|
return nil, 15
|
||||||
|
}
|
||||||
|
if c.Constraint == nil {
|
||||||
|
pool.AddCert(cert)
|
||||||
|
} else {
|
||||||
|
pool.AddCertWithConstraint(cert, c.Constraint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pool, 0
|
||||||
|
}
|
||||||
|
|
||||||
func run() int {
|
func run() int {
|
||||||
args := parse_args()
|
args := parse_args()
|
||||||
if args.showVersion {
|
if args.showVersion {
|
||||||
@@ -267,31 +293,9 @@ func run() int {
|
|||||||
KeepAlive: 30 * time.Second,
|
KeepAlive: 30 * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
caPool := x509.NewCertPool()
|
caPool, exitCode := buildCAPool(args.caFile, mainLogger)
|
||||||
if args.caFile != "" {
|
if exitCode != 0 {
|
||||||
// os.ReadFile replaces deprecated ioutil.ReadFile
|
return exitCode
|
||||||
certs, err := os.ReadFile(args.caFile)
|
|
||||||
if err != nil {
|
|
||||||
mainLogger.Error("Can't load CA file: %v", err)
|
|
||||||
return 15
|
|
||||||
}
|
|
||||||
if ok := caPool.AppendCertsFromPEM(certs); !ok {
|
|
||||||
mainLogger.Error("Can't load certificates from CA file")
|
|
||||||
return 15
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for c := range bundle.Roots() {
|
|
||||||
cert, err := x509.ParseCertificate(c.Certificate)
|
|
||||||
if err != nil {
|
|
||||||
mainLogger.Error("Unable to parse bundled certificate: %v", err)
|
|
||||||
return 15
|
|
||||||
}
|
|
||||||
if c.Constraint == nil {
|
|
||||||
caPool.AddCert(cert)
|
|
||||||
} else {
|
|
||||||
caPool.AddCertWithConstraint(cert, c.Constraint)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
xproxy.RegisterDialerType("http", proxyFromURLWrapper)
|
xproxy.RegisterDialerType("http", proxyFromURLWrapper)
|
||||||
@@ -314,12 +318,12 @@ func run() int {
|
|||||||
if args.apiProxy != "" {
|
if args.apiProxy != "" {
|
||||||
apiProxyURL, err := url.Parse(args.apiProxy)
|
apiProxyURL, err := url.Parse(args.apiProxy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mainLogger.Critical("Unable to parse base proxy URL: %v", err)
|
mainLogger.Critical("Unable to parse api-proxy URL: %v", err)
|
||||||
return 6
|
return 6
|
||||||
}
|
}
|
||||||
pxDialer, err := xproxy.FromURL(apiProxyURL, seclientDialer)
|
pxDialer, err := xproxy.FromURL(apiProxyURL, seclientDialer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mainLogger.Critical("Unable to instantiate base proxy dialer: %v", err)
|
mainLogger.Critical("Unable to instantiate api-proxy dialer: %v", err)
|
||||||
return 7
|
return 7
|
||||||
}
|
}
|
||||||
seclientDialer = pxDialer.(dialer.ContextDialer)
|
seclientDialer = pxDialer.(dialer.ContextDialer)
|
||||||
@@ -336,27 +340,16 @@ func run() int {
|
|||||||
seclientDialer = dialer.NewResolvingDialer(res, seclientDialer)
|
seclientDialer = dialer.NewResolvingDialer(res, seclientDialer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dialing w/o SNI, receiving self-signed certificate, so skip verification.
|
// TLS config for the API connection: SNI suppressed (or faked), cert
|
||||||
// Either way we validate the certificate of the actual proxy server.
|
// verification is skipped at the TLS layer because the API endpoint uses
|
||||||
|
// a self-signed cert — actual peer verification happens in VerifyConnection
|
||||||
|
// inside ProxyDialer for the proxy connections.
|
||||||
tlsConfig := &tls.Config{
|
tlsConfig := &tls.Config{
|
||||||
ServerName: args.fakeSNI,
|
ServerName: args.fakeSNI,
|
||||||
InsecureSkipVerify: true,
|
InsecureSkipVerify: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve which API credentials to use.
|
seclient, err := se.NewSEClient(args.apiLogin, args.apiPassword, buildAPITransport(
|
||||||
apiLogin := args.apiLogin
|
|
||||||
apiPassword := args.apiPassword
|
|
||||||
if args.randomAPICreds {
|
|
||||||
var err error
|
|
||||||
apiLogin, apiPassword, err = se.GenerateRandomAPICreds()
|
|
||||||
if err != nil {
|
|
||||||
mainLogger.Critical("Failed to generate random API credentials: %v", err)
|
|
||||||
return 17
|
|
||||||
}
|
|
||||||
mainLogger.Info("Using randomly generated API credentials (login prefix: %.8s...)", apiLogin)
|
|
||||||
}
|
|
||||||
|
|
||||||
seclient, err := se.NewSEClient(apiLogin, apiPassword, buildAPITransport(
|
|
||||||
seclientDialer.DialContext,
|
seclientDialer.DialContext,
|
||||||
func(ctx context.Context, network, addr string) (net.Conn, error) {
|
func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
conn, err := seclientDialer.DialContext(ctx, network, addr)
|
conn, err := seclientDialer.DialContext(ctx, network, addr)
|
||||||
@@ -460,9 +453,7 @@ func run() int {
|
|||||||
ss = dialer.NewFastestServerSelectionFunc(
|
ss = dialer.NewFastestServerSelectionFunc(
|
||||||
args.serverSelectionTestURL,
|
args.serverSelectionTestURL,
|
||||||
args.serverSelectionDLLimit,
|
args.serverSelectionDLLimit,
|
||||||
&tls.Config{
|
&tls.Config{RootCAs: caPool},
|
||||||
RootCAs: caPool,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
default:
|
default:
|
||||||
panic("unhandled server selection value got past parsing")
|
panic("unhandled server selection value got past parsing")
|
||||||
@@ -497,8 +488,7 @@ func run() int {
|
|||||||
mainLogger.Info("Refreshing login...")
|
mainLogger.Info("Refreshing login...")
|
||||||
reqCtx, cl := context.WithTimeout(ctx, args.timeout)
|
reqCtx, cl := context.WithTimeout(ctx, args.timeout)
|
||||||
defer cl()
|
defer cl()
|
||||||
err := seclient.Login(reqCtx)
|
if err := seclient.Login(reqCtx); err != nil {
|
||||||
if err != nil {
|
|
||||||
mainLogger.Error("Login refresh failed: %v", err)
|
mainLogger.Error("Login refresh failed: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -507,8 +497,7 @@ func run() int {
|
|||||||
mainLogger.Info("Refreshing device password...")
|
mainLogger.Info("Refreshing device password...")
|
||||||
reqCtx, cl = context.WithTimeout(ctx, args.timeout)
|
reqCtx, cl = context.WithTimeout(ctx, args.timeout)
|
||||||
defer cl()
|
defer cl()
|
||||||
err = seclient.DeviceGeneratePassword(reqCtx)
|
if err := seclient.DeviceGeneratePassword(reqCtx); err != nil {
|
||||||
if err != nil {
|
|
||||||
mainLogger.Error("Device password refresh failed: %v", err)
|
mainLogger.Error("Device password refresh failed: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -547,7 +536,6 @@ func printCountries(try func(string, func() error) error, logger *clog.CondLogge
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 11
|
return 11
|
||||||
}
|
}
|
||||||
|
|
||||||
wr := csv.NewWriter(os.Stdout)
|
wr := csv.NewWriter(os.Stdout)
|
||||||
defer wr.Flush()
|
defer wr.Flush()
|
||||||
wr.Write([]string{"country code", "country name"})
|
wr.Write([]string{"country code", "country name"})
|
||||||
@@ -591,10 +579,7 @@ func dpExport(ips []se.SEIPEntry, seclient *se.SEClient, sni string) int {
|
|||||||
u := url.URL{
|
u := url.URL{
|
||||||
Scheme: "https",
|
Scheme: "https",
|
||||||
User: creds,
|
User: creds,
|
||||||
Host: net.JoinHostPort(
|
Host: net.JoinHostPort(ip.IP, strconv.Itoa(int(ip.Ports[0]))),
|
||||||
ip.IP,
|
|
||||||
strconv.Itoa(int(ip.Ports[0])),
|
|
||||||
),
|
|
||||||
RawQuery: url.Values{
|
RawQuery: url.Values{
|
||||||
"sni": []string{sni},
|
"sni": []string{sni},
|
||||||
"peername": []string{fmt.Sprintf("%s%d.%s", strings.ToLower(ip.Geo.CountryCode), i, PROXY_SUFFIX)},
|
"peername": []string{fmt.Sprintf("%s%d.%s", strings.ToLower(ip.Geo.CountryCode), i, PROXY_SUFFIX)},
|
||||||
|
|||||||
+25
-72
@@ -19,10 +19,6 @@ const (
|
|||||||
ANON_PASSWORD_BYTES = 20
|
ANON_PASSWORD_BYTES = 20
|
||||||
DEVICE_ID_BYTES = 20
|
DEVICE_ID_BYTES = 20
|
||||||
READ_LIMIT int64 = 128 * 1024
|
READ_LIMIT int64 = 128 * 1024
|
||||||
|
|
||||||
// Byte length for randomly generated API credential components.
|
|
||||||
RANDOM_API_LOGIN_BYTES = 16
|
|
||||||
RANDOM_API_PASSWORD_BYTES = 24
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SEEndpoints struct {
|
type SEEndpoints struct {
|
||||||
@@ -76,24 +72,10 @@ type SEClient struct {
|
|||||||
|
|
||||||
type StrKV map[string]string
|
type StrKV map[string]string
|
||||||
|
|
||||||
// GenerateRandomAPICreds returns a cryptographically-random (login, password) pair
|
// NewSEClient instantiates a SurfEasy client.
|
||||||
// suitable for use as SurfEasy digest-auth credentials. Call this instead of
|
// apiUsername/apiSecret are the application-level Digest Auth credentials
|
||||||
// using the hardcoded defaults when -random-api-creds is set.
|
// embedded in every Opera client — they are NOT per-user and must not be randomised.
|
||||||
func GenerateRandomAPICreds() (login, password string, err error) {
|
// transport may be nil (uses http.DefaultTransport).
|
||||||
rng := rand.New(RandomSource)
|
|
||||||
login, err = randomCapitalHexString(rng, RANDOM_API_LOGIN_BYTES)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", fmt.Errorf("generate random api login: %w", err)
|
|
||||||
}
|
|
||||||
password, err = randomCapitalHexString(rng, RANDOM_API_PASSWORD_BYTES)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", fmt.Errorf("generate random api password: %w", err)
|
|
||||||
}
|
|
||||||
return login, password, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSEClient instantiates a SurfEasy client with default settings and given API keys.
|
|
||||||
// Optional transport parameter allows overriding the HTTP transport used for API calls.
|
|
||||||
func NewSEClient(apiUsername, apiSecret string, transport http.RoundTripper) (*SEClient, error) {
|
func NewSEClient(apiUsername, apiSecret string, transport http.RoundTripper) (*SEClient, error) {
|
||||||
if transport == nil {
|
if transport == nil {
|
||||||
transport = http.DefaultTransport
|
transport = http.DefaultTransport
|
||||||
@@ -101,7 +83,7 @@ func NewSEClient(apiUsername, apiSecret string, transport http.RoundTripper) (*S
|
|||||||
|
|
||||||
rng := rand.New(RandomSource)
|
rng := rand.New(RandomSource)
|
||||||
|
|
||||||
device_id, err := randomCapitalHexString(rng, DEVICE_ID_BYTES)
|
deviceID, err := randomCapitalHexString(rng, DEVICE_ID_BYTES)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -111,17 +93,15 @@ func NewSEClient(apiUsername, apiSecret string, transport http.RoundTripper) (*S
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
res := &SEClient{
|
return &SEClient{
|
||||||
httpClient: &http.Client{
|
httpClient: &http.Client{
|
||||||
Jar: jar,
|
Jar: jar,
|
||||||
Transport: dac.NewDigestTransport(apiUsername, apiSecret, transport),
|
Transport: dac.NewDigestTransport(apiUsername, apiSecret, transport),
|
||||||
},
|
},
|
||||||
Settings: DefaultSESettings,
|
Settings: DefaultSESettings,
|
||||||
rng: rng,
|
rng: rng,
|
||||||
DeviceID: device_id,
|
DeviceID: deviceID,
|
||||||
}
|
}, nil
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *SEClient) ResetCookies() error {
|
func (c *SEClient) ResetCookies() error {
|
||||||
@@ -142,7 +122,8 @@ func (c *SEClient) AnonRegister(ctx context.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// Each run generates a fresh random subscriber identity — this is the
|
||||||
|
// actual anonymisation layer. The API-level credentials above are fixed.
|
||||||
c.SubscriberEmail = fmt.Sprintf("%s@%s.best.vpn", localPart, c.Settings.ClientType)
|
c.SubscriberEmail = fmt.Sprintf("%s@%s.best.vpn", localPart, c.Settings.ClientType)
|
||||||
c.SubscriberPassword = capitalHexSHA1(c.SubscriberEmail)
|
c.SubscriberPassword = capitalHexSHA1(c.SubscriberEmail)
|
||||||
|
|
||||||
@@ -156,22 +137,19 @@ func (c *SEClient) Register(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *SEClient) register(ctx context.Context) error {
|
func (c *SEClient) register(ctx context.Context) error {
|
||||||
err := c.resetCookies()
|
if err := c.resetCookies(); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var regRes SERegisterSubscriberResponse
|
var regRes SERegisterSubscriberResponse
|
||||||
err = c.rpcCall(ctx, c.Settings.Endpoints.RegisterSubscriber, StrKV{
|
err := c.rpcCall(ctx, c.Settings.Endpoints.RegisterSubscriber, StrKV{
|
||||||
"email": c.SubscriberEmail,
|
"email": c.SubscriberEmail,
|
||||||
"password": c.SubscriberPassword,
|
"password": c.SubscriberPassword,
|
||||||
}, ®Res)
|
}, ®Res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if regRes.Status.Code != SE_STATUS_OK {
|
if regRes.Status.Code != SE_STATUS_OK {
|
||||||
return fmt.Errorf("API responded with error message: code=%d, msg=\"%s\"",
|
return fmt.Errorf("API responded with error message: code=%d, msg=%q",
|
||||||
regRes.Status.Code, regRes.Status.Message)
|
regRes.Status.Code, regRes.Status.Message)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -190,12 +168,10 @@ func (c *SEClient) RegisterDevice(ctx context.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if regRes.Status.Code != SE_STATUS_OK {
|
if regRes.Status.Code != SE_STATUS_OK {
|
||||||
return fmt.Errorf("API responded with error message: code=%d, msg=\"%s\"",
|
return fmt.Errorf("API responded with error message: code=%d, msg=%q",
|
||||||
regRes.Status.Code, regRes.Status.Message)
|
regRes.Status.Code, regRes.Status.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.AssignedDeviceID = regRes.Data.DeviceID
|
c.AssignedDeviceID = regRes.Data.DeviceID
|
||||||
c.DevicePassword = regRes.Data.DevicePassword
|
c.DevicePassword = regRes.Data.DevicePassword
|
||||||
c.AssignedDeviceIDHash = capitalHexSHA1(regRes.Data.DeviceID)
|
c.AssignedDeviceIDHash = capitalHexSHA1(regRes.Data.DeviceID)
|
||||||
@@ -213,12 +189,10 @@ func (c *SEClient) GeoList(ctx context.Context) ([]SEGeoEntry, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if geoListRes.Status.Code != SE_STATUS_OK {
|
if geoListRes.Status.Code != SE_STATUS_OK {
|
||||||
return nil, fmt.Errorf("API responded with error message: code=%d, msg=\"%s\"",
|
return nil, fmt.Errorf("API responded with error message: code=%d, msg=%q",
|
||||||
geoListRes.Status.Code, geoListRes.Status.Message)
|
geoListRes.Status.Code, geoListRes.Status.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
return geoListRes.Data.Geos, nil
|
return geoListRes.Data.Geos, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,12 +208,10 @@ func (c *SEClient) Discover(ctx context.Context, requestedGeo string) ([]SEIPEnt
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if discoverRes.Status.Code != SE_STATUS_OK {
|
if discoverRes.Status.Code != SE_STATUS_OK {
|
||||||
return nil, fmt.Errorf("API responded with error message: code=%d, msg=\"%s\"",
|
return nil, fmt.Errorf("API responded with error message: code=%d, msg=%q",
|
||||||
discoverRes.Status.Code, discoverRes.Status.Message)
|
discoverRes.Status.Code, discoverRes.Status.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
return discoverRes.Data.IPs, nil
|
return discoverRes.Data.IPs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,13 +219,11 @@ func (c *SEClient) Login(ctx context.Context) error {
|
|||||||
c.Mux.Lock()
|
c.Mux.Lock()
|
||||||
defer c.Mux.Unlock()
|
defer c.Mux.Unlock()
|
||||||
|
|
||||||
err := c.resetCookies()
|
if err := c.resetCookies(); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var loginRes SESubscriberLoginResponse
|
var loginRes SESubscriberLoginResponse
|
||||||
err = c.rpcCall(ctx, c.Settings.Endpoints.SubscriberLogin, StrKV{
|
err := c.rpcCall(ctx, c.Settings.Endpoints.SubscriberLogin, StrKV{
|
||||||
"login": c.SubscriberEmail,
|
"login": c.SubscriberEmail,
|
||||||
"password": c.SubscriberPassword,
|
"password": c.SubscriberPassword,
|
||||||
"client_type": c.Settings.ClientType,
|
"client_type": c.Settings.ClientType,
|
||||||
@@ -261,9 +231,8 @@ func (c *SEClient) Login(ctx context.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if loginRes.Status.Code != SE_STATUS_OK {
|
if loginRes.Status.Code != SE_STATUS_OK {
|
||||||
return fmt.Errorf("API responded with error message: code=%d, msg=\"%s\"",
|
return fmt.Errorf("API responded with error message: code=%d, msg=%q",
|
||||||
loginRes.Status.Code, loginRes.Status.Message)
|
loginRes.Status.Code, loginRes.Status.Message)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -280,12 +249,10 @@ func (c *SEClient) DeviceGeneratePassword(ctx context.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if genRes.Status.Code != SE_STATUS_OK {
|
if genRes.Status.Code != SE_STATUS_OK {
|
||||||
return fmt.Errorf("API responded with error message: code=%d, msg=\"%s\"",
|
return fmt.Errorf("API responded with error message: code=%d, msg=%q",
|
||||||
genRes.Status.Code, genRes.Status.Message)
|
genRes.Status.Code, genRes.Status.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.DevicePassword = genRes.Data.DevicePassword
|
c.DevicePassword = genRes.Data.DevicePassword
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -293,7 +260,6 @@ func (c *SEClient) DeviceGeneratePassword(ctx context.Context) error {
|
|||||||
func (c *SEClient) GetProxyCredentials() (string, string) {
|
func (c *SEClient) GetProxyCredentials() (string, string) {
|
||||||
c.Mux.Lock()
|
c.Mux.Lock()
|
||||||
defer c.Mux.Unlock()
|
defer c.Mux.Unlock()
|
||||||
|
|
||||||
return c.AssignedDeviceIDHash, c.DevicePassword
|
return c.AssignedDeviceIDHash, c.DevicePassword
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,7 +272,6 @@ func (c *SEClient) populateRequest(req *http.Request) {
|
|||||||
func (c *SEClient) RpcCall(ctx context.Context, endpoint string, params map[string]string, res interface{}) error {
|
func (c *SEClient) RpcCall(ctx context.Context, endpoint string, params map[string]string, res interface{}) error {
|
||||||
c.Mux.Lock()
|
c.Mux.Lock()
|
||||||
defer c.Mux.Unlock()
|
defer c.Mux.Unlock()
|
||||||
|
|
||||||
return c.rpcCall(ctx, endpoint, params, res)
|
return c.rpcCall(ctx, endpoint, params, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,12 +280,8 @@ func (c *SEClient) rpcCall(ctx context.Context, endpoint string, params map[stri
|
|||||||
for k, v := range params {
|
for k, v := range params {
|
||||||
input[k] = []string{v}
|
input[k] = []string{v}
|
||||||
}
|
}
|
||||||
req, err := http.NewRequestWithContext(
|
req, err := http.NewRequestWithContext(ctx, "POST", endpoint,
|
||||||
ctx,
|
strings.NewReader(input.Encode()))
|
||||||
"POST",
|
|
||||||
endpoint,
|
|
||||||
strings.NewReader(input.Encode()),
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -334,25 +295,17 @@ func (c *SEClient) rpcCall(ctx context.Context, endpoint string, params map[stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
cleanupBody(resp.Body)
|
||||||
return fmt.Errorf("bad http status: %s, headers: %#v", resp.Status, resp.Header)
|
return fmt.Errorf("bad http status: %s, headers: %#v", resp.Status, resp.Header)
|
||||||
}
|
}
|
||||||
|
|
||||||
decoder := json.NewDecoder(resp.Body)
|
err = json.NewDecoder(resp.Body).Decode(res)
|
||||||
err = decoder.Decode(res)
|
|
||||||
cleanupBody(resp.Body)
|
cleanupBody(resp.Body)
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// cleanupBody drains and closes an HTTP response body to allow connection reuse.
|
// cleanupBody drains and closes an HTTP response body to allow connection reuse.
|
||||||
func cleanupBody(body io.ReadCloser) {
|
func cleanupBody(body io.ReadCloser) {
|
||||||
io.Copy(io.Discard, &io.LimitedReader{
|
io.Copy(io.Discard, &io.LimitedReader{R: body, N: READ_LIMIT})
|
||||||
R: body,
|
|
||||||
N: READ_LIMIT,
|
|
||||||
})
|
|
||||||
body.Close()
|
body.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user