mirror of
https://github.com/xtaci/kcptun.git
synced 2024-04-21 12:32:32 +00:00
commit message
This commit is contained in:
+26
@@ -0,0 +1,26 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
client/client
|
||||
server/server
|
||||
@@ -0,0 +1,6 @@
|
||||
FROM golang:latest
|
||||
MAINTAINER xtaci <daniel820313@gmail.com>
|
||||
RUN go get github.com/xtaci/kcptun/client
|
||||
RUN go get github.com/xtaci/kcptun/server
|
||||
EXPOSE 29900/udp
|
||||
EXPOSE 12948
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Daniel Fu
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,80 @@
|
||||
# kcptun(KT)
|
||||
TCP流转换为KCP+UDP流,>>>[下载地址](https://github.com/xtaci/kcptun/releases/latest)<<< 用于***高丢包***环境中的数据传输,工作示意图:
|
||||
```
|
||||
+---------------------------------------+
|
||||
| |
|
||||
| KCPTUN |
|
||||
| |
|
||||
+--------+ | +------------+ +------------+ | +--------+
|
||||
| | | | | | | | | |
|
||||
| Client | +--> | | KCP Client | +---> | KCP Server | | +--> | Server |
|
||||
| | TCP | | | UDP | | | TCP | |
|
||||
+--------+ | +------------+ +------------+ | +--------+
|
||||
| |
|
||||
| |
|
||||
+---------------------------------------+
|
||||
```
|
||||
***kcptun是[kcp](https://github.com/skywind3000/kcp)协议的一个简单应用,可以用于任意tcp网络程序的传输承载,以提高软件网络流畅度(如浏览器,telnet等),降低掉线情况。***
|
||||
|
||||
<img src="kitty.jpg" style="width: 300px;"/>
|
||||
|
||||
### Docker
|
||||
```
|
||||
docker pull xtaci/kcptun
|
||||
```
|
||||
|
||||
### 使用方法
|
||||
```
|
||||
D:\>client_windows_amd64.exe -h
|
||||
NAME:
|
||||
kcptun - kcptun client
|
||||
|
||||
USAGE:
|
||||
client_windows_amd64.exe [global options] command [command options] [arguments...]
|
||||
|
||||
VERSION:
|
||||
20160507
|
||||
|
||||
COMMANDS:
|
||||
help, h Shows a list of commands or help for one command
|
||||
|
||||
GLOBAL OPTIONS:
|
||||
--localaddr, -l ":12948" local listen addr:
|
||||
--remoteaddr, -r "vps:29900" kcp server addr
|
||||
--key "it's a secrect" key for communcation, must be the same as kcptun server [$KCPTUN_KEY]
|
||||
--mode "fast" mode for communication: fast, normal, default
|
||||
--tuncrypt enable tunnel encryption, adds extra secrecy for data transfer
|
||||
--help, -h show help
|
||||
--version, -v print the version
|
||||
|
||||
D:\>server_windows_amd64.exe -h
|
||||
NAME:
|
||||
kcptun - kcptun server
|
||||
|
||||
USAGE:
|
||||
server_windows_amd64.exe [global options] command [command options] [arguments...]
|
||||
|
||||
VERSION:
|
||||
20160507
|
||||
|
||||
COMMANDS:
|
||||
help, h Shows a list of commands or help for one command
|
||||
|
||||
GLOBAL OPTIONS:
|
||||
--listen, -l ":29900" kcp server listen addr:
|
||||
--target, -t "127.0.0.1:12948" target server addr
|
||||
--key "it's a secrect" key for communcation, must be the same as kcptun client [$KCPTUN_KEY]
|
||||
--mode "fast" mode for communication: fast, normal, default
|
||||
--tuncrypt enable tunnel encryption, adds extra secrecy for data transfer
|
||||
--help, -h show help
|
||||
--version, -v print the version
|
||||
```
|
||||
### 适用范围(包括但不限于):
|
||||
1. 网络游戏的数据传输
|
||||
2. 跨运营商的流量传输
|
||||
3. 其他高丢包,高干扰通信环境的TCP数据传输
|
||||
|
||||
# 免责申明
|
||||
用户以各种方式使用本软件(包括但不限于修改使用、直接使用、通过第三方使用)的过程中,不得以任何方式利用本软件直接或间接从事违反中国法律、以及社会公德的行为。软件的使用者需对自身行为负责,因使用软件引发的一切纠纷,由使用者承担全部法律及连带责任。作者不承担任何法律及连带责任。
|
||||
|
||||
对免责声明的解释、修改及更新权均属于作者本人所有。
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/bin/sh
|
||||
|
||||
VERSION=`date -u +%Y%m%d`
|
||||
LDFLAGS="-X main.VERSION=$VERSION"
|
||||
|
||||
OSES=(linux darwin windows)
|
||||
ARCHS=(amd64 386)
|
||||
for os in ${OSES[@]}; do
|
||||
for arch in ${ARCHS[@]}; do
|
||||
suffix=""
|
||||
if [ "$os" == "windows" ]
|
||||
then
|
||||
suffix=".exe"
|
||||
fi
|
||||
env GOOS=$os GOARCH=$arch go build -ldflags "$LDFLAGS" -o client_${os}_${arch}${suffix} github.com/xtaci/kcptun/client
|
||||
env GOOS=$os GOARCH=$arch go build -ldflags "$LDFLAGS" -o server_${os}_${arch}${suffix} github.com/xtaci/kcptun/server
|
||||
tar -zcf kcptun-${os}-${arch}-$VERSION.tar.gz client_${os}_${arch}${suffix} server_${os}_${arch}${suffix}
|
||||
md5 kcptun-${os}-${arch}-$VERSION.tar.gz
|
||||
done
|
||||
done
|
||||
|
||||
# ARM
|
||||
ARMS=(5 6 7)
|
||||
for v in ${ARMS[@]}; do
|
||||
env GOOS=linux GOARCH=arm GOARM=$v go build -ldflags "$LDFLAGS" -o client_linux_arm$v github.com/xtaci/kcptun/client
|
||||
env GOOS=linux GOARCH=arm GOARM=$v go build -ldflags "$LDFLAGS" -o server_linux_arm$v github.com/xtaci/kcptun/server
|
||||
done
|
||||
tar -zcf kcptun-linux-arm-$VERSION.tar.gz client_linux_arm* server_linux_arm*
|
||||
md5 kcptun-linux-arm-$VERSION.tar.gz
|
||||
+218
@@ -0,0 +1,218 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
crand "crypto/rand"
|
||||
"crypto/sha256"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/hashicorp/yamux"
|
||||
"github.com/xtaci/kcp-go"
|
||||
)
|
||||
|
||||
var VERSION = "SELFBUILD"
|
||||
|
||||
type secureConn struct {
|
||||
encoder cipher.Stream
|
||||
decoder cipher.Stream
|
||||
conn net.Conn
|
||||
}
|
||||
|
||||
func newSecureConn(key string, conn net.Conn, iv []byte) *secureConn {
|
||||
sc := new(secureConn)
|
||||
sc.conn = conn
|
||||
commkey := sha256.Sum256([]byte(key))
|
||||
|
||||
// encoder
|
||||
block, err := aes.NewCipher(commkey[:])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return nil
|
||||
}
|
||||
sc.encoder = cipher.NewCFBEncrypter(block, iv[:aes.BlockSize])
|
||||
|
||||
// decoder
|
||||
block, err = aes.NewCipher(commkey[:])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return nil
|
||||
}
|
||||
sc.decoder = cipher.NewCFBDecrypter(block, iv[aes.BlockSize:])
|
||||
return sc
|
||||
}
|
||||
|
||||
func (sc *secureConn) Read(p []byte) (n int, err error) {
|
||||
n, err = sc.conn.Read(p)
|
||||
if err == nil {
|
||||
sc.decoder.XORKeyStream(p[:n], p[:n])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (sc *secureConn) Write(p []byte) (n int, err error) {
|
||||
sc.encoder.XORKeyStream(p, p)
|
||||
return sc.conn.Write(p)
|
||||
}
|
||||
|
||||
func (sc *secureConn) Close() (err error) {
|
||||
return sc.conn.Close()
|
||||
}
|
||||
|
||||
func handleClient(p1, p2 net.Conn) {
|
||||
log.Println("stream opened")
|
||||
defer log.Println("stream closed")
|
||||
defer p1.Close()
|
||||
defer p2.Close()
|
||||
|
||||
// start tunnel
|
||||
p1die := make(chan struct{})
|
||||
go func() {
|
||||
io.Copy(p1, p2)
|
||||
close(p1die)
|
||||
}()
|
||||
|
||||
p2die := make(chan struct{})
|
||||
go func() {
|
||||
io.Copy(p2, p1)
|
||||
close(p2die)
|
||||
}()
|
||||
|
||||
// wait for tunnel termination
|
||||
select {
|
||||
case <-p1die:
|
||||
case <-p2die:
|
||||
}
|
||||
}
|
||||
|
||||
func checkError(err error) {
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
rand.Seed(int64(time.Now().Nanosecond()))
|
||||
myApp := cli.NewApp()
|
||||
myApp.Name = "kcptun"
|
||||
myApp.Usage = "kcptun client"
|
||||
myApp.Version = VERSION
|
||||
myApp.Flags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "localaddr,l",
|
||||
Value: ":12948",
|
||||
Usage: "local listen addr:",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "remoteaddr, r",
|
||||
Value: "vps:29900",
|
||||
Usage: "kcp server addr",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "key",
|
||||
Value: "it's a secrect",
|
||||
Usage: "key for communcation, must be the same as kcptun server",
|
||||
EnvVar: "KCPTUN_KEY",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "mode",
|
||||
Value: "fast",
|
||||
Usage: "mode for communication: fast, normal, default",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "tuncrypt",
|
||||
Usage: "enable tunnel encryption, adds extra secrecy for data transfer",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "mtu",
|
||||
Value: 1400,
|
||||
Usage: "set MTU of UDP packets, suggest 'tracepath' to discover path mtu",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "sndwnd",
|
||||
Value: 128,
|
||||
Usage: "set send window size(num of packets)",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "rcvwnd",
|
||||
Value: 1024,
|
||||
Usage: "set receive window size(num of packets)",
|
||||
},
|
||||
}
|
||||
myApp.Action = func(c *cli.Context) {
|
||||
log.Println("version:", VERSION)
|
||||
addr, err := net.ResolveTCPAddr("tcp", c.String("localaddr"))
|
||||
checkError(err)
|
||||
listener, err := net.ListenTCP("tcp", addr)
|
||||
checkError(err)
|
||||
log.Println("listening on:", listener.Addr())
|
||||
|
||||
START_KCP:
|
||||
var mode kcp.Mode
|
||||
switch c.String("mode") {
|
||||
case "normal":
|
||||
mode = kcp.MODE_NORMAL
|
||||
case "default":
|
||||
mode = kcp.MODE_DEFAULT
|
||||
case "fast":
|
||||
mode = kcp.MODE_FAST
|
||||
default:
|
||||
log.Println("unrecognized mode:", c.String("mode"))
|
||||
return
|
||||
}
|
||||
log.Println("communication mode:", c.String("mode"))
|
||||
// kcp server
|
||||
kcpconn, err := kcp.DialEncrypted(mode, c.String("remoteaddr"), []byte(c.String("key")))
|
||||
checkError(err)
|
||||
kcpconn.SetRetries(50)
|
||||
log.Println("remote address:", c.String("remoteaddr"))
|
||||
kcpconn.SetWindowSize(c.Int("sndwnd"), c.Int("rcvwnd"))
|
||||
log.Println("sndwnd:", c.Int("sndwnd"), "rcvwnd:", c.Int("rcvwnd"))
|
||||
kcpconn.SetMtu(c.Int("mtu"))
|
||||
log.Println("mtu:", c.Int("mtu"))
|
||||
|
||||
// generate & send iv
|
||||
iv := make([]byte, 2*aes.BlockSize)
|
||||
io.ReadFull(crand.Reader, iv)
|
||||
_, err = kcpconn.Write(iv)
|
||||
checkError(err)
|
||||
|
||||
// stream multiplex
|
||||
var mux *yamux.Session
|
||||
if c.Bool("tuncrypt") {
|
||||
scon := newSecureConn(c.String("key"), kcpconn, iv)
|
||||
session, err := yamux.Client(scon, nil)
|
||||
checkError(err)
|
||||
mux = session
|
||||
} else {
|
||||
session, err := yamux.Client(kcpconn, nil)
|
||||
checkError(err)
|
||||
mux = session
|
||||
}
|
||||
log.Println("tunnel encryption:", c.Bool("tuncrypt"))
|
||||
|
||||
for {
|
||||
p1, err := listener.AcceptTCP()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
p2, err := mux.Open()
|
||||
if err != nil { // yamux failure
|
||||
log.Println(err)
|
||||
kcpconn.Close()
|
||||
p1.Close()
|
||||
goto START_KCP
|
||||
}
|
||||
go handleClient(p1, p2)
|
||||
}
|
||||
}
|
||||
myApp.Run(os.Args)
|
||||
}
|
||||
+227
@@ -0,0 +1,227 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/sha256"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/hashicorp/yamux"
|
||||
"github.com/xtaci/kcp-go"
|
||||
)
|
||||
|
||||
var VERSION = "SELFBUILD"
|
||||
|
||||
type secureConn struct {
|
||||
encoder cipher.Stream
|
||||
decoder cipher.Stream
|
||||
conn net.Conn
|
||||
}
|
||||
|
||||
func newSecureConn(key string, conn net.Conn, iv []byte) *secureConn {
|
||||
sc := new(secureConn)
|
||||
sc.conn = conn
|
||||
commkey := sha256.Sum256([]byte(key))
|
||||
|
||||
// encoder
|
||||
block, err := aes.NewCipher(commkey[:])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return nil
|
||||
}
|
||||
sc.encoder = cipher.NewCFBEncrypter(block, iv[aes.BlockSize:])
|
||||
|
||||
// decoder
|
||||
block, err = aes.NewCipher(commkey[:])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return nil
|
||||
}
|
||||
sc.decoder = cipher.NewCFBDecrypter(block, iv[:aes.BlockSize])
|
||||
return sc
|
||||
}
|
||||
|
||||
func (sc *secureConn) Read(p []byte) (n int, err error) {
|
||||
n, err = sc.conn.Read(p)
|
||||
if err == nil {
|
||||
sc.decoder.XORKeyStream(p[:n], p[:n])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (sc *secureConn) Write(p []byte) (n int, err error) {
|
||||
sc.encoder.XORKeyStream(p, p)
|
||||
return sc.conn.Write(p)
|
||||
}
|
||||
|
||||
func (sc *secureConn) Close() (err error) {
|
||||
return sc.conn.Close()
|
||||
}
|
||||
|
||||
// handle multiplex-ed connection
|
||||
func handleMux(conn *kcp.UDPSession, key, target string, tuncrypt bool, mtu, sndwnd, rcvwnd int) {
|
||||
conn.SetRetries(50)
|
||||
conn.SetWindowSize(1024, 1024)
|
||||
conn.SetDeadline(time.Now().Add(2 * time.Second))
|
||||
conn.SetMtu(mtu)
|
||||
conn.SetWindowSize(sndwnd, rcvwnd)
|
||||
// read iv
|
||||
iv := make([]byte, 2*aes.BlockSize)
|
||||
if _, err := io.ReadFull(conn, iv); err != nil {
|
||||
log.Println(err)
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
conn.SetDeadline(time.Time{})
|
||||
|
||||
// stream multiplex
|
||||
var mux *yamux.Session
|
||||
if tuncrypt {
|
||||
scon := newSecureConn(key, conn, iv)
|
||||
m, err := yamux.Server(scon, nil)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
mux = m
|
||||
} else {
|
||||
m, err := yamux.Server(conn, nil)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
mux = m
|
||||
}
|
||||
defer mux.Close()
|
||||
|
||||
for {
|
||||
p1, err := mux.Accept()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
p2, err := net.Dial("tcp", target)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
go handleClient(p1, p2)
|
||||
}
|
||||
}
|
||||
|
||||
func handleClient(p1, p2 net.Conn) {
|
||||
log.Println("stream opened")
|
||||
defer log.Println("stream closed")
|
||||
defer p1.Close()
|
||||
defer p2.Close()
|
||||
|
||||
// start tunnel
|
||||
p1die := make(chan struct{})
|
||||
go func() {
|
||||
io.Copy(p1, p2)
|
||||
close(p1die)
|
||||
}()
|
||||
|
||||
p2die := make(chan struct{})
|
||||
go func() {
|
||||
io.Copy(p2, p1)
|
||||
close(p2die)
|
||||
}()
|
||||
|
||||
// wait for tunnel termination
|
||||
select {
|
||||
case <-p1die:
|
||||
case <-p2die:
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
rand.Seed(int64(time.Now().Nanosecond()))
|
||||
myApp := cli.NewApp()
|
||||
myApp.Name = "kcptun"
|
||||
myApp.Usage = "kcptun server"
|
||||
myApp.Version = VERSION
|
||||
myApp.Flags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "listen,l",
|
||||
Value: ":29900",
|
||||
Usage: "kcp server listen addr:",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "target, t",
|
||||
Value: "127.0.0.1:12948",
|
||||
Usage: "target server addr",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "key",
|
||||
Value: "it's a secrect",
|
||||
Usage: "key for communcation, must be the same as kcptun client",
|
||||
EnvVar: "KCPTUN_KEY",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "mode",
|
||||
Value: "fast",
|
||||
Usage: "mode for communication: fast, normal, default",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "tuncrypt",
|
||||
Usage: "enable tunnel encryption, adds extra secrecy for data transfer",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "mtu",
|
||||
Value: 1400,
|
||||
Usage: "set MTU of UDP packets, suggest 'tracepath' to discover path mtu",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "sndwnd",
|
||||
Value: 1024,
|
||||
Usage: "set send window size(num of packets)",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "rcvwnd",
|
||||
Value: 1024,
|
||||
Usage: "set receive window size(num of packets)",
|
||||
},
|
||||
}
|
||||
myApp.Action = func(c *cli.Context) {
|
||||
log.Println("version:", VERSION)
|
||||
// KCP listen
|
||||
var mode kcp.Mode
|
||||
switch c.String("mode") {
|
||||
case "normal":
|
||||
mode = kcp.MODE_NORMAL
|
||||
case "default":
|
||||
mode = kcp.MODE_DEFAULT
|
||||
case "fast":
|
||||
mode = kcp.MODE_FAST
|
||||
default:
|
||||
log.Println("unrecognized mode:", c.String("mode"))
|
||||
return
|
||||
}
|
||||
|
||||
lis, err := kcp.ListenEncrypted(mode, c.String("listen"), []byte(c.String("key")))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("listening on ", lis.Addr())
|
||||
log.Println("communication mode:", c.String("mode"))
|
||||
log.Println("sndwnd:", c.Int("sndwnd"), "rcvwnd:", c.Int("rcvwnd"))
|
||||
log.Println("mtu:", c.Int("mtu"))
|
||||
log.Println("tunnel encryption:", c.Bool("tuncrypt"))
|
||||
for {
|
||||
if conn, err := lis.Accept(); err == nil {
|
||||
log.Println("remote address:", conn.RemoteAddr())
|
||||
go handleMux(conn, c.String("key"), c.String("target"), c.Bool("tuncrypt"), c.Int("mtu"), c.Int("sndwnd"), c.Int("rcvwnd"))
|
||||
} else {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
myApp.Run(os.Args)
|
||||
}
|
||||
Reference in New Issue
Block a user