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