mirror of
https://github.com/Alexey71/opera-proxy.git
synced 2026-05-14 06:30:59 +00:00
@@ -15,3 +15,4 @@
|
|||||||
# vendor/
|
# vendor/
|
||||||
bin/
|
bin/
|
||||||
*.snap
|
*.snap
|
||||||
|
opera-proxy
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ eu3.sec-tunnel.com,77.111.244.22,443
|
|||||||
| list-countries | - | list available countries and exit |
|
| list-countries | - | list available countries and exit |
|
||||||
| list-proxies | - | output proxy list and exit |
|
| list-proxies | - | output proxy list and exit |
|
||||||
| proxy | String | sets base proxy to use for all dial-outs. Format: `<http\|https\|socks5\|socks5h>://[login:password@]host[:port]` Examples: `http://user:password@192.168.1.1:3128`, `socks5://10.0.0.1:1080` |
|
| proxy | String | sets base proxy to use for all dial-outs. Format: `<http\|https\|socks5\|socks5h>://[login:password@]host[:port]` Examples: `http://user:password@192.168.1.1:3128`, `socks5://10.0.0.1:1080` |
|
||||||
|
| refresh | Duration | login refresh interval (default 4h0m0s) |
|
||||||
| timeout | Duration | timeout for network operations (default 10s) |
|
| timeout | Duration | timeout for network operations (default 10s) |
|
||||||
| verbosity | Number | logging verbosity (10 - debug, 20 - info, 30 - warning, 40 - error, 50 - critical) (default 20) |
|
| verbosity | Number | logging verbosity (10 - debug, 20 - info, 30 - warning, 40 - error, 50 - critical) (default 20) |
|
||||||
| version | - | show program version and exit |
|
| version | - | show program version and exit |
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ import (
|
|||||||
|
|
||||||
type FixedDialer struct {
|
type FixedDialer struct {
|
||||||
fixedAddress string
|
fixedAddress string
|
||||||
next ContextDialer
|
next ContextDialer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFixedDialer(address string, next ContextDialer) *FixedDialer {
|
func NewFixedDialer(address string, next ContextDialer) *FixedDialer {
|
||||||
return &FixedDialer{
|
return &FixedDialer{
|
||||||
fixedAddress: address,
|
fixedAddress: address,
|
||||||
next: next,
|
next: next,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ go 1.16
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/AdguardTeam/dnsproxy v0.36.0
|
github.com/AdguardTeam/dnsproxy v0.36.0
|
||||||
github.com/Snawoot/go-http-digest-auth-client v1.0.0
|
github.com/Snawoot/go-http-digest-auth-client v1.1.3
|
||||||
github.com/miekg/dns v1.1.35
|
github.com/miekg/dns v1.1.35
|
||||||
golang.org/x/net v0.0.0-20210324205630-d1beb07c2056
|
golang.org/x/net v0.0.0-20210324205630-d1beb07c2056
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKU
|
|||||||
github.com/AdguardTeam/golibs v0.4.4 h1:cM9UySQiYFW79zo5XRwnaIWVzfW4eNXmZktMrWbthpw=
|
github.com/AdguardTeam/golibs v0.4.4 h1:cM9UySQiYFW79zo5XRwnaIWVzfW4eNXmZktMrWbthpw=
|
||||||
github.com/AdguardTeam/golibs v0.4.4/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
github.com/AdguardTeam/golibs v0.4.4/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/Snawoot/go-http-digest-auth-client v1.0.0 h1:1xNR4muFtT+PjP1Z2QAZG0fooszF8nWuYv6QlTKgi1E=
|
github.com/Snawoot/go-http-digest-auth-client v1.1.3 h1:Xd/SNBuIUJqotzmxRpbXovBJxmlVZOT19IZZdMdrJ0Q=
|
||||||
github.com/Snawoot/go-http-digest-auth-client v1.0.0/go.mod h1:WiwNiPXTRGyjTGpBtSQJlM2wDPRRPpFGhMkMWpV4uqg=
|
github.com/Snawoot/go-http-digest-auth-client v1.1.3/go.mod h1:WiwNiPXTRGyjTGpBtSQJlM2wDPRRPpFGhMkMWpV4uqg=
|
||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
|
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
API_DOMAIN = "api.sec-tunnel.com"
|
API_DOMAIN = "api.sec-tunnel.com"
|
||||||
PROXY_SUFFIX = "sec-tunnel.com"
|
PROXY_SUFFIX = "sec-tunnel.com"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -54,6 +54,7 @@ type CLIArgs struct {
|
|||||||
apiPassword string
|
apiPassword string
|
||||||
apiAddress string
|
apiAddress string
|
||||||
bootstrapDNS string
|
bootstrapDNS string
|
||||||
|
refresh time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func parse_args() CLIArgs {
|
func parse_args() CLIArgs {
|
||||||
@@ -74,8 +75,9 @@ func parse_args() CLIArgs {
|
|||||||
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.bootstrapDNS, "bootstrap-dns", "",
|
flag.StringVar(&args.bootstrapDNS, "bootstrap-dns", "",
|
||||||
"DNS/DoH/DoT/DoQ resolver for initial discovering of SurfEasy API address. "+
|
"DNS/DoH/DoT/DoQ resolver for initial discovering of SurfEasy API address. "+
|
||||||
"See https://github.com/ameshkov/dnslookup/ for upstream DNS URL format. "+
|
"See https://github.com/ameshkov/dnslookup/ for upstream DNS URL format. "+
|
||||||
"Examples: https://1.1.1.1/dns-query, quic://dns.adguard.com")
|
"Examples: https://1.1.1.1/dns-query, quic://dns.adguard.com")
|
||||||
|
flag.DurationVar(&args.refresh, "refresh", 4*time.Hour, "login refresh interval")
|
||||||
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.")
|
||||||
@@ -227,10 +229,31 @@ func run() int {
|
|||||||
return 13
|
return 13
|
||||||
}
|
}
|
||||||
|
|
||||||
|
runTicker(context.Background(), args.refresh, func(ctx context.Context) {
|
||||||
|
mainLogger.Info("Refreshing login...")
|
||||||
|
reqCtx, cl := context.WithTimeout(ctx, args.timeout)
|
||||||
|
defer cl()
|
||||||
|
err := seclient.Login(reqCtx)
|
||||||
|
if err != nil {
|
||||||
|
mainLogger.Error("Login refresh failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mainLogger.Info("Login refreshed.")
|
||||||
|
|
||||||
|
mainLogger.Info("Refreshing device password...")
|
||||||
|
reqCtx, cl = context.WithTimeout(ctx, args.timeout)
|
||||||
|
defer cl()
|
||||||
|
err = seclient.DeviceGeneratePassword(reqCtx)
|
||||||
|
if err != nil {
|
||||||
|
mainLogger.Error("Device password refresh failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mainLogger.Info("Device password refreshed.")
|
||||||
|
})
|
||||||
|
|
||||||
endpoint := ips[0]
|
endpoint := ips[0]
|
||||||
authHdr := basic_auth_header(seclient.GetProxyCredentials())
|
|
||||||
auth := func() string {
|
auth := func() string {
|
||||||
return authHdr
|
return basic_auth_header(seclient.GetProxyCredentials())
|
||||||
}
|
}
|
||||||
|
|
||||||
handlerDialer := NewProxyDialer(endpoint.NetAddr(), fmt.Sprintf("%s0.%s", args.country, PROXY_SUFFIX), auth, dialer)
|
handlerDialer := NewProxyDialer(endpoint.NetAddr(), fmt.Sprintf("%s0.%s", args.country, PROXY_SUFFIX), auth, dialer)
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package seclient
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/cookiejar"
|
||||||
|
"net/url"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"golang.org/x/net/publicsuffix"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StdJar struct {
|
||||||
|
jar *cookiejar.Jar
|
||||||
|
mux sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStdJar() (*StdJar, error) {
|
||||||
|
var jar StdJar
|
||||||
|
|
||||||
|
err := jar.Reset()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &jar, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *StdJar) SetCookies(u *url.URL, cookies []*http.Cookie) {
|
||||||
|
j.mux.RLock()
|
||||||
|
j.jar.SetCookies(u, cookies)
|
||||||
|
j.mux.RUnlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *StdJar) Cookies(u *url.URL) []*http.Cookie {
|
||||||
|
j.mux.RLock()
|
||||||
|
c := j.jar.Cookies(u)
|
||||||
|
j.mux.RUnlock()
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *StdJar) Reset() error {
|
||||||
|
jar, err := cookiejar.New(&cookiejar.Options{
|
||||||
|
PublicSuffixList: publicsuffix.List,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
j.mux.Lock()
|
||||||
|
j.jar = jar
|
||||||
|
j.mux.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -62,6 +62,15 @@ type SERegisterDeviceResponse struct {
|
|||||||
Status SEStatusPair `json:"return_code"`
|
Status SEStatusPair `json:"return_code"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SEDeviceGeneratePasswordData struct {
|
||||||
|
DevicePassword string `json:"device_password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SEDeviceGeneratePasswordResponse struct {
|
||||||
|
Data SEDeviceGeneratePasswordData `json:"data"`
|
||||||
|
Status SEStatusPair `json:"return_code"`
|
||||||
|
}
|
||||||
|
|
||||||
type SEGeoEntry struct {
|
type SEGeoEntry struct {
|
||||||
Country string `json:"country,omitempty"`
|
Country string `json:"country,omitempty"`
|
||||||
CountryCode string `json:"country_code"`
|
CountryCode string `json:"country_code"`
|
||||||
@@ -94,3 +103,5 @@ type SEDiscoverResponse struct {
|
|||||||
} `json:"data"`
|
} `json:"data"`
|
||||||
Status SEStatusPair `json:"return_code"`
|
Status SEStatusPair `json:"return_code"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SESubscriberLoginResponse SERegisterSubscriberResponse
|
||||||
|
|||||||
+165
-136
@@ -8,12 +8,11 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/cookiejar"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
dac "github.com/Snawoot/go-http-digest-auth-client"
|
dac "github.com/Snawoot/go-http-digest-auth-client"
|
||||||
"golang.org/x/net/publicsuffix"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -24,19 +23,21 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type SEEndpoints struct {
|
type SEEndpoints struct {
|
||||||
RegisterSubscriber string
|
RegisterSubscriber string
|
||||||
SubscriberLogin string
|
SubscriberLogin string
|
||||||
RegisterDevice string
|
RegisterDevice string
|
||||||
GeoList string
|
DeviceGeneratePassword string
|
||||||
Discover string
|
GeoList string
|
||||||
|
Discover string
|
||||||
}
|
}
|
||||||
|
|
||||||
var DefaultSEEndpoints = SEEndpoints{
|
var DefaultSEEndpoints = SEEndpoints{
|
||||||
RegisterSubscriber: "https://api.sec-tunnel.com/v4/register_subscriber",
|
RegisterSubscriber: "https://api.sec-tunnel.com/v4/register_subscriber",
|
||||||
SubscriberLogin: "https://api.sec-tunnel.com/v4/subscriber_login",
|
SubscriberLogin: "https://api.sec-tunnel.com/v4/subscriber_login",
|
||||||
RegisterDevice: "https://api.sec-tunnel.com/v4/register_device",
|
RegisterDevice: "https://api.sec-tunnel.com/v4/register_device",
|
||||||
GeoList: "https://api.sec-tunnel.com/v4/geo_list",
|
DeviceGeneratePassword: "https://api.sec-tunnel.com/v4/device_generate_password",
|
||||||
Discover: "https://api.sec-tunnel.com/v4/discover",
|
GeoList: "https://api.sec-tunnel.com/v4/geo_list",
|
||||||
|
Discover: "https://api.sec-tunnel.com/v4/discover",
|
||||||
}
|
}
|
||||||
|
|
||||||
type SESettings struct {
|
type SESettings struct {
|
||||||
@@ -58,7 +59,7 @@ var DefaultSESettings = SESettings{
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SEClient struct {
|
type SEClient struct {
|
||||||
HttpClient *http.Client
|
httpClient *http.Client
|
||||||
Settings SESettings
|
Settings SESettings
|
||||||
SubscriberEmail string
|
SubscriberEmail string
|
||||||
SubscriberPassword string
|
SubscriberPassword string
|
||||||
@@ -66,9 +67,12 @@ type SEClient struct {
|
|||||||
AssignedDeviceID string
|
AssignedDeviceID string
|
||||||
AssignedDeviceIDHash string
|
AssignedDeviceIDHash string
|
||||||
DevicePassword string
|
DevicePassword string
|
||||||
|
Mux sync.Mutex
|
||||||
rng *rand.Rand
|
rng *rand.Rand
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type StrKV map[string]string
|
||||||
|
|
||||||
// Instantiates SurfEasy client with default settings and given API keys.
|
// Instantiates SurfEasy client with default settings and given API keys.
|
||||||
// Optional `transport` parameter allows to override HTTP transport used
|
// Optional `transport` parameter allows to override HTTP transport used
|
||||||
// for HTTP calls
|
// for HTTP calls
|
||||||
@@ -77,13 +81,6 @@ func NewSEClient(apiUsername, apiSecret string, transport http.RoundTripper) (*S
|
|||||||
transport = http.DefaultTransport
|
transport = http.DefaultTransport
|
||||||
}
|
}
|
||||||
|
|
||||||
jar, err := cookiejar.New(&cookiejar.Options{
|
|
||||||
PublicSuffixList: publicsuffix.List,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rng := rand.New(RandomSource)
|
rng := rand.New(RandomSource)
|
||||||
|
|
||||||
device_id, err := randomCapitalHexString(rng, DEVICE_ID_BYTES)
|
device_id, err := randomCapitalHexString(rng, DEVICE_ID_BYTES)
|
||||||
@@ -91,18 +88,38 @@ func NewSEClient(apiUsername, apiSecret string, transport http.RoundTripper) (*S
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &SEClient{
|
jar, err := NewStdJar()
|
||||||
HttpClient: &http.Client{
|
if err != nil {
|
||||||
Transport: dac.NewDigestTransport(apiUsername, apiSecret, transport),
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := &SEClient{
|
||||||
|
httpClient: &http.Client{
|
||||||
Jar: jar,
|
Jar: jar,
|
||||||
|
Transport: dac.NewDigestTransport(apiUsername, apiSecret, transport),
|
||||||
},
|
},
|
||||||
Settings: DefaultSESettings,
|
Settings: DefaultSESettings,
|
||||||
rng: rng,
|
rng: rng,
|
||||||
DeviceID: device_id,
|
DeviceID: device_id,
|
||||||
}, nil
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SEClient) ResetCookies() error {
|
||||||
|
c.Mux.Lock()
|
||||||
|
defer c.Mux.Unlock()
|
||||||
|
return c.resetCookies()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SEClient) resetCookies() error {
|
||||||
|
return (c.httpClient.Jar.(*StdJar)).Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *SEClient) AnonRegister(ctx context.Context) error {
|
func (c *SEClient) AnonRegister(ctx context.Context) error {
|
||||||
|
c.Mux.Lock()
|
||||||
|
defer c.Mux.Unlock()
|
||||||
|
|
||||||
localPart, err := randomEmailLocalPart(c.rng)
|
localPart, err := randomEmailLocalPart(c.rng)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -111,40 +128,26 @@ func (c *SEClient) AnonRegister(ctx context.Context) error {
|
|||||||
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)
|
||||||
|
|
||||||
return c.Register(ctx)
|
return c.register(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *SEClient) Register(ctx context.Context) error {
|
func (c *SEClient) Register(ctx context.Context) error {
|
||||||
registerInput := url.Values{
|
c.Mux.Lock()
|
||||||
"email": {c.SubscriberEmail},
|
defer c.Mux.Unlock()
|
||||||
"password": {c.SubscriberPassword},
|
return c.register(ctx)
|
||||||
}
|
}
|
||||||
req, err := http.NewRequestWithContext(
|
|
||||||
ctx,
|
|
||||||
"POST",
|
|
||||||
c.Settings.Endpoints.RegisterSubscriber,
|
|
||||||
strings.NewReader(registerInput.Encode()),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.populateRequest(req)
|
|
||||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
||||||
req.Header.Set("Accept", "application/json")
|
|
||||||
|
|
||||||
resp, err := c.HttpClient.Do(req)
|
func (c *SEClient) register(ctx context.Context) error {
|
||||||
|
err := c.resetCookies()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return fmt.Errorf("bad http status: %s", resp.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
decoder := json.NewDecoder(resp.Body)
|
|
||||||
var regRes SERegisterSubscriberResponse
|
var regRes SERegisterSubscriberResponse
|
||||||
err = decoder.Decode(®Res)
|
err = c.rpcCall(ctx, c.Settings.Endpoints.RegisterSubscriber, StrKV{
|
||||||
cleanupBody(resp.Body)
|
"email": c.SubscriberEmail,
|
||||||
|
"password": c.SubscriberPassword,
|
||||||
|
}, ®Res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -157,38 +160,15 @@ func (c *SEClient) Register(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *SEClient) RegisterDevice(ctx context.Context) error {
|
func (c *SEClient) RegisterDevice(ctx context.Context) error {
|
||||||
registerDeviceInput := url.Values{
|
c.Mux.Lock()
|
||||||
"client_type": {c.Settings.ClientType},
|
defer c.Mux.Unlock()
|
||||||
"device_hash": {c.DeviceID},
|
|
||||||
"device_name": {c.Settings.DeviceName},
|
|
||||||
}
|
|
||||||
req, err := http.NewRequestWithContext(
|
|
||||||
ctx,
|
|
||||||
"POST",
|
|
||||||
c.Settings.Endpoints.RegisterDevice,
|
|
||||||
strings.NewReader(registerDeviceInput.Encode()),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.populateRequest(req)
|
|
||||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
||||||
req.Header.Set("Accept", "application/json")
|
|
||||||
|
|
||||||
resp, err := c.HttpClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return fmt.Errorf("bad http status: %s", resp.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
decoder := json.NewDecoder(resp.Body)
|
|
||||||
var regRes SERegisterDeviceResponse
|
var regRes SERegisterDeviceResponse
|
||||||
err = decoder.Decode(®Res)
|
err := c.rpcCall(ctx, c.Settings.Endpoints.RegisterDevice, StrKV{
|
||||||
cleanupBody(resp.Body)
|
"client_type": c.Settings.ClientType,
|
||||||
|
"device_hash": c.DeviceID,
|
||||||
|
"device_name": c.Settings.DeviceName,
|
||||||
|
}, ®Res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -205,36 +185,13 @@ func (c *SEClient) RegisterDevice(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *SEClient) GeoList(ctx context.Context) ([]SEGeoEntry, error) {
|
func (c *SEClient) GeoList(ctx context.Context) ([]SEGeoEntry, error) {
|
||||||
geoListInput := url.Values{
|
c.Mux.Lock()
|
||||||
"device_id": {c.AssignedDeviceIDHash},
|
defer c.Mux.Unlock()
|
||||||
}
|
|
||||||
req, err := http.NewRequestWithContext(
|
|
||||||
ctx,
|
|
||||||
"POST",
|
|
||||||
c.Settings.Endpoints.GeoList,
|
|
||||||
strings.NewReader(geoListInput.Encode()),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c.populateRequest(req)
|
|
||||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
||||||
req.Header.Set("Accept", "application/json")
|
|
||||||
|
|
||||||
resp, err := c.HttpClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return nil, fmt.Errorf("bad http status: %s", resp.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
decoder := json.NewDecoder(resp.Body)
|
|
||||||
var geoListRes SEGeoListResponse
|
var geoListRes SEGeoListResponse
|
||||||
err = decoder.Decode(&geoListRes)
|
err := c.rpcCall(ctx, c.Settings.Endpoints.GeoList, StrKV{
|
||||||
cleanupBody(resp.Body)
|
"device_id": c.AssignedDeviceIDHash,
|
||||||
|
}, &geoListRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -248,37 +205,14 @@ func (c *SEClient) GeoList(ctx context.Context) ([]SEGeoEntry, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *SEClient) Discover(ctx context.Context, requestedGeo string) ([]SEIPEntry, error) {
|
func (c *SEClient) Discover(ctx context.Context, requestedGeo string) ([]SEIPEntry, error) {
|
||||||
geoListInput := url.Values{
|
c.Mux.Lock()
|
||||||
"serial_no": {c.AssignedDeviceIDHash},
|
defer c.Mux.Unlock()
|
||||||
"requested_geo": {requestedGeo},
|
|
||||||
}
|
|
||||||
req, err := http.NewRequestWithContext(
|
|
||||||
ctx,
|
|
||||||
"POST",
|
|
||||||
c.Settings.Endpoints.Discover,
|
|
||||||
strings.NewReader(geoListInput.Encode()),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c.populateRequest(req)
|
|
||||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
||||||
req.Header.Set("Accept", "application/json")
|
|
||||||
|
|
||||||
resp, err := c.HttpClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return nil, fmt.Errorf("bad http status: %s", resp.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
decoder := json.NewDecoder(resp.Body)
|
|
||||||
var discoverRes SEDiscoverResponse
|
var discoverRes SEDiscoverResponse
|
||||||
err = decoder.Decode(&discoverRes)
|
err := c.rpcCall(ctx, c.Settings.Endpoints.Discover, StrKV{
|
||||||
cleanupBody(resp.Body)
|
"serial_no": c.AssignedDeviceIDHash,
|
||||||
|
"requested_geo": requestedGeo,
|
||||||
|
}, &discoverRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -291,7 +225,57 @@ func (c *SEClient) Discover(ctx context.Context, requestedGeo string) ([]SEIPEnt
|
|||||||
return discoverRes.Data.IPs, nil
|
return discoverRes.Data.IPs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *SEClient) Login(ctx context.Context) error {
|
||||||
|
c.Mux.Lock()
|
||||||
|
defer c.Mux.Unlock()
|
||||||
|
|
||||||
|
err := c.resetCookies()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var loginRes SESubscriberLoginResponse
|
||||||
|
err = c.rpcCall(ctx, c.Settings.Endpoints.SubscriberLogin, StrKV{
|
||||||
|
"login": c.SubscriberEmail,
|
||||||
|
"password": c.SubscriberPassword,
|
||||||
|
"client_type": c.Settings.ClientType,
|
||||||
|
}, &loginRes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if loginRes.Status.Code != SE_STATUS_OK {
|
||||||
|
return fmt.Errorf("API responded with error message: code=%d, msg=\"%s\"",
|
||||||
|
loginRes.Status.Code, loginRes.Status.Message)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SEClient) DeviceGeneratePassword(ctx context.Context) error {
|
||||||
|
c.Mux.Lock()
|
||||||
|
defer c.Mux.Unlock()
|
||||||
|
|
||||||
|
var genRes SEDeviceGeneratePasswordResponse
|
||||||
|
err := c.rpcCall(ctx, c.Settings.Endpoints.DeviceGeneratePassword, StrKV{
|
||||||
|
"device_id": c.AssignedDeviceID,
|
||||||
|
}, &genRes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if genRes.Status.Code != SE_STATUS_OK {
|
||||||
|
return fmt.Errorf("API responded with error message: code=%d, msg=\"%s\"",
|
||||||
|
genRes.Status.Code, genRes.Status.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.DevicePassword = genRes.Data.DevicePassword
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *SEClient) GetProxyCredentials() (string, string) {
|
func (c *SEClient) GetProxyCredentials() (string, string) {
|
||||||
|
c.Mux.Lock()
|
||||||
|
defer c.Mux.Unlock()
|
||||||
|
|
||||||
return c.AssignedDeviceIDHash, c.DevicePassword
|
return c.AssignedDeviceIDHash, c.DevicePassword
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,6 +285,51 @@ func (c *SEClient) populateRequest(req *http.Request) {
|
|||||||
req.Header["User-Agent"] = []string{c.Settings.UserAgent}
|
req.Header["User-Agent"] = []string{c.Settings.UserAgent}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *SEClient) RpcCall(ctx context.Context, endpoint string, params map[string]string, res interface{}) error {
|
||||||
|
c.Mux.Lock()
|
||||||
|
defer c.Mux.Unlock()
|
||||||
|
|
||||||
|
return c.rpcCall(ctx, endpoint, params, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SEClient) rpcCall(ctx context.Context, endpoint string, params map[string]string, res interface{}) error {
|
||||||
|
input := make(url.Values)
|
||||||
|
for k, v := range params {
|
||||||
|
input[k] = []string{v}
|
||||||
|
}
|
||||||
|
req, err := http.NewRequestWithContext(
|
||||||
|
ctx,
|
||||||
|
"POST",
|
||||||
|
endpoint,
|
||||||
|
strings.NewReader(input.Encode()),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.populateRequest(req)
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
req.Header.Set("Accept", "application/json")
|
||||||
|
|
||||||
|
resp, err := c.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("bad http status: %s, headers: %#v", resp.Status, resp.Header)
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder := json.NewDecoder(resp.Body)
|
||||||
|
err = decoder.Decode(res)
|
||||||
|
cleanupBody(resp.Body)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Does cleanup of HTTP response in order to make it reusable by keep-alive
|
// Does cleanup of HTTP response in order to make it reusable by keep-alive
|
||||||
// logic of HTTP client
|
// logic of HTTP client
|
||||||
func cleanupBody(body io.ReadCloser) {
|
func cleanupBody(body io.ReadCloser) {
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const COPY_BUF = 128 * 1024
|
const (
|
||||||
|
COPY_BUF = 128 * 1024
|
||||||
|
WALLCLOCK_PRECISION = 1 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
func basic_auth_header(login, password string) string {
|
func basic_auth_header(login, password string) string {
|
||||||
return "Basic " + base64.StdEncoding.EncodeToString(
|
return "Basic " + base64.StdEncoding.EncodeToString(
|
||||||
@@ -143,3 +146,40 @@ func copyBody(wr io.Writer, body io.Reader) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AfterWallClock(d time.Duration) <-chan time.Time {
|
||||||
|
ch := make(chan time.Time, 1)
|
||||||
|
deadline := time.Now().Add(d).Truncate(0)
|
||||||
|
after_ch := time.After(d)
|
||||||
|
ticker := time.NewTicker(WALLCLOCK_PRECISION)
|
||||||
|
go func() {
|
||||||
|
var t time.Time
|
||||||
|
defer ticker.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case t = <-after_ch:
|
||||||
|
ch <- t
|
||||||
|
return
|
||||||
|
case t = <-ticker.C:
|
||||||
|
if t.After(deadline) {
|
||||||
|
ch <- t
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTicker(ctx context.Context, interval time.Duration, cb func(context.Context)) {
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-AfterWallClock(interval):
|
||||||
|
cb(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user