mirror of
https://github.com/Snawoot/hola-proxy.git
synced 2026-04-05 17:08:12 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30295224ee | ||
|
|
4d4348686c | ||
|
|
86b7fece9b | ||
|
|
608da0baa9 | ||
|
|
0c36dee0b7 | ||
|
|
f2fdeea039 | ||
|
|
f5da736ca1 | ||
|
|
dd0eaa7611 | ||
|
|
882bca34bc | ||
|
|
70faaec848 | ||
|
|
4728548594 | ||
|
|
2c555adb35 | ||
|
|
9fee5905bb | ||
|
|
19fd0c9d52 |
58
.github/workflows/docker-ci.yml
vendored
Normal file
58
.github/workflows/docker-ci.yml
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
name: docker-ci
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
-
|
||||
name: Find Git Tag
|
||||
id: tagger
|
||||
uses: jimschubert/query-tag-action@v2
|
||||
with:
|
||||
include: 'v*'
|
||||
exclude: '*-rc*'
|
||||
commit-ish: 'HEAD'
|
||||
skip-unshallow: 'true'
|
||||
abbrev: 7
|
||||
-
|
||||
name: Determine image tag type
|
||||
uses: haya14busa/action-cond@v1
|
||||
id: imgtag
|
||||
with:
|
||||
cond: ${{ github.event_name == 'release' }}
|
||||
if_true: ${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }}:${{ github.event.release.tag_name }},${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }}:latest
|
||||
if_false: ${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }}:latest
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
-
|
||||
name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
-
|
||||
name: Build and push
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/386,linux/arm/v7
|
||||
push: true
|
||||
tags: ${{ steps.imgtag.outputs.value }}
|
||||
build-args: 'GIT_DESC=${{steps.tagger.outputs.tag}}'
|
||||
23
Makefile
23
Makefile
@@ -5,6 +5,9 @@ BUILDOPTS = -a -tags netgo
|
||||
LDFLAGS = -ldflags '-s -w -extldflags "-static" -X main.version=$(VERSION)'
|
||||
LDFLAGS_NATIVE = -ldflags '-s -w -X main.version=$(VERSION)'
|
||||
|
||||
NDK_CC_ARM = $(abspath ../../ndk-toolchain-arm/bin/arm-linux-androideabi-gcc)
|
||||
NDK_CC_ARM64 = $(abspath ../../ndk-toolchain-arm64/bin/aarch64-linux-android21-clang)
|
||||
|
||||
GO := go
|
||||
|
||||
src = $(wildcard *.go)
|
||||
@@ -17,6 +20,9 @@ all: bin-linux-amd64 bin-linux-386 bin-linux-arm \
|
||||
bin-darwin-amd64 bin-darwin-arm64 \
|
||||
bin-windows-amd64 bin-windows-386 bin-windows-arm
|
||||
|
||||
allplus: all \
|
||||
bin-android-arm bin-android-arm64
|
||||
|
||||
bin-native: $(OUTSUFFIX)
|
||||
bin-linux-amd64: $(OUTSUFFIX).linux-amd64
|
||||
bin-linux-386: $(OUTSUFFIX).linux-386
|
||||
@@ -33,6 +39,8 @@ bin-darwin-arm64: $(OUTSUFFIX).darwin-arm64
|
||||
bin-windows-amd64: $(OUTSUFFIX).windows-amd64.exe
|
||||
bin-windows-386: $(OUTSUFFIX).windows-386.exe
|
||||
bin-windows-arm: $(OUTSUFFIX).windows-arm.exe
|
||||
bin-android-arm: $(OUTSUFFIX).android-arm
|
||||
bin-android-arm64: $(OUTSUFFIX).android-arm64
|
||||
|
||||
$(OUTSUFFIX): $(src)
|
||||
$(GO) build $(LDFLAGS_NATIVE) -o $@
|
||||
@@ -82,6 +90,12 @@ $(OUTSUFFIX).windows-386.exe: $(src)
|
||||
$(OUTSUFFIX).windows-arm.exe: $(src)
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=arm GOARM=7 $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
|
||||
$(OUTSUFFIX).android-arm: $(src)
|
||||
CC=$(NDK_CC_ARM) CGO_ENABLED=1 GOOS=android GOARCH=arm GOARM=7 $(GO) build $(LDFLAGS_NATIVE) -o $@
|
||||
|
||||
$(OUTSUFFIX).android-arm64: $(src)
|
||||
CC=$(NDK_CC_ARM64) CGO_ENABLED=1 GOOS=android GOARCH=arm64 $(GO) build $(LDFLAGS_NATIVE) -o $@
|
||||
|
||||
clean:
|
||||
rm -f bin/*
|
||||
|
||||
@@ -102,6 +116,13 @@ install:
|
||||
bin-freebsd-amd64 \
|
||||
bin-freebsd-386 \
|
||||
bin-freebsd-arm \
|
||||
bin-netbsd-amd64 \
|
||||
bin-netbsd-386 \
|
||||
bin-openbsd-amd64 \
|
||||
bin-openbsd-386 \
|
||||
bin-darwin-amd64 \
|
||||
bin-windows-amd64 \
|
||||
bin-windows-386
|
||||
bin-windows-386 \
|
||||
bin-windows-arm \
|
||||
bin-android-arm \
|
||||
bin-android-arm64
|
||||
|
||||
14
README.md
14
README.md
@@ -5,7 +5,7 @@
|
||||
Standalone Hola proxy client. Just run it and it'll start a plain HTTP proxy server forwarding traffic through Hola proxies of your choice.
|
||||
By default the application listens on 127.0.0.1:8080.
|
||||
|
||||
Application is capable to forward traffic via proxies in datacenters (flag `-proxy-type direct`, default) or via peer proxies on residental IPs (consumer ISP) in that country (flag `-proxy-type pool` or `-proxy-type lum`).
|
||||
Application is capable to forward traffic via proxies in datacenters (flag `-proxy-type direct`, default) or via peer proxies on residental IPs (consumer ISP) in that country (flag `-proxy-type lum`).
|
||||
|
||||
---
|
||||
|
||||
@@ -41,6 +41,12 @@ git clone https://ipfs.io/ipns/k51qzi5uqu5dkrgx0hozpy1tlggw5o0whtquyrjlc6pprhvbm
|
||||
|
||||
Pre-built binaries are available [here](https://github.com/Snawoot/hola-proxy/releases/latest).
|
||||
|
||||
Don't forget to make file executable on Unix-like systems (Linux, MacOS, \*BSD, Android). For your convenience rename downloaded file to `hola-proxy` and run within directory where you placed it:
|
||||
|
||||
```sh
|
||||
chmod +x hola-proxy
|
||||
```
|
||||
|
||||
#### Build from source
|
||||
|
||||
Alternatively, you may install hola-proxy from source. Run the following within the source directory:
|
||||
@@ -158,8 +164,12 @@ zagent248.hola.org,165.22.65.3,22222,22223,22224,22225,22226,digitalocean
|
||||
| list-countries | String | list available countries and exit |
|
||||
| list-proxies | - | output proxy list and exit |
|
||||
| proxy | String | sets base proxy to use for all dial-outs. Format: `<http\|https\|socks5\|socks5h>://[login:password@]host[:port]` Examples: `http://user:password@192.168.1.1:3128`, `socks5://10.0.0.1:1080` |
|
||||
| proxy-type | String | proxy type (Datacenter: direct, virt) (Residential: peer, lum, pool) (default "direct") |
|
||||
| proxy-type | String | proxy type (Datacenter: direct) (Residential: lum) (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) |
|
||||
| verbosity | Number | logging verbosity (10 - debug, 20 - info, 30 - warning, 40 - error, 50 - critical) (default 20) |
|
||||
|
||||
## See also
|
||||
|
||||
* [Project wiki](https://github.com/Snawoot/hola-proxy/wiki)
|
||||
|
||||
15
handler.go
15
handler.go
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -15,20 +16,28 @@ type ProxyHandler struct {
|
||||
logger *CondLogger
|
||||
dialer ContextDialer
|
||||
httptransport http.RoundTripper
|
||||
auth AuthProvider
|
||||
}
|
||||
|
||||
func NewProxyHandler(dialer ContextDialer, resolver *Resolver, logger *CondLogger) *ProxyHandler {
|
||||
func NewProxyHandler(dialer, requestDialer ContextDialer, auth AuthProvider, resolver *Resolver, logger *CondLogger) *ProxyHandler {
|
||||
dialer = NewRetryDialer(dialer, resolver, logger)
|
||||
httptransport := &http.Transport{
|
||||
Proxy: func(_ *http.Request) (*url.URL, error) {
|
||||
return &url.URL{
|
||||
Scheme: "http",
|
||||
Host: "void",
|
||||
}, nil
|
||||
},
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
DialContext: dialer.DialContext,
|
||||
DialContext: requestDialer.DialContext,
|
||||
}
|
||||
return &ProxyHandler{
|
||||
logger: logger,
|
||||
dialer: dialer,
|
||||
auth: auth,
|
||||
httptransport: httptransport,
|
||||
}
|
||||
}
|
||||
@@ -74,6 +83,8 @@ func (s *ProxyHandler) HandleRequest(wr http.ResponseWriter, req *http.Request)
|
||||
req.URL.Scheme = "http" // We can't access :scheme pseudo-header, so assume http
|
||||
req.URL.Host = req.Host
|
||||
}
|
||||
delHopHeaders(req.Header)
|
||||
req.Header.Add("Proxy-Authorization", s.auth())
|
||||
resp, err := s.httptransport.RoundTrip(req)
|
||||
if err != nil {
|
||||
s.logger.Error("HTTP fetch error: %v", err)
|
||||
|
||||
@@ -21,8 +21,8 @@ import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 Safari/537.36"
|
||||
const EXT_VER = "1.181.350"
|
||||
const USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36"
|
||||
const EXT_VER = "1.186.562"
|
||||
const EXT_BROWSER = "chrome"
|
||||
const PRODUCT = "cws"
|
||||
const CCGI_URL = "https://client.hola.org/client_cgi/"
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
docker build --build-arg GIT_DESC="$(git describe)" -f "$DOCKERFILE_PATH" -t "$IMAGE_NAME" .
|
||||
9
main.go
9
main.go
@@ -64,15 +64,15 @@ func parse_args() CLIArgs {
|
||||
"(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") // or skip but not mentioned
|
||||
flag.StringVar(&args.proxy_type, "proxy-type", "direct", "proxy type: direct or lum") // or skip but not mentioned
|
||||
// skip would be used something like this: `./bin/hola-proxy -proxy-type skip -force-port-field 24232 -country ua.peer` for debugging
|
||||
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", false, "use regular ports instead of trial ports") // would be nice to not show in help page
|
||||
flag.BoolVar(&args.showVersion, "version", false, "show program version and exit")
|
||||
flag.StringVar(&args.proxy, "proxy", "", "sets base proxy to use for all dial-outs. " +
|
||||
"Format: <http|https|socks5|socks5h>://[login:password@]host[:port] " +
|
||||
flag.StringVar(&args.proxy, "proxy", "", "sets base proxy to use for all dial-outs. "+
|
||||
"Format: <http|https|socks5|socks5h>://[login:password@]host[:port] "+
|
||||
"Examples: http://user:password@192.168.1.1:3128, socks5://10.0.0.1:1080")
|
||||
flag.Parse()
|
||||
if args.country == "" {
|
||||
@@ -164,9 +164,10 @@ func run() int {
|
||||
return 5
|
||||
}
|
||||
handlerDialer := NewProxyDialer(endpoint.NetAddr(), endpoint.TLSName, auth, dialer)
|
||||
requestDialer := NewPlaintextDialer(endpoint.NetAddr(), endpoint.TLSName, dialer)
|
||||
mainLogger.Info("Endpoint: %s", endpoint.URL().String())
|
||||
mainLogger.Info("Starting proxy server...")
|
||||
handler := NewProxyHandler(handlerDialer, resolver, proxyLogger)
|
||||
handler := NewProxyHandler(handlerDialer, requestDialer, auth, resolver, proxyLogger)
|
||||
mainLogger.Info("Init complete.")
|
||||
err = http.ListenAndServe(args.bind_address, handler)
|
||||
mainLogger.Critical("Server terminated with a reason: %v", err)
|
||||
|
||||
62
plaintext.go
Normal file
62
plaintext.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"net"
|
||||
)
|
||||
|
||||
type PlaintextDialer struct {
|
||||
fixedAddress string
|
||||
tlsServerName string
|
||||
next ContextDialer
|
||||
}
|
||||
|
||||
func NewPlaintextDialer(address, tlsServerName string, next ContextDialer) *PlaintextDialer {
|
||||
return &PlaintextDialer{
|
||||
fixedAddress: address,
|
||||
tlsServerName: tlsServerName,
|
||||
next: next,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *PlaintextDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
switch network {
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
default:
|
||||
return nil, errors.New("bad network specified for DialContext: only tcp is supported")
|
||||
}
|
||||
|
||||
conn, err := d.next.DialContext(ctx, "tcp", d.fixedAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if d.tlsServerName != "" {
|
||||
// Custom cert verification logic:
|
||||
// DO NOT send SNI extension of TLS ClientHello
|
||||
// DO peer certificate verification against specified servername
|
||||
conn = tls.Client(conn, &tls.Config{
|
||||
ServerName: "",
|
||||
InsecureSkipVerify: true,
|
||||
VerifyConnection: func(cs tls.ConnectionState) error {
|
||||
opts := x509.VerifyOptions{
|
||||
DNSName: d.tlsServerName,
|
||||
Intermediates: x509.NewCertPool(),
|
||||
}
|
||||
for _, cert := range cs.PeerCertificates[1:] {
|
||||
opts.Intermediates.AddCert(cert)
|
||||
}
|
||||
_, err := cs.PeerCertificates[0].Verify(opts)
|
||||
return err
|
||||
},
|
||||
})
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (d *PlaintextDialer) Dial(network, address string) (net.Conn, error) {
|
||||
return d.DialContext(context.Background(), network, address)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
name: hola-proxy
|
||||
version: '1.5.0'
|
||||
version: '1.5.3'
|
||||
summary: Standalone Hola proxy client.
|
||||
description: |
|
||||
Standalone Hola proxy client. Just run it and it'll start plain HTTP proxy server forwarding traffic via Hola proxies of your choice.
|
||||
|
||||
Reference in New Issue
Block a user