From 7030d16e80770ab1c7c280dfabb4f4985ce1926b Mon Sep 17 00:00:00 2001 From: fatedier <512654112@qq.com> Date: Wed, 27 Jan 2016 21:24:36 +0800 Subject: [PATCH] First available commit --- .gitignore | 4 + Godeps/Godeps.json | 18 + Godeps/Readme | 5 + Godeps/_workspace/.gitignore | 2 + .../src/github.com/astaxie/beego/LICENSE | 13 + .../github.com/astaxie/beego/logs/README.md | 63 ++++ .../src/github.com/astaxie/beego/logs/conn.go | 116 ++++++ .../github.com/astaxie/beego/logs/console.go | 95 +++++ .../github.com/astaxie/beego/logs/es/es.go | 76 ++++ .../src/github.com/astaxie/beego/logs/file.go | 283 ++++++++++++++ .../src/github.com/astaxie/beego/logs/log.go | 350 ++++++++++++++++++ .../src/github.com/astaxie/beego/logs/smtp.go | 165 +++++++++ .../astaxie/beego/utils/captcha/LICENSE | 19 + .../src/github.com/vaughan0/go-ini/LICENSE | 14 + .../src/github.com/vaughan0/go-ini/README.md | 70 ++++ .../src/github.com/vaughan0/go-ini/ini.go | 123 ++++++ .../src/github.com/vaughan0/go-ini/test.ini | 2 + Makefile | 15 + cmd/frpc/config.go | 89 +++++ cmd/frpc/control.go | 67 ++++ cmd/frpc/main.go | 30 ++ cmd/frps/config.go | 95 +++++ cmd/frps/control.go | 134 +++++++ cmd/frps/main.go | 26 ++ conf/frpc.ini | 14 + conf/frps.ini | 15 + pkg/models/client.go | 70 ++++ pkg/models/msg.go | 27 ++ pkg/models/server.go | 116 ++++++ pkg/utils/conn/conn.go | 115 ++++++ pkg/utils/log/log.go | 64 ++++ 31 files changed, 2295 insertions(+) create mode 100644 Godeps/Godeps.json create mode 100644 Godeps/Readme create mode 100644 Godeps/_workspace/.gitignore create mode 100644 Godeps/_workspace/src/github.com/astaxie/beego/LICENSE create mode 100644 Godeps/_workspace/src/github.com/astaxie/beego/logs/README.md create mode 100644 Godeps/_workspace/src/github.com/astaxie/beego/logs/conn.go create mode 100644 Godeps/_workspace/src/github.com/astaxie/beego/logs/console.go create mode 100644 Godeps/_workspace/src/github.com/astaxie/beego/logs/es/es.go create mode 100644 Godeps/_workspace/src/github.com/astaxie/beego/logs/file.go create mode 100644 Godeps/_workspace/src/github.com/astaxie/beego/logs/log.go create mode 100644 Godeps/_workspace/src/github.com/astaxie/beego/logs/smtp.go create mode 100644 Godeps/_workspace/src/github.com/astaxie/beego/utils/captcha/LICENSE create mode 100644 Godeps/_workspace/src/github.com/vaughan0/go-ini/LICENSE create mode 100644 Godeps/_workspace/src/github.com/vaughan0/go-ini/README.md create mode 100644 Godeps/_workspace/src/github.com/vaughan0/go-ini/ini.go create mode 100644 Godeps/_workspace/src/github.com/vaughan0/go-ini/test.ini create mode 100644 Makefile create mode 100644 cmd/frpc/config.go create mode 100644 cmd/frpc/control.go create mode 100644 cmd/frpc/main.go create mode 100644 cmd/frps/config.go create mode 100644 cmd/frps/control.go create mode 100644 cmd/frps/main.go create mode 100644 conf/frpc.ini create mode 100644 conf/frps.ini create mode 100644 pkg/models/client.go create mode 100644 pkg/models/msg.go create mode 100644 pkg/models/server.go create mode 100644 pkg/utils/conn/conn.go create mode 100644 pkg/utils/log/log.go diff --git a/.gitignore b/.gitignore index daf913b..fab4548 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,7 @@ _testmain.go *.exe *.test *.prof + +# Self +bin/ + diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json new file mode 100644 index 0000000..70fd281 --- /dev/null +++ b/Godeps/Godeps.json @@ -0,0 +1,18 @@ +{ + "ImportPath": "frp", + "GoVersion": "go1.4", + "Packages": [ + "./..." + ], + "Deps": [ + { + "ImportPath": "github.com/astaxie/beego/logs", + "Comment": "v1.5.0-9-gfb7314f", + "Rev": "fb7314f8ac86b83ccd34386518d97cf2363e2ae5" + }, + { + "ImportPath": "github.com/vaughan0/go-ini", + "Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1" + } + ] +} diff --git a/Godeps/Readme b/Godeps/Readme new file mode 100644 index 0000000..4cdaa53 --- /dev/null +++ b/Godeps/Readme @@ -0,0 +1,5 @@ +This directory tree is generated automatically by godep. + +Please do not edit. + +See https://github.com/tools/godep for more information. diff --git a/Godeps/_workspace/.gitignore b/Godeps/_workspace/.gitignore new file mode 100644 index 0000000..f037d68 --- /dev/null +++ b/Godeps/_workspace/.gitignore @@ -0,0 +1,2 @@ +/pkg +/bin diff --git a/Godeps/_workspace/src/github.com/astaxie/beego/LICENSE b/Godeps/_workspace/src/github.com/astaxie/beego/LICENSE new file mode 100644 index 0000000..5dbd424 --- /dev/null +++ b/Godeps/_workspace/src/github.com/astaxie/beego/LICENSE @@ -0,0 +1,13 @@ +Copyright 2014 astaxie + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/astaxie/beego/logs/README.md b/Godeps/_workspace/src/github.com/astaxie/beego/logs/README.md new file mode 100644 index 0000000..57d7abc --- /dev/null +++ b/Godeps/_workspace/src/github.com/astaxie/beego/logs/README.md @@ -0,0 +1,63 @@ +## logs +logs is a Go logs manager. It can use many logs adapters. The repo is inspired by `database/sql` . + + +## How to install? + + go get github.com/astaxie/beego/logs + + +## What adapters are supported? + +As of now this logs support console, file,smtp and conn. + + +## How to use it? + +First you must import it + + import ( + "github.com/astaxie/beego/logs" + ) + +Then init a Log (example with console adapter) + + log := NewLogger(10000) + log.SetLogger("console", "") + +> the first params stand for how many channel + +Use it like this: + + log.Trace("trace") + log.Info("info") + log.Warn("warning") + log.Debug("debug") + log.Critical("critical") + + +## File adapter + +Configure file adapter like this: + + log := NewLogger(10000) + log.SetLogger("file", `{"filename":"test.log"}`) + + +## Conn adapter + +Configure like this: + + log := NewLogger(1000) + log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`) + log.Info("info") + + +## Smtp adapter + +Configure like this: + + log := NewLogger(10000) + log.SetLogger("smtp", `{"username":"beegotest@gmail.com","password":"xxxxxxxx","host":"smtp.gmail.com:587","sendTos":["xiemengjun@gmail.com"]}`) + log.Critical("sendmail critical") + time.Sleep(time.Second * 30) diff --git a/Godeps/_workspace/src/github.com/astaxie/beego/logs/conn.go b/Godeps/_workspace/src/github.com/astaxie/beego/logs/conn.go new file mode 100644 index 0000000..2240eec --- /dev/null +++ b/Godeps/_workspace/src/github.com/astaxie/beego/logs/conn.go @@ -0,0 +1,116 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package logs + +import ( + "encoding/json" + "io" + "log" + "net" +) + +// ConnWriter implements LoggerInterface. +// it writes messages in keep-live tcp connection. +type ConnWriter struct { + lg *log.Logger + innerWriter io.WriteCloser + ReconnectOnMsg bool `json:"reconnectOnMsg"` + Reconnect bool `json:"reconnect"` + Net string `json:"net"` + Addr string `json:"addr"` + Level int `json:"level"` +} + +// create new ConnWrite returning as LoggerInterface. +func NewConn() LoggerInterface { + conn := new(ConnWriter) + conn.Level = LevelTrace + return conn +} + +// init connection writer with json config. +// json config only need key "level". +func (c *ConnWriter) Init(jsonconfig string) error { + return json.Unmarshal([]byte(jsonconfig), c) +} + +// write message in connection. +// if connection is down, try to re-connect. +func (c *ConnWriter) WriteMsg(msg string, level int) error { + if level > c.Level { + return nil + } + if c.neddedConnectOnMsg() { + err := c.connect() + if err != nil { + return err + } + } + + if c.ReconnectOnMsg { + defer c.innerWriter.Close() + } + c.lg.Println(msg) + return nil +} + +// implementing method. empty. +func (c *ConnWriter) Flush() { + +} + +// destroy connection writer and close tcp listener. +func (c *ConnWriter) Destroy() { + if c.innerWriter != nil { + c.innerWriter.Close() + } +} + +func (c *ConnWriter) connect() error { + if c.innerWriter != nil { + c.innerWriter.Close() + c.innerWriter = nil + } + + conn, err := net.Dial(c.Net, c.Addr) + if err != nil { + return err + } + + if tcpConn, ok := conn.(*net.TCPConn); ok { + tcpConn.SetKeepAlive(true) + } + + c.innerWriter = conn + c.lg = log.New(conn, "", log.Ldate|log.Ltime) + return nil +} + +func (c *ConnWriter) neddedConnectOnMsg() bool { + if c.Reconnect { + c.Reconnect = false + return true + } + + if c.innerWriter == nil { + return true + } + + return c.ReconnectOnMsg +} + +func init() { + Register("conn", NewConn) +} diff --git a/Godeps/_workspace/src/github.com/astaxie/beego/logs/console.go b/Godeps/_workspace/src/github.com/astaxie/beego/logs/console.go new file mode 100644 index 0000000..ce7ecd5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/astaxie/beego/logs/console.go @@ -0,0 +1,95 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package logs + +import ( + "encoding/json" + "log" + "os" + "runtime" +) + +type Brush func(string) string + +func NewBrush(color string) Brush { + pre := "\033[" + reset := "\033[0m" + return func(text string) string { + return pre + color + "m" + text + reset + } +} + +var colors = []Brush{ + NewBrush("1;37"), // Emergency white + NewBrush("1;36"), // Alert cyan + NewBrush("1;35"), // Critical magenta + NewBrush("1;31"), // Error red + NewBrush("1;33"), // Warning yellow + NewBrush("1;32"), // Notice green + NewBrush("1;34"), // Informational blue + NewBrush("1;34"), // Debug blue +} + +// ConsoleWriter implements LoggerInterface and writes messages to terminal. +type ConsoleWriter struct { + lg *log.Logger + Level int `json:"level"` +} + +// create ConsoleWriter returning as LoggerInterface. +func NewConsole() LoggerInterface { + cw := &ConsoleWriter{ + lg: log.New(os.Stdout, "", log.Ldate|log.Ltime), + Level: LevelDebug, + } + return cw +} + +// init console logger. +// jsonconfig like '{"level":LevelTrace}'. +func (c *ConsoleWriter) Init(jsonconfig string) error { + if len(jsonconfig) == 0 { + return nil + } + return json.Unmarshal([]byte(jsonconfig), c) +} + +// write message in console. +func (c *ConsoleWriter) WriteMsg(msg string, level int) error { + if level > c.Level { + return nil + } + if goos := runtime.GOOS; goos == "windows" { + c.lg.Println(msg) + return nil + } + c.lg.Println(colors[level](msg)) + + return nil +} + +// implementing method. empty. +func (c *ConsoleWriter) Destroy() { + +} + +// implementing method. empty. +func (c *ConsoleWriter) Flush() { + +} + +func init() { + Register("console", NewConsole) +} diff --git a/Godeps/_workspace/src/github.com/astaxie/beego/logs/es/es.go b/Godeps/_workspace/src/github.com/astaxie/beego/logs/es/es.go new file mode 100644 index 0000000..3a73d4d --- /dev/null +++ b/Godeps/_workspace/src/github.com/astaxie/beego/logs/es/es.go @@ -0,0 +1,76 @@ +package es + +import ( + "encoding/json" + "errors" + "fmt" + "net" + "net/url" + "time" + + "github.com/astaxie/beego/logs" + "github.com/belogik/goes" +) + +func NewES() logs.LoggerInterface { + cw := &esLogger{ + Level: logs.LevelDebug, + } + return cw +} + +type esLogger struct { + *goes.Connection + DSN string `json:"dsn"` + Level int `json:"level"` +} + +// {"dsn":"http://localhost:9200/","level":1} +func (el *esLogger) Init(jsonconfig string) error { + err := json.Unmarshal([]byte(jsonconfig), el) + if err != nil { + return err + } + if el.DSN == "" { + return errors.New("empty dsn") + } else if u, err := url.Parse(el.DSN); err != nil { + return err + } else if u.Path == "" { + return errors.New("missing prefix") + } else if host, port, err := net.SplitHostPort(u.Host); err != nil { + return err + } else { + conn := goes.NewConnection(host, port) + el.Connection = conn + } + return nil +} + +func (el *esLogger) WriteMsg(msg string, level int) error { + if level > el.Level { + return nil + } + t := time.Now() + vals := make(map[string]interface{}) + vals["@timestamp"] = t.Format(time.RFC3339) + vals["@msg"] = msg + d := goes.Document{ + Index: fmt.Sprintf("%04d.%02d.%02d", t.Year(), t.Month(), t.Day()), + Type: "logs", + Fields: vals, + } + _, err := el.Index(d, nil) + return err +} + +func (el *esLogger) Destroy() { + +} + +func (el *esLogger) Flush() { + +} + +func init() { + logs.Register("es", NewES) +} diff --git a/Godeps/_workspace/src/github.com/astaxie/beego/logs/file.go b/Godeps/_workspace/src/github.com/astaxie/beego/logs/file.go new file mode 100644 index 0000000..2d3449c --- /dev/null +++ b/Godeps/_workspace/src/github.com/astaxie/beego/logs/file.go @@ -0,0 +1,283 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package logs + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "log" + "os" + "path/filepath" + "strings" + "sync" + "time" +) + +// FileLogWriter implements LoggerInterface. +// It writes messages by lines limit, file size limit, or time frequency. +type FileLogWriter struct { + *log.Logger + mw *MuxWriter + // The opened file + Filename string `json:"filename"` + + Maxlines int `json:"maxlines"` + maxlines_curlines int + + // Rotate at size + Maxsize int `json:"maxsize"` + maxsize_cursize int + + // Rotate daily + Daily bool `json:"daily"` + Maxdays int64 `json:"maxdays"` + daily_opendate int + + Rotate bool `json:"rotate"` + + startLock sync.Mutex // Only one log can write to the file + + Level int `json:"level"` +} + +// an *os.File writer with locker. +type MuxWriter struct { + sync.Mutex + fd *os.File +} + +// write to os.File. +func (l *MuxWriter) Write(b []byte) (int, error) { + l.Lock() + defer l.Unlock() + return l.fd.Write(b) +} + +// set os.File in writer. +func (l *MuxWriter) SetFd(fd *os.File) { + if l.fd != nil { + l.fd.Close() + } + l.fd = fd +} + +// create a FileLogWriter returning as LoggerInterface. +func NewFileWriter() LoggerInterface { + w := &FileLogWriter{ + Filename: "", + Maxlines: 1000000, + Maxsize: 1 << 28, //256 MB + Daily: true, + Maxdays: 7, + Rotate: true, + Level: LevelTrace, + } + // use MuxWriter instead direct use os.File for lock write when rotate + w.mw = new(MuxWriter) + // set MuxWriter as Logger's io.Writer + w.Logger = log.New(w.mw, "", log.Ldate|log.Ltime) + return w +} + +// Init file logger with json config. +// jsonconfig like: +// { +// "filename":"logs/beego.log", +// "maxlines":10000, +// "maxsize":1<<30, +// "daily":true, +// "maxdays":15, +// "rotate":true +// } +func (w *FileLogWriter) Init(jsonconfig string) error { + err := json.Unmarshal([]byte(jsonconfig), w) + if err != nil { + return err + } + if len(w.Filename) == 0 { + return errors.New("jsonconfig must have filename") + } + err = w.startLogger() + return err +} + +// start file logger. create log file and set to locker-inside file writer. +func (w *FileLogWriter) startLogger() error { + fd, err := w.createLogFile() + if err != nil { + return err + } + w.mw.SetFd(fd) + return w.initFd() +} + +func (w *FileLogWriter) docheck(size int) { + w.startLock.Lock() + defer w.startLock.Unlock() + if w.Rotate && ((w.Maxlines > 0 && w.maxlines_curlines >= w.Maxlines) || + (w.Maxsize > 0 && w.maxsize_cursize >= w.Maxsize) || + (w.Daily && time.Now().Day() != w.daily_opendate)) { + if err := w.DoRotate(); err != nil { + fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err) + return + } + } + w.maxlines_curlines++ + w.maxsize_cursize += size +} + +// write logger message into file. +func (w *FileLogWriter) WriteMsg(msg string, level int) error { + if level > w.Level { + return nil + } + n := 24 + len(msg) // 24 stand for the length "2013/06/23 21:00:22 [T] " + w.docheck(n) + w.Logger.Println(msg) + return nil +} + +func (w *FileLogWriter) createLogFile() (*os.File, error) { + // Open the log file + fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660) + return fd, err +} + +func (w *FileLogWriter) initFd() error { + fd := w.mw.fd + finfo, err := fd.Stat() + if err != nil { + return fmt.Errorf("get stat err: %s\n", err) + } + w.maxsize_cursize = int(finfo.Size()) + w.daily_opendate = time.Now().Day() + w.maxlines_curlines = 0 + if finfo.Size() > 0 { + count, err := w.lines() + if err != nil { + return err + } + w.maxlines_curlines = count + } + return nil +} + +func (w *FileLogWriter) lines() (int, error) { + fd, err := os.Open(w.Filename) + if err != nil { + return 0, err + } + defer fd.Close() + + buf := make([]byte, 32768) // 32k + count := 0 + lineSep := []byte{'\n'} + + for { + c, err := fd.Read(buf) + if err != nil && err != io.EOF { + return count, err + } + + count += bytes.Count(buf[:c], lineSep) + + if err == io.EOF { + break + } + } + + return count, nil +} + +// DoRotate means it need to write file in new file. +// new file name like xx.log.2013-01-01.2 +func (w *FileLogWriter) DoRotate() error { + _, err := os.Lstat(w.Filename) + if err == nil { // file exists + // Find the next available number + num := 1 + fname := "" + for ; err == nil && num <= 999; num++ { + fname = w.Filename + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), num) + _, err = os.Lstat(fname) + } + // return error if the last file checked still existed + if err == nil { + return fmt.Errorf("Rotate: Cannot find free log number to rename %s\n", w.Filename) + } + + // block Logger's io.Writer + w.mw.Lock() + defer w.mw.Unlock() + + fd := w.mw.fd + fd.Close() + + // close fd before rename + // Rename the file to its newfound home + err = os.Rename(w.Filename, fname) + if err != nil { + return fmt.Errorf("Rotate: %s\n", err) + } + + // re-start logger + err = w.startLogger() + if err != nil { + return fmt.Errorf("Rotate StartLogger: %s\n", err) + } + + go w.deleteOldLog() + } + + return nil +} + +func (w *FileLogWriter) deleteOldLog() { + dir := filepath.Dir(w.Filename) + filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) { + defer func() { + if r := recover(); r != nil { + returnErr = fmt.Errorf("Unable to delete old log '%s', error: %+v", path, r) + fmt.Println(returnErr) + } + }() + + if !info.IsDir() && info.ModTime().Unix() < (time.Now().Unix()-60*60*24*w.Maxdays) { + if strings.HasPrefix(filepath.Base(path), filepath.Base(w.Filename)) { + os.Remove(path) + } + } + return + }) +} + +// destroy file logger, close file writer. +func (w *FileLogWriter) Destroy() { + w.mw.fd.Close() +} + +// flush file logger. +// there are no buffering messages in file logger in memory. +// flush file means sync file from disk. +func (w *FileLogWriter) Flush() { + w.mw.fd.Sync() +} + +func init() { + Register("file", NewFileWriter) +} diff --git a/Godeps/_workspace/src/github.com/astaxie/beego/logs/log.go b/Godeps/_workspace/src/github.com/astaxie/beego/logs/log.go new file mode 100644 index 0000000..cebbc73 --- /dev/null +++ b/Godeps/_workspace/src/github.com/astaxie/beego/logs/log.go @@ -0,0 +1,350 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Usage: +// +// import "github.com/astaxie/beego/logs" +// +// log := NewLogger(10000) +// log.SetLogger("console", "") +// +// > the first params stand for how many channel +// +// Use it like this: +// +// log.Trace("trace") +// log.Info("info") +// log.Warn("warning") +// log.Debug("debug") +// log.Critical("critical") +// +// more docs http://beego.me/docs/module/logs.md +package logs + +import ( + "fmt" + "path" + "runtime" + "sync" +) + +// RFC5424 log message levels. +const ( + LevelEmergency = iota + LevelAlert + LevelCritical + LevelError + LevelWarning + LevelNotice + LevelInformational + LevelDebug +) + +// Legacy loglevel constants to ensure backwards compatibility. +// +// Deprecated: will be removed in 1.5.0. +const ( + LevelInfo = LevelInformational + LevelTrace = LevelDebug + LevelWarn = LevelWarning +) + +type loggerType func() LoggerInterface + +// LoggerInterface defines the behavior of a log provider. +type LoggerInterface interface { + Init(config string) error + WriteMsg(msg string, level int) error + Destroy() + Flush() +} + +var adapters = make(map[string]loggerType) + +// Register makes a log provide available by the provided name. +// If Register is called twice with the same name or if driver is nil, +// it panics. +func Register(name string, log loggerType) { + if log == nil { + panic("logs: Register provide is nil") + } + if _, dup := adapters[name]; dup { + panic("logs: Register called twice for provider " + name) + } + adapters[name] = log +} + +// BeeLogger is default logger in beego application. +// it can contain several providers and log message into all providers. +type BeeLogger struct { + lock sync.Mutex + level int + enableFuncCallDepth bool + loggerFuncCallDepth int + asynchronous bool + msg chan *logMsg + outputs map[string]LoggerInterface +} + +type logMsg struct { + level int + msg string +} + +// NewLogger returns a new BeeLogger. +// channellen means the number of messages in chan. +// if the buffering chan is full, logger adapters write to file or other way. +func NewLogger(channellen int64) *BeeLogger { + bl := new(BeeLogger) + bl.level = LevelDebug + bl.loggerFuncCallDepth = 2 + bl.msg = make(chan *logMsg, channellen) + bl.outputs = make(map[string]LoggerInterface) + return bl +} + +func (bl *BeeLogger) Async() *BeeLogger { + bl.asynchronous = true + go bl.startLogger() + return bl +} + +// SetLogger provides a given logger adapter into BeeLogger with config string. +// config need to be correct JSON as string: {"interval":360}. +func (bl *BeeLogger) SetLogger(adaptername string, config string) error { + bl.lock.Lock() + defer bl.lock.Unlock() + if log, ok := adapters[adaptername]; ok { + lg := log() + err := lg.Init(config) + bl.outputs[adaptername] = lg + if err != nil { + fmt.Println("logs.BeeLogger.SetLogger: " + err.Error()) + return err + } + } else { + return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adaptername) + } + return nil +} + +// remove a logger adapter in BeeLogger. +func (bl *BeeLogger) DelLogger(adaptername string) error { + bl.lock.Lock() + defer bl.lock.Unlock() + if lg, ok := bl.outputs[adaptername]; ok { + lg.Destroy() + delete(bl.outputs, adaptername) + return nil + } else { + return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adaptername) + } +} + +func (bl *BeeLogger) writerMsg(loglevel int, msg string) error { + lm := new(logMsg) + lm.level = loglevel + if bl.enableFuncCallDepth { + _, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth) + if !ok { + file = "???" + line = 0 + } + _, filename := path.Split(file) + lm.msg = fmt.Sprintf("[%s:%d] %s", filename, line, msg) + } else { + lm.msg = msg + } + if bl.asynchronous { + bl.msg <- lm + } else { + for name, l := range bl.outputs { + err := l.WriteMsg(lm.msg, lm.level) + if err != nil { + fmt.Println("unable to WriteMsg to adapter:", name, err) + return err + } + } + } + return nil +} + +// Set log message level. +// +// If message level (such as LevelDebug) is higher than logger level (such as LevelWarning), +// log providers will not even be sent the message. +func (bl *BeeLogger) SetLevel(l int) { + bl.level = l +} + +// set log funcCallDepth +func (bl *BeeLogger) SetLogFuncCallDepth(d int) { + bl.loggerFuncCallDepth = d +} + +// get log funcCallDepth for wrapper +func (bl *BeeLogger) GetLogFuncCallDepth() int { + return bl.loggerFuncCallDepth +} + +// enable log funcCallDepth +func (bl *BeeLogger) EnableFuncCallDepth(b bool) { + bl.enableFuncCallDepth = b +} + +// start logger chan reading. +// when chan is not empty, write logs. +func (bl *BeeLogger) startLogger() { + for { + select { + case bm := <-bl.msg: + for _, l := range bl.outputs { + err := l.WriteMsg(bm.msg, bm.level) + if err != nil { + fmt.Println("ERROR, unable to WriteMsg:", err) + } + } + } + } +} + +// Log EMERGENCY level message. +func (bl *BeeLogger) Emergency(format string, v ...interface{}) { + if LevelEmergency > bl.level { + return + } + msg := fmt.Sprintf("[M] "+format, v...) + bl.writerMsg(LevelEmergency, msg) +} + +// Log ALERT level message. +func (bl *BeeLogger) Alert(format string, v ...interface{}) { + if LevelAlert > bl.level { + return + } + msg := fmt.Sprintf("[A] "+format, v...) + bl.writerMsg(LevelAlert, msg) +} + +// Log CRITICAL level message. +func (bl *BeeLogger) Critical(format string, v ...interface{}) { + if LevelCritical > bl.level { + return + } + msg := fmt.Sprintf("[C] "+format, v...) + bl.writerMsg(LevelCritical, msg) +} + +// Log ERROR level message. +func (bl *BeeLogger) Error(format string, v ...interface{}) { + if LevelError > bl.level { + return + } + msg := fmt.Sprintf("[E] "+format, v...) + bl.writerMsg(LevelError, msg) +} + +// Log WARNING level message. +func (bl *BeeLogger) Warning(format string, v ...interface{}) { + if LevelWarning > bl.level { + return + } + msg := fmt.Sprintf("[W] "+format, v...) + bl.writerMsg(LevelWarning, msg) +} + +// Log NOTICE level message. +func (bl *BeeLogger) Notice(format string, v ...interface{}) { + if LevelNotice > bl.level { + return + } + msg := fmt.Sprintf("[N] "+format, v...) + bl.writerMsg(LevelNotice, msg) +} + +// Log INFORMATIONAL level message. +func (bl *BeeLogger) Informational(format string, v ...interface{}) { + if LevelInformational > bl.level { + return + } + msg := fmt.Sprintf("[I] "+format, v...) + bl.writerMsg(LevelInformational, msg) +} + +// Log DEBUG level message. +func (bl *BeeLogger) Debug(format string, v ...interface{}) { + if LevelDebug > bl.level { + return + } + msg := fmt.Sprintf("[D] "+format, v...) + bl.writerMsg(LevelDebug, msg) +} + +// Log WARN level message. +// compatibility alias for Warning() +func (bl *BeeLogger) Warn(format string, v ...interface{}) { + if LevelWarning > bl.level { + return + } + msg := fmt.Sprintf("[W] "+format, v...) + bl.writerMsg(LevelWarning, msg) +} + +// Log INFO level message. +// compatibility alias for Informational() +func (bl *BeeLogger) Info(format string, v ...interface{}) { + if LevelInformational > bl.level { + return + } + msg := fmt.Sprintf("[I] "+format, v...) + bl.writerMsg(LevelInformational, msg) +} + +// Log TRACE level message. +// compatibility alias for Debug() +func (bl *BeeLogger) Trace(format string, v ...interface{}) { + if LevelDebug > bl.level { + return + } + msg := fmt.Sprintf("[D] "+format, v...) + bl.writerMsg(LevelDebug, msg) +} + +// flush all chan data. +func (bl *BeeLogger) Flush() { + for _, l := range bl.outputs { + l.Flush() + } +} + +// close logger, flush all chan data and destroy all adapters in BeeLogger. +func (bl *BeeLogger) Close() { + for { + if len(bl.msg) > 0 { + bm := <-bl.msg + for _, l := range bl.outputs { + err := l.WriteMsg(bm.msg, bm.level) + if err != nil { + fmt.Println("ERROR, unable to WriteMsg (while closing logger):", err) + } + } + continue + } + break + } + for _, l := range bl.outputs { + l.Flush() + l.Destroy() + } +} diff --git a/Godeps/_workspace/src/github.com/astaxie/beego/logs/smtp.go b/Godeps/_workspace/src/github.com/astaxie/beego/logs/smtp.go new file mode 100644 index 0000000..95123eb --- /dev/null +++ b/Godeps/_workspace/src/github.com/astaxie/beego/logs/smtp.go @@ -0,0 +1,165 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package logs + +import ( + "crypto/tls" + "encoding/json" + "fmt" + "net" + "net/smtp" + "strings" + "time" +) + +const ( +// no usage +// subjectPhrase = "Diagnostic message from server" +) + +// smtpWriter implements LoggerInterface and is used to send emails via given SMTP-server. +type SmtpWriter struct { + Username string `json:"Username"` + Password string `json:"password"` + Host string `json:"Host"` + Subject string `json:"subject"` + FromAddress string `json:"fromAddress"` + RecipientAddresses []string `json:"sendTos"` + Level int `json:"level"` +} + +// create smtp writer. +func NewSmtpWriter() LoggerInterface { + return &SmtpWriter{Level: LevelTrace} +} + +// init smtp writer with json config. +// config like: +// { +// "Username":"example@gmail.com", +// "password:"password", +// "host":"smtp.gmail.com:465", +// "subject":"email title", +// "fromAddress":"from@example.com", +// "sendTos":["email1","email2"], +// "level":LevelError +// } +func (s *SmtpWriter) Init(jsonconfig string) error { + err := json.Unmarshal([]byte(jsonconfig), s) + if err != nil { + return err + } + return nil +} + +func (s *SmtpWriter) GetSmtpAuth(host string) smtp.Auth { + if len(strings.Trim(s.Username, " ")) == 0 && len(strings.Trim(s.Password, " ")) == 0 { + return nil + } + return smtp.PlainAuth( + "", + s.Username, + s.Password, + host, + ) +} + +func (s *SmtpWriter) sendMail(hostAddressWithPort string, auth smtp.Auth, fromAddress string, recipients []string, msgContent []byte) error { + client, err := smtp.Dial(hostAddressWithPort) + if err != nil { + return err + } + + host, _, _ := net.SplitHostPort(hostAddressWithPort) + tlsConn := &tls.Config{ + InsecureSkipVerify: true, + ServerName: host, + } + if err = client.StartTLS(tlsConn); err != nil { + return err + } + + if auth != nil { + if err = client.Auth(auth); err != nil { + return err + } + } + + if err = client.Mail(fromAddress); err != nil { + return err + } + + for _, rec := range recipients { + if err = client.Rcpt(rec); err != nil { + return err + } + } + + w, err := client.Data() + if err != nil { + return err + } + _, err = w.Write([]byte(msgContent)) + if err != nil { + return err + } + + err = w.Close() + if err != nil { + return err + } + + err = client.Quit() + if err != nil { + return err + } + + return nil +} + +// write message in smtp writer. +// it will send an email with subject and only this message. +func (s *SmtpWriter) WriteMsg(msg string, level int) error { + if level > s.Level { + return nil + } + + hp := strings.Split(s.Host, ":") + + // Set up authentication information. + auth := s.GetSmtpAuth(hp[0]) + + // Connect to the server, authenticate, set the sender and recipient, + // and send the email all in one step. + content_type := "Content-Type: text/plain" + "; charset=UTF-8" + mailmsg := []byte("To: " + strings.Join(s.RecipientAddresses, ";") + "\r\nFrom: " + s.FromAddress + "<" + s.FromAddress + + ">\r\nSubject: " + s.Subject + "\r\n" + content_type + "\r\n\r\n" + fmt.Sprintf(".%s", time.Now().Format("2006-01-02 15:04:05")) + msg) + + return s.sendMail(s.Host, auth, s.FromAddress, s.RecipientAddresses, mailmsg) +} + +// implementing method. empty. +func (s *SmtpWriter) Flush() { + return +} + +// implementing method. empty. +func (s *SmtpWriter) Destroy() { + return +} + +func init() { + Register("smtp", NewSmtpWriter) +} diff --git a/Godeps/_workspace/src/github.com/astaxie/beego/utils/captcha/LICENSE b/Godeps/_workspace/src/github.com/astaxie/beego/utils/captcha/LICENSE new file mode 100644 index 0000000..0ad73ae --- /dev/null +++ b/Godeps/_workspace/src/github.com/astaxie/beego/utils/captcha/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2011-2014 Dmitry Chestnykh + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Godeps/_workspace/src/github.com/vaughan0/go-ini/LICENSE b/Godeps/_workspace/src/github.com/vaughan0/go-ini/LICENSE new file mode 100644 index 0000000..968b453 --- /dev/null +++ b/Godeps/_workspace/src/github.com/vaughan0/go-ini/LICENSE @@ -0,0 +1,14 @@ +Copyright (c) 2013 Vaughan Newton + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Godeps/_workspace/src/github.com/vaughan0/go-ini/README.md b/Godeps/_workspace/src/github.com/vaughan0/go-ini/README.md new file mode 100644 index 0000000..d5cd4e7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/vaughan0/go-ini/README.md @@ -0,0 +1,70 @@ +go-ini +====== + +INI parsing library for Go (golang). + +View the API documentation [here](http://godoc.org/github.com/vaughan0/go-ini). + +Usage +----- + +Parse an INI file: + +```go +import "github.com/vaughan0/go-ini" + +file, err := ini.LoadFile("myfile.ini") +``` + +Get data from the parsed file: + +```go +name, ok := file.Get("person", "name") +if !ok { + panic("'name' variable missing from 'person' section") +} +``` + +Iterate through values in a section: + +```go +for key, value := range file["mysection"] { + fmt.Printf("%s => %s\n", key, value) +} +``` + +Iterate through sections in a file: + +```go +for name, section := range file { + fmt.Printf("Section name: %s\n", name) +} +``` + +File Format +----------- + +INI files are parsed by go-ini line-by-line. Each line may be one of the following: + + * A section definition: [section-name] + * A property: key = value + * A comment: #blahblah _or_ ;blahblah + * Blank. The line will be ignored. + +Properties defined before any section headers are placed in the default section, which has +the empty string as it's key. + +Example: + +```ini +# I am a comment +; So am I! + +[apples] +colour = red or green +shape = applish + +[oranges] +shape = square +colour = blue +``` diff --git a/Godeps/_workspace/src/github.com/vaughan0/go-ini/ini.go b/Godeps/_workspace/src/github.com/vaughan0/go-ini/ini.go new file mode 100644 index 0000000..81aeb32 --- /dev/null +++ b/Godeps/_workspace/src/github.com/vaughan0/go-ini/ini.go @@ -0,0 +1,123 @@ +// Package ini provides functions for parsing INI configuration files. +package ini + +import ( + "bufio" + "fmt" + "io" + "os" + "regexp" + "strings" +) + +var ( + sectionRegex = regexp.MustCompile(`^\[(.*)\]$`) + assignRegex = regexp.MustCompile(`^([^=]+)=(.*)$`) +) + +// ErrSyntax is returned when there is a syntax error in an INI file. +type ErrSyntax struct { + Line int + Source string // The contents of the erroneous line, without leading or trailing whitespace +} + +func (e ErrSyntax) Error() string { + return fmt.Sprintf("invalid INI syntax on line %d: %s", e.Line, e.Source) +} + +// A File represents a parsed INI file. +type File map[string]Section + +// A Section represents a single section of an INI file. +type Section map[string]string + +// Returns a named Section. A Section will be created if one does not already exist for the given name. +func (f File) Section(name string) Section { + section := f[name] + if section == nil { + section = make(Section) + f[name] = section + } + return section +} + +// Looks up a value for a key in a section and returns that value, along with a boolean result similar to a map lookup. +func (f File) Get(section, key string) (value string, ok bool) { + if s := f[section]; s != nil { + value, ok = s[key] + } + return +} + +// Loads INI data from a reader and stores the data in the File. +func (f File) Load(in io.Reader) (err error) { + bufin, ok := in.(*bufio.Reader) + if !ok { + bufin = bufio.NewReader(in) + } + return parseFile(bufin, f) +} + +// Loads INI data from a named file and stores the data in the File. +func (f File) LoadFile(file string) (err error) { + in, err := os.Open(file) + if err != nil { + return + } + defer in.Close() + return f.Load(in) +} + +func parseFile(in *bufio.Reader, file File) (err error) { + section := "" + lineNum := 0 + for done := false; !done; { + var line string + if line, err = in.ReadString('\n'); err != nil { + if err == io.EOF { + done = true + } else { + return + } + } + lineNum++ + line = strings.TrimSpace(line) + if len(line) == 0 { + // Skip blank lines + continue + } + if line[0] == ';' || line[0] == '#' { + // Skip comments + continue + } + + if groups := assignRegex.FindStringSubmatch(line); groups != nil { + key, val := groups[1], groups[2] + key, val = strings.TrimSpace(key), strings.TrimSpace(val) + file.Section(section)[key] = val + } else if groups := sectionRegex.FindStringSubmatch(line); groups != nil { + name := strings.TrimSpace(groups[1]) + section = name + // Create the section if it does not exist + file.Section(section) + } else { + return ErrSyntax{lineNum, line} + } + + } + return nil +} + +// Loads and returns a File from a reader. +func Load(in io.Reader) (File, error) { + file := make(File) + err := file.Load(in) + return file, err +} + +// Loads and returns an INI File from a file on disk. +func LoadFile(filename string) (File, error) { + file := make(File) + err := file.LoadFile(filename) + return file, err +} diff --git a/Godeps/_workspace/src/github.com/vaughan0/go-ini/test.ini b/Godeps/_workspace/src/github.com/vaughan0/go-ini/test.ini new file mode 100644 index 0000000..d13c999 --- /dev/null +++ b/Godeps/_workspace/src/github.com/vaughan0/go-ini/test.ini @@ -0,0 +1,2 @@ +[default] +stuff = things diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..06a5054 --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +export PATH := $(GOPATH)/bin:$(PATH) + +all: build + +build: godep frps frpc + +godep: + @go get github.com/tools/godep + godep restore + +frps: + godep go build -o bin/frps ./cmd/frps + +frpc: + godep go build -o bin/frpc ./cmd/frpc diff --git a/cmd/frpc/config.go b/cmd/frpc/config.go new file mode 100644 index 0000000..a26fe6a --- /dev/null +++ b/cmd/frpc/config.go @@ -0,0 +1,89 @@ +package main + +import ( + "fmt" + "strconv" + + "frp/pkg/models" + + ini "github.com/vaughan0/go-ini" +) + +// common config +var ( + ServerAddr string = "0.0.0.0" + ServerPort int64 = 7000 + LogFile string = "./frpc.log" + LogLevel string = "warn" + LogWay string = "file" +) + +var ProxyClients map[string]*models.ProxyClient = make(map[string]*models.ProxyClient) + + +func LoadConf(confFile string) (err error) { + var tmpStr string + var ok bool + + conf, err := ini.LoadFile(confFile) + if err != nil { + return err + } + + // common + tmpStr, ok = conf.Get("common", "server_addr") + if ok { + ServerAddr = tmpStr + } + + tmpStr, ok = conf.Get("common", "server_port") + if ok { + ServerPort, _ = strconv.ParseInt(tmpStr, 10, 64) + } + + tmpStr, ok = conf.Get("common", "log_file") + if ok { + LogFile = tmpStr + } + + tmpStr, ok = conf.Get("common", "log_level") + if ok { + LogLevel = tmpStr + } + + tmpStr, ok = conf.Get("common", "log_way") + if ok { + LogWay = tmpStr + } + + // servers + for name, section := range conf { + if name != "common" { + proxyClient := &models.ProxyClient{} + proxyClient.Name = name + + proxyClient.Passwd, ok = section["passwd"] + if !ok { + return fmt.Errorf("Parse ini file error: proxy [%s] no passwd found", proxyClient.Name) + } + + portStr, ok := section["local_port"] + if ok { + proxyClient.LocalPort, err = strconv.ParseInt(portStr, 10, 64) + if err != nil { + return fmt.Errorf("Parse ini file error: proxy [%s] local_port error", proxyClient.Name) + } + } else { + return fmt.Errorf("Parse ini file error: proxy [%s] local_port not found", proxyClient.Name) + } + + ProxyClients[proxyClient.Name] = proxyClient + } + } + + if len(ProxyClients) == 0 { + return fmt.Errorf("Parse ini file error: no proxy config found") + } + + return nil +} diff --git a/cmd/frpc/control.go b/cmd/frpc/control.go new file mode 100644 index 0000000..e917a93 --- /dev/null +++ b/cmd/frpc/control.go @@ -0,0 +1,67 @@ +package main + +import ( + "io" + "sync" + "encoding/json" + + "frp/pkg/models" + "frp/pkg/utils/conn" + "frp/pkg/utils/log" +) + +func ControlProcess(cli *models.ProxyClient, wait *sync.WaitGroup) { + defer wait.Done() + + c := &conn.Conn{} + err := c.ConnectServer(ServerAddr, ServerPort) + if err != nil { + log.Error("ProxyName [%s], connect to server [%s:%d] error, %v", cli.Name, ServerAddr, ServerPort, err) + return + } + defer c.Close() + + req := &models.ClientCtlReq{ + Type: models.ControlConn, + ProxyName: cli.Name, + Passwd: cli.Passwd, + } + buf, _ := json.Marshal(req) + err = c.Write(string(buf) + "\n") + if err != nil { + log.Error("ProxyName [%s], write to server error, %v", cli.Name, err) + return + } + + res, err := c.ReadLine() + if err != nil { + log.Error("ProxyName [%s], read from server error, %v", cli.Name, err) + return + } + log.Debug("ProxyName [%s], read [%s]", cli.Name, res) + + clientCtlRes := &models.ClientCtlRes{} + if err = json.Unmarshal([]byte(res), &clientCtlRes); err != nil { + log.Error("ProxyName [%s], format server response error, %v", cli.Name, err) + return + } + + if clientCtlRes.Code != 0 { + log.Error("ProxyName [%s], start proxy error, %s", cli.Name, clientCtlRes.Msg) + return + } + + for { + // ignore response content now + _, err := c.ReadLine() + if err == io.EOF { + log.Debug("ProxyName [%s], server close this control conn", cli.Name) + break + } else if err != nil { + log.Warn("ProxyName [%s], read from server error, %v", cli.Name, err) + continue + } + + cli.StartTunnel(ServerAddr, ServerPort) + } +} diff --git a/cmd/frpc/main.go b/cmd/frpc/main.go new file mode 100644 index 0000000..7f07282 --- /dev/null +++ b/cmd/frpc/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "os" + "sync" + + "frp/pkg/utils/log" +) + +func main() { + err := LoadConf("./frpc.ini") + if err != nil { + os.Exit(-1) + } + + log.InitLog(LogWay, LogFile, LogLevel) + + // wait until all control goroutine exit + var wait sync.WaitGroup + wait.Add(len(ProxyClients)) + + for _, client := range ProxyClients { + go ControlProcess(client, &wait) + } + + log.Info("Start frpc success") + + wait.Wait() + log.Warn("All proxy exit!") +} diff --git a/cmd/frps/config.go b/cmd/frps/config.go new file mode 100644 index 0000000..feb07d5 --- /dev/null +++ b/cmd/frps/config.go @@ -0,0 +1,95 @@ +package main + +import ( + "fmt" + "strconv" + + "frp/pkg/models" + + ini "github.com/vaughan0/go-ini" +) + +// common config +var ( + BindAddr string = "0.0.0.0" + BindPort int64 = 9527 + LogFile string = "./frps.log" + LogLevel string = "warn" + LogWay string = "file" +) + +var ProxyServers map[string]*models.ProxyServer = make(map[string]*models.ProxyServer) + + +func LoadConf(confFile string) (err error) { + var tmpStr string + var ok bool + + conf, err := ini.LoadFile(confFile) + if err != nil { + return err + } + + // common + tmpStr, ok = conf.Get("common", "bind_addr") + if ok { + BindAddr = tmpStr + } + + tmpStr, ok = conf.Get("common", "bind_port") + if ok { + BindPort, _ = strconv.ParseInt(tmpStr, 10, 64) + } + + tmpStr, ok = conf.Get("common", "log_file") + if ok { + LogFile = tmpStr + } + + tmpStr, ok = conf.Get("common", "log_level") + if ok { + LogLevel = tmpStr + } + + tmpStr, ok = conf.Get("common", "log_way") + if ok { + LogWay = tmpStr + } + + // servers + for name, section := range conf { + if name != "common" { + proxyServer := &models.ProxyServer{} + proxyServer.Name = name + + proxyServer.Passwd, ok = section["passwd"] + if !ok { + return fmt.Errorf("Parse ini file error: proxy [%s] no passwd found", proxyServer.Name) + } + + proxyServer.BindAddr, ok = section["bind_addr"] + if !ok { + proxyServer.BindAddr = "0.0.0.0" + } + + portStr, ok := section["listen_port"] + if ok { + proxyServer.ListenPort, err = strconv.ParseInt(portStr, 10, 64) + if err != nil { + return fmt.Errorf("Parse ini file error: proxy [%s] listen_port error", proxyServer.Name) + } + } else { + return fmt.Errorf("Parse ini file error: proxy [%s] listen_port not found", proxyServer.Name) + } + + proxyServer.Init() + ProxyServers[proxyServer.Name] = proxyServer + } + } + + if len(ProxyServers) == 0 { + return fmt.Errorf("Parse ini file error: no proxy config found") + } + + return nil +} diff --git a/cmd/frps/control.go b/cmd/frps/control.go new file mode 100644 index 0000000..62d141e --- /dev/null +++ b/cmd/frps/control.go @@ -0,0 +1,134 @@ +package main + +import ( + "fmt" + "encoding/json" + + "frp/pkg/utils/log" + "frp/pkg/utils/conn" + "frp/pkg/models" +) + +func ProcessControlConn(l *conn.Listener) { + for { + c := l.GetConn() + log.Debug("Get one new conn, %v", c.GetRemoteAddr()) + go controlWorker(c) + } +} + +// control connection from every client and server +func controlWorker(c *conn.Conn) { + // the first message is from client to server + // if error, close connection + res, err := c.ReadLine() + if err != nil { + log.Warn("Read error, %v", err) + return + } + log.Debug("get: %s", res) + + clientCtlReq := &models.ClientCtlReq{} + clientCtlRes := &models.ClientCtlRes{} + if err := json.Unmarshal([]byte(res), &clientCtlReq); err != nil { + log.Warn("Parse err: %v : %s", err, res) + return + } + + // check + succ, msg, needRes := checkProxy(clientCtlReq, c) + if !succ { + clientCtlRes.Code = 1 + clientCtlRes.Msg = msg + } + + if needRes { + buf, _ := json.Marshal(clientCtlRes) + err = c.Write(string(buf) + "\n") + if err != nil { + log.Warn("Write error, %v", err) + } + } else { + // work conn, just return + return + } + + defer c.Close() + // others is from server to client + server, ok := ProxyServers[clientCtlReq.ProxyName] + if !ok { + log.Warn("ProxyName [%s] is not exist", clientCtlReq.ProxyName) + return + } + + serverCtlReq := &models.ClientCtlReq{} + serverCtlReq.Type = models.WorkConn + for { + server.WaitUserConn() + buf, _ := json.Marshal(serverCtlReq) + err = c.Write(string(buf) + "\n") + if err != nil { + log.Warn("ProxyName [%s], write to client error, proxy exit", server.Name) + server.Close() + return + } + + log.Debug("ProxyName [%s], write to client to add work conn success", server.Name) + } + + return +} + +func checkProxy(req *models.ClientCtlReq, c *conn.Conn) (succ bool, msg string, needRes bool) { + succ = false + needRes = true + // check if proxy name exist + server, ok := ProxyServers[req.ProxyName] + if !ok { + msg = fmt.Sprintf("ProxyName [%s] is not exist", req.ProxyName) + log.Warn(msg) + return + } + + // check password + if req.Passwd != server.Passwd { + msg = fmt.Sprintf("ProxyName [%s], password is not correct", req.ProxyName) + log.Warn(msg) + return + } + + // control conn + if req.Type == models.ControlConn { + if server.Status != models.Idle { + msg = fmt.Sprintf("ProxyName [%s], already in use", req.ProxyName) + log.Warn(msg) + return + } + + // start proxy and listen for user conn, no block + err := server.Start() + if err != nil { + msg = fmt.Sprintf("ProxyName [%s], start proxy error: %v", req.ProxyName, err.Error()) + log.Warn(msg) + return + } + + log.Info("ProxyName [%s], start proxy success", req.ProxyName) + } else if req.Type == models.WorkConn { + // work conn + needRes = false + if server.Status != models.Working { + log.Warn("ProxyName [%s], is not working when it gets one new work conn", req.ProxyName) + return + } + + server.CliConnChan <- c + } else { + msg = fmt.Sprintf("ProxyName [%s], type [%d] unsupport", req.ProxyName) + log.Warn(msg) + return + } + + succ = true + return +} diff --git a/cmd/frps/main.go b/cmd/frps/main.go new file mode 100644 index 0000000..1288622 --- /dev/null +++ b/cmd/frps/main.go @@ -0,0 +1,26 @@ +package main + +import ( + "os" + + "frp/pkg/utils/log" + "frp/pkg/utils/conn" +) + +func main() { + err := LoadConf("./frps.ini") + if err != nil { + os.Exit(-1) + } + + log.InitLog(LogWay, LogFile, LogLevel) + + l, err := conn.Listen(BindAddr, BindPort) + if err != nil { + log.Error("Create listener error, %v", err) + os.Exit(-1) + } + + log.Info("Start frps success") + ProcessControlConn(l) +} diff --git a/conf/frpc.ini b/conf/frpc.ini new file mode 100644 index 0000000..d2ba710 --- /dev/null +++ b/conf/frpc.ini @@ -0,0 +1,14 @@ +# common是必须的section +[common] +server_addr = 127.0.0.1 +bind_port = 7000 +log_file = ./frpc.log +# debug, info, warn, error +log_level = info +# file, console +log_way = file + +# test1即为name +[test1] +passwd = 123 +local_port = 22 diff --git a/conf/frps.ini b/conf/frps.ini new file mode 100644 index 0000000..f6a6995 --- /dev/null +++ b/conf/frps.ini @@ -0,0 +1,15 @@ +# common是必须的section +[common] +bind_addr = 0.0.0.0 +bind_port = 7000 +log_file = ./frps.log +# debug, info, warn, error +log_level = info +# file, console +log_way = file + +# test1即为name +[test1] +passwd = 123 +bind_addr = 0.0.0.0 +listen_port = 6000 diff --git a/pkg/models/client.go b/pkg/models/client.go new file mode 100644 index 0000000..1f01d50 --- /dev/null +++ b/pkg/models/client.go @@ -0,0 +1,70 @@ +package models + +import ( + "encoding/json" + + "frp/pkg/utils/conn" + "frp/pkg/utils/log" +) + +type ProxyClient struct { + Name string + Passwd string + LocalPort int64 +} + +func (p *ProxyClient) GetLocalConn() (c *conn.Conn, err error) { + c = &conn.Conn{} + err = c.ConnectServer("127.0.0.1", p.LocalPort) + if err != nil { + log.Error("ProxyName [%s], connect to local port error, %v", p.Name, err) + } + return +} + +func (p *ProxyClient) GetRemoteConn(addr string, port int64) (c *conn.Conn, err error) { + c = &conn.Conn{} + defer func(){ + if err != nil { + c.Close() + } + }() + + err = c.ConnectServer(addr, port) + if err != nil { + log.Error("ProxyName [%s], connect to server [%s:%d] error, %v", p.Name, addr, port, err) + return + } + + req := &ClientCtlReq{ + Type: WorkConn, + ProxyName: p.Name, + Passwd: p.Passwd, + } + + buf, _ := json.Marshal(req) + err = c.Write(string(buf) + "\n") + if err != nil { + log.Error("ProxyName [%s], write to server error, %v", p.Name, err) + return + } + + err = nil + return +} + +func (p *ProxyClient) StartTunnel(serverAddr string, serverPort int64) (err error) { + localConn, err := p.GetLocalConn() + if err != nil { + return + } + remoteConn, err := p.GetRemoteConn(serverAddr, serverPort) + if err != nil { + return + } + + log.Debug("Join two conns, (l[%s] r[%s]) (l[%s] r[%s])", localConn.GetLocalAddr(), localConn.GetRemoteAddr(), + remoteConn.GetLocalAddr(), remoteConn.GetRemoteAddr()) + go conn.Join(localConn, remoteConn) + return nil +} diff --git a/pkg/models/msg.go b/pkg/models/msg.go new file mode 100644 index 0000000..0062556 --- /dev/null +++ b/pkg/models/msg.go @@ -0,0 +1,27 @@ +package models + +type GeneralRes struct { + Code int64 `json:"code"` + Msg string `json:"msg"` +} + +// type +const ( + ControlConn = iota + WorkConn +) + +type ClientCtlReq struct { + Type int64 `json:"type"` + ProxyName string `json:"proxy_name"` + Passwd string `json:"passwd"` +} + +type ClientCtlRes struct { + GeneralRes +} + + +type ServerCtlReq struct { + Type int64 `json:"type"` +} diff --git a/pkg/models/server.go b/pkg/models/server.go new file mode 100644 index 0000000..bd6baa8 --- /dev/null +++ b/pkg/models/server.go @@ -0,0 +1,116 @@ +package models + +import ( + "sync" + "container/list" + + "frp/pkg/utils/conn" + "frp/pkg/utils/log" +) + +const ( + Idle = iota + Working +) + +type ProxyServer struct { + Name string + Passwd string + BindAddr string + ListenPort int64 + + Status int64 + Listener *conn.Listener // accept new connection from remote users + CtlMsgChan chan int64 // every time accept a new user conn, put "1" to the channel + CliConnChan chan *conn.Conn // get client conns from control goroutine + UserConnList *list.List // store user conns + Mutex sync.Mutex +} + +func (p *ProxyServer) Init() { + p.Status = Idle + p.CtlMsgChan = make(chan int64) + p.CliConnChan = make(chan *conn.Conn) + p.UserConnList = list.New() +} + +func (p *ProxyServer) Lock() { + p.Mutex.Lock() +} + +func (p *ProxyServer) Unlock() { + p.Mutex.Unlock() +} + +// start listening for user conns +func (p *ProxyServer) Start() (err error) { + p.Listener, err = conn.Listen(p.BindAddr, p.ListenPort) + if err != nil { + return err + } + + p.Status = Working + + // start a goroutine for listener + go func() { + for { + // block + c := p.Listener.GetConn() + log.Debug("ProxyName [%s], get one new user conn [%s]", p.Name, c.GetRemoteAddr()) + + // put to list + p.Lock() + if p.Status != Working { + log.Debug("ProxyName [%s] is not working, new user conn close", p.Name) + c.Close() + p.Unlock() + return + } + p.UserConnList.PushBack(c) + p.Unlock() + + // put msg to control conn + p.CtlMsgChan <- 1 + } + }() + + // start another goroutine for join two conns from client and user + go func() { + for { + cliConn := <-p.CliConnChan + p.Lock() + element := p.UserConnList.Front() + + var userConn *conn.Conn + if element != nil { + userConn = element.Value.(*conn.Conn) + p.UserConnList.Remove(element) + } else { + cliConn.Close() + continue + } + p.Unlock() + + // msg will transfer to another without modifying + log.Debug("Join two conns, (l[%s] r[%s]) (l[%s] r[%s])", cliConn.GetLocalAddr(), cliConn.GetRemoteAddr(), + userConn.GetLocalAddr(), userConn.GetRemoteAddr()) + go conn.Join(cliConn, userConn) + } + }() + + return nil +} + +func (p *ProxyServer) Close() { + p.Lock() + p.Status = Idle + p.CtlMsgChan = make(chan int64) + p.CliConnChan = make(chan *conn.Conn) + p.UserConnList = list.New() + p.Unlock() +} + +func (p *ProxyServer) WaitUserConn() (res int64) { + res = <-p.CtlMsgChan + return +} diff --git a/pkg/utils/conn/conn.go b/pkg/utils/conn/conn.go new file mode 100644 index 0000000..60929ac --- /dev/null +++ b/pkg/utils/conn/conn.go @@ -0,0 +1,115 @@ +package conn + +import ( + "fmt" + "net" + "bufio" + "sync" + "io" + + "frp/pkg/utils/log" +) + +type Listener struct { + Addr net.Addr + Conns chan *Conn +} + +// wait util get one +func (l *Listener) GetConn() (conn *Conn) { + conn = <-l.Conns + return conn +} + +type Conn struct { + TcpConn *net.TCPConn + Reader *bufio.Reader +} + +func (c *Conn) ConnectServer(host string, port int64) (err error) { + servertAddr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", host, port)) + if err != nil { + return err + } + conn, err := net.DialTCP("tcp", nil, servertAddr) + if err != nil { + return err + } + c.TcpConn = conn + c.Reader = bufio.NewReader(c.TcpConn) + return nil +} + +func (c *Conn) GetRemoteAddr() (addr string) { + return c.TcpConn.RemoteAddr().String() +} + +func (c *Conn) GetLocalAddr() (addr string) { + return c.TcpConn.LocalAddr().String() +} + +func (c *Conn) ReadLine() (buff string, err error) { + buff, err = c.Reader.ReadString('\n') + return buff, err +} + +func (c *Conn) Write(content string) (err error) { + _, err = c.TcpConn.Write([]byte(content)) + return err +} + +func (c *Conn) Close() { + c.TcpConn.Close() +} + +func Listen(bindAddr string, bindPort int64) (l *Listener, err error) { + tcpAddr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", bindAddr, bindPort)) + listener, err := net.ListenTCP("tcp", tcpAddr) + if err != nil { + return l, err + } + + l = &Listener{ + Addr: listener.Addr(), + Conns: make(chan *Conn), + } + + go func() { + for { + conn, err := listener.AcceptTCP() + if err != nil { + log.Error("Accept new tcp connection error, %v", err) + continue + } + + c := &Conn{ + TcpConn: conn, + } + c.Reader = bufio.NewReader(c.TcpConn) + l.Conns <- c + } + }() + return l, err +} + +// will block until conn close +func Join(c1 *Conn, c2 *Conn) { + var wait sync.WaitGroup + pipe := func(to *Conn, from *Conn) { + defer to.Close() + defer from.Close() + defer wait.Done() + + var err error + _, err = io.Copy(to.TcpConn, from.TcpConn) + if err != nil { + log.Warn("join conns error, %v", err) + } + } + + wait.Add(2) + go pipe(c1, c2) + go pipe(c2, c1) + wait.Wait() + return +} diff --git a/pkg/utils/log/log.go b/pkg/utils/log/log.go new file mode 100644 index 0000000..1a55c3c --- /dev/null +++ b/pkg/utils/log/log.go @@ -0,0 +1,64 @@ +package log + +import ( + "github.com/astaxie/beego/logs" +) + +var Log *logs.BeeLogger + +func init() { + Log = logs.NewLogger(1000) + Log.EnableFuncCallDepth(true) + Log.SetLogFuncCallDepth(Log.GetLogFuncCallDepth() + 1) +} + +func InitLog(logWay string, logFile string, logLevel string) { + SetLogFile(logWay, logFile) + SetLogLevel(logLevel) +} + +// logWay: such as file or console +func SetLogFile(logWay string, logFile string) { + if logWay == "console" { + Log.SetLogger("console", "") + } else { + Log.SetLogger("file", `{"filename": "` + logFile + `"}`) + } +} + +// value: error, warning, info, debug +func SetLogLevel(logLevel string) { + level := 4 // warning + + switch logLevel { + case "error": + level = 3 + case "warn": + level = 4 + case "info": + level = 6 + case "debug": + level = 7 + default: + level = 4 + } + + Log.SetLevel(level) +} + +// wrap log +func Error(format string, v ...interface{}) { + Log.Error(format, v...) +} + +func Warn(format string, v ...interface{}) { + Log.Warn(format, v...) +} + +func Info(format string, v ...interface{}) { + Log.Info(format, v...) +} + +func Debug(format string, v ...interface{}) { + Log.Debug(format, v...) +}