#76 support pure HTTP tunnel(PHT)
This commit is contained in:
parent
72d8a598d5
commit
333291e9bc
13
chain.go
13
chain.go
@ -5,6 +5,7 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/ginuerzh/pht"
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
"io"
|
"io"
|
||||||
@ -29,6 +30,7 @@ type ProxyChain struct {
|
|||||||
kcpConfig *KCPConfig
|
kcpConfig *KCPConfig
|
||||||
kcpSession *KCPSession
|
kcpSession *KCPSession
|
||||||
kcpMutex sync.Mutex
|
kcpMutex sync.Mutex
|
||||||
|
phttpClient *pht.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProxyChain(nodes ...ProxyNode) *ProxyChain {
|
func NewProxyChain(nodes ...ProxyNode) *ProxyChain {
|
||||||
@ -96,8 +98,8 @@ func (c *ProxyChain) Init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, node := range c.nodes {
|
for i, node := range c.nodes {
|
||||||
if node.Transport == "kcp" && i > 0 {
|
if (node.Transport == "kcp" || node.Transport == "pht" || node.Transport == "quic") && i > 0 {
|
||||||
glog.Fatal("KCP must be the first node in the proxy chain")
|
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
|
c.kcpConfig = config
|
||||||
return
|
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 {
|
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)
|
cc, err = c.http2Connect(node.Addr)
|
||||||
} else if node.Transport == "kcp" {
|
} else if node.Transport == "kcp" {
|
||||||
cc, err = c.getKCPConn()
|
cc, err = c.getKCPConn()
|
||||||
|
} else if node.Transport == "pht" {
|
||||||
|
cc, err = c.phttpClient.Dial()
|
||||||
} else {
|
} else {
|
||||||
cc, err = net.DialTimeout("tcp", node.Addr, DialTimeout)
|
cc, err = net.DialTimeout("tcp", node.Addr, DialTimeout)
|
||||||
}
|
}
|
||||||
|
13
cmd/gost/vendor/github.com/ginuerzh/gost/chain.go
generated
vendored
13
cmd/gost/vendor/github.com/ginuerzh/gost/chain.go
generated
vendored
@ -5,6 +5,7 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/ginuerzh/pht"
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
"io"
|
"io"
|
||||||
@ -29,6 +30,7 @@ type ProxyChain struct {
|
|||||||
kcpConfig *KCPConfig
|
kcpConfig *KCPConfig
|
||||||
kcpSession *KCPSession
|
kcpSession *KCPSession
|
||||||
kcpMutex sync.Mutex
|
kcpMutex sync.Mutex
|
||||||
|
phttpClient *pht.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProxyChain(nodes ...ProxyNode) *ProxyChain {
|
func NewProxyChain(nodes ...ProxyNode) *ProxyChain {
|
||||||
@ -96,8 +98,8 @@ func (c *ProxyChain) Init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, node := range c.nodes {
|
for i, node := range c.nodes {
|
||||||
if node.Transport == "kcp" && i > 0 {
|
if (node.Transport == "kcp" || node.Transport == "pht" || node.Transport == "quic") && i > 0 {
|
||||||
glog.Fatal("KCP must be the first node in the proxy chain")
|
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
|
c.kcpConfig = config
|
||||||
return
|
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 {
|
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)
|
cc, err = c.http2Connect(node.Addr)
|
||||||
} else if node.Transport == "kcp" {
|
} else if node.Transport == "kcp" {
|
||||||
cc, err = c.getKCPConn()
|
cc, err = c.getKCPConn()
|
||||||
|
} else if node.Transport == "pht" {
|
||||||
|
cc, err = c.phttpClient.Dial()
|
||||||
} else {
|
} else {
|
||||||
cc, err = net.DialTimeout("tcp", node.Addr, DialTimeout)
|
cc, err = net.DialTimeout("tcp", node.Addr, DialTimeout)
|
||||||
}
|
}
|
||||||
|
31
cmd/gost/vendor/github.com/ginuerzh/gost/http.go
generated
vendored
31
cmd/gost/vendor/github.com/ginuerzh/gost/http.go
generated
vendored
@ -4,14 +4,14 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"github.com/ginuerzh/pht"
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
//"strings"
|
|
||||||
"errors"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -383,3 +383,30 @@ func (fw flushWriter) Write(p []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
return
|
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)
|
||||||
|
}
|
||||||
|
2
cmd/gost/vendor/github.com/ginuerzh/gost/node.go
generated
vendored
2
cmd/gost/vendor/github.com/ginuerzh/gost/node.go
generated
vendored
@ -71,7 +71,7 @@ func ParseProxyNode(s string) (node ProxyNode, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch node.Transport {
|
switch node.Transport {
|
||||||
case "ws", "wss", "tls", "http2", "quic", "kcp", "redirect", "ssu":
|
case "ws", "wss", "tls", "http2", "quic", "kcp", "redirect", "ssu", "pht":
|
||||||
case "https":
|
case "https":
|
||||||
node.Protocol = "http"
|
node.Protocol = "http"
|
||||||
node.Transport = "tls"
|
node.Transport = "tls"
|
||||||
|
4
cmd/gost/vendor/github.com/ginuerzh/gost/server.go
generated
vendored
4
cmd/gost/vendor/github.com/ginuerzh/gost/server.go
generated
vendored
@ -122,6 +122,8 @@ func (s *ProxyServer) Serve() error {
|
|||||||
ttl = DefaultTTL
|
ttl = DefaultTTL
|
||||||
}
|
}
|
||||||
return NewShadowUdpServer(s, ttl).ListenAndServe()
|
return NewShadowUdpServer(s, ttl).ListenAndServe()
|
||||||
|
case "pht": // pure http tunnel
|
||||||
|
return NewPureHttpServer(s).ListenAndServe()
|
||||||
default:
|
default:
|
||||||
ln, err = net.Listen("tcp", node.Addr)
|
ln, err = net.Listen("tcp", node.Addr)
|
||||||
}
|
}
|
||||||
@ -238,7 +240,7 @@ func (_ *ProxyServer) transport(conn1, conn2 net.Conn) (err error) {
|
|||||||
|
|
||||||
select {
|
select {
|
||||||
case err = <-errc:
|
case err = <-errc:
|
||||||
//glog.V(LWARNING).Infoln("transport exit", err)
|
// glog.V(LWARNING).Infoln("transport exit", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
21
cmd/gost/vendor/github.com/ginuerzh/pht/LICENSE
generated
vendored
Normal file
21
cmd/gost/vendor/github.com/ginuerzh/pht/LICENSE
generated
vendored
Normal file
@ -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.
|
2
cmd/gost/vendor/github.com/ginuerzh/pht/README.md
generated
vendored
Normal file
2
cmd/gost/vendor/github.com/ginuerzh/pht/README.md
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# pht
|
||||||
|
Pure HTTP Tunnel - Tunnel over HTTP using only GET and POST requests.
|
162
cmd/gost/vendor/github.com/ginuerzh/pht/client.go
generated
vendored
Normal file
162
cmd/gost/vendor/github.com/ginuerzh/pht/client.go
generated
vendored
Normal file
@ -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
|
||||||
|
}
|
133
cmd/gost/vendor/github.com/ginuerzh/pht/conn.go
generated
vendored
Normal file
133
cmd/gost/vendor/github.com/ginuerzh/pht/conn.go
generated
vendored
Normal file
@ -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
|
||||||
|
}
|
199
cmd/gost/vendor/github.com/ginuerzh/pht/server.go
generated
vendored
Normal file
199
cmd/gost/vendor/github.com/ginuerzh/pht/server.go
generated
vendored
Normal file
@ -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
|
||||||
|
}
|
80
cmd/gost/vendor/github.com/ginuerzh/pht/session.go
generated
vendored
Normal file
80
cmd/gost/vendor/github.com/ginuerzh/pht/session.go
generated
vendored
Normal file
@ -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)
|
||||||
|
}
|
12
cmd/gost/vendor/vendor.json
vendored
12
cmd/gost/vendor/vendor.json
vendored
@ -21,10 +21,16 @@
|
|||||||
"revisionTime": "2017-01-19T05:34:58Z"
|
"revisionTime": "2017-01-19T05:34:58Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "VXFPGZtx+GKexkXeL3YljqJLHFk=",
|
"checksumSHA1": "ero0DQrGYph2eFDyEjDnOQV8NHo=",
|
||||||
"path": "github.com/ginuerzh/gost",
|
"path": "github.com/ginuerzh/gost",
|
||||||
"revision": "dc75931858cf96d95525faa22b7ffa0cea3e7d68",
|
"revision": "72d8a598d50f8517d922f233e8f8d37011fcb18f",
|
||||||
"revisionTime": "2017-01-25T04:21:04Z"
|
"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=",
|
"checksumSHA1": "URsJa4y/sUUw/STmbeYx9EKqaYE=",
|
||||||
|
31
http.go
31
http.go
@ -4,14 +4,14 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"github.com/ginuerzh/pht"
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
//"strings"
|
|
||||||
"errors"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -383,3 +383,30 @@ func (fw flushWriter) Write(p []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
return
|
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)
|
||||||
|
}
|
||||||
|
2
node.go
2
node.go
@ -71,7 +71,7 @@ func ParseProxyNode(s string) (node ProxyNode, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch node.Transport {
|
switch node.Transport {
|
||||||
case "ws", "wss", "tls", "http2", "quic", "kcp", "redirect", "ssu":
|
case "ws", "wss", "tls", "http2", "quic", "kcp", "redirect", "ssu", "pht":
|
||||||
case "https":
|
case "https":
|
||||||
node.Protocol = "http"
|
node.Protocol = "http"
|
||||||
node.Transport = "tls"
|
node.Transport = "tls"
|
||||||
|
@ -122,6 +122,8 @@ func (s *ProxyServer) Serve() error {
|
|||||||
ttl = DefaultTTL
|
ttl = DefaultTTL
|
||||||
}
|
}
|
||||||
return NewShadowUdpServer(s, ttl).ListenAndServe()
|
return NewShadowUdpServer(s, ttl).ListenAndServe()
|
||||||
|
case "pht": // pure http tunnel
|
||||||
|
return NewPureHttpServer(s).ListenAndServe()
|
||||||
default:
|
default:
|
||||||
ln, err = net.Listen("tcp", node.Addr)
|
ln, err = net.Listen("tcp", node.Addr)
|
||||||
}
|
}
|
||||||
@ -238,7 +240,7 @@ func (_ *ProxyServer) transport(conn1, conn2 net.Conn) (err error) {
|
|||||||
|
|
||||||
select {
|
select {
|
||||||
case err = <-errc:
|
case err = <-errc:
|
||||||
//glog.V(LWARNING).Infoln("transport exit", err)
|
// glog.V(LWARNING).Infoln("transport exit", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
Loading…
Reference in New Issue
Block a user