Compare commits

...
17 Commits
Author SHA1 Message Date
wweir f9f0ca4ee7 Merge branch 'bug/suggest' 2019-03-27 15:23:32 +08:00
wweir 98c3934762 Optimize suggest trigger 2019-03-27 15:22:25 +08:00
wweir f001f7dab7 Precompile more platform binary file 2019-03-24 18:44:27 +08:00
wweir 36ef329b68 Update todo list 2019-03-24 18:40:34 +08:00
wweir 64778f68ac Merge branch 'feature/socsk5' 2019-03-24 00:16:02 +08:00
wweir 53748cfa85 Support set socsk5 as upstream proxy 2019-03-24 00:15:08 +08:00
wweir d35115b7b3 Fix for vet 2019-03-20 08:48:46 +08:00
wweir 559c5fde6b Merge branch 'feature/dhcp' 2019-03-20 08:45:50 +08:00
wweir eb8080df18 Avoid unnecessary dhcp message 2019-03-20 08:45:37 +08:00
wweir 45ca063a7e Add timeout for dns exchange 2019-03-19 13:30:36 +08:00
wweir 6add7afd19 Fix for go modules 2019-03-15 11:46:51 +08:00
wweir f13687506e Merge branch 'feature/suggest-level' 2019-03-14 08:41:50 +08:00
wweir 199e4cf295 For #11, expose suggest level option 2019-03-14 08:38:18 +08:00
wweir b3ad9f6657 Fix panic in special network environment 2019-03-12 11:53:58 +08:00
wweir f9e9fe366f Fix log message 2019-03-12 11:53:58 +08:00
wweir 103699e25e Add docker build badge 2019-03-11 10:25:43 +08:00
wweir f80755eae5 Shorten install file url 2019-03-11 07:31:27 +08:00
20 changed files with 555 additions and 194 deletions
+19 -4
View File
@@ -18,18 +18,33 @@ jobs:
- run:
name: Build linux Binary
command: |
make build
tar czvf artifacts/sower_linux_amd64.tar.gz sower sower.toml sower-client.service sower-server.service
GOOS=linux GOARCH=amd64 make build
tar czvf artifacts/sower-linux-amd64.tar.gz sower sower.toml sower-client.service sower-server.service
GOOS=linux GOARCH=386 make build
tar czvf artifacts/sower-linux-386.tar.gz sower sower.toml sower-client.service sower-server.service
GOOS=linux GOARCH=arm make build
tar czvf artifacts/sower-linux-arm.tar.gz sower sower.toml sower-client.service sower-server.service
GOOS=linux GOARCH=arm64 make build
tar czvf artifacts/sower-linux-arm64.tar.gz sower sower.toml sower-client.service sower-server.service
GOOS=linux GOARCH=mips make build
tar czvf artifacts/sower-linux-mips.tar.gz sower sower.toml sower-client.service sower-server.service
GOOS=linux GOARCH=mipsle make build
tar czvf artifacts/sower-linux-mipsle.tar.gz sower sower.toml sower-client.service sower-server.service
- run:
name: Build macOS Binary
command: |
GOOS=darwin GOARCH=amd64 make build
tar czvf artifacts/sower_darwin_amd64.tar.gz sower sower.toml cc.wweir.sower.plist
tar czvf artifacts/sower-darwin-amd64.tar.gz sower sower.toml cc.wweir.sower.plist
- run:
name: Build Windows Binary
command: |
GOOS=windows GOARCH=amd64 make build
tar czvf artifacts/sower_windows_amd64.tar.gz sower.exe sower.toml
tar czvf artifacts/sower-windows-amd64.tar.gz sower.exe sower.toml
GOOS=windows GOARCH=386 make build
tar czvf artifacts/sower-windows-386.tar.gz sower.exe sower.toml
- run:
name: Publish Release on GitHub
command: |
+8 -2
View File
@@ -1,14 +1,20 @@
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 ./...
build:
GO111MODULE=on go build -v -ldflags \
go build -ldflags \
"-X main.version=$(shell git describe --tags) \
-X main.date=$(shell date +%Y-%m-%d)"
image:
@@ -27,6 +33,6 @@ run: build kill
$(PWD)/sower -n TCP -v 1 &
sudo $(PWD)/sower &
@sleep 1
curl 127.0.0.1
curl localhost
@sleep 1
@sudo pkill -9 sower || true
+9 -2
View File
@@ -1,6 +1,7 @@
# sower
[![GitHub release](http://img.shields.io/github/release/wweir/sower.svg?style=popout)](https://github.com/wweir/sower/releases)
[![CircleCI](https://img.shields.io/circleci/token/64114421aab286b6e918741071535250a5273f06/project/github/wweir/sower/master.svg?style=popout)](https://circleci.com/gh/wweir/sower)
[![Docker Cloud Build Status](https://img.shields.io/docker/cloud/build/wweir/sower.svg?style=popout)](https://hub.docker.com/r/wweir/sower)
[![GitHub issue](https://img.shields.io/github/issues/wweir/sower.svg?style=popout)](https://github.com/wweir/sower/issues)
[![GitHub star](https://img.shields.io/github/stars/wweir/sower.svg?style=popout)](https://github.com/wweir/sower/stargazers)
[![GitHub license](https://img.shields.io/github/license/wweir/sower.svg?style=popout)](LICENSE)
@@ -25,13 +26,17 @@ After Deployed, please check your config file, it is placed in `/usr/local/etc/s
Auto deploy script support Linux server side and masOS/Linux client side.
```shell
$ bash -c "$(curl -s https://raw.githubusercontent.com/wweir/sower/master/.circleci/install)"
$ bash -c "$(curl -sL https://git.io/fhhdp)"
```
Then modify the configuration file as needed and set `127.0.0.1` as your first domain name server.
In most situation, you just need to modify `/etc/resolv.conf`.
If you wanna uninstall sower, change `install` into `uninstall` and rerun the command.
If you wanna uninstall sower, run:
```shell
$ bash -c "$(curl -sL https://git.io/fhjer)"
```
### Manually deploy
1. Download the precompiled file from https://github.com/wweir/sower/releases
@@ -93,3 +98,5 @@ For more detail, see [透明代理 Sower 技术剖析](https://wweir.cc/post/%E9
- [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
+86 -65
View File
@@ -7,9 +7,10 @@ import (
"os/exec"
"runtime"
"strconv"
"sync"
"time"
"github.com/pkg/errors"
"github.com/golang/glog"
toml "github.com/pelletier/go-toml"
"github.com/wweir/sower/util"
@@ -28,6 +29,7 @@ var Conf = struct {
DNSServer string `toml:"dns_server"`
ClientIP string `toml:"client_ip"`
SuggestLevel string `toml:"suggest_level"`
ClearDNSCache string `toml:"clear_dns_cache"`
BlockList []string `toml:"blocklist"`
@@ -36,40 +38,6 @@ var Conf = struct {
Verbose int `toml:"verbose"`
}{}
// OnRefreash will be executed while init and write new config
var OnRefreash = []func() error{
func() (err error) {
f, err := os.OpenFile(Conf.ConfigFile, os.O_RDONLY, 0644)
if err != nil {
return err
}
defer f.Close()
//safe refresh config
file := Conf.ConfigFile
if err = toml.NewDecoder(f).Decode(&Conf); err != nil {
return err
}
Conf.ConfigFile = file
return flag.Set("v", strconv.Itoa(Conf.Verbose))
},
func() error {
if Conf.ClearDNSCache != "" {
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
defer cancel()
switch runtime.GOOS {
case "windows":
return exec.CommandContext(ctx, "cmd", "/c", Conf.ClearDNSCache).Run()
default:
return exec.CommandContext(ctx, "sh", "-c", Conf.ClearDNSCache).Run()
}
}
return nil
},
}
func init() {
initArgs()
@@ -77,48 +45,101 @@ func init() {
glog.Warningln("no config file has been load:", Conf.ConfigFile)
return
}
for i := range OnRefreash {
if err := OnRefreash[i](); err != nil {
glog.Fatalln(err)
for i := range refreshFns {
if action, err := refreshFns[i](); err != nil {
glog.Fatalln(action+":", err)
}
}
go addSuggestions()
}
// mu keep synchronized add rule(write), do not care read while write
var mu = &sync.Mutex{}
// AddSuggestion add new domain into suggest rules
func AddSuggestion(domain string) {
mu.Lock()
defer mu.Unlock()
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)
// 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 {
glog.Errorln(err)
return
return action, err
}
defer f.Close()
if err := toml.NewEncoder(f).ArraysWithOneElementPerLine(true).Encode(Conf); err != nil {
glog.Errorln(err)
f.Close()
return
//safe refresh config
file := Conf.ConfigFile
if err = toml.NewDecoder(f).Decode(&Conf); err != nil {
return action, err
}
f.Close()
Conf.ConfigFile = file
if err = os.Rename(Conf.ConfigFile+"~", Conf.ConfigFile); err != nil {
glog.Errorln(err)
return
return action, flag.Set("v", strconv.Itoa(Conf.Verbose))
},
func() (string, error) {
action := "clear dns cache"
if Conf.ClearDNSCache != "" {
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
defer cancel()
switch runtime.GOOS {
case "windows":
if out, err := exec.CommandContext(ctx, "cmd", "/c", Conf.ClearDNSCache).CombinedOutput(); err != nil {
return action, errors.Wrapf(err, "cmd: %s, output: %s, error", Conf.ClearDNSCache, out)
}
default:
if out, err := exec.CommandContext(ctx, "sh", "-c", Conf.ClearDNSCache).CombinedOutput(); err != nil {
return action, errors.Wrapf(err, "cmd: %s, output: %s, error", Conf.ClearDNSCache, out)
}
}
}
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
}
}
// reload config
for i := range OnRefreash {
if err := OnRefreash[i](); err != nil {
glog.Errorln(err)
refreshFns = append(refreshFns, fn)
return nil
}
// SuggestCh add domain into suggestios
var SuggestCh = make(chan string)
// addSuggestions add new domain into suggest rules
func addSuggestions() {
for domain := range SuggestCh {
Conf.Suggestions = append(Conf.Suggestions, domain)
Conf.Suggestions = util.NewReverseSecSlice(Conf.Suggestions).Sort().Uniq()
{ // safe write
f, err := os.OpenFile(Conf.ConfigFile+"~", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
glog.Errorln(err)
continue
}
if err := toml.NewEncoder(f).ArraysWithOneElementPerLine(true).Encode(Conf); err != nil {
glog.Errorln(err)
f.Close()
continue
}
f.Close()
if err = os.Rename(Conf.ConfigFile+"~", Conf.ConfigFile); err != nil {
glog.Errorln(err)
continue
}
}
// reload config
for i := range refreshFns {
if action, err := refreshFns[i](); err != nil {
glog.Errorln(action+":", err)
}
}
}
}
+6 -3
View File
@@ -8,19 +8,22 @@ import (
"path/filepath"
"strings"
"github.com/wweir/sower/dns"
"github.com/wweir/sower/proxy/shadow"
"github.com/wweir/sower/proxy/transport"
)
func initArgs() {
flag.StringVar(&Conf.ConfigFile, "f", filepath.Dir(os.Args[0])+"/sower.toml", "config file location")
flag.StringVar(&Conf.NetType, "n", "TCP", "proxy net type ("+strings.Join(transport.ListTransports(), "|")+")")
flag.StringVar(&Conf.Cipher, "C", "AES_128_GCM", "cipher type (AES_128_GCM|AES_192_GCM|AES_256_GCM|CHACHA20_IETF_POLY1305|XCHACHA20_IETF_POLY1305)")
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.ServerAddr, "s", "", "server IP (run in CLIENT MODE if set)")
flag.StringVar(&Conf.HTTPProxy, "H", "", "http proxy listen addr")
flag.StringVar(&Conf.DNSServer, "d", "114.114.114.114", "client dns server")
flag.StringVar(&Conf.ClientIP, "c", "127.0.0.1", "client dns service redirect IP")
flag.StringVar(&Conf.SuggestLevel, "S", "SPEEDUP", "suggest level setting: "+strings.Join(dns.ListSuggestLevels(), ","))
if !flag.Parsed() {
flag.Set("logtostderr", "true")
+5 -3
View File
@@ -1,12 +1,13 @@
net_type="TCP"
cipher="AES_128_GCM"
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:5533" # replce it to remote server
http_proxy=":8080" # eg: 192.168.0.2:8080
dns_server="223.5.5.5" # Keep empty for dynamic setting from net env
dns_server="114.114.114.114" # Keep empty for dynamic setting from net env
client_ip="127.0.0.1" # listen the IP, dns target is the IP
# clear_dns_cache="pkill mDNSResponder" # Windows: "ipconfig /flushdnss"
suggest_level="SPEEDUP" # DISABLE, BLOCK, SPEEDUP
blocklist=[
"**.google.*", # google
"**.goo.gl",
@@ -31,6 +32,7 @@ blocklist=[
"*.github.*",
]
whitelist=[
"**.in-addr.arpa",
"iamp.*.*",
"iamp.*.*.*",
"smtp.*.*",
+6 -2
View File
@@ -29,11 +29,11 @@ func GetDefaultDNSServer() (string, error) {
var conn net.PacketConn
if runtime.GOOS == "windows" {
if conn, err = reuseport.ListenPacket("udp", iface.IP.String()+":68"); err != nil {
if conn, err = reuseport.ListenPacket("udp4", iface.IP.String()+":68"); err != nil {
return "", errors.Wrap(err, "listen dhcp")
}
} else {
if conn, err = reuseport.ListenPacket("udp", "0.0.0.0:68"); err != nil {
if conn, err = reuseport.ListenPacket("udp4", "0.0.0.0:68"); err != nil {
return "", errors.Wrap(err, "listen dhcp")
}
}
@@ -52,5 +52,9 @@ func GetDefaultDNSServer() (string, error) {
pack = dhcp4.Packet(buf[:n])
dnsBytes := pack.ParseOptions()[dhcp4.OptionDomainNameServer]
if len(dnsBytes) < 4 {
return "", errors.New("no DNS setting in upstream network device")
}
return net.IPv4(dnsBytes[0], dnsBytes[1], dnsBytes[2], dnsBytes[3]).String(), nil
}
+85 -40
View File
@@ -1,6 +1,7 @@
package dns
import (
"context"
"net"
"strings"
"sync/atomic"
@@ -9,34 +10,21 @@ import (
"github.com/golang/glog"
"github.com/miekg/dns"
mem "github.com/wweir/mem-go"
"github.com/wweir/sower/conf"
)
const colon = byte(':')
func StartDNS(dnsServer, listenIP string) {
func StartDNS(dnsServer, listenIP string, suggestCh chan<- string, suggestLevel string) {
ip := net.ParseIP(listenIP)
suggest := &intelliSuggest{listenIP, time.Second}
suggest := &intelliSuggest{suggestCh, parseSuggestLevel(suggestLevel), listenIP, time.Second}
mem.DefaultCache = mem.New(time.Hour)
dhcpCh := make(chan struct{})
if dnsServer != "" {
dnsServer = net.JoinHostPort(dnsServer, "53")
} else {
go func() {
for {
<-dhcpCh
host, err := GetDefaultDNSServer()
if err != nil {
glog.Errorln(err)
continue
}
// atomic action
dnsServer = net.JoinHostPort(host, "53")
glog.Infoln("set dns server to", host)
}
}()
go dynamicSetUpstreamDNS(listenIP, &dnsServer, dhcpCh)
dhcpCh <- struct{}{}
}
@@ -54,7 +42,7 @@ func StartDNS(dnsServer, listenIP string) {
domain := r.Question[0].Name
if idx := strings.IndexByte(domain, colon); idx > 0 {
domain = domain[:idx]
domain = domain[:idx] // trim port
}
matchAndServe(w, r, domain, listenIP, dnsServer, dhcpCh, ip, suggest)
@@ -64,6 +52,37 @@ func StartDNS(dnsServer, listenIP string) {
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) {
@@ -74,7 +93,22 @@ func matchAndServe(w dns.ResponseWriter, r *dns.Msg, domain, listenIP, dnsServer
return
}
msg, err := dns.Exchange(r, dnsServer)
if !inWriteList {
go func() {
ip, err := net.LookupIP(domain)
if err != nil || len(ip) == 0 {
glog.V(1).Infoln(ip, err)
return
}
mem.Remember(suggest, addr{domain, ip[0].String()})
}()
}
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
defer cancel()
msg, err := dns.ExchangeContext(ctx, r, dnsServer)
if err != nil {
if dhcpCh != nil {
select {
@@ -82,31 +116,39 @@ func matchAndServe(w dns.ResponseWriter, r *dns.Msg, domain, listenIP, dnsServer
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 fail: %s", domain, err)
glog.V(1).Infof("get dns of %s from %s return empty", domain, dnsServer)
return
}
if !inWriteList {
// trigger suggest logic while dns server OK
// avoid check while DNS setting not correct
go mem.Remember(suggest, domain)
}
w.WriteMsg(msg)
}
type intelliSuggest struct {
listenIP string
timeout time.Duration
suggestCh chan<- string
suggestLevel suggestLevel
listenIP string
timeout time.Duration
}
type addr struct {
domain string
ip string
}
func (i *intelliSuggest) GetOne(domain interface{}) (iface interface{}, e error) {
func (i *intelliSuggest) GetOne(key interface{}) (iface interface{}, e error) {
iface, e = struct{}{}, nil
if i.suggestLevel == DISABLE {
return
}
domain := key.(addr).domain
ip := key.(addr).ip
// kill deadloop, for ugly wildcard setting dns setting
addr := strings.TrimSuffix(domain.(string), ".")
if len(strings.Split(addr, ".")) > 10 {
domain = strings.TrimSuffix(domain, ".")
if strings.Count(domain, ".") > 10 {
return
}
@@ -115,9 +157,9 @@ func (i *intelliSuggest) GetOne(domain interface{}) (iface interface{}, e error)
viaAddr string
port Port
}{
{addr, HTTP},
{ip, HTTP},
{i.listenIP, HTTP},
{addr, HTTPS},
{ip, HTTPS},
{i.listenIP, HTTPS},
}
protos = [...]*int32{
@@ -128,22 +170,25 @@ func (i *intelliSuggest) GetOne(domain interface{}) (iface interface{}, e error)
)
for idx := range pings {
go func(idx int) {
if err := HTTPPing(pings[idx].viaAddr, addr, pings[idx].port, i.timeout); err != nil {
if err := HTTPPing(pings[idx].viaAddr, domain, pings[idx].port, i.timeout); err != nil {
// local ping fail
if pings[idx].viaAddr == addr {
if pings[idx].viaAddr == ip {
atomic.AddInt32(score, 1)
glog.V(1).Infof("local ping %s fail", addr)
glog.V(1).Infof("local ping %s fail", domain)
} else {
atomic.AddInt32(score, -1)
glog.V(1).Infof("remote ping %s fail", addr)
glog.V(1).Infof("remote ping %s fail", domain)
}
// remote ping faster
} else if atomic.CompareAndSwapInt32(protos[idx/2], 0, 1) && pings[idx].viaAddr == i.listenIP {
atomic.AddInt32(score, 1)
glog.V(1).Infof("remote ping %s faster", addr)
} else if pings[idx].viaAddr == i.listenIP {
if atomic.CompareAndSwapInt32(protos[idx/2], 0, 1) && i.suggestLevel == SPEEDUP {
atomic.AddInt32(score, 1)
}
glog.V(1).Infof("remote ping %s faster", domain)
} else {
atomic.CompareAndSwapInt32(protos[idx/2], 0, 2)
return // score change trigger add suggestion
}
@@ -160,8 +205,8 @@ func (i *intelliSuggest) GetOne(domain interface{}) (iface interface{}, e error)
// 2. all remote pings are faster
if atomic.LoadInt32(score) >= int32(len(protos)) {
old := atomic.SwapInt32(score, -1) // avoid readd the suggestion
conf.AddSuggestion(addr)
glog.Infof("suggested domain: %s with score: %d", addr, old)
i.suggestCh <- domain
glog.Infof("suggested domain: %s with score: %d", domain, old)
}
}(idx)
}
+16
View File
@@ -0,0 +1,16 @@
// Code generated by "stringer -type=suggestLevel util.go"; DO NOT EDIT.
package dns
import "strconv"
const _suggestLevel_name = "DISABLEBLOCKSPEEDUPlevelEnd"
var _suggestLevel_index = [...]uint8{0, 7, 12, 19, 27}
func (i suggestLevel) String() string {
if i < 0 || i >= suggestLevel(len(_suggestLevel_index)-1) {
return "suggestLevel(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _suggestLevel_name[_suggestLevel_index[i]:_suggestLevel_index[i+1]]
}
+36 -19
View File
@@ -5,34 +5,22 @@ import (
"github.com/golang/glog"
"github.com/miekg/dns"
"github.com/wweir/sower/conf"
"github.com/wweir/sower/util"
)
var (
blockList *util.Node
whiteList *util.Node
suggestList *util.Node
whiteList *util.Node
)
func init() {
host, _, _ := net.SplitHostPort(conf.Conf.ServerAddr)
//first init
blockList = loadRules("block", conf.Conf.BlockList)
suggestList = loadRules("suggest", conf.Conf.Suggestions)
whiteList = loadRules("white", conf.Conf.WhiteList)
// 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("load config")
conf.OnRefreash = append(conf.OnRefreash, func() error {
blockList = loadRules("block", conf.Conf.BlockList)
suggestList = loadRules("suggest", conf.Conf.Suggestions)
whiteList = loadRules("white", conf.Conf.WhiteList)
whiteList.Add(host)
glog.V(1).Infoln("reload config")
return nil
})
glog.V(1).Infoln("reloaded config")
}
func loadRules(name string, list []string) *util.Node {
@@ -57,3 +45,32 @@ func localA(r *dns.Msg, domain string, localIP net.IP) *dns.Msg {
}
return m
}
//go:generate stringer -type=suggestLevel $GOFILE
type suggestLevel int32
const (
DISABLE suggestLevel = iota
BLOCK
SPEEDUP
levelEnd
)
func ListSuggestLevels() []string {
list := make([]string, 0, int(levelEnd))
for i := suggestLevel(0); i < levelEnd; i++ {
list = append(list, i.String())
}
return list
}
func parseSuggestLevel(level string) suggestLevel {
for i := suggestLevel(0); i < levelEnd; i++ {
if level == i.String() {
return i
}
}
glog.Exitln("invalid suggest level: " + level)
return levelEnd
}
+3 -3
View File
@@ -14,7 +14,7 @@ require (
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f // indirect
github.com/lucas-clemente/quic-go v0.10.1
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced // indirect
github.com/miekg/dns v1.1.5
github.com/miekg/dns v1.1.6
github.com/pelletier/go-toml v1.2.0
github.com/pkg/errors v0.8.1
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
@@ -23,7 +23,7 @@ require (
github.com/ulule/deepcopier v0.0.0-20171107155558-ca99b135e50f // indirect
github.com/wweir/mem-go v0.0.0-20190109100331-8673ab596296
github.com/xtaci/kcp-go v5.0.7+incompatible
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
golang.org/x/net v0.0.0-20190310014029-b774fd8d5c0f // indirect
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a
golang.org/x/net v0.0.0-20190313220215-9f648a60d977 // indirect
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e
)
+7 -5
View File
@@ -21,8 +21,8 @@ github.com/lucas-clemente/quic-go v0.10.1 h1:ipcMmYP9RT+b1YytOKGUY1qndxPGOczVEQk
github.com/lucas-clemente/quic-go v0.10.1/go.mod h1:wuD+2XqEx8G9jtwx5ou2BEYBsE+whgQmlj0Vz/77PrY=
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced h1:zqEC1GJZFbGZA0tRyNZqRjep92K5fujFtFsu5ZW7Aug=
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced/go.mod h1:NCcRLrOTZbzhZvixZLlERbJtDtYsmMw8Jc4vS8Z0g58=
github.com/miekg/dns v1.1.5 h1:7T0Xr4dLK+cpA3vIupAI3aDJCPRU4khl5O2J0LEAV+Y=
github.com/miekg/dns v1.1.5/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.6 h1:jVwb4GDwD65q/gtItR/lIZHjNH93QfeGxZUkzJcW9mc=
github.com/miekg/dns v1.1.6/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
@@ -42,10 +42,12 @@ github.com/wweir/mem-go v0.0.0-20190109100331-8673ab596296 h1:/HkUfg+ZMx/tNdnyJd
github.com/wweir/mem-go v0.0.0-20190109100331-8673ab596296/go.mod h1:k7rjBGWoJ+JKwvfe8juAX0zgybjo/Yo3JGkca5f/06s=
github.com/xtaci/kcp-go v5.0.7+incompatible h1:zs9tc8XRID0m+aetu3qPWZFyRt2UIMqbXIBgw+vcnlE=
github.com/xtaci/kcp-go v5.0.7+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190310014029-b774fd8d5c0f h1:CxcNDReoTQBlkHuyVUepQrMJTSa7q1+j65kVRv6jK3c=
golang.org/x/net v0.0.0-20190310014029-b774fd8d5c0f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a h1:YX8ljsm6wXlHZO+aRz9Exqr0evNhKRNe5K/gi+zKh4U=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190313220215-9f648a60d977 h1:actzWV6iWn3GLqN8dZjzsB+CLt+gaV2+wsxroxiQI8I=
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e h1:ZytStCyV048ZqDsWHiYDdoI2Vd4msMcrDECFxS+tL9c=
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+19 -10
View File
@@ -1,6 +1,8 @@
package main
import (
"net"
"github.com/golang/glog"
"github.com/wweir/sower/conf"
"github.com/wweir/sower/dns"
@@ -11,24 +13,31 @@ import (
var version, date string
func main() {
conf := conf.Conf
glog.Infof("Starting sower(%s %s): %v", version, date, conf)
cfg := &conf.Conf
glog.Infof("Starting sower(%s %s): %v", version, date, cfg)
tran, err := transport.GetTransport(conf.NetType)
tran, err := transport.GetTransport(cfg.NetType)
if err != nil {
glog.Exitln(err)
}
if conf.ServerAddr == "" {
proxy.StartServer(tran, conf.ServerPort, conf.Cipher, conf.Password)
if cfg.ServerAddr == "" {
proxy.StartServer(tran, cfg.ServerPort, cfg.Cipher, cfg.Password)
} else {
if conf.HTTPProxy != "" {
go proxy.StartHttpProxy(tran, conf.ServerAddr,
conf.Cipher, conf.Password, conf.HTTPProxy)
conf.AddRefreshFn(true, func() (string, error) {
host, _, _ := net.SplitHostPort(cfg.ServerAddr)
dns.LoadRules(cfg.BlockList, cfg.Suggestions, cfg.WhiteList, host)
return "load rules", nil
})
isSocks5 := (cfg.NetType == "SOCKS5")
if cfg.HTTPProxy != "" {
go proxy.StartHttpProxy(tran, isSocks5, cfg.ServerAddr, cfg.Cipher, cfg.Password, cfg.HTTPProxy)
}
go dns.StartDNS(conf.DNSServer, conf.ClientIP)
proxy.StartClient(tran, conf.ServerAddr, conf.Cipher, conf.Password, conf.ClientIP)
go dns.StartDNS(cfg.DNSServer, cfg.ClientIP, conf.SuggestCh, cfg.SuggestLevel)
proxy.StartClient(tran, isSocks5, cfg.ServerAddr, cfg.Cipher, cfg.Password, cfg.ClientIP)
}
}
+11 -11
View File
@@ -8,22 +8,14 @@ import (
"github.com/wweir/sower/proxy/transport"
)
func StartClient(tran transport.Transport, server, cipher, password, listenIP string) {
func StartClient(tran transport.Transport, isSocks5 bool, server, cipher, password, listenIP string) {
connCh := listenLocal(listenIP, []string{"80", "443"})
resolved := false
glog.Infoln("Client started.")
for {
conn := <-connCh
if !resolved {
if addr, err := net.ResolveTCPAddr("tcp", server); err != nil {
glog.Errorln(err)
} else {
server = addr.String()
resolved = true
}
}
resolveAddr(&server)
glog.V(1).Infof("new conn from (%s) to (%s)", conn.RemoteAddr(), server)
rc, err := tran.Dial(server)
@@ -32,7 +24,15 @@ func StartClient(tran transport.Transport, server, cipher, password, listenIP st
glog.Errorln(err)
continue
}
rc = shadow.Shadow(rc, cipher, password)
if isSocks5 {
if rc, conn, err = buildSocks5Conn(rc, conn); err != nil {
glog.Errorln(err)
continue
}
} else {
rc = shadow.Shadow(rc, cipher, password)
}
go relay(conn, rc)
}
+30 -22
View File
@@ -6,6 +6,7 @@ import (
"io"
"net"
"net/http"
"net/url"
"time"
"github.com/golang/glog"
@@ -13,25 +14,16 @@ import (
"github.com/wweir/sower/proxy/transport"
)
func StartHttpProxy(tran transport.Transport, server, cipher, password, addr string) {
resolved := false
func StartHttpProxy(tran transport.Transport, isSocks5 bool, server, cipher, password, addr string) {
srv := &http.Server{
Addr: addr,
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !resolved {
if addr, err := net.ResolveTCPAddr("tcp", server); err != nil {
glog.Errorln(err)
} else {
server = addr.String()
resolved = true
}
}
resolveAddr(&server)
if r.Method == http.MethodConnect {
httpsProxy(w, r, tran, server, cipher, password)
httpsProxy(w, r, tran, isSocks5, server, cipher, password)
} else {
httpProxy(w, r, tran, server, cipher, password)
httpProxy(w, r, tran, isSocks5, server, cipher, password)
}
}),
// Disable HTTP/2.
@@ -43,20 +35,28 @@ func StartHttpProxy(tran transport.Transport, server, cipher, password, addr str
}
func httpProxy(w http.ResponseWriter, req *http.Request,
tran transport.Transport, server, cipher, password string) {
tran transport.Transport, isSocks5 bool, server, cipher, password string) {
roundTripper := &http.Transport{
DialContext: func(context.Context, string, string) (net.Conn, error) {
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
if isSocks5 {
roundTripper.Proxy = func(*http.Request) (*url.URL, error) {
return url.Parse("socks5://" + server)
}
} else {
roundTripper.DialContext = func(context.Context, string, string) (net.Conn, error) {
conn, err := tran.Dial(server)
if err != nil {
return nil, err
}
return shadow.Shadow(conn, cipher, password), nil
},
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
}
resp, err := roundTripper.RoundTrip(req)
@@ -77,7 +77,7 @@ func httpProxy(w http.ResponseWriter, req *http.Request,
}
func httpsProxy(w http.ResponseWriter, r *http.Request,
tran transport.Transport, server, cipher, password string) {
tran transport.Transport, isSocks5 bool, server, cipher, password string) {
// local conn
conn, _, err := w.(http.Hijacker).Hijack()
@@ -101,7 +101,15 @@ func httpsProxy(w http.ResponseWriter, r *http.Request,
glog.Errorln("serve https proxy, dial remote fail:", err)
return
}
rc = shadow.Shadow(rc, cipher, password)
if isSocks5 {
if rc, conn, err = buildSocks5Conn(rc, conn); err != nil {
glog.Errorln(err)
return
}
} else {
rc = shadow.Shadow(rc, cipher, password)
}
relay(rc, conn)
}
+9
View File
@@ -17,8 +17,17 @@ const (
AES_256_GCM
CHACHA20_IETF_POLY1305
XCHACHA20_IETF_POLY1305
cipherEnd
)
func ListCiphers() []string {
list := make([]string, 0, int(cipherEnd))
for i := cipherType(0); i < cipherEnd; i++ {
list = append(list, i.String())
}
return list
}
func pickCipher(cipherType, password string) (cipher.AEAD, error) {
var blockSize int
switch cipherType {
+2 -2
View File
@@ -4,9 +4,9 @@ package shadow
import "strconv"
const _cipherType_name = "AES_128_GCMAES_192_GCMAES_256_GCMCHACHA20_IETF_POLY1305XCHACHA20_IETF_POLY1305"
const _cipherType_name = "AES_128_GCMAES_192_GCMAES_256_GCMCHACHA20_IETF_POLY1305XCHACHA20_IETF_POLY1305cipherEnd"
var _cipherType_index = [...]uint8{0, 11, 22, 33, 55, 78}
var _cipherType_index = [...]uint8{0, 11, 22, 33, 55, 78, 87}
func (i cipherType) String() string {
if i < 0 || i >= cipherType(len(_cipherType_index)-1) {
+154
View File
@@ -0,0 +1,154 @@
package socks5
import (
"encoding/binary"
"io"
"net"
"github.com/pkg/errors"
)
func ToSocks5(c net.Conn, domain, port string) net.Conn {
switch port {
case "80":
return &conn{init: make(chan struct{}), Conn: c, domain: domain, port: []byte{0x00, 0x50}}
case "443":
return &conn{init: make(chan struct{}), Conn: c, domain: domain, port: []byte{0x01, 0xbb}}
default:
panic("invalid port: " + port)
}
}
type conn struct {
init chan struct{}
domain string
port []byte
net.Conn
}
func (c *conn) Read(b []byte) (n int, err error) {
<-c.init
return c.Conn.Read(b)
}
func (c *conn) Write(b []byte) (n int, err error) {
select {
case <-c.init:
return c.Conn.Write(b)
default:
}
{
req := &authReq{
VER: 5,
NMETHODS: 1,
METHODS: [1]byte{0}, // NO AUTHENTICATION REQUIRED
}
if err := binary.Write(c.Conn, binary.BigEndian, req); err != nil {
return 0, err
}
}
{
resp := &authResp{}
if err := binary.Read(c.Conn, binary.BigEndian, resp); err != nil {
return 0, err
}
}
{
req := &request{
req: req{
VER: 5, // socks5
CMD: 1, // CONNECT
RSV: 0, // RESERVED
ATYP: 3, // DOMAINNAME
},
DST_ADDR: append([]byte{byte(len(c.domain))}, []byte(c.domain)...),
DST_PORT: c.port,
}
if _, err := c.Conn.Write(req.Bytes()); err != nil {
return 0, err
}
}
{
resp := &response{}
if err := binary.Read(c.Conn, binary.BigEndian, &(resp.resp)); err != nil {
return 0, err
}
switch resp.REP {
case 0x00:
default:
return 0, errors.Errorf("socks5 handshake fail, return code: %d", resp.REP)
}
switch resp.ATYP {
case 0x01: // IPv4
resp.DST_ADDR = make([]byte, net.IPv4len)
if _, err := io.ReadFull(c.Conn, resp.DST_ADDR); err != nil {
return 0, err
}
case 0x03: // domain name
if _, err := io.ReadFull(c.Conn, resp.DST_ADDR[:1]); err != nil {
return 0, err
}
if _, err := io.ReadFull(c.Conn, resp.DST_ADDR[1:1+int(resp.DST_ADDR[0])]); err != nil {
return 0, err
}
case 0x04:
resp.DST_ADDR = make([]byte, net.IPv6len)
if _, err := io.ReadFull(c.Conn, resp.DST_ADDR); err != nil {
return 0, err
}
}
resp.DST_PORT = make([]byte, 2)
if _, err := io.ReadFull(c.Conn, resp.DST_PORT); err != nil {
return 0, err
}
}
close(c.init)
return c.Conn.Write(b)
}
type authReq struct {
VER byte
NMETHODS byte
METHODS [1]byte // 1 to 255, fix to no authentication
}
type authResp struct {
VER byte
METHOD byte
}
type request struct {
req
DST_ADDR []byte // first byte is length
DST_PORT []byte // two bytes
}
type req struct {
VER byte
CMD byte
RSV byte
ATYP byte
}
func (r *request) Bytes() []byte {
out := []byte{r.VER, r.CMD, r.RSV, r.ATYP}
out = append(out, r.DST_ADDR...)
return append(out, r.DST_PORT...)
}
type response struct {
resp
DST_ADDR []byte // first byte is length
DST_PORT []byte // two bytes
}
type resp struct {
VER byte
REP byte
RSV byte
ATYP byte
}
+10 -1
View File
@@ -9,12 +9,17 @@ import (
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) {
@@ -27,7 +32,11 @@ func (t *tcp) Dial(server string) (net.Conn, error) {
return conn, nil
}
func (*tcp) Listen(port string) (<-chan net.Conn, error) {
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
+34
View File
@@ -8,8 +8,42 @@ import (
"time"
"github.com/golang/glog"
"github.com/wweir/sower/proxy/parser"
"github.com/wweir/sower/proxy/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 {
*server = addr.String()
resolved = true
}
}
}
func buildSocks5Conn(c, relay net.Conn) (net.Conn, net.Conn, error) {
conn, addr, err := parser.ParseAddr(relay)
if err != nil {
c.Close()
relay.Close()
return nil, nil, err
}
host, port, err := net.SplitHostPort(addr)
if err != nil {
c.Close()
relay.Close()
return nil, nil, err
}
return socks5.ToSocks5(c, host, port), conn, nil
}
func relay(conn1, conn2 net.Conn) {
wg := &sync.WaitGroup{}
exitFlag := new(int32)