Merge branch 'feature/router'

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