mirror of https://github.com/ginuerzh/gost
15 changed files with 694 additions and 15 deletions
@ -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. |
|||
@ -0,0 +1,2 @@ |
|||
# pht |
|||
Pure HTTP Tunnel - Tunnel over HTTP using only GET and POST requests. |
|||
@ -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 |
|||
} |
|||
@ -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 |
|||
} |
|||
@ -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 |
|||
} |
|||
@ -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) |
|||
} |
|||
Loading…
Reference in new issue