Compare commits

...

17 Commits

Author SHA1 Message Date
Vladislav Yarmak
6bfb8d0aee bump snap version 2021-03-16 00:57:34 +02:00
Snawoot
4faf6aa04b Merge pull request #35 from Snawoot/skip_agent_resolve
Skip upstream agent resolve
2021-03-16 00:56:47 +02:00
Vladislav Yarmak
edd723079d fix endpoint display 2021-03-16 00:49:59 +02:00
Vladislav Yarmak
3cb79059b2 skip upstream agent resolve 2021-03-16 00:46:49 +02:00
Snawoot
3b09f31616 Merge pull request #34 from Snawoot/makefile_upd
makefile: use dynamic linking for native target
2021-03-15 15:45:56 +02:00
Vladislav Yarmak
8d9285c00b makefile: use dynamic linking for native target 2021-03-15 15:42:20 +02:00
Vladislav Yarmak
528e2b2a71 bump snap version 2021-03-15 04:13:04 +02:00
Snawoot
0a473f9662 Merge pull request #32 from Snawoot/fallback
feat: Hola API communication failover
2021-03-15 04:12:15 +02:00
Vladislav Yarmak
17860682be do not resolve fallback agent IP addr, use provided in config 2021-03-15 04:07:03 +02:00
Vladislav Yarmak
1f6c87a797 fix typos 2021-03-15 02:21:23 +02:00
Vladislav Yarmak
72beef10c9 feat: Hola API communication failover 2021-03-15 02:13:42 +02:00
Vladislav Yarmak
ead89d5245 snap: remove unnecessary go-importpath 2021-03-13 06:32:48 +02:00
Vladislav Yarmak
6edd098c82 embed version into snap image 2021-03-13 06:30:34 +02:00
Vladislav Yarmak
752d2ba789 fix dockerfile version embedding 2021-03-13 05:28:49 +02:00
Snawoot
311d1ad74d Merge pull request #30 from Snawoot/docker_builds
embed version into docker image
2021-03-13 05:14:54 +02:00
Vladislav Yarmak
6ac04587cb embed version into docker image 2021-03-13 05:14:19 +02:00
Vladislav Yarmak
8c3538ab4c bump snap version 2021-03-13 04:42:20 +02:00
10 changed files with 395 additions and 79 deletions

View File

@@ -1,8 +1,10 @@
FROM golang AS build
ARG GIT_DESC=undefined
WORKDIR /go/src/github.com/Snawoot/hola-proxy
COPY . .
RUN CGO_ENABLED=0 go build -a -tags netgo -ldflags '-s -w -extldflags "-static"'
RUN CGO_ENABLED=0 go build -a -tags netgo -ldflags '-s -w -extldflags "-static" -X main.version='"$GIT_DESC"
ADD https://curl.haxx.se/ca/cacert.pem /certs.crt
RUN chmod 0644 /certs.crt

View File

@@ -3,6 +3,7 @@ OUTSUFFIX = bin/$(PROGNAME)
VERSION := $(shell git describe)
BUILDOPTS = -a -tags netgo
LDFLAGS = -ldflags '-s -w -extldflags "-static" -X main.version=$(VERSION)'
LDFLAGS_NATIVE = -ldflags '-s -w -X main.version=$(VERSION)'
src = $(wildcard *.go)
@@ -24,7 +25,7 @@ bin-windows-amd64: $(OUTSUFFIX).windows-amd64.exe
bin-windows-386: $(OUTSUFFIX).windows-386.exe
$(OUTSUFFIX): $(src)
CGO_ENABLED=0 go build $(BUILDOPTS) $(LDFLAGS) -o $@
go build $(LDFLAGS_NATIVE) -o $@
$(OUTSUFFIX).linux-amd64: $(src)
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build $(BUILDOPTS) $(LDFLAGS) -o $@

View File

@@ -2,12 +2,12 @@ package main
import (
"context"
"net/http"
"sync"
"time"
)
const DEFAULT_LIST_LIMIT = 3
const API_CALL_ATTEMPTS = 3
func CredService(interval, timeout time.Duration,
country string,
@@ -24,15 +24,21 @@ func CredService(interval, timeout time.Duration,
return
}
for i := 0; i < API_CALL_ATTEMPTS; i++ {
ctx, _ := context.WithTimeout(context.Background(), timeout)
tunnels, user_uuid, err = Tunnels(ctx, country, proxytype, DEFAULT_LIST_LIMIT)
if err == nil {
break
tx_res, tx_err := EnsureTransaction(context.Background(), timeout, func(ctx context.Context, client *http.Client) bool {
tunnels, user_uuid, err = Tunnels(ctx, client, country, proxytype, DEFAULT_LIST_LIMIT)
if err != nil {
logger.Error("Configuration bootstrap error: %v. Retrying with the fallback mechanism...", err)
return false
}
return true
})
if tx_err != nil {
logger.Critical("Transaction recovery mechanism failure: %v", tx_err)
err = tx_err
return
}
if err != nil {
logger.Critical("Configuration bootstrap failed: %v", err)
if !tx_res {
logger.Critical("All attempts failed.")
return
}
auth_header = basic_auth_header(LOGIN_PREFIX+user_uuid,
@@ -48,23 +54,28 @@ func CredService(interval, timeout time.Duration,
for {
<-ticker.C
logger.Info("Rotating credentials...")
for i := 0; i < API_CALL_ATTEMPTS; i++ {
ctx, _ := context.WithTimeout(context.Background(), timeout)
tuns, user_uuid, err = Tunnels(ctx, country, proxytype, DEFAULT_LIST_LIMIT)
if err == nil {
break
tx_res, tx_err := EnsureTransaction(context.Background(), timeout, func(ctx context.Context, client *http.Client) bool {
tuns, user_uuid, err = Tunnels(ctx, client, country, proxytype, DEFAULT_LIST_LIMIT)
if err != nil {
logger.Error("Credential rotation error: %v. Retrying with the fallback mechanism...", err)
return false
}
return true
})
if tx_err != nil {
logger.Critical("Transaction recovery mechanism failure: %v", tx_err)
err = tx_err
continue
}
if err != nil {
logger.Error("Credential rotation failed after %d attempts. Error: %v",
API_CALL_ATTEMPTS, err)
} else {
(&mux).Lock()
auth_header = basic_auth_header(LOGIN_PREFIX+user_uuid,
tuns.AgentKey)
(&mux).Unlock()
logger.Info("Credentials rotated successfully.")
if !tx_res {
logger.Critical("All rotation attempts failed.")
continue
}
(&mux).Lock()
auth_header = basic_auth_header(LOGIN_PREFIX+user_uuid,
tuns.AgentKey)
(&mux).Unlock()
logger.Info("Credentials rotated successfully.")
}
}()
return

23
csrand.go Normal file
View File

@@ -0,0 +1,23 @@
package main
import (
crand "crypto/rand"
"math/big"
)
type secureRandomSource struct{}
var RandomSource secureRandomSource
var int63Limit = big.NewInt(0).Lsh(big.NewInt(1), 63)
func (_ secureRandomSource) Seed(_ int64) {
}
func (_ secureRandomSource) Int63() int64 {
randNum, err := crand.Int(crand.Reader, int63Limit)
if err != nil {
panic(err)
}
return randNum.Int64()
}

View File

@@ -2,36 +2,51 @@ package main
import (
"bufio"
"context"
"crypto/tls"
"fmt"
"io"
"net"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"time"
)
type AuthProvider func() string
type ProxyHandler struct {
auth AuthProvider
upstream string
upstreamAddr string
tlsName string
logger *CondLogger
dialer *net.Dialer
httptransport http.RoundTripper
resolver *Resolver
}
func NewProxyHandler(upstream string, auth AuthProvider, resolver *Resolver, logger *CondLogger) *ProxyHandler {
proxyurl, err := url.Parse("https://" + upstream)
if err != nil {
panic(err)
func NewProxyHandler(upstream *Endpoint, auth AuthProvider, resolver *Resolver, logger *CondLogger) *ProxyHandler {
dialer := &net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}
netaddr := net.JoinHostPort(upstream.Host, fmt.Sprintf("%d", upstream.Port))
httptransport := &http.Transport{
Proxy: http.ProxyURL(proxyurl),
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
Proxy: http.ProxyURL(upstream.URL()),
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
return dialer.DialContext(ctx, "tcp", netaddr)
},
}
return &ProxyHandler{
auth: auth,
upstream: upstream,
upstreamAddr: netaddr,
tlsName: upstream.TLSName,
logger: logger,
dialer: dialer,
httptransport: httptransport,
resolver: resolver,
}
@@ -48,17 +63,25 @@ func (s *ProxyHandler) ServeHTTP(wr http.ResponseWriter, req *http.Request) {
return
}
conn, err := tls.Dial("tcp", s.upstream, nil)
conn, err := s.dialer.DialContext(req.Context(), "tcp", s.upstreamAddr)
if err != nil {
s.logger.Error("Can't dial tls upstream: %v", err)
http.Error(wr, "Can't dial tls upstream", http.StatusBadGateway)
s.logger.Error("Can't dial upstream: %v", err)
http.Error(wr, "Can't dial upstream", http.StatusBadGateway)
return
}
defer conn.Close()
if s.tlsName != "" {
conn = tls.Client(conn, &tls.Config{
ServerName: s.tlsName,
})
defer conn.Close()
}
_, err = conn.Write(rawreq)
if err != nil {
s.logger.Error("Can't write tls upstream: %v", err)
http.Error(wr, "Can't write tls upstream", http.StatusBadGateway)
s.logger.Error("Can't write upstream: %v", err)
http.Error(wr, "Can't write upstream", http.StatusBadGateway)
return
}
bufrd := bufio.NewReader(conn)
@@ -74,14 +97,22 @@ func (s *ProxyHandler) ServeHTTP(wr http.ResponseWriter, req *http.Request) {
proxyResp.Header.Get("X-Hola-Error") == "Forbidden Host" {
s.logger.Info("Request %s denied by upstream. Rescuing it with resolve&rewrite workaround.",
req.URL.String())
conn.Close()
conn, err = tls.Dial("tcp", s.upstream, nil)
conn, err = s.dialer.DialContext(req.Context(), "tcp", s.upstreamAddr)
if err != nil {
s.logger.Error("Can't dial tls upstream: %v", err)
http.Error(wr, "Can't dial tls upstream", http.StatusBadGateway)
s.logger.Error("Can't dial upstream: %v", err)
http.Error(wr, "Can't dial upstream", http.StatusBadGateway)
return
}
defer conn.Close()
if s.tlsName != "" {
conn = tls.Client(conn, &tls.Config{
ServerName: s.tlsName,
})
defer conn.Close()
}
err = rewriteConnectReq(req, s.resolver)
if err != nil {
s.logger.Error("Can't rewrite request: %v", err)
@@ -101,7 +132,6 @@ func (s *ProxyHandler) ServeHTTP(wr http.ResponseWriter, req *http.Request) {
return
}
} else {
defer conn.Close()
responseBytes, err = httputil.DumpResponse(proxyResp, false)
if err != nil {
s.logger.Error("Can't dump response: %v", err)
@@ -160,8 +190,8 @@ func (s *ProxyHandler) ServeHTTP(wr http.ResponseWriter, req *http.Request) {
proxyReq.Header.Set("Proxy-Authorization", s.auth())
rawreq, _ := httputil.DumpRequest(proxyReq, false)
// Prepare upstream TLS conn
conn, err := tls.Dial("tcp", s.upstream, nil)
// Prepare upstream conn
conn, err := s.dialer.DialContext(req.Context(), "tcp", s.upstreamAddr)
if err != nil {
s.logger.Error("Can't dial tls upstream: %v", err)
http.Error(wr, "Can't dial tls upstream", http.StatusBadGateway)
@@ -169,6 +199,13 @@ func (s *ProxyHandler) ServeHTTP(wr http.ResponseWriter, req *http.Request) {
}
defer conn.Close()
if s.tlsName != "" {
conn = tls.Client(conn, &tls.Config{
ServerName: s.tlsName,
})
defer conn.Close()
}
// Send proxy request
_, err = conn.Write(rawreq)
if err != nil {

View File

@@ -3,17 +3,22 @@ package main
import (
"bytes"
"context"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"github.com/campoy/unique"
"github.com/google/uuid"
"io/ioutil"
"math/rand"
"net"
"net/http"
"net/url"
"strconv"
"sync"
"time"
"github.com/campoy/unique"
"github.com/google/uuid"
)
const USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 Safari/537.36"
@@ -25,6 +30,8 @@ const VPN_COUNTRIES_URL = CCGI_URL + "vpn_countries.json"
const BG_INIT_URL = CCGI_URL + "background_init"
const ZGETTUNNELS_URL = CCGI_URL + "zgettunnels"
const LOGIN_PREFIX = "user-uuid-"
const FALLBACK_CONF_URL = "https://www.dropbox.com/s/jemizcvpmf2qb9v/cloud_failover.conf?dl=1"
const AGENT_SUFFIX = ".hola.org"
var TemporaryBanError = errors.New("temporary ban detected")
var PermanentBanError = errors.New("permanent ban detected")
@@ -57,11 +64,66 @@ type ZGetTunnelsResponse struct {
Ztun map[string][]string `json:"ztun"`
}
func do_req(ctx context.Context, method, url string, query, data url.Values) ([]byte, error) {
type FallbackAgent struct {
Name string `json:"name"`
IP string `json:"ip"`
Port uint16 `json:"port"`
}
type fallbackConfResponse struct {
Agents []FallbackAgent `json:"agents"`
UpdatedAt int64 `json:"updated_ts"`
TTL int64 `json:"ttl_ms"`
}
type FallbackConfig struct {
Agents []FallbackAgent
UpdatedAt time.Time
TTL time.Duration
}
func (c *FallbackConfig) UnmarshalJSON(data []byte) error {
r := fallbackConfResponse{}
err := json.Unmarshal(data, &r)
if err != nil {
return err
}
c.Agents = r.Agents
c.UpdatedAt = time.Unix(r.UpdatedAt/1000, (r.UpdatedAt%1000)*1000000)
c.TTL = time.Duration(r.TTL * 1000000)
return nil
}
func (c *FallbackConfig) Expired() bool {
return time.Now().After(c.UpdatedAt.Add(c.TTL))
}
func (c *FallbackConfig) ShuffleAgents() {
rand.New(RandomSource).Shuffle(len(c.Agents), func(i, j int) {
c.Agents[i], c.Agents[j] = c.Agents[j], c.Agents[i]
})
}
func (c *FallbackConfig) Clone() *FallbackConfig {
return &FallbackConfig{
Agents: append([]FallbackAgent(nil), c.Agents...),
UpdatedAt: c.UpdatedAt,
TTL: c.TTL,
}
}
func (a *FallbackAgent) ToProxy() *url.URL {
return &url.URL{
Scheme: "https",
Host: net.JoinHostPort(a.Name+AGENT_SUFFIX,
fmt.Sprintf("%d", a.Port)),
}
}
func do_req(ctx context.Context, client *http.Client, method, url string, query, data url.Values) ([]byte, error) {
var (
client http.Client
req *http.Request
err error
req *http.Request
err error
)
if method == "" {
method = "GET"
@@ -101,10 +163,10 @@ func do_req(ctx context.Context, method, url string, query, data url.Values) ([]
return body, nil
}
func VPNCountries(ctx context.Context) (res CountryList, err error) {
func VPNCountries(ctx context.Context, client *http.Client) (res CountryList, err error) {
params := make(url.Values)
params.Add("browser", EXT_BROWSER)
data, err := do_req(ctx, "", VPN_COUNTRIES_URL, params, nil)
data, err := do_req(ctx, client, "", VPN_COUNTRIES_URL, params, nil)
if err != nil {
return nil, err
}
@@ -119,13 +181,13 @@ func VPNCountries(ctx context.Context) (res CountryList, err error) {
return
}
func background_init(ctx context.Context, user_uuid string) (res BgInitResponse, reterr error) {
func background_init(ctx context.Context, client *http.Client, user_uuid string) (res BgInitResponse, reterr error) {
post_data := make(url.Values)
post_data.Add("login", "1")
post_data.Add("ver", EXT_VER)
qs := make(url.Values)
qs.Add("uuid", user_uuid)
resp, err := do_req(ctx, "POST", BG_INIT_URL, qs, post_data)
resp, err := do_req(ctx, client, "POST", BG_INIT_URL, qs, post_data)
if err != nil {
reterr = err
return
@@ -143,6 +205,7 @@ func background_init(ctx context.Context, user_uuid string) (res BgInitResponse,
}
func zgettunnels(ctx context.Context,
client *http.Client,
user_uuid string,
session_key int64,
country string,
@@ -163,14 +226,14 @@ func zgettunnels(ctx context.Context,
params.Add("country", country)
}
params.Add("limit", strconv.FormatInt(int64(limit), 10))
params.Add("ping_id", strconv.FormatFloat(rand.Float64(), 'f', -1, 64))
params.Add("ping_id", strconv.FormatFloat(rand.New(RandomSource).Float64(), 'f', -1, 64))
params.Add("ext_ver", EXT_VER)
params.Add("browser", EXT_BROWSER)
params.Add("product", PRODUCT)
params.Add("uuid", user_uuid)
params.Add("session_key", strconv.FormatInt(session_key, 10))
params.Add("is_premium", "0")
data, err := do_req(ctx, "", ZGETTUNNELS_URL, params, nil)
data, err := do_req(ctx, client, "", ZGETTUNNELS_URL, params, nil)
if err != nil {
reterr = err
return
@@ -180,17 +243,137 @@ func zgettunnels(ctx context.Context,
return
}
func fetchFallbackConfig(ctx context.Context) (*FallbackConfig, error) {
confRaw, err := do_req(ctx, &http.Client{}, "", FALLBACK_CONF_URL, nil, nil)
if err != nil {
return nil, err
}
l := len(confRaw)
if l < 4 {
return nil, errors.New("bad response length from fallback conf URL")
}
buf := &bytes.Buffer{}
buf.Grow(l)
buf.Write(confRaw[l-3:])
buf.Write(confRaw[:l-3])
b64dec := base64.NewDecoder(base64.RawStdEncoding, buf)
jdec := json.NewDecoder(b64dec)
fbc := &FallbackConfig{}
err = jdec.Decode(fbc)
if err != nil {
return nil, err
}
if fbc.Expired() {
return nil, errors.New("fetched expired fallback config")
}
fbc.ShuffleAgents()
return fbc, nil
}
var (
fbcMux sync.Mutex
cachedFBC *FallbackConfig
)
func GetFallbackProxies(ctx context.Context) (*FallbackConfig, error) {
fbcMux.Lock()
defer fbcMux.Unlock()
var (
fbc *FallbackConfig
err error
)
if cachedFBC == nil || cachedFBC.Expired() {
fbc, err = fetchFallbackConfig(ctx)
if err != nil {
return nil, err
}
cachedFBC = fbc
} else {
fbc = cachedFBC
}
return fbc.Clone(), nil
}
func Tunnels(ctx context.Context,
client *http.Client,
country string,
proxy_type string,
limit uint) (res *ZGetTunnelsResponse, user_uuid string, reterr error) {
u := uuid.New()
user_uuid = hex.EncodeToString(u[:])
initres, err := background_init(ctx, user_uuid)
initres, err := background_init(ctx, client, user_uuid)
if err != nil {
reterr = err
return
}
res, reterr = zgettunnels(ctx, user_uuid, initres.Key, country, proxy_type, limit)
res, reterr = zgettunnels(ctx, client, user_uuid, initres.Key, country, proxy_type, limit)
return
}
// Returns default http client with a proxy override
func httpClientWithProxy(agent *FallbackAgent) *http.Client {
t := &http.Transport{
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
dialer := &net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}
if agent == nil {
t.DialContext = dialer.DialContext
} else {
t.Proxy = http.ProxyURL(agent.ToProxy())
addr := net.JoinHostPort(agent.IP, fmt.Sprintf("%d", agent.Port))
t.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) {
return dialer.DialContext(ctx, "tcp", addr)
}
}
return &http.Client{
Transport: t,
}
}
func EnsureTransaction(baseCtx context.Context, txnTimeout time.Duration, txn func(context.Context, *http.Client) bool) (bool, error) {
client := httpClientWithProxy(nil)
defer client.CloseIdleConnections()
ctx, cancel := context.WithTimeout(baseCtx, txnTimeout)
defer cancel()
if txn(ctx, client) {
return true, nil
}
// Fallback needed
fbc, err := GetFallbackProxies(baseCtx)
if err != nil {
return false, err
}
for _, agent := range fbc.Agents {
client = httpClientWithProxy(&agent)
defer client.CloseIdleConnections()
ctx, cancel = context.WithTimeout(baseCtx, txnTimeout)
defer cancel()
if txn(ctx, client) {
return true, nil
}
}
return false, nil
}

2
hooks/build Normal file
View File

@@ -0,0 +1,2 @@
#!/bin/bash
docker build --build-arg GIT_DESC="$(git describe)" -f "$DOCKERFILE_PATH" -t "$IMAGE_NAME" .

View File

@@ -126,7 +126,7 @@ func run() int {
logWriter.Close()
return 5
}
mainLogger.Info("Endpoint: %s", endpoint)
mainLogger.Info("Endpoint: %s", endpoint.URL().String())
mainLogger.Info("Starting proxy server...")
handler := NewProxyHandler(endpoint, auth, resolver, proxyLogger)
mainLogger.Info("Init complete.")

View File

@@ -1,5 +1,5 @@
name: hola-proxy
version: '1.4.2'
version: '1.4.5'
summary: Standalone Hola proxy client.
description: |
Standalone Hola proxy client. Just run it and it'll start plain HTTP proxy server forwarding traffic via Hola proxies of your choice.
@@ -10,15 +10,18 @@ base: core18
parts:
hola-proxy:
plugin: go
go-importpath: github.com/Snawoot/hola-proxy
source: https://github.com/Snawoot/hola-proxy
source-type: git
source: .
build-packages:
- gcc
override-build:
make &&
cp bin/hola-proxy "$SNAPCRAFT_PART_INSTALL"
stage:
- hola-proxy
apps:
hola-proxy:
command: bin/hola-proxy
command: hola-proxy
plugs:
- network
- network-bind

View File

@@ -18,6 +18,26 @@ import (
"time"
)
type Endpoint struct {
Host string
Port uint16
TLSName string
}
func (e *Endpoint) URL() *url.URL {
if e.TLSName == "" {
return &url.URL{
Scheme: "http",
Host: net.JoinHostPort(e.Host, fmt.Sprintf("%d", e.Port)),
}
} else {
return &url.URL{
Scheme: "https",
Host: net.JoinHostPort(e.TLSName, fmt.Sprintf("%d", e.Port)),
}
}
}
func basic_auth_header(login, password string) string {
return "basic " + base64.StdEncoding.EncodeToString(
[]byte(login+":"+password))
@@ -50,10 +70,24 @@ func proxy(ctx context.Context, left, right net.Conn) {
}
func print_countries(timeout time.Duration) int {
ctx, _ := context.WithTimeout(context.Background(), timeout)
countries, err := VPNCountries(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
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 {
@@ -63,10 +97,25 @@ func print_countries(timeout time.Duration) int {
}
func print_proxies(country string, proxy_type string, limit uint, timeout time.Duration) int {
ctx, _ := context.WithTimeout(context.Background(), timeout)
tunnels, user_uuid, err := Tunnels(ctx, country, proxy_type, limit)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
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)
@@ -94,14 +143,15 @@ func print_proxies(country string, proxy_type string, limit uint, timeout time.D
return 0
}
func get_endpoint(tunnels *ZGetTunnelsResponse, typ string, trial bool, force_port_field string) (string, error) {
var hostname string
for k := range tunnels.IPList {
func get_endpoint(tunnels *ZGetTunnelsResponse, typ string, trial bool, force_port_field string) (*Endpoint, error) {
var hostname, ip string
for k, v := range tunnels.IPList {
hostname = k
ip = v
break
}
if hostname == "" {
return "", errors.New("No tunnels found in API response")
if hostname == "" || ip == "" {
return nil, errors.New("No tunnels found in API response")
}
var port uint16
@@ -128,10 +178,14 @@ func get_endpoint(tunnels *ZGetTunnelsResponse, typ string, trial bool, force_po
port = tunnels.Port.Peer
}
} else {
return "", errors.New("Unsupported port type")
return nil, errors.New("Unsupported port type")
}
}
return net.JoinHostPort(hostname, strconv.FormatUint(uint64(port), 10)), nil
return &Endpoint{
Host: ip,
Port: port,
TLSName: hostname,
}, nil
}
// Hop-by-hop headers. These are removed when sent to the backend.