Refactor to proxy router

This commit is contained in:
wweir
2020-02-05 18:00:09 +08:00
parent 8cd4fe57ba
commit fd5c34dca1
40 changed files with 538 additions and 1777 deletions
+105 -80
View File
@@ -3,128 +3,153 @@ package conf
import (
"flag"
"os"
"strconv"
"sync"
"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"`
var (
version, date string
ServerPort string `toml:"server_port"`
ServerAddr string `toml:"server_addr"`
HTTPProxy string `toml:"http_proxy"`
flushOnce = sync.Once{}
flushMu = sync.Mutex{}
flushCh = make(chan struct{})
DNSServer string `toml:"dns_server"`
ClientIP string `toml:"client_ip"`
SuggestLevel string `toml:"suggest_level"`
ClearDNSCache string `toml:"clear_dns_cache"`
// Conf define the config items
Conf = struct {
ConfigFile string
BlockList []string `toml:"blocklist"`
WhiteList []string `toml:"whitelist"`
Suggestions []string `toml:"suggestions"`
Verbose int `toml:"verbose"`
VersionOnly bool `toml:"-"`
}{}
Upstream struct {
Socks5 string `toml:"socks5"`
DNS string `toml:"dns"`
} `toml:"upstream"`
Downstream struct {
ServeIP string `toml:"serve_ip"`
HTTPProxy string `toml:"http_proxy"`
} `toml:"downstream"`
Router struct {
FlushDNSCmd string `toml:"flush_dns_cmd"`
ProxyLevel int `toml:"proxy_level"`
PortMapping map[string]string `toml:"port_mapping"`
ProxyList []string `toml:"proxy_list"`
DirectList []string `toml:"direct_list"`
DynamicList []string `toml:"dynamic_list"`
} `toml:"router"`
}{}
)
func init() {
initArgs()
if Conf.VersionOnly {
return
flag.StringVar(&Conf.ConfigFile, "f", "", "config file, keep empty for dynamic detect proxy rule")
flag.StringVar(&Conf.Upstream.Socks5, "socks5", "127.0.0.1:1080", "upstream socks5 address")
flag.StringVar(&Conf.Upstream.DNS, "dns", "", "upstream dns ip, keep empty to dynamic detect")
flag.StringVar(&Conf.Downstream.ServeIP, "serve", "127.0.0.1", "serve on address")
flag.StringVar(&Conf.Downstream.HTTPProxy, "http_proxy", "", "serve http proxy, eg: 127.0.0.1:8080")
flag.IntVar(&Conf.Router.ProxyLevel, "level", 2, "dynamic proxy level: 0~4")
Init() // execute platform init logic
if !flag.Parsed() {
flag.Parse()
}
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)
if _, err := os.Stat(Conf.ConfigFile); err == nil {
for i := range loadConfigFns {
if err := loadConfigFns[i].fn(); err != nil {
log.Fatalw("load config", "config", Conf.ConfigFile, "step", loadConfigFns[i].step, "err", err)
}
}
}
go addSuggestions()
log.Infow("start", "version", version, "date", date, "config", Conf)
}
// 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.ConfigFile, os.O_RDONLY, 0644)
if err != nil {
return err
}
defer f.Close()
refreshFns = append(refreshFns, fn)
//safe refresh config
file := Conf.ConfigFile
if err = toml.NewDecoder(f).Decode(&Conf); err != nil {
return err
}
Conf.ConfigFile = file
return nil
}}, {"flush_dns", func() error {
if Conf.Router.FlushDNSCmd != "" {
return execute(Conf.Router.FlushDNSCmd)
}
return nil
}}}
// AddReloadConfigHook add hook function for reload config
func AddReloadConfigHook(step string, fn func() error) {
loadConfigFns = append(loadConfigFns, struct {
step string
fn func() error
}{step, fn})
}
// SuggestCh add domain into suggestios
var SuggestCh = make(chan string)
// AddDynamic add new domain into dynamic list
func AddDynamic(domain string) {
flushMu.Lock()
Conf.Router.DynamicList = append(Conf.Router.DynamicList, domain)
Conf.Router.DynamicList = util.NewReverseSecSlice(Conf.Router.DynamicList).Sort().Uniq()
flushMu.Unlock()
// 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()
flushOnce.Do(func() {
if Conf.ConfigFile != "" {
go flushConf()
}
})
{ // safe write
select {
case flushCh <- struct{}{}:
default:
}
}
func flushConf() {
for range flushCh {
// safe write
if Conf.ConfigFile != "" {
f, err := os.OpenFile(Conf.ConfigFile+"~", 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
}
flushMu.Lock()
if err := toml.NewEncoder(f).ArraysWithOneElementPerLine(true).Encode(Conf); err != nil {
glog.Errorln(err)
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)
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)
}
}
}
+26
View File
@@ -0,0 +1,26 @@
// +build darwin
package conf
import (
"context"
"flag"
"fmt"
"os/exec"
"time"
)
func Init() {
flag.StringVar(&Conf.Router.FlushDNSCmd, "flush_dns", "pkill mDNSResponder || true", "flush dns command")
}
func execute(cmd string) error {
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
defer cancel()
out, err := exec.CommandContext(ctx, "sh", "-c", Conf.Router.FlushDNSCmd).CombinedOutput()
if err != nil {
return fmt.Errorf("cmd: %s, err: %s, output: %s", Conf.Router.FlushDNSCmd, err, out)
}
return nil
}
+25
View File
@@ -0,0 +1,25 @@
// +build linux
package conf
import (
"context"
"fmt"
"os/exec"
"time"
)
func Init() {
flag.StringVar(&Conf.Router.FlushDNSCmd, "flush_dns", "", "flush dns command")
}
func execute(cmd string) error {
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
defer cancel()
out, err := exec.CommandContext(ctx, "sh", "-c", Conf.Router.FlushDNSCmd).CombinedOutput()
if err != nil {
return fmt.Errorf("cmd: %s, err: %s, output: %s", Conf.Router.FlushDNSCmd, 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)
}
+20 -23
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,19 +23,15 @@ 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])
func Init() {
exePath, _ := filepath.Abs(os.Args[0])
logFile := filepath.Join(filepath.Dir(exePath), name+".log")
if !flag.Parsed() {
os.Mkdir("log", 0755)
flag.Set("log_dir", filepath.Dir(os.Args[0])+"/log")
flag.Parse()
}
install := flag.Bool("i", false, "install sower as a service")
uninstall := flag.Bool("u", false, "uninstall sower from service list")
logFile := flag.String("log", logFile, name+" log file path")
flag.StringVar(&Conf.Router.FlushDNSCmd, "flush_dns", "ipconfig /flushdnss", "flush dns command")
flag.Parse()
switch {
case *install:
@@ -46,7 +41,7 @@ func initArgs() {
s.Close()
return fmt.Errorf("service %s already exists", name)
}
s, err = m.CreateService(name, exePath, mgr.Config{
s, err = m.CreateService(name, exePath, mgr.Config{
DisplayName: "Sower Proxy",
StartType: windows.SERVICE_AUTO_START,
})
@@ -77,18 +72,18 @@ func initArgs() {
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)
@@ -110,12 +105,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 +119,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()
@@ -174,6 +169,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", Conf.ClearDNSCache, out, err)
}
return nil
}
+16 -13
View File
@@ -1,14 +1,18 @@
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=[
[upstream]
socks5="127.0.0.1:1080"
dns="" # eg: 223.5.5.5, keep empty to get it from network environment
[downstream]
serve_ip="127.0.0.1"
http_proxy="" # eg: :8080, 127.0.0.1:8080, keep empty to disable it
[router.port_mapping]
# 2222="aaa.bbb.cc:22"
[router]
flush_dns_cmd="" # Platform-related, keep empty to use default command
proxy_level=2 # dynamic detect level, (0~4), more bigger more harder to be proxy
proxy_list=[
"**.google.*", # google
"**.goo.gl",
"**.googleusercontent.com",
@@ -31,7 +35,7 @@ blocklist=[
"*.githubusercontent.com",
"*.github.*",
]
whitelist=[
direct_list=[
"**.in-addr.arpa",
"imap.*.*",
"imap.*.*.*",
@@ -48,4 +52,3 @@ whitelist=[
"**.163.com",
"**.weiyun.com",
]
verbose=0