mirror of
https://github.com/Snawoot/hola-proxy.git
synced 2026-04-04 13:18:14 +00:00
Compare commits
108 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2be66ef0d1 | ||
|
|
7ed761c0c3 | ||
|
|
fa21f85414 | ||
|
|
6940495771 | ||
|
|
7864816161 | ||
|
|
838463b771 | ||
|
|
2ff8391919 | ||
|
|
54fa9a4831 | ||
|
|
679ac56806 | ||
|
|
47721e844a | ||
|
|
fa8ea7356f | ||
|
|
c54c588b17 | ||
|
|
ad9ecc60ff | ||
|
|
527ab8a74a | ||
|
|
613c7778da | ||
|
|
c0d5a118be | ||
|
|
652a7ab662 | ||
|
|
1b1ea6db4f | ||
|
|
32fc635666 | ||
|
|
71de713376 | ||
|
|
39a9ba0bd3 | ||
|
|
77bfe7907b | ||
|
|
ba18da688b | ||
|
|
4af6fdf9ef | ||
|
|
b841678722 | ||
|
|
5c7b550983 | ||
|
|
203ed79b2b | ||
|
|
ce330f66a7 | ||
|
|
e82da1cb7b | ||
|
|
257fbe70a7 | ||
|
|
c9e2a2d5df | ||
|
|
98784aada9 | ||
|
|
518098ac2b | ||
|
|
60eee4ad84 | ||
|
|
223105b010 | ||
|
|
b93ba7718c | ||
|
|
8660c52f26 | ||
|
|
a003e75cb4 | ||
|
|
173fbd5d98 | ||
|
|
4d182dedd9 | ||
|
|
6501950752 | ||
|
|
9118ac4fae | ||
|
|
7755ea54bd | ||
|
|
ff3d976d95 | ||
|
|
f5a2bcafbd | ||
|
|
3e28df5034 | ||
|
|
63c4b893d9 | ||
|
|
dac47f1e34 | ||
|
|
1afd4fbf81 | ||
|
|
8dc0fa17fe | ||
|
|
40dbf436f0 | ||
|
|
618010d407 | ||
|
|
501246d7a2 | ||
|
|
34a8addcd5 | ||
|
|
b88a0f3edf | ||
|
|
8e19a9ce57 | ||
|
|
bab7fafd27 | ||
|
|
ed35d9abac | ||
|
|
37a685bda7 | ||
|
|
4f01c96d19 | ||
|
|
2e5fce3cd4 | ||
|
|
a465e24dc5 | ||
|
|
ed3f67b2fb | ||
|
|
2228571412 | ||
|
|
34588f46e7 | ||
|
|
c47d582556 | ||
|
|
478249d7a8 | ||
|
|
ff5b462c25 | ||
|
|
7355e3a76e | ||
|
|
d030f47c59 | ||
|
|
73e6bca7a3 | ||
|
|
4b8cb56ff5 | ||
|
|
c413ef95b1 | ||
|
|
adddc10149 | ||
|
|
0f13b7635e | ||
|
|
2ee621310d | ||
|
|
1d3a61339f | ||
|
|
6aa3494d71 | ||
|
|
068a2d5b83 | ||
|
|
fadff8c38f | ||
|
|
bd1a37b3d3 | ||
|
|
7d891ac613 | ||
|
|
458efb37ba | ||
|
|
26990c6130 | ||
|
|
880631670e | ||
|
|
27381ce5ff | ||
|
|
5050e96484 | ||
|
|
30295224ee | ||
|
|
4d4348686c | ||
|
|
86b7fece9b | ||
|
|
608da0baa9 | ||
|
|
0c36dee0b7 | ||
|
|
f2fdeea039 | ||
|
|
f5da736ca1 | ||
|
|
dd0eaa7611 | ||
|
|
882bca34bc | ||
|
|
70faaec848 | ||
|
|
4728548594 | ||
|
|
2c555adb35 | ||
|
|
9fee5905bb | ||
|
|
19fd0c9d52 | ||
|
|
a3e1bd0901 | ||
|
|
882b6381db | ||
|
|
9d686e3f70 | ||
|
|
1f44e7548d | ||
|
|
910f76065f | ||
|
|
18dd1776be | ||
|
|
1c98b33978 |
17
.github/stale.yml
vendored
Normal file
17
.github/stale.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 60
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- pinned
|
||||
- security
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: wontfix
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
||||
61
.github/workflows/build.yml
vendored
Normal file
61
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
-
|
||||
name: Read tag
|
||||
id: tag
|
||||
run: echo "tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
||||
-
|
||||
name: Setup Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 'stable'
|
||||
-
|
||||
name: Build
|
||||
run: >-
|
||||
make -j $(nproc)
|
||||
bin-linux-amd64 bin-linux-386 bin-linux-arm bin-linux-arm64
|
||||
bin-linux-mips bin-linux-mipsle bin-linux-mips64 bin-linux-mips64le
|
||||
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-darwin-arm64
|
||||
bin-android-arm bin-android-arm64
|
||||
NDK_CC_ARM64="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang"
|
||||
NDK_CC_ARM="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi21-clang"
|
||||
VERSION=${{steps.tag.outputs.tag}}
|
||||
-
|
||||
name: Setup Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '~1.20'
|
||||
-
|
||||
name: Build
|
||||
run: >-
|
||||
make -j $(nproc)
|
||||
bin-windows-amd64 bin-windows-386 bin-windows-arm
|
||||
VERSION=${{steps.tag.outputs.tag}}
|
||||
GO120=go
|
||||
-
|
||||
name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: bin/*
|
||||
fail_on_unmatched_files: true
|
||||
generate_release_notes: true
|
||||
62
.github/workflows/docker-ci.yml
vendored
Normal file
62
.github/workflows/docker-ci.yml
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
name: docker-ci
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
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: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
# list of Docker images to use as base name for tags
|
||||
images: |
|
||||
${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }}
|
||||
# generate Docker tags based on the following events/attributes
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=sha
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
-
|
||||
name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
-
|
||||
name: Build and push
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/386,linux/arm/v7
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: 'GIT_DESC=${{steps.tagger.outputs.tag}}'
|
||||
@@ -1,10 +1,11 @@
|
||||
FROM golang AS build
|
||||
FROM --platform=$BUILDPLATFORM golang:1.21 AS build
|
||||
|
||||
ARG GIT_DESC=undefined
|
||||
|
||||
WORKDIR /go/src/github.com/Snawoot/hola-proxy
|
||||
COPY . .
|
||||
RUN CGO_ENABLED=0 go build -a -tags netgo -ldflags '-s -w -extldflags "-static" -X main.version='"$GIT_DESC"
|
||||
ARG TARGETOS TARGETARCH
|
||||
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -a -tags netgo -ldflags '-s -w -extldflags "-static" -X main.version='"$GIT_DESC"
|
||||
ADD https://curl.haxx.se/ca/cacert.pem /certs.crt
|
||||
RUN chmod 0644 /certs.crt
|
||||
|
||||
|
||||
64
Makefile
64
Makefile
@@ -1,26 +1,39 @@
|
||||
PROGNAME = hola-proxy
|
||||
OUTSUFFIX = bin/$(PROGNAME)
|
||||
VERSION := $(shell git describe)
|
||||
BUILDOPTS = -a -tags netgo
|
||||
BUILDOPTS = -a -tags netgo -trimpath -asmflags -trimpath
|
||||
LDFLAGS = -ldflags '-s -w -extldflags "-static" -X main.version=$(VERSION)'
|
||||
LDFLAGS_NATIVE = -ldflags '-s -w -X main.version=$(VERSION)'
|
||||
|
||||
GO := go
|
||||
NDK_CC_ARM = $(abspath ../../ndk-toolchain-arm/bin/arm-linux-androideabi-gcc)
|
||||
NDK_CC_ARM64 = $(abspath ../../ndk-toolchain-arm64/bin/aarch64-linux-android21-clang)
|
||||
|
||||
src = $(wildcard *.go)
|
||||
GO := go
|
||||
GO120 := /usr/lib64/go/1.20/bin/go
|
||||
|
||||
src = $(wildcard *.go */*.go */*/*.go) go.mod go.sum
|
||||
|
||||
native: bin-native
|
||||
all: bin-linux-amd64 bin-linux-386 bin-linux-arm \
|
||||
all: bin-linux-amd64 bin-linux-386 bin-linux-arm bin-linux-arm64 \
|
||||
bin-linux-mips bin-linux-mipsle bin-linux-mips64 bin-linux-mips64le \
|
||||
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-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
|
||||
bin-linux-arm: $(OUTSUFFIX).linux-arm
|
||||
bin-linux-arm64: $(OUTSUFFIX).linux-arm64
|
||||
bin-linux-mips: $(OUTSUFFIX).linux-mips
|
||||
bin-linux-mipsle: $(OUTSUFFIX).linux-mipsle
|
||||
bin-linux-mips64: $(OUTSUFFIX).linux-mips64
|
||||
bin-linux-mips64le: $(OUTSUFFIX).linux-mips64le
|
||||
bin-freebsd-amd64: $(OUTSUFFIX).freebsd-amd64
|
||||
bin-freebsd-386: $(OUTSUFFIX).freebsd-386
|
||||
bin-freebsd-arm: $(OUTSUFFIX).freebsd-arm
|
||||
@@ -33,6 +46,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 $@
|
||||
@@ -46,6 +61,21 @@ $(OUTSUFFIX).linux-386: $(src)
|
||||
$(OUTSUFFIX).linux-arm: $(src)
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
|
||||
$(OUTSUFFIX).linux-arm64: $(src)
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
|
||||
$(OUTSUFFIX).linux-mips: $(src)
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=mips GOMIPS=softfloat $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
|
||||
$(OUTSUFFIX).linux-mips64: $(src)
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=mips64 GOMIPS=softfloat $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
|
||||
$(OUTSUFFIX).linux-mipsle: $(src)
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=mipsle GOMIPS=softfloat $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
|
||||
$(OUTSUFFIX).linux-mips64le: $(src)
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=mips64le GOMIPS=softfloat $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
|
||||
$(OUTSUFFIX).freebsd-amd64: $(src)
|
||||
CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
|
||||
@@ -74,13 +104,19 @@ $(OUTSUFFIX).darwin-arm64: $(src)
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
|
||||
$(OUTSUFFIX).windows-amd64.exe: $(src)
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 $(GO120) build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
|
||||
$(OUTSUFFIX).windows-386.exe: $(src)
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=386 $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=386 $(GO120) build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
|
||||
$(OUTSUFFIX).windows-arm.exe: $(src)
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=arm GOARM=7 $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=arm GOARM=7 $(GO120) 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/*
|
||||
@@ -99,9 +135,21 @@ install:
|
||||
bin-linux-amd64 \
|
||||
bin-linux-386 \
|
||||
bin-linux-arm \
|
||||
bin-linux-arm64 \
|
||||
bin-linux-mips \
|
||||
bin-linux-mipsle \
|
||||
bin-linux-mips64 \
|
||||
bin-linux-mips64le \
|
||||
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
|
||||
|
||||
24
README.md
24
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:
|
||||
@@ -150,15 +156,27 @@ zagent248.hola.org,165.22.65.3,22222,22223,22224,22225,22226,digitalocean
|
||||
|
||||
| Argument | Type | Description |
|
||||
| -------- | ---- | ----------- |
|
||||
| backoff-deadline | Duration | total duration of zgettunnels method attempts (default 5m0s) |
|
||||
| backoff-initial | Duration | initial average backoff delay for zgettunnels (randomized by +/-50%) (default 3s) |
|
||||
| bind-address | String | HTTP proxy address to listen to (default "127.0.0.1:8080") |
|
||||
| cafile | String | use custom CA certificate bundle file |
|
||||
| country | String | desired proxy location (default "us") |
|
||||
| dont-use-trial | - | use regular ports instead of trial ports |
|
||||
| ext-ver | String | extension version to mimic in requests. Can be obtained from https://chrome.google.com/webstore/detail/hola-vpn-the-website-unbl/gkojfkhlekighikafcpjkiklfbnlmeio (default "999.999.999") |
|
||||
| force-port-field | Number | force specific port field/num (example 24232 or lum) |
|
||||
| hide-SNI | Boolean | hide SNI in TLS sessions with proxy server (default true) |
|
||||
| limit | Unsigned Integer (Number) | amount of proxies in retrieved list (default 3) |
|
||||
| list-countries | String | list available countries and exit |
|
||||
| list-proxies | - | output proxy list and exit |
|
||||
| proxy-type | String | proxy type (Datacenter: direct, virt) (Residential: peer, lum, pool) (default "direct") |
|
||||
| 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) (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) |
|
||||
| timeout | Duration | timeout for network operations (default 35s) |
|
||||
| user-agent | String | value of User-Agent header in requests (default "Mozilla/5.0 (X11; Linux x86\_64; rv:109.0) Gecko/20100101 Firefox/118.0") |
|
||||
| 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)
|
||||
* [Community in Telegram](https://t.me/alternative_proxy)
|
||||
|
||||
@@ -10,11 +10,13 @@ import (
|
||||
const DEFAULT_LIST_LIMIT = 3
|
||||
|
||||
func CredService(interval, timeout time.Duration,
|
||||
extVer string,
|
||||
country string,
|
||||
proxytype string,
|
||||
logger *CondLogger) (auth AuthProvider,
|
||||
tunnels *ZGetTunnelsResponse,
|
||||
err error) {
|
||||
logger *CondLogger,
|
||||
backoffInitial time.Duration,
|
||||
backoffDeadline time.Duration,
|
||||
) (auth AuthProvider, tunnels *ZGetTunnelsResponse, err error) {
|
||||
var mux sync.Mutex
|
||||
var auth_header, user_uuid string
|
||||
auth = func() (res string) {
|
||||
@@ -25,7 +27,8 @@ func CredService(interval, timeout time.Duration,
|
||||
}
|
||||
|
||||
tx_res, tx_err := EnsureTransaction(context.Background(), timeout, func(ctx context.Context, client *http.Client) bool {
|
||||
tunnels, user_uuid, err = Tunnels(ctx, client, country, proxytype, DEFAULT_LIST_LIMIT)
|
||||
tunnels, user_uuid, err = Tunnels(ctx, logger, client, extVer, country, proxytype,
|
||||
DEFAULT_LIST_LIMIT, timeout, backoffInitial, backoffDeadline)
|
||||
if err != nil {
|
||||
logger.Error("Configuration bootstrap error: %v. Retrying with the fallback mechanism...", err)
|
||||
return false
|
||||
@@ -41,8 +44,7 @@ func CredService(interval, timeout time.Duration,
|
||||
logger.Critical("All attempts failed.")
|
||||
return
|
||||
}
|
||||
auth_header = basic_auth_header(LOGIN_PREFIX+user_uuid,
|
||||
tunnels.AgentKey)
|
||||
auth_header = basic_auth_header(TemplateLogin(user_uuid), tunnels.AgentKey)
|
||||
go func() {
|
||||
var (
|
||||
err error
|
||||
@@ -55,7 +57,8 @@ func CredService(interval, timeout time.Duration,
|
||||
<-ticker.C
|
||||
logger.Info("Rotating credentials...")
|
||||
tx_res, tx_err := EnsureTransaction(context.Background(), timeout, func(ctx context.Context, client *http.Client) bool {
|
||||
tuns, user_uuid, err = Tunnels(ctx, client, country, proxytype, DEFAULT_LIST_LIMIT)
|
||||
tuns, user_uuid, err = Tunnels(ctx, logger, client, extVer, country, proxytype,
|
||||
DEFAULT_LIST_LIMIT, timeout, backoffInitial, backoffDeadline)
|
||||
if err != nil {
|
||||
logger.Error("Credential rotation error: %v. Retrying with the fallback mechanism...", err)
|
||||
return false
|
||||
@@ -72,8 +75,7 @@ func CredService(interval, timeout time.Duration,
|
||||
continue
|
||||
}
|
||||
(&mux).Lock()
|
||||
auth_header = basic_auth_header(LOGIN_PREFIX+user_uuid,
|
||||
tuns.AgentKey)
|
||||
auth_header = basic_auth_header(TemplateLogin(user_uuid), tuns.AgentKey)
|
||||
(&mux).Unlock()
|
||||
logger.Info("Credentials rotated successfully.")
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ type secureRandomSource struct{}
|
||||
var RandomSource secureRandomSource
|
||||
|
||||
var int63Limit = big.NewInt(0).Lsh(big.NewInt(1), 63)
|
||||
var int64Limit = big.NewInt(0).Lsh(big.NewInt(1), 64)
|
||||
|
||||
func (_ secureRandomSource) Seed(_ int64) {
|
||||
}
|
||||
@@ -21,3 +22,11 @@ func (_ secureRandomSource) Int63() int64 {
|
||||
}
|
||||
return randNum.Int64()
|
||||
}
|
||||
|
||||
func (_ secureRandomSource) Uint64() uint64 {
|
||||
randNum, err := crand.Int(crand.Reader, int64Limit)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return randNum.Uint64()
|
||||
}
|
||||
|
||||
107
extver.go
Normal file
107
extver.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultProdVersion = "113.0"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoVerData = errors.New("no version data returned")
|
||||
)
|
||||
|
||||
type StoreExtUpdateResponse struct {
|
||||
XMLName xml.Name `xml:"gupdate"`
|
||||
App *struct {
|
||||
AppID string `xml:"appid,attr"`
|
||||
Status string `xml:"status,attr"`
|
||||
UpdateCheck *struct {
|
||||
Version string `xml:"version,attr"`
|
||||
Status string `xml:"status,attr"`
|
||||
} `xml:"updatecheck"`
|
||||
} `xml:"app"`
|
||||
}
|
||||
|
||||
func GetExtVer(ctx context.Context,
|
||||
prodVersion *string,
|
||||
id string,
|
||||
dialer ContextDialer,
|
||||
) (string, error) {
|
||||
if prodVersion == nil {
|
||||
prodVersion = &defaultProdVersion
|
||||
}
|
||||
if dialer == nil {
|
||||
dialer = &net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
transport := &http.Transport{
|
||||
DialContext: dialer.DialContext,
|
||||
ForceAttemptHTTP2: true,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
}
|
||||
defer transport.CloseIdleConnections()
|
||||
httpClient := &http.Client{
|
||||
Transport: transport,
|
||||
}
|
||||
|
||||
reqURL := (&url.URL{
|
||||
Scheme: "https",
|
||||
Host: "clients2.google.com",
|
||||
Path: "/service/update2/crx",
|
||||
RawQuery: url.Values{
|
||||
"prodversion": {*prodVersion},
|
||||
"acceptformat": {"crx2,crx3"},
|
||||
"x": {url.Values{
|
||||
"id": {id},
|
||||
"uc": {""},
|
||||
}.Encode()},
|
||||
}.Encode(),
|
||||
}).String()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", reqURL, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("chrome web store request construction failed: %w", err)
|
||||
}
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("chrome web store request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return "", fmt.Errorf("bad status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
reader := io.LimitReader(resp.Body, 64*1024)
|
||||
var respData *StoreExtUpdateResponse
|
||||
|
||||
dec := xml.NewDecoder(reader)
|
||||
err = dec.Decode(&respData)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unmarshaling of chrome web store response failed: %w", err)
|
||||
}
|
||||
|
||||
if respData != nil && respData.App != nil &&
|
||||
respData.App.UpdateCheck != nil && respData.App.UpdateCheck.Version != "" {
|
||||
return respData.App.UpdateCheck.Version, nil
|
||||
}
|
||||
|
||||
return "", ErrNoVerData
|
||||
}
|
||||
31
go.mod
31
go.mod
@@ -1,10 +1,33 @@
|
||||
module github.com/Snawoot/hola-proxy
|
||||
|
||||
go 1.13
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/AdguardTeam/dnsproxy v0.25.0
|
||||
github.com/AdguardTeam/dnsproxy v0.61.0
|
||||
github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/miekg/dns v1.1.29
|
||||
github.com/cenkalti/backoff/v4 v4.2.1
|
||||
github.com/google/uuid v1.5.0
|
||||
github.com/miekg/dns v1.1.57
|
||||
golang.org/x/net v0.19.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/AdguardTeam/golibs v0.18.2 // indirect
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
|
||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect
|
||||
github.com/ameshkov/dnscrypt/v2 v2.2.7 // indirect
|
||||
github.com/ameshkov/dnsstamps v1.0.3 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/google/pprof v0.0.0-20231212022811-ec68065c825e // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.13.2 // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
||||
github.com/quic-go/quic-go v0.40.1 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
golang.org/x/crypto v0.17.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b // indirect
|
||||
golang.org/x/mod v0.14.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/tools v0.16.1 // indirect
|
||||
)
|
||||
|
||||
107
go.sum
107
go.sum
@@ -1,64 +1,65 @@
|
||||
github.com/AdguardTeam/dnsproxy v0.25.0 h1:BTUPrrwB01GeQW5d2Xx4pH5HOFXcZxN1MTeNXXuy6vQ=
|
||||
github.com/AdguardTeam/dnsproxy v0.25.0/go.mod h1:z2EljVLJQXFGZcP9pWABftXm9UxpLNqls7H6bMcIvEY=
|
||||
github.com/AdguardTeam/golibs v0.4.0 h1:4VX6LoOqFe9p9Gf55BeD8BvJD6M6RDYmgEiHrENE9KU=
|
||||
github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
||||
github.com/AdguardTeam/dnsproxy v0.61.0 h1:A5tmOpPw9c1kw5L4RKrOPzscRZuzpLaikwXLDsibQnY=
|
||||
github.com/AdguardTeam/dnsproxy v0.61.0/go.mod h1:IdmXdkpc+m+S2EajJkVZDZm//yQ4mQm2FCOugQpc/N8=
|
||||
github.com/AdguardTeam/golibs v0.18.2 h1:fI5Kw5tnIXLmiT/oiZrSpudl88aW3IeLMG4JpEH/5Gs=
|
||||
github.com/AdguardTeam/golibs v0.18.2/go.mod h1:3WunclLLfrVAq7fYQRhd6f168FHOEMssnipVXCxDL/w=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
|
||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=
|
||||
github.com/ameshkov/dnscrypt v1.1.0 h1:2vAt5dD6ZmqlAxEAfzRcLBnkvdf8NI46Kn9InSwQbSI=
|
||||
github.com/ameshkov/dnscrypt v1.1.0/go.mod h1:ikduAxNLCTEfd1AaCgpIA5TgroIVQ8JY3Vb095fiFJg=
|
||||
github.com/ameshkov/dnsstamps v1.0.1 h1:LhGvgWDzhNJh+kBQd/AfUlq1vfVe109huiXw4JhnPug=
|
||||
github.com/ameshkov/dnsstamps v1.0.1/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
||||
github.com/beefsack/go-rate v0.0.0-20180408011153-efa7637bb9b6/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA=
|
||||
github.com/ameshkov/dnscrypt/v2 v2.2.7 h1:aEitLIR8HcxVodZ79mgRcCiC0A0I5kZPBuWGFwwulAw=
|
||||
github.com/ameshkov/dnscrypt/v2 v2.2.7/go.mod h1:qPWhwz6FdSmuK7W4sMyvogrez4MWdtzosdqlr0Rg3ow=
|
||||
github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
|
||||
github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
||||
github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e h1:V9a67dfYqPLAvzk5hMQOXYJlZ4SLIXgyKIE+ZiHzgGQ=
|
||||
github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e/go.mod h1:9IOqJGCPMSc6E5ydlp5NIonxObaeu/Iub/X03EKPVYo=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-test/deep v1.0.5/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/joomcode/errorx v1.0.1 h1:CalpDWz14ZHd68fIqluJasJosAewpz2TFaJALrUxjrk=
|
||||
github.com/joomcode/errorx v1.0.1/go.mod h1:kgco15ekB6cs+4Xjzo7SPeXzx38PbJzBwbnu9qfVNHQ=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg=
|
||||
github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/pprof v0.0.0-20231212022811-ec68065c825e h1:bwOy7hAFd0C91URzMIEBfr6BAz29yk7Qj0cy6S7DJlU=
|
||||
github.com/google/pprof v0.0.0-20231212022811-ec68065c825e/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
|
||||
github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
|
||||
github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
|
||||
github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
|
||||
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sparrc/go-ping v0.0.0-20190613174326-4e5b6552494c/go.mod h1:eMyUVp6f/5jnzM+3zahzl7q6UXLbgSc3MKg/+ow9QW0=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||
github.com/quic-go/quic-go v0.40.1 h1:X3AGzUNFs0jVuO3esAGnTfvdgvL4fq655WaOi1snv1Q=
|
||||
github.com/quic-go/quic-go v0.40.1/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200403201458-baeed622b8d8 h1:fpnn/HnJONpIu6hkXi1u/7rR0NzilgWr4T0JmWkEitk=
|
||||
golang.org/x/crypto v0.0.0-20200403201458-baeed622b8d8/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d h1:nc5K6ox/4lTFbMVSL9WRR81ixkcwXThoiF6yf+R9scA=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b h1:kLiC65FbiHWFAOu+lxwNPujcsl8VYyTYYEZnsOO1WK4=
|
||||
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
|
||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
|
||||
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
|
||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
||||
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)
|
||||
|
||||
122
holaapi.go
122
holaapi.go
@@ -3,6 +3,8 @@ package main
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
@@ -14,27 +16,39 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/campoy/unique"
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"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 EXT_BROWSER = "chrome"
|
||||
const PRODUCT = "cws"
|
||||
const CCGI_URL = "https://client.hola.org/client_cgi/"
|
||||
const VPN_COUNTRIES_URL = CCGI_URL + "vpn_countries.json"
|
||||
const BG_INIT_URL = CCGI_URL + "background_init"
|
||||
const ZGETTUNNELS_URL = CCGI_URL + "zgettunnels"
|
||||
const LOGIN_PREFIX = "user-uuid-"
|
||||
const FALLBACK_CONF_URL = "https://www.dropbox.com/s/jemizcvpmf2qb9v/cloud_failover.conf?dl=1"
|
||||
const AGENT_SUFFIX = ".hola.org"
|
||||
|
||||
var LOGIN_TEMPLATE = template.Must(template.New("LOGIN_TEMPLATE").Parse("user-uuid-{{.uuid}}-is_prem-{{.prem}}"))
|
||||
var TemporaryBanError = errors.New("temporary ban detected")
|
||||
var PermanentBanError = errors.New("permanent ban detected")
|
||||
var EmptyResponseError = errors.New("empty response")
|
||||
|
||||
var userAgent = "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/118.0"
|
||||
|
||||
func SetUserAgent(ua string) {
|
||||
userAgent = ua
|
||||
}
|
||||
|
||||
func GetUserAgent() string {
|
||||
return userAgent
|
||||
}
|
||||
|
||||
type CountryList []string
|
||||
|
||||
@@ -153,7 +167,7 @@ func do_req(ctx context.Context, client *http.Client, method, url string, query,
|
||||
if query != nil {
|
||||
req.URL.RawQuery = query.Encode()
|
||||
}
|
||||
req.Header.Set("User-Agent", USER_AGENT)
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -189,10 +203,10 @@ func VPNCountries(ctx context.Context, client *http.Client) (res CountryList, er
|
||||
return
|
||||
}
|
||||
|
||||
func background_init(ctx context.Context, client *http.Client, user_uuid string) (res BgInitResponse, reterr error) {
|
||||
func background_init(ctx context.Context, client *http.Client, extVer, user_uuid string) (res BgInitResponse, reterr error) {
|
||||
post_data := make(url.Values)
|
||||
post_data.Add("login", "1")
|
||||
post_data.Add("ver", EXT_VER)
|
||||
post_data.Add("ver", extVer)
|
||||
qs := make(url.Values)
|
||||
qs.Add("uuid", user_uuid)
|
||||
resp, err := do_req(ctx, client, "POST", BG_INIT_URL, qs, post_data)
|
||||
@@ -216,6 +230,7 @@ func zgettunnels(ctx context.Context,
|
||||
client *http.Client,
|
||||
user_uuid string,
|
||||
session_key int64,
|
||||
extVer string,
|
||||
country string,
|
||||
proxy_type string,
|
||||
limit uint) (res *ZGetTunnelsResponse, reterr error) {
|
||||
@@ -235,7 +250,7 @@ func zgettunnels(ctx context.Context,
|
||||
}
|
||||
params.Add("limit", strconv.FormatInt(int64(limit), 10))
|
||||
params.Add("ping_id", strconv.FormatFloat(rand.New(RandomSource).Float64(), 'f', -1, 64))
|
||||
params.Add("ext_ver", EXT_VER)
|
||||
params.Add("ext_ver", extVer)
|
||||
params.Add("browser", EXT_BROWSER)
|
||||
params.Add("product", PRODUCT)
|
||||
params.Add("uuid", user_uuid)
|
||||
@@ -246,13 +261,20 @@ func zgettunnels(ctx context.Context,
|
||||
reterr = err
|
||||
return
|
||||
}
|
||||
reterr = json.Unmarshal(data, &tunnels)
|
||||
err = json.Unmarshal(data, &tunnels)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to unmashal zgettunnels response: %w", err)
|
||||
}
|
||||
if len(tunnels.IPList) == 0 {
|
||||
return nil, EmptyResponseError
|
||||
}
|
||||
res = &tunnels
|
||||
return
|
||||
}
|
||||
|
||||
func fetchFallbackConfig(ctx context.Context) (*FallbackConfig, error) {
|
||||
confRaw, err := do_req(ctx, &http.Client{}, "", FALLBACK_CONF_URL, nil, nil)
|
||||
client := httpClientWithProxy(nil)
|
||||
confRaw, err := do_req(ctx, client, "", FALLBACK_CONF_URL, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -312,21 +334,65 @@ func GetFallbackProxies(ctx context.Context) (*FallbackConfig, error) {
|
||||
}
|
||||
|
||||
func Tunnels(ctx context.Context,
|
||||
logger *CondLogger,
|
||||
client *http.Client,
|
||||
extVer string,
|
||||
country string,
|
||||
proxy_type string,
|
||||
limit uint) (res *ZGetTunnelsResponse, user_uuid string, reterr error) {
|
||||
limit uint,
|
||||
timeout time.Duration,
|
||||
backoffInitial time.Duration,
|
||||
backoffDeadline time.Duration,
|
||||
) (res *ZGetTunnelsResponse, user_uuid string, reterr error) {
|
||||
u := uuid.New()
|
||||
user_uuid = hex.EncodeToString(u[:])
|
||||
initres, err := background_init(ctx, client, user_uuid)
|
||||
ctx1, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
initres, err := background_init(ctx1, client, extVer, user_uuid)
|
||||
if err != nil {
|
||||
reterr = err
|
||||
return
|
||||
}
|
||||
res, reterr = zgettunnels(ctx, client, user_uuid, initres.Key, country, proxy_type, limit)
|
||||
var bo backoff.BackOff = &backoff.ExponentialBackOff{
|
||||
InitialInterval: backoffInitial,
|
||||
RandomizationFactor: 0.5,
|
||||
Multiplier: 1.5,
|
||||
MaxInterval: 10 * time.Minute,
|
||||
MaxElapsedTime: backoffDeadline,
|
||||
Stop: backoff.Stop,
|
||||
Clock: backoff.SystemClock,
|
||||
}
|
||||
bo = backoff.WithContext(bo, ctx)
|
||||
err = backoff.RetryNotify(func() error {
|
||||
ctx1, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
res, reterr = zgettunnels(ctx1, client, user_uuid, initres.Key, extVer, country, proxy_type, limit)
|
||||
return reterr
|
||||
}, bo, func(err error, dur time.Duration) {
|
||||
logger.Info("zgettunnels error: %v; will retry after %v", err, dur.Truncate(time.Millisecond))
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error("All attempts failed: %v", err)
|
||||
return nil, "", err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var baseDialer ContextDialer = &net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}
|
||||
|
||||
var tlsConfig *tls.Config
|
||||
|
||||
func UpdateHolaDialer(dialer ContextDialer) {
|
||||
baseDialer = dialer
|
||||
}
|
||||
|
||||
func UpdateHolaTLSConfig(config *tls.Config) {
|
||||
tlsConfig = config
|
||||
}
|
||||
|
||||
// Returns default http client with a proxy override
|
||||
func httpClientWithProxy(agent *FallbackAgent) *http.Client {
|
||||
t := &http.Transport{
|
||||
@@ -335,13 +401,15 @@ func httpClientWithProxy(agent *FallbackAgent) *http.Client {
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
TLSClientConfig: tlsConfig,
|
||||
}
|
||||
var dialer ContextDialer = &net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
var dialer ContextDialer = baseDialer
|
||||
var rootCAs *x509.CertPool
|
||||
if tlsConfig != nil {
|
||||
rootCAs = tlsConfig.RootCAs
|
||||
}
|
||||
if agent != nil {
|
||||
dialer = NewProxyDialer(agent.NetAddr(), agent.Hostname(), nil, dialer)
|
||||
dialer = NewProxyDialer(agent.NetAddr(), agent.Hostname(), rootCAs, nil, true, dialer)
|
||||
}
|
||||
t.DialContext = dialer.DialContext
|
||||
return &http.Client{
|
||||
@@ -349,19 +417,18 @@ func httpClientWithProxy(agent *FallbackAgent) *http.Client {
|
||||
}
|
||||
}
|
||||
|
||||
func EnsureTransaction(baseCtx context.Context, txnTimeout time.Duration, txn func(context.Context, *http.Client) bool) (bool, error) {
|
||||
func EnsureTransaction(ctx context.Context, getFBTimeout time.Duration, txn func(context.Context, *http.Client) bool) (bool, error) {
|
||||
client := httpClientWithProxy(nil)
|
||||
defer client.CloseIdleConnections()
|
||||
|
||||
ctx, cancel := context.WithTimeout(baseCtx, txnTimeout)
|
||||
defer cancel()
|
||||
|
||||
if txn(ctx, client) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Fallback needed
|
||||
fbc, err := GetFallbackProxies(baseCtx)
|
||||
getFBCtx, cancel := context.WithTimeout(ctx, getFBTimeout)
|
||||
defer cancel()
|
||||
fbc, err := GetFallbackProxies(getFBCtx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -369,10 +436,6 @@ func EnsureTransaction(baseCtx context.Context, txnTimeout time.Duration, txn fu
|
||||
for _, agent := range fbc.Agents {
|
||||
client = httpClientWithProxy(&agent)
|
||||
defer client.CloseIdleConnections()
|
||||
|
||||
ctx, cancel = context.WithTimeout(baseCtx, txnTimeout)
|
||||
defer cancel()
|
||||
|
||||
if txn(ctx, client) {
|
||||
return true, nil
|
||||
}
|
||||
@@ -380,3 +443,12 @@ func EnsureTransaction(baseCtx context.Context, txnTimeout time.Duration, txn fu
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func TemplateLogin(user_uuid string) string {
|
||||
var b strings.Builder
|
||||
LOGIN_TEMPLATE.Execute(&b, map[string]string{
|
||||
"uuid": user_uuid,
|
||||
"prem": "0",
|
||||
})
|
||||
return b.String()
|
||||
}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
docker build --build-arg GIT_DESC="$(git describe)" -f "$DOCKERFILE_PATH" -t "$IMAGE_NAME" .
|
||||
126
main.go
126
main.go
@@ -1,13 +1,25 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
xproxy "golang.org/x/net/proxy"
|
||||
)
|
||||
|
||||
const (
|
||||
HolaExtStoreID = "gkojfkhlekighikafcpjkiklfbnlmeio"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -35,6 +47,7 @@ func arg_fail(msg string) {
|
||||
}
|
||||
|
||||
type CLIArgs struct {
|
||||
extVer string
|
||||
country string
|
||||
list_countries, list_proxies, use_trial bool
|
||||
limit uint
|
||||
@@ -45,10 +58,20 @@ type CLIArgs struct {
|
||||
resolver string
|
||||
force_port_field string
|
||||
showVersion bool
|
||||
proxy string
|
||||
caFile string
|
||||
minPause time.Duration
|
||||
maxPause time.Duration
|
||||
backoffInitial time.Duration
|
||||
backoffDeadline time.Duration
|
||||
hideSNI bool
|
||||
userAgent string
|
||||
}
|
||||
|
||||
func parse_args() CLIArgs {
|
||||
var args CLIArgs
|
||||
flag.StringVar(&args.extVer, "ext-ver", "", "extension version to mimic in requests. "+
|
||||
"Can be obtained from https://chrome.google.com/webstore/detail/hola-vpn-the-website-unbl/gkojfkhlekighikafcpjkiklfbnlmeio")
|
||||
flag.StringVar(&args.force_port_field, "force-port-field", "", "force specific port field/num (example 24232 or lum)") // would be nice to not show in help page
|
||||
flag.StringVar(&args.country, "country", "us", "desired proxy location")
|
||||
flag.BoolVar(&args.list_countries, "list-countries", false, "list available countries and exit")
|
||||
@@ -59,13 +82,21 @@ 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.DurationVar(&args.backoffInitial, "backoff-initial", 3*time.Second, "initial average backoff delay for zgettunnels (randomized by +/-50%)")
|
||||
flag.DurationVar(&args.backoffDeadline, "backoff-deadline", 5*time.Minute, "total duration of zgettunnels method attempts")
|
||||
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] "+
|
||||
"Examples: http://user:password@192.168.1.1:3128, socks5://10.0.0.1:1080")
|
||||
flag.StringVar(&args.caFile, "cafile", "", "use custom CA certificate bundle file")
|
||||
flag.StringVar(&args.userAgent, "user-agent", GetUserAgent(), "value of User-Agent header in requests")
|
||||
flag.BoolVar(&args.hideSNI, "hide-SNI", true, "hide SNI in TLS sessions with proxy server")
|
||||
flag.Parse()
|
||||
if args.country == "" {
|
||||
arg_fail("Country can't be empty string.")
|
||||
@@ -86,13 +117,6 @@ func run() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
@@ -105,6 +129,78 @@ func run() int {
|
||||
proxyLogger := NewCondLogger(log.New(logWriter, "PROXY : ",
|
||||
log.LstdFlags|log.Lshortfile),
|
||||
args.verbosity)
|
||||
|
||||
var dialer ContextDialer = &net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}
|
||||
|
||||
var caPool *x509.CertPool
|
||||
if args.caFile != "" {
|
||||
caPool = x509.NewCertPool()
|
||||
certs, err := ioutil.ReadFile(args.caFile)
|
||||
if err != nil {
|
||||
mainLogger.Error("Can't load CA file: %v", err)
|
||||
return 15
|
||||
}
|
||||
if ok := caPool.AppendCertsFromPEM(certs); !ok {
|
||||
mainLogger.Error("Can't load certificates from CA file")
|
||||
return 15
|
||||
}
|
||||
UpdateHolaTLSConfig(&tls.Config{
|
||||
RootCAs: caPool,
|
||||
})
|
||||
}
|
||||
|
||||
proxyFromURLWrapper := func(u *url.URL, next xproxy.Dialer) (xproxy.Dialer, error) {
|
||||
cdialer, ok := next.(ContextDialer)
|
||||
if !ok {
|
||||
return nil, errors.New("only context dialers are accepted")
|
||||
}
|
||||
|
||||
return ProxyDialerFromURL(u, caPool, cdialer)
|
||||
}
|
||||
|
||||
if args.proxy != "" {
|
||||
xproxy.RegisterDialerType("http", proxyFromURLWrapper)
|
||||
xproxy.RegisterDialerType("https", proxyFromURLWrapper)
|
||||
proxyURL, err := url.Parse(args.proxy)
|
||||
if err != nil {
|
||||
mainLogger.Critical("Unable to parse base proxy URL: %v", err)
|
||||
return 6
|
||||
}
|
||||
pxDialer, err := xproxy.FromURL(proxyURL, dialer)
|
||||
if err != nil {
|
||||
mainLogger.Critical("Unable to instantiate base proxy dialer: %v", err)
|
||||
return 7
|
||||
}
|
||||
dialer = pxDialer.(ContextDialer)
|
||||
UpdateHolaDialer(dialer)
|
||||
}
|
||||
|
||||
SetUserAgent(args.userAgent)
|
||||
|
||||
if args.list_countries {
|
||||
return print_countries(args.timeout)
|
||||
}
|
||||
|
||||
if args.extVer == "" {
|
||||
ctx, cl := context.WithTimeout(context.Background(), args.timeout)
|
||||
defer cl()
|
||||
extVer, err := GetExtVer(ctx, nil, HolaExtStoreID, dialer)
|
||||
if err != nil {
|
||||
mainLogger.Critical("Can't detect latest API version. Try to specify -ext-ver parameter. Error: %v", err)
|
||||
return 8
|
||||
}
|
||||
args.extVer = extVer
|
||||
mainLogger.Warning("Detected latest extension version: %q. Pass -ext-ver parameter to skip resolve and speedup startup", args.extVer)
|
||||
cl()
|
||||
}
|
||||
if args.list_proxies {
|
||||
return print_proxies(mainLogger, args.extVer, args.country, args.proxy_type, args.limit, args.timeout,
|
||||
args.backoffInitial, args.backoffDeadline)
|
||||
}
|
||||
|
||||
mainLogger.Info("hola-proxy client version %s is starting...", version)
|
||||
mainLogger.Info("Constructing fallback DNS upstream...")
|
||||
resolver, err := NewResolver(args.resolver, args.timeout)
|
||||
@@ -112,26 +208,24 @@ func run() int {
|
||||
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)
|
||||
auth, tunnels, err := CredService(args.rotate, args.timeout, args.extVer, args.country,
|
||||
args.proxy_type, credLogger, args.backoffInitial, args.backoffDeadline)
|
||||
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, args.force_port_field)
|
||||
if err != nil {
|
||||
mainLogger.Critical("Unable to determine proxy endpoint: %v", err)
|
||||
logWriter.Close()
|
||||
return 5
|
||||
}
|
||||
var dialer ContextDialer = NewProxyDialer(endpoint.NetAddr(), endpoint.TLSName, auth, &net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
})
|
||||
handlerDialer := NewProxyDialer(endpoint.NetAddr(), endpoint.TLSName, caPool, auth, args.hideSNI, dialer)
|
||||
requestDialer := NewPlaintextDialer(endpoint.NetAddr(), endpoint.TLSName, caPool, args.hideSNI, dialer)
|
||||
mainLogger.Info("Endpoint: %s", endpoint.URL().String())
|
||||
mainLogger.Info("Starting proxy server...")
|
||||
handler := NewProxyHandler(dialer, 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)
|
||||
|
||||
71
plaintext.go
Normal file
71
plaintext.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"net"
|
||||
)
|
||||
|
||||
type PlaintextDialer struct {
|
||||
fixedAddress string
|
||||
tlsServerName string
|
||||
next ContextDialer
|
||||
caPool *x509.CertPool
|
||||
hideSNI bool
|
||||
}
|
||||
|
||||
func NewPlaintextDialer(address, tlsServerName string, caPool *x509.CertPool, hideSNI bool, next ContextDialer) *PlaintextDialer {
|
||||
return &PlaintextDialer{
|
||||
fixedAddress: address,
|
||||
tlsServerName: tlsServerName,
|
||||
next: next,
|
||||
caPool: caPool,
|
||||
hideSNI: hideSNI,
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
sni := d.tlsServerName
|
||||
if d.hideSNI {
|
||||
sni = ""
|
||||
}
|
||||
conn = tls.Client(conn, &tls.Config{
|
||||
ServerName: sni,
|
||||
InsecureSkipVerify: true,
|
||||
VerifyConnection: func(cs tls.ConnectionState) error {
|
||||
opts := x509.VerifyOptions{
|
||||
DNSName: d.tlsServerName,
|
||||
Intermediates: x509.NewCertPool(),
|
||||
Roots: d.caPool,
|
||||
}
|
||||
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,9 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/miekg/dns"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Resolver struct {
|
||||
@@ -13,7 +14,7 @@ type Resolver struct {
|
||||
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
|
||||
|
||||
4
retry.go
4
retry.go
@@ -37,3 +37,7 @@ func (d *RetryDialer) DialContext(ctx context.Context, network, address string)
|
||||
}
|
||||
return conn, err
|
||||
}
|
||||
|
||||
func (d *RetryDialer) Dial(network, address string) (net.Conn, error) {
|
||||
return d.DialContext(context.Background(), network, address)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: hola-proxy
|
||||
version: '1.4.6'
|
||||
version: '1.12.1'
|
||||
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.
|
||||
|
||||
60
upstream.go
60
upstream.go
@@ -7,10 +7,13 @@ import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -21,7 +24,12 @@ const (
|
||||
|
||||
var UpstreamBlockedError = errors.New("blocked by upstream")
|
||||
|
||||
type Dialer interface {
|
||||
Dial(network, address string) (net.Conn, error)
|
||||
}
|
||||
|
||||
type ContextDialer interface {
|
||||
Dialer
|
||||
DialContext(ctx context.Context, network, address string) (net.Conn, error)
|
||||
}
|
||||
|
||||
@@ -30,17 +38,54 @@ type ProxyDialer struct {
|
||||
tlsServerName string
|
||||
auth AuthProvider
|
||||
next ContextDialer
|
||||
caPool *x509.CertPool
|
||||
hideSNI bool
|
||||
}
|
||||
|
||||
func NewProxyDialer(address, tlsServerName string, auth AuthProvider, nextDialer ContextDialer) *ProxyDialer {
|
||||
func NewProxyDialer(address, tlsServerName string, caPool *x509.CertPool, auth AuthProvider, hideSNI bool, nextDialer ContextDialer) *ProxyDialer {
|
||||
return &ProxyDialer{
|
||||
address: address,
|
||||
tlsServerName: tlsServerName,
|
||||
auth: auth,
|
||||
next: nextDialer,
|
||||
caPool: caPool,
|
||||
hideSNI: hideSNI,
|
||||
}
|
||||
}
|
||||
|
||||
func ProxyDialerFromURL(u *url.URL, caPool *x509.CertPool, next ContextDialer) (*ProxyDialer, error) {
|
||||
host := u.Hostname()
|
||||
port := u.Port()
|
||||
tlsServerName := ""
|
||||
var auth AuthProvider = nil
|
||||
|
||||
switch strings.ToLower(u.Scheme) {
|
||||
case "http":
|
||||
if port == "" {
|
||||
port = "80"
|
||||
}
|
||||
case "https":
|
||||
if port == "" {
|
||||
port = "443"
|
||||
}
|
||||
tlsServerName = host
|
||||
default:
|
||||
return nil, errors.New("unsupported proxy type")
|
||||
}
|
||||
|
||||
address := net.JoinHostPort(host, port)
|
||||
|
||||
if u.User != nil {
|
||||
username := u.User.Username()
|
||||
password, _ := u.User.Password()
|
||||
authHeader := basic_auth_header(username, password)
|
||||
auth = func() string {
|
||||
return authHeader
|
||||
}
|
||||
}
|
||||
return NewProxyDialer(address, tlsServerName, caPool, auth, false, next), nil
|
||||
}
|
||||
|
||||
func (d *ProxyDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
switch network {
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
@@ -57,13 +102,18 @@ func (d *ProxyDialer) DialContext(ctx context.Context, network, address string)
|
||||
// Custom cert verification logic:
|
||||
// DO NOT send SNI extension of TLS ClientHello
|
||||
// DO peer certificate verification against specified servername
|
||||
sni := d.tlsServerName
|
||||
if d.hideSNI {
|
||||
sni = ""
|
||||
}
|
||||
conn = tls.Client(conn, &tls.Config{
|
||||
ServerName: "",
|
||||
ServerName: sni,
|
||||
InsecureSkipVerify: true,
|
||||
VerifyConnection: func(cs tls.ConnectionState) error {
|
||||
opts := x509.VerifyOptions{
|
||||
DNSName: d.tlsServerName,
|
||||
Intermediates: x509.NewCertPool(),
|
||||
Roots: d.caPool,
|
||||
}
|
||||
for _, cert := range cs.PeerCertificates[1:] {
|
||||
opts.Intermediates.AddCert(cert)
|
||||
@@ -110,12 +160,16 @@ func (d *ProxyDialer) DialContext(ctx context.Context, network, address string)
|
||||
proxyResp.Header.Get("X-Hola-Error") == "Forbidden Host" {
|
||||
return nil, UpstreamBlockedError
|
||||
}
|
||||
return nil, errors.New("Bad response from upstream proxy server")
|
||||
return nil, errors.New(fmt.Sprintf("bad response from upstream proxy server: %s", proxyResp.Status))
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (d *ProxyDialer) Dial(network, address string) (net.Conn, error) {
|
||||
return d.DialContext(context.Background(), network, address)
|
||||
}
|
||||
|
||||
func readResponse(r io.Reader, req *http.Request) (*http.Response, error) {
|
||||
endOfResponse := []byte("\r\n\r\n")
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
21
utils.go
21
utils.go
@@ -8,6 +8,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -111,7 +112,9 @@ func print_countries(timeout time.Duration) int {
|
||||
err error
|
||||
)
|
||||
tx_res, tx_err := EnsureTransaction(context.Background(), timeout, func(ctx context.Context, client *http.Client) bool {
|
||||
countries, err = VPNCountries(ctx, client)
|
||||
ctx1, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
countries, err = VPNCountries(ctx1, client)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Transaction error: %v. Retrying with the fallback mechanism...\n", err)
|
||||
return false
|
||||
@@ -132,14 +135,16 @@ func print_countries(timeout time.Duration) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func print_proxies(country string, proxy_type string, limit uint, timeout time.Duration) int {
|
||||
func print_proxies(logger *CondLogger, extVer, country string, proxy_type string,
|
||||
limit uint, timeout time.Duration, backoffInitial time.Duration, backoffDeadline time.Duration,
|
||||
) int {
|
||||
var (
|
||||
tunnels *ZGetTunnelsResponse
|
||||
user_uuid string
|
||||
err error
|
||||
)
|
||||
tx_res, tx_err := EnsureTransaction(context.Background(), timeout, func(ctx context.Context, client *http.Client) bool {
|
||||
tunnels, user_uuid, err = Tunnels(ctx, client, country, proxy_type, limit)
|
||||
tunnels, user_uuid, err = Tunnels(ctx, logger, client, extVer, country, proxy_type, limit, timeout, backoffInitial, backoffDeadline)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Transaction error: %v. Retrying with the fallback mechanism...\n", err)
|
||||
return false
|
||||
@@ -155,7 +160,7 @@ func print_proxies(country string, proxy_type string, limit uint, timeout time.D
|
||||
return 3
|
||||
}
|
||||
wr := csv.NewWriter(os.Stdout)
|
||||
login := LOGIN_PREFIX + user_uuid
|
||||
login := TemplateLogin(user_uuid)
|
||||
password := tunnels.AgentKey
|
||||
fmt.Println("Login:", login)
|
||||
fmt.Println("Password:", password)
|
||||
@@ -292,3 +297,11 @@ func copyBody(wr io.Writer, body io.Reader) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func RandRange(low, hi int64) int64 {
|
||||
if low >= hi {
|
||||
panic("RandRange: low boundary is greater or equal to high boundary")
|
||||
}
|
||||
delta := hi - low
|
||||
return low + rand.New(RandomSource).Int63n(delta+1)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user