diff --git a/README.md b/README.md
index ef188bd..6233fe7 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 177b303..a859dda 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 bd864bc..899813e 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 3486955..6881a5a 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 cce4b14..5864728 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 fcc0704..806736c 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 2a30e3c..4bc8b3d 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 07a0fe2..2ae0602 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 be254a7..364cb53 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 48b8e3a..e69de29 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 2b97c9c..49bdd62 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 cd6c383..f06deb7 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 8eae47d..d54d011 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 0003615..2f0e0ee 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 f9618e6..de822bf 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 76d3935..7592b19 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 11a6d61..ef34cf4 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 e13eb2c..6dab612 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 c55aa1d..4e58350 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 47b2e65..75220c7 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 76f7716..6100d72 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 602d67c..335f35d 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 d726c3c..76aa5f8 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 8413ad3..0176d77 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 8fcecd1..0594b5e 100755
--- a/web/views/client/list.html
+++ b/web/views/client/list.html
@@ -150,6 +150,13 @@
title: 'key',//标题
visible: true,//false表示不显示
sortable: true,//启用排序
+ formatter: function (value, row, index) {
+ if (!row.NoStore) {
+ return value
+ } else {
+ return "public vkey"
+ }
+ }
},
{
field: 'Addr',//域值
diff --git a/web/views/index/add.html b/web/views/index/add.html
index 08f6de1..63a1d39 100755
--- a/web/views/index/add.html
+++ b/web/views/index/add.html
@@ -12,9 +12,10 @@
-
+
+
+ {{/*