mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-05-31 16:34:22 +03:00
Add admin panel, manager, node_manager, bandwidth limiter, connection limiter, bonding, failover, vless encryption, mkcp transport
This commit is contained in:
164
protocol/bond/conn.go
Normal file
164
protocol/bond/conn.go
Normal file
@@ -0,0 +1,164 @@
|
||||
package bond
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type bondedConn struct {
|
||||
conns []net.Conn
|
||||
downloadRatios []uint8
|
||||
uploadRatios []uint8
|
||||
|
||||
readBuffer []byte
|
||||
readOffset int
|
||||
readSize int
|
||||
}
|
||||
|
||||
func NewBondedConn(conns []net.Conn, downloadRatios, uploadRatios []uint8) *bondedConn {
|
||||
return &bondedConn{
|
||||
conns: conns,
|
||||
downloadRatios: downloadRatios,
|
||||
uploadRatios: uploadRatios,
|
||||
readBuffer: make([]byte, 65535),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *bondedConn) Read(b []byte) (n int, err error) {
|
||||
if c.readOffset == c.readSize {
|
||||
var header [2]byte
|
||||
_, err := io.ReadFull(c.conns[0], header[:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
size := int(binary.BigEndian.Uint16(header[:]))
|
||||
chunkLens := splitByRatios(size, c.downloadRatios)
|
||||
total := 0
|
||||
for i, chunkLen := range chunkLens {
|
||||
if chunkLen == 0 {
|
||||
continue
|
||||
}
|
||||
chunk := c.readBuffer[total : total+chunkLen]
|
||||
n, err := io.ReadFull(c.conns[i], chunk)
|
||||
total += n
|
||||
if err != nil {
|
||||
return total, err
|
||||
}
|
||||
}
|
||||
c.readOffset = 0
|
||||
c.readSize = size
|
||||
}
|
||||
n = copy(b, c.readBuffer[c.readOffset:c.readSize])
|
||||
c.readOffset += n
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (c *bondedConn) Write(b []byte) (n int, err error) {
|
||||
chunkLens := splitByRatios(len(b), c.uploadRatios)
|
||||
var header [2]byte
|
||||
binary.BigEndian.PutUint16(header[:], uint16(len(b)))
|
||||
_, err = c.conns[0].Write(header[:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
total := 0
|
||||
for i, chunkLen := range chunkLens {
|
||||
if chunkLen == 0 {
|
||||
continue
|
||||
}
|
||||
chunk := b[total : total+chunkLen]
|
||||
conn := c.conns[i]
|
||||
subTotal := 0
|
||||
for subTotal < len(chunk) {
|
||||
n, err := conn.Write(chunk[subTotal:])
|
||||
subTotal += n
|
||||
total += n
|
||||
if err != nil {
|
||||
return total, err
|
||||
}
|
||||
if n == 0 {
|
||||
return total, io.ErrUnexpectedEOF
|
||||
}
|
||||
}
|
||||
}
|
||||
return total, err
|
||||
}
|
||||
|
||||
func (c *bondedConn) Close() error {
|
||||
errs := make([]error, 0)
|
||||
for _, conn := range c.conns {
|
||||
err := conn.Close()
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
if len(errs) != 0 {
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *bondedConn) LocalAddr() net.Addr {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *bondedConn) RemoteAddr() net.Addr {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *bondedConn) SetDeadline(t time.Time) error {
|
||||
errs := make([]error, 0)
|
||||
for _, conn := range c.conns {
|
||||
err := conn.SetDeadline(t)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
if len(errs) != 0 {
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *bondedConn) SetReadDeadline(t time.Time) error {
|
||||
errs := make([]error, 0)
|
||||
for _, conn := range c.conns {
|
||||
err := conn.SetReadDeadline(t)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
if len(errs) != 0 {
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *bondedConn) SetWriteDeadline(t time.Time) error {
|
||||
errs := make([]error, 0)
|
||||
for _, conn := range c.conns {
|
||||
err := conn.SetWriteDeadline(t)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
if len(errs) != 0 {
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func splitByRatios(number int, ratios []uint8) []int {
|
||||
result := make([]int, len(ratios))
|
||||
remaining := number
|
||||
for i := 0; i < len(ratios)-1; i++ {
|
||||
part := number * int(ratios[i]) / 100
|
||||
result[i] = part
|
||||
remaining -= part
|
||||
}
|
||||
result[len(ratios)-1] = remaining
|
||||
return result
|
||||
}
|
||||
146
protocol/bond/inbound.go
Normal file
146
protocol/bond/inbound.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package bond
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/patrickmn/go-cache"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/adapter/inbound"
|
||||
"github.com/sagernet/sing-box/common/uot"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/service"
|
||||
)
|
||||
|
||||
func RegisterInbound(registry *inbound.Registry) {
|
||||
inbound.Register[option.BondInboundOptions](registry, C.TypeBond, NewInbound)
|
||||
}
|
||||
|
||||
type Inbound struct {
|
||||
inbound.Adapter
|
||||
logger logger.ContextLogger
|
||||
router adapter.ConnectionRouterEx
|
||||
inbounds []adapter.Inbound
|
||||
conns *cache.Cache
|
||||
|
||||
mtx sync.Mutex
|
||||
}
|
||||
|
||||
func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.BondInboundOptions) (adapter.Inbound, error) {
|
||||
if len(options.Inbounds) == 0 {
|
||||
return nil, E.New("missing tags")
|
||||
}
|
||||
inbound := &Inbound{
|
||||
Adapter: inbound.NewAdapter(C.TypeTunnelServer, tag),
|
||||
logger: logger,
|
||||
router: uot.NewRouter(router, logger),
|
||||
conns: cache.New(C.TCPConnectTimeout, time.Second),
|
||||
}
|
||||
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
||||
inbounds := make([]adapter.Inbound, len(options.Inbounds))
|
||||
for i, inboundOptions := range options.Inbounds {
|
||||
inbound, err := inboundRegistry.UnsafeCreate(ctx, NewRouter(router, logger, inbound.connHandler), logger, inboundOptions.Tag, inboundOptions.Type, inboundOptions.Options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inbounds[i] = inbound
|
||||
}
|
||||
inbound.inbounds = inbounds
|
||||
inbound.conns.OnEvicted(func(s string, i interface{}) {
|
||||
inbound.mtx.Lock()
|
||||
defer inbound.mtx.Unlock()
|
||||
ratioConns := i.(map[uint8]*ratioConn)
|
||||
for _, ratioConn := range ratioConns {
|
||||
if ratioConn != nil {
|
||||
ratioConn.conn.Close()
|
||||
}
|
||||
}
|
||||
})
|
||||
return inbound, nil
|
||||
}
|
||||
|
||||
func (h *Inbound) Start(stage adapter.StartStage) error {
|
||||
for _, inbound := range h.inbounds {
|
||||
err := inbound.Start(stage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Inbound) Close() error {
|
||||
errs := make([]error, 0)
|
||||
for _, inbound := range h.inbounds {
|
||||
err := inbound.Close()
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
if len(errs) != 0 {
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Inbound) connHandler(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error {
|
||||
request, err := ReadRequest(conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.mtx.Lock()
|
||||
defer h.mtx.Unlock()
|
||||
var ratioConns map[uint8]*ratioConn
|
||||
rawRatioConns, ok := h.conns.Get(request.UUID.String())
|
||||
if ok {
|
||||
ratioConns = rawRatioConns.(map[uint8]*ratioConn)
|
||||
} else {
|
||||
ratioConns = make(map[uint8]*ratioConn, request.Count)
|
||||
h.conns.SetDefault(request.UUID.String(), ratioConns)
|
||||
}
|
||||
ratioConns[request.Index] = &ratioConn{
|
||||
conn: conn,
|
||||
downloadRatio: request.DownloadRatio,
|
||||
uploadRatio: request.UploadRatio,
|
||||
}
|
||||
if len(ratioConns) == int(request.Count) {
|
||||
conns := make([]net.Conn, len(ratioConns))
|
||||
downloadRatios := make([]uint8, len(ratioConns))
|
||||
uploadRatios := make([]uint8, len(ratioConns))
|
||||
var totalDownloadRatio, totalUploadRatio uint8
|
||||
for index, ratioConn := range ratioConns {
|
||||
conns[index] = ratioConn.conn
|
||||
downloadRatios[index] = ratioConn.downloadRatio
|
||||
uploadRatios[index] = ratioConn.uploadRatio
|
||||
totalDownloadRatio += ratioConn.downloadRatio
|
||||
totalUploadRatio += ratioConn.uploadRatio
|
||||
delete(ratioConns, index)
|
||||
}
|
||||
if totalDownloadRatio != 100 || totalUploadRatio != 100 {
|
||||
for _, conn := range conns {
|
||||
conn.Close()
|
||||
}
|
||||
return E.New("invalid ratios")
|
||||
}
|
||||
conn = NewBondedConn(conns, downloadRatios, uploadRatios)
|
||||
metadata.Inbound = h.Tag()
|
||||
metadata.InboundType = C.TypeBond
|
||||
metadata.Destination = request.Destination
|
||||
h.router.RouteConnectionEx(ctx, conn, metadata, onClose)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ratioConn struct {
|
||||
conn net.Conn
|
||||
downloadRatio uint8
|
||||
uploadRatio uint8
|
||||
}
|
||||
152
protocol/bond/outbound.go
Normal file
152
protocol/bond/outbound.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package bond
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/adapter/outbound"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/uot"
|
||||
"github.com/sagernet/sing/service"
|
||||
)
|
||||
|
||||
func RegisterOutbound(registry *outbound.Registry) {
|
||||
outbound.Register[option.BondOutboundOptions](registry, C.TypeBond, NewOutbound)
|
||||
}
|
||||
|
||||
type Outbound struct {
|
||||
outbound.Adapter
|
||||
ctx context.Context
|
||||
logger logger.ContextLogger
|
||||
outbounds []adapter.Outbound
|
||||
downloadRatios []uint8
|
||||
uploadRatios []uint8
|
||||
uotClient *uot.Client
|
||||
}
|
||||
|
||||
func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.BondOutboundOptions) (adapter.Outbound, error) {
|
||||
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
||||
outbounds := make([]adapter.Outbound, 0, len(options.Outbounds))
|
||||
downloadRatios := make([]uint8, 0, len(options.Outbounds))
|
||||
uploadRatios := make([]uint8, 0, len(options.Outbounds))
|
||||
var totalDownloadRatio, totalUploadRatio uint8
|
||||
for _, outboundOptions := range options.Outbounds {
|
||||
count := outboundOptions.Count
|
||||
if count == 0 {
|
||||
count = 1
|
||||
}
|
||||
for range count {
|
||||
outbound, err := outboundRegistry.UnsafeCreateOutbound(ctx, router, logger, outboundOptions.Outbound.Tag, outboundOptions.Outbound.Type, outboundOptions.Outbound.Options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outbounds = append(outbounds, outbound)
|
||||
downloadRatios = append(downloadRatios, outboundOptions.DownloadRatio)
|
||||
uploadRatios = append(uploadRatios, outboundOptions.UploadRatio)
|
||||
totalDownloadRatio += outboundOptions.DownloadRatio
|
||||
totalUploadRatio += outboundOptions.UploadRatio
|
||||
}
|
||||
}
|
||||
if totalDownloadRatio != 100 || totalUploadRatio != 100 {
|
||||
return nil, E.New("invalid ratios")
|
||||
}
|
||||
outbound := &Outbound{
|
||||
Adapter: outbound.NewAdapter(C.TypeTunnelClient, tag, []string{N.NetworkTCP, N.NetworkUDP}, []string{}),
|
||||
ctx: ctx,
|
||||
outbounds: outbounds,
|
||||
downloadRatios: downloadRatios,
|
||||
uploadRatios: uploadRatios,
|
||||
logger: logger,
|
||||
}
|
||||
outbound.uotClient = &uot.Client{
|
||||
Dialer: outbound,
|
||||
Version: uot.Version,
|
||||
}
|
||||
return outbound, nil
|
||||
}
|
||||
|
||||
func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
if N.NetworkName(network) == N.NetworkUDP {
|
||||
return h.uotClient.DialContext(ctx, network, destination)
|
||||
}
|
||||
conns := make([]net.Conn, len(h.outbounds))
|
||||
connUUID, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
errs := make([]error, 0, len(conns))
|
||||
var mtx sync.Mutex
|
||||
var wg sync.WaitGroup
|
||||
for i, outbound := range h.outbounds {
|
||||
wg.Go(
|
||||
func() {
|
||||
conn, err := outbound.DialContext(ctx, network, Destination)
|
||||
if err != nil {
|
||||
mtx.Lock()
|
||||
errs = append(errs, err)
|
||||
mtx.Unlock()
|
||||
return
|
||||
}
|
||||
err = WriteRequest(
|
||||
conn,
|
||||
&Request{
|
||||
UUID: connUUID,
|
||||
Index: byte(i),
|
||||
Count: byte(len(h.outbounds)),
|
||||
DownloadRatio: h.uploadRatios[i],
|
||||
UploadRatio: h.downloadRatios[i],
|
||||
Destination: destination,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
mtx.Lock()
|
||||
errs = append(errs, err)
|
||||
mtx.Unlock()
|
||||
return
|
||||
}
|
||||
conns[i] = conn
|
||||
},
|
||||
)
|
||||
}
|
||||
wg.Wait()
|
||||
if len(errs) != 0 {
|
||||
for _, conn := range conns {
|
||||
if conn != nil {
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
return nil, errors.Join(errs...)
|
||||
}
|
||||
conn := NewBondedConn(conns, h.downloadRatios, h.uploadRatios)
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
return h.uotClient.ListenPacket(ctx, destination)
|
||||
}
|
||||
|
||||
func (h *Outbound) Close() error {
|
||||
errs := make([]error, 0)
|
||||
for _, outbound := range h.outbounds {
|
||||
err := common.Close(outbound)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
if len(errs) != 0 {
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
100
protocol/bond/protocol.go
Normal file
100
protocol/bond/protocol.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package bond
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
)
|
||||
|
||||
const (
|
||||
Version = 0
|
||||
)
|
||||
|
||||
var Destination = M.Socksaddr{
|
||||
Fqdn: "sp.bond.sing-box.arpa",
|
||||
Port: 444,
|
||||
}
|
||||
|
||||
var AddressSerializer = M.NewSerializer(
|
||||
M.AddressFamilyByte(0x01, M.AddressFamilyIPv4),
|
||||
M.AddressFamilyByte(0x03, M.AddressFamilyIPv6),
|
||||
M.AddressFamilyByte(0x02, M.AddressFamilyFqdn),
|
||||
M.PortThenAddress(),
|
||||
)
|
||||
|
||||
type Request struct {
|
||||
UUID uuid.UUID
|
||||
Index byte
|
||||
Count byte
|
||||
DownloadRatio byte
|
||||
UploadRatio byte
|
||||
Destination M.Socksaddr
|
||||
}
|
||||
|
||||
func ReadRequest(reader io.Reader) (*Request, error) {
|
||||
var request Request
|
||||
var version uint8
|
||||
err := binary.Read(reader, binary.BigEndian, &version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if version != Version {
|
||||
return nil, E.New("unknown version: ", version)
|
||||
}
|
||||
_, err = io.ReadFull(reader, request.UUID[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = binary.Read(reader, binary.BigEndian, &request.Index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = binary.Read(reader, binary.BigEndian, &request.Count)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = binary.Read(reader, binary.BigEndian, &request.DownloadRatio)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = binary.Read(reader, binary.BigEndian, &request.UploadRatio)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
request.Destination, err = AddressSerializer.ReadAddrPort(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &request, nil
|
||||
}
|
||||
|
||||
func WriteRequest(writer io.Writer, request *Request) error {
|
||||
var requestLen int
|
||||
requestLen += 1 // version
|
||||
requestLen += 16 // UUID
|
||||
requestLen += 1 // index
|
||||
requestLen += 1 // count
|
||||
requestLen += 1 // download ratio
|
||||
requestLen += 1 // upload ratio
|
||||
requestLen += AddressSerializer.AddrPortLen(request.Destination)
|
||||
buffer := buf.NewSize(requestLen)
|
||||
defer buffer.Release()
|
||||
common.Must(
|
||||
buffer.WriteByte(Version),
|
||||
common.Error(buffer.Write(request.UUID[:])),
|
||||
buffer.WriteByte(request.Index),
|
||||
buffer.WriteByte(request.Count),
|
||||
buffer.WriteByte(request.DownloadRatio),
|
||||
buffer.WriteByte(request.UploadRatio),
|
||||
)
|
||||
err := AddressSerializer.WriteAddrPort(buffer, request.Destination)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return common.Error(writer.Write(buffer.Bytes()))
|
||||
}
|
||||
41
protocol/bond/router.go
Normal file
41
protocol/bond/router.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package bond
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type Router struct {
|
||||
adapter.Router
|
||||
logger logger.ContextLogger
|
||||
handler func(context.Context, net.Conn, adapter.InboundContext, N.CloseHandlerFunc) error
|
||||
}
|
||||
|
||||
func NewRouter(router adapter.Router, logger logger.ContextLogger, handler func(context.Context, net.Conn, adapter.InboundContext, N.CloseHandlerFunc) error) *Router {
|
||||
return &Router{Router: router, logger: logger, handler: handler}
|
||||
}
|
||||
|
||||
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||
return r.handler(ctx, conn, metadata, func(error) {})
|
||||
}
|
||||
|
||||
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||
if err := r.handler(ctx, conn, metadata, onClose); err != nil {
|
||||
r.logger.ErrorContext(ctx, err)
|
||||
N.CloseOnHandshakeFailure(conn, onClose, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||
r.logger.ErrorContext(ctx, os.ErrInvalid)
|
||||
N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)
|
||||
}
|
||||
Reference in New Issue
Block a user