├── FEC.png ├── fast.png ├── logo.png ├── kcptun.png ├── logo-small.png ├── layeredparams.png ├── wechat_donate.jpg ├── 0x2e4b43ab3d0983da282592571eef61ae5e60f726.png ├── client ├── tfo_darwin.go ├── tfo_freebsd.go ├── tfo_other.go ├── signal.go ├── tfo.go ├── tfo_linux.go ├── config.go ├── tfo_bsd.go ├── services.go └── main.go ├── server ├── tfo_darwin.go ├── tfo_freebsd.go ├── tfo_other.go ├── signal.go ├── tfo.go ├── tfo_linux.go ├── config.go ├── services.go ├── tfo_bsd.go └── main.go ├── Dockerfile ├── .gitignore ├── .travis.yml ├── go.mod ├── LICENSE.md ├── .github └── ISSUE_TEMPLATE ├── go.sum └── README.md /FEC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cs8425/kcptunB/HEAD/FEC.png -------------------------------------------------------------------------------- /fast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cs8425/kcptunB/HEAD/fast.png -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cs8425/kcptunB/HEAD/logo.png -------------------------------------------------------------------------------- /kcptun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cs8425/kcptunB/HEAD/kcptun.png -------------------------------------------------------------------------------- /logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cs8425/kcptunB/HEAD/logo-small.png -------------------------------------------------------------------------------- /layeredparams.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cs8425/kcptunB/HEAD/layeredparams.png -------------------------------------------------------------------------------- /wechat_donate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cs8425/kcptunB/HEAD/wechat_donate.jpg -------------------------------------------------------------------------------- /0x2e4b43ab3d0983da282592571eef61ae5e60f726.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cs8425/kcptunB/HEAD/0x2e4b43ab3d0983da282592571eef61ae5e60f726.png -------------------------------------------------------------------------------- /client/tfo_darwin.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "syscall" 5 | ) 6 | 7 | const TCP_FASTOPEN int = 261 // 0x105 8 | const opt_LEVEL = syscall.IPPROTO_TCP //syscall.SOL_SOCKET // syscall.SOCK_STREAM 9 | // not support TCP_FASTOPEN_CONNECT 10 | 11 | -------------------------------------------------------------------------------- /client/tfo_freebsd.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "syscall" 5 | ) 6 | 7 | const TCP_FASTOPEN int = 1025 // 0x401 8 | const opt_LEVEL = syscall.IPPROTO_TCP //syscall.SOL_SOCKET // syscall.SOCK_STREAM 9 | // not support TCP_FASTOPEN_CONNECT 10 | 11 | -------------------------------------------------------------------------------- /server/tfo_darwin.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "syscall" 5 | ) 6 | 7 | const TCP_FASTOPEN int = 261 // 0x105 8 | const opt_LEVEL = syscall.IPPROTO_TCP //syscall.SOL_SOCKET // syscall.SOCK_STREAM 9 | // not support TCP_FASTOPEN_CONNECT 10 | 11 | -------------------------------------------------------------------------------- /server/tfo_freebsd.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "syscall" 5 | ) 6 | 7 | const TCP_FASTOPEN int = 1025 // 0x401 8 | const opt_LEVEL = syscall.IPPROTO_TCP //syscall.SOL_SOCKET // syscall.SOCK_STREAM 9 | // not support TCP_FASTOPEN_CONNECT 10 | 11 | -------------------------------------------------------------------------------- /client/tfo_other.go: -------------------------------------------------------------------------------- 1 | // +build !darwin,!freebsd,!linux 2 | 3 | package main 4 | 5 | import ( 6 | "net" 7 | "time" 8 | ) 9 | 10 | func bindTFO(listener *net.TCPListener) { } 11 | 12 | func handleTFO(p1 net.Conn, target string, timeout time.Duration) (net.Conn, error) { 13 | dialer := &net.Dialer{} 14 | if timeout != 0 { 15 | dialer.Timeout = timeout 16 | } 17 | return dialer.Dial("tcp", target) 18 | } 19 | 20 | -------------------------------------------------------------------------------- /server/tfo_other.go: -------------------------------------------------------------------------------- 1 | // +build !darwin,!freebsd,!linux 2 | 3 | package main 4 | 5 | import ( 6 | "net" 7 | "time" 8 | ) 9 | 10 | func bindTFO(listener *net.TCPListener) { } 11 | 12 | func handleTFO(p1 net.Conn, target string, timeout time.Duration) (net.Conn, error) { 13 | dialer := &net.Dialer{} 14 | if timeout != 0 { 15 | dialer.Timeout = timeout 16 | } 17 | return dialer.Dial("tcp", target) 18 | } 19 | 20 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine as builder 2 | MAINTAINER xtaci 3 | RUN apk update && \ 4 | apk upgrade && \ 5 | apk add git 6 | RUN go get -ldflags "-X main.VERSION=$(date -u +%Y%m%d) -s -w" github.com/xtaci/kcptun/client && go get -ldflags "-X main.VERSION=$(date -u +%Y%m%d) -s -w" github.com/xtaci/kcptun/server 7 | 8 | FROM alpine:3.6 9 | COPY --from=builder /go/bin /bin 10 | EXPOSE 29900/udp 11 | EXPOSE 12948 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | client/client 26 | server/server 27 | !build/.keepme 28 | build/ 29 | -------------------------------------------------------------------------------- /client/signal.go: -------------------------------------------------------------------------------- 1 | // +build linux darwin freebsd 2 | 3 | package main 4 | 5 | import ( 6 | "log" 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | 11 | kcp "github.com/xtaci/kcp-go" 12 | ) 13 | 14 | func init() { 15 | go sigHandler() 16 | } 17 | 18 | func sigHandler() { 19 | ch := make(chan os.Signal, 1) 20 | signal.Notify(ch, syscall.SIGUSR1) 21 | signal.Ignore(syscall.SIGPIPE) 22 | 23 | for { 24 | switch <-ch { 25 | case syscall.SIGUSR1: 26 | log.Printf("KCP SNMP:%+v", kcp.DefaultSnmp.Copy()) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /server/signal.go: -------------------------------------------------------------------------------- 1 | // +build linux darwin freebsd 2 | 3 | package main 4 | 5 | import ( 6 | "log" 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | 11 | kcp "github.com/xtaci/kcp-go" 12 | ) 13 | 14 | func init() { 15 | go sigHandler() 16 | } 17 | 18 | func sigHandler() { 19 | ch := make(chan os.Signal, 1) 20 | signal.Notify(ch, syscall.SIGUSR1) 21 | signal.Ignore(syscall.SIGPIPE) 22 | 23 | for { 24 | switch <-ch { 25 | case syscall.SIGUSR1: 26 | log.Printf("KCP SNMP:%+v", kcp.DefaultSnmp.Copy()) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /client/tfo.go: -------------------------------------------------------------------------------- 1 | // +build linux freebsd darwin 2 | 3 | package main 4 | 5 | import ( 6 | "net" 7 | "syscall" 8 | "fmt" 9 | ) 10 | 11 | func bindTFO(listener *net.TCPListener) { 12 | rawconn, err := listener.SyscallConn() 13 | if err != nil { 14 | return 15 | } 16 | 17 | rawconn.Control(func(fd uintptr) { 18 | err := syscall.SetsockoptInt(int(fd), opt_LEVEL, TCP_FASTOPEN, 1) 19 | if err != nil { 20 | fmt.Printf("Failed to set necessary TCP_FASTOPEN socket option: %s\n", err) 21 | return 22 | } 23 | }) 24 | } 25 | 26 | -------------------------------------------------------------------------------- /server/tfo.go: -------------------------------------------------------------------------------- 1 | // +build linux freebsd darwin 2 | 3 | package main 4 | 5 | import ( 6 | "net" 7 | "syscall" 8 | "fmt" 9 | ) 10 | 11 | func bindTFO(listener *net.TCPListener) { 12 | rawconn, err := listener.SyscallConn() 13 | if err != nil { 14 | return 15 | } 16 | 17 | rawconn.Control(func(fd uintptr) { 18 | err := syscall.SetsockoptInt(int(fd), opt_LEVEL, TCP_FASTOPEN, 1) 19 | if err != nil { 20 | fmt.Printf("Failed to set necessary TCP_FASTOPEN socket option: %s\n", err) 21 | return 22 | } 23 | }) 24 | } 25 | 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.10.x 4 | - 1.11.x 5 | - 1.12.x 6 | 7 | before_install: 8 | - go get github.com/mattn/goveralls 9 | - go get golang.org/x/tools/cmd/cover 10 | 11 | install: 12 | - go get github.com/xtaci/kcptun/client 13 | - go get github.com/xtaci/kcptun/server 14 | 15 | before_script: 16 | script: 17 | - cd $HOME/gopath/src/github.com/xtaci/kcptun/client 18 | - $HOME/gopath/bin/goveralls -service=travis-ci 19 | - cd $HOME/gopath/src/github.com/xtaci/kcptun/server 20 | - $HOME/gopath/bin/goveralls -service=travis-ci 21 | - exit 0 22 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cs8425/kcptunB 2 | 3 | require ( 4 | github.com/cs8425/smux v2.0.0+incompatible 5 | github.com/golang/snappy v0.0.1 6 | github.com/klauspost/cpuid v1.2.0 // indirect 7 | github.com/klauspost/reedsolomon v1.9.1 // indirect 8 | github.com/pkg/errors v0.8.1 9 | github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect 10 | github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b // indirect 11 | github.com/tjfoc/gmsm v1.0.1 // indirect 12 | github.com/urfave/cli v1.20.0 13 | github.com/xtaci/kcp-go v5.1.3+incompatible 14 | github.com/xtaci/smux v1.1.1 15 | golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576 16 | golang.org/x/net v0.0.0-20190322120337-addf6b3196f6 // indirect 17 | golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /client/tfo_linux.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package main 4 | 5 | import ( 6 | "syscall" 7 | "net" 8 | "time" 9 | "fmt" 10 | ) 11 | 12 | const TCP_FASTOPEN int = 23 // 0x17 13 | const TCP_FASTOPEN_CONNECT int = 30 // 0x1e 14 | 15 | const opt_LEVEL = syscall.SOL_TCP 16 | 17 | func getTFODialer(timeout time.Duration) *net.Dialer { 18 | dialer := &net.Dialer{} 19 | if timeout != 0 { 20 | dialer.Timeout = timeout 21 | } 22 | dialer.Control = func(network, address string, c syscall.RawConn) error { 23 | c.Control(func(fd uintptr) { 24 | err := syscall.SetsockoptInt(int(fd), opt_LEVEL, TCP_FASTOPEN_CONNECT, 1) 25 | if err != nil { 26 | fmt.Printf("Failed to set necessary TCP_FASTOPEN_CONNECT socket option: %s", err) 27 | return 28 | } 29 | }) 30 | return nil 31 | } 32 | 33 | return dialer 34 | } 35 | 36 | func handleTFO(p1 net.Conn, target string, timeout time.Duration) (net.Conn, error) { 37 | return getTFODialer(timeout).Dial("tcp", target) 38 | } 39 | 40 | -------------------------------------------------------------------------------- /server/tfo_linux.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package main 4 | 5 | import ( 6 | "syscall" 7 | "net" 8 | "time" 9 | "fmt" 10 | ) 11 | 12 | const TCP_FASTOPEN int = 23 // 0x17 13 | const TCP_FASTOPEN_CONNECT int = 30 // 0x1e 14 | 15 | const opt_LEVEL = syscall.SOL_TCP 16 | 17 | func getTFODialer(timeout time.Duration) *net.Dialer { 18 | dialer := &net.Dialer{} 19 | if timeout != 0 { 20 | dialer.Timeout = timeout 21 | } 22 | dialer.Control = func(network, address string, c syscall.RawConn) error { 23 | c.Control(func(fd uintptr) { 24 | err := syscall.SetsockoptInt(int(fd), opt_LEVEL, TCP_FASTOPEN_CONNECT, 1) 25 | if err != nil { 26 | fmt.Printf("Failed to set necessary TCP_FASTOPEN_CONNECT socket option: %s", err) 27 | return 28 | } 29 | }) 30 | return nil 31 | } 32 | 33 | return dialer 34 | } 35 | 36 | func handleTFO(p1 net.Conn, target string, timeout time.Duration) (net.Conn, error) { 37 | return getTFODialer(timeout).Dial("tcp", target) 38 | } 39 | 40 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Daniel Fu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE: -------------------------------------------------------------------------------- 1 | 问问题前先搜索ISSUE,并搞清楚下面的问题: 2 | 3 | 1. 检查 ```-key xxx``` 至少三遍, ***保证***两边一致。 4 | 2. 保证```-nocomp, -datashard, -parityshard, -key, -crypt```两边一致。 5 | 3. 是否在服务器端,正确设定了转发的目标服务器地址 ***--target***。 6 | 4. 如果第3条不确定,尝试在服务器上telnet target port试试。 7 | 5. 防火墙是否关闭了UDP通信。 8 | 6. 两端的版本是否一致? 9 | 7. 是不是最新版本? 10 | 8. 两端分别是什么操作系统? 11 | 9. 两端的输出日志是什么? 12 | 13 | Before firing issue, make sure you figured out the following common questions. 14 | 15 | PLEASE DO SEARCH FIRST. 16 | 17 | 1. Check your ```-key xxx``` for at least 3 times, ***MAKE SURE*** both sides share the same secret. 18 | 2. ```-nocomp, -datashard, -parityshard, -key, -crypt``` ***must be the same*** on both side. 19 | 3. Did you correctly set the ***-target*** on the server side? 20 | 4. ***MAKE SURE*** ```telnet target port``` on your server successful(don't ask me why couldn't). 21 | 5. Does your ***firewall allows UDP*** communications? (including your ISP Cable-Modem) 22 | 6. Are you using the **same version** for both client & server 23 | 7. Are you using the **latest release**? 24 | 8. Which **OS** do you use? 25 | 9. Which end for this issue related to, **client or server**? 26 | 27 | 28 | -------------------------------------------------------------------------------- /server/config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | ) 7 | 8 | // Config for server 9 | type Config struct { 10 | Listen string `json:"listen"` 11 | Target string `json:"target"` 12 | Key string `json:"key"` 13 | Crypt string `json:"crypt"` 14 | Mode string `json:"mode"` 15 | MTU int `json:"mtu"` 16 | SndWnd int `json:"sndwnd"` 17 | RcvWnd int `json:"rcvwnd"` 18 | DataShard int `json:"datashard"` 19 | ParityShard int `json:"parityshard"` 20 | DSCP int `json:"dscp"` 21 | NoComp bool `json:"nocomp"` 22 | AckNodelay bool `json:"acknodelay"` 23 | NoDelay int `json:"nodelay"` 24 | Interval int `json:"interval"` 25 | Resend int `json:"resend"` 26 | NoCongestion int `json:"nc"` 27 | Log string `json:"log"` 28 | SnmpLog string `json:"snmplog"` 29 | SnmpPeriod int `json:"snmpperiod"` 30 | Pprof bool `json:"pprof"` 31 | Quiet bool `json:"quiet"` 32 | 33 | // smux setting 34 | KeepAlive int `json:"keepalive"` 35 | KeepAliveMS int `json:"keepalivems"` 36 | KeepAliveTimeout int `json:"keepalive-timeout"` 37 | SockBuf int `json:"sockbuf"` 38 | StreamBuf int `json:"streambuf"` 39 | StreamBufEn bool `json:"streambuf-en"` 40 | BoostTimeout int `json:"boosttimeout"` 41 | MaxFrameSize int `json:"maxframe"` 42 | PipeBuf int `json:"pipebuf"` 43 | 44 | // extra 45 | DNS []string `json:"dns"` 46 | Service string `json:"ser"` 47 | TFO bool `json:"tfo"` 48 | } 49 | 50 | func parseJSONConfig(config *Config, path string) error { 51 | file, err := os.Open(path) // For read access. 52 | if err != nil { 53 | return err 54 | } 55 | defer file.Close() 56 | 57 | return json.NewDecoder(file).Decode(config) 58 | } 59 | -------------------------------------------------------------------------------- /client/config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | ) 7 | 8 | // Config for client 9 | type Config struct { 10 | LocalAddr string `json:"localaddr"` 11 | RemoteAddr string `json:"remoteaddr"` 12 | Key string `json:"key"` 13 | Crypt string `json:"crypt"` 14 | Mode string `json:"mode"` 15 | Conn int `json:"conn"` 16 | AutoExpire int `json:"autoexpire"` 17 | ScavengeTTL int `json:"scavengettl"` 18 | MTU int `json:"mtu"` 19 | SndWnd int `json:"sndwnd"` 20 | RcvWnd int `json:"rcvwnd"` 21 | DataShard int `json:"datashard"` 22 | ParityShard int `json:"parityshard"` 23 | DSCP int `json:"dscp"` 24 | NoComp bool `json:"nocomp"` 25 | AckNodelay bool `json:"acknodelay"` 26 | NoDelay int `json:"nodelay"` 27 | Interval int `json:"interval"` 28 | Resend int `json:"resend"` 29 | NoCongestion int `json:"nc"` 30 | Log string `json:"log"` 31 | SnmpLog string `json:"snmplog"` 32 | SnmpPeriod int `json:"snmpperiod"` 33 | Quiet bool `json:"quiet"` 34 | 35 | // smux setting 36 | KeepAlive int `json:"keepalive"` 37 | KeepAliveMS int `json:"keepalivems"` 38 | KeepAliveTimeout int `json:"keepalive-timeout"` 39 | SockBuf int `json:"sockbuf"` 40 | StreamBuf int `json:"streambuf"` 41 | StreamBufEn bool `json:"streambuf-en"` 42 | BoostTimeout int `json:"boosttimeout"` 43 | MaxFrameSize int `json:"maxframe"` 44 | PipeBuf int `json:"pipebuf"` 45 | 46 | // extra 47 | Service string `json:"ser"` 48 | TFO bool `json:"tfo"` 49 | } 50 | 51 | func parseJSONConfig(config *Config, path string) error { 52 | file, err := os.Open(path) // For read access. 53 | if err != nil { 54 | return err 55 | } 56 | defer file.Close() 57 | 58 | return json.NewDecoder(file).Decode(config) 59 | } 60 | -------------------------------------------------------------------------------- /server/services.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "net" 7 | "strconv" 8 | "time" 9 | ) 10 | 11 | func replyAndClose(p1 net.Conn, rpy int) { 12 | p1.Write([]byte{0x05, byte(rpy), 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) 13 | p1.Close() 14 | } 15 | 16 | func handleFast(p1 net.Conn, quiet bool, buffersize int, tfo bool) { 17 | var b [320]byte 18 | n, err := p1.Read(b[:]) 19 | if err != nil { 20 | Vlogln(quiet, "[fast client read]", p1, err) 21 | return 22 | } 23 | // b[0:2] // ignore 24 | 25 | var host, port, backend string 26 | switch b[3] { 27 | case 0x01: //IP V4 28 | host = net.IPv4(b[4], b[5], b[6], b[7]).String() 29 | case 0x03: //DOMAINNAME 30 | host = string(b[5 : n-2]) //b[4] domain name length 31 | case 0x04: //IP V6 32 | host = net.IP{b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15], b[16], b[17], b[18], b[19]}.String() 33 | case 0x05: //DOMAINNAME + PORT 34 | backend = string(b[4 : n]) 35 | goto CONN 36 | default: 37 | replyAndClose(p1, 0x08) // X'08' Address type not supported 38 | return 39 | } 40 | port = strconv.Itoa(int(b[n-2])<<8 | int(b[n-1])) 41 | backend = net.JoinHostPort(host, port) 42 | 43 | 44 | CONN: 45 | var p2 net.Conn 46 | if tfo { 47 | p2, err = handleTFO(p1, backend, 5 * time.Second) 48 | } else { 49 | p2, err = net.DialTimeout("tcp", backend, 5 * time.Second) 50 | } 51 | if err != nil { 52 | Vlogln(quiet, "[err]", backend, err) 53 | 54 | switch t := err.(type) { 55 | case *net.AddrError: 56 | replyAndClose(p1, 0x03) // X'03' Network unreachable 57 | 58 | case *net.OpError: 59 | if t.Timeout() { 60 | replyAndClose(p1, 0x06) // X'06' TTL expired 61 | } else if t.Op == "dial" { 62 | replyAndClose(p1, 0x05) // X'05' Connection refused 63 | } 64 | 65 | default: 66 | //replyAndClose(p1, 0x03) // X'03' Network unreachable 67 | //replyAndClose(p1, 0x04) // X'04' Host unreachable 68 | replyAndClose(p1, 0x05) // X'05' Connection refused 69 | //replyAndClose(p1, 0x06) // X'06' TTL expired 70 | } 71 | return 72 | } 73 | defer p2.Close() 74 | 75 | Vlogln(quiet, "[got]", backend) 76 | reply := []byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} 77 | p1.Write(reply) // reply OK 78 | 79 | cp(p1, p2, buffersize) 80 | Vlogln(quiet, "[cls]", backend) 81 | } 82 | 83 | func handleClient(p1 net.Conn, quiet bool, buffersize int, serv string, target string, tfo bool) { 84 | if !quiet { 85 | log.Println("stream opened") 86 | defer log.Println("stream closed") 87 | } 88 | 89 | defer p1.Close() 90 | switch serv { 91 | default: 92 | fallthrough 93 | case "raw": 94 | 95 | var p2 net.Conn 96 | var err error 97 | if tfo { 98 | p2, err = handleTFO(p1, target, 5 * time.Second) 99 | } else { 100 | p2, err = net.DialTimeout("tcp", target, 5 * time.Second) 101 | } 102 | if err != nil { 103 | Vlogln(quiet, "[connect err]", target, err) 104 | return 105 | } 106 | defer p2.Close() 107 | cp(p1, p2, buffersize) 108 | 109 | case "fast": 110 | handleFast(p1, quiet, buffersize, tfo) 111 | } 112 | 113 | } 114 | 115 | func cp(p1 net.Conn, p2 net.Conn, buffersize int) { 116 | defer p2.Close() 117 | 118 | // start tunnel 119 | p1die := make(chan struct{}) 120 | buf1 := make([]byte, buffersize) 121 | go func() { io.CopyBuffer(p1, p2, buf1); close(p1die) }() 122 | 123 | p2die := make(chan struct{}) 124 | buf2 := make([]byte, buffersize) 125 | go func() { io.CopyBuffer(p2, p1, buf2); close(p2die) }() 126 | 127 | // wait for tunnel termination 128 | select { 129 | case <-p1die: 130 | case <-p2die: 131 | } 132 | } 133 | 134 | func Vlogln(quiet bool, v ...interface{}) { 135 | if !quiet { 136 | log.Println(v...) 137 | } 138 | } 139 | 140 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cs8425/kcptunB v0.0.0-20170426031648-25bad3c03cf5 h1:ELNBaoUZgV1cDKdiiTmuXdjjvYjrOUMxt8gCj/GNk+8= 2 | github.com/cs8425/kcptunB v0.0.0-20170426031648-25bad3c03cf5/go.mod h1:8WjlJRaM5EM5p4Ke58WHSI6Qga4wEaishWiDBPbhd84= 3 | github.com/cs8425/smux v2.0.0+incompatible h1:QUahrZWQMMZLB+zVgr/N1QbpHq8iJe2JXlehiBQLk+w= 4 | github.com/cs8425/smux v2.0.0+incompatible/go.mod h1:Cc2CPCRECAz4mDfIUfWGHKbFkGMhJjlYf+2OmRXo5bg= 5 | github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= 6 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 7 | github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE= 8 | github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 9 | github.com/klauspost/reedsolomon v1.9.1 h1:kYrT1MlR4JH6PqOpC+okdb9CDTcwEC/BqpzK4WFyXL8= 10 | github.com/klauspost/reedsolomon v1.9.1/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4= 11 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 12 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 13 | github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU= 14 | github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU= 15 | github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b h1:mnG1fcsIB1d/3vbkBak2MM0u+vhGhlQwpeimUi7QncM= 16 | github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4= 17 | github.com/tjfoc/gmsm v1.0.1 h1:R11HlqhXkDospckjZEihx9SW/2VW0RgdwrykyWMFOQU= 18 | github.com/tjfoc/gmsm v1.0.1/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc= 19 | github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= 20 | github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= 21 | github.com/xtaci/kcp-go v5.0.7+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE= 22 | github.com/xtaci/kcp-go v5.1.1+incompatible h1:A6zXUGblo98vosfEdaHcy0cTBZKY2dByJxICuaV+L5g= 23 | github.com/xtaci/kcp-go v5.1.1+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE= 24 | github.com/xtaci/kcp-go v5.1.2+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE= 25 | github.com/xtaci/kcp-go v5.1.3+incompatible h1:s96+ulBrZlxk4DPRPgEGPBV8o55kYKKFVivDwVgZNcA= 26 | github.com/xtaci/kcp-go v5.1.3+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE= 27 | github.com/xtaci/smux v1.1.1 h1:ZyIo9XHuHkAeENzHR8yGWC+6xUSCTeP2tPTRE8mnLvc= 28 | github.com/xtaci/smux v1.1.1/go.mod h1:f+nYm6SpuHMy/SH0zpbvAFHT1QoMcgLOsWcFip5KfPw= 29 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 30 | golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576 h1:aUX/1G2gFSs4AsJJg2cL3HuoRhCSCz733FE5GUSuaT4= 31 | golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 32 | golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53 h1:kcXqo9vE6fsZY5X5Rd7R1l7fTgnWaDCVmln65REefiE= 33 | golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 34 | golang.org/x/net v0.0.0-20190322120337-addf6b3196f6 h1:78jEq2G3J16aXneH23HSnTQQTCwMHoyO8VEiUH+bpPM= 35 | golang.org/x/net v0.0.0-20190322120337-addf6b3196f6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 36 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 37 | golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 38 | golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 39 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 40 | -------------------------------------------------------------------------------- /client/tfo_bsd.go: -------------------------------------------------------------------------------- 1 | // +build freebsd darwin 2 | 3 | package main 4 | 5 | import ( 6 | "net" 7 | "os" 8 | "syscall" 9 | "time" 10 | ) 11 | 12 | func handleTFO(p1 net.Conn, target string, timeout time.Duration) (net.Conn, error) { 13 | buf := make([]byte, 4096) // TODO: export setting 14 | p1.SetReadDeadline(time.Now().Add(50 * time.Millisecond)) // TODO: export setting 15 | n, err := p1.Read(buf) // try read first packet data 16 | if err, ok := err.(net.Error); ok && err.Timeout() { // read data timeout 17 | p1.SetReadDeadline(time.Time{}) 18 | return net.DialTimeout("tcp", target, timeout) 19 | //return DialTFO(target, buf[:n]) 20 | } 21 | if err != nil { 22 | return nil, err 23 | } 24 | p1.SetReadDeadline(time.Time{}) 25 | 26 | return DialTFO(target, buf[:n]) 27 | } 28 | 29 | func DialTFO(address string, data []byte) (net.Conn, error) { 30 | raddr, err := net.ResolveTCPAddr("tcp", address) 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | var sa syscall.Sockaddr 36 | family := syscall.AF_INET 37 | ip6 := raddr.IP.To16() 38 | ip4 := raddr.IP.To4() 39 | 40 | switch { 41 | case ip6 != nil: //IPv6 42 | family = syscall.AF_INET6 43 | sa6 := &syscall.SockaddrInet6{Port: raddr.Port, ZoneId: uint32(IP6ZoneToInt(raddr.Zone))} 44 | copy(sa6.Addr[:], ip6) 45 | sa = sa6 46 | 47 | case ip4 != nil: 48 | family = syscall.AF_INET 49 | sa4 := &syscall.SockaddrInet4{Port: raddr.Port} 50 | copy(sa4.Addr[:], ip4) 51 | sa = sa4 52 | } 53 | 54 | fd, err := syscall.Socket(family, syscall.SOCK_STREAM, 0) 55 | if err != nil { 56 | return nil, err 57 | } 58 | defer syscall.Close(fd) 59 | 60 | for { 61 | //err = syscall.Sendto(fd, data, syscall.MSG_FASTOPEN, sa) // linux version 62 | err = syscall.Sendto(fd, data, TCP_FASTOPEN, sa) 63 | if err == syscall.EAGAIN { 64 | continue 65 | } 66 | break 67 | } 68 | 69 | if _, ok := err.(syscall.Errno); ok { 70 | return nil, os.NewSyscallError("sendto", err) 71 | } 72 | 73 | return net.FileConn(os.NewFile(uintptr(fd), "TFO: " + raddr.String())) 74 | } 75 | 76 | // from: https://github.com/libp2p/go-sockaddr/blob/master/net/net.go 77 | /* 78 | Copyright (c) 2012 The Go Authors. All rights reserved. 79 | 80 | Redistribution and use in source and binary forms, with or without 81 | modification, are permitted provided that the following conditions are 82 | met: 83 | 84 | * Redistributions of source code must retain the above copyright 85 | notice, this list of conditions and the following disclaimer. 86 | * Redistributions in binary form must reproduce the above 87 | copyright notice, this list of conditions and the following disclaimer 88 | in the documentation and/or other materials provided with the 89 | distribution. 90 | * Neither the name of Google Inc. nor the names of its 91 | contributors may be used to endorse or promote products derived from 92 | this software without specific prior written permission. 93 | 94 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 95 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 96 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 97 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 98 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 99 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 100 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 101 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 102 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 103 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 104 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 105 | */ 106 | // IP6ZoneToInt converts an IP6 Zone net string to a unix int 107 | // returns 0 if zone is "" 108 | func IP6ZoneToInt(zone string) int { 109 | if zone == "" { 110 | return 0 111 | } 112 | if ifi, err := net.InterfaceByName(zone); err == nil { 113 | return ifi.Index 114 | } 115 | n, _, _ := dtoi(zone, 0) 116 | return n 117 | } 118 | 119 | // Bigger than we need, not too big to worry about overflow 120 | const big = 0xFFFFFF 121 | 122 | // Decimal to integer starting at &s[i0]. 123 | // Returns number, new offset, success. 124 | func dtoi(s string, i0 int) (n int, i int, ok bool) { 125 | n = 0 126 | for i = i0; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ { 127 | n = n*10 + int(s[i]-'0') 128 | if n >= big { 129 | return 0, i, false 130 | } 131 | } 132 | if i == i0 { 133 | return 0, i, false 134 | } 135 | return n, i, true 136 | } 137 | 138 | -------------------------------------------------------------------------------- /server/tfo_bsd.go: -------------------------------------------------------------------------------- 1 | // +build freebsd darwin 2 | 3 | package main 4 | 5 | import ( 6 | "net" 7 | "os" 8 | "syscall" 9 | "time" 10 | ) 11 | 12 | func handleTFO(p1 net.Conn, target string, timeout time.Duration) (net.Conn, error) { 13 | buf := make([]byte, 4096) // TODO: export setting 14 | p1.SetReadDeadline(time.Now().Add(50 * time.Millisecond)) // TODO: export setting 15 | n, err := p1.Read(buf) // try read first packet data 16 | if err, ok := err.(net.Error); ok && err.Timeout() { // read data timeout 17 | p1.SetReadDeadline(time.Time{}) 18 | return net.DialTimeout("tcp", target, timeout) 19 | //return DialTFO(target, buf[:n]) 20 | } 21 | if err != nil { 22 | return nil, err 23 | } 24 | p1.SetReadDeadline(time.Time{}) 25 | 26 | return DialTFO(target, buf[:n]) 27 | } 28 | 29 | func DialTFO(address string, data []byte) (net.Conn, error) { 30 | raddr, err := net.ResolveTCPAddr("tcp", address) 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | var sa syscall.Sockaddr 36 | family := syscall.AF_INET 37 | ip6 := raddr.IP.To16() 38 | ip4 := raddr.IP.To4() 39 | 40 | switch { 41 | case ip6 != nil: //IPv6 42 | family = syscall.AF_INET6 43 | sa6 := &syscall.SockaddrInet6{Port: raddr.Port, ZoneId: uint32(IP6ZoneToInt(raddr.Zone))} 44 | copy(sa6.Addr[:], ip6) 45 | sa = sa6 46 | 47 | case ip4 != nil: 48 | family = syscall.AF_INET 49 | sa4 := &syscall.SockaddrInet4{Port: raddr.Port} 50 | copy(sa4.Addr[:], ip4) 51 | sa = sa4 52 | } 53 | 54 | fd, err := syscall.Socket(family, syscall.SOCK_STREAM, 0) 55 | if err != nil { 56 | return nil, err 57 | } 58 | defer syscall.Close(fd) 59 | 60 | for { 61 | //err = syscall.Sendto(fd, data, syscall.MSG_FASTOPEN, sa) // linux version 62 | err = syscall.Sendto(fd, data, TCP_FASTOPEN, sa) 63 | if err == syscall.EAGAIN { 64 | continue 65 | } 66 | break 67 | } 68 | 69 | if _, ok := err.(syscall.Errno); ok { 70 | return nil, os.NewSyscallError("sendto", err) 71 | } 72 | 73 | return net.FileConn(os.NewFile(uintptr(fd), "TFO: " + raddr.String())) 74 | } 75 | 76 | // from: https://github.com/libp2p/go-sockaddr/blob/master/net/net.go 77 | /* 78 | Copyright (c) 2012 The Go Authors. All rights reserved. 79 | 80 | Redistribution and use in source and binary forms, with or without 81 | modification, are permitted provided that the following conditions are 82 | met: 83 | 84 | * Redistributions of source code must retain the above copyright 85 | notice, this list of conditions and the following disclaimer. 86 | * Redistributions in binary form must reproduce the above 87 | copyright notice, this list of conditions and the following disclaimer 88 | in the documentation and/or other materials provided with the 89 | distribution. 90 | * Neither the name of Google Inc. nor the names of its 91 | contributors may be used to endorse or promote products derived from 92 | this software without specific prior written permission. 93 | 94 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 95 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 96 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 97 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 98 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 99 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 100 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 101 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 102 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 103 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 104 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 105 | */ 106 | // IP6ZoneToInt converts an IP6 Zone net string to a unix int 107 | // returns 0 if zone is "" 108 | func IP6ZoneToInt(zone string) int { 109 | if zone == "" { 110 | return 0 111 | } 112 | if ifi, err := net.InterfaceByName(zone); err == nil { 113 | return ifi.Index 114 | } 115 | n, _, _ := dtoi(zone, 0) 116 | return n 117 | } 118 | 119 | // Bigger than we need, not too big to worry about overflow 120 | const big = 0xFFFFFF 121 | 122 | // Decimal to integer starting at &s[i0]. 123 | // Returns number, new offset, success. 124 | func dtoi(s string, i0 int) (n int, i int, ok bool) { 125 | n = 0 126 | for i = i0; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ { 127 | n = n*10 + int(s[i]-'0') 128 | if n >= big { 129 | return 0, i, false 130 | } 131 | } 132 | if i == i0 { 133 | return 0, i, false 134 | } 135 | return n, i, true 136 | } 137 | 138 | -------------------------------------------------------------------------------- /client/services.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "net" 7 | "net/url" 8 | "bytes" 9 | "strings" 10 | "fmt" 11 | 12 | "github.com/cs8425/smux" 13 | ) 14 | 15 | func replyAndClose(p1 net.Conn, rpy int) { 16 | p1.Write([]byte{0x05, byte(rpy), 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) 17 | p1.Close() 18 | } 19 | 20 | // thanks: http://www.golangnote.com/topic/141.html 21 | func handleSocks(p1 net.Conn, sess *smux.Session, quiet bool, buffersize int) { 22 | var b [320]byte 23 | n, err := p1.Read(b[:]) 24 | if err != nil { 25 | Vlogln(quiet, "socks client read", p1, err) 26 | return 27 | } 28 | if b[0] != 0x05 { //only Socket5 29 | return 30 | } 31 | 32 | //reply: NO AUTHENTICATION REQUIRED 33 | p1.Write([]byte{0x05, 0x00}) 34 | 35 | n, err = p1.Read(b[:]) 36 | if b[1] != 0x01 { // 0x01: CONNECT 37 | replyAndClose(p1, 0x07) // X'07' Command not supported 38 | return 39 | } 40 | 41 | var backend string 42 | switch b[3] { 43 | case 0x01: //IP V4 44 | backend = net.IPv4(b[4], b[5], b[6], b[7]).String() 45 | if n != 10 { 46 | replyAndClose(p1, 0x07) // X'07' Command not supported 47 | return 48 | } 49 | case 0x03: //DOMAINNAME 50 | backend = string(b[5 : n-2]) //b[4] domain name length 51 | case 0x04: //IP V6 52 | backend = net.IP{b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15], b[16], b[17], b[18], b[19]}.String() 53 | if n != 22 { 54 | replyAndClose(p1, 0x07) // X'07' Command not supported 55 | return 56 | } 57 | default: 58 | replyAndClose(p1, 0x08) // X'08' Address type not supported 59 | return 60 | } 61 | 62 | p2, err := sess.OpenStream() 63 | if err != nil { 64 | return 65 | } 66 | defer p2.Close() 67 | // send to proxy 68 | p2.Write(b[0:n]) 69 | 70 | var b2 [10]byte 71 | n2, err := p2.Read(b2[:10]) 72 | if n2 < 10 { 73 | Vlogln(quiet, "Dial err replay:", backend, n2) 74 | replyAndClose(p1, 0x03) 75 | return 76 | } 77 | if err != nil || b2[1] != 0x00 { 78 | Vlogln(quiet, "socks err to:", backend, n2, b2[1], err) 79 | replyAndClose(p1, int(b2[1])) 80 | return 81 | } 82 | 83 | Vlogln(quiet, "socks to:", backend) 84 | reply := []byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} 85 | p1.Write(reply) // reply OK 86 | cp(p1, p2, buffersize) 87 | } 88 | 89 | // thanks: http://www.golangnote.com/topic/141.html 90 | func handleHttp(client net.Conn, sess *smux.Session, quiet bool, buffersize int) { 91 | var b [1024]byte 92 | n, err := client.Read(b[:]) 93 | if err != nil { 94 | Vlogln(quiet, "http client read err", client, err) 95 | return 96 | } 97 | var method, host, address string 98 | idx := bytes.IndexByte(b[:], '\n') 99 | if idx == -1 { 100 | Vlogln(quiet, "http client parse err", idx, client.RemoteAddr()) 101 | return 102 | } 103 | fmt.Sscanf(string(b[:idx]), "%s%s", &method, &host) 104 | 105 | if strings.Index(host, "://") == -1 { 106 | host = "//" + host 107 | } 108 | hostPortURL, err := url.Parse(host) 109 | if err != nil { 110 | Vlogln(quiet, "Parse hostPortURL err:", client, hostPortURL, err) 111 | return 112 | } 113 | if strings.Index(hostPortURL.Host, ":") == -1 { // no port, default 80 114 | address = hostPortURL.Host + ":80" 115 | } else { 116 | address = hostPortURL.Host 117 | } 118 | 119 | 120 | p2, err := sess.OpenStream() 121 | if err != nil { 122 | return 123 | } 124 | defer p2.Close() 125 | 126 | Vlogln(quiet, "Dial to:", method, address) 127 | var target = append([]byte{0, 0, 0, 0x05}, []byte(address)...) 128 | p2.Write(target) 129 | 130 | var b2 [10]byte 131 | n2, err := p2.Read(b2[:10]) 132 | if n2 < 10 { 133 | Vlogln(quiet, "Dial err replay:", address, n2) 134 | return 135 | } 136 | if err != nil || b2[1] != 0x00 { 137 | Vlogln(quiet, "Dial err:", address, n2, b2[1], err) 138 | return 139 | } 140 | 141 | if method == "CONNECT" { 142 | client.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n")) 143 | } else { 144 | p2.Write(b[:n]) 145 | } 146 | 147 | cp(client, p2, buffersize) 148 | } 149 | 150 | func handleClient(sess *smux.Session, p1 net.Conn, quiet bool, buffersize int, serv string) { 151 | if !quiet { 152 | log.Println("stream opened") 153 | defer log.Println("stream closed") 154 | } 155 | 156 | defer p1.Close() 157 | switch serv { 158 | case "socks5": 159 | handleSocks(p1, sess, quiet, buffersize) 160 | 161 | case "http": 162 | handleHttp(p1, sess, quiet, buffersize) 163 | 164 | default: 165 | p2, err := sess.OpenStream() 166 | if err != nil { 167 | return 168 | } 169 | defer p2.Close() 170 | cp(p1, p2, buffersize) 171 | } 172 | 173 | } 174 | 175 | func cp(p1 net.Conn, p2 net.Conn, buffersize int) { 176 | defer p2.Close() 177 | 178 | // start tunnel 179 | p1die := make(chan struct{}) 180 | buf1 := make([]byte, buffersize) 181 | go func() { io.CopyBuffer(p1, p2, buf1); close(p1die) }() 182 | 183 | p2die := make(chan struct{}) 184 | buf2 := make([]byte, buffersize) 185 | go func() { io.CopyBuffer(p2, p1, buf2); close(p2die) }() 186 | 187 | // wait for tunnel termination 188 | select { 189 | case <-p1die: 190 | case <-p2die: 191 | } 192 | } 193 | 194 | func Vlogln(quiet bool, v ...interface{}) { 195 | if !quiet { 196 | log.Println(v...) 197 | } 198 | } 199 | 200 | -------------------------------------------------------------------------------- /server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/sha1" 5 | "encoding/csv" 6 | "fmt" 7 | "io" 8 | "log" 9 | "math/rand" 10 | "net" 11 | "net/http" 12 | _ "net/http/pprof" 13 | "os" 14 | "time" 15 | "strings" 16 | _ "unsafe" 17 | 18 | "golang.org/x/crypto/pbkdf2" 19 | 20 | "path/filepath" 21 | 22 | "github.com/golang/snappy" 23 | "github.com/urfave/cli" 24 | kcp "github.com/xtaci/kcp-go" 25 | "github.com/cs8425/smux" 26 | ) 27 | 28 | var ( 29 | // VERSION is injected by buildflags 30 | VERSION = "SELFBUILD" 31 | // SALT is use for pbkdf2 key expansion 32 | SALT = "kcp-go" 33 | ) 34 | 35 | type compStream struct { 36 | conn net.Conn 37 | w *snappy.Writer 38 | r *snappy.Reader 39 | } 40 | 41 | func (c *compStream) Read(p []byte) (n int, err error) { 42 | return c.r.Read(p) 43 | } 44 | 45 | func (c *compStream) Write(p []byte) (n int, err error) { 46 | n, err = c.w.Write(p) 47 | err = c.w.Flush() 48 | return n, err 49 | } 50 | 51 | func (c *compStream) Close() error { 52 | return c.conn.Close() 53 | } 54 | 55 | func newCompStream(conn net.Conn) *compStream { 56 | c := new(compStream) 57 | c.conn = conn 58 | c.w = snappy.NewBufferedWriter(conn) 59 | c.r = snappy.NewReader(conn) 60 | return c 61 | } 62 | 63 | // DNS resolve workaround for android in pure go 64 | 65 | //go:linkname defaultNS net.defaultNS 66 | var defaultNS []string 67 | 68 | func setDefaultNS(addrs []string) { 69 | defaultNS = addrs 70 | } 71 | 72 | // handle multiplex-ed connection 73 | func handleMux(conn io.ReadWriteCloser, config *Config) { 74 | // stream multiplex 75 | smuxConfig := smux.DefaultConfig() 76 | smuxConfig.MaxReceiveBuffer = config.SockBuf 77 | smuxConfig.KeepAliveInterval = time.Duration(config.KeepAliveMS) * time.Millisecond 78 | smuxConfig.KeepAliveTimeout = time.Duration(config.KeepAliveTimeout) * time.Millisecond 79 | smuxConfig.MaxFrameSize = config.MaxFrameSize 80 | smuxConfig.MaxStreamBuffer = config.StreamBuf 81 | smuxConfig.EnableStreamBuffer = config.StreamBufEn 82 | smuxConfig.BoostTimeout = time.Duration(config.BoostTimeout) * time.Millisecond 83 | 84 | 85 | mux, err := smux.Server(conn, smuxConfig) 86 | if err != nil { 87 | log.Println(err) 88 | return 89 | } 90 | defer mux.Close() 91 | for { 92 | p1, err := mux.AcceptStream() 93 | if err != nil { 94 | log.Println(err) 95 | return 96 | } 97 | 98 | go handleClient(p1, config.Quiet, config.PipeBuf, config.Service, config.Target, config.TFO) 99 | } 100 | } 101 | 102 | func checkError(err error) { 103 | if err != nil { 104 | log.Printf("%+v\n", err) 105 | os.Exit(-1) 106 | } 107 | } 108 | 109 | func main() { 110 | rand.Seed(int64(time.Now().Nanosecond())) 111 | if VERSION == "SELFBUILD" { 112 | // add more log flags for debugging 113 | log.SetFlags(log.LstdFlags | log.Lshortfile) 114 | } 115 | myApp := cli.NewApp() 116 | myApp.Name = "kcptun" 117 | myApp.Usage = "server(with SMUX)" 118 | myApp.Version = VERSION 119 | myApp.Flags = []cli.Flag{ 120 | cli.StringFlag{ 121 | Name: "listen,l", 122 | Value: ":29900", 123 | Usage: "kcp server listen address", 124 | }, 125 | cli.StringFlag{ 126 | Name: "target, t", 127 | Value: "127.0.0.1:12948", 128 | Usage: "target server address", 129 | }, 130 | cli.StringFlag{ 131 | Name: "key", 132 | Value: "it's a secrect", 133 | Usage: "pre-shared secret between client and server", 134 | EnvVar: "KCPTUN_KEY", 135 | }, 136 | cli.StringFlag{ 137 | Name: "crypt", 138 | Value: "aes", 139 | Usage: "aes, aes-128, aes-192, salsa20, blowfish, twofish, cast5, 3des, tea, xtea, xor, sm4, none", 140 | }, 141 | cli.StringFlag{ 142 | Name: "mode", 143 | Value: "fast", 144 | Usage: "profiles: fast3, fast2, fast, normal, manual", 145 | }, 146 | cli.IntFlag{ 147 | Name: "mtu", 148 | Value: 1350, 149 | Usage: "set maximum transmission unit for UDP packets", 150 | }, 151 | cli.IntFlag{ 152 | Name: "sndwnd", 153 | Value: 1024, 154 | Usage: "set send window size(num of packets)", 155 | }, 156 | cli.IntFlag{ 157 | Name: "rcvwnd", 158 | Value: 1024, 159 | Usage: "set receive window size(num of packets)", 160 | }, 161 | cli.IntFlag{ 162 | Name: "datashard,ds", 163 | Value: 10, 164 | Usage: "set reed-solomon erasure coding - datashard", 165 | }, 166 | cli.IntFlag{ 167 | Name: "parityshard,ps", 168 | Value: 3, 169 | Usage: "set reed-solomon erasure coding - parityshard", 170 | }, 171 | cli.IntFlag{ 172 | Name: "dscp", 173 | Value: 0, 174 | Usage: "set DSCP(6bit)", 175 | }, 176 | cli.BoolFlag{ 177 | Name: "nocomp", 178 | Usage: "disable compression", 179 | }, 180 | cli.BoolFlag{ 181 | Name: "acknodelay", 182 | Usage: "flush ack immediately when a packet is received", 183 | Hidden: true, 184 | }, 185 | cli.IntFlag{ 186 | Name: "nodelay", 187 | Value: 0, 188 | Hidden: true, 189 | }, 190 | cli.IntFlag{ 191 | Name: "interval", 192 | Value: 50, 193 | Hidden: true, 194 | }, 195 | cli.IntFlag{ 196 | Name: "resend", 197 | Value: 0, 198 | Hidden: true, 199 | }, 200 | cli.IntFlag{ 201 | Name: "nc", 202 | Value: 0, 203 | Hidden: true, 204 | }, 205 | cli.IntFlag{ 206 | Name: "sockbuf", 207 | Value: 4 * 1024 * 1024, // socket buffer size in bytes 208 | Usage: "per-session buffer in bytes", 209 | }, 210 | cli.BoolTFlag{ 211 | Name: "streambuf-en", // enable stream buffer control 212 | Usage: `enable per-socket buffer, use "--streambuf-en=0" to disable`, 213 | }, 214 | cli.IntFlag{ 215 | Name: "streambuf", 216 | Value: 256 * 1024, // each stream buffer max size in bytes 217 | Usage: "per-socket buffer in bytes", 218 | }, 219 | cli.IntFlag{ 220 | Name: "streamboost", 221 | Value: 10 * 1000, // stream boost for startup in ms 222 | Usage: "stream boost for startup in ms, affect tcp slow-start", 223 | }, 224 | cli.IntFlag{ 225 | Name: "maxframe", 226 | Value: 32767, // size in bytes 227 | Usage: "max smux frame size in bytes", 228 | }, 229 | cli.IntFlag{ 230 | Name: "pipebuf", 231 | Value: 256 * 1024, // size in bytes 232 | Usage: "internal io.CopyBuffer buffer size in bytes", 233 | }, 234 | cli.IntFlag{ 235 | Name: "keepalive", 236 | Value: 10, // nat keepalive interval in seconds 237 | Usage: "(deprecated) seconds between heartbeats", 238 | }, 239 | cli.IntFlag{ 240 | Name: "keepalivems", 241 | Value: 10 * 1000, // nat keepalive interval in Milliseconds 242 | Usage: "milliseconds between heartbeats, will overwrite keepalive", 243 | }, 244 | cli.IntFlag{ 245 | Name: "keepalive-timeout", 246 | Value: 75000, // nat keepalive timeout in Milliseconds 247 | Usage: "timeout in milliseconds for heartbeats response", 248 | }, 249 | cli.StringFlag{ 250 | Name: "dns", 251 | Value: "", 252 | Usage: `failback DNS for case that can't parse "/etc/resolv.conf", eg: run on Android; split multi-address by ',', eg: "8.8.8.8:53,8.8.4.4:53"`, 253 | }, 254 | cli.StringFlag{ 255 | Name: "ser", 256 | Value: "raw", 257 | Usage: `enable built-in service, values: raw (pair: raw), fast (socks5-mod-reduce-1-RTT, pair: socks5, http)`, 258 | }, 259 | cli.BoolTFlag{ 260 | Name: "tfo", 261 | Usage: `enable TCP fast open, use "--tfo=0" to disable`, 262 | }, 263 | cli.StringFlag{ 264 | Name: "snmplog", 265 | Value: "", 266 | Usage: "collect snmp to file, aware of timeformat in golang, like: ./snmp-20060102.log", 267 | }, 268 | cli.IntFlag{ 269 | Name: "snmpperiod", 270 | Value: 60, 271 | Usage: "snmp collect period, in seconds", 272 | }, 273 | cli.BoolFlag{ 274 | Name: "pprof", 275 | Usage: "start profiling server on :6060", 276 | }, 277 | cli.StringFlag{ 278 | Name: "log", 279 | Value: "", 280 | Usage: "specify a log file to output, default goes to stderr", 281 | }, 282 | cli.BoolFlag{ 283 | Name: "quiet", 284 | Usage: "to suppress the 'stream open/close' messages", 285 | }, 286 | cli.StringFlag{ 287 | Name: "c", 288 | Value: "", // when the value is not empty, the config path must exists 289 | Usage: "config from json file, which will override the command from shell", 290 | }, 291 | } 292 | myApp.Action = func(c *cli.Context) error { 293 | config := Config{} 294 | config.Listen = c.String("listen") 295 | config.Target = c.String("target") 296 | config.Key = c.String("key") 297 | config.Crypt = c.String("crypt") 298 | config.Mode = c.String("mode") 299 | config.MTU = c.Int("mtu") 300 | config.SndWnd = c.Int("sndwnd") 301 | config.RcvWnd = c.Int("rcvwnd") 302 | config.DataShard = c.Int("datashard") 303 | config.ParityShard = c.Int("parityshard") 304 | config.DSCP = c.Int("dscp") 305 | config.NoComp = c.Bool("nocomp") 306 | config.AckNodelay = c.Bool("acknodelay") 307 | config.NoDelay = c.Int("nodelay") 308 | config.Interval = c.Int("interval") 309 | config.Resend = c.Int("resend") 310 | config.NoCongestion = c.Int("nc") 311 | config.Log = c.String("log") 312 | config.SnmpLog = c.String("snmplog") 313 | config.SnmpPeriod = c.Int("snmpperiod") 314 | config.Pprof = c.Bool("pprof") 315 | config.Quiet = c.Bool("quiet") 316 | 317 | // smux setting 318 | config.SockBuf = c.Int("sockbuf") 319 | config.StreamBuf = c.Int("streambuf") 320 | config.StreamBufEn = c.Bool("streambuf-en") 321 | config.BoostTimeout = c.Int("boosttimeout") 322 | config.MaxFrameSize = c.Int("maxframe") 323 | config.PipeBuf = c.Int("pipebuf") 324 | config.KeepAlive = c.Int("keepalive") 325 | config.KeepAliveMS = c.Int("keepalivems") 326 | config.KeepAliveTimeout = c.Int("keepalive-timeout") 327 | 328 | // extra 329 | config.Service = c.String("ser") 330 | config.TFO = c.Bool("tfo") 331 | 332 | if c.String("dns") != "" { 333 | dnsaddr := strings.Split(c.String("dns"), ",") 334 | if len(dnsaddr) > 0 { 335 | config.DNS = dnsaddr 336 | } 337 | } 338 | 339 | 340 | if c.String("c") != "" { 341 | //Now only support json config file 342 | err := parseJSONConfig(&config, c.String("c")) 343 | checkError(err) 344 | } 345 | 346 | // log redirect 347 | if config.Log != "" { 348 | f, err := os.OpenFile(config.Log, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) 349 | checkError(err) 350 | defer f.Close() 351 | log.SetOutput(f) 352 | } 353 | 354 | switch config.Mode { 355 | case "normal": 356 | config.NoDelay, config.Interval, config.Resend, config.NoCongestion = 0, 40, 2, 1 357 | case "fast": 358 | config.NoDelay, config.Interval, config.Resend, config.NoCongestion = 0, 30, 2, 1 359 | case "fast2": 360 | config.NoDelay, config.Interval, config.Resend, config.NoCongestion = 1, 20, 2, 1 361 | case "fast3": 362 | config.NoDelay, config.Interval, config.Resend, config.NoCongestion = 1, 10, 2, 1 363 | } 364 | 365 | log.Println("version:", VERSION) 366 | log.Println("initiating key derivation") 367 | pass := pbkdf2.Key([]byte(config.Key), []byte(SALT), 4096, 32, sha1.New) 368 | var block kcp.BlockCrypt 369 | switch config.Crypt { 370 | case "sm4": 371 | block, _ = kcp.NewSM4BlockCrypt(pass[:16]) 372 | case "tea": 373 | block, _ = kcp.NewTEABlockCrypt(pass[:16]) 374 | case "xor": 375 | block, _ = kcp.NewSimpleXORBlockCrypt(pass) 376 | case "none": 377 | block, _ = kcp.NewNoneBlockCrypt(pass) 378 | case "aes-128": 379 | block, _ = kcp.NewAESBlockCrypt(pass[:16]) 380 | case "aes-192": 381 | block, _ = kcp.NewAESBlockCrypt(pass[:24]) 382 | case "blowfish": 383 | block, _ = kcp.NewBlowfishBlockCrypt(pass) 384 | case "twofish": 385 | block, _ = kcp.NewTwofishBlockCrypt(pass) 386 | case "cast5": 387 | block, _ = kcp.NewCast5BlockCrypt(pass[:16]) 388 | case "3des": 389 | block, _ = kcp.NewTripleDESBlockCrypt(pass[:24]) 390 | case "xtea": 391 | block, _ = kcp.NewXTEABlockCrypt(pass[:16]) 392 | case "salsa20": 393 | block, _ = kcp.NewSalsa20BlockCrypt(pass) 394 | default: 395 | config.Crypt = "aes" 396 | block, _ = kcp.NewAESBlockCrypt(pass) 397 | } 398 | 399 | if config.KeepAliveMS == 0 { 400 | config.KeepAliveMS = config.KeepAlive * 1000 401 | } 402 | 403 | lis, err := kcp.ListenWithOptions(config.Listen, block, config.DataShard, config.ParityShard) 404 | checkError(err) 405 | log.Println("listening on:", lis.Addr()) 406 | log.Println("target:", config.Target) 407 | log.Println("encryption:", config.Crypt) 408 | log.Println("nodelay parameters:", config.NoDelay, config.Interval, config.Resend, config.NoCongestion) 409 | log.Println("sndwnd:", config.SndWnd, "rcvwnd:", config.RcvWnd) 410 | log.Println("compression:", !config.NoComp) 411 | log.Println("mtu:", config.MTU) 412 | log.Println("datashard:", config.DataShard, "parityshard:", config.ParityShard) 413 | log.Println("acknodelay:", config.AckNodelay) 414 | log.Println("dscp:", config.DSCP) 415 | log.Println("sockbuf:", config.SockBuf) 416 | log.Println("snmplog:", config.SnmpLog) 417 | log.Println("snmpperiod:", config.SnmpPeriod) 418 | log.Println("pprof:", config.Pprof) 419 | log.Println("quiet:", config.Quiet) 420 | 421 | log.Println("keepaliveMS:", config.KeepAliveMS) 422 | log.Println("keepalive-timeout:", config.KeepAliveTimeout) 423 | log.Println("sockbuf:", config.SockBuf) 424 | log.Println("streambuf-en:", config.StreamBufEn) 425 | log.Println("streambuf:", config.StreamBuf) 426 | log.Println("boosttimeout:", config.BoostTimeout) 427 | log.Println("maxframe:", config.MaxFrameSize) 428 | log.Println("pipebuf:", config.PipeBuf) 429 | 430 | log.Println("service:", config.Service) 431 | log.Println("TCP Fast Open(tfo):", config.TFO) 432 | 433 | if len(config.DNS) > 0 { 434 | log.Println("failback-DNS:", config.DNS) 435 | setDefaultNS(config.DNS) 436 | } 437 | 438 | if err := lis.SetDSCP(config.DSCP); err != nil { 439 | log.Println("SetDSCP:", err) 440 | } 441 | if err := lis.SetReadBuffer(config.SockBuf); err != nil { 442 | log.Println("SetReadBuffer:", err) 443 | } 444 | if err := lis.SetWriteBuffer(config.SockBuf); err != nil { 445 | log.Println("SetWriteBuffer:", err) 446 | } 447 | 448 | go snmpLogger(config.SnmpLog, config.SnmpPeriod) 449 | if config.Pprof { 450 | go http.ListenAndServe(":6060", nil) 451 | } 452 | 453 | for { 454 | if conn, err := lis.AcceptKCP(); err == nil { 455 | log.Println("remote address:", conn.RemoteAddr()) 456 | conn.SetStreamMode(true) 457 | conn.SetWriteDelay(false) 458 | conn.SetNoDelay(config.NoDelay, config.Interval, config.Resend, config.NoCongestion) 459 | conn.SetMtu(config.MTU) 460 | conn.SetWindowSize(config.SndWnd, config.RcvWnd) 461 | conn.SetACKNoDelay(config.AckNodelay) 462 | 463 | if config.NoComp { 464 | go handleMux(conn, &config) 465 | } else { 466 | go handleMux(newCompStream(conn), &config) 467 | } 468 | } else { 469 | log.Printf("%+v", err) 470 | } 471 | } 472 | } 473 | myApp.Run(os.Args) 474 | } 475 | 476 | func snmpLogger(path string, interval int) { 477 | if path == "" || interval == 0 { 478 | return 479 | } 480 | ticker := time.NewTicker(time.Duration(interval) * time.Second) 481 | defer ticker.Stop() 482 | for { 483 | select { 484 | case <-ticker.C: 485 | // split path into dirname and filename 486 | logdir, logfile := filepath.Split(path) 487 | // only format logfile 488 | f, err := os.OpenFile(logdir+time.Now().Format(logfile), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) 489 | if err != nil { 490 | log.Println(err) 491 | return 492 | } 493 | w := csv.NewWriter(f) 494 | // write header in empty file 495 | if stat, err := f.Stat(); err == nil && stat.Size() == 0 { 496 | if err := w.Write(append([]string{"Unix"}, kcp.DefaultSnmp.Header()...)); err != nil { 497 | log.Println(err) 498 | } 499 | } 500 | if err := w.Write(append([]string{fmt.Sprint(time.Now().Unix())}, kcp.DefaultSnmp.ToSlice()...)); err != nil { 501 | log.Println(err) 502 | } 503 | kcp.DefaultSnmp.Reset() 504 | w.Flush() 505 | f.Close() 506 | } 507 | } 508 | } 509 | -------------------------------------------------------------------------------- /client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/sha1" 5 | "encoding/csv" 6 | "fmt" 7 | "log" 8 | "math/rand" 9 | "net" 10 | "os" 11 | "time" 12 | 13 | "golang.org/x/crypto/pbkdf2" 14 | 15 | "github.com/golang/snappy" 16 | "github.com/pkg/errors" 17 | "github.com/urfave/cli" 18 | kcp "github.com/xtaci/kcp-go" 19 | "github.com/cs8425/smux" 20 | 21 | "path/filepath" 22 | ) 23 | 24 | var ( 25 | // VERSION is injected by buildflags 26 | VERSION = "SELFBUILD" 27 | // SALT is use for pbkdf2 key expansion 28 | SALT = "kcp-go" 29 | ) 30 | 31 | type compStream struct { 32 | conn net.Conn 33 | w *snappy.Writer 34 | r *snappy.Reader 35 | } 36 | 37 | func (c *compStream) Read(p []byte) (n int, err error) { 38 | return c.r.Read(p) 39 | } 40 | 41 | func (c *compStream) Write(p []byte) (n int, err error) { 42 | n, err = c.w.Write(p) 43 | err = c.w.Flush() 44 | return n, err 45 | } 46 | 47 | func (c *compStream) Close() error { 48 | return c.conn.Close() 49 | } 50 | 51 | func newCompStream(conn net.Conn) *compStream { 52 | c := new(compStream) 53 | c.conn = conn 54 | c.w = snappy.NewBufferedWriter(conn) 55 | c.r = snappy.NewReader(conn) 56 | return c 57 | } 58 | 59 | func checkError(err error) { 60 | if err != nil { 61 | log.Printf("%+v\n", err) 62 | os.Exit(-1) 63 | } 64 | } 65 | 66 | func main() { 67 | rand.Seed(int64(time.Now().Nanosecond())) 68 | if VERSION == "SELFBUILD" { 69 | // add more log flags for debugging 70 | log.SetFlags(log.LstdFlags | log.Lshortfile) 71 | } 72 | myApp := cli.NewApp() 73 | myApp.Name = "kcptun" 74 | myApp.Usage = "client(with SMUX)" 75 | myApp.Version = VERSION 76 | myApp.Flags = []cli.Flag{ 77 | cli.StringFlag{ 78 | Name: "localaddr,l", 79 | Value: ":12948", 80 | Usage: "local listen address", 81 | }, 82 | cli.StringFlag{ 83 | Name: "remoteaddr, r", 84 | Value: "vps:29900", 85 | Usage: "kcp server address", 86 | }, 87 | cli.StringFlag{ 88 | Name: "key", 89 | Value: "it's a secrect", 90 | Usage: "pre-shared secret between client and server", 91 | EnvVar: "KCPTUN_KEY", 92 | }, 93 | cli.StringFlag{ 94 | Name: "crypt", 95 | Value: "aes", 96 | Usage: "aes, aes-128, aes-192, salsa20, blowfish, twofish, cast5, 3des, tea, xtea, xor, sm4, none", 97 | }, 98 | cli.StringFlag{ 99 | Name: "mode", 100 | Value: "fast", 101 | Usage: "profiles: fast3, fast2, fast, normal, manual", 102 | }, 103 | cli.IntFlag{ 104 | Name: "conn", 105 | Value: 1, 106 | Usage: "set num of UDP connections to server", 107 | }, 108 | cli.IntFlag{ 109 | Name: "autoexpire", 110 | Value: 0, 111 | Usage: "set auto expiration time(in seconds) for a single UDP connection, 0 to disable", 112 | }, 113 | cli.IntFlag{ 114 | Name: "scavengettl", 115 | Value: 600, 116 | Usage: "set how long an expired connection can live(in sec), -1 to disable", 117 | }, 118 | cli.IntFlag{ 119 | Name: "mtu", 120 | Value: 1350, 121 | Usage: "set maximum transmission unit for UDP packets", 122 | }, 123 | cli.IntFlag{ 124 | Name: "sndwnd", 125 | Value: 128, 126 | Usage: "set send window size(num of packets)", 127 | }, 128 | cli.IntFlag{ 129 | Name: "rcvwnd", 130 | Value: 512, 131 | Usage: "set receive window size(num of packets)", 132 | }, 133 | cli.IntFlag{ 134 | Name: "datashard,ds", 135 | Value: 10, 136 | Usage: "set reed-solomon erasure coding - datashard", 137 | }, 138 | cli.IntFlag{ 139 | Name: "parityshard,ps", 140 | Value: 3, 141 | Usage: "set reed-solomon erasure coding - parityshard", 142 | }, 143 | cli.IntFlag{ 144 | Name: "dscp", 145 | Value: 0, 146 | Usage: "set DSCP(6bit)", 147 | }, 148 | cli.BoolFlag{ 149 | Name: "nocomp", 150 | Usage: "disable compression", 151 | }, 152 | cli.BoolFlag{ 153 | Name: "acknodelay", 154 | Usage: "flush ack immediately when a packet is received", 155 | Hidden: true, 156 | }, 157 | cli.IntFlag{ 158 | Name: "nodelay", 159 | Value: 0, 160 | Hidden: true, 161 | }, 162 | cli.IntFlag{ 163 | Name: "interval", 164 | Value: 50, 165 | Hidden: true, 166 | }, 167 | cli.IntFlag{ 168 | Name: "resend", 169 | Value: 0, 170 | Hidden: true, 171 | }, 172 | cli.IntFlag{ 173 | Name: "nc", 174 | Value: 0, 175 | Hidden: true, 176 | }, 177 | cli.IntFlag{ 178 | Name: "sockbuf", 179 | Value: 4 * 1024 * 1024, // socket buffer size in bytes 180 | Usage: "per-session buffer in bytes", 181 | }, 182 | cli.BoolTFlag{ 183 | Name: "streambuf-en", // enable stream buffer control 184 | Usage: `enable per-socket buffer, use "--streambuf-en=0" to disable`, 185 | }, 186 | cli.IntFlag{ 187 | Name: "streambuf", 188 | Value: 256 * 1024, // each stream buffer max size in bytes 189 | Usage: "per-socket buffer in bytes", 190 | }, 191 | cli.IntFlag{ 192 | Name: "streamboost", 193 | Value: 10 * 1000, // stream boost for startup in ms 194 | Usage: "stream boost for startup in ms, affect tcp slow-start", 195 | }, 196 | cli.IntFlag{ 197 | Name: "maxframe", 198 | Value: 32767, // size in bytes 199 | Usage: "max smux frame size in bytes", 200 | }, 201 | cli.IntFlag{ 202 | Name: "pipebuf", 203 | Value: 256 * 1024, // size in bytes 204 | Usage: "internal io.CopyBuffer buffer size in bytes", 205 | }, 206 | cli.IntFlag{ 207 | Name: "keepalive", 208 | Value: 10, // nat keepalive interval in seconds 209 | Usage: "(deprecated) seconds between heartbeats", 210 | }, 211 | cli.IntFlag{ 212 | Name: "keepalivems", 213 | Value: 10 * 1000, // nat keepalive interval in Milliseconds 214 | Usage: "milliseconds between heartbeats, will overwrite keepalive", 215 | }, 216 | cli.IntFlag{ 217 | Name: "keepalive-timeout", 218 | Value: 75000, // nat keepalive timeout in Milliseconds 219 | Usage: "timeout in milliseconds for heartbeats response", 220 | }, 221 | cli.StringFlag{ 222 | Name: "ser", 223 | Value: "raw", 224 | Usage: `enable built-in service. values: raw (pair: raw), socks5 (pair: fast), http (pair: fast)`, 225 | }, 226 | cli.BoolTFlag{ 227 | Name: "tfo", 228 | Usage: `enable TCP fast open, use "--tfo=0" to disable`, 229 | }, 230 | cli.StringFlag{ 231 | Name: "snmplog", 232 | Value: "", 233 | Usage: "collect snmp to file, aware of timeformat in golang, like: ./snmp-20060102.log", 234 | }, 235 | cli.IntFlag{ 236 | Name: "snmpperiod", 237 | Value: 60, 238 | Usage: "snmp collect period, in seconds", 239 | }, 240 | cli.StringFlag{ 241 | Name: "log", 242 | Value: "", 243 | Usage: "specify a log file to output, default goes to stderr", 244 | }, 245 | cli.BoolFlag{ 246 | Name: "quiet", 247 | Usage: "to suppress the 'stream open/close' messages", 248 | }, 249 | cli.StringFlag{ 250 | Name: "c", 251 | Value: "", // when the value is not empty, the config path must exists 252 | Usage: "config from json file, which will override the command from shell", 253 | }, 254 | } 255 | myApp.Action = func(c *cli.Context) error { 256 | config := Config{} 257 | config.LocalAddr = c.String("localaddr") 258 | config.RemoteAddr = c.String("remoteaddr") 259 | config.Key = c.String("key") 260 | config.Crypt = c.String("crypt") 261 | config.Mode = c.String("mode") 262 | config.Conn = c.Int("conn") 263 | config.AutoExpire = c.Int("autoexpire") 264 | config.ScavengeTTL = c.Int("scavengettl") 265 | config.MTU = c.Int("mtu") 266 | config.SndWnd = c.Int("sndwnd") 267 | config.RcvWnd = c.Int("rcvwnd") 268 | config.DataShard = c.Int("datashard") 269 | config.ParityShard = c.Int("parityshard") 270 | config.DSCP = c.Int("dscp") 271 | config.NoComp = c.Bool("nocomp") 272 | config.AckNodelay = c.Bool("acknodelay") 273 | config.NoDelay = c.Int("nodelay") 274 | config.Interval = c.Int("interval") 275 | config.Resend = c.Int("resend") 276 | config.NoCongestion = c.Int("nc") 277 | config.Log = c.String("log") 278 | config.SnmpLog = c.String("snmplog") 279 | config.SnmpPeriod = c.Int("snmpperiod") 280 | config.Quiet = c.Bool("quiet") 281 | 282 | // smux setting 283 | config.SockBuf = c.Int("sockbuf") 284 | config.StreamBuf = c.Int("streambuf") 285 | config.StreamBufEn = c.Bool("streambuf-en") 286 | config.BoostTimeout = c.Int("boosttimeout") 287 | config.MaxFrameSize = c.Int("maxframe") 288 | config.PipeBuf = c.Int("pipebuf") 289 | config.KeepAlive = c.Int("keepalive") 290 | config.KeepAliveMS = c.Int("keepalivems") 291 | config.KeepAliveTimeout = c.Int("keepalive-timeout") 292 | 293 | // extra 294 | config.Service = c.String("ser") 295 | config.TFO = c.Bool("tfo") 296 | 297 | if c.String("c") != "" { 298 | err := parseJSONConfig(&config, c.String("c")) 299 | checkError(err) 300 | } 301 | 302 | // log redirect 303 | if config.Log != "" { 304 | f, err := os.OpenFile(config.Log, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) 305 | checkError(err) 306 | defer f.Close() 307 | log.SetOutput(f) 308 | } 309 | 310 | switch config.Mode { 311 | case "normal": 312 | config.NoDelay, config.Interval, config.Resend, config.NoCongestion = 0, 40, 2, 1 313 | case "fast": 314 | config.NoDelay, config.Interval, config.Resend, config.NoCongestion = 0, 30, 2, 1 315 | case "fast2": 316 | config.NoDelay, config.Interval, config.Resend, config.NoCongestion = 1, 20, 2, 1 317 | case "fast3": 318 | config.NoDelay, config.Interval, config.Resend, config.NoCongestion = 1, 10, 2, 1 319 | } 320 | 321 | log.Println("version:", VERSION) 322 | addr, err := net.ResolveTCPAddr("tcp", config.LocalAddr) 323 | checkError(err) 324 | listener, err := net.ListenTCP("tcp", addr) 325 | checkError(err) 326 | 327 | if config.TFO { 328 | bindTFO(listener) 329 | } 330 | 331 | log.Println("initiating key derivation") 332 | pass := pbkdf2.Key([]byte(config.Key), []byte(SALT), 4096, 32, sha1.New) 333 | var block kcp.BlockCrypt 334 | switch config.Crypt { 335 | case "sm4": 336 | block, _ = kcp.NewSM4BlockCrypt(pass[:16]) 337 | case "tea": 338 | block, _ = kcp.NewTEABlockCrypt(pass[:16]) 339 | case "xor": 340 | block, _ = kcp.NewSimpleXORBlockCrypt(pass) 341 | case "none": 342 | block, _ = kcp.NewNoneBlockCrypt(pass) 343 | case "aes-128": 344 | block, _ = kcp.NewAESBlockCrypt(pass[:16]) 345 | case "aes-192": 346 | block, _ = kcp.NewAESBlockCrypt(pass[:24]) 347 | case "blowfish": 348 | block, _ = kcp.NewBlowfishBlockCrypt(pass) 349 | case "twofish": 350 | block, _ = kcp.NewTwofishBlockCrypt(pass) 351 | case "cast5": 352 | block, _ = kcp.NewCast5BlockCrypt(pass[:16]) 353 | case "3des": 354 | block, _ = kcp.NewTripleDESBlockCrypt(pass[:24]) 355 | case "xtea": 356 | block, _ = kcp.NewXTEABlockCrypt(pass[:16]) 357 | case "salsa20": 358 | block, _ = kcp.NewSalsa20BlockCrypt(pass) 359 | default: 360 | config.Crypt = "aes" 361 | block, _ = kcp.NewAESBlockCrypt(pass) 362 | } 363 | 364 | if config.KeepAliveMS == 0 { 365 | config.KeepAliveMS = config.KeepAlive * 1000 366 | } 367 | 368 | log.Println("listening on:", listener.Addr()) 369 | log.Println("encryption:", config.Crypt) 370 | log.Println("nodelay parameters:", config.NoDelay, config.Interval, config.Resend, config.NoCongestion) 371 | log.Println("remote address:", config.RemoteAddr) 372 | log.Println("sndwnd:", config.SndWnd, "rcvwnd:", config.RcvWnd) 373 | log.Println("compression:", !config.NoComp) 374 | log.Println("mtu:", config.MTU) 375 | log.Println("datashard:", config.DataShard, "parityshard:", config.ParityShard) 376 | log.Println("acknodelay:", config.AckNodelay) 377 | log.Println("dscp:", config.DSCP) 378 | log.Println("conn:", config.Conn) 379 | log.Println("autoexpire:", config.AutoExpire) 380 | log.Println("scavengettl:", config.ScavengeTTL) 381 | log.Println("snmplog:", config.SnmpLog) 382 | log.Println("snmpperiod:", config.SnmpPeriod) 383 | log.Println("quiet:", config.Quiet) 384 | 385 | log.Println("keepaliveMS:", config.KeepAliveMS) 386 | log.Println("keepalive-timeout:", config.KeepAliveTimeout) 387 | log.Println("sockbuf:", config.SockBuf) 388 | log.Println("streambuf-en:", config.StreamBufEn) 389 | log.Println("streambuf:", config.StreamBuf) 390 | log.Println("boosttimeout:", config.BoostTimeout) 391 | log.Println("maxframe:", config.MaxFrameSize) 392 | log.Println("pipebuf:", config.PipeBuf) 393 | 394 | log.Println("service:", config.Service) 395 | log.Println("TCP Fast Open(tfo):", config.TFO) 396 | 397 | 398 | smuxConfig := smux.DefaultConfig() 399 | smuxConfig.MaxReceiveBuffer = config.SockBuf 400 | smuxConfig.KeepAliveInterval = time.Duration(config.KeepAliveMS) * time.Millisecond 401 | smuxConfig.KeepAliveTimeout = time.Duration(config.KeepAliveTimeout) * time.Millisecond 402 | smuxConfig.MaxFrameSize = config.MaxFrameSize 403 | smuxConfig.MaxStreamBuffer = config.StreamBuf 404 | smuxConfig.EnableStreamBuffer = config.StreamBufEn 405 | smuxConfig.BoostTimeout = time.Duration(config.BoostTimeout) * time.Millisecond 406 | 407 | createConn := func() (*smux.Session, error) { 408 | kcpconn, err := kcp.DialWithOptions(config.RemoteAddr, block, config.DataShard, config.ParityShard) 409 | if err != nil { 410 | return nil, errors.Wrap(err, "createConn()") 411 | } 412 | kcpconn.SetStreamMode(true) 413 | kcpconn.SetWriteDelay(false) 414 | kcpconn.SetNoDelay(config.NoDelay, config.Interval, config.Resend, config.NoCongestion) 415 | kcpconn.SetWindowSize(config.SndWnd, config.RcvWnd) 416 | kcpconn.SetMtu(config.MTU) 417 | kcpconn.SetACKNoDelay(config.AckNodelay) 418 | 419 | if err := kcpconn.SetDSCP(config.DSCP); err != nil { 420 | log.Println("SetDSCP:", err) 421 | } 422 | if err := kcpconn.SetReadBuffer(config.SockBuf); err != nil { 423 | log.Println("SetReadBuffer:", err) 424 | } 425 | if err := kcpconn.SetWriteBuffer(config.SockBuf); err != nil { 426 | log.Println("SetWriteBuffer:", err) 427 | } 428 | 429 | // stream multiplex 430 | var session *smux.Session 431 | if config.NoComp { 432 | session, err = smux.Client(kcpconn, smuxConfig) 433 | } else { 434 | session, err = smux.Client(newCompStream(kcpconn), smuxConfig) 435 | } 436 | if err != nil { 437 | return nil, errors.Wrap(err, "createConn()") 438 | } 439 | log.Println("connection:", kcpconn.LocalAddr(), "->", kcpconn.RemoteAddr()) 440 | return session, nil 441 | } 442 | 443 | // wait until a connection is ready 444 | waitConn := func() *smux.Session { 445 | for { 446 | if session, err := createConn(); err == nil { 447 | return session 448 | } else { 449 | log.Println("re-connecting:", err) 450 | time.Sleep(time.Second) 451 | } 452 | } 453 | } 454 | 455 | numconn := uint16(config.Conn) 456 | muxes := make([]struct { 457 | session *smux.Session 458 | ttl time.Time 459 | }, numconn) 460 | 461 | for k := range muxes { 462 | muxes[k].session = waitConn() 463 | muxes[k].ttl = time.Now().Add(time.Duration(config.AutoExpire) * time.Second) 464 | } 465 | 466 | chScavenger := make(chan *smux.Session, 128) 467 | go scavenger(chScavenger, config.ScavengeTTL) 468 | go snmpLogger(config.SnmpLog, config.SnmpPeriod) 469 | rr := uint16(0) 470 | for { 471 | p1, err := listener.AcceptTCP() 472 | if err != nil { 473 | log.Fatalln(err) 474 | } 475 | checkError(err) 476 | idx := rr % numconn 477 | 478 | // do auto expiration && reconnection 479 | if muxes[idx].session.IsClosed() || (config.AutoExpire > 0 && time.Now().After(muxes[idx].ttl)) { 480 | chScavenger <- muxes[idx].session 481 | muxes[idx].session = waitConn() 482 | muxes[idx].ttl = time.Now().Add(time.Duration(config.AutoExpire) * time.Second) 483 | } 484 | 485 | go handleClient(muxes[idx].session, p1, config.Quiet, config.PipeBuf, config.Service) 486 | rr++ 487 | } 488 | } 489 | myApp.Run(os.Args) 490 | } 491 | 492 | type scavengeSession struct { 493 | session *smux.Session 494 | ts time.Time 495 | } 496 | 497 | func scavenger(ch chan *smux.Session, ttl int) { 498 | ticker := time.NewTicker(time.Second) 499 | defer ticker.Stop() 500 | var sessionList []scavengeSession 501 | for { 502 | select { 503 | case sess := <-ch: 504 | sessionList = append(sessionList, scavengeSession{sess, time.Now()}) 505 | log.Println("session marked as expired") 506 | case <-ticker.C: 507 | var newList []scavengeSession 508 | for k := range sessionList { 509 | s := sessionList[k] 510 | if s.session.NumStreams() == 0 || s.session.IsClosed() { 511 | log.Println("session normally closed") 512 | s.session.Close() 513 | } else if ttl >= 0 && time.Since(s.ts) >= time.Duration(ttl)*time.Second { 514 | log.Println("session reached scavenge ttl") 515 | s.session.Close() 516 | } else { 517 | newList = append(newList, sessionList[k]) 518 | } 519 | } 520 | sessionList = newList 521 | } 522 | } 523 | } 524 | 525 | func snmpLogger(path string, interval int) { 526 | if path == "" || interval == 0 { 527 | return 528 | } 529 | ticker := time.NewTicker(time.Duration(interval) * time.Second) 530 | defer ticker.Stop() 531 | for { 532 | select { 533 | case <-ticker.C: 534 | // split path into dirname and filename 535 | logdir, logfile := filepath.Split(path) 536 | // only format logfile 537 | f, err := os.OpenFile(logdir+time.Now().Format(logfile), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) 538 | if err != nil { 539 | log.Println(err) 540 | return 541 | } 542 | w := csv.NewWriter(f) 543 | // write header in empty file 544 | if stat, err := f.Stat(); err == nil && stat.Size() == 0 { 545 | if err := w.Write(append([]string{"Unix"}, kcp.DefaultSnmp.Header()...)); err != nil { 546 | log.Println(err) 547 | } 548 | } 549 | if err := w.Write(append([]string{fmt.Sprint(time.Now().Unix())}, kcp.DefaultSnmp.ToSlice()...)); err != nil { 550 | log.Println(err) 551 | } 552 | kcp.DefaultSnmp.Reset() 553 | w.Flush() 554 | f.Close() 555 | } 556 | } 557 | } 558 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kcptunB 2 | 3 | kcptunB, **B** is for Breaking fixes/features. 4 | 5 | The different between original is that kcptunB **include the fixes and features that could NEVER be solved without breaking changes!!** 6 | And add some features that may never be merge. 7 | 8 | #### Config changes 9 | 10 | * keepalive: 11 | * `--keepalive`: **deprecated**, use `--keepalivems` instead 12 | * `--keepalivems`: replace `--keepalive`, but unit is milliseconds 13 | * `--keepalive-timeout`: timeout for heartbeat response, in milliseconds 14 | * `--keepalivems` can and **usually bigger** than `--keepalive-timeout` 15 | * stream buffer: 16 | * `--streambuf-en`: enable per-socket buffer by default, use "--streambuf-en=0" to __disable all breaking fixes/features__ 17 | * `--streambuf`: per-socket buffer size 18 | * `--streamboost`: boost for startup in milliseconds, affect tcp slow-start 19 | * built-in proxy: `--ser` 20 | * see [Usage of built-in proxy](#usage-of-built-in-proxy) 21 | * TCP Fast Open 22 | * `--tfo`: enable TCP fast open, use "--tfo=0" to disable, **enable by default** 23 | * current support & tested: linux (kernel 4.11+ for tcp clients, 3.7+ for tcp servers), FreeBSD (10.3+ for tcp servers, 12.0+ for tcp clients) 24 | * current support but **not tested**: darwin (OS X 10.11+) 25 | * should support but not implementation yet: windows 26 | * not support (yet): Solaris, OpenBSD, NetBSD, DragonFly BSD 27 | * `tcp server side` is kcptun client, `tcp client side` is kcptun server 28 | * system config: 29 | * linux: `sudo sysctl -w net.ipv4.tcp_fastopen=3` or `echo "3" > /proc/sys/net/ipv4/tcp_fastopen` 30 | * FreeBSD: `sysctl -w net.inet.tcp.fastopen.server_enable=1`, `sysctl -w net.inet.tcp.fastopen.client_enable=1` 31 | 32 | #### Usage of built-in proxy 33 | 34 | * server side, `-ser` values: 35 | * `raw`: only connect to `--target` (default) 36 | * `fast`: can connect to anyway client want, but client side MUST set with `socks5` or `http` 37 | 38 | * client side, `-ser` values: 39 | * `raw`: only connect to server side's `--target` (default) 40 | * `socks5`: can connect to anyway via socks5 protocol, but server side MUST set with `fast` 41 | * `http`: can connect to anyway via http proxy, but server side MUST set with `fast` 42 | 43 | ##### example 44 | 45 | 1. Application can use socks5 proxy 46 | 47 | ``` 48 | # Server command: 49 | ./server_linux_amd64 -t "TARGET_IP:8388" -l ":4000" -ser fast 50 | 51 | # Client command: 52 | ./client_darwin_amd64 -r "KCP_SERVER_IP:4000" -l ":8388" -ser socks5 53 | 54 | # Application: 55 | curl -v -x "socks5://127.0.0.1:8388" http://google.com 56 | ``` 57 | 58 | 2. Application can use http proxy 59 | 60 | ``` 61 | # Server command: 62 | ./server_linux_amd64 -t "TARGET_IP:8388" -l ":4000" -ser fast 63 | 64 | # Client command: 65 | ./client_darwin_amd64 -r "KCP_SERVER_IP:4000" -l ":8388" -ser http 66 | 67 | # Application: 68 | curl -v -x "http://127.0.0.1:8388" http://google.com 69 | ``` 70 | 71 | ---- 72 | 73 | # kcptun 74 | 75 | [![Release][13]][14] [![Powered][17]][18] [![MIT licensed][11]][12] [![Build Status][3]][4] [![Go Report Card][5]][6] [![Downloads][15]][16] [![Docker][1]][2] 76 | 77 | [1]: https://images.microbadger.com/badges/image/xtaci/kcptun.svg 78 | [2]: https://microbadger.com/images/xtaci/kcptun 79 | [3]: https://travis-ci.org/xtaci/kcptun.svg?branch=master 80 | [4]: https://travis-ci.org/xtaci/kcptun 81 | [5]: https://goreportcard.com/badge/github.com/xtaci/kcptun 82 | [6]: https://goreportcard.com/report/github.com/xtaci/kcptun 83 | [7]: https://img.shields.io/badge/license-MIT-blue.svg 84 | [8]: https://raw.githubusercontent.com/xtaci/kcptun/master/LICENSE.md 85 | [11]: https://img.shields.io/badge/license-MIT-blue.svg 86 | [12]: LICENSE.md 87 | [13]: https://img.shields.io/github/release/xtaci/kcptun.svg 88 | [14]: https://github.com/xtaci/kcptun/releases/latest 89 | [15]: https://img.shields.io/github/downloads/xtaci/kcptun/total.svg?maxAge=1800 90 | [16]: https://github.com/xtaci/kcptun/releases 91 | [17]: https://img.shields.io/badge/KCP-Powered-blue.svg 92 | [18]: https://github.com/skywind3000/kcp 93 | 94 | kcptun 95 | 96 | > *Disclaimer: kcptun maintains a single website — [github.com/xtaci/kcptun](https://github.com/xtaci/kcptun). Any websites other than [github.com/xtaci/kcptun](https://github.com/xtaci/kcptun) are not endorsed by xtaci.* 97 | 98 | ### QuickStart 99 | 100 | Increase the number of open files on your server, as: 101 | 102 | `ulimit -n 65535`, or write it in `~/.bashrc`. 103 | 104 | Suggested `sysctl.conf` parameters for better handling of UDP packets: 105 | 106 | ``` 107 | net.core.rmem_max=26214400 // BDP - bandwidth delay product 108 | net.core.rmem_default=26214400 109 | net.core.wmem_max=26214400 110 | net.core.wmem_default=26214400 111 | net.core.netdev_max_backlog=2048 // proportional to -rcvwnd 112 | ``` 113 | 114 | You can also increase the per-socket buffer by adding parameter(default 4MB): 115 | ``` 116 | -sockbuf 16777217 117 | ``` 118 | for **slow processors**, increasing this buffer is **CRITICAL** to receive packets properly. 119 | 120 | Download a corresponding one from precompiled [Releases](https://github.com/xtaci/kcptun/releases). 121 | 122 | ``` 123 | KCP Client: ./client_darwin_amd64 -r "KCP_SERVER_IP:4000" -l ":8388" -mode fast3 -nocomp -autoexpire 900 -sockbuf 16777217 -dscp 46 124 | KCP Server: ./server_linux_amd64 -t "TARGET_IP:8388" -l ":4000" -mode fast3 -nocomp -sockbuf 16777217 -dscp 46 125 | ``` 126 | The above commands will establish port forwarding channel for 8388/tcp as: 127 | 128 | > Application -> **KCP Client(8388/tcp) -> KCP Server(4000/udp)** -> Target Server(8388/tcp) 129 | 130 | which tunnels the original connection: 131 | 132 | > Application -> Target Server(8388/tcp) 133 | 134 | ### Install from source 135 | 136 | ``` 137 | $go get -u github.com/xtaci/kcptun/... 138 | ``` 139 | 140 | All precompiled releases are genereated from `build-release.sh` script. 141 | 142 | ### Performance 143 | 144 | fast.com 145 | 146 | ### Basic Tuning Guide 147 | 148 | #### Improving Thoughput 149 | 150 | > **Q: I have a high speed network link, how to reach the maximum bandwidth?** 151 | 152 | > **A:** Increase `-rcvwnd` on KCP Client and `-sndwnd` on KCP Server **simultaneously & gradually**, the mininum one decides the maximum transfer rate of the link, as `wnd * mtu / rtt`; Then try downloading something and to see if it meets your requirements. 153 | (mtu is adjustable by `-mtu`) 154 | 155 | #### Improving Latency 156 | 157 | > **Q: I'm using kcptun for game, I don't want any lag happening.** 158 | 159 | > **A:** Lag means packet loss for most of the time, lags can be improved by changing `-mode`. 160 | 161 | > eg: `-mode fast3` 162 | 163 | > Aggresiveness/Responsiveness on retransmission for embeded modes are: 164 | 165 | > *fast3 > fast2 > fast > normal > default* 166 | 167 | 168 | 169 | ### Expert Tuning Guide 170 | 171 | #### Overview 172 | 173 |

params

174 | 175 | #### Usage 176 | 177 | ``` 178 | $ ./client_linux_amd64 -h 179 | NAME: 180 | kcptun - client(with SMUX) 181 | 182 | USAGE: 183 | client_linux_amd64 [global options] command [command options] [arguments...] 184 | 185 | VERSION: 186 | v2.0.3 187 | 188 | COMMANDS: 189 | help, h Shows a list of commands or help for one command 190 | 191 | GLOBAL OPTIONS: 192 | --localaddr value, -l value local listen address (default: ":12948") 193 | --remoteaddr value, -r value kcp server address (default: "vps:29900") 194 | --key value pre-shared secret between client and server (default: "it's a secrect") [$KCPTUN_KEY] 195 | --crypt value aes, aes-128, aes-192, salsa20, blowfish, twofish, cast5, 3des, tea, xtea, xor, sm4, none (default: "aes") 196 | --mode value profiles: fast3, fast2, fast, normal, manual (default: "fast") 197 | --conn value set num of UDP connections to server (default: 1) 198 | --autoexpire value set auto expiration time(in seconds) for a single UDP connection, 0 to disable (default: 0) 199 | --scavengettl value set how long an expired connection can live(in sec), -1 to disable (default: 600) 200 | --mtu value set maximum transmission unit for UDP packets (default: 1350) 201 | --sndwnd value set send window size(num of packets) (default: 128) 202 | --rcvwnd value set receive window size(num of packets) (default: 512) 203 | --datashard value, --ds value set reed-solomon erasure coding - datashard (default: 10) 204 | --parityshard value, --ps value set reed-solomon erasure coding - parityshard (default: 3) 205 | --dscp value set DSCP(6bit) (default: 0) 206 | --nocomp disable compression 207 | --sockbuf value per-session buffer in bytes (default: 4194304) 208 | --streambuf-en enable per-socket buffer, use "--streambuf-en=0" to disable 209 | --streambuf value per-socket buffer in bytes (default: 262144) 210 | --streamboost value stream boost for startup in ms, affect tcp slow-start (default: 10000) 211 | --maxframe value max smux frame size in bytes (default: 32767) 212 | --pipebuf value internal io.CopyBuffer buffer size in bytes (default: 262144) 213 | --keepalive value (deprecated) seconds between heartbeats (default: 10) 214 | --keepalivems value milliseconds between heartbeats, will overwrite keepalive (default: 10000) 215 | --keepalive-timeout value timeout in milliseconds for heartbeats response (default: 75000) 216 | --ser value enable built-in service. values: raw (pair: raw), socks5 (pair: fast), http (pair: fast) (default: "raw") 217 | --snmplog value collect snmp to file, aware of timeformat in golang, like: ./snmp-20060102.log 218 | --snmpperiod value snmp collect period, in seconds (default: 60) 219 | --log value specify a log file to output, default goes to stderr 220 | --quiet to suppress the 'stream open/close' messages 221 | -c value config from json file, which will override the command from shell 222 | --help, -h show help 223 | --version, -v print the version 224 | 225 | 226 | 227 | $ ./server_linux_amd64 -h 228 | NAME: 229 | kcptun - server(with SMUX) 230 | 231 | USAGE: 232 | server_linux_amd64 [global options] command [command options] [arguments...] 233 | 234 | VERSION: 235 | v2.0.3 236 | 237 | COMMANDS: 238 | help, h Shows a list of commands or help for one command 239 | 240 | GLOBAL OPTIONS: 241 | --listen value, -l value kcp server listen address (default: ":29900") 242 | --target value, -t value target server address (default: "127.0.0.1:12948") 243 | --key value pre-shared secret between client and server (default: "it's a secrect") [$KCPTUN_KEY] 244 | --crypt value aes, aes-128, aes-192, salsa20, blowfish, twofish, cast5, 3des, tea, xtea, xor, sm4, none (default: "aes") 245 | --mode value profiles: fast3, fast2, fast, normal, manual (default: "fast") 246 | --mtu value set maximum transmission unit for UDP packets (default: 1350) 247 | --sndwnd value set send window size(num of packets) (default: 1024) 248 | --rcvwnd value set receive window size(num of packets) (default: 1024) 249 | --datashard value, --ds value set reed-solomon erasure coding - datashard (default: 10) 250 | --parityshard value, --ps value set reed-solomon erasure coding - parityshard (default: 3) 251 | --dscp value set DSCP(6bit) (default: 0) 252 | --nocomp disable compression 253 | --sockbuf value per-session buffer in bytes (default: 4194304) 254 | --streambuf-en enable per-socket buffer, use "--streambuf-en=0" to disable 255 | --streambuf value per-socket buffer in bytes (default: 262144) 256 | --streamboost value stream boost for startup in ms, affect tcp slow-start (default: 10000) 257 | --maxframe value max smux frame size in bytes (default: 32767) 258 | --pipebuf value internal io.CopyBuffer buffer size in bytes (default: 262144) 259 | --keepalive value (deprecated) seconds between heartbeats (default: 10) 260 | --keepalivems value milliseconds between heartbeats, will overwrite keepalive (default: 10000) 261 | --keepalive-timeout value timeout in milliseconds for heartbeats response (default: 75000) 262 | --dns value failback DNS for case that can't parse "/etc/resolv.conf", eg: run on Android; split multi-address by ',', eg: "8.8.8.8:53,8.8.4.4:53" 263 | --ser value enable built-in service, values: raw (pair: raw), fast (socks5-mod-reduce-1-RTT, pair: socks5, http) (default: "raw") 264 | --snmplog value collect snmp to file, aware of timeformat in golang, like: ./snmp-20060102.log 265 | --snmpperiod value snmp collect period, in seconds (default: 60) 266 | --pprof start profiling server on :6060 267 | --log value specify a log file to output, default goes to stderr 268 | --quiet to suppress the 'stream open/close' messages 269 | -c value config from json file, which will override the command from shell 270 | --help, -h show help 271 | --version, -v print the version 272 | 273 | ``` 274 | 275 | #### Forward Error Correction 276 | 277 | In coding theory, the [Reed–Solomon code](https://en.wikipedia.org/wiki/Reed%E2%80%93Solomon_error_correction) belongs to the class of non-binary cyclic error-correcting codes. The Reed–Solomon code is based on univariate polynomials over finite fields. 278 | 279 | It is able to detect and correct multiple symbol errors. By adding t check symbols to the data, a Reed–Solomon code can detect any combination of up to t erroneous symbols, or correct up to ⌊t/2⌋ symbols. As an erasure code, it can correct up to t known erasures, or it can detect and correct combinations of errors and erasures. Furthermore, Reed–Solomon codes are suitable as multiple-burst bit-error correcting codes, since a sequence of b + 1 consecutive bit errors can affect at most two symbols of size b. The choice of t is up to the designer of the code, and may be selected within wide limits. 280 | 281 | ![FED](FEC.png) 282 | 283 | Setting parameters of RS-Code with ```-datashard m -parityshard n``` on **BOTH** KCP Client & KCP Server **MUST** be **IDENTICAL**. 284 | 285 | #### DSCP 286 | 287 | Differentiated services or DiffServ is a computer networking architecture that specifies a simple, scalable and coarse-grained mechanism for classifying and managing network traffic and providing quality of service (QoS) on modern IP networks. DiffServ can, for example, be used to provide low-latency to critical network traffic such as voice or streaming media while providing simple best-effort service to non-critical services such as web traffic or file transfers. 288 | 289 | DiffServ uses a 6-bit differentiated services code point (DSCP) in the 8-bit differentiated services field (DS field) in the IP header for packet classification purposes. The DS field and ECN field replace the outdated IPv4 TOS field. 290 | 291 | setting each side with ```-dscp value```, Here are some [Commonly used DSCP values](https://en.wikipedia.org/wiki/Differentiated_services#Commonly_used_DSCP_values). 292 | 293 | #### Cryptanalysis 294 | 295 | kcptun is shipped with builtin packet encryption powered by various block encryption algorithms and works in [Cipher Feedback Mode](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Feedback_(CFB)), for each packet to be sent, the encryption process will start from encrypting a [nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce) from the [system entropy](https://en.wikipedia.org/wiki//dev/random), so encryption to same plaintexts never leads to a same ciphertexts thereafter. 296 | 297 | The contents of the packets are completely anonymous with encryption, including the headers(FEC,KCP), checksums and contents. Note that, no matter which encryption method you choose on you upper layer, if you disable encryption by specifying `-crypt none` to kcptun, the transmit will be insecure somehow, since the header is ***PLAINTEXT*** to everyone it would be susceptible to header tampering, such as jamming the *sliding window size*, *round-trip time*, *FEC property* and *checksums*. ```aes-128``` is suggested for minimal encryption since modern CPUs are shipped with [AES-NI](https://en.wikipedia.org/wiki/AES_instruction_set) instructions and performs even better than `salsa20`(check the table below). 298 | 299 | Other possible attacks to kcptun includes: a) [traffic analysis](https://en.wikipedia.org/wiki/Traffic_analysis), dataflow on specific websites may have pattern while interchanging data, but this type of eavesdropping has been mitigated by adapting [smux](https://github.com/xtaci/smux) to mix data streams so as to introduce noises, perfect solution to this has not appeared yet, theroretically by shuffling/mixing messages on larger scale network may mitigate this problem. b) [replay attack](https://en.wikipedia.org/wiki/Replay_attack), since the asymmetrical encryption has not been introduced into kcptun for some reason, capturing the packets and replay them on a different machine is possible, (notice: hijacking the session and decrypting the contents is still *impossible*), so upper layers should contain a asymmetrical encryption system to guarantee the authenticity of each message(to process message exactly once), such as HTTPS/OpenSSL/LibreSSL, only by signing the requests with private keys can eliminate this type of attack. 300 | 301 | Important: 302 | 1. `-crypt` and `-key` must be the same on both KCP Client & KCP Server. 303 | 2. `-crypt xor` is also insecure and vulnerable to [known-plaintext attack](https://en.wikipedia.org/wiki/Known-plaintext_attack), do not use this unless you know what you are doing. (*cryptanalysis note: any type of [counter mode](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_(CTR)) is insecure in packet encryption due to the shorten of counter period and leads to iv/nonce collision*) 304 | 305 | Benchmarks for crypto algorithms supported by kcptun: 306 | 307 | ``` 308 | BenchmarkSM4-4 50000 32087 ns/op 93.49 MB/s 0 B/op 0 allocs/op 309 | BenchmarkAES128-4 500000 3274 ns/op 916.15 MB/s 0 B/op 0 allocs/op 310 | BenchmarkAES192-4 500000 3587 ns/op 836.34 MB/s 0 B/op 0 allocs/op 311 | BenchmarkAES256-4 300000 3828 ns/op 783.60 MB/s 0 B/op 0 allocs/op 312 | BenchmarkTEA-4 100000 15359 ns/op 195.32 MB/s 0 B/op 0 allocs/op 313 | BenchmarkXOR-4 20000000 90.2 ns/op 33249.02 MB/s 0 B/op 0 allocs/op 314 | BenchmarkBlowfish-4 50000 26885 ns/op 111.58 MB/s 0 B/op 0 allocs/op 315 | BenchmarkNone-4 30000000 45.8 ns/op 65557.11 MB/s 0 B/op 0 allocs/op 316 | BenchmarkCast5-4 50000 34370 ns/op 87.29 MB/s 0 B/op 0 allocs/op 317 | Benchmark3DES-4 10000 117893 ns/op 25.45 MB/s 0 B/op 0 allocs/op 318 | BenchmarkTwofish-4 50000 33477 ns/op 89.61 MB/s 0 B/op 0 allocs/op 319 | BenchmarkXTEA-4 30000 45825 ns/op 65.47 MB/s 0 B/op 0 allocs/op 320 | BenchmarkSalsa20-4 500000 3282 ns/op 913.90 MB/s 0 B/op 0 allocs/op 321 | ``` 322 | 323 | Benchmark result from openssl 324 | 325 | ``` 326 | $ openssl speed -evp aes-128-cfb 327 | Doing aes-128-cfb for 3s on 16 size blocks: 157794127 aes-128-cfb's in 2.98s 328 | Doing aes-128-cfb for 3s on 64 size blocks: 39614018 aes-128-cfb's in 2.98s 329 | Doing aes-128-cfb for 3s on 256 size blocks: 9971090 aes-128-cfb's in 2.99s 330 | Doing aes-128-cfb for 3s on 1024 size blocks: 2510877 aes-128-cfb's in 2.99s 331 | Doing aes-128-cfb for 3s on 8192 size blocks: 310865 aes-128-cfb's in 2.98s 332 | OpenSSL 1.0.2p 14 Aug 2018 333 | built on: reproducible build, date unspecified 334 | options:bn(64,64) rc4(ptr,int) des(idx,cisc,16,int) aes(partial) idea(int) blowfish(idx) 335 | compiler: clang -I. -I.. -I../include -fPIC -fno-common -DOPENSSL_PIC -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -arch x86_64 -O3 -DL_ENDIAN -Wall -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM -DECP_NISTZ256_ASM 336 | The 'numbers' are in 1000s of bytes per second processed. 337 | type 16 bytes 64 bytes 256 bytes 1024 bytes 8192 bytes 338 | aes-128-cfb 847216.79k 850770.86k 853712.05k 859912.39k 854565.80k 339 | ``` 340 | 341 | The encrytion performance in kcptun is as fast as in openssl library(if not faster). 342 | 343 | 344 | #### Memory Usage Control 345 | 346 | Routers, mobile devices are susceptible to memory consumption; by setting GOGC environment(eg: GOGC=20) will make the garbage collector to recycle faster. 347 | Reference: https://blog.golang.org/go15gc 348 | 349 | #### Compression 350 | 351 | kcptun has builtin snappy algorithms for compressing streams: 352 | 353 | > Snappy is a compression/decompression library. It does not aim for maximum 354 | > compression, or compatibility with any other compression library; instead, 355 | > it aims for very high speeds and reasonable compression. For instance, 356 | > compared to the fastest mode of zlib, Snappy is an order of magnitude faster 357 | > for most inputs, but the resulting compressed files are anywhere from 20% to 358 | > 100% bigger. 359 | 360 | > Reference: http://google.github.io/snappy/ 361 | 362 | Compression may save bandwidth for **PLAINTEXT** data, it's quite useful for specific scenarios as cross-datacenter replications, by compressing the redologs in dbms or kafka-like message queues and then transfer the data streams across the continent can be much faster. 363 | 364 | Compression is enabled by default, you can disable it by setting ```-nocomp``` on **BOTH** KCP Client & KCP Server **MUST** be **IDENTICAL**. 365 | 366 | #### SNMP 367 | 368 | ```go 369 | // Snmp defines network statistics indicator 370 | type Snmp struct { 371 | BytesSent uint64 // raw bytes sent 372 | BytesReceived uint64 373 | MaxConn uint64 374 | ActiveOpens uint64 375 | PassiveOpens uint64 376 | CurrEstab uint64 // count of connections for now 377 | InErrs uint64 // udp read errors 378 | InCsumErrors uint64 // checksum errors from CRC32 379 | KCPInErrors uint64 // packet iput errors from kcp 380 | InSegs uint64 381 | OutSegs uint64 382 | InBytes uint64 // udp bytes received 383 | OutBytes uint64 // udp bytes sent 384 | RetransSegs uint64 385 | FastRetransSegs uint64 386 | EarlyRetransSegs uint64 387 | LostSegs uint64 // number of segs infered as lost 388 | RepeatSegs uint64 // number of segs duplicated 389 | FECRecovered uint64 // correct packets recovered from FEC 390 | FECErrs uint64 // incorrect packets recovered from FEC 391 | FECSegs uint64 // FEC segments received 392 | FECShortShards uint64 // number of data shards that's not enough for recovery 393 | } 394 | ``` 395 | 396 | Sending a `SIGUSR1` signal to KCP Client or KCP Server will dump SNMP information to console, just like `/proc/net/snmp`. You can use this information to do fine-grained tuning. 397 | 398 | ### Manual Control 399 | 400 | https://github.com/skywind3000/kcp/blob/master/README.en.md#protocol-configuration 401 | 402 | `-mode manual -nodelay 1 -interval 20 -resend 2 -nc 1` 403 | 404 | Low-level KCP configuration can be altered by using manual mode like above, make sure you really **UNDERSTAND** what these means before doing **ANY** manual settings. 405 | 406 | 407 | ### Identical Parmeters 408 | 409 | The parameters below **MUST** be **IDENTICAL** on **BOTH** side: 410 | 411 | 1. -key 412 | 1. -crypt 413 | 1. -nocomp 414 | 1. -datashard 415 | 1. -parityshard 416 | 417 | ### References 418 | 419 | 1. https://github.com/skywind3000/kcp -- KCP - A Fast and Reliable ARQ Protocol. 420 | 1. https://github.com/xtaci/kcp-go/ -- A Production-Grade Reliable-UDP Library for golang 421 | 1. https://github.com/klauspost/reedsolomon -- Reed-Solomon Erasure Coding in Go. 422 | 1. https://en.wikipedia.org/wiki/Differentiated_services -- DSCP. 423 | 1. http://google.github.io/snappy/ -- A fast compressor/decompressor. 424 | 1. https://www.backblaze.com/blog/reed-solomon/ -- Reed-Solomon Explained. 425 | 1. http://www.qualcomm.cn/products/raptorq -- RaptorQ Forward Error Correction Scheme for Object Delivery. 426 | 1. https://en.wikipedia.org/wiki/PBKDF2 -- Key stretching. 427 | 1. http://blog.appcanary.com/2016/encrypt-or-compress.html -- Should you encrypt or compress first? 428 | 1. https://github.com/hashicorp/yamux -- Connection multiplexing library. 429 | 1. https://tools.ietf.org/html/rfc6937 -- Proportional Rate Reduction for TCP. 430 | 1. https://tools.ietf.org/html/rfc5827 -- Early Retransmit for TCP and Stream Control Transmission Protocol (SCTP). 431 | 1. http://http2.github.io/ -- What is HTTP/2? 432 | 1. http://www.lartc.org/ -- Linux Advanced Routing & Traffic Control 433 | 1. https://en.wikipedia.org/wiki/Noisy-channel_coding_theorem -- Noisy channel coding theorem 434 | 435 | ### Donate 436 | 437 | via Ethereum(ETH): Address: 0x2e4b43ab3d0983da282592571eef61ae5e60f726 , Or scan here: 438 | 439 | kcptun 440 | 441 | via WeChat 442 | 443 | kcptun 444 | 445 | (注意:我没有任何社交网站的账号,请小心骗子。) 446 | --------------------------------------------------------------------------------