Compare commits

...

136 Commits

Author SHA1 Message Date
Vladislav Yarmak
4af6fdf9ef bump snap version 2023-09-06 13:18:15 +03:00
Snawoot
b841678722 Merge pull request #99 from Snawoot/old_windows
use older golang for windows build for a while
2023-09-06 13:15:47 +03:00
Vladislav Yarmak
5c7b550983 use older golang for windows build for a while 2023-09-06 13:13:57 +03:00
Snawoot
203ed79b2b Merge pull request #98 from Snawoot/fix_auth
Fix auth
2023-09-06 13:10:26 +03:00
Vladislav Yarmak
ce330f66a7 upd dependencies 2023-09-06 13:05:17 +03:00
Vladislav Yarmak
e82da1cb7b fix auth 2023-09-06 13:04:06 +03:00
Vladislav Yarmak
257fbe70a7 upd doc 2023-07-28 13:04:44 +03:00
Vladislav Yarmak
c9e2a2d5df bump snap version 2023-07-28 01:18:06 +03:00
Snawoot
98784aada9 Merge pull request #91 from Snawoot/optional-sni-hide
option to disable SNI hide
2023-07-28 01:17:13 +03:00
Vladislav Yarmak
518098ac2b option to disable SNI hide 2023-07-28 01:15:48 +03:00
Vladislav Yarmak
60eee4ad84 bump snap version 2023-05-30 21:45:32 +03:00
Snawoot
223105b010 Merge pull request #87 from Snawoot/fix_store_xml_parsing
Improve chrome web store XML parsing
2023-05-30 21:45:13 +03:00
Vladislav Yarmak
b93ba7718c improve chrome web store XML parsing 2023-05-30 21:43:36 +03:00
Vladislav Yarmak
8660c52f26 bump snap version 2023-05-29 00:59:18 +03:00
Snawoot
a003e75cb4 Merge pull request #85 from Snawoot/resolver_ext_ver
Auto-resolve ext_ver
2023-05-29 00:58:25 +03:00
Vladislav Yarmak
173fbd5d98 resolve ext_ver 2023-05-29 00:53:00 +03:00
Vladislav Yarmak
4d182dedd9 bump snap version 2023-05-28 16:43:50 +03:00
Snawoot
6501950752 Merge pull request #84 from Snawoot/fix/ext_ver_quickfix
Customizable ext_ver param
2023-05-28 16:43:18 +03:00
Vladislav Yarmak
9118ac4fae fix ext_ver param 2023-05-28 16:38:26 +03:00
Vladislav Yarmak
7755ea54bd bump snap version 2023-05-22 12:30:46 +03:00
Snawoot
ff3d976d95 Merge pull request #82 from Ackater/patch-1
Bump hola api client version
2023-05-22 12:28:37 +03:00
Ackater
f5a2bcafbd Bump hola api client version 2023-05-22 01:28:00 -07:00
Vladislav Yarmak
3e28df5034 go1.20 support 2023-04-27 01:29:58 +03:00
Vladislav Yarmak
63c4b893d9 bump snap version 2023-03-28 11:43:38 +03:00
Snawoot
dac47f1e34 Merge pull request #80 from Snawoot/dependabot/go_modules/golang.org/x/net-0.7.0
Bump golang.org/x/net from 0.5.0 to 0.7.0
2023-03-28 11:35:14 +03:00
Vladislav Yarmak
1afd4fbf81 go mod tidy 2023-03-28 11:34:21 +03:00
Vladislav Yarmak
8dc0fa17fe update all dependencies 2023-03-28 11:33:33 +03:00
dependabot[bot]
40dbf436f0 Bump golang.org/x/net from 0.5.0 to 0.7.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.5.0 to 0.7.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/compare/v0.5.0...v0.7.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-28 08:28:57 +00:00
Vladislav Yarmak
618010d407 bump snap version 2023-02-08 19:33:09 +02:00
Snawoot
501246d7a2 Merge pull request #78 from Snawoot/tunnels_exp_backoff
Tunnels exp backoff
2023-02-08 19:30:47 +02:00
Vladislav Yarmak
34a8addcd5 upd doc 2023-02-08 19:19:29 +02:00
Vladislav Yarmak
b88a0f3edf tunnels exp backoff 2023-02-08 19:19:29 +02:00
Vladislav Yarmak
8e19a9ce57 bump snap version 2023-02-07 19:38:26 +02:00
Snawoot
bab7fafd27 Merge pull request #77 from Snawoot/adjustable_pause
upd doc
2023-02-07 19:37:15 +02:00
Vladislav Yarmak
ed35d9abac upd doc 2023-02-07 19:35:42 +02:00
Snawoot
37a685bda7 Merge pull request #76 from Snawoot/adjustable_pause
adjustable pause
2023-02-07 19:33:22 +02:00
Vladislav Yarmak
4f01c96d19 adjustable pause 2023-02-07 19:32:49 +02:00
Vladislav Yarmak
2e5fce3cd4 bump snap version 2023-02-04 14:36:03 +02:00
Snawoot
a465e24dc5 Merge pull request #75 from Snawoot/bix_netbsd_build
Fix NetBSD build
2023-02-04 14:32:13 +02:00
Vladislav Yarmak
ed3f67b2fb fix NetBSD build 2023-02-04 14:30:38 +02:00
Vladislav Yarmak
2228571412 bump snap version 2023-02-04 13:27:45 +02:00
Snawoot
34588f46e7 Merge pull request #74 from Snawoot/mod_upgrade
Modules upgrade
2023-02-04 13:24:01 +02:00
Vladislav Yarmak
c47d582556 mod upgrade 2023-02-04 13:20:00 +02:00
Vladislav Yarmak
478249d7a8 Dockerfile: fixed golang version 2023-02-02 01:03:26 +02:00
Vladislav Yarmak
ff5b462c25 bump snap version 2023-02-02 00:54:56 +02:00
Vladislav Yarmak
7355e3a76e upd doc 2023-02-02 00:54:24 +02:00
Snawoot
d030f47c59 Merge pull request #72 from Snawoot/fix_tunnels_api
fix tunnels API client
2023-02-02 00:51:34 +02:00
Vladislav Yarmak
73e6bca7a3 extend CSPRNG to support math/rand.Source64 interface 2023-02-02 00:26:36 +02:00
Vladislav Yarmak
4b8cb56ff5 fix tunnels API client 2023-02-02 00:26:36 +02:00
Vladislav Yarmak
c413ef95b1 goimports -w 2023-02-01 20:16:05 +02:00
Vladislav Yarmak
adddc10149 go mod tidy 2022-10-22 10:49:56 +03:00
Vladislav Yarmak
0f13b7635e bump snap version 2022-10-21 21:29:45 +03:00
Snawoot
2ee621310d Merge pull request #69 from Snawoot/go119
fix build with go1.19
2022-10-21 21:28:35 +03:00
Vladislav Yarmak
1d3a61339f fix build with go1.19 2022-10-21 21:28:05 +03:00
Vladislav Yarmak
6aa3494d71 enable stale bot 2022-07-01 20:09:58 +03:00
Vladislav Yarmak
068a2d5b83 bump snap version 2022-06-25 15:39:20 +03:00
Snawoot
fadff8c38f Merge pull request #65 from Snawoot/libs_upgrade
libs upgrade
2022-06-25 15:37:46 +03:00
Vladislav Yarmak
bd1a37b3d3 libs upgrade 2022-06-25 15:36:19 +03:00
Vladislav Yarmak
7d891ac613 bump snap version 2021-07-18 00:56:27 +03:00
Snawoot
458efb37ba Merge pull request #51 from Snawoot/cafile
add cafile option
2021-07-18 00:55:21 +03:00
Vladislav Yarmak
26990c6130 upd doc 2021-07-18 00:53:23 +03:00
Vladislav Yarmak
880631670e add cafile option 2021-07-18 00:52:27 +03:00
Vladislav Yarmak
27381ce5ff makefile: add arm64 to default build 2021-07-17 20:24:21 +03:00
Vladislav Yarmak
5050e96484 makefile: add arm64 and mips 2021-07-17 19:54:52 +03:00
Vladislav Yarmak
30295224ee bump snap version 2021-07-15 23:11:09 +03:00
Vladislav Yarmak
4d4348686c holaapi: update reported client version 2021-07-15 23:10:29 +03:00
Vladislav Yarmak
86b7fece9b bump snap version 2021-07-02 23:41:28 +03:00
Vladislav Yarmak
608da0baa9 remove mentions of unknown proxy types 2021-07-02 23:38:40 +03:00
Vladislav Yarmak
0c36dee0b7 remove dockerhub hooks 2021-06-09 21:46:34 +03:00
Vladislav Yarmak
f2fdeea039 ci: docker 2021-06-09 19:39:22 +03:00
Snawoot
f5da736ca1 Update README.md 2021-05-02 13:04:36 +03:00
Vladislav Yarmak
dd0eaa7611 bump snap version 2021-05-01 17:58:42 +03:00
Snawoot
882bca34bc Merge pull request #45 from Snawoot/fix_http
Fixed plain HTTP request
2021-05-01 17:57:30 +03:00
Vladislav Yarmak
70faaec848 fixed plain HTTP request 2021-05-01 17:55:12 +03:00
Snawoot
4728548594 Merge pull request #43 from aarivex/patch-2
Update README.md
2021-04-01 02:10:13 +03:00
Vladislav Yarmak
2c555adb35 amend chmod notice 2021-04-01 02:09:06 +03:00
Canathan
9fee5905bb Update README.md
Add chmod command section for the linux binary.
2021-03-30 04:57:44 +02:00
Vladislav Yarmak
19fd0c9d52 add android build target 2021-03-28 18:44:12 +03:00
Vladislav Yarmak
a3e1bd0901 bump snap version 2021-03-24 00:39:08 +02:00
Snawoot
882b6381db Merge pull request #42 from Snawoot/base_proxy
Base proxy support
2021-03-24 00:37:39 +02:00
Vladislav Yarmak
9d686e3f70 update help and docs 2021-03-24 00:33:38 +02:00
Vladislav Yarmak
1f44e7548d use proxy stack for list modes 2021-03-23 23:58:28 +02:00
Vladislav Yarmak
910f76065f embed proxy stack into hola api client 2021-03-23 23:36:04 +02:00
Vladislav Yarmak
18dd1776be http(s) proxy support 2021-03-23 23:22:09 +02:00
Vladislav Yarmak
1c98b33978 socks proxy support 2021-03-23 22:30:03 +02:00
Vladislav Yarmak
776411c2d4 bump snap version 2021-03-23 18:39:51 +02:00
Snawoot
f2c7f73548 Merge pull request #41 from Snawoot/no_sni
Major network stack rework
2021-03-23 18:34:13 +02:00
Vladislav Yarmak
af12cee5f0 reworked resolve&tunnel workaround as a stackable dialer 2021-03-23 18:13:31 +02:00
Vladislav Yarmak
05ac2bc146 rework proxy handler on new stack 2021-03-23 17:34:12 +02:00
Vladislav Yarmak
1511ed333a no SNI for upstream proxy dial-outs 2021-03-23 16:03:55 +02:00
Vladislav Yarmak
af955a6cd1 switch fallback to new dialer stack 2021-03-23 15:47:34 +02:00
Vladislav Yarmak
cfd3474af3 WIP: proxy tcp dialer 2021-03-23 14:48:19 +02:00
Snawoot
eb2ae460da Merge pull request #40 from Snawoot/improvements
Makefile improvements
2021-03-20 01:29:02 +02:00
Vladislav Yarmak
dbb9b29c81 experimental build for Windows on ARM 2021-03-20 01:24:03 +02:00
Vladislav Yarmak
839499313b extend *BSD build support 2021-03-20 01:11:57 +02:00
Vladislav Yarmak
58006c4f6b introduce Apple M1 support 2021-03-19 20:57:49 +02:00
Vladislav Yarmak
b23dc2f63f allow go tool override 2021-03-19 17:46:21 +02:00
Snawoot
55788d8188 Merge pull request #39 from rany2/master
Add new helper script
2021-03-17 13:09:10 +02:00
rany
bc0b0df26a Use $(...) notation instead of legacy backticked 2021-03-17 12:27:58 +02:00
rany
700ae4a4f4 try to locate hola-proxy in ~/go/bin/hola-proxy also 2021-03-17 12:26:37 +02:00
rany
8db460ff99 Add new helper script 2021-03-17 10:15:03 +02:00
Snawoot
9efb99cf8e Merge pull request #36 from aarivex/patch-1
Rework README.md
2021-03-16 01:30:19 +02:00
aarivex
af2f1e40d6 Update README.md
Co-authored-by: Snawoot <vladislav@vm-0.com>
2021-03-16 00:26:23 +01:00
aarivex
f6d3de8488 Update README.md
Co-authored-by: Snawoot <vladislav@vm-0.com>
2021-03-16 00:26:17 +01:00
aarivex
fde7f1516b Rework README.md
- Changed `~/go/bin/hola-proxy` to `./hola-proxy`
- Slight improvement of grammar, rewrote some sentences
- Reworked the help output to an arguments table
2021-03-16 00:12:29 +01:00
Vladislav Yarmak
3f92cecac9 makefile: use dynamic linking for install target 2021-03-16 01:12:21 +02:00
Vladislav Yarmak
6bfb8d0aee bump snap version 2021-03-16 00:57:34 +02:00
Snawoot
4faf6aa04b Merge pull request #35 from Snawoot/skip_agent_resolve
Skip upstream agent resolve
2021-03-16 00:56:47 +02:00
Vladislav Yarmak
edd723079d fix endpoint display 2021-03-16 00:49:59 +02:00
Vladislav Yarmak
3cb79059b2 skip upstream agent resolve 2021-03-16 00:46:49 +02:00
Snawoot
3b09f31616 Merge pull request #34 from Snawoot/makefile_upd
makefile: use dynamic linking for native target
2021-03-15 15:45:56 +02:00
Vladislav Yarmak
8d9285c00b makefile: use dynamic linking for native target 2021-03-15 15:42:20 +02:00
Vladislav Yarmak
528e2b2a71 bump snap version 2021-03-15 04:13:04 +02:00
Snawoot
0a473f9662 Merge pull request #32 from Snawoot/fallback
feat: Hola API communication failover
2021-03-15 04:12:15 +02:00
Vladislav Yarmak
17860682be do not resolve fallback agent IP addr, use provided in config 2021-03-15 04:07:03 +02:00
Vladislav Yarmak
1f6c87a797 fix typos 2021-03-15 02:21:23 +02:00
Vladislav Yarmak
72beef10c9 feat: Hola API communication failover 2021-03-15 02:13:42 +02:00
Vladislav Yarmak
ead89d5245 snap: remove unnecessary go-importpath 2021-03-13 06:32:48 +02:00
Vladislav Yarmak
6edd098c82 embed version into snap image 2021-03-13 06:30:34 +02:00
Vladislav Yarmak
752d2ba789 fix dockerfile version embedding 2021-03-13 05:28:49 +02:00
Snawoot
311d1ad74d Merge pull request #30 from Snawoot/docker_builds
embed version into docker image
2021-03-13 05:14:54 +02:00
Vladislav Yarmak
6ac04587cb embed version into docker image 2021-03-13 05:14:19 +02:00
Vladislav Yarmak
8c3538ab4c bump snap version 2021-03-13 04:42:20 +02:00
Snawoot
ff3c0f68fc Merge pull request #29 from Snawoot/versioning
embed version into binary
2021-03-13 04:35:35 +02:00
Vladislav Yarmak
4f72e317fc embed version into local install and local run as well 2021-03-13 04:34:24 +02:00
Vladislav Yarmak
4ddd1284fb embed version into binary 2021-03-13 04:23:36 +02:00
Snawoot
4fe6636003 Merge pull request #27 from rany2/master
Don't repeat response code twice
2021-03-13 00:06:37 +02:00
Snawoot
b5ac6d38e5 Merge pull request #28 from Snawoot/fix_status_log
fix status logging on hola api error
2021-03-13 00:05:44 +02:00
Vladislav Yarmak
0c377308c0 fix status logging on hola api error 2021-03-13 00:04:57 +02:00
rany
3b21036b20 Don't repeat response code twice 2021-03-12 23:28:35 +02:00
Vladislav Yarmak
683bc5f1ec bump snap version 2021-03-12 22:30:29 +02:00
Snawoot
be2e4e5420 Merge pull request #26 from Snawoot/holaapi_req_2xx
hola: expect only 2xx responses
2021-03-12 22:29:46 +02:00
Vladislav Yarmak
95adb40377 hola: expect only 2xx responses 2021-03-12 22:27:31 +02:00
Snawoot
6ced446e68 Merge pull request #25 from Snawoot/detect_block
Detect block
2021-03-12 21:01:12 +02:00
Vladislav Yarmak
9c15f5eda9 add few helper targets to makefile 2021-03-12 20:59:26 +02:00
Vladislav Yarmak
afe3e3f732 detect ban in bg_init response 2021-03-12 20:58:30 +02:00
20 changed files with 1518 additions and 447 deletions

17
.github/stale.yml vendored Normal file
View 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

58
.github/workflows/docker-ci.yml vendored Normal file
View 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}}'

View File

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

129
Makefile
View File

@@ -1,56 +1,155 @@
PROGNAME = hola-proxy
OUTSUFFIX = bin/$(PROGNAME)
VERSION := $(shell git describe)
BUILDOPTS = -a -tags netgo
LDFLAGS = -ldflags '-s -w -extldflags "-static"'
LDFLAGS = -ldflags '-s -w -extldflags "-static" -X main.version=$(VERSION)'
LDFLAGS_NATIVE = -ldflags '-s -w -X main.version=$(VERSION)'
src = $(wildcard *.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)
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-darwin-amd64 \
bin-windows-amd64 bin-windows-386
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
bin-netbsd-amd64: $(OUTSUFFIX).netbsd-amd64
bin-netbsd-386: $(OUTSUFFIX).netbsd-386
bin-openbsd-amd64: $(OUTSUFFIX).openbsd-amd64
bin-openbsd-386: $(OUTSUFFIX).openbsd-386
bin-darwin-amd64: $(OUTSUFFIX).darwin-amd64
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)
CGO_ENABLED=0 go build $(BUILDOPTS) $(LDFLAGS) -o $@
$(GO) build $(LDFLAGS_NATIVE) -o $@
$(OUTSUFFIX).linux-amd64: $(src)
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build $(BUILDOPTS) $(LDFLAGS) -o $@
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
$(OUTSUFFIX).linux-386: $(src)
CGO_ENABLED=0 GOOS=linux GOARCH=386 go build $(BUILDOPTS) $(LDFLAGS) -o $@
CGO_ENABLED=0 GOOS=linux GOARCH=386 $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
$(OUTSUFFIX).linux-arm: $(src)
CGO_ENABLED=0 GOOS=linux GOARCH=arm go build $(BUILDOPTS) $(LDFLAGS) -o $@
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 $@
CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
$(OUTSUFFIX).freebsd-386: $(src)
CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build $(BUILDOPTS) $(LDFLAGS) -o $@
CGO_ENABLED=0 GOOS=freebsd GOARCH=386 $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
$(OUTSUFFIX).freebsd-arm: $(src)
CGO_ENABLED=0 GOOS=freebsd GOARCH=arm go build $(BUILDOPTS) $(LDFLAGS) -o $@
CGO_ENABLED=0 GOOS=freebsd GOARCH=arm $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
$(OUTSUFFIX).netbsd-amd64: $(src)
CGO_ENABLED=0 GOOS=netbsd GOARCH=amd64 $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
$(OUTSUFFIX).netbsd-386: $(src)
CGO_ENABLED=0 GOOS=netbsd GOARCH=386 $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
$(OUTSUFFIX).openbsd-amd64: $(src)
CGO_ENABLED=0 GOOS=openbsd GOARCH=amd64 $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
$(OUTSUFFIX).openbsd-386: $(src)
CGO_ENABLED=0 GOOS=openbsd GOARCH=386 $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
$(OUTSUFFIX).darwin-amd64: $(src)
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build $(BUILDOPTS) $(LDFLAGS) -o $@
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
$(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 $(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/*
fmt:
$(GO) fmt ./...
run:
$(GO) run $(LDFLAGS) .
install:
$(GO) install $(LDFLAGS_NATIVE) .
.PHONY: clean all native fmt install \
bin-native \
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-arm \
bin-android-arm \
bin-android-arm64

View File

@@ -2,9 +2,10 @@
[![hola-proxy](https://snapcraft.io//hola-proxy/badge.svg)](https://snapcraft.io/hola-proxy)
Standalone Hola proxy client. Just run it and it'll start plain HTTP proxy server forwarding traffic via Hola proxies of your choice. By default application listens port on 127.0.0.1:8080.
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`).
---
@@ -31,25 +32,32 @@ git clone https://ipfs.io/ipns/k51qzi5uqu5dkrgx0hozpy1tlggw5o0whtquyrjlc6pprhvbm
* Cross-platform (Windows/Mac OS/Linux/Android (via shell)/\*BSD)
* Uses TLS for secure communication with upstream proxies
* Zero-configuration
* Zero configuration
* Simple and straight forward
## Installation
#### Binary download
#### Binaries
Pre-built binaries available on [releases](https://github.com/Snawoot/hola-proxy/releases/latest) page.
Pre-built binaries are available [here](https://github.com/Snawoot/hola-proxy/releases/latest).
#### From source
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:
Alternatively, you may install hola-proxy from source. Run within source directory
```sh
chmod +x hola-proxy
```
#### Build from source
Alternatively, you may install hola-proxy from source. Run the following within the source directory:
```
go install
make install
```
#### Docker
Docker image is available as well. Here is an example for running proxy via DE as a background service:
A docker image is available as well. Here is an example of running hola-proxy via DE as a background service:
```sh
docker run -d \
@@ -73,7 +81,7 @@ sudo snap install hola-proxy
List available countries:
```
$ ~/go/bin/hola-proxy -list-countries
$ ./hola-proxy -list-countries
ar - Argentina
at - Austria
au - Australia
@@ -121,19 +129,19 @@ us - United States of America
Run proxy via country of your choice:
```
$ ~/go/bin/hola-proxy -country de
$ ./hola-proxy -country de
```
Or run proxy on residental IP:
Or run proxy on residential IP:
```
$ ~/go/bin/hola-proxy -country de -proxy-type lum
$ ./hola-proxy -proxy-type lum
```
Also it is possible to export proxy addresses and credentials:
```
$ ~/go/bin/hola-proxy -country de -list-proxies -limit 3
$ ./hola-proxy -country de -list-proxies -limit 3
Login: user-uuid-0a67c797b3214cbdb432b089c4b801cd
Password: cd123c465901
Proxy-Authorization: basic dXNlci11dWlkLTBhNjdjNzk3YjMyMTRjYmRiNDMyYjA4OWM0YjgwMWNkOmNkMTIzYzQ2NTkwMQ==
@@ -144,33 +152,29 @@ zagent830.hola.org,104.248.24.64,22222,22223,22224,22225,22226,digitalocean
zagent248.hola.org,165.22.65.3,22222,22223,22224,22225,22226,digitalocean
```
## Synopsis
## List of arguments
```
$ ~/go/bin/hola-proxy -h
Usage of /home/user/go/bin/hola-proxy:
-bind-address string
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
-force-port-field string
force specific port field/num (example 24232 or lum)
-limit uint
amount of proxies in retrieved list (default 3)
-list-countries
list available countries and exit
-list-proxies
output proxy list and exit
-proxy-type string
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)
-verbosity int
logging verbosity (10 - debug, 20 - info, 30 - warning, 40 - error, 50 - critical) (default 20)
```
| 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 | 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 35s) |
| 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)

View File

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

32
csrand.go Normal file
View File

@@ -0,0 +1,32 @@
package main
import (
crand "crypto/rand"
"math/big"
)
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) {
}
func (_ secureRandomSource) Int63() int64 {
randNum, err := crand.Int(crand.Reader, int63Limit)
if err != nil {
panic(err)
}
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
View 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
View File

@@ -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.54.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.3.1
github.com/miekg/dns v1.1.55
golang.org/x/net v0.15.0
)
require (
github.com/AdguardTeam/golibs v0.15.0 // 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/golang/mock v1.6.0 // indirect
github.com/google/pprof v0.0.0-20230901174712-0191c66da455 // indirect
github.com/onsi/ginkgo/v2 v2.12.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.3.4 // indirect
github.com/quic-go/quic-go v0.38.1 // indirect
golang.org/x/crypto v0.13.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/tools v0.13.0 // indirect
)

110
go.sum
View File

@@ -1,64 +1,88 @@
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.54.0 h1:OgSitM/EKrMMOi+guWZNwaU1cqRqJKWgR3l3fPWWayI=
github.com/AdguardTeam/dnsproxy v0.54.0/go.mod h1:tG/treaQekcKnugYoKOfm8vt3JGi6CliWta0MkQr15U=
github.com/AdguardTeam/golibs v0.15.0 h1:yOv/fdVkJIOWKr0NlUXAE9RA0DK9GKiBbiGzq47vY7o=
github.com/AdguardTeam/golibs v0.15.0/go.mod h1:66ZLs8P7nk/3IfKroQ1rqtieLk+5eXYXMBKXlVL7KeI=
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.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
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/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/pprof v0.0.0-20230901174712-0191c66da455 h1:YhRUmI1ttDC4sxKY2V62BTI8hCXnyZBV9h38eAanInE=
github.com/google/pprof v0.0.0-20230901174712-0191c66da455/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI=
github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ=
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
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.3.4 h1:MfFAPULvst4yoMgY9QmtpYmfij/em7O8UUi+bNVm7Cg=
github.com/quic-go/qtls-go1-20 v0.3.4/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/quic-go/quic-go v0.38.1 h1:M36YWA5dEhEeT+slOu/SwMEucbYd0YFidxG3KlGPZaE=
github.com/quic-go/quic-go v0.38.1/go.mod h1:ijnZM7JsFIkp4cRyjxJNIzdSfCLmUMg9wdyhGmg+SN4=
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=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
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/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
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/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
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/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
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/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
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/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
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/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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=

View File

@@ -1,122 +1,57 @@
package main
import (
"bufio"
"crypto/tls"
"io"
"fmt"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"time"
)
const BAD_REQ_MSG = "Bad Request\n"
type AuthProvider func() string
type ProxyHandler struct {
auth AuthProvider
upstream string
logger *CondLogger
dialer ContextDialer
httptransport http.RoundTripper
resolver *Resolver
auth AuthProvider
}
func NewProxyHandler(upstream string, auth AuthProvider, resolver *Resolver, logger *CondLogger) *ProxyHandler {
proxyurl, err := url.Parse("https://" + upstream)
if err != nil {
panic(err)
}
func NewProxyHandler(dialer, requestDialer ContextDialer, auth AuthProvider, resolver *Resolver, logger *CondLogger) *ProxyHandler {
dialer = NewRetryDialer(dialer, resolver, logger)
httptransport := &http.Transport{
Proxy: http.ProxyURL(proxyurl),
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: requestDialer.DialContext,
}
return &ProxyHandler{
auth: auth,
upstream: upstream,
logger: logger,
dialer: dialer,
auth: auth,
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
}
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
}
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
func (s *ProxyHandler) HandleTunnel(wr http.ResponseWriter, req *http.Request) {
ctx := req.Context()
conn, err := s.dialer.DialContext(ctx, "tcp", req.RequestURI)
if err != nil {
s.logger.Error("Can't satisfy CONNECT request: %v", err)
http.Error(wr, "Can't satisfy CONNECT request", http.StatusBadGateway)
return
}
if req.ProtoMajor == 0 || req.ProtoMajor == 1 {
// Upgrade client connection
localconn, _, err := hijack(wr)
if err != nil {
@@ -126,95 +61,58 @@ func (s *ProxyHandler) ServeHTTP(wr http.ResponseWriter, req *http.Request) {
}
defer localconn.Close()
if len(responseBytes) > 0 {
_, err = localconn.Write(responseBytes)
if err != nil {
return
}
}
// Inform client connection is built
fmt.Fprintf(localconn, "HTTP/%d.%d 200 OK\r\n\r\n", req.ProtoMajor, req.ProtoMinor)
proxy(req.Context(), localconn, conn)
} else if req.ProtoMajor == 2 {
wr.Header()["Date"] = nil
wr.WriteHeader(http.StatusOK)
flush(wr)
proxyh2(req.Context(), req.Body, wr, 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 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
}
// 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
}
// 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)
s.logger.Error("Unsupported protocol version: %s", req.Proto)
http.Error(wr, "Unsupported protocol version.", http.StatusBadRequest)
return
}
}
func (s *ProxyHandler) HandleRequest(wr http.ResponseWriter, req *http.Request) {
req.RequestURI = ""
if req.ProtoMajor == 2 {
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)
http.Error(wr, "Server Error", http.StatusInternalServerError)
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)
flush(wr)
copyBody(wr, resp.Body)
}
func (s *ProxyHandler) ServeHTTP(wr http.ResponseWriter, req *http.Request) {
s.logger.Info("Request: %v %v %v %v", req.RemoteAddr, req.Proto, req.Method, req.URL)
isConnect := strings.ToUpper(req.Method) == "CONNECT"
if (req.URL.Host == "" || req.URL.Scheme == "" && !isConnect) && req.ProtoMajor < 2 ||
req.Host == "" && req.ProtoMajor == 2 {
http.Error(wr, BAD_REQ_MSG, http.StatusBadRequest)
return
}
delHopHeaders(req.Header)
if isConnect {
s.HandleTunnel(wr, req)
} else {
s.HandleRequest(wr, req)
}
}

53
helper.sh Executable file
View File

@@ -0,0 +1,53 @@
#!/usr/bin/env bash
# arguments <country> <proxytype(default direct, needs explicit country)> <port(default autogen using country+proxytype, needs explicit proxytype)>
country=${1-us}
proxytype=${2-direct}
if [ -z "$3" ]
then
port=17160
for x in {a..z}{a..z} # loop over all possible country codes (676 possibilities)
do
port=$((port+1))
if [ "$x" == "$country" ]
then
true
break
else
false
fi
done || { echo "country code $country is invalid" >&2; exit 1;}
case $proxytype in # port range = 17160+1 -> 17160+676*5
direct) port=$((676*0+port)) ;;
peer) port=$((676*1+port)) ;;
lum) port=$((676*2+port)) ;;
virt) port=$((676*3+port)) ;;
pool) port=$((676*4+port)) ;;
*) echo "proxy-type $proxytype invalid" >&2
exit 1 ;;
esac
else
port=$3
fi
try_binary() {
for x in "${@}"
do
type -a "$x" >/dev/null 2>&1 && { echo "$x"; return 0; } || false
done || return 1
}
binary=$(try_binary "hola-proxy" "$HOME/go/bin/hola-proxy")
if [ -n "$binary" ]
then
echo "country $country"
echo "proxytype $proxytype"
echo "proxy 127.0.0.1:$port"
echo
exec "$binary" -bind-address "127.0.0.1:$port" -country "$country" -proxy-type "$proxytype" -verbosity 50
else
echo "hola-proxy binary cannot be found" >&2
exit 1
fi

View File

@@ -3,33 +3,52 @@ package main
import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/json"
"github.com/campoy/unique"
"github.com/google/uuid"
"errors"
"fmt"
"io/ioutil"
"math/rand"
"net"
"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 USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36"
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")
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"`
Blocked bool `json:"blocked,omitempty"`
Permanent bool `json:"permanent,omitempty"`
}
type PortMap struct {
@@ -50,11 +69,74 @@ type ZGetTunnelsResponse struct {
Ztun map[string][]string `json:"ztun"`
}
func do_req(ctx context.Context, method, url string, query, data url.Values) ([]byte, error) {
type FallbackAgent struct {
Name string `json:"name"`
IP string `json:"ip"`
Port uint16 `json:"port"`
}
type fallbackConfResponse struct {
Agents []FallbackAgent `json:"agents"`
UpdatedAt int64 `json:"updated_ts"`
TTL int64 `json:"ttl_ms"`
}
type FallbackConfig struct {
Agents []FallbackAgent
UpdatedAt time.Time
TTL time.Duration
}
func (c *FallbackConfig) UnmarshalJSON(data []byte) error {
r := fallbackConfResponse{}
err := json.Unmarshal(data, &r)
if err != nil {
return err
}
c.Agents = r.Agents
c.UpdatedAt = time.Unix(r.UpdatedAt/1000, (r.UpdatedAt%1000)*1000000)
c.TTL = time.Duration(r.TTL * 1000000)
return nil
}
func (c *FallbackConfig) Expired() bool {
return time.Now().After(c.UpdatedAt.Add(c.TTL))
}
func (c *FallbackConfig) ShuffleAgents() {
rand.New(RandomSource).Shuffle(len(c.Agents), func(i, j int) {
c.Agents[i], c.Agents[j] = c.Agents[j], c.Agents[i]
})
}
func (c *FallbackConfig) Clone() *FallbackConfig {
return &FallbackConfig{
Agents: append([]FallbackAgent(nil), c.Agents...),
UpdatedAt: c.UpdatedAt,
TTL: c.TTL,
}
}
func (a *FallbackAgent) ToProxy() *url.URL {
return &url.URL{
Scheme: "https",
Host: net.JoinHostPort(a.Name+AGENT_SUFFIX,
fmt.Sprintf("%d", a.Port)),
}
}
func (a *FallbackAgent) Hostname() string {
return a.Name + AGENT_SUFFIX
}
func (a *FallbackAgent) NetAddr() string {
return net.JoinHostPort(a.IP, fmt.Sprintf("%d", a.Port))
}
func do_req(ctx context.Context, client *http.Client, method, url string, query, data url.Values) ([]byte, error) {
var (
client http.Client
req *http.Request
err error
req *http.Request
err error
)
if method == "" {
method = "GET"
@@ -81,6 +163,11 @@ func do_req(ctx context.Context, method, url string, query, data url.Values) ([]
if err != nil {
return nil, err
}
switch resp.StatusCode {
case http.StatusOK, http.StatusCreated, http.StatusAccepted, http.StatusNoContent:
default:
return nil, errors.New(fmt.Sprintf("Bad HTTP response: %s", resp.Status))
}
body, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
@@ -89,10 +176,10 @@ func do_req(ctx context.Context, method, url string, query, data url.Values) ([]
return body, nil
}
func VPNCountries(ctx context.Context) (res CountryList, err error) {
func VPNCountries(ctx context.Context, client *http.Client) (res CountryList, err error) {
params := make(url.Values)
params.Add("browser", EXT_BROWSER)
data, err := do_req(ctx, "", VPN_COUNTRIES_URL, params, nil)
data, err := do_req(ctx, client, "", VPN_COUNTRIES_URL, params, nil)
if err != nil {
return nil, err
}
@@ -107,24 +194,34 @@ func VPNCountries(ctx context.Context) (res CountryList, err error) {
return
}
func background_init(ctx context.Context, user_uuid string) (res BgInitResponse, reterr error) {
func background_init(ctx context.Context, client *http.Client, 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, "POST", BG_INIT_URL, qs, post_data)
resp, err := do_req(ctx, client, "POST", BG_INIT_URL, qs, post_data)
if err != nil {
reterr = err
return
}
reterr = json.Unmarshal(resp, &res)
if reterr == nil && res.Blocked {
if res.Permanent {
reterr = PermanentBanError
} else {
reterr = TemporaryBanError
}
}
return
}
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) {
@@ -143,34 +240,206 @@ func zgettunnels(ctx context.Context,
params.Add("country", country)
}
params.Add("limit", strconv.FormatInt(int64(limit), 10))
params.Add("ping_id", strconv.FormatFloat(rand.Float64(), 'f', -1, 64))
params.Add("ext_ver", EXT_VER)
params.Add("ping_id", strconv.FormatFloat(rand.New(RandomSource).Float64(), 'f', -1, 64))
params.Add("ext_ver", extVer)
params.Add("browser", EXT_BROWSER)
params.Add("product", PRODUCT)
params.Add("uuid", user_uuid)
params.Add("session_key", strconv.FormatInt(session_key, 10))
params.Add("is_premium", "0")
data, err := do_req(ctx, "", ZGETTUNNELS_URL, params, nil)
data, err := do_req(ctx, client, "", ZGETTUNNELS_URL, params, nil)
if err != nil {
reterr = err
return
}
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) {
client := httpClientWithProxy(nil)
confRaw, err := do_req(ctx, client, "", FALLBACK_CONF_URL, nil, nil)
if err != nil {
return nil, err
}
l := len(confRaw)
if l < 4 {
return nil, errors.New("bad response length from fallback conf URL")
}
buf := &bytes.Buffer{}
buf.Grow(l)
buf.Write(confRaw[l-3:])
buf.Write(confRaw[:l-3])
b64dec := base64.NewDecoder(base64.RawStdEncoding, buf)
jdec := json.NewDecoder(b64dec)
fbc := &FallbackConfig{}
err = jdec.Decode(fbc)
if err != nil {
return nil, err
}
if fbc.Expired() {
return nil, errors.New("fetched expired fallback config")
}
fbc.ShuffleAgents()
return fbc, nil
}
var (
fbcMux sync.Mutex
cachedFBC *FallbackConfig
)
func GetFallbackProxies(ctx context.Context) (*FallbackConfig, error) {
fbcMux.Lock()
defer fbcMux.Unlock()
var (
fbc *FallbackConfig
err error
)
if cachedFBC == nil || cachedFBC.Expired() {
fbc, err = fetchFallbackConfig(ctx)
if err != nil {
return nil, err
}
cachedFBC = fbc
} else {
fbc = cachedFBC
}
return fbc.Clone(), nil
}
func Tunnels(ctx context.Context,
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, 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, 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{
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: tlsConfig,
}
var dialer ContextDialer = baseDialer
var rootCAs *x509.CertPool
if tlsConfig != nil {
rootCAs = tlsConfig.RootCAs
}
if agent != nil {
dialer = NewProxyDialer(agent.NetAddr(), agent.Hostname(), rootCAs, nil, true, dialer)
}
t.DialContext = dialer.DialContext
return &http.Client{
Transport: t,
}
}
func EnsureTransaction(ctx context.Context, getFBTimeout time.Duration, txn func(context.Context, *http.Client) bool) (bool, error) {
client := httpClientWithProxy(nil)
defer client.CloseIdleConnections()
if txn(ctx, client) {
return true, nil
}
// Fallback needed
getFBCtx, cancel := context.WithTimeout(ctx, getFBTimeout)
defer cancel()
fbc, err := GetFallbackProxies(getFBCtx)
if err != nil {
return false, err
}
for _, agent := range fbc.Agents {
client = httpClientWithProxy(&agent)
defer client.CloseIdleConnections()
if txn(ctx, client) {
return true, nil
}
}
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()
}

131
main.go
View File

@@ -1,18 +1,30 @@
package main
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
// "os/signal"
// "syscall"
"net"
"net/http"
"net/url"
"os"
"time"
xproxy "golang.org/x/net/proxy"
)
const (
HolaExtStoreID = "gkojfkhlekighikafcpjkiklfbnlmeio"
)
var (
PROTOCOL_WHITELIST map[string]bool
version = "undefined"
)
func init() {
@@ -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
@@ -44,10 +57,20 @@ type CLIArgs struct {
proxy_type string
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
}
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")
@@ -58,12 +81,20 @@ 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.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.")
@@ -79,11 +110,9 @@ func parse_args() CLIArgs {
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)
if args.showVersion {
fmt.Println(version)
return 0
}
logWriter := NewLogWriter(os.Stderr)
@@ -98,28 +127,102 @@ 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)
}
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)
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)
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
}
mainLogger.Info("Endpoint: %s", endpoint)
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(endpoint, auth, 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)
mainLogger.Info("Shutting down...")

71
plaintext.go Normal file
View 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)
}

View File

@@ -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

43
retry.go Normal file
View File

@@ -0,0 +1,43 @@
package main
import (
"context"
"net"
)
type RetryDialer struct {
dialer ContextDialer
resolver *Resolver
logger *CondLogger
}
func NewRetryDialer(dialer ContextDialer, resolver *Resolver, logger *CondLogger) *RetryDialer {
return &RetryDialer{
dialer: dialer,
resolver: resolver,
logger: logger,
}
}
func (d *RetryDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
conn, err := d.dialer.DialContext(ctx, network, address)
if err == UpstreamBlockedError {
d.logger.Info("Destination %s blocked by upstream. Rescuing it with resolve&tunnel workaround.", address)
host, port, err1 := net.SplitHostPort(address)
if err1 != nil {
return conn, err
}
ips := d.resolver.Resolve(host)
if len(ips) == 0 {
return conn, err
}
return d.dialer.DialContext(ctx, network, net.JoinHostPort(ips[0], port))
}
return conn, err
}
func (d *RetryDialer) Dial(network, address string) (net.Conn, error) {
return d.DialContext(context.Background(), network, address)
}

View File

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

198
upstream.go Normal file
View File

@@ -0,0 +1,198 @@
package main
import (
"bufio"
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/http/httputil"
"net/url"
"strings"
)
const (
PROXY_CONNECT_METHOD = "CONNECT"
PROXY_HOST_HEADER = "Host"
PROXY_AUTHORIZATION_HEADER = "Proxy-Authorization"
)
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)
}
type ProxyDialer struct {
address string
tlsServerName string
auth AuthProvider
next ContextDialer
caPool *x509.CertPool
hideSNI bool
}
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":
default:
return nil, errors.New("bad network specified for DialContext: only tcp is supported")
}
conn, err := d.next.DialContext(ctx, "tcp", d.address)
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
},
})
}
req := &http.Request{
Method: PROXY_CONNECT_METHOD,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
RequestURI: address,
Host: address,
Header: http.Header{
PROXY_HOST_HEADER: []string{address},
},
}
if d.auth != nil {
req.Header.Set(PROXY_AUTHORIZATION_HEADER, d.auth())
}
rawreq, err := httputil.DumpRequest(req, false)
if err != nil {
return nil, err
}
_, err = conn.Write(rawreq)
if err != nil {
return nil, err
}
proxyResp, err := readResponse(conn, req)
if err != nil {
return nil, err
}
if proxyResp.StatusCode != http.StatusOK {
if proxyResp.StatusCode == http.StatusForbidden &&
proxyResp.Header.Get("X-Hola-Error") == "Forbidden Host" {
return nil, UpstreamBlockedError
}
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{}
b := make([]byte, 1)
for {
n, err := r.Read(b)
if n < 1 && err == nil {
continue
}
buf.Write(b)
sl := buf.Bytes()
if len(sl) < len(endOfResponse) {
continue
}
if bytes.Equal(sl[len(sl)-4:], endOfResponse) {
break
}
if err != nil {
return nil, err
}
}
return http.ReadResponse(bufio.NewReader(buf), req)
}

221
utils.go
View File

@@ -8,6 +8,7 @@ import (
"errors"
"fmt"
"io"
"math/rand"
"net"
"net/http"
"net/url"
@@ -18,6 +19,32 @@ import (
"time"
)
const COPY_BUF = 128 * 1024
type Endpoint struct {
Host string
Port uint16
TLSName string
}
func (e *Endpoint) URL() *url.URL {
if e.TLSName == "" {
return &url.URL{
Scheme: "http",
Host: net.JoinHostPort(e.Host, fmt.Sprintf("%d", e.Port)),
}
} else {
return &url.URL{
Scheme: "https",
Host: net.JoinHostPort(e.TLSName, fmt.Sprintf("%d", e.Port)),
}
}
}
func (e *Endpoint) NetAddr() string {
return net.JoinHostPort(e.Host, fmt.Sprintf("%d", e.Port))
}
func basic_auth_header(login, password string) string {
return "basic " + base64.StdEncoding.EncodeToString(
[]byte(login+":"+password))
@@ -49,11 +76,57 @@ func proxy(ctx context.Context, left, right net.Conn) {
return
}
func proxyh2(ctx context.Context, leftreader io.ReadCloser, leftwriter io.Writer, right net.Conn) {
wg := sync.WaitGroup{}
ltr := func(dst net.Conn, src io.Reader) {
defer wg.Done()
io.Copy(dst, src)
dst.Close()
}
rtl := func(dst io.Writer, src io.Reader) {
defer wg.Done()
copyBody(dst, src)
}
wg.Add(2)
go ltr(right, leftreader)
go rtl(leftwriter, right)
groupdone := make(chan struct{}, 1)
go func() {
wg.Wait()
groupdone <- struct{}{}
}()
select {
case <-ctx.Done():
leftreader.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)
var (
countries CountryList
err error
)
tx_res, tx_err := EnsureTransaction(context.Background(), timeout, func(ctx context.Context, client *http.Client) bool {
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
}
return true
})
if tx_err != nil {
fmt.Fprintf(os.Stderr, "Transaction recovery mechanism failure: %v.\n", tx_err)
return 4
}
if !tx_res {
fmt.Fprintf(os.Stderr, "All attempts failed.")
return 3
}
for _, code := range countries {
@@ -62,15 +135,32 @@ func print_countries(timeout time.Duration) int {
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)
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, 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
}
return true
})
if tx_err != nil {
fmt.Fprintf(os.Stderr, "Transaction recovery mechanism failure: %v.\n", tx_err)
return 4
}
if !tx_res {
fmt.Fprintf(os.Stderr, "All attempts failed.")
return 3
}
wr := csv.NewWriter(os.Stdout)
login := LOGIN_PREFIX + user_uuid
login := TemplateLogin(user_uuid)
password := tunnels.AgentKey
fmt.Println("Login:", login)
fmt.Println("Password:", password)
@@ -94,14 +184,15 @@ func print_proxies(country string, proxy_type string, limit uint, timeout time.D
return 0
}
func get_endpoint(tunnels *ZGetTunnelsResponse, typ string, trial bool, force_port_field string) (string, error) {
var hostname string
for k := range tunnels.IPList {
func get_endpoint(tunnels *ZGetTunnelsResponse, typ string, trial bool, force_port_field string) (*Endpoint, error) {
var hostname, ip string
for k, v := range tunnels.IPList {
hostname = k
ip = v
break
}
if hostname == "" {
return "", errors.New("No tunnels found in API response")
if hostname == "" || ip == "" {
return nil, errors.New("No tunnels found in API response")
}
var port uint16
@@ -128,10 +219,14 @@ func get_endpoint(tunnels *ZGetTunnelsResponse, typ string, trial bool, force_po
port = tunnels.Port.Peer
}
} else {
return "", errors.New("Unsupported port type")
return nil, errors.New("Unsupported port type")
}
}
return net.JoinHostPort(hostname, strconv.FormatUint(uint64(port), 10)), nil
return &Endpoint{
Host: ip,
Port: port,
TLSName: hostname,
}, nil
}
// Hop-by-hop headers. These are removed when sent to the backend.
@@ -179,76 +274,34 @@ func hijack(hijackable interface{}) (net.Conn, *bufio.ReadWriter, error) {
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
func flush(flusher interface{}) bool {
f, ok := flusher.(http.Flusher)
if !ok {
return false
}
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
f.Flush()
return true
}
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
}
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")
func copyBody(wr io.Writer, body io.Reader) {
buf := make([]byte, COPY_BUF)
for {
bread, read_err := body.Read(buf)
var write_err error
if bread > 0 {
_, write_err = wr.Write(buf[:bread])
flush(wr)
}
if read_err != nil || write_err != nil {
break
}
}
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
}
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)
}