Compare commits

...

117 Commits

Author SHA1 Message Date
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
Vladislav Yarmak
e67b30a116 bump snap version 2021-03-08 01:08:17 +02:00
Snawoot
edc367f194 Merge pull request #22 from rany2/master
Fix nonsense help page
2021-03-08 01:07:48 +02:00
rany
ed77f8a254 help page update in README 2021-03-08 00:19:58 +02:00
rany
45977423f6 Fix -dont-use-trial 2021-03-08 00:18:53 +02:00
rany
64c90af10b change wording in readme 2021-03-08 00:15:55 +02:00
rany
c3180ad245 change wording 2021-03-08 00:15:12 +02:00
rany
0052dea171 Fix nonsense help page 2021-03-08 00:13:33 +02:00
Vladislav Yarmak
e89c83ce05 bump snap version 2021-03-08 00:08:11 +02:00
Snawoot
2f38f5b4d1 Merge pull request #20 from rany2/master
Use trial ports by default and add new virt proxy-type
2021-03-08 00:06:02 +02:00
rany
a880f1d9e3 Remove localhost shortcut 2021-03-07 22:34:38 +02:00
rany
4c10fac620 simplify bind-address to accept port and make it listen to 127.0.0.1 2021-03-07 21:03:57 +02:00
rany
92f28d996a Add a few more flags for debugging 2021-03-07 20:54:47 +02:00
rany
a106ef9e56 Add new virt proxy type (unknown use but used by extension for some sites) + gofmt everything 2021-03-07 19:57:46 +02:00
Vladislav Yarmak
12b05ac0df bump snap version 2021-03-07 13:32:11 +02:00
Snawoot
d798373e66 Merge pull request #18 from rany2/master
Add new proxy-type pool
2021-03-07 13:31:19 +02:00
rany
1d8499dd2e Merge branch 'master' of https://github.com/Snawoot/hola-proxy 2021-03-07 12:52:30 +02:00
rany
e2c9711d73 Add new proxy type pool 2021-03-07 12:47:04 +02:00
rany
5246eff0f5 Add text file with all hola blocked domains 2021-03-07 01:54:50 +02:00
Vladislav Yarmak
45c6521777 bump snap version 2021-03-06 22:27:02 +02:00
Snawoot
db109ce9ad Merge pull request #16 from rany2/master
Fix #15
2021-03-06 22:26:05 +02:00
rany
16192a36ba Fix #15 2021-03-06 22:09:39 +02:00
Vladislav Yarmak
d75ab999c8 bump snap version 2021-03-06 21:16:37 +02:00
Snawoot
a95a6519aa Merge pull request #14 from rany2/master
Fix #13 and enhancements
2021-03-06 21:00:42 +02:00
rany
5774d898d2 update country list from readme 2021-03-06 20:13:43 +02:00
rany
8a3b18e784 update readme 2021-03-06 20:13:01 +02:00
rany
3ebfea6b86 switch back default port 2021-03-06 20:09:56 +02:00
rany
92b7a509e9 remove binary 2021-03-06 20:07:11 +02:00
rany
d4b8306cd4 Fix #13 and enhacements
* Add `-use-trial` to use trial ports.
* Mention Hola and Trial ports in `-list-proxies`
* Add GB to `-list-countries` if UK is present. This is what extension does.
* Add `lum` proxy-type to use residential ips
* Change `peer` and `lum` behavior to also modify the country to `country_code`.peer and `country_code`.pool_lum_`country_code`._shared. This lets them work properly
* Above means that `list-proxies` behavior will change based on `-proxy-type` now(The port isn't the only thing that matters)
2021-03-06 20:03:01 +02:00
Vladislav Yarmak
335894d533 add IPFS mirror 2020-10-24 20:23:54 +03:00
Vladislav Yarmak
d95706046d update readme 2020-08-10 00:50:26 +03:00
Vladislav Yarmak
6fecb99728 add endpoint info to log 2020-05-18 02:00:56 +03:00
Snawoot
0d0cbca13e Merge pull request #3 from Snawoot/readme_upd
doc update
2020-04-20 22:37:57 +03:00
Vladislav Yarmak
3462113454 doc update 2020-04-20 22:34:58 +03:00
Vladislav Yarmak
db9499a936 snap: add required plugs 2020-04-20 22:18:34 +03:00
Vladislav Yarmak
1052329228 add snap build spec 2020-04-20 21:29:45 +03:00
Vladislav Yarmak
94addea5d4 single-layer docker image 2020-04-18 14:04:43 +03:00
Vladislav Yarmak
56a10f2a32 fix docker example 2020-04-18 12:44:40 +03:00
Snawoot
801178a7de Merge pull request #2 from Snawoot/docker
add docker support
2020-04-18 12:33:59 +03:00
Vladislav Yarmak
6d627de3d7 add docker support 2020-04-18 12:30:26 +03:00
Vladislav Yarmak
1a9f547cfe minor improvements 2020-04-14 11:45:03 +03:00
Vladislav Yarmak
1984bb06ee update docs 2020-04-14 00:11:04 +03:00
Snawoot
012d6fbe16 Merge pull request #1 from Snawoot/autoresolve
workaround hola blocked hosts
2020-04-13 23:48:51 +03:00
Vladislav Yarmak
73cdcfb701 polish 2020-04-13 23:48:31 +03:00
Vladislav Yarmak
c626c7cdcc workaround hola blocked hosts 2020-04-13 23:39:58 +03:00
Vladislav Yarmak
f8129259ba print shutdown reason 2020-03-27 01:39:58 +02:00
24 changed files with 2255 additions and 794 deletions

1
.dockerignore Normal file
View File

@@ -0,0 +1 @@
bin/

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}}'

4
.gitignore vendored
View File

@@ -14,3 +14,7 @@
# Dependency directories (remove the comment below to include it)
# vendor/
bin/
*.snap
# hola-proxy binary
hola-proxy

19
Dockerfile Normal file
View File

@@ -0,0 +1,19 @@
FROM golang 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"
ADD https://curl.haxx.se/ca/cacert.pem /certs.crt
RUN chmod 0644 /certs.crt
FROM scratch AS arrange
COPY --from=build /go/src/github.com/Snawoot/hola-proxy/hola-proxy /
COPY --from=build /certs.crt /etc/ssl/certs/ca-certificates.crt
FROM scratch
COPY --from=arrange / /
USER 9999:9999
EXPOSE 8080/tcp
ENTRYPOINT ["/hola-proxy", "-bind-address", "0.0.0.0:8080"]

View File

@@ -1,15 +1,27 @@
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)'
NDK_CC_ARM = $(abspath ../../ndk-toolchain-arm/bin/arm-linux-androideabi-gcc)
NDK_CC_ARM64 = $(abspath ../../ndk-toolchain-arm64/bin/aarch64-linux-android21-clang)
GO := go
src = $(wildcard *.go)
native: bin-native
all: bin-linux-amd64 bin-linux-386 bin-linux-arm \
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
@@ -18,39 +30,99 @@ bin-linux-arm: $(OUTSUFFIX).linux-arm
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).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 $(GO) 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 $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
$(OUTSUFFIX).windows-arm.exe: $(src)
CGO_ENABLED=0 GOOS=windows GOARCH=arm GOARM=7 $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
$(OUTSUFFIX).android-arm: $(src)
CC=$(NDK_CC_ARM) CGO_ENABLED=1 GOOS=android GOARCH=arm GOARM=7 $(GO) build $(LDFLAGS_NATIVE) -o $@
$(OUTSUFFIX).android-arm64: $(src)
CC=$(NDK_CC_ARM64) CGO_ENABLED=1 GOOS=android GOARCH=arm64 $(GO) build $(LDFLAGS_NATIVE) -o $@
clean:
rm -f bin/*
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-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

137
README.md
View File

@@ -1,23 +1,79 @@
# 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.
[![hola-proxy](https://snapcraft.io//hola-proxy/badge.svg)](https://snapcraft.io/hola-proxy)
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 peer`).
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 lum`).
---
:heart: :heart: :heart:
You can say thanks to the author by donations to these wallets:
- ETH: `0xB71250010e8beC90C5f9ddF408251eBA9dD7320e`
- BTC:
- Legacy: `1N89PRvG1CSsUk9sxKwBwudN6TjTPQ1N8a`
- Segwit: `bc1qc0hcyxc000qf0ketv4r44ld7dlgmmu73rtlntw`
---
## Mirrors
IPFS git mirror:
```
git clone https://ipfs.io/ipns/k51qzi5uqu5dkrgx0hozpy1tlggw5o0whtquyrjlc6pprhvbmczr6qtj4ocrv0 hola-proxy
```
## Features
* 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
Pre-built binaries available on [releases](https://github.com/Snawoot/hola-proxy/releases/latest) page.
#### Binaries
Alternatively, you may install hola-proxy from source:
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:
```
go get github.com/Snawoot/hola-proxy
make install
```
#### Docker
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 \
--security-opt no-new-privileges \
-p 127.0.0.1:8080:8080 \
--restart unless-stopped \
--name hola-proxy \
yarmak/hola-proxy -country de
```
#### Snap Store
[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-black.svg)](https://snapcraft.io/hola-proxy)
```bash
sudo snap install hola-proxy
```
## Usage
@@ -25,7 +81,7 @@ go get github.com/Snawoot/hola-proxy
List available countries:
```
$ ~/go/bin/hola-proxy -list-countries
$ ./hola-proxy -list-countries
ar - Argentina
at - Austria
au - Australia
@@ -42,8 +98,10 @@ dk - Denmark
es - Spain
fi - Finland
fr - France
gb - United Kingdom (Great Britain)
gr - Greece
hk - Hong Kong
hr - Croatia
hu - Hungary
id - Indonesia
ie - Ireland
@@ -71,50 +129,47 @@ 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 peer
$ ./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
Login: user-uuid-f4c2c3a8657640048e7243a807867d52
Password: e194c4f457e0
Proxy-Authorization: basic dXNlci11dWlkLWY0YzJjM2E4NjU3NjQwMDQ4ZTcyNDNhODA3ODY3ZDUyOmUxOTRjNGY0NTdlMA==
$ ./hola-proxy -country de -list-proxies -limit 3
Login: user-uuid-0a67c797b3214cbdb432b089c4b801cd
Password: cd123c465901
Proxy-Authorization: basic dXNlci11dWlkLTBhNjdjNzk3YjMyMTRjYmRiNDMyYjA4OWM0YjgwMWNkOmNkMTIzYzQ2NTkwMQ==
Host,IP address,Direct port,Peer port,Vendor
zagent90.hola.org,185.72.246.203,22222,22223,nqhost
zagent249.hola.org,165.22.80.107,22222,22223,digitalocean
zagent248.hola.org,165.22.65.3,22222,22223,digitalocean
host,ip_address,direct,peer,hola,trial,trial_peer,vendor
zagent783.hola.org,165.22.22.6,22222,22223,22224,22225,22226,digitalocean
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")
-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 (default "direct")
-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 |
| -------- | ---- | ----------- |
| bind-address | String | HTTP proxy address to listen to (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 | Number | force specific port field/num (example 24232 or lum) |
| 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 10s) |
| verbosity | Number | logging verbosity (10 - debug, 20 - info, 30 - warning, 40 - error, 50 - critical) (default 20) |
## See also
* [Project wiki](https://github.com/Snawoot/hola-proxy/wiki)

195
blocked-domains.txt Normal file
View File

@@ -0,0 +1,195 @@
equinix.com
digitalocean.com
oceanconservancy.org
mail.yahoo.com
mail.yahoo.co.jp
mail.aol.com
mail.aol.fr
mail.com
gmx.com
couponcabin.com
outlook.live.com
hotmail.live.com
outlook.com
mailto.space
e.mail.ru
login.sso.bluewin.ch
secure.centurylink.net
secureserver.net
webmail.usfamily.net
webmail.skymesh.net.au
hanmail.net
mail.rambler.ru
freeola.co.uk
webmail-04.datacenter.cha.cantv.net
linustechtips.com
3capp-mailcom-lxa01.server.lan
noiholiday.com
hostedemail.com
heyholla.com
hellixeducation.com
unisalento.it
secrel.com.br
mailorama.fr
kiwiirc.com
derpibooru.org
trixiebooru.org
derpiboo.ru
derpicdn.net
alice.it
vcwebmail.ocn.ad.jp
tentaciones18.com
ocn.ad.jp
ocn.ne.jp
webmail.numericable.fr
webmail.scarlet.be
webmailer.de
mail.uk.tiscali.com
elisa.fi
saunalahti.fi
globalgiving.org
synacor.com
knology.net
netmail.pipex.net
sony.com
playstation.com
playstation.net
playstationnetwork.com
sonymusic.com
sonypictures.com
sonyentertainmentnetwork.com
perfora.net
hmail.com
gigablast.com
126.com
ono.com
funded.today
manenetheoffnewconne.ru
dlfeed.com
odbnfhbyagyz4.net
pinformationbwarranties.ru
musttthewithoutalsothas.ru
bjkaolp4ds4z.com
advseeking.com
companolo.com
mijnbankering-nl-server.com
froekuge.com
updateacces.org
sophisticatiretaj.net
soap4.me
boriel.com
ebb.mtn.co.za
osbservices.vodacom.co.za
apps.telkom.co.za
holadebug.com
fretebras.com.br
megacupons.online
vitacostapi.bbhosted.com
mobilepayca.com
mobilepayusa.org
mobilepayusa.mobi
mobilepayusa.net
mobilepay-web.gaiareply.eu
puffin.com
miosmsclub.com
linkedin.com
ticketsnow.com
playerline.com
ticketmaster.com
ticketmaster.com.mx
livenation.com
stubhub.com
tickets.com
craigslist.org
kufar.by
fastpeoplesearch.com
btc4you.com
elbitcoingratis.es
btcmine.net
virtualfaucet.net
rawbitcoins.net
freebitcoins4u.com
canhasbitcoin.com
bitcats.net
faucetbtc.com
bitcoins4free.me
thefreebitcoins.net
freebitcoins.me
5mindoge.com
freedoge.co.in
moondoge.co.in
moonbit.co.in
bonusbitcoin.co
bitcoinspace.net
btc-faucet.co
treebitcoin.com
flatcoin.net
baglitecoin.com
moonliteco.in
freebitco.in
github.com
smartadvices.com
generictadalafilusa.com
getitllc.com
createyourownteeshirtonline.com
mondino.it
daum.net
workbooks.com
forum.qt.io
radicalexploits.com
craigslist.ca
craigslist.co.uk
bisaboard.de
criusenergy.com
arenagroup.com
truqueira.com
cratejoy.com
gaynext.ca
hispachan.org
freenode.net
exigent.com.au
au.edu
runsignup.com
thegreekbookstore.com
arriveonline.com.au
reimageplus.com
goso.info
checkip.dyndns.org
warezshare.org
blocklist.de
twiki.org
intherooms.com
globalinnovationexchange.org
portal.webgods.de
postfinance.ch
gvtc.com
cantv.net
homepage-baukasten.de
paginawebgratis.es
own-free-website.com
sitowebfaidate.it
ma-page.fr
homepage-konstruktor.ru
bedava-sitem.com
stronygratis.pl
news.ycombinator.com
tdchosting.dk
infusionsoft.com
puregaming.es
blogalizate.com
teftv.com
porticolegal.com
physicalmed.es
www.htcmania.com
www.tebeosfera.com
grupopuntacana.rubypanel.com
www.fansdeapple.com
cerebriti.com
educaciontrespuntocero.com
pymescomercial.com
creativat.com
financiacionparaempresas.net
moon.rightdns.com
uptodate.com
judoprincast.com
office365.com

View File

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

View File

@@ -1,70 +1,82 @@
package main
import (
"time"
"sync"
"context"
"context"
"net/http"
"sync"
"time"
)
const DEFAULT_LIST_LIMIT = 3
const API_CALL_ATTEMPTS = 3
func CredService(interval, timeout time.Duration,
country string,
logger *CondLogger) (auth AuthProvider,
tunnels *ZGetTunnelsResponse,
err error) {
var mux sync.Mutex
var auth_header, user_uuid string
auth = func () (res string) {
(&mux).Lock()
res = auth_header
(&mux).Unlock()
return
}
country string,
proxytype string,
logger *CondLogger) (auth AuthProvider,
tunnels *ZGetTunnelsResponse,
err error) {
var mux sync.Mutex
var auth_header, user_uuid string
auth = func() (res string) {
(&mux).Lock()
res = auth_header
(&mux).Unlock()
return
}
for i := 0; i < API_CALL_ATTEMPTS ; i++ {
ctx, _ := context.WithTimeout(context.Background(), timeout)
tunnels, user_uuid, err = Tunnels(ctx, country, DEFAULT_LIST_LIMIT)
if err == nil {
break
}
}
if err != nil {
logger.Critical("Configuration bootstrap failed: %v", err)
return
}
auth_header = basic_auth_header(LOGIN_PREFIX + user_uuid,
tunnels.AgentKey)
go func() {
var (
err error
tuns *ZGetTunnelsResponse
user_uuid string
)
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
<-ticker.C
logger.Info("Rotating credentials...")
for i := 0; i < API_CALL_ATTEMPTS ; i++ {
ctx, _ := context.WithTimeout(context.Background(), timeout)
tuns, user_uuid, err = Tunnels(ctx, country, DEFAULT_LIST_LIMIT)
if err == nil {
break
}
}
if err != nil {
logger.Error("Credential rotation failed after %d attempts. Error: %v",
API_CALL_ATTEMPTS, err)
} else {
(&mux).Lock()
auth_header = basic_auth_header(LOGIN_PREFIX + user_uuid,
tuns.AgentKey)
(&mux).Unlock()
logger.Info("Credentials rotated successfully.")
}
}
}()
return
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)
if err != nil {
logger.Error("Configuration bootstrap 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
return
}
if !tx_res {
logger.Critical("All attempts failed.")
return
}
auth_header = basic_auth_header(LOGIN_PREFIX+user_uuid,
tunnels.AgentKey)
go func() {
var (
err error
tuns *ZGetTunnelsResponse
user_uuid string
)
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
<-ticker.C
logger.Info("Rotating credentials...")
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)
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 !tx_res {
logger.Critical("All rotation attempts failed.")
continue
}
(&mux).Lock()
auth_header = basic_auth_header(LOGIN_PREFIX+user_uuid,
tuns.AgentKey)
(&mux).Unlock()
logger.Info("Credentials rotated successfully.")
}
}()
return
}

23
csrand.go Normal file
View File

@@ -0,0 +1,23 @@
package main
import (
crand "crypto/rand"
"math/big"
)
type secureRandomSource struct{}
var RandomSource secureRandomSource
var int63Limit = big.NewInt(0).Lsh(big.NewInt(1), 63)
func (_ secureRandomSource) Seed(_ int64) {
}
func (_ secureRandomSource) Int63() int64 {
randNum, err := crand.Int(crand.Reader, int63Limit)
if err != nil {
panic(err)
}
return randNum.Int64()
}

8
go.mod
View File

@@ -2,4 +2,10 @@ module github.com/Snawoot/hola-proxy
go 1.13
require github.com/google/uuid v1.1.1
require (
github.com/AdguardTeam/dnsproxy v0.25.0
github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e
github.com/google/uuid v1.1.1
github.com/miekg/dns v1.1.29
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e
)

70
go.sum
View File

@@ -1,2 +1,72 @@
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/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/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/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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
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 h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
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/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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
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 h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
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=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
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 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@@ -1,93 +1,118 @@
package main
import (
"io"
"net/http"
"net/http/httputil"
"crypto/tls"
"strings"
"context"
"time"
"net/url"
"fmt"
"net/http"
"net/url"
"strings"
"time"
)
const BAD_REQ_MSG = "Bad Request\n"
type AuthProvider func() string
type ProxyHandler struct {
auth AuthProvider
upstream string
logger *CondLogger
httptransport http.RoundTripper
logger *CondLogger
dialer ContextDialer
httptransport http.RoundTripper
auth AuthProvider
}
func NewProxyHandler(upstream string, auth AuthProvider, 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),
}
return &ProxyHandler{
auth: auth,
upstream: upstream,
logger: logger,
httptransport: httptransport,
}
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{
logger: logger,
dialer: dialer,
auth: auth,
httptransport: httptransport,
}
}
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 {
s.logger.Error("Can't hijack client connection: %v", err)
http.Error(wr, "Can't hijack client connection", http.StatusInternalServerError)
return
}
defer localconn.Close()
// 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 {
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", req.RemoteAddr, req.Method, req.URL)
req.Header.Set("Proxy-Authorization", s.auth())
if strings.ToUpper(req.Method) == "CONNECT" {
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.StatusInternalServerError)
return
defer conn.Close()
}
hj, ok := wr.(http.Hijacker)
if !ok {
s.logger.Critical("Webserver doesn't support hijacking")
http.Error(wr, "Webserver doesn't support hijacking", http.StatusInternalServerError)
return
}
localconn, _, err := hj.Hijack()
if err != nil {
s.logger.Error("Can't hijack client connection: %v", err)
http.Error(wr, "Can't hijack client connection", http.StatusInternalServerError)
return
}
var emptytime time.Time
err = localconn.SetDeadline(emptytime)
if err != nil {
s.logger.Error("Can't clear deadlines on local connection: %v", err)
http.Error(wr, "Can't clear deadlines on local connection", http.StatusInternalServerError)
return
}
conn.Write(rawreq)
proxy(context.TODO(), localconn, conn)
} else {
req.RequestURI = ""
delHopHeaders(req.Header)
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)
io.Copy(wr, resp.Body)
}
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

@@ -1,20 +1,28 @@
package main
import (
"context"
"net/http"
"net/url"
"io/ioutil"
"encoding/json"
"encoding/hex"
"github.com/google/uuid"
"bytes"
"strconv"
"math/rand"
"bytes"
"context"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"math/rand"
"net"
"net/http"
"net/url"
"strconv"
"sync"
"time"
"github.com/campoy/unique"
"github.com/google/uuid"
)
const USER_AGENT = "Mozilla/5.0 (X11; Fedora; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36"
const EXT_VER = "1.164.641"
const USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36"
const EXT_VER = "1.186.562"
const EXT_BROWSER = "chrome"
const PRODUCT = "cws"
const CCGI_URL = "https://client.hola.org/client_cgi/"
@@ -22,134 +30,360 @@ 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 TemporaryBanError = errors.New("temporary ban detected")
var PermanentBanError = errors.New("permanent ban detected")
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 {
Direct uint16 `json:"direct"`
Hola uint16 `json:"hola"`
Peer uint16 `json:"peer"`
Trial uint16 `json:"trial"`
TrialPeer uint16 `json:"trial_peer"`
Direct uint16 `json:"direct"`
Hola uint16 `json:"hola"`
Peer uint16 `json:"peer"`
Trial uint16 `json:"trial"`
TrialPeer uint16 `json:"trial_peer"`
}
type ZGetTunnelsResponse struct {
AgentKey string `json:"agent_key"`
AgentTypes map[string]string `json:"agent_types"`
IPList map[string]string `json:"ip_list"`
Port PortMap `json:"port"`
Protocol map[string]string `json:"protocol"`
Vendor map[string]string `json:"vendor"`
Ztun map[string][]string `json:"ztun"`
AgentKey string `json:"agent_key"`
AgentTypes map[string]string `json:"agent_types"`
IPList map[string]string `json:"ip_list"`
Port PortMap `json:"port"`
Protocol map[string]string `json:"protocol"`
Vendor map[string]string `json:"vendor"`
Ztun map[string][]string `json:"ztun"`
}
func do_req(ctx context.Context, method, url string, query, data url.Values) ([]byte, error) {
var (
client http.Client
req *http.Request
err error
)
if method == "" {
method = "GET"
}
if data == nil {
req, err = http.NewRequestWithContext(ctx, method, url, nil)
} else {
req, err = http.NewRequestWithContext(ctx,
method,
url,
bytes.NewReader([]byte(data.Encode())))
}
if err != nil {
return nil, err
}
if data != nil {
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}
if query != nil {
req.URL.RawQuery = query.Encode()
}
req.Header.Set("User-Agent", USER_AGENT)
resp, err := client.Do(req)
if err != nil {
return nil, err
}
body, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
return nil, err
}
return body, nil
type FallbackAgent struct {
Name string `json:"name"`
IP string `json:"ip"`
Port uint16 `json:"port"`
}
func VPNCountries(ctx context.Context) (res CountryList, err error) {
params := make(url.Values)
params.Add("browser", EXT_BROWSER)
data, err := do_req(ctx, "", VPN_COUNTRIES_URL, params, nil)
if err != nil {
return nil, err
}
err = json.Unmarshal(data, &res)
return
type fallbackConfResponse struct {
Agents []FallbackAgent `json:"agents"`
UpdatedAt int64 `json:"updated_ts"`
TTL int64 `json:"ttl_ms"`
}
func background_init(ctx context.Context, user_uuid string) (res BgInitResponse, reterr error) {
post_data := make(url.Values)
post_data.Add("login", "1")
post_data.Add("ver", EXT_VER)
qs := make(url.Values)
qs.Add("uuid", user_uuid)
resp, err := do_req(ctx, "POST", BG_INIT_URL, qs, post_data)
if err != nil {
reterr = err
return
}
reterr = json.Unmarshal(resp, &res)
return
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 (
req *http.Request
err error
)
if method == "" {
method = "GET"
}
if data == nil {
req, err = http.NewRequestWithContext(ctx, method, url, nil)
} else {
req, err = http.NewRequestWithContext(ctx,
method,
url,
bytes.NewReader([]byte(data.Encode())))
}
if err != nil {
return nil, err
}
if data != nil {
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}
if query != nil {
req.URL.RawQuery = query.Encode()
}
req.Header.Set("User-Agent", USER_AGENT)
resp, err := client.Do(req)
if err != nil {
return nil, err
}
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 {
return nil, err
}
return body, nil
}
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, client, "", VPN_COUNTRIES_URL, params, nil)
if err != nil {
return nil, err
}
err = json.Unmarshal(data, &res)
for _, a := range res {
if a == "uk" {
res = append(res, "gb")
}
}
less := func(i, j int) bool { return res[i] < res[j] }
unique.Slice(&res, less)
return
}
func background_init(ctx context.Context, client *http.Client, user_uuid string) (res BgInitResponse, reterr error) {
post_data := make(url.Values)
post_data.Add("login", "1")
post_data.Add("ver", EXT_VER)
qs := make(url.Values)
qs.Add("uuid", user_uuid)
resp, err := do_req(ctx, 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,
user_uuid string,
session_key int64,
country string,
limit uint) (res *ZGetTunnelsResponse, reterr error) {
var tunnels ZGetTunnelsResponse
params := make(url.Values)
params.Add("country", country)
params.Add("limit", strconv.FormatInt(int64(limit), 10))
params.Add("ping_id", strconv.FormatFloat(rand.Float64(), 'f', -1, 64))
params.Add("ext_ver", EXT_VER)
params.Add("browser", EXT_BROWSER)
params.Add("product", PRODUCT)
params.Add("uuid", user_uuid)
params.Add("session_key", strconv.FormatInt(session_key, 10))
params.Add("is_premium", "0")
data, err := do_req(ctx, "", ZGETTUNNELS_URL, params, nil)
if err != nil {
reterr = err
return
}
reterr = json.Unmarshal(data, &tunnels)
res = &tunnels
return
client *http.Client,
user_uuid string,
session_key int64,
country string,
proxy_type string,
limit uint) (res *ZGetTunnelsResponse, reterr error) {
var tunnels ZGetTunnelsResponse
params := make(url.Values)
if proxy_type == "lum" {
params.Add("country", country+".pool_lum_"+country+"_shared")
} else if proxy_type == "virt" { // seems to be for brazil and japan only
params.Add("country", country+".pool_virt_pool_"+country)
} else if proxy_type == "peer" {
//params.Add("country", country + ".peer")
params.Add("country", country)
} else if proxy_type == "pool" {
params.Add("country", country+".pool")
} else { // direct or skip
params.Add("country", country)
}
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("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, client, "", ZGETTUNNELS_URL, params, nil)
if err != nil {
reterr = err
return
}
reterr = json.Unmarshal(data, &tunnels)
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,
country string,
limit uint) (res *ZGetTunnelsResponse, user_uuid string, reterr error) {
u := uuid.New()
user_uuid = hex.EncodeToString(u[:])
initres, err := background_init(ctx, user_uuid)
if err != nil {
reterr = err
return
}
res, reterr = zgettunnels(ctx, user_uuid, initres.Key, country, limit)
return
client *http.Client,
country string,
proxy_type string,
limit uint) (res *ZGetTunnelsResponse, user_uuid string, reterr error) {
u := uuid.New()
user_uuid = hex.EncodeToString(u[:])
initres, err := background_init(ctx, client, user_uuid)
if err != nil {
reterr = err
return
}
res, reterr = zgettunnels(ctx, client, user_uuid, initres.Key, country, proxy_type, limit)
return
}
var baseDialer ContextDialer = &net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}
func UpdateHolaDialer(dialer ContextDialer) {
baseDialer = dialer
}
// 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,
}
var dialer ContextDialer = baseDialer
if agent != nil {
dialer = NewProxyDialer(agent.NetAddr(), agent.Hostname(), nil, dialer)
}
t.DialContext = dialer.DialContext
return &http.Client{
Transport: t,
}
}
func EnsureTransaction(baseCtx context.Context, txnTimeout 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)
if err != nil {
return false, err
}
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
}
}
return false, nil
}

View File

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

View File

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

236
main.go
View File

@@ -1,106 +1,180 @@
package main
import (
"log"
"os"
"fmt"
"flag"
// "os/signal"
// "syscall"
"time"
"net/http"
"errors"
"flag"
"fmt"
"log"
"net"
"net/http"
"net/url"
"os"
"time"
xproxy "golang.org/x/net/proxy"
)
var (
PROTOCOL_WHITELIST map[string]bool
PROTOCOL_WHITELIST map[string]bool
version = "undefined"
)
func init() {
PROTOCOL_WHITELIST = map[string]bool{
"HTTP": true,
"http": true,
}
PROTOCOL_WHITELIST = map[string]bool{
"HTTP": true,
"http": true,
}
}
func perror(msg string) {
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, msg)
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, msg)
}
func arg_fail(msg string) {
perror(msg)
perror("Usage:")
flag.PrintDefaults()
os.Exit(2)
perror(msg)
perror("Usage:")
flag.PrintDefaults()
os.Exit(2)
}
type CLIArgs struct {
country string
list_countries, list_proxies bool
limit uint
bind_address string
verbosity int
timeout, rotate time.Duration
proxy_type string
country string
list_countries, list_proxies, use_trial bool
limit uint
bind_address string
verbosity int
timeout, rotate time.Duration
proxy_type string
resolver string
force_port_field string
showVersion bool
proxy string
}
func parse_args() CLIArgs {
var args CLIArgs
flag.StringVar(&args.country, "country", "us", "desired proxy location")
flag.BoolVar(&args.list_countries, "list-countries", false, "list available countries and exit")
flag.BoolVar(&args.list_proxies, "list-proxies", false, "output proxy list and exit")
flag.UintVar(&args.limit, "limit", 3, "amount of proxies in retrieved list")
flag.StringVar(&args.bind_address, "bind-address", "127.0.0.1:8080", "HTTP proxy listen address")
flag.IntVar(&args.verbosity, "verbosity", 20, "logging verbosity " +
"(10 - debug, 20 - info, 30 - warning, 40 - error, 50 - critical)")
flag.DurationVar(&args.timeout, "timeout", 10 * time.Second, "timeout for network operations")
flag.DurationVar(&args.rotate, "rotate", 1 * time.Hour, "rotate user ID once per given period")
flag.StringVar(&args.proxy_type, "proxy-type", "direct", "proxy type: direct or peer")
flag.Parse()
if args.country == "" {
arg_fail("Country can't be empty string.")
}
if args.list_countries && args.list_proxies {
arg_fail("list-countries and list-proxies flags are mutually exclusive")
}
return args
var args CLIArgs
flag.StringVar(&args.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")
flag.BoolVar(&args.list_proxies, "list-proxies", false, "output proxy list and exit")
flag.UintVar(&args.limit, "limit", 3, "amount of proxies in retrieved list")
flag.StringVar(&args.bind_address, "bind-address", "127.0.0.1:8080", "HTTP proxy listen address")
flag.IntVar(&args.verbosity, "verbosity", 20, "logging verbosity "+
"(10 - debug, 20 - info, 30 - warning, 40 - error, 50 - critical)")
flag.DurationVar(&args.timeout, "timeout", 10*time.Second, "timeout for network operations")
flag.DurationVar(&args.rotate, "rotate", 1*time.Hour, "rotate user ID once per given period")
flag.StringVar(&args.proxy_type, "proxy-type", "direct", "proxy type: direct or 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.Parse()
if args.country == "" {
arg_fail("Country can't be empty string.")
}
if args.proxy_type == "" {
arg_fail("Proxy type can't be an empty string.")
}
if args.list_countries && args.list_proxies {
arg_fail("list-countries and list-proxies flags are mutually exclusive")
}
return args
}
func proxyFromURLWrapper(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, cdialer)
}
func run() int {
args := parse_args()
if args.showVersion {
fmt.Println(version)
return 0
}
logWriter := NewLogWriter(os.Stderr)
defer logWriter.Close()
mainLogger := NewCondLogger(log.New(logWriter, "MAIN : ",
log.LstdFlags|log.Lshortfile),
args.verbosity)
credLogger := NewCondLogger(log.New(logWriter, "CRED : ",
log.LstdFlags|log.Lshortfile),
args.verbosity)
proxyLogger := NewCondLogger(log.New(logWriter, "PROXY : ",
log.LstdFlags|log.Lshortfile),
args.verbosity)
var dialer ContextDialer = &net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}
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.list_proxies {
return print_proxies(args.country, args.proxy_type, args.limit, args.timeout)
}
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)
if err != nil {
mainLogger.Critical("Unable to instantiate credential service: %v", err)
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)
return 5
}
handlerDialer := NewProxyDialer(endpoint.NetAddr(), endpoint.TLSName, auth, dialer)
requestDialer := NewPlaintextDialer(endpoint.NetAddr(), endpoint.TLSName, dialer)
mainLogger.Info("Endpoint: %s", endpoint.URL().String())
mainLogger.Info("Starting proxy server...")
handler := NewProxyHandler(handlerDialer, 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...")
return 0
}
func main() {
args := parse_args()
if args.list_countries {
os.Exit(print_countries(args.timeout))
}
if args.list_proxies {
os.Exit(print_proxies(args.country, args.limit, args.timeout))
}
logWriter := NewLogWriter(os.Stderr)
defer logWriter.Close()
mainLogger := NewCondLogger(log.New(logWriter, "MAIN : ",
log.LstdFlags | log.Lshortfile),
args.verbosity)
credLogger := NewCondLogger(log.New(logWriter, "CRED : ",
log.LstdFlags | log.Lshortfile),
args.verbosity)
proxyLogger := NewCondLogger(log.New(logWriter, "PROXY : ",
log.LstdFlags | log.Lshortfile),
args.verbosity)
mainLogger.Info("Initializing configuration provider...")
auth, tunnels, err := CredService(args.rotate, args.timeout, args.country, credLogger)
if err != nil {
os.Exit(4)
}
endpoint, err := get_endpoint(tunnels, args.proxy_type)
if err != nil {
mainLogger.Critical("Unable to determine proxy endpoint: %v", err)
os.Exit(5)
}
mainLogger.Info("Starting proxy server...")
handler := NewProxyHandler(endpoint, auth, proxyLogger)
http.ListenAndServe(args.bind_address, handler)
mainLogger.Info("Shutting down...")
os.Exit(run())
}

62
plaintext.go Normal file
View File

@@ -0,0 +1,62 @@
package main
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"net"
)
type PlaintextDialer struct {
fixedAddress string
tlsServerName string
next ContextDialer
}
func NewPlaintextDialer(address, tlsServerName string, next ContextDialer) *PlaintextDialer {
return &PlaintextDialer{
fixedAddress: address,
tlsServerName: tlsServerName,
next: next,
}
}
func (d *PlaintextDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
switch network {
case "tcp", "tcp4", "tcp6":
default:
return nil, errors.New("bad network specified for DialContext: only tcp is supported")
}
conn, err := d.next.DialContext(ctx, "tcp", d.fixedAddress)
if err != nil {
return nil, err
}
if d.tlsServerName != "" {
// Custom cert verification logic:
// DO NOT send SNI extension of TLS ClientHello
// DO peer certificate verification against specified servername
conn = tls.Client(conn, &tls.Config{
ServerName: "",
InsecureSkipVerify: true,
VerifyConnection: func(cs tls.ConnectionState) error {
opts := x509.VerifyOptions{
DNSName: d.tlsServerName,
Intermediates: x509.NewCertPool(),
}
for _, cert := range cs.PeerCertificates[1:] {
opts.Intermediates.AddCert(cert)
}
_, err := cs.PeerCertificates[0].Verify(opts)
return err
},
})
}
return conn, nil
}
func (d *PlaintextDialer) Dial(network, address string) (net.Conn, error) {
return d.DialContext(context.Background(), network, address)
}

82
resolver.go Normal file
View File

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

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)
}

27
snapcraft.yaml Normal file
View File

@@ -0,0 +1,27 @@
name: hola-proxy
version: '1.5.3'
summary: Standalone Hola proxy client.
description: |
Standalone Hola proxy client. Just run it and it'll start plain HTTP proxy server forwarding traffic via Hola proxies of your choice.
confinement: strict
base: core18
parts:
hola-proxy:
plugin: go
source: .
build-packages:
- gcc
override-build:
make &&
cp bin/hola-proxy "$SNAPCRAFT_PART_INSTALL"
stage:
- hola-proxy
apps:
hola-proxy:
command: hola-proxy
plugs:
- network
- network-bind

189
upstream.go Normal file
View File

@@ -0,0 +1,189 @@
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
}
func NewProxyDialer(address, tlsServerName string, auth AuthProvider, nextDialer ContextDialer) *ProxyDialer {
return &ProxyDialer{
address: address,
tlsServerName: tlsServerName,
auth: auth,
next: nextDialer,
}
}
func ProxyDialerFromURL(u *url.URL, 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, auth, 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
conn = tls.Client(conn, &tls.Config{
ServerName: "",
InsecureSkipVerify: true,
VerifyConnection: func(cs tls.ConnectionState) error {
opts := x509.VerifyOptions{
DNSName: d.tlsServerName,
Intermediates: x509.NewCertPool(),
}
for _, cert := range cs.PeerCertificates[1:] {
opts.Intermediates.AddCert(cert)
}
_, err := cs.PeerCertificates[0].Verify(opts)
return err
},
})
}
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)
}

343
utils.go
View File

@@ -1,112 +1,227 @@
package main
import (
"fmt"
"context"
"net"
"sync"
"io"
"os"
"time"
"encoding/base64"
"encoding/csv"
"errors"
"strings"
"strconv"
"net/http"
"bufio"
"context"
"encoding/base64"
"encoding/csv"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"sync"
"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))
return "basic " + base64.StdEncoding.EncodeToString(
[]byte(login+":"+password))
}
func proxy(ctx context.Context, left, right net.Conn) {
wg := sync.WaitGroup{}
cpy := func (dst, src net.Conn) {
defer wg.Done()
io.Copy(dst, src)
dst.Close()
}
wg.Add(2)
go cpy(left, right)
go cpy(right, left)
groupdone := make(chan struct{})
go func() {
wg.Wait()
groupdone <-struct{}{}
}()
select {
case <-ctx.Done():
left.Close()
right.Close()
case <-groupdone:
return
}
<-groupdone
return
wg := sync.WaitGroup{}
cpy := func(dst, src net.Conn) {
defer wg.Done()
io.Copy(dst, src)
dst.Close()
}
wg.Add(2)
go cpy(left, right)
go cpy(right, left)
groupdone := make(chan struct{})
go func() {
wg.Wait()
groupdone <- struct{}{}
}()
select {
case <-ctx.Done():
left.Close()
right.Close()
case <-groupdone:
return
}
<-groupdone
return
}
func 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)
return 3
}
for _, code := range countries {
fmt.Printf("%v - %v\n", code, ISO3166[strings.ToUpper(code)])
}
return 0
var (
countries CountryList
err error
)
tx_res, tx_err := EnsureTransaction(context.Background(), timeout, func(ctx context.Context, client *http.Client) bool {
countries, err = VPNCountries(ctx, 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 {
fmt.Printf("%v - %v\n", code, ISO3166[strings.ToUpper(code)])
}
return 0
}
func print_proxies(country string, limit uint, timeout time.Duration) int {
ctx, _ := context.WithTimeout(context.Background(), timeout)
tunnels, user_uuid, err := Tunnels(ctx, country, limit)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
return 3
}
wr := csv.NewWriter(os.Stdout)
login := LOGIN_PREFIX + user_uuid
password := tunnels.AgentKey
fmt.Println("Login:", login)
fmt.Println("Password:", password)
fmt.Println("Proxy-Authorization:",
basic_auth_header(login, password))
fmt.Println("")
wr.Write([]string{"Host", "IP address", "Direct port", "Peer port", "Vendor"})
for host, ip := range tunnels.IPList {
if (PROTOCOL_WHITELIST[tunnels.Protocol[host]]) {
wr.Write([]string{host,
ip,
strconv.FormatUint(uint64(tunnels.Port.Direct), 10),
strconv.FormatUint(uint64(tunnels.Port.Peer), 10),
tunnels.Vendor[host]})
}
}
wr.Flush()
return 0
func print_proxies(country string, proxy_type string, limit uint, timeout 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)
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
password := tunnels.AgentKey
fmt.Println("Login:", login)
fmt.Println("Password:", password)
fmt.Println("Proxy-Authorization:",
basic_auth_header(login, password))
fmt.Println("")
wr.Write([]string{"host", "ip_address", "direct", "peer", "hola", "trial", "trial_peer", "vendor"})
for host, ip := range tunnels.IPList {
if PROTOCOL_WHITELIST[tunnels.Protocol[host]] {
wr.Write([]string{host,
ip,
strconv.FormatUint(uint64(tunnels.Port.Direct), 10),
strconv.FormatUint(uint64(tunnels.Port.Peer), 10),
strconv.FormatUint(uint64(tunnels.Port.Hola), 10),
strconv.FormatUint(uint64(tunnels.Port.Trial), 10),
strconv.FormatUint(uint64(tunnels.Port.TrialPeer), 10),
tunnels.Vendor[host]})
}
}
wr.Flush()
return 0
}
func get_endpoint(tunnels *ZGetTunnelsResponse, typ string) (string, error) {
var hostname string
for k, _ := range tunnels.IPList {
hostname = k
break
}
if hostname == "" {
return "", errors.New("No tunnels found in API response")
}
var port uint16
if typ == "direct" {
port = tunnels.Port.Direct
} else if typ == "peer" {
port = tunnels.Port.Peer
} else {
return "", errors.New("Unsupported port type")
}
return net.JoinHostPort(hostname, strconv.FormatUint(uint64(port), 10)), nil
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 == "" || ip == "" {
return nil, errors.New("No tunnels found in API response")
}
var port uint16
if force_port_field != "" {
port2, err := strconv.ParseUint(force_port_field, 0, 16)
if err == nil {
port = (uint16)(port2)
typ = "skip"
} else {
typ = force_port_field
}
}
if typ != "skip" {
if typ == "direct" || typ == "lum" || typ == "pool" || typ == "virt" {
if !trial {
port = tunnels.Port.Trial
} else {
port = tunnels.Port.Direct
}
} else if typ == "peer" {
if !trial {
port = tunnels.Port.TrialPeer
} else {
port = tunnels.Port.Peer
}
} else {
return nil, errors.New("Unsupported port type")
}
}
return &Endpoint{
Host: ip,
Port: port,
TLSName: hostname,
}, nil
}
// Hop-by-hop headers. These are removed when sent to the backend.
@@ -115,6 +230,7 @@ var hopHeaders = []string{
"Connection",
"Keep-Alive",
"Proxy-Authenticate",
"Proxy-Connection",
"Te", // canonicalized version of "TE"
"Trailers",
"Transfer-Encoding",
@@ -135,3 +251,44 @@ func delHopHeaders(header http.Header) {
}
}
func hijack(hijackable interface{}) (net.Conn, *bufio.ReadWriter, error) {
hj, ok := hijackable.(http.Hijacker)
if !ok {
return nil, nil, errors.New("Connection doesn't support hijacking")
}
conn, rw, err := hj.Hijack()
if err != nil {
return nil, nil, err
}
var emptytime time.Time
err = conn.SetDeadline(emptytime)
if err != nil {
conn.Close()
return nil, nil, err
}
return conn, rw, nil
}
func flush(flusher interface{}) bool {
f, ok := flusher.(http.Flusher)
if !ok {
return false
}
f.Flush()
return true
}
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
}
}
}