mirror of
https://github.com/wweir/sower.git
synced 2024-04-21 12:42:15 +00:00
Refactor to proxy router
This commit is contained in:
@@ -15,8 +15,8 @@ test:
|
||||
go test ./...
|
||||
build:
|
||||
go build -ldflags \
|
||||
"-X main.version=$(shell git describe --tags) \
|
||||
-X main.date=$(shell date +%Y-%m-%d)"
|
||||
"-X conf.version=$(shell git describe --tags) \
|
||||
-X conf.date=$(shell date +%Y-%m-%d)"
|
||||
image:
|
||||
docker build -t sower -f .github/Dockerfile .
|
||||
|
||||
|
||||
+105
-80
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
+107
-141
@@ -1,35 +1,37 @@
|
||||
package dns
|
||||
|
||||
/*
|
||||
* Deep integration with package conf: github.com/wweir/sower/conf
|
||||
*/
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/miekg/dns"
|
||||
mem "github.com/wweir/mem-go"
|
||||
"github.com/wweir/sower/util"
|
||||
"github.com/wweir/sower/conf"
|
||||
"github.com/wweir/sower/internal/http"
|
||||
internal_net "github.com/wweir/sower/internal/net"
|
||||
"github.com/wweir/sower/internal/socks5"
|
||||
"github.com/wweir/utils/log"
|
||||
"github.com/wweir/utils/mem"
|
||||
)
|
||||
|
||||
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{}{}
|
||||
func ServeDNS() {
|
||||
serveIP := net.ParseIP(conf.Conf.Downstream.ServeIP)
|
||||
if conf.Conf.Downstream.ServeIP == "" || serveIP.String() != conf.Conf.Downstream.ServeIP {
|
||||
log.Fatalw("invalid listen ip", "ip", conf.Conf.Downstream.ServeIP)
|
||||
}
|
||||
dnsServer, err := PickUpstreamDNS(serveIP.String(), conf.Conf.Upstream.DNS)
|
||||
if err != nil {
|
||||
log.Fatalw("")
|
||||
}
|
||||
|
||||
d := &detect{proxy: conf.Conf.Upstream.Socks5}
|
||||
|
||||
dns.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) {
|
||||
// *Msg r has an TSIG record and it was validated
|
||||
@@ -44,162 +46,126 @@ func StartDNS(dnsServer, listenIP string, suggestCh chan<- string, level string)
|
||||
}
|
||||
|
||||
domain := r.Question[0].Name
|
||||
if idx := strings.IndexByte(domain, colon); idx > 0 {
|
||||
if idx := strings.IndexByte(domain, ':'); idx > 0 {
|
||||
domain = domain[:idx] // trim port
|
||||
}
|
||||
|
||||
matchAndServe(w, r, domain, listenIP, dnsServer, dhcpCh, ip, suggest)
|
||||
if err := matchAndServe(w, r, serveIP, d, domain, dnsServer); err != nil {
|
||||
server, err := PickUpstreamDNS(serveIP.String(), dnsServer)
|
||||
if err != nil {
|
||||
log.Errorw("detect upstream dns fail", "err", err)
|
||||
} else {
|
||||
dnsServer = server
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
server := &dns.Server{Addr: net.JoinHostPort(listenIP, "53"), Net: "udp"}
|
||||
glog.Fatalln(server.ListenAndServe())
|
||||
server := &dns.Server{Addr: ":53", Net: "udp"}
|
||||
log.Fatalw("dns serve fail", "err", 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,
|
||||
}},
|
||||
func PickUpstreamDNS(listenIP string, dnsServer string) (string, error) {
|
||||
if dnsServer == "" {
|
||||
return internal_net.GetDefaultDNSServer()
|
||||
}
|
||||
|
||||
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)
|
||||
if _, port, err := net.SplitHostPort(dnsServer); err != nil {
|
||||
return "", fmt.Errorf("parse upstream dns(%s) server fail: %w", dnsServer, err)
|
||||
} else if port == "" {
|
||||
return net.JoinHostPort(dnsServer, "53"), nil
|
||||
}
|
||||
return dnsServer, nil
|
||||
}
|
||||
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
|
||||
const timeout = 200 * time.Millisecond
|
||||
|
||||
func matchAndServe(w dns.ResponseWriter, r *dns.Msg, serveIP net.IP, d *detect, domain, dnsServer string) error {
|
||||
if (!whiteList.Match(domain)) &&
|
||||
(blockList.Match(domain) || suggestList.Match(domain)) {
|
||||
w.WriteMsg(localA(r, domain, serveIP))
|
||||
return nil
|
||||
}
|
||||
|
||||
go mem.Remember(suggest, domain)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
|
||||
if err := mem.Remember(d, domain); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
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
|
||||
if err != nil || msg == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.WriteMsg(msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
type intelliSuggest struct {
|
||||
suggestCh chan<- string
|
||||
level level
|
||||
listenIP string
|
||||
timeout time.Duration
|
||||
type detect struct {
|
||||
proxy string
|
||||
port http.Port
|
||||
}
|
||||
|
||||
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
|
||||
func (d *detect) Get(key interface{}) error {
|
||||
// break deadloop, for ugly wildcard setting dns setting
|
||||
domain := strings.TrimSuffix(key.(string), ".")
|
||||
if strings.Count(domain, ".") > 10 {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
ip, err := net.LookupIP(domain)
|
||||
if err != nil || len(ip) == 0 {
|
||||
glog.V(1).Infoln(domain, ip, err)
|
||||
return
|
||||
wg := sync.WaitGroup{}
|
||||
httpScore, httpsScore := new(int32), new(int32)
|
||||
for _, ping := range [...]detect{{"", http.HTTP}, {"", http.HTTPS}} {
|
||||
wg.Add(1)
|
||||
go func(ping detect) {
|
||||
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 [...]detect{{d.proxy, http.HTTP}, {d.proxy, http.HTTPS}} {
|
||||
wg.Add(1)
|
||||
go func(ping detect) {
|
||||
defer wg.Done()
|
||||
|
||||
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)
|
||||
|
||||
if conn, err := net.Dial("tcp", ping.proxy); err != nil {
|
||||
return
|
||||
} 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
|
||||
}
|
||||
conn = socks5.ToSocks5(conn, domain, ping.port.String())
|
||||
if err := ping.port.PingWithConn(domain, conn, timeout); err != nil {
|
||||
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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}(idx)
|
||||
}(ping)
|
||||
}
|
||||
return
|
||||
|
||||
wg.Wait()
|
||||
if int(*httpScore+*httpsScore) >= conf.Conf.Router.ProxyLevel {
|
||||
conf.AddDynamic(domain)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -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]]
|
||||
}
|
||||
+15
-47
@@ -3,30 +3,27 @@ package dns
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/wweir/sower/conf"
|
||||
"github.com/wweir/sower/util"
|
||||
"github.com/wweir/utils/log"
|
||||
)
|
||||
|
||||
var (
|
||||
blockList *util.Node
|
||||
suggestList *util.Node
|
||||
whiteList *util.Node
|
||||
)
|
||||
var blockList *util.Node
|
||||
var suggestList *util.Node
|
||||
var 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 init() {
|
||||
reloadFn := func() error {
|
||||
whiteList = util.NewNodeFromRules(".", conf.Conf.Router.DirectList...)
|
||||
blockList = util.NewNodeFromRules(".", conf.Conf.Router.ProxyList...)
|
||||
suggestList = util.NewNodeFromRules(".", conf.Conf.Router.DynamicList...)
|
||||
log.Infow("reload config rules")
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
reloadFn()
|
||||
conf.AddReloadConfigHook("reload rules", reloadFn)
|
||||
}
|
||||
|
||||
func localA(r *dns.Msg, domain string, localIP net.IP) *dns.Msg {
|
||||
@@ -45,32 +42,3 @@ func localA(r *dns.Msg, domain string, localIP net.IP) *dns.Msg {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -3,25 +3,11 @@ 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-20200201061343-d44781e298da
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5
|
||||
)
|
||||
|
||||
@@ -1,110 +1,98 @@
|
||||
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-20200201061343-d44781e298da h1:d4/Y71orUtfo1Z8AnKKCQWBqupjY9zZpCsoU3G5hssw=
|
||||
github.com/wweir/utils v0.0.0-20200201061343-d44781e298da/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 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
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-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/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 {
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,9 @@
|
||||
package net
|
||||
|
||||
import "net"
|
||||
|
||||
// Iface is net interface address info
|
||||
type Iface struct {
|
||||
net.HardwareAddr
|
||||
net.IP
|
||||
}
|
||||
@@ -2,11 +2,10 @@ package socks5
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func ToSocks5(c net.Conn, domain, port string) net.Conn {
|
||||
@@ -75,7 +74,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 {
|
||||
@@ -1,50 +1,89 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
"github.com/wweir/sower/internal/socks5"
|
||||
"github.com/wweir/sower/mux"
|
||||
"github.com/wweir/utils/log"
|
||||
)
|
||||
|
||||
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)
|
||||
go dns.ServeDNS()
|
||||
|
||||
tran, err := transport.GetTransport(cfg.NetType)
|
||||
go proxy(conf.Conf.Upstream.Socks5, conf.Conf.Downstream.ServeIP, "80", mux.ParseHTTP)
|
||||
go proxy(conf.Conf.Upstream.Socks5, conf.Conf.Downstream.ServeIP, "443", mux.ParseHTTPS)
|
||||
|
||||
for port, target := range conf.Conf.Router.PortMapping {
|
||||
go proxy(conf.Conf.Upstream.Socks5, conf.Conf.Downstream.ServeIP, port,
|
||||
func(conn net.Conn) (net.Conn, string, error) {
|
||||
return conn, target, nil
|
||||
})
|
||||
}
|
||||
|
||||
select {}
|
||||
}
|
||||
|
||||
func proxy(socks5Addr, serveIP, port string, mux func(net.Conn) (net.Conn, string, error)) {
|
||||
ln, err := net.Listen("tcp", net.JoinHostPort(serveIP, port))
|
||||
if err != nil {
|
||||
glog.Exitln(err)
|
||||
log.Fatalw("listen", "ip", serveIP, "port", port, "err", err)
|
||||
}
|
||||
|
||||
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)
|
||||
log.Infow("start proxy", "port", port)
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
log.Fatalw("listen", "ip", serveIP, "port", port, "err", err)
|
||||
}
|
||||
|
||||
go dns.StartDNS(cfg.DNSServer, cfg.ClientIP, conf.SuggestCh, cfg.SuggestLevel)
|
||||
proxy.StartClient(tran, isSocks5, serverAddr, cfg.Cipher, cfg.Password, cfg.ClientIP)
|
||||
if conn, target, err := mux(conn); err != nil {
|
||||
log.Errorw("mux parse", "target", target, "err", err)
|
||||
continue
|
||||
} else {
|
||||
go relay(socks5Addr, target, conn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func relay(socks5Addr, target string, conn net.Conn) {
|
||||
var socks5Conn net.Conn
|
||||
if c, err := net.Dial("tcp", socks5Addr); err != nil {
|
||||
log.Errorw("dial socks5", "addr", socks5Addr, "err", err)
|
||||
return
|
||||
} else if host, port, err := net.SplitHostPort(target); err != nil {
|
||||
log.Errorw("dial socks5", "target", target, "err", err)
|
||||
return
|
||||
} else {
|
||||
socks5Conn = socks5.ToSocks5(c, host, port)
|
||||
}
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
exitFlag := new(int32)
|
||||
wg.Add(2)
|
||||
go redirect(conn, socks5Conn, wg, exitFlag)
|
||||
redirect(socks5Conn, conn, wg, exitFlag)
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func redirect(dst, src net.Conn, wg *sync.WaitGroup, exitFlag *int32) {
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package mux
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/wweir/sower/util"
|
||||
)
|
||||
|
||||
func ParseHTTP(conn net.Conn) (net.Conn, 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 strings.Contains(resp.Host, ":") {
|
||||
return teeConn, resp.Host, nil
|
||||
}
|
||||
return teeConn, resp.Host + ":80", nil
|
||||
}
|
||||
|
||||
func ParseHTTPS(conn net.Conn) (net.Conn, string, error) {
|
||||
teeConn := &util.TeeConn{Conn: conn}
|
||||
teeConn.StartOrReset()
|
||||
defer teeConn.Stop()
|
||||
|
||||
host, _, err := extractSNI(io.Reader(teeConn))
|
||||
if err != nil {
|
||||
return teeConn, "", err
|
||||
}
|
||||
if strings.Contains(host, ":") {
|
||||
return teeConn, host, nil
|
||||
}
|
||||
return teeConn, host + ":443", nil
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package parser
|
||||
package mux
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"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"
|
||||
)
|
||||
|
||||
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) {
|
||||
resolveAddr(&server)
|
||||
|
||||
if r.Method == http.MethodConnect {
|
||||
httpsProxy(w, r, tran, isSocks5, server, cipher, password)
|
||||
} else {
|
||||
httpProxy(w, r, tran, isSocks5, server, cipher, password)
|
||||
}
|
||||
}),
|
||||
// Disable HTTP/2.
|
||||
TLSNextProto: map[string]func(*http.Server, *tls.Conn, http.Handler){},
|
||||
IdleTimeout: 90 * time.Second,
|
||||
}
|
||||
|
||||
glog.Fatalln(srv.ListenAndServe())
|
||||
}
|
||||
|
||||
func httpProxy(w http.ResponseWriter, r *http.Request,
|
||||
tran transport.Transport, isSocks5 bool, server, cipher, password string) {
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
for k, vs := range resp.Header {
|
||||
for _, v := range vs {
|
||||
w.Header().Add(k, v)
|
||||
}
|
||||
}
|
||||
w.WriteHeader(resp.StatusCode)
|
||||
io.Copy(w, resp.Body)
|
||||
}
|
||||
|
||||
func httpsProxy(w http.ResponseWriter, r *http.Request,
|
||||
tran transport.Transport, isSocks5 bool, server, cipher, password string) {
|
||||
|
||||
// local conn
|
||||
conn, _, err := w.(http.Hijacker).Hijack()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
|
||||
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)
|
||||
|
||||
} else {
|
||||
rc = shadow.Shadow(rc, cipher, password)
|
||||
rc = parser.NewHttpsConn(rc, port)
|
||||
}
|
||||
|
||||
relay(rc, conn)
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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]]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// 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 relay(conn1, conn2 net.Conn) {
|
||||
wg := &sync.WaitGroup{}
|
||||
exitFlag := new(int32)
|
||||
wg.Add(2)
|
||||
go redirect(conn2, conn1, wg, exitFlag)
|
||||
redirect(conn1, conn2, wg, exitFlag)
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Iface is net interface address info
|
||||
type Iface struct {
|
||||
net.HardwareAddr
|
||||
net.IP
|
||||
}
|
||||
|
||||
func WithTimeout(fn func() error, timeout time.Duration) error {
|
||||
var okCh = make(chan struct{})
|
||||
var err error
|
||||
|
||||
go func() {
|
||||
err = fn()
|
||||
close(okCh)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-okCh:
|
||||
return err
|
||||
case <-time.After(timeout):
|
||||
return errors.New("timeout: " + timeout.String())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user