mirror of
https://github.com/Snawoot/windscribe-proxy.git
synced 2026-04-04 17:58:13 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
378547283b | ||
|
|
4fe260ff46 | ||
|
|
07faf6dfea | ||
|
|
f3d2b06f48 | ||
|
|
abdbbbe687 | ||
|
|
d0b50bc52b | ||
|
|
cfaf0bd449 | ||
|
|
b11ab2192e | ||
|
|
4d74c597a9 | ||
|
|
9011828de5 | ||
|
|
ed775ab070 | ||
|
|
abb10a90d6 | ||
|
|
c96b7ca763 | ||
|
|
857d435bf9 | ||
|
|
b016b8a4d3 |
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}}'
|
||||
@@ -7,13 +7,15 @@ COPY . .
|
||||
RUN CGO_ENABLED=0 go build -a -tags netgo -ldflags '-s -w -extldflags "-static" -X main.version='"$GIT_DESC"
|
||||
ADD https://curl.haxx.se/ca/cacert.pem /certs.crt
|
||||
RUN chmod 0644 /certs.crt
|
||||
RUN mkdir /state
|
||||
|
||||
FROM scratch AS arrange
|
||||
COPY --from=build /go/src/github.com/Snawoot/windscribe-proxy/windscribe-proxy /
|
||||
COPY --from=build /certs.crt /etc/ssl/certs/ca-certificates.crt
|
||||
COPY --from=build --chown=9999:9999 /state /state
|
||||
|
||||
FROM scratch
|
||||
COPY --from=arrange / /
|
||||
USER 9999:9999
|
||||
EXPOSE 18080/tcp
|
||||
ENTRYPOINT ["/windscribe-proxy", "-bind-address", "0.0.0.0:28080"]
|
||||
ENTRYPOINT ["/windscribe-proxy", "-state-file", "/state/wndstate.json", "-bind-address", "0.0.0.0:28080"]
|
||||
|
||||
2
Makefile
2
Makefile
@@ -10,7 +10,7 @@ NDK_CC_ARM64 = $(abspath ../../ndk-toolchain-arm64/bin/aarch64-linux-android21-c
|
||||
|
||||
GO := go
|
||||
|
||||
src = $(wildcard *.go)
|
||||
src = $(wildcard *.go */*.go)
|
||||
|
||||
native: bin-native
|
||||
all: bin-linux-amd64 bin-linux-386 bin-linux-arm \
|
||||
|
||||
83
README.md
83
README.md
@@ -1,3 +1,82 @@
|
||||
# windscribe-proxy
|
||||
windscribe-proxy
|
||||
================
|
||||
|
||||
Work in progress...
|
||||
Standalone Windscribe proxy client. Younger brother of [opera-proxy](https://github.com/Snawoot/opera-proxy/).
|
||||
|
||||
Just run it and it'll start a plain HTTP proxy server forwarding traffic through Windscribe proxies of your choice.
|
||||
By default the application listens on 127.0.0.1:28080.
|
||||
|
||||
## Features
|
||||
|
||||
* Cross-platform (Windows/Mac OS/Linux/Android (via shell)/\*BSD)
|
||||
* Uses TLS for secure communication with upstream proxies
|
||||
* Zero configuration
|
||||
* Simple and straightforward
|
||||
|
||||
## Installation
|
||||
|
||||
#### Binaries
|
||||
|
||||
Pre-built binaries are available [here](https://github.com/Snawoot/windscribe-proxy/releases/latest).
|
||||
|
||||
#### Build from source
|
||||
|
||||
Alternatively, you may install windscribe-proxy from source. Run the following within the source directory:
|
||||
|
||||
```
|
||||
make install
|
||||
```
|
||||
|
||||
#### Docker
|
||||
|
||||
A docker image is available as well. Here is an example of running windscribe-proxy as a background service:
|
||||
|
||||
```sh
|
||||
docker run -d \
|
||||
--security-opt no-new-privileges \
|
||||
-p 127.0.0.1:28080:28080 \
|
||||
--restart unless-stopped \
|
||||
--name windscribe-proxy \
|
||||
yarmak/windscribe-proxy
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
List available locations:
|
||||
|
||||
```
|
||||
windscribe-proxy -list-locations
|
||||
```
|
||||
|
||||
Run proxy via location of your choice:
|
||||
|
||||
```
|
||||
windscribe-proxy -location Germany/Frankfurt
|
||||
```
|
||||
|
||||
Also it is possible to export proxy addresses and credentials:
|
||||
|
||||
```
|
||||
windscribe-proxy -list-proxies
|
||||
```
|
||||
|
||||
## List of arguments
|
||||
|
||||
| Argument | Type | Description |
|
||||
| -------- | ---- | ----------- |
|
||||
| auth-secret | String | client auth secret (default `952b4412f002315aa50751032fcaab03`) |
|
||||
| bind-address | String | HTTP proxy listen address (default `127.0.0.1:28080`) |
|
||||
| cafile | String | use custom CA certificate bundle file |
|
||||
| list-locations | - | list available locations and exit |
|
||||
| list-proxies | - | output proxy list and exit |
|
||||
| location | String | desired proxy location. Default: best location |
|
||||
| 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` |
|
||||
| resolver | String | Use DNS/DoH/DoT/DoQ resolver for all dial-outs. See https://github.com/ameshkov/dnslookup/ for upstream DNS URL format. Examples: `https://1.1.1.1/dns-query`, `quic://dns.adguard.com` |
|
||||
| state-file | String | file name used to persist Windscribe API client state. Default: `wndstate.json` |
|
||||
| timeout | Duration | timeout for network operations. Default: `10s` |
|
||||
| verbosity | Number | logging verbosity (10 - debug, 20 - info, 30 - warning, 40 - error, 50 - critical). Default: `20` |
|
||||
| version | - | show program version and exit |
|
||||
|
||||
## See also
|
||||
|
||||
* [Project wiki](https://github.com/Snawoot/windscribe-proxy/wiki)
|
||||
|
||||
1
go.mod
1
go.mod
@@ -4,6 +4,7 @@ go 1.16
|
||||
|
||||
require (
|
||||
github.com/AdguardTeam/dnsproxy v0.37.7
|
||||
github.com/ReneKroon/ttlcache/v2 v2.7.0
|
||||
github.com/miekg/dns v1.1.43
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4
|
||||
)
|
||||
|
||||
21
go.sum
21
go.sum
@@ -13,6 +13,8 @@ github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKU
|
||||
github.com/AdguardTeam/golibs v0.4.4 h1:cM9UySQiYFW79zo5XRwnaIWVzfW4eNXmZktMrWbthpw=
|
||||
github.com/AdguardTeam/golibs v0.4.4/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/ReneKroon/ttlcache/v2 v2.7.0 h1:sZeaSwA2UN/y/h7CvkW15Kovd2Oiy76CBDORiOwHPwI=
|
||||
github.com/ReneKroon/ttlcache/v2 v2.7.0/go.mod h1:mBxvsNY+BT8qLLd6CuAJubbKo6r0jh3nb5et22bbfGY=
|
||||
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=
|
||||
@@ -155,14 +157,19 @@ github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:Udh
|
||||
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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/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=
|
||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||
go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
|
||||
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
||||
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=
|
||||
@@ -178,7 +185,11 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL
|
||||
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/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
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/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=
|
||||
@@ -195,6 +206,7 @@ golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
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 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
@@ -208,6 +220,7 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
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-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=
|
||||
@@ -225,6 +238,7 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -245,9 +259,14 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
||||
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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
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-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20210112230658-8b4aab62c064/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.1 h1:wGiQel/hW0NnEkJUk8lbzkX2gFJU6PFxf1v5OlCfuOs=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
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=
|
||||
|
||||
272
main.go
272
main.go
@@ -2,7 +2,6 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
@@ -11,11 +10,13 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
//"strings"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
xproxy "golang.org/x/net/proxy"
|
||||
@@ -24,8 +25,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
DEFAULT_CLIENT_AUTH_SECRET = "952b4412f002315aa50751032fcaab03"
|
||||
ASSUMED_PROXY_PORT = 443
|
||||
DEFAULT_CLIENT_AUTH_SECRET = "952b4412f002315aa50751032fcaab03"
|
||||
ASSUMED_PROXY_PORT uint16 = 443
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -45,17 +46,15 @@ func arg_fail(msg string) {
|
||||
}
|
||||
|
||||
type CLIArgs struct {
|
||||
country string
|
||||
listCountries bool
|
||||
location string
|
||||
listLocations bool
|
||||
listProxies bool
|
||||
bindAddress string
|
||||
verbosity int
|
||||
timeout time.Duration
|
||||
showVersion bool
|
||||
proxy string
|
||||
bootstrapDNS string
|
||||
refresh time.Duration
|
||||
refreshRetry time.Duration
|
||||
resolver string
|
||||
caFile string
|
||||
clientAuthSecret string
|
||||
stateFile string
|
||||
@@ -63,8 +62,8 @@ type CLIArgs struct {
|
||||
|
||||
func parse_args() CLIArgs {
|
||||
var args CLIArgs
|
||||
flag.StringVar(&args.country, "country", "EU", "desired proxy location")
|
||||
flag.BoolVar(&args.listCountries, "list-countries", false, "list available countries and exit")
|
||||
flag.StringVar(&args.location, "location", "", "desired proxy location. Default: best location")
|
||||
flag.BoolVar(&args.listLocations, "list-locations", false, "list available locations and exit")
|
||||
flag.BoolVar(&args.listProxies, "list-proxies", false, "output proxy list and exit")
|
||||
flag.StringVar(&args.bindAddress, "bind-address", "127.0.0.1:28080", "HTTP proxy listen address")
|
||||
flag.IntVar(&args.verbosity, "verbosity", 20, "logging verbosity "+
|
||||
@@ -75,22 +74,17 @@ func parse_args() CLIArgs {
|
||||
"Format: <http|https|socks5|socks5h>://[login:password@]host[:port] "+
|
||||
"Examples: http://user:password@192.168.1.1:3128, socks5://10.0.0.1:1080")
|
||||
// TODO: implement DNS resolving or remove it
|
||||
flag.StringVar(&args.bootstrapDNS, "bootstrap-dns", "",
|
||||
"DNS/DoH/DoT/DoQ resolver for initial discovering of SurfEasy API address. "+
|
||||
flag.StringVar(&args.resolver, "resolver", "",
|
||||
"Use DNS/DoH/DoT/DoQ resolver for all dial-outs. "+
|
||||
"See https://github.com/ameshkov/dnslookup/ for upstream DNS URL format. "+
|
||||
"Examples: https://1.1.1.1/dns-query, quic://dns.adguard.com")
|
||||
flag.DurationVar(&args.refresh, "refresh", 4*time.Hour, "login refresh interval")
|
||||
flag.DurationVar(&args.refreshRetry, "refresh-retry", 5*time.Second, "login refresh retry interval")
|
||||
flag.StringVar(&args.caFile, "cafile", "", "use custom CA certificate bundle file")
|
||||
flag.StringVar(&args.clientAuthSecret, "auth-secret", DEFAULT_CLIENT_AUTH_SECRET, "client auth secret")
|
||||
flag.StringVar(&args.stateFile, "state-file", "wndstate.json", "file name used to persist "+
|
||||
"Windscribe API client state")
|
||||
flag.Parse()
|
||||
if args.country == "" {
|
||||
arg_fail("Country can't be empty string.")
|
||||
}
|
||||
if args.listCountries && args.listProxies {
|
||||
arg_fail("list-countries and list-proxies flags are mutually exclusive")
|
||||
if args.listLocations && args.listProxies {
|
||||
arg_fail("list-locations and list-proxies flags are mutually exclusive")
|
||||
}
|
||||
return args
|
||||
}
|
||||
@@ -105,6 +99,7 @@ func proxyFromURLWrapper(u *url.URL, next xproxy.Dialer) (xproxy.Dialer, error)
|
||||
}
|
||||
|
||||
func run() int {
|
||||
var err error
|
||||
args := parse_args()
|
||||
if args.showVersion {
|
||||
fmt.Println(version)
|
||||
@@ -120,6 +115,9 @@ func run() int {
|
||||
proxyLogger := NewCondLogger(log.New(logWriter, "PROXY : ",
|
||||
log.LstdFlags|log.Lshortfile),
|
||||
args.verbosity)
|
||||
resolverLogger := NewCondLogger(log.New(logWriter, "RESOLVER: ",
|
||||
log.LstdFlags|log.Lshortfile),
|
||||
args.verbosity)
|
||||
|
||||
mainLogger.Info("windscribe-proxy client version %s is starting...", version)
|
||||
|
||||
@@ -128,6 +126,20 @@ func run() int {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
if args.proxy != "" {
|
||||
xproxy.RegisterDialerType("http", proxyFromURLWrapper)
|
||||
xproxy.RegisterDialerType("https", proxyFromURLWrapper)
|
||||
@@ -144,22 +156,19 @@ func run() int {
|
||||
dialer = pxDialer.(ContextDialer)
|
||||
}
|
||||
|
||||
if args.resolver != "" {
|
||||
dialer, err = NewResolvingDialer(args.resolver, args.timeout, dialer, resolverLogger)
|
||||
if err != nil {
|
||||
mainLogger.Critical("Unable to instantiate resolver: %v", err)
|
||||
return 5
|
||||
}
|
||||
}
|
||||
|
||||
wndclientDialer := dialer
|
||||
|
||||
// TODO: properly validate cert, move TLSDialer to utils
|
||||
tlsConfig := &tls.Config{
|
||||
ServerName: "",
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
wndclient, err := wndclient.NewWndClient(&http.Transport{
|
||||
DialContext: wndclientDialer.DialContext,
|
||||
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
conn, err := wndclientDialer.DialContext(ctx, network, addr)
|
||||
if err != nil {
|
||||
return conn, err
|
||||
}
|
||||
return tls.Client(conn, tlsConfig), nil
|
||||
},
|
||||
wndc, err := wndclient.NewWndClient(&http.Transport{
|
||||
DialContext: wndclientDialer.DialContext,
|
||||
DialTLSContext: NewNoSNIDialer(caPool, wndclientDialer).DialTLSContext,
|
||||
ForceAttemptHTTP2: true,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
@@ -175,90 +184,110 @@ func run() int {
|
||||
state, err := loadState(args.stateFile)
|
||||
if err != nil {
|
||||
mainLogger.Warning("Failed to load client state: %v. Performing cold init...", err)
|
||||
err = coldInit(wndclient, args.timeout)
|
||||
err = coldInit(wndc, args.timeout)
|
||||
if err != nil {
|
||||
mainLogger.Critical("Cold init failed: %v", err)
|
||||
return 9
|
||||
}
|
||||
err = saveState(args.stateFile, &wndclient.State)
|
||||
err = saveState(args.stateFile, &wndc.State)
|
||||
if err != nil {
|
||||
mainLogger.Error("Unable to save state file! Error: %v", err)
|
||||
}
|
||||
} else {
|
||||
wndclient.State = *state
|
||||
wndc.Mux.Lock()
|
||||
wndc.State = *state
|
||||
wndc.Mux.Unlock()
|
||||
}
|
||||
|
||||
ctx, cl := context.WithTimeout(context.Background(), args.timeout)
|
||||
serverList, err := wndclient.ServerList(ctx)
|
||||
cl()
|
||||
if err != nil {
|
||||
mainLogger.Critical("Server list retrieve failed: %v", err)
|
||||
return 12
|
||||
var serverList wndclient.ServerList
|
||||
if args.listProxies || args.listLocations || args.location != "" {
|
||||
ctx, cl := context.WithTimeout(context.Background(), args.timeout)
|
||||
serverList, err = wndc.ServerList(ctx)
|
||||
cl()
|
||||
if err != nil {
|
||||
mainLogger.Critical("Server list retrieve failed: %v", err)
|
||||
return 12
|
||||
}
|
||||
}
|
||||
|
||||
if args.listProxies {
|
||||
username, password := wndclient.GetProxyCredentials()
|
||||
username, password := wndc.GetProxyCredentials()
|
||||
return printProxies(username, password, serverList)
|
||||
}
|
||||
|
||||
//if len(ips) == 0 {
|
||||
// mainLogger.Critical("Empty endpoint list!")
|
||||
// return 13
|
||||
//}
|
||||
|
||||
//runTicker(context.Background(), args.refresh, args.refreshRetry, func(ctx context.Context) error {
|
||||
// mainLogger.Info("Refreshing login...")
|
||||
// reqCtx, cl := context.WithTimeout(ctx, args.timeout)
|
||||
// defer cl()
|
||||
// err := wndclient.Login(reqCtx)
|
||||
// if err != nil {
|
||||
// mainLogger.Error("Login refresh failed: %v", err)
|
||||
// return err
|
||||
// }
|
||||
// mainLogger.Info("Login refreshed.")
|
||||
|
||||
// mainLogger.Info("Refreshing device password...")
|
||||
// reqCtx, cl = context.WithTimeout(ctx, args.timeout)
|
||||
// defer cl()
|
||||
// err = wndclient.DeviceGeneratePassword(reqCtx)
|
||||
// if err != nil {
|
||||
// mainLogger.Error("Device password refresh failed: %v", err)
|
||||
// return err
|
||||
// }
|
||||
// mainLogger.Info("Device password refreshed.")
|
||||
// return nil
|
||||
//})
|
||||
|
||||
//endpoint := ips[0]
|
||||
auth := func() string {
|
||||
return basic_auth_header(wndclient.GetProxyCredentials())
|
||||
if args.listLocations {
|
||||
return printLocations(serverList)
|
||||
}
|
||||
|
||||
var caPool *x509.CertPool
|
||||
if args.caFile != "" {
|
||||
caPool = x509.NewCertPool()
|
||||
certs, err := ioutil.ReadFile(args.caFile)
|
||||
var proxyHostname string
|
||||
if args.location == "" {
|
||||
ctx, cl := context.WithTimeout(context.Background(), args.timeout)
|
||||
bestLocation, err := wndc.BestLocation(ctx)
|
||||
cl()
|
||||
if err != nil {
|
||||
mainLogger.Error("Can't load CA file: %v", err)
|
||||
return 15
|
||||
mainLogger.Critical("Unable to get best location endpoint: %v", err)
|
||||
return 13
|
||||
}
|
||||
if ok := caPool.AppendCertsFromPEM(certs); !ok {
|
||||
mainLogger.Error("Can't load certificates from CA file")
|
||||
return 15
|
||||
proxyHostname = bestLocation.Hostname
|
||||
} else {
|
||||
proxyHostname = pickServer(serverList, args.location)
|
||||
if proxyHostname == "" {
|
||||
mainLogger.Critical("Server for location \"%s\" not found.", args.location)
|
||||
return 13
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: set servername
|
||||
//handlerDialer := NewProxyDialer(endpoint.NetAddr(), "", auth, caPool, dialer)
|
||||
//mainLogger.Info("Endpoint: %s", endpoint.NetAddr())
|
||||
//mainLogger.Info("Starting proxy server...")
|
||||
//handler := NewProxyHandler(handlerDialer, proxyLogger)
|
||||
//mainLogger.Info("Init complete.")
|
||||
//err = http.ListenAndServe(args.bindAddress, handler)
|
||||
//mainLogger.Critical("Server terminated with a reason: %v", err)
|
||||
//mainLogger.Info("Shutting down...")
|
||||
_ = proxyLogger
|
||||
_ = auth
|
||||
auth := func() string {
|
||||
return basic_auth_header(wndc.GetProxyCredentials())
|
||||
}
|
||||
|
||||
proxyNetAddr := net.JoinHostPort(proxyHostname, strconv.FormatUint(uint64(ASSUMED_PROXY_PORT), 10))
|
||||
handlerDialer := NewProxyDialer(proxyNetAddr, proxyHostname, auth, caPool, dialer)
|
||||
mainLogger.Info("Endpoint: %s", proxyNetAddr)
|
||||
mainLogger.Info("Starting proxy server...")
|
||||
handler := NewProxyHandler(handlerDialer, proxyLogger)
|
||||
mainLogger.Info("Init complete.")
|
||||
err = http.ListenAndServe(args.bindAddress, handler)
|
||||
mainLogger.Critical("Server terminated with a reason: %v", err)
|
||||
mainLogger.Info("Shutting down...")
|
||||
return 0
|
||||
}
|
||||
|
||||
type locationPair struct {
|
||||
country string
|
||||
city string
|
||||
}
|
||||
|
||||
func printLocations(serverList wndclient.ServerList) int {
|
||||
var locs []locationPair
|
||||
for _, country := range serverList {
|
||||
for _, group := range country.Groups {
|
||||
if len(group.Hosts) > 1 {
|
||||
locs = append(locs, locationPair{country.Name, group.City})
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(locs) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
sort.Slice(locs, func(i, j int) bool {
|
||||
if locs[i].country < locs[j].country {
|
||||
return true
|
||||
}
|
||||
if locs[i].country == locs[j].country && locs[i].city < locs[j].city {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
var prevLoc locationPair
|
||||
for _, loc := range locs {
|
||||
if loc != prevLoc {
|
||||
fmt.Println(loc.country + "/" + loc.city)
|
||||
prevLoc = loc
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -269,24 +298,48 @@ func printProxies(username, password string, serverList wndclient.ServerList) in
|
||||
fmt.Println("Proxy password:", password)
|
||||
fmt.Println("Proxy-Authorization:", basic_auth_header(username, password))
|
||||
fmt.Println("")
|
||||
//wr.Write([]string{"host", "ip_address", "port"})
|
||||
//for i, ip := range ips {
|
||||
// for _, port := range ip.Ports {
|
||||
// wr.Write([]string{
|
||||
// fmt.Sprintf("%s%d.%s", strings.ToLower(ip.Geo.CountryCode), i, PROXY_SUFFIX),
|
||||
// ip.IP,
|
||||
// fmt.Sprintf("%d", port),
|
||||
// })
|
||||
// }
|
||||
//}
|
||||
wr.Write([]string{"location", "hostname", "port"})
|
||||
for _, country := range serverList {
|
||||
for _, group := range country.Groups {
|
||||
for _, host := range group.Hosts {
|
||||
wr.Write([]string{
|
||||
country.Name + "/" + group.City,
|
||||
host.Hostname,
|
||||
strconv.FormatUint(uint64(ASSUMED_PROXY_PORT), 10),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func pickServer(serverList wndclient.ServerList, location string) string {
|
||||
var candidates []string
|
||||
for _, country := range serverList {
|
||||
for _, group := range country.Groups {
|
||||
for _, host := range group.Hosts {
|
||||
if country.Name+"/"+group.City == location {
|
||||
candidates = append(candidates, host.Hostname)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(candidates) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
return candidates[rnd.Intn(len(candidates))]
|
||||
}
|
||||
|
||||
func loadState(filename string) (*wndclient.WndClientState, error) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var state wndclient.WndClientState
|
||||
dec := json.NewDecoder(file)
|
||||
@@ -303,6 +356,7 @@ func saveState(filename string, state *wndclient.WndClientState) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
enc := json.NewEncoder(file)
|
||||
enc.SetIndent("", " ")
|
||||
@@ -310,23 +364,23 @@ func saveState(filename string, state *wndclient.WndClientState) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func coldInit(wndclient *wndclient.WndClient, timeout time.Duration) error {
|
||||
func coldInit(wndc *wndclient.WndClient, timeout time.Duration) error {
|
||||
ctx, cl := context.WithTimeout(context.Background(), timeout)
|
||||
err := wndclient.RegisterToken(ctx)
|
||||
err := wndc.RegisterToken(ctx)
|
||||
cl()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cl = context.WithTimeout(context.Background(), timeout)
|
||||
err = wndclient.Users(ctx)
|
||||
err = wndc.Users(ctx)
|
||||
cl()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cl = context.WithTimeout(context.Background(), timeout)
|
||||
err = wndclient.ServerCredentials(ctx)
|
||||
err = wndc.ServerCredentials(ctx)
|
||||
cl()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
150
resolver.go
150
resolver.go
@@ -1,16 +1,25 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/miekg/dns"
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/ReneKroon/ttlcache/v2"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type Resolver struct {
|
||||
upstream upstream.Upstream
|
||||
}
|
||||
|
||||
const DOT = 0x2e
|
||||
const (
|
||||
DOT = 0x2e
|
||||
DNS_CACHE_SIZE_LIMIT = 1024
|
||||
)
|
||||
|
||||
func NewResolver(address string, timeout time.Duration) (*Resolver, error) {
|
||||
opts := upstream.Options{Timeout: timeout}
|
||||
@@ -26,9 +35,8 @@ func (r *Resolver) ResolveA(domain string) []string {
|
||||
if len(domain) == 0 {
|
||||
return res
|
||||
}
|
||||
if domain[len(domain)-1] != DOT {
|
||||
domain = domain + "."
|
||||
}
|
||||
domain = absDomain(domain)
|
||||
|
||||
req := dns.Msg{}
|
||||
req.Id = dns.Id()
|
||||
req.RecursionDesired = true
|
||||
@@ -52,9 +60,8 @@ func (r *Resolver) ResolveAAAA(domain string) []string {
|
||||
if len(domain) == 0 {
|
||||
return res
|
||||
}
|
||||
if domain[len(domain)-1] != DOT {
|
||||
domain = domain + "."
|
||||
}
|
||||
domain = absDomain(domain)
|
||||
|
||||
req := dns.Msg{}
|
||||
req.Id = dns.Id()
|
||||
req.RecursionDesired = true
|
||||
@@ -80,3 +87,128 @@ func (r *Resolver) Resolve(domain string) []string {
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
type ResolvingDialer struct {
|
||||
next ContextDialer
|
||||
upstream upstream.Upstream
|
||||
cache4 *ttlcache.Cache
|
||||
cache6 *ttlcache.Cache
|
||||
logger *CondLogger
|
||||
}
|
||||
|
||||
func NewResolvingDialer(resolverAddress string, timeout time.Duration, next ContextDialer, logger *CondLogger) (*ResolvingDialer, error) {
|
||||
opts := upstream.Options{Timeout: timeout}
|
||||
u, err := upstream.AddressToUpstream(resolverAddress, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cache4 := ttlcache.NewCache()
|
||||
cache6 := ttlcache.NewCache()
|
||||
d := &ResolvingDialer{
|
||||
upstream: u,
|
||||
next: next,
|
||||
cache4: cache4,
|
||||
cache6: cache6,
|
||||
logger: logger,
|
||||
}
|
||||
cache4.SetLoaderFunction(d.resolveA)
|
||||
cache6.SetLoaderFunction(d.resolveAAAA)
|
||||
cache4.SetCacheSizeLimit(DNS_CACHE_SIZE_LIMIT)
|
||||
cache6.SetCacheSizeLimit(DNS_CACHE_SIZE_LIMIT)
|
||||
cache4.SkipTTLExtensionOnHit(true)
|
||||
cache6.SkipTTLExtensionOnHit(true)
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (d *ResolvingDialer) resolveA(domain string) (interface{}, time.Duration, error) {
|
||||
d.logger.Debug("resolveA(%#v)", domain)
|
||||
return d.resolve(domain, dns.TypeA)
|
||||
}
|
||||
|
||||
func (d *ResolvingDialer) resolveAAAA(domain string) (interface{}, time.Duration, error) {
|
||||
d.logger.Debug("resolveAAAA(%#v)", domain)
|
||||
return d.resolve(domain, dns.TypeAAAA)
|
||||
}
|
||||
|
||||
func (d *ResolvingDialer) resolve(domain string, typ uint16) (string, time.Duration, error) {
|
||||
if len(domain) == 0 {
|
||||
return "", 0, errors.New("empty domain name")
|
||||
}
|
||||
domain = absDomain(domain)
|
||||
|
||||
req := dns.Msg{}
|
||||
req.Id = dns.Id()
|
||||
req.RecursionDesired = true
|
||||
req.Question = []dns.Question{
|
||||
{Name: domain, Qtype: typ, Qclass: dns.ClassINET},
|
||||
}
|
||||
reply, err := d.upstream.Exchange(&req)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
for _, rr := range reply.Answer {
|
||||
if a, ok := rr.(*dns.A); ok {
|
||||
return a.A.String(), (time.Second * time.Duration(a.Hdr.Ttl)), nil
|
||||
}
|
||||
}
|
||||
return "", 0, errors.New("no data in DNS response")
|
||||
}
|
||||
|
||||
func (d *ResolvingDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
name, port, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if net.ParseIP(name) != nil || len(name) == 0 {
|
||||
// Address is already in numeric form
|
||||
return d.next.DialContext(ctx, network, address)
|
||||
}
|
||||
|
||||
if len(network) == 0 {
|
||||
return d.next.DialContext(ctx, network, address)
|
||||
}
|
||||
|
||||
name = absDomain(name)
|
||||
switch network[len(network)-1] {
|
||||
case '4':
|
||||
res, err := d.cache4.Get(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name = res.(string)
|
||||
case '6':
|
||||
res, err := d.cache6.Get(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name = res.(string)
|
||||
default:
|
||||
res, err := d.cache4.Get(name)
|
||||
if err != nil {
|
||||
res, err = d.cache6.Get(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
name = res.(string)
|
||||
}
|
||||
newAddress := net.JoinHostPort(name, port)
|
||||
d.logger.Debug("resolve rewrite: %s => %s", address, newAddress)
|
||||
return d.next.DialContext(ctx, network, newAddress)
|
||||
}
|
||||
|
||||
func (d *ResolvingDialer) Dial(network, address string) (net.Conn, error) {
|
||||
return d.DialContext(context.Background(), network, address)
|
||||
}
|
||||
|
||||
func absDomain(domain string) string {
|
||||
if domain == "" {
|
||||
return ""
|
||||
}
|
||||
if domain[len(domain)-1] != DOT {
|
||||
domain = domain + "."
|
||||
}
|
||||
return strings.ToLower(domain)
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
name: windscribe-proxy
|
||||
version: '1.0.0'
|
||||
summary: Standalone Windscribe proxies client.
|
||||
description: |
|
||||
Standalone Windscribe proxies client. Just run it and it'll start plain HTTP proxy server forwarding traffic via proxies of your choice.
|
||||
|
||||
confinement: strict
|
||||
base: core18
|
||||
|
||||
parts:
|
||||
windscribe-proxy:
|
||||
plugin: go
|
||||
source: .
|
||||
build-packages:
|
||||
- gcc
|
||||
override-build:
|
||||
make &&
|
||||
cp bin/windscribe-proxy "$SNAPCRAFT_PART_INSTALL"
|
||||
stage:
|
||||
- windscribe-proxy
|
||||
|
||||
apps:
|
||||
windscribe-proxy:
|
||||
command: windscribe-proxy
|
||||
plugs:
|
||||
- network
|
||||
- network-bind
|
||||
53
upstream.go
53
upstream.go
@@ -32,12 +32,11 @@ type ContextDialer interface {
|
||||
}
|
||||
|
||||
type ProxyDialer struct {
|
||||
address string
|
||||
tlsServerName string
|
||||
auth AuthProvider
|
||||
next ContextDialer
|
||||
intermediateWorkaround bool
|
||||
caPool *x509.CertPool
|
||||
address string
|
||||
tlsServerName string
|
||||
auth AuthProvider
|
||||
next ContextDialer
|
||||
caPool *x509.CertPool
|
||||
}
|
||||
|
||||
func NewProxyDialer(address, tlsServerName string, auth AuthProvider, caPool *x509.CertPool, nextDialer ContextDialer) *ProxyDialer {
|
||||
@@ -185,3 +184,45 @@ func readResponse(r io.Reader, req *http.Request) (*http.Response, error) {
|
||||
}
|
||||
return http.ReadResponse(bufio.NewReader(buf), req)
|
||||
}
|
||||
|
||||
type NoSNIDialer struct {
|
||||
caPool *x509.CertPool
|
||||
next ContextDialer
|
||||
}
|
||||
|
||||
func NewNoSNIDialer(caPool *x509.CertPool, nextDialer ContextDialer) *NoSNIDialer {
|
||||
return &NoSNIDialer{
|
||||
caPool: caPool,
|
||||
next: nextDialer,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *NoSNIDialer) DialTLSContext(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
conn, err := d.next.DialContext(ctx, network, addr)
|
||||
|
||||
name, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
ServerName: "",
|
||||
InsecureSkipVerify: true,
|
||||
VerifyConnection: func(cs tls.ConnectionState) error {
|
||||
opts := x509.VerifyOptions{
|
||||
DNSName: name,
|
||||
Intermediates: x509.NewCertPool(),
|
||||
Roots: d.caPool,
|
||||
}
|
||||
for _, cert := range cs.PeerCertificates[1:] {
|
||||
opts.Intermediates.AddCert(cert)
|
||||
}
|
||||
_, err := cs.PeerCertificates[0].Verify(opts)
|
||||
return err
|
||||
},
|
||||
}
|
||||
if err != nil {
|
||||
return conn, err
|
||||
}
|
||||
return tls.Client(conn, tlsConfig), nil
|
||||
}
|
||||
|
||||
@@ -82,3 +82,17 @@ type ServerListGroupHost struct {
|
||||
Hostname string `json:"hostname"`
|
||||
Weight float64 `json:"weight"`
|
||||
}
|
||||
|
||||
type BestLocation struct {
|
||||
CountryCode string `json:"country_code"`
|
||||
ShortName string `json:"short_name"`
|
||||
LocationName string `json:"location_name"`
|
||||
CityName string `json:"city_name"`
|
||||
DCID int `json:"dc_id"`
|
||||
ServerID int `json:"server_id"`
|
||||
Hostname string `json:"hostname"`
|
||||
}
|
||||
|
||||
type BestLocationResponse struct {
|
||||
Data *BestLocation `json:"data"`
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ type WndEndpoints struct {
|
||||
Users string `json:"Users"`
|
||||
ServerList string `json:"serverlist"`
|
||||
ServerCredentials string `json:"ServerCredentials"`
|
||||
BestLocation string `json:"BestLocation"`
|
||||
}
|
||||
|
||||
var DefaultWndEndpoints = WndEndpoints{
|
||||
@@ -40,6 +41,7 @@ var DefaultWndEndpoints = WndEndpoints{
|
||||
Users: "https://api.windscribe.com/Users",
|
||||
ServerList: "https://assets.windscribe.com/serverlist",
|
||||
ServerCredentials: "https://api.windscribe.com/ServerCredentials",
|
||||
BestLocation: "https://api.windscribe.com/BestLocation",
|
||||
}
|
||||
|
||||
type WndSettings struct {
|
||||
@@ -193,6 +195,35 @@ func (c *WndClient) ServerCredentials(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *WndClient) BestLocation(ctx context.Context) (*BestLocation, error) {
|
||||
c.Mux.Lock()
|
||||
defer c.Mux.Unlock()
|
||||
|
||||
clientAuthHash, authTime := MakeAuthHash(c.State.Settings.ClientAuthSecret)
|
||||
|
||||
requestUrl, err := url.Parse(c.State.Settings.Endpoints.BestLocation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queryValues := requestUrl.Query()
|
||||
queryValues.Set("client_auth_hash", clientAuthHash)
|
||||
queryValues.Set("session_auth_hash", c.State.SessionAuthHash)
|
||||
queryValues.Set("time", strconv.FormatInt(authTime, 10))
|
||||
requestUrl.RawQuery = queryValues.Encode()
|
||||
|
||||
var output BestLocationResponse
|
||||
|
||||
err = c.getJSON(ctx, requestUrl.String(), &output)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if output.Data == nil {
|
||||
return nil, ErrNoDataInResponse
|
||||
}
|
||||
|
||||
return output.Data, nil
|
||||
}
|
||||
|
||||
func (c *WndClient) ServerList(ctx context.Context) (ServerList, error) {
|
||||
c.Mux.Lock()
|
||||
defer c.Mux.Unlock()
|
||||
|
||||
Reference in New Issue
Block a user