mirror of
https://github.com/wweir/sower.git
synced 2024-04-21 12:42:15 +00:00
Refactor to tls
This commit is contained in:
+1
-1
@@ -4,7 +4,7 @@ FROM golang:1.13-alpine AS compiler
|
||||
RUN apk add --no-cache git make
|
||||
|
||||
# enable go modules
|
||||
WORKDIR /workdir
|
||||
WORKDIR /src
|
||||
COPY . .
|
||||
|
||||
# do not worry about downloading dependency, sower will fix this.
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<array>
|
||||
<string>/usr/local/bin/sower</string>
|
||||
<string>-f</string>
|
||||
<string>/usr/local/etc/sower.toml</string>
|
||||
<string>/etc/sower/sower.toml</string>
|
||||
</array>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
|
||||
-127
@@ -1,127 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
[[ -d /tmp/sower ]] || mkdir /tmp/sower
|
||||
cd /tmp/sower
|
||||
|
||||
echo ======== Install sower to your system ========
|
||||
succ_message(){
|
||||
if [[ -z $IP ]]; then
|
||||
IP=127.0.0.1
|
||||
fi
|
||||
echo
|
||||
echo ======== Installed sower to your system ========
|
||||
echo The config file is: /usr/local/etc/sower.toml
|
||||
echo please set $IP as the first domain name server manually.
|
||||
}
|
||||
|
||||
# main logic
|
||||
VERSION="$(curl -s "https://api.github.com/repos/wweir/sower/releases/latest" | awk -F '"' '/tag_name/{printf $4}')"
|
||||
case "$(uname -s)" in
|
||||
"Darwin")
|
||||
curl -SLf https://github.com/wweir/sower/releases/download/$VERSION/sower-darwin-amd64.tar.gz | tar xzv
|
||||
sudo mkdir -p /usr/local/bin/
|
||||
sudo mv sower /usr/local/bin/
|
||||
|
||||
if [[ -r /usr/local/etc/sower.toml ]]; then
|
||||
echo The config file already exists, keep the original file
|
||||
else
|
||||
printf "Please enter remote server adddress: "
|
||||
read ADDRESS
|
||||
printf "Please enter remote server password (default: 12345678): "
|
||||
read PASSWORD
|
||||
if [[ -z $PASSWORD ]]; then
|
||||
PASSWORD="12345678"
|
||||
fi
|
||||
printf "Please enter which IP do you wanna listen (default: 127.0.0.1):"
|
||||
read IP
|
||||
if [[ -z $IP ]]; then
|
||||
IP="127.0.0.1"
|
||||
fi
|
||||
echo
|
||||
|
||||
sed -i~ "s/# server_addr=\"remote-server/server_addr=\"$ADDRESS/" sower.toml
|
||||
sed -i~ "s/client_ip=\"127.0.0.1\"/client_ip=\"$IP\"/" sower.toml
|
||||
sed -i~ "s/# clear_dns_cache/clear_dns_cache/" sower.toml
|
||||
sed -i~ "s/\"12345678\"/\"$PASSWORD\"/" sower.toml
|
||||
sudo mkdir -p /usr/local/etc/
|
||||
sudo mv sower.toml /usr/local/etc/
|
||||
fi
|
||||
|
||||
echo
|
||||
echo Register auto start service, root privilege is needed!
|
||||
echo
|
||||
sudo mv cc.wweir.sower.plist /Library/LaunchDaemons/
|
||||
sudo chown root:wheel /Library/LaunchDaemons/cc.wweir.sower.plist
|
||||
sudo launchctl load -w /Library/LaunchDaemons/cc.wweir.sower.plist
|
||||
succ_message
|
||||
;;
|
||||
|
||||
"Linux")
|
||||
if [[ "$(cat /proc/1/comm)" != "systemd" ]]; then
|
||||
echo do not support auto deploy on SysVinit
|
||||
exit 1
|
||||
fi
|
||||
printf "Server side or client side, which do you wanna install [c/s]: "
|
||||
read SIDE
|
||||
echo
|
||||
|
||||
case "$SIDE" in
|
||||
"c")
|
||||
curl -SLf https://github.com/wweir/sower/releases/download/$VERSION/sower-linux-amd64.tar.gz | tar xzv
|
||||
|
||||
sudo mv sower /usr/local/bin/
|
||||
|
||||
if [[ -r /usr/local/etc/sower.toml ]]; then
|
||||
echo The config file already exists, keep the original file
|
||||
else
|
||||
printf "Please enter remote server adddress: "
|
||||
read ADDRESS
|
||||
printf "Please enter remote server password (default: 12345678): "
|
||||
read PASSWORD
|
||||
if [[ -z $PASSWORD ]]; then
|
||||
PASSWORD="12345678"
|
||||
fi
|
||||
printf "Please enter which IP do you wanna listen (default: 127.0.0.1):"
|
||||
read IP
|
||||
if [[ -z $IP ]]; then
|
||||
IP="127.0.0.1"
|
||||
fi
|
||||
echo
|
||||
|
||||
sed -i~ "s/# server_addr=\"remote-server/server_addr=\"$ADDRESS/" sower.toml
|
||||
sed -i~ "s/client_ip=\"127.0.0.1\"/client_ip=\"$IP\"/" sower.toml
|
||||
sed -i~ "s/\"12345678\"/\"$PASSWORD\"/" sower.toml
|
||||
sudo mv sower.toml /usr/local/etc/
|
||||
fi
|
||||
|
||||
sudo mv sower-client.service /etc/systemd/system/
|
||||
sudo systemctl enable sower-client
|
||||
sudo systemctl start sower-client
|
||||
succ_message
|
||||
;;
|
||||
|
||||
"s")
|
||||
curl -SLf https://github.com/wweir/sower/releases/download/$VERSION/sower-linux-amd64.tar.gz | tar xzv
|
||||
printf "Please enter remote server password (default: 12345678): "
|
||||
read PASSWORD
|
||||
echo
|
||||
if [[ -z $PASSWORD ]]; then
|
||||
PASSWORD="12345678"
|
||||
fi
|
||||
|
||||
sudo mv sower /usr/local/bin/
|
||||
sed -i~ "s%bin/sower%bin/sower -p $PASSWORD%" sower-server.service
|
||||
sudo mv sower-server.service /etc/systemd/system/
|
||||
sudo systemctl enable sower-server
|
||||
sudo systemctl start sower-server
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "invalid: $SIDE" && exit 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
echo Do not support auto deploy on this platform.
|
||||
;;
|
||||
esac
|
||||
@@ -9,6 +9,6 @@ WantedBy=multi-user.target
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=/tmp
|
||||
ExecStart=/usr/local/bin/sower -f /usr/local/etc/sower.toml
|
||||
ExecStart=/usr/local/bin/sower -f /etc/sower/sower.toml
|
||||
RestartSec=3
|
||||
Restart=on-failure
|
||||
|
||||
@@ -9,6 +9,6 @@ WantedBy=multi-user.target
|
||||
Type=simple
|
||||
User=nobody
|
||||
WorkingDirectory=/tmp
|
||||
ExecStart=/usr/local/bin/sower -n TCP -v 1
|
||||
ExecStart=/usr/local/bin/sower -s 127.0.0.1:8080
|
||||
RestartSec=3
|
||||
Restart=on-failure
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -u -x -e
|
||||
|
||||
# main logic
|
||||
case "$(uname -s)" in
|
||||
"Darwin")
|
||||
sudo launchctl unload /Library/LaunchDaemons/cc.wweir.sower.plist
|
||||
sudo rm -rf /Library/LaunchDaemons/cc.wweir.sower.plist
|
||||
rm -rf /usr/local/bin/sower /usr/local/etc/sower.toml
|
||||
;;
|
||||
"Linux")
|
||||
sudo systemctl disable sower-client ||true
|
||||
sudo systemctl disable sower-server || true
|
||||
sudo rm -rf /etc/systemd/system/sower-client.service /etc/systemd/system/sower-server.service
|
||||
sudo rm -rf /usr/local/bin/sower /usr/local/etc/sower.toml
|
||||
;;
|
||||
*)
|
||||
echo Do not support auto deploy on this platform.
|
||||
;;
|
||||
esac
|
||||
@@ -9,7 +9,7 @@ ifeq ("", "$(shell which stringer)")
|
||||
go get -v golang.org/x/tools/cmd/stringer
|
||||
endif
|
||||
go generate ./...
|
||||
|
||||
cd transport/grpc; protoc --gogofaster_out=plugins=grpc:. *.proto
|
||||
test:
|
||||
go vet ./...
|
||||
go test ./...
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
[](https://github.com/wweir/sower/stargazers)
|
||||
[](LICENSE)
|
||||
|
||||
# **In refactor**
|
||||
|
||||
中文介绍见 [Wiki](https://github.com/wweir/sower/wiki)
|
||||
|
||||
The sower is a cross-platform intelligent transparent proxy tool base on DNS solution.
|
||||
|
||||
+83
-77
@@ -4,12 +4,46 @@ import (
|
||||
"flag"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
toml "github.com/pelletier/go-toml"
|
||||
"github.com/wweir/sower/util"
|
||||
"github.com/wweir/utils/log"
|
||||
)
|
||||
|
||||
type client struct {
|
||||
Address string `toml:"address"`
|
||||
|
||||
HTTPProxy struct {
|
||||
Address string `toml:"address"`
|
||||
} `toml:"http_proxy"`
|
||||
|
||||
DNS struct {
|
||||
RedirectIP string `toml:"redirect_ip"`
|
||||
Relay string `toml:"relay"`
|
||||
FlushCmd string `toml:"flush_cmd"`
|
||||
} `toml:"dns"`
|
||||
|
||||
Router struct {
|
||||
PortMapping map[string]string `toml:"port_mapping"`
|
||||
DetectLevel int `toml:"detect_level"`
|
||||
DetectTimeout string `toml:"detect_timeout"`
|
||||
|
||||
ProxyList []string `toml:"proxy_list"`
|
||||
DirectList []string `toml:"direct_list"`
|
||||
DynamicList []string `toml:"dynamic_list"`
|
||||
DirectRules *util.Node `toml:"-"`
|
||||
ProxyRules *util.Node `toml:"-"`
|
||||
DynamicRules *util.Node `toml:"-"`
|
||||
} `toml:"router"`
|
||||
}
|
||||
type server struct {
|
||||
Relay string `toml:"relay"`
|
||||
CertFile string `toml:"cert_file"`
|
||||
KeyFile string `toml:"key_file"`
|
||||
CertEmail string `toml:"cert_email"`
|
||||
}
|
||||
|
||||
var (
|
||||
version, date string
|
||||
|
||||
@@ -17,54 +51,53 @@ var (
|
||||
flushMu = sync.Mutex{}
|
||||
flushCh = make(chan struct{})
|
||||
|
||||
// Conf define the config items
|
||||
Conf = struct {
|
||||
ConfigFile string
|
||||
|
||||
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"`
|
||||
}{}
|
||||
Server = server{}
|
||||
Client = client{}
|
||||
conf = struct {
|
||||
file string
|
||||
Server *server `toml:"server"`
|
||||
Client *client `toml:"client"`
|
||||
}{"", &Server, &Client}
|
||||
Password string
|
||||
)
|
||||
|
||||
func init() {
|
||||
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")
|
||||
var err error
|
||||
defer func() {
|
||||
if timeout, err = time.ParseDuration(Client.Router.DetectTimeout); err != nil {
|
||||
log.Fatalw("parse dynamic detect timeout", "val", Client.Router.DetectTimeout, "err", err)
|
||||
}
|
||||
|
||||
log.Infow("start", "version", version, "date", date, "conf", &conf)
|
||||
passwordData = []byte(Password)
|
||||
}()
|
||||
|
||||
flag.StringVar(&conf.file, "f", "", "config file, rewrite all other parameters if set")
|
||||
flag.StringVar(&Password, "passwd", "", "password, use domain if not set")
|
||||
flag.StringVar(&Server.Relay, "s", "", "relay to http service, eg: 127.0.0.1:8080")
|
||||
flag.StringVar(&Server.CertFile, "s_cert", "", "tls cert file, empty to auto get cert")
|
||||
flag.StringVar(&Server.KeyFile, "s_key", "", "tls key file, empty to auto get cert")
|
||||
flag.StringVar(&Client.Address, "c", "", "remote server, eg: aa.bb.cc") // TODO: socks5://127.0.0.1:1080
|
||||
flag.StringVar(&Client.HTTPProxy.Address, "http_proxy", ":8080", "http proxy, empty to disable")
|
||||
flag.StringVar(&Client.DNS.RedirectIP, "dns_redirect", "", "redirect ip, eg: 127.0.0.1, empty to disable dns")
|
||||
flag.StringVar(&Client.DNS.Relay, "dns_relay", "", "dns relay server ip, keep empty to dynamic detect")
|
||||
flag.IntVar(&Client.Router.DetectLevel, "level", 2, "dynamic rule detect level: 0~4")
|
||||
flag.StringVar(&Client.Router.DetectTimeout, "timeout", "300ms", "dynamic rule detect timeout")
|
||||
|
||||
Init() // execute platform init logic
|
||||
if !flag.Parsed() {
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
if conf.file == "" {
|
||||
return
|
||||
}
|
||||
|
||||
log.Infow("start", "version", version, "date", date, "config", Conf)
|
||||
for i := range loadConfigFns {
|
||||
if err = loadConfigFns[i].fn(); err != nil {
|
||||
log.Fatalw("load config", "config", conf.file, "step", loadConfigFns[i].step, "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// refreshFns will be executed while init and write new config
|
||||
@@ -72,66 +105,39 @@ var loadConfigFns = []struct {
|
||||
step string
|
||||
fn func() error
|
||||
}{{"load_config", func() error {
|
||||
f, err := os.OpenFile(Conf.ConfigFile, os.O_RDONLY, 0644)
|
||||
f, err := os.OpenFile(conf.file, os.O_RDONLY, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
//safe refresh config
|
||||
file := Conf.ConfigFile
|
||||
if err = toml.NewDecoder(f).Decode(&Conf); err != nil {
|
||||
return err
|
||||
}
|
||||
Conf.ConfigFile = file
|
||||
return toml.NewDecoder(f).Decode(&conf)
|
||||
|
||||
}}, {"load_rules", func() error {
|
||||
Client.Router.DirectRules = util.NewNodeFromRules(Client.Router.DirectList...)
|
||||
Client.Router.ProxyRules = util.NewNodeFromRules(Client.Router.ProxyList...)
|
||||
Client.Router.DynamicRules = util.NewNodeFromRules(Client.Router.DynamicList...)
|
||||
return nil
|
||||
|
||||
}}, {"flush_dns", func() error {
|
||||
if Conf.Router.FlushDNSCmd != "" {
|
||||
return execute(Conf.Router.FlushDNSCmd)
|
||||
if Client.DNS.FlushCmd != "" {
|
||||
return execute(Client.DNS.FlushCmd)
|
||||
}
|
||||
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})
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
flushOnce.Do(func() {
|
||||
if Conf.ConfigFile != "" {
|
||||
go flushConf()
|
||||
}
|
||||
})
|
||||
|
||||
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 conf.file != "" {
|
||||
f, err := os.OpenFile(conf.file+"~", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
log.Errorw("flush config", "step", "flush", "err", err)
|
||||
continue
|
||||
}
|
||||
|
||||
flushMu.Lock()
|
||||
if err := toml.NewEncoder(f).ArraysWithOneElementPerLine(true).Encode(Conf); err != nil {
|
||||
if err := toml.NewEncoder(f).ArraysWithOneElementPerLine(true).Encode(conf); err != nil {
|
||||
log.Errorw("flush config", "step", "flush", "err", err)
|
||||
flushMu.Unlock()
|
||||
f.Close()
|
||||
@@ -140,7 +146,7 @@ func flushConf() {
|
||||
flushMu.Unlock()
|
||||
f.Close()
|
||||
|
||||
if err = os.Rename(Conf.ConfigFile+"~", Conf.ConfigFile); err != nil {
|
||||
if err = os.Rename(conf.file+"~", conf.file); err != nil {
|
||||
log.Errorw("flush config", "step", "flush", "err", err)
|
||||
continue
|
||||
}
|
||||
|
||||
+3
-3
@@ -11,16 +11,16 @@ import (
|
||||
)
|
||||
|
||||
func Init() {
|
||||
flag.StringVar(&Conf.Router.FlushDNSCmd, "flush_dns", "pkill mDNSResponder || true", "flush dns command")
|
||||
flag.StringVar(&Client.DNS.FlushCmd, "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()
|
||||
out, err := exec.CommandContext(ctx, "sh", "-c", Client.DNS.FlushCmd).CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cmd: %s, err: %s, output: %s", Conf.Router.FlushDNSCmd, err, out)
|
||||
return fmt.Errorf("cmd: %s, err: %s, output: %s", Client.DNS.FlushCmd, err, out)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+4
-3
@@ -4,22 +4,23 @@ package conf
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Init() {
|
||||
flag.StringVar(&Conf.Router.FlushDNSCmd, "flush_dns", "", "flush dns command")
|
||||
flag.StringVar(&Client.DNS.FlushCmd, "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()
|
||||
out, err := exec.CommandContext(ctx, "sh", "-c", Client.DNS.FlushCmd).CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cmd: %s, err: %s, output: %s", Conf.Router.FlushDNSCmd, err, out)
|
||||
return fmt.Errorf("cmd: %s, err: %s, output: %s", Client.DNS.FlushCmd, err, out)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -25,12 +25,10 @@ const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndCon
|
||||
|
||||
func Init() {
|
||||
exePath, _ := filepath.Abs(os.Args[0])
|
||||
logFile := filepath.Join(filepath.Dir(exePath), name+".log")
|
||||
|
||||
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.StringVar(&Client.DNS.FlushCmd, "flush_dns", "ipconfig /flushdnss", "flush dns command")
|
||||
flag.Parse()
|
||||
|
||||
switch {
|
||||
@@ -42,6 +40,7 @@ func Init() {
|
||||
return fmt.Errorf("service %s already exists", name)
|
||||
}
|
||||
s, err = m.CreateService(name, exePath, mgr.Config{
|
||||
|
||||
DisplayName: "Sower Proxy",
|
||||
StartType: windows.SERVICE_AUTO_START,
|
||||
})
|
||||
@@ -153,7 +152,7 @@ func execute(cmd string) error {
|
||||
defer cancel()
|
||||
|
||||
var cmds []string
|
||||
for _, cmd := range strings.Split(Conf.ClearDNSCache, " ") {
|
||||
for _, cmd := range strings.Split(Client.DNS.FlushCmd, " ") {
|
||||
if cmd == "" {
|
||||
continue
|
||||
}
|
||||
@@ -170,7 +169,7 @@ func execute(cmd string) error {
|
||||
command := exec.CommandContext(ctx, cmds[0], cmds[1:]...)
|
||||
command.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
||||
if out, err := command.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("cmd: %s, output: %s, err: %w", Conf.ClearDNSCache, out, err)
|
||||
return fmt.Errorf("cmd: %s, output: %s, err: %w", Client.DNS.FlushCmd, out, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/wweir/sower/internal/http"
|
||||
"github.com/wweir/sower/util"
|
||||
"github.com/wweir/utils/log"
|
||||
"github.com/wweir/utils/mem"
|
||||
)
|
||||
|
||||
type dynamic struct {
|
||||
port http.Port
|
||||
}
|
||||
|
||||
var cache = mem.New(2 * time.Hour)
|
||||
var detect = &dynamic{}
|
||||
var passwordData []byte
|
||||
var timeout time.Duration
|
||||
|
||||
// ShouldProxy check if the domain shoule request though proxy
|
||||
func ShouldProxy(domain string) bool {
|
||||
if Client.Router.DirectRules.Match(domain) {
|
||||
return false
|
||||
}
|
||||
if Client.Router.ProxyRules.Match(domain) {
|
||||
return true
|
||||
}
|
||||
if Client.Router.DynamicRules.Match(domain) {
|
||||
return true
|
||||
}
|
||||
|
||||
cache.Remember(detect, domain)
|
||||
return Client.Router.DynamicRules.Match(domain)
|
||||
}
|
||||
|
||||
func (d *dynamic) Get(key interface{}) (err error) {
|
||||
// break deadloop, for ugly wildcard setting dns setting
|
||||
domain := strings.TrimSuffix(key.(string), ".")
|
||||
if strings.Count(domain, ".") > 10 {
|
||||
return nil
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
httpScore, httpsScore := new(int32), new(int32)
|
||||
for _, ping := range [...]dynamic{{port: http.HTTP}, {port: http.HTTPS}} {
|
||||
wg.Add(1)
|
||||
go func(ping dynamic) {
|
||||
defer wg.Done()
|
||||
|
||||
if err := ping.port.Ping(domain, timeout); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch ping.port {
|
||||
case http.HTTP:
|
||||
if !atomic.CompareAndSwapInt32(httpScore, 0, 2) {
|
||||
atomic.AddInt32(httpScore, 1)
|
||||
}
|
||||
case http.HTTPS:
|
||||
if !atomic.CompareAndSwapInt32(httpsScore, 0, 2) {
|
||||
atomic.AddInt32(httpScore, 1)
|
||||
}
|
||||
}
|
||||
}(ping)
|
||||
}
|
||||
for _, ping := range [...]dynamic{{port: http.HTTP}, {port: http.HTTPS}} {
|
||||
wg.Add(1)
|
||||
go func(ping dynamic) {
|
||||
defer wg.Done()
|
||||
|
||||
var conn net.Conn
|
||||
if conn, err = tls.Dial("tcp", net.JoinHostPort(Client.Address, "443"), &tls.Config{}); err != nil {
|
||||
log.Errorw("tls dial", "addr", net.JoinHostPort(Client.Address, "443"), "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
if ping.port == http.HTTP {
|
||||
conn = http.NewTgtConn(conn, passwordData, http.TGT_HTTP, "", 80)
|
||||
} else {
|
||||
conn = http.NewTgtConn(conn, passwordData, http.TGT_HTTPS, "", 443)
|
||||
}
|
||||
|
||||
if err := ping.port.PingWithConn(domain, conn, timeout); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch ping.port {
|
||||
case http.HTTP:
|
||||
if !atomic.CompareAndSwapInt32(httpScore, 0, -2) {
|
||||
atomic.AddInt32(httpScore, -1)
|
||||
}
|
||||
case http.HTTPS:
|
||||
if !atomic.CompareAndSwapInt32(httpsScore, 0, -2) {
|
||||
atomic.AddInt32(httpScore, -1)
|
||||
}
|
||||
}
|
||||
}(ping)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
if int(*httpScore+*httpsScore)+conf.Client.Router.DetectLevel < 0 {
|
||||
addDynamic(domain)
|
||||
log.Infow("add rule", "domain", domain, "http_score", *httpScore, "https_score", *httpsScore)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// addDynamic add new domain into dynamic list
|
||||
func addDynamic(domain string) {
|
||||
flushMu.Lock()
|
||||
Client.Router.DynamicList = util.NewReverseSecSlice(
|
||||
append(Client.Router.DynamicList, domain)).Sort().Uniq()
|
||||
Client.Router.DynamicRules = util.NewNodeFromRules(Client.Router.DynamicList...)
|
||||
flushMu.Unlock()
|
||||
|
||||
flushOnce.Do(func() {
|
||||
if conf.file != "" {
|
||||
go flushConf()
|
||||
}
|
||||
})
|
||||
|
||||
select {
|
||||
case flushCh <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
+22
-11
@@ -1,17 +1,28 @@
|
||||
[upstream]
|
||||
socks5="127.0.0.1:1080"
|
||||
dns="" # eg: 223.5.5.5, keep empty to get it from network environment
|
||||
# [server]
|
||||
# relay="127.0.0.1:8080"
|
||||
# cert_file=""
|
||||
# key_file=""
|
||||
# cert_email="abc@email.com"
|
||||
|
||||
[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"
|
||||
[client]
|
||||
address="aa.bb.cc"
|
||||
|
||||
[client.http_proxy]
|
||||
address=":8080"
|
||||
|
||||
[client.dns]
|
||||
redirect_ip="127.0.0.1"
|
||||
relay="" # empty to dynamic detect
|
||||
flush_cmd="" # macOS: pkill mDNSResponder || true, Windows: ipconfig /flushdnss
|
||||
|
||||
# [client.router.port_mapping]
|
||||
# ":2222"="aa.bb.cc:22"
|
||||
|
||||
[client.router]
|
||||
detect_level=2 # 0~4, the bigger the harder to add
|
||||
detect_timeout="300ms"
|
||||
|
||||
[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",
|
||||
|
||||
+11
-100
@@ -1,38 +1,27 @@
|
||||
package dns
|
||||
|
||||
/*
|
||||
* Deep integration with package conf: github.com/wweir/sower/conf
|
||||
*/
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"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"
|
||||
_net "github.com/wweir/sower/internal/net"
|
||||
"github.com/wweir/utils/log"
|
||||
"github.com/wweir/utils/mem"
|
||||
)
|
||||
|
||||
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)
|
||||
func ServeDNS(redirectIP, relayServer string) {
|
||||
serveIP := net.ParseIP(redirectIP)
|
||||
if redirectIP == "" || serveIP.String() != redirectIP {
|
||||
log.Fatalw("invalid listen ip", "ip", redirectIP)
|
||||
}
|
||||
dnsServer, err := PickUpstreamDNS(serveIP.String(), conf.Conf.Upstream.DNS)
|
||||
dnsServer, err := PickUpstreamDNS(serveIP.String(), relayServer)
|
||||
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
|
||||
if r.IsTsig() != nil && w.TsigStatus() == nil {
|
||||
@@ -50,7 +39,7 @@ func ServeDNS() {
|
||||
domain = domain[:idx] // trim port
|
||||
}
|
||||
|
||||
if err := matchAndServe(w, r, serveIP, d, domain, dnsServer); err != nil {
|
||||
if err := matchAndServe(w, r, serveIP, domain, dnsServer); err != nil {
|
||||
server, err := PickUpstreamDNS(serveIP.String(), dnsServer)
|
||||
if err != nil {
|
||||
log.Errorw("detect upstream dns fail", "err", err)
|
||||
@@ -66,7 +55,7 @@ func ServeDNS() {
|
||||
|
||||
func PickUpstreamDNS(listenIP string, dnsServer string) (string, error) {
|
||||
if dnsServer == "" {
|
||||
return internal_net.GetDefaultDNSServer()
|
||||
return _net.GetDefaultDNSServer()
|
||||
}
|
||||
|
||||
if _, port, err := net.SplitHostPort(dnsServer); err != nil {
|
||||
@@ -77,22 +66,13 @@ func PickUpstreamDNS(listenIP string, dnsServer string) (string, error) {
|
||||
return dnsServer, nil
|
||||
}
|
||||
|
||||
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)) {
|
||||
func matchAndServe(w dns.ResponseWriter, r *dns.Msg, serveIP net.IP, domain, dnsServer string) error {
|
||||
if conf.ShouldProxy(domain) {
|
||||
w.WriteMsg(localA(r, domain, serveIP))
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
msg, err := dns.Exchange(r, dnsServer)
|
||||
if err != nil || msg == nil {
|
||||
return err
|
||||
}
|
||||
@@ -100,72 +80,3 @@ func matchAndServe(w dns.ResponseWriter, r *dns.Msg, serveIP net.IP, d *detect,
|
||||
w.WriteMsg(msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
type detect struct {
|
||||
proxy string
|
||||
port http.Port
|
||||
}
|
||||
|
||||
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 nil
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
if conn, err := net.Dial("tcp", ping.proxy); err != nil {
|
||||
return
|
||||
} else {
|
||||
conn = socks5.ToSocks5(conn, domain, ping.port.String())
|
||||
if err := ping.port.PingWithConn(domain, conn, timeout); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch ping.port {
|
||||
case http.HTTP:
|
||||
if !atomic.CompareAndSwapInt32(httpScore, 0, -2) {
|
||||
atomic.AddInt32(httpScore, -1)
|
||||
}
|
||||
case http.HTTPS:
|
||||
if !atomic.CompareAndSwapInt32(httpsScore, 0, -2) {
|
||||
atomic.AddInt32(httpScore, -1)
|
||||
}
|
||||
}
|
||||
}(ping)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
if int(*httpScore+*httpsScore) >= conf.Conf.Router.ProxyLevel {
|
||||
conf.AddDynamic(domain)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
-20
@@ -4,28 +4,8 @@ import (
|
||||
"net"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/wweir/sower/conf"
|
||||
"github.com/wweir/sower/util"
|
||||
"github.com/wweir/utils/log"
|
||||
)
|
||||
|
||||
var blockList *util.Node
|
||||
var suggestList *util.Node
|
||||
var whiteList *util.Node
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
reloadFn()
|
||||
conf.AddReloadConfigHook("reload rules", reloadFn)
|
||||
}
|
||||
|
||||
func localA(r *dns.Msg, domain string, localIP net.IP) *dns.Msg {
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(r)
|
||||
|
||||
@@ -8,6 +8,7 @@ require (
|
||||
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
|
||||
github.com/wweir/utils v0.0.0-20200214114658-f6f356a08736
|
||||
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe
|
||||
)
|
||||
|
||||
@@ -43,8 +43,8 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy
|
||||
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/utils v0.0.0-20200201061343-d44781e298da h1:d4/Y71orUtfo1Z8AnKKCQWBqupjY9zZpCsoU3G5hssw=
|
||||
github.com/wweir/utils v0.0.0-20200201061343-d44781e298da/go.mod h1:Nv4eBGkUJiHDPgVowJJlQNcJYgMfst6IkjaDThH2/yI=
|
||||
github.com/wweir/utils v0.0.0-20200214114658-f6f356a08736 h1:x6LiUrnHR4CCqzRzB0zqiB9hzVFcRhSKrVlZDGW0FcM=
|
||||
github.com/wweir/utils v0.0.0-20200214114658-f6f356a08736/go.mod h1:Nv4eBGkUJiHDPgVowJJlQNcJYgMfst6IkjaDThH2/yI=
|
||||
go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc=
|
||||
@@ -55,8 +55,9 @@ 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-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/crypto v0.0.0-20200214034016-1d94cc7ab1c6 h1:Sy5bstxEqwwbYs6n0/pBuxKENqOeZUgD45Gp3Q3pqLg=
|
||||
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
@@ -72,10 +73,10 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
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/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/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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/md5"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/wweir/sower/util"
|
||||
)
|
||||
|
||||
const (
|
||||
TGT_OTHER byte = iota
|
||||
TGT_HTTP
|
||||
TGT_HTTPS
|
||||
)
|
||||
|
||||
// Write Addr
|
||||
type conn struct {
|
||||
typ byte
|
||||
password []byte
|
||||
domain []byte
|
||||
port uint16
|
||||
init bool
|
||||
net.Conn
|
||||
}
|
||||
|
||||
func NewTgtConn(c net.Conn, password []byte, tgtType byte, domain string, port uint16) net.Conn {
|
||||
return &conn{
|
||||
typ: tgtType,
|
||||
password: password,
|
||||
domain: []byte(domain),
|
||||
port: port,
|
||||
init: true,
|
||||
Conn: c,
|
||||
}
|
||||
}
|
||||
|
||||
// other => type + checksum + port + domain_length ++ domain + data
|
||||
// http => type + checksum ++ data
|
||||
// https => type + checksum + port ++ data
|
||||
type header struct {
|
||||
Type byte
|
||||
Checksum byte
|
||||
Port uint16
|
||||
DomainLength uint8
|
||||
}
|
||||
|
||||
func (c *conn) Write(b []byte) (n int, err error) {
|
||||
if c.init {
|
||||
c.init = false
|
||||
domainLength := byte(len(c.domain))
|
||||
if err := binary.Write(c.Conn, binary.BigEndian, &header{
|
||||
Type: c.typ,
|
||||
Checksum: checksum(c.password, c.port, domainLength),
|
||||
Port: c.port,
|
||||
DomainLength: domainLength,
|
||||
}); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
n, err := c.Conn.Write(append(c.domain, b...))
|
||||
return n - len(c.domain), err
|
||||
}
|
||||
|
||||
return c.Conn.Write(b)
|
||||
}
|
||||
|
||||
// ParseAddr parse target addr from net.Conn
|
||||
func ParseAddr(conn net.Conn, password []byte) (c net.Conn, domain string, port uint16, err error) {
|
||||
teeConn := &util.TeeConn{Conn: conn}
|
||||
teeConn.StartOrReset()
|
||||
defer func() {
|
||||
if err != nil {
|
||||
teeConn.Close()
|
||||
} else {
|
||||
teeConn.Stop()
|
||||
}
|
||||
}()
|
||||
|
||||
head := new(header)
|
||||
if err = binary.Read(conn, binary.BigEndian, head); err != nil {
|
||||
return teeConn, "", 0, nil
|
||||
}
|
||||
if head.Checksum != checksum(password, head.Port, head.DomainLength) {
|
||||
return teeConn, "", 0, nil
|
||||
}
|
||||
|
||||
switch head.Type {
|
||||
case TGT_OTHER:
|
||||
buf := make([]byte, int(head.DomainLength))
|
||||
if _, err = io.ReadFull(conn, buf); err != nil {
|
||||
return teeConn, "", 0, err
|
||||
}
|
||||
|
||||
return teeConn, string(buf), head.Port, nil
|
||||
|
||||
case TGT_HTTP:
|
||||
teeConn.DropAndRestart()
|
||||
|
||||
var resp *http.Request
|
||||
resp, err = http.ReadRequest(bufio.NewReader(teeConn))
|
||||
if err != nil {
|
||||
return nil, "", 0, err
|
||||
}
|
||||
|
||||
idx := strings.LastIndex(resp.Host, ":")
|
||||
if idx == -1 {
|
||||
return teeConn, resp.Host, 80, nil
|
||||
}
|
||||
|
||||
var port uint64
|
||||
if port, err = strconv.ParseUint(resp.Host[idx+1:], 10, 16); err != nil {
|
||||
return nil, "", 0, err
|
||||
}
|
||||
return teeConn, resp.Host[:idx], uint16(port), nil
|
||||
|
||||
case TGT_HTTPS:
|
||||
teeConn.DropAndRestart()
|
||||
|
||||
domain, _, err := extractSNI(teeConn)
|
||||
if err != nil {
|
||||
return nil, "", 0, err
|
||||
}
|
||||
return teeConn, domain, head.Port, nil
|
||||
|
||||
default:
|
||||
return nil, "", 0, errors.New("invalid request")
|
||||
}
|
||||
}
|
||||
|
||||
var errChecksum = errors.New("invalid checksum")
|
||||
|
||||
func checksum(password []byte, port uint16, length uint8) (val byte) {
|
||||
nums := md5.Sum(append(password, byte(port), length))
|
||||
for _, b := range nums {
|
||||
val += b
|
||||
}
|
||||
return val
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseAddr1(t *testing.T) {
|
||||
c1, c2 := net.Pipe()
|
||||
|
||||
go func() {
|
||||
c1 = NewTgtConn(c1, nil, TGT_HTTP, "", 0)
|
||||
req, _ := http.NewRequest("GET", "http://wweir.cc", bytes.NewReader([]byte{1, 2, 3}))
|
||||
req.Write(c1)
|
||||
}()
|
||||
|
||||
c2, host, port, err := ParseAddr(c2, nil)
|
||||
|
||||
if err != nil || host != "wweir.cc" || port != 80 {
|
||||
t.Error(err, host, port)
|
||||
}
|
||||
|
||||
req, err := http.ReadRequest(bufio.NewReader(c2))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil || len(data) != 3 || data[0] != 1 {
|
||||
t.Error(err, data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAddr2(t *testing.T) {
|
||||
c1, c2 := net.Pipe()
|
||||
|
||||
go func() {
|
||||
c1 = NewTgtConn(c1, nil, TGT_HTTPS, "", 443)
|
||||
c1.Write(HTTPS.PingMsg("wweir.cc"))
|
||||
}()
|
||||
|
||||
_, host, port, err := ParseAddr(c2, nil)
|
||||
|
||||
if err != nil || host != "wweir.cc" || port != 443 {
|
||||
t.Error(err, host, port)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAddr3(t *testing.T) {
|
||||
c1, c2 := net.Pipe()
|
||||
|
||||
go func() {
|
||||
c1 = NewTgtConn(c1, nil, TGT_OTHER, "wweir.cc", 1080)
|
||||
c1.Write(HTTPS.PingMsg("wweir.cc"))
|
||||
}()
|
||||
|
||||
_, host, port, err := ParseAddr(c2, nil)
|
||||
|
||||
if err != nil || host != "wweir.cc" || port != 1080 {
|
||||
t.Error(err, host, port)
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mux
|
||||
package http
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
@@ -0,0 +1,15 @@
|
||||
package net_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/wweir/sower/internal/net"
|
||||
)
|
||||
|
||||
func Example_dns() {
|
||||
got, err := net.GetDefaultDNSServer()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(got)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package net_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/wweir/sower/internal/net"
|
||||
)
|
||||
|
||||
func Example_iface() {
|
||||
got, err := net.PickInternetInterface()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(got)
|
||||
}
|
||||
@@ -1,89 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/wweir/sower/conf"
|
||||
"github.com/wweir/sower/dns"
|
||||
"github.com/wweir/sower/internal/socks5"
|
||||
"github.com/wweir/sower/mux"
|
||||
"github.com/wweir/utils/log"
|
||||
"github.com/wweir/sower/proxy"
|
||||
)
|
||||
|
||||
func main() {
|
||||
go dns.ServeDNS()
|
||||
|
||||
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
|
||||
})
|
||||
if conf.Server.Relay != "" {
|
||||
proxy.StartServer(conf.Server.Relay, conf.Password,
|
||||
conf.Server.CertFile, conf.Server.KeyFile, conf.Server.CertEmail)
|
||||
}
|
||||
|
||||
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 {
|
||||
log.Fatalw("listen", "ip", serveIP, "port", port, "err", err)
|
||||
}
|
||||
|
||||
log.Infow("start proxy", "port", port)
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
log.Fatalw("listen", "ip", serveIP, "port", port, "err", err)
|
||||
if conf.Client.Address != "" {
|
||||
if conf.Client.DNS.RedirectIP != "" {
|
||||
go dns.ServeDNS(conf.Client.DNS.RedirectIP, conf.Server.Relay)
|
||||
}
|
||||
|
||||
if conn, target, err := mux(conn); err != nil {
|
||||
log.Errorw("mux parse", "target", target, "err", err)
|
||||
continue
|
||||
} else {
|
||||
go relay(socks5Addr, target, conn)
|
||||
}
|
||||
proxy.StartClient(conf.Password, conf.Client.Address, conf.Client.HTTPProxy.Address,
|
||||
conf.Client.DNS.RedirectIP, conf.Client.Router.PortMapping)
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/wweir/sower/conf"
|
||||
_http "github.com/wweir/sower/internal/http"
|
||||
"github.com/wweir/utils/log"
|
||||
)
|
||||
|
||||
func startHTTPProxy(httpProxyAddr, serverAddr string, password []byte) {
|
||||
srv := &http.Server{
|
||||
Addr: httpProxyAddr,
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodConnect {
|
||||
httpsProxy(w, r, serverAddr, password)
|
||||
} else {
|
||||
httpProxy(w, r, serverAddr, password)
|
||||
}
|
||||
}),
|
||||
// Disable HTTP/2.
|
||||
TLSNextProto: map[string]func(*http.Server, *tls.Conn, http.Handler){},
|
||||
IdleTimeout: 90 * time.Second,
|
||||
}
|
||||
|
||||
go log.Fatalw("serve http proxy", "addr", httpProxyAddr, "err", srv.ListenAndServe())
|
||||
}
|
||||
|
||||
func httpProxy(w http.ResponseWriter, r *http.Request, serverAddr string, password []byte) {
|
||||
host, _, err := net.SplitHostPort(r.Host)
|
||||
if err != nil {
|
||||
host = r.Host
|
||||
}
|
||||
|
||||
roundTripper := &http.Transport{}
|
||||
if conf.ShouldProxy(host) {
|
||||
roundTripper.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
conn, err := tls.Dial("tcp", net.JoinHostPort(serverAddr, "443"), &tls.Config{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return _http.NewTgtConn(conn, password, _http.TGT_HTTP, "", 80), nil
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := roundTripper.RoundTrip(r)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
||||
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, serverAddr string, password []byte) {
|
||||
var host string
|
||||
var port = uint16(443)
|
||||
if h, p, err := net.SplitHostPort(r.Host); err != nil {
|
||||
host = r.Host
|
||||
} else if pNum, err := strconv.ParseUint(p, 10, 16); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
||||
return
|
||||
} else {
|
||||
host = h
|
||||
port = uint16(pNum)
|
||||
}
|
||||
|
||||
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()
|
||||
return
|
||||
}
|
||||
|
||||
var rc net.Conn
|
||||
if conf.ShouldProxy(host) {
|
||||
rc, err = tls.Dial("tcp", serverAddr, &tls.Config{})
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
rc = _http.NewTgtConn(rc, password, _http.TGT_HTTPS, "", port)
|
||||
|
||||
} else {
|
||||
rc, err = net.Dial("tcp", net.JoinHostPort(host, strconv.Itoa(int(port))))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
relay(conn, rc)
|
||||
}
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
_http "github.com/wweir/sower/internal/http"
|
||||
"github.com/wweir/utils/log"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
)
|
||||
|
||||
const configDir = "/etc/sower"
|
||||
|
||||
type head struct {
|
||||
checksum byte
|
||||
length byte
|
||||
}
|
||||
|
||||
func StartClient(password, serverAddr, httpProxy, dnsRedirectIP string, forwardMap map[string]string) {
|
||||
passwordData := []byte(password)
|
||||
if httpProxy != "" {
|
||||
startHTTPProxy(httpProxy, serverAddr, passwordData)
|
||||
}
|
||||
|
||||
relayToRemote := func(tgtType byte, lnAddr string, host string, port uint16) {
|
||||
ln, err := net.Listen("tcp", lnAddr)
|
||||
if err != nil {
|
||||
log.Fatalw("tcp listen", "port", lnAddr, "err", err)
|
||||
}
|
||||
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
log.Errorw("tcp accept", "port", lnAddr, "err", err)
|
||||
continue
|
||||
}
|
||||
|
||||
go func(conn net.Conn) {
|
||||
rc, err := tls.Dial("tcp", serverAddr, &tls.Config{})
|
||||
if err != nil {
|
||||
log.Errorw("tls dial", "addr", serverAddr, "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
relay(conn, _http.NewTgtConn(rc, passwordData, tgtType, host, port))
|
||||
}(conn)
|
||||
}
|
||||
}
|
||||
|
||||
if dnsRedirectIP != "" {
|
||||
go relayToRemote(_http.TGT_HTTP, dnsRedirectIP+":http", "", 80)
|
||||
go relayToRemote(_http.TGT_HTTPS, dnsRedirectIP+":http", "", 443)
|
||||
}
|
||||
|
||||
for from, to := range forwardMap {
|
||||
go func(from, to string) {
|
||||
host, portStr, err := net.SplitHostPort(to)
|
||||
if err != nil {
|
||||
log.Fatalw("parse port forward", "target", to, "err", err)
|
||||
}
|
||||
portNum, err := strconv.ParseUint(portStr, 10, 16)
|
||||
if err != nil {
|
||||
log.Fatalw("parse port forward", "target", to, "err", err)
|
||||
}
|
||||
port := uint16(portNum)
|
||||
|
||||
relayToRemote(_http.TGT_OTHER, from, host, port)
|
||||
}(from, to)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func StartServer(relayTarget, password, certFile, keyFile, email string) {
|
||||
certManager := autocert.Manager{
|
||||
Prompt: autocert.AcceptTOS,
|
||||
Cache: autocert.DirCache(configDir), //folder for storing certificates
|
||||
Email: email,
|
||||
}
|
||||
tlsConf := &tls.Config{GetCertificate: certManager.GetCertificate}
|
||||
if certFile != "" && keyFile != "" {
|
||||
if cert, err := tls.LoadX509KeyPair(certFile, keyFile); err != nil {
|
||||
log.Fatalw("load certificate", "cert", certFile, "key", keyFile, "err", err)
|
||||
} else {
|
||||
tlsConf = &tls.Config{Certificates: []tls.Certificate{cert}}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to redirect 80 to 443
|
||||
go http.ListenAndServe(":http", certManager.HTTPHandler(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
if host, _, err := net.SplitHostPort(r.Host); err != nil {
|
||||
r.URL.Host = r.Host
|
||||
} else {
|
||||
r.URL.Host = host
|
||||
}
|
||||
r.URL.Scheme = "https"
|
||||
http.Redirect(w, r, r.URL.String(), 301)
|
||||
})))
|
||||
|
||||
ln, err := tls.Listen("tcp", ":https", tlsConf)
|
||||
if err != nil {
|
||||
log.Fatalw("tcp listen", "err", err)
|
||||
}
|
||||
|
||||
passwordData := []byte(password)
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
log.Errorw("tcp accept", "err", err)
|
||||
continue
|
||||
}
|
||||
|
||||
go func(conn net.Conn) {
|
||||
conn, domain, port, err := _http.ParseAddr(conn, passwordData)
|
||||
if err != nil {
|
||||
log.Errorw("parse relay target", "err", err)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
addr := relayTarget
|
||||
if domain != "" {
|
||||
addr = net.JoinHostPort(domain, strconv.Itoa(int(port)))
|
||||
}
|
||||
|
||||
rc, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
log.Errorw("tcp dial", "host", domain, "addr", addr, "err", err)
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
relay(conn, rc)
|
||||
}(conn)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
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) {
|
||||
io.Copy(dst, src)
|
||||
|
||||
if atomic.CompareAndSwapInt32(exitFlag, 0, 1) {
|
||||
// wakeup blocked goroutine
|
||||
now := time.Now()
|
||||
src.SetDeadline(now)
|
||||
dst.SetDeadline(now)
|
||||
}
|
||||
|
||||
wg.Done()
|
||||
}
|
||||
+5
-5
@@ -14,11 +14,8 @@ type node struct {
|
||||
node map[string]*node
|
||||
}
|
||||
|
||||
func NewNode(sep string) *Node {
|
||||
return &Node{node{node: map[string]*node{}}, sep, &sync.RWMutex{}}
|
||||
}
|
||||
func NewNodeFromRules(sep string, rules ...string) *Node {
|
||||
n := NewNode(sep)
|
||||
func NewNodeFromRules(rules ...string) *Node {
|
||||
n := &Node{node{node: map[string]*node{}}, ".", &sync.RWMutex{}}
|
||||
for i := range rules {
|
||||
n.Add(rules[i])
|
||||
}
|
||||
@@ -69,6 +66,9 @@ func (n *node) add(secs []string) {
|
||||
}
|
||||
|
||||
func (n *Node) Match(item string) bool {
|
||||
if n == nil {
|
||||
return false
|
||||
}
|
||||
return n.matchSecs(strings.Split(n.trim(item), n.sep), false)
|
||||
}
|
||||
|
||||
|
||||
@@ -15,14 +15,14 @@ func TestNode_Match(t *testing.T) {
|
||||
tests []test
|
||||
}{{
|
||||
"simple",
|
||||
NewNodeFromRules(".", "a.wweir.cc", "b.wweir.cc"),
|
||||
NewNodeFromRules("a.wweir.cc", "b.wweir.cc"),
|
||||
[]test{
|
||||
{"a.wweir.cc", true},
|
||||
{"b.wweir.cc", true},
|
||||
},
|
||||
}, {
|
||||
"parent",
|
||||
NewNodeFromRules(".", "wweir.cc", "a.wweir.cc"),
|
||||
NewNodeFromRules("wweir.cc", "a.wweir.cc"),
|
||||
[]test{
|
||||
{"wweir.cc", true},
|
||||
{"a.wweir.cc", true},
|
||||
@@ -30,7 +30,7 @@ func TestNode_Match(t *testing.T) {
|
||||
},
|
||||
}, {
|
||||
"fuzz1",
|
||||
NewNodeFromRules(".", "wweir.cc", "a.wweir.cc", "*.wweir.cc"),
|
||||
NewNodeFromRules("wweir.cc", "a.wweir.cc", "*.wweir.cc"),
|
||||
[]test{
|
||||
{"wweir.cc", true},
|
||||
{"a.wweir.cc", true},
|
||||
@@ -39,7 +39,7 @@ func TestNode_Match(t *testing.T) {
|
||||
},
|
||||
}, {
|
||||
"fuzz2",
|
||||
NewNodeFromRules(".", "a.*.cc", "c.wweir.*"),
|
||||
NewNodeFromRules("a.*.cc", "c.wweir.*"),
|
||||
[]test{
|
||||
{"wweir.cc", false},
|
||||
{"a.wweir.cc", true},
|
||||
@@ -48,7 +48,7 @@ func TestNode_Match(t *testing.T) {
|
||||
},
|
||||
}, {
|
||||
"fuzz3",
|
||||
NewNodeFromRules(".", "*.*.cc", "iamp.*.*"),
|
||||
NewNodeFromRules("*.*.cc", "iamp.*.*"),
|
||||
[]test{
|
||||
{"wweir.cc", false},
|
||||
{"a.wweir.cc", true},
|
||||
@@ -57,7 +57,7 @@ func TestNode_Match(t *testing.T) {
|
||||
},
|
||||
}, {
|
||||
"fuzz4",
|
||||
NewNodeFromRules(".", "**.cc", "a.**.com", "**.wweir.*"),
|
||||
NewNodeFromRules("**.cc", "a.**.com", "**.wweir.*"),
|
||||
[]test{
|
||||
{"wweir.cc", true},
|
||||
{"a.wweir.cc", true},
|
||||
|
||||
@@ -15,6 +15,10 @@ func (t *TeeConn) StartOrReset() {
|
||||
t.offset = 0
|
||||
t.tee = true
|
||||
}
|
||||
func (t *TeeConn) DropAndRestart() {
|
||||
t.buf = []byte{}
|
||||
t.tee = true
|
||||
}
|
||||
func (t *TeeConn) Stop() {
|
||||
t.offset = 0
|
||||
t.tee = false
|
||||
|
||||
Reference in New Issue
Block a user