mirror of
https://github.com/Alexey71/opera-proxy.git
synced 2026-05-15 15:11:00 +00:00
chore: some changes
This commit is contained in:
@@ -14,12 +14,12 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
-
|
-
|
||||||
name: Setup Go
|
name: Setup Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version: 'stable'
|
go-version: 'stable'
|
||||||
-
|
-
|
||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
VERSION=${{steps.tag.outputs.tag}}
|
VERSION=${{steps.tag.outputs.tag}}
|
||||||
-
|
-
|
||||||
name: Release
|
name: Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v3
|
||||||
with:
|
with:
|
||||||
files: bin/*
|
files: bin/*
|
||||||
fail_on_unmatched_files: true
|
fail_on_unmatched_files: true
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
opera-proxy
|
# opera-proxy fork
|
||||||
===========
|
|
||||||
|
- Original author - [Snawoot](https://github.com/Snawoot)
|
||||||
|
- Fork based - [Alexey71](https://github.com/Alexey71)
|
||||||
|
- Some ideas - [SLY-F0X](https://github.com/SLY-F0X)
|
||||||
|
|
||||||
Standalone Opera VPN client.
|
Standalone Opera VPN client.
|
||||||
|
|
||||||
@@ -8,10 +11,10 @@ By default the application listens on 127.0.0.1:18080.
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* Cross-platform (Windows/Mac OS/Linux/Android (via shell)/\*BSD)
|
- Cross-platform (Windows/Mac OS/Linux/Android (via shell)/\*BSD)
|
||||||
* Uses TLS for secure communication with upstream proxies
|
- Uses TLS for secure communication with upstream proxies
|
||||||
* Zero configuration
|
- Zero configuration
|
||||||
* Simple and straightforward
|
- Simple and straightforward
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|||||||
+14
-3
@@ -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{}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
module github.com/Alexey71/opera-proxy
|
module github.com/xteamlyer/opera-proxy
|
||||||
|
|
||||||
go 1.25.0
|
go 1.25.0
|
||||||
|
|
||||||
toolchain go1.25.9
|
toolchain go1.26.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Alexey71/go-http-digest-auth-client v1.1.3
|
github.com/Alexey71/go-http-digest-auth-client v1.1.3
|
||||||
github.com/Alexey71/go-multierror v1.1.3
|
github.com/Alexey71/go-multierror v1.1.3
|
||||||
github.com/ncruces/go-dns v1.3.3
|
github.com/ncruces/go-dns v1.3.3
|
||||||
github.com/things-go/go-socks5 v0.1.1
|
github.com/things-go/go-socks5 v0.1.1
|
||||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20260413170323-a8e9237a216b
|
golang.org/x/crypto/x509roots/fallback v0.0.0-20260423152011-b9e53593a607
|
||||||
golang.org/x/net v0.53.0
|
golang.org/x/net v0.53.0
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ github.com/things-go/go-socks5 v0.1.1 h1:48hy9cHEXPKeG91G/g4n8zW4uynzPUQy/FkcrJ7
|
|||||||
github.com/things-go/go-socks5 v0.1.1/go.mod h1:1YBHVYG7Oli5ae+Pwkp630cPAwY1pjUPmohO1n0Emg0=
|
github.com/things-go/go-socks5 v0.1.1/go.mod h1:1YBHVYG7Oli5ae+Pwkp630cPAwY1pjUPmohO1n0Emg0=
|
||||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20260413170323-a8e9237a216b h1:ZG2SxTKsx1w3pUpOMD9dliRYnhWC5R5jmL6UDPCbYj4=
|
golang.org/x/crypto/x509roots/fallback v0.0.0-20260413170323-a8e9237a216b h1:ZG2SxTKsx1w3pUpOMD9dliRYnhWC5R5jmL6UDPCbYj4=
|
||||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20260413170323-a8e9237a216b/go.mod h1:+UoQFNBq2p2wO+Q6ddVtYc25GZ6VNdOMyyrd4nrqrKs=
|
golang.org/x/crypto/x509roots/fallback v0.0.0-20260413170323-a8e9237a216b/go.mod h1:+UoQFNBq2p2wO+Q6ddVtYc25GZ6VNdOMyyrd4nrqrKs=
|
||||||
|
golang.org/x/crypto/x509roots/fallback v0.0.0-20260423152011-b9e53593a607 h1:WlWLkLEVGjGS9LziRuLo1Ut+UkpzbXxFQh94eUUVjeg=
|
||||||
|
golang.org/x/crypto/x509roots/fallback v0.0.0-20260423152011-b9e53593a607/go.mod h1:+UoQFNBq2p2wO+Q6ddVtYc25GZ6VNdOMyyrd4nrqrKs=
|
||||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|||||||
+36
-12
@@ -12,32 +12,49 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Alexey71/opera-proxy/dialer"
|
"github.com/xteamlyer/opera-proxy/dialer"
|
||||||
clog "github.com/Alexey71/opera-proxy/log"
|
clog "github.com/xteamlyer/opera-proxy/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
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
|
||||||
httptransport http.RoundTripper
|
httptransport http.RoundTripper
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProxyHandler(dialer dialer.ContextDialer, logger *clog.CondLogger) *ProxyHandler {
|
func NewProxyHandler(d dialer.ContextDialer, logger *clog.CondLogger) *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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,7 +136,10 @@ func proxy(ctx context.Context, left, right net.Conn) {
|
|||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
cpy := func(dst, src net.Conn) {
|
cpy := 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)
|
||||||
@@ -145,7 +165,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()
|
||||||
io.Copy(dst, src)
|
bufp := copyBufPool.Get().(*[]byte)
|
||||||
|
defer copyBufPool.Put(bufp)
|
||||||
|
io.CopyBuffer(dst, src, *bufp)
|
||||||
dst.Close()
|
dst.Close()
|
||||||
}
|
}
|
||||||
rtl := func(dst io.Writer, src io.Reader) {
|
rtl := func(dst io.Writer, src io.Reader) {
|
||||||
@@ -226,12 +248,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 {
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/Alexey71/opera-proxy/dialer"
|
"github.com/xteamlyer/opera-proxy/dialer"
|
||||||
"github.com/things-go/go-socks5"
|
"github.com/things-go/go-socks5"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -23,12 +22,12 @@ import (
|
|||||||
|
|
||||||
xproxy "golang.org/x/net/proxy"
|
xproxy "golang.org/x/net/proxy"
|
||||||
|
|
||||||
"github.com/Alexey71/opera-proxy/clock"
|
"github.com/xteamlyer/opera-proxy/clock"
|
||||||
"github.com/Alexey71/opera-proxy/dialer"
|
"github.com/xteamlyer/opera-proxy/dialer"
|
||||||
"github.com/Alexey71/opera-proxy/handler"
|
"github.com/xteamlyer/opera-proxy/handler"
|
||||||
clog "github.com/Alexey71/opera-proxy/log"
|
clog "github.com/xteamlyer/opera-proxy/log"
|
||||||
"github.com/Alexey71/opera-proxy/resolver"
|
"github.com/xteamlyer/opera-proxy/resolver"
|
||||||
se "github.com/Alexey71/opera-proxy/seclient"
|
se "github.com/xteamlyer/opera-proxy/seclient"
|
||||||
|
|
||||||
_ "golang.org/x/crypto/x509roots/fallback"
|
_ "golang.org/x/crypto/x509roots/fallback"
|
||||||
"golang.org/x/crypto/x509roots/fallback/bundle"
|
"golang.org/x/crypto/x509roots/fallback/bundle"
|
||||||
@@ -37,6 +36,15 @@ import (
|
|||||||
const (
|
const (
|
||||||
API_DOMAIN = "api2.sec-tunnel.com"
|
API_DOMAIN = "api2.sec-tunnel.com"
|
||||||
PROXY_SUFFIX = "sec-tunnel.com"
|
PROXY_SUFFIX = "sec-tunnel.com"
|
||||||
|
|
||||||
|
// 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) {
|
||||||
@@ -112,6 +120,9 @@ 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
|
||||||
@@ -156,7 +167,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 (default raised to 30s for better API reliability)")
|
||||||
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] "+
|
||||||
@@ -164,8 +176,13 @@ 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", "SurfEasy API login")
|
flag.StringVar(&args.apiLogin, "api-login", "se0316",
|
||||||
flag.StringVar(&args.apiPassword, "api-password", "SILrMEPBmJuhomxWkfm3JalqHX2Eheg1YhlEZiMh8II", "SurfEasy API password")
|
"SurfEasy API login (ignored when -random-api-creds is set)")
|
||||||
|
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",
|
||||||
@@ -180,10 +197,13 @@ func parse_args() *CLIArgs {
|
|||||||
flag.StringVar(&args.fakeSNI, "fake-SNI", "", "domain name to use as SNI in communications with servers")
|
flag.StringVar(&args.fakeSNI, "fake-SNI", "", "domain name to use as SNI in communications with servers")
|
||||||
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", 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 (default raised to 60s)")
|
||||||
|
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()
|
||||||
if args.country == "" {
|
if args.country == "" {
|
||||||
@@ -200,10 +220,27 @@ func proxyFromURLWrapper(u *url.URL, next xproxy.Dialer) (xproxy.Dialer, error)
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("only context dialers are accepted")
|
return nil, errors.New("only context dialers are accepted")
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func run() int {
|
func run() int {
|
||||||
args := parse_args()
|
args := parse_args()
|
||||||
if args.showVersion {
|
if args.showVersion {
|
||||||
@@ -232,7 +269,8 @@ func run() int {
|
|||||||
|
|
||||||
caPool := x509.NewCertPool()
|
caPool := x509.NewCertPool()
|
||||||
if args.caFile != "" {
|
if args.caFile != "" {
|
||||||
certs, err := ioutil.ReadFile(args.caFile)
|
// os.ReadFile replaces deprecated ioutil.ReadFile
|
||||||
|
certs, err := os.ReadFile(args.caFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mainLogger.Error("Can't load CA file: %v", err)
|
mainLogger.Error("Can't load CA file: %v", err)
|
||||||
return 15
|
return 15
|
||||||
@@ -290,35 +328,44 @@ func run() int {
|
|||||||
mainLogger.Info("Using fixed API host address = %s", args.apiAddress)
|
mainLogger.Info("Using fixed API host address = %s", 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.
|
||||||
// Either way we'll validate certificate of actual proxy server.
|
// Either way we validate the certificate of the actual proxy server.
|
||||||
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,
|
// Resolve which API credentials to use.
|
||||||
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
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,
|
||||||
|
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 {
|
||||||
mainLogger.Critical("Unable to construct SEClient: %v", err)
|
mainLogger.Critical("Unable to construct SEClient: %v", err)
|
||||||
return 8
|
return 8
|
||||||
@@ -473,7 +520,7 @@ func run() int {
|
|||||||
if args.socksMode {
|
if args.socksMode {
|
||||||
socks, initError := handler.NewSocksServer(handlerDialer, socksLogger)
|
socks, initError := handler.NewSocksServer(handlerDialer, socksLogger)
|
||||||
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.")
|
||||||
@@ -557,10 +604,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
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"extends": [
|
||||||
|
"config:recommended"
|
||||||
|
]
|
||||||
|
}
|
||||||
+25
-8
@@ -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"
|
||||||
@@ -20,6 +19,10 @@ 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 {
|
||||||
@@ -73,9 +76,24 @@ type SEClient struct {
|
|||||||
|
|
||||||
type StrKV map[string]string
|
type StrKV map[string]string
|
||||||
|
|
||||||
// Instantiates SurfEasy client with default settings and given API keys.
|
// GenerateRandomAPICreds returns a cryptographically-random (login, password) pair
|
||||||
// Optional `transport` parameter allows to override HTTP transport used
|
// suitable for use as SurfEasy digest-auth credentials. Call this instead of
|
||||||
// for HTTP calls
|
// using the hardcoded defaults when -random-api-creds is set.
|
||||||
|
func GenerateRandomAPICreds() (login, password string, err error) {
|
||||||
|
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
|
||||||
@@ -293,7 +311,7 @@ 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}
|
||||||
}
|
}
|
||||||
@@ -330,10 +348,9 @@ func (c *SEClient) rpcCall(ctx context.Context, endpoint string, params map[stri
|
|||||||
return nil
|
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,
|
R: body,
|
||||||
N: READ_LIMIT,
|
N: READ_LIMIT,
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user