diff --git a/handler.go b/handler.go index 278866b..cb544f1 100644 --- a/handler.go +++ b/handler.go @@ -17,8 +17,7 @@ type ProxyHandler struct { httptransport http.RoundTripper } -func NewProxyHandler(dialer ContextDialer, resolver *Resolver, logger *CondLogger) *ProxyHandler { - dialer = NewRetryDialer(dialer, resolver, logger) +func NewProxyHandler(dialer ContextDialer, logger *CondLogger) *ProxyHandler { httptransport := &http.Transport{ MaxIdleConns: 100, IdleConnTimeout: 90 * time.Second, diff --git a/main.go b/main.go index 1034f03..03a4833 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,8 @@ package main import ( + "encoding/csv" + "context" "errors" "flag" "fmt" @@ -12,6 +14,8 @@ import ( "time" xproxy "golang.org/x/net/proxy" + + se "github.com/Snawoot/opera-proxy/seclient" ) var ( @@ -32,20 +36,24 @@ func arg_fail(msg string) { type CLIArgs struct { country string - bind_address string + listCountries bool + listProxies bool + bindAddress string verbosity int timeout time.Duration resolver string showVersion bool proxy string + apiLogin string + apiPassword string } func parse_args() CLIArgs { var args CLIArgs 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.list_proxies, "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.BoolVar(&args.listCountries, "list-countries", false, "list available countries and exit") + flag.BoolVar(&args.listProxies, "list-proxies", false, "output proxy list and exit") + flag.StringVar(&args.bindAddress, "bind-address", "127.0.0.1:18080", "HTTP proxy listen address") 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") @@ -56,11 +64,13 @@ func parse_args() CLIArgs { flag.StringVar(&args.proxy, "proxy", "", "sets base proxy to use for all dial-outs. "+ "Format: ://[login:password@]host[:port] "+ "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() if args.country == "" { 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") } return args @@ -92,6 +102,8 @@ func run() int { log.LstdFlags|log.Lshortfile), args.verbosity) + mainLogger.Info("opera-proxy client version %s is starting...", version) + var dialer ContextDialer = &net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, @@ -112,33 +124,139 @@ func run() int { dialer = pxDialer.(ContextDialer) } - if args.list_countries { - return print_countries(args.timeout) - } - if args.list_proxies { - return print_proxies(args.country, args.proxy_type, args.limit, args.timeout) - } - - mainLogger.Info("opera-proxy client version %s is starting...", version) - mainLogger.Info("Constructing fallback DNS upstream...") - resolver, err := NewResolver(args.resolver, args.timeout) + seclient, err := se.NewSEClient(args.apiLogin, args.apiPassword, &http.Transport{ + DialContext: dialer.DialContext, + ForceAttemptHTTP2: true, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + }) if err != nil { - mainLogger.Critical("Unable to instantiate DNS resolver: %v", err) - return 6 + mainLogger.Critical("Unable to construct SEClient: %v", err) + return 8 } - // TODO: get creds here - handlerDialer := NewProxyDialer(endpoint.NetAddr(), endpoint.TLSName, auth, dialer) - mainLogger.Info("Endpoint: %s", endpoint.URL().String()) + ctx, cl := context.WithTimeout(context.Background(), args.timeout) + err = seclient.AnonRegister(ctx) + 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...") handler := NewProxyHandler(handlerDialer, proxyLogger) 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.Info("Shutting down...") 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() { os.Exit(run()) } diff --git a/seclient/messages.go b/seclient/messages.go index 9c1d59a..af0f39c 100644 --- a/seclient/messages.go +++ b/seclient/messages.go @@ -2,6 +2,8 @@ package seclient import ( "encoding/json" + "net" + "fmt" "errors" "strconv" ) @@ -78,6 +80,14 @@ type SEIPEntry struct { 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 { Data struct { IPs []SEIPEntry `json:"ips"` diff --git a/seclient/seclient.go b/seclient/seclient.go index c98205b..929d9fa 100644 --- a/seclient/seclient.go +++ b/seclient/seclient.go @@ -291,6 +291,10 @@ func (c *SEClient) Discover(ctx context.Context, requestedGeo string) ([]SEIPEnt return discoverRes.Data.IPs, nil } +func (c *SEClient) GetProxyCredentials() (string, string) { + return c.AssignedDeviceIDHash, c.DevicePassword +} + func (c *SEClient) populateRequest(req *http.Request) { req.Header["SE-Client-Version"] = []string{c.Settings.ClientVersion} req.Header["SE-Operating-System"] = []string{c.Settings.OperatingSystem} diff --git a/utils.go b/utils.go index df487fd..83a71d0 100644 --- a/utils.go +++ b/utils.go @@ -4,16 +4,10 @@ import ( "bufio" "context" "encoding/base64" - "encoding/csv" "errors" - "fmt" "io" "net" "net/http" - "net/url" - "os" - "strconv" - "strings" "sync" "time" ) @@ -81,80 +75,6 @@ func proxyh2(ctx context.Context, leftreader io.ReadCloser, leftwriter io.Writer 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. // http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html var hopHeaders = []string{