mirror of
https://github.com/Snawoot/hola-proxy.git
synced 2026-04-04 20:48:18 +00:00
Compare commits
68 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
068a2d5b83 | ||
|
|
fadff8c38f | ||
|
|
bd1a37b3d3 | ||
|
|
7d891ac613 | ||
|
|
458efb37ba | ||
|
|
26990c6130 | ||
|
|
880631670e | ||
|
|
27381ce5ff | ||
|
|
5050e96484 | ||
|
|
30295224ee | ||
|
|
4d4348686c | ||
|
|
86b7fece9b | ||
|
|
608da0baa9 | ||
|
|
0c36dee0b7 | ||
|
|
f2fdeea039 | ||
|
|
f5da736ca1 | ||
|
|
dd0eaa7611 | ||
|
|
882bca34bc | ||
|
|
70faaec848 | ||
|
|
4728548594 | ||
|
|
2c555adb35 | ||
|
|
9fee5905bb | ||
|
|
19fd0c9d52 | ||
|
|
a3e1bd0901 | ||
|
|
882b6381db | ||
|
|
9d686e3f70 | ||
|
|
1f44e7548d | ||
|
|
910f76065f | ||
|
|
18dd1776be | ||
|
|
1c98b33978 | ||
|
|
776411c2d4 | ||
|
|
f2c7f73548 | ||
|
|
af12cee5f0 | ||
|
|
05ac2bc146 | ||
|
|
1511ed333a | ||
|
|
af955a6cd1 | ||
|
|
cfd3474af3 | ||
|
|
eb2ae460da | ||
|
|
dbb9b29c81 | ||
|
|
839499313b | ||
|
|
58006c4f6b | ||
|
|
b23dc2f63f | ||
|
|
55788d8188 | ||
|
|
bc0b0df26a | ||
|
|
700ae4a4f4 | ||
|
|
8db460ff99 | ||
|
|
9efb99cf8e | ||
|
|
af2f1e40d6 | ||
|
|
f6d3de8488 | ||
|
|
fde7f1516b | ||
|
|
3f92cecac9 | ||
|
|
6bfb8d0aee | ||
|
|
4faf6aa04b | ||
|
|
edd723079d | ||
|
|
3cb79059b2 | ||
|
|
3b09f31616 | ||
|
|
8d9285c00b | ||
|
|
528e2b2a71 | ||
|
|
0a473f9662 | ||
|
|
17860682be | ||
|
|
1f6c87a797 | ||
|
|
72beef10c9 | ||
|
|
ead89d5245 | ||
|
|
6edd098c82 | ||
|
|
752d2ba789 | ||
|
|
311d1ad74d | ||
|
|
6ac04587cb | ||
|
|
8c3538ab4c |
58
.github/workflows/docker-ci.yml
vendored
Normal file
58
.github/workflows/docker-ci.yml
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
name: docker-ci
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
-
|
||||
name: Find Git Tag
|
||||
id: tagger
|
||||
uses: jimschubert/query-tag-action@v2
|
||||
with:
|
||||
include: 'v*'
|
||||
exclude: '*-rc*'
|
||||
commit-ish: 'HEAD'
|
||||
skip-unshallow: 'true'
|
||||
abbrev: 7
|
||||
-
|
||||
name: Determine image tag type
|
||||
uses: haya14busa/action-cond@v1
|
||||
id: imgtag
|
||||
with:
|
||||
cond: ${{ github.event_name == 'release' }}
|
||||
if_true: ${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }}:${{ github.event.release.tag_name }},${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }}:latest
|
||||
if_false: ${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }}:latest
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
-
|
||||
name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
-
|
||||
name: Build and push
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/386,linux/arm/v7
|
||||
push: true
|
||||
tags: ${{ steps.imgtag.outputs.value }}
|
||||
build-args: 'GIT_DESC=${{steps.tagger.outputs.tag}}'
|
||||
@@ -1,8 +1,10 @@
|
||||
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"'
|
||||
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
|
||||
|
||||
|
||||
112
Makefile
112
Makefile
@@ -3,76 +3,152 @@ OUTSUFFIX = bin/$(PROGNAME)
|
||||
VERSION := $(shell git describe)
|
||||
BUILDOPTS = -a -tags netgo
|
||||
LDFLAGS = -ldflags '-s -w -extldflags "-static" -X main.version=$(VERSION)'
|
||||
LDFLAGS_NATIVE = -ldflags '-s -w -X main.version=$(VERSION)'
|
||||
|
||||
src = $(wildcard *.go)
|
||||
NDK_CC_ARM = $(abspath ../../ndk-toolchain-arm/bin/arm-linux-androideabi-gcc)
|
||||
NDK_CC_ARM64 = $(abspath ../../ndk-toolchain-arm64/bin/aarch64-linux-android21-clang)
|
||||
|
||||
GO := go
|
||||
|
||||
src = $(wildcard *.go */*.go */*/*.go) go.mod go.sum
|
||||
|
||||
native: bin-native
|
||||
all: bin-linux-amd64 bin-linux-386 bin-linux-arm \
|
||||
all: bin-linux-amd64 bin-linux-386 bin-linux-arm bin-linux-arm64 \
|
||||
bin-linux-mips bin-linux-mipsle bin-linux-mips64 bin-linux-mips64le \
|
||||
bin-freebsd-amd64 bin-freebsd-386 bin-freebsd-arm \
|
||||
bin-darwin-amd64 \
|
||||
bin-windows-amd64 bin-windows-386
|
||||
bin-netbsd-amd64 bin-netbsd-386 \
|
||||
bin-openbsd-amd64 bin-openbsd-386 \
|
||||
bin-darwin-amd64 bin-darwin-arm64 \
|
||||
bin-windows-amd64 bin-windows-386 bin-windows-arm
|
||||
|
||||
allplus: all \
|
||||
bin-android-arm bin-android-arm64
|
||||
|
||||
bin-native: $(OUTSUFFIX)
|
||||
bin-linux-amd64: $(OUTSUFFIX).linux-amd64
|
||||
bin-linux-386: $(OUTSUFFIX).linux-386
|
||||
bin-linux-arm: $(OUTSUFFIX).linux-arm
|
||||
bin-linux-arm64: $(OUTSUFFIX).linux-arm64
|
||||
bin-linux-mips: $(OUTSUFFIX).linux-mips
|
||||
bin-linux-mipsle: $(OUTSUFFIX).linux-mipsle
|
||||
bin-linux-mips64: $(OUTSUFFIX).linux-mips64
|
||||
bin-linux-mips64le: $(OUTSUFFIX).linux-mips64le
|
||||
bin-freebsd-amd64: $(OUTSUFFIX).freebsd-amd64
|
||||
bin-freebsd-386: $(OUTSUFFIX).freebsd-386
|
||||
bin-freebsd-arm: $(OUTSUFFIX).freebsd-arm
|
||||
bin-netbsd-amd64: $(OUTSUFFIX).netbsd-amd64
|
||||
bin-netbsd-386: $(OUTSUFFIX).netbsd-386
|
||||
bin-openbsd-amd64: $(OUTSUFFIX).openbsd-amd64
|
||||
bin-openbsd-386: $(OUTSUFFIX).openbsd-386
|
||||
bin-darwin-amd64: $(OUTSUFFIX).darwin-amd64
|
||||
bin-darwin-arm64: $(OUTSUFFIX).darwin-arm64
|
||||
bin-windows-amd64: $(OUTSUFFIX).windows-amd64.exe
|
||||
bin-windows-386: $(OUTSUFFIX).windows-386.exe
|
||||
bin-windows-arm: $(OUTSUFFIX).windows-arm.exe
|
||||
bin-android-arm: $(OUTSUFFIX).android-arm
|
||||
bin-android-arm64: $(OUTSUFFIX).android-arm64
|
||||
|
||||
$(OUTSUFFIX): $(src)
|
||||
CGO_ENABLED=0 go build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
$(GO) build $(LDFLAGS_NATIVE) -o $@
|
||||
|
||||
$(OUTSUFFIX).linux-amd64: $(src)
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
|
||||
$(OUTSUFFIX).linux-386: $(src)
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=386 go build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=386 $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
|
||||
$(OUTSUFFIX).linux-arm: $(src)
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm go build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
|
||||
$(OUTSUFFIX).linux-arm64: $(src)
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
|
||||
$(OUTSUFFIX).linux-mips: $(src)
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=mips GOMIPS=softfloat $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
|
||||
$(OUTSUFFIX).linux-mips64: $(src)
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=mips64 GOMIPS=softfloat $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
|
||||
$(OUTSUFFIX).linux-mipsle: $(src)
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=mipsle GOMIPS=softfloat $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
|
||||
$(OUTSUFFIX).linux-mips64le: $(src)
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=mips64le GOMIPS=softfloat $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
|
||||
$(OUTSUFFIX).freebsd-amd64: $(src)
|
||||
CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
|
||||
$(OUTSUFFIX).freebsd-386: $(src)
|
||||
CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
CGO_ENABLED=0 GOOS=freebsd GOARCH=386 $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
|
||||
$(OUTSUFFIX).freebsd-arm: $(src)
|
||||
CGO_ENABLED=0 GOOS=freebsd GOARCH=arm go build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
CGO_ENABLED=0 GOOS=freebsd GOARCH=arm $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
|
||||
$(OUTSUFFIX).netbsd-amd64: $(src)
|
||||
CGO_ENABLED=0 GOOS=netbsd GOARCH=amd64 $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
|
||||
$(OUTSUFFIX).netbsd-386: $(src)
|
||||
CGO_ENABLED=0 GOOS=netbsd GOARCH=386 $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
|
||||
$(OUTSUFFIX).openbsd-amd64: $(src)
|
||||
CGO_ENABLED=0 GOOS=openbsd GOARCH=amd64 $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
|
||||
$(OUTSUFFIX).openbsd-386: $(src)
|
||||
CGO_ENABLED=0 GOOS=openbsd GOARCH=386 $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
|
||||
$(OUTSUFFIX).darwin-amd64: $(src)
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
|
||||
$(OUTSUFFIX).darwin-arm64: $(src)
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 $(GO) build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
|
||||
$(OUTSUFFIX).windows-amd64.exe: $(src)
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 $(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 ./...
|
||||
$(GO) fmt ./...
|
||||
|
||||
run:
|
||||
go run $(LDFLAGS) .
|
||||
$(GO) run $(LDFLAGS) .
|
||||
|
||||
install:
|
||||
go install $(BUILDOPTS) $(LDFLAGS) .
|
||||
$(GO) install $(LDFLAGS_NATIVE) .
|
||||
|
||||
.PHONY: clean all native fmt install \
|
||||
bin-native \
|
||||
bin-linux-amd64 \
|
||||
bin-linux-386 \
|
||||
bin-linux-arm \
|
||||
bin-linux-arm64 \
|
||||
bin-linux-mips \
|
||||
bin-linux-mipsle \
|
||||
bin-linux-mips64 \
|
||||
bin-linux-mips64le \
|
||||
bin-freebsd-amd64 \
|
||||
bin-freebsd-386 \
|
||||
bin-freebsd-arm \
|
||||
bin-netbsd-amd64 \
|
||||
bin-netbsd-386 \
|
||||
bin-openbsd-amd64 \
|
||||
bin-openbsd-386 \
|
||||
bin-darwin-amd64 \
|
||||
bin-windows-amd64 \
|
||||
bin-windows-386
|
||||
bin-windows-386 \
|
||||
bin-windows-arm \
|
||||
bin-android-arm \
|
||||
bin-android-arm64
|
||||
|
||||
84
README.md
84
README.md
@@ -2,9 +2,10 @@
|
||||
|
||||
[](https://snapcraft.io/hola-proxy)
|
||||
|
||||
Standalone Hola proxy client. Just run it and it'll start plain HTTP proxy server forwarding traffic via Hola proxies of your choice. By default application listens port on 127.0.0.1:8080.
|
||||
Standalone Hola proxy client. Just run it and it'll start a plain HTTP proxy server forwarding traffic through Hola proxies of your choice.
|
||||
By default the application listens on 127.0.0.1:8080.
|
||||
|
||||
Application is capable to forward traffic via proxies in datacenters (flag `-proxy-type direct`, default) or via peer proxies on residental IPs (consumer ISP) in that country (flag `-proxy-type pool` or `-proxy-type lum`).
|
||||
Application is capable to forward traffic via proxies in datacenters (flag `-proxy-type direct`, default) or via peer proxies on residental IPs (consumer ISP) in that country (flag `-proxy-type lum`).
|
||||
|
||||
---
|
||||
|
||||
@@ -31,17 +32,24 @@ git clone https://ipfs.io/ipns/k51qzi5uqu5dkrgx0hozpy1tlggw5o0whtquyrjlc6pprhvbm
|
||||
|
||||
* Cross-platform (Windows/Mac OS/Linux/Android (via shell)/\*BSD)
|
||||
* Uses TLS for secure communication with upstream proxies
|
||||
* Zero-configuration
|
||||
* Zero configuration
|
||||
* Simple and straight forward
|
||||
|
||||
## Installation
|
||||
|
||||
#### Binary download
|
||||
#### Binaries
|
||||
|
||||
Pre-built binaries available on [releases](https://github.com/Snawoot/hola-proxy/releases/latest) page.
|
||||
Pre-built binaries are available [here](https://github.com/Snawoot/hola-proxy/releases/latest).
|
||||
|
||||
#### From source
|
||||
Don't forget to make file executable on Unix-like systems (Linux, MacOS, \*BSD, Android). For your convenience rename downloaded file to `hola-proxy` and run within directory where you placed it:
|
||||
|
||||
Alternatively, you may install hola-proxy from source. Run within source directory
|
||||
```sh
|
||||
chmod +x hola-proxy
|
||||
```
|
||||
|
||||
#### Build from source
|
||||
|
||||
Alternatively, you may install hola-proxy from source. Run the following within the source directory:
|
||||
|
||||
```
|
||||
make install
|
||||
@@ -49,7 +57,7 @@ make install
|
||||
|
||||
#### Docker
|
||||
|
||||
Docker image is available as well. Here is an example for running proxy via DE as a background service:
|
||||
A docker image is available as well. Here is an example of running hola-proxy via DE as a background service:
|
||||
|
||||
```sh
|
||||
docker run -d \
|
||||
@@ -73,7 +81,7 @@ sudo snap install hola-proxy
|
||||
List available countries:
|
||||
|
||||
```
|
||||
$ ~/go/bin/hola-proxy -list-countries
|
||||
$ ./hola-proxy -list-countries
|
||||
ar - Argentina
|
||||
at - Austria
|
||||
au - Australia
|
||||
@@ -121,19 +129,19 @@ us - United States of America
|
||||
Run proxy via country of your choice:
|
||||
|
||||
```
|
||||
$ ~/go/bin/hola-proxy -country de
|
||||
$ ./hola-proxy -country de
|
||||
```
|
||||
|
||||
Or run proxy on residental IP:
|
||||
Or run proxy on residential IP:
|
||||
|
||||
```
|
||||
$ ~/go/bin/hola-proxy -country de -proxy-type lum
|
||||
$ ./hola-proxy -proxy-type lum
|
||||
```
|
||||
|
||||
Also it is possible to export proxy addresses and credentials:
|
||||
|
||||
```
|
||||
$ ~/go/bin/hola-proxy -country de -list-proxies -limit 3
|
||||
$ ./hola-proxy -country de -list-proxies -limit 3
|
||||
Login: user-uuid-0a67c797b3214cbdb432b089c4b801cd
|
||||
Password: cd123c465901
|
||||
Proxy-Authorization: basic dXNlci11dWlkLTBhNjdjNzk3YjMyMTRjYmRiNDMyYjA4OWM0YjgwMWNkOmNkMTIzYzQ2NTkwMQ==
|
||||
@@ -144,33 +152,25 @@ zagent830.hola.org,104.248.24.64,22222,22223,22224,22225,22226,digitalocean
|
||||
zagent248.hola.org,165.22.65.3,22222,22223,22224,22225,22226,digitalocean
|
||||
```
|
||||
|
||||
## Synopsis
|
||||
## List of arguments
|
||||
|
||||
```
|
||||
$ ~/go/bin/hola-proxy -h
|
||||
Usage of /home/user/go/bin/hola-proxy:
|
||||
-bind-address string
|
||||
HTTP proxy listen address (default "127.0.0.1:8080")
|
||||
-country string
|
||||
desired proxy location (default "us")
|
||||
-dont-use-trial
|
||||
use regular ports instead of trial ports
|
||||
-force-port-field string
|
||||
force specific port field/num (example 24232 or lum)
|
||||
-limit uint
|
||||
amount of proxies in retrieved list (default 3)
|
||||
-list-countries
|
||||
list available countries and exit
|
||||
-list-proxies
|
||||
output proxy list and exit
|
||||
-proxy-type string
|
||||
proxy type: direct or peer or lum or virt or pool (default "direct")
|
||||
-resolver string
|
||||
DNS/DoH/DoT resolver to workaround Hola blocked hosts. See https://github.com/ameshkov/dnslookup/ for upstream DNS URL format. (default "https://cloudflare-dns.com/dns-query")
|
||||
-rotate duration
|
||||
rotate user ID once per given period (default 1h0m0s)
|
||||
-timeout duration
|
||||
timeout for network operations (default 10s)
|
||||
-verbosity int
|
||||
logging verbosity (10 - debug, 20 - info, 30 - warning, 40 - error, 50 - critical) (default 20)
|
||||
```
|
||||
| Argument | Type | Description |
|
||||
| -------- | ---- | ----------- |
|
||||
| bind-address | String | HTTP proxy address to listen to (default "127.0.0.1:8080") |
|
||||
| cafile | String | use custom CA certificate bundle file |
|
||||
| country | String | desired proxy location (default "us") |
|
||||
| dont-use-trial | - | use regular ports instead of trial ports |
|
||||
| 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)
|
||||
|
||||
@@ -2,12 +2,12 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const DEFAULT_LIST_LIMIT = 3
|
||||
const API_CALL_ATTEMPTS = 3
|
||||
|
||||
func CredService(interval, timeout time.Duration,
|
||||
country string,
|
||||
@@ -24,15 +24,21 @@ func CredService(interval, timeout time.Duration,
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < API_CALL_ATTEMPTS; i++ {
|
||||
ctx, _ := context.WithTimeout(context.Background(), timeout)
|
||||
tunnels, user_uuid, err = Tunnels(ctx, country, proxytype, DEFAULT_LIST_LIMIT)
|
||||
if err == nil {
|
||||
break
|
||||
tx_res, tx_err := EnsureTransaction(context.Background(), timeout, func(ctx context.Context, client *http.Client) bool {
|
||||
tunnels, user_uuid, err = Tunnels(ctx, 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 err != nil {
|
||||
logger.Critical("Configuration bootstrap failed: %v", err)
|
||||
if !tx_res {
|
||||
logger.Critical("All attempts failed.")
|
||||
return
|
||||
}
|
||||
auth_header = basic_auth_header(LOGIN_PREFIX+user_uuid,
|
||||
@@ -48,23 +54,28 @@ func CredService(interval, timeout time.Duration,
|
||||
for {
|
||||
<-ticker.C
|
||||
logger.Info("Rotating credentials...")
|
||||
for i := 0; i < API_CALL_ATTEMPTS; i++ {
|
||||
ctx, _ := context.WithTimeout(context.Background(), timeout)
|
||||
tuns, user_uuid, err = Tunnels(ctx, country, proxytype, DEFAULT_LIST_LIMIT)
|
||||
if err == nil {
|
||||
break
|
||||
tx_res, tx_err := EnsureTransaction(context.Background(), timeout, func(ctx context.Context, client *http.Client) bool {
|
||||
tuns, user_uuid, err = Tunnels(ctx, 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 err != nil {
|
||||
logger.Error("Credential rotation failed after %d attempts. Error: %v",
|
||||
API_CALL_ATTEMPTS, err)
|
||||
} else {
|
||||
(&mux).Lock()
|
||||
auth_header = basic_auth_header(LOGIN_PREFIX+user_uuid,
|
||||
tuns.AgentKey)
|
||||
(&mux).Unlock()
|
||||
logger.Info("Credentials rotated successfully.")
|
||||
if !tx_res {
|
||||
logger.Critical("All rotation attempts failed.")
|
||||
continue
|
||||
}
|
||||
(&mux).Lock()
|
||||
auth_header = basic_auth_header(LOGIN_PREFIX+user_uuid,
|
||||
tuns.AgentKey)
|
||||
(&mux).Unlock()
|
||||
logger.Info("Credentials rotated successfully.")
|
||||
}
|
||||
}()
|
||||
return
|
||||
|
||||
23
csrand.go
Normal file
23
csrand.go
Normal 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()
|
||||
}
|
||||
14
go.mod
14
go.mod
@@ -3,8 +3,16 @@ module github.com/Snawoot/hola-proxy
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/AdguardTeam/dnsproxy v0.25.0
|
||||
github.com/AdguardTeam/dnsproxy v0.43.1
|
||||
github.com/AdguardTeam/golibs v0.10.9 // indirect
|
||||
github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/miekg/dns v1.1.29
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/lucas-clemente/quic-go v0.27.2 // indirect
|
||||
github.com/miekg/dns v1.1.50
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e
|
||||
golang.org/x/sys v0.0.0-20220624220833-87e55d714810 // indirect
|
||||
golang.org/x/tools v0.1.11 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
|
||||
)
|
||||
|
||||
345
go.sum
345
go.sum
@@ -1,64 +1,365 @@
|
||||
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=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
|
||||
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
|
||||
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
|
||||
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
||||
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
github.com/AdguardTeam/dnsproxy v0.43.1 h1:E777KfQAi+VurOoWEdGQ5iqjSOOAzzbTfLOEzj8heCs=
|
||||
github.com/AdguardTeam/dnsproxy v0.43.1/go.mod h1:JUGTm5dmlll47JltztsT0N//pVJjdg6zu0SNeUeaA7g=
|
||||
github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
||||
github.com/AdguardTeam/golibs v0.10.8/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
|
||||
github.com/AdguardTeam/golibs v0.10.9 h1:F9oP2da0dQ9RQDM1lGR7LxUTfUWu8hEFOs4icwAkKM0=
|
||||
github.com/AdguardTeam/golibs v0.10.9/go.mod h1:W+5rznZa1cSNSFt+gPS7f4Wytnr9fOrd5ZYqwadPw14=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
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/dnscrypt/v2 v2.2.3 h1:X9UP5AHtwp46Ji+sGFfF/1Is6OPI/SjxLqhKpx0P5UI=
|
||||
github.com/ameshkov/dnscrypt/v2 v2.2.3/go.mod h1:xJB9cE1/GF+NB6EEQqRlkoa4bjcV2w7VYn1G+zVq7Bs=
|
||||
github.com/ameshkov/dnsstamps v1.0.1/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
||||
github.com/beefsack/go-rate v0.0.0-20180408011153-efa7637bb9b6/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA=
|
||||
github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
|
||||
github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/beefsack/go-rate v0.0.0-20200827232406-6cde80facd47/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||
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/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
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/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
||||
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/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.3/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/lucas-clemente/quic-go v0.27.1/go.mod h1:AzgQoPda7N+3IqMMMkywBKggIFo2KT6pfnlrQ2QieeI=
|
||||
github.com/lucas-clemente/quic-go v0.27.2 h1:zsMwwniyybb8B/UDNXRSYee7WpQJVOcjQEGgpw2ikXs=
|
||||
github.com/lucas-clemente/quic-go v0.27.2/go.mod h1:vXgO/11FBSKM+js1NxoaQ/bPtVFYfB7uxhfHXyMhl1A=
|
||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.5 h1:o9JrYPPco/Nukd/HpOHMHZoBDXQqoNtUCmny98/1uqQ=
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.5/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.1/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s=
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.2 h1:JADBlm0LYiVbuSySCHeY863dNkcpMmDR7s0bLKJeYlQ=
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.2/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s=
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.1/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.2 h1:JH6jmzbduz0ITVQ7ShevK10Av5+jBEKAHMntXmIV7kM=
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.2/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/miekg/dns v1.1.44/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
||||
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||
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/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
|
||||
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
|
||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
||||
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
|
||||
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
|
||||
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
|
||||
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
|
||||
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
|
||||
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
|
||||
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
|
||||
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
|
||||
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
|
||||
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
|
||||
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
|
||||
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
|
||||
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
|
||||
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
|
||||
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
|
||||
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
|
||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
|
||||
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
|
||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/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-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
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/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210929193557-e81a3d93ecf6/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d h1:nc5K6ox/4lTFbMVSL9WRR81ixkcwXThoiF6yf+R9scA=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220624220833-87e55d714810 h1:rHZQSjJdAI4Xf5Qzeh2bBc5YJIkPFVM6oDtMFYmgws0=
|
||||
golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
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.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
golang.org/x/tools v0.1.11 h1:loJ25fNOEhSXfHrpoGj91eCUThwdNX6u24rO1xnNteY=
|
||||
golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f h1:uF6paiQQebLeSXkrTqHqz0MXhXXS1KgF41eUdBNvxK0=
|
||||
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
||||
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
||||
|
||||
264
handler.go
264
handler.go
@@ -1,122 +1,57 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const BAD_REQ_MSG = "Bad Request\n"
|
||||
|
||||
type AuthProvider func() string
|
||||
|
||||
type ProxyHandler struct {
|
||||
auth AuthProvider
|
||||
upstream string
|
||||
logger *CondLogger
|
||||
dialer ContextDialer
|
||||
httptransport http.RoundTripper
|
||||
resolver *Resolver
|
||||
auth AuthProvider
|
||||
}
|
||||
|
||||
func NewProxyHandler(upstream string, auth AuthProvider, resolver *Resolver, logger *CondLogger) *ProxyHandler {
|
||||
proxyurl, err := url.Parse("https://" + upstream)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
func NewProxyHandler(dialer, requestDialer ContextDialer, auth AuthProvider, resolver *Resolver, logger *CondLogger) *ProxyHandler {
|
||||
dialer = NewRetryDialer(dialer, resolver, logger)
|
||||
httptransport := &http.Transport{
|
||||
Proxy: http.ProxyURL(proxyurl),
|
||||
Proxy: func(_ *http.Request) (*url.URL, error) {
|
||||
return &url.URL{
|
||||
Scheme: "http",
|
||||
Host: "void",
|
||||
}, nil
|
||||
},
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
DialContext: requestDialer.DialContext,
|
||||
}
|
||||
return &ProxyHandler{
|
||||
auth: auth,
|
||||
upstream: upstream,
|
||||
logger: logger,
|
||||
dialer: dialer,
|
||||
auth: auth,
|
||||
httptransport: httptransport,
|
||||
resolver: resolver,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ProxyHandler) ServeHTTP(wr http.ResponseWriter, req *http.Request) {
|
||||
s.logger.Info("Request: %v %v %v", req.RemoteAddr, req.Method, req.URL)
|
||||
if strings.ToUpper(req.Method) == "CONNECT" {
|
||||
req.Header.Set("Proxy-Authorization", s.auth())
|
||||
rawreq, err := httputil.DumpRequest(req, false)
|
||||
if err != nil {
|
||||
s.logger.Error("Can't dump request: %v", err)
|
||||
http.Error(wr, "Can't dump request", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
conn, err := tls.Dial("tcp", s.upstream, nil)
|
||||
if err != nil {
|
||||
s.logger.Error("Can't dial tls upstream: %v", err)
|
||||
http.Error(wr, "Can't dial tls upstream", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = conn.Write(rawreq)
|
||||
if err != nil {
|
||||
s.logger.Error("Can't write tls upstream: %v", err)
|
||||
http.Error(wr, "Can't write tls upstream", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
bufrd := bufio.NewReader(conn)
|
||||
proxyResp, err := http.ReadResponse(bufrd, req)
|
||||
responseBytes := make([]byte, 0)
|
||||
if err != nil {
|
||||
s.logger.Error("Can't read response from upstream: %v", err)
|
||||
http.Error(wr, "Can't read response from upstream", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
if proxyResp.StatusCode == http.StatusForbidden &&
|
||||
proxyResp.Header.Get("X-Hola-Error") == "Forbidden Host" {
|
||||
s.logger.Info("Request %s denied by upstream. Rescuing it with resolve&rewrite workaround.",
|
||||
req.URL.String())
|
||||
conn.Close()
|
||||
conn, err = tls.Dial("tcp", s.upstream, nil)
|
||||
if err != nil {
|
||||
s.logger.Error("Can't dial tls upstream: %v", err)
|
||||
http.Error(wr, "Can't dial tls upstream", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
err = rewriteConnectReq(req, s.resolver)
|
||||
if err != nil {
|
||||
s.logger.Error("Can't rewrite request: %v", err)
|
||||
http.Error(wr, "Can't rewrite request", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
rawreq, err = httputil.DumpRequest(req, false)
|
||||
if err != nil {
|
||||
s.logger.Error("Can't dump request: %v", err)
|
||||
http.Error(wr, "Can't dump request", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
_, err = conn.Write(rawreq)
|
||||
if err != nil {
|
||||
s.logger.Error("Can't write tls upstream: %v", err)
|
||||
http.Error(wr, "Can't write tls upstream", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
defer conn.Close()
|
||||
responseBytes, err = httputil.DumpResponse(proxyResp, false)
|
||||
if err != nil {
|
||||
s.logger.Error("Can't dump response: %v", err)
|
||||
http.Error(wr, "Can't dump response", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
buffered := bufrd.Buffered()
|
||||
if buffered > 0 {
|
||||
trailer := make([]byte, buffered)
|
||||
bufrd.Read(trailer)
|
||||
responseBytes = append(responseBytes, trailer...)
|
||||
}
|
||||
}
|
||||
bufrd = nil
|
||||
func (s *ProxyHandler) HandleTunnel(wr http.ResponseWriter, req *http.Request) {
|
||||
ctx := req.Context()
|
||||
conn, err := s.dialer.DialContext(ctx, "tcp", req.RequestURI)
|
||||
if err != nil {
|
||||
s.logger.Error("Can't satisfy CONNECT request: %v", err)
|
||||
http.Error(wr, "Can't satisfy CONNECT request", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
if req.ProtoMajor == 0 || req.ProtoMajor == 1 {
|
||||
// Upgrade client connection
|
||||
localconn, _, err := hijack(wr)
|
||||
if err != nil {
|
||||
@@ -126,95 +61,58 @@ func (s *ProxyHandler) ServeHTTP(wr http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
defer localconn.Close()
|
||||
|
||||
if len(responseBytes) > 0 {
|
||||
_, err = localconn.Write(responseBytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
// Inform client connection is built
|
||||
fmt.Fprintf(localconn, "HTTP/%d.%d 200 OK\r\n\r\n", req.ProtoMajor, req.ProtoMinor)
|
||||
|
||||
proxy(req.Context(), localconn, conn)
|
||||
} else if req.ProtoMajor == 2 {
|
||||
wr.Header()["Date"] = nil
|
||||
wr.WriteHeader(http.StatusOK)
|
||||
flush(wr)
|
||||
proxyh2(req.Context(), req.Body, wr, conn)
|
||||
} else {
|
||||
delHopHeaders(req.Header)
|
||||
orig_req := req.Clone(req.Context())
|
||||
req.RequestURI = ""
|
||||
req.Header.Set("Proxy-Authorization", s.auth())
|
||||
resp, err := s.httptransport.RoundTrip(req)
|
||||
if err != nil {
|
||||
s.logger.Error("HTTP fetch error: %v", err)
|
||||
http.Error(wr, "Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if resp.StatusCode == http.StatusForbidden &&
|
||||
resp.Header.Get("X-Hola-Error") == "Forbidden Host" {
|
||||
s.logger.Info("Request %s denied by upstream. Rescuing it with resolve&tunnel workaround.",
|
||||
req.URL.String())
|
||||
resp.Body.Close()
|
||||
|
||||
// Prepare tunnel request
|
||||
proxyReq, err := makeConnReq(orig_req.RequestURI, s.resolver)
|
||||
if err != nil {
|
||||
s.logger.Error("Can't rewrite request: %v", err)
|
||||
http.Error(wr, "Can't rewrite request", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
proxyReq.Header.Set("Proxy-Authorization", s.auth())
|
||||
rawreq, _ := httputil.DumpRequest(proxyReq, false)
|
||||
|
||||
// Prepare upstream TLS conn
|
||||
conn, err := tls.Dial("tcp", s.upstream, nil)
|
||||
if err != nil {
|
||||
s.logger.Error("Can't dial tls upstream: %v", err)
|
||||
http.Error(wr, "Can't dial tls upstream", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Send proxy request
|
||||
_, err = conn.Write(rawreq)
|
||||
if err != nil {
|
||||
s.logger.Error("Can't write tls upstream: %v", err)
|
||||
http.Error(wr, "Can't write tls upstream", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
// Read proxy response
|
||||
bufrd := bufio.NewReader(conn)
|
||||
proxyResp, err := http.ReadResponse(bufrd, proxyReq)
|
||||
if err != nil {
|
||||
s.logger.Error("Can't read response from upstream: %v", err)
|
||||
http.Error(wr, "Can't read response from upstream", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
if proxyResp.StatusCode != http.StatusOK {
|
||||
delHopHeaders(proxyResp.Header)
|
||||
copyHeader(wr.Header(), proxyResp.Header)
|
||||
wr.WriteHeader(proxyResp.StatusCode)
|
||||
}
|
||||
|
||||
// Send tunneled request
|
||||
orig_req.RequestURI = ""
|
||||
orig_req.Header.Set("Connection", "close")
|
||||
rawreq, _ = httputil.DumpRequest(orig_req, false)
|
||||
_, err = conn.Write(rawreq)
|
||||
if err != nil {
|
||||
s.logger.Error("Can't write tls upstream: %v", err)
|
||||
http.Error(wr, "Can't write tls upstream", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
// Read tunneled response
|
||||
resp, err = http.ReadResponse(bufrd, orig_req)
|
||||
if err != nil {
|
||||
s.logger.Error("Can't read response from upstream: %v", err)
|
||||
http.Error(wr, "Can't read response from upstream", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
s.logger.Info("%v %v %v %v", req.RemoteAddr, req.Method, req.URL, resp.Status)
|
||||
delHopHeaders(resp.Header)
|
||||
copyHeader(wr.Header(), resp.Header)
|
||||
wr.WriteHeader(resp.StatusCode)
|
||||
io.Copy(wr, resp.Body)
|
||||
s.logger.Error("Unsupported protocol version: %s", req.Proto)
|
||||
http.Error(wr, "Unsupported protocol version.", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ProxyHandler) HandleRequest(wr http.ResponseWriter, req *http.Request) {
|
||||
req.RequestURI = ""
|
||||
if req.ProtoMajor == 2 {
|
||||
req.URL.Scheme = "http" // We can't access :scheme pseudo-header, so assume http
|
||||
req.URL.Host = req.Host
|
||||
}
|
||||
delHopHeaders(req.Header)
|
||||
req.Header.Add("Proxy-Authorization", s.auth())
|
||||
resp, err := s.httptransport.RoundTrip(req)
|
||||
if err != nil {
|
||||
s.logger.Error("HTTP fetch error: %v", err)
|
||||
http.Error(wr, "Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
s.logger.Info("%v %v %v %v", req.RemoteAddr, req.Method, req.URL, resp.Status)
|
||||
delHopHeaders(resp.Header)
|
||||
copyHeader(wr.Header(), resp.Header)
|
||||
wr.WriteHeader(resp.StatusCode)
|
||||
flush(wr)
|
||||
copyBody(wr, resp.Body)
|
||||
}
|
||||
|
||||
func (s *ProxyHandler) ServeHTTP(wr http.ResponseWriter, req *http.Request) {
|
||||
s.logger.Info("Request: %v %v %v %v", req.RemoteAddr, req.Proto, req.Method, req.URL)
|
||||
|
||||
isConnect := strings.ToUpper(req.Method) == "CONNECT"
|
||||
if (req.URL.Host == "" || req.URL.Scheme == "" && !isConnect) && req.ProtoMajor < 2 ||
|
||||
req.Host == "" && req.ProtoMajor == 2 {
|
||||
http.Error(wr, BAD_REQ_MSG, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
delHopHeaders(req.Header)
|
||||
if isConnect {
|
||||
s.HandleTunnel(wr, req)
|
||||
} else {
|
||||
s.HandleRequest(wr, req)
|
||||
}
|
||||
}
|
||||
|
||||
53
helper.sh
Executable file
53
helper.sh
Executable 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
|
||||
238
holaapi.go
238
holaapi.go
@@ -3,21 +3,28 @@ package main
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/campoy/unique"
|
||||
"github.com/google/uuid"
|
||||
"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; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 Safari/537.36"
|
||||
const EXT_VER = "1.181.350"
|
||||
const USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36"
|
||||
const EXT_VER = "1.186.562"
|
||||
const EXT_BROWSER = "chrome"
|
||||
const PRODUCT = "cws"
|
||||
const CCGI_URL = "https://client.hola.org/client_cgi/"
|
||||
@@ -25,6 +32,8 @@ 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")
|
||||
@@ -57,11 +66,74 @@ type ZGetTunnelsResponse struct {
|
||||
Ztun map[string][]string `json:"ztun"`
|
||||
}
|
||||
|
||||
func do_req(ctx context.Context, method, url string, query, data url.Values) ([]byte, error) {
|
||||
type FallbackAgent struct {
|
||||
Name string `json:"name"`
|
||||
IP string `json:"ip"`
|
||||
Port uint16 `json:"port"`
|
||||
}
|
||||
|
||||
type fallbackConfResponse struct {
|
||||
Agents []FallbackAgent `json:"agents"`
|
||||
UpdatedAt int64 `json:"updated_ts"`
|
||||
TTL int64 `json:"ttl_ms"`
|
||||
}
|
||||
|
||||
type FallbackConfig struct {
|
||||
Agents []FallbackAgent
|
||||
UpdatedAt time.Time
|
||||
TTL time.Duration
|
||||
}
|
||||
|
||||
func (c *FallbackConfig) UnmarshalJSON(data []byte) error {
|
||||
r := fallbackConfResponse{}
|
||||
err := json.Unmarshal(data, &r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Agents = r.Agents
|
||||
c.UpdatedAt = time.Unix(r.UpdatedAt/1000, (r.UpdatedAt%1000)*1000000)
|
||||
c.TTL = time.Duration(r.TTL * 1000000)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *FallbackConfig) Expired() bool {
|
||||
return time.Now().After(c.UpdatedAt.Add(c.TTL))
|
||||
}
|
||||
|
||||
func (c *FallbackConfig) ShuffleAgents() {
|
||||
rand.New(RandomSource).Shuffle(len(c.Agents), func(i, j int) {
|
||||
c.Agents[i], c.Agents[j] = c.Agents[j], c.Agents[i]
|
||||
})
|
||||
}
|
||||
|
||||
func (c *FallbackConfig) Clone() *FallbackConfig {
|
||||
return &FallbackConfig{
|
||||
Agents: append([]FallbackAgent(nil), c.Agents...),
|
||||
UpdatedAt: c.UpdatedAt,
|
||||
TTL: c.TTL,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *FallbackAgent) ToProxy() *url.URL {
|
||||
return &url.URL{
|
||||
Scheme: "https",
|
||||
Host: net.JoinHostPort(a.Name+AGENT_SUFFIX,
|
||||
fmt.Sprintf("%d", a.Port)),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *FallbackAgent) Hostname() string {
|
||||
return a.Name + AGENT_SUFFIX
|
||||
}
|
||||
|
||||
func (a *FallbackAgent) NetAddr() string {
|
||||
return net.JoinHostPort(a.IP, fmt.Sprintf("%d", a.Port))
|
||||
}
|
||||
|
||||
func do_req(ctx context.Context, client *http.Client, method, url string, query, data url.Values) ([]byte, error) {
|
||||
var (
|
||||
client http.Client
|
||||
req *http.Request
|
||||
err error
|
||||
req *http.Request
|
||||
err error
|
||||
)
|
||||
if method == "" {
|
||||
method = "GET"
|
||||
@@ -101,10 +173,10 @@ func do_req(ctx context.Context, method, url string, query, data url.Values) ([]
|
||||
return body, nil
|
||||
}
|
||||
|
||||
func VPNCountries(ctx context.Context) (res CountryList, err error) {
|
||||
func VPNCountries(ctx context.Context, client *http.Client) (res CountryList, err error) {
|
||||
params := make(url.Values)
|
||||
params.Add("browser", EXT_BROWSER)
|
||||
data, err := do_req(ctx, "", VPN_COUNTRIES_URL, params, nil)
|
||||
data, err := do_req(ctx, client, "", VPN_COUNTRIES_URL, params, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -119,13 +191,13 @@ func VPNCountries(ctx context.Context) (res CountryList, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func background_init(ctx context.Context, user_uuid string) (res BgInitResponse, reterr error) {
|
||||
func background_init(ctx context.Context, client *http.Client, 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)
|
||||
resp, err := do_req(ctx, client, "POST", BG_INIT_URL, qs, post_data)
|
||||
if err != nil {
|
||||
reterr = err
|
||||
return
|
||||
@@ -143,6 +215,7 @@ func background_init(ctx context.Context, user_uuid string) (res BgInitResponse,
|
||||
}
|
||||
|
||||
func zgettunnels(ctx context.Context,
|
||||
client *http.Client,
|
||||
user_uuid string,
|
||||
session_key int64,
|
||||
country string,
|
||||
@@ -163,14 +236,14 @@ func zgettunnels(ctx context.Context,
|
||||
params.Add("country", country)
|
||||
}
|
||||
params.Add("limit", strconv.FormatInt(int64(limit), 10))
|
||||
params.Add("ping_id", strconv.FormatFloat(rand.Float64(), 'f', -1, 64))
|
||||
params.Add("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, "", ZGETTUNNELS_URL, params, nil)
|
||||
data, err := do_req(ctx, client, "", ZGETTUNNELS_URL, params, nil)
|
||||
if err != nil {
|
||||
reterr = err
|
||||
return
|
||||
@@ -180,17 +253,150 @@ func zgettunnels(ctx context.Context,
|
||||
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,
|
||||
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, user_uuid)
|
||||
initres, err := background_init(ctx, client, user_uuid)
|
||||
if err != nil {
|
||||
reterr = err
|
||||
return
|
||||
}
|
||||
res, reterr = zgettunnels(ctx, user_uuid, initres.Key, country, proxy_type, limit)
|
||||
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,
|
||||
}
|
||||
|
||||
var tlsConfig *tls.Config
|
||||
|
||||
func UpdateHolaDialer(dialer ContextDialer) {
|
||||
baseDialer = dialer
|
||||
}
|
||||
|
||||
func UpdateHolaTLSConfig(config *tls.Config) {
|
||||
tlsConfig = config
|
||||
}
|
||||
|
||||
// Returns default http client with a proxy override
|
||||
func httpClientWithProxy(agent *FallbackAgent) *http.Client {
|
||||
t := &http.Transport{
|
||||
ForceAttemptHTTP2: true,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
TLSClientConfig: tlsConfig,
|
||||
}
|
||||
var dialer ContextDialer = baseDialer
|
||||
var rootCAs *x509.CertPool
|
||||
if tlsConfig != nil {
|
||||
rootCAs = tlsConfig.RootCAs
|
||||
}
|
||||
if agent != nil {
|
||||
dialer = NewProxyDialer(agent.NetAddr(), agent.Hostname(), rootCAs, nil, 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
|
||||
}
|
||||
|
||||
92
main.go
92
main.go
@@ -1,14 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
// "os/signal"
|
||||
// "syscall"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
xproxy "golang.org/x/net/proxy"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -46,6 +52,8 @@ type CLIArgs struct {
|
||||
resolver string
|
||||
force_port_field string
|
||||
showVersion bool
|
||||
proxy string
|
||||
caFile string
|
||||
}
|
||||
|
||||
func parse_args() CLIArgs {
|
||||
@@ -60,13 +68,17 @@ func parse_args() CLIArgs {
|
||||
"(10 - debug, 20 - info, 30 - warning, 40 - error, 50 - critical)")
|
||||
flag.DurationVar(&args.timeout, "timeout", 10*time.Second, "timeout for network operations")
|
||||
flag.DurationVar(&args.rotate, "rotate", 1*time.Hour, "rotate user ID once per given period")
|
||||
flag.StringVar(&args.proxy_type, "proxy-type", "direct", "proxy type: direct or peer or lum or virt or pool") // or skip but not mentioned
|
||||
flag.StringVar(&args.proxy_type, "proxy-type", "direct", "proxy type: direct or lum") // or skip but not mentioned
|
||||
// skip would be used something like this: `./bin/hola-proxy -proxy-type skip -force-port-field 24232 -country ua.peer` for debugging
|
||||
flag.StringVar(&args.resolver, "resolver", "https://cloudflare-dns.com/dns-query",
|
||||
"DNS/DoH/DoT resolver to workaround Hola blocked hosts. "+
|
||||
"See https://github.com/ameshkov/dnslookup/ for upstream DNS URL format.")
|
||||
flag.BoolVar(&args.use_trial, "dont-use-trial", false, "use regular ports instead of trial ports") // would be nice to not show in help page
|
||||
flag.BoolVar(&args.showVersion, "version", false, "show program version and exit")
|
||||
flag.StringVar(&args.proxy, "proxy", "", "sets base proxy to use for all dial-outs. "+
|
||||
"Format: <http|https|socks5|socks5h>://[login:password@]host[:port] "+
|
||||
"Examples: http://user:password@192.168.1.1:3128, socks5://10.0.0.1:1080")
|
||||
flag.StringVar(&args.caFile, "cafile", "", "use custom CA certificate bundle file")
|
||||
flag.Parse()
|
||||
if args.country == "" {
|
||||
arg_fail("Country can't be empty string.")
|
||||
@@ -87,13 +99,6 @@ func run() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
if args.list_countries {
|
||||
return print_countries(args.timeout)
|
||||
}
|
||||
if args.list_proxies {
|
||||
return print_proxies(args.country, args.proxy_type, args.limit, args.timeout)
|
||||
}
|
||||
|
||||
logWriter := NewLogWriter(os.Stderr)
|
||||
defer logWriter.Close()
|
||||
|
||||
@@ -106,6 +111,62 @@ func run() int {
|
||||
proxyLogger := NewCondLogger(log.New(logWriter, "PROXY : ",
|
||||
log.LstdFlags|log.Lshortfile),
|
||||
args.verbosity)
|
||||
|
||||
var dialer ContextDialer = &net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}
|
||||
|
||||
var caPool *x509.CertPool
|
||||
if args.caFile != "" {
|
||||
caPool = x509.NewCertPool()
|
||||
certs, err := ioutil.ReadFile(args.caFile)
|
||||
if err != nil {
|
||||
mainLogger.Error("Can't load CA file: %v", err)
|
||||
return 15
|
||||
}
|
||||
if ok := caPool.AppendCertsFromPEM(certs); !ok {
|
||||
mainLogger.Error("Can't load certificates from CA file")
|
||||
return 15
|
||||
}
|
||||
UpdateHolaTLSConfig(&tls.Config{
|
||||
RootCAs: caPool,
|
||||
})
|
||||
}
|
||||
|
||||
proxyFromURLWrapper := func(u *url.URL, next xproxy.Dialer) (xproxy.Dialer, error) {
|
||||
cdialer, ok := next.(ContextDialer)
|
||||
if !ok {
|
||||
return nil, errors.New("only context dialers are accepted")
|
||||
}
|
||||
|
||||
return ProxyDialerFromURL(u, caPool, cdialer)
|
||||
}
|
||||
|
||||
if args.proxy != "" {
|
||||
xproxy.RegisterDialerType("http", proxyFromURLWrapper)
|
||||
xproxy.RegisterDialerType("https", proxyFromURLWrapper)
|
||||
proxyURL, err := url.Parse(args.proxy)
|
||||
if err != nil {
|
||||
mainLogger.Critical("Unable to parse base proxy URL: %v", err)
|
||||
return 6
|
||||
}
|
||||
pxDialer, err := xproxy.FromURL(proxyURL, dialer)
|
||||
if err != nil {
|
||||
mainLogger.Critical("Unable to instantiate base proxy dialer: %v", err)
|
||||
return 7
|
||||
}
|
||||
dialer = pxDialer.(ContextDialer)
|
||||
UpdateHolaDialer(dialer)
|
||||
}
|
||||
|
||||
if args.list_countries {
|
||||
return print_countries(args.timeout)
|
||||
}
|
||||
if args.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)
|
||||
@@ -113,22 +174,23 @@ func run() int {
|
||||
mainLogger.Critical("Unable to instantiate DNS resolver: %v", err)
|
||||
return 6
|
||||
}
|
||||
|
||||
mainLogger.Info("Initializing configuration provider...")
|
||||
auth, tunnels, err := CredService(args.rotate, args.timeout, args.country, args.proxy_type, credLogger)
|
||||
if err != nil {
|
||||
mainLogger.Critical("Unable to instantiate credential service: %v", err)
|
||||
logWriter.Close()
|
||||
return 4
|
||||
}
|
||||
endpoint, err := get_endpoint(tunnels, args.proxy_type, args.use_trial, args.force_port_field)
|
||||
if err != nil {
|
||||
mainLogger.Critical("Unable to determine proxy endpoint: %v", err)
|
||||
logWriter.Close()
|
||||
return 5
|
||||
}
|
||||
mainLogger.Info("Endpoint: %s", endpoint)
|
||||
handlerDialer := NewProxyDialer(endpoint.NetAddr(), endpoint.TLSName, caPool, auth, dialer)
|
||||
requestDialer := NewPlaintextDialer(endpoint.NetAddr(), endpoint.TLSName, caPool, dialer)
|
||||
mainLogger.Info("Endpoint: %s", endpoint.URL().String())
|
||||
mainLogger.Info("Starting proxy server...")
|
||||
handler := NewProxyHandler(endpoint, auth, resolver, proxyLogger)
|
||||
handler := NewProxyHandler(handlerDialer, requestDialer, auth, resolver, proxyLogger)
|
||||
mainLogger.Info("Init complete.")
|
||||
err = http.ListenAndServe(args.bind_address, handler)
|
||||
mainLogger.Critical("Server terminated with a reason: %v", err)
|
||||
|
||||
65
plaintext.go
Normal file
65
plaintext.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"net"
|
||||
)
|
||||
|
||||
type PlaintextDialer struct {
|
||||
fixedAddress string
|
||||
tlsServerName string
|
||||
next ContextDialer
|
||||
caPool *x509.CertPool
|
||||
}
|
||||
|
||||
func NewPlaintextDialer(address, tlsServerName string, caPool *x509.CertPool, next ContextDialer) *PlaintextDialer {
|
||||
return &PlaintextDialer{
|
||||
fixedAddress: address,
|
||||
tlsServerName: tlsServerName,
|
||||
next: next,
|
||||
caPool: caPool,
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
Roots: d.caPool,
|
||||
}
|
||||
for _, cert := range cs.PeerCertificates[1:] {
|
||||
opts.Intermediates.AddCert(cert)
|
||||
}
|
||||
_, err := cs.PeerCertificates[0].Verify(opts)
|
||||
return err
|
||||
},
|
||||
})
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (d *PlaintextDialer) Dial(network, address string) (net.Conn, error) {
|
||||
return d.DialContext(context.Background(), network, address)
|
||||
}
|
||||
@@ -13,7 +13,7 @@ type Resolver struct {
|
||||
const DOT = 0x2e
|
||||
|
||||
func NewResolver(address string, timeout time.Duration) (*Resolver, error) {
|
||||
opts := upstream.Options{Timeout: timeout}
|
||||
opts := &upstream.Options{Timeout: timeout}
|
||||
u, err := upstream.AddressToUpstream(address, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
43
retry.go
Normal file
43
retry.go
Normal 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)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
name: hola-proxy
|
||||
version: '1.4.2'
|
||||
version: '1.5.5'
|
||||
summary: Standalone Hola proxy client.
|
||||
description: |
|
||||
Standalone Hola proxy client. Just run it and it'll start plain HTTP proxy server forwarding traffic via Hola proxies of your choice.
|
||||
@@ -10,15 +10,18 @@ base: core18
|
||||
parts:
|
||||
hola-proxy:
|
||||
plugin: go
|
||||
go-importpath: github.com/Snawoot/hola-proxy
|
||||
source: https://github.com/Snawoot/hola-proxy
|
||||
source-type: git
|
||||
source: .
|
||||
build-packages:
|
||||
- gcc
|
||||
override-build:
|
||||
make &&
|
||||
cp bin/hola-proxy "$SNAPCRAFT_PART_INSTALL"
|
||||
stage:
|
||||
- hola-proxy
|
||||
|
||||
apps:
|
||||
hola-proxy:
|
||||
command: bin/hola-proxy
|
||||
command: hola-proxy
|
||||
plugs:
|
||||
- network
|
||||
- network-bind
|
||||
|
||||
192
upstream.go
Normal file
192
upstream.go
Normal file
@@ -0,0 +1,192 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
PROXY_CONNECT_METHOD = "CONNECT"
|
||||
PROXY_HOST_HEADER = "Host"
|
||||
PROXY_AUTHORIZATION_HEADER = "Proxy-Authorization"
|
||||
)
|
||||
|
||||
var UpstreamBlockedError = errors.New("blocked by upstream")
|
||||
|
||||
type Dialer interface {
|
||||
Dial(network, address string) (net.Conn, error)
|
||||
}
|
||||
|
||||
type ContextDialer interface {
|
||||
Dialer
|
||||
DialContext(ctx context.Context, network, address string) (net.Conn, error)
|
||||
}
|
||||
|
||||
type ProxyDialer struct {
|
||||
address string
|
||||
tlsServerName string
|
||||
auth AuthProvider
|
||||
next ContextDialer
|
||||
caPool *x509.CertPool
|
||||
}
|
||||
|
||||
func NewProxyDialer(address, tlsServerName string, caPool *x509.CertPool, auth AuthProvider, nextDialer ContextDialer) *ProxyDialer {
|
||||
return &ProxyDialer{
|
||||
address: address,
|
||||
tlsServerName: tlsServerName,
|
||||
auth: auth,
|
||||
next: nextDialer,
|
||||
caPool: caPool,
|
||||
}
|
||||
}
|
||||
|
||||
func ProxyDialerFromURL(u *url.URL, caPool *x509.CertPool, next ContextDialer) (*ProxyDialer, error) {
|
||||
host := u.Hostname()
|
||||
port := u.Port()
|
||||
tlsServerName := ""
|
||||
var auth AuthProvider = nil
|
||||
|
||||
switch strings.ToLower(u.Scheme) {
|
||||
case "http":
|
||||
if port == "" {
|
||||
port = "80"
|
||||
}
|
||||
case "https":
|
||||
if port == "" {
|
||||
port = "443"
|
||||
}
|
||||
tlsServerName = host
|
||||
default:
|
||||
return nil, errors.New("unsupported proxy type")
|
||||
}
|
||||
|
||||
address := net.JoinHostPort(host, port)
|
||||
|
||||
if u.User != nil {
|
||||
username := u.User.Username()
|
||||
password, _ := u.User.Password()
|
||||
authHeader := basic_auth_header(username, password)
|
||||
auth = func() string {
|
||||
return authHeader
|
||||
}
|
||||
}
|
||||
return NewProxyDialer(address, tlsServerName, caPool, auth, 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(),
|
||||
Roots: d.caPool,
|
||||
}
|
||||
for _, cert := range cs.PeerCertificates[1:] {
|
||||
opts.Intermediates.AddCert(cert)
|
||||
}
|
||||
_, err := cs.PeerCertificates[0].Verify(opts)
|
||||
return err
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
req := &http.Request{
|
||||
Method: PROXY_CONNECT_METHOD,
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
RequestURI: address,
|
||||
Host: address,
|
||||
Header: http.Header{
|
||||
PROXY_HOST_HEADER: []string{address},
|
||||
},
|
||||
}
|
||||
|
||||
if d.auth != nil {
|
||||
req.Header.Set(PROXY_AUTHORIZATION_HEADER, d.auth())
|
||||
}
|
||||
|
||||
rawreq, err := httputil.DumpRequest(req, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = conn.Write(rawreq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proxyResp, err := readResponse(conn, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if proxyResp.StatusCode != http.StatusOK {
|
||||
if proxyResp.StatusCode == http.StatusForbidden &&
|
||||
proxyResp.Header.Get("X-Hola-Error") == "Forbidden Host" {
|
||||
return nil, UpstreamBlockedError
|
||||
}
|
||||
return nil, errors.New(fmt.Sprintf("bad response from upstream proxy server: %s", proxyResp.Status))
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (d *ProxyDialer) Dial(network, address string) (net.Conn, error) {
|
||||
return d.DialContext(context.Background(), network, address)
|
||||
}
|
||||
|
||||
func readResponse(r io.Reader, req *http.Request) (*http.Response, error) {
|
||||
endOfResponse := []byte("\r\n\r\n")
|
||||
buf := &bytes.Buffer{}
|
||||
b := make([]byte, 1)
|
||||
for {
|
||||
n, err := r.Read(b)
|
||||
if n < 1 && err == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
buf.Write(b)
|
||||
sl := buf.Bytes()
|
||||
if len(sl) < len(endOfResponse) {
|
||||
continue
|
||||
}
|
||||
|
||||
if bytes.Equal(sl[len(sl)-4:], endOfResponse) {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return http.ReadResponse(bufio.NewReader(buf), req)
|
||||
}
|
||||
204
utils.go
204
utils.go
@@ -18,6 +18,32 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const COPY_BUF = 128 * 1024
|
||||
|
||||
type Endpoint struct {
|
||||
Host string
|
||||
Port uint16
|
||||
TLSName string
|
||||
}
|
||||
|
||||
func (e *Endpoint) URL() *url.URL {
|
||||
if e.TLSName == "" {
|
||||
return &url.URL{
|
||||
Scheme: "http",
|
||||
Host: net.JoinHostPort(e.Host, fmt.Sprintf("%d", e.Port)),
|
||||
}
|
||||
} else {
|
||||
return &url.URL{
|
||||
Scheme: "https",
|
||||
Host: net.JoinHostPort(e.TLSName, fmt.Sprintf("%d", e.Port)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Endpoint) NetAddr() string {
|
||||
return net.JoinHostPort(e.Host, fmt.Sprintf("%d", e.Port))
|
||||
}
|
||||
|
||||
func basic_auth_header(login, password string) string {
|
||||
return "basic " + base64.StdEncoding.EncodeToString(
|
||||
[]byte(login+":"+password))
|
||||
@@ -49,11 +75,55 @@ func proxy(ctx context.Context, left, right net.Conn) {
|
||||
return
|
||||
}
|
||||
|
||||
func proxyh2(ctx context.Context, leftreader io.ReadCloser, leftwriter io.Writer, right net.Conn) {
|
||||
wg := sync.WaitGroup{}
|
||||
ltr := func(dst net.Conn, src io.Reader) {
|
||||
defer wg.Done()
|
||||
io.Copy(dst, src)
|
||||
dst.Close()
|
||||
}
|
||||
rtl := func(dst io.Writer, src io.Reader) {
|
||||
defer wg.Done()
|
||||
copyBody(dst, src)
|
||||
}
|
||||
wg.Add(2)
|
||||
go ltr(right, leftreader)
|
||||
go rtl(leftwriter, right)
|
||||
groupdone := make(chan struct{}, 1)
|
||||
go func() {
|
||||
wg.Wait()
|
||||
groupdone <- struct{}{}
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
leftreader.Close()
|
||||
right.Close()
|
||||
case <-groupdone:
|
||||
return
|
||||
}
|
||||
<-groupdone
|
||||
return
|
||||
}
|
||||
|
||||
func print_countries(timeout time.Duration) int {
|
||||
ctx, _ := context.WithTimeout(context.Background(), timeout)
|
||||
countries, err := VPNCountries(ctx)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
var (
|
||||
countries CountryList
|
||||
err error
|
||||
)
|
||||
tx_res, tx_err := EnsureTransaction(context.Background(), timeout, func(ctx context.Context, client *http.Client) bool {
|
||||
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 {
|
||||
@@ -63,10 +133,25 @@ func print_countries(timeout time.Duration) int {
|
||||
}
|
||||
|
||||
func print_proxies(country string, proxy_type string, limit uint, timeout time.Duration) int {
|
||||
ctx, _ := context.WithTimeout(context.Background(), timeout)
|
||||
tunnels, user_uuid, err := Tunnels(ctx, country, proxy_type, limit)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
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)
|
||||
@@ -94,14 +179,15 @@ func print_proxies(country string, proxy_type string, limit uint, timeout time.D
|
||||
return 0
|
||||
}
|
||||
|
||||
func get_endpoint(tunnels *ZGetTunnelsResponse, typ string, trial bool, force_port_field string) (string, error) {
|
||||
var hostname string
|
||||
for k := range tunnels.IPList {
|
||||
func get_endpoint(tunnels *ZGetTunnelsResponse, typ string, trial bool, force_port_field string) (*Endpoint, error) {
|
||||
var hostname, ip string
|
||||
for k, v := range tunnels.IPList {
|
||||
hostname = k
|
||||
ip = v
|
||||
break
|
||||
}
|
||||
if hostname == "" {
|
||||
return "", errors.New("No tunnels found in API response")
|
||||
if hostname == "" || ip == "" {
|
||||
return nil, errors.New("No tunnels found in API response")
|
||||
}
|
||||
|
||||
var port uint16
|
||||
@@ -128,10 +214,14 @@ func get_endpoint(tunnels *ZGetTunnelsResponse, typ string, trial bool, force_po
|
||||
port = tunnels.Port.Peer
|
||||
}
|
||||
} else {
|
||||
return "", errors.New("Unsupported port type")
|
||||
return nil, errors.New("Unsupported port type")
|
||||
}
|
||||
}
|
||||
return net.JoinHostPort(hostname, strconv.FormatUint(uint64(port), 10)), nil
|
||||
return &Endpoint{
|
||||
Host: ip,
|
||||
Port: port,
|
||||
TLSName: hostname,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Hop-by-hop headers. These are removed when sent to the backend.
|
||||
@@ -179,76 +269,26 @@ func hijack(hijackable interface{}) (net.Conn, *bufio.ReadWriter, error) {
|
||||
return conn, rw, nil
|
||||
}
|
||||
|
||||
func rewriteConnectReq(req *http.Request, resolver *Resolver) error {
|
||||
origHost := req.Host
|
||||
origAddr, origPort, err := net.SplitHostPort(origHost)
|
||||
if err == nil {
|
||||
origHost = origAddr
|
||||
func flush(flusher interface{}) bool {
|
||||
f, ok := flusher.(http.Flusher)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
addrs := resolver.Resolve(origHost)
|
||||
if len(addrs) == 0 {
|
||||
return errors.New("Can't resolve host")
|
||||
}
|
||||
if origPort == "" {
|
||||
req.URL.Host = addrs[0]
|
||||
req.Host = addrs[0]
|
||||
req.RequestURI = addrs[0]
|
||||
} else {
|
||||
req.URL.Host = net.JoinHostPort(addrs[0], origPort)
|
||||
req.Host = net.JoinHostPort(addrs[0], origPort)
|
||||
req.RequestURI = net.JoinHostPort(addrs[0], origPort)
|
||||
}
|
||||
return nil
|
||||
f.Flush()
|
||||
return true
|
||||
}
|
||||
|
||||
func rewriteReq(req *http.Request, resolver *Resolver) error {
|
||||
origHost := req.URL.Host
|
||||
origAddr, origPort, err := net.SplitHostPort(origHost)
|
||||
if err == nil {
|
||||
origHost = origAddr
|
||||
}
|
||||
addrs := resolver.Resolve(origHost)
|
||||
if len(addrs) == 0 {
|
||||
return errors.New("Can't resolve host")
|
||||
}
|
||||
if origPort == "" {
|
||||
req.URL.Host = addrs[0]
|
||||
req.Host = addrs[0]
|
||||
} else {
|
||||
req.URL.Host = net.JoinHostPort(addrs[0], origPort)
|
||||
req.Host = net.JoinHostPort(addrs[0], origPort)
|
||||
}
|
||||
req.Header.Set("Host", origHost)
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeConnReq(uri string, resolver *Resolver) (*http.Request, error) {
|
||||
parsed_url, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
origAddr, origPort, err := net.SplitHostPort(parsed_url.Host)
|
||||
if err != nil {
|
||||
origAddr = parsed_url.Host
|
||||
switch strings.ToLower(parsed_url.Scheme) {
|
||||
case "https":
|
||||
origPort = "443"
|
||||
case "http":
|
||||
origPort = "80"
|
||||
default:
|
||||
return nil, errors.New("Unknown scheme")
|
||||
func copyBody(wr io.Writer, body io.Reader) {
|
||||
buf := make([]byte, COPY_BUF)
|
||||
for {
|
||||
bread, read_err := body.Read(buf)
|
||||
var write_err error
|
||||
if bread > 0 {
|
||||
_, write_err = wr.Write(buf[:bread])
|
||||
flush(wr)
|
||||
}
|
||||
if read_err != nil || write_err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
addrs := resolver.Resolve(origAddr)
|
||||
if len(addrs) == 0 {
|
||||
return nil, errors.New("Can't resolve host")
|
||||
}
|
||||
new_uri := net.JoinHostPort(addrs[0], origPort)
|
||||
req, err := http.NewRequest("CONNECT", "http://"+new_uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.RequestURI = new_uri
|
||||
req.Host = new_uri
|
||||
return req, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user