mirror of https://github.com/ginuerzh/gost
16 changed files with 664 additions and 98 deletions
@ -0,0 +1,472 @@ |
|||
package gost |
|||
|
|||
import ( |
|||
"crypto/sha1" |
|||
"encoding/csv" |
|||
"fmt" |
|||
"net" |
|||
"os" |
|||
"time" |
|||
|
|||
"golang.org/x/crypto/pbkdf2" |
|||
|
|||
"sync" |
|||
|
|||
"github.com/go-log/log" |
|||
"github.com/klauspost/compress/snappy" |
|||
"gopkg.in/xtaci/kcp-go.v2" |
|||
"gopkg.in/xtaci/smux.v1" |
|||
) |
|||
|
|||
var ( |
|||
// SALT is the default salt for KCP cipher.
|
|||
SALT = "kcp-go" |
|||
) |
|||
|
|||
// KCPConfig describes the config for KCP.
|
|||
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"` |
|||
SnmpLog string `json:"snmplog"` |
|||
SnmpPeriod int `json:"snmpperiod"` |
|||
Signal bool `json:"signal"` // Signal enables the signal SIGUSR1 feature.
|
|||
} |
|||
|
|||
// Init initializes the KCP config.
|
|||
func (c *KCPConfig) Init() { |
|||
switch c.Mode { |
|||
case "normal": |
|||
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 0, 50, 2, 1 |
|||
case "fast2": |
|||
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 1, 30, 2, 1 |
|||
case "fast3": |
|||
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 1, 20, 2, 1 |
|||
case "fast": |
|||
fallthrough |
|||
default: |
|||
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 0, 40, 2, 1 |
|||
} |
|||
} |
|||
|
|||
var ( |
|||
// DefaultKCPConfig is the default KCP config.
|
|||
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: 50, |
|||
Resend: 0, |
|||
NoCongestion: 0, |
|||
SockBuf: 4194304, |
|||
KeepAlive: 10, |
|||
SnmpLog: "", |
|||
SnmpPeriod: 60, |
|||
Signal: false, |
|||
} |
|||
) |
|||
|
|||
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 kcpSession struct { |
|||
conn net.Conn |
|||
session *smux.Session |
|||
} |
|||
|
|||
func (session *kcpSession) GetConn() (*kcpConn, error) { |
|||
stream, err := session.session.OpenStream() |
|||
if err != nil { |
|||
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 kcpTransporter struct { |
|||
sessions map[string]*kcpSession |
|||
sessionMutex sync.Mutex |
|||
config *KCPConfig |
|||
} |
|||
|
|||
// KCPTransporter creates a Transporter that is used by KCP proxy client.
|
|||
func KCPTransporter(config *KCPConfig) Transporter { |
|||
if config == nil { |
|||
config = DefaultKCPConfig |
|||
} |
|||
config.Init() |
|||
|
|||
go snmpLogger(config.SnmpLog, config.SnmpPeriod) |
|||
if config.Signal { |
|||
go kcpSigHandler() |
|||
} |
|||
|
|||
return &kcpTransporter{ |
|||
config: config, |
|||
sessions: make(map[string]*kcpSession), |
|||
} |
|||
} |
|||
|
|||
func (tr *kcpTransporter) Dial(addr string) (conn net.Conn, err error) { |
|||
tr.sessionMutex.Lock() |
|||
session, ok := tr.sessions[addr] |
|||
if !ok { |
|||
session, err = tr.dial(addr, tr.config) |
|||
if err != nil { |
|||
tr.sessionMutex.Unlock() |
|||
return |
|||
} |
|||
tr.sessions[addr] = session |
|||
} |
|||
tr.sessionMutex.Unlock() |
|||
|
|||
conn, err = session.GetConn() |
|||
if err != nil { |
|||
tr.sessionMutex.Lock() |
|||
session.Close() |
|||
delete(tr.sessions, addr) |
|||
tr.sessionMutex.Unlock() |
|||
} |
|||
return |
|||
} |
|||
|
|||
func (tr *kcpTransporter) dial(addr string, config *KCPConfig) (*kcpSession, error) { |
|||
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 { |
|||
log.Log("[kcp]", err) |
|||
} |
|||
if err := kcpconn.SetReadBuffer(config.SockBuf); err != nil { |
|||
log.Log("[kcp]", err) |
|||
} |
|||
if err := kcpconn.SetWriteBuffer(config.SockBuf); err != nil { |
|||
log.Log("[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 (tr *kcpTransporter) Handshake(conn net.Conn) (net.Conn, error) { |
|||
return conn, nil |
|||
} |
|||
|
|||
type kcpListener struct { |
|||
config *KCPConfig |
|||
ln *kcp.Listener |
|||
connChan chan net.Conn |
|||
errChan chan error |
|||
} |
|||
|
|||
// KCPListener creates a Listener for KCP proxy server.
|
|||
func KCPListener(addr string, config *KCPConfig) (Listener, error) { |
|||
if config == nil { |
|||
config = DefaultKCPConfig |
|||
} |
|||
config.Init() |
|||
|
|||
ln, err := kcp.ListenWithOptions(addr, |
|||
blockCrypt(config.Key, config.Crypt, SALT), config.DataShard, config.ParityShard) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
if err = ln.SetDSCP(config.DSCP); err != nil { |
|||
log.Log("[kcp]", err) |
|||
} |
|||
if err = ln.SetReadBuffer(config.SockBuf); err != nil { |
|||
log.Log("[kcp]", err) |
|||
} |
|||
if err = ln.SetWriteBuffer(config.SockBuf); err != nil { |
|||
log.Log("[kcp]", err) |
|||
} |
|||
|
|||
go snmpLogger(config.SnmpLog, config.SnmpPeriod) |
|||
if config.Signal { |
|||
go kcpSigHandler() |
|||
} |
|||
|
|||
l := &kcpListener{ |
|||
config: config, |
|||
ln: ln, |
|||
connChan: make(chan net.Conn, 128), |
|||
errChan: make(chan error), |
|||
} |
|||
go l.acceptLoop() |
|||
|
|||
return l, nil |
|||
} |
|||
|
|||
func (l *kcpListener) acceptLoop() { |
|||
for { |
|||
conn, err := l.ln.AcceptKCP() |
|||
if err != nil { |
|||
log.Log("[kcp] accept:", err) |
|||
continue |
|||
} |
|||
conn.SetStreamMode(true) |
|||
conn.SetNoDelay(l.config.NoDelay, l.config.Interval, l.config.Resend, l.config.NoCongestion) |
|||
conn.SetMtu(l.config.MTU) |
|||
conn.SetWindowSize(l.config.SndWnd, l.config.RcvWnd) |
|||
conn.SetACKNoDelay(l.config.AckNodelay) |
|||
conn.SetKeepAlive(l.config.KeepAlive) |
|||
go l.mux(conn) |
|||
} |
|||
} |
|||
|
|||
func (l *kcpListener) Accept() (conn net.Conn, err error) { |
|||
select { |
|||
case conn = <-l.connChan: |
|||
case err = <-l.errChan: |
|||
} |
|||
return |
|||
} |
|||
|
|||
func (l *kcpListener) mux(conn net.Conn) { |
|||
smuxConfig := smux.DefaultConfig() |
|||
smuxConfig.MaxReceiveBuffer = l.config.SockBuf |
|||
|
|||
log.Logf("[kcp] %s - %s", conn.RemoteAddr(), l.Addr()) |
|||
|
|||
if !l.config.NoComp { |
|||
conn = newCompStreamConn(conn) |
|||
} |
|||
|
|||
mux, err := smux.Server(conn, smuxConfig) |
|||
if err != nil { |
|||
log.Log("[kcp]", err) |
|||
return |
|||
} |
|||
defer mux.Close() |
|||
|
|||
log.Logf("[kcp] %s <-> %s", conn.RemoteAddr(), l.Addr()) |
|||
defer log.Logf("[kcp] %s >-< %s", conn.RemoteAddr(), l.Addr()) |
|||
|
|||
for { |
|||
stream, err := mux.AcceptStream() |
|||
if err != nil { |
|||
log.Log("[kcp] accept stream:", err) |
|||
return |
|||
} |
|||
|
|||
select { |
|||
case l.connChan <- newKCPConn(conn, stream): |
|||
default: |
|||
log.Logf("[kcp] %s - %s: connection queue is full", conn.RemoteAddr(), l.Addr()) |
|||
} |
|||
} |
|||
} |
|||
|
|||
func (l *kcpListener) Addr() net.Addr { |
|||
return l.ln.Addr() |
|||
} |
|||
|
|||
func (l *kcpListener) Close() error { |
|||
return l.ln.Close() |
|||
} |
|||
|
|||
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 |
|||
} |
|||
|
|||
func snmpLogger(format string, interval int) { |
|||
if format == "" || interval == 0 { |
|||
return |
|||
} |
|||
ticker := time.NewTicker(time.Duration(interval) * time.Second) |
|||
defer ticker.Stop() |
|||
for { |
|||
select { |
|||
case <-ticker.C: |
|||
f, err := os.OpenFile(time.Now().Format(format), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) |
|||
if err != nil { |
|||
log.Log("[kcp]", err) |
|||
return |
|||
} |
|||
w := csv.NewWriter(f) |
|||
// write header in empty file
|
|||
if stat, err := f.Stat(); err == nil && stat.Size() == 0 { |
|||
if err := w.Write(append([]string{"Unix"}, kcp.DefaultSnmp.Header()...)); err != nil { |
|||
log.Log("[kcp]", err) |
|||
} |
|||
} |
|||
if err := w.Write(append([]string{fmt.Sprint(time.Now().Unix())}, kcp.DefaultSnmp.ToSlice()...)); err != nil { |
|||
log.Log("[kcp]", err) |
|||
} |
|||
kcp.DefaultSnmp.Reset() |
|||
w.Flush() |
|||
f.Close() |
|||
} |
|||
} |
|||
} |
|||
|
|||
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,5 @@ |
|||
// +build windows
|
|||
|
|||
package gost |
|||
|
|||
func kcpSigHandler() {} |
|||
@ -0,0 +1,24 @@ |
|||
// +build !windows
|
|||
|
|||
package gost |
|||
|
|||
import ( |
|||
"os" |
|||
"os/signal" |
|||
"syscall" |
|||
|
|||
"github.com/go-log/log" |
|||
"gopkg.in/xtaci/kcp-go.v2" |
|||
) |
|||
|
|||
func kcpSigHandler() { |
|||
ch := make(chan os.Signal, 1) |
|||
signal.Notify(ch, syscall.SIGUSR1) |
|||
|
|||
for { |
|||
switch <-ch { |
|||
case syscall.SIGUSR1: |
|||
log.Logf("[kcp] SNMP: %+v", kcp.DefaultSnmp.Copy()) |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue