2021-03-25 20:45:48 +02:00
package main
import (
2024-08-01 12:01:24 +03:00
"bytes"
2021-03-27 00:43:33 +02:00
"context"
2021-03-27 02:48:07 +02:00
"crypto/tls"
2021-04-28 17:56:53 +03:00
"crypto/x509"
2021-03-27 02:48:07 +02:00
"encoding/csv"
2021-03-26 22:34:43 +02:00
"errors"
"flag"
"fmt"
2024-08-01 12:01:24 +03:00
"io"
2021-04-28 17:56:53 +03:00
"io/ioutil"
2021-03-25 20:45:48 +02:00
"log"
2021-03-26 22:34:43 +02:00
"net"
"net/http"
"net/url"
"os"
2021-03-27 01:42:32 +02:00
"strings"
2021-03-27 02:48:07 +02:00
"time"
2021-03-25 20:45:48 +02:00
2021-03-26 22:34:43 +02:00
xproxy "golang.org/x/net/proxy"
2021-03-27 00:43:33 +02:00
2024-11-03 15:17:29 +02:00
"github.com/Snawoot/opera-proxy/clock"
"github.com/Snawoot/opera-proxy/dialer"
"github.com/Snawoot/opera-proxy/handler"
clog "github.com/Snawoot/opera-proxy/log"
2025-08-12 19:38:17 +03:00
"github.com/Snawoot/opera-proxy/resolver"
2021-03-27 00:43:33 +02:00
se "github.com/Snawoot/opera-proxy/seclient"
2021-03-25 20:45:48 +02:00
)
2021-03-28 22:21:23 +03:00
const (
2024-10-05 19:01:39 +03:00
API_DOMAIN = "api2.sec-tunnel.com"
2021-03-28 22:21:23 +03:00
PROXY_SUFFIX = "sec-tunnel.com"
)
2021-03-26 22:34:43 +02:00
var (
2021-03-26 22:40:27 +02:00
version = "undefined"
2021-03-25 20:45:48 +02:00
)
2021-03-26 22:34:43 +02:00
func perror ( msg string ) {
fmt . Fprintln ( os . Stderr , "" )
fmt . Fprintln ( os . Stderr , msg )
}
func arg_fail ( msg string ) {
perror ( msg )
perror ( "Usage:" )
flag . PrintDefaults ( )
os . Exit ( 2 )
}
2024-08-01 12:01:24 +03:00
type CSVArg struct {
values [ ] string
}
func ( a * CSVArg ) String ( ) string {
if len ( a . values ) == 0 {
return ""
}
buf := new ( bytes . Buffer )
wr := csv . NewWriter ( buf )
wr . Write ( a . values )
wr . Flush ( )
return strings . TrimRight ( buf . String ( ) , "\n" )
}
func ( a * CSVArg ) Set ( line string ) error {
rd := csv . NewReader ( strings . NewReader ( line ) )
rd . FieldsPerRecord = - 1
rd . TrimLeadingSpace = true
values , err := rd . Read ( )
if err == io . EOF {
a . values = nil
return nil
}
if err != nil {
return fmt . Errorf ( "unable to parse comma-separated argument: %w" , err )
}
a . values = values
return nil
}
2025-09-14 15:16:02 +03:00
type serverSelectionArg struct {
value dialer . ServerSelection
}
func ( a * serverSelectionArg ) Set ( s string ) error {
v , err := dialer . ParseServerSelection ( s )
if err != nil {
return err
}
a . value = v
return nil
}
func ( a * serverSelectionArg ) String ( ) string {
return a . value . String ( )
}
2021-03-26 22:34:43 +02:00
type CLIArgs struct {
2025-09-14 15:16:02 +03:00
country string
listCountries bool
listProxies bool
bindAddress string
socksMode bool
verbosity int
timeout time . Duration
showVersion bool
proxy string
apiLogin string
apiPassword string
apiAddress string
apiClientType string
apiClientVersion string
apiUserAgent string
bootstrapDNS * CSVArg
refresh time . Duration
refreshRetry time . Duration
initRetries int
initRetryInterval time . Duration
certChainWorkaround bool
caFile string
fakeSNI string
overrideProxyAddress string
serverSelection serverSelectionArg
serverSelectionTimeout time . Duration
serverSelectionTestURL string
serverSelectionDLLimit int64
2021-03-26 22:34:43 +02:00
}
2024-08-01 12:01:24 +03:00
func parse_args ( ) * CLIArgs {
args := & CLIArgs {
bootstrapDNS : & CSVArg {
values : [ ] string {
"https://1.1.1.3/dns-query" ,
2024-08-01 12:49:41 +03:00
"https://8.8.8.8/dns-query" ,
"https://dns.google/dns-query" ,
2024-08-01 12:01:24 +03:00
"https://security.cloudflare-dns.com/dns-query" ,
2025-01-04 15:25:27 +02:00
"https://fidelity.vm-0.com/q" ,
2024-08-01 12:01:24 +03:00
"https://wikimedia-dns.org/dns-query" ,
"https://dns.adguard-dns.com/dns-query" ,
"https://dns.quad9.net/dns-query" ,
"https://doh.cleanbrowsing.org/doh/adult-filter/" ,
} ,
} ,
2025-09-14 15:16:02 +03:00
serverSelection : serverSelectionArg { dialer . ServerSelectionFastest } ,
2024-08-01 12:01:24 +03:00
}
2021-03-26 22:40:27 +02:00
flag . StringVar ( & args . country , "country" , "EU" , "desired proxy location" )
2021-03-27 00:43:33 +02:00
flag . BoolVar ( & args . listCountries , "list-countries" , false , "list available countries and exit" )
flag . BoolVar ( & args . listProxies , "list-proxies" , false , "output proxy list and exit" )
2025-04-11 23:20:07 +07:00
flag . StringVar ( & args . bindAddress , "bind-address" , "127.0.0.1:18080" , "proxy listen address" )
flag . BoolVar ( & args . socksMode , "socks-mode" , false , "listen for SOCKS requests instead of HTTP" )
2021-03-26 22:34:43 +02:00
flag . IntVar ( & args . verbosity , "verbosity" , 20 , "logging verbosity " +
"(10 - debug, 20 - info, 30 - warning, 40 - error, 50 - critical)" )
flag . DurationVar ( & args . timeout , "timeout" , 10 * time . Second , "timeout for network operations" )
flag . BoolVar ( & args . showVersion , "version" , false , "show program version and exit" )
2021-03-26 22:40:27 +02:00
flag . StringVar ( & args . proxy , "proxy" , "" , "sets base proxy to use for all dial-outs. " +
"Format: <http|https|socks5|socks5h>://[login:password@]host[:port] " +
2021-03-26 22:34:43 +02:00
"Examples: http://user:password@192.168.1.1:3128, socks5://10.0.0.1:1080" )
2024-10-06 00:08:17 +03:00
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 . apiUserAgent , "api-user-agent" , se . DefaultSESettings . UserAgent , "user agent reported to SurfEasy API" )
2021-03-27 00:43:33 +02:00
flag . StringVar ( & args . apiLogin , "api-login" , "se0316" , "SurfEasy API login" )
flag . StringVar ( & args . apiPassword , "api-password" , "SILrMEPBmJuhomxWkfm3JalqHX2Eheg1YhlEZiMh8II" , "SurfEasy API password" )
2021-03-28 22:21:23 +03:00
flag . StringVar ( & args . apiAddress , "api-address" , "" , fmt . Sprintf ( "override IP address of %s" , API_DOMAIN ) )
2024-08-01 12:01:24 +03:00
flag . Var ( args . bootstrapDNS , "bootstrap-dns" ,
2025-08-12 19:38:17 +03:00
"comma-separated list of DNS/DoH/DoT resolvers for initial discovery of SurfEasy API address. " +
2025-08-13 16:22:16 +03:00
"Supported schemes are: dns://, https://, tls://, tcp://. " +
2025-08-12 19:38:17 +03:00
"Examples: https://1.1.1.1/dns-query,tls://9.9.9.9:853" )
2021-03-30 16:29:54 +03:00
flag . DurationVar ( & args . refresh , "refresh" , 4 * time . Hour , "login refresh interval" )
2021-04-23 13:01:53 +03:00
flag . DurationVar ( & args . refreshRetry , "refresh-retry" , 5 * time . Second , "login refresh retry interval" )
2024-11-05 15:59:10 +02:00
flag . IntVar ( & args . initRetries , "init-retries" , 0 , "number of attempts for initialization steps, zero for unlimited retry" )
2025-01-27 14:59:22 +02:00
flag . DurationVar ( & args . initRetryInterval , "init-retry-interval" , 5 * time . Second , "delay between initialization retries" )
2021-04-28 17:56:53 +03:00
flag . BoolVar ( & args . certChainWorkaround , "certchain-workaround" , true ,
"add bundled cross-signed intermediate cert to certchain to make it check out on old systems" )
flag . StringVar ( & args . caFile , "cafile" , "" , "use custom CA certificate bundle file" )
2024-12-21 20:47:28 +02:00
flag . StringVar ( & args . fakeSNI , "fake-SNI" , "" , "domain name to use as SNI in communications with servers" )
2025-01-27 14:59:22 +02:00
flag . StringVar ( & args . overrideProxyAddress , "override-proxy-address" , "" , "use fixed proxy address instead of server address returned by SurfEasy API" )
2025-09-14 15:16:02 +03:00
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 . 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" )
flag . Int64Var ( & args . serverSelectionDLLimit , "server-selection-dl-limit" , 0 , "restrict amount of downloaded data per connection by fastest server selection" )
2021-03-26 22:34:43 +02:00
flag . Parse ( )
if args . country == "" {
arg_fail ( "Country can't be empty string." )
}
2021-03-27 00:43:33 +02:00
if args . listCountries && args . listProxies {
2021-03-26 22:34:43 +02:00
arg_fail ( "list-countries and list-proxies flags are mutually exclusive" )
2021-03-25 20:45:48 +02:00
}
2021-03-26 22:34:43 +02:00
return args
}
2021-03-25 20:45:48 +02:00
2021-03-26 22:34:43 +02:00
func proxyFromURLWrapper ( u * url . URL , next xproxy . Dialer ) ( xproxy . Dialer , error ) {
2024-11-03 15:17:29 +02:00
cdialer , ok := next . ( dialer . ContextDialer )
2021-03-26 22:34:43 +02:00
if ! ok {
return nil , errors . New ( "only context dialers are accepted" )
2021-03-25 20:45:48 +02:00
}
2021-03-25 22:09:36 +02:00
2024-11-03 15:17:29 +02:00
return dialer . ProxyDialerFromURL ( u , cdialer )
2021-03-26 22:34:43 +02:00
}
func run ( ) int {
args := parse_args ( )
if args . showVersion {
fmt . Println ( version )
return 0
2021-03-25 22:09:36 +02:00
}
2021-03-26 21:52:09 +02:00
2024-11-03 15:17:29 +02:00
logWriter := clog . NewLogWriter ( os . Stderr )
2021-03-26 22:34:43 +02:00
defer logWriter . Close ( )
2024-11-03 15:17:29 +02:00
mainLogger := clog . NewCondLogger ( log . New ( logWriter , "MAIN : " ,
2021-03-26 22:34:43 +02:00
log . LstdFlags | log . Lshortfile ) ,
args . verbosity )
2024-11-03 15:17:29 +02:00
proxyLogger := clog . NewCondLogger ( log . New ( logWriter , "PROXY : " ,
2021-03-26 22:34:43 +02:00
log . LstdFlags | log . Lshortfile ) ,
args . verbosity )
2025-04-11 23:20:07 +07:00
socksLogger := log . New ( logWriter , "SOCKS : " ,
log . LstdFlags | log . Lshortfile )
2021-03-26 22:34:43 +02:00
2021-03-27 00:43:33 +02:00
mainLogger . Info ( "opera-proxy client version %s is starting..." , version )
2024-11-03 15:17:29 +02:00
var d dialer . ContextDialer = & net . Dialer {
2021-03-26 22:34:43 +02:00
Timeout : 30 * time . Second ,
KeepAlive : 30 * time . Second ,
}
2021-03-27 02:48:07 +02:00
2025-09-14 15:16:02 +03:00
var caPool * x509 . CertPool
if args . caFile != "" {
caPool = x509 . NewCertPool ( )
certs , err := ioutil . 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
}
}
2021-03-26 22:34:43 +02:00
if args . proxy != "" {
xproxy . RegisterDialerType ( "http" , proxyFromURLWrapper )
xproxy . RegisterDialerType ( "https" , proxyFromURLWrapper )
proxyURL , err := url . Parse ( args . proxy )
if err != nil {
mainLogger . Critical ( "Unable to parse base proxy URL: %v" , err )
return 6
}
2024-11-03 15:17:29 +02:00
pxDialer , err := xproxy . FromURL ( proxyURL , d )
2021-03-26 22:34:43 +02:00
if err != nil {
mainLogger . Critical ( "Unable to instantiate base proxy dialer: %v" , err )
return 7
}
2024-11-03 15:17:29 +02:00
d = pxDialer . ( dialer . ContextDialer )
2021-03-26 22:34:43 +02:00
}
2024-11-03 15:17:29 +02:00
seclientDialer := d
2024-11-04 13:26:09 +02:00
if args . apiAddress != "" {
2025-09-14 15:16:02 +03:00
mainLogger . Info ( "Using fixed API host address = %s" , args . apiAddress )
2024-11-04 13:26:09 +02:00
seclientDialer = dialer . NewFixedDialer ( args . apiAddress , d )
} else if len ( args . bootstrapDNS . values ) > 0 {
2025-08-12 19:38:17 +03:00
resolver , err := resolver . FastFromURLs ( args . bootstrapDNS . values ... )
2024-11-04 13:26:09 +02:00
if err != nil {
mainLogger . Critical ( "Unable to instantiate DNS resolver: %v" , err )
return 4
2021-03-28 22:21:23 +03:00
}
2024-11-04 13:26:09 +02:00
seclientDialer = dialer . NewResolvingDialer ( resolver , d )
2021-03-27 02:48:07 +02:00
}
2021-03-27 01:48:24 +02:00
// Dialing w/o SNI, receiving self-signed certificate, so skip verification.
// Either way we'll validate certificate of actual proxy server.
tlsConfig := & tls . Config {
2024-12-21 20:47:28 +02:00
ServerName : args . fakeSNI ,
2021-03-27 01:48:24 +02:00
InsecureSkipVerify : true ,
}
2021-03-27 00:43:33 +02:00
seclient , err := se . NewSEClient ( args . apiLogin , args . apiPassword , & http . Transport {
2021-03-27 02:48:07 +02:00
DialContext : seclientDialer . DialContext ,
DialTLSContext : func ( ctx context . Context , network , addr string ) ( net . Conn , error ) {
conn , err := seclientDialer . DialContext ( ctx , network , addr )
2021-03-27 01:48:24 +02:00
if err != nil {
return conn , err
}
return tls . Client ( conn , tlsConfig ) , nil
} ,
2021-03-27 00:43:33 +02:00
ForceAttemptHTTP2 : true ,
MaxIdleConns : 100 ,
IdleConnTimeout : 90 * time . Second ,
TLSHandshakeTimeout : 10 * time . Second ,
ExpectContinueTimeout : 1 * time . Second ,
} )
if err != nil {
mainLogger . Critical ( "Unable to construct SEClient: %v" , err )
return 8
2021-03-26 22:34:43 +02:00
}
2024-11-05 15:54:05 +02:00
seclient . Settings . ClientType = args . apiClientType
seclient . Settings . ClientVersion = args . apiClientVersion
seclient . Settings . UserAgent = args . apiUserAgent
2021-03-27 00:43:33 +02:00
2024-11-05 15:54:05 +02:00
try := retryPolicy ( args . initRetries , args . initRetryInterval , mainLogger )
err = try ( "anonymous registration" , func ( ) error {
ctx , cl := context . WithTimeout ( context . Background ( ) , args . timeout )
defer cl ( )
return seclient . AnonRegister ( ctx )
} )
2021-03-27 00:43:33 +02:00
if err != nil {
return 9
2021-03-26 21:52:09 +02:00
}
2021-03-26 22:09:37 +02:00
2024-11-05 15:54:05 +02:00
err = try ( "device registration" , func ( ) error {
ctx , cl := context . WithTimeout ( context . Background ( ) , args . timeout )
defer cl ( )
return seclient . RegisterDevice ( ctx )
} )
2021-03-26 22:09:37 +02:00
if err != nil {
2021-03-27 00:43:33 +02:00
return 10
}
2021-04-02 01:39:33 +03:00
if args . listCountries {
2024-11-05 15:54:05 +02:00
return printCountries ( try , mainLogger , args . timeout , seclient )
2021-04-02 01:39:33 +03:00
}
2025-09-14 15:16:02 +03:00
handlerDialerFactory := func ( endpointAddr string ) dialer . ContextDialer {
return dialer . NewProxyDialer (
dialer . WrapStringToCb ( endpointAddr ) ,
dialer . WrapStringToCb ( fmt . Sprintf ( "%s0.%s" , args . country , PROXY_SUFFIX ) ) ,
dialer . WrapStringToCb ( args . fakeSNI ) ,
func ( ) ( string , error ) {
return dialer . BasicAuthHeader ( seclient . GetProxyCredentials ( ) ) , nil
} ,
args . certChainWorkaround ,
caPool ,
d )
}
2024-11-05 15:54:05 +02:00
var ips [ ] se . SEIPEntry
2025-09-14 15:16:02 +03:00
var handlerDialer dialer . ContextDialer
if args . overrideProxyAddress == "" || args . listProxies {
err = try ( "discover" , func ( ) error {
ctx , cl := context . WithTimeout ( context . Background ( ) , args . timeout )
defer cl ( )
res , err := seclient . Discover ( ctx , fmt . Sprintf ( "\"%s\",," , args . country ) )
if err != nil {
return err
}
if len ( res ) == 0 {
return errors . New ( "empty endpoints list!" )
}
if args . listProxies {
ips = res
return nil
}
2025-09-14 22:56:19 +03:00
mainLogger . Info ( "Discovered endpoints: %v. Starting server selection routine %q." , res , args . serverSelection . value )
2025-09-14 15:16:02 +03:00
var ss dialer . SelectionFunc
switch args . serverSelection . value {
case dialer . ServerSelectionFirst :
ss = dialer . SelectFirst
case dialer . ServerSelectionRandom :
ss = dialer . SelectRandom
case dialer . ServerSelectionFastest :
ss = dialer . NewFastestServerSelectionFunc (
args . serverSelectionTestURL ,
args . serverSelectionDLLimit ,
2025-09-20 18:11:00 +03:00
& tls . Config {
RootCAs : caPool ,
} ,
2025-09-14 15:16:02 +03:00
)
default :
panic ( "unhandled server selection value got past parsing" )
}
dialers := make ( [ ] dialer . ContextDialer , len ( res ) )
for i , ep := range res {
dialers [ i ] = handlerDialerFactory ( ep . NetAddr ( ) )
}
ctx , cl = context . WithTimeout ( context . Background ( ) , args . serverSelectionTimeout )
defer cl ( )
handlerDialer , err = ss ( ctx , dialers )
2025-09-14 22:56:19 +03:00
if err != nil {
return err
}
if addresser , ok := handlerDialer . ( interface { Address ( ) ( string , error ) } ) ; ok {
if epAddr , err := addresser . Address ( ) ; err == nil {
mainLogger . Info ( "Selected endpoint address: %s" , epAddr )
}
}
return nil
2025-09-14 15:16:02 +03:00
} )
if err != nil {
return 12
}
} else {
sanitizedEndpoint := sanitizeFixedProxyAddress ( args . overrideProxyAddress )
handlerDialer = handlerDialerFactory ( sanitizedEndpoint )
mainLogger . Info ( "Endpoint override: %s" , sanitizedEndpoint )
2021-04-02 01:39:33 +03:00
}
if args . listProxies {
return printProxies ( ips , seclient )
}
2024-11-03 15:17:29 +02:00
clock . RunTicker ( context . Background ( ) , args . refresh , args . refreshRetry , func ( ctx context . Context ) error {
2021-03-30 16:29:54 +03:00
mainLogger . Info ( "Refreshing login..." )
2021-04-02 01:15:12 +03:00
reqCtx , cl := context . WithTimeout ( ctx , args . timeout )
2021-03-30 16:29:54 +03:00
defer cl ( )
2021-04-02 01:15:12 +03:00
err := seclient . Login ( reqCtx )
2021-03-30 16:29:54 +03:00
if err != nil {
2021-04-02 02:03:16 +03:00
mainLogger . Error ( "Login refresh failed: %v" , err )
2021-04-23 13:01:53 +03:00
return err
2021-03-30 16:29:54 +03:00
}
mainLogger . Info ( "Login refreshed." )
2021-04-02 01:15:12 +03:00
mainLogger . Info ( "Refreshing device password..." )
reqCtx , cl = context . WithTimeout ( ctx , args . timeout )
defer cl ( )
err = seclient . DeviceGeneratePassword ( reqCtx )
if err != nil {
2021-04-02 02:03:16 +03:00
mainLogger . Error ( "Device password refresh failed: %v" , err )
2021-04-23 13:01:53 +03:00
return err
2021-04-02 01:15:12 +03:00
}
mainLogger . Info ( "Device password refreshed." )
2021-04-23 13:01:53 +03:00
return nil
2021-03-30 16:29:54 +03:00
} )
2021-03-26 22:34:43 +02:00
mainLogger . Info ( "Starting proxy server..." )
2025-04-11 23:20:07 +07:00
if args . socksMode {
socks , initError := handler . NewSocksServer ( handlerDialer , socksLogger )
if initError != nil {
mainLogger . Critical ( "Failed to start: %v" , err )
return 16
}
mainLogger . Info ( "Init complete." )
err = socks . ListenAndServe ( "tcp" , args . bindAddress )
} else {
h := handler . NewProxyHandler ( handlerDialer , proxyLogger )
mainLogger . Info ( "Init complete." )
err = http . ListenAndServe ( args . bindAddress , h )
}
2021-03-26 22:34:43 +02:00
mainLogger . Critical ( "Server terminated with a reason: %v" , err )
mainLogger . Info ( "Shutting down..." )
return 0
}
2024-11-05 15:54:05 +02:00
func printCountries ( try func ( string , func ( ) error ) error , logger * clog . CondLogger , timeout time . Duration , seclient * se . SEClient ) int {
var list [ ] se . SEGeoEntry
err := try ( "geolist" , func ( ) error {
ctx , cl := context . WithTimeout ( context . Background ( ) , timeout )
defer cl ( )
l , err := seclient . GeoList ( ctx )
list = l
return err
} )
2021-03-27 00:43:33 +02:00
if err != nil {
return 11
}
wr := csv . NewWriter ( os . Stdout )
defer wr . Flush ( )
wr . Write ( [ ] string { "country code" , "country name" } )
for _ , country := range list {
wr . Write ( [ ] string { country . CountryCode , country . Country } )
}
return 0
}
2021-03-27 01:42:32 +02:00
func printProxies ( ips [ ] se . SEIPEntry , seclient * se . SEClient ) int {
2021-03-27 00:43:33 +02:00
wr := csv . NewWriter ( os . Stdout )
2021-03-27 01:42:32 +02:00
defer wr . Flush ( )
login , password := seclient . GetProxyCredentials ( )
fmt . Println ( "Proxy login:" , login )
fmt . Println ( "Proxy password:" , password )
2024-11-03 15:17:29 +02:00
fmt . Println ( "Proxy-Authorization:" , dialer . BasicAuthHeader ( login , password ) )
2021-03-27 00:43:33 +02:00
fmt . Println ( "" )
2021-03-27 01:42:32 +02:00
wr . Write ( [ ] string { "host" , "ip_address" , "port" } )
for i , ip := range ips {
for _ , port := range ip . Ports {
wr . Write ( [ ] string {
2021-03-28 22:21:23 +03:00
fmt . Sprintf ( "%s%d.%s" , strings . ToLower ( ip . Geo . CountryCode ) , i , PROXY_SUFFIX ) ,
2021-03-27 01:42:32 +02:00
ip . IP ,
fmt . Sprintf ( "%d" , port ) ,
} )
2021-03-27 00:43:33 +02:00
}
}
return 0
}
2025-09-14 15:16:02 +03:00
func sanitizeFixedProxyAddress ( addr string ) string {
if _ , _ , err := net . SplitHostPort ( addr ) ; err == nil {
return addr
}
return net . JoinHostPort ( addr , "443" )
}
2021-03-26 22:34:43 +02:00
func main ( ) {
os . Exit ( run ( ) )
2021-03-25 20:45:48 +02:00
}
2024-11-05 15:54:05 +02:00
func retryPolicy ( retries int , retryInterval time . Duration , logger * clog . CondLogger ) func ( string , func ( ) error ) error {
return func ( name string , f func ( ) error ) error {
var err error
2025-01-27 14:59:22 +02:00
for i := 1 ; retries <= 0 || i <= retries ; i ++ {
2024-11-05 15:54:05 +02:00
if i > 1 {
logger . Warning ( "Retrying action %q in %v..." , name , retryInterval )
time . Sleep ( retryInterval )
}
logger . Info ( "Attempting action %q, attempt #%d..." , name , i )
err = f ( )
if err == nil {
logger . Info ( "Action %q succeeded on attempt #%d" , name , i )
return nil
}
logger . Warning ( "Action %q failed: %v" , name , err )
}
logger . Critical ( "All attempts for action %q have failed. Last error: %v" , name , err )
return err
}
}