From 333291e9bc766a76d6df5243a7022c5f028be17c Mon Sep 17 00:00:00 2001 From: "rui.zheng" Date: Sun, 5 Feb 2017 14:35:38 +0800 Subject: [PATCH] #76 support pure HTTP tunnel(PHT) --- chain.go | 13 +- .../vendor/github.com/ginuerzh/gost/chain.go | 13 +- .../vendor/github.com/ginuerzh/gost/http.go | 31 ++- .../vendor/github.com/ginuerzh/gost/node.go | 2 +- .../vendor/github.com/ginuerzh/gost/server.go | 4 +- .../vendor/github.com/ginuerzh/pht/LICENSE | 21 ++ .../vendor/github.com/ginuerzh/pht/README.md | 2 + .../vendor/github.com/ginuerzh/pht/client.go | 162 ++++++++++++++ .../vendor/github.com/ginuerzh/pht/conn.go | 133 ++++++++++++ .../vendor/github.com/ginuerzh/pht/server.go | 199 ++++++++++++++++++ .../vendor/github.com/ginuerzh/pht/session.go | 80 +++++++ cmd/gost/vendor/vendor.json | 12 +- http.go | 31 ++- node.go | 2 +- server.go | 4 +- 15 files changed, 694 insertions(+), 15 deletions(-) create mode 100644 cmd/gost/vendor/github.com/ginuerzh/pht/LICENSE create mode 100644 cmd/gost/vendor/github.com/ginuerzh/pht/README.md create mode 100644 cmd/gost/vendor/github.com/ginuerzh/pht/client.go create mode 100644 cmd/gost/vendor/github.com/ginuerzh/pht/conn.go create mode 100644 cmd/gost/vendor/github.com/ginuerzh/pht/server.go create mode 100644 cmd/gost/vendor/github.com/ginuerzh/pht/session.go diff --git a/chain.go b/chain.go index 9178067..ae57704 100644 --- a/chain.go +++ b/chain.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "encoding/base64" "errors" + "github.com/ginuerzh/pht" "github.com/golang/glog" "golang.org/x/net/http2" "io" @@ -29,6 +30,7 @@ type ProxyChain struct { kcpConfig *KCPConfig kcpSession *KCPSession kcpMutex sync.Mutex + phttpClient *pht.Client } func NewProxyChain(nodes ...ProxyNode) *ProxyChain { @@ -96,8 +98,8 @@ func (c *ProxyChain) Init() { } for i, node := range c.nodes { - if node.Transport == "kcp" && i > 0 { - glog.Fatal("KCP must be the first node in the proxy chain") + if (node.Transport == "kcp" || node.Transport == "pht" || node.Transport == "quic") && i > 0 { + glog.Fatal("KCP/PHT/QUIC must be the first node in the proxy chain") } } @@ -118,6 +120,11 @@ func (c *ProxyChain) Init() { c.kcpConfig = config return } + + if c.nodes[0].Transport == "pht" { + glog.V(LINFO).Infoln("Pure HTTP mode is enabled") + c.phttpClient = pht.NewClient(c.nodes[0].Addr, c.nodes[0].Get("key")) + } } func (c *ProxyChain) KCPEnabled() bool { @@ -269,6 +276,8 @@ func (c *ProxyChain) travelNodes(withHttp2 bool, nodes ...ProxyNode) (conn *Prox cc, err = c.http2Connect(node.Addr) } else if node.Transport == "kcp" { cc, err = c.getKCPConn() + } else if node.Transport == "pht" { + cc, err = c.phttpClient.Dial() } else { cc, err = net.DialTimeout("tcp", node.Addr, DialTimeout) } diff --git a/cmd/gost/vendor/github.com/ginuerzh/gost/chain.go b/cmd/gost/vendor/github.com/ginuerzh/gost/chain.go index 9178067..ae57704 100644 --- a/cmd/gost/vendor/github.com/ginuerzh/gost/chain.go +++ b/cmd/gost/vendor/github.com/ginuerzh/gost/chain.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "encoding/base64" "errors" + "github.com/ginuerzh/pht" "github.com/golang/glog" "golang.org/x/net/http2" "io" @@ -29,6 +30,7 @@ type ProxyChain struct { kcpConfig *KCPConfig kcpSession *KCPSession kcpMutex sync.Mutex + phttpClient *pht.Client } func NewProxyChain(nodes ...ProxyNode) *ProxyChain { @@ -96,8 +98,8 @@ func (c *ProxyChain) Init() { } for i, node := range c.nodes { - if node.Transport == "kcp" && i > 0 { - glog.Fatal("KCP must be the first node in the proxy chain") + if (node.Transport == "kcp" || node.Transport == "pht" || node.Transport == "quic") && i > 0 { + glog.Fatal("KCP/PHT/QUIC must be the first node in the proxy chain") } } @@ -118,6 +120,11 @@ func (c *ProxyChain) Init() { c.kcpConfig = config return } + + if c.nodes[0].Transport == "pht" { + glog.V(LINFO).Infoln("Pure HTTP mode is enabled") + c.phttpClient = pht.NewClient(c.nodes[0].Addr, c.nodes[0].Get("key")) + } } func (c *ProxyChain) KCPEnabled() bool { @@ -269,6 +276,8 @@ func (c *ProxyChain) travelNodes(withHttp2 bool, nodes ...ProxyNode) (conn *Prox cc, err = c.http2Connect(node.Addr) } else if node.Transport == "kcp" { cc, err = c.getKCPConn() + } else if node.Transport == "pht" { + cc, err = c.phttpClient.Dial() } else { cc, err = net.DialTimeout("tcp", node.Addr, DialTimeout) } diff --git a/cmd/gost/vendor/github.com/ginuerzh/gost/http.go b/cmd/gost/vendor/github.com/ginuerzh/gost/http.go index b9f7e4e..c710c48 100644 --- a/cmd/gost/vendor/github.com/ginuerzh/gost/http.go +++ b/cmd/gost/vendor/github.com/ginuerzh/gost/http.go @@ -4,14 +4,14 @@ import ( "bufio" "crypto/tls" "encoding/base64" + "errors" + "github.com/ginuerzh/pht" "github.com/golang/glog" "golang.org/x/net/http2" "io" "net" "net/http" "net/http/httputil" - //"strings" - "errors" "time" ) @@ -383,3 +383,30 @@ func (fw flushWriter) Write(p []byte) (n int, err error) { } return } + +type PureHttpServer struct { + Base *ProxyServer + Handler func(net.Conn) +} + +func NewPureHttpServer(base *ProxyServer) *PureHttpServer { + return &PureHttpServer{ + Base: base, + } +} + +func (s *PureHttpServer) ListenAndServe() error { + server := pht.Server{ + Addr: s.Base.Node.Addr, + Key: s.Base.Node.Get("key"), + } + if server.Handler == nil { + server.Handler = s.handleConn + } + return server.ListenAndServe() +} + +func (s *PureHttpServer) handleConn(conn net.Conn) { + glog.V(LINFO).Infof("[pht] %s - %s", conn.RemoteAddr(), conn.LocalAddr()) + s.Base.handleConn(conn) +} diff --git a/cmd/gost/vendor/github.com/ginuerzh/gost/node.go b/cmd/gost/vendor/github.com/ginuerzh/gost/node.go index e72d0bb..4f8b020 100644 --- a/cmd/gost/vendor/github.com/ginuerzh/gost/node.go +++ b/cmd/gost/vendor/github.com/ginuerzh/gost/node.go @@ -71,7 +71,7 @@ func ParseProxyNode(s string) (node ProxyNode, err error) { } switch node.Transport { - case "ws", "wss", "tls", "http2", "quic", "kcp", "redirect", "ssu": + case "ws", "wss", "tls", "http2", "quic", "kcp", "redirect", "ssu", "pht": case "https": node.Protocol = "http" node.Transport = "tls" diff --git a/cmd/gost/vendor/github.com/ginuerzh/gost/server.go b/cmd/gost/vendor/github.com/ginuerzh/gost/server.go index 63ff9a0..4bafc91 100644 --- a/cmd/gost/vendor/github.com/ginuerzh/gost/server.go +++ b/cmd/gost/vendor/github.com/ginuerzh/gost/server.go @@ -122,6 +122,8 @@ func (s *ProxyServer) Serve() error { ttl = DefaultTTL } return NewShadowUdpServer(s, ttl).ListenAndServe() + case "pht": // pure http tunnel + return NewPureHttpServer(s).ListenAndServe() default: ln, err = net.Listen("tcp", node.Addr) } @@ -238,7 +240,7 @@ func (_ *ProxyServer) transport(conn1, conn2 net.Conn) (err error) { select { case err = <-errc: - //glog.V(LWARNING).Infoln("transport exit", err) + // glog.V(LWARNING).Infoln("transport exit", err) } return diff --git a/cmd/gost/vendor/github.com/ginuerzh/pht/LICENSE b/cmd/gost/vendor/github.com/ginuerzh/pht/LICENSE new file mode 100644 index 0000000..de5e3f4 --- /dev/null +++ b/cmd/gost/vendor/github.com/ginuerzh/pht/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 ginuerzh + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/cmd/gost/vendor/github.com/ginuerzh/pht/README.md b/cmd/gost/vendor/github.com/ginuerzh/pht/README.md new file mode 100644 index 0000000..c787647 --- /dev/null +++ b/cmd/gost/vendor/github.com/ginuerzh/pht/README.md @@ -0,0 +1,2 @@ +# pht +Pure HTTP Tunnel - Tunnel over HTTP using only GET and POST requests. diff --git a/cmd/gost/vendor/github.com/ginuerzh/pht/client.go b/cmd/gost/vendor/github.com/ginuerzh/pht/client.go new file mode 100644 index 0000000..dd09e91 --- /dev/null +++ b/cmd/gost/vendor/github.com/ginuerzh/pht/client.go @@ -0,0 +1,162 @@ +package pht + +import ( + "bufio" + "bytes" + "encoding/base64" + "errors" + "fmt" + "io/ioutil" + "net" + "net/http" + "strings" + "time" +) + +type Client struct { + Host string + Key string + httpClient *http.Client + manager *sessionManager +} + +func NewClient(host, key string) *Client { + return &Client{ + Host: host, + Key: key, + httpClient: &http.Client{}, + manager: newSessionManager(), + } +} + +func (c *Client) Dial() (net.Conn, error) { + r, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s%s", c.Host, tokenURI), nil) + if err != nil { + return nil, err + } + r.Header.Set("Authorization", "key="+c.Key) + resp, err := c.httpClient.Do(r) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, errors.New(resp.Status) + } + + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + token := strings.TrimPrefix(string(data), "token=") + if token == "" { + return nil, errors.New("invalid token") + } + + session := newSession(0, 0) + c.manager.SetSession(token, session) + + go c.sendDataLoop(token) + go c.recvDataLoop(token) + + return newConn(session), nil +} + +func (c *Client) sendDataLoop(token string) error { + session := c.manager.GetSession(token) + if session == nil { + return errors.New("invalid token") + } + + for { + select { + case b, ok := <-session.wchan: + var data string + if len(b) > 0 { + data = base64.StdEncoding.EncodeToString(b) + } + r, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s%s", c.Host, pushURI), bytes.NewBufferString(data+"\n")) + if err != nil { + return err + } + r.Header.Set("Authorization", fmt.Sprintf("key=%s; token=%s", c.Key, token)) + if !ok { + c.manager.DelSession(token) + resp, err := c.httpClient.Do(r) + if err != nil { // TODO: retry + return err + } + resp.Body.Close() + return nil // session is closed + } + + resp, err := c.httpClient.Do(r) + if err != nil { // TODO: retry + return err + } + resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return errors.New(resp.Status) + } + } + } +} + +func (c *Client) recvDataLoop(token string) error { + session := c.manager.GetSession(token) + if session == nil { + return errors.New("invalid token") + } + + for { + err := c.recvData(token, session) + if err != nil { + close(session.rchan) + c.manager.DelSession(token) + return err + } + } +} + +func (c *Client) recvData(token string, s *session) error { + r, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s%s", c.Host, pollURI), nil) + if err != nil { + return err + } + r.Header.Set("Authorization", fmt.Sprintf("key=%s; token=%s", c.Key, token)) + resp, err := c.httpClient.Do(r) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return errors.New(resp.Status) + } + + scanner := bufio.NewScanner(resp.Body) + for scanner.Scan() { + select { + case <-s.closed: + return errors.New("session closed") + default: + } + + b, err := base64.StdEncoding.DecodeString(scanner.Text()) + if err != nil { + return err + } + select { + case s.rchan <- b: + case <-s.closed: + return errors.New("session closed") + case <-time.After(time.Second * 90): + return errors.New("timeout") + } + + if err := scanner.Err(); err != nil { + return err + } + } + return nil +} diff --git a/cmd/gost/vendor/github.com/ginuerzh/pht/conn.go b/cmd/gost/vendor/github.com/ginuerzh/pht/conn.go new file mode 100644 index 0000000..3dd7f1e --- /dev/null +++ b/cmd/gost/vendor/github.com/ginuerzh/pht/conn.go @@ -0,0 +1,133 @@ +package pht + +import ( + "errors" + "io" + "net" + "time" +) + +type conn struct { + session *session + rb []byte // read buffer + remoteAddr net.Addr + localAddr net.Addr + rTimer, wTimer *time.Timer + closed chan interface{} +} + +func newConn(session *session) *conn { + conn := &conn{ + session: session, + rTimer: time.NewTimer(time.Hour * 65535), + wTimer: time.NewTimer(time.Hour * 65535), + closed: make(chan interface{}), + } + conn.rTimer.Stop() + conn.wTimer.Stop() + + return conn +} + +func (conn *conn) Read(b []byte) (n int, err error) { + select { + case <-conn.closed: + err = errors.New("read: use of closed network connection") + return + default: + } + + if len(conn.rb) > 0 { + n = copy(b, conn.rb) + conn.rb = conn.rb[n:] + return + } + + select { + case data, ok := <-conn.session.rchan: + if !ok { + err = io.EOF + return + } + n = copy(b, data) + conn.rb = data[n:] + case <-conn.rTimer.C: + err = errors.New("read timeout") + case <-conn.closed: + err = io.EOF + } + + return +} + +func (conn *conn) Write(b []byte) (n int, err error) { + select { + case <-conn.closed: + err = errors.New("write: use of closed network connection") + return + default: + } + + if len(b) == 0 { + return + } + + data := make([]byte, len(b)) + copy(data, b) + + select { + case conn.session.wchan <- data: + n = len(b) + case <-conn.wTimer.C: + err = errors.New("write timeout") + case <-conn.closed: + err = errors.New("connection is closed") + } + + return +} + +func (conn *conn) Close() error { + close(conn.closed) + close(conn.session.closed) + close(conn.session.wchan) + return nil +} + +func (conn *conn) LocalAddr() net.Addr { + return conn.localAddr +} + +func (conn *conn) RemoteAddr() net.Addr { + return conn.remoteAddr +} + +func (conn *conn) SetReadDeadline(t time.Time) error { + if t.IsZero() { + conn.rTimer.Stop() + return nil + } + conn.rTimer.Reset(t.Sub(time.Now())) + return nil +} + +func (conn *conn) SetWriteDeadline(t time.Time) error { + if t.IsZero() { + conn.wTimer.Stop() + return nil + } + conn.wTimer.Reset(t.Sub(time.Now())) + return nil +} + +func (conn *conn) SetDeadline(t time.Time) error { + if t.IsZero() { + conn.rTimer.Stop() + conn.wTimer.Stop() + return nil + } + d := t.Sub(time.Now()) + conn.rTimer.Reset(d) + conn.wTimer.Reset(d) + return nil +} diff --git a/cmd/gost/vendor/github.com/ginuerzh/pht/server.go b/cmd/gost/vendor/github.com/ginuerzh/pht/server.go new file mode 100644 index 0000000..8aa7960 --- /dev/null +++ b/cmd/gost/vendor/github.com/ginuerzh/pht/server.go @@ -0,0 +1,199 @@ +package pht + +import ( + "bufio" + "encoding/base64" + "fmt" + "net" + "net/http" + "strings" + "time" +) + +const ( + tokenURI = "/token" + pushURI = "/push" + pollURI = "/poll" +) + +type Server struct { + Addr string + Key string + Handler func(net.Conn) + manager *sessionManager +} + +func (s *Server) ListenAndServe() error { + s.manager = newSessionManager() + + mux := http.NewServeMux() + mux.Handle(tokenURI, http.HandlerFunc(s.tokenHandler)) + mux.Handle(pushURI, http.HandlerFunc(s.pushHandler)) + mux.Handle(pollURI, http.HandlerFunc(s.pollHandler)) + + return http.ListenAndServe(s.Addr, mux) +} + +func (s *Server) tokenHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + + m := parseAuth(r.Header.Get("Authorization")) + if m["key"] != s.Key { + w.WriteHeader(http.StatusForbidden) + return + } + + token, session, err := s.manager.NewSession(0, 0) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + conn, err := s.upgrade(session, r) + if err != nil { + s.manager.DelSession(token) + w.WriteHeader(http.StatusInternalServerError) + return + } + + if s.Handler != nil { + go s.Handler(conn) + } + + w.Write([]byte(fmt.Sprintf("token=%s", token))) +} + +func (s *Server) pushHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + + m := parseAuth(r.Header.Get("Authorization")) + if m["key"] != s.Key { + w.WriteHeader(http.StatusForbidden) + return + } + + token := m["token"] + session := s.manager.GetSession(token) + if session == nil { + w.WriteHeader(http.StatusUnauthorized) + return + } + + br := bufio.NewReader(r.Body) + data, err := br.ReadString('\n') + if err != nil { + s.manager.DelSession(token) + close(session.rchan) + w.WriteHeader(http.StatusInternalServerError) + return + } + + data = strings.TrimSuffix(data, "\n") + if len(data) == 0 { + s.manager.DelSession(token) + close(session.rchan) + return + } + + b, err := base64.StdEncoding.DecodeString(data) + if err != nil { + s.manager.DelSession(token) + close(session.rchan) + return + } + + select { + case <-session.closed: + s.manager.DelSession(token) + return + case session.rchan <- b: + w.WriteHeader(http.StatusOK) + case <-time.After(time.Second * 90): + s.manager.DelSession(token) + w.WriteHeader(http.StatusRequestTimeout) + } +} + +func (s *Server) pollHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + + m := parseAuth(r.Header.Get("Authorization")) + if m["key"] != s.Key { + w.WriteHeader(http.StatusForbidden) + return + } + + token := m["token"] + session := s.manager.GetSession(token) + if session == nil { + w.WriteHeader(http.StatusUnauthorized) + return + } + + w.WriteHeader(http.StatusOK) + if fw, ok := w.(http.Flusher); ok { + fw.Flush() + } + + for { + select { + case data, ok := <-session.wchan: + if !ok { + s.manager.DelSession(token) + return // session is closed + } + bw := bufio.NewWriter(w) + bw.WriteString(base64.StdEncoding.EncodeToString(data)) + bw.WriteString("\n") + if err := bw.Flush(); err != nil { + return + } + + if fw, ok := w.(http.Flusher); ok { + fw.Flush() + } + case <-time.After(time.Second * 25): + return + } + } +} + +func (s *Server) upgrade(sess *session, r *http.Request) (net.Conn, error) { + conn := newConn(sess) + raddr, err := net.ResolveTCPAddr("tcp", r.RemoteAddr) + if err != nil { + raddr = &net.TCPAddr{} + } + conn.remoteAddr = raddr + + laddr, err := net.ResolveTCPAddr("tcp", s.Addr) + if err != nil { + laddr = &net.TCPAddr{} + } + conn.localAddr = laddr + + return conn, nil +} + +func parseAuth(auth string) map[string]string { + mkv := make(map[string]string) + + for _, s := range strings.Split(auth, ";") { + n := strings.Index(s, "=") + if n < 0 { + continue + } + mkv[strings.TrimSpace(s[:n])] = strings.TrimSpace(s[n+1:]) + } + + return mkv +} diff --git a/cmd/gost/vendor/github.com/ginuerzh/pht/session.go b/cmd/gost/vendor/github.com/ginuerzh/pht/session.go new file mode 100644 index 0000000..99b3a9f --- /dev/null +++ b/cmd/gost/vendor/github.com/ginuerzh/pht/session.go @@ -0,0 +1,80 @@ +package pht + +import ( + "crypto/rand" + "encoding/hex" + "sync" +) + +const ( + defaultRChanLen = 64 + defaultWChanLen = 64 +) + +type session struct { + rchan chan []byte + wchan chan []byte + closed chan interface{} +} + +func newSession(rlen, wlen int) *session { + if rlen <= 0 { + rlen = defaultRChanLen + } + if wlen <= 0 { + wlen = defaultWChanLen + } + + return &session{ + rchan: make(chan []byte, rlen), + wchan: make(chan []byte, wlen), + closed: make(chan interface{}), + } +} + +type sessionManager struct { + sessions map[string]*session + mux sync.Mutex +} + +func newSessionManager() *sessionManager { + return &sessionManager{ + sessions: make(map[string]*session), + mux: sync.Mutex{}, + } +} + +func (m *sessionManager) NewSession(rlen, wlen int) (token string, s *session, err error) { + var nonce [16]byte + if _, err = rand.Read(nonce[:]); err != nil { + return + } + token = hex.EncodeToString(nonce[:]) + s = newSession(rlen, wlen) + + m.mux.Lock() + defer m.mux.Unlock() + m.sessions[token] = s + + return +} + +func (m *sessionManager) SetSession(token string, session *session) { + m.mux.Lock() + defer m.mux.Unlock() + m.sessions[token] = session +} + +func (m *sessionManager) GetSession(token string) *session { + m.mux.Lock() + defer m.mux.Unlock() + + return m.sessions[token] +} + +func (m *sessionManager) DelSession(token string) { + m.mux.Lock() + defer m.mux.Unlock() + + delete(m.sessions, token) +} diff --git a/cmd/gost/vendor/vendor.json b/cmd/gost/vendor/vendor.json index 9cb9f4d..aaf0b34 100644 --- a/cmd/gost/vendor/vendor.json +++ b/cmd/gost/vendor/vendor.json @@ -21,10 +21,16 @@ "revisionTime": "2017-01-19T05:34:58Z" }, { - "checksumSHA1": "VXFPGZtx+GKexkXeL3YljqJLHFk=", + "checksumSHA1": "ero0DQrGYph2eFDyEjDnOQV8NHo=", "path": "github.com/ginuerzh/gost", - "revision": "dc75931858cf96d95525faa22b7ffa0cea3e7d68", - "revisionTime": "2017-01-25T04:21:04Z" + "revision": "72d8a598d50f8517d922f233e8f8d37011fcb18f", + "revisionTime": "2017-01-25T04:23:26Z" + }, + { + "checksumSHA1": "+XIOnTW0rv8Kr/amkXgMraNeUr4=", + "path": "github.com/ginuerzh/pht", + "revision": "f90acb425616767b84789d10e50a6fb652cd1e9a", + "revisionTime": "2017-02-05T06:05:58Z" }, { "checksumSHA1": "URsJa4y/sUUw/STmbeYx9EKqaYE=", diff --git a/http.go b/http.go index b9f7e4e..c710c48 100644 --- a/http.go +++ b/http.go @@ -4,14 +4,14 @@ import ( "bufio" "crypto/tls" "encoding/base64" + "errors" + "github.com/ginuerzh/pht" "github.com/golang/glog" "golang.org/x/net/http2" "io" "net" "net/http" "net/http/httputil" - //"strings" - "errors" "time" ) @@ -383,3 +383,30 @@ func (fw flushWriter) Write(p []byte) (n int, err error) { } return } + +type PureHttpServer struct { + Base *ProxyServer + Handler func(net.Conn) +} + +func NewPureHttpServer(base *ProxyServer) *PureHttpServer { + return &PureHttpServer{ + Base: base, + } +} + +func (s *PureHttpServer) ListenAndServe() error { + server := pht.Server{ + Addr: s.Base.Node.Addr, + Key: s.Base.Node.Get("key"), + } + if server.Handler == nil { + server.Handler = s.handleConn + } + return server.ListenAndServe() +} + +func (s *PureHttpServer) handleConn(conn net.Conn) { + glog.V(LINFO).Infof("[pht] %s - %s", conn.RemoteAddr(), conn.LocalAddr()) + s.Base.handleConn(conn) +} diff --git a/node.go b/node.go index e72d0bb..4f8b020 100644 --- a/node.go +++ b/node.go @@ -71,7 +71,7 @@ func ParseProxyNode(s string) (node ProxyNode, err error) { } switch node.Transport { - case "ws", "wss", "tls", "http2", "quic", "kcp", "redirect", "ssu": + case "ws", "wss", "tls", "http2", "quic", "kcp", "redirect", "ssu", "pht": case "https": node.Protocol = "http" node.Transport = "tls" diff --git a/server.go b/server.go index 63ff9a0..4bafc91 100644 --- a/server.go +++ b/server.go @@ -122,6 +122,8 @@ func (s *ProxyServer) Serve() error { ttl = DefaultTTL } return NewShadowUdpServer(s, ttl).ListenAndServe() + case "pht": // pure http tunnel + return NewPureHttpServer(s).ListenAndServe() default: ln, err = net.Listen("tcp", node.Addr) } @@ -238,7 +240,7 @@ func (_ *ProxyServer) transport(conn1, conn2 net.Conn) (err error) { select { case err = <-errc: - //glog.V(LWARNING).Infoln("transport exit", err) + // glog.V(LWARNING).Infoln("transport exit", err) } return