Compare commits

...
73 Commits
Author SHA1 Message Date
wweir d35115b7b3 Fix for vet 2019-03-20 08:48:46 +08:00
wweir 559c5fde6b Merge branch 'feature/dhcp' 2019-03-20 08:45:50 +08:00
wweir eb8080df18 Avoid unnecessary dhcp message 2019-03-20 08:45:37 +08:00
wweir 45ca063a7e Add timeout for dns exchange 2019-03-19 13:30:36 +08:00
wweir 6add7afd19 Fix for go modules 2019-03-15 11:46:51 +08:00
wweir f13687506e Merge branch 'feature/suggest-level' 2019-03-14 08:41:50 +08:00
wweir 199e4cf295 For #11, expose suggest level option 2019-03-14 08:38:18 +08:00
wweir b3ad9f6657 Fix panic in special network environment 2019-03-12 11:53:58 +08:00
wweir f9e9fe366f Fix log message 2019-03-12 11:53:58 +08:00
wweir 103699e25e Add docker build badge 2019-03-11 10:25:43 +08:00
wweir f80755eae5 Shorten install file url 2019-03-11 07:31:27 +08:00
wweir 5b217b794d Merge branch 'refactor/codes' 2019-03-11 00:22:15 +08:00
wweir 26055c5203 Refactor deploy 2019-03-11 00:11:07 +08:00
wweir 029611c078 Refactor proxy 2019-03-11 00:09:02 +08:00
wweir 89adc6a2c9 Change code style 2019-03-10 21:03:11 +08:00
wweir 8c95c59e5e Merge branch 'bug/dhcp' 2019-03-10 13:28:50 +08:00
wweir 4f8376c041 Fix #7, refactor dhcp implementation for dynamic DNS on Windows 2019-03-10 13:26:33 +08:00
wweir 10172f8be2 Build docker image in docker hub 2019-03-08 21:55:18 +08:00
wweir 2831cdf284 Add Windows flush dns cmd 2019-03-07 07:43:22 +08:00
wweir 06558ac635 Optimize http ping score logic 2019-03-06 11:06:37 +08:00
wweir 3cf4deddb7 Merge branch 'bug/ci-windows' 2019-03-06 08:13:10 +08:00
wweir 679263be9a Fix #8, fix ci script 2019-03-06 08:12:48 +08:00
wweir 21d8ec4ec7 Merge branch 'refactor/http-ping' 2019-03-06 07:44:17 +08:00
wweir 4916245522 Optimize http ping logic 2019-03-06 07:43:45 +08:00
wweir 11c2808320 Add description 2019-03-02 10:25:17 +08:00
wweir 21ddd79e4d Fix deploy script 2019-03-02 09:29:21 +08:00
wweir 9a158d4088 Optimize QUIC paramaters 2019-03-02 09:22:31 +08:00
wweir 88990cddd9 Update dependency 2019-03-02 09:07:20 +08:00
wweir ca1b2650ab Optimize install script 2019-03-02 08:07:01 +08:00
wweir 8ff1046ebf Merge branch 'bug/suffix-tree' 2019-02-28 17:38:52 +08:00
wweir dfe81f6f23 Fix suffix tree match logic 2019-02-28 17:38:34 +08:00
wweir 972875ec36 Optimize default rules 2019-02-28 16:14:40 +08:00
wweir 89b7a41d46 Add rule for mail 2019-02-28 15:40:57 +08:00
wweir 8f598dbc17 Optimize ci workflow 2019-02-27 14:28:54 +08:00
wweir 192b4f798b Optimize suggest logic 2019-02-27 14:16:02 +08:00
wweir 2adefe0dfc Fix clear dns command for windows 2019-02-26 17:33:39 +08:00
wweir 7b0c13ef1c Merge branch 'refactor/watch-config' 2019-02-24 18:20:31 +08:00
wweir 63bb934148 Release config file immediatly 2019-02-24 18:18:43 +08:00
wweir cacc732a4e Remove fsnotify, add refresh trigger for config 2019-02-24 17:43:17 +08:00
wweir 17d079dcb2 Fix #3, Merge branch 'feature/windows_svc' 2019-02-24 16:12:05 +08:00
wweir 413a93f6f7 Support Windows service deploy 2019-02-24 16:10:19 +08:00
wweir 6c5f5cd74f Add windows service support 2019-02-24 15:58:38 +08:00
wweir e948541efe Merge branch 'chore/code' 2019-02-19 16:53:21 +08:00
wweir ffa2128274 Move code files path 2019-02-19 16:53:08 +08:00
wweir b53b7874b6 Update README.md 2019-02-19 16:24:47 +08:00
wweir f3117edfa0 For macOS without brew 2019-02-19 13:04:36 +08:00
wweir ba8980da73 Limit dns detect timeout short than 1s for multi dns setting 2019-02-17 07:29:23 +08:00
wweir e3ec74b74f Merge branch 'bug/resolve-addr' 2019-02-16 22:41:28 +08:00
wweir 6e67193894 Release on tag 2019-02-16 22:40:54 +08:00
wweir af0c5f895c Fix resolve for dhcp 2019-02-16 22:26:20 +08:00
wweir 4883e240cc Merge branch 'feature/score' @release 2019-02-15 22:48:06 +08:00
wweir 18ee9ca993 Refactor intelligent detect to score logic 2019-02-15 22:47:51 +08:00
wweir 4fff2a1f3b Merge branch 'bug/dhcp-linux' @release 2019-02-15 00:28:53 +08:00
wweir 3e82890c33 Fix dhcp conflict with dhcpcd and dhclient 2019-02-15 00:27:03 +08:00
wweir 01d66d737b Merge branch 'bug/suggest' @release 2019-02-13 21:08:00 +08:00
wweir e342dadaf1 Fix http ping logic for suggestions 2019-02-13 21:07:40 +08:00
wweir 7ba9f1a23c Fix intelligent detect @release 2019-02-11 20:06:21 +08:00
wweir 7240208cb3 Merge branch 'bug/dhcp' @release 2019-02-02 08:35:33 +08:00
wweir a27748d835 Fix race 2019-02-02 08:35:16 +08:00
wweir 39797d077b Merge branch 'feature/dhcp' @release 2019-02-02 06:22:29 +08:00
wweir b6df69358a Fix #2, add dhcp dynamic setting upstream dns, ipv4 only 2019-02-02 06:21:51 +08:00
wweir ae525f9b40 Fix wait timeout 2019-01-31 07:36:41 +08:00
wweir 29eb0e6e1c Fix toml spell issue 2019-01-31 06:51:57 +08:00
wweir aef941b17e Add a reminder to check the configuration 2019-01-31 06:50:26 +08:00
wweir 9bc5a32074 Add more fuzz match support @release 2019-01-29 13:34:52 +08:00
wweir 8194083f54 Add more fuzz rules support @release 2019-01-28 22:41:07 +08:00
wweir c20903137d Fix #5, add ipv6 support @release 2019-01-28 16:54:27 +08:00
wweir 2f3c98b8d3 Clean code 2019-01-27 08:35:12 +08:00
wweir bc99bda1b3 Clear unnessary block rule 2019-01-25 23:13:48 +08:00
wweir ceedef4203 Add https ping @release 2019-01-25 22:46:28 +08:00
wweir 712b689708 Force enable go modules 2019-01-25 16:13:22 +08:00
wweir d5260d66df Fix match rule 2019-01-25 15:27:56 +08:00
wweir bde27f64ec Fix 433 detect 2019-01-23 22:31:37 +08:00
54 changed files with 1528 additions and 887 deletions
+1 -1
View File
@@ -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
View File
@@ -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/
+48 -13
View File
@@ -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
View File
@@ -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
+13 -5
View File
@@ -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
+45 -34
View File
@@ -1,22 +1,59 @@
# sower
[![GitHub release](http://img.shields.io/github/release/wweir/sower.svg?style=popout)](https://github.com/wweir/sower/releases)
[![CircleCI](https://img.shields.io/circleci/token/64114421aab286b6e918741071535250a5273f06/project/github/wweir/sower/master.svg?style=popout)](https://circleci.com/gh/wweir/sower)
[![Docker Cloud Build Status](https://img.shields.io/docker/cloud/build/wweir/sower.svg?style=popout)](https://hub.docker.com/r/wweir/sower)
[![GitHub issue](https://img.shields.io/github/issues/wweir/sower.svg?style=popout)](https://github.com/wweir/sower/issues)
[![GitHub star](https://img.shields.io/github/stars/wweir/sower.svg?style=popout)](https://github.com/wweir/sower/stargazers)
[![GitHub license](https://img.shields.io/github/license/wweir/sower.svg?style=popout)](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
View File
@@ -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)
}
}
}
}
+32
View File
@@ -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()
}
}
+146
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
+13
View File
@@ -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
View File
@@ -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
}
+131
View File
@@ -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()
}
+16
View File
@@ -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
View File
@@ -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
}
+11 -19
View File
@@ -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
)
+18 -54
View File
@@ -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=
+23 -9
View File
@@ -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
View File
@@ -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
View File
@@ -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)
-65
View File
@@ -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
}
-56
View File
@@ -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
}
-16
View File
@@ -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"
+1 -1
View File
@@ -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"
-56
View File
@@ -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
}
-59
View File
@@ -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}
}
}
-67
View File
@@ -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
View File
@@ -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) {
-26
View File
@@ -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
}
-35
View File
@@ -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
}
+115
View File
@@ -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
}
+136
View File
@@ -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}
}
}
+49
View File
@@ -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
}
+30
View File
@@ -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
}
-9
View File
@@ -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)
-44
View File
@@ -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
}
+30
View File
@@ -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")
}
+16
View File
@@ -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)
}
}
+51
View File
@@ -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
}
+18
View File
@@ -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
View File
@@ -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
}
+79
View File
@@ -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)
}
}
})
}
}
+30
View File
@@ -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())
}
}