diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..389256f14 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof +client/client +server/server diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..3961fc3b6 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,16 @@ +language: go +go: + - 1.6 +before_install: + - go get github.com/mattn/goveralls + - go get golang.org/x/tools/cmd/cover +install: + - go get github.com/xtaci/kcptun/client + - go get github.com/xtaci/kcptun/server +before_script: +script: + - cd $HOME/gopath/src/github.com/xtaci/kcptun/client + - $HOME/gopath/bin/goveralls -service=travis-ci + - cd $HOME/gopath/src/github.com/xtaci/kcptun/server + - $HOME/gopath/bin/goveralls -service=travis-ci + - exit 0 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..d28964022 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM golang:latest +MAINTAINER xtaci +RUN go get github.com/xtaci/kcptun/client +RUN go get github.com/xtaci/kcptun/server +EXPOSE 29900/udp +EXPOSE 12948 diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 000000000..88a8eea23 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Daniel Fu + +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. diff --git a/README.md b/README.md new file mode 100644 index 000000000..912342630 --- /dev/null +++ b/README.md @@ -0,0 +1,136 @@ +# kcptun +[![GoDoc][1]][2] [![Release][13]][14] [![Powered][17]][18] [![Build Status][3]][4] [![Go Report Card][5]][6] [![Downloads][15]][16] +[1]: https://godoc.org/github.com/xtaci/kcptun?status.svg +[2]: https://godoc.org/github.com/xtaci/kcptun +[3]: https://travis-ci.org/xtaci/kcptun.svg?branch=master +[4]: https://travis-ci.org/xtaci/kcptun +[5]: https://goreportcard.com/badge/github.com/xtaci/kcptun +[6]: https://goreportcard.com/report/github.com/xtaci/kcptun +[7]: https://img.shields.io/badge/license-MIT-blue.svg +[8]: https://raw.githubusercontent.com/xtaci/kcptun/master/LICENSE.md +[9]: https://img.shields.io/github/stars/xtaci/kcptun.svg +[10]: https://github.com/xtaci/kcptun/stargazers +[11]: https://img.shields.io/github/forks/xtaci/kcptun.svg +[12]: https://github.com/xtaci/kcptun/network +[13]: https://img.shields.io/github/release/xtaci/kcptun.svg +[14]: https://github.com/xtaci/kcptun/releases/latest +[15]: https://img.shields.io/github/downloads/xtaci/kcptun/total.svg?maxAge=2592000 +[16]: https://github.com/xtaci/kcptun/releases +[17]: https://img.shields.io/badge/KCP-Powered-blue.svg +[18]: https://github.com/skywind3000/kcp +[19]: https://img.shields.io/docker/pulls/xtaci/kcptun.svg?maxAge=2592000 +[20]: https://hub.docker.com/r/xtaci/kcptun/ + +TCP流转换为KCP+UDP流,:zap:***[官方下载地址](https://github.com/xtaci/kcptun/releases/latest)***:zap:工作示意图: + +![kcptun](kcptun.png) + +***kcptun是[kcp-go](https://github.com/xtaci/kcp-go)的一个测试应用,可以用于任意tcp网络程序的传输承载(尤其用于游戏数据传输测试),用于优化丢包环境下的网络流畅度。*** + +### *快速设定* :lollipop: +``` +服务器: ./server_linux_amd64 -t "127.0.0.1:1080" -l ":554" -mode fast2 // 转发到本地1080端口 +客户端: ./client_darwin_amd64 -r "服务器IP地址:554" -l ":1080" -mode fast2 // 监听本地1080端口 +``` + +### *使用の方法* :lollipop: +![client](client.png) +![server](server.png) + +### *适用范围限定* :lollipop: +1. 实时网络游戏的数据传输 +2. 跨运营商的流量传输 +3. 其他高丢包通信链路的TCP承载 + +### *推荐参数* :lollipop: +``` +适用大部分ADSL接入(非对称上下行)的参数(实验环境电信100M ADSL) +SERVER: -mtu 1400 -sndwnd 2048 -rcvwnd 2048 -mode fast2 +CLIENT: -mtu 1400 -sndwnd 256 -rcvwnd 2048 -mode fast2 -dscp 46 +``` + +*简易调优方法*: +> 第一步:同时在两端逐步增大client rcvwnd和server sndwnd; +> 第二步:尝试下载,观察如果带宽利用率接近物理带宽则停止,否则跳转到第一步。 + +*巭孬嫑乱动* + +### *DSCP* :lollipop: +DSCP差分服务代码点(Differentiated Services Code Point),IETF于1998年12月发布了Diff-Serv(Differentiated Service)的QoS分类标准。它在每个数据包IP头部的服务类别TOS标识字节中,利用已使用的6比特和未使用的2比特,通过编码值来区分优先级。 +常用DSCP值可以参考[Wikipedia DSCP](https://en.wikipedia.org/wiki/Differentiated_services#Commonly_used_DSCP_values),至于有没有用,完全取决于数据包经过的设备。 + +### *内置模式* :lollipop: +响应速度: +*fast3 >* ***[fast2]*** *> fast > normal > default* +有效载荷比: +*default > normal > fast >* ***[fast2]*** *> fast3* +中间mode参数比较均衡,总之就是越快越浪费带宽,推荐模式 ***fast2*** +更高级的 ***手动档*** 需要理解KCP协议,并通过 ***隐藏参数*** 调整,例如: +``` + -mode manual -nodelay 1 -resend 2 -nc 1 -interval 20 +``` + +### *前向纠错* :lollipop: +前向纠错采用Reed Solomon纠删码, 它的基本原理如下: 给定n个数据块d1, d2,…, dn,n和一个正整数m, RS根据n个数据块生成m个校验块, c1, c2,…, cm。 对于任意的n和m, 从n个原始数据块和m 个校验块中任取n块就能解码出原始数据, 即RS最多容忍m个数据块或者校验块同时丢失。 + +![reed-solomon](rs.png) + +通过```-datashard 10 -parityshard 3``` 可以调整Reed Solomon参数。 + +### *Snappy数据流压缩* :lollipop: +> Snappy is a compression/decompression library. It does not aim for maximum +> compression, or compatibility with any other compression library; instead, +> it aims for very high speeds and reasonable compression. For instance, +> compared to the fastest mode of zlib, Snappy is an order of magnitude faster +> for most inputs, but the resulting compressed files are anywhere from 20% to +> 100% bigger. + +Reference: http://google.github.io/snappy/ + +### *SNMP* :lollipop: +```go +// Snmp defines network statistics indicator +type Snmp struct { + BytesSent uint64 // payload bytes sent + BytesReceived uint64 + MaxConn uint64 + ActiveOpens uint64 + PassiveOpens uint64 + CurrEstab uint64 + InErrs uint64 + InCsumErrors uint64 // checksum errors + InSegs uint64 + OutSegs uint64 + OutBytes uint64 // udp bytes sent + RetransSegs uint64 + FastRetransSegs uint64 + EarlyRetransSegs uint64 + LostSegs uint64 + RepeatSegs uint64 + FECRecovered uint64 + FECErrs uint64 + FECSegs uint64 // fec segments received +} +``` + +使用```kill -SIGUSR1 pid``` 可以在控制台打印出SNMP信息,通常用于精细调整***当前链路的有效载荷比***。 +观察```RetransSegs,FastRetransSegs,LostSegs,OutSegs```这几者的数值比例,用于参考调整```-mode manual,fec```的参数。 + +### *性能对比* :lollipop: +``` +root@vultr:~# iperf -s +------------------------------------------------------------ +Server listening on TCP port 5001 +TCP window size: 4.00 MByte (default) +------------------------------------------------------------ +[ 4] local 172.7.7.1 port 5001 connected with 172.7.7.2 port 55453 +[ ID] Interval Transfer Bandwidth +[ 4] 0.0-18.0 sec 5.50 MBytes 2.56 Mbits/sec <-- connection via kcptun +[ 5] local 45.32.xxx.xxx port 5001 connected with 218.88.xxx.xxx port 17220 +[ 5] 0.0-17.9 sec 2.12 MBytes 997 Kbits/sec <-- direct connnection via tcp +``` + +### *免责申明* :warning: +用户以各种方式使用本软件(包括但不限于修改使用、直接使用、通过第三方使用)的过程中,不得以任何方式利用本软件直接或间接从事违反中国法律、以及社会公德的行为。软件的使用者需对自身行为负责,因使用软件引发的一切纠纷,由使用者承担全部法律及连带责任。作者不承担任何法律及连带责任。 + +对免责声明的解释、修改及更新权均属于作者本人所有。 diff --git a/build-release.sh b/build-release.sh new file mode 100755 index 000000000..a92856670 --- /dev/null +++ b/build-release.sh @@ -0,0 +1,41 @@ +#!/bin/bash +MD5='md5sum' +unamestr=`uname` +if [[ "$unamestr" == 'Darwin' ]]; then + MD5='md5' +fi + +UPX=false +if hash upx 2>/dev/null; then + UPX=true +fi + +VERSION=`date -u +%Y%m%d` +LDFLAGS="-X main.VERSION=$VERSION -s -w" + +OSES=(linux darwin windows freebsd) +ARCHS=(amd64 386) +for os in ${OSES[@]}; do + for arch in ${ARCHS[@]}; do + suffix="" + if [ "$os" == "windows" ] + then + suffix=".exe" + fi + env GOOS=$os GOARCH=$arch go build -ldflags "$LDFLAGS" -o client_${os}_${arch}${suffix} github.com/xtaci/kcptun/client + env GOOS=$os GOARCH=$arch go build -ldflags "$LDFLAGS" -o server_${os}_${arch}${suffix} github.com/xtaci/kcptun/server + if $UPX; then upx -9 client_${os}_${arch}${suffix} server_${os}_${arch}${suffix};fi + tar -zcf kcptun-${os}-${arch}-$VERSION.tar.gz client_${os}_${arch}${suffix} server_${os}_${arch}${suffix} + $MD5 kcptun-${os}-${arch}-$VERSION.tar.gz + done +done + +# ARM +ARMS=(5 6 7) +for v in ${ARMS[@]}; do + env GOOS=linux GOARCH=arm GOARM=$v go build -ldflags "$LDFLAGS" -o client_linux_arm$v github.com/xtaci/kcptun/client + env GOOS=linux GOARCH=arm GOARM=$v go build -ldflags "$LDFLAGS" -o server_linux_arm$v github.com/xtaci/kcptun/server +done +if $UPX; then upx -9 client_linux_arm* server_linux_arm*;fi +tar -zcf kcptun-linux-arm-$VERSION.tar.gz client_linux_arm* server_linux_arm* +$MD5 kcptun-linux-arm-$VERSION.tar.gz diff --git a/client.png b/client.png new file mode 100644 index 000000000..8e9ae5d4c Binary files /dev/null and b/client.png differ diff --git a/client/main.go b/client/main.go new file mode 100644 index 000000000..27f5c9114 --- /dev/null +++ b/client/main.go @@ -0,0 +1,282 @@ +package main + +import ( + "crypto/sha1" + "io" + "log" + "math/rand" + "net" + "os" + "time" + + "golang.org/x/crypto/pbkdf2" + + "github.com/golang/snappy" + "github.com/hashicorp/yamux" + "github.com/urfave/cli" + "github.com/xtaci/kcp-go" +) + +var ( + // VERSION is injected by buildflags + VERSION = "SELFBUILD" + // SALT is use for pbkdf2 key expansion + SALT = "kcp-go" +) + +type compStream struct { + conn net.Conn + w *snappy.Writer + r *snappy.Reader +} + +func (c *compStream) Read(p []byte) (n int, err error) { + return c.r.Read(p) +} + +func (c *compStream) Write(p []byte) (n int, err error) { + n, err = c.w.Write(p) + err = c.w.Flush() + return n, err +} + +func (c *compStream) Close() error { + return c.conn.Close() +} + +func newCompStream(conn net.Conn) *compStream { + c := new(compStream) + c.conn = conn + c.w = snappy.NewBufferedWriter(conn) + c.r = snappy.NewReader(conn) + return c +} + +func handleClient(p1, p2 io.ReadWriteCloser) { + log.Println("stream opened") + defer log.Println("stream closed") + defer p1.Close() + defer p2.Close() + + // start tunnel + p1die := make(chan struct{}) + go func() { + io.Copy(p1, p2) + close(p1die) + }() + + p2die := make(chan struct{}) + go func() { + io.Copy(p2, p1) + close(p2die) + }() + + // wait for tunnel termination + select { + case <-p1die: + case <-p2die: + } +} + +func checkError(err error) { + if err != nil { + log.Println(err) + os.Exit(-1) + } +} + +func main() { + rand.Seed(int64(time.Now().Nanosecond())) + myApp := cli.NewApp() + myApp.Name = "kcptun" + myApp.Usage = "kcptun client" + myApp.Version = VERSION + myApp.Flags = []cli.Flag{ + cli.StringFlag{ + Name: "localaddr,l", + Value: ":12948", + Usage: "local listen address", + }, + cli.StringFlag{ + Name: "remoteaddr, r", + Value: "vps:29900", + Usage: "kcp server address", + }, + cli.StringFlag{ + Name: "key", + Value: "it's a secrect", + Usage: "key for communcation, must be the same as kcptun server", + EnvVar: "KCPTUN_KEY", + }, + cli.StringFlag{ + Name: "crypt", + Value: "aes", + Usage: "methods for encryption: aes, tea, xor, none", + }, + cli.StringFlag{ + Name: "mode", + Value: "fast", + Usage: "mode for communication: fast3, fast2, fast, normal", + }, + cli.IntFlag{ + Name: "conn", + Value: 1, + Usage: "establish N physical connections as specified by 'conn' to server", + }, + cli.IntFlag{ + Name: "mtu", + Value: 1350, + Usage: "set MTU of UDP packets, suggest 'tracepath' to discover path mtu", + }, + cli.IntFlag{ + Name: "sndwnd", + Value: 128, + Usage: "set send window size(num of packets)", + }, + cli.IntFlag{ + Name: "rcvwnd", + Value: 1024, + Usage: "set receive window size(num of packets)", + }, + cli.BoolFlag{ + Name: "nocomp", + Usage: "disable compression", + }, + cli.IntFlag{ + Name: "datashard", + Value: 10, + Usage: "set reed-solomon erasure coding - datashard", + }, + cli.IntFlag{ + Name: "parityshard", + Value: 3, + Usage: "set reed-solomon erasure coding - parityshard", + }, + cli.BoolFlag{ + Name: "acknodelay", + Usage: "flush ack immediately when a packet is received", + }, + cli.IntFlag{ + Name: "dscp", + Value: 0, + Usage: "set DSCP(6bit)", + }, + cli.IntFlag{ + Name: "nodelay", + Value: 0, + Hidden: true, + }, + cli.IntFlag{ + Name: "interval", + Value: 40, + Hidden: true, + }, + cli.IntFlag{ + Name: "resend", + Value: 0, + Hidden: true, + }, + cli.IntFlag{ + Name: "nc", + Value: 0, + Hidden: true, + }, + } + myApp.Action = func(c *cli.Context) { + log.Println("version:", VERSION) + addr, err := net.ResolveTCPAddr("tcp", c.String("localaddr")) + checkError(err) + listener, err := net.ListenTCP("tcp", addr) + checkError(err) + pass := pbkdf2.Key([]byte(c.String("key")), []byte(SALT), 4096, 32, sha1.New) + + // kcp server + nodelay, interval, resend, nc := c.Int("nodelay"), c.Int("interval"), c.Int("resend"), c.Int("nc") + + switch c.String("mode") { + case "normal": + nodelay, interval, resend, nc = 0, 30, 2, 1 + case "fast": + nodelay, interval, resend, nc = 0, 20, 2, 1 + case "fast2": + nodelay, interval, resend, nc = 1, 20, 2, 1 + case "fast3": + nodelay, interval, resend, nc = 1, 10, 2, 1 + } + + log.Println("listening on:", listener.Addr()) + log.Println("encryption:", c.String("crypt")) + log.Println("nodelay parameters:", nodelay, interval, resend, nc) + log.Println("remote address:", c.String("remoteaddr")) + log.Println("sndwnd:", c.Int("sndwnd"), "rcvwnd:", c.Int("rcvwnd")) + log.Println("compression:", !c.Bool("nocomp")) + log.Println("mtu:", c.Int("mtu")) + log.Println("datashard:", c.Int("datashard"), "parityshard:", c.Int("parityshard")) + log.Println("acknodelay:", c.Bool("acknodelay")) + log.Println("dscp:", c.Int("dscp")) + log.Println("conn:", c.Int("conn")) + + createConn := func() *yamux.Session { + var block kcp.BlockCrypt + switch c.String("crypt") { + case "tea": + block, _ = kcp.NewTEABlockCrypt(pass[:16]) + case "xor": + block, _ = kcp.NewSimpleXORBlockCrypt(pass) + case "none": + block, _ = kcp.NewNoneBlockCrypt(pass) + default: + block, _ = kcp.NewAESBlockCrypt(pass) + } + kcpconn, err := kcp.DialWithOptions(c.String("remoteaddr"), block, c.Int("datashard"), c.Int("parityshard")) + checkError(err) + kcpconn.SetNoDelay(nodelay, interval, resend, nc) + kcpconn.SetWindowSize(c.Int("sndwnd"), c.Int("rcvwnd")) + kcpconn.SetMtu(c.Int("mtu")) + kcpconn.SetACKNoDelay(c.Bool("acknodelay")) + kcpconn.SetDSCP(c.Int("dscp")) + + // stream multiplex + config := &yamux.Config{ + AcceptBacklog: 256, + EnableKeepAlive: true, + KeepAliveInterval: 30 * time.Second, + ConnectionWriteTimeout: 30 * time.Second, + MaxStreamWindowSize: 16777216, + LogOutput: os.Stderr, + } + var session *yamux.Session + if c.Bool("nocomp") { + session, err = yamux.Client(kcpconn, config) + } else { + session, err = yamux.Client(newCompStream(kcpconn), config) + } + checkError(err) + return session + } + + numconn := uint16(c.Int("conn")) + var muxes []*yamux.Session + for i := uint16(0); i < numconn; i++ { + muxes = append(muxes, createConn()) + } + + rr := uint16(0) + for { + p1, err := listener.AcceptTCP() + checkError(err) + mux := muxes[rr%numconn] + p2, err := mux.Open() + if err != nil { // yamux failure + log.Println(err) + p1.Close() + mux.Close() + muxes[rr%numconn] = createConn() + continue + } + go handleClient(p1, p2) + rr++ + } + } + myApp.Run(os.Args) +} diff --git a/client/signal.go b/client/signal.go new file mode 100644 index 000000000..1da239d64 --- /dev/null +++ b/client/signal.go @@ -0,0 +1,28 @@ +// +build linux darwin freebsd + +package main + +import ( + "log" + "os" + "os/signal" + "syscall" + + "github.com/xtaci/kcp-go" +) + +func init() { + go sigHandler() +} + +func sigHandler() { + ch := make(chan os.Signal, 1) + signal.Notify(ch, syscall.SIGUSR1) + + for { + switch <-ch { + case syscall.SIGUSR1: + log.Printf("KCP SNMP:%+v", kcp.DefaultSnmp.Copy()) + } + } +} diff --git a/kcptun.png b/kcptun.png new file mode 100644 index 000000000..d951a9157 Binary files /dev/null and b/kcptun.png differ diff --git a/logo.png b/logo.png new file mode 100644 index 000000000..4b5d1e08e Binary files /dev/null and b/logo.png differ diff --git a/rs.png b/rs.png new file mode 100644 index 000000000..3d228c084 Binary files /dev/null and b/rs.png differ diff --git a/server.png b/server.png new file mode 100644 index 000000000..b519ad74c Binary files /dev/null and b/server.png differ diff --git a/server/main.go b/server/main.go new file mode 100644 index 000000000..4b0d92d0b --- /dev/null +++ b/server/main.go @@ -0,0 +1,268 @@ +package main + +import ( + "crypto/sha1" + "io" + "log" + "math/rand" + "net" + "os" + "time" + + "golang.org/x/crypto/pbkdf2" + + "github.com/golang/snappy" + "github.com/hashicorp/yamux" + "github.com/urfave/cli" + "github.com/xtaci/kcp-go" +) + +var ( + // VERSION is injected by buildflags + VERSION = "SELFBUILD" + // SALT is use for pbkdf2 key expansion + SALT = "kcp-go" +) + +type compStream struct { + conn net.Conn + w *snappy.Writer + r *snappy.Reader +} + +func (c *compStream) Read(p []byte) (n int, err error) { + return c.r.Read(p) +} + +func (c *compStream) Write(p []byte) (n int, err error) { + n, err = c.w.Write(p) + err = c.w.Flush() + return n, err +} + +func (c *compStream) Close() error { + return c.conn.Close() +} + +func newCompStream(conn net.Conn) *compStream { + c := new(compStream) + c.conn = conn + c.w = snappy.NewBufferedWriter(conn) + c.r = snappy.NewReader(conn) + return c +} + +// handle multiplex-ed connection +func handleMux(conn io.ReadWriteCloser, target string) { + // stream multiplex + var mux *yamux.Session + config := &yamux.Config{ + AcceptBacklog: 256, + EnableKeepAlive: true, + KeepAliveInterval: 30 * time.Second, + ConnectionWriteTimeout: 30 * time.Second, + MaxStreamWindowSize: 16777216, + LogOutput: os.Stderr, + } + m, err := yamux.Server(conn, config) + if err != nil { + log.Println(err) + return + } + mux = m + defer mux.Close() + + for { + p1, err := mux.Accept() + if err != nil { + log.Println(err) + return + } + p2, err := net.DialTimeout("tcp", target, 5*time.Second) + if err != nil { + log.Println(err) + return + } + go handleClient(p1, p2) + } +} + +func handleClient(p1, p2 io.ReadWriteCloser) { + log.Println("stream opened") + defer log.Println("stream closed") + defer p1.Close() + defer p2.Close() + + // start tunnel + p1die := make(chan struct{}) + go func() { + io.Copy(p1, p2) + close(p1die) + }() + + p2die := make(chan struct{}) + go func() { + io.Copy(p2, p1) + close(p2die) + }() + + // wait for tunnel termination + select { + case <-p1die: + case <-p2die: + } +} + +func main() { + rand.Seed(int64(time.Now().Nanosecond())) + myApp := cli.NewApp() + myApp.Name = "kcptun" + myApp.Usage = "kcptun server" + myApp.Version = VERSION + myApp.Flags = []cli.Flag{ + cli.StringFlag{ + Name: "listen,l", + Value: ":29900", + Usage: "kcp server listen address", + }, + cli.StringFlag{ + Name: "target, t", + Value: "127.0.0.1:12948", + Usage: "target server address", + }, + cli.StringFlag{ + Name: "key", + Value: "it's a secrect", + Usage: "key for communcation, must be the same as kcptun client", + EnvVar: "KCPTUN_KEY", + }, + cli.StringFlag{ + Name: "crypt", + Value: "aes", + Usage: "methods for encryption: aes, tea, xor, none", + }, + cli.StringFlag{ + Name: "mode", + Value: "fast", + Usage: "mode for communication: fast3, fast2, fast, normal", + }, + cli.IntFlag{ + Name: "mtu", + Value: 1350, + Usage: "set MTU of UDP packets, suggest 'tracepath' to discover path mtu", + }, + cli.IntFlag{ + Name: "sndwnd", + Value: 1024, + Usage: "set send window size(num of packets)", + }, + cli.IntFlag{ + Name: "rcvwnd", + Value: 1024, + Usage: "set receive window size(num of packets)", + }, + cli.BoolFlag{ + Name: "nocomp", + Usage: "disable compression", + }, + cli.IntFlag{ + Name: "datashard", + Value: 10, + Usage: "set reed-solomon erasure coding - datashard", + }, + cli.IntFlag{ + Name: "parityshard", + Value: 3, + Usage: "set reed-solomon erasure coding - parityshard", + }, + cli.BoolFlag{ + Name: "acknodelay", + Usage: "flush ack immediately when a packet is received", + }, + cli.IntFlag{ + Name: "dscp", + Value: 0, + Usage: "set DSCP(6bit)", + }, + cli.IntFlag{ + Name: "nodelay", + Value: 0, + Hidden: true, + }, + cli.IntFlag{ + Name: "interval", + Value: 40, + Hidden: true, + }, + cli.IntFlag{ + Name: "resend", + Value: 0, + Hidden: true, + }, + cli.IntFlag{ + Name: "nc", + Value: 0, + Hidden: true, + }, + } + myApp.Action = func(c *cli.Context) { + log.Println("version:", VERSION) + nodelay, interval, resend, nc := c.Int("nodelay"), c.Int("interval"), c.Int("resend"), c.Int("nc") + switch c.String("mode") { + case "normal": + nodelay, interval, resend, nc = 0, 30, 2, 1 + case "fast": + nodelay, interval, resend, nc = 0, 20, 2, 1 + case "fast2": + nodelay, interval, resend, nc = 1, 20, 2, 1 + case "fast3": + nodelay, interval, resend, nc = 1, 10, 2, 1 + } + + pass := pbkdf2.Key([]byte(c.String("key")), []byte(SALT), 4096, 32, sha1.New) + var block kcp.BlockCrypt + switch c.String("crypt") { + case "tea": + block, _ = kcp.NewTEABlockCrypt(pass[:16]) + case "xor": + block, _ = kcp.NewSimpleXORBlockCrypt(pass) + case "none": + block, _ = kcp.NewNoneBlockCrypt(pass) + default: + block, _ = kcp.NewAESBlockCrypt(pass) + } + + lis, err := kcp.ListenWithOptions(c.String("listen"), block, c.Int("datashard"), c.Int("parityshard")) + if err != nil { + log.Fatal(err) + } + log.Println("listening on ", lis.Addr()) + log.Println("encryption:", c.String("crypt")) + log.Println("nodelay parameters:", nodelay, interval, resend, nc) + log.Println("sndwnd:", c.Int("sndwnd"), "rcvwnd:", c.Int("rcvwnd")) + log.Println("compression:", !c.Bool("nocomp")) + log.Println("mtu:", c.Int("mtu")) + log.Println("datashard:", c.Int("datashard"), "parityshard:", c.Int("parityshard")) + log.Println("acknodelay:", c.Bool("acknodelay")) + log.Println("dscp:", c.Int("dscp")) + for { + if conn, err := lis.Accept(); err == nil { + log.Println("remote address:", conn.RemoteAddr()) + conn.SetNoDelay(nodelay, interval, resend, nc) + conn.SetMtu(c.Int("mtu")) + conn.SetWindowSize(c.Int("sndwnd"), c.Int("rcvwnd")) + conn.SetACKNoDelay(c.Bool("acknodelay")) + conn.SetDSCP(c.Int("dscp")) + + if c.Bool("nocomp") { + go handleMux(conn, c.String("target")) + } else { + go handleMux(newCompStream(conn), c.String("target")) + } + } else { + log.Println(err) + } + } + } + myApp.Run(os.Args) +} diff --git a/server/signal.go b/server/signal.go new file mode 100644 index 000000000..1da239d64 --- /dev/null +++ b/server/signal.go @@ -0,0 +1,28 @@ +// +build linux darwin freebsd + +package main + +import ( + "log" + "os" + "os/signal" + "syscall" + + "github.com/xtaci/kcp-go" +) + +func init() { + go sigHandler() +} + +func sigHandler() { + ch := make(chan os.Signal, 1) + signal.Notify(ch, syscall.SIGUSR1) + + for { + switch <-ch { + case syscall.SIGUSR1: + log.Printf("KCP SNMP:%+v", kcp.DefaultSnmp.Copy()) + } + } +}