mirror of
https://github.com/wweir/sower.git
synced 2026-10-19 16:14:33 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9f0ca4ee7 | ||
|
|
98c3934762 | ||
|
|
f001f7dab7 | ||
|
|
36ef329b68 | ||
|
|
64778f68ac | ||
|
|
53748cfa85 | ||
|
|
d35115b7b3 | ||
|
|
559c5fde6b | ||
|
|
eb8080df18 | ||
|
|
45ca063a7e | ||
|
|
6add7afd19 | ||
|
|
f13687506e | ||
|
|
199e4cf295 | ||
|
|
b3ad9f6657 | ||
|
|
f9e9fe366f | ||
|
|
103699e25e | ||
|
|
f80755eae5 | ||
|
|
5b217b794d | ||
|
|
26055c5203 | ||
|
|
029611c078 | ||
|
|
89adc6a2c9 | ||
|
|
8c95c59e5e | ||
|
|
4f8376c041 | ||
|
|
10172f8be2 | ||
|
|
2831cdf284 | ||
|
|
06558ac635 | ||
|
|
3cf4deddb7 | ||
|
|
679263be9a | ||
|
|
21d8ec4ec7 | ||
|
|
4916245522 | ||
|
|
11c2808320 | ||
|
|
21ddd79e4d | ||
|
|
9a158d4088 | ||
|
|
88990cddd9 | ||
|
|
ca1b2650ab | ||
|
|
8ff1046ebf | ||
|
|
dfe81f6f23 | ||
|
|
972875ec36 | ||
|
|
89b7a41d46 | ||
|
|
8f598dbc17 | ||
|
|
192b4f798b | ||
|
|
2adefe0dfc | ||
|
|
7b0c13ef1c | ||
|
|
63bb934148 | ||
|
|
cacc732a4e | ||
|
|
17d079dcb2 | ||
|
|
413a93f6f7 | ||
|
|
6c5f5cd74f | ||
|
|
e948541efe | ||
|
|
ffa2128274 | ||
|
|
b53b7874b6 | ||
|
|
f3117edfa0 | ||
|
|
ba8980da73 | ||
|
|
e3ec74b74f | ||
|
|
6e67193894 | ||
|
|
af0c5f895c | ||
|
|
4883e240cc | ||
|
|
18ee9ca993 | ||
|
|
4fff2a1f3b | ||
|
|
3e82890c33 | ||
|
|
01d66d737b | ||
|
|
e342dadaf1 | ||
|
|
7ba9f1a23c | ||
|
|
7240208cb3 | ||
|
|
a27748d835 | ||
|
|
39797d077b | ||
|
|
b6df69358a | ||
|
|
ae525f9b40 | ||
|
|
29eb0e6e1c | ||
|
|
aef941b17e | ||
|
|
9bc5a32074 | ||
|
|
8194083f54 | ||
|
|
c20903137d | ||
|
|
2f3c98b8d3 | ||
|
|
bc99bda1b3 | ||
|
|
ceedef4203 | ||
|
|
712b689708 | ||
|
|
d5260d66df | ||
|
|
bde27f64ec | ||
|
|
04193eb608 | ||
|
|
41a1adc707 | ||
|
|
c9191ec928 | ||
|
|
14da5b711e | ||
|
|
59257c0673 | ||
|
|
7db92a19be | ||
|
|
fbae2999dc | ||
|
|
92991da6a0 | ||
|
|
b60b5fceb3 | ||
|
|
756a8c8d17 | ||
|
|
e02da26936 | ||
|
|
c5adcc4421 | ||
|
|
c20c3621c6 | ||
|
|
055c1a2901 | ||
|
|
fc2ea8b8c0 | ||
|
|
3e6b9e96ca | ||
|
|
2264d73c45 | ||
|
|
99ba06b2d4 | ||
|
|
a8e026ff7f | ||
|
|
9e377e4a2d | ||
|
|
107174fb6b | ||
|
|
3f8f5c2ef1 | ||
|
|
918b964b78 | ||
|
|
34722a460c | ||
|
|
8734486c48 | ||
|
|
8d0ee2a9ea | ||
|
|
98247845ad | ||
|
|
72dacaf1de | ||
|
|
56d5090841 | ||
|
|
821cbc302e | ||
|
|
f6eddb9f96 | ||
|
|
344531c301 | ||
|
|
df897f72c6 | ||
|
|
dd2fe95900 | ||
|
|
08a5d4363b | ||
|
|
e712dcb78c | ||
|
|
e9f5fc4789 | ||
|
|
f76f31d054 | ||
|
|
e0de537042 | ||
|
|
b050e312b2 | ||
|
|
d78a757200 | ||
|
|
b2282a58a2 | ||
|
|
0fb2271cac | ||
|
|
a8089af235 | ||
|
|
6e4a6c794e | ||
|
|
1d620b8191 | ||
|
|
9586e7bc8e | ||
|
|
ec4286e6d4 | ||
|
|
c01f217939 | ||
|
|
c62e3a3284 | ||
|
|
578d9162aa | ||
|
|
002ac49e65 | ||
|
|
ee47f02cb5 | ||
|
|
b1127e8f9e | ||
|
|
282969b696 | ||
|
|
2daaa8f3ef | ||
|
|
d6776345a0 | ||
|
|
0e03605c37 | ||
|
|
9ca58982b7 | ||
|
|
60452da6b1 | ||
|
|
049e805b4a | ||
|
|
b83f202bad | ||
|
|
cb88395e66 | ||
|
|
d8d5d4ea29 | ||
|
|
fe0ccdfec8 | ||
|
|
1c45fae006 |
@@ -0,0 +1,18 @@
|
||||
# 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" ]
|
||||
@@ -0,0 +1,18 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,57 @@
|
||||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/golang:1.12
|
||||
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/
|
||||
Executable
+127
@@ -0,0 +1,127 @@
|
||||
#!/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
|
||||
@@ -0,0 +1,14 @@
|
||||
[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
|
||||
@@ -0,0 +1,14 @@
|
||||
[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
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/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
|
||||
+5
-5
@@ -12,11 +12,11 @@
|
||||
*.out
|
||||
|
||||
.DS_Store
|
||||
**/.DS_Store
|
||||
._*
|
||||
*.swp
|
||||
*.swo
|
||||
.vscode
|
||||
.idea
|
||||
sower
|
||||
cert.pem
|
||||
**/cert.pem
|
||||
privkey.pem
|
||||
**/privkey.pem
|
||||
sower.exe*
|
||||
/sower.toml
|
||||
|
||||
@@ -1,19 +1,38 @@
|
||||
export GO111MODULE=on
|
||||
SERVER:=127.0.0.1:5533
|
||||
|
||||
default: build
|
||||
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 ./...
|
||||
build:
|
||||
GOBIN=$(PWD) go install -v
|
||||
go build -ldflags \
|
||||
"-X main.version=$(shell git describe --tags) \
|
||||
-X main.date=$(shell date +%Y-%m-%d)"
|
||||
image:
|
||||
docker build -t sower -f .circleci/Dockerfile .
|
||||
|
||||
kill:
|
||||
sudo pkill -9 sower || true
|
||||
|
||||
client: build kill
|
||||
sudo $(PWD)/sower -f conf/sower.toml -logtostderr
|
||||
sudo $(PWD)/sower
|
||||
|
||||
server: build kill
|
||||
$(PWD)/sower -logtostderr -v 1
|
||||
$(PWD)/sower -n TCP -v 1
|
||||
|
||||
run: build kill
|
||||
$(PWD)/sower -logtostderr -v 1 &
|
||||
sudo $(PWD)/sower -f conf/sower.toml -logtostderr
|
||||
$(PWD)/sower -n TCP -v 1 &
|
||||
sudo $(PWD)/sower &
|
||||
@sleep 1
|
||||
curl localhost
|
||||
@sleep 1
|
||||
@sudo pkill -9 sower || true
|
||||
|
||||
@@ -1,7 +1,59 @@
|
||||
# sower
|
||||
Yet another cross platform transparent proxy tool
|
||||
[](https://github.com/wweir/sower/releases)
|
||||
[](https://circleci.com/gh/wweir/sower)
|
||||
[](https://hub.docker.com/r/wweir/sower)
|
||||
[](https://github.com/wweir/sower/issues)
|
||||
[](https://github.com/wweir/sower/stargazers)
|
||||
[](LICENSE)
|
||||
|
||||
## architecture
|
||||
中文介绍见 [Wiki](https://github.com/wweir/sower/wiki)
|
||||
|
||||
The sower is a cross-platform intelligent transparent proxy tool base on DNS solution.
|
||||
|
||||
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.
|
||||
|
||||
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 also provides an http(s) proxy listening on `:8080` by default. You can turn it off or use another port at any time.
|
||||
|
||||
|
||||
## 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)
|
||||
|
||||
### Auto deploy
|
||||
Auto deploy script support Linux server side and masOS/Linux client side.
|
||||
|
||||
```shell
|
||||
$ bash -c "$(curl -sL https://git.io/fhhdp)"
|
||||
```
|
||||
|
||||
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)"
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
### Docker deploy
|
||||
The auto build docker images are [wweir/sower](https://hub.docker.com/r/wweir/sower).
|
||||
|
||||
It is very simple to use it on the server side. Export the port(5533) and run it directly.
|
||||
|
||||
But the client is more troublesome and needs some understanding of the working mechanism of the sower.
|
||||
|
||||
|
||||
## Architecture
|
||||
```
|
||||
request target servers
|
||||
<-------------+ +------------->
|
||||
@@ -11,38 +63,40 @@ Yet another cross platform transparent proxy tool
|
||||
| | relay service| |
|
||||
| +-----+---------------------+ |
|
||||
| | | |
|
||||
| | parsr http(s) target url | |
|
||||
| | parse http(s) target url | |
|
||||
| | | |
|
||||
| +---------------------------+ |
|
||||
| |
|
||||
| shadow service |
|
||||
+--------^----------------------+
|
||||
| request domain server
|
||||
quic connection +---------->
|
||||
quic / KCP / TCP +---------->
|
||||
| |
|
||||
+--------+---client+------+-----+
|
||||
| | |
|
||||
| | |
|
||||
| shadow service | |
|
||||
| relay service | dns |
|
||||
| | server |
|
||||
| | service |
|
||||
| | |
|
||||
| 127.0.0.1 or other |
|
||||
| | |
|
||||
+-------^----------+---^----^---+
|
||||
| | |
|
||||
| | | +----->
|
||||
| +----------+ | |
|
||||
+-^-----^----------+---^----^---+
|
||||
| | | |
|
||||
| | | | +----->
|
||||
http(s) proxy | +----------+ | |
|
||||
2 1 1 2
|
||||
+ + + +
|
||||
block request normal request
|
||||
blocked request normal request
|
||||
|
||||
```
|
||||
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/)
|
||||
|
||||
## install
|
||||
1. install server on `server node` by `make server`
|
||||
2. write config file, example: [conf/sower.toml](https://github.com/wweir/sower/blob/master/conf/sower.toml)
|
||||
3. install client on `client node` by `make client`
|
||||
4. add `127.0.0.1` as you first domain server
|
||||
|
||||
## todo
|
||||
- [ ] authenticate
|
||||
- [ ] broker
|
||||
## 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
|
||||
+118
-54
@@ -1,81 +1,145 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/golang/glog"
|
||||
"github.com/wweir/fsnotify"
|
||||
toml "github.com/pelletier/go-toml"
|
||||
"github.com/wweir/sower/util"
|
||||
)
|
||||
|
||||
// Conf define the config items
|
||||
var Conf = struct {
|
||||
ConfigFile string
|
||||
ServerPort string `toml:"server_port"`
|
||||
ServerAddr string `toml:"server_addr"`
|
||||
DnsServer string `toml:"dns_server"`
|
||||
ClientIP string `toml:"client_ip"`
|
||||
ClientIPNet net.IP `toml:"-"`
|
||||
ConfigFile string
|
||||
NetType string `toml:"net_type"`
|
||||
Cipher string `toml:"cipher"`
|
||||
Password string `toml:"password"`
|
||||
|
||||
ServerPort string `toml:"server_port"`
|
||||
ServerAddr string `toml:"server_addr"`
|
||||
HTTPProxy string `toml:"http_proxy"`
|
||||
|
||||
DNSServer string `toml:"dns_server"`
|
||||
ClientIP string `toml:"client_ip"`
|
||||
SuggestLevel string `toml:"suggest_level"`
|
||||
ClearDNSCache string `toml:"clear_dns_cache"`
|
||||
|
||||
BlockList []string `toml:"blocklist"`
|
||||
WhiteList []string `toml:"whitelist"`
|
||||
Suggestions []string `toml:"suggestions"`
|
||||
Verbose int `toml:"verbose"`
|
||||
}{}
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&Conf.ConfigFile, "f", "", "config file location")
|
||||
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.DnsServer, "d", "114.114.114.114", "client dns server")
|
||||
flag.StringVar(&Conf.ClientIP, "c", "127.0.0.1", "client dns service redirect IP")
|
||||
initArgs()
|
||||
|
||||
if !flag.Parsed() {
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
if Conf.ConfigFile == "" {
|
||||
if _, err := os.Stat(Conf.ConfigFile); os.IsNotExist(err) {
|
||||
glog.Warningln("no config file has been load:", Conf.ConfigFile)
|
||||
return
|
||||
}
|
||||
if err := OnRefreash[0](); err != nil {
|
||||
panic(err)
|
||||
for i := range refreshFns {
|
||||
if action, err := refreshFns[i](); err != nil {
|
||||
glog.Fatalln(action+":", err)
|
||||
}
|
||||
}
|
||||
watchConfigFile()
|
||||
|
||||
go addSuggestions()
|
||||
}
|
||||
|
||||
var OnRefreash = []func() error{func() error {
|
||||
if _, err := toml.DecodeFile(Conf.ConfigFile, &Conf); err != nil {
|
||||
return err
|
||||
}
|
||||
Conf.ClientIPNet = net.ParseIP(Conf.ClientIP)
|
||||
// for glog
|
||||
if err := flag.Set("v", strconv.Itoa(Conf.Verbose)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}}
|
||||
// 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()
|
||||
|
||||
// watchConfigFile changes, fsnotify take too much cpu time, DIY
|
||||
func watchConfigFile() {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
if err := watcher.Add(Conf.ConfigFile); err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
//safe refresh config
|
||||
file := Conf.ConfigFile
|
||||
if err = toml.NewDecoder(f).Decode(&Conf); err != nil {
|
||||
return action, err
|
||||
}
|
||||
Conf.ConfigFile = file
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case event := <-watcher.Events:
|
||||
glog.Infof("watch %s event: %v", Conf.ConfigFile, event)
|
||||
for i := range OnRefreash {
|
||||
if err := OnRefreash[i](); err != nil {
|
||||
glog.Errorln(err)
|
||||
}
|
||||
return action, flag.Set("v", strconv.Itoa(Conf.Verbose))
|
||||
},
|
||||
func() (string, error) {
|
||||
action := "clear dns cache"
|
||||
if Conf.ClearDNSCache != "" {
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
if out, err := exec.CommandContext(ctx, "cmd", "/c", Conf.ClearDNSCache).CombinedOutput(); err != nil {
|
||||
return action, errors.Wrapf(err, "cmd: %s, output: %s, error", Conf.ClearDNSCache, out)
|
||||
}
|
||||
default:
|
||||
if out, err := exec.CommandContext(ctx, "sh", "-c", Conf.ClearDNSCache).CombinedOutput(); err != nil {
|
||||
return action, errors.Wrapf(err, "cmd: %s, output: %s, error", Conf.ClearDNSCache, out)
|
||||
}
|
||||
case err := <-watcher.Errors:
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
refreshFns = append(refreshFns, fn)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
glog.Errorln(err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := toml.NewEncoder(f).ArraysWithOneElementPerLine(true).Encode(Conf); err != nil {
|
||||
glog.Errorln(err)
|
||||
f.Close()
|
||||
continue
|
||||
}
|
||||
f.Close()
|
||||
|
||||
if err = os.Rename(Conf.ConfigFile+"~", Conf.ConfigFile); err != nil {
|
||||
glog.Errorln(err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// reload config
|
||||
for i := range refreshFns {
|
||||
if action, err := refreshFns[i](); err != nil {
|
||||
glog.Errorln(action+":", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// +build !windows
|
||||
|
||||
package conf
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/wweir/sower/dns"
|
||||
"github.com/wweir/sower/proxy/shadow"
|
||||
"github.com/wweir/sower/proxy/transport"
|
||||
)
|
||||
|
||||
func initArgs() {
|
||||
flag.StringVar(&Conf.ConfigFile, "f", filepath.Dir(os.Args[0])+"/sower.toml", "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, "S", "SPEEDUP", "suggest level setting: "+strings.Join(dns.ListSuggestLevels(), ","))
|
||||
|
||||
if !flag.Parsed() {
|
||||
flag.Set("logtostderr", "true")
|
||||
flag.Parse()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// +build windows
|
||||
|
||||
package conf
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.org/x/sys/windows/svc"
|
||||
"golang.org/x/sys/windows/svc/eventlog"
|
||||
"golang.org/x/sys/windows/svc/mgr"
|
||||
)
|
||||
|
||||
const name = "sower"
|
||||
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue
|
||||
|
||||
func initArgs() {
|
||||
flag.StringVar(&Conf.ConfigFile, "f", filepath.Dir(os.Args[0])+"/sower.toml", "config file location")
|
||||
install := flag.Bool("install", false, "install sower as a service")
|
||||
uninstall := flag.Bool("uninstall", false, "uninstall sower from service list")
|
||||
|
||||
if !flag.Parsed() {
|
||||
flag.Set("log_dir", filepath.Dir(os.Args[0]))
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
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, os.Args[0], 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()
|
||||
})
|
||||
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)
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func serviceDo(fn func(*mgr.Service) error) {
|
||||
mgrDo(func(m *mgr.Mgr) error {
|
||||
s, err := m.OpenService(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not access service: %v", err)
|
||||
}
|
||||
defer s.Close()
|
||||
return fn(s)
|
||||
})
|
||||
}
|
||||
func mgrDo(fn func(m *mgr.Mgr) error) {
|
||||
m, err := mgr.Connect()
|
||||
if err != nil {
|
||||
glog.Exitln(err)
|
||||
}
|
||||
defer m.Disconnect()
|
||||
|
||||
if err := fn(m); err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
return
|
||||
}
|
||||
defer elog.Close()
|
||||
elog.Info(1, strings.Join(args, "-"))
|
||||
|
||||
changes <- svc.Status{State: svc.StartPending}
|
||||
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
|
||||
for {
|
||||
c := <-r
|
||||
switch c.Cmd {
|
||||
case svc.Interrogate:
|
||||
changes <- c.CurrentStatus
|
||||
// Testing deadlock from https://code.google.com/p/winsvc/issues/detail?id=4
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
changes <- c.CurrentStatus
|
||||
case svc.Stop, svc.Shutdown:
|
||||
changes <- svc.Status{State: svc.StopPending}
|
||||
return
|
||||
case svc.Pause:
|
||||
changes <- svc.Status{State: svc.Paused, Accepts: cmdsAccepted}
|
||||
case svc.Continue:
|
||||
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
|
||||
default:
|
||||
elog.Error(1, fmt.Sprintf("unexpected control request #%d", c))
|
||||
}
|
||||
}
|
||||
}
|
||||
+48
-32
@@ -1,35 +1,51 @@
|
||||
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="127.0.0.1:5533" # replce it to server "IP"
|
||||
dns_server="114.114.114.114"
|
||||
# client_ip="192.168.0.1"
|
||||
# server_addr="remote-server:5533" # replce it to remote server
|
||||
http_proxy=":8080" # eg: 192.168.0.2:8080
|
||||
dns_server="114.114.114.114" # 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" # Windows: "ipconfig /flushdnss"
|
||||
suggest_level="SPEEDUP" # DISABLE, BLOCK, SPEEDUP
|
||||
blocklist=[
|
||||
"*.google.com", # google
|
||||
"*.googleusercontent.com",
|
||||
"*.youtube.com", # youtube
|
||||
"*.ytimg.com",
|
||||
"*.ggpht.com",
|
||||
"*.googlevideo.com",
|
||||
"*.vimeo.com",
|
||||
"*.facebook.com", # facebook
|
||||
"*.xx.fbcdn.net",
|
||||
"*.twitter.com", # twitter
|
||||
"*.twimg.com",
|
||||
"*.*.blogspot.com", # blogspot
|
||||
"www.blogger.com",
|
||||
"www.feedburner.com",
|
||||
"img1.blogblog.com",
|
||||
"*.golang.org", # golang
|
||||
"go.googlesource.com",
|
||||
"gist.github.com",
|
||||
"*.wikipedia.org", # wikipeida
|
||||
"*.wikimedia.org",
|
||||
"*.wikisource.org",
|
||||
"lookup-api.apple.com",
|
||||
"lookup-api.apple.com.edgekey.net",
|
||||
"e16991.b.akamaiedge.net",
|
||||
"*.web.telegram.org", # telegram
|
||||
"cloud.feedly.com", # feedly
|
||||
"*.nytimes.com", # New York times
|
||||
"*.nyt.com",
|
||||
"**.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.*",
|
||||
]
|
||||
verbose=0
|
||||
whitelist=[
|
||||
"**.in-addr.arpa",
|
||||
"iamp.*.*",
|
||||
"iamp.*.*.*",
|
||||
"smtp.*.*",
|
||||
"smtp.*.*.*",
|
||||
"pop.*.*",
|
||||
"pop.*.*.*",
|
||||
"imap-mail.outlook.com",
|
||||
"**.qq.com",
|
||||
"**.baidu.com",
|
||||
"*.aliyun.com",
|
||||
"**.cn",
|
||||
"**.icloud.com",
|
||||
"**.163.com",
|
||||
"**.weiyun.com",
|
||||
]
|
||||
verbose=0
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"net"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"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()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "pick interface")
|
||||
}
|
||||
|
||||
rand.Read(xid)
|
||||
pack := dhcp4.RequestPacket(dhcp4.Discover, iface.HardwareAddr, net.IPv4(0, 0, 0, 0), xid, true, []dhcp4.Option{
|
||||
{Code: dhcp4.OptionRequestedIPAddress, Value: []byte(iface.IP.To4())},
|
||||
{Code: dhcp4.End},
|
||||
})
|
||||
|
||||
var conn net.PacketConn
|
||||
if runtime.GOOS == "windows" {
|
||||
if conn, err = reuseport.ListenPacket("udp4", iface.IP.String()+":68"); err != nil {
|
||||
return "", errors.Wrap(err, "listen dhcp")
|
||||
}
|
||||
} else {
|
||||
if conn, err = reuseport.ListenPacket("udp4", "0.0.0.0:68"); err != nil {
|
||||
return "", errors.Wrap(err, "listen dhcp")
|
||||
}
|
||||
}
|
||||
defer conn.Close()
|
||||
conn.SetDeadline(time.Now().Add(time.Second))
|
||||
|
||||
if _, err := conn.WriteTo([]byte(pack), broadcastAddr); err != nil {
|
||||
return "", errors.Wrap(err, "write broadcast")
|
||||
}
|
||||
|
||||
buf := make([]byte, 1500 /*MTU*/)
|
||||
n, _, err := conn.ReadFrom(buf)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "read dhcp offer")
|
||||
}
|
||||
|
||||
pack = dhcp4.Packet(buf[:n])
|
||||
dnsBytes := pack.ParseOptions()[dhcp4.OptionDomainNameServer]
|
||||
if len(dnsBytes) < 4 {
|
||||
return "", errors.New("no DNS setting in upstream network device")
|
||||
}
|
||||
|
||||
return net.IPv4(dnsBytes[0], dnsBytes[1], dnsBytes[2], dnsBytes[3]).String(), nil
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package dns
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestGetDefaultDNSServer(t *testing.T) {
|
||||
t.Skip("skip for some enviroment not support dhcp and permission")
|
||||
|
||||
if got, err := GetDefaultDNSServer(); err != nil {
|
||||
t.Errorf("GetDefaultDNSServer() return error: %s", err)
|
||||
} else {
|
||||
t.Logf("GetDefaultDNSServer() return IP: %v", got)
|
||||
}
|
||||
}
|
||||
+171
-42
@@ -1,20 +1,38 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/wweir/sower/conf"
|
||||
mem "github.com/wweir/mem-go"
|
||||
)
|
||||
|
||||
func StartDNS(dnsServer string) {
|
||||
const colon = byte(':')
|
||||
|
||||
func StartDNS(dnsServer, listenIP string, suggestCh chan<- string, suggestLevel string) {
|
||||
ip := net.ParseIP(listenIP)
|
||||
|
||||
suggest := &intelliSuggest{suggestCh, parseSuggestLevel(suggestLevel), 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 {
|
||||
r.SetTsig(r.Extra[len(r.Extra)-1].(*dns.TSIG).Hdr.Name, dns.HmacMD5, 300, time.Now().Unix())
|
||||
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
|
||||
@@ -22,64 +40,175 @@ func StartDNS(dnsServer string) {
|
||||
return
|
||||
}
|
||||
|
||||
if len(conf.Conf.BlockList) == 0 {
|
||||
bestTry(w, r, r.Question[0].Name, dnsServer)
|
||||
} else {
|
||||
manual(w, r, r.Question[0].Name, dnsServer)
|
||||
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: ":53", Net: "udp"}
|
||||
server := &dns.Server{Addr: net.JoinHostPort(listenIP, "53"), Net: "udp"}
|
||||
glog.Fatalln(server.ListenAndServe())
|
||||
}
|
||||
|
||||
func bestTry(w dns.ResponseWriter, r *dns.Msg, domain, dnsServer string) {
|
||||
msg, err := dns.Exchange(r, dnsServer+":53")
|
||||
if err != nil || len(msg.Answer) == 0 {
|
||||
w.WriteMsg(localA(r, domain))
|
||||
return
|
||||
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,
|
||||
}},
|
||||
}
|
||||
|
||||
var ip string
|
||||
switch msg.Answer[0].(type) {
|
||||
case *dns.A:
|
||||
ip = msg.Answer[0].(*dns.A).A.String()
|
||||
case *dns.AAAA:
|
||||
ip = "[" + msg.Answer[0].(*dns.AAAA).AAAA.String() + "]"
|
||||
default:
|
||||
w.WriteMsg(msg)
|
||||
}
|
||||
for {
|
||||
<-dhcpCh
|
||||
if _, err := dns.Exchange(msg, *dnsServer); err == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := net.DialTimeout("tcp", ip+":http", time.Second); err != nil {
|
||||
glog.V(2).Infoln(ip+":80", err)
|
||||
w.WriteMsg(localA(r, domain))
|
||||
return
|
||||
host, err := GetDefaultDNSServer()
|
||||
if err != nil {
|
||||
glog.Errorln(err)
|
||||
continue
|
||||
}
|
||||
|
||||
// atomic action
|
||||
*dnsServer = net.JoinHostPort(host, "53")
|
||||
glog.Infoln("set dns server to", host)
|
||||
}
|
||||
w.WriteMsg(msg)
|
||||
}
|
||||
func matchAndServe(w dns.ResponseWriter, r *dns.Msg, domain, listenIP, dnsServer string,
|
||||
dhcpCh chan struct{}, ipNet net.IP, suggest *intelliSuggest) {
|
||||
|
||||
func manual(w dns.ResponseWriter, r *dns.Msg, domain, dnsServer string) {
|
||||
if rule.Match(strings.TrimSuffix(domain, ".")) {
|
||||
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))
|
||||
w.WriteMsg(localA(r, domain, ipNet))
|
||||
return
|
||||
}
|
||||
glog.V(2).Infof("match %s fail", domain)
|
||||
|
||||
msg, err := dns.Exchange(r, dnsServer+":53")
|
||||
if err != nil || len(msg.Answer) == 0 {
|
||||
w.WriteMsg(localA(r, domain))
|
||||
if !inWriteList {
|
||||
go func() {
|
||||
ip, err := net.LookupIP(domain)
|
||||
if err != nil || len(ip) == 0 {
|
||||
glog.V(1).Infoln(ip, err)
|
||||
return
|
||||
}
|
||||
|
||||
mem.Remember(suggest, addr{domain, ip[0].String()})
|
||||
}()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func localA(r *dns.Msg, domain string) *dns.Msg {
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(r)
|
||||
m.Answer = []dns.RR{&dns.A{
|
||||
Hdr: dns.RR_Header{Name: domain, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 720},
|
||||
A: conf.Conf.ClientIPNet,
|
||||
}}
|
||||
return m
|
||||
type intelliSuggest struct {
|
||||
suggestCh chan<- string
|
||||
suggestLevel suggestLevel
|
||||
listenIP string
|
||||
timeout time.Duration
|
||||
}
|
||||
type addr struct {
|
||||
domain string
|
||||
ip string
|
||||
}
|
||||
|
||||
func (i *intelliSuggest) GetOne(key interface{}) (iface interface{}, e error) {
|
||||
iface, e = struct{}{}, nil
|
||||
if i.suggestLevel == DISABLE {
|
||||
return
|
||||
}
|
||||
|
||||
domain := key.(addr).domain
|
||||
ip := key.(addr).ip
|
||||
|
||||
// kill deadloop, for ugly wildcard setting dns setting
|
||||
domain = strings.TrimSuffix(domain, ".")
|
||||
if strings.Count(domain, ".") > 10 {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
pings = [...]struct {
|
||||
viaAddr string
|
||||
port Port
|
||||
}{
|
||||
{ip, HTTP},
|
||||
{i.listenIP, HTTP},
|
||||
{ip, HTTPS},
|
||||
{i.listenIP, HTTPS},
|
||||
}
|
||||
protos = [...]*int32{
|
||||
new(int32), /*HTTP*/
|
||||
new(int32), /*HTTPS*/
|
||||
}
|
||||
score = new(int32)
|
||||
)
|
||||
for idx := range pings {
|
||||
go func(idx int) {
|
||||
if err := HTTPPing(pings[idx].viaAddr, domain, pings[idx].port, i.timeout); err != nil {
|
||||
// local ping fail
|
||||
if pings[idx].viaAddr == ip {
|
||||
atomic.AddInt32(score, 1)
|
||||
glog.V(1).Infof("local ping %s fail", domain)
|
||||
} else {
|
||||
atomic.AddInt32(score, -1)
|
||||
glog.V(1).Infof("remote ping %s fail", domain)
|
||||
}
|
||||
|
||||
// remote ping faster
|
||||
} else if pings[idx].viaAddr == i.listenIP {
|
||||
if atomic.CompareAndSwapInt32(protos[idx/2], 0, 1) && i.suggestLevel == 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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
conn.SetDeadline(time.Now().Add(timeout))
|
||||
if _, err = conn.Write(port.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 {
|
||||
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) pingMsg(domain string) []byte {
|
||||
switch p {
|
||||
case HTTP:
|
||||
return []byte("TRACE / HTTP/1.1\r\nHost: " + domain + "\r\n\r\n")
|
||||
case HTTPS:
|
||||
return NewClientHelloSNIMsg(domain)
|
||||
default:
|
||||
panic("invalid port")
|
||||
}
|
||||
}
|
||||
|
||||
// SNI ==========================
|
||||
type clientHelloSNI struct {
|
||||
ContentType uint8
|
||||
Version uint16
|
||||
Length uint16
|
||||
handshakeProtocol
|
||||
}
|
||||
type handshakeProtocol struct {
|
||||
HandshakeType uint8
|
||||
LengthExpand uint8
|
||||
Length uint16
|
||||
Version uint16
|
||||
Random [32]byte
|
||||
SessionIDLength uint8
|
||||
CipherSuitesLength uint16
|
||||
CipherSuite uint16
|
||||
CompressionMethodsLength uint8
|
||||
CompressionMethod uint8
|
||||
ExtensionsLength uint16
|
||||
extensionServerName
|
||||
}
|
||||
type extensionServerName struct {
|
||||
Type uint16
|
||||
Length uint16
|
||||
serverNameIndicationExtension
|
||||
}
|
||||
type serverNameIndicationExtension struct {
|
||||
ServerNameListlength uint16
|
||||
ServerNameType uint8
|
||||
ServerNamelength uint16
|
||||
// ServerName []byte // Disable for fix length
|
||||
}
|
||||
|
||||
func NewClientHelloSNIMsg(domain string) []byte {
|
||||
length := uint16(len(domain))
|
||||
msg := &clientHelloSNI{
|
||||
ContentType: 0x16, // Content Type: Handshake (22)
|
||||
Version: 0x0301, // Version: TLS 1.0 (0x0301)
|
||||
Length: length + 56,
|
||||
handshakeProtocol: handshakeProtocol{
|
||||
HandshakeType: 0x01, // Handshake Type: Client Hello (1)
|
||||
Length: length + 52,
|
||||
Version: 0x0303, // Version: TLS 1.2 (0x0303)
|
||||
Random: [32]byte{}, // [32]byte{},
|
||||
SessionIDLength: 0x0, // Session ID Length: 0
|
||||
CipherSuitesLength: 2, // Cipher Suites Length: 84
|
||||
CipherSuite: tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||||
CompressionMethodsLength: 1, // Compression Methods Length: 1
|
||||
CompressionMethod: 0x00, // Compression null
|
||||
ExtensionsLength: length + 9,
|
||||
extensionServerName: extensionServerName{
|
||||
Type: 0x0000, // Type: server_name (0)
|
||||
Length: length + 5,
|
||||
serverNameIndicationExtension: serverNameIndicationExtension{
|
||||
ServerNameListlength: length + 3,
|
||||
ServerNameType: 0x00, // Server Name Type: host_name (0)
|
||||
ServerNamelength: length,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(make([]byte, 0, length+61))
|
||||
binary.Write(buf, binary.BigEndian, msg)
|
||||
buf.WriteString(domain)
|
||||
return buf.Bytes()
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Node struct {
|
||||
Node map[string]*Node
|
||||
}
|
||||
|
||||
func NewNode() *Node {
|
||||
return &Node{Node: map[string]*Node{}}
|
||||
}
|
||||
func NewNodeFromRule(rules ...string) *Node {
|
||||
node := NewNode()
|
||||
for i := range rules {
|
||||
node.Add(strings.Split(rules[i], "."))
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
func (n *Node) String() string {
|
||||
return n.string("")
|
||||
}
|
||||
func (n *Node) string(prefix string) (out string) {
|
||||
for key, val := range n.Node {
|
||||
out += prefix + key + "\n" + val.string(prefix+" ")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (n *Node) Add(secs []string) {
|
||||
length := len(secs)
|
||||
switch length {
|
||||
case 0:
|
||||
return
|
||||
case 1:
|
||||
n.Node[secs[length-1]] = NewNode()
|
||||
default:
|
||||
subNode, ok := n.Node[secs[length-1]]
|
||||
if !ok {
|
||||
subNode = NewNode()
|
||||
n.Node[secs[length-1]] = subNode
|
||||
}
|
||||
subNode.Add(secs[:length-1])
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Node) Match(addr string) bool {
|
||||
return n.matchSecs(strings.Split(addr, "."))
|
||||
}
|
||||
|
||||
func (n *Node) matchSecs(secs []string) bool {
|
||||
length := len(secs)
|
||||
if length == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
if n, ok := n.Node[secs[length-1]]; ok {
|
||||
return n.matchSecs(secs[:length-1])
|
||||
}
|
||||
|
||||
if n, ok := n.Node["*"]; ok {
|
||||
return n.matchSecs(secs[:length-1])
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Code generated by "stringer -type=suggestLevel util.go"; DO NOT EDIT.
|
||||
|
||||
package dns
|
||||
|
||||
import "strconv"
|
||||
|
||||
const _suggestLevel_name = "DISABLEBLOCKSPEEDUPlevelEnd"
|
||||
|
||||
var _suggestLevel_index = [...]uint8{0, 7, 12, 19, 27}
|
||||
|
||||
func (i suggestLevel) String() string {
|
||||
if i < 0 || i >= suggestLevel(len(_suggestLevel_index)-1) {
|
||||
return "suggestLevel(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _suggestLevel_name[_suggestLevel_index[i]:_suggestLevel_index[i+1]]
|
||||
}
|
||||
+68
-11
@@ -1,19 +1,76 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/wweir/sower/conf"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/wweir/sower/util"
|
||||
)
|
||||
|
||||
var rule *Node
|
||||
var (
|
||||
blockList *util.Node
|
||||
suggestList *util.Node
|
||||
whiteList *util.Node
|
||||
)
|
||||
|
||||
func init() {
|
||||
rule = NewNodeFromRule(conf.Conf.BlockList...)
|
||||
glog.V(2).Infof("block rule:\n%s", rule)
|
||||
|
||||
conf.OnRefreash = append(conf.OnRefreash, func() error {
|
||||
rule = NewNodeFromRule(conf.Conf.BlockList...)
|
||||
glog.V(2).Infof("block rule:\n%s", rule)
|
||||
return nil
|
||||
})
|
||||
// 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=suggestLevel $GOFILE
|
||||
type suggestLevel int32
|
||||
|
||||
const (
|
||||
DISABLE suggestLevel = iota
|
||||
BLOCK
|
||||
SPEEDUP
|
||||
levelEnd
|
||||
)
|
||||
|
||||
func ListSuggestLevels() []string {
|
||||
list := make([]string, 0, int(levelEnd))
|
||||
for i := suggestLevel(0); i < levelEnd; i++ {
|
||||
list = append(list, i.String())
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func parseSuggestLevel(level string) suggestLevel {
|
||||
for i := suggestLevel(0); i < levelEnd; i++ {
|
||||
if level == i.String() {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
glog.Exitln("invalid suggest level: " + level)
|
||||
return levelEnd
|
||||
}
|
||||
|
||||
@@ -1,21 +1,29 @@
|
||||
module github.com/wweir/sower
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/bifurcation/mint v0.0.0-20181105071958-a14404e9a861 // indirect
|
||||
github.com/bifurcation/mint v0.0.0-20181105071958-a14404e9a861 // indirect; lock for quic
|
||||
github.com/cheekybits/genny v1.0.0 // indirect
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
|
||||
github.com/golang/mock v1.1.1 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.0 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.1 // indirect
|
||||
github.com/klauspost/cpuid v1.2.0 // indirect
|
||||
github.com/klauspost/reedsolomon v1.9.1 // indirect
|
||||
github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414
|
||||
github.com/libp2p/go-reuseport v0.0.1
|
||||
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f // indirect
|
||||
github.com/lucas-clemente/quic-go v0.10.0
|
||||
github.com/lucas-clemente/quic-go v0.10.1
|
||||
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced // indirect
|
||||
github.com/miekg/dns v1.0.15
|
||||
github.com/onsi/ginkgo v1.7.0 // indirect
|
||||
github.com/onsi/gomega v1.4.3 // indirect
|
||||
github.com/wweir/fsnotify v1.4.8
|
||||
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869 // indirect
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a // indirect
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b // indirect
|
||||
github.com/miekg/dns v1.1.6
|
||||
github.com/pelletier/go-toml v1.2.0
|
||||
github.com/pkg/errors v0.8.1
|
||||
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.0.7+incompatible
|
||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a
|
||||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977 // indirect
|
||||
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e
|
||||
)
|
||||
|
||||
@@ -1,54 +1,53 @@
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/bifurcation/mint v0.0.0-20181105071958-a14404e9a861 h1:x17NvoJaphEzay72TFej4OSSsgu3xRYBLkbIwdofS/4=
|
||||
github.com/bifurcation/mint v0.0.0-20181105071958-a14404e9a861/go.mod h1:zVt7zX3K/aDCk9Tj+VM7YymsX66ERvzCJzw8rFCX2JU=
|
||||
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||
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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
|
||||
github.com/golang/mock v1.1.1/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/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/reedsolomon v1.9.1 h1:kYrT1MlR4JH6PqOpC+okdb9CDTcwEC/BqpzK4WFyXL8=
|
||||
github.com/klauspost/reedsolomon v1.9.1/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
|
||||
github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414 h1:6wnYc2S/lVM7BvR32BM74ph7bPgqMztWopMYKgVyEho=
|
||||
github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414/go.mod h1:0AqAH3ZogsCrvrtUpvc6EtVKbc3w6xwZhkvGLuqyi3o=
|
||||
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/aes12 v0.0.0-20171027163421-cd47fb39b79f h1:sSeNEkJrs+0F9TUau0CgWTTNEwF23HST3Eq0A+QIx+A=
|
||||
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f/go.mod h1:JpH9J1c9oX6otFSgdUHwUBUizmKlrMjxWnIAjff4m04=
|
||||
github.com/lucas-clemente/quic-go v0.10.0 h1:xEF+pSHYAOcu+U10Meunf+DTtc8vhQDRqlA0BJ6hufc=
|
||||
github.com/lucas-clemente/quic-go v0.10.0/go.mod h1:wuD+2XqEx8G9jtwx5ou2BEYBsE+whgQmlj0Vz/77PrY=
|
||||
github.com/lucas-clemente/quic-go v0.10.1 h1:ipcMmYP9RT+b1YytOKGUY1qndxPGOczVEQkAVz3CZrs=
|
||||
github.com/lucas-clemente/quic-go v0.10.1/go.mod h1:wuD+2XqEx8G9jtwx5ou2BEYBsE+whgQmlj0Vz/77PrY=
|
||||
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced h1:zqEC1GJZFbGZA0tRyNZqRjep92K5fujFtFsu5ZW7Aug=
|
||||
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced/go.mod h1:NCcRLrOTZbzhZvixZLlERbJtDtYsmMw8Jc4vS8Z0g58=
|
||||
github.com/miekg/dns v1.0.15 h1:9+UupePBQCG6zf1q/bGmTO1vumoG13jsrbWOSX1W6Tw=
|
||||
github.com/miekg/dns v1.0.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
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/wweir/fsnotify v1.4.8 h1:zxOoqwE5p91F3rmcYLQheoSeTlIVqwi2b5T7UlpElWE=
|
||||
github.com/wweir/fsnotify v1.4.8/go.mod h1:4GuxIc23DXfJI1/LhBXG5eE/wJ4CzL+kk5M7woxVprc=
|
||||
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869 h1:kkXA53yGe04D0adEYJwEVQjeBppL01Exg+fnMjfUraU=
|
||||
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/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-20181122145206-62eef0e2fa9b h1:MQE+LT/ABUuuvEZ+YQAMSXindAdUh7slEmAkup74op4=
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
github.com/miekg/dns v1.1.6 h1:jVwb4GDwD65q/gtItR/lIZHjNH93QfeGxZUkzJcW9mc=
|
||||
github.com/miekg/dns v1.1.6/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
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/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.0.7+incompatible h1:zs9tc8XRID0m+aetu3qPWZFyRt2UIMqbXIBgw+vcnlE=
|
||||
github.com/xtaci/kcp-go v5.0.7+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a h1:YX8ljsm6wXlHZO+aRz9Exqr0evNhKRNe5K/gi+zKh4U=
|
||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977 h1:actzWV6iWn3GLqN8dZjzsB+CLt+gaV2+wsxroxiQI8I=
|
||||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e h1:ZytStCyV048ZqDsWHiYDdoI2Vd4msMcrDECFxS+tL9c=
|
||||
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
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 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
||||
@@ -1,19 +1,43 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"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"
|
||||
)
|
||||
|
||||
var version, date string
|
||||
|
||||
func main() {
|
||||
glog.Infoln("Starting:", conf.Conf)
|
||||
cfg := &conf.Conf
|
||||
glog.Infof("Starting sower(%s %s): %v", version, date, cfg)
|
||||
|
||||
tran, err := transport.GetTransport(cfg.NetType)
|
||||
if err != nil {
|
||||
glog.Exitln(err)
|
||||
}
|
||||
|
||||
if cfg.ServerAddr == "" {
|
||||
proxy.StartServer(tran, cfg.ServerPort, cfg.Cipher, cfg.Password)
|
||||
|
||||
if conf.Conf.ServerAddr == "" {
|
||||
proxy.StartServer(conf.Conf.ServerPort)
|
||||
} else {
|
||||
go dns.StartDNS(conf.Conf.DnsServer)
|
||||
proxy.StartClient(conf.Conf.ServerAddr)
|
||||
conf.AddRefreshFn(true, func() (string, error) {
|
||||
host, _, _ := net.SplitHostPort(cfg.ServerAddr)
|
||||
dns.LoadRules(cfg.BlockList, cfg.Suggestions, cfg.WhiteList, host)
|
||||
return "load rules", nil
|
||||
})
|
||||
|
||||
isSocks5 := (cfg.NetType == "SOCKS5")
|
||||
|
||||
if cfg.HTTPProxy != "" {
|
||||
go proxy.StartHttpProxy(tran, isSocks5, cfg.ServerAddr, cfg.Cipher, cfg.Password, cfg.HTTPProxy)
|
||||
}
|
||||
|
||||
go dns.StartDNS(cfg.DNSServer, cfg.ClientIP, conf.SuggestCh, cfg.SuggestLevel)
|
||||
proxy.StartClient(tran, isSocks5, cfg.ServerAddr, cfg.Cipher, cfg.Password, cfg.ClientIP)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type TeeConn struct {
|
||||
net.Conn
|
||||
buf []byte
|
||||
offset int
|
||||
Tee bool // read
|
||||
}
|
||||
|
||||
func (t *TeeConn) Reset() {
|
||||
t.offset = 0
|
||||
}
|
||||
|
||||
func (t *TeeConn) Read(b []byte) (n int, err error) {
|
||||
length := len(t.buf) - t.offset
|
||||
if length > 0 {
|
||||
n = copy(b, t.buf[t.offset:])
|
||||
t.offset += n
|
||||
return
|
||||
}
|
||||
|
||||
n, err = t.Conn.Read(b)
|
||||
if t.Tee {
|
||||
t.buf = append(t.buf, b[:n]...)
|
||||
t.offset += n
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func ParseAddr(conn net.Conn) (teeConn *TeeConn, addr string, err error) {
|
||||
teeConn = &TeeConn{Conn: conn, Tee: true}
|
||||
defer func() {
|
||||
teeConn.Reset()
|
||||
teeConn.Tee = false
|
||||
}()
|
||||
|
||||
buf := make([]byte, 1)
|
||||
if n, err := teeConn.Read(buf); err != nil || n != 1 {
|
||||
return teeConn, "", fmt.Errorf("Read conn fail: %v, readed: %d %v", err, n, buf)
|
||||
}
|
||||
|
||||
teeConn.Reset()
|
||||
|
||||
// https
|
||||
if buf[0] == 0x16 { // SSL handleshake
|
||||
host, _, err := extractSNI(io.Reader(teeConn))
|
||||
if err != nil {
|
||||
return teeConn, "", err
|
||||
}
|
||||
if strings.Contains(host, ":") {
|
||||
return teeConn, host, nil
|
||||
}
|
||||
return teeConn, host + ":443", nil
|
||||
}
|
||||
|
||||
// http
|
||||
b := bufio.NewReader(teeConn)
|
||||
resp, err := http.ReadRequest(b)
|
||||
if err != nil {
|
||||
return teeConn, "", err
|
||||
}
|
||||
if strings.Contains(resp.Host, ":") {
|
||||
return teeConn, resp.Host, nil
|
||||
}
|
||||
return teeConn, resp.Host + ":80", nil
|
||||
}
|
||||
+26
-62
@@ -1,85 +1,48 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/wweir/sower/proxy/shadow"
|
||||
"github.com/wweir/sower/proxy/transport"
|
||||
)
|
||||
|
||||
func StartClient(server string) {
|
||||
connCh := listenLocal([]string{":80", ":443"})
|
||||
reDialCh := make(chan net.Conn, 10)
|
||||
var conn net.Conn
|
||||
func StartClient(tran transport.Transport, isSocks5 bool, server, cipher, password, listenIP string) {
|
||||
connCh := listenLocal(listenIP, []string{"80", "443"})
|
||||
|
||||
glog.Infoln("Client started.")
|
||||
for {
|
||||
sess, err := quic.DialAddr(server, &tls.Config{InsecureSkipVerify: true}, nil)
|
||||
conn := <-connCh
|
||||
|
||||
resolveAddr(&server)
|
||||
glog.V(1).Infof("new conn from (%s) to (%s)", conn.RemoteAddr(), server)
|
||||
|
||||
rc, err := tran.Dial(server)
|
||||
if err != nil {
|
||||
if sess, err = quic.DialAddr(server, &tls.Config{InsecureSkipVerify: true}, nil); err != nil {
|
||||
glog.Errorf("connect to remote(%s) fail:%s\n", server, err)
|
||||
time.Sleep(2 * time.Second)
|
||||
conn.Close()
|
||||
glog.Errorln(err)
|
||||
continue
|
||||
}
|
||||
|
||||
if isSocks5 {
|
||||
if rc, conn, err = buildSocks5Conn(rc, conn); err != nil {
|
||||
glog.Errorln(err)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
rc = shadow.Shadow(rc, cipher, password)
|
||||
}
|
||||
glog.Infoln("new session to", sess.RemoteAddr())
|
||||
|
||||
for { // session rotate logic
|
||||
select {
|
||||
case conn = <-connCh:
|
||||
case conn = <-reDialCh:
|
||||
}
|
||||
|
||||
// sync action to reuse sigle sess
|
||||
if !openStream(conn, sess, reDialCh) {
|
||||
sess.Close()
|
||||
break
|
||||
}
|
||||
}
|
||||
go relay(conn, rc)
|
||||
}
|
||||
}
|
||||
|
||||
func openStream(conn net.Conn, sess quic.Session, reDialCh chan<- net.Conn) bool {
|
||||
glog.V(1).Infoln("new request from", conn.RemoteAddr())
|
||||
|
||||
okCh := make(chan struct{})
|
||||
go func() {
|
||||
stream, err := sess.OpenStream()
|
||||
if err != nil {
|
||||
glog.Warningf("connect to remote(%s) fail:%s\n", sess.RemoteAddr(), err)
|
||||
reDialCh <- conn
|
||||
close(okCh)
|
||||
return
|
||||
}
|
||||
defer stream.Close()
|
||||
|
||||
select {
|
||||
case okCh <- struct{}{}:
|
||||
default:
|
||||
close(okCh)
|
||||
return
|
||||
}
|
||||
close(okCh)
|
||||
|
||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
relay(&streamConn{stream, sess}, conn)
|
||||
conn.Close()
|
||||
}()
|
||||
|
||||
select {
|
||||
case _, ok := <-okCh: // false means close on error
|
||||
return ok
|
||||
case <-time.After(time.Second):
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func listenLocal(ports []string) <-chan net.Conn {
|
||||
func listenLocal(listenIP string, ports []string) <-chan net.Conn {
|
||||
connCh := make(chan net.Conn, 10)
|
||||
for i := range ports {
|
||||
go func(port string) {
|
||||
ln, err := net.Listen("tcp", port)
|
||||
ln, err := net.Listen("tcp", net.JoinHostPort(listenIP, port))
|
||||
if err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
@@ -87,10 +50,11 @@ func listenLocal(ports []string) <-chan net.Conn {
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
glog.Errorln("accept", port, "fail:", err)
|
||||
glog.Errorln("accept", listenIP+port, "fail:", err)
|
||||
continue
|
||||
}
|
||||
|
||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
connCh <- conn
|
||||
}
|
||||
}(ports[i])
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/wweir/sower/proxy/shadow"
|
||||
"github.com/wweir/sower/proxy/transport"
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
if r.Method == http.MethodConnect {
|
||||
httpsProxy(w, r, tran, isSocks5, server, cipher, password)
|
||||
} else {
|
||||
httpProxy(w, r, tran, isSocks5, server, cipher, password)
|
||||
}
|
||||
}),
|
||||
// Disable HTTP/2.
|
||||
TLSNextProto: map[string]func(*http.Server, *tls.Conn, http.Handler){},
|
||||
IdleTimeout: 90 * time.Second,
|
||||
}
|
||||
|
||||
glog.Fatalln(srv.ListenAndServe())
|
||||
}
|
||||
|
||||
func httpProxy(w http.ResponseWriter, req *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
|
||||
}
|
||||
return shadow.Shadow(conn, cipher, password), nil
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := roundTripper.RoundTrip(req)
|
||||
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)
|
||||
}
|
||||
|
||||
func httpsProxy(w http.ResponseWriter, r *http.Request,
|
||||
tran transport.Transport, isSocks5 bool, server, cipher, password string) {
|
||||
|
||||
// local conn
|
||||
conn, _, err := w.(http.Hijacker).Hijack()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
|
||||
if _, err := conn.Write([]byte(r.Proto + " 200 Connection established\r\n\r\n")); err != nil {
|
||||
conn.Close()
|
||||
glog.Errorln("serve https proxy, write data fail:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// remote conn
|
||||
rc, err := tran.Dial(server)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
||||
glog.Errorln("serve https proxy, dial remote fail:", err)
|
||||
return
|
||||
}
|
||||
|
||||
if isSocks5 {
|
||||
if rc, conn, err = buildSocks5Conn(rc, conn); err != nil {
|
||||
glog.Errorln(err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
rc = shadow.Shadow(rc, cipher, password)
|
||||
}
|
||||
|
||||
relay(rc, conn)
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/wweir/sower/util"
|
||||
)
|
||||
|
||||
func ParseAddr(conn net.Conn) (teeConn *util.TeeConn, addr string, err error) {
|
||||
teeConn = &util.TeeConn{Conn: conn}
|
||||
teeConn.StartOrReset()
|
||||
defer teeConn.Stop()
|
||||
|
||||
buf := make([]byte, 1)
|
||||
if n, err := teeConn.Read(buf); err != nil {
|
||||
return teeConn, "", fmt.Errorf("Read conn fail: %v, readed: %v", err, buf[:n])
|
||||
}
|
||||
|
||||
teeConn.StartOrReset()
|
||||
|
||||
// https
|
||||
if buf[0] == 0x16 { // SSL handleshake
|
||||
host, _, err := extractSNI(io.Reader(teeConn))
|
||||
if err != nil {
|
||||
return teeConn, "", err
|
||||
}
|
||||
if strings.Contains(host, ":") {
|
||||
return teeConn, host, nil
|
||||
}
|
||||
return teeConn, host + ":443", nil
|
||||
}
|
||||
|
||||
// http
|
||||
b := bufio.NewReader(teeConn)
|
||||
resp, err := http.ReadRequest(b)
|
||||
if err != nil {
|
||||
return teeConn, "", err
|
||||
}
|
||||
if strings.Contains(resp.Host, ":") {
|
||||
return teeConn, resp.Host, nil
|
||||
}
|
||||
return teeConn, resp.Host + ":80", nil
|
||||
}
|
||||
+25
-34
@@ -2,60 +2,51 @@ package proxy
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/wweir/sower/parser"
|
||||
"github.com/wweir/sower/proxy/parser"
|
||||
"github.com/wweir/sower/proxy/shadow"
|
||||
"github.com/wweir/sower/proxy/transport"
|
||||
)
|
||||
|
||||
func StartServer(port string) {
|
||||
ln, err := quic.ListenAddr(":"+port, mockTlsPem(), nil)
|
||||
if err != nil {
|
||||
glog.Fatalln(err)
|
||||
func StartServer(tran transport.Transport, port, cipher, password string) {
|
||||
if port == "" {
|
||||
glog.Fatalln("port must set")
|
||||
}
|
||||
if !strings.Contains(port, ":") {
|
||||
port = ":" + port
|
||||
}
|
||||
|
||||
connCh, err := tran.Listen(port)
|
||||
if err != nil {
|
||||
glog.Fatalf("listen %v fail: %s", port, err)
|
||||
}
|
||||
|
||||
glog.Infoln("Server started.")
|
||||
for {
|
||||
sess, err := ln.Accept()
|
||||
if err != nil {
|
||||
glog.Errorln(err)
|
||||
continue
|
||||
}
|
||||
go acceptSession(sess)
|
||||
conn := <-connCh
|
||||
conn = shadow.Shadow(conn, cipher, password)
|
||||
|
||||
go handle(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func acceptSession(sess quic.Session) {
|
||||
glog.V(1).Infoln("new session from ", sess.RemoteAddr())
|
||||
defer sess.Close()
|
||||
|
||||
for {
|
||||
stream, err := sess.AcceptStream()
|
||||
if err != nil {
|
||||
glog.Errorln(err)
|
||||
return
|
||||
}
|
||||
|
||||
go acceptStream(stream, sess)
|
||||
}
|
||||
}
|
||||
|
||||
func acceptStream(stream quic.Stream, sess quic.Session) {
|
||||
glog.V(1).Infoln("new stream from ", sess.RemoteAddr())
|
||||
defer stream.Close()
|
||||
|
||||
conn, addr, err := parser.ParseAddr(&streamConn{stream, sess})
|
||||
func handle(conn net.Conn) {
|
||||
conn, addr, err := parser.ParseAddr(conn)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
glog.Warningln(err)
|
||||
return
|
||||
}
|
||||
glog.V(1).Infoln(addr)
|
||||
glog.V(1).Infof("new conn from %s to %s", conn.RemoteAddr(), addr)
|
||||
|
||||
rc, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
glog.Warningln(err)
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
rc.(*net.TCPConn).SetKeepAlive(true)
|
||||
|
||||
relay(rc, conn)
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package shadow
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
)
|
||||
|
||||
//go:generate stringer -type=cipherType $GOFILE
|
||||
type cipherType int
|
||||
|
||||
const (
|
||||
AES_128_GCM cipherType = 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 := cipherType(0); i < cipherEnd; i++ {
|
||||
list = append(list, i.String())
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func pickCipher(cipherType, password string) (cipher.AEAD, error) {
|
||||
var blockSize int
|
||||
switch cipherType {
|
||||
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: " + cipherType)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Code generated by "stringer -type=cipherType cipher.go"; DO NOT EDIT.
|
||||
|
||||
package shadow
|
||||
|
||||
import "strconv"
|
||||
|
||||
const _cipherType_name = "AES_128_GCMAES_192_GCMAES_256_GCMCHACHA20_IETF_POLY1305XCHACHA20_IETF_POLY1305cipherEnd"
|
||||
|
||||
var _cipherType_index = [...]uint8{0, 11, 22, 33, 55, 78, 87}
|
||||
|
||||
func (i cipherType) String() string {
|
||||
if i < 0 || i >= cipherType(len(_cipherType_index)-1) {
|
||||
return "cipherType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _cipherType_name[_cipherType_index[i]:_cipherType_index[i+1]]
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// Package shadow transter conn to be a crypto conn
|
||||
// support aead mode only
|
||||
// payload: [size](2+Overhead bytes) + [content](size+Overhead bytes)
|
||||
package shadow
|
||||
@@ -0,0 +1,111 @@
|
||||
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&0xFF00)>>8), byte(dataSize&0xFF)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package socks5
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func ToSocks5(c net.Conn, domain, port string) net.Conn {
|
||||
switch port {
|
||||
case "80":
|
||||
return &conn{init: make(chan struct{}), Conn: c, domain: domain, port: []byte{0x00, 0x50}}
|
||||
case "443":
|
||||
return &conn{init: make(chan struct{}), Conn: c, domain: domain, port: []byte{0x01, 0xbb}}
|
||||
default:
|
||||
panic("invalid port: " + port)
|
||||
}
|
||||
}
|
||||
|
||||
type conn struct {
|
||||
init chan struct{}
|
||||
domain string
|
||||
port []byte
|
||||
net.Conn
|
||||
}
|
||||
|
||||
func (c *conn) Read(b []byte) (n int, err error) {
|
||||
<-c.init
|
||||
return c.Conn.Read(b)
|
||||
}
|
||||
|
||||
func (c *conn) Write(b []byte) (n int, err error) {
|
||||
select {
|
||||
case <-c.init:
|
||||
return c.Conn.Write(b)
|
||||
default:
|
||||
}
|
||||
|
||||
{
|
||||
req := &authReq{
|
||||
VER: 5,
|
||||
NMETHODS: 1,
|
||||
METHODS: [1]byte{0}, // NO AUTHENTICATION REQUIRED
|
||||
}
|
||||
if err := binary.Write(c.Conn, binary.BigEndian, req); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
{
|
||||
resp := &authResp{}
|
||||
if err := binary.Read(c.Conn, binary.BigEndian, resp); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
{
|
||||
req := &request{
|
||||
req: req{
|
||||
VER: 5, // socks5
|
||||
CMD: 1, // CONNECT
|
||||
RSV: 0, // RESERVED
|
||||
ATYP: 3, // DOMAINNAME
|
||||
},
|
||||
DST_ADDR: append([]byte{byte(len(c.domain))}, []byte(c.domain)...),
|
||||
DST_PORT: c.port,
|
||||
}
|
||||
|
||||
if _, err := c.Conn.Write(req.Bytes()); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
{
|
||||
resp := &response{}
|
||||
if err := binary.Read(c.Conn, binary.BigEndian, &(resp.resp)); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
switch resp.REP {
|
||||
case 0x00:
|
||||
default:
|
||||
return 0, errors.Errorf("socks5 handshake fail, return code: %d", resp.REP)
|
||||
}
|
||||
|
||||
switch resp.ATYP {
|
||||
case 0x01: // IPv4
|
||||
resp.DST_ADDR = make([]byte, net.IPv4len)
|
||||
if _, err := io.ReadFull(c.Conn, resp.DST_ADDR); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case 0x03: // domain name
|
||||
if _, err := io.ReadFull(c.Conn, resp.DST_ADDR[:1]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := io.ReadFull(c.Conn, resp.DST_ADDR[1:1+int(resp.DST_ADDR[0])]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case 0x04:
|
||||
resp.DST_ADDR = make([]byte, net.IPv6len)
|
||||
if _, err := io.ReadFull(c.Conn, resp.DST_ADDR); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
resp.DST_PORT = make([]byte, 2)
|
||||
if _, err := io.ReadFull(c.Conn, resp.DST_PORT); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
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,115 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"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()
|
||||
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()
|
||||
if err != nil {
|
||||
glog.Errorln(err)
|
||||
return
|
||||
}
|
||||
|
||||
connCh <- &streamConn{stream, sess}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
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
|
||||
}
|
||||
+44
-46
@@ -1,74 +1,72 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"io"
|
||||
"math/big"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/wweir/sower/proxy/parser"
|
||||
"github.com/wweir/sower/proxy/socks5"
|
||||
)
|
||||
|
||||
type streamConn struct {
|
||||
quic.Stream
|
||||
sess quic.Session
|
||||
// race safe
|
||||
var resolved = false
|
||||
|
||||
func resolveAddr(server *string) {
|
||||
if !resolved {
|
||||
if addr, err := net.ResolveTCPAddr("tcp", *server); err != nil {
|
||||
glog.Errorln(err)
|
||||
} else {
|
||||
*server = addr.String()
|
||||
resolved = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *streamConn) LocalAddr() net.Addr {
|
||||
return s.sess.LocalAddr()
|
||||
}
|
||||
func buildSocks5Conn(c, relay net.Conn) (net.Conn, net.Conn, error) {
|
||||
conn, addr, err := parser.ParseAddr(relay)
|
||||
if err != nil {
|
||||
c.Close()
|
||||
relay.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
func (s *streamConn) RemoteAddr() net.Addr {
|
||||
return s.sess.RemoteAddr()
|
||||
host, port, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
c.Close()
|
||||
relay.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return socks5.ToSocks5(c, host, port), conn, nil
|
||||
}
|
||||
|
||||
func relay(conn1, conn2 net.Conn) {
|
||||
wg := &sync.WaitGroup{}
|
||||
exitFlag := new(int32)
|
||||
wg.Add(2)
|
||||
go redirect(conn1, conn2, wg, exitFlag)
|
||||
redirect(conn2, conn1, wg, exitFlag)
|
||||
go redirect(conn2, conn1, wg, exitFlag)
|
||||
redirect(conn1, conn2, wg, exitFlag)
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func redirect(conn1, conn2 net.Conn, wg *sync.WaitGroup, exitFlag *int32) {
|
||||
if _, err := io.Copy(conn2, conn1); err != nil && (atomic.LoadInt32(exitFlag) == 0) {
|
||||
glog.V(1).Infof("%s<>%s -> %s<>%s: %s", conn1.RemoteAddr(), conn1.LocalAddr(), conn2.LocalAddr(), conn2.RemoteAddr(), err)
|
||||
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)
|
||||
}
|
||||
|
||||
if atomic.CompareAndSwapInt32(exitFlag, 0, 1) {
|
||||
// wakeup blocked goroutine
|
||||
now := time.Now()
|
||||
src.SetDeadline(now)
|
||||
dst.SetDeadline(now)
|
||||
} else {
|
||||
src.Close()
|
||||
dst.Close()
|
||||
}
|
||||
|
||||
// wakeup all conn goroutine
|
||||
atomic.AddInt32(exitFlag, 1)
|
||||
now := time.Now()
|
||||
conn1.SetDeadline(now)
|
||||
conn2.SetDeadline(now)
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
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}}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// +build !windows
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
)
|
||||
|
||||
// PickInterface pick the first active net interface
|
||||
func PickInterface() (*Iface, error) {
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range ifaces {
|
||||
if len(ifaces[i].HardwareAddr) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
addrs, _ := ifaces[i].Addrs()
|
||||
for _, addr := range addrs {
|
||||
if ip := addr.(*net.IPNet).IP.To4(); ip != nil {
|
||||
return &Iface{ifaces[i].HardwareAddr, ip}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, errors.New("no valid interface")
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPickInterface(t *testing.T) {
|
||||
t.Skip("skip for some enviroment not have net interface")
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// +build windows
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// PickInterface pick the first active net interface
|
||||
func PickInterface() (*Iface, error) {
|
||||
list, err := getAdapterList()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for {
|
||||
if list.DhcpEnabled == 1 && list.LeaseObtained != 0 {
|
||||
ip := bytes.TrimRight(list.IpAddressList.IpAddress.String[:], string([]byte{0}))
|
||||
return &Iface{net.HardwareAddr(list.Address[:list.AddressLength]), net.ParseIP(string(ip))}, nil
|
||||
}
|
||||
|
||||
if list.Next != nil {
|
||||
list = list.Next
|
||||
} else {
|
||||
return nil, errors.New("no valid interface")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getAdapterList() (*syscall.IpAdapterInfo, error) {
|
||||
b := make([]byte, 1000)
|
||||
l := uint32(len(b))
|
||||
a := (*syscall.IpAdapterInfo)(unsafe.Pointer(&b[0]))
|
||||
// TODO(mikio): GetAdaptersInfo returns IP_ADAPTER_INFO that
|
||||
// contains IPv4 address list only. We should use another API
|
||||
// for fetching IPv6 stuff from the kernel.
|
||||
err := syscall.GetAdaptersInfo(a, &l)
|
||||
if err == syscall.ERROR_BUFFER_OVERFLOW {
|
||||
b = make([]byte, l)
|
||||
a = (*syscall.IpAdapterInfo)(unsafe.Pointer(&b[0]))
|
||||
err = syscall.GetAdaptersInfo(a, &l)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, os.NewSyscallError("GetAdaptersInfo", err)
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ReverseSecSlice struct {
|
||||
sort.StringSlice
|
||||
}
|
||||
|
||||
func NewReverseSecSlice(a []string) *ReverseSecSlice {
|
||||
return &ReverseSecSlice{sort.StringSlice(a)}
|
||||
}
|
||||
|
||||
func (p *ReverseSecSlice) Sort() *ReverseSecSlice {
|
||||
sort.Sort(p)
|
||||
return p
|
||||
}
|
||||
func (p *ReverseSecSlice) Uniq() []string {
|
||||
olds := []string(p.StringSlice)
|
||||
|
||||
last := ""
|
||||
strs := make([]string, 0, len(olds))
|
||||
for _, str := range olds {
|
||||
if str != last {
|
||||
strs = append(strs, str)
|
||||
}
|
||||
last = str
|
||||
}
|
||||
return strs
|
||||
}
|
||||
|
||||
func (p *ReverseSecSlice) Less(i, j int) bool {
|
||||
secsI := strings.Split(p.StringSlice[i], ".")
|
||||
secsJ := strings.Split(p.StringSlice[j], ".")
|
||||
|
||||
lenI := len(secsI) - 1
|
||||
lenJ := len(secsJ) - 1
|
||||
length := lenI
|
||||
if lenI > lenJ {
|
||||
length = lenJ
|
||||
}
|
||||
|
||||
for idx := 0; idx <= length; idx++ {
|
||||
if secsI[lenI-idx] == secsJ[lenJ-idx] {
|
||||
continue
|
||||
}
|
||||
return secsI[lenI-idx] < secsJ[lenJ-idx]
|
||||
}
|
||||
return lenI < lenJ
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReverseSecSlice_Less(t *testing.T) {
|
||||
type fields struct {
|
||||
StringSlice sort.StringSlice
|
||||
}
|
||||
type args struct {
|
||||
i int
|
||||
j int
|
||||
}
|
||||
slice := fields{sort.StringSlice([]string{
|
||||
"a.b.c",
|
||||
"a.b.c",
|
||||
"d.b.c",
|
||||
"d.a.b.c",
|
||||
"",
|
||||
"",
|
||||
})}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want bool
|
||||
}{{
|
||||
name: "equal",
|
||||
fields: slice,
|
||||
args: args{0, 1},
|
||||
want: false,
|
||||
}, {
|
||||
name: "less",
|
||||
fields: slice,
|
||||
args: args{0, 2},
|
||||
want: true,
|
||||
}, {
|
||||
name: "length",
|
||||
fields: slice,
|
||||
args: args{0, 3},
|
||||
want: true,
|
||||
}, {
|
||||
name: "single_empty",
|
||||
fields: slice,
|
||||
args: args{0, 4},
|
||||
want: false,
|
||||
}, {
|
||||
name: "all_empty",
|
||||
fields: slice,
|
||||
args: args{4, 5},
|
||||
want: false,
|
||||
}}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
p := &ReverseSecSlice{
|
||||
StringSlice: tt.fields.StringSlice,
|
||||
}
|
||||
if got := p.Less(tt.args.i, tt.args.j); got != tt.want {
|
||||
t.Errorf("ReverseSecSlice.Less() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Node struct {
|
||||
node
|
||||
sep string
|
||||
*sync.RWMutex
|
||||
}
|
||||
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)
|
||||
for i := range rules {
|
||||
n.Add(rules[i])
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (n *Node) String() string {
|
||||
n.RLock()
|
||||
defer n.RUnlock()
|
||||
return n.string("", " ")
|
||||
}
|
||||
func (n *node) string(prefix, indent string) (out string) {
|
||||
for key, val := range n.node {
|
||||
out += prefix + key + "\n" + val.string(prefix+indent, indent)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (n *Node) trim(item string) string {
|
||||
return strings.TrimSuffix(item, n.sep)
|
||||
}
|
||||
|
||||
func (n *Node) Add(item string) {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
n.add(strings.Split(n.trim(item), n.sep))
|
||||
}
|
||||
func (n *node) add(secs []string) {
|
||||
length := len(secs)
|
||||
switch length {
|
||||
case 0:
|
||||
return
|
||||
case 1:
|
||||
n.node[secs[length-1]] = &node{node: map[string]*node{"": &node{}}}
|
||||
default:
|
||||
sec := secs[length-1]
|
||||
if sec == "**" {
|
||||
sec = "*"
|
||||
}
|
||||
|
||||
subNode, ok := n.node[sec]
|
||||
if !ok {
|
||||
subNode = &node{node: map[string]*node{}}
|
||||
n.node[sec] = subNode
|
||||
}
|
||||
subNode.add(secs[:length-1])
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Node) Match(item string) bool {
|
||||
return n.matchSecs(strings.Split(n.trim(item), n.sep), false)
|
||||
}
|
||||
|
||||
func (n *node) matchSecs(secs []string, fuzzNode bool) bool {
|
||||
length := len(secs)
|
||||
if length == 0 {
|
||||
if _, ok := n.node[""]; ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := n.node["**"]; ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := n.node["*"]; ok {
|
||||
return !fuzzNode
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if n, ok := n.node[secs[length-1]]; ok {
|
||||
if n.matchSecs(secs[:length-1], false) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if n, ok := n.node["*"]; ok {
|
||||
if n.matchSecs(secs[:length-1], true) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if _, ok := n.node["**"]; ok {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNode_Match(t *testing.T) {
|
||||
type test struct {
|
||||
arg string
|
||||
want bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
node *Node
|
||||
tests []test
|
||||
}{{
|
||||
"simple",
|
||||
NewNodeFromRules(".", "a.wweir.cc", "b.wweir.cc"),
|
||||
[]test{
|
||||
{"a.wweir.cc", true},
|
||||
{"b.wweir.cc", true},
|
||||
},
|
||||
}, {
|
||||
"parent",
|
||||
NewNodeFromRules(".", "wweir.cc", "a.wweir.cc"),
|
||||
[]test{
|
||||
{"wweir.cc", true},
|
||||
{"a.wweir.cc", true},
|
||||
{"b.wweir.cc", false},
|
||||
},
|
||||
}, {
|
||||
"fuzz1",
|
||||
NewNodeFromRules(".", "wweir.cc", "a.wweir.cc", "*.wweir.cc"),
|
||||
[]test{
|
||||
{"wweir.cc", true},
|
||||
{"a.wweir.cc", true},
|
||||
{"b.wweir.cc", true},
|
||||
{"a.b.wweir.cc", false},
|
||||
},
|
||||
}, {
|
||||
"fuzz2",
|
||||
NewNodeFromRules(".", "a.*.cc", "c.wweir.*"),
|
||||
[]test{
|
||||
{"wweir.cc", false},
|
||||
{"a.wweir.cc", true},
|
||||
{"b.wweir.cc", false},
|
||||
{"c.wweir.cc", true},
|
||||
},
|
||||
}, {
|
||||
"fuzz3",
|
||||
NewNodeFromRules(".", "*.*.cc", "iamp.*.*"),
|
||||
[]test{
|
||||
{"wweir.cc", false},
|
||||
{"a.wweir.cc", true},
|
||||
{"b.wweir.cc", true},
|
||||
{"iamp.wweir.cc", true},
|
||||
},
|
||||
}, {
|
||||
"fuzz4",
|
||||
NewNodeFromRules(".", "**.cc", "a.**.com", "**.wweir.*"),
|
||||
[]test{
|
||||
{"wweir.cc", true},
|
||||
{"a.wweir.cc", true},
|
||||
{"a.b.wweir.cc", true},
|
||||
{"a.fuzz.com", true},
|
||||
{"b.fuzz.com", false},
|
||||
{"www.wweir.com", true},
|
||||
},
|
||||
}}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
for _, test := range tt.tests {
|
||||
if got := tt.node.Match(test.arg); got != test.want {
|
||||
t.Errorf("Node.Match(%s) = %v, want %v", test.arg, got, test.want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
type TeeConn struct {
|
||||
net.Conn
|
||||
buf []byte
|
||||
offset int
|
||||
tee bool // read
|
||||
}
|
||||
|
||||
func (t *TeeConn) StartOrReset() {
|
||||
t.offset = 0
|
||||
t.tee = true
|
||||
}
|
||||
func (t *TeeConn) Stop() {
|
||||
t.offset = 0
|
||||
t.tee = false
|
||||
}
|
||||
|
||||
func (t *TeeConn) Read(b []byte) (n int, err error) {
|
||||
length := len(t.buf) - t.offset
|
||||
if length > 0 {
|
||||
n = copy(b, t.buf[t.offset:])
|
||||
t.offset += n
|
||||
return
|
||||
}
|
||||
|
||||
n, err = t.Conn.Read(b)
|
||||
if t.tee {
|
||||
t.buf = append(t.buf, b[:n]...)
|
||||
t.offset += n
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
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