Compare commits

...
18 Commits
16 changed files with 353 additions and 129 deletions
+8 -4
View File
@@ -13,17 +13,21 @@ jobs:
cp deploy/* .
mkdir artifacts
- run:
name: build linux Binary
name: Run Unit Test
command: |
make test
- run:
name: Build linux Binary
command: |
make build
tar czvf artifacts/sower_linux_amd64.tar.gz sower sower.toml sower-client.service sower-server.service
- run:
name: build macOS Binary
name: Build macOS Binary
command: |
GOOS=darwin GOARCH=amd64 make build
tar czvf artifacts/sower_darwin_amd64.tar.gz sower sower.toml cc.wweir.sower.plist
- run:
name: build Windows Binary
name: Build Windows Binary
command: |
GOOS=windows GOARCH=amd64 make build
tar czvf artifacts/sower_windows_amd64.tar.gz sower sower.toml
@@ -38,7 +42,7 @@ jobs:
ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete ${VERSION%-*-*} artifacts/
fi
- run:
name: build docker image
name: Build Docker Image
command: |
if git log -1 --pretty='%s' | grep -oE '(^|\s)@release(\s|$)'; then
docker login -u $DOCKER_USER -p $DOCKER_PASS
+3 -5
View File
@@ -12,12 +12,10 @@
*.out
.DS_Store
**/.DS_Store
._*
*.swp
*.swo
.vscode
.idea
sower
cert.pem
**/cert.pem
privkey.pem
**/privkey.pem
/sower.toml
+5 -4
View File
@@ -1,10 +1,11 @@
SERVER:=127.0.0.1:5533
default: build
default: test build
generate:
go generate ./...
test:
go test -race ./...
build:
GO111MODULE=on go build -v -ldflags \
"-X main.version=$(shell git describe --tags) \
@@ -16,14 +17,14 @@ kill:
sudo pkill -9 sower || true
client: build kill
sudo $(PWD)/sower -f conf/sower.toml
sudo $(PWD)/sower
server: build kill
$(PWD)/sower -n TCP -v 1
run: build kill
$(PWD)/sower -n TCP -v 1 &
sudo $(PWD)/sower -f conf/sower.toml &
sudo $(PWD)/sower &
@sleep 1
curl 127.0.0.1
@sleep 1
+4 -2
View File
@@ -17,6 +17,8 @@ Sower also provides an http(s) proxy listening on `:8080` by default. You can tu
## Installation
After Deployed, please check your config file, it is placed in `/usr/local/etc/sower.toml` by default. Here is the example config file [**conf/sower.toml**](https://github.com/wweir/sower/blob/master/conf/sower.toml)
### Auto deploy
Auto deploy script support Linux server side and masOS/Linux client side.
@@ -39,7 +41,7 @@ If you wanna uninstall sower, change `install` into `uninstall` and rerun the co
### Docker deploy
The auto build docker images are [wweir/sower](https://hub.docker.com/r/wweir/sower).
It is very simple to use it on the server side. Map the port and run it directly.
It is very simple to use it on the server side. Export the port(5533) and run it directly.
But the client is more troublesome and needs some understanding of the working mechanism of the sower.
@@ -88,4 +90,4 @@ For more detail, see [透明代理 Sower 技术剖析](https://wweir.cc/post/%E9
- [x] CI/CD
- [ ] relay optimization
- [ ] deploy script for all normal platform
- [x] dns rule intelligent suggestions
- [x] dns rule intelligent suggestions
+1 -1
View File
@@ -114,7 +114,7 @@ func watchConfigFile() {
}
}
glog.Infof("watch %s event: %v", Conf.ConfigFile, event)
glog.V(1).Infof("watch %s event: %v", Conf.ConfigFile, event)
for i := range OnRefreash {
if err := OnRefreash[i](); err != nil {
glog.Errorln(err)
+33 -15
View File
@@ -4,23 +4,41 @@ password="12345678"
server_port="5533"
# server_addr="remote-server:5533" # replce it to remote server
http_proxy=":8080" # eg: 192.168.0.2:8080
dns_server="223.5.5.5" # Alibaba public dns
dns_server="" # 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="killall -HUP mDNSResponder"
blocklist=[
"*.google.com", # google
"*.goo.gl",
"*.googleusercontent.com",
"*.googleapis.com",
"*.youtube.com", # youtube
"*.ytimg.com",
"*.ggpht.com",
"*.googlevideo.com",
"*.facebook.com", # facebook
"*.xx.fbcdn.net",
"*.twitter.com", # twitter
"*.twimg.com",
"*.blogspot.com", # blogspot
"*.wikipedia.org", # wikipeida
"**.google.com", # google
"**.goo.gl",
"**.googleusercontent.com",
"**.googleapis.com",
"*.googlesource.com",
"**.youtube.com", # youtube
"**.ytimg.com",
"**.ggpht.com",
"**.googlevideo.com",
"**.facebook.com", # facebook
"**.fbcdn.net",
"**.twitter.com", # twitter
"**.twimg.com",
"**.blogspot.com", # blogspot
"**.appspot.com",
"**.wikipedia.org", # wikipeida
"*.cloudfront.net",
"**.amazon.com",
"**.amazonaws.com",
"*.githubusercontent.com",
"*.github.com",
"*.github.io",
"**.mozilla.com",
"**.mozilla.net",
"**.akadns.net",
]
whitelist=[
"iamp.*.*.*",
"iamp.*.*",
"imap-mail.outlook.com",
"**.qq.com",
"**.baidu.com",
]
verbose=0
+70 -34
View File
@@ -3,6 +3,7 @@ package dns
import (
"net"
"strings"
"sync/atomic"
"time"
"github.com/golang/glog"
@@ -16,9 +17,28 @@ const colon = byte(':')
func StartDNS(dnsServer, listenIP string) {
ip := net.ParseIP(listenIP)
suggest := &intelliSuggest{listenIP, 2 * time.Second, []string{"80", "443"}}
suggest := &intelliSuggest{listenIP, time.Second}
mem.DefaultCache = mem.New(time.Hour)
dnsServer = net.JoinHostPort(dnsServer, "53")
dhcpCh := make(chan struct{})
if dnsServer != "" {
dnsServer = net.JoinHostPort(dnsServer, "53")
} else {
go func() {
for {
<-dhcpCh
host := util.GetDefaultDNSServer()
if host == "" {
continue
}
// atomic action
dnsServer = net.JoinHostPort(host, "53")
glog.Infoln("set dns server to", host)
}
}()
dhcpCh <- struct{}{}
}
dns.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) {
// *Msg r has an TSIG record and it was validated
@@ -37,15 +57,15 @@ func StartDNS(dnsServer, listenIP string) {
domain = domain[:idx]
}
matchAndServe(w, r, domain, listenIP, dnsServer, ip, suggest)
matchAndServe(w, r, domain, listenIP, dnsServer, dhcpCh, ip, suggest)
})
server := &dns.Server{Addr: net.JoinHostPort(listenIP, "53"), Net: "udp"}
glog.Fatalln(server.ListenAndServe())
}
func matchAndServe(w dns.ResponseWriter, r *dns.Msg, domain, listenIP,
dnsServer string, ipNet net.IP, suggest *intelliSuggest) {
func matchAndServe(w dns.ResponseWriter, r *dns.Msg, domain, listenIP, dnsServer string,
dhcpCh chan struct{}, ipNet net.IP, suggest *intelliSuggest) {
inWriteList := whiteList.Match(domain)
@@ -60,6 +80,12 @@ func matchAndServe(w dns.ResponseWriter, r *dns.Msg, domain, listenIP,
}
msg, err := dns.Exchange(r, dnsServer)
if err != nil && dhcpCh != nil {
select {
case dhcpCh <- struct{}{}:
default:
}
}
if msg == nil { // expose any response except nil
glog.V(1).Infof("get dns of %s fail: %s", domain, err)
return
@@ -70,11 +96,10 @@ func matchAndServe(w dns.ResponseWriter, r *dns.Msg, domain, listenIP,
type intelliSuggest struct {
listenIP string
timeout time.Duration
ports []string
}
func (i *intelliSuggest) GetOne(domain interface{}) (iface interface{}, e error) {
iface = struct{}{}
iface, e = struct{}{}, nil
// kill deadloop, for ugly wildcard setting dns setting
addr := strings.TrimSuffix(domain.(string), ".")
@@ -82,36 +107,47 @@ func (i *intelliSuggest) GetOne(domain interface{}) (iface interface{}, e error)
return
}
for _, port := range i.ports {
// give local dial a hand, make it not so easy to be added into suggestions
util.HTTPPing(addr+port, addr, i.timeout/5)
localCh := util.HTTPPing(addr+port, addr, i.timeout)
remoteCh := util.HTTPPing(i.listenIP+port, addr, i.timeout)
// give local dial a hand, make it not so easy to be added into suggestions
util.HTTPPing(addr, addr, util.Http, i.timeout/50)
select {
case err := <-localCh:
if err == nil {
glog.V(2).Infoln(addr, "local first, succ")
continue
}
if err = <-remoteCh; err != nil {
glog.V(2).Infoln(addr, "local first, all fail")
continue
}
glog.V(2).Infoln(addr, "local first, remote succ")
conf.AddSuggest(addr)
case err := <-remoteCh:
if err != nil {
glog.V(2).Infoln(addr, "remote first, fail")
continue
}
glog.V(2).Infoln(addr, "remote first, succ")
conf.AddSuggest(addr)
var (
pings = []struct {
viaAddr string
port util.Port
}{
{addr, util.Http},
{addr, util.Https},
{i.listenIP, util.Http},
{i.listenIP, util.Https},
}
half = int32(len(pings) / 2)
score = new(int32)
)
for idx := range pings {
go func(idx int) {
err := util.HTTPPing(pings[idx].viaAddr, addr, pings[idx].port, i.timeout)
if err != nil {
// local ping fail
if pings[idx].viaAddr == addr {
atomic.AddInt32(score, 1)
}
} else {
// remote ping succ
if pings[idx].viaAddr == i.listenIP {
atomic.AddInt32(score, 1)
glog.Infof("added suggest domain: %s", addr)
return
// local ping took longger than remote
} else if atomic.LoadInt32(score) >= half {
atomic.AddInt32(score, 1)
}
}
if atomic.LoadInt32(score) > half {
atomic.StoreInt32(score, 0) // avoid redo add action
conf.AddSuggest(addr)
glog.Infof("added suggest domain: %s", addr)
}
}(idx)
}
return
}
+2 -2
View File
@@ -20,7 +20,7 @@ func init() {
//first init
blockList = loadRules("block", conf.Conf.BlockList)
suggestList = loadRules("suggest", conf.Conf.BlockList)
suggestList = loadRules("suggest", conf.Conf.Suggestions)
whiteList = loadRules("white", conf.Conf.WhiteList)
whiteList.Add(host)
@@ -35,7 +35,7 @@ func init() {
func loadRules(name string, list []string) *util.Node {
rule := util.NewNodeFromRules(".", list...)
glog.V(2).Infof("load %s rule:\n%s", name, rule)
glog.V(3).Infof("load %s rule:\n%s", name, rule)
return rule
}
+6 -5
View File
@@ -6,11 +6,11 @@ require (
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
github.com/hashicorp/golang-lru v0.5.0 // indirect
github.com/klauspost/cpuid v1.2.0 // indirect
github.com/klauspost/reedsolomon v1.8.0 // indirect
github.com/klauspost/reedsolomon v1.9.0 // indirect
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f // indirect
github.com/lucas-clemente/quic-go v0.10.1
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced // indirect
github.com/miekg/dns v1.1.3
github.com/miekg/dns v1.1.4
github.com/pelletier/go-toml v1.2.0
github.com/pkg/errors v0.8.1
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
@@ -19,8 +19,9 @@ require (
github.com/ulule/deepcopier v0.0.0-20171107155558-ca99b135e50f // indirect
github.com/wweir/fsnotify v1.4.8
github.com/wweir/mem-go v0.0.0-20190109100331-8673ab596296
github.com/wweir/netboot v0.0.2
github.com/xtaci/kcp-go v5.0.7+incompatible
golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 // indirect
golang.org/x/sys v0.0.0-20190124100055-b90733256f2e // indirect
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd // indirect
golang.org/x/sys v0.0.0-20190213121743-983097b1a8a3 // indirect
)
+12 -10
View File
@@ -8,16 +8,16 @@ github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCO
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/reedsolomon v1.8.0 h1:lvvOkvk64cE1EGbBIgFk7WSOOsI1GexpuLiT7zjab6g=
github.com/klauspost/reedsolomon v1.8.0/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
github.com/klauspost/reedsolomon v1.9.0 h1:usyTY5K7D2B6WOHn2jmpB7ky8Qom96mShZmmq3OW4JU=
github.com/klauspost/reedsolomon v1.9.0/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f h1:sSeNEkJrs+0F9TUau0CgWTTNEwF23HST3Eq0A+QIx+A=
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f/go.mod h1:JpH9J1c9oX6otFSgdUHwUBUizmKlrMjxWnIAjff4m04=
github.com/lucas-clemente/quic-go v0.10.1 h1:ipcMmYP9RT+b1YytOKGUY1qndxPGOczVEQkAVz3CZrs=
github.com/lucas-clemente/quic-go v0.10.1/go.mod h1:wuD+2XqEx8G9jtwx5ou2BEYBsE+whgQmlj0Vz/77PrY=
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced h1:zqEC1GJZFbGZA0tRyNZqRjep92K5fujFtFsu5ZW7Aug=
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced/go.mod h1:NCcRLrOTZbzhZvixZLlERbJtDtYsmMw8Jc4vS8Z0g58=
github.com/miekg/dns v1.1.3 h1:1g0r1IvskvgL8rR+AcHzUA+oFmGcQlaIm4IqakufeMM=
github.com/miekg/dns v1.1.3/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.4 h1:rCMZsU2ScVSYcAsOXgmC6+AKOK+6pmQTOcw03nfwYV0=
github.com/miekg/dns v1.1.4/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
@@ -34,11 +34,13 @@ github.com/wweir/fsnotify v1.4.8 h1:zxOoqwE5p91F3rmcYLQheoSeTlIVqwi2b5T7UlpElWE=
github.com/wweir/fsnotify v1.4.8/go.mod h1:4GuxIc23DXfJI1/LhBXG5eE/wJ4CzL+kk5M7woxVprc=
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/wweir/netboot v0.0.2 h1:DFuNd97kxzgblExEKn4Nqf1du0m9qUnaTQ70rFgIjLU=
github.com/wweir/netboot v0.0.2/go.mod h1:p/WOnPHdqwgAI6YLEfdmwjwutbc/zDZ7EojAHlXLi4s=
github.com/xtaci/kcp-go v5.0.7+incompatible h1:zs9tc8XRID0m+aetu3qPWZFyRt2UIMqbXIBgw+vcnlE=
github.com/xtaci/kcp-go v5.0.7+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE=
golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b h1:Elez2XeF2p9uyVj0yEUDqQ56NFcDtcBNkYP7yv8YbUE=
golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 h1:ulvT7fqt0yHWzpJwI57MezWnYDVpCAYBVuYst/L+fAY=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sys v0.0.0-20190124100055-b90733256f2e h1:3GIlrlVLfkoipSReOMNAgApI0ajnalyLa/EZHHca/XI=
golang.org/x/sys v0.0.0-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67 h1:ng3VDlRp5/DHpSWl02R4rM9I+8M2rhmsuLwAMmkLQWE=
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd h1:HuTn7WObtcDo9uEEU7rEqL0jYthdXAmZ6PP+meazmaU=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sys v0.0.0-20190213121743-983097b1a8a3 h1:+KlxhGbYkFs8lMfwKn+2ojry1ID5eBSMXprS2u/wqCE=
golang.org/x/sys v0.0.0-20190213121743-983097b1a8a3/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+10 -5
View File
@@ -31,17 +31,22 @@ func NewClient(netType string) Client {
func StartClient(netType, server, cipher, password, listenIP string) {
connCh := listenLocal(listenIP, []string{"80", "443"})
client := NewClient(netType)
if addr, err := net.ResolveTCPAddr("tcp", server); err != nil {
glog.Fatalln(err)
} else {
server = addr.String()
}
resolved := false
glog.Infoln("Client started.")
for {
conn := <-connCh
glog.V(1).Infof("new conn from (%s) to (%s)", conn.RemoteAddr(), server)
if !resolved {
if addr, err := net.ResolveTCPAddr("tcp", server); err != nil {
glog.Fatalln(err)
} else {
server = addr.String()
resolved = true
}
}
rc, err := client.Dial(server)
if err != nil {
conn.Close()
+91
View File
@@ -0,0 +1,91 @@
package util
import (
"math/rand"
"net"
"runtime"
"github.com/golang/glog"
"github.com/wweir/netboot/dhcp4"
)
var xid = make([]byte, 4)
func GetDefaultDNSServer() string {
pack := &dhcp4.Packet{
Type: dhcp4.MsgDiscover,
Broadcast: true,
}
options := map[dhcp4.Option][]byte{
dhcp4.OptRequestedOptions: []byte{byte(dhcp4.OptDNSServers)},
}
NewConn := dhcp4.NewConn
if runtime.GOOS == "linux" {
NewConn = dhcp4.NewSnooperConn
}
ifaces := mustGetInterfaces()
for _, iface := range ifaces {
conn, err := NewConn(iface.IP.String() + ":68")
if err != nil { // maybe in use
glog.V(1).Infoln(err)
continue
}
defer conn.Close()
rand.Read(xid)
pack.TransactionID = xid
pack.HardwareAddr = iface.Interface.HardwareAddr
options[dhcp4.OptClientIdentifier] = iface.Interface.HardwareAddr
pack.Options = dhcp4.Options(options)
if err := conn.SendDHCP(pack, iface.Interface); err != nil {
glog.Errorln(err)
continue
}
pack, _, err = conn.RecvDHCP()
if err != nil {
glog.Errorln(err)
continue
}
ips, err := pack.Options.IPs(dhcp4.OptDNSServers)
if err != nil {
glog.Errorln(err)
continue
}
return ips[0].String() // if len(ips) == 0, err should not be wrong size
}
return ""
}
type netIface struct {
*net.Interface
net.Addr
net.IP
}
func mustGetInterfaces() []*netIface {
ifaces, err := net.Interfaces()
if err != nil {
glog.Fatalln(err)
}
v4Iface := make([]*netIface, 0, len(ifaces))
for i := range ifaces {
if len(ifaces[i].HardwareAddr) == 0 {
continue
}
addrs, _ := ifaces[i].Addrs()
for _, addr := range addrs {
if ip := addr.(*net.IPNet).IP.To4(); ip != nil {
v4Iface = append(v4Iface, &netIface{&ifaces[i], addr, ip})
break
}
}
}
return v4Iface
}
+20
View File
@@ -0,0 +1,20 @@
package util
import "testing"
func TestGetDefaultDNSServer(t *testing.T) {
t.Skip("skip for some enviroment not support dhcp")
tests := []struct {
name string
}{{
"",
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := GetDefaultDNSServer(); got == "" {
t.Errorf("GetDefaultDNSServer() return empty")
}
})
}
}
+35 -25
View File
@@ -10,36 +10,15 @@ import (
)
// HTTPPing try connect to a http(s) server with domain though the http addr
func HTTPPing(tcpAddr, domain string, timeout time.Duration) <-chan error {
errCh := make(chan error)
go func() {
errCh <- httpPing(tcpAddr, domain, timeout)
}()
return errCh
}
func httpPing(tcpAddr, domain string, timeout time.Duration) error {
conn, err := net.DialTimeout("tcp", tcpAddr, timeout)
func HTTPPing(viaHost, domain string, port Port, timeout time.Duration) (err error) {
conn, err := net.DialTimeout("tcp", port.JoinAddr(viaHost), timeout)
if err != nil {
return err
}
defer conn.Close()
host, port, err := net.SplitHostPort(tcpAddr)
if err != nil {
return err
}
var msg []byte
switch port {
case ":80":
msg = []byte("TRACE / HTTP/1.1\r\nHost: " + domain + "\r\n\r\n")
case ":443":
msg = NewClientHelloSNIMsg(domain)
}
conn.SetDeadline(time.Now().Add(timeout))
if _, err = conn.Write(msg); err != nil {
if _, err = conn.Write(port.pingMsg(domain)); err != nil {
return err
}
@@ -47,12 +26,43 @@ func httpPing(tcpAddr, domain string, timeout time.Duration) error {
// 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 && host == domain {
if err == io.EOF && viaHost == domain {
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) pingMsg(domain string) []byte {
switch p {
case Http:
return []byte("TRACE / HTTP/1.1\r\nHost: " + domain + "\r\n\r\n")
case Https:
return NewClientHelloSNIMsg(domain)
default:
panic("invalid port")
}
}
// SNI ==========================
type clientHelloSNI struct {
ContentType uint8
Version uint16
+24 -9
View File
@@ -54,33 +54,48 @@ func (n *node) add(secs []string) {
case 1:
n.node[secs[length-1]] = &node{node: map[string]*node{"": &node{}}}
default:
subNode, ok := n.node[secs[length-1]]
sec := secs[length-1]
if sec == "**" {
sec = "*"
}
subNode, ok := n.node[sec]
if !ok {
subNode = &node{node: map[string]*node{}}
n.node[secs[length-1]] = subNode
n.node[sec] = subNode
}
subNode.add(secs[:length-1])
}
}
func (n *Node) Match(item string) bool {
return n.matchSecs(strings.Split(n.trim(item), n.sep))
return n.matchSecs(strings.Split(n.trim(item), n.sep), false)
}
func (n *node) matchSecs(secs []string) bool {
func (n *node) matchSecs(secs []string, fuzzNode bool) bool {
length := len(secs)
if length == 0 {
if _, ok := n.node[""]; ok {
return true
}
_, ok := n.node["*"]
return ok
if _, ok := n.node["**"]; ok {
return true
}
if _, ok := n.node["*"]; ok {
return !fuzzNode
}
return false
}
if n, ok := n.node[secs[length-1]]; ok {
return n.matchSecs(secs[:length-1])
return n.matchSecs(secs[:length-1], false)
}
if n, ok := n.node["*"]; ok {
return n.matchSecs(secs[:length-1], true)
}
if _, ok := n.node["**"]; ok {
return true
}
_, ok := n.node["*"]
return ok
return false
}
+29 -8
View File
@@ -30,18 +30,39 @@ func TestNode_Match(t *testing.T) {
},
}, {
"fuzz1",
NewNodeFromRules(".", "wweir.cc", "*.wweir.cc"),
[]test{
{"wweir.cc", true},
{"a.wweir.cc", true},
},
}, {
"fuzz2",
NewNodeFromRules(".", "a.wweir.cc", "*.wweir.cc"),
NewNodeFromRules(".", "wweir.cc", "a.wweir.cc", "*.wweir.cc"),
[]test{
{"wweir.cc", true},
{"a.wweir.cc", true},
{"b.wweir.cc", true},
{"a.b.wweir.cc", false},
},
}, {
"fuzz2",
NewNodeFromRules(".", "a.*.cc"),
[]test{
{"wweir.cc", false},
{"a.wweir.cc", true},
{"b.wweir.cc", false},
},
}, {
"fuzz3",
NewNodeFromRules(".", "*.*.cc", "iamp.*.*"),
[]test{
{"wweir.cc", false},
{"a.wweir.cc", true},
{"b.wweir.cc", true},
{"iamp.wweir.cc", true},
},
}, {
"fuzz4",
NewNodeFromRules(".", "**.cc", "a.**.com"),
[]test{
{"wweir.cc", true},
{"a.wweir.cc", true},
{"a.b.wweir.cc", true},
{"a.wweir.com", true},
{"b.wweir.com", false},
},
}}
for _, tt := range tests {