mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-10 19:36:36 +08:00
init
This commit is contained in:
@@ -0,0 +1,259 @@
|
||||
package redirect
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/core/handler"
|
||||
md "github.com/go-gost/core/metadata"
|
||||
"github.com/go-gost/core/observer/stats"
|
||||
"github.com/go-gost/core/recorder"
|
||||
xbypass "github.com/go-gost/x/bypass"
|
||||
ctxvalue "github.com/go-gost/x/ctx"
|
||||
xnet "github.com/go-gost/x/internal/net"
|
||||
xstats "github.com/go-gost/x/observer/stats"
|
||||
"github.com/go-gost/x/internal/util/sniffing"
|
||||
tls_util "github.com/go-gost/x/internal/util/tls"
|
||||
rate_limiter "github.com/go-gost/x/limiter/rate"
|
||||
stats_wrapper "github.com/go-gost/x/observer/stats/wrapper"
|
||||
xrecorder "github.com/go-gost/x/recorder"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.HandlerRegistry().Register("red", NewHandler)
|
||||
registry.HandlerRegistry().Register("redir", NewHandler)
|
||||
registry.HandlerRegistry().Register("redirect", NewHandler)
|
||||
}
|
||||
|
||||
type redirectHandler struct {
|
||||
md metadata
|
||||
options handler.Options
|
||||
recorder recorder.RecorderObject
|
||||
certPool tls_util.CertPool
|
||||
}
|
||||
|
||||
func NewHandler(opts ...handler.Option) handler.Handler {
|
||||
options := handler.Options{}
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
return &redirectHandler{
|
||||
options: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *redirectHandler) Init(md md.Metadata) (err error) {
|
||||
if err = h.parseMetadata(md); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, ro := range h.options.Recorders {
|
||||
if ro.Record == xrecorder.RecorderServiceHandler {
|
||||
h.recorder = ro
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if h.md.certificate != nil && h.md.privateKey != nil {
|
||||
h.certPool = tls_util.NewMemoryCertPool()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (h *redirectHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) (err error) {
|
||||
defer conn.Close()
|
||||
|
||||
start := time.Now()
|
||||
|
||||
ro := &xrecorder.HandlerRecorderObject{
|
||||
Service: h.options.Service,
|
||||
Network: "tcp",
|
||||
RemoteAddr: conn.RemoteAddr().String(),
|
||||
LocalAddr: conn.LocalAddr().String(),
|
||||
Time: start,
|
||||
SID: string(ctxvalue.SidFromContext(ctx)),
|
||||
}
|
||||
ro.ClientIP, _, _ = net.SplitHostPort(conn.RemoteAddr().String())
|
||||
|
||||
log := h.options.Logger.WithFields(map[string]any{
|
||||
"remote": conn.RemoteAddr().String(),
|
||||
"local": conn.LocalAddr().String(),
|
||||
"sid": ctxvalue.SidFromContext(ctx),
|
||||
})
|
||||
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
|
||||
|
||||
pStats := xstats.Stats{}
|
||||
conn = stats_wrapper.WrapConn(conn, &pStats)
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
ro.Err = err.Error()
|
||||
}
|
||||
ro.InputBytes = pStats.Get(stats.KindInputBytes)
|
||||
ro.OutputBytes = pStats.Get(stats.KindOutputBytes)
|
||||
ro.Duration = time.Since(start)
|
||||
if err := ro.Record(ctx, h.recorder.Recorder); err != nil {
|
||||
log.Errorf("record: %v", err)
|
||||
}
|
||||
|
||||
log.WithFields(map[string]any{
|
||||
"duration": time.Since(start),
|
||||
"inputBytes": ro.InputBytes,
|
||||
"outputBytes": ro.OutputBytes,
|
||||
}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr())
|
||||
}()
|
||||
|
||||
if !h.checkRateLimit(conn.RemoteAddr()) {
|
||||
return rate_limiter.ErrRateLimit
|
||||
}
|
||||
|
||||
var dstAddr net.Addr
|
||||
|
||||
if h.md.tproxy {
|
||||
dstAddr = conn.LocalAddr()
|
||||
} else {
|
||||
dstAddr, err = h.getOriginalDstAddr(conn)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ro.Host = dstAddr.String()
|
||||
ro.Dst = dstAddr.String()
|
||||
|
||||
log = log.WithFields(map[string]any{
|
||||
"dst": fmt.Sprintf("%s/%s", dstAddr, dstAddr.Network()),
|
||||
"host": dstAddr.String(),
|
||||
})
|
||||
|
||||
if h.md.sniffing {
|
||||
if h.md.sniffingTimeout > 0 {
|
||||
conn.SetReadDeadline(time.Now().Add(h.md.sniffingTimeout))
|
||||
}
|
||||
|
||||
br := bufio.NewReader(conn)
|
||||
proto, _ := sniffing.Sniff(ctx, br)
|
||||
ro.Proto = proto
|
||||
|
||||
if h.md.sniffingTimeout > 0 {
|
||||
conn.SetReadDeadline(time.Time{})
|
||||
}
|
||||
|
||||
dial := func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
var cc net.Conn
|
||||
var err error
|
||||
if address != "" {
|
||||
host, _, _ := net.SplitHostPort(address)
|
||||
if host == "" {
|
||||
host = address
|
||||
}
|
||||
_, port, _ := net.SplitHostPort(dstAddr.String())
|
||||
address = net.JoinHostPort(strings.Trim(host, "[]"), port)
|
||||
ro.Host = address
|
||||
|
||||
var buf bytes.Buffer
|
||||
cc, err = h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), "tcp", address)
|
||||
ro.Route = buf.String()
|
||||
if err != nil && !h.md.sniffingFallback {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if cc == nil {
|
||||
if h.options.Bypass != nil && h.options.Bypass.Contains(ctx, "tcp", dstAddr.String()) {
|
||||
return nil, xbypass.ErrBypass
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
cc, err = h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), "tcp", dstAddr.String())
|
||||
ro.Route = buf.String()
|
||||
ro.Host = dstAddr.String()
|
||||
}
|
||||
|
||||
return cc, err
|
||||
}
|
||||
dialTLS := func(ctx context.Context, network, address string, cfg *tls.Config) (net.Conn, error) {
|
||||
return dial(ctx, network, address)
|
||||
}
|
||||
|
||||
sniffer := &sniffing.Sniffer{
|
||||
Websocket: h.md.sniffingWebsocket,
|
||||
WebsocketSampleRate: h.md.sniffingWebsocketSampleRate,
|
||||
Recorder: h.recorder.Recorder,
|
||||
RecorderOptions: h.recorder.Options,
|
||||
Certificate: h.md.certificate,
|
||||
PrivateKey: h.md.privateKey,
|
||||
NegotiatedProtocol: h.md.alpn,
|
||||
CertPool: h.certPool,
|
||||
MitmBypass: h.md.mitmBypass,
|
||||
ReadTimeout: h.md.readTimeout,
|
||||
}
|
||||
|
||||
conn = xnet.NewReadWriteConn(br, conn, conn)
|
||||
switch proto {
|
||||
case sniffing.ProtoHTTP:
|
||||
return sniffer.HandleHTTP(ctx, conn,
|
||||
sniffing.WithDial(dial),
|
||||
sniffing.WithDialTLS(dialTLS),
|
||||
sniffing.WithBypass(h.options.Bypass),
|
||||
sniffing.WithRecorderObject(ro),
|
||||
sniffing.WithLog(log),
|
||||
)
|
||||
case sniffing.ProtoTLS:
|
||||
return sniffer.HandleTLS(ctx, conn,
|
||||
sniffing.WithDial(dial),
|
||||
sniffing.WithDialTLS(dialTLS),
|
||||
sniffing.WithBypass(h.options.Bypass),
|
||||
sniffing.WithRecorderObject(ro),
|
||||
sniffing.WithLog(log),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("%s >> %s", conn.RemoteAddr(), dstAddr)
|
||||
|
||||
if h.options.Bypass != nil &&
|
||||
h.options.Bypass.Contains(ctx, dstAddr.Network(), dstAddr.String()) {
|
||||
log.Debug("bypass: ", dstAddr)
|
||||
return xbypass.ErrBypass
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
cc, err := h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), dstAddr.Network(), dstAddr.String())
|
||||
ro.Route = buf.String()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
defer cc.Close()
|
||||
|
||||
t := time.Now()
|
||||
log.Infof("%s <-> %s", conn.RemoteAddr(), dstAddr)
|
||||
xnet.Transport(conn, cc)
|
||||
log.WithFields(map[string]any{
|
||||
"duration": time.Since(t),
|
||||
}).Infof("%s >-< %s", conn.RemoteAddr(), dstAddr)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *redirectHandler) checkRateLimit(addr net.Addr) bool {
|
||||
if h.options.RateLimiter == nil {
|
||||
return true
|
||||
}
|
||||
host, _, _ := net.SplitHostPort(addr.String())
|
||||
if limiter := h.options.RateLimiter.Limiter(host); limiter != nil {
|
||||
return limiter.Allow(1)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package redirect
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (h *redirectHandler) getOriginalDstAddr(conn net.Conn) (addr net.Addr, err error) {
|
||||
host, port, err := localToRemote(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
portNumber, _ := strconv.Atoi(port)
|
||||
addr = &net.TCPAddr{
|
||||
IP: net.ParseIP(host),
|
||||
Port: portNumber,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func localToRemote(clientConn net.Conn) (string, string, error) {
|
||||
host, port, err := net.SplitHostPort(clientConn.RemoteAddr().String())
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
out, err := exec.Command("sudo", "-n", "/sbin/pfctl", "-s", "state").Output()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
remoteAddr, remotePort, err := translatePfctlOutput(host, port, string(out))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return remoteAddr, remotePort, err
|
||||
}
|
||||
|
||||
func translatePfctlOutput(address string, port, s string) (string, string, error) {
|
||||
// We may get an ipv4-mapped ipv6 address here, e.g. ::ffff:127.0.0.1.
|
||||
// Those still appear as "127.0.0.1" in the table, so we need to strip the prefix.
|
||||
// re := regexp.MustCompile(`^::ffff:((\d+\.\d+\.\d+\.\d+$))`)
|
||||
// strippedAddress := re.ReplaceAllString(address, "")
|
||||
strippedAddress := address
|
||||
|
||||
// ALL tcp 192.168.1.13:57474 -> 23.205.82.58:443 ESTABLISHED:ESTABLISHED
|
||||
spec := net.JoinHostPort(strippedAddress, port)
|
||||
|
||||
lines := strings.Split(s, "\n")
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, "ESTABLISHED:ESTABLISHED") {
|
||||
if strings.Contains(line, spec) {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) > 4 {
|
||||
return net.SplitHostPort(fields[4])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", "", fmt.Errorf("could not resolve original destination")
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package redirect
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func (h *redirectHandler) getOriginalDstAddr(conn net.Conn) (addr net.Addr, err error) {
|
||||
tcpAddr, ok := conn.RemoteAddr().(*net.TCPAddr)
|
||||
if !ok {
|
||||
err = errors.New("wrong connection type, must be TCP Conn")
|
||||
return
|
||||
}
|
||||
|
||||
sc, ok := conn.(syscall.Conn)
|
||||
if !ok {
|
||||
err = errors.New("wrong connection type, must be syscall.Conn")
|
||||
return
|
||||
}
|
||||
|
||||
rc, err := sc.SyscallConn()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var cerr error
|
||||
err = rc.Control(func(fd uintptr) {
|
||||
if tcpAddr.IP.To4() != nil {
|
||||
mreq, err := unix.GetsockoptIPv6Mreq(int(fd), unix.IPPROTO_IP, unix.SO_ORIGINAL_DST)
|
||||
if err != nil {
|
||||
cerr = err
|
||||
return
|
||||
}
|
||||
|
||||
addr = &net.TCPAddr{
|
||||
IP: net.IP(mreq.Multiaddr[4:8]),
|
||||
Port: int(mreq.Multiaddr[2])<<8 + int(mreq.Multiaddr[3]),
|
||||
}
|
||||
} else {
|
||||
info, err := unix.GetsockoptIPv6MTUInfo(int(fd), unix.IPPROTO_IPV6, unix.SO_ORIGINAL_DST)
|
||||
if err != nil {
|
||||
cerr = err
|
||||
return
|
||||
}
|
||||
|
||||
var buf = make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(buf, info.Addr.Port)
|
||||
addr = &net.TCPAddr{
|
||||
IP: net.IP(info.Addr.Addr[:]),
|
||||
Port: int(binary.NativeEndian.Uint16(buf)),
|
||||
}
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if cerr != nil {
|
||||
return nil, cerr
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
//go:build !linux && !darwin
|
||||
|
||||
package redirect
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
)
|
||||
|
||||
func (h *redirectHandler) getOriginalDstAddr(_ net.Conn) (addr net.Addr, err error) {
|
||||
err = errors.New("TCP redirect is not available on non-linux platform")
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package redirect
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/core/bypass"
|
||||
mdata "github.com/go-gost/core/metadata"
|
||||
mdutil "github.com/go-gost/x/metadata/util"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
type metadata struct {
|
||||
readTimeout time.Duration
|
||||
tproxy bool
|
||||
|
||||
sniffing bool
|
||||
sniffingTimeout time.Duration
|
||||
sniffingFallback bool
|
||||
sniffingWebsocket bool
|
||||
sniffingWebsocketSampleRate float64
|
||||
|
||||
certificate *x509.Certificate
|
||||
privateKey crypto.PrivateKey
|
||||
alpn string
|
||||
mitmBypass bypass.Bypass
|
||||
}
|
||||
|
||||
func (h *redirectHandler) parseMetadata(md mdata.Metadata) (err error) {
|
||||
h.md.readTimeout = mdutil.GetDuration(md, "readTimeout")
|
||||
if h.md.readTimeout <= 0 {
|
||||
h.md.readTimeout = 15 * time.Second
|
||||
}
|
||||
h.md.tproxy = mdutil.GetBool(md, "tproxy")
|
||||
|
||||
h.md.sniffing = mdutil.GetBool(md, "sniffing")
|
||||
h.md.sniffingTimeout = mdutil.GetDuration(md, "sniffing.timeout")
|
||||
h.md.sniffingFallback = mdutil.GetBool(md, "sniffing.fallback")
|
||||
h.md.sniffingWebsocket = mdutil.GetBool(md, "sniffing.websocket")
|
||||
h.md.sniffingWebsocketSampleRate = mdutil.GetFloat(md, "sniffing.websocket.sampleRate")
|
||||
|
||||
certFile := mdutil.GetString(md, "mitm.certFile", "mitm.caCertFile")
|
||||
keyFile := mdutil.GetString(md, "mitm.keyFile", "mitm.caKeyFile")
|
||||
if certFile != "" && keyFile != "" {
|
||||
tlsCert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.md.certificate, err = x509.ParseCertificate(tlsCert.Certificate[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.md.privateKey = tlsCert.PrivateKey
|
||||
}
|
||||
h.md.alpn = mdutil.GetString(md, "mitm.alpn")
|
||||
h.md.mitmBypass = registry.BypassRegistry().Get(mdutil.GetString(md, "mitm.bypass"))
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package redirect
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/core/handler"
|
||||
md "github.com/go-gost/core/metadata"
|
||||
"github.com/go-gost/core/observer/stats"
|
||||
"github.com/go-gost/core/recorder"
|
||||
xbypass "github.com/go-gost/x/bypass"
|
||||
ctxvalue "github.com/go-gost/x/ctx"
|
||||
xnet "github.com/go-gost/x/internal/net"
|
||||
rate_limiter "github.com/go-gost/x/limiter/rate"
|
||||
xstats "github.com/go-gost/x/observer/stats"
|
||||
stats_wrapper "github.com/go-gost/x/observer/stats/wrapper"
|
||||
xrecorder "github.com/go-gost/x/recorder"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.HandlerRegistry().Register("redu", NewHandler)
|
||||
}
|
||||
|
||||
type redirectHandler struct {
|
||||
md metadata
|
||||
options handler.Options
|
||||
recorder recorder.RecorderObject
|
||||
}
|
||||
|
||||
func NewHandler(opts ...handler.Option) handler.Handler {
|
||||
options := handler.Options{}
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
return &redirectHandler{
|
||||
options: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *redirectHandler) Init(md md.Metadata) (err error) {
|
||||
if err = h.parseMetadata(md); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, ro := range h.options.Recorders {
|
||||
if ro.Record == xrecorder.RecorderServiceHandler {
|
||||
h.recorder = ro
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (h *redirectHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) (err error) {
|
||||
defer conn.Close()
|
||||
|
||||
start := time.Now()
|
||||
|
||||
ro := &xrecorder.HandlerRecorderObject{
|
||||
Service: h.options.Service,
|
||||
RemoteAddr: conn.RemoteAddr().String(),
|
||||
LocalAddr: conn.LocalAddr().String(),
|
||||
Time: start,
|
||||
SID: string(ctxvalue.SidFromContext(ctx)),
|
||||
}
|
||||
ro.ClientIP, _, _ = net.SplitHostPort(conn.RemoteAddr().String())
|
||||
|
||||
log := h.options.Logger.WithFields(map[string]any{
|
||||
"remote": conn.RemoteAddr().String(),
|
||||
"local": conn.LocalAddr().String(),
|
||||
"sid": ctxvalue.SidFromContext(ctx),
|
||||
})
|
||||
|
||||
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
|
||||
|
||||
pStats := xstats.Stats{}
|
||||
conn = stats_wrapper.WrapConn(conn, &pStats)
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
ro.Err = err.Error()
|
||||
}
|
||||
ro.Duration = time.Since(start)
|
||||
ro.InputBytes = pStats.Get(stats.KindInputBytes)
|
||||
ro.OutputBytes = pStats.Get(stats.KindOutputBytes)
|
||||
if err := ro.Record(ctx, h.recorder.Recorder); err != nil {
|
||||
log.Error("record: %v", err)
|
||||
}
|
||||
|
||||
log.WithFields(map[string]any{
|
||||
"duration": time.Since(start),
|
||||
"inputBytes": ro.InputBytes,
|
||||
"outputBytes": ro.OutputBytes,
|
||||
}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr())
|
||||
}()
|
||||
|
||||
if !h.checkRateLimit(conn.RemoteAddr()) {
|
||||
return rate_limiter.ErrRateLimit
|
||||
}
|
||||
|
||||
dstAddr := conn.LocalAddr()
|
||||
ro.Network = dstAddr.Network()
|
||||
ro.Host = dstAddr.String()
|
||||
|
||||
log = log.WithFields(map[string]any{
|
||||
"dst": fmt.Sprintf("%s/%s", dstAddr, dstAddr.Network()),
|
||||
"host": dstAddr.String(),
|
||||
})
|
||||
|
||||
log.Debugf("%s >> %s", conn.RemoteAddr(), dstAddr)
|
||||
|
||||
if h.options.Bypass != nil &&
|
||||
h.options.Bypass.Contains(ctx, dstAddr.Network(), dstAddr.String()) {
|
||||
log.Debug("bypass: ", dstAddr)
|
||||
return xbypass.ErrBypass
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
cc, err := h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), dstAddr.Network(), dstAddr.String())
|
||||
ro.Route = buf.String()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
defer cc.Close()
|
||||
|
||||
t := time.Now()
|
||||
log.Infof("%s <-> %s", conn.RemoteAddr(), dstAddr)
|
||||
xnet.Transport(conn, cc)
|
||||
log.WithFields(map[string]any{
|
||||
"duration": time.Since(t),
|
||||
}).Infof("%s >-< %s", conn.RemoteAddr(), dstAddr)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *redirectHandler) checkRateLimit(addr net.Addr) bool {
|
||||
if h.options.RateLimiter == nil {
|
||||
return true
|
||||
}
|
||||
host, _, _ := net.SplitHostPort(addr.String())
|
||||
if limiter := h.options.RateLimiter.Limiter(host); limiter != nil {
|
||||
return limiter.Allow(1)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package redirect
|
||||
|
||||
import (
|
||||
mdata "github.com/go-gost/core/metadata"
|
||||
)
|
||||
|
||||
type metadata struct{}
|
||||
|
||||
func (h *redirectHandler) parseMetadata(md mdata.Metadata) (err error) {
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user