Add new virt proxy type (unknown use but used by extension for some sites) + gofmt everything

This commit is contained in:
rany
2021-03-07 19:57:46 +02:00
parent 12b05ac0df
commit a106ef9e56
10 changed files with 1037 additions and 1036 deletions

View File

@@ -153,6 +153,8 @@ Usage of /home/user/go/bin/hola-proxy:
HTTP proxy listen address (default "127.0.0.1:8080")
-country string
desired proxy location (default "us")
-dont-use-trial
use regular ports instead of trial ports (default true)
-limit uint
amount of proxies in retrieved list (default 3)
-list-countries
@@ -160,15 +162,13 @@ Usage of /home/user/go/bin/hola-proxy:
-list-proxies
output proxy list and exit
-proxy-type string
proxy type: direct or peer or lum (default "direct")
proxy type: direct or peer or lum or virt or pool (default "direct")
-resolver string
DNS/DoH/DoT resolver to workaround Hola blocked hosts. See https://github.com/ameshkov/dnslookup/ for upstream DNS URL format. (default "https://cloudflare-dns.com/dns-query")
-rotate duration
rotate user ID once per given period (default 1h0m0s)
-timeout duration
timeout for network operations (default 10s)
-use-trial
use trial ports instead of regular ports
-verbosity int
logging verbosity (10 - debug, 20 - info, 30 - warning, 40 - error, 50 - critical) (default 20)
```

View File

@@ -1,58 +1,58 @@
package main
import (
"log"
"fmt"
"fmt"
"log"
)
const (
CRITICAL = 50
ERROR = 40
WARNING = 30
INFO = 20
DEBUG = 10
NOTSET = 0
CRITICAL = 50
ERROR = 40
WARNING = 30
INFO = 20
DEBUG = 10
NOTSET = 0
)
type CondLogger struct {
logger *log.Logger
verbosity int
logger *log.Logger
verbosity int
}
func (cl *CondLogger) Log(verb int, format string, v ...interface{}) error {
if verb >= cl.verbosity {
return cl.logger.Output(2, fmt.Sprintf(format, v...))
}
return nil
if verb >= cl.verbosity {
return cl.logger.Output(2, fmt.Sprintf(format, v...))
}
return nil
}
func (cl *CondLogger) log(verb int, format string, v ...interface{}) error {
if verb >= cl.verbosity {
return cl.logger.Output(3, fmt.Sprintf(format, v...))
}
return nil
if verb >= cl.verbosity {
return cl.logger.Output(3, fmt.Sprintf(format, v...))
}
return nil
}
func (cl *CondLogger) Critical(s string, v ...interface{}) error {
return cl.log(CRITICAL, "CRITICAL " + s, v...)
return cl.log(CRITICAL, "CRITICAL "+s, v...)
}
func (cl *CondLogger) Error(s string, v ...interface{}) error {
return cl.log(ERROR, "ERROR " + s, v...)
return cl.log(ERROR, "ERROR "+s, v...)
}
func (cl *CondLogger) Warning(s string, v ...interface{}) error {
return cl.log(WARNING, "WARNING " + s, v...)
return cl.log(WARNING, "WARNING "+s, v...)
}
func (cl *CondLogger) Info(s string, v ...interface{}) error {
return cl.log(INFO, "INFO " + s, v...)
return cl.log(INFO, "INFO "+s, v...)
}
func (cl *CondLogger) Debug(s string, v ...interface{}) error {
return cl.log(DEBUG, "DEBUG " + s, v...)
return cl.log(DEBUG, "DEBUG "+s, v...)
}
func NewCondLogger(logger *log.Logger, verbosity int) *CondLogger {
return &CondLogger{verbosity: verbosity, logger: logger}
return &CondLogger{verbosity: verbosity, logger: logger}
}

View File

@@ -1,71 +1,71 @@
package main
import (
"time"
"sync"
"context"
"context"
"sync"
"time"
)
const DEFAULT_LIST_LIMIT = 3
const API_CALL_ATTEMPTS = 3
func CredService(interval, timeout time.Duration,
country string,
proxytype string,
logger *CondLogger) (auth AuthProvider,
tunnels *ZGetTunnelsResponse,
err error) {
var mux sync.Mutex
var auth_header, user_uuid string
auth = func () (res string) {
(&mux).Lock()
res = auth_header
(&mux).Unlock()
return
}
country string,
proxytype string,
logger *CondLogger) (auth AuthProvider,
tunnels *ZGetTunnelsResponse,
err error) {
var mux sync.Mutex
var auth_header, user_uuid string
auth = func() (res string) {
(&mux).Lock()
res = auth_header
(&mux).Unlock()
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
}
}
if err != nil {
logger.Critical("Configuration bootstrap failed: %v", err)
return
}
auth_header = basic_auth_header(LOGIN_PREFIX + user_uuid,
tunnels.AgentKey)
go func() {
var (
err error
tuns *ZGetTunnelsResponse
user_uuid string
)
ticker := time.NewTicker(interval)
defer ticker.Stop()
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
}
}
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.")
}
}
}()
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
}
}
if err != nil {
logger.Critical("Configuration bootstrap failed: %v", err)
return
}
auth_header = basic_auth_header(LOGIN_PREFIX+user_uuid,
tunnels.AgentKey)
go func() {
var (
err error
tuns *ZGetTunnelsResponse
user_uuid string
)
ticker := time.NewTicker(interval)
defer ticker.Stop()
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
}
}
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.")
}
}
}()
return
}

View File

@@ -1,220 +1,220 @@
package main
import (
"io"
"net/http"
"net/http/httputil"
"crypto/tls"
"strings"
"net/url"
"bufio"
"bufio"
"crypto/tls"
"io"
"net/http"
"net/http/httputil"
"net/url"
"strings"
)
type AuthProvider func() string
type ProxyHandler struct {
auth AuthProvider
upstream string
logger *CondLogger
httptransport http.RoundTripper
resolver *Resolver
auth AuthProvider
upstream string
logger *CondLogger
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)
}
proxyurl, err := url.Parse("https://" + upstream)
if err != nil {
panic(err)
}
httptransport := &http.Transport{
Proxy: http.ProxyURL(proxyurl),
}
return &ProxyHandler{
auth: auth,
upstream: upstream,
logger: logger,
httptransport: httptransport,
resolver: resolver,
}
Proxy: http.ProxyURL(proxyurl),
}
return &ProxyHandler{
auth: auth,
upstream: upstream,
logger: logger,
httptransport: httptransport,
resolver: resolver,
}
}
func (s *ProxyHandler) ServeHTTP(wr http.ResponseWriter, req *http.Request) {
s.logger.Info("Request: %v %v %v", req.RemoteAddr, req.Method, req.URL)
if strings.ToUpper(req.Method) == "CONNECT" {
req.Header.Set("Proxy-Authorization", s.auth())
rawreq, err := httputil.DumpRequest(req, false)
if err != nil {
s.logger.Error("Can't dump request: %v", err)
http.Error(wr, "Can't dump request", http.StatusInternalServerError)
return
}
if strings.ToUpper(req.Method) == "CONNECT" {
req.Header.Set("Proxy-Authorization", s.auth())
rawreq, err := httputil.DumpRequest(req, false)
if err != nil {
s.logger.Error("Can't dump request: %v", err)
http.Error(wr, "Can't dump request", http.StatusInternalServerError)
return
}
conn, err := tls.Dial("tcp", s.upstream, nil)
if err != nil {
s.logger.Error("Can't dial tls upstream: %v", err)
http.Error(wr, "Can't dial tls upstream", http.StatusBadGateway)
return
}
conn, err := tls.Dial("tcp", s.upstream, nil)
if err != nil {
s.logger.Error("Can't dial tls upstream: %v", err)
http.Error(wr, "Can't dial tls upstream", http.StatusBadGateway)
return
}
_, 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)
return
}
bufrd := bufio.NewReader(conn)
proxyResp, err := http.ReadResponse(bufrd, req)
responseBytes := make([]byte, 0)
if err != nil {
s.logger.Error("Can't read response from upstream: %v", err)
http.Error(wr, "Can't read response from upstream", http.StatusBadGateway)
return
}
_, 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)
return
}
bufrd := bufio.NewReader(conn)
proxyResp, err := http.ReadResponse(bufrd, req)
responseBytes := make([]byte, 0)
if err != nil {
s.logger.Error("Can't read response from upstream: %v", err)
http.Error(wr, "Can't read response from upstream", http.StatusBadGateway)
return
}
if (proxyResp.StatusCode == http.StatusForbidden &&
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)
if err != nil {
s.logger.Error("Can't dial tls upstream: %v", err)
http.Error(wr, "Can't dial tls upstream", http.StatusBadGateway)
return
}
defer conn.Close()
err = rewriteConnectReq(req, s.resolver)
if err != nil {
s.logger.Error("Can't rewrite request: %v", err)
http.Error(wr, "Can't rewrite request", http.StatusInternalServerError)
return
}
rawreq, err = httputil.DumpRequest(req, false)
if err != nil {
s.logger.Error("Can't dump request: %v", err)
http.Error(wr, "Can't dump request", http.StatusInternalServerError)
return
}
_, 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)
return
}
} else {
defer conn.Close()
responseBytes, err = httputil.DumpResponse(proxyResp, false)
if err != nil {
s.logger.Error("Can't dump response: %v", err)
http.Error(wr, "Can't dump response", http.StatusInternalServerError)
return
}
buffered := bufrd.Buffered()
if buffered > 0 {
trailer := make([]byte, buffered)
bufrd.Read(trailer)
responseBytes = append(responseBytes, trailer...)
}
}
bufrd = nil
if proxyResp.StatusCode == http.StatusForbidden &&
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)
if err != nil {
s.logger.Error("Can't dial tls upstream: %v", err)
http.Error(wr, "Can't dial tls upstream", http.StatusBadGateway)
return
}
defer conn.Close()
err = rewriteConnectReq(req, s.resolver)
if err != nil {
s.logger.Error("Can't rewrite request: %v", err)
http.Error(wr, "Can't rewrite request", http.StatusInternalServerError)
return
}
rawreq, err = httputil.DumpRequest(req, false)
if err != nil {
s.logger.Error("Can't dump request: %v", err)
http.Error(wr, "Can't dump request", http.StatusInternalServerError)
return
}
_, 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)
return
}
} else {
defer conn.Close()
responseBytes, err = httputil.DumpResponse(proxyResp, false)
if err != nil {
s.logger.Error("Can't dump response: %v", err)
http.Error(wr, "Can't dump response", http.StatusInternalServerError)
return
}
buffered := bufrd.Buffered()
if buffered > 0 {
trailer := make([]byte, buffered)
bufrd.Read(trailer)
responseBytes = append(responseBytes, trailer...)
}
}
bufrd = nil
// Upgrade client connection
localconn, _, err := hijack(wr)
if err != nil {
s.logger.Error("Can't hijack client connection: %v", err)
http.Error(wr, "Can't hijack client connection", http.StatusInternalServerError)
return
}
defer localconn.Close()
// Upgrade client connection
localconn, _, err := hijack(wr)
if err != nil {
s.logger.Error("Can't hijack client connection: %v", err)
http.Error(wr, "Can't hijack client connection", http.StatusInternalServerError)
return
}
defer localconn.Close()
if len(responseBytes) > 0 {
_, err = localconn.Write(responseBytes)
if err != nil {
return
}
}
proxy(req.Context(), localconn, conn)
} else {
delHopHeaders(req.Header)
orig_req := req.Clone(req.Context())
req.RequestURI = ""
req.Header.Set("Proxy-Authorization", s.auth())
resp, err := s.httptransport.RoundTrip(req)
if err != nil {
s.logger.Error("HTTP fetch error: %v", err)
http.Error(wr, "Server Error", http.StatusInternalServerError)
return
}
if (resp.StatusCode == http.StatusForbidden &&
resp.Header.Get("X-Hola-Error") == "Forbidden Host") {
s.logger.Info("Request %s denied by upstream. Rescuing it with resolve&tunnel workaround.",
req.URL.String())
resp.Body.Close()
if len(responseBytes) > 0 {
_, err = localconn.Write(responseBytes)
if err != nil {
return
}
}
proxy(req.Context(), localconn, conn)
} else {
delHopHeaders(req.Header)
orig_req := req.Clone(req.Context())
req.RequestURI = ""
req.Header.Set("Proxy-Authorization", s.auth())
resp, err := s.httptransport.RoundTrip(req)
if err != nil {
s.logger.Error("HTTP fetch error: %v", err)
http.Error(wr, "Server Error", http.StatusInternalServerError)
return
}
if resp.StatusCode == http.StatusForbidden &&
resp.Header.Get("X-Hola-Error") == "Forbidden Host" {
s.logger.Info("Request %s denied by upstream. Rescuing it with resolve&tunnel workaround.",
req.URL.String())
resp.Body.Close()
// Prepare tunnel request
proxyReq, err := makeConnReq(orig_req.RequestURI, s.resolver)
if err != nil {
s.logger.Error("Can't rewrite request: %v", err)
http.Error(wr, "Can't rewrite request", http.StatusBadGateway)
return
}
proxyReq.Header.Set("Proxy-Authorization", s.auth())
rawreq, _ := httputil.DumpRequest(proxyReq, false)
// Prepare tunnel request
proxyReq, err := makeConnReq(orig_req.RequestURI, s.resolver)
if err != nil {
s.logger.Error("Can't rewrite request: %v", err)
http.Error(wr, "Can't rewrite request", http.StatusBadGateway)
return
}
proxyReq.Header.Set("Proxy-Authorization", s.auth())
rawreq, _ := httputil.DumpRequest(proxyReq, false)
// Prepare upstream TLS conn
conn, err := tls.Dial("tcp", s.upstream, nil)
if err != nil {
s.logger.Error("Can't dial tls upstream: %v", err)
http.Error(wr, "Can't dial tls upstream", http.StatusBadGateway)
return
}
defer conn.Close()
// Prepare upstream TLS conn
conn, err := tls.Dial("tcp", s.upstream, nil)
if err != nil {
s.logger.Error("Can't dial tls upstream: %v", err)
http.Error(wr, "Can't dial tls upstream", http.StatusBadGateway)
return
}
defer conn.Close()
// Send proxy request
_, 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)
return
}
// Send proxy request
_, 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)
return
}
// Read proxy response
bufrd := bufio.NewReader(conn)
proxyResp, err := http.ReadResponse(bufrd, proxyReq)
if err != nil {
s.logger.Error("Can't read response from upstream: %v", err)
http.Error(wr, "Can't read response from upstream", http.StatusBadGateway)
return
}
if proxyResp.StatusCode != http.StatusOK {
delHopHeaders(proxyResp.Header)
copyHeader(wr.Header(), proxyResp.Header)
wr.WriteHeader(proxyResp.StatusCode)
}
// Read proxy response
bufrd := bufio.NewReader(conn)
proxyResp, err := http.ReadResponse(bufrd, proxyReq)
if err != nil {
s.logger.Error("Can't read response from upstream: %v", err)
http.Error(wr, "Can't read response from upstream", http.StatusBadGateway)
return
}
if proxyResp.StatusCode != http.StatusOK {
delHopHeaders(proxyResp.Header)
copyHeader(wr.Header(), proxyResp.Header)
wr.WriteHeader(proxyResp.StatusCode)
}
// Send tunneled request
orig_req.RequestURI = ""
orig_req.Header.Set("Connection", "close")
rawreq, _ = httputil.DumpRequest(orig_req, false)
_, 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)
return
}
// Send tunneled request
orig_req.RequestURI = ""
orig_req.Header.Set("Connection", "close")
rawreq, _ = httputil.DumpRequest(orig_req, false)
_, 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)
return
}
// Read tunneled response
resp, err = http.ReadResponse(bufrd, orig_req)
if err != nil {
s.logger.Error("Can't read response from upstream: %v", err)
http.Error(wr, "Can't read response from upstream", http.StatusBadGateway)
return
}
}
defer resp.Body.Close()
s.logger.Info("%v %v %v %v", req.RemoteAddr, req.Method, req.URL, resp.Status)
delHopHeaders(resp.Header)
copyHeader(wr.Header(), resp.Header)
wr.WriteHeader(resp.StatusCode)
io.Copy(wr, resp.Body)
}
// Read tunneled response
resp, err = http.ReadResponse(bufrd, orig_req)
if err != nil {
s.logger.Error("Can't read response from upstream: %v", err)
http.Error(wr, "Can't read response from upstream", http.StatusBadGateway)
return
}
}
defer resp.Body.Close()
s.logger.Info("%v %v %v %v", req.RemoteAddr, req.Method, req.URL, resp.Status)
delHopHeaders(resp.Header)
copyHeader(wr.Header(), resp.Header)
wr.WriteHeader(resp.StatusCode)
io.Copy(wr, resp.Body)
}
}

View File

@@ -1,17 +1,17 @@
package main
import (
"context"
"net/http"
"net/url"
"io/ioutil"
"encoding/json"
"encoding/hex"
"github.com/google/uuid"
"bytes"
"strconv"
"math/rand"
"github.com/campoy/unique"
"bytes"
"context"
"encoding/hex"
"encoding/json"
"github.com/campoy/unique"
"github.com/google/uuid"
"io/ioutil"
"math/rand"
"net/http"
"net/url"
"strconv"
)
const USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 Safari/537.36"
@@ -27,148 +27,150 @@ const LOGIN_PREFIX = "user-uuid-"
type CountryList []string
type BgInitResponse struct {
Ver string `json:"ver"`
Key int64 `json:"key"`
Country string `json:"country"`
Ver string `json:"ver"`
Key int64 `json:"key"`
Country string `json:"country"`
}
type PortMap struct {
Direct uint16 `json:"direct"`
Hola uint16 `json:"hola"`
Peer uint16 `json:"peer"`
Trial uint16 `json:"trial"`
TrialPeer uint16 `json:"trial_peer"`
Direct uint16 `json:"direct"`
Hola uint16 `json:"hola"`
Peer uint16 `json:"peer"`
Trial uint16 `json:"trial"`
TrialPeer uint16 `json:"trial_peer"`
}
type ZGetTunnelsResponse struct {
AgentKey string `json:"agent_key"`
AgentTypes map[string]string `json:"agent_types"`
IPList map[string]string `json:"ip_list"`
Port PortMap `json:"port"`
Protocol map[string]string `json:"protocol"`
Vendor map[string]string `json:"vendor"`
Ztun map[string][]string `json:"ztun"`
AgentKey string `json:"agent_key"`
AgentTypes map[string]string `json:"agent_types"`
IPList map[string]string `json:"ip_list"`
Port PortMap `json:"port"`
Protocol map[string]string `json:"protocol"`
Vendor map[string]string `json:"vendor"`
Ztun map[string][]string `json:"ztun"`
}
func do_req(ctx context.Context, method, url string, query, data url.Values) ([]byte, error) {
var (
client http.Client
req *http.Request
err error
)
if method == "" {
method = "GET"
}
if data == nil {
req, err = http.NewRequestWithContext(ctx, method, url, nil)
} else {
req, err = http.NewRequestWithContext(ctx,
method,
url,
bytes.NewReader([]byte(data.Encode())))
}
if err != nil {
return nil, err
}
if data != nil {
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}
if query != nil {
req.URL.RawQuery = query.Encode()
}
req.Header.Set("User-Agent", USER_AGENT)
resp, err := client.Do(req)
if err != nil {
return nil, err
}
body, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
return nil, err
}
return body, nil
var (
client http.Client
req *http.Request
err error
)
if method == "" {
method = "GET"
}
if data == nil {
req, err = http.NewRequestWithContext(ctx, method, url, nil)
} else {
req, err = http.NewRequestWithContext(ctx,
method,
url,
bytes.NewReader([]byte(data.Encode())))
}
if err != nil {
return nil, err
}
if data != nil {
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}
if query != nil {
req.URL.RawQuery = query.Encode()
}
req.Header.Set("User-Agent", USER_AGENT)
resp, err := client.Do(req)
if err != nil {
return nil, err
}
body, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
return nil, err
}
return body, nil
}
func VPNCountries(ctx context.Context) (res CountryList, err error) {
params := make(url.Values)
params.Add("browser", EXT_BROWSER)
data, err := do_req(ctx, "", VPN_COUNTRIES_URL, params, nil)
if err != nil {
return nil, err
}
err = json.Unmarshal(data, &res)
for _, a := range res {
if a == "uk" {
res = append(res, "gb")
}
}
less := func(i, j int) bool { return res[i] < res[j] }
unique.Slice(&res, less)
return
params := make(url.Values)
params.Add("browser", EXT_BROWSER)
data, err := do_req(ctx, "", VPN_COUNTRIES_URL, params, nil)
if err != nil {
return nil, err
}
err = json.Unmarshal(data, &res)
for _, a := range res {
if a == "uk" {
res = append(res, "gb")
}
}
less := func(i, j int) bool { return res[i] < res[j] }
unique.Slice(&res, less)
return
}
func background_init(ctx context.Context, 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)
if err != nil {
reterr = err
return
}
reterr = json.Unmarshal(resp, &res)
return
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)
if err != nil {
reterr = err
return
}
reterr = json.Unmarshal(resp, &res)
return
}
func zgettunnels(ctx context.Context,
user_uuid string,
session_key int64,
country string,
proxy_type string,
limit uint) (res *ZGetTunnelsResponse, reterr error) {
var tunnels ZGetTunnelsResponse
params := make(url.Values)
if proxy_type == "lum" {
params.Add("country", country + ".pool_lum_" + country + "_shared")
} else if proxy_type == "peer" {
//params.Add("country", country + ".peer")
params.Add("country", country)
} else if proxy_type == "pool" {
params.Add("country", country + ".pool")
} else {
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("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)
if err != nil {
reterr = err
return
}
reterr = json.Unmarshal(data, &tunnels)
res = &tunnels
return
user_uuid string,
session_key int64,
country string,
proxy_type string,
limit uint) (res *ZGetTunnelsResponse, reterr error) {
var tunnels ZGetTunnelsResponse
params := make(url.Values)
if proxy_type == "lum" {
params.Add("country", country+".pool_lum_"+country+"_shared")
} else if proxy_type == "virt" { // seems to be for brazil and japan only
params.Add("country", country+".pool_virt_pool_"+country)
} else if proxy_type == "peer" {
//params.Add("country", country + ".peer")
params.Add("country", country)
} else if proxy_type == "pool" {
params.Add("country", country+".pool")
} else {
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("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)
if err != nil {
reterr = err
return
}
reterr = json.Unmarshal(data, &tunnels)
res = &tunnels
return
}
func Tunnels(ctx context.Context,
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)
if err != nil {
reterr = err
return
}
res, reterr = zgettunnels(ctx, user_uuid, initres.Key, country, proxy_type, limit)
return
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)
if err != nil {
reterr = err
return
}
res, reterr = zgettunnels(ctx, user_uuid, initres.Key, country, proxy_type, limit)
return
}

View File

@@ -3,252 +3,252 @@ package main
var ISO3166 map[string]string
func init() {
ISO3166 = map[string]string{
"AD": "Andorra",
"AE": "United Arab Emirates",
"AF": "Afghanistan",
"AG": "Antigua & Barbuda",
"AI": "Anguilla",
"AL": "Albania",
"AM": "Armenia",
"AN": "Netherlands Antilles",
"AO": "Angola",
"AQ": "Antarctica",
"AR": "Argentina",
"AS": "American Samoa",
"AT": "Austria",
"AU": "Australia",
"AW": "Aruba",
"AZ": "Azerbaijan",
"BA": "Bosnia and Herzegovina",
"BB": "Barbados",
"BD": "Bangladesh",
"BE": "Belgium",
"BF": "Burkina Faso",
"BG": "Bulgaria",
"BH": "Bahrain",
"BI": "Burundi",
"BJ": "Benin",
"BM": "Bermuda",
"BN": "Brunei Darussalam",
"BO": "Bolivia",
"BR": "Brazil",
"BS": "Bahama",
"BT": "Bhutan",
"BU": "Burma (no longer exists)",
"BV": "Bouvet Island",
"BW": "Botswana",
"BY": "Belarus",
"BZ": "Belize",
"CA": "Canada",
"CC": "Cocos (Keeling) Islands",
"CF": "Central African Republic",
"CG": "Congo",
"CH": "Switzerland",
"CI": "Cote D\"ivoire (Ivory Coast)",
"CK": "Cook Iislands",
"CL": "Chile",
"CM": "Cameroon",
"CN": "China",
"CO": "Colombia",
"CR": "Costa Rica",
"CS": "Czechoslovakia (no longer exists)",
"CU": "Cuba",
"CV": "Cape Verde",
"CX": "Christmas Island",
"CY": "Cyprus",
"CZ": "Czech Republic",
"DD": "German Democratic Republic (no longer exists)",
"DE": "Germany",
"DJ": "Djibouti",
"DK": "Denmark",
"DM": "Dominica",
"DO": "Dominican Republic",
"DZ": "Algeria",
"EC": "Ecuador",
"EE": "Estonia",
"EG": "Egypt",
"EH": "Western Sahara",
"ER": "Eritrea",
"ES": "Spain",
"ET": "Ethiopia",
"FI": "Finland",
"FJ": "Fiji",
"FK": "Falkland Islands (Malvinas)",
"FM": "Micronesia",
"FO": "Faroe Islands",
"FR": "France",
"FX": "France, Metropolitan",
"GA": "Gabon",
"GB": "United Kingdom (Great Britain)",
"GD": "Grenada",
"GE": "Georgia",
"GF": "French Guiana",
"GH": "Ghana",
"GI": "Gibraltar",
"GL": "Greenland",
"GM": "Gambia",
"GN": "Guinea",
"GP": "Guadeloupe",
"GQ": "Equatorial Guinea",
"GR": "Greece",
"GS": "South Georgia and the South Sandwich Islands",
"GT": "Guatemala",
"GU": "Guam",
"GW": "Guinea-Bissau",
"GY": "Guyana",
"HK": "Hong Kong",
"HM": "Heard & McDonald Islands",
"HN": "Honduras",
"HR": "Croatia",
"HT": "Haiti",
"HU": "Hungary",
"ID": "Indonesia",
"IE": "Ireland",
"IL": "Israel",
"IN": "India",
"IO": "British Indian Ocean Territory",
"IQ": "Iraq",
"IR": "Islamic Republic of Iran",
"IS": "Iceland",
"IT": "Italy",
"JM": "Jamaica",
"JO": "Jordan",
"JP": "Japan",
"KE": "Kenya",
"KG": "Kyrgyzstan",
"KH": "Cambodia",
"KI": "Kiribati",
"KM": "Comoros",
"KN": "St. Kitts and Nevis",
"KP": "Korea, Democratic People\"s Republic of",
"KR": "Korea, Republic of",
"KW": "Kuwait",
"KY": "Cayman Islands",
"KZ": "Kazakhstan",
"LA": "Lao People\"s Democratic Republic",
"LB": "Lebanon",
"LC": "Saint Lucia",
"LI": "Liechtenstein",
"LK": "Sri Lanka",
"LR": "Liberia",
"LS": "Lesotho",
"LT": "Lithuania",
"LU": "Luxembourg",
"LV": "Latvia",
"LY": "Libyan Arab Jamahiriya",
"MA": "Morocco",
"MC": "Monaco",
"MD": "Moldova, Republic of",
"MG": "Madagascar",
"MH": "Marshall Islands",
"ML": "Mali",
"MN": "Mongolia",
"MM": "Myanmar",
"MO": "Macau",
"MP": "Northern Mariana Islands",
"MQ": "Martinique",
"MR": "Mauritania",
"MS": "Monserrat",
"MT": "Malta",
"MU": "Mauritius",
"MV": "Maldives",
"MW": "Malawi",
"MX": "Mexico",
"MY": "Malaysia",
"MZ": "Mozambique",
"NA": "Namibia",
"NC": "New Caledonia",
"NE": "Niger",
"NF": "Norfolk Island",
"NG": "Nigeria",
"NI": "Nicaragua",
"NL": "Netherlands",
"NO": "Norway",
"NP": "Nepal",
"NR": "Nauru",
"NT": "Neutral Zone (no longer exists)",
"NU": "Niue",
"NZ": "New Zealand",
"OM": "Oman",
"PA": "Panama",
"PE": "Peru",
"PF": "French Polynesia",
"PG": "Papua New Guinea",
"PH": "Philippines",
"PK": "Pakistan",
"PL": "Poland",
"PM": "St. Pierre & Miquelon",
"PN": "Pitcairn",
"PR": "Puerto Rico",
"PT": "Portugal",
"PW": "Palau",
"PY": "Paraguay",
"QA": "Qatar",
"RE": "Reunion",
"RO": "Romania",
"RU": "Russian Federation",
"RW": "Rwanda",
"SA": "Saudi Arabia",
"SB": "Solomon Islands",
"SC": "Seychelles",
"SD": "Sudan",
"SE": "Sweden",
"SG": "Singapore",
"SH": "St. Helena",
"SI": "Slovenia",
"SJ": "Svalbard & Jan Mayen Islands",
"SK": "Slovakia",
"SL": "Sierra Leone",
"SM": "San Marino",
"SN": "Senegal",
"SO": "Somalia",
"SR": "Suriname",
"ST": "Sao Tome & Principe",
"SU": "Union of Soviet Socialist Republics (no longer exists)",
"SV": "El Salvador",
"SY": "Syrian Arab Republic",
"SZ": "Swaziland",
"TC": "Turks & Caicos Islands",
"TD": "Chad",
"TF": "French Southern Territories",
"TG": "Togo",
"TH": "Thailand",
"TJ": "Tajikistan",
"TK": "Tokelau",
"TM": "Turkmenistan",
"TN": "Tunisia",
"TO": "Tonga",
"TP": "East Timor",
"TR": "Turkey",
"TT": "Trinidad & Tobago",
"TV": "Tuvalu",
"TW": "Taiwan, Province of China",
"TZ": "Tanzania, United Republic of",
"UA": "Ukraine",
"UG": "Uganda",
"UK": "United Kingdom",
"UM": "United States Minor Outlying Islands",
"US": "United States of America",
"UY": "Uruguay",
"UZ": "Uzbekistan",
"VA": "Vatican City State (Holy See)",
"VC": "St. Vincent & the Grenadines",
"VE": "Venezuela",
"VG": "British Virgin Islands",
"VI": "United States Virgin Islands",
"VN": "Viet Nam",
"VU": "Vanuatu",
"WF": "Wallis & Futuna Islands",
"WS": "Samoa",
"YD": "Democratic Yemen (no longer exists)",
"YE": "Yemen",
"YT": "Mayotte",
"YU": "Yugoslavia",
"ZA": "South Africa",
"ZM": "Zambia",
"ZR": "Zaire",
"ZW": "Zimbabwe",
"ZZ": "Unknown or unspecified country",
}
ISO3166 = map[string]string{
"AD": "Andorra",
"AE": "United Arab Emirates",
"AF": "Afghanistan",
"AG": "Antigua & Barbuda",
"AI": "Anguilla",
"AL": "Albania",
"AM": "Armenia",
"AN": "Netherlands Antilles",
"AO": "Angola",
"AQ": "Antarctica",
"AR": "Argentina",
"AS": "American Samoa",
"AT": "Austria",
"AU": "Australia",
"AW": "Aruba",
"AZ": "Azerbaijan",
"BA": "Bosnia and Herzegovina",
"BB": "Barbados",
"BD": "Bangladesh",
"BE": "Belgium",
"BF": "Burkina Faso",
"BG": "Bulgaria",
"BH": "Bahrain",
"BI": "Burundi",
"BJ": "Benin",
"BM": "Bermuda",
"BN": "Brunei Darussalam",
"BO": "Bolivia",
"BR": "Brazil",
"BS": "Bahama",
"BT": "Bhutan",
"BU": "Burma (no longer exists)",
"BV": "Bouvet Island",
"BW": "Botswana",
"BY": "Belarus",
"BZ": "Belize",
"CA": "Canada",
"CC": "Cocos (Keeling) Islands",
"CF": "Central African Republic",
"CG": "Congo",
"CH": "Switzerland",
"CI": "Cote D\"ivoire (Ivory Coast)",
"CK": "Cook Iislands",
"CL": "Chile",
"CM": "Cameroon",
"CN": "China",
"CO": "Colombia",
"CR": "Costa Rica",
"CS": "Czechoslovakia (no longer exists)",
"CU": "Cuba",
"CV": "Cape Verde",
"CX": "Christmas Island",
"CY": "Cyprus",
"CZ": "Czech Republic",
"DD": "German Democratic Republic (no longer exists)",
"DE": "Germany",
"DJ": "Djibouti",
"DK": "Denmark",
"DM": "Dominica",
"DO": "Dominican Republic",
"DZ": "Algeria",
"EC": "Ecuador",
"EE": "Estonia",
"EG": "Egypt",
"EH": "Western Sahara",
"ER": "Eritrea",
"ES": "Spain",
"ET": "Ethiopia",
"FI": "Finland",
"FJ": "Fiji",
"FK": "Falkland Islands (Malvinas)",
"FM": "Micronesia",
"FO": "Faroe Islands",
"FR": "France",
"FX": "France, Metropolitan",
"GA": "Gabon",
"GB": "United Kingdom (Great Britain)",
"GD": "Grenada",
"GE": "Georgia",
"GF": "French Guiana",
"GH": "Ghana",
"GI": "Gibraltar",
"GL": "Greenland",
"GM": "Gambia",
"GN": "Guinea",
"GP": "Guadeloupe",
"GQ": "Equatorial Guinea",
"GR": "Greece",
"GS": "South Georgia and the South Sandwich Islands",
"GT": "Guatemala",
"GU": "Guam",
"GW": "Guinea-Bissau",
"GY": "Guyana",
"HK": "Hong Kong",
"HM": "Heard & McDonald Islands",
"HN": "Honduras",
"HR": "Croatia",
"HT": "Haiti",
"HU": "Hungary",
"ID": "Indonesia",
"IE": "Ireland",
"IL": "Israel",
"IN": "India",
"IO": "British Indian Ocean Territory",
"IQ": "Iraq",
"IR": "Islamic Republic of Iran",
"IS": "Iceland",
"IT": "Italy",
"JM": "Jamaica",
"JO": "Jordan",
"JP": "Japan",
"KE": "Kenya",
"KG": "Kyrgyzstan",
"KH": "Cambodia",
"KI": "Kiribati",
"KM": "Comoros",
"KN": "St. Kitts and Nevis",
"KP": "Korea, Democratic People\"s Republic of",
"KR": "Korea, Republic of",
"KW": "Kuwait",
"KY": "Cayman Islands",
"KZ": "Kazakhstan",
"LA": "Lao People\"s Democratic Republic",
"LB": "Lebanon",
"LC": "Saint Lucia",
"LI": "Liechtenstein",
"LK": "Sri Lanka",
"LR": "Liberia",
"LS": "Lesotho",
"LT": "Lithuania",
"LU": "Luxembourg",
"LV": "Latvia",
"LY": "Libyan Arab Jamahiriya",
"MA": "Morocco",
"MC": "Monaco",
"MD": "Moldova, Republic of",
"MG": "Madagascar",
"MH": "Marshall Islands",
"ML": "Mali",
"MN": "Mongolia",
"MM": "Myanmar",
"MO": "Macau",
"MP": "Northern Mariana Islands",
"MQ": "Martinique",
"MR": "Mauritania",
"MS": "Monserrat",
"MT": "Malta",
"MU": "Mauritius",
"MV": "Maldives",
"MW": "Malawi",
"MX": "Mexico",
"MY": "Malaysia",
"MZ": "Mozambique",
"NA": "Namibia",
"NC": "New Caledonia",
"NE": "Niger",
"NF": "Norfolk Island",
"NG": "Nigeria",
"NI": "Nicaragua",
"NL": "Netherlands",
"NO": "Norway",
"NP": "Nepal",
"NR": "Nauru",
"NT": "Neutral Zone (no longer exists)",
"NU": "Niue",
"NZ": "New Zealand",
"OM": "Oman",
"PA": "Panama",
"PE": "Peru",
"PF": "French Polynesia",
"PG": "Papua New Guinea",
"PH": "Philippines",
"PK": "Pakistan",
"PL": "Poland",
"PM": "St. Pierre & Miquelon",
"PN": "Pitcairn",
"PR": "Puerto Rico",
"PT": "Portugal",
"PW": "Palau",
"PY": "Paraguay",
"QA": "Qatar",
"RE": "Reunion",
"RO": "Romania",
"RU": "Russian Federation",
"RW": "Rwanda",
"SA": "Saudi Arabia",
"SB": "Solomon Islands",
"SC": "Seychelles",
"SD": "Sudan",
"SE": "Sweden",
"SG": "Singapore",
"SH": "St. Helena",
"SI": "Slovenia",
"SJ": "Svalbard & Jan Mayen Islands",
"SK": "Slovakia",
"SL": "Sierra Leone",
"SM": "San Marino",
"SN": "Senegal",
"SO": "Somalia",
"SR": "Suriname",
"ST": "Sao Tome & Principe",
"SU": "Union of Soviet Socialist Republics (no longer exists)",
"SV": "El Salvador",
"SY": "Syrian Arab Republic",
"SZ": "Swaziland",
"TC": "Turks & Caicos Islands",
"TD": "Chad",
"TF": "French Southern Territories",
"TG": "Togo",
"TH": "Thailand",
"TJ": "Tajikistan",
"TK": "Tokelau",
"TM": "Turkmenistan",
"TN": "Tunisia",
"TO": "Tonga",
"TP": "East Timor",
"TR": "Turkey",
"TT": "Trinidad & Tobago",
"TV": "Tuvalu",
"TW": "Taiwan, Province of China",
"TZ": "Tanzania, United Republic of",
"UA": "Ukraine",
"UG": "Uganda",
"UK": "United Kingdom",
"UM": "United States Minor Outlying Islands",
"US": "United States of America",
"UY": "Uruguay",
"UZ": "Uzbekistan",
"VA": "Vatican City State (Holy See)",
"VC": "St. Vincent & the Grenadines",
"VE": "Venezuela",
"VG": "British Virgin Islands",
"VI": "United States Virgin Islands",
"VN": "Viet Nam",
"VU": "Vanuatu",
"WF": "Wallis & Futuna Islands",
"WS": "Samoa",
"YD": "Democratic Yemen (no longer exists)",
"YE": "Yemen",
"YT": "Mayotte",
"YU": "Yugoslavia",
"ZA": "South Africa",
"ZM": "Zambia",
"ZR": "Zaire",
"ZW": "Zimbabwe",
"ZZ": "Unknown or unspecified country",
}
}

View File

@@ -1,57 +1,57 @@
package main
import (
"io"
"errors"
"time"
"errors"
"io"
"time"
)
const MAX_LOG_QLEN = 128
const QUEUE_SHUTDOWN_TIMEOUT = 500 * time.Millisecond
type LogWriter struct {
writer io.Writer
ch chan []byte
done chan struct{}
writer io.Writer
ch chan []byte
done chan struct{}
}
func (lw *LogWriter) Write(p []byte) (int, error) {
if p == nil {
return 0, errors.New("Can't write nil byte slice")
}
buf := make([]byte, len(p))
copy(buf, p)
select {
case lw.ch <- buf:
return len(p), nil
default:
return 0, errors.New("Writer queue overflow")
}
if p == nil {
return 0, errors.New("Can't write nil byte slice")
}
buf := make([]byte, len(p))
copy(buf, p)
select {
case lw.ch <- buf:
return len(p), nil
default:
return 0, errors.New("Writer queue overflow")
}
}
func NewLogWriter(writer io.Writer) *LogWriter {
lw := &LogWriter{writer,
make(chan []byte, MAX_LOG_QLEN),
make(chan struct{})}
go lw.loop()
return lw
lw := &LogWriter{writer,
make(chan []byte, MAX_LOG_QLEN),
make(chan struct{})}
go lw.loop()
return lw
}
func (lw *LogWriter) loop() {
for p := range lw.ch {
if p == nil {
break
}
lw.writer.Write(p)
}
lw.done <- struct{}{}
for p := range lw.ch {
if p == nil {
break
}
lw.writer.Write(p)
}
lw.done <- struct{}{}
}
func (lw *LogWriter) Close() {
lw.ch <- nil
timer := time.After(QUEUE_SHUTDOWN_TIMEOUT)
select {
case <-timer:
case <-lw.done:
}
lw.ch <- nil
timer := time.After(QUEUE_SHUTDOWN_TIMEOUT)
select {
case <-timer:
case <-lw.done:
}
}

197
main.go
View File

@@ -1,129 +1,128 @@
package main
import (
"log"
"os"
"fmt"
"flag"
// "os/signal"
// "syscall"
"time"
"net/http"
"flag"
"fmt"
"log"
"os"
// "os/signal"
// "syscall"
"net/http"
"time"
)
var (
PROTOCOL_WHITELIST map[string]bool
PROTOCOL_WHITELIST map[string]bool
)
func init() {
PROTOCOL_WHITELIST = map[string]bool{
"HTTP": true,
"http": true,
}
PROTOCOL_WHITELIST = map[string]bool{
"HTTP": true,
"http": true,
}
}
func perror(msg string) {
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, msg)
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, msg)
}
func arg_fail(msg string) {
perror(msg)
perror("Usage:")
flag.PrintDefaults()
os.Exit(2)
perror(msg)
perror("Usage:")
flag.PrintDefaults()
os.Exit(2)
}
type CLIArgs struct {
country string
list_countries, list_proxies, use_trial bool
limit uint
bind_address string
verbosity int
timeout, rotate time.Duration
proxy_type string
resolver string
country string
list_countries, list_proxies, use_trial bool
limit uint
bind_address string
verbosity int
timeout, rotate time.Duration
proxy_type string
resolver string
}
func parse_args() CLIArgs {
var args CLIArgs
flag.StringVar(&args.country, "country", "us", "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.UintVar(&args.limit, "limit", 3, "amount of proxies in retrieved list")
flag.StringVar(&args.bind_address, "bind-address", "127.0.0.1:8080", "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")
flag.DurationVar(&args.rotate, "rotate", 1 * time.Hour, "rotate user ID once per given period")
flag.StringVar(&args.proxy_type, "proxy-type", "direct", "proxy type: direct or peer or lum or pool")
flag.StringVar(&args.resolver, "resolver", "https://cloudflare-dns.com/dns-query",
"DNS/DoH/DoT resolver to workaround Hola blocked hosts. " +
"See https://github.com/ameshkov/dnslookup/ for upstream DNS URL format.")
flag.BoolVar(&args.use_trial, "use-trial", false, "use trial ports instead of regular ports")
flag.Parse()
if args.country == "" {
arg_fail("Country can't be empty string.")
}
if args.proxy_type == "" {
arg_fail("Proxy type can't be an empty string.")
}
if args.list_countries && args.list_proxies {
arg_fail("list-countries and list-proxies flags are mutually exclusive")
}
return args
var args CLIArgs
flag.StringVar(&args.country, "country", "us", "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.UintVar(&args.limit, "limit", 3, "amount of proxies in retrieved list")
flag.StringVar(&args.bind_address, "bind-address", "127.0.0.1:8080", "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")
flag.DurationVar(&args.rotate, "rotate", 1*time.Hour, "rotate user ID once per given period")
flag.StringVar(&args.proxy_type, "proxy-type", "direct", "proxy type: direct or peer or lum or virt or pool")
flag.StringVar(&args.resolver, "resolver", "https://cloudflare-dns.com/dns-query",
"DNS/DoH/DoT resolver to workaround Hola blocked hosts. "+
"See https://github.com/ameshkov/dnslookup/ for upstream DNS URL format.")
flag.BoolVar(&args.use_trial, "dont-use-trial", true, "use regular ports instead of trial ports")
flag.Parse()
if args.country == "" {
arg_fail("Country can't be empty string.")
}
if args.proxy_type == "" {
arg_fail("Proxy type can't be an empty string.")
}
if args.list_countries && args.list_proxies {
arg_fail("list-countries and list-proxies flags are mutually exclusive")
}
return args
}
func run() int {
args := parse_args()
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)
}
args := parse_args()
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)
}
logWriter := NewLogWriter(os.Stderr)
defer logWriter.Close()
logWriter := NewLogWriter(os.Stderr)
defer logWriter.Close()
mainLogger := NewCondLogger(log.New(logWriter, "MAIN : ",
log.LstdFlags | log.Lshortfile),
args.verbosity)
credLogger := NewCondLogger(log.New(logWriter, "CRED : ",
log.LstdFlags | log.Lshortfile),
args.verbosity)
proxyLogger := NewCondLogger(log.New(logWriter, "PROXY : ",
log.LstdFlags | log.Lshortfile),
args.verbosity)
mainLogger.Info("Constructing fallback DNS upstream...")
resolver, err := NewResolver(args.resolver, args.timeout)
if err != nil {
mainLogger.Critical("Unable to instantiate DNS resolver: %v", err)
return 6
}
mainLogger.Info("Initializing configuration provider...")
auth, tunnels, err := CredService(args.rotate, args.timeout, args.country, args.proxy_type, credLogger)
if err != nil {
mainLogger.Critical("Unable to instantiate credential service: %v", err)
logWriter.Close()
return 4
}
endpoint, err := get_endpoint(tunnels, args.proxy_type, args.use_trial)
if err != nil {
mainLogger.Critical("Unable to determine proxy endpoint: %v", err)
logWriter.Close()
return 5
}
mainLogger.Info("Endpoint: %s", endpoint)
mainLogger.Info("Starting proxy server...")
handler := NewProxyHandler(endpoint, auth, resolver, proxyLogger)
err = http.ListenAndServe(args.bind_address, handler)
mainLogger.Critical("Server terminated with a reason: %v", err)
mainLogger.Info("Shutting down...")
return 0
mainLogger := NewCondLogger(log.New(logWriter, "MAIN : ",
log.LstdFlags|log.Lshortfile),
args.verbosity)
credLogger := NewCondLogger(log.New(logWriter, "CRED : ",
log.LstdFlags|log.Lshortfile),
args.verbosity)
proxyLogger := NewCondLogger(log.New(logWriter, "PROXY : ",
log.LstdFlags|log.Lshortfile),
args.verbosity)
mainLogger.Info("Constructing fallback DNS upstream...")
resolver, err := NewResolver(args.resolver, args.timeout)
if err != nil {
mainLogger.Critical("Unable to instantiate DNS resolver: %v", err)
return 6
}
mainLogger.Info("Initializing configuration provider...")
auth, tunnels, err := CredService(args.rotate, args.timeout, args.country, args.proxy_type, credLogger)
if err != nil {
mainLogger.Critical("Unable to instantiate credential service: %v", err)
logWriter.Close()
return 4
}
endpoint, err := get_endpoint(tunnels, args.proxy_type, args.use_trial)
if err != nil {
mainLogger.Critical("Unable to determine proxy endpoint: %v", err)
logWriter.Close()
return 5
}
mainLogger.Info("Endpoint: %s", endpoint)
mainLogger.Info("Starting proxy server...")
handler := NewProxyHandler(endpoint, auth, resolver, proxyLogger)
err = http.ListenAndServe(args.bind_address, handler)
mainLogger.Critical("Server terminated with a reason: %v", err)
mainLogger.Info("Shutting down...")
return 0
}
func main() {
os.Exit(run())
os.Exit(run())
}

View File

@@ -3,80 +3,80 @@ package main
import (
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/miekg/dns"
"time"
"time"
)
type Resolver struct {
upstream upstream.Upstream
upstream upstream.Upstream
}
const DOT = 0x2e
func NewResolver(address string, timeout time.Duration) (*Resolver, error) {
opts := upstream.Options{Timeout: timeout}
opts := upstream.Options{Timeout: timeout}
u, err := upstream.AddressToUpstream(address, opts)
if err != nil {
return nil, err
}
return &Resolver{upstream: u}, nil
return nil, err
}
return &Resolver{upstream: u}, nil
}
func (r *Resolver) ResolveA(domain string) []string {
res := make([]string, 0)
if len(domain) == 0 {
return res
}
if domain[len(domain)-1] != DOT {
domain = domain + "."
}
req := dns.Msg{}
req.Id = dns.Id()
req.RecursionDesired = true
req.Question = []dns.Question{
{Name: domain, Qtype: dns.TypeA, Qclass: dns.ClassINET},
}
reply, err := r.upstream.Exchange(&req)
if err != nil {
return res
}
for _, rr := range reply.Answer {
if a, ok := rr.(*dns.A); ok {
res = append(res, a.A.String())
}
}
return res
res := make([]string, 0)
if len(domain) == 0 {
return res
}
if domain[len(domain)-1] != DOT {
domain = domain + "."
}
req := dns.Msg{}
req.Id = dns.Id()
req.RecursionDesired = true
req.Question = []dns.Question{
{Name: domain, Qtype: dns.TypeA, Qclass: dns.ClassINET},
}
reply, err := r.upstream.Exchange(&req)
if err != nil {
return res
}
for _, rr := range reply.Answer {
if a, ok := rr.(*dns.A); ok {
res = append(res, a.A.String())
}
}
return res
}
func (r *Resolver) ResolveAAAA(domain string) []string {
res := make([]string, 0)
if len(domain) == 0 {
return res
}
if domain[len(domain)-1] != DOT {
domain = domain + "."
}
req := dns.Msg{}
req.Id = dns.Id()
req.RecursionDesired = true
req.Question = []dns.Question{
{Name: domain, Qtype: dns.TypeAAAA, Qclass: dns.ClassINET},
}
reply, err := r.upstream.Exchange(&req)
if err != nil {
return res
}
for _, rr := range reply.Answer {
if a, ok := rr.(*dns.AAAA); ok {
res = append(res, a.AAAA.String())
}
}
return res
res := make([]string, 0)
if len(domain) == 0 {
return res
}
if domain[len(domain)-1] != DOT {
domain = domain + "."
}
req := dns.Msg{}
req.Id = dns.Id()
req.RecursionDesired = true
req.Question = []dns.Question{
{Name: domain, Qtype: dns.TypeAAAA, Qclass: dns.ClassINET},
}
reply, err := r.upstream.Exchange(&req)
if err != nil {
return res
}
for _, rr := range reply.Answer {
if a, ok := rr.(*dns.AAAA); ok {
res = append(res, a.AAAA.String())
}
}
return res
}
func (r *Resolver) Resolve(domain string) []string {
res := r.ResolveA(domain)
if len(res) == 0 {
res = r.ResolveAAAA(domain)
}
return res
res := r.ResolveA(domain)
if len(res) == 0 {
res = r.ResolveAAAA(domain)
}
return res
}

368
utils.go
View File

@@ -1,125 +1,125 @@
package main
import (
"fmt"
"context"
"net"
"sync"
"io"
"os"
"time"
"encoding/base64"
"encoding/csv"
"errors"
"strings"
"strconv"
"net/http"
"net/url"
"bufio"
"bufio"
"context"
"encoding/base64"
"encoding/csv"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"sync"
"time"
)
func basic_auth_header(login, password string) string {
return "basic " + base64.StdEncoding.EncodeToString(
[]byte(login + ":" + password))
return "basic " + base64.StdEncoding.EncodeToString(
[]byte(login+":"+password))
}
func proxy(ctx context.Context, left, right net.Conn) {
wg := sync.WaitGroup{}
cpy := func (dst, src net.Conn) {
defer wg.Done()
io.Copy(dst, src)
dst.Close()
}
wg.Add(2)
go cpy(left, right)
go cpy(right, left)
groupdone := make(chan struct{})
go func() {
wg.Wait()
groupdone <-struct{}{}
}()
select {
case <-ctx.Done():
left.Close()
right.Close()
case <-groupdone:
return
}
<-groupdone
return
wg := sync.WaitGroup{}
cpy := func(dst, src net.Conn) {
defer wg.Done()
io.Copy(dst, src)
dst.Close()
}
wg.Add(2)
go cpy(left, right)
go cpy(right, left)
groupdone := make(chan struct{})
go func() {
wg.Wait()
groupdone <- struct{}{}
}()
select {
case <-ctx.Done():
left.Close()
right.Close()
case <-groupdone:
return
}
<-groupdone
return
}
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)
return 3
}
for _, code := range countries {
fmt.Printf("%v - %v\n", code, ISO3166[strings.ToUpper(code)])
}
return 0
ctx, _ := context.WithTimeout(context.Background(), timeout)
countries, err := VPNCountries(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
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 {
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)
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
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)
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 get_endpoint(tunnels *ZGetTunnelsResponse, typ string, trial bool) (string, error) {
var hostname string
for k, _ := range tunnels.IPList {
hostname = k
break
}
if hostname == "" {
return "", errors.New("No tunnels found in API response")
}
var port uint16
if typ == "direct" || typ == "lum" || typ == "pool" {
if trial {
port = tunnels.Port.Trial
} else {
port = tunnels.Port.Direct
}
} else if typ == "peer" {
if trial {
port = tunnels.Port.TrialPeer
} else {
port = tunnels.Port.Peer
}
} else {
return "", errors.New("Unsupported port type")
}
return net.JoinHostPort(hostname, strconv.FormatUint(uint64(port), 10)), nil
var hostname string
for k := range tunnels.IPList {
hostname = k
break
}
if hostname == "" {
return "", errors.New("No tunnels found in API response")
}
var port uint16
if typ == "direct" || typ == "lum" || typ == "pool" || typ == "virt" {
if trial {
port = tunnels.Port.Trial
} else {
port = tunnels.Port.Direct
}
} else if typ == "peer" {
if trial {
port = tunnels.Port.TrialPeer
} else {
port = tunnels.Port.Peer
}
} else {
return "", errors.New("Unsupported port type")
}
return net.JoinHostPort(hostname, strconv.FormatUint(uint64(port), 10)), nil
}
// Hop-by-hop headers. These are removed when sent to the backend.
@@ -150,93 +150,93 @@ func delHopHeaders(header http.Header) {
}
func hijack(hijackable interface{}) (net.Conn, *bufio.ReadWriter, error) {
hj, ok := hijackable.(http.Hijacker)
if !ok {
return nil, nil, errors.New("Connection doesn't support hijacking")
}
conn, rw, err := hj.Hijack()
if err != nil {
return nil, nil, err
}
var emptytime time.Time
err = conn.SetDeadline(emptytime)
if err != nil {
conn.Close()
return nil, nil, err
}
return conn, rw, nil
hj, ok := hijackable.(http.Hijacker)
if !ok {
return nil, nil, errors.New("Connection doesn't support hijacking")
}
conn, rw, err := hj.Hijack()
if err != nil {
return nil, nil, err
}
var emptytime time.Time
err = conn.SetDeadline(emptytime)
if err != nil {
conn.Close()
return nil, nil, err
}
return conn, rw, nil
}
func rewriteConnectReq(req *http.Request, resolver *Resolver) error {
origHost := req.Host
origAddr, origPort, err := net.SplitHostPort(origHost)
if err == nil {
origHost = origAddr
}
addrs := resolver.Resolve(origHost)
if len(addrs) == 0 {
return errors.New("Can't resolve host")
}
if origPort == "" {
req.URL.Host = addrs[0]
req.Host = addrs[0]
req.RequestURI = addrs[0]
} else {
req.URL.Host = net.JoinHostPort(addrs[0], origPort)
req.Host = net.JoinHostPort(addrs[0], origPort)
req.RequestURI = net.JoinHostPort(addrs[0], origPort)
}
return nil
origHost := req.Host
origAddr, origPort, err := net.SplitHostPort(origHost)
if err == nil {
origHost = origAddr
}
addrs := resolver.Resolve(origHost)
if len(addrs) == 0 {
return errors.New("Can't resolve host")
}
if origPort == "" {
req.URL.Host = addrs[0]
req.Host = addrs[0]
req.RequestURI = addrs[0]
} else {
req.URL.Host = net.JoinHostPort(addrs[0], origPort)
req.Host = net.JoinHostPort(addrs[0], origPort)
req.RequestURI = net.JoinHostPort(addrs[0], origPort)
}
return nil
}
func rewriteReq(req *http.Request, resolver *Resolver) error {
origHost := req.URL.Host
origAddr, origPort, err := net.SplitHostPort(origHost)
if err == nil {
origHost = origAddr
}
addrs := resolver.Resolve(origHost)
if len(addrs) == 0 {
return errors.New("Can't resolve host")
}
if origPort == "" {
req.URL.Host = addrs[0]
req.Host = addrs[0]
} else {
req.URL.Host = net.JoinHostPort(addrs[0], origPort)
req.Host = net.JoinHostPort(addrs[0], origPort)
}
req.Header.Set("Host", origHost)
return nil
origHost := req.URL.Host
origAddr, origPort, err := net.SplitHostPort(origHost)
if err == nil {
origHost = origAddr
}
addrs := resolver.Resolve(origHost)
if len(addrs) == 0 {
return errors.New("Can't resolve host")
}
if origPort == "" {
req.URL.Host = addrs[0]
req.Host = addrs[0]
} else {
req.URL.Host = net.JoinHostPort(addrs[0], origPort)
req.Host = net.JoinHostPort(addrs[0], origPort)
}
req.Header.Set("Host", origHost)
return nil
}
func makeConnReq(uri string, resolver *Resolver) (*http.Request, error) {
parsed_url, err := url.Parse(uri)
if err != nil {
return nil, err
}
origAddr, origPort, err := net.SplitHostPort(parsed_url.Host)
if err != nil {
origAddr = parsed_url.Host
switch strings.ToLower(parsed_url.Scheme) {
case "https":
origPort = "443"
case "http":
origPort = "80"
default:
return nil, errors.New("Unknown scheme")
}
}
addrs := resolver.Resolve(origAddr)
if len(addrs) == 0 {
return nil, errors.New("Can't resolve host")
}
new_uri := net.JoinHostPort(addrs[0], origPort)
req, err := http.NewRequest("CONNECT", "http://" + new_uri, nil)
if err != nil {
return nil, err
}
req.RequestURI = new_uri
req.Host = new_uri
return req, nil
parsed_url, err := url.Parse(uri)
if err != nil {
return nil, err
}
origAddr, origPort, err := net.SplitHostPort(parsed_url.Host)
if err != nil {
origAddr = parsed_url.Host
switch strings.ToLower(parsed_url.Scheme) {
case "https":
origPort = "443"
case "http":
origPort = "80"
default:
return nil, errors.New("Unknown scheme")
}
}
addrs := resolver.Resolve(origAddr)
if len(addrs) == 0 {
return nil, errors.New("Can't resolve host")
}
new_uri := net.JoinHostPort(addrs[0], origPort)
req, err := http.NewRequest("CONNECT", "http://"+new_uri, nil)
if err != nil {
return nil, err
}
req.RequestURI = new_uri
req.Host = new_uri
return req, nil
}