diff --git a/Makefile b/Makefile index 7f267a9..11cdb37 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,9 @@ SERVER:=127.0.0.1:5533 default: build +generate: + go generate ./... + build: GOBIN=$(PWD) go install -v @@ -12,8 +15,8 @@ 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 & + $(PWD)/sower -n QUIC -logtostderr -v 1 & sudo $(PWD)/sower -f conf/sower.toml -logtostderr diff --git a/conf/conf.go b/conf/conf.go index cedccd0..7ec2407 100644 --- a/conf/conf.go +++ b/conf/conf.go @@ -15,20 +15,25 @@ 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:"-"` - ClearDnsCache string `toml:"clear_dns_cache"` - BlockList []string `toml:"blocklist"` - Suggestions []string `toml:"suggestions"` - Verbose int `toml:"verbose"` + ConfigFile string + NetType string `toml:"net_type"` + + 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)") 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") diff --git a/conf/sower.toml b/conf/sower.toml index 5364f42..cd5bfa0 100644 --- a/conf/sower.toml +++ b/conf/sower.toml @@ -1,3 +1,4 @@ +net_type="QUIC" server_port="5533" server_addr="remote-server:5533" # replce it to remote server dns_server="114.114.114.114" @@ -54,6 +55,7 @@ blocklist=[ "e16991.b.akamaiedge.net", "*.web.telegram.org", # telegram "core.telegram.org", + "telegram.org", "telegram.me", "t.me", "*.slack.com", #slack @@ -75,5 +77,6 @@ blocklist=[ "*.qualtrics.com", "*.starfieldtech.com", "*.typekit.net", + "www.v2ray.com", ] verbose=0 diff --git a/go.mod b/go.mod index 9aa3239..39dbf94 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( 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/wweir/fsnotify v1.4.8 golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 // indirect golang.org/x/net v0.0.0-20181201002055-351d144fa1fc // indirect diff --git a/go.sum b/go.sum index 3e98a5c..6066654 100644 --- a/go.sum +++ b/go.sum @@ -29,6 +29,8 @@ 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/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-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= diff --git a/main.go b/main.go index 1e91fd6..24d4513 100644 --- a/main.go +++ b/main.go @@ -11,9 +11,9 @@ func main() { glog.Infoln("Starting:", conf.Conf) if conf.Conf.ServerAddr == "" { - proxy.StartServer(conf.Conf.ServerPort) + proxy.StartServer(conf.Conf.NetType, conf.Conf.ServerPort) } else { go dns.StartDNS(conf.Conf.DnsServer) - proxy.StartClient(conf.Conf.ServerAddr) + proxy.StartClient(conf.Conf.NetType, conf.Conf.ServerAddr) } } diff --git a/parser/addr.go b/parse/addr.go similarity index 61% rename from parser/addr.go rename to parse/addr.go index fbc39a5..02ed7c4 100644 --- a/parser/addr.go +++ b/parse/addr.go @@ -9,46 +9,17 @@ import ( "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 - }() + teeConn = &TeeConn{Conn: conn} + teeConn.StartOrReset() + defer teeConn.Stop() 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() + teeConn.StartOrReset() // https if buf[0] == 0x16 { // SSL handleshake diff --git a/parser/sni.go b/parse/sni.go similarity index 100% rename from parser/sni.go rename to parse/sni.go diff --git a/parse/tee_conn.go b/parse/tee_conn.go new file mode 100644 index 0000000..518899e --- /dev/null +++ b/parse/tee_conn.go @@ -0,0 +1,35 @@ +package parser + +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 +} diff --git a/proxy/client.go b/proxy/client.go index e80a41a..e8ba29f 100644 --- a/proxy/client.go +++ b/proxy/client.go @@ -1,85 +1,37 @@ package proxy import ( - "crypto/tls" "net" - "time" "github.com/golang/glog" - "github.com/lucas-clemente/quic-go" + "github.com/wweir/sower/proxy/quic" ) -func StartClient(server string) { - connCh := listenLocal([]string{":80", ":443"}) - reDialCh := make(chan net.Conn, 10) - var conn net.Conn - var count int - - for { - sess, err := quic.DialAddr(server, &tls.Config{InsecureSkipVerify: true}, dialConf) - if err != nil { - if sess, err = quic.DialAddr(server, &tls.Config{InsecureSkipVerify: true}, dialConf); err != nil { - glog.Errorf("connect to remote(%s) fail:%s\n", server, err) - time.Sleep(2 * time.Second) - continue - } - } - glog.Infof("new session from (%s) to (%s)", sess.LocalAddr(), sess.RemoteAddr()) - - count = 0 - for { // session rotate logic - select { - case conn = <-connCh: - case conn = <-reDialCh: - } - count++ - - // sync action to reuse sigle sess - if !openStream(conn, sess, count, reDialCh) { - sess.Close() - break - } - } - } +type Client interface { + Dial(server string) (net.Conn, error) } -func openStream(conn net.Conn, sess quic.Session, count int, reDialCh chan<- net.Conn) bool { - glog.V(2).Infoln("new request from", conn.RemoteAddr()) +func StartClient(netType, server string) { + var connCh = listenLocal([]string{":80", ":443"}) + var client Client + switch netType { + case QUIC.String(): + client = quic.NewClient() + case KCP.String(): + } - okCh := make(chan struct{}) - go func() { - stream, err := sess.OpenStream() + for { + conn := <-connCh + glog.V(1).Infof("new conn from (%s)", conn.RemoteAddr()) + + rc, err := client.Dial(server) if err != nil { - glog.Warningf("start stream to (%s) fail:%s\n", sess.RemoteAddr(), err) - reDialCh <- conn - close(okCh) - return + conn.Close() + glog.Errorln(err) + continue } - defer stream.Close() - glog.V(2).Infof("START stream\t%d", count) - defer glog.V(2).Infof("CLOSE stream\t%d", count) - - select { - case okCh <- struct{}{}: - default: - close(okCh) - return - } - close(okCh) - - if err := conn.(*net.TCPConn).SetKeepAlive(true); err != nil { - glog.Warningln(err) - } - relay(sess, &streamConn{stream, sess}, conn) - conn.Close() - }() - - select { - case _, ok := <-okCh: // false means close on error - return ok - case <-time.After(500 * time.Millisecond): - return false + go relay(conn, rc) } } diff --git a/proxy/nettype_string.go b/proxy/nettype_string.go new file mode 100644 index 0000000..7641bf5 --- /dev/null +++ b/proxy/nettype_string.go @@ -0,0 +1,16 @@ +// Code generated by "stringer -type=netType util.go"; DO NOT EDIT. + +package proxy + +import "strconv" + +const _netType_name = "QUICKCP" + +var _netType_index = [...]uint8{0, 4, 7} + +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]] +} diff --git a/proxy/quic/client.go b/proxy/quic/client.go new file mode 100644 index 0000000..db740bf --- /dev/null +++ b/proxy/quic/client.go @@ -0,0 +1,51 @@ +package quic + +import ( + "crypto/tls" + "net" + "time" + + quic "github.com/lucas-clemente/quic-go" + "github.com/pkg/errors" +) + +type client struct { + server string + 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(c.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 +} diff --git a/proxy/quic/server.go b/proxy/quic/server.go new file mode 100644 index 0000000..198d526 --- /dev/null +++ b/proxy/quic/server.go @@ -0,0 +1,59 @@ +package quic + +import ( + "net" + "time" + + "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.Errorln(err) + continue + } + 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} + } +} diff --git a/proxy/quic/util.go b/proxy/quic/util.go new file mode 100644 index 0000000..ce867d1 --- /dev/null +++ b/proxy/quic/util.go @@ -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()) + } +} diff --git a/proxy/server.go b/proxy/server.go index bb13861..58b6e5f 100644 --- a/proxy/server.go +++ b/proxy/server.go @@ -2,53 +2,51 @@ 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/quic" ) -func StartServer(port string) { - ln, err := quic.ListenAddr(":"+port, mockTlsPem(), dialConf) +type Server interface { + Listen(port string) (<-chan net.Conn, error) +} + +func StartServer(netType, port string) { + var server Server + switch netType { + case QUIC.String(): + server = quic.NewServer() + case KCP.String(): + } + + 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) } for { - sess, err := ln.Accept() - if err != nil { - glog.Errorln(err) - continue - } - go acceptSession(sess) + conn := <-connCh + go handle(conn) } } -func acceptSession(sess quic.Session) { - glog.V(1).Infoln("new session from ", sess.RemoteAddr()) - defer sess.Close() +func handle(conn net.Conn) { + defer conn.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}) + conn, addr, err := parser.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 { @@ -59,5 +57,5 @@ func acceptStream(stream quic.Stream, sess quic.Session) { if err := rc.(*net.TCPConn).SetKeepAlive(true); err != nil { glog.Warningln(err) } - relay(sess, rc, conn) + relay(rc, conn) } diff --git a/proxy/util.go b/proxy/util.go index ebb783b..3cc389d 100644 --- a/proxy/util.go +++ b/proxy/util.go @@ -1,58 +1,35 @@ package proxy import ( - "crypto/rand" - "crypto/rsa" - "crypto/tls" - "crypto/x509" - "encoding/pem" "io" - "math/big" "net" - "strings" "sync" "sync/atomic" "time" "github.com/golang/glog" - "github.com/lucas-clemente/quic-go" ) -var dialConf = &quic.Config{ - HandshakeTimeout: 5 * time.Second, - MaxIncomingStreams: 1024, - KeepAlive: true, -} +//go:generate stringer -type=netType +type netType int -type streamConn struct { - quic.Stream - sess quic.Session -} +const ( + QUIC netType = iota + KCP +) -func (s *streamConn) LocalAddr() net.Addr { - return s.sess.LocalAddr() -} - -func (s *streamConn) RemoteAddr() net.Addr { - return s.sess.RemoteAddr() -} - -func relay(sess quic.Session, conn1, conn2 net.Conn) { +func relay(conn1, conn2 net.Conn) { wg := &sync.WaitGroup{} exitFlag := new(int32) wg.Add(2) - go redirect(sess, conn1, conn2, wg, exitFlag) - redirect(sess, conn2, conn1, wg, exitFlag) + go redirect(conn1, conn2, wg, exitFlag) + redirect(conn2, conn1, wg, exitFlag) wg.Wait() } -func redirect(sess quic.Session, conn1, conn2 net.Conn, wg *sync.WaitGroup, exitFlag *int32) { +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) - - if strings.Contains(err.Error(), "PeerGoingAway") { //for internal package, hard code here - sess.Close() - } } // wakeup all conn goroutine @@ -62,24 +39,3 @@ func redirect(sess quic.Session, conn1, conn2 net.Conn, wg *sync.WaitGroup, exit 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}} -}