First available commit

This commit is contained in:
fatedier 2016-01-27 21:24:36 +08:00
parent 7ea8751e56
commit 7030d16e80
31 changed files with 2295 additions and 0 deletions

4
.gitignore vendored
View File

@ -22,3 +22,7 @@ _testmain.go
*.exe
*.test
*.prof
# Self
bin/

18
Godeps/Godeps.json generated Normal file
View File

@ -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"
}
]
}

5
Godeps/Readme generated Normal file
View File

@ -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.

2
Godeps/_workspace/.gitignore generated vendored Normal file
View File

@ -0,0 +1,2 @@
/pkg
/bin

13
Godeps/_workspace/src/github.com/astaxie/beego/LICENSE generated vendored Normal file
View File

@ -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.

View File

@ -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)

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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()
}
}

View File

@ -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)
}

View File

@ -0,0 +1,19 @@
Copyright (c) 2011-2014 Dmitry Chestnykh <dmitry@codingrobots.com>
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.

View File

@ -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.

View File

@ -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
```

123
Godeps/_workspace/src/github.com/vaughan0/go-ini/ini.go generated vendored Normal file
View File

@ -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
}

View File

@ -0,0 +1,2 @@
[default]
stuff = things

15
Makefile Normal file
View File

@ -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

89
cmd/frpc/config.go Normal file
View File

@ -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
}

67
cmd/frpc/control.go Normal file
View File

@ -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)
}
}

30
cmd/frpc/main.go Normal file
View File

@ -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!")
}

95
cmd/frps/config.go Normal file
View File

@ -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
}

134
cmd/frps/control.go Normal file
View File

@ -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
}

26
cmd/frps/main.go Normal file
View File

@ -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)
}

14
conf/frpc.ini Normal file
View File

@ -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

15
conf/frps.ini Normal file
View File

@ -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

70
pkg/models/client.go Normal file
View File

@ -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
}

27
pkg/models/msg.go Normal file
View File

@ -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"`
}

116
pkg/models/server.go Normal file
View File

@ -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
}

115
pkg/utils/conn/conn.go Normal file
View File

@ -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
}

64
pkg/utils/log/log.go Normal file
View File

@ -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...)
}