Compare commits

...
27 Commits
Author SHA1 Message Date
wweir b050e312b2 Redesign shadow payload 2019-01-12 14:32:44 +08:00
wweir d78a757200 Fix shadow conn 2019-01-12 13:53:13 +08:00
wweir b2282a58a2 Fix relay logic 2019-01-10 08:02:04 +08:00
wweir 0fb2271cac Fix shadow 2019-01-10 07:54:44 +08:00
wweir a8089af235 Fix shadow 2019-01-07 22:28:32 +08:00
wweir 6e4a6c794e Fix shadow 2019-01-06 12:17:01 +08:00
wweir 1d620b8191 Fix shadow conn 2019-01-05 10:02:50 +08:00
wweir 9586e7bc8e Test close conn 2019-01-04 16:55:59 +08:00
wweir ec4286e6d4 Refactor crypto 2019-01-04 16:14:15 +08:00
wweir c01f217939 Fix crypto 2019-01-04 15:19:56 +08:00
wweir c62e3a3284 Add crypto support 2019-01-04 14:34:00 +08:00
wweir 578d9162aa Add tcp support 2018-12-17 07:41:34 +08:00
wweir 002ac49e65 Add password flag 2018-12-16 23:25:51 +08:00
wweir ee47f02cb5 Add kcp support 2018-12-16 23:14:58 +08:00
wweir b1127e8f9e Fix generate command 2018-12-16 21:30:57 +08:00
wweir 282969b696 Refactor: refactor proxy 2018-12-16 21:25:48 +08:00
wweir 2daaa8f3ef Close session on peer going away error 2018-12-13 22:27:40 +08:00
wweir d6776345a0 Update idle timeout 2018-12-09 12:20:34 +08:00
wweir 0e03605c37 Support clear dns cache hook 2018-12-09 10:34:53 +08:00
wweir 9ca58982b7 Update quic dial config 2018-12-08 19:29:39 +08:00
wweir 60452da6b1 Support dns query with port 2018-12-08 10:38:57 +08:00
wweir 049e805b4a Fix dns rule match logic 2018-12-08 09:41:47 +08:00
wweir b83f202bad Add suggestion for manual rule 2018-12-08 08:10:46 +08:00
wweir cb88395e66 Update config 2018-12-07 16:50:39 +08:00
wweir d8d5d4ea29 Update dependency 2018-12-04 23:40:44 +08:00
wweir fe0ccdfec8 Fix bestTry logic 2018-12-02 09:45:41 +08:00
wweir 1c45fae006 Fix unsigned domain point to local 2018-12-02 09:37:59 +08:00
25 changed files with 850 additions and 270 deletions
+10 -3
View File
@@ -2,6 +2,9 @@ SERVER:=127.0.0.1:5533
default: build
generate:
go generate ./...
build:
GOBIN=$(PWD) go install -v
@@ -12,8 +15,12 @@ client: build kill
sudo $(PWD)/sower -f conf/sower.toml -logtostderr
server: build kill
$(PWD)/sower -logtostderr -v 1
$(PWD)/sower -n QUIC -logtostderr -v 1
run: build kill
$(PWD)/sower -logtostderr -v 1 &
sudo $(PWD)/sower -f conf/sower.toml -logtostderr
$(PWD)/sower -n QUIC -logtostderr -v 1 &
sudo $(PWD)/sower -f conf/sower.toml -logtostderr &
sleep 1
@curl 127.0.0.1
sleep 1
sudo pkill -9 sower || true
+35 -8
View File
@@ -1,9 +1,13 @@
package conf
import (
"context"
"flag"
"net"
"os/exec"
"path/filepath"
"strconv"
"time"
"github.com/BurntSushi/toml"
"github.com/golang/glog"
@@ -11,18 +15,27 @@ import (
)
var Conf = struct {
ConfigFile string
ServerPort string `toml:"server_port"`
ServerAddr string `toml:"server_addr"`
DnsServer string `toml:"dns_server"`
ClientIP string `toml:"client_ip"`
ClientIPNet net.IP `toml:"-"`
ConfigFile string
NetType string `toml:"net_type"`
Password string `toml:"password"`
ServerPort string `toml:"server_port"`
ServerAddr string `toml:"server_addr"`
DnsServer string `toml:"dns_server"`
ClientIP string `toml:"client_ip"`
ClientIPNet net.IP `toml:"-"`
ClearDnsCache string `toml:"clear_dns_cache"`
BlockList []string `toml:"blocklist"`
Suggestions []string `toml:"suggestions"`
Verbose int `toml:"verbose"`
}{}
func init() {
flag.StringVar(&Conf.ConfigFile, "f", "", "config file location")
flag.StringVar(&Conf.NetType, "n", "QUIC", "proxy net type (QUIC|KCP|TCP)")
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.DnsServer, "d", "114.114.114.114", "client dns server")
@@ -46,6 +59,15 @@ var OnRefreash = []func() error{func() error {
return err
}
Conf.ClientIPNet = net.ParseIP(Conf.ClientIP)
// clear dns cache
if Conf.ClearDnsCache != "" {
ctx, _ := context.WithTimeout(context.TODO(), 5*time.Second)
if err := exec.CommandContext(ctx, "sh", "-c", Conf.ClearDnsCache).Run(); err != nil {
glog.Errorln(err)
}
}
// for glog
if err := flag.Set("v", strconv.Itoa(Conf.Verbose)); err != nil {
return err
@@ -53,13 +75,12 @@ var OnRefreash = []func() error{func() error {
return nil
}}
// watchConfigFile changes, fsnotify take too much cpu time, DIY
func watchConfigFile() {
watcher, err := fsnotify.NewWatcher()
if err != nil {
glog.Fatalln(err)
}
if err := watcher.Add(Conf.ConfigFile); err != nil {
if err := watcher.Add(filepath.Dir(Conf.ConfigFile)); err != nil {
glog.Fatalln(err)
}
@@ -67,6 +88,12 @@ func watchConfigFile() {
for {
select {
case event := <-watcher.Events:
if event.Op == fsnotify.Rename {
if err := watcher.Add(Conf.ConfigFile); err != nil {
glog.Errorln(err)
}
}
glog.Infof("watch %s event: %v", Conf.ConfigFile, event)
for i := range OnRefreash {
if err := OnRefreash[i](); err != nil {
+60 -12
View File
@@ -1,35 +1,83 @@
net_type="KCP"
password="12345678"
server_port="5533"
server_addr="127.0.0.1:5533" # replce it to server "IP"
server_addr="remote-server:5533" # replce it to remote server
dns_server="114.114.114.114"
# client_ip="192.168.0.1"
# clear_dns_cache="sudo killall -HUP mDNSResponder"
blocklist=[
"*.google.com", # google
"ocsp.pki.goog",
"g.co",
"*.goo.gl",
"*.googleusercontent.com",
"*.googleapis.com",
"gmail.com",
"*.youtube.com", # youtube
"*.ytimg.com",
"*.ggpht.com",
"*.googlevideo.com",
"*.vimeo.com",
"*.facebook.com", # facebook
"*.xx.fbcdn.net",
"*.xx.fbcdn.net",
"connect.facebook.net",
"*.twitter.com", # twitter
"*.twimg.com",
"*.*.blogspot.com", # blogspot
"t.co",
"*.twimg.com",
"www.linkedin.com",
"www.reddit.com", # reddit
"www.redditmedia.com",
"*.blogspot.com", # blogspot
"www.blogger.com",
"www.feedburner.com",
"img1.blogblog.com",
"*.aws.amazon.com", # amazon
"m.media-amazon.com",
"*.awsstatic.com",
"*.s3.amazonaws.com",
"*.cloudfront.net", # atlassian
"synchrony-cdn.atlassian.com",
"avatar-cdn.atlassian.com",
"*.medium.com",
"accounts-static.cdn.mozilla.net", # firefox
"*.services.mozilla.com",
"pocket-image-cache.com", # pocket
"*.golang.org", # golang
"go.googlesource.com",
"gist.github.com",
"godoc.org",
"gist.github.com", # github
"*.k8s.io", # k8s
"k8s.gcr.io",
"*.wikipedia.org", # wikipeida
"*.wikimedia.org",
"*.wikisource.org",
"lookup-api.apple.com",
"lookup-api.apple.com.edgekey.net",
"e16991.b.akamaiedge.net",
"*.wikimedia.org",
"*.wikisource.org",
"lookup-api.apple.com",
"lookup-api.apple.com.edgekey.net",
"e16991.b.akamaiedge.net",
"*.web.telegram.org", # telegram
"cloud.feedly.com", # feedly
"core.telegram.org",
"telegram.org",
"telegram.me",
"t.me",
"*.slack.com", #slack
"*.slack-edge.com",
"js.driftt.com",
"cloud.feedly.com", # feedly
"static.grammarly.com", # grammarly
"apkpure.com", # apkpure
"download.apkpure.com",
"*.nytimes.com", # New York times
"*.nyt.com",
"bandwagonhost.com", # bandwagonhost
"www.bwh1.net",
"*.akadns.net", # suggestions
"*.haxx.se",
"shadowsocks.org",
"t66y.com",
"ocsp.godaddy.com",
"*.qualtrics.com",
"*.starfieldtech.com",
"*.typekit.net",
"www.v2ray.com",
]
verbose=0
verbose=0
+35 -9
View File
@@ -10,6 +10,8 @@ import (
"github.com/wweir/sower/conf"
)
const colon = byte(':')
func StartDNS(dnsServer string) {
dns.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) {
// *Msg r has an TSIG record and it was validated
@@ -22,10 +24,15 @@ func StartDNS(dnsServer string) {
return
}
domain := r.Question[0].Name
if idx := strings.IndexByte(domain, colon); idx > 0 {
domain = domain[:idx]
}
if len(conf.Conf.BlockList) == 0 {
bestTry(w, r, r.Question[0].Name, dnsServer)
bestTry(w, r, domain, dnsServer)
} else {
manual(w, r, r.Question[0].Name, dnsServer)
manual(w, r, domain, dnsServer)
}
})
@@ -34,9 +41,12 @@ func StartDNS(dnsServer string) {
}
func bestTry(w dns.ResponseWriter, r *dns.Msg, domain, dnsServer string) {
msg, err := dns.Exchange(r, dnsServer+":53")
if err != nil || len(msg.Answer) == 0 {
w.WriteMsg(localA(r, domain))
msg, _ := dns.Exchange(r, dnsServer+":53")
if msg == nil {
return
}
if len(msg.Answer) == 0 { // expose any response
w.WriteMsg(msg)
return
}
@@ -48,6 +58,7 @@ func bestTry(w dns.ResponseWriter, r *dns.Msg, domain, dnsServer string) {
ip = "[" + msg.Answer[0].(*dns.AAAA).AAAA.String() + "]"
default:
w.WriteMsg(msg)
return
}
if _, err := net.DialTimeout("tcp", ip+":http", time.Second); err != nil {
@@ -64,21 +75,36 @@ func manual(w dns.ResponseWriter, r *dns.Msg, domain, dnsServer string) {
w.WriteMsg(localA(r, domain))
return
}
glog.V(2).Infof("match %s fail", domain)
msg, err := dns.Exchange(r, dnsServer+":53")
if err != nil || len(msg.Answer) == 0 {
w.WriteMsg(localA(r, domain))
if msg == nil { // expose any response except nil
glog.V(1).Infof("get dns of %s fail: %s", domain, err)
return
}
w.WriteMsg(msg)
if conf.Conf.Verbose != 0 && len(msg.Answer) != 0 {
go func() {
_, err := net.DialTimeout("tcp", domain+":http", 2*time.Second)
if err == nil || !strings.Contains(err.Error(), "timeout") {
return
}
_, err = net.DialTimeout("tcp", domain+":https", 3*time.Second)
if err == nil || !strings.Contains(err.Error(), "timeout") {
return
}
glog.V(1).Infof("SUGGEST check (%s) http(s) service: %s", domain, err)
}()
}
}
func localA(r *dns.Msg, domain string) *dns.Msg {
m := new(dns.Msg)
m.SetReply(r)
m.Answer = []dns.RR{&dns.A{
Hdr: dns.RR_Header{Name: domain, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 720},
Hdr: dns.RR_Header{Name: domain, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 20},
A: conf.Conf.ClientIPNet,
}}
return m
+11 -6
View File
@@ -53,16 +53,21 @@ func (n *Node) Match(addr string) bool {
func (n *Node) matchSecs(secs []string) bool {
length := len(secs)
if length == 0 {
return true
switch len(n.Node) {
case 0:
return true
case 1:
_, ok := n.Node["*"]
return ok
default:
return false
}
}
if n, ok := n.Node[secs[length-1]]; ok {
return n.matchSecs(secs[:length-1])
}
if n, ok := n.Node["*"]; ok {
return n.matchSecs(secs[:length-1])
}
return false
_, ok := n.Node["*"]
return ok
}
+11 -4
View File
@@ -7,15 +7,22 @@ require (
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
github.com/golang/mock v1.1.1 // indirect
github.com/hashicorp/golang-lru v0.5.0 // indirect
github.com/klauspost/cpuid v1.2.0 // indirect
github.com/klauspost/reedsolomon v1.7.0 // indirect
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f // indirect
github.com/lucas-clemente/quic-go v0.10.0
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced // indirect
github.com/miekg/dns v1.0.15
github.com/miekg/dns v1.1.1
github.com/onsi/ginkgo v1.7.0 // indirect
github.com/onsi/gomega v1.4.3 // indirect
github.com/pkg/errors v0.8.0
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/wweir/fsnotify v1.4.8
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869 // indirect
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a // indirect
github.com/xtaci/kcp-go v4.3.2+incompatible
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 // indirect
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc // indirect
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b // indirect
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 // indirect
)
+22 -8
View File
@@ -16,32 +16,46 @@ 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/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.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/reedsolomon v1.7.0 h1:pLFmRKGko2ZieiTGyo9DahLCIuljyxm+Zzhz/fYEonE=
github.com/klauspost/reedsolomon v1.7.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.0 h1:xEF+pSHYAOcu+U10Meunf+DTtc8vhQDRqlA0BJ6hufc=
github.com/lucas-clemente/quic-go v0.10.0/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.0.15 h1:9+UupePBQCG6zf1q/bGmTO1vumoG13jsrbWOSX1W6Tw=
github.com/miekg/dns v1.0.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.1 h1:DVkblRdiScEnEr0LR9nTnEQqHYycjkXW9bOjd+2EL2o=
github.com/miekg/dns v1.1.1/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
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/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/wweir/fsnotify v1.4.8 h1:zxOoqwE5p91F3rmcYLQheoSeTlIVqwi2b5T7UlpElWE=
github.com/wweir/fsnotify v1.4.8/go.mod h1:4GuxIc23DXfJI1/LhBXG5eE/wJ4CzL+kk5M7woxVprc=
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869 h1:kkXA53yGe04D0adEYJwEVQjeBppL01Exg+fnMjfUraU=
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
github.com/xtaci/kcp-go v4.3.2+incompatible h1:vM5gqHI8p5xa3eXQH279+16fG86gCz/IRamaQvWG+a8=
github.com/xtaci/kcp-go v4.3.2+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/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-20181122145206-62eef0e2fa9b h1:MQE+LT/ABUuuvEZ+YQAMSXindAdUh7slEmAkup74op4=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 h1:YAFjXN64LMvktoUZH9zgY4lGc/msGN7HQfoSuKCgaDU=
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+6 -5
View File
@@ -8,12 +8,13 @@ import (
)
func main() {
glog.Infoln("Starting:", conf.Conf)
conf := conf.Conf
glog.Infoln("Starting:", conf)
if conf.Conf.ServerAddr == "" {
proxy.StartServer(conf.Conf.ServerPort)
if conf.ServerAddr == "" {
proxy.StartServer(conf.NetType, conf.ServerPort, conf.Password)
} else {
go dns.StartDNS(conf.Conf.DnsServer)
proxy.StartClient(conf.Conf.ServerAddr)
go dns.StartDNS(conf.DnsServer)
proxy.StartClient(conf.NetType, conf.ServerAddr, conf.Password)
}
}
+46
View File
@@ -0,0 +1,46 @@
package parse
import (
"bufio"
"fmt"
"io"
"net"
"net/http"
"strings"
)
func ParseAddr(conn net.Conn) (teeConn *TeeConn, addr string, err error) {
teeConn = &TeeConn{Conn: conn}
teeConn.StartOrReset()
defer teeConn.Stop()
buf := make([]byte, 1)
if n, err := teeConn.Read(buf); err != nil {
return teeConn, "", fmt.Errorf("Read conn fail: %v, readed: %v", err, buf[:n])
}
teeConn.StartOrReset()
// https
if buf[0] == 0x16 { // SSL handleshake
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
}
// http
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
}
+1 -1
View File
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package parser
package parse
import (
"encoding/binary"
+37
View File
@@ -0,0 +1,37 @@
package parse
import (
"net"
)
type TeeConn struct {
net.Conn
buf []byte
offset int
tee bool // read
}
func (t *TeeConn) StartOrReset() {
t.offset = 0
t.tee = true
}
func (t *TeeConn) Stop() {
t.offset = 0
t.tee = false
}
func (t *TeeConn) Read(b []byte) (n int, err error) {
length := len(t.buf) - t.offset
if length > 0 {
n = copy(b, t.buf[t.offset:])
t.offset += n
return
}
n, err = t.Conn.Read(b)
if t.tee {
t.buf = append(t.buf, b[:n]...)
t.offset += n
}
return n, err
}
-75
View File
@@ -1,75 +0,0 @@
package parser
import (
"bufio"
"fmt"
"io"
"net"
"net/http"
"strings"
)
type TeeConn struct {
net.Conn
buf []byte
offset int
Tee bool // read
}
func (t *TeeConn) Reset() {
t.offset = 0
}
func (t *TeeConn) Read(b []byte) (n int, err error) {
length := len(t.buf) - t.offset
if length > 0 {
n = copy(b, t.buf[t.offset:])
t.offset += n
return
}
n, err = t.Conn.Read(b)
if t.Tee {
t.buf = append(t.buf, b[:n]...)
t.offset += n
}
return n, err
}
func ParseAddr(conn net.Conn) (teeConn *TeeConn, addr string, err error) {
teeConn = &TeeConn{Conn: conn, Tee: true}
defer func() {
teeConn.Reset()
teeConn.Tee = false
}()
buf := make([]byte, 1)
if n, err := teeConn.Read(buf); err != nil || n != 1 {
return teeConn, "", fmt.Errorf("Read conn fail: %v, readed: %d %v", err, n, buf)
}
teeConn.Reset()
// https
if buf[0] == 0x16 { // SSL handleshake
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
}
// http
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
}
+29 -58
View File
@@ -1,77 +1,48 @@
package proxy
import (
"crypto/tls"
"net"
"time"
"github.com/golang/glog"
"github.com/lucas-clemente/quic-go"
"github.com/wweir/sower/proxy/kcp"
"github.com/wweir/sower/proxy/quic"
"github.com/wweir/sower/proxy/tcp"
"github.com/wweir/sower/shadow"
)
func StartClient(server string) {
connCh := listenLocal([]string{":80", ":443"})
reDialCh := make(chan net.Conn, 10)
var conn net.Conn
for {
sess, err := quic.DialAddr(server, &tls.Config{InsecureSkipVerify: true}, nil)
if err != nil {
if sess, err = quic.DialAddr(server, &tls.Config{InsecureSkipVerify: true}, nil); err != nil {
glog.Errorf("connect to remote(%s) fail:%s\n", server, err)
time.Sleep(2 * time.Second)
continue
}
}
glog.Infoln("new session to", sess.RemoteAddr())
for { // session rotate logic
select {
case conn = <-connCh:
case conn = <-reDialCh:
}
// sync action to reuse sigle sess
if !openStream(conn, sess, reDialCh) {
sess.Close()
break
}
}
}
type Client interface {
Dial(server string) (net.Conn, error)
}
func openStream(conn net.Conn, sess quic.Session, reDialCh chan<- net.Conn) bool {
glog.V(1).Infoln("new request from", conn.RemoteAddr())
func StartClient(netType, server, password string) {
var connCh = listenLocal([]string{":80", ":443"})
var client Client
switch netType {
case QUIC.String():
client = quic.NewClient()
case KCP.String():
client = kcp.NewClient()
case TCP.String():
client = tcp.NewClient()
}
glog.Infoln("Client started.")
okCh := make(chan struct{})
go func() {
stream, err := sess.OpenStream()
for {
conn := <-connCh
glog.V(1).Infof("new conn from (%s) to (%s)", conn.RemoteAddr(), server)
rc, err := client.Dial(server)
if err != nil {
glog.Warningf("connect to remote(%s) fail:%s\n", sess.RemoteAddr(), err)
reDialCh <- conn
close(okCh)
return
conn.Close()
glog.Errorln(err)
continue
}
defer stream.Close()
select {
case okCh <- struct{}{}:
default:
close(okCh)
return
if rc, err = shadow.Shadow(rc, password); err != nil {
glog.Fatalln(err)
}
close(okCh)
conn.(*net.TCPConn).SetKeepAlive(true)
relay(&streamConn{stream, sess}, conn)
conn.Close()
}()
select {
case _, ok := <-okCh: // false means close on error
return ok
case <-time.After(time.Second):
return false
go relay(conn, rc)
}
}
+65
View File
@@ -0,0 +1,65 @@
package kcp
import (
"net"
"github.com/pkg/errors"
kcp "github.com/xtaci/kcp-go"
)
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
}
func NewClient() *client {
return &client{
DataShard: 10,
ParityShard: 3,
DSCP: 0,
SockBuf: 4194304,
NoDelay: 0,
Interval: 50,
Resend: 0,
NoCongestion: 0,
SndWnd: 0,
RcvWnd: 0,
MTU: 1350,
}
}
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
}
+56
View File
@@ -0,0 +1,56 @@
package kcp
import (
"net"
"github.com/golang/glog"
"github.com/pkg/errors"
kcp "github.com/xtaci/kcp-go"
)
type server struct {
DataShard int
ParityShard int
DSCP int
SockBuf int
}
func NewServer(password string) *server {
return &server{
DataShard: 10,
ParityShard: 3,
DSCP: 0,
SockBuf: 4194304,
}
}
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
}
+16
View File
@@ -0,0 +1,16 @@
// Code generated by "stringer -type=netType util.go"; DO NOT EDIT.
package proxy
import "strconv"
const _netType_name = "QUICKCPTCP"
var _netType_index = [...]uint8{0, 4, 7, 10}
func (i netType) String() string {
if i < 0 || i >= netType(len(_netType_index)-1) {
return "netType(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _netType_name[_netType_index[i]:_netType_index[i+1]]
}
+50
View File
@@ -0,0 +1,50 @@
package quic
import (
"crypto/tls"
"net"
"time"
quic "github.com/lucas-clemente/quic-go"
"github.com/pkg/errors"
)
type client struct {
conf *quic.Config
sess quic.Session
}
func NewClient() *client {
return &client{
// conf: &quic.Config{
// HandshakeTimeout: 5 * time.Second,
// MaxIncomingStreams: 1024,
// KeepAlive: true,
// },
}
}
func (c *client) Dial(server string) (net.Conn, error) {
if c.sess == nil {
if sess, err := quic.DialAddr(server, &tls.Config{InsecureSkipVerify: true}, c.conf); err != nil {
return nil, errors.Wrap(err, "session")
} else {
c.sess = sess
}
}
var stream quic.Stream
if err := 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
}
+57
View File
@@ -0,0 +1,57 @@
package quic
import (
"net"
"github.com/golang/glog"
quic "github.com/lucas-clemente/quic-go"
"github.com/pkg/errors"
)
type server struct {
conf *quic.Config
}
func NewServer() *server {
return &server{
// conf: &quic.Config{
// HandshakeTimeout: 5 * time.Second,
// MaxIncomingStreams: 1024,
// KeepAlive: true,
// },
}
}
func (s *server) Listen(port string) (<-chan net.Conn, error) {
ln, err := quic.ListenAddr(port, mockTlsPem(), s.conf)
if err != nil {
return nil, errors.WithStack(err)
}
connCh := make(chan net.Conn)
go func() {
for {
sess, err := ln.Accept()
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()
if err != nil {
glog.Errorln(err)
return
}
connCh <- &streamConn{stream, sess}
}
}
+67
View File
@@ -0,0 +1,67 @@
package quic
import (
"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"
)
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 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())
}
}
+42 -32
View File
@@ -2,61 +2,71 @@ package proxy
import (
"net"
"strings"
"github.com/golang/glog"
"github.com/lucas-clemente/quic-go"
"github.com/wweir/sower/parser"
"github.com/wweir/sower/parse"
"github.com/wweir/sower/proxy/kcp"
"github.com/wweir/sower/proxy/quic"
"github.com/wweir/sower/proxy/tcp"
"github.com/wweir/sower/shadow"
)
func StartServer(port string) {
ln, err := quic.ListenAddr(":"+port, mockTlsPem(), nil)
type Server interface {
Listen(port string) (<-chan net.Conn, error)
}
func StartServer(netType, port, password string) {
var server Server
switch netType {
case QUIC.String():
server = quic.NewServer()
case KCP.String():
server = kcp.NewServer(password)
case TCP.String():
server = tcp.NewServer()
}
if port == "" {
glog.Fatalln("port must set")
}
if !strings.Contains(port, ":") {
port = ":" + port
}
connCh, err := server.Listen(port)
if err != nil {
glog.Fatalln(err)
glog.Fatalf("listen %v fail: %s", port, err)
}
glog.Infoln("Server started.")
for {
sess, err := ln.Accept()
conn := <-connCh
conn, err := shadow.Shadow(conn, password)
if err != nil {
glog.Errorln(err)
continue
glog.Fatalln(err)
}
go acceptSession(sess)
go handle(conn)
}
}
func acceptSession(sess quic.Session) {
glog.V(1).Infoln("new session from ", sess.RemoteAddr())
defer sess.Close()
for {
stream, err := sess.AcceptStream()
if err != nil {
glog.Errorln(err)
return
}
go acceptStream(stream, sess)
}
}
func acceptStream(stream quic.Stream, sess quic.Session) {
glog.V(1).Infoln("new stream from ", sess.RemoteAddr())
defer stream.Close()
conn, addr, err := parser.ParseAddr(&streamConn{stream, sess})
func handle(conn net.Conn) {
conn, addr, err := parse.ParseAddr(conn)
if err != nil {
glog.Warningln(err)
return
}
glog.V(1).Infoln(addr)
glog.V(1).Infof("new conn from %s to %s", conn.RemoteAddr(), addr)
rc, err := net.Dial("tcp", addr)
if err != nil {
glog.Warningln(err)
return
}
defer rc.Close()
rc.(*net.TCPConn).SetKeepAlive(true)
if err := rc.(*net.TCPConn).SetKeepAlive(true); err != nil {
glog.Warningln(err)
}
relay(rc, conn)
}
+14
View File
@@ -0,0 +1,14 @@
package tcp
import "net"
type client struct {
}
func NewClient() *client {
return &client{}
}
func (c *client) Dial(server string) (net.Conn, error) {
return net.Dial("tcp", server)
}
+34
View File
@@ -0,0 +1,34 @@
package tcp
import (
"net"
"github.com/golang/glog"
)
type server struct {
}
func NewServer() *server {
return &server{}
}
func (s *server) Listen(port string) (<-chan net.Conn, error) {
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)
}
connCh <- conn
}
}()
return connCh, nil
}
+21 -49
View File
@@ -1,74 +1,46 @@
package proxy
import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"io"
"math/big"
"net"
"sync"
"sync/atomic"
"time"
"github.com/golang/glog"
"github.com/lucas-clemente/quic-go"
)
type streamConn struct {
quic.Stream
sess quic.Session
}
//go:generate stringer -type=netType $GOFILE
type netType int
func (s *streamConn) LocalAddr() net.Addr {
return s.sess.LocalAddr()
}
func (s *streamConn) RemoteAddr() net.Addr {
return s.sess.RemoteAddr()
}
const (
QUIC netType = iota
KCP
TCP
)
func relay(conn1, conn2 net.Conn) {
wg := &sync.WaitGroup{}
exitFlag := new(int32)
wg.Add(2)
go redirect(conn1, conn2, wg, exitFlag)
redirect(conn2, conn1, wg, exitFlag)
go redirect(conn2, conn1, wg, exitFlag)
redirect(conn1, conn2, wg, exitFlag)
wg.Wait()
}
func redirect(conn1, conn2 net.Conn, wg *sync.WaitGroup, exitFlag *int32) {
if _, err := io.Copy(conn2, conn1); err != nil && (atomic.LoadInt32(exitFlag) == 0) {
glog.V(1).Infof("%s<>%s -> %s<>%s: %s", conn1.RemoteAddr(), conn1.LocalAddr(), conn2.LocalAddr(), conn2.RemoteAddr(), err)
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) {
now := time.Now()
src.SetReadDeadline(now)
dst.SetWriteDeadline(now)
} else {
src.Close()
dst.Close()
}
// wakeup all conn goroutine
atomic.AddInt32(exitFlag, 1)
now := time.Now()
conn1.SetDeadline(now)
conn2.SetDeadline(now)
wg.Done()
}
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}}
}
+6
View File
@@ -0,0 +1,6 @@
// transter conn to be a crypto conn
// stream mode, blocksize is 1350, for mtu
// data [0 ... 1332]
// block [size][2 ... 1350]
// payload size + data size + aead overhead => (2+1332+16)
package shadow
+119
View File
@@ -0,0 +1,119 @@
package shadow
import (
"crypto/aes"
"crypto/cipher"
"encoding/binary"
"io"
"math/rand"
"net"
"github.com/pkg/errors"
)
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&0xFF00)>>8), byte(dataSize&0xFF)
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 len(b), err
}
func Shadow(c net.Conn, password string) (net.Conn, error) {
block, err := aes.NewCipher([]byte(password + password)[:16])
if err != nil {
return nil, errors.Wrap(err, "password too short")
}
aead, err := cipher.NewGCM(block)
if err != nil {
return nil, errors.Wrap(err, "GCM")
}
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,
}, nil
}
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
}
}