diff --git a/README.md b/README.md index ef188bd0..6233fe7d 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ go语言编写,无第三方依赖,各个平台都已经编译在release中 * [流量数据持久化](#流量数据持久化) * [自定义客户端连接密钥](#自定义客户端连接密钥) * [关闭公钥访问](#关闭公钥访问) + * [关闭web管理](#关闭web管理) * [客户端](#客户端) * [客户端启动](#客户端启动) * [无配置文件模式](#无配置文件模式) @@ -60,7 +61,9 @@ go语言编写,无第三方依赖,各个平台都已经编译在release中 * [udp隧道](#udp隧道模式) * [http正向代理](#http代理模式) * [socks5代理](#socks5代理模式) - * [私密代理](#私密代理) + * [私密代理](#私密代理模式) + * [p2p服务](#p2p代理) + * [文件访问代理](#文件访问模式) * [断线重连](#断线重连) * [状态检查](#状态检查) * [重载配置文件](#重载配置文件) @@ -161,17 +164,21 @@ go语言编写,无第三方依赖,各个平台都已经编译在release中 ---|--- httpport | web管理端口 password | web界面管理密码 -bridePort | 服务端客户端通信端口 +username | web界面管理账号 +bridgePort | 服务端客户端通信端口 pemPath | ssl certFile绝对路径 keyPath | ssl keyFile绝对路径 httpsProxyPort | 域名代理https代理监听端口 httpProxyPort | 域名代理http代理监听端口 -authip|web api免验证IP地址 +authKey|web api密钥 bridgeType|客户端与服务端连接方式kcp或tcp publicVkey|客户端以配置文件模式启动时的密钥,设置为空表示关闭客户端配置文件连接模式 ipLimit|是否限制ip访问,true或false或忽略 flowStoreInterval|服务端流量数据持久化间隔,单位分钟,忽略表示不持久化 logLevel|日志输出级别 +cryptKey | 获取服务端authKey时的aes加密密钥,16位 +serverIp| 服务端Ip,使用p2p模式必填 +p2pPort|p2p模式开启的udp端口 ### 详细说明 @@ -213,7 +220,7 @@ logLevel|日志输出级别 ./npc -server=1.1.1.1:8284 -vkey=客户端的密钥 ``` - 在该客户端隧道管理中添加一条tcp隧道,填写监听的端口(8001)、内网目标ip和目标端口(10.1.50.101:22),选择压缩方式,保存。 -- 访问公网服务器ip(127.0.0.1),填写的监听端口(8001),相当于访问内网ip(10.1.50.101):目标端口(22),例如:`ssh -p 8001 root@127.0.0.1` +- 访问公网服务器ip(127.0.0.1),填写的监听端口(8001),相当于访问内网ip(10.1.50.101):目标端口(22),例如:`ssh -p 8001 root@1.1.1.1` #### udp隧道 @@ -271,7 +278,7 @@ logLevel|日志输出级别 **适用范围:** 无需占用多余的端口、安全性要求较高可以防止其他人连接的TCP服务,例如ssh。 **假设场景:** -无需新增多的端将映射内网服务器10.1.50.2的22端口 +无需新增多的端将映射内网服务器10.1.50.2的22端口,公网服务器ip为1.1.1.1,网桥端口为8284 **使用步骤** - 在客户端管理中创建一个客户端,记录下验证密钥 @@ -284,7 +291,7 @@ logLevel|日志输出级别 ```ini [common] -server=127.0.0.1:8284 +server=1.1.1.1:8284 tp=tcp vkey=123 [secret_ssh] @@ -295,6 +302,37 @@ port=1000 假设用户名为root,现在执行`ssh -p 1000 root@127.0.0.1`即可访问ssh +#### p2p服务 + +**适用范围:** 大流量传输场景,流量不经过公网服务器,但是由于p2p穿透和nat类型关系较大,成功率不高。 + +**假设场景:** +内网1机器ip为10.1.50.2 内网2机器ip为10.2.50.2 口,公网服务器ip为1.1.1.1,网桥端口为8284 + +想通过访问机器1的2001端口---->访问到内网2机器的22端口 + +**使用步骤** +- 在客户端管理中创建一个客户端,记录下验证密钥 +- 内网机器2客户端运行 +``` +./npc -server=1.1.1.1:8284 -vkey=客户端的密钥 +``` +- 添加一条p2p代理,并设置唯一密钥p2pssh +- 在需要连接的机器上(即机器1)以配置文件模式启动客户端,内容如下 + +```ini +[common] +server=1.1.1.1:8284 +tp=tcp +vkey=123 +[p2p_ssh] +password=p2pssh +port=2001 +``` +**注意:** p2p前缀必须存在,password为web管理上添加的唯一密钥 + +假设机器2用户名为root,现在执行`ssh -p 2001 root@127.0.0.1`即可访问机器2的ssh + ### 使用https @@ -368,6 +406,9 @@ web上可以自定义客户端连接的密钥,但是必须具有唯一性 ### 关闭公钥访问 可以将`nps.conf`中的`publicVkey`设置为空或者删除 +### 关闭web管理 +可以将`nps.conf`中的`httpport`设置为空或者删除 + ## 客户端 ### 客户端启动 @@ -432,7 +473,7 @@ header_xxx|请求header修改或添加,header_proxy表示添加header proxy:np ```ini [tcp] -mode=tcp +mode=tcpServer target=127.0.0.1:8080 port=9001 ``` @@ -446,7 +487,7 @@ target|内网目标 ```ini [udp] -mode=udp +mode=udpServer target=127.0.0.1:8080 port=9002 ``` @@ -459,7 +500,7 @@ target|内网目标 ```ini [http] -mode=httpProxy +mode=httpProxyServer port=9003 ``` 项 | 含义 @@ -470,7 +511,7 @@ port | 在服务端的代理端口 ```ini [socks5] -mode=socks5 +mode=socks5Server port=9004 ``` 项 | 含义 @@ -487,10 +528,44 @@ target=10.1.50.2:22 ``` 项 | 含义 ---|--- -mode | secret +mode | secretServer +password | 唯一密钥 +target|内网目标 + +##### p2p代理模式 + +```ini +[p2p_ssh] +mode=p2p +password=ssh2 +target=10.1.50.2:22 +``` +项 | 含义 +---|--- +mode | p2p password | 唯一密钥 target|内网目标 +##### 文件访问模式 +利用nps提供一个公网可访问的本地文件服务 + +```ini +[file] +mode=file +port=9100 +local_path=/tmp/ +strip_pre=/web/ +```` + +项 | 含义 +---|--- +mode | file +port | 服务端开启的端口 +local_path|本地文件目录 +strip_pre|前缀 + +对于`strip_pre`,访问公网`ip:9100/web/`相当于访问`/tmp/`目录 + #### 断线重连 ```ini [common] @@ -600,7 +675,7 @@ allowPorts=9001-9009,10001,11000-12000 ```ini [tcp] -mode=tcp +mode=tcpServer port=9001-9009,10001,11000-12000 target=8001-8009,10002,13000-14000 ``` @@ -609,7 +684,7 @@ target=8001-8009,10002,13000-14000 ### 端口范围映射到其他机器 ```ini [tcp] -mode=tcp +mode=tcpServer port=9001-9009,10001,11000-12000 target=8001-8009,10002,13000-14000 targetAddr=10.1.50.2 @@ -707,6 +782,33 @@ time为有效小时数,例如time=2,在当前时间后的两小时内,本 ## webAPI +### webAPI验证说明 +- 采用auth_key的验证方式 +- 在提交的每个请求后面附带两个参数,`auth_key` 和`timestamp` + +``` +auth_key的生成方式为:md5(配置文件中的auth_key+当前时间戳) +``` + +``` +timestamp为当前时间戳 +``` + +**注意:** 为保证安全,时间戳的有效范围为20秒内,所以每次提交请求必须重新生成。 + +### 获取服务端authKey + +如果想获取authKey,服务端提供获取authKey的接口 + +``` +POST /auth/getauthkey +``` +将返回加密后的authKey,采用aes cbc加密,请使用与服务端配置文件中cryptKey相同的密钥进行解密 + + +### 详细文档 +- 此文档近期可能更新较慢,建议自行抓包 + 为方便第三方扩展,在web模式下可利用webAPI进行相关操作,详情见 [webAPI文档](https://github.com/cnlh/nps/wiki/webAPI%E6%96%87%E6%A1%A3) diff --git a/bridge/bridge.go b/bridge/bridge.go index 177b3030..a859ddaf 100755 --- a/bridge/bridge.go +++ b/bridge/bridge.go @@ -21,15 +21,18 @@ import ( ) type Client struct { - tunnel *mux.Mux - signal *conn.Conn + tunnel *mux.Mux + signal *conn.Conn + file *mux.Mux + retryTime int // it will be add 1 when ping not ok until to 3 will close the client sync.RWMutex } -func NewClient(t *mux.Mux, s *conn.Conn) *Client { +func NewClient(t, f *mux.Mux, s *conn.Conn) *Client { return &Client{ signal: s, tunnel: t, + file: f, } } @@ -64,6 +67,7 @@ func NewTunnel(tunnelPort int, tunnelType string, ipVerify bool, runList map[int } func (s *Bridge) StartTunnel() error { + go s.ping() var err error if s.tunnelType == "kcp" { s.kcpListener, err = kcp.ListenWithOptions(":"+strconv.Itoa(s.TunnelPort), nil, 150, 3) @@ -117,15 +121,17 @@ func (s *Bridge) cliProcess(c *conn.Conn) { c.Close() return } + //write server version to client c.Write([]byte(crypt.Md5(version.GetVersion()))) c.SetReadDeadline(5, s.tunnelType) var buf []byte var err error + //get vkey from client if buf, err = c.GetShortContent(32); err != nil { c.Close() return } - //验证 + //verify id, err := file.GetCsvDb().GetIdByVerifyKey(string(buf), c.Conn.RemoteAddr().String()) if err != nil { logs.Info("Current client connection validation error, close this client:", c.Conn.RemoteAddr()) @@ -150,7 +156,9 @@ func (s *Bridge) DelClient(id int, isOther bool) { if c, err := file.GetCsvDb().GetClient(id); err == nil && c.NoStore { s.CloseClient <- c.Id } - v.signal.Close() + if v.signal != nil { + v.signal.Close() + } delete(s.Client, id) } } @@ -170,13 +178,9 @@ func (s *Bridge) typeDeal(typeVal string, c *conn.Conn, id int) { v.signal = c v.Unlock() } else { - s.Client[id] = NewClient(nil, c) + s.Client[id] = NewClient(nil, nil, c) s.clientLock.Unlock() } - go func(id int) { - binary.Read(c, binary.LittleEndian, true) - s.DelClient(id, false) - }(id) logs.Info("clientId %d connection succeeded, address:%s ", id, c.Conn.RemoteAddr()) case common.WORK_CHAN: s.clientLock.Lock() @@ -186,17 +190,38 @@ func (s *Bridge) typeDeal(typeVal string, c *conn.Conn, id int) { v.tunnel = mux.NewMux(c.Conn) v.Unlock() } else { - s.Client[id] = NewClient(mux.NewMux(c.Conn), nil) + s.Client[id] = NewClient(mux.NewMux(c.Conn), nil, nil) s.clientLock.Unlock() } case common.WORK_CONFIG: - go s.getConfig(c) + var isPub bool + client, err := file.GetCsvDb().GetClient(id); + if err == nil { + if client.VerifyKey == beego.AppConfig.String("publicVkey") { + isPub = true + } else { + isPub = false + } + } + binary.Write(c, binary.LittleEndian, isPub) + go s.getConfig(c, isPub, client) case common.WORK_REGISTER: go s.register(c) case common.WORK_SECRET: if b, err := c.GetShortContent(32); err == nil { s.SecretChan <- conn.NewSecret(string(b), c) } + case common.WORK_FILE: + s.clientLock.Lock() + if v, ok := s.Client[id]; ok { + s.clientLock.Unlock() + v.Lock() + v.file = mux.NewMux(c.Conn) + v.Unlock() + } else { + s.Client[id] = NewClient(nil, mux.NewMux(c.Conn), nil) + s.clientLock.Unlock() + } case common.WORK_P2P: //read md5 secret if b, err := c.GetShortContent(32); err != nil { @@ -238,10 +263,12 @@ func (s *Bridge) register(c *conn.Conn) { } } -func (s *Bridge) SendLinkInfo(clientId int, link *conn.Link, linkAddr string) (target net.Conn, err error) { +func (s *Bridge) SendLinkInfo(clientId int, link *conn.Link, linkAddr string, t *file.Tunnel) (target net.Conn, err error) { s.clientLock.Lock() if v, ok := s.Client[clientId]; ok { s.clientLock.Unlock() + + //If ip is restricted to do ip verification if s.ipVerify { s.registerLock.Lock() ip := common.GetIpByAddr(linkAddr) @@ -255,18 +282,27 @@ func (s *Bridge) SendLinkInfo(clientId int, link *conn.Link, linkAddr string) (t } s.registerLock.Unlock() } - - if v.tunnel == nil { + var tunnel *mux.Mux + if t != nil && t.Mode == "file" { + tunnel = v.file + } else { + tunnel = v.tunnel + } + if tunnel == nil { err = errors.New("the client connect error") return } - if target, err = v.tunnel.NewConn(); err != nil { + if target, err = tunnel.NewConn(); err != nil { + return + } + + if t != nil && t.Mode == "file" { return } if _, err = conn.NewConn(target).SendLinkInfo(link); err != nil { - logs.Warn("new connect error ,the target %s refuse to connect", link.Host) + logs.Info("new connect error ,the target %s refuse to connect", link.Host) return } @@ -277,9 +313,36 @@ func (s *Bridge) SendLinkInfo(clientId int, link *conn.Link, linkAddr string) (t return } +func (s *Bridge) ping() { + ticker := time.NewTicker(time.Second * 5) + for { + select { + case <-ticker.C: + s.clientLock.Lock() + arr := make([]int, 0) + for k, v := range s.Client { + if v.tunnel == nil { + v.retryTime += 1 + if v.retryTime >= 3 { + arr = append(arr, k) + } + continue + } + if v.tunnel.IsClose { + arr = append(arr, k) + } + } + s.clientLock.Unlock() + for _, v := range arr { + logs.Info("the client %d closed", v) + s.DelClient(v, false) + } + } + } +} + //get config and add task from client config -func (s *Bridge) getConfig(c *conn.Conn) { - var client *file.Client +func (s *Bridge) getConfig(c *conn.Conn, isPub bool, client *file.Client) { var fail bool for { @@ -292,7 +355,6 @@ func (s *Bridge) getConfig(c *conn.Conn) { if b, err := c.GetShortContent(32); err != nil { break } else { - logs.Warn(string(b)) var str string id, err := file.GetCsvDb().GetClientIdByVkey(string(b)) if err != nil { @@ -327,17 +389,26 @@ func (s *Bridge) getConfig(c *conn.Conn) { c.Write([]byte(client.VerifyKey)) } case common.NEW_HOST: - if h, err := c.GetHostInfo(); err != nil { - fail = true - c.WriteAddFail() - break - } else if file.GetCsvDb().IsHostExist(h) { + h, err := c.GetHostInfo() + if err != nil { fail = true c.WriteAddFail() break + } + h.Client = client + if h.Location == "" { + h.Location = "/" + } + if !client.HasHost(h) { + if file.GetCsvDb().IsHostExist(h) { + fail = true + c.WriteAddFail() + break + } else { + file.GetCsvDb().NewHost(h) + c.WriteAddOk() + } } else { - h.Client = client - file.GetCsvDb().NewHost(h) c.WriteAddOk() } case common.NEW_TASK: @@ -381,18 +452,22 @@ func (s *Bridge) getConfig(c *conn.Conn) { tl.NoStore = true tl.Client = client tl.Password = t.Password - if err := file.GetCsvDb().NewTask(tl); err != nil { - logs.Notice("Add task error ", err.Error()) - fail = true - c.WriteAddFail() - break - } - if b := tool.TestServerPort(tl.Port, tl.Mode); !b && t.Mode != "secret" { - fail = true - c.WriteAddFail() - break - } else { - s.OpenTask <- tl + tl.LocalPath = t.LocalPath + tl.StripPre = t.StripPre + if !client.HasTunnel(tl) { + if err := file.GetCsvDb().NewTask(tl); err != nil { + logs.Notice("Add task error ", err.Error()) + fail = true + c.WriteAddFail() + break + } + if b := tool.TestServerPort(tl.Port, tl.Mode); !b && t.Mode != "secret" && t.Mode != "p2p" { + fail = true + c.WriteAddFail() + break + } else { + s.OpenTask <- tl + } } c.WriteAddOk() } @@ -400,7 +475,7 @@ func (s *Bridge) getConfig(c *conn.Conn) { } } if fail && client != nil { - s.CloseClient <- client.Id + s.DelClient(client.Id, false) } c.Close() } diff --git a/client/client.go b/client/client.go index bd864bc4..899813e2 100755 --- a/client/client.go +++ b/client/client.go @@ -17,6 +17,8 @@ type TRPClient struct { stop chan bool proxyUrl string vKey string + tunnel *mux.Mux + signal *conn.Conn } //new client @@ -39,16 +41,19 @@ retry: time.Sleep(time.Second * 5) goto retry } + logs.Info("Successful connection with server %s", s.svrAddr) + go s.ping() s.processor(c) } func (s *TRPClient) Close() { - s.stop <- true + s.signal.Close() } //处理 func (s *TRPClient) processor(c *conn.Conn) { + s.signal = c go s.dealChan() for { flags, err := c.ReadFlag() @@ -176,9 +181,9 @@ func (s *TRPClient) dealChan() { return } go func() { - l := mux.NewMux(tunnel.Conn) + s.tunnel = mux.NewMux(tunnel.Conn) for { - src, err := l.Accept() + src, err := s.tunnel.Accept() if err != nil { logs.Warn(err) break @@ -196,6 +201,7 @@ func (s *TRPClient) srcProcess(src net.Conn) { logs.Error("get connection info from server error ", err) return } + //host for target processing lk.Host = common.FormatAddress(lk.Host) //connect to target if targetConn, err := net.Dial(lk.ConnType, lk.Host); err != nil { @@ -206,3 +212,18 @@ func (s *TRPClient) srcProcess(src net.Conn) { conn.CopyWaitGroup(src, targetConn, lk.Crypt, lk.Compress, nil, nil) } } + +func (s *TRPClient) ping() { + ticker := time.NewTicker(time.Second * 5) +loop: + for { + select { + case <-ticker.C: + if s.tunnel.IsClose { + s.Close() + ticker.Stop() + break loop + } + } + } +} diff --git a/client/control.go b/client/control.go index 3486955b..6881a5ac 100644 --- a/client/control.go +++ b/client/control.go @@ -1,6 +1,7 @@ package client import ( + "encoding/binary" "errors" "github.com/cnlh/nps/lib/common" "github.com/cnlh/nps/lib/config" @@ -41,7 +42,8 @@ func GetTaskStatus(path string) { } else if _, err := c.Write([]byte(crypt.Md5(string(f)))); err != nil { log.Fatalln(err) } - + var isPub bool + binary.Read(c, binary.LittleEndian, &isPub) if l, err := c.GetLen(); err != nil { log.Fatalln(err) } else if b, err := c.GetShortContent(l); err != nil { @@ -104,25 +106,30 @@ re: logs.Error(err) goto re } - - // send global configuration to server and get status of config setting - if _, err := c.SendConfigInfo(cnf.CommonConfig); err != nil { - logs.Error(err) - goto re - } - if !c.GetAddStatus() { - logs.Error(errAdd) - goto re - } + var isPub bool + binary.Read(c, binary.LittleEndian, &isPub) // get tmp password var b []byte - if b, err = c.GetShortContent(16); err != nil { - logs.Error(err) - goto re - } else { - ioutil.WriteFile(filepath.Join(common.GetTmpPath(), "npc_vkey.txt"), []byte(string(b)), 0600) + vkey := cnf.CommonConfig.VKey + if isPub { + // send global configuration to server and get status of config setting + if _, err := c.SendConfigInfo(cnf.CommonConfig); err != nil { + logs.Error(err) + goto re + } + if !c.GetAddStatus() { + logs.Error(errAdd) + goto re + } + + if b, err = c.GetShortContent(16); err != nil { + logs.Error(err) + goto re + } + vkey = string(b) } + ioutil.WriteFile(filepath.Join(common.GetTmpPath(), "npc_vkey.txt"), []byte(vkey), 0600) //send hosts to server for _, v := range cnf.Hosts { @@ -146,6 +153,10 @@ re: logs.Error(errAdd, v.Ports) goto re } + if v.Mode == "file" { + //start local file server + go startLocalFileServer(cnf.CommonConfig, v, vkey) + } } //create local server secret or p2p @@ -154,7 +165,7 @@ re: } c.Close() - NewRPClient(cnf.CommonConfig.Server, string(b), cnf.CommonConfig.Tp, cnf.CommonConfig.ProxyUrl).Start() + NewRPClient(cnf.CommonConfig.Server, vkey, cnf.CommonConfig.Tp, cnf.CommonConfig.ProxyUrl).Start() CloseLocalServer() goto re } diff --git a/client/local.go b/client/local.go index cce4b14c..58647281 100644 --- a/client/local.go +++ b/client/local.go @@ -5,31 +5,52 @@ import ( "github.com/cnlh/nps/lib/config" "github.com/cnlh/nps/lib/conn" "github.com/cnlh/nps/lib/crypt" + "github.com/cnlh/nps/lib/file" "github.com/cnlh/nps/lib/mux" "github.com/cnlh/nps/vender/github.com/astaxie/beego/logs" "github.com/cnlh/nps/vender/github.com/xtaci/kcp" "net" + "net/http" "strings" ) var LocalServer []*net.TCPListener var udpConn net.Conn var muxSession *mux.Mux +var fileServer []*http.Server func CloseLocalServer() { for _, v := range LocalServer { v.Close() } + for _, v := range fileServer { + v.Close() + } +} + +func startLocalFileServer(config *config.CommonConfig, t *file.Tunnel, vkey string) { + remoteConn, err := NewConn(config.Tp, vkey, config.Server, common.WORK_FILE, config.ProxyUrl) + if err != nil { + logs.Error("Local connection server failed ", err.Error()) + return + } + srv := &http.Server{ + Handler: http.StripPrefix(t.StripPre, http.FileServer(http.Dir(t.LocalPath))), + } + logs.Info("start local file system, local path %s, strip prefix %s ,remote port %s ", t.LocalPath, t.StripPre, t.Ports) + fileServer = append(fileServer, srv) + listener := mux.NewMux(remoteConn.Conn) + logs.Warn(srv.Serve(listener)) } func StartLocalServer(l *config.LocalServer, config *config.CommonConfig) error { listener, err := net.ListenTCP("tcp", &net.TCPAddr{net.ParseIP("0.0.0.0"), l.Port, ""}) if err != nil { - logs.Error("Local listener startup failed port %d, error %s", l.Port, err.Error()) + logs.Error("local listener startup failed port %d, error %s", l.Port, err.Error()) return err } LocalServer = append(LocalServer, listener) - logs.Info("Successful start-up of local monitoring, port", l.Port) + logs.Info("successful start-up of local monitoring, port", l.Port) for { c, err := listener.AcceptTCP() if err != nil { @@ -52,9 +73,11 @@ func processSecret(localTcpConn net.Conn, config *config.CommonConfig, l *config remoteConn, err := NewConn(config.Tp, config.VKey, config.Server, common.WORK_SECRET, config.ProxyUrl) if err != nil { logs.Error("Local connection server failed ", err.Error()) + return } if _, err := remoteConn.Write([]byte(crypt.Md5(l.Password))); err != nil { logs.Error("Local connection server failed ", err.Error()) + return } conn.CopyWaitGroup(remoteConn, localTcpConn, false, false, nil, nil) } @@ -62,6 +85,9 @@ func processSecret(localTcpConn net.Conn, config *config.CommonConfig, l *config func processP2P(localTcpConn net.Conn, config *config.CommonConfig, l *config.LocalServer) { if udpConn == nil { newUdpConn(config, l) + if udpConn == nil { + return + } muxSession = mux.NewMux(udpConn) } nowConn, err := muxSession.NewConn() @@ -110,6 +136,7 @@ func newUdpConn(config *config.CommonConfig, l *config.LocalServer) { conn.SetUdpSession(localKcpConn) if err != nil { logs.Error(err) + return } //写入密钥、provider身份 if _, err := localKcpConn.Write([]byte(crypt.Md5(l.Password))); err != nil { diff --git a/conf/clients.csv b/conf/clients.csv index fcc07042..806736cc 100644 --- a/conf/clients.csv +++ b/conf/clients.csv @@ -1,3 +1 @@ -2,test1,,true,dsads,dsddsda,0,false,0,0,0 -5,rilj9h70ux8yz3d2,,true,,,0,false,0,0,0 -8,88,111,true,,,0,false,0,70,0 +2,corjmrbhr33otit1,,true,,,0,false,0,0,0 diff --git a/conf/hosts.csv b/conf/hosts.csv index 2a30e3cb..4bc8b3d6 100644 --- a/conf/hosts.csv +++ b/conf/hosts.csv @@ -1,3 +1 @@ -b.o.com,127.0.0.1:8080,2,,,,,2,0,0 -a.o.com,127.0.0.1:8082,8,,127.0.0.1:8080,,/,3,62428000,807503 -c.o.com,127.0.0.1:8082,8,,,,,4,0,0 +b.o.com,127.0.0.1:8080,2,,,111,/,3,0,0 diff --git a/conf/npc.conf b/conf/npc.conf index 07a0fe20..2ae0602f 100644 --- a/conf/npc.conf +++ b/conf/npc.conf @@ -4,6 +4,9 @@ tp=tcp vkey=123 auto_reconnection=true +[web] +host=a.o.com +target=127.0.0.1:8080 [tcp] mode=tcp target=8006-8010,8012 @@ -18,6 +21,13 @@ port=9005 mode=httpProxy port=9004 + +[file] +mode=file +port=9100 +local_path=./ +strip_pre=/web/ + [s_ssh] mode=secret password=1234 diff --git a/conf/nps.conf b/conf/nps.conf index be254a7a..364cb53d 100755 --- a/conf/nps.conf +++ b/conf/nps.conf @@ -1,7 +1,7 @@ appname = nps #Web Management Port -httpport = +httpport = 8080 #Boot mode(dev|pro) runmode = dev diff --git a/conf/tasks.csv b/conf/tasks.csv index 48b8e3a8..e69de29b 100644 --- a/conf/tasks.csv +++ b/conf/tasks.csv @@ -1,4 +0,0 @@ -0,p2p,,1,32,8,p2p ssh,0,0,p2pssh -9002,tcp,127.0.0.1:808022,1,1,8,dsa,0,0, -9001,tcp,5900,1,48,8,,0,0, -9999,socks5,,1,66,8,,0,0, diff --git a/image/web2.png b/image/web2.png index 2b97c9c2..49bdd62a 100644 Binary files a/image/web2.png and b/image/web2.png differ diff --git a/lib/common/const.go b/lib/common/const.go index cd6c3831..f06deb76 100644 --- a/lib/common/const.go +++ b/lib/common/const.go @@ -9,6 +9,7 @@ const ( WORK_CONFIG = "conf" WORK_REGISTER = "rgst" WORK_SECRET = "sert" + WORK_FILE = "file" WORK_P2P = "p2pm" WORK_P2P_VISITOR = "p2pv" WORK_P2P_PROVIDER = "p2pp" diff --git a/lib/config/config.go b/lib/config/config.go index 8eae47d4..d54d011f 100644 --- a/lib/config/config.go +++ b/lib/config/config.go @@ -183,6 +183,10 @@ func dealTunnel(s string) *file.Tunnel { t.TargetAddr = item[1] case "password": t.Password = item[1] + case "local_path": + t.LocalPath = item[1] + case "strip_pre": + t.StripPre = item[1] } } return t diff --git a/lib/conn/conn.go b/lib/conn/conn.go index 00036158..2f0e0ee6 100755 --- a/lib/conn/conn.go +++ b/lib/conn/conn.go @@ -302,7 +302,7 @@ func (s *Conn) SendTaskInfo(t *file.Tunnel) (int, error) { */ raw := bytes.NewBuffer([]byte{}) binary.Write(raw, binary.LittleEndian, []byte(common.NEW_TASK)) - common.BinaryWrite(raw, t.Mode, t.Ports, t.Target, t.Remark, t.TargetAddr, t.Password) + common.BinaryWrite(raw, t.Mode, t.Ports, t.Target, t.Remark, t.TargetAddr, t.Password, t.LocalPath, t.StripPre) s.Lock() defer s.Unlock() return s.Write(raw.Bytes()) @@ -329,6 +329,8 @@ func (s *Conn) GetTaskInfo() (t *file.Tunnel, err error) { t.Remark = arr[3] t.TargetAddr = arr[4] t.Password = arr[5] + t.LocalPath = arr[6] + t.StripPre = arr[7] t.NoStore = true } return diff --git a/lib/file/file.go b/lib/file/file.go index f9618e60..de822bff 100644 --- a/lib/file/file.go +++ b/lib/file/file.go @@ -148,7 +148,7 @@ func (s *Csv) GetIdByVerifyKey(vKey string, addr string) (int, error) { func (s *Csv) NewTask(t *Tunnel) error { for _, v := range s.Tasks { - if v.Mode == "secret" && v.Password == t.Password { + if (v.Mode == "secret" || v.Mode == "p2p") && v.Password == t.Password { return errors.New(fmt.Sprintf("Secret mode keys %s must be unique", t.Password)) } } @@ -159,10 +159,8 @@ func (s *Csv) NewTask(t *Tunnel) error { } func (s *Csv) UpdateTask(t *Tunnel) error { - for k, v := range s.Tasks { + for _, v := range s.Tasks { if v.Id == t.Id { - s.Tasks = append(s.Tasks[:k], s.Tasks[k+1:]...) - s.Tasks = append(s.Tasks, t) s.StoreTasksToCsv() return nil } @@ -332,6 +330,9 @@ func (s *Csv) NewHost(t *Host) error { if s.IsHostExist(t) { return errors.New("host has exist") } + if t.Location == "" { + t.Location = "/" + } t.Flow = new(Flow) s.Hosts = append(s.Hosts, t) s.StoreHostToCsv() @@ -339,10 +340,8 @@ func (s *Csv) NewHost(t *Host) error { } func (s *Csv) UpdateHost(t *Host) error { - for k, v := range s.Hosts { + for _, v := range s.Hosts { if v.Host == t.Host { - s.Hosts = append(s.Hosts[:k], s.Hosts[k+1:]...) - s.Hosts = append(s.Hosts, t) s.StoreHostToCsv() return nil } @@ -465,7 +464,7 @@ func (s *Csv) GetClient(id int) (v *Client, err error) { } func (s *Csv) GetClientIdByVkey(vkey string) (id int, err error) { for _, v := range s.Clients { - if v.VerifyKey == vkey { + if crypt.Md5(v.VerifyKey) == vkey { id = v.Id return } diff --git a/lib/file/obj.go b/lib/file/obj.go index 76d39351..7592b19c 100644 --- a/lib/file/obj.go +++ b/lib/file/obj.go @@ -77,6 +77,24 @@ func (s *Client) GetConn() bool { return false } +func (s *Client) HasTunnel(t *Tunnel) bool { + for _, v := range GetCsvDb().Tasks { + if v.Client.Id == s.Id && v.Port == t.Port { + return true + } + } + return false +} + +func (s *Client) HasHost(h *Host) bool { + for _, v := range GetCsvDb().Hosts { + if v.Client.Id == s.Id && v.Host == h.Host && h.Location == v.Location { + return true + } + } + return false +} + type Tunnel struct { Id int //Id Port int //服务端监听端口 @@ -91,6 +109,8 @@ type Tunnel struct { Remark string //备注 TargetAddr string NoStore bool + LocalPath string + StripPre string } type Config struct { diff --git a/lib/mux/conn.go b/lib/mux/conn.go index 11a6d619..ef34cf4a 100644 --- a/lib/mux/conn.go +++ b/lib/mux/conn.go @@ -140,7 +140,7 @@ func (s *conn) Close() error { close(s.connStatusOkCh) close(s.connStatusFailCh) close(s.readCh) - if !s.mux.isClose { + if !s.mux.IsClose { s.sendMsgCh <- NewMsg(s.connId, nil) } return nil diff --git a/lib/mux/mux.go b/lib/mux/mux.go index e13eb2c9..6dab6124 100644 --- a/lib/mux/mux.go +++ b/lib/mux/mux.go @@ -21,6 +21,8 @@ const ( MUX_NEW_CONN MUX_PING MUX_CONN_CLOSE + MUX_PING_RETURN + RETRY_TIME = 2 //Heart beat allowed fault tolerance times ) type Mux struct { @@ -32,7 +34,8 @@ type Mux struct { newConnCh chan *conn id int32 closeChan chan struct{} - isClose bool + IsClose bool + pingOk int sync.Mutex } @@ -45,7 +48,7 @@ func NewMux(c net.Conn) *Mux { id: 0, closeChan: make(chan struct{}), newConnCh: make(chan *conn), - isClose: false, + IsClose: false, } //read session by flag go m.readSession() @@ -57,7 +60,7 @@ func NewMux(c net.Conn) *Mux { } func (s *Mux) NewConn() (*conn, error) { - if s.isClose { + if s.IsClose { return nil, errors.New("the mux has closed") } conn := NewConn(s.getId(), s, s.sendMsgCh, s.sendStatusCh) @@ -82,7 +85,7 @@ func (s *Mux) NewConn() (*conn, error) { } func (s *Mux) Accept() (net.Conn, error) { - if s.isClose { + if s.IsClose { return nil, errors.New("accpet error,the conn has closed") } return <-s.newConnCh, nil @@ -107,10 +110,11 @@ func (s *Mux) ping() { raw.Reset() binary.Write(raw, binary.LittleEndian, MUX_PING_FLAG) binary.Write(raw, binary.LittleEndian, MUX_PING) - if _, err := s.conn.Write(raw.Bytes()); err != nil { + if _, err := s.conn.Write(raw.Bytes()); err != nil || s.pingOk > RETRY_TIME { s.Close() break } + s.pingOk += 1 } }() select { @@ -176,6 +180,13 @@ func (s *Mux) readSession() { s.conn.Write(raw.Bytes()) continue case MUX_PING_FLAG: //ping + raw.Reset() + binary.Write(raw, binary.LittleEndian, MUX_PING_RETURN) + binary.Write(raw, binary.LittleEndian, MUX_PING) + s.conn.Write(raw.Bytes()) + continue + case MUX_PING_RETURN: + s.pingOk -= 1 continue case MUX_NEW_MSG: if n, err = ReadLenBytes(buf, s.conn); err != nil { @@ -212,10 +223,10 @@ func (s *Mux) readSession() { } func (s *Mux) Close() error { - if s.isClose { + if s.IsClose { return errors.New("the mux has closed") } - s.isClose = true + s.IsClose = true s.connMap.Close() s.closeChan <- struct{}{} s.closeChan <- struct{}{} diff --git a/server/proxy/base.go b/server/proxy/base.go index c55aa1da..4e583501 100644 --- a/server/proxy/base.go +++ b/server/proxy/base.go @@ -6,6 +6,7 @@ import ( "github.com/cnlh/nps/lib/common" "github.com/cnlh/nps/lib/conn" "github.com/cnlh/nps/lib/file" + "github.com/cnlh/nps/vender/github.com/astaxie/beego/logs" "net" "net/http" "sync" @@ -74,13 +75,17 @@ func (s *BaseServer) checkFlow() error { func (s *BaseServer) DealClient(c *conn.Conn, addr string, rb []byte, tp string) error { link := conn.NewLink(tp, addr, s.task.Client.Cnf.Crypt, s.task.Client.Cnf.Compress, c.Conn.RemoteAddr().String()) - if target, err := s.bridge.SendLinkInfo(s.task.Client.Id, link, c.Conn.RemoteAddr().String()); err != nil { + if target, err := s.bridge.SendLinkInfo(s.task.Client.Id, link, c.Conn.RemoteAddr().String(), s.task); err != nil { + logs.Warn("task id %d get connection from client id %d error %s", s.task.Id, s.task.Client.Id, err.Error()) c.Close() return err } else { + if rb != nil { + target.Write(rb) + } conn.CopyWaitGroup(target, c, link.Crypt, link.Compress, s.task.Client.Rate, s.task.Client.Flow) } - + s.task.Client.AddConn() return nil } diff --git a/server/proxy/http.go b/server/proxy/http.go index 47b2e652..75220c7e 100644 --- a/server/proxy/http.go +++ b/server/proxy/http.go @@ -147,7 +147,7 @@ func (s *httpServer) process(c *conn.Conn, r *http.Request) { break } lk := conn.NewLink(common.CONN_TCP, host.Target, host.Client.Cnf.Crypt, host.Client.Cnf.Compress, r.RemoteAddr) - if target, err = s.bridge.SendLinkInfo(host.Client.Id, lk, c.Conn.RemoteAddr().String()); err != nil { + if target, err = s.bridge.SendLinkInfo(host.Client.Id, lk, c.Conn.RemoteAddr().String(), nil); err != nil { logs.Notice("connect to target %s error %s", lk.Host, err) break } diff --git a/server/proxy/socks5.go b/server/proxy/socks5.go index 76f77161..6100d72b 100755 --- a/server/proxy/socks5.go +++ b/server/proxy/socks5.go @@ -144,7 +144,7 @@ func (s *Sock5ModeServer) doConnect(c net.Conn, command uint8) { //s.DealClient(conn.NewConn(c), addr, nil, ltype) link := conn.NewLink(ltype, addr, s.task.Client.Cnf.Crypt, s.task.Client.Cnf.Compress, c.RemoteAddr().String()) - if target, err := s.bridge.SendLinkInfo(s.task.Client.Id, link, c.RemoteAddr().String()); err != nil { + if target, err := s.bridge.SendLinkInfo(s.task.Client.Id, link, c.RemoteAddr().String(),s.task); err != nil { c.Close() return } else { diff --git a/server/proxy/udp.go b/server/proxy/udp.go index 602d67c2..335f35d0 100755 --- a/server/proxy/udp.go +++ b/server/proxy/udp.go @@ -50,7 +50,7 @@ func (s *UdpModeServer) process(addr *net.UDPAddr, data []byte) { if err := s.checkFlow(); err != nil { return } - if target, err := s.bridge.SendLinkInfo(s.task.Client.Id, link, addr.String()); err != nil { + if target, err := s.bridge.SendLinkInfo(s.task.Client.Id, link, addr.String(), s.task); err != nil { return } else { s.task.Flow.Add(int64(len(data)), 0) diff --git a/server/server.go b/server/server.go index d726c3ca..76aa5f81 100644 --- a/server/server.go +++ b/server/server.go @@ -79,7 +79,7 @@ func DealBridgeTask() { func StartNewServer(bridgePort int, cnf *file.Tunnel, bridgeType string) { Bridge = bridge.NewTunnel(bridgePort, bridgeType, common.GetBoolByStr(beego.AppConfig.String("ipLimit")), RunList) if err := Bridge.StartTunnel(); err != nil { - logs.Error("服务端开启失败", err) + logs.Error("start server bridge error", err) os.Exit(0) } else { logs.Info("Server startup, the bridge type is %s, the bridge port is %d", bridgeType, bridgePort) @@ -103,7 +103,7 @@ func StartNewServer(bridgePort int, cnf *file.Tunnel, bridgeType string) { func NewMode(Bridge *bridge.Bridge, c *file.Tunnel) proxy.Service { var service proxy.Service switch c.Mode { - case "tcp": + case "tcp", "file": service = proxy.NewTunnelModeServer(proxy.ProcessTunnel, Bridge, c) case "socks5": service = proxy.NewSock5ModeServer(Bridge, c) @@ -134,6 +134,7 @@ func StopServer(id int) error { if err := svr.Close(); err != nil { return err } + logs.Info("stop server id %d", id) } if t, err := file.GetCsvDb().GetTask(id); err != nil { return err @@ -144,7 +145,7 @@ func StopServer(id int) error { delete(RunList, id) return nil } - return errors.New("未在运行中") + return errors.New("task is not running") } //add task diff --git a/web/controllers/index.go b/web/controllers/index.go index 8413ad3a..0176d779 100755 --- a/web/controllers/index.go +++ b/web/controllers/index.go @@ -43,6 +43,11 @@ func (s *IndexController) Http() { s.SetType("httpProxy") s.display("index/list") } +func (s *IndexController) File() { + s.SetInfo("file server") + s.SetType("file") + s.display("index/list") +} func (s *IndexController) Secret() { s.SetInfo("secret") @@ -85,14 +90,16 @@ func (s *IndexController) Add() { s.display() } else { t := &file.Tunnel{ - Port: s.GetIntNoErr("port"), - Mode: s.GetString("type"), - Target: s.GetString("target"), - Id: file.GetCsvDb().GetTaskId(), - Status: true, - Remark: s.GetString("remark"), - Password: s.GetString("password"), - Flow: &file.Flow{}, + Port: s.GetIntNoErr("port"), + Mode: s.GetString("type"), + Target: s.GetString("target"), + Id: file.GetCsvDb().GetTaskId(), + Status: true, + Remark: s.GetString("remark"), + Password: s.GetString("password"), + LocalPath: s.GetString("local_path"), + StripPre: s.GetString("strip_pre"), + Flow: &file.Flow{}, } if !tool.TestServerPort(t.Port, t.Mode) { s.AjaxErr("The port cannot be opened because it may has been occupied or is no longer allowed.") @@ -101,7 +108,9 @@ func (s *IndexController) Add() { if t.Client, err = file.GetCsvDb().GetClient(s.GetIntNoErr("client_id")); err != nil { s.AjaxErr(err.Error()) } - file.GetCsvDb().NewTask(t) + if err := file.GetCsvDb().NewTask(t); err != nil { + s.AjaxErr(err.Error()) + } if err := server.AddTask(t); err != nil { s.AjaxErr(err.Error()) } else { @@ -140,11 +149,15 @@ func (s *IndexController) Edit() { t.Target = s.GetString("target") t.Password = s.GetString("password") t.Id = id + t.LocalPath = s.GetString("local_path") + t.StripPre = s.GetString("strip_pre") t.Remark = s.GetString("remark") if t.Client, err = file.GetCsvDb().GetClient(s.GetIntNoErr("client_id")); err != nil { s.AjaxErr("modified error") } file.GetCsvDb().UpdateTask(t) + server.StopServer(t.Id) + server.StartTask(t.Id) } s.AjaxOk("modified success") } diff --git a/web/views/client/list.html b/web/views/client/list.html index 8fcecd16..0594b5e5 100755 --- a/web/views/client/list.html +++ b/web/views/client/list.html @@ -150,6 +150,13 @@