mirror of
https://github.com/wweir/sower.git
synced 2026-10-20 00:15:34 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b050e312b2 | ||
|
|
d78a757200 | ||
|
|
b2282a58a2 | ||
|
|
0fb2271cac | ||
|
|
a8089af235 | ||
|
|
6e4a6c794e | ||
|
|
1d620b8191 | ||
|
|
9586e7bc8e | ||
|
|
ec4286e6d4 | ||
|
|
c01f217939 | ||
|
|
c62e3a3284 | ||
|
|
578d9162aa | ||
|
|
002ac49e65 | ||
|
|
ee47f02cb5 | ||
|
|
b1127e8f9e | ||
|
|
282969b696 | ||
|
|
2daaa8f3ef | ||
|
|
d6776345a0 | ||
|
|
0e03605c37 | ||
|
|
9ca58982b7 | ||
|
|
60452da6b1 | ||
|
|
049e805b4a | ||
|
|
b83f202bad | ||
|
|
cb88395e66 | ||
|
|
d8d5d4ea29 | ||
|
|
fe0ccdfec8 | ||
|
|
1c45fae006 |
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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]]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
@@ -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}}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user