mirror of
https://github.com/wweir/sower.git
synced 2026-10-19 16:14:33 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -1,5 +1,5 @@
|
||||
# Compile
|
||||
FROM golang:1.11-alpine AS compiler
|
||||
FROM golang:1.12-alpine AS compiler
|
||||
|
||||
RUN apk add --no-cache git make
|
||||
|
||||
+16
-25
@@ -2,50 +2,41 @@ version: 2
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/golang:1.11.4
|
||||
- image: circleci/golang:1.12
|
||||
steps:
|
||||
- checkout # check out the code in the project directory
|
||||
- setup_remote_docker # install docker in docker
|
||||
- run:
|
||||
name: Prepare Environment
|
||||
command: |
|
||||
cp conf/sower.toml .
|
||||
cp deploy/* .
|
||||
cp .circleci/* .
|
||||
mkdir artifacts
|
||||
- run:
|
||||
name: build linux Binary
|
||||
name: Run Unit Test
|
||||
command: |
|
||||
make test
|
||||
- run:
|
||||
name: Build linux Binary
|
||||
command: |
|
||||
make build
|
||||
tar czvf artifacts/sower_linux_amd64.tar.gz sower sower.toml sower-client.service sower-server.service
|
||||
- run:
|
||||
name: build macOS Binary
|
||||
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
|
||||
name: Build Windows Binary
|
||||
command: |
|
||||
GOOS=windows GOARCH=amd64 make build
|
||||
tar czvf artifacts/sower_windows_amd64.tar.gz sower sower.toml
|
||||
tar czvf artifacts/sower_windows_amd64.tar.gz sower.exe sower.toml
|
||||
- run:
|
||||
name: Publish Release on GitHub
|
||||
command: |
|
||||
if git log -1 --pretty='%s' | grep -oE '(^|\s)@release(\s|$)'; then
|
||||
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
|
||||
git describe --tags | grep - && exit
|
||||
|
||||
VERSION=$(git describe --tags)
|
||||
ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete ${VERSION%-*-*} artifacts/
|
||||
fi
|
||||
- run:
|
||||
name: build docker image
|
||||
command: |
|
||||
if git log -1 --pretty='%s' | grep -oE '(^|\s)@release(\s|$)'; then
|
||||
docker login -u $DOCKER_USER -p $DOCKER_PASS
|
||||
make image
|
||||
VERSION=$(git describe --tags)
|
||||
docker tag sower wweir/sower:${VERSION%-*-*}
|
||||
docker push wweir/sower:${VERSION%-*-*}
|
||||
docker tag sower wweir/sower:latest
|
||||
docker push wweir/sower:latest
|
||||
fi
|
||||
GHR_VERSION="$(curl -s "https://api.github.com/repos/tcnksm/ghr/releases/latest" | awk -F '"' '/tag_name/{printf $4}')"
|
||||
curl -SLf https://github.com/tcnksm/ghr/releases/download/"$GHR_VERSION"/ghr_"$GHR_VERSION"_linux_amd64.tar.gz | tar xzC /go/bin --strip-components=1
|
||||
|
||||
VERSION=$(git describe --tags)
|
||||
ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete ${VERSION%-*-*} artifacts/
|
||||
|
||||
@@ -1,34 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
set -u -e
|
||||
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 127.0.0.1 as the first domain name server manually.
|
||||
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")
|
||||
printf "Please enter remote server adddress: "
|
||||
read ADDRESS
|
||||
echo
|
||||
|
||||
curl -SLf https://github.com/wweir/sower/releases/download/$VERSION/sower_darwin_amd64.tar.gz | tar xzv
|
||||
|
||||
mv sower /usr/local/bin/
|
||||
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
|
||||
mv sower.toml /usr/local/etc/
|
||||
sed -i~ "s/\"12345678\"/\"$PASSWORD\"/" sower.toml
|
||||
sudo mkdir -p /usr/local/etc/
|
||||
sudo mv sower.toml /usr/local/etc/
|
||||
fi
|
||||
|
||||
echo
|
||||
@@ -51,10 +67,6 @@ case "$(uname -s)" in
|
||||
|
||||
case "$SIDE" in
|
||||
"c")
|
||||
printf "Please enter remote server adddress: "
|
||||
read ADDRESS
|
||||
echo
|
||||
|
||||
curl -SLf https://github.com/wweir/sower/releases/download/$VERSION/sower_linux_amd64.tar.gz | tar xzv
|
||||
|
||||
sudo mv sower /usr/local/bin/
|
||||
@@ -62,7 +74,23 @@ case "$(uname -s)" in
|
||||
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
|
||||
|
||||
@@ -74,8 +102,15 @@ case "$(uname -s)" in
|
||||
|
||||
"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
|
||||
+4
-5
@@ -12,12 +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,30 +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:
|
||||
go build -v -ldflags \
|
||||
"-X main.version=$(shell git describe --tags) \
|
||||
-X main.date=$(shell date +%Y-%m-%d)"
|
||||
image:
|
||||
docker build -t sower -f deploy/Dockerfile .
|
||||
docker build -t sower -f .circleci/Dockerfile .
|
||||
|
||||
kill:
|
||||
sudo pkill -9 sower || true
|
||||
|
||||
client: build kill
|
||||
sudo $(PWD)/sower -f /usr/local/etc/sower.toml
|
||||
sudo $(PWD)/sower
|
||||
|
||||
server: build kill
|
||||
$(PWD)/sower -n TCP -v 1
|
||||
|
||||
run: build kill
|
||||
$(PWD)/sower -n TCP -v 1 &
|
||||
sudo $(PWD)/sower -f /usr/local/etc/sower.toml &
|
||||
sudo $(PWD)/sower &
|
||||
@sleep 1
|
||||
curl 127.0.0.1
|
||||
curl localhost
|
||||
@sleep 1
|
||||
@sudo pkill -9 sower || true
|
||||
|
||||
@@ -1,22 +1,59 @@
|
||||
# sower
|
||||
[](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)
|
||||
|
||||
中文介绍见 [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, which listens to port `8080` by default. You can turn it off or use another port at any time.
|
||||
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.
|
||||
|
||||
The first time you visit a new website, sower will detect if the domain in block list and add it in suggect list. So that, you do not need to care about the rules, sower will handle it in a intelligent way. The only situation you should care about rules is some site network speed too slow.
|
||||
|
||||
## Architecture
|
||||
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/)
|
||||
```
|
||||
request target servers
|
||||
<-------------+ +------------->
|
||||
@@ -51,39 +88,13 @@ http(s) proxy | +----------+ | |
|
||||
blocked request normal request
|
||||
|
||||
```
|
||||
|
||||
## Installation
|
||||
### Auto deploy
|
||||
Auto deploy script support Linux server side and masOS/Linux client side.
|
||||
|
||||
```shell
|
||||
$ bash -c "$(curl -s https://raw.githubusercontent.com/wweir/sower/master/deploy/install)"
|
||||
```
|
||||
|
||||
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, change `install` into `uninstall` and rerun the command.
|
||||
|
||||
### 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. Map the port and run it directly.
|
||||
|
||||
But the client is more troublesome and needs some understanding of the working mechanism of the sower.
|
||||
For more detail, see [透明代理 Sower 技术剖析](https://wweir.cc/post/%E9%80%8F%E6%98%8E%E4%BB%A3%E7%90%86-sower-%E6%8A%80%E6%9C%AF%E5%89%96%E6%9E%90/)
|
||||
|
||||
|
||||
## Todo
|
||||
- [x] authenticate
|
||||
- [ ] broker
|
||||
- [ ] broker(waiting for QUIC implementation to be stable)
|
||||
- [x] CI/CD
|
||||
- [ ] relay optimization
|
||||
- [ ] deploy script for all normal platform
|
||||
- [x] dns rule intelligent suggestions
|
||||
- [x] relay optimization
|
||||
- [x] deploy script for all normal platform
|
||||
- [x] dns rule intelligent suggestions
|
||||
|
||||
+86
-106
@@ -5,18 +5,18 @@ import (
|
||||
"flag"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/golang/glog"
|
||||
toml "github.com/pelletier/go-toml"
|
||||
"github.com/wweir/fsnotify"
|
||||
"github.com/wweir/sower/util"
|
||||
)
|
||||
|
||||
// Conf define the config items
|
||||
var Conf = struct {
|
||||
ConfigFile string
|
||||
NetType string `toml:"net_type"`
|
||||
@@ -29,6 +29,7 @@ var Conf = struct {
|
||||
|
||||
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"`
|
||||
@@ -36,130 +37,109 @@ var Conf = struct {
|
||||
Suggestions []string `toml:"suggestions"`
|
||||
Verbose int `toml:"verbose"`
|
||||
}{}
|
||||
var mu = &sync.Mutex{}
|
||||
|
||||
var OnRefreash = []func() error{
|
||||
func() (err error) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
f, err := os.OpenFile(Conf.ConfigFile, os.O_RDONLY, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
file := Conf.ConfigFile
|
||||
if err = toml.NewDecoder(f).Decode(&Conf); err != nil {
|
||||
return err
|
||||
}
|
||||
Conf.ConfigFile = file
|
||||
|
||||
return flag.Set("v", strconv.Itoa(Conf.Verbose))
|
||||
},
|
||||
func() error {
|
||||
if Conf.ClearDNSCache != "" {
|
||||
ctx, _ := context.WithTimeout(context.TODO(), 5*time.Second)
|
||||
if err := exec.CommandContext(ctx, "sh", "-c", Conf.ClearDNSCache).Run(); err != nil {
|
||||
glog.Errorln(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&Conf.ConfigFile, "f", filepath.Dir(os.Args[0])+"/sower.toml", "config file location")
|
||||
flag.StringVar(&Conf.NetType, "n", "TCP", "proxy net type (QUIC|KCP|TCP)")
|
||||
flag.StringVar(&Conf.Cipher, "C", "AES_128_GCM", "cipher type (AES_128_GCM|AES_192_GCM|AES_256_GCM|CHACHA20_IETF_POLY1305|XCHACHA20_IETF_POLY1305)")
|
||||
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")
|
||||
|
||||
if !flag.Parsed() {
|
||||
flag.Set("logtostderr", "true")
|
||||
flag.Parse()
|
||||
}
|
||||
initArgs()
|
||||
|
||||
if _, err := os.Stat(Conf.ConfigFile); os.IsNotExist(err) {
|
||||
glog.Warningln("no config file has been load:", Conf.ConfigFile)
|
||||
return
|
||||
}
|
||||
for i := range OnRefreash {
|
||||
if err := OnRefreash[i](); err != nil {
|
||||
glog.Fatalln(err)
|
||||
for i := range refreshFns {
|
||||
if action, err := refreshFns[i](); err != nil {
|
||||
glog.Fatalln(action+":", err)
|
||||
}
|
||||
}
|
||||
watchConfigFile()
|
||||
|
||||
go addSuggestions()
|
||||
}
|
||||
|
||||
func watchConfigFile() {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
if err := watcher.Add(filepath.Dir(Conf.ConfigFile)); err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
// 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()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case event := <-watcher.Events:
|
||||
if event.Op == fsnotify.Rename {
|
||||
if err := watcher.Add(Conf.ConfigFile); err != nil {
|
||||
glog.Errorln(err)
|
||||
}
|
||||
}
|
||||
//safe refresh config
|
||||
file := Conf.ConfigFile
|
||||
if err = toml.NewDecoder(f).Decode(&Conf); err != nil {
|
||||
return action, err
|
||||
}
|
||||
Conf.ConfigFile = file
|
||||
|
||||
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
|
||||
},
|
||||
}
|
||||
|
||||
func AddSuggest(domain string) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
{ // uinque key
|
||||
// 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)
|
||||
sort.Sort(util.NewReverseSecSlice(Conf.Suggestions))
|
||||
Conf.Suggestions = util.NewReverseSecSlice(Conf.Suggestions).Sort().Uniq()
|
||||
|
||||
last := ""
|
||||
suggestions := make([]string, 0, len(Conf.Suggestions))
|
||||
for _, suggestion := range Conf.Suggestions {
|
||||
if suggestion != last {
|
||||
suggestions = append(suggestions, suggestion)
|
||||
{ // 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
|
||||
}
|
||||
last = suggestion
|
||||
}
|
||||
Conf.Suggestions = suggestions
|
||||
}
|
||||
|
||||
// safe write
|
||||
f, err := os.OpenFile(Conf.ConfigFile+"~", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
glog.Errorln(err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if err := toml.NewEncoder(f).ArraysWithOneElementPerLine(true).Encode(Conf); err != nil {
|
||||
glog.Errorln(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = os.Rename(Conf.ConfigFile+"~", Conf.ConfigFile); err != nil {
|
||||
glog.Errorln(err)
|
||||
// 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", "proxy net type: "+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))
|
||||
}
|
||||
}
|
||||
}
|
||||
+41
-27
@@ -1,36 +1,50 @@
|
||||
net_type="TCP"
|
||||
cipher="AES_128_GCM"
|
||||
net_type="TCP" # TCP, KCP, QUIC
|
||||
cipher="AES_128_GCM" # AES_128_GCM, AES_192_GCM, AES_256_GCM, CHACHA20_IETF_POLY1305, XCHACHA20_IETF_POLY1305
|
||||
password="12345678"
|
||||
server_port="5533"
|
||||
# server_addr="remote-server:5533" # replce it to remote server
|
||||
http_proxy=":8080" # eg: 192.168.0.2:8080
|
||||
dns_server="223.5.5.5" # Alibaba public dns
|
||||
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="killall -HUP mDNSResponder"
|
||||
# clear_dns_cache="pkill mDNSResponder" # Windows: "ipconfig /flushdnss"
|
||||
suggest_level="SPEEDUP" # DISABLE, BLOCK, SPEEDUP
|
||||
blocklist=[
|
||||
"*.google.com", # google
|
||||
"*.goo.gl",
|
||||
"*.googleusercontent.com",
|
||||
"*.googleapis.com",
|
||||
"*.youtube.com", # youtube
|
||||
"*.ytimg.com",
|
||||
"*.ggpht.com",
|
||||
"*.googlevideo.com",
|
||||
"*.facebook.com", # facebook
|
||||
"*.xx.fbcdn.net",
|
||||
"*.twitter.com", # twitter
|
||||
"*.twimg.com",
|
||||
"*.blogspot.com", # blogspot
|
||||
"*.wikipedia.org", # wikipeida
|
||||
"*.wikimedia.org",
|
||||
"*.wikisource.org",
|
||||
"*.web.telegram.org", # telegram
|
||||
"*.github.com", # github is too slow in direct connection
|
||||
"*.github.io",
|
||||
"*.s3.amazonaws.com", # aws s3, for github release file
|
||||
"**.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",
|
||||
"lookup-api.apple.com", # macOS lookup service
|
||||
"lookup-api.apple.com.edgekey.net",
|
||||
"e16991.b.akamaiedge.net",
|
||||
"**.amazon.com",
|
||||
"**.amazonaws.com",
|
||||
"*.githubusercontent.com",
|
||||
"*.github.*",
|
||||
]
|
||||
whitelist=[
|
||||
"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)
|
||||
}
|
||||
}
|
||||
+133
-48
@@ -1,28 +1,38 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/miekg/dns"
|
||||
mem "github.com/wweir/mem-go"
|
||||
"github.com/wweir/sower/conf"
|
||||
"github.com/wweir/sower/util"
|
||||
)
|
||||
|
||||
const colon = byte(':')
|
||||
|
||||
func StartDNS(dnsServer, listenIP string) {
|
||||
func StartDNS(dnsServer, listenIP string, suggestCh chan<- string, suggestLevel string) {
|
||||
ip := net.ParseIP(listenIP)
|
||||
suggest := &intelliSuggest{listenIP, 2 * time.Second, []string{":80", ":443"}}
|
||||
|
||||
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
|
||||
@@ -35,44 +45,92 @@ func StartDNS(dnsServer, listenIP string) {
|
||||
domain = domain[:idx]
|
||||
}
|
||||
|
||||
matchAndServe(w, r, domain, listenIP, dnsServer, ip, suggest)
|
||||
matchAndServe(w, r, domain, listenIP, dnsServer, dhcpCh, ip, suggest)
|
||||
})
|
||||
|
||||
server := &dns.Server{Addr: listenIP + ":53", Net: "udp"}
|
||||
server := &dns.Server{Addr: net.JoinHostPort(listenIP, "53"), Net: "udp"}
|
||||
glog.Fatalln(server.ListenAndServe())
|
||||
}
|
||||
|
||||
func matchAndServe(w dns.ResponseWriter, r *dns.Msg, domain, listenIP,
|
||||
dnsServer string, ipNet net.IP, suggest *intelliSuggest) {
|
||||
func dynamicSetUpstreamDNS(listenIP string, dnsServer *string, dhcpCh <-chan struct{}) {
|
||||
addr, _ := dns.ReverseAddr(listenIP)
|
||||
msg := &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Id: dns.Id(),
|
||||
RecursionDesired: false,
|
||||
},
|
||||
Question: []dns.Question{{
|
||||
Name: addr,
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
}},
|
||||
}
|
||||
|
||||
for {
|
||||
<-dhcpCh
|
||||
if _, err := dns.Exchange(msg, *dnsServer); err == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
host, err := GetDefaultDNSServer()
|
||||
if err != nil {
|
||||
glog.Errorln(err)
|
||||
continue
|
||||
}
|
||||
|
||||
// atomic action
|
||||
*dnsServer = net.JoinHostPort(host, "53")
|
||||
glog.Infoln("set dns server to", host)
|
||||
}
|
||||
}
|
||||
func matchAndServe(w dns.ResponseWriter, r *dns.Msg, domain, listenIP, dnsServer string,
|
||||
dhcpCh chan struct{}, ipNet net.IP, suggest *intelliSuggest) {
|
||||
|
||||
inWriteList := whiteList.Match(domain)
|
||||
|
||||
if !inWriteList && (blockList.Match(domain) || suggestList.Match(domain)) {
|
||||
glog.V(2).Infof("match %s suss", domain)
|
||||
w.WriteMsg(localA(r, domain, ipNet))
|
||||
return
|
||||
}
|
||||
|
||||
if !inWriteList {
|
||||
go mem.Remember(suggest, domain)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
msg, err := dns.ExchangeContext(ctx, r, dnsServer)
|
||||
if err != nil {
|
||||
if dhcpCh != nil {
|
||||
select {
|
||||
case dhcpCh <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
glog.V(1).Infof("get dns of %s from %s fail: %s", domain, dnsServer, err)
|
||||
return
|
||||
} else if msg == nil { // expose any response except nil
|
||||
glog.V(1).Infof("get dns of %s from %s return empty", domain, dnsServer)
|
||||
return
|
||||
}
|
||||
|
||||
msg, err := dns.Exchange(r, dnsServer+":53")
|
||||
if msg == nil { // expose any response except nil
|
||||
glog.V(1).Infof("get dns of %s fail: %s", domain, err)
|
||||
return
|
||||
if !inWriteList {
|
||||
// trigger suggest logic while dns server OK
|
||||
// avoid check while DNS setting not correct
|
||||
go mem.Remember(suggest, domain)
|
||||
}
|
||||
w.WriteMsg(msg)
|
||||
}
|
||||
|
||||
type intelliSuggest struct {
|
||||
listenIP string
|
||||
timeout time.Duration
|
||||
ports []string
|
||||
suggestCh chan<- string
|
||||
suggestLevel suggestLevel
|
||||
listenIP string
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func (i *intelliSuggest) GetOne(domain interface{}) (iface interface{}, e error) {
|
||||
iface = struct{}{}
|
||||
iface, e = struct{}{}, nil
|
||||
if i.suggestLevel == DISABLE {
|
||||
return
|
||||
}
|
||||
|
||||
// kill deadloop, for ugly wildcard setting dns setting
|
||||
addr := strings.TrimSuffix(domain.(string), ".")
|
||||
@@ -80,36 +138,63 @@ func (i *intelliSuggest) GetOne(domain interface{}) (iface interface{}, e error)
|
||||
return
|
||||
}
|
||||
|
||||
for _, port := range i.ports {
|
||||
// give local dial a hand, make it not so easy to be added into suggestions
|
||||
util.HTTPPing(addr+port, addr, i.timeout/10)
|
||||
localCh := util.HTTPPing(addr+port, addr, i.timeout)
|
||||
remoteCh := util.HTTPPing(i.listenIP+port, addr, i.timeout)
|
||||
|
||||
select {
|
||||
case err := <-localCh:
|
||||
if err == nil {
|
||||
glog.V(2).Infoln(addr, "local first, succ")
|
||||
return
|
||||
}
|
||||
if err = <-remoteCh; err != nil {
|
||||
glog.V(2).Infoln(addr, "local first, all fail")
|
||||
return
|
||||
}
|
||||
glog.V(2).Infoln(addr, "local first, remote succ")
|
||||
conf.AddSuggest(addr)
|
||||
|
||||
case err := <-remoteCh:
|
||||
if err != nil {
|
||||
glog.V(2).Infoln(addr, "remote first, fail")
|
||||
return
|
||||
}
|
||||
glog.V(2).Infoln(addr, "remote first, succ")
|
||||
conf.AddSuggest(addr)
|
||||
var (
|
||||
pings = [...]struct {
|
||||
viaAddr string
|
||||
port Port
|
||||
}{
|
||||
{addr, HTTP},
|
||||
{i.listenIP, HTTP},
|
||||
{addr, 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, addr, pings[idx].port, i.timeout); err != nil {
|
||||
// local ping fail
|
||||
if pings[idx].viaAddr == addr {
|
||||
atomic.AddInt32(score, 1)
|
||||
glog.V(1).Infof("local ping %s fail", addr)
|
||||
} else {
|
||||
atomic.AddInt32(score, -1)
|
||||
glog.V(1).Infof("remote ping %s fail", addr)
|
||||
}
|
||||
|
||||
glog.Infof("added suggest domain: %s", addr)
|
||||
return
|
||||
// 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", addr)
|
||||
|
||||
} 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 <- addr
|
||||
glog.Infof("suggested domain: %s with score: %d", addr, 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()
|
||||
}
|
||||
@@ -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]]
|
||||
}
|
||||
+49
-19
@@ -5,42 +5,72 @@ import (
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/wweir/sower/conf"
|
||||
"github.com/wweir/sower/util"
|
||||
)
|
||||
|
||||
var (
|
||||
blockList *util.Node
|
||||
whiteList *util.Node
|
||||
suggestList *util.Node
|
||||
whiteList *util.Node
|
||||
)
|
||||
|
||||
func init() {
|
||||
//first init
|
||||
blockList = loadRules("block", conf.Conf.BlockList)
|
||||
whiteList = loadRules("white", conf.Conf.WhiteList)
|
||||
suggestList = loadRules("suggest", conf.Conf.BlockList)
|
||||
|
||||
conf.OnRefreash = append(conf.OnRefreash, func() error {
|
||||
blockList = loadRules("block", conf.Conf.BlockList)
|
||||
whiteList = loadRules("white", conf.Conf.WhiteList)
|
||||
suggestList = loadRules("suggest", conf.Conf.Suggestions)
|
||||
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(2).Infof("load %s rule:\n%s", name, rule)
|
||||
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)
|
||||
m.Answer = []dns.RR{&dns.A{
|
||||
Hdr: dns.RR_Header{Name: domain, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 20},
|
||||
A: localIP,
|
||||
}}
|
||||
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,37 +1,29 @@
|
||||
module github.com/wweir/sower
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.3.1 // indirect
|
||||
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/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
|
||||
github.com/golang/mock v1.2.0 // indirect
|
||||
github.com/guregu/null v3.4.0+incompatible // 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.8.0 // indirect
|
||||
github.com/lib/pq v1.0.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.1
|
||||
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced // indirect
|
||||
github.com/miekg/dns v1.1.3
|
||||
github.com/onsi/ginkgo v1.7.0 // indirect
|
||||
github.com/onsi/gomega v1.4.3 // 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/satori/go.uuid v1.2.0 // indirect
|
||||
github.com/stretchr/testify v1.3.0 // indirect
|
||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
|
||||
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b // indirect
|
||||
github.com/tjfoc/gmsm v1.0.1 // indirect
|
||||
github.com/ulule/deepcopier v0.0.0-20171107155558-ca99b135e50f // indirect
|
||||
github.com/wweir/fsnotify v1.4.8
|
||||
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-20190122013713-64072686203f
|
||||
golang.org/x/net v0.0.0-20190119204137-ed066c81e75e // indirect
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 // indirect
|
||||
golang.org/x/sys v0.0.0-20190122071731-054c452bb702 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.2 // indirect
|
||||
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,55 +1,34 @@
|
||||
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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/guregu/null v3.4.0+incompatible h1:a4mw37gBO7ypcBlTJeZGuMpSxxFTV9qFfFKgWxQSGaM=
|
||||
github.com/guregu/null v3.4.0+incompatible/go.mod h1:ePGpQaN9cw0tj45IR5E5ehMvsFlLlQZAkkOXZurJ3NM=
|
||||
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.8.0 h1:lvvOkvk64cE1EGbBIgFk7WSOOsI1GexpuLiT7zjab6g=
|
||||
github.com/klauspost/reedsolomon v1.8.0/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
|
||||
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
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.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.1.3 h1:1g0r1IvskvgL8rR+AcHzUA+oFmGcQlaIm4IqakufeMM=
|
||||
github.com/miekg/dns v1.1.3/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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU=
|
||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
|
||||
@@ -59,31 +38,16 @@ 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/fsnotify v1.4.8 h1:zxOoqwE5p91F3rmcYLQheoSeTlIVqwi2b5T7UlpElWE=
|
||||
github.com/wweir/fsnotify v1.4.8/go.mod h1:4GuxIc23DXfJI1/LhBXG5eE/wJ4CzL+kk5M7woxVprc=
|
||||
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-20190122013713-64072686203f h1:u1CmMhe3a44hy8VIgpInORnI01UVaUYheqR7x9BxT3c=
|
||||
golang.org/x/crypto v0.0.0-20190122013713-64072686203f/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-20190119204137-ed066c81e75e h1:MDa3fSUp6MdYHouVmCCNz/zaH2a6CRcxY3VhT/K3C5Q=
|
||||
golang.org/x/net v0.0.0-20190119204137-ed066c81e75e/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-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/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-20190122071731-054c452bb702 h1:Lk4tbZFnlyPgV+sLgTw5yGfzrlOn9kx4vSombi2FFlY=
|
||||
golang.org/x/sys v0.0.0-20190122071731-054c452bb702/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
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/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
||||
@@ -1,28 +1,42 @@
|
||||
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() {
|
||||
conf := conf.Conf
|
||||
glog.Infof("Starting sower(%s %s): %v", version, date, conf)
|
||||
cfg := &conf.Conf
|
||||
glog.Infof("Starting sower(%s %s): %v", version, date, cfg)
|
||||
|
||||
if conf.ServerAddr == "" {
|
||||
proxy.StartServer(conf.NetType, conf.ServerPort, conf.Cipher, conf.Password)
|
||||
tran, err := transport.GetTransport(cfg.NetType)
|
||||
if err != nil {
|
||||
glog.Exitln(err)
|
||||
}
|
||||
|
||||
if cfg.ServerAddr == "" {
|
||||
proxy.StartServer(tran, cfg.ServerPort, cfg.Cipher, cfg.Password)
|
||||
|
||||
} else {
|
||||
if conf.HTTPProxy != "" {
|
||||
go proxy.StartHttpProxy(conf.NetType, conf.ServerAddr,
|
||||
conf.Cipher, conf.Password, conf.HTTPProxy)
|
||||
conf.AddRefreshFn(true, func() (string, error) {
|
||||
host, _, _ := net.SplitHostPort(cfg.ServerAddr)
|
||||
dns.LoadRules(cfg.BlockList, cfg.Suggestions, cfg.WhiteList, host)
|
||||
return "load rules", nil
|
||||
})
|
||||
|
||||
if cfg.HTTPProxy != "" {
|
||||
go proxy.StartHttpProxy(tran, cfg.ServerAddr,
|
||||
cfg.Cipher, cfg.Password, cfg.HTTPProxy)
|
||||
}
|
||||
|
||||
go dns.StartDNS(conf.DNSServer, conf.ClientIP)
|
||||
proxy.StartClient(conf.NetType, conf.ServerAddr, conf.Cipher, conf.Password, conf.ClientIP)
|
||||
go dns.StartDNS(cfg.DNSServer, cfg.ClientIP, conf.SuggestCh, cfg.SuggestLevel)
|
||||
proxy.StartClient(tran, cfg.ServerAddr, cfg.Cipher, cfg.Password, cfg.ClientIP)
|
||||
}
|
||||
}
|
||||
|
||||
+16
-35
@@ -2,50 +2,31 @@ package proxy
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/wweir/sower/proxy/kcp"
|
||||
"github.com/wweir/sower/proxy/quic"
|
||||
"github.com/wweir/sower/proxy/tcp"
|
||||
"github.com/wweir/sower/shadow"
|
||||
"github.com/wweir/sower/proxy/shadow"
|
||||
"github.com/wweir/sower/proxy/transport"
|
||||
)
|
||||
|
||||
type Client interface {
|
||||
Dial(server string) (net.Conn, error)
|
||||
}
|
||||
|
||||
func NewClient(netType string) Client {
|
||||
switch netType {
|
||||
case QUIC.String():
|
||||
return quic.NewClient()
|
||||
case KCP.String():
|
||||
return kcp.NewClient()
|
||||
case TCP.String():
|
||||
return tcp.NewClient()
|
||||
default:
|
||||
glog.Fatalln("invalid net type: " + netType)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func StartClient(netType, server, cipher, password, listenIP string) {
|
||||
connCh := listenLocal(listenIP, []string{":80", ":443"})
|
||||
client := NewClient(netType)
|
||||
if idx := strings.Index(server, ":"); idx > 0 {
|
||||
ips, err := net.LookupIP(server[:idx])
|
||||
if err != nil || len(ips) == 0 {
|
||||
glog.Fatalln(err, ips)
|
||||
}
|
||||
server = ips[0].String() + server[idx:]
|
||||
}
|
||||
func StartClient(tran transport.Transport, server, cipher, password, listenIP string) {
|
||||
connCh := listenLocal(listenIP, []string{"80", "443"})
|
||||
resolved := false
|
||||
|
||||
glog.Infoln("Client started.")
|
||||
for {
|
||||
conn := <-connCh
|
||||
|
||||
if !resolved {
|
||||
if addr, err := net.ResolveTCPAddr("tcp", server); err != nil {
|
||||
glog.Errorln(err)
|
||||
} else {
|
||||
server = addr.String()
|
||||
resolved = true
|
||||
}
|
||||
}
|
||||
glog.V(1).Infof("new conn from (%s) to (%s)", conn.RemoteAddr(), server)
|
||||
|
||||
rc, err := client.Dial(server)
|
||||
rc, err := tran.Dial(server)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
glog.Errorln(err)
|
||||
@@ -61,7 +42,7 @@ 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", listenIP+port)
|
||||
ln, err := net.Listen("tcp", net.JoinHostPort(listenIP, port))
|
||||
if err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
|
||||
+23
-9
@@ -9,19 +9,29 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/wweir/sower/shadow"
|
||||
"github.com/wweir/sower/proxy/shadow"
|
||||
"github.com/wweir/sower/proxy/transport"
|
||||
)
|
||||
|
||||
func StartHttpProxy(netType, server, cipher, password, addr string) {
|
||||
client := NewClient(netType)
|
||||
func StartHttpProxy(tran transport.Transport, server, cipher, password, addr string) {
|
||||
resolved := false
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: addr,
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !resolved {
|
||||
if addr, err := net.ResolveTCPAddr("tcp", server); err != nil {
|
||||
glog.Errorln(err)
|
||||
} else {
|
||||
server = addr.String()
|
||||
resolved = true
|
||||
}
|
||||
}
|
||||
|
||||
if r.Method == http.MethodConnect {
|
||||
httpsProxy(w, r, client, server, cipher, password)
|
||||
httpsProxy(w, r, tran, server, cipher, password)
|
||||
} else {
|
||||
httpProxy(w, r, client, server, cipher, password)
|
||||
httpProxy(w, r, tran, server, cipher, password)
|
||||
}
|
||||
}),
|
||||
// Disable HTTP/2.
|
||||
@@ -32,10 +42,12 @@ func StartHttpProxy(netType, server, cipher, password, addr string) {
|
||||
glog.Fatalln(srv.ListenAndServe())
|
||||
}
|
||||
|
||||
func httpProxy(w http.ResponseWriter, req *http.Request, client Client, server, cipher, password string) {
|
||||
func httpProxy(w http.ResponseWriter, req *http.Request,
|
||||
tran transport.Transport, server, cipher, password string) {
|
||||
|
||||
roundTripper := &http.Transport{
|
||||
DialContext: func(context.Context, string, string) (net.Conn, error) {
|
||||
conn, err := client.Dial(server)
|
||||
conn, err := tran.Dial(server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -64,7 +76,9 @@ func httpProxy(w http.ResponseWriter, req *http.Request, client Client, server,
|
||||
io.Copy(w, resp.Body)
|
||||
}
|
||||
|
||||
func httpsProxy(w http.ResponseWriter, r *http.Request, client Client, server, cipher, password string) {
|
||||
func httpsProxy(w http.ResponseWriter, r *http.Request,
|
||||
tran transport.Transport, server, cipher, password string) {
|
||||
|
||||
// local conn
|
||||
conn, _, err := w.(http.Hijacker).Hijack()
|
||||
if err != nil {
|
||||
@@ -80,7 +94,7 @@ func httpsProxy(w http.ResponseWriter, r *http.Request, client Client, server, c
|
||||
}
|
||||
|
||||
// remote conn
|
||||
rc, err := client.Dial(server)
|
||||
rc, err := tran.Dial(server)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
package kcp
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
kcp "github.com/xtaci/kcp-go"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func NewClient() *client {
|
||||
return &client{
|
||||
DataShard: 10,
|
||||
ParityShard: 3,
|
||||
DSCP: 0,
|
||||
SockBuf: 4194304,
|
||||
NoDelay: 0,
|
||||
Interval: 50,
|
||||
Resend: 0,
|
||||
NoCongestion: 0,
|
||||
SndWnd: 0,
|
||||
RcvWnd: 0,
|
||||
MTU: 1350,
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package kcp
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/pkg/errors"
|
||||
kcp "github.com/xtaci/kcp-go"
|
||||
)
|
||||
|
||||
type server struct {
|
||||
DataShard int
|
||||
ParityShard int
|
||||
DSCP int
|
||||
SockBuf int
|
||||
}
|
||||
|
||||
func NewServer() *server {
|
||||
return &server{
|
||||
DataShard: 10,
|
||||
ParityShard: 3,
|
||||
DSCP: 0,
|
||||
SockBuf: 4194304,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) Listen(port string) (<-chan net.Conn, error) {
|
||||
ln, err := kcp.ListenWithOptions(port, nil, s.DataShard, s.ParityShard)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := ln.SetDSCP(s.DSCP); err != nil {
|
||||
return nil, errors.Wrap(err, "SetDSCP")
|
||||
}
|
||||
if err := ln.SetReadBuffer(s.SockBuf); err != nil {
|
||||
return nil, errors.Wrap(err, "SetReadBuffer")
|
||||
}
|
||||
if err := ln.SetWriteBuffer(s.SockBuf); err != nil {
|
||||
return nil, errors.Wrap(err, "SetWriteBuffer")
|
||||
}
|
||||
|
||||
connCh := make(chan net.Conn)
|
||||
go func() {
|
||||
for {
|
||||
conn, err := ln.AcceptKCP()
|
||||
if err != nil {
|
||||
glog.Fatalln("KCP listen:", err)
|
||||
}
|
||||
|
||||
connCh <- conn
|
||||
}
|
||||
}()
|
||||
|
||||
return connCh, nil
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
// Code generated by "stringer -type=netType util.go"; DO NOT EDIT.
|
||||
|
||||
package proxy
|
||||
|
||||
import "strconv"
|
||||
|
||||
const _netType_name = "QUICKCPTCP"
|
||||
|
||||
var _netType_index = [...]uint8{0, 4, 7, 10}
|
||||
|
||||
func (i netType) String() string {
|
||||
if i < 0 || i >= netType(len(_netType_index)-1) {
|
||||
return "netType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _netType_name[_netType_index[i]:_netType_index[i+1]]
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package parse
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package parse
|
||||
package parser
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
@@ -1,56 +0,0 @@
|
||||
package quic
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
quic "github.com/lucas-clemente/quic-go"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type client struct {
|
||||
conf *quic.Config
|
||||
sess quic.Session
|
||||
}
|
||||
|
||||
func NewClient() *client {
|
||||
return &client{
|
||||
conf: &quic.Config{
|
||||
HandshakeTimeout: 5 * time.Second,
|
||||
MaxIncomingStreams: 1024,
|
||||
KeepAlive: true,
|
||||
IdleTimeout: 5 * time.Minute,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) Dial(server string) (net.Conn, error) {
|
||||
if c.sess == nil {
|
||||
if sess, err := quic.DialAddr(server, &tls.Config{InsecureSkipVerify: true}, c.conf); 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 := 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
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package quic
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
quic "github.com/lucas-clemente/quic-go"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type server struct {
|
||||
conf *quic.Config
|
||||
}
|
||||
|
||||
func NewServer() *server {
|
||||
return &server{
|
||||
conf: &quic.Config{
|
||||
HandshakeTimeout: 5 * time.Second,
|
||||
MaxIncomingStreams: 1024,
|
||||
KeepAlive: true,
|
||||
IdleTimeout: 5 * time.Minute,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) Listen(port string) (<-chan net.Conn, error) {
|
||||
ln, err := quic.ListenAddr(port, mockTlsPem(), s.conf)
|
||||
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}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package quic
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
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 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())
|
||||
}
|
||||
}
|
||||
+6
-26
@@ -5,32 +5,12 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/wweir/sower/parse"
|
||||
"github.com/wweir/sower/proxy/kcp"
|
||||
"github.com/wweir/sower/proxy/quic"
|
||||
"github.com/wweir/sower/proxy/tcp"
|
||||
"github.com/wweir/sower/shadow"
|
||||
"github.com/wweir/sower/proxy/parser"
|
||||
"github.com/wweir/sower/proxy/shadow"
|
||||
"github.com/wweir/sower/proxy/transport"
|
||||
)
|
||||
|
||||
type Server interface {
|
||||
Listen(port string) (<-chan net.Conn, error)
|
||||
}
|
||||
|
||||
func NewServer(netType string) Server {
|
||||
switch netType {
|
||||
case QUIC.String():
|
||||
return quic.NewServer()
|
||||
case KCP.String():
|
||||
return kcp.NewServer()
|
||||
case TCP.String():
|
||||
return tcp.NewServer()
|
||||
default:
|
||||
glog.Fatalln("invalid net type: " + netType)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func StartServer(netType, port, cipher, password string) {
|
||||
func StartServer(tran transport.Transport, port, cipher, password string) {
|
||||
if port == "" {
|
||||
glog.Fatalln("port must set")
|
||||
}
|
||||
@@ -38,7 +18,7 @@ func StartServer(netType, port, cipher, password string) {
|
||||
port = ":" + port
|
||||
}
|
||||
|
||||
connCh, err := NewServer(netType).Listen(port)
|
||||
connCh, err := tran.Listen(port)
|
||||
if err != nil {
|
||||
glog.Fatalf("listen %v fail: %s", port, err)
|
||||
}
|
||||
@@ -53,7 +33,7 @@ func StartServer(netType, port, cipher, password string) {
|
||||
}
|
||||
|
||||
func handle(conn net.Conn) {
|
||||
conn, addr, err := parse.ParseAddr(conn)
|
||||
conn, addr, err := parser.ParseAddr(conn)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
glog.Warningln(err)
|
||||
|
||||
@@ -17,8 +17,17 @@ const (
|
||||
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 {
|
||||
@@ -4,9 +4,9 @@ package shadow
|
||||
|
||||
import "strconv"
|
||||
|
||||
const _cipherType_name = "AES_128_GCMAES_192_GCMAES_256_GCMCHACHA20_IETF_POLY1305XCHACHA20_IETF_POLY1305"
|
||||
const _cipherType_name = "AES_128_GCMAES_192_GCMAES_256_GCMCHACHA20_IETF_POLY1305XCHACHA20_IETF_POLY1305cipherEnd"
|
||||
|
||||
var _cipherType_index = [...]uint8{0, 11, 22, 33, 55, 78}
|
||||
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) {
|
||||
@@ -1,26 +0,0 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type client struct {
|
||||
DialTimeout time.Duration
|
||||
}
|
||||
|
||||
func NewClient() *client {
|
||||
return &client{
|
||||
DialTimeout: 5 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) Dial(server string) (net.Conn, error) {
|
||||
conn, err := net.DialTimeout("tcp", server, c.DialTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
return conn, nil
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
type server struct {
|
||||
}
|
||||
|
||||
func NewServer() *server {
|
||||
return &server{}
|
||||
}
|
||||
|
||||
func (s *server) Listen(port string) (<-chan net.Conn, error) {
|
||||
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,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,49 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
type tcp struct {
|
||||
DialTimeout time.Duration
|
||||
}
|
||||
|
||||
func init() {
|
||||
transports["TCP"] = &tcp{
|
||||
DialTimeout: 5 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
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 (*tcp) Listen(port string) (<-chan net.Conn, error) {
|
||||
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
|
||||
}
|
||||
@@ -10,15 +10,6 @@ import (
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
//go:generate stringer -type=netType $GOFILE
|
||||
type netType int
|
||||
|
||||
const (
|
||||
QUIC netType = iota
|
||||
KCP
|
||||
TCP
|
||||
)
|
||||
|
||||
func relay(conn1, conn2 net.Conn) {
|
||||
wg := &sync.WaitGroup{}
|
||||
exitFlag := new(int32)
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// HTTPPing try connect to a http server with domain though the http addr
|
||||
|
||||
func HTTPPing(tcpAddr, domain string, timeout time.Duration) <-chan error {
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
errCh <- httpPing(tcpAddr, domain, timeout)
|
||||
}()
|
||||
return errCh
|
||||
}
|
||||
|
||||
func httpPing(tcpAddr, domain string, timeout time.Duration) error {
|
||||
conn, err := net.DialTimeout("tcp", tcpAddr, timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
conn.SetDeadline(time.Now().Add(timeout))
|
||||
if _, err = conn.Write(
|
||||
[]byte("TRACE / HTTP/1.1\r\nHost: " + domain + "\r\n\r\n")); 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 {
|
||||
idx := strings.Index(tcpAddr, ":")
|
||||
if idx >= 0 && tcpAddr[:idx] == domain {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -13,6 +13,24 @@ 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], ".")
|
||||
+30
-15
@@ -52,39 +52,54 @@ func (n *node) add(secs []string) {
|
||||
case 0:
|
||||
return
|
||||
case 1:
|
||||
n.node[secs[length-1]] = &node{node: map[string]*node{}}
|
||||
n.node[secs[length-1]] = &node{node: map[string]*node{"": &node{}}}
|
||||
default:
|
||||
subNode, ok := n.node[secs[length-1]]
|
||||
sec := secs[length-1]
|
||||
if sec == "**" {
|
||||
sec = "*"
|
||||
}
|
||||
|
||||
subNode, ok := n.node[sec]
|
||||
if !ok {
|
||||
subNode = &node{node: map[string]*node{}}
|
||||
n.node[secs[length-1]] = subNode
|
||||
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))
|
||||
return n.matchSecs(strings.Split(n.trim(item), n.sep), false)
|
||||
}
|
||||
|
||||
func (n *node) matchSecs(secs []string) bool {
|
||||
func (n *node) matchSecs(secs []string, fuzzNode bool) bool {
|
||||
length := len(secs)
|
||||
if length == 0 {
|
||||
switch len(n.node) {
|
||||
case 0:
|
||||
if _, ok := n.node[""]; ok {
|
||||
return true
|
||||
case 1:
|
||||
_, ok := n.node["*"]
|
||||
return ok
|
||||
default:
|
||||
return false
|
||||
}
|
||||
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 {
|
||||
return n.matchSecs(secs[:length-1])
|
||||
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
|
||||
}
|
||||
|
||||
_, ok := n.node["*"]
|
||||
return ok
|
||||
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,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