commit 8b61a91ef0b8737f03c5017eef2736a96b371813 Author: xtaci Date: Sat May 14 15:54:59 2016 +0800 commit message diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..389256f --- /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/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d289640 --- /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 0000000..88a8eea --- /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 0000000..457b794 --- /dev/null +++ b/README.md @@ -0,0 +1,80 @@ +# kcptun(KT) +TCP流转换为KCP+UDP流,>>>[下载地址](https://github.com/xtaci/kcptun/releases/latest)<<< 用于***高丢包***环境中的数据传输,工作示意图: +``` + +---------------------------------------+ + | | + | KCPTUN | + | | ++--------+ | +------------+ +------------+ | +--------+ +| | | | | | | | | | +| Client | +--> | | KCP Client | +---> | KCP Server | | +--> | Server | +| | TCP | | | UDP | | | TCP | | ++--------+ | +------------+ +------------+ | +--------+ + | | + | | + +---------------------------------------+ +``` +***kcptun是[kcp](https://github.com/skywind3000/kcp)协议的一个简单应用,可以用于任意tcp网络程序的传输承载,以提高软件网络流畅度(如浏览器,telnet等),降低掉线情况。*** + + + +### Docker +``` +docker pull xtaci/kcptun +``` + +### 使用方法 +``` +D:\>client_windows_amd64.exe -h +NAME: + kcptun - kcptun client + +USAGE: + client_windows_amd64.exe [global options] command [command options] [arguments...] + +VERSION: + 20160507 + +COMMANDS: + help, h Shows a list of commands or help for one command + +GLOBAL OPTIONS: + --localaddr, -l ":12948" local listen addr: + --remoteaddr, -r "vps:29900" kcp server addr + --key "it's a secrect" key for communcation, must be the same as kcptun server [$KCPTUN_KEY] + --mode "fast" mode for communication: fast, normal, default + --tuncrypt enable tunnel encryption, adds extra secrecy for data transfer + --help, -h show help + --version, -v print the version + +D:\>server_windows_amd64.exe -h +NAME: + kcptun - kcptun server + +USAGE: + server_windows_amd64.exe [global options] command [command options] [arguments...] + +VERSION: + 20160507 + +COMMANDS: + help, h Shows a list of commands or help for one command + +GLOBAL OPTIONS: + --listen, -l ":29900" kcp server listen addr: + --target, -t "127.0.0.1:12948" target server addr + --key "it's a secrect" key for communcation, must be the same as kcptun client [$KCPTUN_KEY] + --mode "fast" mode for communication: fast, normal, default + --tuncrypt enable tunnel encryption, adds extra secrecy for data transfer + --help, -h show help + --version, -v print the version +``` +### 适用范围(包括但不限于): +1. 网络游戏的数据传输 +2. 跨运营商的流量传输 +3. 其他高丢包,高干扰通信环境的TCP数据传输 + +# 免责申明 +用户以各种方式使用本软件(包括但不限于修改使用、直接使用、通过第三方使用)的过程中,不得以任何方式利用本软件直接或间接从事违反中国法律、以及社会公德的行为。软件的使用者需对自身行为负责,因使用软件引发的一切纠纷,由使用者承担全部法律及连带责任。作者不承担任何法律及连带责任。 + +对免责声明的解释、修改及更新权均属于作者本人所有。 diff --git a/build-release.sh b/build-release.sh new file mode 100755 index 0000000..10377e9 --- /dev/null +++ b/build-release.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +VERSION=`date -u +%Y%m%d` +LDFLAGS="-X main.VERSION=$VERSION" + +OSES=(linux darwin windows) +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 + 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 +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/main.go b/client/main.go new file mode 100644 index 0000000..b1cdffb --- /dev/null +++ b/client/main.go @@ -0,0 +1,218 @@ +package main + +import ( + "crypto/aes" + "crypto/cipher" + crand "crypto/rand" + "crypto/sha256" + "io" + "log" + "math/rand" + "net" + "os" + "time" + + "github.com/codegangsta/cli" + "github.com/hashicorp/yamux" + "github.com/xtaci/kcp-go" +) + +var VERSION = "SELFBUILD" + +type secureConn struct { + encoder cipher.Stream + decoder cipher.Stream + conn net.Conn +} + +func newSecureConn(key string, conn net.Conn, iv []byte) *secureConn { + sc := new(secureConn) + sc.conn = conn + commkey := sha256.Sum256([]byte(key)) + + // encoder + block, err := aes.NewCipher(commkey[:]) + if err != nil { + log.Println(err) + return nil + } + sc.encoder = cipher.NewCFBEncrypter(block, iv[:aes.BlockSize]) + + // decoder + block, err = aes.NewCipher(commkey[:]) + if err != nil { + log.Println(err) + return nil + } + sc.decoder = cipher.NewCFBDecrypter(block, iv[aes.BlockSize:]) + return sc +} + +func (sc *secureConn) Read(p []byte) (n int, err error) { + n, err = sc.conn.Read(p) + if err == nil { + sc.decoder.XORKeyStream(p[:n], p[:n]) + } + return +} + +func (sc *secureConn) Write(p []byte) (n int, err error) { + sc.encoder.XORKeyStream(p, p) + return sc.conn.Write(p) +} + +func (sc *secureConn) Close() (err error) { + return sc.conn.Close() +} + +func handleClient(p1, p2 net.Conn) { + 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 addr:", + }, + cli.StringFlag{ + Name: "remoteaddr, r", + Value: "vps:29900", + Usage: "kcp server addr", + }, + 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: "mode", + Value: "fast", + Usage: "mode for communication: fast, normal, default", + }, + cli.BoolFlag{ + Name: "tuncrypt", + Usage: "enable tunnel encryption, adds extra secrecy for data transfer", + }, + cli.IntFlag{ + Name: "mtu", + Value: 1400, + 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)", + }, + } + 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) + log.Println("listening on:", listener.Addr()) + + START_KCP: + var mode kcp.Mode + switch c.String("mode") { + case "normal": + mode = kcp.MODE_NORMAL + case "default": + mode = kcp.MODE_DEFAULT + case "fast": + mode = kcp.MODE_FAST + default: + log.Println("unrecognized mode:", c.String("mode")) + return + } + log.Println("communication mode:", c.String("mode")) + // kcp server + kcpconn, err := kcp.DialEncrypted(mode, c.String("remoteaddr"), []byte(c.String("key"))) + checkError(err) + kcpconn.SetRetries(50) + log.Println("remote address:", c.String("remoteaddr")) + kcpconn.SetWindowSize(c.Int("sndwnd"), c.Int("rcvwnd")) + log.Println("sndwnd:", c.Int("sndwnd"), "rcvwnd:", c.Int("rcvwnd")) + kcpconn.SetMtu(c.Int("mtu")) + log.Println("mtu:", c.Int("mtu")) + + // generate & send iv + iv := make([]byte, 2*aes.BlockSize) + io.ReadFull(crand.Reader, iv) + _, err = kcpconn.Write(iv) + checkError(err) + + // stream multiplex + var mux *yamux.Session + if c.Bool("tuncrypt") { + scon := newSecureConn(c.String("key"), kcpconn, iv) + session, err := yamux.Client(scon, nil) + checkError(err) + mux = session + } else { + session, err := yamux.Client(kcpconn, nil) + checkError(err) + mux = session + } + log.Println("tunnel encryption:", c.Bool("tuncrypt")) + + for { + p1, err := listener.AcceptTCP() + if err != nil { + log.Println(err) + continue + } + p2, err := mux.Open() + if err != nil { // yamux failure + log.Println(err) + kcpconn.Close() + p1.Close() + goto START_KCP + } + go handleClient(p1, p2) + } + } + myApp.Run(os.Args) +} diff --git a/kitty.jpg b/kitty.jpg new file mode 100644 index 0000000..7b3f372 Binary files /dev/null and b/kitty.jpg differ diff --git a/server/main.go b/server/main.go new file mode 100644 index 0000000..5e7f338 --- /dev/null +++ b/server/main.go @@ -0,0 +1,227 @@ +package main + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/sha256" + "io" + "log" + "math/rand" + "net" + "os" + "time" + + "github.com/codegangsta/cli" + "github.com/hashicorp/yamux" + "github.com/xtaci/kcp-go" +) + +var VERSION = "SELFBUILD" + +type secureConn struct { + encoder cipher.Stream + decoder cipher.Stream + conn net.Conn +} + +func newSecureConn(key string, conn net.Conn, iv []byte) *secureConn { + sc := new(secureConn) + sc.conn = conn + commkey := sha256.Sum256([]byte(key)) + + // encoder + block, err := aes.NewCipher(commkey[:]) + if err != nil { + log.Println(err) + return nil + } + sc.encoder = cipher.NewCFBEncrypter(block, iv[aes.BlockSize:]) + + // decoder + block, err = aes.NewCipher(commkey[:]) + if err != nil { + log.Println(err) + return nil + } + sc.decoder = cipher.NewCFBDecrypter(block, iv[:aes.BlockSize]) + return sc +} + +func (sc *secureConn) Read(p []byte) (n int, err error) { + n, err = sc.conn.Read(p) + if err == nil { + sc.decoder.XORKeyStream(p[:n], p[:n]) + } + return +} + +func (sc *secureConn) Write(p []byte) (n int, err error) { + sc.encoder.XORKeyStream(p, p) + return sc.conn.Write(p) +} + +func (sc *secureConn) Close() (err error) { + return sc.conn.Close() +} + +// handle multiplex-ed connection +func handleMux(conn *kcp.UDPSession, key, target string, tuncrypt bool, mtu, sndwnd, rcvwnd int) { + conn.SetRetries(50) + conn.SetWindowSize(1024, 1024) + conn.SetDeadline(time.Now().Add(2 * time.Second)) + conn.SetMtu(mtu) + conn.SetWindowSize(sndwnd, rcvwnd) + // read iv + iv := make([]byte, 2*aes.BlockSize) + if _, err := io.ReadFull(conn, iv); err != nil { + log.Println(err) + conn.Close() + return + } + conn.SetDeadline(time.Time{}) + + // stream multiplex + var mux *yamux.Session + if tuncrypt { + scon := newSecureConn(key, conn, iv) + m, err := yamux.Server(scon, nil) + if err != nil { + log.Println(err) + return + } + mux = m + } else { + m, err := yamux.Server(conn, nil) + 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.Dial("tcp", target) + if err != nil { + log.Println(err) + return + } + go handleClient(p1, p2) + } +} + +func handleClient(p1, p2 net.Conn) { + 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 addr:", + }, + cli.StringFlag{ + Name: "target, t", + Value: "127.0.0.1:12948", + Usage: "target server addr", + }, + 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: "mode", + Value: "fast", + Usage: "mode for communication: fast, normal, default", + }, + cli.BoolFlag{ + Name: "tuncrypt", + Usage: "enable tunnel encryption, adds extra secrecy for data transfer", + }, + cli.IntFlag{ + Name: "mtu", + Value: 1400, + 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)", + }, + } + myApp.Action = func(c *cli.Context) { + log.Println("version:", VERSION) + // KCP listen + var mode kcp.Mode + switch c.String("mode") { + case "normal": + mode = kcp.MODE_NORMAL + case "default": + mode = kcp.MODE_DEFAULT + case "fast": + mode = kcp.MODE_FAST + default: + log.Println("unrecognized mode:", c.String("mode")) + return + } + + lis, err := kcp.ListenEncrypted(mode, c.String("listen"), []byte(c.String("key"))) + if err != nil { + log.Fatal(err) + } + log.Println("listening on ", lis.Addr()) + log.Println("communication mode:", c.String("mode")) + log.Println("sndwnd:", c.Int("sndwnd"), "rcvwnd:", c.Int("rcvwnd")) + log.Println("mtu:", c.Int("mtu")) + log.Println("tunnel encryption:", c.Bool("tuncrypt")) + for { + if conn, err := lis.Accept(); err == nil { + log.Println("remote address:", conn.RemoteAddr()) + go handleMux(conn, c.String("key"), c.String("target"), c.Bool("tuncrypt"), c.Int("mtu"), c.Int("sndwnd"), c.Int("rcvwnd")) + } else { + log.Println(err) + } + } + } + myApp.Run(os.Args) +}