#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"
|
||||
"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)
|
||||
}
|
||||
|
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"
|
||||
"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)
|
||||
}
|
||||
|
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"
|
||||
"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)
|
||||
}
|
||||
|
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 {
|
||||
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"
|
||||
|
2
cmd/gost/vendor/github.com/ginuerzh/gost/server.go
generated
vendored
2
cmd/gost/vendor/github.com/ginuerzh/gost/server.go
generated
vendored
@ -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)
|
||||
}
|
||||
|
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"
|
||||
},
|
||||
{
|
||||
"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=",
|
||||
|
31
http.go
31
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)
|
||||
}
|
||||
|
2
node.go
2
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"
|
||||
|
Loading…
Reference in New Issue
Block a user