From a106ef9e562c61811bab674e25356e6cc619a8bb Mon Sep 17 00:00:00 2001 From: rany Date: Sun, 7 Mar 2021 19:57:46 +0200 Subject: [PATCH] Add new virt proxy type (unknown use but used by extension for some sites) + gofmt everything --- README.md | 6 +- condlog.go | 48 ++--- credservice.go | 120 ++++++------ handler.go | 384 +++++++++++++++++++------------------- holaapi.go | 270 ++++++++++++++------------- iso3166.go | 496 ++++++++++++++++++++++++------------------------- logwriter.go | 70 +++---- main.go | 197 ++++++++++---------- resolver.go | 114 ++++++------ utils.go | 368 ++++++++++++++++++------------------ 10 files changed, 1037 insertions(+), 1036 deletions(-) diff --git a/README.md b/README.md index 5b310b8..9b39edc 100644 --- a/README.md +++ b/README.md @@ -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) ``` diff --git a/condlog.go b/condlog.go index cb76d2b..96a18f3 100644 --- a/condlog.go +++ b/condlog.go @@ -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} } diff --git a/credservice.go b/credservice.go index cd6c07f..c534df3 100644 --- a/credservice.go +++ b/credservice.go @@ -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 } diff --git a/handler.go b/handler.go index 52efb6a..495d1ec 100644 --- a/handler.go +++ b/handler.go @@ -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) + } } diff --git a/holaapi.go b/holaapi.go index 8611f06..1a9c6aa 100644 --- a/holaapi.go +++ b/holaapi.go @@ -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 } diff --git a/iso3166.go b/iso3166.go index cf42fed..3e5136d 100644 --- a/iso3166.go +++ b/iso3166.go @@ -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", + } } diff --git a/logwriter.go b/logwriter.go index a880db0..657c2f3 100644 --- a/logwriter.go +++ b/logwriter.go @@ -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: + } } diff --git a/main.go b/main.go index eb49727..30eab73 100644 --- a/main.go +++ b/main.go @@ -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()) } diff --git a/resolver.go b/resolver.go index 6c77054..bd069c6 100644 --- a/resolver.go +++ b/resolver.go @@ -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 } diff --git a/utils.go b/utils.go index ec01a5f..805ac87 100644 --- a/utils.go +++ b/utils.go @@ -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 }