Some change

This commit is contained in:
xteamlyer
2026-04-30 19:04:28 +03:00
parent b99a41e040
commit 63b9203be4
4 changed files with 165 additions and 113 deletions
+14 -3
View File
@@ -115,9 +115,14 @@ func (d *ProxyDialer) DialContext(ctx context.Context, network, address string)
return nil, err return nil, err
} }
if uTLSServerName != "" { if uTLSServerName != "" {
// Custom cert verification logic: // Custom TLS verification strategy:
// DO NOT send SNI extension of TLS ClientHello // - Do NOT send SNI in ClientHello (use fakeSNI, may be empty string).
// DO peer certificate verification against specified servername // - Verify the peer certificate against the real server name using
// the explicit caPool (Mozilla NSS bundle via bundle.Roots()).
//
// No cross-signed intermediate injection needed: bundle.Roots() already
// contains USERTrust ECC CA as a trusted root, so Go's chain builder
// resolves Opera's certificate chain without any manual patching.
conn = tls.Client(conn, &tls.Config{ conn = tls.Client(conn, &tls.Config{
ServerName: fakeSNI, ServerName: fakeSNI,
InsecureSkipVerify: true, InsecureSkipVerify: true,
@@ -186,6 +191,12 @@ func (d *ProxyDialer) Address() (string, error) {
return d.address() return d.address()
} }
// readResponse reads an HTTP/1.1 response from the raw conn after a CONNECT
// request. It reads byte-by-byte until the \r\n\r\n header terminator is found,
// then hands the accumulated bytes to http.ReadResponse.
//
// Note: byte-by-byte reading is intentional — we must not over-read past the
// end of headers into the tunneled TLS stream.
func readResponse(r io.Reader, req *http.Request) (*http.Response, error) { func readResponse(r io.Reader, req *http.Request) (*http.Response, error) {
endOfResponse := []byte("\r\n\r\n") endOfResponse := []byte("\r\n\r\n")
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
+34 -9
View File
@@ -19,8 +19,24 @@ import (
const ( const (
COPY_BUF = 128 * 1024 COPY_BUF = 128 * 1024
BAD_REQ_MSG = "Bad Request\n" BAD_REQ_MSG = "Bad Request\n"
// Reduced idle pool: the proxy handler makes upstream connections per request,
// not persistent keep-alive sessions. 10 total / 2 per host is plenty and
// avoids leaking hundreds of idle goroutines/sockets under bursty traffic.
TRANSPORT_MAX_IDLE_CONNS = 10
TRANSPORT_MAX_IDLE_CONNS_PER_HOST = 2
TRANSPORT_IDLE_CONN_TIMEOUT = 60 * time.Second
) )
// copyBufPool reuses 128 KiB buffers for bidirectional data relay,
// avoiding per-connection heap allocations.
var copyBufPool = sync.Pool{
New: func() any {
b := make([]byte, COPY_BUF)
return &b
},
}
type ProxyHandler struct { type ProxyHandler struct {
logger *clog.CondLogger logger *clog.CondLogger
dialer dialer.ContextDialer dialer dialer.ContextDialer
@@ -28,17 +44,18 @@ type ProxyHandler struct {
fakeSNI string fakeSNI string
} }
func NewProxyHandler(dialer dialer.ContextDialer, logger *clog.CondLogger, fakeSNI string) *ProxyHandler { func NewProxyHandler(d dialer.ContextDialer, logger *clog.CondLogger, fakeSNI string) *ProxyHandler {
httptransport := &http.Transport{ httptransport := &http.Transport{
MaxIdleConns: 100, MaxIdleConns: TRANSPORT_MAX_IDLE_CONNS,
IdleConnTimeout: 90 * time.Second, MaxIdleConnsPerHost: TRANSPORT_MAX_IDLE_CONNS_PER_HOST,
IdleConnTimeout: TRANSPORT_IDLE_CONN_TIMEOUT,
TLSHandshakeTimeout: 10 * time.Second, TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second, ExpectContinueTimeout: 1 * time.Second,
DialContext: dialer.DialContext, DialContext: d.DialContext,
} }
return &ProxyHandler{ return &ProxyHandler{
logger: logger, logger: logger,
dialer: dialer, dialer: d,
httptransport: httptransport, httptransport: httptransport,
fakeSNI: fakeSNI, fakeSNI: fakeSNI,
} }
@@ -130,7 +147,10 @@ func proxy(ctx context.Context, left net.Conn, leftReader io.Reader, right net.C
} }
rtl := func(dst, src net.Conn) { rtl := func(dst, src net.Conn) {
defer wg.Done() defer wg.Done()
io.Copy(dst, src) // Grab a pooled buffer for this copy direction.
bufp := copyBufPool.Get().(*[]byte)
defer copyBufPool.Put(bufp)
io.CopyBuffer(dst, src, *bufp)
dst.Close() dst.Close()
} }
wg.Add(2) wg.Add(2)
@@ -156,6 +176,9 @@ func proxyh2(ctx context.Context, leftreader io.ReadCloser, leftwriter io.Writer
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
ltr := func(dst net.Conn, src io.Reader) { ltr := func(dst net.Conn, src io.Reader) {
defer wg.Done() defer wg.Done()
bufp := copyBufPool.Get().(*[]byte)
defer copyBufPool.Put(bufp)
io.CopyBuffer(dst, src, *bufp)
copyWithSNIRewrite(dst, src, fakeSNI) copyWithSNIRewrite(dst, src, fakeSNI)
dst.Close() dst.Close()
} }
@@ -237,12 +260,14 @@ func flush(flusher interface{}) bool {
} }
func copyBody(wr io.Writer, body io.Reader) { func copyBody(wr io.Writer, body io.Reader) {
buf := make([]byte, COPY_BUF) // Use pooled buffer to avoid per-call allocation.
bufp := copyBufPool.Get().(*[]byte)
defer copyBufPool.Put(bufp)
for { for {
bread, read_err := body.Read(buf) bread, read_err := body.Read(*bufp)
var write_err error var write_err error
if bread > 0 { if bread > 0 {
_, write_err = wr.Write(buf[:bread]) _, write_err = wr.Write((*bufp)[:bread])
flush(wr) flush(wr)
} }
if read_err != nil || write_err != nil { if read_err != nil || write_err != nil {
+91 -60
View File
@@ -11,7 +11,6 @@ import (
"flag" "flag"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log" "log"
"net" "net"
"net/http" "net/http"
@@ -42,6 +41,15 @@ const (
PROXY_SUFFIX = "sec-tunnel.com" PROXY_SUFFIX = "sec-tunnel.com"
DefaultDiscoverCSVFallback = "proxies.csv" DefaultDiscoverCSVFallback = "proxies.csv"
DefaultProxyBypassFallback = "proxy-bypass.txt" DefaultProxyBypassFallback = "proxy-bypass.txt"
// Default timeouts increased to reduce premature API errors on slow networks.
DEFAULT_TIMEOUT = 30 * time.Second
DEFAULT_SERVER_SELECTION_TIMEOUT = 60 * time.Second
// Reduced idle connection pool to lower resource usage on embedded/low-RAM hosts.
HTTP_MAX_IDLE_CONNS = 10
HTTP_MAX_IDLE_CONNS_PER_HOST = 3
HTTP_IDLE_CONN_TIMEOUT = 60 * time.Second
) )
func perror(msg string) { func perror(msg string) {
@@ -188,7 +196,8 @@ func parse_args() *CLIArgs {
flag.BoolVar(&args.socksMode, "socks-mode", false, "listen for SOCKS requests instead of HTTP") flag.BoolVar(&args.socksMode, "socks-mode", false, "listen for SOCKS requests instead of HTTP")
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", 10*time.Second, "timeout for network operations") flag.DurationVar(&args.timeout, "timeout", DEFAULT_TIMEOUT,
"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] "+
@@ -221,10 +230,13 @@ func parse_args() *CLIArgs {
flag.DurationVar(&args.proxySpeedTimeout, "proxy-speed-timeout", 15*time.Second, "timeout for a single proxy speed measurement") flag.DurationVar(&args.proxySpeedTimeout, "proxy-speed-timeout", 15*time.Second, "timeout for a single proxy speed measurement")
flag.Int64Var(&args.proxySpeedDLLimit, "proxy-speed-dl-limit", 262144, "limit of downloaded bytes for proxy speed measurement") flag.Int64Var(&args.proxySpeedDLLimit, "proxy-speed-dl-limit", 262144, "limit of downloaded bytes for proxy speed measurement")
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", 30*time.Second, "timeout given for server selection function to produce result") flag.DurationVar(&args.serverSelectionTimeout, "server-selection-timeout", DEFAULT_SERVER_SELECTION_TIMEOUT,
flag.StringVar(&args.serverSelectionTestURL, "server-selection-test-url", "https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js", "timeout given for server selection function to produce result")
flag.StringVar(&args.serverSelectionTestURL, "server-selection-test-url",
"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")
flag.Int64Var(&args.serverSelectionDLLimit, "server-selection-dl-limit", 0, "restrict amount of downloaded data per connection by fastest server selection") flag.Int64Var(&args.serverSelectionDLLimit, "server-selection-dl-limit", 0,
"restrict amount of downloaded data per connection by fastest server selection")
flag.Func("config", "read configuration from file with space-separated keys and values", readConfig) flag.Func("config", "read configuration from file with space-separated keys and values", readConfig)
flag.Parse() flag.Parse()
flag.Visit(func(f *flag.Flag) { flag.Visit(func(f *flag.Flag) {
@@ -276,6 +288,58 @@ func proxyFromURLWrapper(u *url.URL, next xproxy.Dialer) (xproxy.Dialer, error)
return dialer.ProxyDialerFromURL(u, cdialer) return dialer.ProxyDialerFromURL(u, cdialer)
} }
// buildAPITransport returns an http.Transport tuned for infrequent API calls:
// reduced idle pool (saves goroutines/sockets), no forced HTTP/2.
func buildAPITransport(
dialCtx func(context.Context, string, string) (net.Conn, error),
dialTLSCtx func(context.Context, string, string) (net.Conn, error),
) *http.Transport {
return &http.Transport{
DialContext: dialCtx,
DialTLSContext: dialTLSCtx,
ForceAttemptHTTP2: false,
MaxIdleConns: HTTP_MAX_IDLE_CONNS,
MaxIdleConnsPerHost: HTTP_MAX_IDLE_CONNS_PER_HOST,
IdleConnTimeout: HTTP_IDLE_CONN_TIMEOUT,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
}
// 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 normalizeAPIProxy(raw string) (string, error) { func normalizeAPIProxy(raw string) (string, error) {
raw = strings.TrimSpace(raw) raw = strings.TrimSpace(raw)
if raw == "" { if raw == "" {
@@ -441,27 +505,25 @@ func newSEClient(args *CLIArgs, baseDialer dialer.ContextDialer, caPool *x509.Ce
seclientDialer = dialer.NewResolvingDialer(resolver, seclientDialer) seclientDialer = dialer.NewResolvingDialer(resolver, 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'll validate certificate of 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,
} }
seclient, err := se.NewSEClient(args.apiLogin, args.apiPassword, &http.Transport{
DialContext: seclientDialer.DialContext, seclient, err := se.NewSEClient(args.apiLogin, args.apiPassword, buildAPITransport(
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) { seclientDialer.DialContext,
func(ctx context.Context, network, addr string) (net.Conn, error) {
conn, err := seclientDialer.DialContext(ctx, network, addr) conn, err := seclientDialer.DialContext(ctx, network, addr)
if err != nil { if err != nil {
return conn, err return conn, err
} }
return tls.Client(conn, tlsConfig), nil return tls.Client(conn, tlsConfig), nil
}, },
ForceAttemptHTTP2: true, ))
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
})
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to construct SEClient: %w", err) return nil, fmt.Errorf("unable to construct SEClient: %w", err)
} }
@@ -659,30 +721,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 {
certs, err := ioutil.ReadFile(args.caFile) return exitCode
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)
@@ -793,12 +834,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)
@@ -806,12 +847,12 @@ func run() int {
if args.apiAddress != "" { if args.apiAddress != "" {
seclientDialer = dialer.NewFixedDialer(args.apiAddress, seclientDialer) seclientDialer = dialer.NewFixedDialer(args.apiAddress, seclientDialer)
} else if len(args.bootstrapDNS.values) > 0 { } else if len(args.bootstrapDNS.values) > 0 {
resolver, err := resolver.FastFromURLs(caPool, args.bootstrapDNS.values...) res, err := resolver.FastFromURLs(caPool, args.bootstrapDNS.values...)
if err != nil { if err != nil {
mainLogger.Critical("Unable to instantiate DNS resolver: %v", err) mainLogger.Critical("Unable to instantiate DNS resolver: %v", err)
return 4 return 4
} }
seclientDialer = dialer.NewResolvingDialer(resolver, seclientDialer) seclientDialer = dialer.NewResolvingDialer(res, seclientDialer)
} }
// Dialing w/o SNI, receiving self-signed certificate, so skip verification. // Dialing w/o SNI, receiving self-signed certificate, so skip verification.
@@ -959,9 +1000,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")
@@ -1012,8 +1051,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
} }
@@ -1022,8 +1060,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
} }
@@ -1035,7 +1072,7 @@ func run() int {
if args.socksMode { if args.socksMode {
socks, initError := handler.NewSocksServer(handlerDialer, socksLogger, args.fakeSNI) socks, initError := handler.NewSocksServer(handlerDialer, socksLogger, args.fakeSNI)
if initError != nil { if initError != nil {
mainLogger.Critical("Failed to start: %v", err) mainLogger.Critical("Failed to start: %v", initError)
return 16 return 16
} }
mainLogger.Info("Init complete.") mainLogger.Info("Init complete.")
@@ -1155,10 +1192,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)},
@@ -1168,10 +1202,7 @@ func dpExport(ips []se.SEIPEntry, seclient *se.SEClient, sni string) int {
if gotOne { if gotOne {
key = "#proxy" key = "#proxy"
} }
wr.Write([]string{ wr.Write([]string{key, u.String()})
key,
u.String(),
})
gotOne = true gotOne = true
} }
return 0 return 0
+26 -41
View File
@@ -5,7 +5,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"math/rand" "math/rand"
"net/http" "net/http"
"net/url" "net/url"
@@ -16,10 +15,10 @@ import (
) )
const ( const (
ANON_EMAIL_LOCALPART_BYTES = 32 ANON_EMAIL_LOCALPART_BYTES = 32
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
) )
type SEEndpoints struct { type SEEndpoints struct {
@@ -73,9 +72,10 @@ type SEClient struct {
type StrKV map[string]string type StrKV map[string]string
// Instantiates SurfEasy client with default settings and given API keys. // NewSEClient instantiates a SurfEasy client.
// Optional `transport` parameter allows to override HTTP transport used // apiUsername/apiSecret are the application-level Digest Auth credentials
// for HTTP calls // embedded in every Opera client — they are NOT per-user and must not be randomised.
// transport may be nil (uses http.DefaultTransport).
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
@@ -83,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
} }
@@ -93,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 {
@@ -125,6 +123,8 @@ func (c *SEClient) AnonRegister(ctx context.Context) error {
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)
@@ -138,13 +138,12 @@ 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,
}, &regRes) }, &regRes)
@@ -225,13 +224,12 @@ 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,
@@ -287,16 +285,12 @@ func (c *SEClient) RpcCall(ctx context.Context, endpoint string, params map[stri
} }
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 {
input := make(url.Values) input := make(url.Values, len(params))
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
} }
@@ -310,26 +304,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)
return err
if err != nil {
return err
}
return nil
} }
// Does cleanup of HTTP response in order to make it reusable by keep-alive // cleanupBody drains and closes an HTTP response body to allow connection reuse.
// logic of HTTP client
func cleanupBody(body io.ReadCloser) { func cleanupBody(body io.ReadCloser) {
io.Copy(ioutil.Discard, &io.LimitedReader{ io.Copy(io.Discard, &io.LimitedReader{R: body, N: READ_LIMIT})
R: body,
N: READ_LIMIT,
})
body.Close() body.Close()
} }