mirror of
https://github.com/wweir/sower.git
synced 2026-10-19 16:14:33 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7214bfdb4 | ||
|
|
291ae3f85a | ||
|
|
07c51d42ff | ||
|
|
90c497b8da | ||
|
|
28bd12d220 | ||
|
|
c0f39fc2e0 | ||
|
|
bcc6d7c504 | ||
|
|
5d1827b2da | ||
|
|
c38d519f92 | ||
|
|
dd4444b71c | ||
|
|
899d64a92c | ||
|
|
7d77f4a85e | ||
|
|
4681dd9845 | ||
|
|
85226890c5 | ||
|
|
1a24286bbc | ||
|
|
fd5c34dca1 | ||
|
|
8cd4fe57ba | ||
|
|
545b153f50 | ||
|
|
697e2aa1d5 | ||
|
|
2fe1708d3f | ||
|
|
cbe943a020 |
@@ -1,18 +0,0 @@
|
||||
# Compile
|
||||
FROM golang:1.12-alpine AS compiler
|
||||
|
||||
RUN apk add --no-cache git make
|
||||
|
||||
# enable go modules
|
||||
WORKDIR /go-mod
|
||||
COPY . .
|
||||
|
||||
# do not worry about downloading dependency, sower will fix this.
|
||||
RUN CGO_ENABLED=0 make build
|
||||
|
||||
|
||||
# Build image
|
||||
FROM scratch
|
||||
|
||||
COPY --from=compiler /go-mod/sower /sower
|
||||
ENTRYPOINT [ "/sower" ]
|
||||
@@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>cc.wweir.sower</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/usr/local/bin/sower</string>
|
||||
<string>-f</string>
|
||||
<string>/usr/local/etc/sower.toml</string>
|
||||
</array>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,57 +0,0 @@
|
||||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/golang:1.13
|
||||
steps:
|
||||
- checkout # check out the code in the project directory
|
||||
- run:
|
||||
name: Prepare Environment
|
||||
command: |
|
||||
cp conf/sower.toml .
|
||||
cp .circleci/* .
|
||||
mkdir artifacts
|
||||
- run:
|
||||
name: Run Unit Test
|
||||
command: |
|
||||
make test
|
||||
- run:
|
||||
name: Build linux Binary
|
||||
command: |
|
||||
GOOS=linux GOARCH=amd64 make build
|
||||
tar czvf artifacts/sower-linux-amd64.tar.gz sower sower.toml sower-client.service sower-server.service
|
||||
GOOS=linux GOARCH=386 make build
|
||||
tar czvf artifacts/sower-linux-386.tar.gz sower sower.toml sower-client.service sower-server.service
|
||||
GOOS=linux GOARCH=arm make build
|
||||
tar czvf artifacts/sower-linux-arm.tar.gz sower sower.toml sower-client.service sower-server.service
|
||||
GOOS=linux GOARCH=arm64 make build
|
||||
tar czvf artifacts/sower-linux-arm64.tar.gz sower sower.toml sower-client.service sower-server.service
|
||||
GOOS=linux GOARCH=mips make build
|
||||
tar czvf artifacts/sower-linux-mips.tar.gz sower sower.toml sower-client.service sower-server.service
|
||||
GOOS=linux GOARCH=mipsle make build
|
||||
tar czvf artifacts/sower-linux-mipsle.tar.gz sower sower.toml sower-client.service sower-server.service
|
||||
|
||||
- run:
|
||||
name: Build macOS Binary
|
||||
command: |
|
||||
GOOS=darwin GOARCH=amd64 make build
|
||||
tar czvf artifacts/sower-darwin-amd64.tar.gz sower sower.toml cc.wweir.sower.plist
|
||||
|
||||
- run:
|
||||
name: Build Windows Binary
|
||||
command: |
|
||||
GOOS=windows GOARCH=amd64 make build
|
||||
tar czvf artifacts/sower-windows-amd64.tar.gz sower.exe sower.toml
|
||||
GOOS=windows GOARCH=386 make build
|
||||
tar czvf artifacts/sower-windows-386.tar.gz sower.exe sower.toml
|
||||
|
||||
- run:
|
||||
name: Publish Release on GitHub
|
||||
command: |
|
||||
git describe --tags | grep - && exit
|
||||
|
||||
GHR_VERSION="$(curl -s "https://api.github.com/repos/tcnksm/ghr/releases/latest" | awk -F '"' '/tag_name/{printf $4}')"
|
||||
curl -SLf https://github.com/tcnksm/ghr/releases/download/"$GHR_VERSION"/ghr_"$GHR_VERSION"_linux_amd64.tar.gz | tar xzC /go/bin --strip-components=1
|
||||
|
||||
VERSION=$(git describe --tags)
|
||||
ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete ${VERSION%-*-*} artifacts/
|
||||
@@ -1,127 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
[[ -d /tmp/sower ]] || mkdir /tmp/sower
|
||||
cd /tmp/sower
|
||||
|
||||
echo ======== Install sower to your system ========
|
||||
succ_message(){
|
||||
if [[ -z $IP ]]; then
|
||||
IP=127.0.0.1
|
||||
fi
|
||||
echo
|
||||
echo ======== Installed sower to your system ========
|
||||
echo The config file is: /usr/local/etc/sower.toml
|
||||
echo please set $IP as the first domain name server manually.
|
||||
}
|
||||
|
||||
# main logic
|
||||
VERSION="$(curl -s "https://api.github.com/repos/wweir/sower/releases/latest" | awk -F '"' '/tag_name/{printf $4}')"
|
||||
case "$(uname -s)" in
|
||||
"Darwin")
|
||||
curl -SLf https://github.com/wweir/sower/releases/download/$VERSION/sower-darwin-amd64.tar.gz | tar xzv
|
||||
sudo mkdir -p /usr/local/bin/
|
||||
sudo mv sower /usr/local/bin/
|
||||
|
||||
if [[ -r /usr/local/etc/sower.toml ]]; then
|
||||
echo The config file already exists, keep the original file
|
||||
else
|
||||
printf "Please enter remote server adddress: "
|
||||
read ADDRESS
|
||||
printf "Please enter remote server password (default: 12345678): "
|
||||
read PASSWORD
|
||||
if [[ -z $PASSWORD ]]; then
|
||||
PASSWORD="12345678"
|
||||
fi
|
||||
printf "Please enter which IP do you wanna listen (default: 127.0.0.1):"
|
||||
read IP
|
||||
if [[ -z $IP ]]; then
|
||||
IP="127.0.0.1"
|
||||
fi
|
||||
echo
|
||||
|
||||
sed -i~ "s/# server_addr=\"remote-server/server_addr=\"$ADDRESS/" sower.toml
|
||||
sed -i~ "s/client_ip=\"127.0.0.1\"/client_ip=\"$IP\"/" sower.toml
|
||||
sed -i~ "s/# clear_dns_cache/clear_dns_cache/" sower.toml
|
||||
sed -i~ "s/\"12345678\"/\"$PASSWORD\"/" sower.toml
|
||||
sudo mkdir -p /usr/local/etc/
|
||||
sudo mv sower.toml /usr/local/etc/
|
||||
fi
|
||||
|
||||
echo
|
||||
echo Register auto start service, root privilege is needed!
|
||||
echo
|
||||
sudo mv cc.wweir.sower.plist /Library/LaunchDaemons/
|
||||
sudo chown root:wheel /Library/LaunchDaemons/cc.wweir.sower.plist
|
||||
sudo launchctl load -w /Library/LaunchDaemons/cc.wweir.sower.plist
|
||||
succ_message
|
||||
;;
|
||||
|
||||
"Linux")
|
||||
if [[ "$(cat /proc/1/comm)" != "systemd" ]]; then
|
||||
echo do not support auto deploy on SysVinit
|
||||
exit 1
|
||||
fi
|
||||
printf "Server side or client side, which do you wanna install [c/s]: "
|
||||
read SIDE
|
||||
echo
|
||||
|
||||
case "$SIDE" in
|
||||
"c")
|
||||
curl -SLf https://github.com/wweir/sower/releases/download/$VERSION/sower-linux-amd64.tar.gz | tar xzv
|
||||
|
||||
sudo mv sower /usr/local/bin/
|
||||
|
||||
if [[ -r /usr/local/etc/sower.toml ]]; then
|
||||
echo The config file already exists, keep the original file
|
||||
else
|
||||
printf "Please enter remote server adddress: "
|
||||
read ADDRESS
|
||||
printf "Please enter remote server password (default: 12345678): "
|
||||
read PASSWORD
|
||||
if [[ -z $PASSWORD ]]; then
|
||||
PASSWORD="12345678"
|
||||
fi
|
||||
printf "Please enter which IP do you wanna listen (default: 127.0.0.1):"
|
||||
read IP
|
||||
if [[ -z $IP ]]; then
|
||||
IP="127.0.0.1"
|
||||
fi
|
||||
echo
|
||||
|
||||
sed -i~ "s/# server_addr=\"remote-server/server_addr=\"$ADDRESS/" sower.toml
|
||||
sed -i~ "s/client_ip=\"127.0.0.1\"/client_ip=\"$IP\"/" sower.toml
|
||||
sed -i~ "s/\"12345678\"/\"$PASSWORD\"/" sower.toml
|
||||
sudo mv sower.toml /usr/local/etc/
|
||||
fi
|
||||
|
||||
sudo mv sower-client.service /etc/systemd/system/
|
||||
sudo systemctl enable sower-client
|
||||
sudo systemctl start sower-client
|
||||
succ_message
|
||||
;;
|
||||
|
||||
"s")
|
||||
curl -SLf https://github.com/wweir/sower/releases/download/$VERSION/sower-linux-amd64.tar.gz | tar xzv
|
||||
printf "Please enter remote server password (default: 12345678): "
|
||||
read PASSWORD
|
||||
echo
|
||||
if [[ -z $PASSWORD ]]; then
|
||||
PASSWORD="12345678"
|
||||
fi
|
||||
|
||||
sudo mv sower /usr/local/bin/
|
||||
sed -i~ "s%bin/sower%bin/sower -p $PASSWORD%" sower-server.service
|
||||
sudo mv sower-server.service /etc/systemd/system/
|
||||
sudo systemctl enable sower-server
|
||||
sudo systemctl start sower-server
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "invalid: $SIDE" && exit 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
echo Do not support auto deploy on this platform.
|
||||
;;
|
||||
esac
|
||||
@@ -1,14 +0,0 @@
|
||||
[Unit]
|
||||
Description=Sower client service
|
||||
After=network.target
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=/tmp
|
||||
ExecStart=/usr/local/bin/sower -f /usr/local/etc/sower.toml
|
||||
RestartSec=3
|
||||
Restart=on-failure
|
||||
@@ -1,14 +0,0 @@
|
||||
[Unit]
|
||||
Description=Sower server service
|
||||
After=network.target
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=nobody
|
||||
WorkingDirectory=/tmp
|
||||
ExecStart=/usr/local/bin/sower -n TCP -v 1
|
||||
RestartSec=3
|
||||
Restart=on-failure
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -u -x -e
|
||||
|
||||
# main logic
|
||||
case "$(uname -s)" in
|
||||
"Darwin")
|
||||
sudo launchctl unload /Library/LaunchDaemons/cc.wweir.sower.plist
|
||||
sudo rm -rf /Library/LaunchDaemons/cc.wweir.sower.plist
|
||||
rm -rf /usr/local/bin/sower /usr/local/etc/sower.toml
|
||||
;;
|
||||
"Linux")
|
||||
sudo systemctl disable sower-client ||true
|
||||
sudo systemctl disable sower-server || true
|
||||
sudo rm -rf /etc/systemd/system/sower-client.service /etc/systemd/system/sower-server.service
|
||||
sudo rm -rf /usr/local/bin/sower /usr/local/etc/sower.toml
|
||||
;;
|
||||
*)
|
||||
echo Do not support auto deploy on this platform.
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,17 @@
|
||||
# Compile
|
||||
FROM golang:1.14-alpine AS compiler
|
||||
|
||||
RUN apk add --no-cache git make
|
||||
|
||||
# enable go modules
|
||||
WORKDIR /src
|
||||
COPY . .
|
||||
|
||||
RUN CGO_ENABLED=0 make build
|
||||
|
||||
|
||||
# Build image
|
||||
FROM scratch
|
||||
|
||||
COPY --from=compiler /src/sower /sower
|
||||
ENTRYPOINT [ "/sower" ]
|
||||
@@ -1,37 +0,0 @@
|
||||
name: Go
|
||||
on: [push]
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Set up Go 1.13
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.13
|
||||
id: go
|
||||
|
||||
- name: build matrix
|
||||
run: |
|
||||
GOOS=linux GOARCH=amd64 make build
|
||||
tar czvf artifacts/sower-linux-amd64.tar.gz sower sower.toml sower-client.service sower-server.service
|
||||
GOOS=linux GOARCH=386 make build
|
||||
tar czvf artifacts/sower-linux-386.tar.gz sower sower.toml sower-client.service sower-server.service
|
||||
GOOS=linux GOARCH=arm make build
|
||||
tar czvf artifacts/sower-linux-arm.tar.gz sower sower.toml sower-client.service sower-server.service
|
||||
GOOS=linux GOARCH=arm64 make build
|
||||
tar czvf artifacts/sower-linux-arm64.tar.gz sower sower.toml sower-client.service sower-server.service
|
||||
GOOS=linux GOARCH=mips make build
|
||||
tar czvf artifacts/sower-linux-mips.tar.gz sower sower.toml sower-client.service sower-server.service
|
||||
GOOS=linux GOARCH=mipsle make build
|
||||
tar czvf artifacts/sower-linux-mipsle.tar.gz sower sower.toml sower-client.service sower-server.service
|
||||
|
||||
GOOS=darwin GOARCH=amd64 make build
|
||||
tar czvf artifacts/sower-darwin-amd64.tar.gz sower sower.toml cc.wweir.sower.plist
|
||||
@@ -0,0 +1,16 @@
|
||||
name: Go
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v2-beta
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: test and build
|
||||
run: |
|
||||
make
|
||||
@@ -0,0 +1,53 @@
|
||||
name: Release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v2-beta
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: build matrix
|
||||
run: |
|
||||
cp conf/sower.toml sower.toml
|
||||
|
||||
GOOS=linux GOARCH=amd64 make build
|
||||
tar czvf sower-linux-amd64.tar.gz sower sower.toml
|
||||
GOOS=linux GOARCH=386 make build
|
||||
tar czvf sower-linux-386.tar.gz sower sower.toml
|
||||
GOOS=linux GOARCH=arm make build
|
||||
tar czvf sower-linux-arm.tar.gz sower sower.toml
|
||||
GOOS=linux GOARCH=arm64 make build
|
||||
tar czvf sower-linux-arm64.tar.gz sower sower.toml
|
||||
GOOS=linux GOARCH=mips make build
|
||||
tar czvf sower-linux-mips.tar.gz sower sower.toml
|
||||
GOOS=linux GOARCH=mipsle make build
|
||||
tar czvf sower-linux-mipsle.tar.gz sower sower.toml
|
||||
|
||||
GOOS=darwin GOARCH=amd64 make build
|
||||
tar czvf sower-darwin-amd64.tar.gz sower sower.toml
|
||||
|
||||
GOOS=windows GOARCH=amd64 make build
|
||||
tar czvf sower-windows-amd64.tar.gz sower.exe sower.toml
|
||||
GOOS=windows GOARCH=386 make build
|
||||
tar czvf sower-windows-386.tar.gz sower.exe sower.toml
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@master
|
||||
with:
|
||||
files: |
|
||||
sower-linux-amd64.tar.gz
|
||||
sower-linux-386.tar.gz
|
||||
sower-linux-arm.tar.gz
|
||||
sower-linux-arm64.tar.gz
|
||||
sower-linux-mips.tar.gz
|
||||
sower-linux-mipsle.tar.gz
|
||||
sower-darwin-amd64.tar.gz
|
||||
sower-windows-amd64.tar.gz
|
||||
sower-windows-386.tar.gz
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -1,43 +1,12 @@
|
||||
export GO111MODULE=on
|
||||
SERVER:=127.0.0.1:5533
|
||||
|
||||
default: test build
|
||||
|
||||
generate:
|
||||
ifeq ("", "$(shell which stringer)")
|
||||
@echo installing generator
|
||||
go get -v golang.org/x/tools/cmd/stringer
|
||||
endif
|
||||
go generate ./...
|
||||
|
||||
test:
|
||||
go vet ./...
|
||||
go test ./...
|
||||
go list ./... | grep -v internal | xargs go test
|
||||
build:
|
||||
go build -ldflags \
|
||||
"-X main.version=$(shell git describe --tags) \
|
||||
-X main.date=$(shell date +%Y-%m-%d)"
|
||||
go build -ldflags "-w -s \
|
||||
-X conf.version=$(shell git describe --tags --always) \
|
||||
-X conf.date=$(shell date +%Y-%m-%d)"
|
||||
image:
|
||||
docker build -t sower -f .circleci/Dockerfile .
|
||||
docker build -t sower -f .github/Dockerfile .
|
||||
|
||||
kill:
|
||||
sudo pkill -9 sower || true
|
||||
|
||||
client: build kill
|
||||
sudo $(PWD)/sower -s 127.0.0.1:5533 -H "127.0.0.1:8080"
|
||||
|
||||
server: build
|
||||
$(PWD)/sower -f ''
|
||||
|
||||
run: build kill
|
||||
$(PWD)/sower -f '' &
|
||||
sudo $(PWD)/sower -f '' -s 127.0.0.1:5533 -H "127.0.0.1:8080" &
|
||||
|
||||
@sleep 1
|
||||
HTTP_PROXY=http://127.0.0.1:8080 curl http://baidu.com || true
|
||||
@echo
|
||||
HTTPS_PROXY=http://127.0.0.1:8080 curl https://baidu.com || true
|
||||
@echo
|
||||
|
||||
@sleep 1
|
||||
@sudo pkill -9 sower || true
|
||||
|
||||
@@ -1,102 +1,90 @@
|
||||
# sower
|
||||
[](https://github.com/wweir/sower/releases)
|
||||
[](https://circleci.com/gh/wweir/sower)
|
||||
[](https://github.com/wweir/sower/actions)
|
||||
[](https://hub.docker.com/r/wweir/sower)
|
||||
[](https://github.com/wweir/sower/issues)
|
||||
[](https://github.com/wweir/sower/stargazers)
|
||||
[](LICENSE)
|
||||
|
||||
|
||||
中文介绍见 [Wiki](https://github.com/wweir/sower/wiki)
|
||||
|
||||
The sower is a cross-platform intelligent transparent proxy tool base on DNS solution.
|
||||
The sower is a cross-platform intelligent transparent proxy tool.
|
||||
|
||||
The first time you visit a new website, sower will detect if the domain in block list and add it in suggect list. So that, you do not need to care about the rules, sower will handle it in a intelligent way.
|
||||
The first time you visit a new website, the sower will detect if the domain is accessible and add it in the dynamic detect list. So, you do not need to care about the rules, sower will handle it intelligently.
|
||||
|
||||
If you wanna enjoy the sower, you need to deploy sower on both server and client side.
|
||||
On client side, sower listening UDP `53` and TCP `80`/`443` ports, so that you need run it with privileged.
|
||||
On server side, it just listening to a port (default `5533`), parse and relay the request to target server.
|
||||
Sower provider both http_proxy/https_proxy and DNS-based proxy. All these kinds of proxy support intelligent router. You can also port-forward any TCP request to remote, such as SSH / SMTP / POP3.
|
||||
|
||||
Sower also provides an http(s) proxy listening on `:8080` by default. You can turn it off or use another port at any time.
|
||||
You can enjoy it by setting http_proxy or your DNS without any other settings.
|
||||
|
||||
If you already have another proxy solution, you can use it's socks5(h) service as a parent proxy to enjoy the sower's intelligent router.
|
||||
|
||||
|
||||
## Installation
|
||||
After Deployed, please check your config file, it is placed in `/usr/local/etc/sower.toml` by default. Here is the example config file [**conf/sower.toml**](https://github.com/wweir/sower/blob/master/conf/sower.toml)
|
||||
To enjoy the sower, you need to deploy sower on both server-side and client-side.
|
||||
|
||||
### Auto deploy
|
||||
Auto deploy script support Linux server side and masOS/Linux client side.
|
||||
The installation script has been integrated into the sower. You can install sower as system service by running `./sower -install 'xxx'` in terminal on the three most popular operating system platforms: Linux / Windows and masOS.
|
||||
|
||||
```shell
|
||||
$ bash -c "$(curl -sL https://git.io/fhhdp)"
|
||||
## Server
|
||||
*If you already have another proxy solution with socks5h support, you can skip server-side.*
|
||||
|
||||
At the server-side, the sower runs just like a web server proxy.
|
||||
It redirects HTTP requests to HTTPS and proxy https requests to the upstream HTTP service.
|
||||
You can use your certificate or use the auto-generated certificate by the sower.
|
||||
|
||||
What you must set is the upstream HTTP service. You can set it by parameter `-s`, eg:
|
||||
``` shell
|
||||
# sower -s 127.0.0.1:8080
|
||||
```
|
||||
|
||||
Then modify the configuration file as needed and set `127.0.0.1` as your first domain name server.
|
||||
In most situation, you just need to modify `/etc/resolv.conf`.
|
||||
|
||||
If you wanna uninstall sower, run:
|
||||
|
||||
```shell
|
||||
$ bash -c "$(curl -sL https://git.io/fhjer)"
|
||||
## Client
|
||||
The easiest way to run it is:
|
||||
``` shell
|
||||
# sower -c aa.bb.cc # the `aa.bb.cc` can also be `socks5h://127.0.0.1:1080`
|
||||
```
|
||||
But a configuration file is recommended to persist dynamic rules in client side.
|
||||
|
||||
### Manually deploy
|
||||
1. Download the precompiled file from https://github.com/wweir/sower/releases
|
||||
2. Decompression the file into a folder
|
||||
3. Run `./sower -h` for help
|
||||
5. Config domain name server
|
||||
4. Config auto start
|
||||
There are 3 kinds of proxy solutions, they are HTTP(S)_PROXY / DNS-based proxy / port-forward.
|
||||
|
||||
### Docker deploy
|
||||
The auto build docker images are [wweir/sower](https://hub.docker.com/r/wweir/sower).
|
||||
### HTTP(S)_PROXY
|
||||
An HTTP(S)_PROXY listening on `:8080` is set by default if you run sower as client mode.
|
||||
|
||||
It is very simple to use it on the server side. Export the port(5533) and run it directly.
|
||||
### DNS-based proxy
|
||||
**DNS-based** solution is not recommanded now, for incompatible with TLS 1.3 esni extention. DNS flush logic has been dropped, you should flush your dns cache manually
|
||||
in Windows and macOS.
|
||||
|
||||
But the client is more troublesome and needs some understanding of the working mechanism of the sower.
|
||||
You can set the `dns_serve_ip` field in the configuration file to start the DNS-based proxy. You should also set the value of `dns_upstream` as your default DNS in OS.
|
||||
|
||||
If you want to enjoy the full experience provided by the sower, you can take sower as your private DNS on a long-running server and set it as your default DNS in your router.
|
||||
|
||||
### port-forward
|
||||
The port-forward can be only setted in configuration file, you can set it in section `client.port_mapping`, eg:
|
||||
``` toml
|
||||
[client.port_mapping]
|
||||
":2222"="aa.bb.cc:22"
|
||||
```
|
||||
|
||||
|
||||
## Architecture
|
||||
```
|
||||
request target servers
|
||||
<-------------+ +------------->
|
||||
| |
|
||||
| |
|
||||
+------------server-------------+
|
||||
| | relay service| |
|
||||
| +-----+---------------------+ |
|
||||
| | | |
|
||||
| | parse http(s) target url | |
|
||||
| | | |
|
||||
| +---------------------------+ |
|
||||
| shadow service |
|
||||
+--------^----------------------+
|
||||
| request domain server
|
||||
quic / KCP / TCP +---------->
|
||||
| |
|
||||
+--------+---client+------+-----+
|
||||
| | |
|
||||
| shadow service | |
|
||||
| relay service | dns |
|
||||
| | service |
|
||||
| | |
|
||||
| 127.0.0.1 or other |
|
||||
| | |
|
||||
+-^-----^----------+---^----^---+
|
||||
| | | |
|
||||
| | | | +----->
|
||||
http(s) proxy | +----------+ | |
|
||||
2 1 1 2
|
||||
+ + + +
|
||||
blocked request normal request
|
||||
|
||||
relay <--+ +-> target
|
||||
http service | | service
|
||||
+-------+-------+----+
|
||||
| sower server |
|
||||
+----^-------^-------+
|
||||
80 443
|
||||
301 http -+ +----- https
|
||||
to https | service
|
||||
protected
|
||||
by tls
|
||||
socks5 |
|
||||
dns <---+ ^ | +--> direct
|
||||
relay | | | | request
|
||||
+---+---+----+--+----+
|
||||
| sower client |
|
||||
+----^--^----^---^---+
|
||||
| | | |
|
||||
dns --+ + + +-- port
|
||||
80 http(s) forward
|
||||
443 proxy
|
||||
```
|
||||
For more detail, see [透明代理 Sower 技术剖析](https://wweir.cc/post/%E9%80%8F%E6%98%8E%E4%BB%A3%E7%90%86-sower-%E6%8A%80%E6%9C%AF%E5%89%96%E6%9E%90/)
|
||||
|
||||
|
||||
## Todo
|
||||
- [x] authenticate
|
||||
- [ ] broker(waiting for QUIC implementation to be stable)
|
||||
- [x] CI/CD
|
||||
- [x] relay optimization
|
||||
- [x] deploy script for all normal platform
|
||||
- [x] dns rule intelligent suggestions
|
||||
- [x] use socks5 as upstream proxy
|
||||
- [ ] multi port http_proxy support
|
||||
+110
-87
@@ -3,128 +3,151 @@ package conf
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"strconv"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/golang/glog"
|
||||
toml "github.com/pelletier/go-toml"
|
||||
"github.com/wweir/sower/util"
|
||||
"github.com/wweir/utils/log"
|
||||
)
|
||||
|
||||
// Conf define the config items
|
||||
var Conf = struct {
|
||||
ConfigFile string
|
||||
NetType string `toml:"net_type"`
|
||||
Cipher string `toml:"cipher"`
|
||||
Password string `toml:"password"`
|
||||
type client struct {
|
||||
Address string `toml:"address"`
|
||||
HTTPProxy string `toml:"http_proxy"`
|
||||
DNSServeIP string `toml:"dns_serve_ip"`
|
||||
DNSUpstream string `toml:"dns_upstream"`
|
||||
PortForward map[string]string `toml:"port_forward"`
|
||||
|
||||
ServerPort string `toml:"server_port"`
|
||||
ServerAddr string `toml:"server_addr"`
|
||||
HTTPProxy string `toml:"http_proxy"`
|
||||
Router struct {
|
||||
DetectLevel int `toml:"detect_level"`
|
||||
DetectTimeout string `toml:"detect_timeout"`
|
||||
|
||||
DNSServer string `toml:"dns_server"`
|
||||
ClientIP string `toml:"client_ip"`
|
||||
SuggestLevel string `toml:"suggest_level"`
|
||||
ClearDNSCache string `toml:"clear_dns_cache"`
|
||||
ProxyList []string `toml:"proxy_list"`
|
||||
DirectList []string `toml:"direct_list"`
|
||||
DynamicList []string `toml:"dynamic_list"`
|
||||
} `toml:"router"`
|
||||
}
|
||||
type server struct {
|
||||
Upstream string `toml:"upstream"`
|
||||
CertFile string `toml:"cert_file"`
|
||||
KeyFile string `toml:"key_file"`
|
||||
CertEmail string `toml:"cert_email"`
|
||||
}
|
||||
|
||||
BlockList []string `toml:"blocklist"`
|
||||
WhiteList []string `toml:"whitelist"`
|
||||
Suggestions []string `toml:"suggestions"`
|
||||
Verbose int `toml:"verbose"`
|
||||
VersionOnly bool `toml:"-"`
|
||||
}{}
|
||||
var (
|
||||
version, date string
|
||||
|
||||
installCmd string
|
||||
execFile, _ = os.Executable()
|
||||
execDir, _ = filepath.Abs(filepath.Dir(execFile))
|
||||
// Conf full config, include common and server / client
|
||||
Conf = struct {
|
||||
file string
|
||||
Password string `toml:"password"`
|
||||
Server server `toml:"server"`
|
||||
Client client `toml:"client"`
|
||||
}{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
initArgs()
|
||||
if Conf.VersionOnly {
|
||||
flag.StringVar(&Conf.Password, "password", "", "password")
|
||||
flag.StringVar(&Conf.Server.Upstream, "s", "", "upstream http service, eg: 127.0.0.1:8080")
|
||||
flag.StringVar(&Conf.Server.CertFile, "s_cert", "", "tls cert file, gen cert from letsencrypt if empty")
|
||||
flag.StringVar(&Conf.Server.KeyFile, "s_key", "", "tls key file, gen cert from letsencrypt if empty")
|
||||
flag.StringVar(&Conf.Client.Address, "c", "", "remote server domain, eg: aa.bb.cc, socks5h://127.0.0.1:1080")
|
||||
flag.StringVar(&Conf.Client.HTTPProxy, "http_proxy", ":8080", "http proxy, empty to disable")
|
||||
flag.IntVar(&Conf.Client.Router.DetectLevel, "level", 2, "dynamic rule detect level: 0~4")
|
||||
flag.StringVar(&Conf.Client.Router.DetectTimeout, "timeout", "300ms", "dynamic rule detect timeout")
|
||||
uninstallFlag := flag.Bool("uninstall", false, "uninstall service")
|
||||
_init() // execute platform init logic
|
||||
|
||||
if !flag.Parsed() {
|
||||
flag.Parse()
|
||||
}
|
||||
switch {
|
||||
case *uninstallFlag:
|
||||
uninstall()
|
||||
os.Exit(0)
|
||||
case installCmd != "":
|
||||
install()
|
||||
os.Exit(0)
|
||||
default:
|
||||
runAsService()
|
||||
}
|
||||
|
||||
defer log.Infow("starting", "config", &Conf)
|
||||
if Conf.file == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := os.Stat(Conf.ConfigFile); os.IsNotExist(err) {
|
||||
glog.Warningln("no config file has been load:", Conf.ConfigFile)
|
||||
return
|
||||
}
|
||||
for i := range refreshFns {
|
||||
if action, err := refreshFns[i](); err != nil {
|
||||
glog.Fatalln(action+":", err)
|
||||
for i := range loadConfigFns {
|
||||
if err := loadConfigFns[i].fn(); err != nil {
|
||||
log.Fatalw("load config", "config", Conf.file, "step", loadConfigFns[i].step, "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
go addSuggestions()
|
||||
}
|
||||
|
||||
// refreshFns will be executed while init and write new config
|
||||
var refreshFns = []func() (string, error){
|
||||
func() (string, error) {
|
||||
action := "load config"
|
||||
f, err := os.OpenFile(Conf.ConfigFile, os.O_RDONLY, 0644)
|
||||
if err != nil {
|
||||
return action, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
//safe refresh config
|
||||
file := Conf.ConfigFile
|
||||
if err = toml.NewDecoder(f).Decode(&Conf); err != nil {
|
||||
return action, err
|
||||
}
|
||||
Conf.ConfigFile = file
|
||||
|
||||
return action, flag.Set("v", strconv.Itoa(Conf.Verbose))
|
||||
},
|
||||
func() (string, error) {
|
||||
action := "clear dns cache"
|
||||
if Conf.ClearDNSCache != "" {
|
||||
return action, execute(Conf.ClearDNSCache)
|
||||
}
|
||||
return action, nil
|
||||
},
|
||||
}
|
||||
|
||||
// AddRefreshFn add refreshh function for reload config
|
||||
func AddRefreshFn(init bool, fn func() (string, error)) error {
|
||||
if init {
|
||||
if _, err := fn(); err != nil {
|
||||
return err
|
||||
}
|
||||
var loadConfigFns = []struct {
|
||||
step string
|
||||
fn func() error
|
||||
}{{"load_config", func() error {
|
||||
f, err := os.OpenFile(Conf.file, os.O_RDONLY, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
refreshFns = append(refreshFns, fn)
|
||||
return nil
|
||||
return toml.NewDecoder(f).Decode(&Conf)
|
||||
}}}
|
||||
|
||||
// flushCh to avoid parallel persist
|
||||
var flushCh = make(chan struct{})
|
||||
var flushOnce = sync.Once{}
|
||||
|
||||
// PersistRule persist dynamic rule into config file
|
||||
func PersistRule(domain string) {
|
||||
flushOnce.Do(func() {
|
||||
go flushConfDaemon()
|
||||
})
|
||||
|
||||
log.Infow("persist dynamic rule into config", "domain", domain)
|
||||
Conf.Client.Router.DynamicList = append(Conf.Client.Router.DynamicList, domain)
|
||||
select {
|
||||
case flushCh <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// SuggestCh add domain into suggestios
|
||||
var SuggestCh = make(chan string)
|
||||
|
||||
// addSuggestions add new domain into suggest rules
|
||||
func addSuggestions() {
|
||||
for domain := range SuggestCh {
|
||||
Conf.Suggestions = append(Conf.Suggestions, domain)
|
||||
Conf.Suggestions = util.NewReverseSecSlice(Conf.Suggestions).Sort().Uniq()
|
||||
|
||||
{ // safe write
|
||||
f, err := os.OpenFile(Conf.ConfigFile+"~", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
func flushConfDaemon() {
|
||||
for range flushCh {
|
||||
// safe write file
|
||||
if Conf.file != "" {
|
||||
f, err := os.OpenFile(Conf.file+"~", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
glog.Errorln(err)
|
||||
log.Errorw("flush config", "step", "flush", "err", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := toml.NewEncoder(f).ArraysWithOneElementPerLine(true).Encode(Conf); err != nil {
|
||||
glog.Errorln(err)
|
||||
Conf.Client.Router.DynamicList =
|
||||
util.NewReverseSecSlice(Conf.Client.Router.DynamicList).Sort().Uniq()
|
||||
|
||||
if err := toml.NewEncoder(f).ArraysWithOneElementPerLine(true).Encode(&Conf); err != nil {
|
||||
log.Errorw("flush config", "step", "flush", "err", err)
|
||||
f.Close()
|
||||
continue
|
||||
}
|
||||
f.Close()
|
||||
|
||||
if err = os.Rename(Conf.ConfigFile+"~", Conf.ConfigFile); err != nil {
|
||||
glog.Errorln(err)
|
||||
if err = os.Rename(Conf.file+"~", Conf.file); err != nil {
|
||||
log.Errorw("flush config", "step", "flush", "err", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// reload config
|
||||
for i := range refreshFns {
|
||||
if action, err := refreshFns[i](); err != nil {
|
||||
glog.Errorln(action+":", err)
|
||||
for i := range loadConfigFns {
|
||||
if err := loadConfigFns[i].fn(); err != nil {
|
||||
log.Errorw("flush config", "step", loadConfigFns[i].step, "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
// +build darwin
|
||||
|
||||
package conf
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/wweir/utils/log"
|
||||
)
|
||||
|
||||
const svcPath = "/Library/LaunchDaemons/sower.plist"
|
||||
const svcFile = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>sower</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/bin/sh</string>
|
||||
<string>-c</string>
|
||||
<string>%s %s</string>
|
||||
</array>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>`
|
||||
|
||||
var ConfigDir = ""
|
||||
|
||||
func _init() {
|
||||
if _, err := os.Stat(execDir + "/sower.toml"); err == nil {
|
||||
ConfigDir = execDir
|
||||
} else {
|
||||
dir, _ := os.UserConfigDir()
|
||||
ConfigDir = filepath.Join("/", dir, "sower")
|
||||
}
|
||||
|
||||
if _, err := os.Stat(ConfigDir + "/sower.toml"); err != nil {
|
||||
flag.StringVar(&Conf.file, "f", "", "config file, rewrite all other parameters if set")
|
||||
} else {
|
||||
flag.StringVar(&Conf.file, "f", ConfigDir+"/sower.toml", "config file, rewrite all other parameters if set")
|
||||
}
|
||||
|
||||
flag.StringVar(&installCmd, "install", "", "install service with cmd, eg: '-f \""+ConfigDir+"/sower.toml\"'")
|
||||
}
|
||||
|
||||
func runAsService() {}
|
||||
|
||||
func install() {
|
||||
if err := ioutil.WriteFile(svcPath, []byte(fmt.Sprintf(svcFile, execFile, installCmd)), 0644); err != nil {
|
||||
log.Fatalw("write service file", "err", err)
|
||||
}
|
||||
|
||||
execute("launchctl unload " + svcPath)
|
||||
if err := execute("launchctl load -w " + svcPath); err != nil {
|
||||
log.Fatalw("install service", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
func uninstall() {
|
||||
execute("launchctl unload " + svcPath)
|
||||
os.Remove(svcPath)
|
||||
os.RemoveAll("/etc/sower")
|
||||
}
|
||||
|
||||
func execute(cmd string) error {
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
out, err := exec.CommandContext(ctx, "sh", "-c", cmd).CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cmd: %s, err: %s, output: %s", cmd, err, out)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// +build linux
|
||||
|
||||
package conf
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/wweir/utils/log"
|
||||
)
|
||||
|
||||
const svcPath = "/etc/systemd/system/sower.service"
|
||||
const svcFile = `[Unit]
|
||||
Description=Sower client service
|
||||
After=network.target
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=/tmp
|
||||
ExecStart=%s %s
|
||||
RestartSec=3
|
||||
Restart=on-failure`
|
||||
|
||||
var ConfigDir = ""
|
||||
|
||||
func _init() {
|
||||
if _, err := os.Stat(execDir + "/sower.toml"); err == nil {
|
||||
ConfigDir = execDir
|
||||
|
||||
} else if stat, err := os.Stat("/etc/sower"); err == nil && stat.IsDir() {
|
||||
ConfigDir = "/etc/sower"
|
||||
|
||||
} else {
|
||||
dir, _ := os.UserConfigDir()
|
||||
ConfigDir = filepath.Join("/", dir, "sower")
|
||||
}
|
||||
|
||||
if _, err := os.Stat(ConfigDir + "/sower.toml"); err != nil {
|
||||
flag.StringVar(&Conf.file, "f", "", "config file, rewrite all other parameters if set")
|
||||
} else {
|
||||
flag.StringVar(&Conf.file, "f", ConfigDir+"/sower.toml", "config file, rewrite all other parameters if set")
|
||||
}
|
||||
|
||||
flag.StringVar(&installCmd, "install", "", "install service with cmd, eg: '-f "+ConfigDir+"/sower.toml'")
|
||||
}
|
||||
|
||||
func runAsService() {}
|
||||
|
||||
func install() {
|
||||
if err := ioutil.WriteFile(svcPath, []byte(fmt.Sprintf(svcFile, execFile, installCmd)), 0644); err != nil {
|
||||
log.Fatalw("write service file", "err", err)
|
||||
}
|
||||
if err := execute("systemctl daemon-reload"); err != nil {
|
||||
log.Fatalw("install service", "err", err)
|
||||
}
|
||||
if err := execute("systemctl enable sower"); err != nil {
|
||||
log.Fatalw("install service", "err", err)
|
||||
}
|
||||
if err := execute("systemctl start sower"); err != nil {
|
||||
log.Fatalw("install service", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
func uninstall() {
|
||||
execute("systemctl stop sower")
|
||||
execute("systemctl disable sower")
|
||||
os.Remove(svcPath)
|
||||
os.RemoveAll("/etc/sower")
|
||||
}
|
||||
|
||||
func execute(cmd string) error {
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
out, err := exec.CommandContext(ctx, "sh", "-c", cmd).CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cmd: %s, err: %s, output: %s", cmd, err, out)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
package conf
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wweir/sower/dns"
|
||||
"github.com/wweir/sower/proxy/shadow"
|
||||
"github.com/wweir/sower/proxy/transport"
|
||||
)
|
||||
|
||||
func initArgs() {
|
||||
cfgFile, _ := filepath.Abs(filepath.Join(filepath.Dir(os.Args[0]), "sower.toml"))
|
||||
flag.StringVar(&Conf.ConfigFile, "f", cfgFile, "config file location")
|
||||
flag.StringVar(&Conf.NetType, "n", "TCP", "net type (socks5 client only): "+strings.Join(transport.ListTransports(), ","))
|
||||
flag.StringVar(&Conf.Cipher, "C", "AES_128_GCM", "cipher type: "+strings.Join(shadow.ListCiphers(), ","))
|
||||
flag.StringVar(&Conf.Password, "p", "12345678", "password")
|
||||
flag.StringVar(&Conf.ServerPort, "P", "5533", "server mode listen port")
|
||||
flag.StringVar(&Conf.ServerAddr, "s", "", "server IP (run in CLIENT mode if set)")
|
||||
flag.StringVar(&Conf.HTTPProxy, "H", "", "http proxy listen addr")
|
||||
flag.StringVar(&Conf.DNSServer, "d", "114.114.114.114", "client dns server")
|
||||
flag.StringVar(&Conf.ClientIP, "c", "127.0.0.1", "client dns service redirect IP")
|
||||
flag.StringVar(&Conf.SuggestLevel, "l", "SPEEDUP", "suggest level setting: "+strings.Join(dns.ListSuggestLevels(), ","))
|
||||
flag.BoolVar(&Conf.VersionOnly, "V", false, "print sower version")
|
||||
|
||||
if !flag.Parsed() {
|
||||
flag.Set("logtostderr", "true")
|
||||
flag.Parse()
|
||||
}
|
||||
}
|
||||
|
||||
func execute(cmd string) error {
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
out, err := exec.CommandContext(ctx, "sh", "-c", Conf.ClearDNSCache).CombinedOutput()
|
||||
return errors.Wrapf(err, "cmd: %s, output: %s, error", Conf.ClearDNSCache, out)
|
||||
}
|
||||
+64
-73
@@ -13,8 +13,7 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wweir/utils/log"
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.org/x/sys/windows/svc"
|
||||
"golang.org/x/sys/windows/svc/eventlog"
|
||||
@@ -24,79 +23,69 @@ import (
|
||||
const name = "sower"
|
||||
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue
|
||||
|
||||
func initArgs() {
|
||||
cfgFile, _ := filepath.Abs(filepath.Join(filepath.Dir(os.Args[0]), "sower.toml"))
|
||||
flag.StringVar(&Conf.ConfigFile, "f", cfgFile, "config file location")
|
||||
flag.BoolVar(&Conf.VersionOnly, "V", false, "print sower version")
|
||||
install := flag.Bool("install", false, "install sower as a service")
|
||||
uninstall := flag.Bool("uninstall", false, "uninstall sower from service list")
|
||||
exePath,_:=filepath.Abs(os.Args[0])
|
||||
var ConfigDir = execDir
|
||||
|
||||
if !flag.Parsed() {
|
||||
os.Mkdir("log", 0755)
|
||||
flag.Set("log_dir", filepath.Dir(os.Args[0])+"/log")
|
||||
flag.Parse()
|
||||
}
|
||||
func _init() {
|
||||
flag.StringVar(&Conf.file, "f", filepath.Join(execDir, "sower.toml"), "config file, rewrite all other parameters if set")
|
||||
flag.StringVar(&installCmd, "install", "", "put any character to install as a service, eg: true")
|
||||
}
|
||||
|
||||
switch {
|
||||
case *install:
|
||||
mgrDo(func(m *mgr.Mgr) error {
|
||||
s, err := m.OpenService(name)
|
||||
if err == nil {
|
||||
s.Close()
|
||||
return fmt.Errorf("service %s already exists", name)
|
||||
}
|
||||
s, err = m.CreateService(name, exePath, mgr.Config{
|
||||
DisplayName: "Sower Proxy",
|
||||
StartType: windows.SERVICE_AUTO_START,
|
||||
})
|
||||
func runAsService() {
|
||||
os.Chdir(filepath.Dir(os.Args[0]))
|
||||
if active, err := svc.IsAnInteractiveSession(); err != nil {
|
||||
log.Fatalf("failed to determine if we are running in an interactive session: %v", err)
|
||||
} else if !active {
|
||||
go func() {
|
||||
elog, err := eventlog.Open(name)
|
||||
if err != nil {
|
||||
return err
|
||||
log.Fatalw("install service", "err", err)
|
||||
}
|
||||
defer s.Close()
|
||||
err = eventlog.InstallAsEventCreate(name, eventlog.Error|eventlog.Warning|eventlog.Info)
|
||||
if err != nil {
|
||||
s.Delete()
|
||||
return fmt.Errorf("SetupEventLogSource() failed: %s", err)
|
||||
defer elog.Close()
|
||||
|
||||
if err := svc.Run(name, &myservice{}); err != nil {
|
||||
elog.Error(1, fmt.Sprintf("%s service failed: %v", name, err))
|
||||
log.Fatalw("install service", "err", err)
|
||||
}
|
||||
|
||||
return s.Start()
|
||||
})
|
||||
os.Exit(0)
|
||||
|
||||
case *uninstall:
|
||||
serviceDo(func(s *mgr.Service) error {
|
||||
err := s.Delete()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return eventlog.Remove(name)
|
||||
})
|
||||
os.Exit(0)
|
||||
|
||||
default:
|
||||
os.Chdir(filepath.Dir(os.Args[0]))
|
||||
if active, err := svc.IsAnInteractiveSession(); err != nil {
|
||||
glog.Exitf("failed to determine if we are running in an interactive session: %v", err)
|
||||
} else if !active {
|
||||
go func() {
|
||||
elog, err := eventlog.Open(name)
|
||||
if err != nil {
|
||||
glog.Exitln(err)
|
||||
}
|
||||
defer elog.Close()
|
||||
|
||||
if err := svc.Run(name, &myservice{}); err != nil {
|
||||
elog.Error(1, fmt.Sprintf("%s service failed: %v", name, err))
|
||||
glog.Exitln(err)
|
||||
}
|
||||
elog.Info(1, fmt.Sprintf("winsvc.RunAsService: %s service stopped", name))
|
||||
os.Exit(0)
|
||||
}()
|
||||
}
|
||||
elog.Info(1, fmt.Sprintf("winsvc.RunAsService: %s service stopped", name))
|
||||
os.Exit(0)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func install() {
|
||||
mgrDo(func(m *mgr.Mgr) error {
|
||||
s, err := m.OpenService(name)
|
||||
if err == nil {
|
||||
s.Close()
|
||||
return fmt.Errorf("service %s already exists", name)
|
||||
}
|
||||
s, err = m.CreateService(name, execFile, mgr.Config{
|
||||
DisplayName: "Sower Proxy",
|
||||
StartType: windows.SERVICE_AUTO_START,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
err = eventlog.InstallAsEventCreate(name, eventlog.Error|eventlog.Warning|eventlog.Info)
|
||||
if err != nil {
|
||||
s.Delete()
|
||||
return fmt.Errorf("SetupEventLogSource() failed: %s", err)
|
||||
}
|
||||
|
||||
return s.Start()
|
||||
})
|
||||
}
|
||||
|
||||
func uninstall() {
|
||||
serviceDo(func(s *mgr.Service) error {
|
||||
err := s.Delete()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return eventlog.Remove(name)
|
||||
})
|
||||
}
|
||||
func serviceDo(fn func(*mgr.Service) error) {
|
||||
mgrDo(func(m *mgr.Mgr) error {
|
||||
s, err := m.OpenService(name)
|
||||
@@ -110,12 +99,12 @@ func serviceDo(fn func(*mgr.Service) error) {
|
||||
func mgrDo(fn func(m *mgr.Mgr) error) {
|
||||
m, err := mgr.Connect()
|
||||
if err != nil {
|
||||
glog.Exitln(err)
|
||||
log.Fatalw("install service", "err", err)
|
||||
}
|
||||
defer m.Disconnect()
|
||||
|
||||
if err := fn(m); err != nil {
|
||||
glog.Fatalln(err)
|
||||
log.Fatalw("install service", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +113,7 @@ type myservice struct{}
|
||||
func (m *myservice) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
|
||||
elog, err := eventlog.Open(name)
|
||||
if err != nil {
|
||||
glog.Errorln(err)
|
||||
log.Errorw("install service", "err", err)
|
||||
return
|
||||
}
|
||||
defer elog.Close()
|
||||
@@ -158,7 +147,7 @@ func execute(cmd string) error {
|
||||
defer cancel()
|
||||
|
||||
var cmds []string
|
||||
for _, cmd := range strings.Split(Conf.ClearDNSCache, " ") {
|
||||
for _, cmd := range strings.Split(cmd, " ") {
|
||||
if cmd == "" {
|
||||
continue
|
||||
}
|
||||
@@ -174,6 +163,8 @@ func execute(cmd string) error {
|
||||
|
||||
command := exec.CommandContext(ctx, cmds[0], cmds[1:]...)
|
||||
command.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
||||
out, err := command.CombinedOutput()
|
||||
return errors.Wrapf(err, "cmd: %s, output: %s, error", Conf.ClearDNSCache, out)
|
||||
if out, err := command.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("cmd: %s, output: %s, err: %w", cmd, out, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+55
-51
@@ -1,51 +1,55 @@
|
||||
net_type="TCP" # TCP, KCP, QUIC, SOCKS5(client only)
|
||||
cipher="AES_128_GCM" # AES_128_GCM, AES_192_GCM, AES_256_GCM, CHACHA20_IETF_POLY1305, XCHACHA20_IETF_POLY1305
|
||||
password="12345678"
|
||||
server_port="5533"
|
||||
# server_addr="remote-server" # replce it to remote server
|
||||
http_proxy=":8080" # eg: 192.168.0.2:8080
|
||||
dns_server="" # Keep empty for dynamic setting from net env
|
||||
client_ip="127.0.0.1" # listen the IP, dns target is the IP
|
||||
# clear_dns_cache="pkill mDNSResponder || true" # Windows: "ipconfig /flushdnss"
|
||||
suggest_level="SPEEDUP" # DISABLE, BLOCK, SPEEDUP
|
||||
blocklist=[
|
||||
"**.google.*", # google
|
||||
"**.goo.gl",
|
||||
"**.googleusercontent.com",
|
||||
"**.googleapis.com",
|
||||
"*.googlesource.com",
|
||||
"**.youtube.com", # youtube
|
||||
"**.ytimg.com",
|
||||
"**.ggpht.com",
|
||||
"**.googlevideo.com",
|
||||
"**.facebook.com", # facebook
|
||||
"**.fbcdn.net",
|
||||
"**.twitter.com", # twitter
|
||||
"**.twimg.com",
|
||||
"**.blogspot.com", # blogspot
|
||||
"**.appspot.com",
|
||||
"**.wikipedia.org", # wikipeida
|
||||
"*.cloudfront.net",
|
||||
"**.amazon.com",
|
||||
"**.amazonaws.com",
|
||||
"*.githubusercontent.com",
|
||||
"*.github.*",
|
||||
]
|
||||
whitelist=[
|
||||
"**.in-addr.arpa",
|
||||
"imap.*.*",
|
||||
"imap.*.*.*",
|
||||
"smtp.*.*",
|
||||
"smtp.*.*.*",
|
||||
"pop.*.*",
|
||||
"pop.*.*.*",
|
||||
"imap-mail.outlook.com",
|
||||
"**.qq.com",
|
||||
"**.baidu.com",
|
||||
"*.aliyun.com",
|
||||
"**.cn",
|
||||
"**.icloud.com",
|
||||
"**.163.com",
|
||||
"**.weiyun.com",
|
||||
]
|
||||
verbose=0
|
||||
password="" # sower password
|
||||
|
||||
[client]
|
||||
address = "" # aa.bb.cc, socks5h://127.0.0.1:1080
|
||||
http_proxy=":8080"
|
||||
dns_serve_ip="" # eg: 127.0.0.1. keep empty to disable DNS solution
|
||||
dns_upstream="" # keep empty to set via dhcp, not effective in any environment
|
||||
|
||||
[client.port_forward]
|
||||
# eg: ":2222"="aa.bb.cc:22"
|
||||
|
||||
[client.router]
|
||||
detect_level = 2 # 0~4, the bigger the harder to add
|
||||
detect_timeout = "300ms"
|
||||
direct_list = [
|
||||
"**.in-addr.arpa",
|
||||
"imap.*.*",
|
||||
"imap.*.*.*",
|
||||
"smtp.*.*",
|
||||
"smtp.*.*.*",
|
||||
"pop.*.*",
|
||||
"pop.*.*.*",
|
||||
"**.cn",
|
||||
]
|
||||
proxy_list = [
|
||||
"**.google.*",
|
||||
"**.goo.gl",
|
||||
"**.googleusercontent.com",
|
||||
"**.googleapis.com",
|
||||
"*.googlesource.com",
|
||||
"**.youtube.com",
|
||||
"**.ytimg.com",
|
||||
"**.ggpht.com",
|
||||
"**.googlevideo.com",
|
||||
"**.facebook.com",
|
||||
"**.fbcdn.net",
|
||||
"**.twitter.com",
|
||||
"**.twimg.com",
|
||||
"**.blogspot.com",
|
||||
"**.appspot.com",
|
||||
"**.wikipedia.org",
|
||||
"*.cloudfront.net",
|
||||
"**.amazon.com",
|
||||
"**.amazonaws.com",
|
||||
"*.githubusercontent.com",
|
||||
"*.githubassets.com",
|
||||
"*.github.*",
|
||||
]
|
||||
|
||||
|
||||
[server]
|
||||
cert_email = "" # eg: user@aa.bb.cc
|
||||
cert_file = "" # eg: /etc/ssl/server.crt
|
||||
key_file = "" # eg: /etc/ssl/server.key
|
||||
upstream = "" # eg: 127.0.0.1:8080
|
||||
@@ -1,22 +0,0 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetDefaultDNSServer(t *testing.T) {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
case "darwin":
|
||||
default:
|
||||
t.Skip("skip for some enviroment not support dhcp and permission set")
|
||||
return
|
||||
}
|
||||
|
||||
if got, err := GetDefaultDNSServer(); err != nil {
|
||||
t.Errorf("GetDefaultDNSServer() return error: %s", err)
|
||||
} else {
|
||||
t.Logf("GetDefaultDNSServer() return IP: %v", got)
|
||||
}
|
||||
}
|
||||
-203
@@ -1,203 +0,0 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/miekg/dns"
|
||||
mem "github.com/wweir/mem-go"
|
||||
"github.com/wweir/sower/util"
|
||||
)
|
||||
|
||||
const colon = byte(':')
|
||||
|
||||
func StartDNS(dnsServer, listenIP string, suggestCh chan<- string, level string) {
|
||||
ip := net.ParseIP(listenIP)
|
||||
|
||||
suggest := &intelliSuggest{suggestCh, parseSuggestLevel(level), listenIP, time.Second}
|
||||
mem.DefaultCache = mem.New(time.Hour)
|
||||
|
||||
dhcpCh := make(chan struct{})
|
||||
if dnsServer != "" {
|
||||
dnsServer = net.JoinHostPort(dnsServer, "53")
|
||||
} else {
|
||||
go dynamicSetUpstreamDNS(listenIP, &dnsServer, dhcpCh)
|
||||
dhcpCh <- struct{}{}
|
||||
}
|
||||
|
||||
dns.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) {
|
||||
// *Msg r has an TSIG record and it was validated
|
||||
if r.IsTsig() != nil && w.TsigStatus() == nil {
|
||||
lastTsig := r.Extra[len(r.Extra)-1].(*dns.TSIG)
|
||||
r.SetTsig(lastTsig.Hdr.Name, dns.HmacMD5, 300, time.Now().Unix())
|
||||
}
|
||||
|
||||
//https://stackoverflow.com/questions/4082081/requesting-a-and-aaaa-records-in-single-dns-query/4083071#4083071
|
||||
if len(r.Question) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
domain := r.Question[0].Name
|
||||
if idx := strings.IndexByte(domain, colon); idx > 0 {
|
||||
domain = domain[:idx] // trim port
|
||||
}
|
||||
|
||||
matchAndServe(w, r, domain, listenIP, dnsServer, dhcpCh, ip, suggest)
|
||||
})
|
||||
|
||||
server := &dns.Server{Addr: net.JoinHostPort(listenIP, "53"), Net: "udp"}
|
||||
glog.Fatalln(server.ListenAndServe())
|
||||
}
|
||||
|
||||
func dynamicSetUpstreamDNS(listenIP string, dnsServer *string, dhcpCh <-chan struct{}) {
|
||||
addr, _ := dns.ReverseAddr(listenIP)
|
||||
msg := &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Id: dns.Id(),
|
||||
RecursionDesired: false,
|
||||
},
|
||||
Question: []dns.Question{{
|
||||
Name: addr,
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
}},
|
||||
}
|
||||
|
||||
for {
|
||||
<-dhcpCh
|
||||
if _, err := dns.Exchange(msg, *dnsServer); err == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
host, err := GetDefaultDNSServer()
|
||||
if err != nil {
|
||||
glog.Errorln(err)
|
||||
continue
|
||||
}
|
||||
|
||||
// atomic action
|
||||
*dnsServer = net.JoinHostPort(host, "53")
|
||||
glog.Infoln("set dns server to", host)
|
||||
}
|
||||
}
|
||||
func matchAndServe(w dns.ResponseWriter, r *dns.Msg, domain, listenIP, dnsServer string,
|
||||
dhcpCh chan struct{}, ipNet net.IP, suggest *intelliSuggest) {
|
||||
|
||||
inWriteList := whiteList.Match(domain)
|
||||
if !inWriteList && (blockList.Match(domain) || suggestList.Match(domain)) {
|
||||
glog.V(2).Infof("match %s suss", domain)
|
||||
w.WriteMsg(localA(r, domain, ipNet))
|
||||
return
|
||||
}
|
||||
|
||||
go mem.Remember(suggest, domain)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
msg, err := dns.ExchangeContext(ctx, r, dnsServer)
|
||||
if err != nil {
|
||||
if dhcpCh != nil {
|
||||
select {
|
||||
case dhcpCh <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
glog.V(1).Infof("get dns of %s from %s fail: %s", domain, dnsServer, err)
|
||||
return
|
||||
} else if msg == nil { // expose any response except nil
|
||||
glog.V(1).Infof("get dns of %s from %s return empty", domain, dnsServer)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteMsg(msg)
|
||||
}
|
||||
|
||||
type intelliSuggest struct {
|
||||
suggestCh chan<- string
|
||||
level level
|
||||
listenIP string
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func (i *intelliSuggest) GetOne(key interface{}) (iface interface{}, e error) {
|
||||
iface, e = struct{}{}, nil
|
||||
if i.level == DISABLE {
|
||||
return
|
||||
}
|
||||
|
||||
// kill deadloop, for ugly wildcard setting dns setting
|
||||
domain := strings.TrimSuffix(key.(string), ".")
|
||||
if strings.Count(domain, ".") > 10 {
|
||||
return
|
||||
}
|
||||
|
||||
ip, err := net.LookupIP(domain)
|
||||
if err != nil || len(ip) == 0 {
|
||||
glog.V(1).Infoln(domain, ip, err)
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
pings = [...]struct {
|
||||
viaAddr string
|
||||
port util.Port
|
||||
}{
|
||||
{ip[0].String(), util.HTTP},
|
||||
{i.listenIP, util.HTTP},
|
||||
{ip[0].String(), util.HTTPS},
|
||||
{i.listenIP, util.HTTPS},
|
||||
}
|
||||
protos = [...]*int32{
|
||||
new(int32), /*HTTP*/
|
||||
new(int32), /*HTTPS*/
|
||||
}
|
||||
score = new(int32)
|
||||
)
|
||||
for idx := range pings {
|
||||
go func(idx int) {
|
||||
if err := util.HTTPPing(pings[idx].viaAddr, domain, pings[idx].port, i.timeout); err != nil {
|
||||
// local ping fail
|
||||
if pings[idx].viaAddr == i.listenIP {
|
||||
atomic.AddInt32(score, -1)
|
||||
glog.V(1).Infof("remote ping %s fail", domain)
|
||||
} else {
|
||||
atomic.AddInt32(score, 1)
|
||||
glog.V(1).Infof("local ping %s fail", domain)
|
||||
}
|
||||
|
||||
// remote ping faster
|
||||
} else if pings[idx].viaAddr == i.listenIP {
|
||||
if atomic.CompareAndSwapInt32(protos[idx/2], 0, 1) && i.level == SPEEDUP {
|
||||
atomic.AddInt32(score, 1)
|
||||
}
|
||||
glog.V(1).Infof("remote ping %s faster", domain)
|
||||
|
||||
} else {
|
||||
atomic.CompareAndSwapInt32(protos[idx/2], 0, 2)
|
||||
return // score change trigger add suggestion
|
||||
}
|
||||
|
||||
// check all remote pings are faster
|
||||
if atomic.LoadInt32(score) == int32(len(protos)) {
|
||||
for i := range protos {
|
||||
if atomic.LoadInt32(protos[i]) != 1 {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 1. local fail and remote success
|
||||
// 2. all remote pings are faster
|
||||
if atomic.LoadInt32(score) >= int32(len(protos)) {
|
||||
old := atomic.SwapInt32(score, -1) // avoid readd the suggestion
|
||||
i.suggestCh <- domain
|
||||
glog.Infof("suggested domain: %s with score: %d", domain, old)
|
||||
}
|
||||
}(idx)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
// Code generated by "stringer -type=level util.go"; DO NOT EDIT.
|
||||
|
||||
package dns
|
||||
|
||||
import "strconv"
|
||||
|
||||
const _level_name = "DISABLEBLOCKSPEEDUPlevelEnd"
|
||||
|
||||
var _level_index = [...]uint8{0, 7, 12, 19, 27}
|
||||
|
||||
func (i level) String() string {
|
||||
if i < 0 || i >= level(len(_level_index)-1) {
|
||||
return "level(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _level_name[_level_index[i]:_level_index[i+1]]
|
||||
}
|
||||
-76
@@ -1,76 +0,0 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/wweir/sower/util"
|
||||
)
|
||||
|
||||
var (
|
||||
blockList *util.Node
|
||||
suggestList *util.Node
|
||||
whiteList *util.Node
|
||||
)
|
||||
|
||||
// LoadRules init rules from config
|
||||
func LoadRules(blocklist, suggestions, whitelist []string, host string) {
|
||||
blockList = loadRules("block", blocklist)
|
||||
suggestList = loadRules("suggest", suggestions)
|
||||
whiteList = loadRules("white", whitelist)
|
||||
whiteList.Add(host)
|
||||
glog.V(1).Infoln("reloaded config")
|
||||
}
|
||||
|
||||
func loadRules(name string, list []string) *util.Node {
|
||||
rule := util.NewNodeFromRules(".", list...)
|
||||
glog.V(3).Infof("load %s rule:\n%s", name, rule)
|
||||
return rule
|
||||
}
|
||||
|
||||
func localA(r *dns.Msg, domain string, localIP net.IP) *dns.Msg {
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(r)
|
||||
if localIP.To4() != nil {
|
||||
m.Answer = []dns.RR{&dns.A{
|
||||
Hdr: dns.RR_Header{Name: domain, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 20},
|
||||
A: localIP,
|
||||
}}
|
||||
} else {
|
||||
m.Answer = []dns.RR{&dns.AAAA{
|
||||
Hdr: dns.RR_Header{Name: domain, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 20},
|
||||
AAAA: localIP,
|
||||
}}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
//go:generate stringer -type=level $GOFILE
|
||||
type level int32
|
||||
|
||||
const (
|
||||
DISABLE level = iota
|
||||
BLOCK
|
||||
SPEEDUP
|
||||
levelEnd
|
||||
)
|
||||
|
||||
func ListSuggestLevels() []string {
|
||||
list := make([]string, 0, int(levelEnd))
|
||||
for i := level(0); i < levelEnd; i++ {
|
||||
list = append(list, i.String())
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func parseSuggestLevel(suggestLevel string) level {
|
||||
for i := level(0); i < levelEnd; i++ {
|
||||
if suggestLevel == i.String() {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
glog.Exitln("invalid suggest level: " + suggestLevel)
|
||||
return levelEnd
|
||||
}
|
||||
@@ -3,25 +3,12 @@ module github.com/wweir/sower
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
|
||||
github.com/guregu/null v3.4.0+incompatible // indirect
|
||||
github.com/klauspost/cpuid v1.2.1 // indirect
|
||||
github.com/klauspost/reedsolomon v1.9.2 // indirect
|
||||
github.com/krolaw/dhcp4 v0.0.0-20190909130307-a50d88189771
|
||||
github.com/lib/pq v1.2.0 // indirect
|
||||
github.com/libp2p/go-reuseport v0.0.1
|
||||
github.com/lucas-clemente/quic-go v0.12.0
|
||||
github.com/miekg/dns v1.1.18
|
||||
github.com/pelletier/go-toml v1.4.0
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/satori/go.uuid v1.2.0 // indirect
|
||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
|
||||
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b // indirect
|
||||
github.com/tjfoc/gmsm v1.0.1 // indirect
|
||||
github.com/ulule/deepcopier v0.0.0-20171107155558-ca99b135e50f // indirect
|
||||
github.com/wweir/mem-go v0.0.0-20190109100331-8673ab596296
|
||||
github.com/xtaci/kcp-go v5.4.10+incompatible
|
||||
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae // indirect
|
||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe
|
||||
github.com/miekg/dns v1.1.27
|
||||
github.com/pelletier/go-toml v1.6.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/wweir/utils v0.0.0-20200214114658-f6f356a08736
|
||||
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4
|
||||
)
|
||||
|
||||
@@ -1,110 +1,102 @@
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||
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/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.0 h1:kbxbvI4Un1LUWKxufD+BiE6AEExYYgkQLQmLFqA1LFk=
|
||||
github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/guregu/null v3.4.0+incompatible h1:a4mw37gBO7ypcBlTJeZGuMpSxxFTV9qFfFKgWxQSGaM=
|
||||
github.com/guregu/null v3.4.0+incompatible/go.mod h1:ePGpQaN9cw0tj45IR5E5ehMvsFlLlQZAkkOXZurJ3NM=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w=
|
||||
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/reedsolomon v1.9.2 h1:E9CMS2Pqbv+C7tsrYad4YC9MfhnMVWhMRsTi7U0UB18=
|
||||
github.com/klauspost/reedsolomon v1.9.2/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
|
||||
github.com/influxdata/influxdb v1.7.9/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY=
|
||||
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
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/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/krolaw/dhcp4 v0.0.0-20190909130307-a50d88189771 h1:t2c2B9g1ZVhMYduqmANSEGVD3/1WlsrEYNPtVoFlENk=
|
||||
github.com/krolaw/dhcp4 v0.0.0-20190909130307-a50d88189771/go.mod h1:0AqAH3ZogsCrvrtUpvc6EtVKbc3w6xwZhkvGLuqyi3o=
|
||||
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/libp2p/go-reuseport v0.0.1 h1:7PhkfH73VXfPJYKQ6JwS5I/eVcoyYi9IMNGc6FWpFLw=
|
||||
github.com/libp2p/go-reuseport v0.0.1/go.mod h1:jn6RmB1ufnQwl0Q1f+YxAj8isJgDCQzaaxIFYDhcYEA=
|
||||
github.com/lucas-clemente/quic-go v0.12.0 h1:TRbvZ6F++sofeGbh+Z2IIyIOhl8KyGnYuA06g2yrHdI=
|
||||
github.com/lucas-clemente/quic-go v0.12.0/go.mod h1:UXJJPE4RfFef/xPO5wQm0tITK8gNfqwTxjbE7s3Vb8s=
|
||||
github.com/marten-seemann/qpack v0.1.0/go.mod h1:LFt1NU/Ptjip0C2CPkhimBz5CGE3WGDAUWqna+CNTrI=
|
||||
github.com/marten-seemann/qtls v0.3.2 h1:O7awy4bHEzSX/K3h+fZig3/Vo03s/RxlxgsAk9sYamI=
|
||||
github.com/marten-seemann/qtls v0.3.2/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
|
||||
github.com/miekg/dns v1.1.18 h1:S82KA03bsvMvziY41d0WitiplMCt8QhawbSQLtoqsdI=
|
||||
github.com/miekg/dns v1.1.18/go.mod h1:WgzbA6oji13JREwiNsRDNfl7jYdPnmz+VEuLrA+/48M=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg=
|
||||
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM=
|
||||
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4=
|
||||
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.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/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU=
|
||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
|
||||
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b h1:mnG1fcsIB1d/3vbkBak2MM0u+vhGhlQwpeimUi7QncM=
|
||||
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
|
||||
github.com/tjfoc/gmsm v1.0.1 h1:R11HlqhXkDospckjZEihx9SW/2VW0RgdwrykyWMFOQU=
|
||||
github.com/tjfoc/gmsm v1.0.1/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/ulule/deepcopier v0.0.0-20171107155558-ca99b135e50f h1:QatZ4lsJBY3x1+Imst9g95+vUl7m52dqM9Pi4aSMW8w=
|
||||
github.com/ulule/deepcopier v0.0.0-20171107155558-ca99b135e50f/go.mod h1:BNLmYJ8oMJPIPpNx5968jCyUhwEU1XT3YsuOqtbo5qo=
|
||||
github.com/wweir/mem-go v0.0.0-20190109100331-8673ab596296 h1:/HkUfg+ZMx/tNdnyJdVlhyv+xO3A7ZlpfL9nFLWLYcc=
|
||||
github.com/wweir/mem-go v0.0.0-20190109100331-8673ab596296/go.mod h1:k7rjBGWoJ+JKwvfe8juAX0zgybjo/Yo3JGkca5f/06s=
|
||||
github.com/xtaci/kcp-go v5.4.10+incompatible h1:FgH1ji3efEmRFaHEeyim1RYN4Q/c8BT8VqXMklBmh84=
|
||||
github.com/xtaci/kcp-go v5.4.10+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE=
|
||||
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
|
||||
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
|
||||
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
github.com/wweir/utils v0.0.0-20200214114658-f6f356a08736 h1:x6LiUrnHR4CCqzRzB0zqiB9hzVFcRhSKrVlZDGW0FcM=
|
||||
github.com/wweir/utils v0.0.0-20200214114658-f6f356a08736/go.mod h1:Nv4eBGkUJiHDPgVowJJlQNcJYgMfst6IkjaDThH2/yI=
|
||||
go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc=
|
||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU=
|
||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 h1:ACG4HJsFiNMf47Y4PeRoebLNy/2lXT9EtprMuTFWt1M=
|
||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6 h1:Sy5bstxEqwwbYs6n0/pBuxKENqOeZUgD45Gp3Q3pqLg=
|
||||
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/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-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
|
||||
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-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 h1:sfkvUWPNGwSV+8/fNqctR5lS2AqCSqYwXdrjCxp/dXo=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnfG5kSmgy9KZR9sW3W5QeA=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
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 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
|
||||
@@ -1,50 +1,45 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/wweir/sower/conf"
|
||||
"github.com/wweir/sower/dns"
|
||||
"github.com/wweir/sower/proxy"
|
||||
"github.com/wweir/sower/proxy/transport"
|
||||
"github.com/wweir/sower/router"
|
||||
)
|
||||
|
||||
var version, date string
|
||||
|
||||
func main() {
|
||||
cfg := &conf.Conf
|
||||
if cfg.VersionOnly {
|
||||
config, _ := json.MarshalIndent(cfg, "", "\t")
|
||||
fmt.Printf("Version:\n\t%s %s\nConfig:\n%s", version, date, config)
|
||||
return
|
||||
}
|
||||
glog.Infof("Starting sower(%s %s): %v", version, date, cfg)
|
||||
switch {
|
||||
case conf.Conf.Server.Upstream != "":
|
||||
proxy.StartServer(conf.Conf.Server.Upstream, conf.Conf.Password, conf.ConfigDir,
|
||||
conf.Conf.Server.CertFile, conf.Conf.Server.KeyFile, conf.Conf.Server.CertEmail)
|
||||
|
||||
tran, err := transport.GetTransport(cfg.NetType)
|
||||
if err != nil {
|
||||
glog.Exitln(err)
|
||||
}
|
||||
|
||||
if cfg.ServerAddr == "" {
|
||||
proxy.StartServer(tran, cfg.ServerPort, cfg.Cipher, cfg.Password)
|
||||
|
||||
} else {
|
||||
conf.AddRefreshFn(true, func() (string, error) {
|
||||
dns.LoadRules(cfg.BlockList, cfg.Suggestions, cfg.WhiteList, cfg.ServerAddr)
|
||||
return "load rules", nil
|
||||
})
|
||||
|
||||
isSocks5 := (cfg.NetType == "SOCKS5")
|
||||
serverAddr := net.JoinHostPort(cfg.ServerAddr, cfg.ServerPort)
|
||||
|
||||
if cfg.HTTPProxy != "" {
|
||||
go proxy.StartHttpProxy(tran, isSocks5, serverAddr, cfg.Cipher, cfg.Password, cfg.HTTPProxy)
|
||||
case conf.Conf.Client.Address != "":
|
||||
route := &router.Route{
|
||||
ProxyAddress: conf.Conf.Client.Address,
|
||||
ProxyPassword: conf.Conf.Password,
|
||||
DetectLevel: conf.Conf.Client.Router.DetectLevel,
|
||||
DetectTimeout: conf.Conf.Client.Router.DetectTimeout,
|
||||
DirectList: conf.Conf.Client.Router.DirectList,
|
||||
ProxyList: conf.Conf.Client.Router.ProxyList,
|
||||
DynamicList: conf.Conf.Client.Router.DynamicList,
|
||||
PersistFn: conf.PersistRule,
|
||||
}
|
||||
|
||||
go dns.StartDNS(cfg.DNSServer, cfg.ClientIP, conf.SuggestCh, cfg.SuggestLevel)
|
||||
proxy.StartClient(tran, isSocks5, serverAddr, cfg.Cipher, cfg.Password, cfg.ClientIP)
|
||||
go proxy.StartHTTPProxy(conf.Conf.Client.HTTPProxy, conf.Conf.Client.Address,
|
||||
[]byte(conf.Conf.Password), route.ShouldProxy)
|
||||
|
||||
if conf.Conf.Client.DNSServeIP != "" {
|
||||
go proxy.StartDNS(conf.Conf.Client.DNSServeIP, conf.Conf.Client.DNSUpstream,
|
||||
route.ShouldProxy)
|
||||
}
|
||||
|
||||
proxy.StartClient(conf.Conf.Client.Address, conf.Conf.Password,
|
||||
conf.Conf.Client.DNSServeIP != "", conf.Conf.Client.PortForward)
|
||||
|
||||
default:
|
||||
fmt.Println()
|
||||
flag.Usage()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/wweir/sower/proxy/parser"
|
||||
"github.com/wweir/sower/proxy/shadow"
|
||||
"github.com/wweir/sower/proxy/socks5"
|
||||
"github.com/wweir/sower/proxy/transport"
|
||||
)
|
||||
|
||||
func StartClient(tran transport.Transport, isSocks5 bool, server, cipher, password, listenIP string) {
|
||||
conn80 := listenLocal(listenIP, "80")
|
||||
conn443 := listenLocal(listenIP, "443")
|
||||
var isHttp bool
|
||||
var conn net.Conn
|
||||
|
||||
glog.Infoln("Client started.")
|
||||
for {
|
||||
select {
|
||||
case conn = <-conn80:
|
||||
isHttp = true
|
||||
case conn = <-conn443:
|
||||
isHttp = false
|
||||
}
|
||||
|
||||
resolveAddr(&server)
|
||||
glog.V(1).Infof("new conn from (%s) to (%s)", conn.RemoteAddr(), server)
|
||||
|
||||
rc, err := tran.Dial(server)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
glog.Errorln(err)
|
||||
continue
|
||||
}
|
||||
|
||||
switch {
|
||||
case isSocks5 && isHttp:
|
||||
c, host, port, err := parser.ParseHttpAddr(conn)
|
||||
if err != nil {
|
||||
c.Close()
|
||||
rc.Close()
|
||||
glog.Errorln(err)
|
||||
continue
|
||||
}
|
||||
|
||||
conn = c
|
||||
rc = socks5.ToSocks5(rc, host, port)
|
||||
|
||||
case isSocks5 && !isHttp:
|
||||
c, host, err := parser.ParseHttpsHost(conn)
|
||||
if err != nil {
|
||||
c.Close()
|
||||
rc.Close()
|
||||
glog.Errorln(err)
|
||||
continue
|
||||
}
|
||||
|
||||
conn = c
|
||||
rc = socks5.ToSocks5(rc, host, "443")
|
||||
|
||||
case !isSocks5 && isHttp:
|
||||
rc = shadow.Shadow(rc, cipher, password)
|
||||
rc = parser.NewHttpConn(rc)
|
||||
|
||||
case !isSocks5 && !isHttp:
|
||||
rc = shadow.Shadow(rc, cipher, password)
|
||||
rc = parser.NewHttpsConn(rc, "443")
|
||||
}
|
||||
|
||||
go relay(conn, rc)
|
||||
}
|
||||
}
|
||||
|
||||
func listenLocal(listenIP string, port string) <-chan net.Conn {
|
||||
connCh := make(chan net.Conn, 10)
|
||||
go func() {
|
||||
ln, err := net.Listen("tcp", net.JoinHostPort(listenIP, port))
|
||||
if err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
glog.Errorln("accept", listenIP+port, "fail:", err)
|
||||
continue
|
||||
}
|
||||
|
||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
connCh <- conn
|
||||
}
|
||||
}()
|
||||
|
||||
glog.Infoln("listening port:", port)
|
||||
return connCh
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package dns
|
||||
package dhcp
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
@@ -9,14 +9,13 @@ import (
|
||||
"github.com/krolaw/dhcp4"
|
||||
"github.com/libp2p/go-reuseport"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wweir/sower/util"
|
||||
)
|
||||
|
||||
var xid = make([]byte, 4)
|
||||
var broadcastAddr, _ = net.ResolveUDPAddr("udp", "255.255.255.255:67")
|
||||
|
||||
func GetDefaultDNSServer() (string, error) {
|
||||
iface, err := util.PickInterface()
|
||||
iface, err := PickInternetInterface()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "pick interface")
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package dhcp_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/wweir/sower/proxy/dhcp"
|
||||
)
|
||||
|
||||
func Example_dns() {
|
||||
got, err := dhcp.GetDefaultDNSServer()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(got)
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
// +build !windows
|
||||
|
||||
package util
|
||||
package dhcp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
)
|
||||
|
||||
// PickInterface pick the first active net interface
|
||||
func PickInterface() (*Iface, error) {
|
||||
// PickInternetInterface pick the first active net interface
|
||||
func PickInternetInterface() (*Iface, error) {
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -0,0 +1,15 @@
|
||||
package dhcp_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/wweir/sower/proxy/dhcp"
|
||||
)
|
||||
|
||||
func Example_iface() {
|
||||
got, err := dhcp.PickInternetInterface()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(got)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
// +build windows
|
||||
|
||||
package util
|
||||
package dhcp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// PickInterface pick the first active net interface
|
||||
func PickInterface() (*Iface, error) {
|
||||
// PickInternetInterface pick the first active net interface
|
||||
func PickInternetInterface() (*Iface, error) {
|
||||
list, err := getAdapterList()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -0,0 +1,9 @@
|
||||
package dhcp
|
||||
|
||||
import "net"
|
||||
|
||||
// Iface is net interface address info
|
||||
type Iface struct {
|
||||
net.HardwareAddr
|
||||
net.IP
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/wweir/sower/proxy/dhcp"
|
||||
"github.com/wweir/utils/log"
|
||||
)
|
||||
|
||||
func StartDNS(redirectIP, relayServer string, shouldProxy func(string) bool) {
|
||||
serveIP := net.ParseIP(redirectIP)
|
||||
if redirectIP == "" || serveIP.String() != redirectIP {
|
||||
log.Fatalw("invalid listen ip", "ip", redirectIP)
|
||||
}
|
||||
|
||||
var err error
|
||||
if relayServer, err = pickRelayAddr(relayServer); err != nil {
|
||||
log.Fatalw("pick upstream dns server", "err", err)
|
||||
}
|
||||
log.Infow("detect upstream dns", "addr", relayServer)
|
||||
|
||||
dns.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) {
|
||||
// *Msg r has an TSIG record and it was validated
|
||||
if r.IsTsig() != nil && w.TsigStatus() == nil {
|
||||
lastTsig := r.Extra[len(r.Extra)-1].(*dns.TSIG)
|
||||
r.SetTsig(lastTsig.Hdr.Name, dns.HmacMD5, 300, time.Now().Unix())
|
||||
}
|
||||
|
||||
//https://stackoverflow.com/questions/4082081/requesting-a-and-aaaa-records-in-single-dns-query/4083071#4083071
|
||||
if len(r.Question) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
domain := r.Question[0].Name
|
||||
if idx := strings.IndexByte(domain, ':'); idx > 0 {
|
||||
domain = domain[:idx] // trim port
|
||||
}
|
||||
|
||||
if shouldProxy(domain) {
|
||||
w.WriteMsg(localA(r, domain, serveIP))
|
||||
|
||||
} else if msg, err := dns.Exchange(r, relayServer); err != nil || msg == nil {
|
||||
server, err := pickRelayAddr(relayServer)
|
||||
if err != nil {
|
||||
log.Errorw("detect upstream dns", "err", err)
|
||||
} else if relayServer != server {
|
||||
relayServer = server
|
||||
log.Infow("detect upstream dns", "addr", relayServer)
|
||||
}
|
||||
|
||||
} else {
|
||||
w.WriteMsg(msg)
|
||||
}
|
||||
})
|
||||
|
||||
server := &dns.Server{Addr: net.JoinHostPort(redirectIP, "53"), Net: "udp"}
|
||||
log.Infow("start dns", "addr", server.Addr)
|
||||
log.Fatalw("dns serve fail", "err", server.ListenAndServe())
|
||||
}
|
||||
|
||||
func pickRelayAddr(relayServer string) (_ string, err error) {
|
||||
if relayServer == "" {
|
||||
if relayServer, err = dhcp.GetDefaultDNSServer(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if _, _, err := net.SplitHostPort(relayServer); err != nil {
|
||||
return net.JoinHostPort(relayServer, "53"), nil
|
||||
}
|
||||
return relayServer, nil
|
||||
}
|
||||
|
||||
func localA(r *dns.Msg, domain string, localIP net.IP) *dns.Msg {
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(r)
|
||||
if localIP.To4() != nil {
|
||||
m.Answer = []dns.RR{&dns.A{
|
||||
Hdr: dns.RR_Header{Name: domain, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 20},
|
||||
A: localIP,
|
||||
}}
|
||||
} else {
|
||||
m.Answer = []dns.RR{&dns.AAAA{
|
||||
Hdr: dns.RR_Header{Name: domain, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 20},
|
||||
AAAA: localIP,
|
||||
}}
|
||||
}
|
||||
return m
|
||||
}
|
||||
+36
-86
@@ -3,29 +3,34 @@ package proxy
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"net/http/httputil"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/wweir/sower/proxy/parser"
|
||||
"github.com/wweir/sower/proxy/shadow"
|
||||
"github.com/wweir/sower/proxy/socks5"
|
||||
"github.com/wweir/sower/proxy/transport"
|
||||
"github.com/wweir/sower/transport"
|
||||
"github.com/wweir/utils/log"
|
||||
)
|
||||
|
||||
func StartHttpProxy(tran transport.Transport, isSocks5 bool, server, cipher, password, addr string) {
|
||||
srv := &http.Server{
|
||||
Addr: addr,
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
resolveAddr(&server)
|
||||
// StartHTTPProxy start http reverse proxy.
|
||||
// The httputil.ReverseProxy do not supply enough support for https request.
|
||||
func StartHTTPProxy(httpProxyAddr, serverAddr string, password []byte, shouldProxy func(string) bool) {
|
||||
proxy := httputil.ReverseProxy{
|
||||
Director: func(r *http.Request) {},
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
addr, _ = withDefaultPort(addr, "80")
|
||||
return transport.Dial(serverAddr, addr, password)
|
||||
}},
|
||||
}
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: httpProxyAddr,
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodConnect {
|
||||
httpsProxy(w, r, tran, isSocks5, server, cipher, password)
|
||||
httpsProxy(w, r, serverAddr, password, shouldProxy)
|
||||
} else {
|
||||
httpProxy(w, r, tran, isSocks5, server, cipher, password)
|
||||
proxy.ServeHTTP(w, r)
|
||||
}
|
||||
}),
|
||||
// Disable HTTP/2.
|
||||
@@ -33,95 +38,40 @@ func StartHttpProxy(tran transport.Transport, isSocks5 bool, server, cipher, pas
|
||||
IdleTimeout: 90 * time.Second,
|
||||
}
|
||||
|
||||
glog.Fatalln(srv.ListenAndServe())
|
||||
}
|
||||
|
||||
func httpProxy(w http.ResponseWriter, r *http.Request,
|
||||
tran transport.Transport, isSocks5 bool, server, cipher, password string) {
|
||||
|
||||
roundTripper := &http.Transport{
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
}
|
||||
|
||||
if isSocks5 {
|
||||
roundTripper.Proxy = func(*http.Request) (*url.URL, error) {
|
||||
return url.Parse("socks5://" + server)
|
||||
}
|
||||
|
||||
} else {
|
||||
roundTripper.DialContext = func(context.Context, string, string) (net.Conn, error) {
|
||||
conn, err := tran.Dial(server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn = shadow.Shadow(conn, cipher, password)
|
||||
return parser.NewHttpConn(conn), nil
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := roundTripper.RoundTrip(r)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
||||
glog.Errorln("serve https proxy, get remote data:", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
for k, vs := range resp.Header {
|
||||
for _, v := range vs {
|
||||
w.Header().Add(k, v)
|
||||
}
|
||||
}
|
||||
w.WriteHeader(resp.StatusCode)
|
||||
io.Copy(w, resp.Body)
|
||||
log.Infow("start sower http proxy", "http_proxy", httpProxyAddr)
|
||||
go log.Fatalw("serve http proxy", "addr", httpProxyAddr, "err", srv.ListenAndServe())
|
||||
}
|
||||
|
||||
func httpsProxy(w http.ResponseWriter, r *http.Request,
|
||||
tran transport.Transport, isSocks5 bool, server, cipher, password string) {
|
||||
serverAddr string, password []byte, shouldProxy func(string) bool) {
|
||||
|
||||
target, host := withDefaultPort(r.Host, "443")
|
||||
|
||||
// local conn
|
||||
conn, _, err := w.(http.Hijacker).Hijack()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
defer conn.Close()
|
||||
|
||||
if _, err := conn.Write([]byte(r.Proto + " 200 Connection established\r\n\r\n")); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
||||
conn.Close()
|
||||
glog.Errorln("serve https proxy, write data fail:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// remote conn
|
||||
rc, err := tran.Dial(server)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
||||
conn.Close()
|
||||
glog.Errorln("serve https proxy, dial remote fail:", err)
|
||||
return
|
||||
}
|
||||
|
||||
host, port, err := net.SplitHostPort(r.Host)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
||||
conn.Close()
|
||||
glog.Errorln("serve https proxy, dial remote fail:", err)
|
||||
return
|
||||
}
|
||||
|
||||
if isSocks5 {
|
||||
rc = socks5.ToSocks5(rc, host, port)
|
||||
|
||||
var rc net.Conn
|
||||
if shouldProxy(host) {
|
||||
rc, err = transport.Dial(serverAddr, target, password)
|
||||
} else {
|
||||
rc = shadow.Shadow(rc, cipher, password)
|
||||
rc = parser.NewHttpsConn(rc, port)
|
||||
rc, err = net.Dial("tcp", target)
|
||||
}
|
||||
if err != nil {
|
||||
conn.Write([]byte("sower dial " + serverAddr + " fail: " + err.Error()))
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
relay(rc, conn)
|
||||
relay(conn, rc)
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// Package parser transter conn to be a parser conn
|
||||
//
|
||||
// init request payload:
|
||||
// <type>(1) + <size>(2))(+Overhead) + <data>(size+Overhead)
|
||||
// data definition:
|
||||
// 0x00(any): [size](1) + [addr:port] + content
|
||||
// 0x01(http): content
|
||||
// 0x02(https): [port](2) + content
|
||||
//
|
||||
// init response payload:
|
||||
// ([status code](2) + <size>(2))(+Overhead) + <content>(size+Overhead)
|
||||
package parser
|
||||
@@ -1,163 +0,0 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wweir/sower/util"
|
||||
)
|
||||
|
||||
const (
|
||||
OTHER byte = iota
|
||||
HTTP
|
||||
HTTPS
|
||||
)
|
||||
|
||||
// Write Addr
|
||||
type conn struct {
|
||||
typ byte
|
||||
domain string
|
||||
port string
|
||||
init bool
|
||||
net.Conn
|
||||
}
|
||||
|
||||
func NewOtherConn(c net.Conn, domain, port string) net.Conn {
|
||||
return &conn{
|
||||
typ: OTHER,
|
||||
domain: domain,
|
||||
port: port,
|
||||
init: true,
|
||||
Conn: c,
|
||||
}
|
||||
}
|
||||
func NewHttpConn(c net.Conn) net.Conn {
|
||||
return &conn{
|
||||
typ: HTTP,
|
||||
init: true,
|
||||
Conn: c,
|
||||
}
|
||||
}
|
||||
func NewHttpsConn(c net.Conn, port string) net.Conn {
|
||||
return &conn{
|
||||
typ: HTTPS,
|
||||
port: port,
|
||||
init: true,
|
||||
Conn: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *conn) Write(b []byte) (n int, err error) {
|
||||
if c.init {
|
||||
var pkg []byte
|
||||
var prefixLen int
|
||||
switch c.typ {
|
||||
case OTHER:
|
||||
// type + domain + ':' + port + data
|
||||
prefixLen = 1 + len(c.domain) + 1 + len(c.port)
|
||||
pkg = make([]byte, 0, prefixLen+len(b))
|
||||
pkg = append(pkg, OTHER)
|
||||
pkg = append(pkg, byte(len(c.domain)+1+len(c.port)))
|
||||
pkg = append(pkg, []byte(c.domain+":"+c.port)...)
|
||||
|
||||
case HTTP:
|
||||
// type + data
|
||||
prefixLen = 1
|
||||
pkg = make([]byte, 0, prefixLen+len(b))
|
||||
pkg = append(pkg, HTTP)
|
||||
|
||||
case HTTPS:
|
||||
// type + port + data
|
||||
prefixLen = 1 + 2
|
||||
pkg = make([]byte, 0, prefixLen+len(b))
|
||||
pkg = append(pkg, HTTPS)
|
||||
port, _ := strconv.Atoi(c.port)
|
||||
pkg = append(pkg, byte(port>>8), byte(port))
|
||||
}
|
||||
|
||||
c.init = false
|
||||
n, err := c.Conn.Write(append(pkg, b...))
|
||||
// n should larger than prefix length, if not, err is not nil
|
||||
return n - prefixLen, err
|
||||
}
|
||||
|
||||
return c.Conn.Write(b)
|
||||
}
|
||||
|
||||
// Read Addr
|
||||
func ParseAddr(conn net.Conn) (net.Conn, string, string, error) {
|
||||
buf := make([]byte, 1)
|
||||
if _, err := io.ReadFull(conn, buf); err != nil {
|
||||
return conn, "", "", err
|
||||
}
|
||||
|
||||
switch buf[0] {
|
||||
case OTHER:
|
||||
if _, err := io.ReadFull(conn, buf); err != nil {
|
||||
return conn, "", "", err
|
||||
}
|
||||
buf = make([]byte, int(buf[0]))
|
||||
if _, err := io.ReadFull(conn, buf); err != nil {
|
||||
return conn, "", "", err
|
||||
}
|
||||
|
||||
addr := string(buf)
|
||||
if idx := strings.LastIndex(addr, ":"); idx != -1 {
|
||||
return conn, addr[:idx], addr[idx+1:], nil
|
||||
}
|
||||
return conn, "", "", errors.New("invalid payload")
|
||||
|
||||
case HTTP:
|
||||
return ParseHttpAddr(conn)
|
||||
|
||||
case HTTPS:
|
||||
buf = make([]byte, 2)
|
||||
if _, err := io.ReadFull(conn, buf); err != nil {
|
||||
return conn, "", "", err
|
||||
}
|
||||
port := strconv.Itoa(int(buf[0])<<8 + int(buf[1]))
|
||||
|
||||
conn, domain, err := ParseHttpsHost(conn)
|
||||
return conn, domain, port, err
|
||||
|
||||
default:
|
||||
return conn, "", "", errors.Errorf("not supported type (%v)", buf[0])
|
||||
}
|
||||
}
|
||||
|
||||
func ParseHttpAddr(conn net.Conn) (net.Conn, string, string, error) {
|
||||
teeConn := &util.TeeConn{Conn: conn}
|
||||
teeConn.StartOrReset()
|
||||
defer teeConn.Stop()
|
||||
|
||||
b := bufio.NewReader(teeConn)
|
||||
resp, err := http.ReadRequest(b)
|
||||
if err != nil {
|
||||
return teeConn, "", "", err
|
||||
}
|
||||
|
||||
if idx := strings.LastIndex(resp.Host, ":"); idx != -1 {
|
||||
return teeConn, resp.Host[:idx], resp.Host[idx+1:], nil
|
||||
}
|
||||
return teeConn, resp.Host, "80", nil
|
||||
}
|
||||
|
||||
func ParseHttpsHost(conn net.Conn) (net.Conn, string, error) {
|
||||
teeConn := &util.TeeConn{Conn: conn}
|
||||
teeConn.StartOrReset()
|
||||
defer teeConn.Stop()
|
||||
|
||||
domain, _, err := extractSNI(teeConn)
|
||||
if err != nil {
|
||||
return teeConn, "", err
|
||||
} else if domain == "" {
|
||||
return teeConn, "", errors.New("ClientHello did not present an SNI extension")
|
||||
}
|
||||
|
||||
return teeConn, domain, nil
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/wweir/sower/proxy/shadow"
|
||||
"github.com/wweir/sower/util"
|
||||
)
|
||||
|
||||
func TestParseAddr1(t *testing.T) {
|
||||
c1, c2 := net.Pipe()
|
||||
|
||||
go func() {
|
||||
c1 = NewHttpConn(c1)
|
||||
req, _ := http.NewRequest("GET", "http://wweir.cc", bytes.NewReader([]byte{1, 2, 3}))
|
||||
req.Write(c1)
|
||||
}()
|
||||
|
||||
c2, host, port, err := ParseAddr(c2)
|
||||
|
||||
if err != nil || host != "wweir.cc" || port != "80" {
|
||||
t.Error(err, host, port)
|
||||
}
|
||||
|
||||
req, err := http.ReadRequest(bufio.NewReader(c2))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil || len(data) != 3 || data[0] != 1 {
|
||||
t.Error(err, data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAddr2(t *testing.T) {
|
||||
c1, c2 := net.Pipe()
|
||||
|
||||
go func() {
|
||||
c1 = NewHttpsConn(c1, "443")
|
||||
c1.Write(util.HTTPS.PingMsg("wweir.cc"))
|
||||
}()
|
||||
|
||||
_, host, port, err := ParseAddr(c2)
|
||||
|
||||
if err != nil || host != "wweir.cc" || port != "443" {
|
||||
t.Error(err, host, port)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAddr3(t *testing.T) {
|
||||
c1, c2 := net.Pipe()
|
||||
|
||||
go func() {
|
||||
c1 = NewOtherConn(c1, "wweir.cc", "1080")
|
||||
c1.Write(util.HTTPS.PingMsg("wweir.cc"))
|
||||
}()
|
||||
|
||||
_, host, port, err := ParseAddr(c2)
|
||||
|
||||
if err != nil || host != "wweir.cc" || port != "1080" {
|
||||
t.Error(err, host, port)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAddr4(t *testing.T) {
|
||||
c1, c2 := net.Pipe()
|
||||
|
||||
go func() {
|
||||
c1 = shadow.Shadow(c1, "AES_128_GCM", "12345678")
|
||||
c1 = NewHttpConn(c1)
|
||||
req, _ := http.NewRequest("GET", "http://wweir.cc", bytes.NewReader([]byte{1, 2, 3}))
|
||||
req.Write(c1)
|
||||
}()
|
||||
|
||||
c2 = shadow.Shadow(c2, "AES_128_GCM", "12345678")
|
||||
c2, host, port, err := ParseAddr(c2)
|
||||
|
||||
if err != nil || host != "wweir.cc" || port != "80" {
|
||||
t.Error(err, host, port)
|
||||
}
|
||||
|
||||
req, err := http.ReadRequest(bufio.NewReader(c2))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil || len(data) != 3 || data[0] != 1 {
|
||||
t.Error(err, data)
|
||||
}
|
||||
}
|
||||
@@ -1,232 +0,0 @@
|
||||
// Copyright 2016 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package parser
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
func extractSNI(r io.Reader) (string, int, error) {
|
||||
handshake, tlsver, err := handshakeRecord(r)
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("reading TLS record: %s", err)
|
||||
}
|
||||
|
||||
sni, err := parseHello(handshake)
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("reading ClientHello: %s", err)
|
||||
}
|
||||
if len(sni) == 0 {
|
||||
// ClientHello did not present an SNI extension. Valid packet,
|
||||
// no hostname.
|
||||
return "", tlsver, nil
|
||||
}
|
||||
|
||||
hostname, err := parseSNI(sni)
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("parsing SNI extension: %s", err)
|
||||
}
|
||||
return hostname, tlsver, nil
|
||||
}
|
||||
|
||||
// Extract the indicated hostname, if any, from the given SNI
|
||||
// extension bytes.
|
||||
func parseSNI(b []byte) (string, error) {
|
||||
b, _, err := vector(b, 2)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var ret []byte
|
||||
for len(b) >= 3 {
|
||||
typ := b[0]
|
||||
ret, b, err = vector(b[1:], 2)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("truncated SNI extension")
|
||||
}
|
||||
|
||||
if typ == sniHostnameID {
|
||||
return string(ret), nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(b) != 0 {
|
||||
return "", fmt.Errorf("trailing garbage at end of SNI extension")
|
||||
}
|
||||
|
||||
// No DNS-based SNI present.
|
||||
return "", nil
|
||||
}
|
||||
|
||||
const sniExtensionID = 0
|
||||
const sniHostnameID = 0
|
||||
|
||||
// Parse a TLS handshake record as a ClientHello message and extract
|
||||
// the SNI extension bytes, if any.
|
||||
func parseHello(b []byte) ([]byte, error) {
|
||||
if len(b) == 0 {
|
||||
return nil, errors.New("zero length handshake record")
|
||||
}
|
||||
if b[0] != 1 {
|
||||
return nil, fmt.Errorf("non-ClientHello handshake record type %d", b[0])
|
||||
}
|
||||
|
||||
// We're expecting a stricter TLS parser to run after we've
|
||||
// proxied, so we ignore any trailing bytes that might be present
|
||||
// (e.g. another handshake message).
|
||||
b, _, err := vector(b[1:], 3)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading ClientHello: %s", err)
|
||||
}
|
||||
|
||||
// ClientHello must be at least 34 bytes to reach the first vector
|
||||
// length byte. The actual minimal size is larger than that, but
|
||||
// vector() will correctly handle truncated packets.
|
||||
if len(b) < 34 {
|
||||
return nil, errors.New("ClientHello packet too short")
|
||||
}
|
||||
|
||||
if b[0] != 3 {
|
||||
return nil, fmt.Errorf("ClientHello has unsupported version %d.%d", b[0], b[1])
|
||||
}
|
||||
switch b[1] {
|
||||
case 1, 2, 3:
|
||||
// TLS 1.0, TLS 1.1, TLS 1.2
|
||||
default:
|
||||
return nil, fmt.Errorf("TLS record has unsupported version %d.%d", b[0], b[1])
|
||||
}
|
||||
|
||||
// Skip over version and random struct
|
||||
b = b[34:]
|
||||
|
||||
// We don't technically care about SessionID, but we care that the
|
||||
// framing is well-formed all the way up to the SNI field, so that
|
||||
// we are sure that we're pulling the same SNI bytes as the
|
||||
// eventual TLS implementation.
|
||||
vec, b, err := vector(b, 1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading ClientHello SessionID: %s", err)
|
||||
}
|
||||
if len(vec) > 32 {
|
||||
return nil, fmt.Errorf("ClientHello SessionID too long (%db)", len(vec))
|
||||
}
|
||||
|
||||
// Likewise, we're just checking the bare minimum of framing.
|
||||
vec, b, err = vector(b, 2)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading ClientHello CipherSuites: %s", err)
|
||||
}
|
||||
if len(vec) < 2 || len(vec)%2 != 0 {
|
||||
return nil, fmt.Errorf("ClientHello CipherSuites invalid length %d", len(vec))
|
||||
}
|
||||
|
||||
vec, b, err = vector(b, 1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading ClientHello CompressionMethods: %s", err)
|
||||
}
|
||||
if len(vec) < 1 {
|
||||
return nil, fmt.Errorf("ClientHello CompressionMethods invalid length %d", len(vec))
|
||||
}
|
||||
|
||||
// Finally, we reach the extensions.
|
||||
if len(b) == 0 {
|
||||
// No extensions. This is not an error, it just means we have
|
||||
// no SNI payload.
|
||||
return nil, nil
|
||||
}
|
||||
b, vec, err = vector(b, 2)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading ClientHello extensions: %s", err)
|
||||
}
|
||||
if len(vec) != 0 {
|
||||
return nil, fmt.Errorf("%d bytes of trailing garbage in ClientHello", len(vec))
|
||||
}
|
||||
|
||||
for len(b) >= 4 {
|
||||
typ := binary.BigEndian.Uint16(b[:2])
|
||||
vec, b, err = vector(b[2:], 2)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading ClientHello extension %d: %s", typ, err)
|
||||
}
|
||||
if typ == sniExtensionID {
|
||||
// Found the SNI extension, return its payload. We don't
|
||||
// care about anything in the packet beyond this point.
|
||||
return vec, nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(b) != 0 {
|
||||
return nil, fmt.Errorf("%d bytes of trailing garbage in ClientHello", len(b))
|
||||
}
|
||||
|
||||
// Successfully parsed all extensions, but there was no SNI.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
const maxTLSRecordLength = 16384
|
||||
|
||||
// Read one TLS record, which must be for the handshake protocol, from r.
|
||||
func handshakeRecord(r io.Reader) ([]byte, int, error) {
|
||||
var hdr struct {
|
||||
Type uint8
|
||||
Major, Minor uint8
|
||||
Length uint16
|
||||
}
|
||||
if err := binary.Read(r, binary.BigEndian, &hdr); err != nil {
|
||||
return nil, 0, fmt.Errorf("reading TLS record header: %s", err)
|
||||
}
|
||||
|
||||
if hdr.Type != 22 {
|
||||
return nil, 0, fmt.Errorf("TLS record is not a handshake")
|
||||
}
|
||||
|
||||
if hdr.Major != 3 {
|
||||
return nil, 0, fmt.Errorf("TLS record has unsupported version %d.%d", hdr.Major, hdr.Minor)
|
||||
}
|
||||
switch hdr.Minor {
|
||||
case 1, 2, 3:
|
||||
// TLS 1.0, TLS 1.1, TLS 1.2
|
||||
default:
|
||||
return nil, 0, fmt.Errorf("TLS record has unsupported version %d.%d", hdr.Major, hdr.Minor)
|
||||
}
|
||||
|
||||
if hdr.Length > maxTLSRecordLength {
|
||||
return nil, 0, fmt.Errorf("TLS record length is greater than %d", maxTLSRecordLength)
|
||||
}
|
||||
|
||||
ret := make([]byte, hdr.Length)
|
||||
if _, err := io.ReadFull(r, ret); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return ret, int(hdr.Minor), nil
|
||||
}
|
||||
|
||||
func vector(b []byte, lenBytes int) ([]byte, []byte, error) {
|
||||
if len(b) < lenBytes {
|
||||
return nil, nil, errors.New("not enough space in packet for vector")
|
||||
}
|
||||
var l int
|
||||
for _, b := range b[:lenBytes] {
|
||||
l = (l << 8) + int(b)
|
||||
}
|
||||
if len(b) < l+lenBytes {
|
||||
return nil, nil, errors.New("not enough space in packet for vector")
|
||||
}
|
||||
return b[lenBytes : l+lenBytes], b[l+lenBytes:], nil
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/wweir/sower/transport"
|
||||
"github.com/wweir/utils/log"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
)
|
||||
|
||||
func StartClient(serverAddr, password string, enableDNS bool, forwards map[string]string) {
|
||||
passwordData := []byte(password)
|
||||
|
||||
relayToRemote := func(lnAddr, target string,
|
||||
parseFn func(net.Conn) (net.Conn, string, error)) {
|
||||
|
||||
ln, err := net.Listen("tcp", lnAddr)
|
||||
if err != nil {
|
||||
log.Fatalw("tcp listen", "port", lnAddr, "err", err)
|
||||
}
|
||||
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
log.Errorw("tcp accept", "port", lnAddr, "err", err)
|
||||
continue
|
||||
}
|
||||
|
||||
go func(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
if parseFn != nil {
|
||||
if conn, target, err = parseFn(conn); err != nil {
|
||||
log.Warnw("parse target", "err", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
rc, err := transport.Dial(serverAddr, target, passwordData)
|
||||
if err != nil {
|
||||
log.Warnw("dial", "addr", serverAddr, "err", err)
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
relay(conn, rc)
|
||||
}(conn)
|
||||
}
|
||||
}
|
||||
|
||||
for from, to := range forwards {
|
||||
go relayToRemote(from, to, nil)
|
||||
}
|
||||
|
||||
if enableDNS {
|
||||
go relayToRemote(":80", "", ParseHTTP)
|
||||
go relayToRemote(":443", "", ParseHTTPS)
|
||||
}
|
||||
|
||||
log.Infow("start sower client", "dns solution", enableDNS, "forwards", forwards)
|
||||
|
||||
select {}
|
||||
}
|
||||
|
||||
func StartServer(relayTarget, password, cacheDir, certFile, keyFile, email string) {
|
||||
dir, _ := os.UserCacheDir()
|
||||
dir = filepath.Join("/", dir, "sower")
|
||||
log.Infow("certificate cache dir", "dir", dir)
|
||||
|
||||
certManager := autocert.Manager{
|
||||
Prompt: autocert.AcceptTOS,
|
||||
Email: email,
|
||||
Cache: autocert.DirCache(dir),
|
||||
}
|
||||
|
||||
tlsConf := &tls.Config{
|
||||
GetCertificate: certManager.GetCertificate,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
NextProtos: []string{"http/1.1", "h2"},
|
||||
}
|
||||
if certFile != "" && keyFile != "" {
|
||||
if cert, err := tls.LoadX509KeyPair(certFile, keyFile); err != nil {
|
||||
log.Fatalw("load certificate", "cert", certFile, "key", keyFile, "err", err)
|
||||
} else {
|
||||
tlsConf.GetCertificate = nil
|
||||
tlsConf.Certificates = []tls.Certificate{cert}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to redirect 80 to 443
|
||||
go http.ListenAndServe(":80", certManager.HTTPHandler(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
if host, _, err := net.SplitHostPort(r.Host); err != nil {
|
||||
r.URL.Host = r.Host
|
||||
} else {
|
||||
r.URL.Host = host
|
||||
}
|
||||
r.URL.Scheme = "https"
|
||||
http.Redirect(w, r, r.URL.String(), 301)
|
||||
})))
|
||||
|
||||
ln, err := tls.Listen("tcp", ":443", tlsConf)
|
||||
if err != nil {
|
||||
log.Fatalw("tcp listen", "err", err)
|
||||
}
|
||||
|
||||
log.Infow("start sower server", "relay_to", relayTarget)
|
||||
|
||||
passwordData := []byte(password)
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
log.Errorw("tcp accept", "err", err)
|
||||
continue
|
||||
}
|
||||
|
||||
go func(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
conn, target := transport.ToProxyConn(conn, passwordData)
|
||||
if target == "" {
|
||||
target = relayTarget
|
||||
}
|
||||
|
||||
rc, err := net.Dial("tcp", target)
|
||||
if err != nil {
|
||||
log.Errorw("tcp dial", "addr", target, "err", err)
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
relay(conn, rc)
|
||||
}(conn)
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/wweir/sower/proxy/parser"
|
||||
"github.com/wweir/sower/proxy/shadow"
|
||||
"github.com/wweir/sower/proxy/transport"
|
||||
)
|
||||
|
||||
func StartServer(tran transport.Transport, port, cipher, password string) {
|
||||
if port == "" {
|
||||
glog.Fatalln("port must set")
|
||||
}
|
||||
if !strings.HasPrefix(port, ":") {
|
||||
port = ":" + port
|
||||
}
|
||||
|
||||
connCh, err := tran.Listen(port)
|
||||
if err != nil {
|
||||
glog.Fatalf("listen %v fail: %s", port, err)
|
||||
}
|
||||
|
||||
glog.Infoln("Server started.")
|
||||
for {
|
||||
go handle(<-connCh, cipher, password)
|
||||
}
|
||||
}
|
||||
|
||||
func handle(conn net.Conn, cipher, password string) {
|
||||
conn = shadow.Shadow(conn, cipher, password)
|
||||
conn, host, port, err := parser.ParseAddr(conn)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
glog.Warningln(err)
|
||||
return
|
||||
}
|
||||
glog.V(1).Infof("new conn from %s to %s:%s", conn.RemoteAddr(), host, port)
|
||||
|
||||
rc, err := net.Dial("tcp", net.JoinHostPort(host, port))
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
glog.Warningln(err)
|
||||
return
|
||||
}
|
||||
rc.(*net.TCPConn).SetKeepAlive(true)
|
||||
|
||||
relay(rc, conn)
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package shadow
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
)
|
||||
|
||||
//go:generate stringer -type=typ $GOFILE
|
||||
type typ int
|
||||
|
||||
const (
|
||||
AES_128_GCM typ = iota
|
||||
AES_192_GCM
|
||||
AES_256_GCM
|
||||
CHACHA20_IETF_POLY1305
|
||||
XCHACHA20_IETF_POLY1305
|
||||
cipherEnd
|
||||
)
|
||||
|
||||
func ListCiphers() []string {
|
||||
list := make([]string, 0, int(cipherEnd))
|
||||
for i := typ(0); i < cipherEnd; i++ {
|
||||
list = append(list, i.String())
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func pickCipher(typ, password string) (cipher.AEAD, error) {
|
||||
var blockSize int
|
||||
switch typ {
|
||||
case AES_128_GCM.String():
|
||||
blockSize = 16
|
||||
case AES_192_GCM.String():
|
||||
blockSize = 24
|
||||
case AES_256_GCM.String():
|
||||
blockSize = 32
|
||||
|
||||
case CHACHA20_IETF_POLY1305.String():
|
||||
return chacha20poly1305.New(genKey(password, 256))
|
||||
case XCHACHA20_IETF_POLY1305.String():
|
||||
return chacha20poly1305.NewX(genKey(password, 256))
|
||||
|
||||
default:
|
||||
return nil, errors.New("do not support cipher type: " + typ)
|
||||
}
|
||||
|
||||
// aes gcm
|
||||
block, err := aes.NewCipher(genKey(password, blockSize))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "password")
|
||||
}
|
||||
|
||||
aead, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, AES_128_GCM.String())
|
||||
}
|
||||
return aead, nil
|
||||
}
|
||||
|
||||
func genKey(filler string, size int) []byte {
|
||||
res := make([]byte, size)
|
||||
if filler == "" {
|
||||
panic("password should not be empty")
|
||||
}
|
||||
|
||||
fillerByte := []byte(filler)
|
||||
length := len(fillerByte)
|
||||
for i := 0; ; i++ {
|
||||
if copy(res[i*length:], fillerByte) != length {
|
||||
return res
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
// Package shadow transter conn to be a crypto conn
|
||||
// support aead mode only
|
||||
// data payload:
|
||||
// <size>(2+Overhead) + <content>(size+Overhead)
|
||||
package shadow
|
||||
@@ -1,111 +0,0 @@
|
||||
package shadow
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
)
|
||||
|
||||
const MAX_SIZE = 0xFFFF
|
||||
|
||||
type conn struct {
|
||||
maxSize int
|
||||
aead cipher.AEAD
|
||||
encryptNonce func() []byte
|
||||
decryptNonce func() []byte
|
||||
writeBuf []byte
|
||||
readBuf []byte
|
||||
readOffset int
|
||||
net.Conn
|
||||
}
|
||||
|
||||
func (c *conn) Read(b []byte) (n int, err error) {
|
||||
// read from buffer
|
||||
if c.readOffset != 0 {
|
||||
dataSize := len(c.readBuf) - c.aead.Overhead()
|
||||
n = copy(b, c.readBuf[c.readOffset:dataSize])
|
||||
c.readOffset += n
|
||||
|
||||
if c.readOffset == dataSize {
|
||||
c.readOffset = 0
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// read from conn
|
||||
dataSize := 0
|
||||
{ //read data size
|
||||
c.readBuf = make([]byte, 2+c.aead.Overhead())
|
||||
if _, err = io.ReadFull(c.Conn, c.readBuf); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = c.aead.Open(c.readBuf[:0], c.decryptNonce(), c.readBuf, nil); err != nil {
|
||||
return
|
||||
}
|
||||
dataSize = int(c.readBuf[0])<<8 + int(c.readBuf[1])
|
||||
}
|
||||
{ // read data
|
||||
c.readBuf = make([]byte, dataSize+c.aead.Overhead())
|
||||
if _, err = io.ReadFull(c.Conn, c.readBuf); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = c.aead.Open(c.readBuf[:0], c.decryptNonce(), c.readBuf, nil); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// buffer extra data
|
||||
if n = copy(b, c.readBuf[:dataSize]); n < dataSize {
|
||||
c.readOffset = n
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *conn) Write(b []byte) (n int, err error) {
|
||||
bLen := len(b)
|
||||
dataSize := MAX_SIZE - (2 + c.aead.Overhead()) - c.aead.Overhead()
|
||||
if bLen < c.maxSize {
|
||||
dataSize = bLen
|
||||
}
|
||||
|
||||
// BigEndian
|
||||
c.writeBuf[0], c.writeBuf[1] = byte(dataSize>>8), byte(dataSize)
|
||||
|
||||
c.aead.Seal(c.writeBuf[:0], c.encryptNonce(), c.writeBuf[:2], nil)
|
||||
c.aead.Seal(c.writeBuf[:2+c.aead.Overhead()], c.encryptNonce(), b[:dataSize], nil)
|
||||
|
||||
_, err = c.Conn.Write(c.writeBuf[:dataSize+(2+c.aead.Overhead())+c.aead.Overhead()])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return dataSize, err
|
||||
}
|
||||
|
||||
func Shadow(c net.Conn, cipher, password string) net.Conn {
|
||||
aead, err := pickCipher(cipher, password)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &conn{
|
||||
maxSize: MAX_SIZE - (2 - aead.Overhead()) - aead.Overhead(),
|
||||
aead: aead,
|
||||
encryptNonce: newNonce(password, aead.NonceSize()),
|
||||
decryptNonce: newNonce(password, aead.NonceSize()),
|
||||
writeBuf: make([]byte, 0xFFFF),
|
||||
Conn: c,
|
||||
}
|
||||
}
|
||||
|
||||
func newNonce(password string, size int) func() []byte {
|
||||
num, _ := binary.Varint([]byte(password))
|
||||
rnd := rand.New(rand.NewSource(num))
|
||||
|
||||
buf := make([]byte, size)
|
||||
return func() []byte {
|
||||
rnd.Read(buf)
|
||||
return buf
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package shadow
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestShadow(t *testing.T) {
|
||||
c1, c2 := net.Pipe()
|
||||
|
||||
go func() {
|
||||
conn := Shadow(c1, "AES_128_GCM", "12345678")
|
||||
conn.Write([]byte{1, 2})
|
||||
}()
|
||||
|
||||
conn := Shadow(c2, "AES_128_GCM", "12345678")
|
||||
buf := make([]byte, 3)
|
||||
n, _ := conn.Read(buf)
|
||||
if n!=2|| buf[0] != 1 || buf[1] != 2 {
|
||||
t.Error(buf)
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
// Code generated by "stringer -type=typ cipher.go"; DO NOT EDIT.
|
||||
|
||||
package shadow
|
||||
|
||||
import "strconv"
|
||||
|
||||
const _typ_name = "AES_128_GCMAES_192_GCMAES_256_GCMCHACHA20_IETF_POLY1305XCHACHA20_IETF_POLY1305cipherEnd"
|
||||
|
||||
var _typ_index = [...]uint8{0, 11, 22, 33, 55, 78, 87}
|
||||
|
||||
func (i typ) String() string {
|
||||
if i < 0 || i >= typ(len(_typ_index)-1) {
|
||||
return "typ(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _typ_name[_typ_index[i]:_typ_index[i+1]]
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/pkg/errors"
|
||||
kcp "github.com/xtaci/kcp-go"
|
||||
)
|
||||
|
||||
type kcpTran struct {
|
||||
client
|
||||
server
|
||||
}
|
||||
type client struct {
|
||||
DataShard int
|
||||
ParityShard int
|
||||
DSCP int
|
||||
SockBuf int
|
||||
AckNodelay bool
|
||||
NoDelay int
|
||||
Interval int
|
||||
Resend int
|
||||
NoCongestion int
|
||||
SndWnd int
|
||||
RcvWnd int
|
||||
MTU int
|
||||
}
|
||||
type server struct {
|
||||
DataShard int
|
||||
ParityShard int
|
||||
DSCP int
|
||||
SockBuf int
|
||||
}
|
||||
|
||||
func init() {
|
||||
transports["KCP"] = &kcpTran{
|
||||
client: client{
|
||||
DataShard: 10,
|
||||
ParityShard: 3,
|
||||
DSCP: 0,
|
||||
SockBuf: 4194304,
|
||||
NoDelay: 0,
|
||||
Interval: 50,
|
||||
Resend: 0,
|
||||
NoCongestion: 0,
|
||||
SndWnd: 0,
|
||||
RcvWnd: 0,
|
||||
MTU: 1350,
|
||||
},
|
||||
server: server{
|
||||
DataShard: 10,
|
||||
ParityShard: 3,
|
||||
DSCP: 0,
|
||||
SockBuf: 4194304,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) Dial(server string) (net.Conn, error) {
|
||||
conn, err := kcp.DialWithOptions(server, nil, c.DataShard, c.ParityShard)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "dial")
|
||||
}
|
||||
|
||||
conn.SetStreamMode(true)
|
||||
conn.SetWriteDelay(false)
|
||||
conn.SetNoDelay(c.NoDelay, c.Interval, c.Resend, c.NoCongestion)
|
||||
conn.SetWindowSize(c.SndWnd, c.RcvWnd)
|
||||
conn.SetMtu(c.MTU)
|
||||
conn.SetACKNoDelay(c.AckNodelay)
|
||||
|
||||
if err := conn.SetDSCP(c.DSCP); err != nil {
|
||||
return nil, errors.Wrap(err, "SetDSCP")
|
||||
}
|
||||
if err := conn.SetReadBuffer(c.SockBuf); err != nil {
|
||||
return nil, errors.Wrap(err, "SetReadBuffer")
|
||||
}
|
||||
if err := conn.SetWriteBuffer(c.SockBuf); err != nil {
|
||||
return nil, errors.Wrap(err, "SetWriteBuffer")
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (s *server) Listen(port string) (<-chan net.Conn, error) {
|
||||
ln, err := kcp.ListenWithOptions(port, nil, s.DataShard, s.ParityShard)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := ln.SetDSCP(s.DSCP); err != nil {
|
||||
return nil, errors.Wrap(err, "SetDSCP")
|
||||
}
|
||||
if err := ln.SetReadBuffer(s.SockBuf); err != nil {
|
||||
return nil, errors.Wrap(err, "SetReadBuffer")
|
||||
}
|
||||
if err := ln.SetWriteBuffer(s.SockBuf); err != nil {
|
||||
return nil, errors.Wrap(err, "SetWriteBuffer")
|
||||
}
|
||||
|
||||
connCh := make(chan net.Conn)
|
||||
go func() {
|
||||
for {
|
||||
conn, err := ln.AcceptKCP()
|
||||
if err != nil {
|
||||
glog.Fatalln("KCP listen:", err)
|
||||
}
|
||||
|
||||
connCh <- conn
|
||||
}
|
||||
}()
|
||||
|
||||
return connCh, nil
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"math/big"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
quic "github.com/lucas-clemente/quic-go"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wweir/sower/util"
|
||||
)
|
||||
|
||||
type quicTran struct {
|
||||
clientConf *quic.Config
|
||||
sess quic.Session
|
||||
|
||||
serverConf *quic.Config
|
||||
}
|
||||
|
||||
func init() {
|
||||
transports["QUIC"] = &quicTran{
|
||||
|
||||
clientConf: &quic.Config{
|
||||
HandshakeTimeout: time.Second,
|
||||
KeepAlive: true,
|
||||
IdleTimeout: time.Minute,
|
||||
},
|
||||
serverConf: &quic.Config{
|
||||
MaxIncomingStreams: 1024,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *quicTran) Dial(server string) (net.Conn, error) {
|
||||
if c.sess == nil {
|
||||
if sess, err := quic.DialAddr(server, &tls.Config{InsecureSkipVerify: true}, c.clientConf); err != nil {
|
||||
return nil, errors.Wrap(err, "session")
|
||||
} else {
|
||||
go func() {
|
||||
<-sess.Context().Done()
|
||||
sess.Close()
|
||||
c.sess = nil
|
||||
}()
|
||||
c.sess = sess
|
||||
}
|
||||
}
|
||||
|
||||
var stream quic.Stream
|
||||
if err := util.WithTimeout(func() (err error) {
|
||||
if stream, err = c.sess.OpenStream(); err != nil {
|
||||
c.sess = nil
|
||||
}
|
||||
return
|
||||
}, time.Second); err != nil {
|
||||
return nil, errors.Wrap(err, "stream")
|
||||
}
|
||||
|
||||
return &streamConn{
|
||||
Stream: stream,
|
||||
sess: c.sess,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type streamConn struct {
|
||||
quic.Stream
|
||||
sess quic.Session
|
||||
}
|
||||
|
||||
func (s *streamConn) LocalAddr() net.Addr {
|
||||
return s.sess.LocalAddr()
|
||||
}
|
||||
|
||||
func (s *streamConn) RemoteAddr() net.Addr {
|
||||
return s.sess.RemoteAddr()
|
||||
}
|
||||
|
||||
func mockTlsPem() *tls.Config {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||
if err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
template := x509.Certificate{SerialNumber: big.NewInt(1)}
|
||||
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
|
||||
if err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
|
||||
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
|
||||
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
|
||||
|
||||
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
|
||||
if err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
return &tls.Config{Certificates: []tls.Certificate{tlsCert}}
|
||||
}
|
||||
|
||||
func (s *quicTran) Listen(port string) (<-chan net.Conn, error) {
|
||||
ln, err := quic.ListenAddr(port, mockTlsPem(), s.serverConf)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
connCh := make(chan net.Conn)
|
||||
go func() {
|
||||
for {
|
||||
sess, err := ln.Accept(context.Background())
|
||||
if err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
go accept(sess, connCh)
|
||||
}
|
||||
}()
|
||||
return connCh, nil
|
||||
}
|
||||
|
||||
func accept(sess quic.Session, connCh chan<- net.Conn) {
|
||||
glog.V(1).Infoln("new session from ", sess.RemoteAddr())
|
||||
defer sess.Close()
|
||||
|
||||
for {
|
||||
stream, err := sess.AcceptStream(context.Background())
|
||||
if err != nil {
|
||||
glog.Errorln(err)
|
||||
return
|
||||
}
|
||||
|
||||
connCh <- &streamConn{stream, sess}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
type tcp struct {
|
||||
DialTimeout time.Duration
|
||||
isSocks5 bool
|
||||
}
|
||||
|
||||
func init() {
|
||||
transports["TCP"] = &tcp{
|
||||
DialTimeout: 5 * time.Second,
|
||||
}
|
||||
transports["SOCKS5"] = &tcp{
|
||||
DialTimeout: 5 * time.Second,
|
||||
isSocks5: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tcp) Dial(server string) (net.Conn, error) {
|
||||
conn, err := net.DialTimeout("tcp", server, t.DialTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (t *tcp) Listen(port string) (<-chan net.Conn, error) {
|
||||
if t.isSocks5 {
|
||||
panic("not support run as socks5 server")
|
||||
}
|
||||
|
||||
ln, err := net.Listen("tcp", port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
connCh := make(chan net.Conn)
|
||||
go func() {
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
glog.Fatalln("TCP listen:", err)
|
||||
}
|
||||
|
||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
connCh <- conn
|
||||
}
|
||||
}()
|
||||
return connCh, nil
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Transport interface {
|
||||
Dial(server string) (net.Conn, error)
|
||||
Listen(port string) (<-chan net.Conn, error)
|
||||
}
|
||||
|
||||
var transports = map[string]Transport{}
|
||||
|
||||
func ListTransports() []string {
|
||||
list := make([]string, 0, len(transports))
|
||||
for key := range transports {
|
||||
list = append(list, key)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func GetTransport(netType string) (Transport, error) {
|
||||
tran, ok := transports[netType]
|
||||
if !ok {
|
||||
return nil, errors.New("invalid net type: " + netType)
|
||||
}
|
||||
return tran, nil
|
||||
}
|
||||
+40
-19
@@ -1,28 +1,55 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/wweir/sower/util"
|
||||
)
|
||||
|
||||
// race safe
|
||||
var resolved = false
|
||||
func ParseHTTP(conn net.Conn) (net.Conn, string, error) {
|
||||
teeConn := &util.TeeConn{Conn: conn}
|
||||
defer teeConn.Stop()
|
||||
|
||||
func resolveAddr(server *string) {
|
||||
if !resolved {
|
||||
if addr, err := net.ResolveTCPAddr("tcp", *server); err != nil {
|
||||
glog.Errorln(err)
|
||||
} else {
|
||||
glog.Infof("remote server (%s)=>(%s)", *server, addr)
|
||||
*server = addr.String()
|
||||
resolved = true
|
||||
}
|
||||
resp, err := http.ReadRequest(bufio.NewReader(teeConn))
|
||||
if err != nil {
|
||||
return teeConn, "", err
|
||||
}
|
||||
|
||||
if _, _, err := net.SplitHostPort(resp.Host); err != nil {
|
||||
resp.Host = net.JoinHostPort(resp.Host, "80")
|
||||
}
|
||||
|
||||
return teeConn, resp.Host, nil
|
||||
}
|
||||
|
||||
func ParseHTTPS(conn net.Conn) (net.Conn, string, error) {
|
||||
teeConn := &util.TeeConn{Conn: conn}
|
||||
defer teeConn.Stop()
|
||||
|
||||
var domain string
|
||||
tls.Server(teeConn, &tls.Config{
|
||||
GetConfigForClient: func(hello *tls.ClientHelloInfo) (*tls.Config, error) {
|
||||
domain = hello.ServerName
|
||||
return nil, nil
|
||||
},
|
||||
}).Handshake()
|
||||
|
||||
return teeConn, net.JoinHostPort(domain, "443"), nil
|
||||
}
|
||||
|
||||
func withDefaultPort(addr string, port string) (address, host string) {
|
||||
host, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return net.JoinHostPort(addr, port), addr
|
||||
}
|
||||
return addr, host
|
||||
}
|
||||
|
||||
func relay(conn1, conn2 net.Conn) {
|
||||
@@ -33,20 +60,14 @@ func relay(conn1, conn2 net.Conn) {
|
||||
redirect(conn1, conn2, wg, exitFlag)
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func redirect(dst, src net.Conn, wg *sync.WaitGroup, exitFlag *int32) {
|
||||
if _, err := io.Copy(dst, src); err != nil {
|
||||
glog.V(1).Infof("%s<>%s -> %s<>%s: %s", src.RemoteAddr(), src.LocalAddr(), dst.LocalAddr(), dst.RemoteAddr(), err)
|
||||
}
|
||||
io.Copy(dst, src)
|
||||
|
||||
if atomic.CompareAndSwapInt32(exitFlag, 0, 1) {
|
||||
// wakeup blocked goroutine
|
||||
now := time.Now()
|
||||
src.SetDeadline(now)
|
||||
dst.SetDeadline(now)
|
||||
} else {
|
||||
src.Close()
|
||||
dst.Close()
|
||||
}
|
||||
|
||||
wg.Done()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package util
|
||||
package router
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -6,49 +6,48 @@ import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// HTTPPing try connect to a http(s) server with domain though the http addr
|
||||
func HTTPPing(viaHost, domain string, port Port, timeout time.Duration) (err error) {
|
||||
conn, err := net.DialTimeout("tcp", port.JoinAddr(viaHost), timeout)
|
||||
// Port ==========================
|
||||
type Port uint16
|
||||
|
||||
const (
|
||||
HTTP Port = 80
|
||||
HTTPS Port = 443
|
||||
)
|
||||
|
||||
// Ping try connect to a http(s) server with domain though the http addr
|
||||
func (p Port) Ping(domain string, timeout time.Duration) error {
|
||||
conn, err := net.DialTimeout("tcp", net.JoinHostPort(domain, p.String()), timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
return p.PingWithConn(domain, conn, timeout)
|
||||
}
|
||||
|
||||
// PingWithConn try connect to a http(s) server with domain though the http addr
|
||||
func (p Port) PingWithConn(domain string, conn net.Conn, timeout time.Duration) error {
|
||||
conn.SetDeadline(time.Now().Add(timeout))
|
||||
if _, err = conn.Write(port.PingMsg(domain)); err != nil {
|
||||
if _, err := conn.Write(p.PingMsg(domain)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// err -> nil: read something succ
|
||||
// err -> io.EOF: no such domain or connection refused
|
||||
// err -> timeout: tcp package has been dropped
|
||||
_, err = conn.Read(make([]byte, 1))
|
||||
if err == io.EOF && viaHost == domain {
|
||||
_, err := conn.Read(make([]byte, 1))
|
||||
if err == nil || err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Port ==========================
|
||||
type Port uint16
|
||||
|
||||
const (
|
||||
HTTP Port = iota
|
||||
HTTPS
|
||||
)
|
||||
|
||||
func (p Port) JoinAddr(addr string) string {
|
||||
switch p {
|
||||
case HTTP:
|
||||
return addr + ":80"
|
||||
case HTTPS:
|
||||
return addr + ":443"
|
||||
default:
|
||||
panic("invalid port")
|
||||
}
|
||||
func (p Port) String() string {
|
||||
return strconv.Itoa(int(p))
|
||||
}
|
||||
|
||||
func (p Port) PingMsg(domain string) []byte {
|
||||
@@ -99,7 +98,7 @@ func NewClientHelloSNIMsg(domain string) []byte {
|
||||
length := uint16(len(domain))
|
||||
msg := &clientHelloSNI{
|
||||
ContentType: 0x16, // Content Type: Handshake (22)
|
||||
Version: 0x0301, // Version: TLS 1.0 (0x0301)
|
||||
Version: 0x0301, // Version: TLS 1.0
|
||||
Length: length + 56,
|
||||
handshakeProtocol: handshakeProtocol{
|
||||
HandshakeType: 0x01, // Handshake Type: Client Hello (1)
|
||||
@@ -0,0 +1,151 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/wweir/sower/transport"
|
||||
"github.com/wweir/sower/util"
|
||||
"github.com/wweir/utils/log"
|
||||
"github.com/wweir/utils/mem"
|
||||
)
|
||||
|
||||
// Route implement a router for each request
|
||||
type Route struct {
|
||||
port Port
|
||||
once sync.Once
|
||||
cache *mem.Cache
|
||||
|
||||
ProxyAddress string
|
||||
ProxyPassword string
|
||||
password []byte
|
||||
|
||||
DetectLevel int // dynamic detect proxy level
|
||||
DetectTimeout string // dynamic detect timeout
|
||||
timeout time.Duration
|
||||
|
||||
DirectList []string
|
||||
directRule *util.Node
|
||||
ProxyList []string
|
||||
proxyRule *util.Node
|
||||
DynamicList []string
|
||||
dynamicRule *util.Node
|
||||
PersistFn func(string)
|
||||
}
|
||||
|
||||
// ShouldProxy check if the domain shoule request though proxy
|
||||
func (r *Route) ShouldProxy(domain string) bool {
|
||||
r.once.Do(func() {
|
||||
r.cache = mem.New(4 * time.Hour)
|
||||
r.password = []byte(r.ProxyPassword)
|
||||
r.directRule = util.NewNodeFromRules(r.DirectList...)
|
||||
r.proxyRule = util.NewNodeFromRules(r.ProxyList...)
|
||||
r.dynamicRule = util.NewNodeFromRules(r.DynamicList...)
|
||||
|
||||
if timeout, err := time.ParseDuration(r.DetectTimeout); err != nil {
|
||||
r.timeout = 300 * time.Millisecond
|
||||
log.Warnw("parse detect timeout", "err", err, "default", r.timeout)
|
||||
} else {
|
||||
r.timeout = timeout
|
||||
}
|
||||
})
|
||||
|
||||
// break deadlook, for wildcard
|
||||
if strings.Count(domain, ".") > 4 {
|
||||
return false
|
||||
}
|
||||
domain = strings.TrimSuffix(domain, ".")
|
||||
|
||||
if domain == r.ProxyAddress {
|
||||
return false
|
||||
}
|
||||
if r.directRule.Match(domain) {
|
||||
return false
|
||||
}
|
||||
if r.proxyRule.Match(domain) {
|
||||
return true
|
||||
}
|
||||
if r.dynamicRule.Match(domain) {
|
||||
return true
|
||||
}
|
||||
|
||||
r.cache.Remember(r, domain)
|
||||
return r.dynamicRule.Match(domain)
|
||||
}
|
||||
|
||||
// Get implement for cache
|
||||
func (r *Route) Get(key interface{}) (err error) {
|
||||
domain := key.(string)
|
||||
|
||||
httpScore, httpsScore := r.detect(domain)
|
||||
log.Infow("detect", "domain", domain, "http", httpScore, "https", httpsScore)
|
||||
|
||||
if httpScore+httpsScore >= r.DetectLevel {
|
||||
r.dynamicRule.Add(domain)
|
||||
if r.PersistFn != nil {
|
||||
r.PersistFn(domain)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// detect and caculate direct connection and proxy connection score
|
||||
func (r *Route) detect(domain string) (http, https int) {
|
||||
wg := sync.WaitGroup{}
|
||||
httpScore, httpsScore := new(int32), new(int32)
|
||||
for _, ping := range [...]*Route{{port: HTTP}, {port: HTTPS}} {
|
||||
wg.Add(1)
|
||||
go func(ping *Route) {
|
||||
defer wg.Done()
|
||||
|
||||
if err := ping.port.Ping(domain, r.timeout); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch ping.port {
|
||||
case HTTP:
|
||||
if !atomic.CompareAndSwapInt32(httpScore, 0, -2) {
|
||||
atomic.AddInt32(httpScore, -1)
|
||||
}
|
||||
case HTTPS:
|
||||
if !atomic.CompareAndSwapInt32(httpsScore, 0, -2) {
|
||||
atomic.AddInt32(httpScore, -1)
|
||||
}
|
||||
}
|
||||
}(ping)
|
||||
}
|
||||
for _, ping := range [...]*Route{{port: HTTP}, {port: HTTPS}} {
|
||||
wg.Add(1)
|
||||
go func(ping *Route) {
|
||||
defer wg.Done()
|
||||
|
||||
target := net.JoinHostPort(domain, ping.port.String())
|
||||
conn, err := transport.Dial(r.ProxyAddress, target, r.password)
|
||||
if err != nil {
|
||||
log.Errorw("sower dial", "addr", r.ProxyAddress, "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := ping.port.PingWithConn(domain, conn, r.timeout); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch ping.port {
|
||||
case HTTP:
|
||||
if !atomic.CompareAndSwapInt32(httpScore, 0, 2) {
|
||||
atomic.AddInt32(httpScore, 1)
|
||||
}
|
||||
case HTTPS:
|
||||
if !atomic.CompareAndSwapInt32(httpsScore, 0, 2) {
|
||||
atomic.AddInt32(httpScore, 1)
|
||||
}
|
||||
}
|
||||
}(ping)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return int(*httpScore), int(*httpsScore)
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/wweir/sower/util"
|
||||
)
|
||||
|
||||
// checksum(>=0x80) + port + target_length + target + data
|
||||
// data(HTTP/HTTPS, first byte < 0x7F)
|
||||
type head struct {
|
||||
Checksum byte
|
||||
Port uint16
|
||||
AddrLen uint8
|
||||
}
|
||||
|
||||
func ToProxyConn(conn net.Conn, password []byte) (net.Conn, string) {
|
||||
teeConn := &util.TeeConn{Conn: conn}
|
||||
defer teeConn.Stop()
|
||||
|
||||
h := &head{}
|
||||
if err := binary.Read(teeConn, binary.BigEndian, h); err != nil || h.Checksum < 0x80 {
|
||||
return teeConn, ""
|
||||
}
|
||||
|
||||
buf := make([]byte, int(h.AddrLen))
|
||||
if _, err := io.ReadFull(teeConn, buf); err != nil {
|
||||
return teeConn, ""
|
||||
}
|
||||
|
||||
if h.Checksum != sumChecksum(buf, password) {
|
||||
return teeConn, ""
|
||||
}
|
||||
|
||||
teeConn.Reset()
|
||||
return teeConn, net.JoinHostPort(string(buf), strconv.Itoa(int(h.Port)))
|
||||
}
|
||||
|
||||
func DialTlsProxyConn(address, tgtHost string, tgtPort uint16, tlsCfg *tls.Config, password []byte) (net.Conn, error) {
|
||||
conn, err := tls.Dial("tcp", address, tlsCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h := &head{
|
||||
Checksum: sumChecksum([]byte(tgtHost), password),
|
||||
Port: tgtPort,
|
||||
AddrLen: uint8(len(tgtHost)),
|
||||
}
|
||||
if err := binary.Write(conn, binary.BigEndian, h); err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data := []byte(tgtHost)
|
||||
for n, nn := 0, 0; nn < len(data); nn += n {
|
||||
if n, err = conn.Write(data[nn:]); err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func sumChecksum(target, password []byte) byte {
|
||||
return md5.Sum(append(target, password...))[0] | 0x80
|
||||
}
|
||||
@@ -1,18 +1,42 @@
|
||||
package socks5
|
||||
package transport
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ToSocks5(c net.Conn, domain, port string) net.Conn {
|
||||
num, _ := strconv.Atoi(port)
|
||||
bytes := []byte{byte(num >> 8), byte(num)}
|
||||
return &conn{init: make(chan struct{}), Conn: c, domain: domain, port: bytes}
|
||||
func IsSocks5Schema(addr string) (string, bool) {
|
||||
if strings.HasPrefix(addr, "socks5://") {
|
||||
return strings.TrimPrefix(addr, "socks5://"), true
|
||||
}
|
||||
|
||||
if strings.HasPrefix(addr, "socks5h://") {
|
||||
return strings.TrimPrefix(addr, "socks5h://"), true
|
||||
}
|
||||
|
||||
return addr, false
|
||||
}
|
||||
|
||||
func ToSocks5(c net.Conn, address string) (net.Conn, error) {
|
||||
host, port, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p, err := strconv.Atoi(port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &conn{
|
||||
init: make(chan struct{}),
|
||||
Conn: c,
|
||||
domain: host,
|
||||
port: []byte{byte(p >> 8), byte(p)},
|
||||
}, nil
|
||||
}
|
||||
|
||||
type conn struct {
|
||||
@@ -75,7 +99,7 @@ func (c *conn) Write(b []byte) (n int, err error) {
|
||||
switch resp.REP {
|
||||
case 0x00:
|
||||
default:
|
||||
return 0, errors.Errorf("socks5 handshake fail, return code: %d", resp.REP)
|
||||
return 0, fmt.Errorf("socks5 handshake fail, return code: %d", resp.REP)
|
||||
}
|
||||
|
||||
switch resp.ATYP {
|
||||
@@ -107,44 +131,3 @@ func (c *conn) Write(b []byte) (n int, err error) {
|
||||
close(c.init)
|
||||
return c.Conn.Write(b)
|
||||
}
|
||||
|
||||
type authReq struct {
|
||||
VER byte
|
||||
NMETHODS byte
|
||||
METHODS [1]byte // 1 to 255, fix to no authentication
|
||||
}
|
||||
|
||||
type authResp struct {
|
||||
VER byte
|
||||
METHOD byte
|
||||
}
|
||||
|
||||
type request struct {
|
||||
req
|
||||
DST_ADDR []byte // first byte is length
|
||||
DST_PORT []byte // two bytes
|
||||
}
|
||||
type req struct {
|
||||
VER byte
|
||||
CMD byte
|
||||
RSV byte
|
||||
ATYP byte
|
||||
}
|
||||
|
||||
func (r *request) Bytes() []byte {
|
||||
out := []byte{r.VER, r.CMD, r.RSV, r.ATYP}
|
||||
out = append(out, r.DST_ADDR...)
|
||||
return append(out, r.DST_PORT...)
|
||||
}
|
||||
|
||||
type response struct {
|
||||
resp
|
||||
DST_ADDR []byte // first byte is length
|
||||
DST_PORT []byte // two bytes
|
||||
}
|
||||
type resp struct {
|
||||
VER byte
|
||||
REP byte
|
||||
RSV byte
|
||||
ATYP byte
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package transport
|
||||
|
||||
// https://tools.ietf.org/html/rfc1928
|
||||
|
||||
type authReq struct {
|
||||
VER byte
|
||||
NMETHODS byte
|
||||
METHODS [1]byte // 1 to 255, fix to no authentication
|
||||
}
|
||||
|
||||
type authResp struct {
|
||||
VER byte
|
||||
METHOD byte
|
||||
}
|
||||
|
||||
type request struct {
|
||||
req
|
||||
DST_ADDR []byte // first byte is length
|
||||
DST_PORT []byte // two bytes
|
||||
}
|
||||
type req struct {
|
||||
VER byte
|
||||
CMD byte
|
||||
RSV byte
|
||||
ATYP byte
|
||||
}
|
||||
|
||||
func (r *request) Bytes() []byte {
|
||||
out := []byte{r.VER, r.CMD, r.RSV, r.ATYP}
|
||||
out = append(out, r.DST_ADDR...)
|
||||
return append(out, r.DST_PORT...)
|
||||
}
|
||||
|
||||
type response struct {
|
||||
resp
|
||||
DST_ADDR []byte // first byte is length
|
||||
DST_PORT []byte // two bytes
|
||||
}
|
||||
type resp struct {
|
||||
VER byte
|
||||
REP byte
|
||||
RSV byte
|
||||
ATYP byte
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func Dial(address, target string, password []byte) (net.Conn, error) {
|
||||
if addr, ok := IsSocks5Schema(address); ok {
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if conn, err = ToSocks5(conn, target); err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
host, port, err := net.SplitHostPort(target)
|
||||
if err != nil {
|
||||
port = "443"
|
||||
}
|
||||
p, err := strconv.Atoi(port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// tls.Config is same as golang http pkg default behavior
|
||||
return DialTlsProxyConn(net.JoinHostPort(address, "443"),
|
||||
host, uint16(p), &tls.Config{}, password)
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPickInterface(t *testing.T) {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
case "darwin":
|
||||
default:
|
||||
t.Skip("skip for some enviroment not have net interface")
|
||||
return
|
||||
}
|
||||
|
||||
got, err := PickInterface()
|
||||
if err != nil {
|
||||
t.Errorf("PickInterface() error = %v", err)
|
||||
} else {
|
||||
t.Logf("PickInterface() got: MAC: %s IP: %s", got.HardwareAddr, got.IP)
|
||||
}
|
||||
}
|
||||
+8
-5
@@ -14,11 +14,8 @@ type node struct {
|
||||
node map[string]*node
|
||||
}
|
||||
|
||||
func NewNode(sep string) *Node {
|
||||
return &Node{node{node: map[string]*node{}}, sep, &sync.RWMutex{}}
|
||||
}
|
||||
func NewNodeFromRules(sep string, rules ...string) *Node {
|
||||
n := NewNode(sep)
|
||||
func NewNodeFromRules(rules ...string) *Node {
|
||||
n := &Node{node{node: map[string]*node{}}, ".", &sync.RWMutex{}}
|
||||
for i := range rules {
|
||||
n.Add(rules[i])
|
||||
}
|
||||
@@ -69,6 +66,12 @@ func (n *node) add(secs []string) {
|
||||
}
|
||||
|
||||
func (n *Node) Match(item string) bool {
|
||||
if n == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
n.RLock()
|
||||
defer n.RUnlock()
|
||||
return n.matchSecs(strings.Split(n.trim(item), n.sep), false)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package util
|
||||
package util_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/wweir/sower/util"
|
||||
)
|
||||
|
||||
func TestNode_Match(t *testing.T) {
|
||||
@@ -11,18 +13,18 @@ func TestNode_Match(t *testing.T) {
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
node *Node
|
||||
node *util.Node
|
||||
tests []test
|
||||
}{{
|
||||
"simple",
|
||||
NewNodeFromRules(".", "a.wweir.cc", "b.wweir.cc"),
|
||||
util.NewNodeFromRules("a.wweir.cc", "b.wweir.cc"),
|
||||
[]test{
|
||||
{"a.wweir.cc", true},
|
||||
{"b.wweir.cc", true},
|
||||
},
|
||||
}, {
|
||||
"parent",
|
||||
NewNodeFromRules(".", "wweir.cc", "a.wweir.cc"),
|
||||
util.NewNodeFromRules("wweir.cc", "a.wweir.cc"),
|
||||
[]test{
|
||||
{"wweir.cc", true},
|
||||
{"a.wweir.cc", true},
|
||||
@@ -30,7 +32,7 @@ func TestNode_Match(t *testing.T) {
|
||||
},
|
||||
}, {
|
||||
"fuzz1",
|
||||
NewNodeFromRules(".", "wweir.cc", "a.wweir.cc", "*.wweir.cc"),
|
||||
util.NewNodeFromRules("wweir.cc", "a.wweir.cc", "*.wweir.cc"),
|
||||
[]test{
|
||||
{"wweir.cc", true},
|
||||
{"a.wweir.cc", true},
|
||||
@@ -39,7 +41,7 @@ func TestNode_Match(t *testing.T) {
|
||||
},
|
||||
}, {
|
||||
"fuzz2",
|
||||
NewNodeFromRules(".", "a.*.cc", "c.wweir.*"),
|
||||
util.NewNodeFromRules("a.*.cc", "c.wweir.*"),
|
||||
[]test{
|
||||
{"wweir.cc", false},
|
||||
{"a.wweir.cc", true},
|
||||
@@ -48,7 +50,7 @@ func TestNode_Match(t *testing.T) {
|
||||
},
|
||||
}, {
|
||||
"fuzz3",
|
||||
NewNodeFromRules(".", "*.*.cc", "iamp.*.*"),
|
||||
util.NewNodeFromRules("*.*.cc", "iamp.*.*"),
|
||||
[]test{
|
||||
{"wweir.cc", false},
|
||||
{"a.wweir.cc", true},
|
||||
@@ -57,7 +59,7 @@ func TestNode_Match(t *testing.T) {
|
||||
},
|
||||
}, {
|
||||
"fuzz4",
|
||||
NewNodeFromRules(".", "**.cc", "a.**.com", "**.wweir.*"),
|
||||
util.NewNodeFromRules("**.cc", "a.**.com", "**.wweir.*"),
|
||||
[]test{
|
||||
{"wweir.cc", true},
|
||||
{"a.wweir.cc", true},
|
||||
|
||||
+20
-7
@@ -1,23 +1,28 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
)
|
||||
|
||||
type TeeConn struct {
|
||||
net.Conn
|
||||
buf []byte
|
||||
offset int
|
||||
tee bool // read
|
||||
buf []byte
|
||||
offset int
|
||||
stop bool // read
|
||||
EnableWrite bool
|
||||
}
|
||||
|
||||
func (t *TeeConn) StartOrReset() {
|
||||
func (t *TeeConn) Reread() {
|
||||
t.offset = 0
|
||||
}
|
||||
func (t *TeeConn) Reset() {
|
||||
t.buf = []byte{}
|
||||
t.offset = 0
|
||||
t.tee = true
|
||||
}
|
||||
func (t *TeeConn) Stop() {
|
||||
t.offset = 0
|
||||
t.tee = false
|
||||
t.stop = true
|
||||
}
|
||||
|
||||
func (t *TeeConn) Read(b []byte) (n int, err error) {
|
||||
@@ -29,9 +34,17 @@ func (t *TeeConn) Read(b []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
n, err = t.Conn.Read(b)
|
||||
if t.tee {
|
||||
if !t.stop {
|
||||
t.buf = append(t.buf, b[:n]...)
|
||||
t.offset += n
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (t *TeeConn) Write(b []byte) (n int, err error) {
|
||||
if t.stop || t.EnableWrite {
|
||||
return t.Conn.Write(b)
|
||||
}
|
||||
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Iface is net interface address info
|
||||
type Iface struct {
|
||||
net.HardwareAddr
|
||||
net.IP
|
||||
}
|
||||
|
||||
func WithTimeout(fn func() error, timeout time.Duration) error {
|
||||
var okCh = make(chan struct{})
|
||||
var err error
|
||||
|
||||
go func() {
|
||||
err = fn()
|
||||
close(okCh)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-okCh:
|
||||
return err
|
||||
case <-time.After(timeout):
|
||||
return errors.New("timeout: " + timeout.String())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user