rename models to pkg (#2005)

This commit is contained in:
fatedier
2020-09-23 13:49:14 +08:00
committed by GitHub
parent 710ecf44f5
commit 3fbdea0f6b
111 changed files with 196 additions and 196 deletions

51
pkg/util/limit/reader.go Normal file
View File

@@ -0,0 +1,51 @@
// Copyright 2019 fatedier, fatedier@gmail.com
//
// 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 limit
import (
"context"
"io"
"golang.org/x/time/rate"
)
type Reader struct {
r io.Reader
limiter *rate.Limiter
}
func NewReader(r io.Reader, limiter *rate.Limiter) *Reader {
return &Reader{
r: r,
limiter: limiter,
}
}
func (r *Reader) Read(p []byte) (n int, err error) {
b := r.limiter.Burst()
if b < len(p) {
p = p[:b]
}
n, err = r.r.Read(p)
if err != nil {
return
}
err = r.limiter.WaitN(context.Background(), n)
if err != nil {
return
}
return
}

60
pkg/util/limit/writer.go Normal file
View File

@@ -0,0 +1,60 @@
// Copyright 2019 fatedier, fatedier@gmail.com
//
// 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 limit
import (
"context"
"io"
"golang.org/x/time/rate"
)
type Writer struct {
w io.Writer
limiter *rate.Limiter
}
func NewWriter(w io.Writer, limiter *rate.Limiter) *Writer {
return &Writer{
w: w,
limiter: limiter,
}
}
func (w *Writer) Write(p []byte) (n int, err error) {
var nn int
b := w.limiter.Burst()
for {
end := len(p)
if end == 0 {
break
}
if b < len(p) {
end = b
}
err = w.limiter.WaitN(context.Background(), end)
if err != nil {
return
}
nn, err = w.w.Write(p[:end])
n += nn
if err != nil {
return
}
p = p[end:]
}
return
}

93
pkg/util/log/log.go Normal file
View File

@@ -0,0 +1,93 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// 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 log
import (
"fmt"
"github.com/fatedier/beego/logs"
)
// Log is the under log object
var Log *logs.BeeLogger
func init() {
Log = logs.NewLogger(200)
Log.EnableFuncCallDepth(true)
Log.SetLogFuncCallDepth(Log.GetLogFuncCallDepth() + 1)
}
func InitLog(logWay string, logFile string, logLevel string, maxdays int64, disableLogColor bool) {
SetLogFile(logWay, logFile, maxdays, disableLogColor)
SetLogLevel(logLevel)
}
// SetLogFile to configure log params
// logWay: file or console
func SetLogFile(logWay string, logFile string, maxdays int64, disableLogColor bool) {
if logWay == "console" {
params := ""
if disableLogColor {
params = fmt.Sprintf(`{"color": false}`)
}
Log.SetLogger("console", params)
} else {
params := fmt.Sprintf(`{"filename": "%s", "maxdays": %d}`, logFile, maxdays)
Log.SetLogger("file", params)
}
}
// SetLogLevel set log level, default is warning
// value: error, warning, info, debug, trace
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
case "trace":
level = 8
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...)
}
func Trace(format string, v ...interface{}) {
Log.Trace(format, v...)
}

View File

@@ -0,0 +1,60 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// 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 metric
import (
"sync/atomic"
)
type Counter interface {
Count() int32
Inc(int32)
Dec(int32)
Snapshot() Counter
Clear()
}
func NewCounter() Counter {
return &StandardCounter{
count: 0,
}
}
type StandardCounter struct {
count int32
}
func (c *StandardCounter) Count() int32 {
return atomic.LoadInt32(&c.count)
}
func (c *StandardCounter) Inc(count int32) {
atomic.AddInt32(&c.count, count)
}
func (c *StandardCounter) Dec(count int32) {
atomic.AddInt32(&c.count, -count)
}
func (c *StandardCounter) Snapshot() Counter {
tmp := &StandardCounter{
count: atomic.LoadInt32(&c.count),
}
return tmp
}
func (c *StandardCounter) Clear() {
atomic.StoreInt32(&c.count, 0)
}

View File

@@ -0,0 +1,23 @@
package metric
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCounter(t *testing.T) {
assert := assert.New(t)
c := NewCounter()
c.Inc(10)
assert.EqualValues(10, c.Count())
c.Dec(5)
assert.EqualValues(5, c.Count())
cTmp := c.Snapshot()
assert.EqualValues(5, cTmp.Count())
c.Clear()
assert.EqualValues(0, c.Count())
}

View File

@@ -0,0 +1,134 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// 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 metric
import (
"sync"
"time"
)
type DateCounter interface {
TodayCount() int64
GetLastDaysCount(lastdays int64) []int64
Inc(int64)
Dec(int64)
Snapshot() DateCounter
Clear()
}
func NewDateCounter(reserveDays int64) DateCounter {
if reserveDays <= 0 {
reserveDays = 1
}
return newStandardDateCounter(reserveDays)
}
type StandardDateCounter struct {
reserveDays int64
counts []int64
lastUpdateDate time.Time
mu sync.Mutex
}
func newStandardDateCounter(reserveDays int64) *StandardDateCounter {
now := time.Now()
now = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
s := &StandardDateCounter{
reserveDays: reserveDays,
counts: make([]int64, reserveDays),
lastUpdateDate: now,
}
return s
}
func (c *StandardDateCounter) TodayCount() int64 {
c.mu.Lock()
defer c.mu.Unlock()
c.rotate(time.Now())
return c.counts[0]
}
func (c *StandardDateCounter) GetLastDaysCount(lastdays int64) []int64 {
if lastdays > c.reserveDays {
lastdays = c.reserveDays
}
counts := make([]int64, lastdays)
c.mu.Lock()
defer c.mu.Unlock()
c.rotate(time.Now())
for i := 0; i < int(lastdays); i++ {
counts[i] = c.counts[i]
}
return counts
}
func (c *StandardDateCounter) Inc(count int64) {
c.mu.Lock()
defer c.mu.Unlock()
c.rotate(time.Now())
c.counts[0] += count
}
func (c *StandardDateCounter) Dec(count int64) {
c.mu.Lock()
defer c.mu.Unlock()
c.rotate(time.Now())
c.counts[0] -= count
}
func (c *StandardDateCounter) Snapshot() DateCounter {
c.mu.Lock()
defer c.mu.Unlock()
tmp := newStandardDateCounter(c.reserveDays)
for i := 0; i < int(c.reserveDays); i++ {
tmp.counts[i] = c.counts[i]
}
return tmp
}
func (c *StandardDateCounter) Clear() {
c.mu.Lock()
defer c.mu.Unlock()
for i := 0; i < int(c.reserveDays); i++ {
c.counts[i] = 0
}
}
// rotate
// Must hold the lock before calling this function.
func (c *StandardDateCounter) rotate(now time.Time) {
now = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
days := int(now.Sub(c.lastUpdateDate).Hours() / 24)
defer func() {
c.lastUpdateDate = now
}()
if days <= 0 {
return
} else if days >= int(c.reserveDays) {
c.counts = make([]int64, c.reserveDays)
return
}
newCounts := make([]int64, c.reserveDays)
for i := days; i < int(c.reserveDays); i++ {
newCounts[i] = c.counts[i-days]
}
c.counts = newCounts
}

View File

@@ -0,0 +1,27 @@
package metric
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestDateCounter(t *testing.T) {
assert := assert.New(t)
dc := NewDateCounter(3)
dc.Inc(10)
assert.EqualValues(10, dc.TodayCount())
dc.Dec(5)
assert.EqualValues(5, dc.TodayCount())
counts := dc.GetLastDaysCount(3)
assert.EqualValues(3, len(counts))
assert.EqualValues(5, counts[0])
assert.EqualValues(0, counts[1])
assert.EqualValues(0, counts[2])
dcTmp := dc.Snapshot()
assert.EqualValues(5, dcTmp.TodayCount())
}

View File

@@ -0,0 +1,34 @@
// Copyright 2020 fatedier, fatedier@gmail.com
//
// 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 metric
// GaugeMetric represents a single numerical value that can arbitrarily go up
// and down.
type GaugeMetric interface {
Inc()
Dec()
Set(float64)
}
// CounterMetric represents a single numerical value that only ever
// goes up.
type CounterMetric interface {
Inc()
}
// HistogramMetric counts individual observations.
type HistogramMetric interface {
Observe(float64)
}

243
pkg/util/net/conn.go Normal file
View File

@@ -0,0 +1,243 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// 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 net
import (
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"net"
"sync/atomic"
"time"
"github.com/fatedier/frp/pkg/util/xlog"
gnet "github.com/fatedier/golib/net"
kcp "github.com/fatedier/kcp-go"
)
type ContextGetter interface {
Context() context.Context
}
type ContextSetter interface {
WithContext(ctx context.Context)
}
func NewLogFromConn(conn net.Conn) *xlog.Logger {
if c, ok := conn.(ContextGetter); ok {
return xlog.FromContextSafe(c.Context())
}
return xlog.New()
}
func NewContextFromConn(conn net.Conn) context.Context {
if c, ok := conn.(ContextGetter); ok {
return c.Context()
}
return context.Background()
}
// ContextConn is the connection with context
type ContextConn struct {
net.Conn
ctx context.Context
}
func NewContextConn(ctx context.Context, c net.Conn) *ContextConn {
return &ContextConn{
Conn: c,
ctx: ctx,
}
}
func (c *ContextConn) WithContext(ctx context.Context) {
c.ctx = ctx
}
func (c *ContextConn) Context() context.Context {
return c.ctx
}
type WrapReadWriteCloserConn struct {
io.ReadWriteCloser
underConn net.Conn
}
func WrapReadWriteCloserToConn(rwc io.ReadWriteCloser, underConn net.Conn) net.Conn {
return &WrapReadWriteCloserConn{
ReadWriteCloser: rwc,
underConn: underConn,
}
}
func (conn *WrapReadWriteCloserConn) LocalAddr() net.Addr {
if conn.underConn != nil {
return conn.underConn.LocalAddr()
}
return (*net.TCPAddr)(nil)
}
func (conn *WrapReadWriteCloserConn) RemoteAddr() net.Addr {
if conn.underConn != nil {
return conn.underConn.RemoteAddr()
}
return (*net.TCPAddr)(nil)
}
func (conn *WrapReadWriteCloserConn) SetDeadline(t time.Time) error {
if conn.underConn != nil {
return conn.underConn.SetDeadline(t)
}
return &net.OpError{Op: "set", Net: "wrap", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
func (conn *WrapReadWriteCloserConn) SetReadDeadline(t time.Time) error {
if conn.underConn != nil {
return conn.underConn.SetReadDeadline(t)
}
return &net.OpError{Op: "set", Net: "wrap", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
func (conn *WrapReadWriteCloserConn) SetWriteDeadline(t time.Time) error {
if conn.underConn != nil {
return conn.underConn.SetWriteDeadline(t)
}
return &net.OpError{Op: "set", Net: "wrap", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
type CloseNotifyConn struct {
net.Conn
// 1 means closed
closeFlag int32
closeFn func()
}
// closeFn will be only called once
func WrapCloseNotifyConn(c net.Conn, closeFn func()) net.Conn {
return &CloseNotifyConn{
Conn: c,
closeFn: closeFn,
}
}
func (cc *CloseNotifyConn) Close() (err error) {
pflag := atomic.SwapInt32(&cc.closeFlag, 1)
if pflag == 0 {
err = cc.Close()
if cc.closeFn != nil {
cc.closeFn()
}
}
return
}
type StatsConn struct {
net.Conn
closed int64 // 1 means closed
totalRead int64
totalWrite int64
statsFunc func(totalRead, totalWrite int64)
}
func WrapStatsConn(conn net.Conn, statsFunc func(total, totalWrite int64)) *StatsConn {
return &StatsConn{
Conn: conn,
statsFunc: statsFunc,
}
}
func (statsConn *StatsConn) Read(p []byte) (n int, err error) {
n, err = statsConn.Conn.Read(p)
statsConn.totalRead += int64(n)
return
}
func (statsConn *StatsConn) Write(p []byte) (n int, err error) {
n, err = statsConn.Conn.Write(p)
statsConn.totalWrite += int64(n)
return
}
func (statsConn *StatsConn) Close() (err error) {
old := atomic.SwapInt64(&statsConn.closed, 1)
if old != 1 {
err = statsConn.Conn.Close()
if statsConn.statsFunc != nil {
statsConn.statsFunc(statsConn.totalRead, statsConn.totalWrite)
}
}
return
}
func ConnectServer(protocol string, addr string) (c net.Conn, err error) {
switch protocol {
case "tcp":
return net.Dial("tcp", addr)
case "kcp":
kcpConn, errRet := kcp.DialWithOptions(addr, nil, 10, 3)
if errRet != nil {
err = errRet
return
}
kcpConn.SetStreamMode(true)
kcpConn.SetWriteDelay(true)
kcpConn.SetNoDelay(1, 20, 2, 1)
kcpConn.SetWindowSize(128, 512)
kcpConn.SetMtu(1350)
kcpConn.SetACKNoDelay(false)
kcpConn.SetReadBuffer(4194304)
kcpConn.SetWriteBuffer(4194304)
c = kcpConn
return
default:
return nil, fmt.Errorf("unsupport protocol: %s", protocol)
}
}
func ConnectServerByProxy(proxyURL string, protocol string, addr string) (c net.Conn, err error) {
switch protocol {
case "tcp":
return gnet.DialTcpByProxy(proxyURL, addr)
case "kcp":
// http proxy is not supported for kcp
return ConnectServer(protocol, addr)
case "websocket":
return ConnectWebsocketServer(addr)
default:
return nil, fmt.Errorf("unsupport protocol: %s", protocol)
}
}
func ConnectServerByProxyWithTLS(proxyURL string, protocol string, addr string, tlsConfig *tls.Config) (c net.Conn, err error) {
c, err = ConnectServerByProxy(proxyURL, protocol, addr)
if err != nil {
return
}
if tlsConfig == nil {
return
}
c = WrapTLSClientConn(c, tlsConfig)
return
}

115
pkg/util/net/http.go Normal file
View File

@@ -0,0 +1,115 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// 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 net
import (
"compress/gzip"
"io"
"net/http"
"strings"
)
type HTTPAuthWraper struct {
h http.Handler
user string
passwd string
}
func NewHTTPBasicAuthWraper(h http.Handler, user, passwd string) http.Handler {
return &HTTPAuthWraper{
h: h,
user: user,
passwd: passwd,
}
}
func (aw *HTTPAuthWraper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
user, passwd, hasAuth := r.BasicAuth()
if (aw.user == "" && aw.passwd == "") || (hasAuth && user == aw.user && passwd == aw.passwd) {
aw.h.ServeHTTP(w, r)
} else {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}
}
type HTTPAuthMiddleware struct {
user string
passwd string
}
func NewHTTPAuthMiddleware(user, passwd string) *HTTPAuthMiddleware {
return &HTTPAuthMiddleware{
user: user,
passwd: passwd,
}
}
func (authMid *HTTPAuthMiddleware) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
reqUser, reqPasswd, hasAuth := r.BasicAuth()
if (authMid.user == "" && authMid.passwd == "") ||
(hasAuth && reqUser == authMid.user && reqPasswd == authMid.passwd) {
next.ServeHTTP(w, r)
} else {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}
})
}
func HTTPBasicAuth(h http.HandlerFunc, user, passwd string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
reqUser, reqPasswd, hasAuth := r.BasicAuth()
if (user == "" && passwd == "") ||
(hasAuth && reqUser == user && reqPasswd == passwd) {
h.ServeHTTP(w, r)
} else {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}
}
}
type HTTPGzipWraper struct {
h http.Handler
}
func (gw *HTTPGzipWraper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
gw.h.ServeHTTP(w, r)
return
}
w.Header().Set("Content-Encoding", "gzip")
gz := gzip.NewWriter(w)
defer gz.Close()
gzr := gzipResponseWriter{Writer: gz, ResponseWriter: w}
gw.h.ServeHTTP(gzr, r)
}
func MakeHTTPGzipHandler(h http.Handler) http.Handler {
return &HTTPGzipWraper{
h: h,
}
}
type gzipResponseWriter struct {
io.Writer
http.ResponseWriter
}
func (w gzipResponseWriter) Write(b []byte) (int, error) {
return w.Writer.Write(b)
}

99
pkg/util/net/kcp.go Normal file
View File

@@ -0,0 +1,99 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// 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 net
import (
"fmt"
"net"
kcp "github.com/fatedier/kcp-go"
)
type KCPListener struct {
listener net.Listener
acceptCh chan net.Conn
closeFlag bool
}
func ListenKcp(bindAddr string, bindPort int) (l *KCPListener, err error) {
listener, err := kcp.ListenWithOptions(fmt.Sprintf("%s:%d", bindAddr, bindPort), nil, 10, 3)
if err != nil {
return l, err
}
listener.SetReadBuffer(4194304)
listener.SetWriteBuffer(4194304)
l = &KCPListener{
listener: listener,
acceptCh: make(chan net.Conn),
closeFlag: false,
}
go func() {
for {
conn, err := listener.AcceptKCP()
if err != nil {
if l.closeFlag {
close(l.acceptCh)
return
}
continue
}
conn.SetStreamMode(true)
conn.SetWriteDelay(true)
conn.SetNoDelay(1, 20, 2, 1)
conn.SetMtu(1350)
conn.SetWindowSize(1024, 1024)
conn.SetACKNoDelay(false)
l.acceptCh <- conn
}
}()
return l, err
}
func (l *KCPListener) Accept() (net.Conn, error) {
conn, ok := <-l.acceptCh
if !ok {
return conn, fmt.Errorf("channel for kcp listener closed")
}
return conn, nil
}
func (l *KCPListener) Close() error {
if !l.closeFlag {
l.closeFlag = true
l.listener.Close()
}
return nil
}
func (l *KCPListener) Addr() net.Addr {
return l.listener.Addr()
}
func NewKCPConnFromUDP(conn *net.UDPConn, connected bool, raddr string) (net.Conn, error) {
kcpConn, err := kcp.NewConnEx(1, connected, raddr, nil, 10, 3, conn)
if err != nil {
return nil, err
}
kcpConn.SetStreamMode(true)
kcpConn.SetWriteDelay(true)
kcpConn.SetNoDelay(1, 20, 2, 1)
kcpConn.SetMtu(1350)
kcpConn.SetWindowSize(1024, 1024)
kcpConn.SetACKNoDelay(false)
return kcpConn, nil
}

69
pkg/util/net/listener.go Normal file
View File

@@ -0,0 +1,69 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// 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 net
import (
"fmt"
"net"
"sync"
"github.com/fatedier/golib/errors"
)
// Custom listener
type CustomListener struct {
acceptCh chan net.Conn
closed bool
mu sync.Mutex
}
func NewCustomListener() *CustomListener {
return &CustomListener{
acceptCh: make(chan net.Conn, 64),
}
}
func (l *CustomListener) Accept() (net.Conn, error) {
conn, ok := <-l.acceptCh
if !ok {
return nil, fmt.Errorf("listener closed")
}
return conn, nil
}
func (l *CustomListener) PutConn(conn net.Conn) error {
err := errors.PanicToError(func() {
select {
case l.acceptCh <- conn:
default:
conn.Close()
}
})
return err
}
func (l *CustomListener) Close() error {
l.mu.Lock()
defer l.mu.Unlock()
if !l.closed {
close(l.acceptCh)
l.closed = true
}
return nil
}
func (l *CustomListener) Addr() net.Addr {
return (*net.TCPAddr)(nil)
}

57
pkg/util/net/tls.go Normal file
View File

@@ -0,0 +1,57 @@
// Copyright 2019 fatedier, fatedier@gmail.com
//
// 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 net
import (
"crypto/tls"
"fmt"
"net"
"time"
gnet "github.com/fatedier/golib/net"
)
var (
FRPTLSHeadByte = 0x17
)
func WrapTLSClientConn(c net.Conn, tlsConfig *tls.Config) (out net.Conn) {
c.Write([]byte{byte(FRPTLSHeadByte)})
out = tls.Client(c, tlsConfig)
return
}
func CheckAndEnableTLSServerConnWithTimeout(c net.Conn, tlsConfig *tls.Config, tlsOnly bool, timeout time.Duration) (out net.Conn, err error) {
sc, r := gnet.NewSharedConnSize(c, 2)
buf := make([]byte, 1)
var n int
c.SetReadDeadline(time.Now().Add(timeout))
n, err = r.Read(buf)
c.SetReadDeadline(time.Time{})
if err != nil {
return
}
if n == 1 && int(buf[0]) == FRPTLSHeadByte {
out = tls.Server(c, tlsConfig)
} else {
if tlsOnly {
err = fmt.Errorf("non-TLS connection received on a TlsOnly server")
return
}
out = sc
}
return
}

258
pkg/util/net/udp.go Normal file
View File

@@ -0,0 +1,258 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// 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 net
import (
"fmt"
"io"
"net"
"sync"
"time"
"github.com/fatedier/golib/pool"
)
type UDPPacket struct {
Buf []byte
LocalAddr net.Addr
RemoteAddr net.Addr
}
type FakeUDPConn struct {
l *UDPListener
localAddr net.Addr
remoteAddr net.Addr
packets chan []byte
closeFlag bool
lastActive time.Time
mu sync.RWMutex
}
func NewFakeUDPConn(l *UDPListener, laddr, raddr net.Addr) *FakeUDPConn {
fc := &FakeUDPConn{
l: l,
localAddr: laddr,
remoteAddr: raddr,
packets: make(chan []byte, 20),
}
go func() {
for {
time.Sleep(5 * time.Second)
fc.mu.RLock()
if time.Now().Sub(fc.lastActive) > 10*time.Second {
fc.mu.RUnlock()
fc.Close()
break
}
fc.mu.RUnlock()
}
}()
return fc
}
func (c *FakeUDPConn) putPacket(content []byte) {
defer func() {
if err := recover(); err != nil {
}
}()
select {
case c.packets <- content:
default:
}
}
func (c *FakeUDPConn) Read(b []byte) (n int, err error) {
content, ok := <-c.packets
if !ok {
return 0, io.EOF
}
c.mu.Lock()
c.lastActive = time.Now()
c.mu.Unlock()
if len(b) < len(content) {
n = len(b)
} else {
n = len(content)
}
copy(b, content)
return n, nil
}
func (c *FakeUDPConn) Write(b []byte) (n int, err error) {
c.mu.RLock()
if c.closeFlag {
c.mu.RUnlock()
return 0, io.ErrClosedPipe
}
c.mu.RUnlock()
packet := &UDPPacket{
Buf: b,
LocalAddr: c.localAddr,
RemoteAddr: c.remoteAddr,
}
c.l.writeUDPPacket(packet)
c.mu.Lock()
c.lastActive = time.Now()
c.mu.Unlock()
return len(b), nil
}
func (c *FakeUDPConn) Close() error {
c.mu.Lock()
defer c.mu.Unlock()
if !c.closeFlag {
c.closeFlag = true
close(c.packets)
}
return nil
}
func (c *FakeUDPConn) IsClosed() bool {
c.mu.RLock()
defer c.mu.RUnlock()
return c.closeFlag
}
func (c *FakeUDPConn) LocalAddr() net.Addr {
return c.localAddr
}
func (c *FakeUDPConn) RemoteAddr() net.Addr {
return c.remoteAddr
}
func (c *FakeUDPConn) SetDeadline(t time.Time) error {
return nil
}
func (c *FakeUDPConn) SetReadDeadline(t time.Time) error {
return nil
}
func (c *FakeUDPConn) SetWriteDeadline(t time.Time) error {
return nil
}
type UDPListener struct {
addr net.Addr
acceptCh chan net.Conn
writeCh chan *UDPPacket
readConn net.Conn
closeFlag bool
fakeConns map[string]*FakeUDPConn
}
func ListenUDP(bindAddr string, bindPort int) (l *UDPListener, err error) {
udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", bindAddr, bindPort))
if err != nil {
return l, err
}
readConn, err := net.ListenUDP("udp", udpAddr)
l = &UDPListener{
addr: udpAddr,
acceptCh: make(chan net.Conn),
writeCh: make(chan *UDPPacket, 1000),
fakeConns: make(map[string]*FakeUDPConn),
}
// for reading
go func() {
for {
buf := pool.GetBuf(1450)
n, remoteAddr, err := readConn.ReadFromUDP(buf)
if err != nil {
close(l.acceptCh)
close(l.writeCh)
return
}
fakeConn, exist := l.fakeConns[remoteAddr.String()]
if !exist || fakeConn.IsClosed() {
fakeConn = NewFakeUDPConn(l, l.Addr(), remoteAddr)
l.fakeConns[remoteAddr.String()] = fakeConn
}
fakeConn.putPacket(buf[:n])
l.acceptCh <- fakeConn
}
}()
// for writing
go func() {
for {
packet, ok := <-l.writeCh
if !ok {
return
}
if addr, ok := packet.RemoteAddr.(*net.UDPAddr); ok {
readConn.WriteToUDP(packet.Buf, addr)
}
}
}()
return
}
func (l *UDPListener) writeUDPPacket(packet *UDPPacket) (err error) {
defer func() {
if errRet := recover(); errRet != nil {
err = fmt.Errorf("udp write closed listener")
}
}()
l.writeCh <- packet
return
}
func (l *UDPListener) WriteMsg(buf []byte, remoteAddr *net.UDPAddr) (err error) {
// only set remote addr here
packet := &UDPPacket{
Buf: buf,
RemoteAddr: remoteAddr,
}
err = l.writeUDPPacket(packet)
return
}
func (l *UDPListener) Accept() (net.Conn, error) {
conn, ok := <-l.acceptCh
if !ok {
return conn, fmt.Errorf("channel for udp listener closed")
}
return conn, nil
}
func (l *UDPListener) Close() error {
if !l.closeFlag {
l.closeFlag = true
if l.readConn != nil {
l.readConn.Close()
}
}
return nil
}
func (l *UDPListener) Addr() net.Addr {
return l.addr
}

103
pkg/util/net/websocket.go Normal file
View File

@@ -0,0 +1,103 @@
package net
import (
"errors"
"fmt"
"net"
"net/http"
"net/url"
"time"
"golang.org/x/net/websocket"
)
var (
ErrWebsocketListenerClosed = errors.New("websocket listener closed")
)
const (
FrpWebsocketPath = "/~!frp"
)
type WebsocketListener struct {
ln net.Listener
acceptCh chan net.Conn
server *http.Server
httpMutex *http.ServeMux
}
// NewWebsocketListener to handle websocket connections
// ln: tcp listener for websocket connections
func NewWebsocketListener(ln net.Listener) (wl *WebsocketListener) {
wl = &WebsocketListener{
acceptCh: make(chan net.Conn),
}
muxer := http.NewServeMux()
muxer.Handle(FrpWebsocketPath, websocket.Handler(func(c *websocket.Conn) {
notifyCh := make(chan struct{})
conn := WrapCloseNotifyConn(c, func() {
close(notifyCh)
})
wl.acceptCh <- conn
<-notifyCh
}))
wl.server = &http.Server{
Addr: ln.Addr().String(),
Handler: muxer,
}
go wl.server.Serve(ln)
return
}
func ListenWebsocket(bindAddr string, bindPort int) (*WebsocketListener, error) {
tcpLn, err := net.Listen("tcp", fmt.Sprintf("%s:%d", bindAddr, bindPort))
if err != nil {
return nil, err
}
l := NewWebsocketListener(tcpLn)
return l, nil
}
func (p *WebsocketListener) Accept() (net.Conn, error) {
c, ok := <-p.acceptCh
if !ok {
return nil, ErrWebsocketListenerClosed
}
return c, nil
}
func (p *WebsocketListener) Close() error {
return p.server.Close()
}
func (p *WebsocketListener) Addr() net.Addr {
return p.ln.Addr()
}
// addr: domain:port
func ConnectWebsocketServer(addr string) (net.Conn, error) {
addr = "ws://" + addr + FrpWebsocketPath
uri, err := url.Parse(addr)
if err != nil {
return nil, err
}
origin := "http://" + uri.Host
cfg, err := websocket.NewConfig(addr, origin)
if err != nil {
return nil, err
}
cfg.Dialer = &net.Dialer{
Timeout: 10 * time.Second,
}
conn, err := websocket.DialConfig(cfg)
if err != nil {
return nil, err
}
return conn, nil
}

View File

@@ -0,0 +1,68 @@
// Copyright 2020 guylewin, guy@lewin.co.il
//
// 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 tcpmux
import (
"bufio"
"fmt"
"io"
"net"
"net/http"
"time"
"github.com/fatedier/frp/pkg/util/util"
"github.com/fatedier/frp/pkg/util/vhost"
)
type HTTPConnectTCPMuxer struct {
*vhost.Muxer
}
func NewHTTPConnectTCPMuxer(listener net.Listener, timeout time.Duration) (*HTTPConnectTCPMuxer, error) {
mux, err := vhost.NewMuxer(listener, getHostFromHTTPConnect, nil, sendHTTPOk, nil, timeout)
return &HTTPConnectTCPMuxer{mux}, err
}
func readHTTPConnectRequest(rd io.Reader) (host string, err error) {
bufioReader := bufio.NewReader(rd)
req, err := http.ReadRequest(bufioReader)
if err != nil {
return
}
if req.Method != "CONNECT" {
err = fmt.Errorf("connections to tcp vhost must be of method CONNECT")
return
}
host = util.GetHostFromAddr(req.Host)
return
}
func sendHTTPOk(c net.Conn) error {
return util.OkResponse().Write(c)
}
func getHostFromHTTPConnect(c net.Conn) (_ net.Conn, _ map[string]string, err error) {
reqInfoMap := make(map[string]string, 0)
host, err := readHTTPConnectRequest(c)
if err != nil {
return nil, reqInfoMap, err
}
reqInfoMap["Host"] = host
reqInfoMap["Scheme"] = "tcp"
return c, reqInfoMap, nil
}

44
pkg/util/util/http.go Normal file
View File

@@ -0,0 +1,44 @@
// Copyright 2020 guylewin, guy@lewin.co.il
//
// 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 util
import (
"net/http"
"strings"
)
func OkResponse() *http.Response {
header := make(http.Header)
res := &http.Response{
Status: "OK",
StatusCode: 200,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: header,
}
return res
}
func GetHostFromAddr(addr string) (host string) {
strs := strings.Split(addr, ":")
if len(strs) > 1 {
host = strs[0]
} else {
host = addr
}
return
}

110
pkg/util/util/util.go Normal file
View File

@@ -0,0 +1,110 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// 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 util
import (
"crypto/md5"
"crypto/rand"
"encoding/hex"
"fmt"
"strconv"
"strings"
)
// RandID return a rand string used in frp.
func RandID() (id string, err error) {
return RandIDWithLen(8)
}
// RandIDWithLen return a rand string with idLen length.
func RandIDWithLen(idLen int) (id string, err error) {
b := make([]byte, idLen)
_, err = rand.Read(b)
if err != nil {
return
}
id = fmt.Sprintf("%x", b)
return
}
func GetAuthKey(token string, timestamp int64) (key string) {
token = token + fmt.Sprintf("%d", timestamp)
md5Ctx := md5.New()
md5Ctx.Write([]byte(token))
data := md5Ctx.Sum(nil)
return hex.EncodeToString(data)
}
func CanonicalAddr(host string, port int) (addr string) {
if port == 80 || port == 443 {
addr = host
} else {
addr = fmt.Sprintf("%s:%d", host, port)
}
return
}
func ParseRangeNumbers(rangeStr string) (numbers []int64, err error) {
rangeStr = strings.TrimSpace(rangeStr)
numbers = make([]int64, 0)
// e.g. 1000-2000,2001,2002,3000-4000
numRanges := strings.Split(rangeStr, ",")
for _, numRangeStr := range numRanges {
// 1000-2000 or 2001
numArray := strings.Split(numRangeStr, "-")
// length: only 1 or 2 is correct
rangeType := len(numArray)
if rangeType == 1 {
// single number
singleNum, errRet := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64)
if errRet != nil {
err = fmt.Errorf("range number is invalid, %v", errRet)
return
}
numbers = append(numbers, singleNum)
} else if rangeType == 2 {
// range numbers
min, errRet := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64)
if errRet != nil {
err = fmt.Errorf("range number is invalid, %v", errRet)
return
}
max, errRet := strconv.ParseInt(strings.TrimSpace(numArray[1]), 10, 64)
if errRet != nil {
err = fmt.Errorf("range number is invalid, %v", errRet)
return
}
if max < min {
err = fmt.Errorf("range number is invalid")
return
}
for i := min; i <= max; i++ {
numbers = append(numbers, i)
}
} else {
err = fmt.Errorf("range number is invalid")
return
}
}
return
}
func GenerateResponseErrorString(summary string, err error, detailed bool) string {
if detailed {
return err.Error()
}
return summary
}

View File

@@ -0,0 +1,48 @@
package util
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestRandId(t *testing.T) {
assert := assert.New(t)
id, err := RandID()
assert.NoError(err)
t.Log(id)
assert.Equal(16, len(id))
}
func TestGetAuthKey(t *testing.T) {
assert := assert.New(t)
key := GetAuthKey("1234", 1488720000)
t.Log(key)
assert.Equal("6df41a43725f0c770fd56379e12acf8c", key)
}
func TestParseRangeNumbers(t *testing.T) {
assert := assert.New(t)
numbers, err := ParseRangeNumbers("2-5")
if assert.NoError(err) {
assert.Equal([]int64{2, 3, 4, 5}, numbers)
}
numbers, err = ParseRangeNumbers("1")
if assert.NoError(err) {
assert.Equal([]int64{1}, numbers)
}
numbers, err = ParseRangeNumbers("3-5,8")
if assert.NoError(err) {
assert.Equal([]int64{3, 4, 5, 8}, numbers)
}
numbers, err = ParseRangeNumbers(" 3-5,8, 10-12 ")
if assert.NoError(err) {
assert.Equal([]int64{3, 4, 5, 8, 10, 11, 12}, numbers)
}
_, err = ParseRangeNumbers("3-a")
assert.Error(err)
}

View File

@@ -0,0 +1,82 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// 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 version
import (
"strconv"
"strings"
)
var version string = "0.34.0"
func Full() string {
return version
}
func getSubVersion(v string, position int) int64 {
arr := strings.Split(v, ".")
if len(arr) < 3 {
return 0
}
res, _ := strconv.ParseInt(arr[position], 10, 64)
return res
}
func Proto(v string) int64 {
return getSubVersion(v, 0)
}
func Major(v string) int64 {
return getSubVersion(v, 1)
}
func Minor(v string) int64 {
return getSubVersion(v, 2)
}
// add every case there if server will not accept client's protocol and return false
func Compat(client string) (ok bool, msg string) {
if LessThan(client, "0.18.0") {
return false, "Please upgrade your frpc version to at least 0.18.0"
}
return true, ""
}
func LessThan(client string, server string) bool {
vc := Proto(client)
vs := Proto(server)
if vc > vs {
return false
} else if vc < vs {
return true
}
vc = Major(client)
vs = Major(server)
if vc > vs {
return false
} else if vc < vs {
return true
}
vc = Minor(client)
vs = Minor(server)
if vc > vs {
return false
} else if vc < vs {
return true
}
return false
}

View File

@@ -0,0 +1,65 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// 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 version
import (
"fmt"
"strconv"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestFull(t *testing.T) {
assert := assert.New(t)
version := Full()
arr := strings.Split(version, ".")
assert.Equal(3, len(arr))
proto, err := strconv.ParseInt(arr[0], 10, 64)
assert.NoError(err)
assert.True(proto >= 0)
major, err := strconv.ParseInt(arr[1], 10, 64)
assert.NoError(err)
assert.True(major >= 0)
minor, err := strconv.ParseInt(arr[2], 10, 64)
assert.NoError(err)
assert.True(minor >= 0)
}
func TestVersion(t *testing.T) {
assert := assert.New(t)
proto := Proto(Full())
major := Major(Full())
minor := Minor(Full())
parseVerion := fmt.Sprintf("%d.%d.%d", proto, major, minor)
version := Full()
assert.Equal(parseVerion, version)
}
func TestCompact(t *testing.T) {
assert := assert.New(t)
ok, _ := Compat("0.9.0")
assert.False(ok)
ok, _ = Compat("10.0.0")
assert.True(ok)
ok, _ = Compat("0.10.0")
assert.False(ok)
}

206
pkg/util/vhost/http.go Normal file
View File

@@ -0,0 +1,206 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// 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 vhost
import (
"bytes"
"context"
"errors"
"fmt"
"log"
"net"
"net/http"
"strings"
"time"
frpLog "github.com/fatedier/frp/pkg/util/log"
"github.com/fatedier/frp/pkg/util/util"
"github.com/fatedier/golib/pool"
)
var (
ErrNoDomain = errors.New("no such domain")
)
type HTTPReverseProxyOptions struct {
ResponseHeaderTimeoutS int64
}
type HTTPReverseProxy struct {
proxy *ReverseProxy
vhostRouter *Routers
responseHeaderTimeout time.Duration
}
func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *HTTPReverseProxy {
if option.ResponseHeaderTimeoutS <= 0 {
option.ResponseHeaderTimeoutS = 60
}
rp := &HTTPReverseProxy{
responseHeaderTimeout: time.Duration(option.ResponseHeaderTimeoutS) * time.Second,
vhostRouter: vhostRouter,
}
proxy := &ReverseProxy{
Director: func(req *http.Request) {
req.URL.Scheme = "http"
url := req.Context().Value(RouteInfoURL).(string)
oldHost := util.GetHostFromAddr(req.Context().Value(RouteInfoHost).(string))
host := rp.GetRealHost(oldHost, url)
if host != "" {
req.Host = host
}
req.URL.Host = req.Host
headers := rp.GetHeaders(oldHost, url)
for k, v := range headers {
req.Header.Set(k, v)
}
},
Transport: &http.Transport{
ResponseHeaderTimeout: rp.responseHeaderTimeout,
DisableKeepAlives: true,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
url := ctx.Value(RouteInfoURL).(string)
host := util.GetHostFromAddr(ctx.Value(RouteInfoHost).(string))
remote := ctx.Value(RouteInfoRemote).(string)
return rp.CreateConnection(host, url, remote)
},
},
BufferPool: newWrapPool(),
ErrorLog: log.New(newWrapLogger(), "", 0),
ErrorHandler: func(rw http.ResponseWriter, req *http.Request, err error) {
frpLog.Warn("do http proxy request error: %v", err)
rw.WriteHeader(http.StatusNotFound)
rw.Write(getNotFoundPageContent())
},
}
rp.proxy = proxy
return rp
}
// Register register the route config to reverse proxy
// reverse proxy will use CreateConnFn from routeCfg to create a connection to the remote service
func (rp *HTTPReverseProxy) Register(routeCfg RouteConfig) error {
err := rp.vhostRouter.Add(routeCfg.Domain, routeCfg.Location, &routeCfg)
if err != nil {
return err
}
return nil
}
// UnRegister unregister route config by domain and location
func (rp *HTTPReverseProxy) UnRegister(domain string, location string) {
rp.vhostRouter.Del(domain, location)
}
func (rp *HTTPReverseProxy) GetRealHost(domain string, location string) (host string) {
vr, ok := rp.getVhost(domain, location)
if ok {
host = vr.payload.(*RouteConfig).RewriteHost
}
return
}
func (rp *HTTPReverseProxy) GetHeaders(domain string, location string) (headers map[string]string) {
vr, ok := rp.getVhost(domain, location)
if ok {
headers = vr.payload.(*RouteConfig).Headers
}
return
}
// CreateConnection create a new connection by route config
func (rp *HTTPReverseProxy) CreateConnection(domain string, location string, remoteAddr string) (net.Conn, error) {
vr, ok := rp.getVhost(domain, location)
if ok {
fn := vr.payload.(*RouteConfig).CreateConnFn
if fn != nil {
return fn(remoteAddr)
}
}
return nil, fmt.Errorf("%v: %s %s", ErrNoDomain, domain, location)
}
func (rp *HTTPReverseProxy) CheckAuth(domain, location, user, passwd string) bool {
vr, ok := rp.getVhost(domain, location)
if ok {
checkUser := vr.payload.(*RouteConfig).Username
checkPasswd := vr.payload.(*RouteConfig).Password
if (checkUser != "" || checkPasswd != "") && (checkUser != user || checkPasswd != passwd) {
return false
}
}
return true
}
// getVhost get vhost router by domain and location
func (rp *HTTPReverseProxy) getVhost(domain string, location string) (vr *Router, ok bool) {
// first we check the full hostname
// if not exist, then check the wildcard_domain such as *.example.com
vr, ok = rp.vhostRouter.Get(domain, location)
if ok {
return
}
domainSplit := strings.Split(domain, ".")
if len(domainSplit) < 3 {
return nil, false
}
for {
if len(domainSplit) < 3 {
return nil, false
}
domainSplit[0] = "*"
domain = strings.Join(domainSplit, ".")
vr, ok = rp.vhostRouter.Get(domain, location)
if ok {
return vr, true
}
domainSplit = domainSplit[1:]
}
}
func (rp *HTTPReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
domain := util.GetHostFromAddr(req.Host)
location := req.URL.Path
user, passwd, _ := req.BasicAuth()
if !rp.CheckAuth(domain, location, user, passwd) {
rw.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
rp.proxy.ServeHTTP(rw, req)
}
type wrapPool struct{}
func newWrapPool() *wrapPool { return &wrapPool{} }
func (p *wrapPool) Get() []byte { return pool.GetBuf(32 * 1024) }
func (p *wrapPool) Put(buf []byte) { pool.PutBuf(buf) }
type wrapLogger struct{}
func newWrapLogger() *wrapLogger { return &wrapLogger{} }
func (l *wrapLogger) Write(p []byte) (n int, err error) {
frpLog.Warn("%s", string(bytes.TrimRight(p, "\n")))
return len(p), nil
}

193
pkg/util/vhost/https.go Normal file
View File

@@ -0,0 +1,193 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// 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 vhost
import (
"fmt"
"io"
"net"
"strings"
"time"
gnet "github.com/fatedier/golib/net"
"github.com/fatedier/golib/pool"
)
const (
typeClientHello uint8 = 1 // Type client hello
)
// TLS extension numbers
const (
extensionServerName uint16 = 0
extensionStatusRequest uint16 = 5
extensionSupportedCurves uint16 = 10
extensionSupportedPoints uint16 = 11
extensionSignatureAlgorithms uint16 = 13
extensionALPN uint16 = 16
extensionSCT uint16 = 18
extensionSessionTicket uint16 = 35
extensionNextProtoNeg uint16 = 13172 // not IANA assigned
extensionRenegotiationInfo uint16 = 0xff01
)
type HTTPSMuxer struct {
*Muxer
}
func NewHTTPSMuxer(listener net.Listener, timeout time.Duration) (*HTTPSMuxer, error) {
mux, err := NewMuxer(listener, GetHTTPSHostname, nil, nil, nil, timeout)
return &HTTPSMuxer{mux}, err
}
func readHandshake(rd io.Reader) (host string, err error) {
data := pool.GetBuf(1024)
origin := data
defer pool.PutBuf(origin)
_, err = io.ReadFull(rd, data[:47])
if err != nil {
return
}
length, err := rd.Read(data[47:])
if err != nil {
return
}
length += 47
data = data[:length]
if uint8(data[5]) != typeClientHello {
err = fmt.Errorf("readHandshake: type[%d] is not clientHello", uint16(data[5]))
return
}
// session
sessionIDLen := int(data[43])
if sessionIDLen > 32 || len(data) < 44+sessionIDLen {
err = fmt.Errorf("readHandshake: sessionIdLen[%d] is long", sessionIDLen)
return
}
data = data[44+sessionIDLen:]
if len(data) < 2 {
err = fmt.Errorf("readHandshake: dataLen[%d] after session is short", len(data))
return
}
// cipher suite numbers
cipherSuiteLen := int(data[0])<<8 | int(data[1])
if cipherSuiteLen%2 == 1 || len(data) < 2+cipherSuiteLen {
err = fmt.Errorf("readHandshake: dataLen[%d] after cipher suite is short", len(data))
return
}
data = data[2+cipherSuiteLen:]
if len(data) < 1 {
err = fmt.Errorf("readHandshake: cipherSuiteLen[%d] is long", cipherSuiteLen)
return
}
// compression method
compressionMethodsLen := int(data[0])
if len(data) < 1+compressionMethodsLen {
err = fmt.Errorf("readHandshake: compressionMethodsLen[%d] is long", compressionMethodsLen)
return
}
data = data[1+compressionMethodsLen:]
if len(data) == 0 {
// ClientHello is optionally followed by extension data
err = fmt.Errorf("readHandshake: there is no extension data to get servername")
return
}
if len(data) < 2 {
err = fmt.Errorf("readHandshake: extension dataLen[%d] is too short", len(data))
return
}
extensionsLength := int(data[0])<<8 | int(data[1])
data = data[2:]
if extensionsLength != len(data) {
err = fmt.Errorf("readHandshake: extensionsLen[%d] is not equal to dataLen[%d]", extensionsLength, len(data))
return
}
for len(data) != 0 {
if len(data) < 4 {
err = fmt.Errorf("readHandshake: extensionsDataLen[%d] is too short", len(data))
return
}
extension := uint16(data[0])<<8 | uint16(data[1])
length := int(data[2])<<8 | int(data[3])
data = data[4:]
if len(data) < length {
err = fmt.Errorf("readHandshake: extensionLen[%d] is long", length)
return
}
switch extension {
case extensionRenegotiationInfo:
if length != 1 || data[0] != 0 {
err = fmt.Errorf("readHandshake: extension reNegotiationInfoLen[%d] is short", length)
return
}
case extensionNextProtoNeg:
case extensionStatusRequest:
case extensionServerName:
d := data[:length]
if len(d) < 2 {
err = fmt.Errorf("readHandshake: remiaining dataLen[%d] is short", len(d))
return
}
namesLen := int(d[0])<<8 | int(d[1])
d = d[2:]
if len(d) != namesLen {
err = fmt.Errorf("readHandshake: nameListLen[%d] is not equal to dataLen[%d]", namesLen, len(d))
return
}
for len(d) > 0 {
if len(d) < 3 {
err = fmt.Errorf("readHandshake: extension serverNameLen[%d] is short", len(d))
return
}
nameType := d[0]
nameLen := int(d[1])<<8 | int(d[2])
d = d[3:]
if len(d) < nameLen {
err = fmt.Errorf("readHandshake: nameLen[%d] is not equal to dataLen[%d]", nameLen, len(d))
return
}
if nameType == 0 {
serverName := string(d[:nameLen])
host = strings.TrimSpace(serverName)
return host, nil
}
d = d[nameLen:]
}
}
data = data[length:]
}
err = fmt.Errorf("Unknown error")
return
}
func GetHTTPSHostname(c net.Conn) (_ net.Conn, _ map[string]string, err error) {
reqInfoMap := make(map[string]string, 0)
sc, rd := gnet.NewSharedConn(c)
host, err := readHandshake(rd)
if err != nil {
return nil, reqInfoMap, err
}
reqInfoMap["Host"] = host
reqInfoMap["Scheme"] = "https"
return sc, reqInfoMap, nil
}

100
pkg/util/vhost/resource.go Normal file
View File

@@ -0,0 +1,100 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// 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 vhost
import (
"bytes"
"io/ioutil"
"net/http"
frpLog "github.com/fatedier/frp/pkg/util/log"
"github.com/fatedier/frp/pkg/util/version"
)
var (
NotFoundPagePath = ""
)
const (
NotFound = `<!DOCTYPE html>
<html>
<head>
<title>Not Found</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>The page you requested was not found.</h1>
<p>Sorry, the page you are looking for is currently unavailable.<br/>
Please try again later.</p>
<p>The server is powered by <a href="https://github.com/fatedier/frp">frp</a>.</p>
<p><em>Faithfully yours, frp.</em></p>
</body>
</html>
`
)
func getNotFoundPageContent() []byte {
var (
buf []byte
err error
)
if NotFoundPagePath != "" {
buf, err = ioutil.ReadFile(NotFoundPagePath)
if err != nil {
frpLog.Warn("read custom 404 page error: %v", err)
buf = []byte(NotFound)
}
} else {
buf = []byte(NotFound)
}
return buf
}
func notFoundResponse() *http.Response {
header := make(http.Header)
header.Set("server", "frp/"+version.Full())
header.Set("Content-Type", "text/html")
res := &http.Response{
Status: "Not Found",
StatusCode: 404,
Proto: "HTTP/1.0",
ProtoMajor: 1,
ProtoMinor: 0,
Header: header,
Body: ioutil.NopCloser(bytes.NewReader(getNotFoundPageContent())),
}
return res
}
func noAuthResponse() *http.Response {
header := make(map[string][]string)
header["WWW-Authenticate"] = []string{`Basic realm="Restricted"`}
res := &http.Response{
Status: "401 Not authorized",
StatusCode: 401,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: header,
}
return res
}

View File

@@ -0,0 +1,563 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// HTTP reverse proxy handler
package vhost
import (
"context"
"fmt"
"io"
"log"
"net"
"net/http"
"net/url"
"strings"
"sync"
"time"
"golang.org/x/net/http/httpguts"
)
// ReverseProxy is an HTTP Handler that takes an incoming request and
// sends it to another server, proxying the response back to the
// client.
type ReverseProxy struct {
// Director must be a function which modifies
// the request into a new request to be sent
// using Transport. Its response is then copied
// back to the original client unmodified.
// Director must not access the provided Request
// after returning.
Director func(*http.Request)
// The transport used to perform proxy requests.
// If nil, http.DefaultTransport is used.
Transport http.RoundTripper
// FlushInterval specifies the flush interval
// to flush to the client while copying the
// response body.
// If zero, no periodic flushing is done.
// A negative value means to flush immediately
// after each write to the client.
// The FlushInterval is ignored when ReverseProxy
// recognizes a response as a streaming response;
// for such responses, writes are flushed to the client
// immediately.
FlushInterval time.Duration
// ErrorLog specifies an optional logger for errors
// that occur when attempting to proxy the request.
// If nil, logging is done via the log package's standard logger.
ErrorLog *log.Logger
// BufferPool optionally specifies a buffer pool to
// get byte slices for use by io.CopyBuffer when
// copying HTTP response bodies.
BufferPool BufferPool
// ModifyResponse is an optional function that modifies the
// Response from the backend. It is called if the backend
// returns a response at all, with any HTTP status code.
// If the backend is unreachable, the optional ErrorHandler is
// called without any call to ModifyResponse.
//
// If ModifyResponse returns an error, ErrorHandler is called
// with its error value. If ErrorHandler is nil, its default
// implementation is used.
ModifyResponse func(*http.Response) error
// ErrorHandler is an optional function that handles errors
// reaching the backend or errors from ModifyResponse.
//
// If nil, the default is to log the provided error and return
// a 502 Status Bad Gateway response.
ErrorHandler func(http.ResponseWriter, *http.Request, error)
}
// A BufferPool is an interface for getting and returning temporary
// byte slices for use by io.CopyBuffer.
type BufferPool interface {
Get() []byte
Put([]byte)
}
func singleJoiningSlash(a, b string) string {
aslash := strings.HasSuffix(a, "/")
bslash := strings.HasPrefix(b, "/")
switch {
case aslash && bslash:
return a + b[1:]
case !aslash && !bslash:
return a + "/" + b
}
return a + b
}
// NewSingleHostReverseProxy returns a new ReverseProxy that routes
// URLs to the scheme, host, and base path provided in target. If the
// target's path is "/base" and the incoming request was for "/dir",
// the target request will be for /base/dir.
// NewSingleHostReverseProxy does not rewrite the Host header.
// To rewrite Host headers, use ReverseProxy directly with a custom
// Director policy.
func NewSingleHostReverseProxy(target *url.URL) *ReverseProxy {
targetQuery := target.RawQuery
director := func(req *http.Request) {
req.URL.Scheme = target.Scheme
req.URL.Host = target.Host
req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path)
if targetQuery == "" || req.URL.RawQuery == "" {
req.URL.RawQuery = targetQuery + req.URL.RawQuery
} else {
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
}
if _, ok := req.Header["User-Agent"]; !ok {
// explicitly disable User-Agent so it's not set to default value
req.Header.Set("User-Agent", "")
}
}
return &ReverseProxy{Director: director}
}
func copyHeader(dst, src http.Header) {
for k, vv := range src {
for _, v := range vv {
dst.Add(k, v)
}
}
}
// Hop-by-hop headers. These are removed when sent to the backend.
// As of RFC 7230, hop-by-hop headers are required to appear in the
// Connection header field. These are the headers defined by the
// obsoleted RFC 2616 (section 13.5.1) and are used for backward
// compatibility.
var hopHeaders = []string{
"Connection",
"Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google
"Keep-Alive",
"Proxy-Authenticate",
"Proxy-Authorization",
"Te", // canonicalized version of "TE"
"Trailer", // not Trailers per URL above; https://www.rfc-editor.org/errata_search.php?eid=4522
"Transfer-Encoding",
"Upgrade",
}
func (p *ReverseProxy) defaultErrorHandler(rw http.ResponseWriter, req *http.Request, err error) {
p.logf("http: proxy error: %v", err)
rw.WriteHeader(http.StatusBadGateway)
}
func (p *ReverseProxy) getErrorHandler() func(http.ResponseWriter, *http.Request, error) {
if p.ErrorHandler != nil {
return p.ErrorHandler
}
return p.defaultErrorHandler
}
// modifyResponse conditionally runs the optional ModifyResponse hook
// and reports whether the request should proceed.
func (p *ReverseProxy) modifyResponse(rw http.ResponseWriter, res *http.Response, req *http.Request) bool {
if p.ModifyResponse == nil {
return true
}
if err := p.ModifyResponse(res); err != nil {
res.Body.Close()
p.getErrorHandler()(rw, req, err)
return false
}
return true
}
func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
transport := p.Transport
if transport == nil {
transport = http.DefaultTransport
}
ctx := req.Context()
if cn, ok := rw.(http.CloseNotifier); ok {
var cancel context.CancelFunc
ctx, cancel = context.WithCancel(ctx)
defer cancel()
notifyChan := cn.CloseNotify()
go func() {
select {
case <-notifyChan:
cancel()
case <-ctx.Done():
}
}()
}
outreq := req.WithContext(ctx)
if req.ContentLength == 0 {
outreq.Body = nil // Issue 16036: nil Body for http.Transport retries
}
// =============================
// Modified for frp
outreq = outreq.WithContext(context.WithValue(outreq.Context(), RouteInfoURL, req.URL.Path))
outreq = outreq.WithContext(context.WithValue(outreq.Context(), RouteInfoHost, req.Host))
outreq = outreq.WithContext(context.WithValue(outreq.Context(), RouteInfoRemote, req.RemoteAddr))
// =============================
p.Director(outreq)
outreq.Close = false
reqUpType := upgradeType(outreq.Header)
removeConnectionHeaders(outreq.Header)
// Remove hop-by-hop headers to the backend. Especially
// important is "Connection" because we want a persistent
// connection, regardless of what the client sent to us.
for _, h := range hopHeaders {
hv := outreq.Header.Get(h)
if hv == "" {
continue
}
if h == "Te" && hv == "trailers" {
// Issue 21096: tell backend applications that
// care about trailer support that we support
// trailers. (We do, but we don't go out of
// our way to advertise that unless the
// incoming client request thought it was
// worth mentioning)
continue
}
outreq.Header.Del(h)
}
// After stripping all the hop-by-hop connection headers above, add back any
// necessary for protocol upgrades, such as for websockets.
if reqUpType != "" {
outreq.Header.Set("Connection", "Upgrade")
outreq.Header.Set("Upgrade", reqUpType)
}
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
// If we aren't the first proxy retain prior
// X-Forwarded-For information as a comma+space
// separated list and fold multiple headers into one.
if prior, ok := outreq.Header["X-Forwarded-For"]; ok {
clientIP = strings.Join(prior, ", ") + ", " + clientIP
}
outreq.Header.Set("X-Forwarded-For", clientIP)
}
res, err := transport.RoundTrip(outreq)
if err != nil {
p.getErrorHandler()(rw, outreq, err)
return
}
// Deal with 101 Switching Protocols responses: (WebSocket, h2c, etc)
if res.StatusCode == http.StatusSwitchingProtocols {
if !p.modifyResponse(rw, res, outreq) {
return
}
p.handleUpgradeResponse(rw, outreq, res)
return
}
removeConnectionHeaders(res.Header)
for _, h := range hopHeaders {
res.Header.Del(h)
}
if !p.modifyResponse(rw, res, outreq) {
return
}
copyHeader(rw.Header(), res.Header)
// The "Trailer" header isn't included in the Transport's response,
// at least for *http.Transport. Build it up from Trailer.
announcedTrailers := len(res.Trailer)
if announcedTrailers > 0 {
trailerKeys := make([]string, 0, len(res.Trailer))
for k := range res.Trailer {
trailerKeys = append(trailerKeys, k)
}
rw.Header().Add("Trailer", strings.Join(trailerKeys, ", "))
}
rw.WriteHeader(res.StatusCode)
err = p.copyResponse(rw, res.Body, p.flushInterval(req, res))
if err != nil {
defer res.Body.Close()
// Since we're streaming the response, if we run into an error all we can do
// is abort the request. Issue 23643: ReverseProxy should use ErrAbortHandler
// on read error while copying body.
if !shouldPanicOnCopyError(req) {
p.logf("suppressing panic for copyResponse error in test; copy error: %v", err)
return
}
panic(http.ErrAbortHandler)
}
res.Body.Close() // close now, instead of defer, to populate res.Trailer
if len(res.Trailer) > 0 {
// Force chunking if we saw a response trailer.
// This prevents net/http from calculating the length for short
// bodies and adding a Content-Length.
if fl, ok := rw.(http.Flusher); ok {
fl.Flush()
}
}
if len(res.Trailer) == announcedTrailers {
copyHeader(rw.Header(), res.Trailer)
return
}
for k, vv := range res.Trailer {
k = http.TrailerPrefix + k
for _, v := range vv {
rw.Header().Add(k, v)
}
}
}
var inOurTests bool // whether we're in our own tests
// shouldPanicOnCopyError reports whether the reverse proxy should
// panic with http.ErrAbortHandler. This is the right thing to do by
// default, but Go 1.10 and earlier did not, so existing unit tests
// weren't expecting panics. Only panic in our own tests, or when
// running under the HTTP server.
func shouldPanicOnCopyError(req *http.Request) bool {
if inOurTests {
// Our tests know to handle this panic.
return true
}
if req.Context().Value(http.ServerContextKey) != nil {
// We seem to be running under an HTTP server, so
// it'll recover the panic.
return true
}
// Otherwise act like Go 1.10 and earlier to not break
// existing tests.
return false
}
// removeConnectionHeaders removes hop-by-hop headers listed in the "Connection" header of h.
// See RFC 7230, section 6.1
func removeConnectionHeaders(h http.Header) {
for _, f := range h["Connection"] {
for _, sf := range strings.Split(f, ",") {
if sf = strings.TrimSpace(sf); sf != "" {
h.Del(sf)
}
}
}
}
// flushInterval returns the p.FlushInterval value, conditionally
// overriding its value for a specific request/response.
func (p *ReverseProxy) flushInterval(req *http.Request, res *http.Response) time.Duration {
resCT := res.Header.Get("Content-Type")
// For Server-Sent Events responses, flush immediately.
// The MIME type is defined in https://www.w3.org/TR/eventsource/#text-event-stream
if resCT == "text/event-stream" {
return -1 // negative means immediately
}
// TODO: more specific cases? e.g. res.ContentLength == -1?
return p.FlushInterval
}
func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader, flushInterval time.Duration) error {
if flushInterval != 0 {
if wf, ok := dst.(writeFlusher); ok {
mlw := &maxLatencyWriter{
dst: wf,
latency: flushInterval,
}
defer mlw.stop()
// set up initial timer so headers get flushed even if body writes are delayed
mlw.flushPending = true
mlw.t = time.AfterFunc(flushInterval, mlw.delayedFlush)
dst = mlw
}
}
var buf []byte
if p.BufferPool != nil {
buf = p.BufferPool.Get()
defer p.BufferPool.Put(buf)
}
_, err := p.copyBuffer(dst, src, buf)
return err
}
// copyBuffer returns any write errors or non-EOF read errors, and the amount
// of bytes written.
func (p *ReverseProxy) copyBuffer(dst io.Writer, src io.Reader, buf []byte) (int64, error) {
if len(buf) == 0 {
buf = make([]byte, 32*1024)
}
var written int64
for {
nr, rerr := src.Read(buf)
if rerr != nil && rerr != io.EOF && rerr != context.Canceled {
p.logf("httputil: ReverseProxy read error during body copy: %v", rerr)
}
if nr > 0 {
nw, werr := dst.Write(buf[:nr])
if nw > 0 {
written += int64(nw)
}
if werr != nil {
return written, werr
}
if nr != nw {
return written, io.ErrShortWrite
}
}
if rerr != nil {
if rerr == io.EOF {
rerr = nil
}
return written, rerr
}
}
}
func (p *ReverseProxy) logf(format string, args ...interface{}) {
if p.ErrorLog != nil {
p.ErrorLog.Printf(format, args...)
} else {
log.Printf(format, args...)
}
}
type writeFlusher interface {
io.Writer
http.Flusher
}
type maxLatencyWriter struct {
dst writeFlusher
latency time.Duration // non-zero; negative means to flush immediately
mu sync.Mutex // protects t, flushPending, and dst.Flush
t *time.Timer
flushPending bool
}
func (m *maxLatencyWriter) Write(p []byte) (n int, err error) {
m.mu.Lock()
defer m.mu.Unlock()
n, err = m.dst.Write(p)
if m.latency < 0 {
m.dst.Flush()
return
}
if m.flushPending {
return
}
if m.t == nil {
m.t = time.AfterFunc(m.latency, m.delayedFlush)
} else {
m.t.Reset(m.latency)
}
m.flushPending = true
return
}
func (m *maxLatencyWriter) delayedFlush() {
m.mu.Lock()
defer m.mu.Unlock()
if !m.flushPending { // if stop was called but AfterFunc already started this goroutine
return
}
m.dst.Flush()
m.flushPending = false
}
func (m *maxLatencyWriter) stop() {
m.mu.Lock()
defer m.mu.Unlock()
m.flushPending = false
if m.t != nil {
m.t.Stop()
}
}
func upgradeType(h http.Header) string {
if !httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade") {
return ""
}
return strings.ToLower(h.Get("Upgrade"))
}
func (p *ReverseProxy) handleUpgradeResponse(rw http.ResponseWriter, req *http.Request, res *http.Response) {
reqUpType := upgradeType(req.Header)
resUpType := upgradeType(res.Header)
if reqUpType != resUpType {
p.getErrorHandler()(rw, req, fmt.Errorf("backend tried to switch protocol %q when %q was requested", resUpType, reqUpType))
return
}
copyHeader(res.Header, rw.Header())
hj, ok := rw.(http.Hijacker)
if !ok {
p.getErrorHandler()(rw, req, fmt.Errorf("can't switch protocols using non-Hijacker ResponseWriter type %T", rw))
return
}
backConn, ok := res.Body.(io.ReadWriteCloser)
if !ok {
p.getErrorHandler()(rw, req, fmt.Errorf("internal error: 101 switching protocols response with non-writable body"))
return
}
defer backConn.Close()
conn, brw, err := hj.Hijack()
if err != nil {
p.getErrorHandler()(rw, req, fmt.Errorf("Hijack failed on protocol switch: %v", err))
return
}
defer conn.Close()
res.Body = nil // so res.Write only writes the headers; we have res.Body in backConn above
if err := res.Write(brw); err != nil {
p.getErrorHandler()(rw, req, fmt.Errorf("response write: %v", err))
return
}
if err := brw.Flush(); err != nil {
p.getErrorHandler()(rw, req, fmt.Errorf("response flush: %v", err))
return
}
errc := make(chan error, 1)
spc := switchProtocolCopier{user: conn, backend: backConn}
go spc.copyToBackend(errc)
go spc.copyFromBackend(errc)
<-errc
return
}
// switchProtocolCopier exists so goroutines proxying data back and
// forth have nice names in stacks.
type switchProtocolCopier struct {
user, backend io.ReadWriter
}
func (c switchProtocolCopier) copyFromBackend(errc chan<- error) {
_, err := io.Copy(c.user, c.backend)
errc <- err
}
func (c switchProtocolCopier) copyToBackend(errc chan<- error) {
_, err := io.Copy(c.backend, c.user)
errc <- err
}

119
pkg/util/vhost/router.go Normal file
View File

@@ -0,0 +1,119 @@
package vhost
import (
"errors"
"sort"
"strings"
"sync"
)
var (
ErrRouterConfigConflict = errors.New("router config conflict")
)
type Routers struct {
RouterByDomain map[string][]*Router
mutex sync.RWMutex
}
type Router struct {
domain string
location string
payload interface{}
}
func NewRouters() *Routers {
return &Routers{
RouterByDomain: make(map[string][]*Router),
}
}
func (r *Routers) Add(domain, location string, payload interface{}) error {
r.mutex.Lock()
defer r.mutex.Unlock()
if _, exist := r.exist(domain, location); exist {
return ErrRouterConfigConflict
}
vrs, found := r.RouterByDomain[domain]
if !found {
vrs = make([]*Router, 0, 1)
}
vr := &Router{
domain: domain,
location: location,
payload: payload,
}
vrs = append(vrs, vr)
sort.Sort(sort.Reverse(ByLocation(vrs)))
r.RouterByDomain[domain] = vrs
return nil
}
func (r *Routers) Del(domain, location string) {
r.mutex.Lock()
defer r.mutex.Unlock()
vrs, found := r.RouterByDomain[domain]
if !found {
return
}
newVrs := make([]*Router, 0)
for _, vr := range vrs {
if vr.location != location {
newVrs = append(newVrs, vr)
}
}
r.RouterByDomain[domain] = newVrs
}
func (r *Routers) Get(host, path string) (vr *Router, exist bool) {
r.mutex.RLock()
defer r.mutex.RUnlock()
vrs, found := r.RouterByDomain[host]
if !found {
return
}
// can't support load balance, will to do
for _, vr = range vrs {
if strings.HasPrefix(path, vr.location) {
return vr, true
}
}
return
}
func (r *Routers) exist(host, path string) (vr *Router, exist bool) {
vrs, found := r.RouterByDomain[host]
if !found {
return
}
for _, vr = range vrs {
if path == vr.location {
return vr, true
}
}
return
}
// sort by location
type ByLocation []*Router
func (a ByLocation) Len() int {
return len(a)
}
func (a ByLocation) Swap(i, j int) {
a[i], a[j] = a[j], a[i]
}
func (a ByLocation) Less(i, j int) bool {
return strings.Compare(a[i].location, a[j].location) < 0
}

245
pkg/util/vhost/vhost.go Normal file
View File

@@ -0,0 +1,245 @@
// 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 vhost
import (
"context"
"fmt"
"net"
"strings"
"time"
"github.com/fatedier/frp/pkg/util/log"
frpNet "github.com/fatedier/frp/pkg/util/net"
"github.com/fatedier/frp/pkg/util/xlog"
"github.com/fatedier/golib/errors"
)
type RouteInfo string
const (
RouteInfoURL RouteInfo = "url"
RouteInfoHost RouteInfo = "host"
RouteInfoRemote RouteInfo = "remote"
)
type muxFunc func(net.Conn) (net.Conn, map[string]string, error)
type httpAuthFunc func(net.Conn, string, string, string) (bool, error)
type hostRewriteFunc func(net.Conn, string) (net.Conn, error)
type successFunc func(net.Conn) error
type Muxer struct {
listener net.Listener
timeout time.Duration
vhostFunc muxFunc
authFunc httpAuthFunc
successFunc successFunc
rewriteFunc hostRewriteFunc
registryRouter *Routers
}
func NewMuxer(listener net.Listener, vhostFunc muxFunc, authFunc httpAuthFunc, successFunc successFunc, rewriteFunc hostRewriteFunc, timeout time.Duration) (mux *Muxer, err error) {
mux = &Muxer{
listener: listener,
timeout: timeout,
vhostFunc: vhostFunc,
authFunc: authFunc,
successFunc: successFunc,
rewriteFunc: rewriteFunc,
registryRouter: NewRouters(),
}
go mux.run()
return mux, nil
}
type CreateConnFunc func(remoteAddr string) (net.Conn, error)
// RouteConfig is the params used to match HTTP requests
type RouteConfig struct {
Domain string
Location string
RewriteHost string
Username string
Password string
Headers map[string]string
CreateConnFn CreateConnFunc
}
// listen for a new domain name, if rewriteHost is not empty and rewriteFunc is not nil
// then rewrite the host header to rewriteHost
func (v *Muxer) Listen(ctx context.Context, cfg *RouteConfig) (l *Listener, err error) {
l = &Listener{
name: cfg.Domain,
location: cfg.Location,
rewriteHost: cfg.RewriteHost,
userName: cfg.Username,
passWord: cfg.Password,
mux: v,
accept: make(chan net.Conn),
ctx: ctx,
}
err = v.registryRouter.Add(cfg.Domain, cfg.Location, l)
if err != nil {
return
}
return l, nil
}
func (v *Muxer) getListener(name, path string) (l *Listener, exist bool) {
// first we check the full hostname
// if not exist, then check the wildcard_domain such as *.example.com
vr, found := v.registryRouter.Get(name, path)
if found {
return vr.payload.(*Listener), true
}
domainSplit := strings.Split(name, ".")
if len(domainSplit) < 3 {
return
}
for {
if len(domainSplit) < 3 {
return
}
domainSplit[0] = "*"
name = strings.Join(domainSplit, ".")
vr, found = v.registryRouter.Get(name, path)
if found {
return vr.payload.(*Listener), true
}
domainSplit = domainSplit[1:]
}
}
func (v *Muxer) run() {
for {
conn, err := v.listener.Accept()
if err != nil {
return
}
go v.handle(conn)
}
}
func (v *Muxer) handle(c net.Conn) {
if err := c.SetDeadline(time.Now().Add(v.timeout)); err != nil {
c.Close()
return
}
sConn, reqInfoMap, err := v.vhostFunc(c)
if err != nil {
log.Warn("get hostname from http/https request error: %v", err)
c.Close()
return
}
name := strings.ToLower(reqInfoMap["Host"])
path := strings.ToLower(reqInfoMap["Path"])
l, ok := v.getListener(name, path)
if !ok {
res := notFoundResponse()
res.Write(c)
log.Debug("http request for host [%s] path [%s] not found", name, path)
c.Close()
return
}
xl := xlog.FromContextSafe(l.ctx)
if v.successFunc != nil {
if err := v.successFunc(c); err != nil {
xl.Info("success func failure on vhost connection: %v", err)
c.Close()
return
}
}
// if authFunc is exist and userName/password is set
// then verify user access
if l.mux.authFunc != nil && l.userName != "" && l.passWord != "" {
bAccess, err := l.mux.authFunc(c, l.userName, l.passWord, reqInfoMap["Authorization"])
if bAccess == false || err != nil {
xl.Debug("check http Authorization failed")
res := noAuthResponse()
res.Write(c)
c.Close()
return
}
}
if err = sConn.SetDeadline(time.Time{}); err != nil {
c.Close()
return
}
c = sConn
xl.Debug("get new http request host [%s] path [%s]", name, path)
err = errors.PanicToError(func() {
l.accept <- c
})
if err != nil {
xl.Warn("listener is already closed, ignore this request")
}
}
type Listener struct {
name string
location string
rewriteHost string
userName string
passWord string
mux *Muxer // for closing Muxer
accept chan net.Conn
ctx context.Context
}
func (l *Listener) Accept() (net.Conn, error) {
xl := xlog.FromContextSafe(l.ctx)
conn, ok := <-l.accept
if !ok {
return nil, fmt.Errorf("Listener closed")
}
// if rewriteFunc is exist
// rewrite http requests with a modified host header
// if l.rewriteHost is empty, nothing to do
if l.mux.rewriteFunc != nil {
sConn, err := l.mux.rewriteFunc(conn, l.rewriteHost)
if err != nil {
xl.Warn("host header rewrite failed: %v", err)
return nil, fmt.Errorf("host header rewrite failed")
}
xl.Debug("rewrite host to [%s] success", l.rewriteHost)
conn = sConn
}
return frpNet.NewContextConn(l.ctx, conn), nil
}
func (l *Listener) Close() error {
l.mux.registryRouter.Del(l.name, l.location)
close(l.accept)
return nil
}
func (l *Listener) Name() string {
return l.name
}
func (l *Listener) Addr() net.Addr {
return (*net.TCPAddr)(nil)
}

42
pkg/util/xlog/ctx.go Normal file
View File

@@ -0,0 +1,42 @@
// Copyright 2019 fatedier, fatedier@gmail.com
//
// 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 xlog
import (
"context"
)
type key int
const (
xlogKey key = 0
)
func NewContext(ctx context.Context, xl *Logger) context.Context {
return context.WithValue(ctx, xlogKey, xl)
}
func FromContext(ctx context.Context) (xl *Logger, ok bool) {
xl, ok = ctx.Value(xlogKey).(*Logger)
return
}
func FromContextSafe(ctx context.Context) *Logger {
xl, ok := ctx.Value(xlogKey).(*Logger)
if !ok {
xl = New()
}
return xl
}

73
pkg/util/xlog/xlog.go Normal file
View File

@@ -0,0 +1,73 @@
// Copyright 2019 fatedier, fatedier@gmail.com
//
// 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 xlog
import (
"github.com/fatedier/frp/pkg/util/log"
)
// Logger is not thread safety for operations on prefix
type Logger struct {
prefixes []string
prefixString string
}
func New() *Logger {
return &Logger{
prefixes: make([]string, 0),
}
}
func (l *Logger) ResetPrefixes() (old []string) {
old = l.prefixes
l.prefixes = make([]string, 0)
l.prefixString = ""
return
}
func (l *Logger) AppendPrefix(prefix string) *Logger {
l.prefixes = append(l.prefixes, prefix)
l.prefixString += "[" + prefix + "] "
return l
}
func (l *Logger) Spawn() *Logger {
nl := New()
for _, v := range l.prefixes {
nl.AppendPrefix(v)
}
return nl
}
func (l *Logger) Error(format string, v ...interface{}) {
log.Log.Error(l.prefixString+format, v...)
}
func (l *Logger) Warn(format string, v ...interface{}) {
log.Log.Warn(l.prefixString+format, v...)
}
func (l *Logger) Info(format string, v ...interface{}) {
log.Log.Info(l.prefixString+format, v...)
}
func (l *Logger) Debug(format string, v ...interface{}) {
log.Log.Debug(l.prefixString+format, v...)
}
func (l *Logger) Trace(format string, v ...interface{}) {
log.Log.Trace(l.prefixString+format, v...)
}