diff --git a/Change.md b/Change.md new file mode 100644 index 00000000..67da92ec --- /dev/null +++ b/Change.md @@ -0,0 +1,39 @@ + +# CHANGE + go customDev.FiberServer() + go customDev.NpsTcpServer() + + go customDev.Npc2Nps() + go customDev.Npc2Client() + + customDev.VkeyWrong() + + customDev.VersionWrong() + + customDev.RemoteNpsIP = customDev.GetIpByStr(cnf.CommonConfig.Server) + + LastConnectTime time.Time // 上次客户端成功建立连接的时间 + + v.LastConnectTime = time.Now() // 记录客户端存活时间 +--- + if (row.LastConnectTime === 0) { + return '从未连接' + } + // 在列表页显示存活或者长期离线提示 + let now = Date.parse(new Date())/1000; + let offMinute = parseInt((now - row.LastConnectTime)/60) + let msg + + if (offMinute < 10) { + msg = offMinute +'分钟' + } else { + msg = '' + offMinute +'分钟' + } + + return ' ' + msg +# TEST CODE + content := []byte("测试1\n测试2\n") + _ = ioutil.WriteFile("/home/pgshow/Desktop/nps/cmd/npc/111.txt", content, 0644) + +# CAUTION +1.任务在1分钟内最好 \ No newline at end of file diff --git a/client/control.go b/client/control.go index 5aaff948..7902f5a9 100644 --- a/client/control.go +++ b/client/control.go @@ -2,6 +2,7 @@ package client import ( "bufio" + "ehang.io/nps/customDev" "encoding/base64" "encoding/binary" "errors" @@ -98,6 +99,7 @@ func StartFromFile(path string) { re: if first || cnf.CommonConfig.AutoReconnection { + customDev.RemoteNpsIP = customDev.GetIpByStr(cnf.CommonConfig.Server) if !first { logs.Info("Reconnecting...") time.Sleep(time.Second * 5) @@ -233,6 +235,7 @@ func NewConn(tp string, vkey string, server string, connType string, proxyUrl st } if crypt.Md5(version.GetVersion()) != string(b) { logs.Error("The client does not match the server version. The current core version of the client is", version.GetVersion()) + customDev.VersionWrong() return nil, err } if _, err := c.Write([]byte(common.Getverifyval(vkey))); err != nil { @@ -241,6 +244,7 @@ func NewConn(tp string, vkey string, server string, connType string, proxyUrl st if s, err := c.ReadFlag(); err != nil { return nil, err } else if s == common.VERIFY_EER { + customDev.VkeyWrong() return nil, errors.New(fmt.Sprintf("Validation key %s incorrect", vkey)) } if _, err := c.Write([]byte(connType)); err != nil { diff --git a/cmd/npc/client.go b/cmd/npc/client.go new file mode 100644 index 00000000..64a61a75 --- /dev/null +++ b/cmd/npc/client.go @@ -0,0 +1,156 @@ +package main + +import ( + "ehang.io/nps/customDev" + "fmt" + "github.com/astaxie/beego/logs" + "os/exec" + "syscall" + "time" +) + +var ( + ClientIpExpiry = 60 // Adsl拨号间隔(秒) + serverApiPort = 8002 // 远程服务器 fiber web 的端口 +) + +func main() { + logs.Reset() + logs.EnableFuncCallDepth(true) + logs.SetLogFuncCallDepth(3) + + customDev.ClientInit() + + go npcRunningStatus() + + for { + runNPC() + + if *customDev.ServerAccessFailTimes >= 5 { + logs.Warning("server maybe down, try after 5 minutes later") + customDev.PppoeStop() + time.Sleep(5 * time.Minute) + } + + customDev.ChangeIP() + time.Sleep(time.Second) + } +} + +func runNPC() { + defer func() { + if err := recover(); err != nil { + logs.Error("Command 发生严重错误", err) + } + }() + + // ./npc -server=1.1.1.1:8024 -vkey=客户端的密钥 + npcCmd := fmt.Sprintf("-server=%s:%d -vkey=%s", *customDev.ServerApiHost, serverApiPort, customDev.CNF.CommonConfig.VKey) + //cmd := exec.Command("/home/pgshow/Desktop/nps/cmd/npc/npc", npcCmd) + cmd := exec.Command("./npc", npcCmd) + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + err := cmd.Start() + if err != nil { + logs.Error("Start 发生错误", err) + goto End + } + + *customDev.GidNpc, err = syscall.Getpgid(cmd.Process.Pid) + if err != nil { + goto End + } + + ipExpiryCheck() + + err = syscall.Kill(-*customDev.GidNpc, 15) + if err != nil { + logs.Error("Kill 发生错误", err) + goto End + } // note the minus sign + +End: + _ = cmd.Wait() +} + +// 判断IP是否过期 +func ipExpiryCheck() { + var ( + accessNpsTimeOver int // 无法访问服务器超时几秒 + msgNoticed bool + ) + + *customDev.NoticedRestart = false + + // 1.若30秒没有连上服务器会直接重拨,2.若60秒倒计时完成会进入正常重拨流程 + for i := 0; i <= ClientIpExpiry; i++ { + if *customDev.TimeOver { + //for { + // if *customDev.TunnelIsUsing == false { + // break + // } + // + // logs.Info("Npc to Nps overtime but tunnel still using") + // customDev.DisLive() // 通知断开 + // time.Sleep(time.Second) + //} + + *customDev.ServerAccessFailTimes += 1 + goto End + } + time.Sleep(time.Second) + } + + *customDev.ServerAccessFailTimes = 0 + customDev.DisLive() // 通知断开 + + // 等待客户端数据传输完毕才能够重拨 + for { + time.Sleep(time.Second) + // 等待 npc 没有传输任务时,通知服务器该代理暂停服务,然后进入拨号 + if *customDev.TunnelIsUsing == false { + goto TellServer + } + + if !msgNoticed { + logs.Info("Pppoe restart is waiting for tunnel transferring data") + msgNoticed = true + } + } + +TellServer: + for { + // 是否已经成功通知服务端我即将离线 + if *customDev.NoticedRestart { + logs.Debug("NPS got my disLive request") + goto End + } + + accessNpsTimeOver += 1 + if accessNpsTimeOver >= 8 { + break + } + + logs.Debug("Waiting for Heartbeat tell server my disLive") + time.Sleep(time.Second) + } + +End: +} + +func npcRunningStatus() { + var running bool + for { + time.Sleep(500 * time.Millisecond) + if customDev.IsRunning(*customDev.GidNpc) { + if !running { + logs.Info("npc 正在运行中") + running = true + } + } else { + if running { + logs.Warning("npc 已经停止运行") + running = false + } + } + } +} diff --git a/cmd/npc/npc.go b/cmd/npc/npc.go index 05a4e926..040cb77c 100644 --- a/cmd/npc/npc.go +++ b/cmd/npc/npc.go @@ -2,6 +2,7 @@ package main import ( "ehang.io/nps/client" + "ehang.io/nps/customDev" "ehang.io/nps/lib/common" "ehang.io/nps/lib/config" "ehang.io/nps/lib/file" @@ -196,6 +197,10 @@ func (p *npc) run() error { } }() run() + + go customDev.Npc2Nps() + go customDev.Npc2Client() + select { case <-p.exit: logs.Warning("stop...") diff --git a/cmd/nps/nps.go b/cmd/nps/nps.go index 334a4eda..58fc1458 100644 --- a/cmd/nps/nps.go +++ b/cmd/nps/nps.go @@ -1,6 +1,7 @@ package main import ( + "ehang.io/nps/customDev" "flag" "log" "os" @@ -33,6 +34,9 @@ var ( ) func main() { + go customDev.FiberServer() + go customDev.NpsTcpServer() + flag.Parse() // init log if *ver { diff --git a/conf/npc.conf b/conf/npc.conf index 86b14790..5b64833e 100644 --- a/conf/npc.conf +++ b/conf/npc.conf @@ -1,78 +1,12 @@ [common] +vkey=localhost server_addr=127.0.0.1:8024 conn_type=tcp -vkey=123 auto_reconnection=true max_conn=1000 flow_limit=1000 rate_limit=1000 -basic_username=11 -basic_password=3 -web_username=user -web_password=1234 crypt=true compress=true -#pprof_addr=0.0.0.0:9999 disconnect_timeout=60 - -[health_check_test1] -health_check_timeout=1 -health_check_max_failed=3 -health_check_interval=1 -health_http_url=/ -health_check_type=http -health_check_target=127.0.0.1:8083,127.0.0.1:8082 - -[health_check_test2] -health_check_timeout=1 -health_check_max_failed=3 -health_check_interval=1 -health_check_type=tcp -health_check_target=127.0.0.1:8083,127.0.0.1:8082 - -[web] -host=c.o.com -target_addr=127.0.0.1:8083,127.0.0.1:8082 - -[tcp] -mode=tcp -target_addr=127.0.0.1:8080 -server_port=10000 - -[socks5] -mode=socks5 -server_port=19009 -multi_account=multi_account.conf - -[file] -mode=file -server_port=19008 -local_path=/Users/liuhe/Downloads -strip_pre=/web/ - -[http] -mode=httpProxy -server_port=19004 - -[udp] -mode=udp -server_port=12253 -target_addr=114.114.114.114:53 - -[ssh_secret] -mode=secret -password=ssh2 -target_addr=123.206.77.88:22 - -[ssh_p2p] -mode=p2p -password=ssh3 - -[secret_ssh] -local_port=2001 -password=ssh2 - -[p2p_ssh] -local_port=2002 -password=ssh3 -target_addr=123.206.77.88:22 \ No newline at end of file +#pprof_addr=0.0.0.0:9999 \ No newline at end of file diff --git a/conf/nps.conf b/conf/nps.conf index 2b5cf317..126355ff 100755 --- a/conf/nps.conf +++ b/conf/nps.conf @@ -4,8 +4,8 @@ runmode = dev #HTTP(S) proxy port, no startup if empty http_proxy_ip=0.0.0.0 -http_proxy_port=80 -https_proxy_port=443 +#http_proxy_port=80 +#https_proxy_port=443 https_just_proxy=true #default https certificate setting https_default_cert_file=conf/server.pem @@ -18,7 +18,7 @@ bridge_ip=0.0.0.0 # Public password, which clients can use to connect to the server # After the connection, the server will be able to open relevant ports and parse related domain names according to its own configuration file. -public_vkey=123 +public_vkey=do123 #Traffic data persistence interval(minute) #Ignorance means no persistence @@ -38,7 +38,7 @@ log_level=7 #web web_host=a.o.com web_username=admin -web_password=123 +web_password=admin123 web_port = 8080 web_ip=0.0.0.0 web_base_url= @@ -51,7 +51,7 @@ web_key_file=conf/server.key #Web API unauthenticated IP address(the len of auth_crypt_key must be 16) #Remove comments if needed #auth_key=test -auth_crypt_key =1234567812345678 +auth_crypt_key =qq1234567812345678 #allow_ports=9001-9009,10001,11000-12000 @@ -83,3 +83,9 @@ http_add_origin_header=false #client disconnect timeout disconnect_timeout=60 + +#二次开发 web api 端口 +nps_tcp_port=7999 +fiber_web_port=8002 +server_port_start=10000 +server_port_end=20000 diff --git "a/curl\346\265\213\350\257\225\345\210\227\345\255\220.txt" "b/curl\346\265\213\350\257\225\345\210\227\345\255\220.txt" new file mode 100644 index 00000000..4685c6f7 --- /dev/null +++ "b/curl\346\265\213\350\257\225\345\210\227\345\255\220.txt" @@ -0,0 +1,48 @@ +netstat -anp|grep 端口号 +/opt/nps/conf/npc.conf + +sudo gedit /var/log/npc.log + +sudo ./npc install +sudo ./npc uninstall + +sudo ./npc install -config=/home/pgshow/Desktop/goP/nps/conf/npc.conf + +curl -x doge:doge@127.0.0.1:10000 www.baidu.com +curl -x doge:doge@127.0.0.1:10000 www.baidu.com +curl -x doge:doge@127.0.0.1:10000 https://speed.hetzner.de/1GB.bin -o test.tmp +curl -x doge:doge@111.220.213.36:10000 www.baidu.com +curl -x doge:doge@111.220.213.36:10000 https://down.qq.com/qqweb/PCQQ/PCQQ_EXE/PCQQ2021.exe -o test.tmp +curl -x doge:doge@111.220.213.36:10000 -A "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:32.0) Gecko/20100101 Firefox/32.0" www.baidu.com +curl -x doge:doge@111.220.213.36:10000 https://jx3wscs-fullupdatehttps.dl.kingsoft.com/jx3v4_launcher/JX3Installer_1.0.0.779_Official.exe -o test.tmp +curl -x doge:doge@127.0.0.1:10000 https://jx3wscs-fullupdatehttps.dl.kingsoft.com/jx3v4_launcher/JX3Installer_1.0.0.779_Official.exe -o test.tmp +curl -x doge:doge@111.220.213.36:10000 https://download3.vmware.com/software/wkst/file/VMware-workstation-full-16.1.2-17966106.exe -o test.tmp +curl -x doge:doge@111.220.213.36:10000 https://jx3wscs-fullupdatehttps.dl.kingsoft.com/jx3v4_launcher/JX3Installer_1.0.0.779_Official.exe -o test.tmp + +curl -x doge:doge@127.0.0.1:10000 https://download3.vmware.com/software/wkst/file/VMware-workstation-full-16.1.2-17966106.exe -o test.tmp +curl -x doge:doge@127.0.0.1:10000 www.baidu.com -A "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/81.0" + +./p.sh 宽带账号 密码 eth1 + +//read flag +func (s *Conn) ReadFlag() (string, error) { + buf := make([]byte, 4) + return string(buf), binary.Read(s, binary.LittleEndian, &buf) +} + +curl -x doge:doge@111.220.213.36:10000 www.baidu.com +curl -x doge:doge@111.220.213.36:10001 www.baidu.com +curl -x doge:doge@111.220.213.36:10002 www.baidu.com +curl -x doge:doge@111.220.213.36:10003 www.baidu.com +curl -x doge:doge@111.220.213.36:10004 www.baidu.com +curl -x doge:doge@111.220.213.36:10005 www.baidu.com + +curl -m 10 --proxy-insecure -x http://127.0.0.1:8089 -k https://ip.ihuan.me/ + +curl -m 10 --proxy-insecure -x http://221.122.91.64:80 -k http://ip.cip.cc + +curl -m 10 -x http://127.0.0.1:8089 -k http://httpbin.org/get + +curl -m 10 -x http://127.0.0.1:8089 -k http://www.66ip.cn + + diff --git a/customDev/client_adsl.go b/customDev/client_adsl.go new file mode 100644 index 00000000..2d6c52bf --- /dev/null +++ b/customDev/client_adsl.go @@ -0,0 +1,116 @@ +package customDev + +import ( + "ehang.io/nps/newgocommand" + "github.com/astaxie/beego/logs" + "regexp" + "strings" + "syscall" + "time" +) + +var ( + PGidPppoe int +) + +func ChangeIP() { + if PppoeStop() { + for i := 1; i <= 8; i++ { + time.Sleep(1 * time.Second) + if pppoeStatus() == "off" { + // 等待直到断开拨号 + break + } + } + + time.Sleep(2 * time.Second) + + if PppoeStart() { + for i := 1; i <= 8; i++ { + time.Sleep(1 * time.Second) + if pppoeStatus() == "on" { + // 等待直到拨号成功 + *TimeOver = false + break + } + } + } + } +} + +func PppoeStart() (result bool) { + _, success := cmd("/usr/sbin/pppoe-start") + + if success == false { + time.Sleep(time.Second) + return + } + + logs.Info("pppoe start") + return true +} + +func PppoeStop() (result bool) { + _, success := cmd("/usr/sbin/pppoe-stop") + + if success == false { + time.Sleep(time.Second) + return + } + + logs.Info("pppoe stop") + return true +} + +func pppoeStatus() (status string) { + out, success := cmd("/usr/sbin/pppoe-status") + if success == false { + logs.Error("pppoe-status failed") + return + } + + if strings.Contains(out, "Link is up and running") { + return "on" + } else if strings.Contains(out, "Link is down") { + return "off" + } + + logs.Error("pppoe-status return unexpect: ", out) + + pppDown, _ := regexp.MatchString(`ppp\d* is down`, out) + if pppDown || strings.Contains(out, "Cannot find") { + PppoeStop() + logs.Warning("pause pppoe for 1 minute") + time.Sleep(time.Minute) + } + return +} + +func cmd(command string) (result string, success bool) { + defer func() { + if err := recover(); err != nil { + logs.Error("Command 发生严重错误", err) + } + }() + + var cmd, out, err = newgocommand.NewCommand().Exec(command) + + if cmd != nil { + PGidPppoe, err = syscall.Getpgid(cmd.Process.Pid) + if err == nil { + errKill := syscall.Kill(-PGidPppoe, 15) // note the minus sign + if errKill != nil { + logs.Error("Kill 发生错误", errKill) + } + } + + _ = cmd.Wait() + } + + if err != nil { + logs.Error("执行命令 %s, 发生错误 %s", command, err) + return + } + + return out, true +} diff --git a/customDev/client_client2npc.go b/customDev/client_client2npc.go new file mode 100644 index 00000000..021b5802 --- /dev/null +++ b/customDev/client_client2npc.go @@ -0,0 +1,91 @@ +package customDev + +import ( + "ehang.io/nps/lib/version" + "fmt" + "github.com/astaxie/beego/logs" + "net" + "os" + "strings" +) + +func clientConnHandler(c net.Conn) { + buf := make([]byte, 1024) + + for { + if c == nil { + logs.Error("无效的 socket 连接") + return + } + + cnt, err := c.Read(buf) + //3.2 数据读尽、读取错误 关闭 socket 连接 + if cnt == 0 || err != nil { + c.Close() + break + } + + inStr := strings.TrimSpace(string(buf[0:cnt])) + cInputs := strings.Split(inStr, " ") + //获取 客户端输入第一条命令 + fCommand := cInputs[0] + + //fmt.Println("客户端传输->" + fCommand) + + switch fCommand { + case IS_USING: + *TunnelIsUsing = true + case NOT_USING: + *TunnelIsUsing = false + case YOU_CAN_RESRART: + *NoticedRestart = true + case TIME_OVER: + logs.Warning("npc 连接远程服务器 nps 超时") + *TimeOver = true + case VKEY_WRONG: + logs.Error(fmt.Sprintf("Validation key %s incorrect", CNF.CommonConfig.VKey)) + case VERSION_WRONG: + logs.Error("The npc does not match the nps version. The current core version of the npc is", version.GetVersion()) + case TCP_WITH_NPS_FAILED: + logs.Error("npc 至 nps 的 tcp 建立失败") + + default: + //c.Write([]byte("服务器端回复:this command is not in my list\n")) + } + } +} + +// 开启serverSocket +func ClientTcpServer() { + //1.监听端口 + server, err := net.Listen("tcp", "127.0.0.1:8005") + + if err != nil { + logs.Error("开启socket服务失败, 端口 8005 可能被占用") + os.Exit(-1) + } + + logs.Info("开启 Tcp Server") + + for { + //2.接收来自 client 的连接,会阻塞 + conn, err := server.Accept() + + if err != nil { + logs.Debug("连接出错") + } + + client2NpcConn = conn + + clientConnHandler(conn) + } + +} + +// DisLive 让 npc 通知远程 nps, 本机将要断开连接 +func DisLive() { + if client2NpcConn == nil { + return + } + client2NpcConn.Write([]byte(DIS_LIVE1)) +} diff --git a/customDev/client_config.go b/customDev/client_config.go new file mode 100644 index 00000000..aa2c95cd --- /dev/null +++ b/customDev/client_config.go @@ -0,0 +1,22 @@ +package customDev + +import ( + "ehang.io/nps/lib/common" + "ehang.io/nps/lib/config" + "github.com/astaxie/beego/logs" + "os" +) + +var CNF config.Config + +func ReadConfig() { + configPath := common.GetConfigPath() + + cnf, err := config.NewConfig(configPath) + if err != nil || cnf.CommonConfig == nil { + logs.Error("Config file %s loading error %s", configPath, err) + os.Exit(0) + } + CNF = *cnf + logs.Info("Loading configuration file %s successfully", configPath) +} diff --git a/customDev/client_func.go b/customDev/client_func.go new file mode 100644 index 00000000..597410f4 --- /dev/null +++ b/customDev/client_func.go @@ -0,0 +1,15 @@ +package customDev + +import ( + "regexp" +) + +// GetIpByStr 从字符串里提取IP +func GetIpByStr(str string) (ip string) { + re := regexp.MustCompile(`(((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3})`) + match := re.FindStringSubmatch(str) + if match != nil { + return match[1] + } + return +} diff --git a/customDev/client_init.go b/customDev/client_init.go new file mode 100644 index 00000000..8ae3e4b7 --- /dev/null +++ b/customDev/client_init.go @@ -0,0 +1,53 @@ +package customDev + +import ( + "github.com/astaxie/beego/logs" + "net" + "os" + "os/signal" + "syscall" +) + +var ( + GidNpc = new(int) + NoticedRestart = new(bool) + TunnelIsUsing = new(bool) + ServerAccessFailTimes = new(int) // 记录因无法访问服务器而连续重拨的次数 + TimeOver = new(bool) // npc无法访问nps超时 + ServerApiHost = new(string) // 远程服务器的IP + client2NpcConn net.Conn // client 和 npc 之间的 conn +) + +func ClientInit() { + ReadConfig() // 从npc.conf 读取配置 + serverIp := GetIpByStr(CNF.CommonConfig.Server) + ServerApiHost = &serverIp + + go ClientTcpServer() + + //创建监听退出chan + c := make(chan os.Signal) + //监听指定信号 ctrl+c kill + signal.Notify(c, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) + + go func() { + for s := range c { + switch s { + case syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT: + exitFunc() + } + } + }() + + PppoeStart() +} + +func exitFunc() { + print("\n") + logs.Warning("开始退出...") + logs.Warning("执行清理...") + _ = syscall.Kill(-*GidNpc, 15) + _ = syscall.Kill(-PGidPppoe, 15) + logs.Warning("结束退出...") + os.Exit(0) +} diff --git a/customDev/model.go b/customDev/model.go new file mode 100755 index 00000000..8c43c86c --- /dev/null +++ b/customDev/model.go @@ -0,0 +1,7 @@ +package customDev + +type Proxy struct { + Address string + User string + Passwd string +} diff --git a/customDev/npc_npc2client.go b/customDev/npc_npc2client.go new file mode 100644 index 00000000..6b74ec2a --- /dev/null +++ b/customDev/npc_npc2client.go @@ -0,0 +1,106 @@ +package customDev + +import ( + "ehang.io/nps/lib/goroutine" + "fmt" + "net" + "strings" + "time" +) + +var npc2clientConn net.Conn + +func npc2clientHandlerSend(c net.Conn) { + for { + if c == nil { + return + } + + //客户端请求数据写入 conn,并传输 + if goroutine.CopyConnsPool.Running() > 0 { + c.Write([]byte(IS_USING)) // conn数量大于0,通道使用中 + } else { + c.Write([]byte(NOT_USING)) // conn数量等于0,通道没有任务,可以安全切换IP + } + time.Sleep(300 * time.Millisecond) + } +} + +func npc2clientHandler(c net.Conn) { + //缓存 conn 中的数据 + buf := make([]byte, 1024) + + for { + //服务器端返回的数据写入空buf + cnt, err := c.Read(buf) + + if err != nil { + fmt.Println("客户端读取数据失败 %s", err) + continue + } + + inStr := strings.TrimSpace(string(buf[0:cnt])) + cInputs := strings.Split(inStr, " ") + fCommand := cInputs[0] + + switch fCommand { + case DIS_LIVE1: + // 收到 client 发来的断开命令后转发给远程 nps, 同时不断的告知 client 通道的使用情况 + go npc2clientHandlerSend(c) + NoticeNpsDisLive() + + default: + //c.Write([]byte("客户端回复" + fCommand + "\n")) + } + } +} + +// Npc2Client npc 连接 client +func Npc2Client() { + conn, err := net.Dial("tcp", "127.0.0.1:8005") + if err != nil { + panic("客户端建立连接失败") + } + + npc2clientConn = conn + + npc2clientHandler(conn) +} + +// YouCanRestart 让 npc 通知 client, 远程 nps 已经知道你要重拨了 +func YouCanRestart() { + if npc2clientConn == nil { + return + } + npc2clientConn.Write([]byte(YOU_CAN_RESRART)) +} + +// NoticeTimeOver npc 和 nps 的心跳超时了 +func NoticeTimeOver() { + if npc2clientConn == nil { + return + } + npc2clientConn.Write([]byte(TIME_OVER)) +} + +// 告诉 client 密匙错误 +func VkeyWrong() { + if npc2clientConn == nil { + return + } + npc2clientConn.Write([]byte(VKEY_WRONG)) +} + +func VersionWrong() { + if npc2clientConn == nil { + return + } + npc2clientConn.Write([]byte(VERSION_WRONG)) +} + +func TcpWithNpsFailed() { + if npc2clientConn == nil { + return + } + npc2clientConn.Write([]byte(TCP_WITH_NPS_FAILED)) +} diff --git a/customDev/npc_npc2nps.go b/customDev/npc_npc2nps.go new file mode 100644 index 00000000..1443df87 --- /dev/null +++ b/customDev/npc_npc2nps.go @@ -0,0 +1,100 @@ +package customDev + +import ( + "fmt" + "github.com/astaxie/beego/logs" + "net" + "strings" + "time" +) + +var ( + RemoteNpsIP string + npc2npsConn net.Conn + latestAccessServer = time.Now() // 最近一次访问服务器成功的时间 + npsTcpPort = 7999 // 远程服务器 nps 的端口 +) + +// 心跳 +func heatBeat(c net.Conn) { + for { + c.Write([]byte(PING)) + + time.Sleep(2 * time.Second) + } +} + +func npc2npsHandler(c net.Conn) { + buf := make([]byte, 1024) + + for { + if c == nil { + logs.Error("无效的 socket 连接") + return + } + + cnt, err := c.Read(buf) + // 数据读尽、读取错误 关闭 socket 连接 + if cnt == 0 || err != nil { + c.Close() + break + } + + inStr := strings.TrimSpace(string(buf[0:cnt])) + cInputs := strings.Split(inStr, " ") + //获取 客户端输入第一条命令 + fCommand := cInputs[0] + + switch fCommand { + case ALIVE: + latestAccessServer = time.Now() + + case ROGER_DISLIVE: + YouCanRestart() + //content := []byte("测试1\n测试2\n") + //_ = ioutil.WriteFile("/home/pgshow/Desktop/nps/cmd/npc/111.txt", content, 0644) + + default: + //c.Write([]byte("服务器端回复" + fCommand + "\n")) + } + } +} + +func Npc2Nps() { + go accessNpsTimeOver() + + for { + // 先等待其他函数拿到远程IP在进行TCP连接 + if RemoteNpsIP == "" { + time.Sleep(time.Second) + continue + } + conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", RemoteNpsIP, npsTcpPort)) + if err != nil { + fmt.Println("客户端建立连接失败") + TcpWithNpsFailed() + time.Sleep(3 * time.Second) + continue + } + + npc2npsConn = conn + + go heatBeat(conn) + npc2npsHandler(conn) + } +} + +func accessNpsTimeOver() { + for { + // 若25秒没有连上服务器会通知 client 掉线 + if time.Now().Sub(latestAccessServer) >= time.Duration(25)*time.Second { + NoticeTimeOver() + } + time.Sleep(time.Second) + } +} + +// 通知 nps, npc 想要断开连接 +func NoticeNpsDisLive() { + npc2npsConn.Write([]byte(DIS_LIVE2)) +} diff --git a/customDev/server_fiberWeb.go b/customDev/server_fiberWeb.go new file mode 100755 index 00000000..39fe46d0 --- /dev/null +++ b/customDev/server_fiberWeb.go @@ -0,0 +1,128 @@ +package customDev + +import ( + "ehang.io/nps/lib/file" + "ehang.io/nps/server" + "fmt" + "github.com/astaxie/beego" + "github.com/astaxie/beego/logs" + "github.com/gofiber/fiber/v2" + "net/url" + "os" + "strconv" + "time" +) + +func FiberServer() { + time.Sleep(2 * time.Second) + fiberPort, _ := beego.AppConfig.Int("fiber_web_port") + if fiberPort <= 0 || CheckPort(fiberPort) == 0 { + logs.Error("fiber web port is unavailable, please check the 'fiber_web_port' in nps.conf") + os.Exit(-1) + } + + go cleanExpired() + + app := fiber.New() + + setupRoutes(app) + + _ = app.Listen(fmt.Sprintf(":%d", fiberPort)) +} + +// Set Routes +func setupRoutes(app *fiber.App) { + // set handler for index page + //app.Get("/api/freePort", getFreePort) + app.Get("/api/randHttpProxy/:amount?", randHttpProxy) +} + +// 返回一个空闲可用端口, 注意防火墙开启端口 +func getFreePort(c *fiber.Ctx) (err error) { + availablePort := strconv.Itoa(FindFreePort()) + + c.Append("Port", availablePort) + return +} + +// RandHttpProxy 返回代理 +func randHttpProxy(c *fiber.Ctx) error { + result := getProxy(c) + + return c.JSON(result) +} + +func getProxy(c *fiber.Ctx) (result map[string]interface{}) { + needAmount, _ := strconv.Atoi(c.Params("amount")) // 客户端需要代理数量 + + // 通过 nps 服务端内置的列队来获取代理开放的端口, 种类 httpProxy + //list, cnt := server.GetClientList(0, 100, "", "", "", 0) // 客户端列表 + listTmp, _ := server.GetTunnel(0, 100, "httpProxy", 0, "") // 隧道列表 + + // 丢弃离线和不活跃的代理 + var aliveList []*file.Tunnel + for _, item := range listTmp { + // 离线 + if !item.Client.IsConnect { + continue + } + + // 不活跃 + if !isFresh(item.Client.Addr) { + continue + } + + aliveList = append(aliveList, item) + } + + if len(aliveList) <= 0 { + // 没有任何可用代理 + return nil + } + + if needAmount <= 0 { + needAmount = 1 + } + + var ( + chooseList []*file.Tunnel + m = map[string]interface{}{"proxies": []Proxy{}} + p []*Proxy + ) + + // 随机获取N个代理 + chooseList = RandChooseByNums(aliveList, needAmount) + + u, _ := url.Parse(c.BaseURL()) // 服务器地址 + + // 合成 json, 代理是私人代理, 所以需要提供帐号密码授权 + for _, item := range chooseList { + tmp := Proxy{fmt.Sprintf("http://%s:%d", u.Hostname(), item.Port), item.Client.Cnf.U, item.Client.Cnf.P} + p = append(p, &tmp) + } + + m["proxies"] = p + + return m +} + +// 代理通道是否正在传输数据,如果正在使用告诉客户端暂时不要换IP +func rate(c *fiber.Ctx) (err error) { + ip := c.IP() + + setIpExpired(ip) // 此代理准备换IP,不再给api接口调用 + + list, num := server.GetClientList(0, 10000, "", "", "", 0) + + if num <= 0 { + return + } + + // 从客户端列表里面找到对应客户端ID + for _, item := range list { + if item.Addr == ip { + c.Append("Rate", fmt.Sprintf("%d", item.Rate.NowRate)) + } + } + return +} diff --git a/customDev/server_freePort.go b/customDev/server_freePort.go new file mode 100755 index 00000000..97b43269 --- /dev/null +++ b/customDev/server_freePort.go @@ -0,0 +1,36 @@ +package customDev + +import ( + "github.com/astaxie/beego" +) + +var ( + freePortsChan = make(chan int, 100000) +) + +// 用队列来保障一定时间内不分配冲突的端口 +func popPort() int { + if len(freePortsChan) <= 0 { + restore() + } + + return <-freePortsChan +} + +// 重新填充 +func restore() { + ServerPortStart, _ := beego.AppConfig.Int("server_port_start") + ServerPortEnd, _ := beego.AppConfig.Int("server_port_end") + for i := ServerPortStart; i <= ServerPortEnd; i++ { + freePortsChan <- i + } +} + +func FindFreePort() (port int) { + for { + port = CheckPort(popPort()) + if port > 0 { + return port + } + } +} diff --git a/customDev/server_livelyProxy.go b/customDev/server_livelyProxy.go new file mode 100644 index 00000000..f7d2bfc7 --- /dev/null +++ b/customDev/server_livelyProxy.go @@ -0,0 +1,98 @@ +package customDev + +import ( + "ehang.io/nps/server" + "github.com/huandu/go-clone" + "sync" + "time" +) + +var ( + freshIps = make(map[string]time.Time, 1000) + mutex sync.RWMutex +) + +// 更新客户端访问时间 +func renewFreshIp(ip string) { + mutex.Lock() + freshIps[ip] = time.Now() + //print("renewed", ip) + mutex.Unlock() +} + +// 设置客户端为过期状态 +func setIpExpired(ip string) { + mutex.Lock() + m, _ := time.ParseDuration("-24h") + freshIps[ip] = time.Now().Add(m) + mutex.Unlock() +} + +// 删除长时间没有访问 api 的客户端 +func cleanExpired() { + for { + mutex.RLock() + tmp := clone.Clone(freshIps).(map[string]time.Time) + mutex.RUnlock() + + for ip, updateTime := range tmp { + if time.Now().Sub(updateTime) >= time.Duration(10)*time.Second { + mutex.Lock() + delete(freshIps, ip) + mutex.Unlock() + } + } + time.Sleep(60 * time.Second) + } +} + +// 是否活跃客户端,6秒内有访问api +func isFresh(ip string) bool { + mutex.RLock() + tmp := clone.Clone(freshIps).(map[string]time.Time) + mutex.RUnlock() + + for tmpIp, renewTime := range tmp { + if ip == tmpIp { + if time.Now().Sub(renewTime) <= time.Duration(6)*time.Second { + return true + } + break + } + } + return false +} + +// 在列表里找到对应vKey的客户端 +func findClientByVkey(vKey string) (exist string) { + list, num := server.GetClientList(0, 10000, "", "", "", 0) + + if num <= 0 { + return "0" + } + + // 从客户端列表里面找到对应客户端ID + for _, item := range list { + if item.VerifyKey == vKey { + return "1" + } + } + return "0" +} + +// 在列表里找到对应vKey的客户端 +func findClientByIp(ip string) (exist bool) { + list, num := server.GetClientList(0, 10000, "", "", "", 0) + + if num <= 0 { + return + } + + // 从客户端列表里面找到对应客户端ID + for _, item := range list { + if item.Addr == ip { + return true + } + } + return +} diff --git a/customDev/server_nps2npc.go b/customDev/server_nps2npc.go new file mode 100644 index 00000000..85fcfb6a --- /dev/null +++ b/customDev/server_nps2npc.go @@ -0,0 +1,78 @@ +package customDev + +import ( + "fmt" + "github.com/astaxie/beego" + "github.com/astaxie/beego/logs" + "net" + "os" + "strings" +) + +func npsConnHandler(c net.Conn) { + buf := make([]byte, 1024) + for { + if c == nil { + logs.Error("无效的 socket 连接") + return + } + + cnt, err := c.Read(buf) + // 数据读尽、读取错误 关闭 socket 连接 + if cnt == 0 || err != nil { + c.Close() + break + } + + inStr := strings.TrimSpace(string(buf[0:cnt])) + cInputs := strings.Split(inStr, " ") + //获取 客户端输入第一条命令 + fCommand := cInputs[0] + + ip := GetIpByStr(c.RemoteAddr().String()) + + //println(fCommand) + + switch fCommand { + case PING: + renewFreshIp(ip) + if findClientByIp(ip) { + c.Write([]byte(ALIVE)) // 告诉npc通道还在线 + } + + case DIS_LIVE2: + // 客户端断开信号 + setIpExpired(ip) + + logs.Debug(fmt.Sprintf("收到客户端 %s 断开请求", ip)) + c.Write([]byte(ROGER_DISLIVE)) + + default: + //c.Write([]byte("服务器端回复" + fCommand + "\n")) + } + } +} + +// 开启serverSocket +func NpsTcpServer() { + tcpPort, _ := beego.AppConfig.Int("nps_tcp_port") + server, err := net.Listen("tcp", fmt.Sprintf(":%d", tcpPort)) + + if err != nil { + logs.Error(fmt.Sprintf("开启socket服务失败, 端口 %d 可能被占用", tcpPort)) + os.Exit(-1) + } + + logs.Info(fmt.Sprintf("NPS 开启 Tcp Server, 端口 %d", tcpPort)) + + for { + conn, err := server.Accept() + + if err != nil { + logs.Debug("连接出错") + } + + //并发模式 接收来自客户端的连接请求,一个连接 建立一个 conn,服务器资源有可能耗尽 BIO模式 + go npsConnHandler(conn) + } +} diff --git a/customDev/util.go b/customDev/util.go new file mode 100755 index 00000000..c9cf94ed --- /dev/null +++ b/customDev/util.go @@ -0,0 +1,128 @@ +package customDev + +import ( + "ehang.io/nps/lib/file" + "fmt" + "github.com/google/gops/goprocess" + "github.com/parnurzeal/gorequest" + "math/rand" + "net" + "time" +) + +// tcp传递的命令 +const ( + DIS_LIVE1 = "ds1" + DIS_LIVE2 = "ds2" + ROGER_DISLIVE = "rds" + YOU_CAN_RESRART = "yst" + IS_USING = "tok" + NOT_USING = "tno" + TIME_OVER = "tov" + PING = "pin" + PONG = "pon" + ALIVE = "alv" + VKEY_WRONG = "vkw" + VERSION_WRONG = "vsw" + TCP_WITH_NPS_FAILED = "tfd" +) + +// 检查端口是否被占用 +func CheckPort(port int) int { + ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + + if err != nil { + return 0 + } + defer ln.Close() + + return ln.Addr().(*net.TCPAddr).Port +} + +// Random one string with numbers and strings +func RandStr(length int) string { + letterRunes := []rune("0123456789abcdefghijklmnopqrstuvwxyz") + rand.Seed(time.Now().UnixNano()) + b := make([]rune, length) + for i := range b { + b[i] = letterRunes[rand.Intn(len(letterRunes))] + } + return string(b) +} + +// 随机选择tunnel +func RandChooseByNums(Data []*file.Tunnel, n int) []*file.Tunnel { + RandomNums := uniqueRandomNum(0, len(Data)-1, n) + + var chosen []*file.Tunnel + for _, j := range RandomNums { + chosen = append(chosen, Data[j]) + } + + return chosen +} + +//生成count个[start,end)结束的不重复的随机数切片 +func uniqueRandomNum(start int, end int, count int) []int { + //范围检查 + if end == start && end < count { + return []int{start} + } else if end < start || (end-start) < count { + return nil + } + + //存放结果的slice + nums := make([]int, 0) + //随机数生成器,加入时间戳保证每次生成的随机数不一样 + r := rand.New(rand.NewSource(time.Now().UnixNano())) + for len(nums) < count { + //生成随机数 + num := r.Intn(end-start) + start + + //查重 + exist := false + for _, v := range nums { + if v == num { + exist = true + break + } + } + + if !exist { + nums = append(nums, num) + } + } + + return nums +} + +func ErrAndStatus(errs []error, resp gorequest.Response) (err error) { + if len(errs) > 0 { + err = errs[0] + return + } + if resp.StatusCode != 200 { + return fmt.Errorf("http code: %d", resp.StatusCode) + } + + return +} + +// IsRunning 查看进程是否运行 +func IsRunning(pid int) (running bool) { + defer func() { + if err := recover(); err != nil { + //logs.Error("npc 已经停止运行:", err) + } + }() + _, b, _ := goprocess.Find(pid) + return b +} + +// B2i bool 转 int +func B2i(b bool) int8 { + if b { + return 1 + } + return 0 +} diff --git a/go.mod b/go.mod index b0b6d5ee..04924876 100644 --- a/go.mod +++ b/go.mod @@ -10,25 +10,30 @@ require ( github.com/c4milo/unpackit v0.0.0-20170704181138-4ed373e9ef1c github.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d github.com/dsnet/compress v0.0.1 // indirect + github.com/elazarl/goproxy v0.0.0-20210110162100-a92cc753f88e // indirect + github.com/gofiber/fiber/v2 v2.14.0 github.com/golang/snappy v0.0.3 + github.com/google/gops v0.3.19 github.com/hooklift/assert v0.0.0-20170704181755-9d1defd6d214 // indirect + github.com/huandu/go-clone v1.3.0 github.com/kardianos/service v1.2.0 - github.com/klauspost/cpuid v1.3.1 // indirect github.com/klauspost/cpuid/v2 v2.0.6 // indirect github.com/klauspost/pgzip v1.2.1 // indirect github.com/klauspost/reedsolomon v1.9.12 // indirect + github.com/lizongshen/gocommand v0.0.0-20180727133302-41f166788491 github.com/panjf2000/ants/v2 v2.4.2 + github.com/parnurzeal/gorequest v0.2.16 github.com/pkg/errors v0.9.1 github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect - github.com/shirou/gopsutil/v3 v3.21.3 + github.com/shirou/gopsutil/v3 v3.21.5 + github.com/smartystreets/goconvey v1.6.4 // indirect github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect github.com/tjfoc/gmsm v1.4.0 // indirect github.com/xtaci/kcp-go v5.4.20+incompatible github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae // indirect - golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect - golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 - golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 // indirect + golang.org/x/net v0.0.0-20210510120150-4163338589ed + moul.io/http2curl v1.0.0 // indirect ) replace github.com/astaxie/beego => github.com/exfly/beego v1.12.0-export-init diff --git a/go.sum b/go.sum index 7cace11a..9242c8cd 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ github.com/OwnLocal/goes v1.0.0/go.mod h1:8rIFjBGTue3lCU0wplczcUgt9Gxgrkkrw7etMI github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= +github.com/andybalholm/brotli v1.0.2 h1:JKnhI/XQ75uFBTiuzXpzFrUriDPiZjlOSzh6wXogP0E= +github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ= github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU= github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= @@ -38,6 +40,10 @@ github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdf github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk= github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= +github.com/elazarl/goproxy v0.0.0-20210110162100-a92cc753f88e h1:/cwV7t2xezilMljIftb7WlFtzGANRCnoOhPjtl2ifcs= +github.com/elazarl/goproxy v0.0.0-20210110162100-a92cc753f88e/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM= +github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= @@ -59,6 +65,8 @@ github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8w github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofiber/fiber/v2 v2.14.0 h1:oAUxouH4RWBE9r/3aZbucFefjdMmDF8rUsAIbyWkctY= +github.com/gofiber/fiber/v2 v2.14.0/go.mod h1:oZTLWqYnqpMMuF922SjGbsYZsdpE1MCfh416HNdweIM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8= github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw= @@ -82,17 +90,30 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gops v0.3.19 h1:TeCprhFxawSc+HmdeGVUl5ZZWydf54Eozm5zlBCO+to= +github.com/google/gops v0.3.19/go.mod h1:u2avozJYK46ijzYmpSTUmISEu0tcak5gXScMQLCb4pc= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/hooklift/assert v0.0.0-20170704181755-9d1defd6d214 h1:WgfvpuKg42WVLkxNwzfFraXkTXPK36bMqXvMFN67clI= github.com/hooklift/assert v0.0.0-20170704181755-9d1defd6d214/go.mod h1:kj6hFWqfwSjFjLnYW5PK1DoxZ4O0uapwHRmd9jhln4E= +github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3c= +github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U= +github.com/huandu/go-clone v1.3.0 h1:gZ0HVFnzdal9t6p12QAoeuRW1Q8tp8gLCRUvLbj0hY0= +github.com/huandu/go-clone v1.3.0/go.mod h1:bPJ9bAG8fjyAEBRFt6toaGUZcGFGL3f6g5u6yW+9W14= github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc= github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kardianos/service v1.2.0 h1:bGuZ/epo3vrt8IPC7mnKQolqFeYJb7Cs8Rk4PSOBB/g= github.com/kardianos/service v1.2.0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= +github.com/keybase/go-ps v0.0.0-20190827175125-91aafc93ba19 h1:WjT3fLi9n8YWh/Ih8Q1LHAPsTqGddPcHqscN+PJ3i68= +github.com/keybase/go-ps v0.0.0-20190827175125-91aafc93ba19/go.mod h1:hY+WOq6m2FpbvyrI93sMaypsttvaIL5nhVR92dTMUcQ= github.com/klauspost/compress v1.4.1 h1:8VMb5+0wMgdBykOV96DwNwKFQ+WTI4pzYURP99CcB9E= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.12.2 h1:2KCfW3I9M7nSc5wOqXAlW2v2U6v+w6cbjvbfp+OykW8= +github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s= -github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4= github.com/klauspost/cpuid/v2 v2.0.2 h1:pd2FBxFydtPn2ywTLStbFg9CJKrojATnpeJWSP7Ys4k= github.com/klauspost/cpuid/v2 v2.0.2/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.6 h1:dQ5ueTiftKxp0gyjKSx5+8BtPWkyQbd95m8Gys/RarI= @@ -105,6 +126,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lizongshen/gocommand v0.0.0-20180727133302-41f166788491 h1:Xo2fQIr7RBW4ZoVYiAZAm3ApUSgajejzi4Ywvmppcgo= +github.com/lizongshen/gocommand v0.0.0-20180727133302-41f166788491/go.mod h1:OjxV/FH+1T6ai0NEQYvxbkzF3AWjiTeYLJ2fWi/VSWQ= github.com/lucor/goinfo v0.0.0-20200401173949-526b5363a13a/go.mod h1:ORP3/rB5IsulLEBwQZCJyyV6niqmI7P4EWSmkug+1Ng= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= @@ -113,6 +136,8 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/panjf2000/ants/v2 v2.4.2 h1:kesjjo8JipN3vNNg1XaiXaeSs6xJweBTgenkBtsrHf8= github.com/panjf2000/ants/v2 v2.4.2/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A= +github.com/parnurzeal/gorequest v0.2.16 h1:T/5x+/4BT+nj+3eSknXmCTnEVGSzFzPGdpqmUVVZXHQ= +github.com/parnurzeal/gorequest v0.2.16/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -121,13 +146,18 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 h1:X+yvsM2yrEktyI+b2qND5gpH8YhURn0k8OCaeRnkINo= github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg= -github.com/shirou/gopsutil/v3 v3.21.3 h1:wgcdAHZS2H6qy4JFewVTtqfiYxFzCeEJod/mLztdPG8= -github.com/shirou/gopsutil/v3 v3.21.3/go.mod h1:ghfMypLDrFSWN2c9cDYFLHyynQ+QUht0cv/18ZqVczw= +github.com/shirou/gopsutil/v3 v3.21.5 h1:YUBf0w/KPLk7w1803AYBnH7BmA+1Z/Q5MEZxpREUaB4= +github.com/shirou/gopsutil/v3 v3.21.5/go.mod h1:ghfMypLDrFSWN2c9cDYFLHyynQ+QUht0cv/18ZqVczw= github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw= github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg= github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 h1:HunZiaEKNGVdhTRQOVpMmj5MQnGnv+e8uZNu3xFLgyM= @@ -143,6 +173,8 @@ github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0= github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU= github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU= @@ -156,7 +188,14 @@ github.com/tklauser/numcpus v0.2.1 h1:ct88eFm+Q7m2ZfXJdan1xYoXKlmwsfP+k88q05KvlZ github.com/tklauser/numcpus v0.2.1/go.mod h1:9aU+wOc6WjUIZEwWMP62PL/41d65P+iks1gBkr4QyP8= github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.26.0 h1:k5Tooi31zPG/g8yS6o2RffRO2C9B9Kah9SY8j/S7058= +github.com/valyala/fasthttp v1.26.0/go.mod h1:cmWIqlu99AO/RKcp1HWaViTqc57FswJOfYYdPJBl8BA= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc= +github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/xtaci/kcp-go v5.4.20+incompatible h1:TN1uey3Raw0sTz0Fg8GkfM0uH3YwzhnZWQ1bABv5xAg= github.com/xtaci/kcp-go v5.4.20+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE= github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM= @@ -168,8 +207,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -192,8 +231,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210510120150-4163338589ed h1:p9UgmWI9wKpfYmgaV/IZKGdXc5qEK45tDwwwDyjS26I= +golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -210,9 +249,10 @@ golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 h1:9UQO31fZ+0aKQOFldThf7BKPM golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -220,10 +260,13 @@ golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190808195139-e713427fea3f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -261,3 +304,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +moul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8= +moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE= +rsc.io/goversion v1.2.0 h1:SPn+NLTiAG7w30IRK/DKp1BjvpWabYgxlLp/+kx5J8w= +rsc.io/goversion v1.2.0/go.mod h1:Eih9y/uIBS3ulggl7KNJ09xGSLcuNaLgmvvqa07sgfo= diff --git a/lib/common/util.go b/lib/common/util.go index cb87daa1..40a30a3c 100755 --- a/lib/common/util.go +++ b/lib/common/util.go @@ -283,6 +283,7 @@ func CopyBuffer(dst io.Writer, src io.Reader, label ...string) (written int64, e //if len(pr)>0 && pr[0] && nr > 50 { // logs.Warn(string(buf[:50])) //} + //print(nr, "\n") if nr > 0 { nw, ew := dst.Write(buf[0:nr]) if nw > 0 { @@ -302,6 +303,9 @@ func CopyBuffer(dst io.Writer, src io.Reader, label ...string) (written int64, e break } } + //print(fmt.Printf("end....%d\n", goroutine.CopyConnsPool.Running())) + //goroutine.CopyConnsPool.Running() + //customDev.GetConnNum() return written, err } diff --git a/lib/file/obj.go b/lib/file/obj.go index 31f3e63b..f0634148 100644 --- a/lib/file/obj.go +++ b/lib/file/obj.go @@ -32,6 +32,7 @@ type Config struct { } type Client struct { + LastConnectTime int64 // 上次客户端成功建立连接的时间 Cnf *Config Id int //id VerifyKey string //verify key diff --git a/newgocommand/LICENSE b/newgocommand/LICENSE new file mode 100644 index 00000000..cb0b5fd7 --- /dev/null +++ b/newgocommand/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 lizongshen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/newgocommand/README.md b/newgocommand/README.md new file mode 100644 index 00000000..1b310700 --- /dev/null +++ b/newgocommand/README.md @@ -0,0 +1,18 @@ +# gocommand + + + package main + + import ( + "github.com/lizongshen/gocommand" + "log" + ) + + func main() { + _, out, err := gocommand.NewCommand().Exec("ls /") + if err != nil { + log.Panic(err) + } + + log.Println(out) + } diff --git a/newgocommand/command.go b/newgocommand/command.go new file mode 100644 index 00000000..5555ec3f --- /dev/null +++ b/newgocommand/command.go @@ -0,0 +1,21 @@ +package newgocommand + +import ( + "runtime" +) + +// Command的初始化函数 +func NewCommand() Commander { + var cmd Commander + + switch runtime.GOOS { + case "linux": + cmd = NewLinuxCommand() + case "windows": + cmd = NewWindowsCommand() + default: + cmd = NewLinuxCommand() + } + + return cmd +} diff --git a/newgocommand/command_test.go b/newgocommand/command_test.go new file mode 100644 index 00000000..2070ed22 --- /dev/null +++ b/newgocommand/command_test.go @@ -0,0 +1,13 @@ +package newgocommand + +import ( + "testing" +) + +// 测试Command初始化 +func TestNewCommand(t *testing.T) { + var cmd = NewCommand() + if _, ok := cmd.(Commander); !ok { + t.Errorf("new Commander err") + } +} diff --git a/newgocommand/commander.go b/newgocommand/commander.go new file mode 100644 index 00000000..7d547e98 --- /dev/null +++ b/newgocommand/commander.go @@ -0,0 +1,23 @@ +package newgocommand + +import "os/exec" + +// 命令行接口 +type Commander interface { + // 执行命令行并返回结果 + // args: 命令行参数 + // return: 进程的pid, 命令行结果, 错误消息 + Exec(args ...string) (*exec.Cmd, string, error) + + // 异步执行命令行并通过channel返回结果 + // stdout: chan结果 + // args: 命令行参数 + // return: 进程的pid + // exception: 协程内的命令行发生错误时,会panic异常 + ExecAsync(stdout chan string, args ...string) int + + // 执行命令行(忽略返回值) + // args: 命令行参数 + // return: 错误消息 + ExecIgnoreResult(args ...string) error +} diff --git a/newgocommand/example/example_main.go b/newgocommand/example/example_main.go new file mode 100644 index 00000000..b2ae4ed2 --- /dev/null +++ b/newgocommand/example/example_main.go @@ -0,0 +1,17 @@ +package main + +import ( + "log" + + "github.com/lizongshen/gocommand" +) + +func main() { + _, out, err := gocommand.NewCommand().Exec("ls /") + if err != nil { + log.Panic(err) + } + + log.Println(out) + +} diff --git a/newgocommand/linuxcommand.go b/newgocommand/linuxcommand.go new file mode 100644 index 00000000..9fdd02e9 --- /dev/null +++ b/newgocommand/linuxcommand.go @@ -0,0 +1,102 @@ +package newgocommand + +import ( + "io/ioutil" + "os" + "os/exec" + "syscall" +) + +// LinuxCommand结构体 +type LinuxCommand struct { +} + +// LinuxCommand的初始化函数 +func NewLinuxCommand() *LinuxCommand { + return &LinuxCommand{} +} + +// 执行命令行并返回结果 +// args: 命令行参数 +// return: 进程的pid, 命令行结果, 错误消息 +func (lc *LinuxCommand) Exec(args ...string) (*exec.Cmd, string, error) { + args = append([]string{"-c"}, args...) + cmd := exec.Command(os.Getenv("SHELL"), args...) + + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + + outpip, err := cmd.StdoutPipe() + defer outpip.Close() + + if err != nil { + return cmd, "", err + } + + err = cmd.Start() + if err != nil { + return cmd, "", err + } + + out, err := ioutil.ReadAll(outpip) + if err != nil { + return cmd, "", err + } + + return cmd, string(out), nil +} + +// 异步执行命令行并通过channel返回结果 +// stdout: chan结果 +// args: 命令行参数 +// return: 进程的pid +// exception: 协程内的命令行发生错误时,会panic异常 +func (lc *LinuxCommand) ExecAsync(stdout chan string, args ...string) int { + var pidChan = make(chan int, 1) + + go func() { + args = append([]string{"-c"}, args...) + cmd := exec.Command(os.Getenv("SHELL"), args...) + + cmd.SysProcAttr = &syscall.SysProcAttr{} + + outpip, err := cmd.StdoutPipe() + defer outpip.Close() + + if err != nil { + panic(err) + } + + err = cmd.Start() + if err != nil { + panic(err) + } + + pidChan <- cmd.Process.Pid + + out, err := ioutil.ReadAll(outpip) + if err != nil { + panic(err) + } + + stdout <- string(out) + }() + + return <-pidChan +} + +// 执行命令行(忽略返回值) +// args: 命令行参数 +// return: 错误消息 +func (lc *LinuxCommand) ExecIgnoreResult(args ...string) error { + + args = append([]string{"-c"}, args...) + cmd := exec.Command(os.Getenv("SHELL"), args...) + + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.SysProcAttr = &syscall.SysProcAttr{} + + err := cmd.Run() + + return err +} diff --git a/newgocommand/linuxcommand_test.go b/newgocommand/linuxcommand_test.go new file mode 100644 index 00000000..81910acc --- /dev/null +++ b/newgocommand/linuxcommand_test.go @@ -0,0 +1,66 @@ +package newgocommand + +import ( + "fmt" + "runtime" + "strings" + "testing" +) + +// 测试Linux Exec +func TestLinuxExec(t *testing.T) { + if runtime.GOOS == "linux" { + var cmd = NewLinuxCommand() + pid, out, err := cmd.Exec("ls /") + + if err != nil { + t.Errorf("exec err: %s", err) + } + + if pid == 0 { + t.Errorf("exec err: pid is %d", pid) + } + + if !strings.Contains(fmt.Sprintf("%s", out), "usr") { + t.Errorf("exec err: [ls /] command not contains usr") + } + } +} + +// 测试Linux异步Exec +func TestLinuxExecAsync(t *testing.T) { + if runtime.GOOS == "linux" { + var cmd = NewLinuxCommand() + + rc := make(chan string, 1) + pid := cmd.ExecAsync(rc, "ls /") + + r, ok := <-rc + if !ok { + t.Errorf("exec async read chan err!") + } + + if r == "" { + t.Errorf("exec async err!") + } + + if pid == 0 { + t.Errorf("exec async err: pid is %d", pid) + } + + if !strings.Contains(r, "usr") { + t.Errorf("exec async err: [ls /] command not contains usr") + } + } +} + +// 测试Linux下的Exec(无等待) +func TestLinuxExecNoWait(t *testing.T) { + if runtime.GOOS == "linux" { + var cmd = NewLinuxCommand() + err := cmd.ExecIgnoreResult("ls /") + if err != nil { + t.Errorf("exec nowait err: %s", err) + } + } +} diff --git a/newgocommand/windowscommand.go b/newgocommand/windowscommand.go new file mode 100644 index 00000000..762e5103 --- /dev/null +++ b/newgocommand/windowscommand.go @@ -0,0 +1,101 @@ +package newgocommand + +import ( + "io/ioutil" + "os" + "os/exec" + "syscall" +) + +// WindowsCommand结构体 +type WindowsCommand struct { +} + +// WindowsCommand的初始化函数 +func NewWindowsCommand() *WindowsCommand { + return &WindowsCommand{} +} + +// 执行命令行并返回结果 +// args: 命令行参数 +// return: 进程的pid, 命令行结果, 错误消息 +func (lc *WindowsCommand) Exec(args ...string) (*exec.Cmd, string, error) { + args = append([]string{"-c"}, args...) + cmd := exec.Command("cmd", args...) + + cmd.SysProcAttr = &syscall.SysProcAttr{} + + outpip, err := cmd.StdoutPipe() + defer outpip.Close() + + if err != nil { + return cmd, "", err + } + + err = cmd.Start() + if err != nil { + return cmd, "", err + } + + out, err := ioutil.ReadAll(outpip) + if err != nil { + return cmd, "", err + } + + return cmd, string(out), nil +} + +// 异步执行命令行并通过channel返回结果 +// stdout: chan结果 +// args: 命令行参数 +// return: 进程的pid +// exception: 协程内的命令行发生错误时,会panic异常 +func (lc *WindowsCommand) ExecAsync(stdout chan string, args ...string) int { + var pidChan = make(chan int, 1) + + go func() { + args = append([]string{"-c"}, args...) + cmd := exec.Command("cmd", args...) + + cmd.SysProcAttr = &syscall.SysProcAttr{} + + outpip, err := cmd.StdoutPipe() + defer outpip.Close() + + if err != nil { + panic(err) + } + + err = cmd.Start() + if err != nil { + panic(err) + } + + pidChan <- cmd.Process.Pid + + out, err := ioutil.ReadAll(outpip) + if err != nil { + panic(err) + } + + stdout <- string(out) + }() + + return <-pidChan +} + +// 执行命令行(忽略返回值) +// args: 命令行参数 +// return: 错误消息 +func (lc *WindowsCommand) ExecIgnoreResult(args ...string) error { + args = append([]string{"-c"}, args...) + cmd := exec.Command("cmd", args...) + + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.SysProcAttr = &syscall.SysProcAttr{} + + err := cmd.Run() + + return err +} diff --git a/newgocommand/windowscommand_test.go b/newgocommand/windowscommand_test.go new file mode 100644 index 00000000..ec26693d --- /dev/null +++ b/newgocommand/windowscommand_test.go @@ -0,0 +1,66 @@ +package newgocommand + +import ( + "fmt" + "runtime" + "strings" + "testing" +) + +// 测试Windows Exec +func TestWindowsExec(t *testing.T) { + if runtime.GOOS == "windows" { + var cmd = NewWindowsCommand() + pid, out, err := cmd.Exec("dir c:/") + + if err != nil { + t.Errorf("exec err: %s", err) + } + + if pid == 0 { + t.Errorf("exec err: pid is %d", pid) + } + + if !strings.Contains(fmt.Sprintf("%s", out), "Windows") { + t.Errorf("exec err: [dir c:/] command not contains Windows") + } + } +} + +// 测试Windows异步Exec +func TestWindowsExecAsync(t *testing.T) { + if runtime.GOOS == "windows" { + var cmd = NewWindowsCommand() + + rc := make(chan string, 1) + pid := cmd.ExecAsync(rc, "dir c:/") + + r, ok := <-rc + if !ok { + t.Errorf("exec async read chan err!") + } + + if r == "" { + t.Errorf("exec async err!") + } + + if pid == 0 { + t.Errorf("exec async err: pid is %d", pid) + } + + if !strings.Contains(r, "Windows") { + t.Errorf("exec async err: [dir c:/ command not contains Windows") + } + } +} + +// 测试Windows下的Exec(无等待) +func TestExecIgnoreResult(t *testing.T) { + if runtime.GOOS == "windows" { + var cmd = NewWindowsCommand() + err := cmd.ExecIgnoreResult("dir c:/") + if err != nil { + t.Errorf("exec nowait err: %s", err) + } + } +} diff --git a/server/server.go b/server/server.go index 148e8e47..8c1feca2 100644 --- a/server/server.go +++ b/server/server.go @@ -286,6 +286,7 @@ func dealClientData() { if vv, ok := Bridge.Client.Load(v.Id); ok { v.IsConnect = true v.Version = vv.(*bridge.Client).Version + v.LastConnectTime = time.Now().Unix() // 记录客户端存活时间 } else { v.IsConnect = false } diff --git a/web/views/client/list.html b/web/views/client/list.html index e26e5d86..c82d8220 100644 --- a/web/views/client/list.html +++ b/web/views/client/list.html @@ -164,7 +164,21 @@
if (value) { return '' } else { - return '' + if (row.LastConnectTime === 0) { + return '从未连接' + } + // 在列表页显示存活或者长期离线提示 + let now = Date.parse(new Date())/1000; + let offMinute = parseInt((now - row.LastConnectTime)/60) + let msg + + if (offMinute < 10) { + msg = offMinute +'分钟' + } else { + msg = '' + offMinute +'分钟' + } + + return ' ' + msg } } }, diff --git a/web/views/index/list.html b/web/views/index/list.html index 9918ffcc..090f3ef3 100644 --- a/web/views/index/list.html +++ b/web/views/index/list.html @@ -181,7 +181,22 @@
if (row.Client.IsConnect) { return '' } else { - return '' + if (row.Client.LastConnectTime === 0) { + return '从未连接' + } + + // 在列表页显示存活或者长期离线提示 + let now = Date.parse(new Date())/1000; + let offMinute = parseInt((now - row.Client.LastConnectTime)/60) + let msg + + if (offMinute < 10) { + msg = offMinute +'分钟' + } else { + msg = '' + offMinute +'分钟' + } + + return ' ' + msg } } },