Jelajahi Sumber

File mode|pubVkey optimization

刘河 6 tahun lalu
induk
melakukan
1c1aa5ec5b

+ 115 - 13
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)
 

+ 115 - 40
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()
 }

+ 24 - 3
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
+			}
+		}
+	}
+}

+ 28 - 17
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
 }

+ 29 - 2
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 {

+ 1 - 3
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

+ 1 - 3
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

+ 10 - 0
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

+ 1 - 1
conf/nps.conf

@@ -1,7 +1,7 @@
 appname = nps
 
 #Web Management Port
-httpport =
+httpport = 8080
 
 #Boot mode(dev|pro)
 runmode = dev

+ 0 - 4
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,

TEMPAT SAMPAH
image/web2.png


+ 1 - 0
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"

+ 4 - 0
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

+ 3 - 1
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

+ 7 - 8
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
 		}

+ 20 - 0
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 {

+ 1 - 1
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

+ 18 - 7
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{}{}

+ 7 - 2
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
 }

+ 1 - 1
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
 			}

+ 1 - 1
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 {

+ 1 - 1
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)

+ 4 - 3
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

+ 22 - 9
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")
 	}

+ 7 - 0
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',//域值

+ 23 - 4
web/views/index/add.html

@@ -12,9 +12,10 @@
                                 <option {{if eq "udp" .type}}selected{{end}} value="udp">udp</option>
                                 <option {{if eq "socks5" .type}}selected{{end}} value="socks5">socks5
                                 </option>
-                                <option {{if eq "httpProxy" .type}}selected{{end}} value="httpProxy">http
-                                <option {{if eq "secret" .type}}selected{{end}} value="secret">secret
-                                <option {{if eq "p2p" .type}}selected{{end}} value="p2p">p2p
+                                <option {{if eq "httpProxy" .type}}selected{{end}} value="httpProxy">http</option>
+                                <option {{if eq "secret" .type}}selected{{end}} value="secret">secret</option>
+                                <option {{if eq "p2p" .type}}selected{{end}} value="p2p">p2p</option>
+                            {{/*<option {{if eq "file" .type}}selected{{end}} value="file">file*/}}
                             </select>
                         </div>
                     </div>
@@ -33,6 +34,7 @@
                             <input class="form-control" type="text" name="port" placeholder="such as 8024">
                         </div>
                     </div>
+
                     <div class="form-group" id="target">
                         <label class="col-sm-2 control-label">target of Intranet(ip:port)</label>
                         <div class="col-sm-10">
@@ -50,6 +52,22 @@
                         </div>
                     </div>
 
+                    <div class="form-group" id="local_path">
+                        <label class="col-sm-2 control-label">local path</label>
+                        <div class="col-sm-10">
+                            <input class="form-control" type="text" name="local_path"
+                                   placeholder="such as /tmp">
+                        </div>
+                    </div>
+
+                    <div class="form-group" id="strip_pre">
+                        <label class="col-sm-2 control-label">strip pre</label>
+                        <div class="col-sm-10">
+                            <input class="form-control" type="text" name="strip_pre"
+                                   placeholder="such as static">
+                        </div>
+                    </div>
+
                     <div class="form-group" id="password">
                         <label class="col-sm-2 control-label">unique identification key</label>
                         <div class="col-sm-10">
@@ -75,13 +93,14 @@
 </div>
 <script>
     var arr = []
-    arr["all"] = ["type", "port", "compress", "u", "p", "target", "password"]
+    arr["all"] = ["type", "port", "compress", "u", "p", "target", "password", "strip_pre", "local_path"]
     arr["tcp"] = ["type", "port", "target", "compress", "u", "p", "tcp隧道模式,提供一条tcp隧道,适用于ssh、远程桌面等,添加后会自动生成一个客户端验证key<br>在内网机器执行<span style='color: red'>./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口</span><br>建立成功后,访问公网服务器的设定端口,则相当于访问内网目标地址的目标端口"]
     arr["udp"] = ["type", "port", "target", "compress", "udp隧道模式,提供一条udp隧道,适用于dns、内网dns访问等,添加后会自动生成一个客户端验证key<br>在内网机器执行<span style='color: red'>./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口</span><br>建立成功后,访问公网服务器的设定端口,则相当于访问内网目标地址的udp目标端口"]
     arr["socks5"] = ["type", "port", "compress", "u", "p", "socks5代理模式,内网socks5代理,配合proxifer,可如同使用vpn一样访问内网设备或资源,添加后会自动生成一个客户端验证key<br>在内网机器执行<span style='color: red'>./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口</span><br>建立成功后,在外网环境下本机配置socks5代理,即访问内网设备或者资源 "]
     arr["httpProxy"] = ["type", "port", "compress", "u", "p", " http代理模式,内网http代理,可访问内网网站,添加后会自动生成一个客户端验证key<br>在内网机器执行<span style='color: red'>./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口</span><br>建立成功后,在外网环境下本机配置http代理,即访问内网站点"]
     arr["secret"] = ["type", "target", "compress", "password", "u", "p", " http代理模式,内网http代理,可访问内网网站,添加后会自动生成一个客户端验证key<br>在内网机器执行<span style='color: red'>./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口</span><br>建立成功后,在外网环境下本机配置http代理,即访问内网站点"]
     arr["p2p"] = ["type", "compress", "password", "u", "p", " http代理模式,内网http代理,可访问内网网站,添加后会自动生成一个客户端验证key<br>在内网机器执行<span style='color: red'>./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口</span><br>建立成功后,在外网环境下本机配置http代理,即访问内网站点"]
+    arr["file"] = ["type", "strip_pre", "local_path", "port", " http代理模式,内网http代理,可访问内网网站,添加后会自动生成一个客户端验证key<br>在内网机器执行<span style='color: red'>./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口</span><br>建立成功后,在外网环境下本机配置http代理,即访问内网站点"]
     arrClientHide = ["compress", "u", "p", "crypt", "mux"]
 
     function resetForm() {

+ 30 - 12
web/views/index/edit.html

@@ -13,9 +13,10 @@
                                 <option {{if eq "udp" .t.Mode}}selected{{end}} value="udp">udp</option>
                                 <option {{if eq "socks5" .t.Mode}}selected{{end}} value="socks5">socks5
                                 </option>
-                                <option {{if eq "httpProxy" .t.Mode}}selected{{end}} value="httpProxy">http
-                                <option {{if eq "secret" .t.Mode}}selected{{end}} value="secret">secret
-                                <option {{if eq "p2p" .t.Mode}}selected{{end}} value="p2p">p2p
+                                <option {{if eq "httpProxy" .t.Mode}}selected{{end}} value="httpProxy">http</option>
+                                <option {{if eq "secret" .t.Mode}}selected{{end}} value="secret">secret</option>
+                                <option {{if eq "p2p" .t.Mode}}selected{{end}} value="p2p">p2p</option>
+                                <option {{if eq "file" .t.Mode}}selected{{end}} value="file">file</option>
                             </select>
                         </div>
                     </div>
@@ -31,7 +32,8 @@
                     <div class="form-group" id="port">
                         <label class="col-sm-2 control-label">port of server</label>
                         <div class="col-sm-10">
-                            <input value="{{.t.Port}}" class="form-control" type="text" name="port" placeholder="such as 8024">
+                            <input value="{{.t.Port}}" class="form-control" type="text" name="port"
+                                   placeholder="such as 8024">
                         </div>
                     </div>
                     <div class="form-group" id="target">
@@ -46,11 +48,28 @@
                     <div class="form-group" id="client_id">
                         <label class="col-sm-2 control-label">id of client</label>
                         <div class="col-sm-10">
-                            <input value="{{.t.Client.Id}}" value="{{.client_id}}" class="form-control" type="text" name="client_id"
+                            <input value="{{.t.Client.Id}}" value="{{.client_id}}" class="form-control" type="text"
+                                   name="client_id"
                                    placeholder="id of client">
                         </div>
                     </div>
 
+                    <div class="form-group" id="local_path">
+                        <label class="col-sm-2 control-label">local path</label>
+                        <div class="col-sm-10">
+                            <input value="{{.t.LocalPath}}" class="form-control" type="text" name="local_path"
+                                   placeholder="such as /tmp">
+                        </div>
+                    </div>
+
+                    <div class="form-group" id="strip_pre">
+                        <label class="col-sm-2 control-label">strip pre</label>
+                        <div class="col-sm-10">
+                            <input value="{{.t.StripPre}}" class="form-control" type="text" name="strip_pre"
+                                   placeholder="such as static">
+                        </div>
+                    </div>
+
                     <div class="form-group" id="password">
                         <label class="col-sm-2 control-label">unique identification key</label>
                         <div class="col-sm-10">
@@ -63,8 +82,8 @@
                     <div class="form-group">
                         <div class="col-sm-4 col-sm-offset-2">
                             <button class="btn btn-success" href="#" id="add"><i
-                                class="fa fa-fw fa-lg fa-eye"></i>save
-                        </button>
+                                    class="fa fa-fw fa-lg fa-eye"></i>save
+                            </button>
                         </div>
                     </div>
 
@@ -73,17 +92,16 @@
         </div>
     </div>
 </div>
-
-</main>
 <script>
     var arr = []
-    arr["all"] = ["type", "port", "compress", "u", "p", "target","password"]
+    arr["all"] = ["type", "port", "compress", "u", "p", "target", "password", "local_path", "strip_pre"]
     arr["tcp"] = ["type", "port", "target", "u", "p", "compress"]
     arr["udp"] = ["type", "port", "target", "compress"]
     arr["socks5"] = ["type", "port", "compress", "u", "p"]
     arr["httpProxy"] = ["type", "port", "compress", "u", "p"]
-    arr["secret"] = ["type", "target", "compress", "u", "p","password"]
-    arr["p2p"] = ["type", "compress", "u", "p","password"]
+    arr["secret"] = ["type", "target", "compress", "u", "p", "password"]
+    arr["p2p"] = ["type", "password"]
+    arr["file"] = ["type", "port", "local_path", "strip_pre"]
     arrClientHide = ["compress", "u", "p", "crypt", "mux"]
 
     function resetForm() {

+ 0 - 59
web/views/index/index.html

@@ -1,78 +1,22 @@
-{{/*<div class="row">*/}}
-{{/*<div class="col-md-3">*/}}
-{{/*<div class="widget-small warning coloured-icon"><i class="icon fa fa-html5 fa-3x"></i>*/}}
-{{/*<div class="info">*/}}
-{{/*<h4>客户端连接端口</h4>*/}}
-{{/*<p><b>{{.p}}</b></p>*/}}
-{{/*</div>*/}}
-{{/*</div>*/}}
-{{/*</div>*/}}
-{{/*<div class="col-md-3">*/}}
-{{/*<div class="widget-small danger coloured-icon"><i class="icon fa fa-home fa-3x"></i>*/}}
-{{/*<div class="info">*/}}
-{{/*<h4>当前TCP连接总数</h4>*/}}
-{{/*<p><b>{{.data.tcpCount}}</b></p>*/}}
-{{/*</div>*/}}
-{{/*</div>*/}}
-{{/*</div>*/}}
-{{/*<div class="col-md-3">*/}}
-{{/*<div class="widget-small primary coloured-icon"><i class="icon fa fa-users fa-3x"></i>*/}}
-{{/*<div class="info">*/}}
-{{/*<h4>总客户端数</h4>*/}}
-{{/*<p><b>{{.data.clientCount}}</b></p>*/}}
-{{/*</div>*/}}
-{{/*</div>*/}}
-{{/*</div>*/}}
-{{/*<div class="col-md-3">*/}}
-{{/*<div class="widget-small info coloured-icon"><i class="icon fa fa-user-secret fa-3x"></i>*/}}
-{{/*<div class="info">*/}}
-{{/*<h4>在线客户端数</h4>*/}}
-{{/*<p><b>{{.data.clientOnlineCount}}</b></p>*/}}
-{{/*</div>*/}}
-{{/*</div>*/}}
-{{/*</div>*/}}
-{{/*</div>*/}}
-{{/*<div class="row">*/}}
-{{/*<div class="col-md-6">*/}}
-{{/*<div class="tile">*/}}
-{{/*<h3 class="tile-title">流量</h3>*/}}
-{{/*<div id="flow" style="width: 600px;height:400px;"></div>*/}}
-{{/*</div>*/}}
-{{/*</div>*/}}
-{{/*<div class="col-md-6">*/}}
-{{/*<div class="tile">*/}}
-{{/*<h3 class="tile-title">代理类型</h3>*/}}
-{{/*</div>*/}}
-{{/*</div>*/}}
-{{/*</div>*/}}
-
-
-
 <div class="wrapper wrapper-content">
     <div class="row">
         <div class="col-lg-3">
             <div class="ibox float-e-margins">
                 <div class="ibox-title">
-                {{/*<span class="label label-success pull-right">月</span>*/}}
                     <h5>client connection port</h5>
                 </div>
                 <div class="ibox-content">
                     <h1 class="no-margins">{{.p}}</h1>
-                {{/*<div class="stat-percent font-bold text-success">98% <i class="fa fa-bolt"></i></div>*/}}
-                {{/*<small>总收入</small>*/}}
                 </div>
             </div>
         </div>
         <div class="col-lg-3">
             <div class="ibox float-e-margins">
                 <div class="ibox-title">
-                {{/*<span class="label label-info pull-right">季</span>*/}}
                     <h5>number of clients</h5>
                 </div>
                 <div class="ibox-content">
                     <h1 class="no-margins">{{.data.clientCount}}</h1>
-                {{/*<div class="stat-percent font-bold text-info">20% <i class="fa fa-level-up"></i></div>*/}}
-                {{/*<small>新订单</small>*/}}
                 </div>
             </div>
         </div>
@@ -92,13 +36,10 @@
         <div class="col-lg-3">
             <div class="ibox float-e-margins">
                 <div class="ibox-title">
-                {{/*<span class="label label-danger pull-right">低值</span>*/}}
                     <h5>number of tcp connections</h5>
                 </div>
                 <div class="ibox-content">
                     <h1 class="no-margins">{{.data.tcpCount}}</h1>
-                {{/*<div class="stat-percent font-bold text-danger">38% <i class="fa fa-level-down"></i></div>*/}}
-                {{/*<small>第一个月</small>*/}}
                 </div>
             </div>
         </div>

+ 3 - 0
web/views/public/layout.html

@@ -73,6 +73,9 @@
                 <li class="{{if eq "p2p" .menu}}active{{end}}">
                     <a href="/index/p2p"><i class="fa fa-dashcube"></i> <span class="nav-label">p2p</span></a>
                 </li>
+                <li class="{{if eq "file" .menu}}active{{end}}">
+                    <a href="/index/file"><i class="fa fa-laptop"></i> <span class="nav-label">file</span></a>
+                </li>
             </ul>
 
         </div>