From 824b12a2f80166ef393e12d903dbed1b74b245a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=B2=B3?= Date: Mon, 8 Apr 2019 17:01:08 +0800 Subject: [PATCH] =?UTF-8?q?Modular=20=E3=80=81Functional=20enhancement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bridge/bridge.go | 10 ++- client/client.go | 113 +++++++-------------------- client/control.go | 140 +++++++++++++++++++++++++++++++++- client/local.go | 88 +++++++++------------ cmd/npc/npc.go | 45 +++++++++-- cmd/nps/nps.go | 5 +- conf/clients.json | 4 + conf/hosts.json | 2 + conf/npc.conf | 4 +- conf/nps.conf | 19 ++++- conf/tasks.json | 3 + lib/cache/lru.go | 102 +++++++++++++++++++++++++ lib/common/const.go | 4 + lib/common/util.go | 18 +++++ lib/conn/link.go | 4 +- lib/file/obj.go | 19 ++++- server/proxy/base.go | 6 +- server/proxy/http.go | 95 +++++++++++++++++------ server/proxy/https.go | 9 ++- server/proxy/p2p.go | 54 ++++++++----- server/proxy/socks5.go | 2 +- server/proxy/tcp.go | 4 +- server/proxy/udp.go | 4 +- server/server.go | 8 +- server/test/test.go | 4 +- server/tool/utils.go | 10 +-- web/controllers/base.go | 4 + web/controllers/client.go | 7 +- web/controllers/index.go | 9 ++- web/controllers/login.go | 32 ++++++++ web/views/client/add.html | 9 +++ web/views/client/edit.html | 11 +++ web/views/client/list.html | 1 + web/views/index/add.html | 20 ++++- web/views/index/edit.html | 17 ++++- web/views/index/hadd.html | 11 +++ web/views/index/hedit.html | 11 +++ web/views/index/index.html | 3 +- web/views/index/list.html | 21 +++-- web/views/login/index.html | 3 + web/views/login/register.html | 61 +++++++++++++++ 41 files changed, 754 insertions(+), 242 deletions(-) create mode 100644 lib/cache/lru.go create mode 100644 web/views/login/register.html diff --git a/bridge/bridge.go b/bridge/bridge.go index 1dc67a7..d0d86e9 100755 --- a/bridge/bridge.go +++ b/bridge/bridge.go @@ -152,7 +152,6 @@ func (s *Bridge) GetHealthFromClient(id int, c *conn.Conn) { //验证失败,返回错误验证flag,并且关闭连接 func (s *Bridge) verifyError(c *conn.Conn) { c.Write([]byte(common.VERIFY_EER)) - c.Conn.Close() } func (s *Bridge) verifySuccess(c *conn.Conn) { @@ -291,11 +290,16 @@ func (s *Bridge) register(c *conn.Conn) { } } -func (s *Bridge) SendLinkInfo(clientId int, link *conn.Link, linkAddr string, t *file.Tunnel) (target net.Conn, err error) { +func (s *Bridge) SendLinkInfo(clientId int, link *conn.Link, t *file.Tunnel) (target net.Conn, err error) { + //if the proxy type is local + if link.LocalProxy { + target, err = net.Dial(link.ConnType, link.Host) + return + } if v, ok := s.Client.Load(clientId); ok { //If ip is restricted to do ip verification if s.ipVerify { - ip := common.GetIpByAddr(linkAddr) + ip := common.GetIpByAddr(link.RemoteAddr) if v, ok := s.Register.Load(ip); !ok { return nil, errors.New(fmt.Sprintf("The ip %s is not in the validation list", ip)) } else { diff --git a/client/client.go b/client/client.go index a345d07..1659b5c 100755 --- a/client/client.go +++ b/client/client.go @@ -8,7 +8,6 @@ import ( "github.com/cnlh/nps/vender/github.com/astaxie/beego/logs" "github.com/cnlh/nps/vender/github.com/xtaci/kcp" "net" - "os" "time" ) @@ -43,106 +42,47 @@ retry: time.Sleep(time.Second * 5) goto retry } - logs.Info("Successful connection with server %s", s.svrAddr) + //monitor the connection go s.ping() - s.processor(c) -} - -//处理 -func (s *TRPClient) processor(c *conn.Conn) { s.signal = c - go s.dealChan() + //start a channel connection + go s.newChan() + //start health check if the it's open if s.cnf != nil && len(s.cnf.Healths) > 0 { go heathCheck(s.cnf.Healths, s.signal) } + //msg connection, eg udp + s.handleMain() +} + +//handle main connection +func (s *TRPClient) handleMain() { for { - flags, err := c.ReadFlag() + flags, err := s.signal.ReadFlag() if err != nil { logs.Error("Accept server data error %s, end this service", err.Error()) break } switch flags { - case common.VERIFY_EER: - logs.Error("VKey:%s is incorrect, the server refuses to connect, please check", s.vKey) - os.Exit(0) - case common.RES_CLOSE: - logs.Error("The authentication key is connected by another client or the server closes the client.") - os.Exit(0) - case common.RES_MSG: - logs.Error("Server-side return error") - break case common.NEW_UDP_CONN: - //读取服务端地址、密钥 继续做处理 - if lAddr, err := c.GetShortLenContent(); err != nil { + //read server udp addr and password + if lAddr, err := s.signal.GetShortLenContent(); err != nil { logs.Warn(err) return - } else if pwd, err := c.GetShortLenContent(); err == nil { + } else if pwd, err := s.signal.GetShortLenContent(); err == nil { go s.newUdpConn(string(lAddr), string(pwd)) } - default: - logs.Warn("The error could not be resolved") - break } } - c.Close() s.Close() } func (s *TRPClient) newUdpConn(rAddr string, md5Password string) { - tmpConn, err := common.GetLocalUdpAddr() - if err != nil { - logs.Error(err) - return - } - localAddr, _ := net.ResolveUDPAddr("udp", tmpConn.LocalAddr().String()) - localConn, err := net.ListenUDP("udp", localAddr) - if err != nil { - logs.Error(err) - return - } - localKcpConn, err := kcp.NewConn(rAddr, nil, 150, 3, localConn) - if err != nil { - logs.Error(err) - return - } - conn.SetUdpSession(localKcpConn) - localToolConn := conn.NewConn(localKcpConn) - //写入密钥、provider身份 - if _, err := localToolConn.Write([]byte(md5Password)); err != nil { - logs.Error(err) - return - } - if _, err := localToolConn.Write([]byte(common.WORK_P2P_PROVIDER)); err != nil { - logs.Error(err) - return - } - //接收服务端传的visitor地址 - var b []byte - if b, err = localToolConn.GetShortLenContent(); err != nil { - logs.Error(err) - return - } - //向visitor地址发送测试消息 - visitorAddr, err := net.ResolveUDPAddr("udp", string(b)) - if err != nil { - logs.Error(err) - return - } - //向目标IP发送探测包 - if _, err := localConn.WriteTo([]byte("test"), visitorAddr); err != nil { - logs.Error(err) - return - } - //给服务端发反馈 - if _, err := localToolConn.Write([]byte(common.VERIFY_SUCCESS)); err != nil { - logs.Error(err) - return - } - //关闭与服务端的连接 - localConn.Close() - //关闭与服务端udp conn,建立新的监听 - if localConn, err = net.ListenUDP("udp", localAddr); err != nil { + var localConn net.PacketConn + var err error + var remoteAddress string + if remoteAddress, localConn, err = handleP2PUdp(rAddr, md5Password, common.WORK_P2P_PROVIDER); err != nil { logs.Error(err) return } @@ -151,6 +91,7 @@ func (s *TRPClient) newUdpConn(rAddr string, md5Password string) { logs.Error(err) return } + logs.Trace("start local p2p udp listen, local address", localConn.LocalAddr().String()) //接收新的监听,得到conn, for { udpTunnel, err := l.AcceptKCP() @@ -159,23 +100,24 @@ func (s *TRPClient) newUdpConn(rAddr string, md5Password string) { l.Close() return } - if udpTunnel.RemoteAddr().String() == string(b) { + if udpTunnel.RemoteAddr().String() == string(remoteAddress) { conn.SetUdpSession(udpTunnel) - //读取link,设置msgCh 设置msgConn消息回传响应机制 + logs.Trace("successful connection with client ,address %s", udpTunnel.RemoteAddr().String()) + //read link info from remote l := mux.NewMux(udpTunnel, s.bridgeConnType) for { connMux, err := l.Accept() if err != nil { continue } - go s.srcProcess(connMux) + go s.handleChan(connMux) } } } } //mux tunnel -func (s *TRPClient) dealChan() { +func (s *TRPClient) newChan() { tunnel, err := NewConn(s.bridgeConnType, s.vKey, s.svrAddr, common.WORK_CHAN, s.proxyUrl) if err != nil { logs.Error("connect to ", s.svrAddr, "error:", err) @@ -189,11 +131,11 @@ func (s *TRPClient) dealChan() { s.Close() break } - go s.srcProcess(src) + go s.handleChan(src) } } -func (s *TRPClient) srcProcess(src net.Conn) { +func (s *TRPClient) handleChan(src net.Conn) { lk, err := conn.NewConn(src).GetLinkInfo() if err != nil { src.Close() @@ -218,9 +160,8 @@ loop: for { select { case <-s.ticker.C: - if s.tunnel.IsClose { + if s.tunnel != nil && s.tunnel.IsClose { s.Close() - s.ticker.Stop() break loop } } diff --git a/client/control.go b/client/control.go index 0cbbc3d..84c0a02 100644 --- a/client/control.go +++ b/client/control.go @@ -10,6 +10,7 @@ import ( "github.com/cnlh/nps/lib/crypt" "github.com/cnlh/nps/lib/version" "github.com/cnlh/nps/vender/github.com/astaxie/beego/logs" + "github.com/cnlh/nps/vender/github.com/ccding/go-stun/stun" "github.com/cnlh/nps/vender/github.com/xtaci/kcp" "github.com/cnlh/nps/vender/golang.org/x/net/proxy" "io/ioutil" @@ -162,7 +163,7 @@ re: //create local server secret or p2p for _, v := range cnf.LocalServer { - go StartLocalServer(v, cnf.CommonConfig) + go startLocalServer(v, cnf.CommonConfig) } c.Close() @@ -238,6 +239,7 @@ func NewConn(tp string, vkey string, server string, connType string, proxyUrl st return c, nil } +//http proxy connection func NewHttpProxyConn(url *url.URL, remoteAddr string) (net.Conn, error) { req := &http.Request{ Method: "CONNECT", @@ -266,7 +268,143 @@ func NewHttpProxyConn(url *url.URL, remoteAddr string) (net.Conn, error) { return proxyConn, nil } +//get a basic auth string func basicAuth(username, password string) string { auth := username + ":" + password return base64.StdEncoding.EncodeToString([]byte(auth)) } + +func handleP2PUdp(rAddr, md5Password, role string) (remoteAddress string, c net.PacketConn, err error) { + tmpConn, err := common.GetLocalUdpAddr() + if err != nil { + logs.Error(err) + return + } + localConn, err := newUdpConnByAddr(tmpConn.LocalAddr().String()) + if err != nil { + logs.Error(err) + return + } + localKcpConn, err := kcp.NewConn(rAddr, nil, 150, 3, localConn) + if err != nil { + logs.Error(err) + return + } + conn.SetUdpSession(localKcpConn) + localToolConn := conn.NewConn(localKcpConn) + //get local nat type + //localNatType, host, err := stun.NewClient().Discover() + //if err != nil || host == nil { + // err = errors.New("get nat type error") + // return + //} + localNatType := stun.NATRestricted + //write password + if _, err = localToolConn.Write([]byte(md5Password)); err != nil { + return + } + //write role + if _, err = localToolConn.Write([]byte(role)); err != nil { + return + } + if err = binary.Write(localToolConn, binary.LittleEndian, int32(localNatType)); err != nil { + return + } + //get another type address and nat type from server + var remoteAddr []byte + var remoteNatType int32 + if remoteAddr, err = localToolConn.GetShortLenContent(); err != nil { + return + } + if err = binary.Read(localToolConn, binary.LittleEndian, &remoteNatType); err != nil { + return + } + localConn.Close() + //logs.Trace("remote nat type %d,local nat type %s", remoteNatType, localNatType) + if remoteAddress, err = sendP2PTestMsg(string(remoteAddr), tmpConn.LocalAddr().String()); err != nil { + return + } + c, err = newUdpConnByAddr(tmpConn.LocalAddr().String()) + return +} + +func handleP2P(natType1, natType2 int, addr1, addr2 string, role string) (string, error) { + switch natType1 { + case int(stun.NATFull): + return sendP2PTestMsg(addr2, addr1) + case int(stun.NATRestricted): + switch natType2 { + case int(stun.NATFull), int(stun.NATRestricted), int(stun.NATPortRestricted), int(stun.NATSymetric): + return sendP2PTestMsg(addr2, addr1) + } + case int(stun.NATPortRestricted): + switch natType2 { + case int(stun.NATFull), int(stun.NATRestricted), int(stun.NATPortRestricted): + return sendP2PTestMsg(addr2, addr1) + } + case int(stun.NATSymetric): + switch natType2 { + case int(stun.NATFull), int(stun.NATRestricted): + return sendP2PTestMsg(addr2, addr1) + } + } + return "", errors.New("not support p2p") +} + +func sendP2PTestMsg(remoteAddr string, localAddr string) (string, error) { + remoteUdpAddr, err := net.ResolveUDPAddr("udp", remoteAddr) + if err != nil { + return "", err + } + localConn, err := newUdpConnByAddr(localAddr) + defer localConn.Close() + if err != nil { + return "", err + } + buf := make([]byte, 10) + for i := 20; i > 0; i-- { + logs.Trace("try send test packet to target %s", remoteAddr) + if _, err := localConn.WriteTo([]byte(common.WORK_P2P_CONNECT), remoteUdpAddr); err != nil { + return "", err + } + localConn.SetReadDeadline(time.Now().Add(time.Millisecond * 500)) + n, addr, err := localConn.ReadFromUDP(buf) + localConn.SetReadDeadline(time.Time{}) + switch string(buf[:n]) { + case common.WORK_P2P_SUCCESS: + for i := 20; i > 0; i-- { + if _, err = localConn.WriteTo([]byte(common.WORK_P2P_END), addr); err != nil { + return "", err + } + } + return addr.String(), nil + case common.WORK_P2P_END: + logs.Trace("Remotely Address %s Reply Packet Successfully Received", addr.String()) + return addr.String(), nil + case common.WORK_P2P_CONNECT: + go func() { + for i := 20; i > 0; i-- { + logs.Trace("try send receive success packet to target %s", remoteAddr) + if _, err = localConn.WriteTo([]byte(common.WORK_P2P_SUCCESS), addr); err != nil { + return + } + time.Sleep(time.Second) + } + }() + } + } + localConn.Close() + return "", errors.New("connect to the target failed, maybe the nat type is not support p2p") +} + +func newUdpConnByAddr(addr string) (*net.UDPConn, error) { + udpAddr, err := net.ResolveUDPAddr("udp", addr) + if err != nil { + return nil, err + } + udpConn, err := net.ListenUDP("udp", udpAddr) + if err != nil { + return nil, err + } + return udpConn, nil +} diff --git a/client/local.go b/client/local.go index 1160b2b..89dc3ae 100644 --- a/client/local.go +++ b/client/local.go @@ -11,12 +11,16 @@ import ( "github.com/cnlh/nps/vender/github.com/xtaci/kcp" "net" "net/http" + "sync" ) -var LocalServer []*net.TCPListener -var udpConn net.Conn -var muxSession *mux.Mux -var fileServer []*http.Server +var ( + LocalServer []*net.TCPListener + udpConn net.Conn + muxSession *mux.Mux + fileServer []*http.Server + lock sync.Mutex +) func CloseLocalServer() { for _, v := range LocalServer { @@ -39,10 +43,10 @@ func startLocalFileServer(config *config.CommonConfig, t *file.Tunnel, vkey stri 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, common.CONN_TCP) - logs.Warn(srv.Serve(listener)) + logs.Error(srv.Serve(listener)) } -func StartLocalServer(l *config.LocalServer, config *config.CommonConfig) error { +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()) @@ -52,15 +56,15 @@ func StartLocalServer(l *config.LocalServer, config *config.CommonConfig) error logs.Info("successful start-up of local monitoring, port", l.Port) conn.Accept(listener, func(c net.Conn) { if l.Type == "secret" { - processSecret(c, config, l) + handleSecret(c, config, l) } else { - processP2P(c, config, l) + handleP2PVisitor(c, config, l) } }) return nil } -func processSecret(localTcpConn net.Conn, config *config.CommonConfig, l *config.LocalServer) { +func handleSecret(localTcpConn net.Conn, config *config.CommonConfig, l *config.LocalServer) { 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()) @@ -73,21 +77,28 @@ func processSecret(localTcpConn net.Conn, config *config.CommonConfig, l *config conn.CopyWaitGroup(remoteConn.Conn, localTcpConn, false, false, nil, nil, false, nil) } -func processP2P(localTcpConn net.Conn, config *config.CommonConfig, l *config.LocalServer) { +func handleP2PVisitor(localTcpConn net.Conn, config *config.CommonConfig, l *config.LocalServer) { +restart: + lock.Lock() if udpConn == nil { newUdpConn(config, l) if udpConn == nil { + lock.Unlock() return } muxSession = mux.NewMux(udpConn, "kcp") } + lock.Unlock() + logs.Trace("start trying to connect with the server") nowConn, err := muxSession.NewConn() if err != nil { - logs.Error(err) + udpConn = nil + logs.Error(err, "reconnect......") + goto restart return } //TODO just support compress now because there is not tls file in client packages - link := conn.NewLink(common.CONN_TCP, l.Target, false, config.Client.Cnf.Compress, localTcpConn.LocalAddr().String()) + link := conn.NewLink(common.CONN_TCP, l.Target, false, config.Client.Cnf.Compress, localTcpConn.LocalAddr().String(), false) if _, err := conn.NewConn(nowConn).SendInfo(link, ""); err != nil { logs.Error(err) return @@ -111,49 +122,18 @@ func newUdpConn(config *config.CommonConfig, l *config.LocalServer) { logs.Error(err) return } - //与服务端udp建立连接 - tmpConn, err := common.GetLocalUdpAddr() - if err != nil { + var localConn net.PacketConn + var remoteAddress string + if remoteAddress, localConn, err = handleP2PUdp(string(rAddr), crypt.Md5(l.Password), common.WORK_P2P_VISITOR); err != nil { + logs.Error(err) + return + } + udpTunnel, err := kcp.NewConn(remoteAddress, nil, 150, 3, localConn) + if err != nil || udpTunnel == nil { logs.Warn(err) return } - //与服务端建立udp连接 - localAddr, _ := net.ResolveUDPAddr("udp", tmpConn.LocalAddr().String()) - localConn, err := net.ListenUDP("udp", localAddr) - if err != nil { - logs.Error(err) - return - } - localKcpConn, err := kcp.NewConn(string(rAddr), nil, 150, 3, localConn) - if err != nil { - logs.Error(err) - return - } - conn.SetUdpSession(localKcpConn) - //写入密钥、provider身份 - if _, err := localKcpConn.Write([]byte(crypt.Md5(l.Password))); err != nil { - logs.Error(err) - return - } - if _, err := localKcpConn.Write([]byte(common.WORK_P2P_VISITOR)); err != nil { - logs.Error(err) - return - } - //接收服务端传的visitor地址 - if b, err := conn.NewConn(localKcpConn).GetShortLenContent(); err != nil { - logs.Error(err) - return - } else { - //关闭与服务端连接 - localConn.Close() - //建立新的连接 - localConn, err = net.ListenUDP("udp", localAddr) - udpTunnel, err := kcp.NewConn(string(b), nil, 150, 3, localConn) - if err != nil || udpTunnel == nil { - logs.Warn(err) - return - } - conn.SetUdpSession(udpTunnel) - udpConn = udpTunnel - } + logs.Trace("successful create a connection with server", remoteAddress) + conn.SetUdpSession(udpTunnel) + udpConn = udpTunnel } diff --git a/cmd/npc/npc.go b/cmd/npc/npc.go index 4cf66df..ffadb9d 100644 --- a/cmd/npc/npc.go +++ b/cmd/npc/npc.go @@ -2,11 +2,15 @@ package main import ( "flag" + "fmt" "github.com/cnlh/nps/client" "github.com/cnlh/nps/lib/common" + "github.com/cnlh/nps/lib/config" "github.com/cnlh/nps/lib/daemon" + "github.com/cnlh/nps/lib/file" "github.com/cnlh/nps/lib/version" "github.com/cnlh/nps/vender/github.com/astaxie/beego/logs" + "github.com/cnlh/nps/vender/github.com/ccding/go-stun/stun" "os" "strings" "time" @@ -21,18 +25,33 @@ var ( proxyUrl = flag.String("proxy", "", "proxy socks5 url(eg:socks5://111:222@127.0.0.1:9007)") logLevel = flag.String("log_level", "7", "log level 0~7") registerTime = flag.Int("time", 2, "register time long /h") + localPort = flag.Int("local_port", 2000, "p2p local port") + password = flag.String("password", "", "p2p password flag") + target = flag.String("target", "", "p2p target") + localType = flag.String("local_type", "p2p", "p2p target") + logPath = flag.String("log_path", "npc.log", "npc log path") ) func main() { flag.Parse() - if len(os.Args) > 2 { + if len(os.Args) >= 2 { switch os.Args[1] { case "status": - path := strings.Replace(os.Args[2], "-config=", "", -1) - client.GetTaskStatus(path) + if len(os.Args) > 2 { + path := strings.Replace(os.Args[2], "-config=", "", -1) + client.GetTaskStatus(path) + } case "register": flag.CommandLine.Parse(os.Args[2:]) client.RegisterLocalIp(*serverAddr, *verifyKey, *connType, *proxyUrl, *registerTime) + case "nat": + nat, host, err := stun.NewClient().Discover() + if err != nil || host == nil { + logs.Error("get nat type error", err) + return + } + fmt.Printf("nat type: %s \npublic address: %s\n", nat.String(), host.String()) + os.Exit(0) } } daemon.InitDaemon("npc", common.GetRunPath(), common.GetTmpPath()) @@ -41,7 +60,23 @@ func main() { if *logType == "stdout" { logs.SetLogger(logs.AdapterConsole, `{"level":`+*logLevel+`,"color":true}`) } else { - logs.SetLogger(logs.AdapterFile, `{"level":`+*logLevel+`,"filename":"npc_log.log","daily":false,"color":true}`) + logs.SetLogger(logs.AdapterFile, `{"level":`+*logLevel+`,"filename":"`+*logPath+`","daily":false,"color":true}`) + } + //p2p or secret command + if *password != "" { + commonConfig := new(config.CommonConfig) + commonConfig.Server = *serverAddr + commonConfig.VKey = *verifyKey + commonConfig.Tp = *connType + localServer := new(config.LocalServer) + localServer.Type = *localType + localServer.Password = *password + localServer.Target = *target + localServer.Port = *localPort + commonConfig.Client = new(file.Client) + commonConfig.Client.Cnf = new(file.Config) + client.StartLocalServer(localServer, commonConfig) + return } env := common.GetEnvMap() if *serverAddr == "" { @@ -50,7 +85,7 @@ func main() { if *verifyKey == "" { *verifyKey, _ = env["NPC_SERVER_VKEY"] } - logs.Info("the version of client is %s", version.VERSION) + logs.Info("the version of client is %s, the core version of client is %s", version.VERSION, version.GetVersion()) if *verifyKey != "" && *serverAddr != "" && *configPath == "" { for { client.NewRPClient(*serverAddr, *verifyKey, *connType, *proxyUrl, nil).Start() diff --git a/cmd/nps/nps.go b/cmd/nps/nps.go index 8590125..e9b4d37 100644 --- a/cmd/nps/nps.go +++ b/cmd/nps/nps.go @@ -50,7 +50,7 @@ func main() { if *logType == "stdout" { logs.SetLogger(logs.AdapterConsole, `{"level":`+level+`,"color":true}`) } else { - logs.SetLogger(logs.AdapterFile, `{"level":`+level+`,"filename":"nps_log.log","daily":false,"color":true}`) + logs.SetLogger(logs.AdapterFile, `{"level":`+level+`,"filename":"`+beego.AppConfig.String("log_path")+`","daily":false,"color":true}`) } task := &file.Tunnel{ Mode: "webServer", @@ -62,7 +62,8 @@ func main() { } logs.Info("the version of server is %s ,allow client version to be %s", version.VERSION, version.GetVersion()) connection.InitConnectionService() - crypt.InitTls(filepath.Join(beego.AppPath, "conf", "server.pem"), filepath.Join(beego.AppPath, "conf", "server.key")) + crypt.InitTls(filepath.Join(common.GetRunPath(), "conf", "server.pem"), filepath.Join(common.GetRunPath(), "conf", "server.key")) tool.InitAllowPort() + tool.StartSystemInfo() server.StartNewServer(bridgePort, task, beego.AppConfig.String("bridge_type")) } diff --git a/conf/clients.json b/conf/clients.json index e69de29..f053c14 100644 --- a/conf/clients.json +++ b/conf/clients.json @@ -0,0 +1,4 @@ +{"Cnf":{"U":"","P":"","Compress":false,"Crypt":false},"Id":4,"VerifyKey":"6h7x7tjvkocgltep","Addr":"","Remark":"","Status":true,"IsConnect":false,"RateLimit":0,"Flow":{"ExportFlow":0,"InletFlow":0,"FlowLimit":0},"Rate":{"NowRate":0},"NoStore":false,"NoDisplay":false,"MaxConn":0,"NowConn":0,"WebUserName":"admin22","WebPassword":"123","ConfigConnAllow":false,"MaxTunnelNum":0} +*#*{"Cnf":{"U":"","P":"","Compress":false,"Crypt":false},"Id":6,"VerifyKey":"xf3nwghskyw4e7g4","Addr":"","Remark":"","Status":true,"IsConnect":false,"RateLimit":0,"Flow":{"ExportFlow":0,"InletFlow":0,"FlowLimit":0},"Rate":{"NowRate":0},"NoStore":false,"NoDisplay":false,"MaxConn":0,"NowConn":0,"WebUserName":"admin3","WebPassword":"123","ConfigConnAllow":false,"MaxTunnelNum":0} +*#*{"Cnf":{"U":"","P":"","Compress":false,"Crypt":false},"Id":2,"VerifyKey":"06j3twjj9vjy2kdg","Addr":"","Remark":"","Status":true,"IsConnect":false,"RateLimit":0,"Flow":{"ExportFlow":2298489,"InletFlow":92324,"FlowLimit":0},"Rate":{"NowRate":0},"NoStore":false,"NoDisplay":false,"MaxConn":0,"NowConn":0,"WebUserName":"admin55","WebPassword":"123","ConfigConnAllow":false,"MaxTunnelNum":0} +*#* \ No newline at end of file diff --git a/conf/hosts.json b/conf/hosts.json index e69de29..fb2faba 100644 --- a/conf/hosts.json +++ b/conf/hosts.json @@ -0,0 +1,2 @@ +{"Id":1,"Host":"a.o.com","HeaderChange":"","HostChange":"","Location":"/","Remark":"","Scheme":"all","CertFilePath":"","KeyFilePath":"","NoStore":false,"IsClose":false,"Flow":{"ExportFlow":2298489,"InletFlow":92303,"FlowLimit":0},"Client":{"Cnf":{"U":"","P":"","Compress":false,"Crypt":false},"Id":2,"VerifyKey":"06j3twjj9vjy2kdg","Addr":"127.0.0.1","Remark":"","Status":true,"IsConnect":true,"RateLimit":0,"Flow":{"ExportFlow":2298489,"InletFlow":92324,"FlowLimit":0},"Rate":{"NowRate":0},"NoStore":false,"NoDisplay":false,"MaxConn":0,"NowConn":4,"WebUserName":"admin2","WebPassword":"123","ConfigConnAllow":false,"MaxTunnelNum":0},"Target":{"TargetStr":"127.0.0.1:8082","TargetArr":null,"LocalProxy":false}} +*#* \ No newline at end of file diff --git a/conf/npc.conf b/conf/npc.conf index 281b540..ec6c28e 100644 --- a/conf/npc.conf +++ b/conf/npc.conf @@ -1,5 +1,5 @@ [common] -server_addr=127.0.0.1:8024 +server_addr=123.206.77.88:8024 conn_type=tcp vkey=123 auto_reconnection=true @@ -44,7 +44,7 @@ server_port=19009 [file] mode=file server_port=19008 -local_path=./ +local_path=/Users/liuhe/Downloads strip_pre=/web/ [http] diff --git a/conf/nps.conf b/conf/nps.conf index 69e96ad..ae9c920 100755 --- a/conf/nps.conf +++ b/conf/nps.conf @@ -1,6 +1,6 @@ appname = nps #Boot mode(dev|pro) -runmode = pro +runmode = dev #HTTP(S) proxy port, no startup if empty http_proxy_ip=0.0.0.0 @@ -26,13 +26,14 @@ public_vkey=123 # log level LevelEmergency->0 LevelAlert->1 LevelCritical->2 LevelError->3 LevelWarning->4 LevelNotice->5 LevelInformational->6 LevelDebug->7 log_level=7 +log_path=nps.log #Whether to restrict IP access, true or false or ignore #ip_limit=true #p2p -#p2p_ip=127.0.0.1 -#p2p_port=6000 +p2p_ip=127.0.0.1 +p2p_port=6000 #web web_host=a.o.com @@ -48,12 +49,22 @@ auth_crypt_key =1234567812345678 #allow_ports=9001-9009,10001,11000-12000 #Web management multi-user login -#allow_user_login=true +allow_user_login=false +allow_user_register=false +allow_user_change_username=false + #extension allow_flow_limit=false allow_rate_limit=false +allow_tunnel_num_limit=false +allow_local_proxy=false allow_connection_num_limit=false allow_multi_ip=false +system_info_display=false + +#cache +http_cache=true +http_cache_length=100 diff --git a/conf/tasks.json b/conf/tasks.json index e69de29..1274ac8 100644 --- a/conf/tasks.json +++ b/conf/tasks.json @@ -0,0 +1,3 @@ +{"Id":1,"Port":0,"ServerIp":"","Mode":"p2p","Status":true,"RunStatus":true,"Client":{"Cnf":{"U":"","P":"","Compress":false,"Crypt":false},"Id":2,"VerifyKey":"06j3twjj9vjy2kdg","Addr":"127.0.0.1","Remark":"","Status":true,"IsConnect":true,"RateLimit":0,"Flow":{"ExportFlow":0,"InletFlow":0,"FlowLimit":0},"Rate":{"NowRate":0},"NoStore":false,"NoDisplay":false,"MaxConn":0,"NowConn":1,"WebUserName":"admin2","WebPassword":"123","ConfigConnAllow":false,"MaxTunnelNum":0},"Ports":"","Flow":{"ExportFlow":0,"InletFlow":0,"FlowLimit":0},"Password":"p2ptest","Remark":"","TargetAddr":"","NoStore":false,"LocalPath":"","StripPre":"","Target":{"TargetStr":"","TargetArr":null,"LocalProxy":false},"HealthCheckTimeout":0,"HealthMaxFail":0,"HealthCheckInterval":0,"HealthNextTime":"0001-01-01T00:00:00Z","HealthMap":null,"HttpHealthUrl":"","HealthRemoveArr":null,"HealthCheckType":"","HealthCheckTarget":""} +*#*{"Id":2,"Port":0,"ServerIp":"","Mode":"secret","Status":true,"RunStatus":true,"Client":{"Cnf":{"U":"","P":"","Compress":false,"Crypt":false},"Id":2,"VerifyKey":"06j3twjj9vjy2kdg","Addr":"127.0.0.1","Remark":"","Status":true,"IsConnect":true,"RateLimit":0,"Flow":{"ExportFlow":0,"InletFlow":0,"FlowLimit":0},"Rate":{"NowRate":0},"NoStore":false,"NoDisplay":false,"MaxConn":0,"NowConn":1,"WebUserName":"admin2","WebPassword":"123","ConfigConnAllow":false,"MaxTunnelNum":0},"Ports":"","Flow":{"ExportFlow":0,"InletFlow":21,"FlowLimit":0},"Password":"secrettest","Remark":"","TargetAddr":"","NoStore":false,"LocalPath":"","StripPre":"","Target":{"TargetStr":"118.89.159.126:22","TargetArr":null,"LocalProxy":false},"HealthCheckTimeout":0,"HealthMaxFail":0,"HealthCheckInterval":0,"HealthNextTime":"0001-01-01T00:00:00Z","HealthMap":null,"HttpHealthUrl":"","HealthRemoveArr":null,"HealthCheckType":"","HealthCheckTarget":""} +*#* \ No newline at end of file diff --git a/lib/cache/lru.go b/lib/cache/lru.go new file mode 100644 index 0000000..41db229 --- /dev/null +++ b/lib/cache/lru.go @@ -0,0 +1,102 @@ +package cache + +import ( + "container/list" + "sync" +) + +// Cache is an LRU cache. It is safe for concurrent access. +type Cache struct { + // MaxEntries is the maximum number of cache entries before + // an item is evicted. Zero means no limit. + MaxEntries int + + //Execute this callback function when an element is culled + OnEvicted func(key Key, value interface{}) + + ll *list.List //list + cache sync.Map +} + +// A Key may be any value that is comparable. See http://golang.org/ref/spec#Comparison_operators +type Key interface{} + +type entry struct { + key Key + value interface{} +} + +// New creates a new Cache. +// If maxEntries is 0, the cache has no length limit. +// that eviction is done by the caller. +func New(maxEntries int) *Cache { + return &Cache{ + MaxEntries: maxEntries, + ll: list.New(), + //cache: make(map[interface{}]*list.Element), + } +} + +// If the key value already exists, move the key to the front +func (c *Cache) Add(key Key, value interface{}) { + if ee, ok := c.cache.Load(key); ok { + c.ll.MoveToFront(ee.(*list.Element)) // move to the front + ee.(*list.Element).Value.(*entry).value = value + return + } + ele := c.ll.PushFront(&entry{key, value}) + c.cache.Store(key, ele) + if c.MaxEntries != 0 && c.ll.Len() > c.MaxEntries { // Remove the oldest element if the limit is exceeded + c.RemoveOldest() + } +} + +// Get looks up a key's value from the cache. +func (c *Cache) Get(key Key) (value interface{}, ok bool) { + if ele, hit := c.cache.Load(key); hit { + c.ll.MoveToFront(ele.(*list.Element)) + return ele.(*list.Element).Value.(*entry).value, true + } + return +} + +// Remove removes the provided key from the cache. +func (c *Cache) Remove(key Key) { + if ele, hit := c.cache.Load(key); hit { + c.removeElement(ele.(*list.Element)) + } +} + +// RemoveOldest removes the oldest item from the cache. +func (c *Cache) RemoveOldest() { + ele := c.ll.Back() + if ele != nil { + c.removeElement(ele) + } +} + +func (c *Cache) removeElement(e *list.Element) { + c.ll.Remove(e) + kv := e.Value.(*entry) + c.cache.Delete(kv.key) + if c.OnEvicted != nil { + c.OnEvicted(kv.key, kv.value) + } +} + +// Len returns the number of items in the cache. +func (c *Cache) Len() int { + return c.ll.Len() +} + +// Clear purges all stored items from the cache. +func (c *Cache) Clear() { + if c.OnEvicted != nil { + c.cache.Range(func(key, value interface{}) bool { + kv := value.(*list.Element).Value.(*entry) + c.OnEvicted(kv.key, kv.value) + return true + }) + } + c.ll = nil +} diff --git a/lib/common/const.go b/lib/common/const.go index 0014125..ffb2fa6 100644 --- a/lib/common/const.go +++ b/lib/common/const.go @@ -13,6 +13,10 @@ const ( WORK_P2P = "p2pm" WORK_P2P_VISITOR = "p2pv" WORK_P2P_PROVIDER = "p2pp" + WORK_P2P_CONNECT = "p2pc" + WORK_P2P_SUCCESS = "p2ps" + WORK_P2P_END = "p2pe" + WORK_P2P_LAST = "p2pl" WORK_STATUS = "stus" RES_MSG = "msg0" RES_CLOSE = "clse" diff --git a/lib/common/util.go b/lib/common/util.go index 14365d6..c0e101c 100755 --- a/lib/common/util.go +++ b/lib/common/util.go @@ -218,6 +218,7 @@ func GetPorts(p string) []int { return ps } +//is the string a port func IsPort(p string) bool { pi, err := strconv.Atoi(p) if err != nil { @@ -229,6 +230,7 @@ func IsPort(p string) bool { return true } +//if the s is just a port,return 127.0.0.1:s func FormatAddress(s string) string { if strings.Contains(s, ":") { return s @@ -236,6 +238,7 @@ func FormatAddress(s string) string { return "127.0.0.1:" + s } +//get address from the complete address func GetIpByAddr(addr string) string { arr := strings.Split(addr, ":") return arr[0] @@ -279,6 +282,7 @@ func GetLocalUdpAddr() (net.Conn, error) { return tmpConn, tmpConn.Close() } +//parse template func ParseStr(str string) (string, error) { tmp := template.New("npc") var err error @@ -305,6 +309,7 @@ func GetEnvMap() map[string]string { return m } +//throw the empty element of the string array func TrimArr(arr []string) []string { newArr := make([]string, 0) for _, v := range arr { @@ -315,6 +320,7 @@ func TrimArr(arr []string) []string { return newArr } +// func IsArrContains(arr []string, val string) bool { if arr == nil { return false @@ -327,6 +333,7 @@ func IsArrContains(arr []string, val string) bool { return false } +//remove value from string array func RemoveArrVal(arr []string, val string) []string { for k, v := range arr { if v == val { @@ -337,6 +344,7 @@ func RemoveArrVal(arr []string, val string) []string { return arr } +//convert bytes to num func BytesToNum(b []byte) int { var str string for i := 0; i < len(b); i++ { @@ -346,6 +354,7 @@ func BytesToNum(b []byte) int { return int(x) } +//get the length of the sync map func GeSynctMapLen(m sync.Map) int { var c int m.Range(func(key, value interface{}) bool { @@ -354,3 +363,12 @@ func GeSynctMapLen(m sync.Map) int { }) return c } + +func GetExtFromPath(path string) string { + s := strings.Split(path, ".") + re, err := regexp.Compile(`(\w+)`) + if err != nil { + return "" + } + return string(re.Find([]byte(s[0]))) +} diff --git a/lib/conn/link.go b/lib/conn/link.go index f7a50fb..974827d 100644 --- a/lib/conn/link.go +++ b/lib/conn/link.go @@ -17,15 +17,17 @@ type Link struct { Host string //目标 Crypt bool //加密 Compress bool + LocalProxy bool RemoteAddr string } -func NewLink(connType string, host string, crypt bool, compress bool, remoteAddr string) *Link { +func NewLink(connType string, host string, crypt bool, compress bool, remoteAddr string, localProxy bool) *Link { return &Link{ RemoteAddr: remoteAddr, ConnType: connType, Host: host, Crypt: crypt, Compress: compress, + LocalProxy: localProxy, } } diff --git a/lib/file/obj.go b/lib/file/obj.go index 2895302..3e7acf0 100644 --- a/lib/file/obj.go +++ b/lib/file/obj.go @@ -48,6 +48,7 @@ type Client struct { WebUserName string //the username of web login WebPassword string //the password of web login ConfigConnAllow bool //is allow connected by config file + MaxTunnelNum int sync.RWMutex } @@ -97,6 +98,17 @@ func (s *Client) HasTunnel(t *Tunnel) (exist bool) { return } +func (s *Client) GetTunnelNum() (num int) { + GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool { + v := value.(*Tunnel) + if v.Client.Id == s.Id { + num++ + } + return true + }) + return +} + func (s *Client) HasHost(h *Host) bool { var has bool GetDb().JsonDb.Hosts.Range(func(key, value interface{}) bool { @@ -164,9 +176,10 @@ type Host struct { } type Target struct { - nowIndex int - TargetStr string - TargetArr []string + nowIndex int + TargetStr string + TargetArr []string + LocalProxy bool sync.RWMutex } diff --git a/server/proxy/base.go b/server/proxy/base.go index 73b13cd..a0d648b 100644 --- a/server/proxy/base.go +++ b/server/proxy/base.go @@ -75,9 +75,9 @@ func (s *BaseServer) CheckFlowAndConnNum(client *file.Client) error { } //与客户端建立通道 -func (s *BaseServer) DealClient(c *conn.Conn, client *file.Client, addr string, rb []byte, tp string, f func(), flow *file.Flow) error { - link := conn.NewLink(tp, addr, client.Cnf.Crypt, client.Cnf.Compress, c.Conn.RemoteAddr().String()) - if target, err := s.bridge.SendLinkInfo(client.Id, link, c.Conn.RemoteAddr().String(), s.task); err != nil { +func (s *BaseServer) DealClient(c *conn.Conn, client *file.Client, addr string, rb []byte, tp string, f func(), flow *file.Flow, localProxy bool) error { + link := conn.NewLink(tp, addr, client.Cnf.Crypt, client.Cnf.Compress, c.Conn.RemoteAddr().String(), localProxy) + if target, err := s.bridge.SendLinkInfo(client.Id, link, s.task); err != nil { logs.Warn("get connection from client id %d error %s", client.Id, err.Error()) c.Close() return err diff --git a/server/proxy/http.go b/server/proxy/http.go index 95834b4..83a8b7e 100644 --- a/server/proxy/http.go +++ b/server/proxy/http.go @@ -4,11 +4,11 @@ import ( "bufio" "crypto/tls" "github.com/cnlh/nps/bridge" + "github.com/cnlh/nps/lib/cache" "github.com/cnlh/nps/lib/common" "github.com/cnlh/nps/lib/conn" "github.com/cnlh/nps/lib/file" "github.com/cnlh/nps/server/connection" - "github.com/cnlh/nps/vender/github.com/astaxie/beego" "github.com/cnlh/nps/vender/github.com/astaxie/beego/logs" "io" "net" @@ -17,6 +17,7 @@ import ( "os" "path/filepath" "strconv" + "strings" "sync" ) @@ -27,12 +28,13 @@ type httpServer struct { httpServer *http.Server httpsServer *http.Server httpsListener net.Listener + useCache bool + cache *cache.Cache + cacheLen int } -func NewHttp(bridge *bridge.Bridge, c *file.Tunnel) *httpServer { - httpPort, _ := beego.AppConfig.Int("http_proxy_port") - httpsPort, _ := beego.AppConfig.Int("https_proxy_port") - return &httpServer{ +func NewHttp(bridge *bridge.Bridge, c *file.Tunnel, httpPort, httpsPort int, useCache bool, cacheLen int) *httpServer { + httpServer := &httpServer{ BaseServer: BaseServer{ task: c, bridge: bridge, @@ -40,7 +42,13 @@ func NewHttp(bridge *bridge.Bridge, c *file.Tunnel) *httpServer { }, httpPort: httpPort, httpsPort: httpsPort, + useCache: useCache, + cacheLen: cacheLen, } + if useCache { + httpServer.cache = cache.New(cacheLen) + } + return httpServer } func (s *httpServer) Start() error { @@ -71,7 +79,7 @@ func (s *httpServer) Start() error { logs.Error(err) os.Exit(0) } - logs.Error(NewHttpsServer(s.httpsListener, s.bridge).Start()) + logs.Error(NewHttpsServer(s.httpsListener, s.bridge, s.useCache, s.cacheLen).Start()) }() } return nil @@ -100,12 +108,12 @@ func (s *httpServer) handleTunneling(w http.ResponseWriter, r *http.Request) { if err != nil { http.Error(w, err.Error(), http.StatusServiceUnavailable) } - s.process(conn.NewConn(c), r) + s.httpHandle(conn.NewConn(c), r) } -func (s *httpServer) process(c *conn.Conn, r *http.Request) { +func (s *httpServer) httpHandle(c *conn.Conn, r *http.Request) { var ( - isConn = true + isConn = false host *file.Host target net.Conn lastHost *file.Host @@ -114,7 +122,7 @@ func (s *httpServer) process(c *conn.Conn, r *http.Request) { scheme = r.URL.Scheme lk *conn.Link targetAddr string - wg sync.WaitGroup + readReq bool ) if host, err = file.GetDb().GetInfoByHost(r.Host, r); err != nil { logs.Notice("the url %s %s %s can't be parsed!", r.URL.Scheme, r.Host, r.RequestURI) @@ -126,7 +134,6 @@ func (s *httpServer) process(c *conn.Conn, r *http.Request) { return } defer host.Client.AddConn() - logs.Trace("new %s connection,clientId %d,host %s,url %s,remote address %s", r.URL.Scheme, host.Client.Id, r.Host, r.URL, r.RemoteAddr) lastHost = host for { start: @@ -139,22 +146,43 @@ func (s *httpServer) process(c *conn.Conn, r *http.Request) { logs.Warn(err.Error()) break } - lk = conn.NewLink(common.CONN_TCP, targetAddr, host.Client.Cnf.Crypt, host.Client.Cnf.Compress, r.RemoteAddr) - if target, err = s.bridge.SendLinkInfo(host.Client.Id, lk, c.Conn.RemoteAddr().String(), nil); err != nil { + lk = conn.NewLink(common.CONN_TCP, targetAddr, host.Client.Cnf.Crypt, host.Client.Cnf.Compress, r.RemoteAddr, host.Target.LocalProxy) + if target, err = s.bridge.SendLinkInfo(host.Client.Id, lk, nil); err != nil { logs.Notice("connect to target %s error %s", lk.Host, err) break } connClient = conn.GetConn(target, lk.Crypt, lk.Compress, host.Client.Rate, true) isConn = false go func() { - wg.Add(1) - w, _ := common.CopyBuffer(c, connClient) - host.Flow.Add(0, w) - c.Close() - target.Close() - wg.Done() + defer connClient.Close() + defer c.Close() + if resp, err := http.ReadResponse(bufio.NewReader(connClient), r); err != nil { + return + } else { + //if the cache is start and the response is in the extension,store the response to the cache list + if s.useCache && strings.Contains(r.URL.Path, ".") { + b, err := httputil.DumpResponse(resp, true) + if err != nil { + return + } + c.Write(b) + host.Flow.Add(0, int64(len(b))) + s.cache.Add(filepath.Join(host.Host, r.URL.Path), b) + } else { + b, err := httputil.DumpResponse(resp, false) + if err != nil { + return + } + c.Write(b) + if bodyLen, err := common.CopyBuffer(c, resp.Body); err != nil { + return + } else { + host.Flow.Add(0, int64(len(b))+bodyLen) + } + } + } }() - } else { + } else if readReq { r, err = http.ReadRequest(bufio.NewReader(c)) if err != nil { break @@ -167,7 +195,6 @@ func (s *httpServer) process(c *conn.Conn, r *http.Request) { if r.Method == "OST" { r.Method = "POST" } - logs.Trace("new %s connection,clientId %d,host %s,url %s,remote address %s", r.URL.Scheme, host.Client.Id, r.Host, r.URL, r.RemoteAddr) if hostTmp, err := file.GetDb().GetInfoByHost(r.Host, r); err != nil { logs.Notice("the url %s %s %s can't be parsed!", r.URL.Scheme, r.Host, r.RequestURI) break @@ -178,13 +205,36 @@ func (s *httpServer) process(c *conn.Conn, r *http.Request) { goto start } } + //if the cache start and the request is in the cache list, return the cache + if s.useCache { + if v, ok := s.cache.Get(filepath.Join(host.Host, r.URL.Path)); ok { + n, err := c.Write(v.([]byte)) + if err != nil { + break + } + logs.Trace("%s request, method %s, host %s, url %s, remote address %s, return cache", r.URL.Scheme, r.Method, r.Host, r.URL.Path, c.RemoteAddr().String()) + host.Flow.Add(0, int64(n)) + //if return cache and does not create a new conn with client and Connection is not set or close, close the connection. + if connClient == nil && (strings.ToLower(r.Header.Get("Connection")) == "close" || strings.ToLower(r.Header.Get("Connection")) == "") { + c.Close() + break + } + readReq = true + goto start + } + } + if connClient == nil { + isConn = true + goto start + } + readReq = true //change the host and header and set proxy setting common.ChangeHostAndHeader(r, host.HostChange, host.HeaderChange, c.Conn.RemoteAddr().String()) b, err := httputil.DumpRequest(r, false) if err != nil { break } - logs.Trace("%s request, method %s, host %s, url %s, remote address %s, target %s", r.URL.Scheme, r.Method, r.Host, r.RequestURI, r.RemoteAddr, lk.Host) + logs.Trace("%s request, method %s, host %s, url %s, remote address %s, target %s", r.URL.Scheme, r.Method, r.Host, r.URL.Path, c.RemoteAddr().String(), lk.Host) //write connClient.Write(b) if bodyLen, err := common.CopyBuffer(connClient, r.Body); err != nil { @@ -201,7 +251,6 @@ end: if target != nil { target.Close() } - wg.Wait() } func (s *httpServer) NewServer(port int, scheme string) *http.Server { diff --git a/server/proxy/https.go b/server/proxy/https.go index 6762789..0da1f89 100644 --- a/server/proxy/https.go +++ b/server/proxy/https.go @@ -2,6 +2,7 @@ package proxy import ( "github.com/cnlh/nps/bridge" + "github.com/cnlh/nps/lib/cache" "github.com/cnlh/nps/lib/common" "github.com/cnlh/nps/lib/conn" "github.com/cnlh/nps/lib/crypt" @@ -21,9 +22,13 @@ type HttpsServer struct { httpsListenerMap sync.Map } -func NewHttpsServer(l net.Listener, bridge *bridge.Bridge) *HttpsServer { +func NewHttpsServer(l net.Listener, bridge *bridge.Bridge, useCache bool, cacheLen int) *HttpsServer { https := &HttpsServer{listener: l} https.bridge = bridge + https.useCache = useCache + if useCache { + https.cache = cache.New(cacheLen) + } return https } @@ -116,7 +121,7 @@ func (https *HttpsServer) handleHttps(c net.Conn) { logs.Warn(err.Error()) } logs.Trace("new https connection,clientId %d,host %s,remote address %s", host.Client.Id, r.Host, c.RemoteAddr().String()) - https.DealClient(conn.NewConn(c), host.Client, targetAddr, rb, common.CONN_TCP, nil, host.Flow) + https.DealClient(conn.NewConn(c), host.Client, targetAddr, rb, common.CONN_TCP, nil, host.Flow, host.Target.LocalProxy) } type HttpsListener struct { diff --git a/server/proxy/p2p.go b/server/proxy/p2p.go index f7864c4..2ec9429 100644 --- a/server/proxy/p2p.go +++ b/server/proxy/p2p.go @@ -1,8 +1,10 @@ package proxy import ( + "encoding/binary" "github.com/cnlh/nps/lib/common" "github.com/cnlh/nps/lib/conn" + "github.com/cnlh/nps/vender/github.com/astaxie/beego/logs" "net" "strconv" "time" @@ -15,10 +17,12 @@ type P2PServer struct { } type p2p struct { - provider *conn.Conn - visitor *conn.Conn - visitorAddr string - providerAddr string + provider *conn.Conn + visitor *conn.Conn + visitorAddr string + providerAddr string + providerNatType int32 + visitorNatType int32 } func NewP2PServer(p2pPort int) *P2PServer { @@ -35,49 +39,57 @@ func (s *P2PServer) Start() error { } func (s *P2PServer) p2pProcess(c *conn.Conn) { - //获取密钥 var ( - f string - b []byte - err error - v *p2p - ok bool + f string + b []byte + err error + v *p2p + ok bool + natType int32 ) if b, err = c.GetShortContent(32); err != nil { return } - //获取角色 + //get role if f, err = c.ReadFlag(); err != nil { return } + //get nat type + if err := binary.Read(c, binary.LittleEndian, &natType); err != nil { + return + } if v, ok = s.p2p[string(b)]; !ok { v = new(p2p) s.p2p[string(b)] = v } + logs.Trace("new p2p connection ,role %s , password %s, nat type %s ,local address %s", f, string(b), strconv.Itoa(int(natType)), c.RemoteAddr().String()) //存储 if f == common.WORK_P2P_VISITOR { + logs.Warn("try visitor") v.visitorAddr = c.Conn.RemoteAddr().String() + v.visitorNatType = natType v.visitor = c - for { - time.Sleep(time.Second) + for i := 20; i > 0; i-- { if v.provider != nil { + v.provider.WriteLenContent([]byte(v.visitorAddr)) + binary.Write(v.provider, binary.LittleEndian, v.visitorNatType) break } + time.Sleep(time.Second) } - if _, err := v.provider.ReadFlag(); err == nil { - v.visitor.WriteLenContent([]byte(v.providerAddr)) - delete(s.p2p, string(b)) - } else { - } + v.provider = nil } else { v.providerAddr = c.Conn.RemoteAddr().String() + v.providerNatType = natType v.provider = c - for { - time.Sleep(time.Second) + for i := 20; i > 0; i-- { if v.visitor != nil { - v.provider.WriteLenContent([]byte(v.visitorAddr)) + v.visitor.WriteLenContent([]byte(v.providerAddr)) + binary.Write(v.visitor, binary.LittleEndian, v.providerNatType) break } + time.Sleep(time.Second) } + v.visitor = nil } } diff --git a/server/proxy/socks5.go b/server/proxy/socks5.go index a2af9e8..1ab20a9 100755 --- a/server/proxy/socks5.go +++ b/server/proxy/socks5.go @@ -142,7 +142,7 @@ func (s *Sock5ModeServer) doConnect(c net.Conn, command uint8) { } s.DealClient(conn.NewConn(c), s.task.Client, addr, nil, ltype, func() { s.sendReply(c, succeeded) - }, s.task.Flow) + }, s.task.Flow, s.task.Target.LocalProxy) return } diff --git a/server/proxy/tcp.go b/server/proxy/tcp.go index cb275f0..89f4edd 100755 --- a/server/proxy/tcp.go +++ b/server/proxy/tcp.go @@ -94,7 +94,7 @@ func ProcessTunnel(c *conn.Conn, s *TunnelModeServer) error { logs.Warn("tcp port %d ,client id %d,task id %d connect error %s", s.task.Port, s.task.Client.Id, s.task.Id, err.Error()) return err } - return s.DealClient(c, s.task.Client, targetAddr, nil, common.CONN_TCP, nil, s.task.Flow) + return s.DealClient(c, s.task.Client, targetAddr, nil, common.CONN_TCP, nil, s.task.Flow, s.task.Target.LocalProxy) } //http proxy @@ -112,5 +112,5 @@ func ProcessHttp(c *conn.Conn, s *TunnelModeServer) error { if err := s.auth(r, c, s.task.Client.Cnf.U, s.task.Client.Cnf.P); err != nil { return err } - return s.DealClient(c, s.task.Client, addr, rb, common.CONN_TCP, nil, s.task.Flow) + return s.DealClient(c, s.task.Client, addr, rb, common.CONN_TCP, nil, s.task.Flow, s.task.Target.LocalProxy) } diff --git a/server/proxy/udp.go b/server/proxy/udp.go index 558d534..62358a4 100755 --- a/server/proxy/udp.go +++ b/server/proxy/udp.go @@ -54,8 +54,8 @@ func (s *UdpModeServer) process(addr *net.UDPAddr, data []byte) { return } defer s.task.Client.AddConn() - link := conn.NewLink(common.CONN_UDP, s.task.Target.TargetStr, s.task.Client.Cnf.Crypt, s.task.Client.Cnf.Compress, addr.String()) - if target, err := s.bridge.SendLinkInfo(s.task.Client.Id, link, addr.String(), s.task); err != nil { + link := conn.NewLink(common.CONN_UDP, s.task.Target.TargetStr, s.task.Client.Cnf.Crypt, s.task.Client.Cnf.Compress, addr.String(), s.task.Target.LocalProxy) + if target, err := s.bridge.SendLinkInfo(s.task.Client.Id, link, 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 87662af..b382401 100644 --- a/server/server.go +++ b/server/server.go @@ -70,7 +70,7 @@ func DealBridgeTask() { logs.Info("Connections exceed the current client %d limit", t.Client.Id) s.Conn.Close() } else if t.Status { - go proxy.NewBaseServer(Bridge, t).DealClient(s.Conn, t.Client, t.Target.TargetStr, nil, common.CONN_TCP, nil, t.Flow) + go proxy.NewBaseServer(Bridge, t).DealClient(s.Conn, t.Client, t.Target.TargetStr, nil, common.CONN_TCP, nil, t.Flow, t.Target.LocalProxy) } else { s.Conn.Close() logs.Trace("This key %s cannot be processed,status is close", s.Password) @@ -140,7 +140,11 @@ func NewMode(Bridge *bridge.Bridge, c *file.Tunnel) proxy.Service { AddTask(t) service = proxy.NewWebServer(Bridge) case "httpHostServer": - service = proxy.NewHttp(Bridge, c) + httpPort, _ := beego.AppConfig.Int("http_proxy_port") + httpsPort, _ := beego.AppConfig.Int("https_proxy_port") + useCache, _ := beego.AppConfig.Bool("http_cache") + cacheLen, _ := beego.AppConfig.Int("http_cache_length") + service = proxy.NewHttp(Bridge, c, httpPort, httpsPort, useCache, cacheLen) } return service } diff --git a/server/test/test.go b/server/test/test.go index 4b52d57..3b83565 100644 --- a/server/test/test.go +++ b/server/test/test.go @@ -51,10 +51,10 @@ func TestServerConfig() { if port, err := strconv.Atoi(p); err != nil { log.Fatalln("get https port error", err) } else { - if !common.FileExists(filepath.Join(beego.AppPath, beego.AppConfig.String("pemPath"))) { + if !common.FileExists(filepath.Join(common.GetRunPath(), beego.AppConfig.String("pemPath"))) { log.Fatalf("ssl certFile %s is not exist", beego.AppConfig.String("pemPath")) } - if !common.FileExists(filepath.Join(beego.AppPath, beego.AppConfig.String("ketPath"))) { + if !common.FileExists(filepath.Join(common.GetRunPath(), beego.AppConfig.String("ketPath"))) { log.Fatalf("ssl keyFile %s is not exist", beego.AppConfig.String("pemPath")) } isInArr(&postTcpArr, port, "http port", "tcp") diff --git a/server/tool/utils.go b/server/tool/utils.go index 6b73362..f22bfe6 100644 --- a/server/tool/utils.go +++ b/server/tool/utils.go @@ -17,9 +17,11 @@ var ( ServerStatus []map[string]interface{} ) -func init() { - ServerStatus = make([]map[string]interface{}, 0, 1500) - go getSeverStatus() +func StartSystemInfo() { + if b, err := beego.AppConfig.Bool("system_info_display"); err == nil && b { + ServerStatus = make([]map[string]interface{}, 0, 1500) + go getSeverStatus() + } } func InitAllowPort() { @@ -86,5 +88,3 @@ func getSeverStatus() { ServerStatus = append(ServerStatus, m) } } - - diff --git a/web/controllers/base.go b/web/controllers/base.go index eca446b..6c0bfe2 100755 --- a/web/controllers/base.go +++ b/web/controllers/base.go @@ -49,6 +49,10 @@ func (s *BaseController) Prepare() { s.Data["allow_rate_limit"], _ = beego.AppConfig.Bool("allow_rate_limit") s.Data["allow_connection_num_limit"], _ = beego.AppConfig.Bool("allow_connection_num_limit") s.Data["allow_multi_ip"], _ = beego.AppConfig.Bool("allow_multi_ip") + s.Data["system_info_display"], _ = beego.AppConfig.Bool("system_info_display") + s.Data["allow_tunnel_num_limit"], _ = beego.AppConfig.Bool("allow_tunnel_num_limit") + s.Data["allow_local_proxy"], _ = beego.AppConfig.Bool("allow_local_proxy") + s.Data["allow_user_change_username"], _ = beego.AppConfig.Bool("allow_user_change_username") } //加载模板 diff --git a/web/controllers/client.go b/web/controllers/client.go index de8e551..b54823d 100644 --- a/web/controllers/client.go +++ b/web/controllers/client.go @@ -54,6 +54,7 @@ func (s *ClientController) Add() { MaxConn: s.GetIntNoErr("max_conn"), WebUserName: s.GetString("web_username"), WebPassword: s.GetString("web_password"), + MaxTunnelNum: s.GetIntNoErr("max_tunnel"), Flow: &file.Flow{ ExportFlow: 0, InletFlow: 0, @@ -116,13 +117,17 @@ func (s *ClientController) Edit() { c.Flow.FlowLimit = int64(s.GetIntNoErr("flow_limit")) c.RateLimit = s.GetIntNoErr("rate_limit") c.MaxConn = s.GetIntNoErr("max_conn") + c.MaxTunnelNum = s.GetIntNoErr("max_tunnel") } c.Remark = s.GetString("remark") c.Cnf.U = s.GetString("u") c.Cnf.P = s.GetString("p") c.Cnf.Compress = common.GetBoolByStr(s.GetString("compress")) c.Cnf.Crypt = s.GetBoolNoErr("crypt") - c.WebUserName = s.GetString("web_username") + b, err := beego.AppConfig.Bool("allow_user_change_username") + if s.GetSession("isAdmin").(bool) || (err == nil && b) { + c.WebUserName = s.GetString("web_username") + } c.WebPassword = s.GetString("web_password") c.ConfigConnAllow = s.GetBoolNoErr("config_conn_allow") if c.Rate != nil { diff --git a/web/controllers/index.go b/web/controllers/index.go index 651f8b2..3feae78 100755 --- a/web/controllers/index.go +++ b/web/controllers/index.go @@ -93,7 +93,7 @@ func (s *IndexController) Add() { Port: s.GetIntNoErr("port"), ServerIp: s.GetString("server_ip"), Mode: s.GetString("type"), - Target: &file.Target{TargetStr: s.GetString("target")}, + Target: &file.Target{TargetStr: s.GetString("target"), LocalProxy: s.GetBoolNoErr("local_proxy")}, Id: int(file.GetDb().JsonDb.GetTaskId()), Status: true, Remark: s.GetString("remark"), @@ -109,6 +109,9 @@ func (s *IndexController) Add() { if t.Client, err = file.GetDb().GetClient(s.GetIntNoErr("client_id")); err != nil { s.AjaxErr(err.Error()) } + if t.Client.MaxTunnelNum != 0 && t.Client.GetTunnelNum() >= t.Client.MaxTunnelNum { + s.AjaxErr("The number of tunnels exceeds the limit") + } if err := file.GetDb().NewTask(t); err != nil { s.AjaxErr(err.Error()) } @@ -166,6 +169,7 @@ func (s *IndexController) Edit() { t.LocalPath = s.GetString("local_path") t.StripPre = s.GetString("strip_pre") t.Remark = s.GetString("remark") + t.Target.LocalProxy = s.GetBoolNoErr("local_proxy") file.GetDb().UpdateTask(t) server.StopServer(t.Id) server.StartTask(t.Id) @@ -244,7 +248,7 @@ func (s *IndexController) AddHost() { h := &file.Host{ Id: int(file.GetDb().JsonDb.GetHostId()), Host: s.GetString("host"), - Target: &file.Target{TargetStr: s.GetString("target")}, + Target: &file.Target{TargetStr: s.GetString("target"), LocalProxy: s.GetBoolNoErr("local_proxy")}, HeaderChange: s.GetString("header"), HostChange: s.GetString("hostchange"), Remark: s.GetString("remark"), @@ -304,6 +308,7 @@ func (s *IndexController) EditHost() { h.Scheme = s.GetString("scheme") h.KeyFilePath = s.GetString("key_file_path") h.CertFilePath = s.GetString("cert_file_path") + h.Target.LocalProxy = s.GetBoolNoErr("local_proxy") file.GetDb().JsonDb.StoreHostToJsonFile() } s.AjaxOk("modified success") diff --git a/web/controllers/login.go b/web/controllers/login.go index 90f70cd..3c8cec9 100755 --- a/web/controllers/login.go +++ b/web/controllers/login.go @@ -13,6 +13,7 @@ type LoginController struct { } func (self *LoginController) Index() { + self.Data["register_allow"], _ = beego.AppConfig.Bool("allow_user_register") self.TplName = "login/index.html" } func (self *LoginController) Verify() { @@ -55,6 +56,37 @@ func (self *LoginController) Verify() { } self.ServeJSON() } +func (self *LoginController) Register() { + if self.Ctx.Request.Method == "GET" { + self.TplName = "login/register.html" + } else { + if b, err := beego.AppConfig.Bool("allow_user_register"); err != nil || !b { + self.Data["json"] = map[string]interface{}{"status": 0, "msg": "register is not allow"} + self.ServeJSON() + return + } + if self.GetString("username") == "" || self.GetString("password") == "" || self.GetString("username") == beego.AppConfig.String("web_username") { + self.Data["json"] = map[string]interface{}{"status": 0, "msg": "please check your input"} + self.ServeJSON() + return + } + t := &file.Client{ + Id: int(file.GetDb().JsonDb.GetClientId()), + Status: true, + Cnf: &file.Config{}, + WebUserName: self.GetString("username"), + WebPassword: self.GetString("password"), + Flow: &file.Flow{}, + } + if err := file.GetDb().NewClient(t); err != nil { + self.Data["json"] = map[string]interface{}{"status": 0, "msg": err.Error()} + } else { + self.Data["json"] = map[string]interface{}{"status": 1, "msg": "register success"} + } + self.ServeJSON() + } +} + func (self *LoginController) Out() { self.SetSession("auth", false) self.Redirect("/login/index", 302) diff --git a/web/views/client/add.html b/web/views/client/add.html index 30469d1..07719bd 100755 --- a/web/views/client/add.html +++ b/web/views/client/add.html @@ -39,6 +39,15 @@ placeholder="empty means to be unrestricted"> + {{end}} + {{if eq true .allow_tunnel_num_limit}} +
+ +
+ +
+
{{end}}
diff --git a/web/views/client/edit.html b/web/views/client/edit.html index 80651d4..20db215 100755 --- a/web/views/client/edit.html +++ b/web/views/client/edit.html @@ -44,6 +44,15 @@
{{end}} + {{if eq true .allow_tunnel_num_limit}} +
+ +
+ +
+
+ {{end}} {{end}}
@@ -72,6 +81,7 @@
{{end}} {{if eq true .allow_user_login}} + {{if or (eq true .allow_user_change_username) (eq true .isAdmin)}}
@@ -79,6 +89,7 @@ placeholder="empty means to be unrestricted">
+ {{end}}
diff --git a/web/views/client/list.html b/web/views/client/list.html index 018400f..ce49bfe 100755 --- a/web/views/client/list.html +++ b/web/views/client/list.html @@ -127,6 +127,7 @@ + '当前连接数:' + row.NowConn + `       ` + '流量限制:' + row.Flow.FlowLimit + `m       ` + '带宽限制:' + row.RateLimit + `kb/s       ` + + '隧道数限制:' + row.MaxTunnelNum + `       ` + 'web登陆用户名:' + row.WebUserName + `       ` + 'web登陆密码:' + row.WebPassword + `       ` + `       ` + "

" diff --git a/web/views/index/add.html b/web/views/index/add.html index ccbfc9c..7d049eb 100755 --- a/web/views/index/add.html +++ b/web/views/index/add.html @@ -43,8 +43,20 @@
+ {{if eq true .allow_local_proxy}} +
+ +
+ +
+
+ {{end}} +
- +
@@ -103,9 +115,9 @@
+ + + +