This commit is contained in:
Vladislav Yarmak 2021-03-27 00:43:33 +02:00
parent e6b0ede774
commit 9cd2684ac0
5 changed files with 154 additions and 103 deletions

View file

@ -17,8 +17,7 @@ type ProxyHandler struct {
httptransport http.RoundTripper httptransport http.RoundTripper
} }
func NewProxyHandler(dialer ContextDialer, resolver *Resolver, logger *CondLogger) *ProxyHandler { func NewProxyHandler(dialer ContextDialer, logger *CondLogger) *ProxyHandler {
dialer = NewRetryDialer(dialer, resolver, logger)
httptransport := &http.Transport{ httptransport := &http.Transport{
MaxIdleConns: 100, MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second, IdleConnTimeout: 90 * time.Second,

160
main.go
View file

@ -1,6 +1,8 @@
package main package main
import ( import (
"encoding/csv"
"context"
"errors" "errors"
"flag" "flag"
"fmt" "fmt"
@ -12,6 +14,8 @@ import (
"time" "time"
xproxy "golang.org/x/net/proxy" xproxy "golang.org/x/net/proxy"
se "github.com/Snawoot/opera-proxy/seclient"
) )
var ( var (
@ -32,20 +36,24 @@ func arg_fail(msg string) {
type CLIArgs struct { type CLIArgs struct {
country string country string
bind_address string listCountries bool
listProxies bool
bindAddress string
verbosity int verbosity int
timeout time.Duration timeout time.Duration
resolver string resolver string
showVersion bool showVersion bool
proxy string proxy string
apiLogin string
apiPassword string
} }
func parse_args() CLIArgs { func parse_args() CLIArgs {
var args CLIArgs var args CLIArgs
flag.StringVar(&args.country, "country", "EU", "desired proxy location") flag.StringVar(&args.country, "country", "EU", "desired proxy location")
flag.BoolVar(&args.list_countries, "list-countries", false, "list available countries and exit") flag.BoolVar(&args.listCountries, "list-countries", false, "list available countries and exit")
flag.BoolVar(&args.list_proxies, "list-proxies", false, "output proxy list and exit") flag.BoolVar(&args.listProxies, "list-proxies", false, "output proxy list and exit")
flag.StringVar(&args.bind_address, "bind-address", "127.0.0.1:8080", "HTTP proxy listen address") flag.StringVar(&args.bindAddress, "bind-address", "127.0.0.1:18080", "HTTP proxy listen address")
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", 10*time.Second, "timeout for network operations")
@ -56,11 +64,13 @@ func parse_args() CLIArgs {
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] "+
"Examples: http://user:password@192.168.1.1:3128, socks5://10.0.0.1:1080") "Examples: http://user:password@192.168.1.1:3128, socks5://10.0.0.1:1080")
flag.StringVar(&args.apiLogin, "api-login", "se0316", "SurfEasy API login")
flag.StringVar(&args.apiPassword, "api-password", "SILrMEPBmJuhomxWkfm3JalqHX2Eheg1YhlEZiMh8II", "SurfEasy API password")
flag.Parse() flag.Parse()
if args.country == "" { if args.country == "" {
arg_fail("Country can't be empty string.") arg_fail("Country can't be empty string.")
} }
if args.list_countries && args.list_proxies { if args.listCountries && args.listProxies {
arg_fail("list-countries and list-proxies flags are mutually exclusive") arg_fail("list-countries and list-proxies flags are mutually exclusive")
} }
return args return args
@ -92,6 +102,8 @@ func run() int {
log.LstdFlags|log.Lshortfile), log.LstdFlags|log.Lshortfile),
args.verbosity) args.verbosity)
mainLogger.Info("opera-proxy client version %s is starting...", version)
var dialer ContextDialer = &net.Dialer{ var dialer ContextDialer = &net.Dialer{
Timeout: 30 * time.Second, Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second, KeepAlive: 30 * time.Second,
@ -112,33 +124,139 @@ func run() int {
dialer = pxDialer.(ContextDialer) dialer = pxDialer.(ContextDialer)
} }
if args.list_countries { seclient, err := se.NewSEClient(args.apiLogin, args.apiPassword, &http.Transport{
return print_countries(args.timeout) DialContext: dialer.DialContext,
} ForceAttemptHTTP2: true,
if args.list_proxies { MaxIdleConns: 100,
return print_proxies(args.country, args.proxy_type, args.limit, args.timeout) IdleConnTimeout: 90 * time.Second,
} TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
mainLogger.Info("opera-proxy client version %s is starting...", version) })
mainLogger.Info("Constructing fallback DNS upstream...")
resolver, err := NewResolver(args.resolver, args.timeout)
if err != nil { if err != nil {
mainLogger.Critical("Unable to instantiate DNS resolver: %v", err) mainLogger.Critical("Unable to construct SEClient: %v", err)
return 6 return 8
} }
// TODO: get creds here ctx, cl := context.WithTimeout(context.Background(), args.timeout)
handlerDialer := NewProxyDialer(endpoint.NetAddr(), endpoint.TLSName, auth, dialer) err = seclient.AnonRegister(ctx)
mainLogger.Info("Endpoint: %s", endpoint.URL().String()) if err != nil {
mainLogger.Critical("Unable to perform anonymous registration: %v", err)
return 9
}
cl()
ctx, cl = context.WithTimeout(context.Background(), args.timeout)
err = seclient.RegisterDevice(ctx)
if err != nil {
mainLogger.Critical("Unable to perform device registration: %v", err)
return 10
}
cl()
if args.listCountries {
return printCountries(mainLogger, args.timeout, seclient)
}
ctx, cl = context.WithTimeout(context.Background(), args.timeout)
// TODO: learn about requested_geo value format
ips, err := seclient.Discover(ctx, fmt.Sprintf("\"%s\",,", args.country))
if err != nil {
mainLogger.Critical("Endpoint discovery failed: %v", err)
return 12
}
if args.listProxies {
//return printProxies(args.country, args.proxy_type, args.limit, args.timeout)
return 666
}
if len(ips) == 0 {
mainLogger.Critical("Empty endpoint list!")
return 13
}
endpoint := ips[0]
authHdr := basic_auth_header(seclient.GetProxyCredentials())
auth := func () string {
return authHdr
}
handlerDialer := NewProxyDialer(endpoint.NetAddr(), fmt.Sprintf("%s0.sec-tunnel.com", args.country), auth, dialer)
mainLogger.Info("Endpoint: %s", endpoint.NetAddr())
mainLogger.Info("Starting proxy server...") mainLogger.Info("Starting proxy server...")
handler := NewProxyHandler(handlerDialer, proxyLogger) handler := NewProxyHandler(handlerDialer, proxyLogger)
mainLogger.Info("Init complete.") mainLogger.Info("Init complete.")
err = http.ListenAndServe(args.bind_address, handler) err = http.ListenAndServe(args.bindAddress, handler)
mainLogger.Critical("Server terminated with a reason: %v", err) mainLogger.Critical("Server terminated with a reason: %v", err)
mainLogger.Info("Shutting down...") mainLogger.Info("Shutting down...")
return 0 return 0
} }
func printCountries(logger *CondLogger, timeout time.Duration, seclient *se.SEClient) int {
ctx, cl := context.WithTimeout(context.Background(), timeout)
defer cl()
list, err := seclient.GeoList(ctx)
if err != nil {
logger.Critical("GeoList error: %v", err)
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
}
func printProxies(country string, proxy_type string, limit uint, timeout time.Duration) int {
/*var (
tunnels *ZGetTunnelsResponse
user_uuid string
err error
)
tx_res, tx_err := EnsureTransaction(context.Background(), timeout, func(ctx context.Context, client *http.Client) bool {
tunnels, user_uuid, err = Tunnels(ctx, client, country, proxy_type, limit)
if err != nil {
fmt.Fprintf(os.Stderr, "Transaction error: %v. Retrying with the fallback mechanism...\n", err)
return false
}
return true
})
if tx_err != nil {
fmt.Fprintf(os.Stderr, "Transaction recovery mechanism failure: %v.\n", tx_err)
return 4
}
if !tx_res {
fmt.Fprintf(os.Stderr, "All attempts failed.")
return 3
}
wr := csv.NewWriter(os.Stdout)
login := LOGIN_PREFIX + user_uuid
password := tunnels.AgentKey
fmt.Println("Login:", login)
fmt.Println("Password:", password)
fmt.Println("Proxy-Authorization:",
basic_auth_header(login, password))
fmt.Println("")
wr.Write([]string{"host", "ip_address", "direct", "peer", "hola", "trial", "trial_peer", "vendor"})
for host, ip := range tunnels.IPList {
if PROTOCOL_WHITELIST[tunnels.Protocol[host]] {
wr.Write([]string{host,
ip,
strconv.FormatUint(uint64(tunnels.Port.Direct), 10),
strconv.FormatUint(uint64(tunnels.Port.Peer), 10),
strconv.FormatUint(uint64(tunnels.Port.Hola), 10),
strconv.FormatUint(uint64(tunnels.Port.Trial), 10),
strconv.FormatUint(uint64(tunnels.Port.TrialPeer), 10),
tunnels.Vendor[host]})
}
}
wr.Flush()*/
return 0
}
func main() { func main() {
os.Exit(run()) os.Exit(run())
} }

View file

@ -2,6 +2,8 @@ package seclient
import ( import (
"encoding/json" "encoding/json"
"net"
"fmt"
"errors" "errors"
"strconv" "strconv"
) )
@ -78,6 +80,14 @@ type SEIPEntry struct {
Ports []uint16 `json:"ports"` Ports []uint16 `json:"ports"`
} }
func (e *SEIPEntry) NetAddr() string {
if len(e.Ports) == 0 {
return net.JoinHostPort(e.IP, "443")
} else {
return net.JoinHostPort(e.IP, fmt.Sprintf("%d", e.Ports[0]))
}
}
type SEDiscoverResponse struct { type SEDiscoverResponse struct {
Data struct { Data struct {
IPs []SEIPEntry `json:"ips"` IPs []SEIPEntry `json:"ips"`

View file

@ -291,6 +291,10 @@ func (c *SEClient) Discover(ctx context.Context, requestedGeo string) ([]SEIPEnt
return discoverRes.Data.IPs, nil return discoverRes.Data.IPs, nil
} }
func (c *SEClient) GetProxyCredentials() (string, string) {
return c.AssignedDeviceIDHash, c.DevicePassword
}
func (c *SEClient) populateRequest(req *http.Request) { func (c *SEClient) populateRequest(req *http.Request) {
req.Header["SE-Client-Version"] = []string{c.Settings.ClientVersion} req.Header["SE-Client-Version"] = []string{c.Settings.ClientVersion}
req.Header["SE-Operating-System"] = []string{c.Settings.OperatingSystem} req.Header["SE-Operating-System"] = []string{c.Settings.OperatingSystem}

View file

@ -4,16 +4,10 @@ import (
"bufio" "bufio"
"context" "context"
"encoding/base64" "encoding/base64"
"encoding/csv"
"errors" "errors"
"fmt"
"io" "io"
"net" "net"
"net/http" "net/http"
"net/url"
"os"
"strconv"
"strings"
"sync" "sync"
"time" "time"
) )
@ -81,80 +75,6 @@ func proxyh2(ctx context.Context, leftreader io.ReadCloser, leftwriter io.Writer
return return
} }
func print_countries(timeout time.Duration) int {
var (
countries CountryList
err error
)
tx_res, tx_err := EnsureTransaction(context.Background(), timeout, func(ctx context.Context, client *http.Client) bool {
countries, err = VPNCountries(ctx, client)
if err != nil {
fmt.Fprintf(os.Stderr, "Transaction error: %v. Retrying with the fallback mechanism...\n", err)
return false
}
return true
})
if tx_err != nil {
fmt.Fprintf(os.Stderr, "Transaction recovery mechanism failure: %v.\n", tx_err)
return 4
}
if !tx_res {
fmt.Fprintf(os.Stderr, "All attempts failed.")
return 3
}
for _, code := range countries {
fmt.Printf("%v - %v\n", code, ISO3166[strings.ToUpper(code)])
}
return 0
}
func print_proxies(country string, proxy_type string, limit uint, timeout time.Duration) int {
var (
tunnels *ZGetTunnelsResponse
user_uuid string
err error
)
tx_res, tx_err := EnsureTransaction(context.Background(), timeout, func(ctx context.Context, client *http.Client) bool {
tunnels, user_uuid, err = Tunnels(ctx, client, country, proxy_type, limit)
if err != nil {
fmt.Fprintf(os.Stderr, "Transaction error: %v. Retrying with the fallback mechanism...\n", err)
return false
}
return true
})
if tx_err != nil {
fmt.Fprintf(os.Stderr, "Transaction recovery mechanism failure: %v.\n", tx_err)
return 4
}
if !tx_res {
fmt.Fprintf(os.Stderr, "All attempts failed.")
return 3
}
wr := csv.NewWriter(os.Stdout)
login := LOGIN_PREFIX + user_uuid
password := tunnels.AgentKey
fmt.Println("Login:", login)
fmt.Println("Password:", password)
fmt.Println("Proxy-Authorization:",
basic_auth_header(login, password))
fmt.Println("")
wr.Write([]string{"host", "ip_address", "direct", "peer", "hola", "trial", "trial_peer", "vendor"})
for host, ip := range tunnels.IPList {
if PROTOCOL_WHITELIST[tunnels.Protocol[host]] {
wr.Write([]string{host,
ip,
strconv.FormatUint(uint64(tunnels.Port.Direct), 10),
strconv.FormatUint(uint64(tunnels.Port.Peer), 10),
strconv.FormatUint(uint64(tunnels.Port.Hola), 10),
strconv.FormatUint(uint64(tunnels.Port.Trial), 10),
strconv.FormatUint(uint64(tunnels.Port.TrialPeer), 10),
tunnels.Vendor[host]})
}
}
wr.Flush()
return 0
}
// Hop-by-hop headers. These are removed when sent to the backend. // Hop-by-hop headers. These are removed when sent to the backend.
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html // http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
var hopHeaders = []string{ var hopHeaders = []string{