mirror of https://github.com/ginuerzh/gost
13 changed files with 627 additions and 151 deletions
@ -1,80 +0,0 @@ |
|||||
package main |
|
||||
|
|
||||
import ( |
|
||||
"bufio" |
|
||||
"flag" |
|
||||
"fmt" |
|
||||
"github.com/ginuerzh/gost" |
|
||||
"github.com/golang/glog" |
|
||||
"golang.org/x/net/http2" |
|
||||
"log" |
|
||||
"net/http" |
|
||||
"net/http/httputil" |
|
||||
"net/url" |
|
||||
) |
|
||||
|
|
||||
var ( |
|
||||
proxyNodes stringlist |
|
||||
urls []string |
|
||||
) |
|
||||
|
|
||||
func init() { |
|
||||
flag.Var(&proxyNodes, "F", "forward address, can make a forward chain") |
|
||||
flag.Parse() |
|
||||
if flag.NArg() == 0 { |
|
||||
log.Fatal("please specific at least one request URL") |
|
||||
} |
|
||||
urls = flag.Args() |
|
||||
if glog.V(5) { |
|
||||
http2.VerboseLogs = true |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
type stringlist []string |
|
||||
|
|
||||
func (list *stringlist) String() string { |
|
||||
return fmt.Sprintf("%s", *list) |
|
||||
} |
|
||||
func (list *stringlist) Set(value string) error { |
|
||||
*list = append(*list, value) |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
func main() { |
|
||||
chain := gost.NewProxyChain() |
|
||||
if err := chain.AddProxyNodeString(proxyNodes...); err != nil { |
|
||||
log.Fatal(err) |
|
||||
} |
|
||||
chain.TryEnableHttp2() |
|
||||
|
|
||||
for _, u := range urls { |
|
||||
url, err := url.Parse(u) |
|
||||
if err != nil { |
|
||||
log.Println("Invalid url:", u) |
|
||||
continue |
|
||||
} |
|
||||
|
|
||||
log.Println("GET", u) |
|
||||
conn, err := chain.Dial(url.Host) |
|
||||
if err != nil { |
|
||||
log.Fatal(err) |
|
||||
} |
|
||||
req, err := http.NewRequest("GET", u, nil) |
|
||||
if err != nil { |
|
||||
log.Fatal(err) |
|
||||
} |
|
||||
|
|
||||
if err := req.Write(conn); err != nil { |
|
||||
log.Fatal(err) |
|
||||
} |
|
||||
resp, err := http.ReadResponse(bufio.NewReader(conn), req) |
|
||||
if err != nil { |
|
||||
log.Fatal(err) |
|
||||
} |
|
||||
defer resp.Body.Close() |
|
||||
|
|
||||
header, _ := httputil.DumpResponse(resp, false) |
|
||||
log.Println(string(header)) |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -1,53 +0,0 @@ |
|||||
package main |
|
||||
|
|
||||
import ( |
|
||||
"crypto/tls" |
|
||||
"flag" |
|
||||
"fmt" |
|
||||
"github.com/ginuerzh/gost" |
|
||||
"log" |
|
||||
"sync" |
|
||||
) |
|
||||
|
|
||||
var ( |
|
||||
proxyNodes stringlist |
|
||||
) |
|
||||
|
|
||||
func init() { |
|
||||
flag.Var(&proxyNodes, "L", "proxy server node") |
|
||||
flag.Parse() |
|
||||
} |
|
||||
|
|
||||
type stringlist []string |
|
||||
|
|
||||
func (list *stringlist) String() string { |
|
||||
return fmt.Sprintf("%s", *list) |
|
||||
} |
|
||||
func (list *stringlist) Set(value string) error { |
|
||||
*list = append(*list, value) |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
func main() { |
|
||||
chain := gost.NewProxyChain() |
|
||||
var wg sync.WaitGroup |
|
||||
for _, ns := range proxyNodes { |
|
||||
serverNode, err := gost.ParseProxyNode(ns) |
|
||||
if err != nil { |
|
||||
log.Println(err) |
|
||||
continue |
|
||||
} |
|
||||
wg.Add(1) |
|
||||
go func(node gost.ProxyNode) { |
|
||||
defer wg.Done() |
|
||||
cert, err := gost.LoadCertificate(node.Get("cert"), node.Get("key")) |
|
||||
if err != nil { |
|
||||
log.Println(err) |
|
||||
return |
|
||||
} |
|
||||
server := gost.NewProxyServer(node, chain, &tls.Config{Certificates: []tls.Certificate{cert}}) |
|
||||
log.Fatal(server.Serve()) |
|
||||
}(serverNode) |
|
||||
} |
|
||||
wg.Wait() |
|
||||
} |
|
||||
@ -0,0 +1,369 @@ |
|||||
|
// KCP feature is based on https://github.com/xtaci/kcptun
|
||||
|
|
||||
|
package gost |
||||
|
|
||||
|
import ( |
||||
|
"crypto/sha1" |
||||
|
"encoding/json" |
||||
|
"github.com/golang/glog" |
||||
|
"github.com/klauspost/compress/snappy" |
||||
|
"golang.org/x/crypto/pbkdf2" |
||||
|
"gopkg.in/xtaci/kcp-go.v2" |
||||
|
"gopkg.in/xtaci/smux.v1" |
||||
|
"net" |
||||
|
"os" |
||||
|
"time" |
||||
|
) |
||||
|
|
||||
|
const ( |
||||
|
DefaultKCPConfigFile = "kcp.json" |
||||
|
) |
||||
|
|
||||
|
var ( |
||||
|
SALT = "kcp-go" |
||||
|
) |
||||
|
|
||||
|
type KCPConfig struct { |
||||
|
Key string `json:"key"` |
||||
|
Crypt string `json:"crypt"` |
||||
|
Mode string `json:"mode"` |
||||
|
MTU int `json:"mtu"` |
||||
|
SndWnd int `json:"sndwnd"` |
||||
|
RcvWnd int `json:"rcvwnd"` |
||||
|
DataShard int `json:"datashard"` |
||||
|
ParityShard int `json:"parityshard"` |
||||
|
DSCP int `json:"dscp"` |
||||
|
NoComp bool `json:"nocomp"` |
||||
|
AckNodelay bool `json:"acknodelay"` |
||||
|
NoDelay int `json:"nodelay"` |
||||
|
Interval int `json:"interval"` |
||||
|
Resend int `json:"resend"` |
||||
|
NoCongestion int `json:"nc"` |
||||
|
SockBuf int `json:"sockbuf"` |
||||
|
KeepAlive int `json:"keepalive"` |
||||
|
} |
||||
|
|
||||
|
func ParseKCPConfig(configFile string) (*KCPConfig, error) { |
||||
|
if configFile == "" { |
||||
|
configFile = DefaultKCPConfigFile |
||||
|
} |
||||
|
file, err := os.Open(configFile) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
defer file.Close() |
||||
|
|
||||
|
config := &KCPConfig{} |
||||
|
if err = json.NewDecoder(file).Decode(config); err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
return config, nil |
||||
|
} |
||||
|
|
||||
|
func (c *KCPConfig) Init() { |
||||
|
switch c.Mode { |
||||
|
case "normal": |
||||
|
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 0, 30, 2, 1 |
||||
|
case "fast2": |
||||
|
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 1, 20, 2, 1 |
||||
|
case "fast3": |
||||
|
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 1, 10, 2, 1 |
||||
|
case "fast": |
||||
|
fallthrough |
||||
|
default: |
||||
|
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 0, 20, 2, 1 |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
var ( |
||||
|
DefaultKCPConfig = &KCPConfig{ |
||||
|
Key: "it's a secrect", |
||||
|
Crypt: "aes", |
||||
|
Mode: "fast", |
||||
|
MTU: 1350, |
||||
|
SndWnd: 1024, |
||||
|
RcvWnd: 1024, |
||||
|
DataShard: 10, |
||||
|
ParityShard: 3, |
||||
|
DSCP: 0, |
||||
|
NoComp: false, |
||||
|
AckNodelay: false, |
||||
|
NoDelay: 0, |
||||
|
Interval: 40, |
||||
|
Resend: 0, |
||||
|
NoCongestion: 0, |
||||
|
SockBuf: 4194304, |
||||
|
KeepAlive: 10, |
||||
|
} |
||||
|
) |
||||
|
|
||||
|
type KCPServer struct { |
||||
|
Base *ProxyServer |
||||
|
Config *KCPConfig |
||||
|
} |
||||
|
|
||||
|
func NewKCPServer(base *ProxyServer, config *KCPConfig) *KCPServer { |
||||
|
return &KCPServer{Base: base, Config: config} |
||||
|
} |
||||
|
|
||||
|
func (s *KCPServer) ListenAndServe() (err error) { |
||||
|
if s.Config == nil { |
||||
|
s.Config = DefaultKCPConfig |
||||
|
} |
||||
|
s.Config.Init() |
||||
|
|
||||
|
ln, err := kcp.ListenWithOptions(s.Base.Node.Addr, |
||||
|
blockCrypt(s.Config.Key, s.Config.Crypt, SALT), s.Config.DataShard, s.Config.ParityShard) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
if err = ln.SetDSCP(s.Config.DSCP); err != nil { |
||||
|
glog.V(LWARNING).Infoln("[kcp]", err) |
||||
|
} |
||||
|
if err = ln.SetReadBuffer(s.Config.SockBuf); err != nil { |
||||
|
glog.V(LWARNING).Infoln("[kcp]", err) |
||||
|
} |
||||
|
if err = ln.SetWriteBuffer(s.Config.SockBuf); err != nil { |
||||
|
glog.V(LWARNING).Infoln("[kcp]", err) |
||||
|
} |
||||
|
|
||||
|
for { |
||||
|
conn, err := ln.AcceptKCP() |
||||
|
if err != nil { |
||||
|
glog.V(LWARNING).Infoln(err) |
||||
|
continue |
||||
|
} |
||||
|
|
||||
|
conn.SetStreamMode(true) |
||||
|
conn.SetNoDelay(s.Config.NoDelay, s.Config.Interval, s.Config.Resend, s.Config.NoCongestion) |
||||
|
conn.SetMtu(s.Config.MTU) |
||||
|
conn.SetWindowSize(s.Config.SndWnd, s.Config.RcvWnd) |
||||
|
conn.SetACKNoDelay(s.Config.AckNodelay) |
||||
|
conn.SetKeepAlive(s.Config.KeepAlive) |
||||
|
|
||||
|
go s.handleMux(conn) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func (s *KCPServer) handleMux(conn net.Conn) { |
||||
|
smuxConfig := smux.DefaultConfig() |
||||
|
smuxConfig.MaxReceiveBuffer = s.Config.SockBuf |
||||
|
|
||||
|
glog.V(LINFO).Infof("[kcp] %s - %s", conn.RemoteAddr(), s.Base.Node.Addr) |
||||
|
|
||||
|
if !s.Config.NoComp { |
||||
|
conn = newCompStreamConn(conn) |
||||
|
} |
||||
|
|
||||
|
mux, err := smux.Server(conn, smuxConfig) |
||||
|
if err != nil { |
||||
|
glog.V(LWARNING).Infoln("[kcp]", err) |
||||
|
return |
||||
|
} |
||||
|
defer mux.Close() |
||||
|
|
||||
|
glog.V(LINFO).Infof("[kcp] %s <-> %s", conn.RemoteAddr(), s.Base.Node.Addr) |
||||
|
defer glog.V(LINFO).Infof("[kcp] %s >-< %s", conn.RemoteAddr(), s.Base.Node.Addr) |
||||
|
|
||||
|
for { |
||||
|
stream, err := mux.AcceptStream() |
||||
|
if err != nil { |
||||
|
glog.V(LWARNING).Infoln("[kcp]", err) |
||||
|
return |
||||
|
} |
||||
|
go s.Base.handleConn(NewKCPConn(conn, stream)) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func blockCrypt(key, crypt, salt string) (block kcp.BlockCrypt) { |
||||
|
pass := pbkdf2.Key([]byte(key), []byte(salt), 4096, 32, sha1.New) |
||||
|
|
||||
|
switch crypt { |
||||
|
case "tea": |
||||
|
block, _ = kcp.NewTEABlockCrypt(pass[:16]) |
||||
|
case "xor": |
||||
|
block, _ = kcp.NewSimpleXORBlockCrypt(pass) |
||||
|
case "none": |
||||
|
block, _ = kcp.NewNoneBlockCrypt(pass) |
||||
|
case "aes-128": |
||||
|
block, _ = kcp.NewAESBlockCrypt(pass[:16]) |
||||
|
case "aes-192": |
||||
|
block, _ = kcp.NewAESBlockCrypt(pass[:24]) |
||||
|
case "blowfish": |
||||
|
block, _ = kcp.NewBlowfishBlockCrypt(pass) |
||||
|
case "twofish": |
||||
|
block, _ = kcp.NewTwofishBlockCrypt(pass) |
||||
|
case "cast5": |
||||
|
block, _ = kcp.NewCast5BlockCrypt(pass[:16]) |
||||
|
case "3des": |
||||
|
block, _ = kcp.NewTripleDESBlockCrypt(pass[:24]) |
||||
|
case "xtea": |
||||
|
block, _ = kcp.NewXTEABlockCrypt(pass[:16]) |
||||
|
case "salsa20": |
||||
|
block, _ = kcp.NewSalsa20BlockCrypt(pass) |
||||
|
case "aes": |
||||
|
fallthrough |
||||
|
default: // aes
|
||||
|
block, _ = kcp.NewAESBlockCrypt(pass) |
||||
|
} |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
type KCPSession struct { |
||||
|
conn net.Conn |
||||
|
session *smux.Session |
||||
|
} |
||||
|
|
||||
|
func DialKCP(addr string, config *KCPConfig) (*KCPSession, error) { |
||||
|
if config == nil { |
||||
|
config = DefaultKCPConfig |
||||
|
} |
||||
|
config.Init() |
||||
|
|
||||
|
kcpconn, err := kcp.DialWithOptions(addr, |
||||
|
blockCrypt(config.Key, config.Crypt, SALT), config.DataShard, config.ParityShard) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
kcpconn.SetStreamMode(true) |
||||
|
kcpconn.SetNoDelay(config.NoDelay, config.Interval, config.Resend, config.NoCongestion) |
||||
|
kcpconn.SetWindowSize(config.SndWnd, config.RcvWnd) |
||||
|
kcpconn.SetMtu(config.MTU) |
||||
|
kcpconn.SetACKNoDelay(config.AckNodelay) |
||||
|
kcpconn.SetKeepAlive(config.KeepAlive) |
||||
|
|
||||
|
if err := kcpconn.SetDSCP(config.DSCP); err != nil { |
||||
|
glog.V(LWARNING).Infoln("[kcp]", err) |
||||
|
} |
||||
|
if err := kcpconn.SetReadBuffer(config.SockBuf); err != nil { |
||||
|
glog.V(LWARNING).Infoln("[kcp]", err) |
||||
|
} |
||||
|
if err := kcpconn.SetWriteBuffer(config.SockBuf); err != nil { |
||||
|
glog.V(LWARNING).Infoln("[kcp]", err) |
||||
|
} |
||||
|
|
||||
|
// stream multiplex
|
||||
|
smuxConfig := smux.DefaultConfig() |
||||
|
smuxConfig.MaxReceiveBuffer = config.SockBuf |
||||
|
var conn net.Conn = kcpconn |
||||
|
if !config.NoComp { |
||||
|
conn = newCompStreamConn(kcpconn) |
||||
|
} |
||||
|
session, err := smux.Client(conn, smuxConfig) |
||||
|
if err != nil { |
||||
|
conn.Close() |
||||
|
return nil, err |
||||
|
} |
||||
|
return &KCPSession{conn: conn, session: session}, nil |
||||
|
} |
||||
|
|
||||
|
func (session *KCPSession) GetConn() (*KCPConn, error) { |
||||
|
stream, err := session.session.OpenStream() |
||||
|
if err != nil { |
||||
|
session.Close() |
||||
|
return nil, err |
||||
|
} |
||||
|
return NewKCPConn(session.conn, stream), nil |
||||
|
} |
||||
|
|
||||
|
func (session *KCPSession) Close() error { |
||||
|
return session.session.Close() |
||||
|
} |
||||
|
|
||||
|
func (session *KCPSession) IsClosed() bool { |
||||
|
return session.session.IsClosed() |
||||
|
} |
||||
|
|
||||
|
func (session *KCPSession) NumStreams() int { |
||||
|
return session.session.NumStreams() |
||||
|
} |
||||
|
|
||||
|
type KCPConn struct { |
||||
|
conn net.Conn |
||||
|
stream *smux.Stream |
||||
|
} |
||||
|
|
||||
|
func NewKCPConn(conn net.Conn, stream *smux.Stream) *KCPConn { |
||||
|
return &KCPConn{conn: conn, stream: stream} |
||||
|
} |
||||
|
|
||||
|
func (c *KCPConn) Read(b []byte) (n int, err error) { |
||||
|
return c.stream.Read(b) |
||||
|
} |
||||
|
|
||||
|
func (c *KCPConn) Write(b []byte) (n int, err error) { |
||||
|
return c.stream.Write(b) |
||||
|
} |
||||
|
|
||||
|
func (c *KCPConn) Close() error { |
||||
|
return c.stream.Close() |
||||
|
} |
||||
|
|
||||
|
func (c *KCPConn) LocalAddr() net.Addr { |
||||
|
return c.conn.LocalAddr() |
||||
|
} |
||||
|
|
||||
|
func (c *KCPConn) RemoteAddr() net.Addr { |
||||
|
return c.conn.RemoteAddr() |
||||
|
} |
||||
|
|
||||
|
func (c *KCPConn) SetDeadline(t time.Time) error { |
||||
|
return c.conn.SetDeadline(t) |
||||
|
} |
||||
|
|
||||
|
func (c *KCPConn) SetReadDeadline(t time.Time) error { |
||||
|
return c.conn.SetReadDeadline(t) |
||||
|
} |
||||
|
|
||||
|
func (c *KCPConn) SetWriteDeadline(t time.Time) error { |
||||
|
return c.conn.SetWriteDeadline(t) |
||||
|
} |
||||
|
|
||||
|
type compStreamConn struct { |
||||
|
conn net.Conn |
||||
|
w *snappy.Writer |
||||
|
r *snappy.Reader |
||||
|
} |
||||
|
|
||||
|
func newCompStreamConn(conn net.Conn) *compStreamConn { |
||||
|
c := new(compStreamConn) |
||||
|
c.conn = conn |
||||
|
c.w = snappy.NewBufferedWriter(conn) |
||||
|
c.r = snappy.NewReader(conn) |
||||
|
return c |
||||
|
} |
||||
|
|
||||
|
func (c *compStreamConn) Read(b []byte) (n int, err error) { |
||||
|
return c.r.Read(b) |
||||
|
} |
||||
|
|
||||
|
func (c *compStreamConn) Write(b []byte) (n int, err error) { |
||||
|
n, err = c.w.Write(b) |
||||
|
err = c.w.Flush() |
||||
|
return n, err |
||||
|
} |
||||
|
|
||||
|
func (c *compStreamConn) Close() error { |
||||
|
return c.conn.Close() |
||||
|
} |
||||
|
|
||||
|
func (c *compStreamConn) LocalAddr() net.Addr { |
||||
|
return c.conn.LocalAddr() |
||||
|
} |
||||
|
|
||||
|
func (c *compStreamConn) RemoteAddr() net.Addr { |
||||
|
return c.conn.RemoteAddr() |
||||
|
} |
||||
|
|
||||
|
func (c *compStreamConn) SetDeadline(t time.Time) error { |
||||
|
return c.conn.SetDeadline(t) |
||||
|
} |
||||
|
|
||||
|
func (c *compStreamConn) SetReadDeadline(t time.Time) error { |
||||
|
return c.conn.SetReadDeadline(t) |
||||
|
} |
||||
|
|
||||
|
func (c *compStreamConn) SetWriteDeadline(t time.Time) error { |
||||
|
return c.conn.SetWriteDeadline(t) |
||||
|
} |
||||
@ -0,0 +1,80 @@ |
|||||
|
package gost |
||||
|
|
||||
|
import ( |
||||
|
"bufio" |
||||
|
"crypto/tls" |
||||
|
"github.com/golang/glog" |
||||
|
"github.com/lucas-clemente/quic-go/h2quic" |
||||
|
"io" |
||||
|
"net/http" |
||||
|
"net/http/httputil" |
||||
|
) |
||||
|
|
||||
|
type QuicServer struct { |
||||
|
Base *ProxyServer |
||||
|
Handler http.Handler |
||||
|
TLSConfig *tls.Config |
||||
|
} |
||||
|
|
||||
|
func NewQuicServer(base *ProxyServer) *QuicServer { |
||||
|
return &QuicServer{Base: base} |
||||
|
} |
||||
|
|
||||
|
func (s *QuicServer) ListenAndServeTLS(config *tls.Config) error { |
||||
|
server := &h2quic.Server{ |
||||
|
Server: &http.Server{ |
||||
|
Addr: s.Base.Node.Addr, |
||||
|
Handler: s.Handler, |
||||
|
TLSConfig: config, |
||||
|
}, |
||||
|
} |
||||
|
if server.Handler == nil { |
||||
|
server.Handler = http.HandlerFunc(s.HandleRequest) |
||||
|
} |
||||
|
return server.ListenAndServe() |
||||
|
} |
||||
|
|
||||
|
func (s *QuicServer) HandleRequest(w http.ResponseWriter, req *http.Request) { |
||||
|
target := req.Host |
||||
|
glog.V(LINFO).Infof("[quic] %s %s - %s %s", req.Method, req.RemoteAddr, target, req.Proto) |
||||
|
|
||||
|
if glog.V(LDEBUG) { |
||||
|
dump, _ := httputil.DumpRequest(req, false) |
||||
|
glog.Infoln(string(dump)) |
||||
|
} |
||||
|
|
||||
|
c, err := s.Base.Chain.Dial(target) |
||||
|
if err != nil { |
||||
|
glog.V(LWARNING).Infof("[quic] %s -> %s : %s", req.RemoteAddr, target, err) |
||||
|
w.WriteHeader(http.StatusServiceUnavailable) |
||||
|
return |
||||
|
} |
||||
|
defer c.Close() |
||||
|
|
||||
|
glog.V(LINFO).Infof("[quic] %s <-> %s", req.RemoteAddr, target) |
||||
|
|
||||
|
req.Header.Set("Connection", "Keep-Alive") |
||||
|
if err = req.Write(c); err != nil { |
||||
|
glog.V(LWARNING).Infof("[quic] %s -> %s : %s", req.RemoteAddr, target, err) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
resp, err := http.ReadResponse(bufio.NewReader(c), req) |
||||
|
if err != nil { |
||||
|
glog.V(LWARNING).Infoln(err) |
||||
|
return |
||||
|
} |
||||
|
defer resp.Body.Close() |
||||
|
|
||||
|
for k, v := range resp.Header { |
||||
|
for _, vv := range v { |
||||
|
w.Header().Add(k, vv) |
||||
|
} |
||||
|
} |
||||
|
w.WriteHeader(resp.StatusCode) |
||||
|
if _, err := io.Copy(flushWriter{w}, resp.Body); err != nil { |
||||
|
glog.V(LWARNING).Infof("[quic] %s <- %s : %s", req.RemoteAddr, target, err) |
||||
|
} |
||||
|
|
||||
|
glog.V(LINFO).Infof("[quic] %s >-< %s", req.RemoteAddr, target) |
||||
|
} |
||||
Loading…
Reference in new issue