diff --git a/bridge/bridge.go b/bridge/bridge.go index 2188259..63e9f33 100755 --- a/bridge/bridge.go +++ b/bridge/bridge.go @@ -8,11 +8,11 @@ import ( "github.com/cnlh/nps/lib/conn" "github.com/cnlh/nps/lib/crypt" "github.com/cnlh/nps/lib/file" - "github.com/cnlh/nps/lib/lg" "github.com/cnlh/nps/lib/pool" + "github.com/cnlh/nps/lib/version" "github.com/cnlh/nps/server/tool" + "github.com/cnlh/nps/vender/github.com/astaxie/beego/logs" "github.com/cnlh/nps/vender/github.com/xtaci/kcp" - "log" "net" "strconv" "sync" @@ -48,6 +48,7 @@ type Bridge struct { tunnelType string //bridge type kcp or tcp OpenTask chan *file.Tunnel CloseClient chan int + SecretChan chan *conn.Secret clientLock sync.RWMutex Register map[string]time.Time registerLock sync.RWMutex @@ -65,6 +66,7 @@ func NewTunnel(tunnelPort int, tunnelType string, ipVerify bool, runList map[int t.Register = make(map[string]time.Time) t.ipVerify = ipVerify t.runList = runList + t.SecretChan = make(chan *conn.Secret) return t } @@ -80,7 +82,7 @@ func (s *Bridge) StartTunnel() error { c, err := s.kcpListener.AcceptKCP() conn.SetUdpSession(c) if err != nil { - lg.Println(err) + logs.Warn(err) continue } go s.cliProcess(conn.NewConn(c)) @@ -95,7 +97,7 @@ func (s *Bridge) StartTunnel() error { for { c, err := s.tcpListener.Accept() if err != nil { - lg.Println(err) + logs.Warn(err) continue } go s.cliProcess(conn.NewConn(c)) @@ -116,6 +118,12 @@ func (s *Bridge) verifySuccess(c *conn.Conn) { } func (s *Bridge) cliProcess(c *conn.Conn) { + c.Write([]byte(crypt.Md5(version.GetVersion()))) + if b, err := c.ReadFlag(); err != nil || string(b) != version.VERSION_OK { + logs.Info("The client %s version does not match", c.Conn.RemoteAddr()) + c.Close() + return + } c.SetReadDeadline(5, s.tunnelType) var buf []byte var err error @@ -126,7 +134,7 @@ func (s *Bridge) cliProcess(c *conn.Conn) { //验证 id, err := file.GetCsvDb().GetIdByVerifyKey(string(buf), c.Conn.RemoteAddr().String()) if err != nil { - lg.Println("当前客户端连接校验错误,关闭此客户端:", c.Conn.RemoteAddr()) + logs.Info("Current client connection validation error, close this client:", c.Conn.RemoteAddr()) s.verifyError(c) return } else { @@ -136,7 +144,7 @@ func (s *Bridge) cliProcess(c *conn.Conn) { if flag, err := c.ReadFlag(); err == nil { s.typeDeal(flag, c, id) } else { - log.Println(err, flag) + logs.Warn(err, flag) } return } @@ -182,7 +190,7 @@ func (s *Bridge) typeDeal(typeVal string, c *conn.Conn, id int) { s.Client[id] = NewClient(nil, c, nil) s.clientLock.Unlock() } - lg.Printf("clientId %d connection succeeded, address:%s ", id, c.Conn.RemoteAddr()) + logs.Info("clientId %d connection succeeded, address:%s ", id, c.Conn.RemoteAddr()) go s.GetStatus(id) case common.WORK_CHAN: s.clientLock.Lock() @@ -200,6 +208,10 @@ func (s *Bridge) typeDeal(typeVal string, c *conn.Conn, id int) { go s.GetConfig(c) case common.WORK_REGISTER: go s.register(c) + case common.WORD_SECRET: + if b, err := c.ReadLen(32); err == nil { + s.SecretChan <- conn.NewSecret(string(b), c) + } case common.WORK_SEND_STATUS: s.clientLock.Lock() if v, ok := s.Client[id]; ok { @@ -293,7 +305,7 @@ func (s *Bridge) SendLinkInfo(clientId int, link *conn.Link, linkAddr string) (t v.signal.SendLinkInfo(link) if err != nil { - lg.Println("send link information error:", err, link.Id) + logs.Warn("send link information error:", err, link.Id) s.DelClient(clientId) return } @@ -314,7 +326,7 @@ func (s *Bridge) SendLinkInfo(clientId int, link *conn.Link, linkAddr string) (t } } else { s.clientLock.Unlock() - err = errors.New("the connection is not connect") + err = errors.New(fmt.Sprintf("the client %d is not connect", clientId)) } return } @@ -328,6 +340,7 @@ func (s *Bridge) DelClient(id int) { func (s *Bridge) GetConfig(c *conn.Conn) { var client *file.Client var fail bool + for { flag, err := c.ReadFlag() if err != nil { @@ -357,19 +370,15 @@ func (s *Bridge) GetConfig(c *conn.Conn) { binary.Write(c, binary.LittleEndian, []byte(str)) } case common.NEW_CONF: - //new client ,Set the client not to store to the file - client = file.NewClient(crypt.GetRandomString(16), true, false) - client.Remark = "public veky" - //Send the key to the client - file.GetCsvDb().NewClient(client) - c.Write([]byte(client.VerifyKey)) - - if config, err := c.GetConfigInfo(); err != nil { + var err error + if client, err = c.GetConfigInfo(); err != nil { + c.Write([]byte(client.VerifyKey)) fail = true c.WriteAddFail() break } else { - client.Cnf = config + c.Write([]byte(client.VerifyKey)) + file.GetCsvDb().NewClient(client) c.WriteAddOk() } case common.NEW_HOST: @@ -380,6 +389,7 @@ func (s *Bridge) GetConfig(c *conn.Conn) { } else if file.GetCsvDb().IsHostExist(h) { fail = true c.WriteAddFail() + break } else { h.Client = client file.GetCsvDb().NewHost(h) @@ -397,6 +407,13 @@ func (s *Bridge) GetConfig(c *conn.Conn) { fail = true c.WriteAddFail() break + } else if t.Mode == "secretServer" { + ports = append(ports, 0) + } + if len(ports) == 0 { + fail = true + c.WriteAddFail() + break } for i := 0; i < len(ports); i++ { tl := new(file.Tunnel) @@ -407,17 +424,24 @@ func (s *Bridge) GetConfig(c *conn.Conn) { tl.Remark = t.Remark } else { tl.Remark = t.Remark + "_" + strconv.Itoa(tl.Port) - tl.Target = strconv.Itoa(targets[i]) + tl.Target = t.TargetAddr + ":" + strconv.Itoa(targets[i]) } tl.Id = file.GetCsvDb().GetTaskId() tl.Status = true tl.Flow = new(file.Flow) tl.NoStore = true tl.Client = client - file.GetCsvDb().NewTask(tl) - if b := tool.TestServerPort(tl.Port, tl.Mode); !b { + 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 != "secretServer" { + fail = true + c.WriteAddFail() + break } else { s.OpenTask <- tl } @@ -460,7 +484,7 @@ func (s *Bridge) clientCopy(clientId int) { for { if id, err := client.tunnel.GetLen(); err != nil { - lg.Println("read msg content length error close client") + logs.Info("read msg content length error close client") s.delClient(clientId) break } else { @@ -470,7 +494,7 @@ func (s *Bridge) clientCopy(clientId int) { if content, err := client.tunnel.GetMsgContent(link); err != nil { pool.PutBufPoolCopy(content) s.delClient(clientId) - lg.Println("read msg content error", err, "close client") + logs.Notice("read msg content error", err, "close client") break } else { link.MsgCh <- content diff --git a/client/client.go b/client/client.go index df6bb5e..eb150b1 100755 --- a/client/client.go +++ b/client/client.go @@ -3,9 +3,10 @@ package client import ( "github.com/cnlh/nps/lib/common" "github.com/cnlh/nps/lib/conn" - "github.com/cnlh/nps/lib/lg" "github.com/cnlh/nps/lib/pool" + "github.com/cnlh/nps/vender/github.com/astaxie/beego/logs" "net" + "os" "sync" "time" ) @@ -40,11 +41,11 @@ func (s *TRPClient) Start() { retry: c, err := NewConn(s.bridgeConnType, s.vKey, s.svrAddr, common.WORK_MAIN, s.proxyUrl) if err != nil { - lg.Println("The connection server failed and will be reconnected in five seconds") + logs.Error("The connection server failed and will be reconnected in five seconds") time.Sleep(time.Second * 5) goto retry } - lg.Printf("Successful connection with server %s", s.svrAddr) + logs.Info("Successful connection with server %s", s.svrAddr) s.processor(c) } @@ -65,12 +66,13 @@ func (s *TRPClient) processor(c *conn.Conn) { for { flags, err := c.ReadFlag() if err != nil { - lg.Printf("Accept server data error %s, end this service", err.Error()) + logs.Error("Accept server data error %s, end this service", err.Error()) break } switch flags { case common.VERIFY_EER: - lg.Fatalf("VKey:%s is incorrect, the server refuses to connect, please check", s.vKey) + logs.Error("VKey:%s is incorrect, the server refuses to connect, please check", s.vKey) + os.Exit(0) case common.NEW_CONN: if link, err := c.GetLinkInfo(); err != nil { break @@ -83,12 +85,13 @@ func (s *TRPClient) processor(c *conn.Conn) { link.Run(false) } case common.RES_CLOSE: - lg.Fatalln("The authentication key is connected by another client or the server closes the client.") + logs.Error("The authentication key is connected by another client or the server closes the client.") + os.Exit(0) case common.RES_MSG: - lg.Println("Server-side return error") + logs.Error("Server-side return error") break default: - lg.Println("The error could not be resolved") + logs.Warn("The error could not be resolved") break } } @@ -103,7 +106,7 @@ func (s *TRPClient) linkProcess(link *conn.Link, c *conn.Conn) { if err != nil { c.WriteFail(link.Id) - lg.Println("connect to ", link.Host, "error:", err) + logs.Warn("connect to ", link.Host, "error:", err) return } c.WriteSuccess(link.Id) @@ -134,7 +137,7 @@ func (s *TRPClient) getMsgStatus() { var err error s.msgTunnel, err = NewConn(s.bridgeConnType, s.vKey, s.svrAddr, common.WORK_SEND_STATUS, s.proxyUrl) if err != nil { - lg.Println("connect to ", s.svrAddr, "error:", err) + logs.Error("connect to ", s.svrAddr, "error:", err) return } go func() { @@ -160,7 +163,7 @@ func (s *TRPClient) dealChan() { var err error s.tunnel, err = NewConn(s.bridgeConnType, s.vKey, s.svrAddr, common.WORK_CHAN, s.proxyUrl) if err != nil { - lg.Println("connect to ", s.svrAddr, "error:", err) + logs.Error("connect to ", s.svrAddr, "error:", err) return } go func() { diff --git a/client/control.go b/client/control.go index 9737aca..621de16 100644 --- a/client/control.go +++ b/client/control.go @@ -1,11 +1,14 @@ package client import ( + "encoding/binary" "errors" "github.com/cnlh/nps/lib/common" "github.com/cnlh/nps/lib/config" "github.com/cnlh/nps/lib/conn" - "github.com/cnlh/nps/lib/lg" + "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/xtaci/kcp" "github.com/cnlh/nps/vender/golang.org/x/net/proxy" "io/ioutil" @@ -39,7 +42,7 @@ func GetTaskStatus(path string) { if l, err := c.GetLen(); err != nil { log.Fatalln(err) } else if b, err := c.ReadLen(l); err != nil { - lg.Fatalln(err) + log.Fatalln(err) } else { arr := strings.Split(string(b), common.CONN_DATA_SEQ) for _, v := range cnf.Hosts { @@ -74,14 +77,16 @@ var errAdd = errors.New("The server returned an error, which port or host may ha func StartFromFile(path string) { first := true cnf, err := config.NewConfig(path) - if err != nil { - lg.Fatalln(err) + if err != nil || cnf.CommonConfig == nil { + logs.Error("Config file %s loading error", path) + os.Exit(0) } - lg.Printf("Loading configuration file %s successfully", path) + + logs.Info("Loading configuration file %s successfully", path) re: if first || cnf.CommonConfig.AutoReconnection { if !first { - lg.Println("Reconnecting...") + logs.Info("Reconnecting...") time.Sleep(time.Second * 5) } } else { @@ -90,48 +95,51 @@ re: first = false c, err := NewConn(cnf.CommonConfig.Tp, cnf.CommonConfig.VKey, cnf.CommonConfig.Server, common.WORK_CONFIG, cnf.CommonConfig.ProxyUrl) if err != nil { - lg.Println(err) + logs.Error(err) goto re } - if _, err := c.SendConfigInfo(cnf.CommonConfig.Cnf); err != nil { - lg.Println(err) + if _, err := c.SendConfigInfo(cnf.CommonConfig); err != nil { + logs.Error(err) goto re } var b []byte if b, err = c.ReadLen(16); err != nil { - lg.Println(err) + logs.Error(err) goto re } else { ioutil.WriteFile(filepath.Join(common.GetTmpPath(), "npc_vkey.txt"), []byte(string(b)), 0600) } if !c.GetAddStatus() { - lg.Println(errAdd) + logs.Error(errAdd) goto re } + for _, v := range cnf.Hosts { if _, err := c.SendHostInfo(v); err != nil { - lg.Println(err) + logs.Error(err) goto re } if !c.GetAddStatus() { - lg.Println(errAdd, v.Host) + logs.Error(errAdd, v.Host) goto re } } for _, v := range cnf.Tasks { if _, err := c.SendTaskInfo(v); err != nil { - lg.Println(err) + logs.Error(err) goto re } if !c.GetAddStatus() { - lg.Println(errAdd, v.Ports) + logs.Error(errAdd, v.Ports) goto re } } - + for _, v := range cnf.LocalServer { + go StartLocalServer(v, cnf.CommonConfig) + } c.Close() - NewRPClient(cnf.CommonConfig.Server, string(b), cnf.CommonConfig.Tp, cnf.CommonConfig.ProxyUrl).Start() + CloseLocalServer() goto re } @@ -163,16 +171,30 @@ func NewConn(tp string, vkey string, server string, connType string, proxyUrl st return nil, err } c := conn.NewConn(connection) + if b, err := c.ReadLen(32); err != nil { + logs.Error(err) + os.Exit(0) + } else if crypt.Md5(version.GetVersion()) != string(b) { + logs.Error("The client does not match the server version. The current version of the client is", version.GetVersion()) + os.Exit(0) + } else if binary.Write(c, binary.LittleEndian, []byte(version.VERSION_OK)); err != nil { + logs.Error(err) + os.Exit(0) + } if _, err := c.Write([]byte(common.Getverifyval(vkey))); err != nil { - lg.Println(err) + logs.Error(err) + os.Exit(0) } if s, err := c.ReadFlag(); err != nil { - lg.Println(err) + logs.Error(err) + os.Exit(0) } else if s == common.VERIFY_EER { - lg.Fatalf("Validation key %s incorrect", vkey) + logs.Error("Validation key %s incorrect", vkey) + os.Exit(0) } if _, err := c.Write([]byte(connType)); err != nil { - lg.Println(err) + logs.Error(err) + os.Exit(0) } c.SetAlive(tp) diff --git a/client/local.go b/client/local.go new file mode 100644 index 0000000..69a2d90 --- /dev/null +++ b/client/local.go @@ -0,0 +1,54 @@ +package client + +import ( + "github.com/cnlh/nps/lib/common" + "github.com/cnlh/nps/lib/config" + "github.com/cnlh/nps/lib/crypt" + "github.com/cnlh/nps/vender/github.com/astaxie/beego/logs" + "net" + "strings" +) + +var LocalServer []*net.TCPListener + +func CloseLocalServer() { + for _, v := range LocalServer { + v.Close() + } +} + +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()) + return err + } + LocalServer = append(LocalServer, listener) + logs.Info("Successful start-up of local monitoring, port", l.Port) + for { + c, err := listener.AcceptTCP() + if err != nil { + if strings.Contains(err.Error(), "use of closed network connection") { + break + } + logs.Info(err) + continue + } + go process(c, config, l) + } + return nil +} + +func process(conn net.Conn, config *config.CommonConfig, l *config.LocalServer) { + c, err := NewConn(config.Tp, config.VKey, config.Server, common.WORD_SECRET, config.ProxyUrl) + if err != nil { + logs.Error("Local connection server failed ", err.Error()) + } + if _, err := c.Write([]byte(crypt.Md5(l.Password))); err != nil { + logs.Error("Local connection server failed ", err.Error()) + } + go common.CopyBuffer(c, conn) + common.CopyBuffer(conn, c) + c.Close() + conn.Close() +} diff --git a/cmd/npc/npc.go b/cmd/npc/npc.go index 5a9e807..8bf63c0 100644 --- a/cmd/npc/npc.go +++ b/cmd/npc/npc.go @@ -5,14 +5,12 @@ import ( "github.com/cnlh/nps/client" "github.com/cnlh/nps/lib/common" "github.com/cnlh/nps/lib/daemon" - "github.com/cnlh/nps/lib/lg" + "github.com/cnlh/nps/vender/github.com/astaxie/beego/logs" "os" "strings" "time" ) -const VERSION = "v0.0.15" - var ( serverAddr = flag.String("server", "", "Server addr (ip:port)") configPath = flag.String("config", "npc.conf", "Configuration file path") @@ -20,6 +18,7 @@ var ( logType = flag.String("log", "stdout", "Log output mode(stdout|file)") connType = flag.String("type", "tcp", "Connection type with the server(kcp|tcp)") 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") ) @@ -37,14 +36,14 @@ func main() { } daemon.InitDaemon("npc", common.GetRunPath(), common.GetTmpPath()) if *logType == "stdout" { - lg.InitLogFile("npc", true, common.GetLogPath()) + logs.SetLogger(logs.AdapterConsole, `{"level":`+*logLevel+`,"color":true}`) } else { - lg.InitLogFile("npc", false, common.GetLogPath()) + logs.SetLogger(logs.AdapterFile, `{"level":`+*logLevel+`,"filename":"npc_log.log"}`) } if *verifyKey != "" && *serverAddr != "" { for { client.NewRPClient(*serverAddr, *verifyKey, *connType, *proxyUrl).Start() - lg.Println("It will be reconnected in five seconds") + logs.Info("It will be reconnected in five seconds") time.Sleep(time.Second * 5) } } else { diff --git a/cmd/nps/nps.go b/cmd/nps/nps.go index cfe4073..c636fd2 100644 --- a/cmd/nps/nps.go +++ b/cmd/nps/nps.go @@ -6,19 +6,18 @@ import ( "github.com/cnlh/nps/lib/daemon" "github.com/cnlh/nps/lib/file" "github.com/cnlh/nps/lib/install" - "github.com/cnlh/nps/lib/lg" "github.com/cnlh/nps/server" "github.com/cnlh/nps/server/test" "github.com/cnlh/nps/vender/github.com/astaxie/beego" + "github.com/cnlh/nps/vender/github.com/astaxie/beego/logs" _ "github.com/cnlh/nps/web/routers" "log" "os" "path/filepath" ) -const VERSION = "v0.0.15" - var ( + level string logType = flag.String("log", "stdout", "Log output mode(stdout|file)") ) @@ -37,17 +36,22 @@ func main() { return } } + if level = beego.AppConfig.String("logLevel"); level == "" { + level = "7" + } + logs.Reset() if *logType == "stdout" { - lg.InitLogFile("nps", true, common.GetLogPath()) + logs.SetLogger(logs.AdapterConsole, `{"level":`+level+`,"color":true}`) } else { - lg.InitLogFile("nps", false, common.GetLogPath()) + logs.SetLogger(logs.AdapterFile, `{"level":`+level+`,"filename":"nps_log.log"}`) } task := &file.Tunnel{ Mode: "webServer", } bridgePort, err := beego.AppConfig.Int("bridgePort") if err != nil { - lg.Fatalln("Getting bridgePort error", err) + logs.Error("Getting bridgePort error", err) + os.Exit(0) } beego.LoadAppConfig("ini", filepath.Join(common.GetRunPath(), "conf", "app.conf")) server.StartNewServer(bridgePort, task, beego.AppConfig.String("bridgeType")) diff --git a/conf/app.conf b/conf/app.conf index c2b6df1..61829df 100755 --- a/conf/app.conf +++ b/conf/app.conf @@ -36,4 +36,11 @@ bridgeType=tcp # After the connection, the server will be able to open relevant ports and parse related domain names according to its own configuration file. publicVkey=123 +#Traffic data persistence interval(minute) +#Ignorance means no persistence +flowStoreInterval=1 +#log level +#LevelEmergency->0 LevelAlert->1 LevelCritical->2 LevelError->3 LevelWarning->4 +#LevelNotice->5 LevelInformational->6 LevelDebug->7 +logLevel=7 \ No newline at end of file diff --git a/conf/clients.csv b/conf/clients.csv index ab4f932..c8ea96d 100644 --- a/conf/clients.csv +++ b/conf/clients.csv @@ -1 +1 @@ -7,7hv3avgeg2ldzvx7,,true,,,0,,0,0 +7,7hv3avgeg2ldzvx7,,true,,,1,,0,999,10 diff --git a/conf/hosts.csv b/conf/hosts.csv index e69de29..0e9598d 100644 --- a/conf/hosts.csv +++ b/conf/hosts.csv @@ -0,0 +1 @@ +b.o.com,127.0.0.1:8082,7,,,,/,3,12995709,38827 diff --git a/conf/npc.conf b/conf/npc.conf index 75e7cec..50b45e4 100644 --- a/conf/npc.conf +++ b/conf/npc.conf @@ -4,32 +4,24 @@ tp=tcp vkey=123 auto_reconnection=true crypt=true + [web1] host=a.o.com host_change=www.proxy.com -target=127.0.0.1:8082 +target=127.0.0.1:8080 location=/ [web2] -host=a.proxy.com -target=127.0.0.1:8080,127.0.0.1:8082 +host=127.0.0.1 host_change=www.proxy.com -header_set_proxy=nps +target=127.0.0.1:8080 +location=/cdn -[tcp] -mode=tcpServer -target=8001-8005,8082 -port=9001-9005,9006 +[ssh_1118] +mode=secretServer +password=1111 +target=123.206.77.88:22 -[socks5] -mode=socks5Server -port=9007 - -[http] -mode=httpProxyServer -port=9008 - -[udp] -mode=udpServer -port=53 -target=114.114.114.114:53 +[secret_ssh] +password=1111 +port=1000 diff --git a/conf/tasks.csv b/conf/tasks.csv index c0a0acf..a5a67d9 100644 --- a/conf/tasks.csv +++ b/conf/tasks.csv @@ -1 +1,3 @@ -9010,socks5Server,,1,27,7, +9010,socks5Server,,1,27,7,,0,0, +9002,tcpServer,127.0.0.1:8082,1,28,7,,9175971,17054, +0,secretServer,123.206.77.88:22,1,30,7,私密隧道测试,15620,12628,tests diff --git a/lib/common/const.go b/lib/common/const.go index b9d1b50..cc21265 100644 --- a/lib/common/const.go +++ b/lib/common/const.go @@ -6,22 +6,22 @@ const ( COMPRESS_NONE_DECODE COMPRESS_SNAPY_ENCODE COMPRESS_SNAPY_DECODE - VERIFY_EER = "vkey" - VERIFY_SUCCESS = "sucs" - WORK_MAIN = "main" - WORK_CHAN = "chan" - WORK_SEND_STATUS = "sdst" - WORK_CONFIG = "conf" - WORK_REGISTER = "rgst" - WORK_STATUS = "stus" - RES_SIGN = "sign" - RES_MSG = "msg0" - RES_CLOSE = "clse" - NEW_CONN = "conn" //新连接标志 - NEW_TASK = "task" //新连接标志 - NEW_CONF = "conf" //新连接标志 - NEW_HOST = "host" //新连接标志 - + VERIFY_EER = "vkey" + VERIFY_SUCCESS = "sucs" + WORK_MAIN = "main" + WORK_CHAN = "chan" + WORK_SEND_STATUS = "sdst" + WORK_CONFIG = "conf" + WORK_REGISTER = "rgst" + WORD_SECRET = "sert" + WORK_STATUS = "stus" + RES_SIGN = "sign" + RES_MSG = "msg0" + RES_CLOSE = "clse" + NEW_CONN = "conn" //新连接标志 + NEW_TASK = "task" //新连接标志 + NEW_CONF = "conf" //新连接标志 + NEW_HOST = "host" //新连接标志 CONN_TCP = "tcp" CONN_UDP = "udp" UnauthorizedBytes = `HTTP/1.1 401 Unauthorized diff --git a/lib/common/util.go b/lib/common/util.go index 2ee4e07..12e2db7 100755 --- a/lib/common/util.go +++ b/lib/common/util.go @@ -5,6 +5,8 @@ import ( "encoding/base64" "encoding/binary" "github.com/cnlh/nps/lib/crypt" + "github.com/cnlh/nps/lib/pool" + "io" "io/ioutil" "net" "net/http" @@ -246,3 +248,10 @@ func GetIpByAddr(addr string) string { arr := strings.Split(addr, ":") return arr[0] } + +func CopyBuffer(dst io.Writer, src io.Reader) (written int64, err error) { + buf := pool.BufPoolCopy.Get().([]byte) + io.CopyBuffer(dst, src, buf) + pool.PutBufPoolCopy(buf) + return written, err +} diff --git a/lib/config/config.go b/lib/config/config.go index 4fafad4..b010072 100644 --- a/lib/config/config.go +++ b/lib/config/config.go @@ -1,6 +1,7 @@ package config import ( + "errors" "github.com/cnlh/nps/lib/common" "github.com/cnlh/nps/lib/file" "regexp" @@ -14,6 +15,11 @@ type CommonConfig struct { AutoReconnection bool Cnf *file.Config ProxyUrl string + Client *file.Client +} +type LocalServer struct { + Port int + Password string } type Config struct { content string @@ -21,6 +27,7 @@ type Config struct { CommonConfig *CommonConfig Hosts []*file.Host Tasks []*file.Tunnel + LocalServer []*LocalServer } func NewConfig(path string) (c *Config, err error) { @@ -44,6 +51,11 @@ func NewConfig(path string) (c *Config, err error) { nextIndex = len(c.content) } nowContent = c.content[nowIndex:nextIndex] + + if strings.Index(getTitleContent(c.title[i]), "secret") == 0 { + c.LocalServer = append(c.LocalServer, delLocalService(nowContent)) + continue + } switch c.title[i] { case "[common]": c.CommonConfig = dealCommon(nowContent) @@ -68,9 +80,11 @@ func getTitleContent(s string) string { re, _ := regexp.Compile(`[\[\]]`) return re.ReplaceAllString(s, "") } + func dealCommon(s string) *CommonConfig { c := &CommonConfig{} c.Cnf = new(file.Config) + c.Client = file.NewClient("", true, true) for _, v := range strings.Split(s, "\n") { item := strings.Split(v, "=") if len(item) == 0 { @@ -97,10 +111,19 @@ func dealCommon(s string) *CommonConfig { c.Cnf.Crypt = common.GetBoolByStr(item[1]) case "proxy_socks5_url": c.ProxyUrl = item[1] + case "rate_limit": + c.Client.RateLimit = common.GetIntNoErrByStr(item[1]) + case "flow_limit": + c.Client.Flow.FlowLimit = int64(common.GetIntNoErrByStr(item[1])) + case "max_conn": + c.Client.MaxConn = common.GetIntNoErrByStr(item[1]) + case "remark": + c.Client.Remark = item[1] } } return c } + func dealHost(s string) *file.Host { h := &file.Host{} var headerChange string @@ -146,12 +169,35 @@ func dealTunnel(s string) *file.Tunnel { t.Mode = item[1] case "target": t.Target = item[1] + case "targetAddr": + t.TargetAddr = item[1] + case "password": + t.Password = item[1] } } return t } +func delLocalService(s string) *LocalServer { + l := new(LocalServer) + for _, v := range strings.Split(s, "\n") { + item := strings.Split(v, "=") + if len(item) == 0 { + continue + } else if len(item) == 1 { + item = append(item, "") + } + switch item[0] { + case "port": + l.Port = common.GetIntNoErrByStr(item[1]) + case "password": + l.Password = item[1] + } + } + return l +} + func getAllTitle(content string) (arr []string, err error) { var re *regexp.Regexp re, err = regexp.Compile(`\[.+?\]`) @@ -159,5 +205,13 @@ func getAllTitle(content string) (arr []string, err error) { return } arr = re.FindAllString(content, -1) + m := make(map[string]bool) + for _, v := range arr { + if _, ok := m[v]; ok { + err = errors.New("Item names are not allowed to be duplicated") + return + } + m[v] = true + } return } diff --git a/lib/conn/conn.go b/lib/conn/conn.go index 7aaacb1..145ca38 100755 --- a/lib/conn/conn.go +++ b/lib/conn/conn.go @@ -6,6 +6,8 @@ import ( "encoding/binary" "errors" "github.com/cnlh/nps/lib/common" + "github.com/cnlh/nps/lib/config" + "github.com/cnlh/nps/lib/crypt" "github.com/cnlh/nps/lib/file" "github.com/cnlh/nps/lib/pool" "github.com/cnlh/nps/lib/rate" @@ -317,7 +319,7 @@ func (s *Conn) GetHostInfo() (h *file.Host, err error) { } //send task info -func (s *Conn) SendConfigInfo(c *file.Config) (int, error) { +func (s *Conn) SendConfigInfo(c *config.CommonConfig) (int, error) { /* The task info is formed as follows: +----+-----+---------+ @@ -328,14 +330,15 @@ func (s *Conn) SendConfigInfo(c *file.Config) (int, error) { */ raw := bytes.NewBuffer([]byte{}) binary.Write(raw, binary.LittleEndian, []byte(common.NEW_CONF)) - common.BinaryWrite(raw, c.U, c.P, common.GetStrByBool(c.Crypt), c.Compress) + common.BinaryWrite(raw, c.Cnf.U, c.Cnf.P, common.GetStrByBool(c.Cnf.Crypt), c.Cnf.Compress, strconv.Itoa(c.Client.RateLimit), + strconv.Itoa(int(c.Client.Flow.FlowLimit)), strconv.Itoa(c.Client.MaxConn), c.Client.Remark) s.Lock() defer s.Unlock() return s.Write(raw.Bytes()) } //get task info -func (s *Conn) GetConfigInfo() (c *file.Config, err error) { +func (s *Conn) GetConfigInfo() (c *file.Client, err error) { var l int var b []byte if l, err = s.GetLen(); err != nil { @@ -344,12 +347,16 @@ func (s *Conn) GetConfigInfo() (c *file.Config, err error) { return } else { arr := strings.Split(string(b), common.CONN_DATA_SEQ) - c = new(file.Config) - c.U = arr[0] - c.P = arr[1] - c.Crypt = common.GetBoolByStr(arr[2]) - c.Compress = arr[3] - c.CompressDecode, c.CompressDecode = common.GetCompressType(arr[3]) + c = file.NewClient(crypt.GetRandomString(16), true, false) + c.Cnf.U = arr[0] + c.Cnf.P = arr[1] + c.Cnf.Crypt = common.GetBoolByStr(arr[2]) + c.Cnf.Compress = arr[3] + c.RateLimit = common.GetIntNoErrByStr(arr[4]) + c.Flow.FlowLimit = int64(common.GetIntNoErrByStr(arr[5])) + c.MaxConn = common.GetIntNoErrByStr(arr[6]) + c.Remark = arr[7] + c.Cnf.CompressDecode, c.Cnf.CompressDecode = common.GetCompressType(arr[3]) } return } @@ -366,7 +373,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) + common.BinaryWrite(raw, t.Mode, t.Ports, t.Target, t.Remark, t.TargetAddr, t.Password) s.Lock() defer s.Unlock() return s.Write(raw.Bytes()) @@ -390,6 +397,8 @@ func (s *Conn) GetTaskInfo() (t *file.Tunnel, err error) { t.Status = true t.Flow = new(file.Flow) t.Remark = arr[3] + t.TargetAddr = arr[4] + t.Password = arr[5] t.NoStore = true } return diff --git a/lib/conn/link.go b/lib/conn/link.go index 6e342c2..ccd2e46 100644 --- a/lib/conn/link.go +++ b/lib/conn/link.go @@ -8,6 +8,18 @@ import ( "net" ) +type Secret struct { + Password string + Conn *Conn +} + +func NewSecret(p string, conn *Conn) *Secret { + return &Secret{ + Password: p, + Conn: conn, + } +} + type Link struct { Id int //id ConnType string //连接类型 diff --git a/lib/file/csv.go b/lib/file/csv.go index 01e4358..bd57081 100644 --- a/lib/file/csv.go +++ b/lib/file/csv.go @@ -14,7 +14,9 @@ var ( func GetCsvDb() *Csv { once.Do(func() { CsvDb = NewCsv(common.GetRunPath()) - CsvDb.Init() + CsvDb.LoadClientFromCsv() + CsvDb.LoadTaskFromCsv() + CsvDb.LoadHostFromCsv() }) return CsvDb } diff --git a/lib/file/file.go b/lib/file/file.go index 0160444..17bb968 100644 --- a/lib/file/file.go +++ b/lib/file/file.go @@ -3,9 +3,11 @@ package file import ( "encoding/csv" "errors" + "fmt" "github.com/cnlh/nps/lib/common" - "github.com/cnlh/nps/lib/lg" + "github.com/cnlh/nps/lib/crypt" "github.com/cnlh/nps/lib/rate" + "github.com/cnlh/nps/vender/github.com/astaxie/beego/logs" "net/http" "os" "path/filepath" @@ -33,17 +35,11 @@ type Csv struct { sync.Mutex } -func (s *Csv) Init() { - s.LoadClientFromCsv() - s.LoadTaskFromCsv() - s.LoadHostFromCsv() -} - func (s *Csv) StoreTasksToCsv() { // 创建文件 csvFile, err := os.Create(filepath.Join(s.RunPath, "conf", "tasks.csv")) if err != nil { - lg.Fatalf(err.Error()) + logs.Error(err.Error()) } defer csvFile.Close() writer := csv.NewWriter(csvFile) @@ -59,10 +55,13 @@ func (s *Csv) StoreTasksToCsv() { strconv.Itoa(task.Id), strconv.Itoa(task.Client.Id), task.Remark, + strconv.Itoa(int(task.Flow.ExportFlow)), + strconv.Itoa(int(task.Flow.InletFlow)), + task.Password, } err := writer.Write(record) if err != nil { - lg.Fatalf(err.Error()) + logs.Error(err.Error()) } } writer.Flush() @@ -90,20 +89,24 @@ func (s *Csv) LoadTaskFromCsv() { path := filepath.Join(s.RunPath, "conf", "tasks.csv") records, err := s.openFile(path) if err != nil { - lg.Fatalln("配置文件打开错误:", path) + logs.Error("Profile Opening Error:", path) + os.Exit(0) } var tasks []*Tunnel // 将每一行数据保存到内存slice中 for _, item := range records { post := &Tunnel{ - Port: common.GetIntNoErrByStr(item[0]), - Mode: item[1], - Target: item[2], - Status: common.GetBoolByStr(item[3]), - Id: common.GetIntNoErrByStr(item[4]), - Remark: item[6], + Port: common.GetIntNoErrByStr(item[0]), + Mode: item[1], + Target: item[2], + Status: common.GetBoolByStr(item[3]), + Id: common.GetIntNoErrByStr(item[4]), + Remark: item[6], + Password: item[9], } post.Flow = new(Flow) + post.Flow.ExportFlow = int64(common.GetIntNoErrByStr(item[7])) + post.Flow.InletFlow = int64(common.GetIntNoErrByStr(item[8])) if post.Client, err = s.GetClient(common.GetIntNoErrByStr(item[5])); err != nil { continue } @@ -142,10 +145,16 @@ func (s *Csv) GetIdByVerifyKey(vKey string, addr string) (int, error) { return 0, errors.New("not found") } -func (s *Csv) NewTask(t *Tunnel) { +func (s *Csv) NewTask(t *Tunnel) error { + for _, v := range s.Tasks { + if v.Mode == "secretServer" && v.Password == t.Password { + return errors.New(fmt.Sprintf("Secret mode keys %s must be unique", t.Password)) + } + } t.Flow = new(Flow) s.Tasks = append(s.Tasks, t) s.StoreTasksToCsv() + return nil } func (s *Csv) UpdateTask(t *Tunnel) error { @@ -171,6 +180,16 @@ func (s *Csv) DelTask(id int) error { return errors.New("不存在") } +//md5 password +func (s *Csv) GetSecretTask(p string) *Tunnel { + for _, v := range s.Tasks { + if crypt.Md5(v.Password) == p { + return v + } + } + return nil +} + func (s *Csv) GetTask(id int) (v *Tunnel, err error) { for _, v = range s.Tasks { if v.Id == id { @@ -205,6 +224,8 @@ func (s *Csv) StoreHostToCsv() { host.Remark, host.Location, strconv.Itoa(host.Id), + strconv.Itoa(int(host.Flow.ExportFlow)), + strconv.Itoa(int(host.Flow.InletFlow)), } err1 := writer.Write(record) if err1 != nil { @@ -219,7 +240,8 @@ func (s *Csv) LoadClientFromCsv() { path := filepath.Join(s.RunPath, "conf", "clients.csv") records, err := s.openFile(path) if err != nil { - lg.Fatalln("配置文件打开错误:", path) + logs.Error("Profile Opening Error:", path) + os.Exit(0) } var clients []*Client // 将每一行数据保存到内存slice中 @@ -236,6 +258,7 @@ func (s *Csv) LoadClientFromCsv() { Crypt: common.GetBoolByStr(item[6]), Compress: item[7], }, + MaxConn: common.GetIntNoErrByStr(item[10]), } if post.Id > s.ClientIncreaseId { s.ClientIncreaseId = post.Id @@ -255,7 +278,8 @@ func (s *Csv) LoadHostFromCsv() { path := filepath.Join(s.RunPath, "conf", "hosts.csv") records, err := s.openFile(path) if err != nil { - lg.Fatalln("配置文件打开错误:", path) + logs.Error("Profile Opening Error:", path) + os.Exit(0) } var hosts []*Host // 将每一行数据保存到内存slice中 @@ -273,6 +297,8 @@ func (s *Csv) LoadHostFromCsv() { continue } post.Flow = new(Flow) + post.Flow.ExportFlow = int64(common.GetIntNoErrByStr(item[8])) + post.Flow.InletFlow = int64(common.GetIntNoErrByStr(item[9])) hosts = append(hosts, post) if post.Id > s.HostIncreaseId { s.HostIncreaseId = post.Id @@ -350,7 +376,9 @@ func (s *Csv) NewClient(c *Client) { if c.Id == 0 { c.Id = s.GetClientId() } - c.Flow = new(Flow) + if c.Flow == nil { + c.Flow = new(Flow) + } s.Lock() defer s.Unlock() s.Clients = append(s.Clients, c) @@ -433,6 +461,8 @@ func (s *Csv) GetHostById(id int) (h *Host, err error) { //get key by host from x func (s *Csv) GetInfoByHost(host string, r *http.Request) (h *Host, err error) { var hosts []*Host + //Handling Ported Access + host = common.GetIpByAddr(host) for _, v := range s.Hosts { //Remove http(s) http(s)://a.proxy.com //*.proxy.com *.a.proxy.com Do some pan-parsing @@ -467,7 +497,7 @@ func (s *Csv) StoreClientsToCsv() { // 创建文件 csvFile, err := os.Create(filepath.Join(s.RunPath, "conf", "clients.csv")) if err != nil { - lg.Fatalln(err.Error()) + logs.Error(err.Error()) } defer csvFile.Close() writer := csv.NewWriter(csvFile) @@ -486,10 +516,11 @@ func (s *Csv) StoreClientsToCsv() { client.Cnf.Compress, strconv.Itoa(client.RateLimit), strconv.Itoa(int(client.Flow.FlowLimit)), + strconv.Itoa(int(client.MaxConn)), } err := writer.Write(record) if err != nil { - lg.Fatalln(err.Error()) + logs.Error(err.Error()) } } writer.Flush() diff --git a/lib/file/obj.go b/lib/file/obj.go index bbb4253..a9c5400 100644 --- a/lib/file/obj.go +++ b/lib/file/obj.go @@ -33,6 +33,8 @@ type Client struct { Rate *rate.Rate //速度控制 NoStore bool NoDisplay bool + MaxConn int //客户端最大连接数 + NowConn int //当前连接数 id int sync.RWMutex } @@ -62,18 +64,40 @@ func (s *Client) GetId() int { return s.id } +func (s *Client) CutConn() { + s.Lock() + defer s.Unlock() + s.NowConn++ +} + +func (s *Client) AddConn() { + s.Lock() + defer s.Unlock() + s.NowConn-- +} + +func (s *Client) GetConn() bool { + s.CutConn() + if s.MaxConn == 0 || s.NowConn < s.MaxConn { + return true + } + return false +} + type Tunnel struct { - Id int //Id - Port int //服务端监听端口 - Mode string //启动方式 - Target string //目标 - Status bool //设置是否开启 - RunStatus bool //当前运行状态 - Client *Client //所属客户端id - Ports string //客户端与服务端传递 - Flow *Flow - Remark string //备注 - NoStore bool + Id int //Id + Port int //服务端监听端口 + Mode string //启动方式 + Target string //目标 + Status bool //设置是否开启 + RunStatus bool //当前运行状态 + Client *Client //所属客户端id + Ports string //客户端与服务端传递 + Flow *Flow + Password string //私密模式密码,唯一 + Remark string //备注 + TargetAddr string + NoStore bool } type Config struct { @@ -114,15 +138,3 @@ func (s *Host) GetRandomTarget() string { } return s.TargetArr[s.NowIndex] } - -//深拷贝Config -func DeepCopyConfig(c *Config) *Config { - return &Config{ - U: c.U, - P: c.P, - Compress: c.Compress, - Crypt: c.Crypt, - CompressEncode: c.CompressEncode, - CompressDecode: c.CompressDecode, - } -} diff --git a/lib/lg/log.go b/lib/lg/log.go deleted file mode 100644 index 985dee7..0000000 --- a/lib/lg/log.go +++ /dev/null @@ -1,45 +0,0 @@ -package lg - -import ( - "log" - "os" - "path/filepath" - "runtime" -) - -var Log *log.Logger - -func InitLogFile(f string, isStdout bool, logPath string) { - var prefix string - if !isStdout { - logFile, err := os.OpenFile(filepath.Join(logPath, f+"_log.txt"), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0766) - if err != nil { - log.Fatalln("open file error !", err) - } - if runtime.GOOS == "windows" { - prefix = "\r\n" - } - Log = log.New(logFile, prefix, log.Ldate|log.Ltime) - } else { - Log = log.New(os.Stdout, "", log.Ldate|log.Ltime) - } -} - -func Println(v ...interface{}) { - Log.Println(v...) -} - -func Fatalln(v ...interface{}) { - Log.SetPrefix("error ") - Log.Fatalln(v...) - Log.SetPrefix("") -} -func Fatalf(format string, v ...interface{}) { - Log.SetPrefix("error ") - Log.Fatalf(format, v...) - Log.SetPrefix("") -} - -func Printf(format string, v ...interface{}) { - Log.Printf(format, v...) -} diff --git a/lib/version/version.go b/lib/version/version.go new file mode 100644 index 0000000..ddc9b4f --- /dev/null +++ b/lib/version/version.go @@ -0,0 +1,8 @@ +package version + +const VERSION = "0.0.16" +const VERSION_OK = "vrok" + +func GetVersion() string { + return VERSION +} diff --git a/server/proxy/base.go b/server/proxy/base.go index 5f560a4..6660777 100644 --- a/server/proxy/base.go +++ b/server/proxy/base.go @@ -17,8 +17,8 @@ type Service interface { Close() error } -//server base struct -type server struct { +//Server BaseServer struct +type BaseServer struct { id int bridge *bridge.Bridge task *file.Tunnel @@ -26,21 +26,30 @@ type server struct { sync.Mutex } -func (s *server) FlowAdd(in, out int64) { +func NewBaseServer(bridge *bridge.Bridge, task *file.Tunnel) *BaseServer { + return &BaseServer{ + bridge: bridge, + task: task, + errorContent: nil, + Mutex: sync.Mutex{}, + } +} + +func (s *BaseServer) FlowAdd(in, out int64) { s.Lock() defer s.Unlock() s.task.Flow.ExportFlow += out s.task.Flow.InletFlow += in } -func (s *server) FlowAddHost(host *file.Host, in, out int64) { +func (s *BaseServer) FlowAddHost(host *file.Host, in, out int64) { s.Lock() defer s.Unlock() host.Flow.ExportFlow += out host.Flow.InletFlow += in } -func (s *server) linkCopy(link *conn.Link, c *conn.Conn, rb []byte, tunnel *conn.Conn, flow *file.Flow) { +func (s *BaseServer) linkCopy(link *conn.Link, c *conn.Conn, rb []byte, tunnel *conn.Conn, flow *file.Flow) { if rb != nil { if _, err := tunnel.SendMsg(rb, link); err != nil { c.Close() @@ -68,16 +77,17 @@ func (s *server) linkCopy(link *conn.Link, c *conn.Conn, rb []byte, tunnel *conn } <-link.StatusCh } + s.task.Client.AddConn() pool.PutBufPoolCopy(buf) } -func (s *server) writeConnFail(c net.Conn) { +func (s *BaseServer) writeConnFail(c net.Conn) { c.Write([]byte(common.ConnectionFailBytes)) c.Write(s.errorContent) } //权限认证 -func (s *server) auth(r *http.Request, c *conn.Conn, u, p string) error { +func (s *BaseServer) auth(r *http.Request, c *conn.Conn, u, p string) error { if u != "" && p != "" && !common.CheckAuth(r, u, p) { c.Write([]byte(common.UnauthorizedBytes)) c.Close() @@ -86,9 +96,23 @@ func (s *server) auth(r *http.Request, c *conn.Conn, u, p string) error { return nil } -func (s *server) checkFlow() error { +func (s *BaseServer) checkFlow() error { if s.task.Client.Flow.FlowLimit > 0 && (s.task.Client.Flow.FlowLimit<<20) < (s.task.Client.Flow.ExportFlow+s.task.Client.Flow.InletFlow) { return errors.New("Traffic exceeded") } return nil } + +//与客户端建立通道 +func (s *BaseServer) DealClient(c *conn.Conn, addr string, rb []byte) error { + link := conn.NewLink(s.task.Client.GetId(), common.CONN_TCP, addr, s.task.Client.Cnf.CompressEncode, s.task.Client.Cnf.CompressDecode, s.task.Client.Cnf.Crypt, c, s.task.Flow, nil, s.task.Client.Rate, nil) + + if tunnel, err := s.bridge.SendLinkInfo(s.task.Client.Id, link, c.Conn.RemoteAddr().String()); err != nil { + c.Close() + return err + } else { + link.Run(true) + s.linkCopy(link, c, rb, tunnel, s.task.Flow) + } + return nil +} diff --git a/server/proxy/http.go b/server/proxy/http.go index 4816ad6..642a501 100644 --- a/server/proxy/http.go +++ b/server/proxy/http.go @@ -7,17 +7,18 @@ import ( "github.com/cnlh/nps/lib/common" "github.com/cnlh/nps/lib/conn" "github.com/cnlh/nps/lib/file" - "github.com/cnlh/nps/lib/lg" "github.com/cnlh/nps/vender/github.com/astaxie/beego" + "github.com/cnlh/nps/vender/github.com/astaxie/beego/logs" "net/http" "net/http/httputil" + "os" "path/filepath" "strconv" "sync" ) type httpServer struct { - server + BaseServer httpPort int //http端口 httpsPort int //https监听端口 pemPath string @@ -31,7 +32,7 @@ func NewHttp(bridge *bridge.Bridge, c *file.Tunnel) *httpServer { pemPath := beego.AppConfig.String("pemPath") keyPath := beego.AppConfig.String("keyPath") return &httpServer{ - server: server{ + BaseServer: BaseServer{ task: c, bridge: bridge, Mutex: sync.Mutex{}, @@ -54,26 +55,30 @@ func (s *httpServer) Start() error { if s.httpPort > 0 { http = s.NewServer(s.httpPort) go func() { - lg.Println("Start http listener, port is", s.httpPort) + logs.Info("Start http listener, port is", s.httpPort) err := http.ListenAndServe() if err != nil { - lg.Fatalln(err) + logs.Error(err) + os.Exit(0) } }() } if s.httpsPort > 0 { if !common.FileExists(s.pemPath) { - lg.Fatalf("ssl certFile %s is not exist", s.pemPath) + logs.Error("ssl certFile %s is not exist", s.pemPath) + os.Exit(0) } if !common.FileExists(s.keyPath) { - lg.Fatalf("ssl keyFile %s exist", s.keyPath) + logs.Error("ssl keyFile %s exist", s.keyPath) + os.Exit(0) } https = s.NewServer(s.httpsPort) go func() { - lg.Println("Start https listener, port is", s.httpsPort) + logs.Info("Start https listener, port is", s.httpsPort) err := https.ListenAndServeTLS(s.pemPath, s.keyPath) if err != nil { - lg.Fatalln(err) + logs.Error(err) + os.Exit(0) } }() } @@ -118,9 +123,14 @@ func (s *httpServer) process(c *conn.Conn, r *http.Request) { err error ) if host, err = file.GetCsvDb().GetInfoByHost(r.Host, r); err != nil { - lg.Printf("the url %s %s can't be parsed!", r.Host, r.RequestURI) + logs.Notice("the url %s %s can't be parsed!", r.Host, r.RequestURI) goto end + } else if !host.Client.GetConn() { + logs.Notice("Connections exceed the current client %d limit", host.Client.Id) + c.Close() + return } else { + logs.Trace("New http(s) connection,clientId %d,host %s,url %s,remote address %s", host.Client.Id, r.Host, r.URL, r.RemoteAddr) lastHost = host } for { @@ -137,22 +147,24 @@ func (s *httpServer) process(c *conn.Conn, r *http.Request) { } lk = conn.NewLink(host.Client.GetId(), common.CONN_TCP, host.GetRandomTarget(), host.Client.Cnf.CompressEncode, host.Client.Cnf.CompressDecode, host.Client.Cnf.Crypt, c, host.Flow, nil, host.Client.Rate, nil) if tunnel, err = s.bridge.SendLinkInfo(host.Client.Id, lk, c.Conn.RemoteAddr().String()); err != nil { - lg.Println(err) + logs.Notice(err) break } lk.Run(true) isConn = false } else { r, err = http.ReadRequest(bufio.NewReader(c)) + logs.Trace("New http(s) connection,clientId %d,host %s,url %s,remote address %s", host.Client.Id, r.Host, r.URL, r.RemoteAddr) if err != nil { break } if host, err = file.GetCsvDb().GetInfoByHost(r.Host, r); err != nil { - lg.Printf("the url %s %s is not found !", r.Host, r.RequestURI) + logs.Notice("the url %s %s can't be parsed!", r.Host, r.RequestURI) break } else if host != lastHost { lastHost = host isConn = true + host.Client.AddConn() goto start } } @@ -176,6 +188,9 @@ end: tunnel.SendMsg([]byte(common.IO_EOF), lk) } c.Close() + if host != nil { + host.Client.AddConn() + } } func (s *httpServer) NewServer(port int) *http.Server { diff --git a/server/proxy/socks5.go b/server/proxy/socks5.go index eaafc4e..8e01fb3 100755 --- a/server/proxy/socks5.go +++ b/server/proxy/socks5.go @@ -7,7 +7,7 @@ import ( "github.com/cnlh/nps/lib/common" "github.com/cnlh/nps/lib/conn" "github.com/cnlh/nps/lib/file" - "github.com/cnlh/nps/lib/lg" + "github.com/cnlh/nps/vender/github.com/astaxie/beego/logs" "io" "net" "strconv" @@ -48,7 +48,7 @@ const ( ) type Sock5ModeServer struct { - server + BaseServer listener net.Listener } @@ -67,7 +67,7 @@ func (s *Sock5ModeServer) handleRequest(c net.Conn) { _, err := io.ReadFull(c, header) if err != nil { - lg.Println("illegal request", err) + logs.Warn("illegal request", err) c.Close() return } @@ -165,7 +165,6 @@ func (s *Sock5ModeServer) handleBind(c net.Conn) { //udp func (s *Sock5ModeServer) handleUDP(c net.Conn) { - lg.Println("UDP Associate") /* +----+------+------+----------+----------+----------+ |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | @@ -178,7 +177,7 @@ func (s *Sock5ModeServer) handleUDP(c net.Conn) { // relay udp datagram silently, without any notification to the requesting client if buf[2] != 0 { // does not support fragmentation, drop it - lg.Println("does not support fragmentation, drop") + logs.Warn("does not support fragmentation, drop") dummy := make([]byte, maxUDPPacketSize) c.Read(dummy) } @@ -190,13 +189,13 @@ func (s *Sock5ModeServer) handleUDP(c net.Conn) { func (s *Sock5ModeServer) handleConn(c net.Conn) { buf := make([]byte, 2) if _, err := io.ReadFull(c, buf); err != nil { - lg.Println("negotiation err", err) + logs.Warn("negotiation err", err) c.Close() return } if version := buf[0]; version != 5 { - lg.Println("only support socks5, request from: ", c.RemoteAddr()) + logs.Warn("only support socks5, request from: ", c.RemoteAddr()) c.Close() return } @@ -204,7 +203,7 @@ func (s *Sock5ModeServer) handleConn(c net.Conn) { methods := make([]byte, nMethods) if len, err := c.Read(methods); len != int(nMethods) || err != nil { - lg.Println("wrong method") + logs.Warn("wrong method") c.Close() return } @@ -213,7 +212,7 @@ func (s *Sock5ModeServer) handleConn(c net.Conn) { c.Write(buf) if err := s.Auth(c); err != nil { c.Close() - lg.Println("Validation failed:", err) + logs.Warn("Validation failed:", err) return } } else { @@ -271,9 +270,15 @@ func (s *Sock5ModeServer) Start() error { if strings.Contains(err.Error(), "use of closed network connection") { break } - lg.Fatalln("accept error: ", err) + logs.Warn("accept error: ", err) + } + if s.task.Client.GetConn() { + logs.Trace("New socks5 connection,client %d,remote address %s", s.task.Client.Id, conn.RemoteAddr()) + go s.handleConn(conn) + } else { + logs.Warn("Connections exceed the current client %d limit", s.task.Client.Id) + conn.Close() } - go s.handleConn(conn) } return nil } diff --git a/server/proxy/tcp.go b/server/proxy/tcp.go index d38f8f8..d58fceb 100755 --- a/server/proxy/tcp.go +++ b/server/proxy/tcp.go @@ -6,15 +6,16 @@ import ( "github.com/cnlh/nps/lib/common" "github.com/cnlh/nps/lib/conn" "github.com/cnlh/nps/lib/file" - "github.com/cnlh/nps/lib/lg" "github.com/cnlh/nps/vender/github.com/astaxie/beego" + "github.com/cnlh/nps/vender/github.com/astaxie/beego/logs" "net" + "os" "path/filepath" "strings" ) type TunnelModeServer struct { - server + BaseServer process process listener *net.TCPListener } @@ -41,24 +42,16 @@ func (s *TunnelModeServer) Start() error { if strings.Contains(err.Error(), "use of closed network connection") { break } - lg.Println(err) + logs.Info(err) continue } - go s.process(conn.NewConn(c), s) - } - return nil -} - -//与客户端建立通道 -func (s *TunnelModeServer) dealClient(c *conn.Conn, cnf *file.Config, addr string, method string, rb []byte) error { - link := conn.NewLink(s.task.Client.GetId(), common.CONN_TCP, addr, cnf.CompressEncode, cnf.CompressDecode, cnf.Crypt, c, s.task.Flow, nil, s.task.Client.Rate, nil) - - if tunnel, err := s.bridge.SendLinkInfo(s.task.Client.Id, link, c.Conn.RemoteAddr().String()); err != nil { - c.Close() - return err - } else { - link.Run(true) - s.linkCopy(link, c, rb, tunnel, s.task.Flow) + if s.task.Client.GetConn() { + logs.Trace("New tcp connection,client %d,remote address %s", s.task.Client.Id, c.RemoteAddr()) + go s.process(conn.NewConn(c), s) + } else { + logs.Info("Connections exceed the current client %d limit", s.task.Client.Id) + c.Close() + } } return nil } @@ -70,17 +63,18 @@ func (s *TunnelModeServer) Close() error { //web管理方式 type WebServer struct { - server + BaseServer } //开始 func (s *WebServer) Start() error { p, _ := beego.AppConfig.Int("httpport") if !common.TestTcpPort(p) { - lg.Fatalf("Web management port %d is occupied", p) + logs.Error("Web management port %d is occupied", p) + os.Exit(0) } beego.BConfig.WebConfig.Session.SessionOn = true - lg.Println("Web management start, access port is", p) + logs.Info("Web management start, access port is", p) beego.SetStaticPath("/static", filepath.Join(common.GetRunPath(), "web", "static")) beego.SetViewsPath(filepath.Join(common.GetRunPath(), "web", "views")) beego.Run() @@ -102,23 +96,23 @@ type process func(c *conn.Conn, s *TunnelModeServer) error //tcp隧道模式 func ProcessTunnel(c *conn.Conn, s *TunnelModeServer) error { - return s.dealClient(c, s.task.Client.Cnf, s.task.Target, "", nil) + return s.DealClient(c, s.task.Target, nil) } //http代理模式 func ProcessHttp(c *conn.Conn, s *TunnelModeServer) error { - method, addr, rb, err, r := c.GetHost() + _, addr, rb, err, r := c.GetHost() if err != nil { c.Close() - lg.Println(err) + logs.Info(err) return err } if r.Method == "CONNECT" { c.Write([]byte("HTTP/1.1 200 Connection Established\r\n")) - rb = nil //reset + rb = nil } 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.Cnf, addr, method, rb) + return s.DealClient(c, addr, rb) } diff --git a/server/proxy/udp.go b/server/proxy/udp.go index 70b26f6..42efae7 100755 --- a/server/proxy/udp.go +++ b/server/proxy/udp.go @@ -6,12 +6,13 @@ import ( "github.com/cnlh/nps/lib/conn" "github.com/cnlh/nps/lib/file" "github.com/cnlh/nps/lib/pool" + "github.com/cnlh/nps/vender/github.com/astaxie/beego/logs" "net" "strings" ) type UdpModeServer struct { - server + BaseServer listener *net.UDPConn udpMap map[string]*conn.Conn } @@ -40,6 +41,7 @@ func (s *UdpModeServer) Start() error { } continue } + logs.Trace("New ydo connection,client %d,remote address %s", s.task.Client.Id, addr) go s.process(addr, buf[:n]) } return nil diff --git a/server/server.go b/server/server.go index d33e43d..2fa091b 100644 --- a/server/server.go +++ b/server/server.go @@ -5,10 +5,12 @@ import ( "github.com/cnlh/nps/bridge" "github.com/cnlh/nps/lib/common" "github.com/cnlh/nps/lib/file" - "github.com/cnlh/nps/lib/lg" "github.com/cnlh/nps/server/proxy" "github.com/cnlh/nps/server/tool" "github.com/cnlh/nps/vender/github.com/astaxie/beego" + "github.com/cnlh/nps/vender/github.com/astaxie/beego/logs" + "os" + "time" ) var ( @@ -31,7 +33,6 @@ func InitFromCsv() { //Initialize services in server-side files for _, v := range file.GetCsvDb().Tasks { if v.Status { - lg.Println("task start info: mode:", v.Mode, "port:", v.Port) AddTask(v) } } @@ -45,6 +46,19 @@ func DealBridgeTask() { case id := <-Bridge.CloseClient: DelTunnelAndHostByClientId(id) file.GetCsvDb().DelClient(id) + case s := <-Bridge.SecretChan: + logs.Trace("New secret connection, addr", s.Conn.Conn.RemoteAddr()) + if t := file.GetCsvDb().GetSecretTask(s.Password); t != nil { + if !t.Client.GetConn() { + logs.Info("Connections exceed the current client %d limit", t.Client.Id) + s.Conn.Close() + } else { + go proxy.NewBaseServer(Bridge, t).DealClient(s.Conn, t.Target, nil) + } + } else { + logs.Trace("This key %s cannot be processed", s.Password) + s.Conn.Close() + } } } } @@ -53,18 +67,19 @@ 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 { - lg.Fatalln("服务端开启失败", err) + logs.Error("服务端开启失败", err) + os.Exit(0) } else { - lg.Printf("Server startup, the bridge type is %s, the bridge port is %d", bridgeType, bridgePort) + logs.Info("Server startup, the bridge type is %s, the bridge port is %d", bridgeType, bridgePort) } go DealBridgeTask() if svr := NewMode(Bridge, cnf); svr != nil { if err := svr.Start(); err != nil { - lg.Fatalln(err) + logs.Error(err) } RunList[cnf.Id] = svr } else { - lg.Fatalln("启动模式%s不正确", cnf.Mode) + logs.Error("Incorrect startup mode %s", cnf.Mode) } } @@ -103,12 +118,12 @@ func StopServer(id int) error { if err := svr.Close(); err != nil { return err } - if t, err := file.GetCsvDb().GetTask(id); err != nil { - return err - } else { - t.Status = false - file.GetCsvDb().UpdateTask(t) - } + } + if t, err := file.GetCsvDb().GetTask(id); err != nil { + return err + } else { + t.Status = false + file.GetCsvDb().UpdateTask(t) } delete(RunList, id) return nil @@ -118,15 +133,24 @@ func StopServer(id int) error { //add task func AddTask(t *file.Tunnel) error { + if t.Mode == "secretServer" { + logs.Info("secret task %s start ", t.Remark) + RunList[t.Id] = nil + return nil + } if b := tool.TestServerPort(t.Port, t.Mode); !b && t.Mode != "httpHostServer" { - lg.Printf("taskId %d start error port %d Open Failed", t.Id, t.Port) + logs.Error("taskId %d start error port %d open failed", t.Id, t.Port) return errors.New("the port open error") } + if minute, err := beego.AppConfig.Int("flowStoreInterval"); err == nil && minute > 0 { + go flowSession(time.Minute * time.Duration(minute)) + } if svr := NewMode(Bridge, t); svr != nil { + logs.Info("tunnel task %s start mode:%s port %d", t.Remark, t.Mode, t.Port) RunList[t.Id] = svr go func() { if err := svr.Start(); err != nil { - lg.Println("clientId %d taskId %d start error %s", t.Client.Id, t.Id, err) + logs.Error("clientId %d taskId %d start error %s", t.Client.Id, t.Id, err) delete(RunList, t.Id) return } @@ -272,5 +296,21 @@ func GetDashboardData() map[string]int { data["udpServerCount"] += 1 } } + tcpCount := 0 + for _, v := range file.GetCsvDb().Clients { + tcpCount += v.NowConn + } + data["tcpCount"] = tcpCount return data } + +func flowSession(m time.Duration) { + ticker := time.NewTicker(m) + for { + select { + case <-ticker.C: + file.GetCsvDb().StoreHostToCsv() + file.GetCsvDb().StoreTasksToCsv() + } + } +} diff --git a/server/tool/utils.go b/server/tool/utils.go index 19f641e..37399df 100644 --- a/server/tool/utils.go +++ b/server/tool/utils.go @@ -13,6 +13,9 @@ func init() { } func TestServerPort(p int, m string) (b bool) { + if p > 65535 || p <= 0 { + return false + } if len(ports) != 0 { if !common.InIntArr(ports, p) { return false diff --git a/web/controllers/client.go b/web/controllers/client.go index 10e9153..c227869 100644 --- a/web/controllers/client.go +++ b/web/controllers/client.go @@ -42,6 +42,7 @@ func (s *ClientController) Add() { Crypt: s.GetBoolNoErr("crypt"), }, RateLimit: s.GetIntNoErr("rate_limit"), + MaxConn: s.GetIntNoErr("max_conn"), Flow: &file.Flow{ ExportFlow: 0, InletFlow: 0, @@ -94,6 +95,7 @@ func (s *ClientController) Edit() { c.Cnf.Crypt = s.GetBoolNoErr("crypt") c.Flow.FlowLimit = int64(s.GetIntNoErr("flow_limit")) c.RateLimit = s.GetIntNoErr("rate_limit") + c.MaxConn = s.GetIntNoErr("max_conn") if c.Rate != nil { c.Rate.Stop() } diff --git a/web/controllers/index.go b/web/controllers/index.go index 4af86cc..34d20d2 100755 --- a/web/controllers/index.go +++ b/web/controllers/index.go @@ -44,6 +44,12 @@ func (s *IndexController) Http() { s.display("index/list") } +func (s *IndexController) Secret() { + s.SetInfo("私密代理管理") + s.SetType("secretServer") + s.display("index/list") +} + func (s *IndexController) Host() { s.SetInfo("host模式管理") s.SetType("hostServer") @@ -74,13 +80,14 @@ 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"), - 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"), + 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.") @@ -126,6 +133,7 @@ func (s *IndexController) Edit() { t.Port = s.GetIntNoErr("port") t.Mode = s.GetString("type") t.Target = s.GetString("target") + t.Password = s.GetString("password") t.Id = id t.Remark = s.GetString("remark") if t.Client, err = file.GetCsvDb().GetClient(s.GetIntNoErr("client_id")); err != nil { diff --git a/web/views/client/add.html b/web/views/client/add.html index d3b651c..bd45213 100755 --- a/web/views/client/add.html +++ b/web/views/client/add.html @@ -16,6 +16,10 @@ +