mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-07-23 22:44:07 +08:00
Fix log for rejected connections
This commit is contained in:
parent
9b8ab3e61e
commit
cba0e46aba
2
go.mod
2
go.mod
@ -33,7 +33,7 @@ require (
|
|||||||
github.com/sagernet/sing-shadowsocks v0.2.8
|
github.com/sagernet/sing-shadowsocks v0.2.8
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
||||||
github.com/sagernet/sing-shadowtls v0.2.0
|
github.com/sagernet/sing-shadowtls v0.2.0
|
||||||
github.com/sagernet/sing-tun v0.6.8
|
github.com/sagernet/sing-tun v0.6.9
|
||||||
github.com/sagernet/sing-vmess v0.2.3
|
github.com/sagernet/sing-vmess v0.2.3
|
||||||
github.com/sagernet/smux v1.5.34-mod.2
|
github.com/sagernet/smux v1.5.34-mod.2
|
||||||
github.com/sagernet/utls v1.6.7
|
github.com/sagernet/utls v1.6.7
|
||||||
|
4
go.sum
4
go.sum
@ -133,8 +133,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq
|
|||||||
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
||||||
github.com/sagernet/sing-shadowtls v0.2.0 h1:cLKe4OAOFwuhmAIuPLj//CIL7Q9js+pIDardhJ+/osk=
|
github.com/sagernet/sing-shadowtls v0.2.0 h1:cLKe4OAOFwuhmAIuPLj//CIL7Q9js+pIDardhJ+/osk=
|
||||||
github.com/sagernet/sing-shadowtls v0.2.0/go.mod h1:agU+Fw5X+xnWVyRHyFthoZCX3MfWKCFPm4JUf+1oaxo=
|
github.com/sagernet/sing-shadowtls v0.2.0/go.mod h1:agU+Fw5X+xnWVyRHyFthoZCX3MfWKCFPm4JUf+1oaxo=
|
||||||
github.com/sagernet/sing-tun v0.6.8 h1:tr+LKHe09C2I9GfNuB2vnzaZm+ekoNlAhLLrdiLjtAA=
|
github.com/sagernet/sing-tun v0.6.9 h1:uP8O4Q7U9QesjWumgxd2S9fjT3c6aEPWl5RB6uBdVB8=
|
||||||
github.com/sagernet/sing-tun v0.6.8/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
github.com/sagernet/sing-tun v0.6.9/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
||||||
github.com/sagernet/sing-vmess v0.2.3 h1:z6Ym8dnZG7k1fP3+54vz8G0tvRVJeOoTFFeUPwXTD44=
|
github.com/sagernet/sing-vmess v0.2.3 h1:z6Ym8dnZG7k1fP3+54vz8G0tvRVJeOoTFFeUPwXTD44=
|
||||||
github.com/sagernet/sing-vmess v0.2.3/go.mod h1:jDAZ0A0St1zVRkyvhAPRySOFfhC+4SQtO5VYyeFotgA=
|
github.com/sagernet/sing-vmess v0.2.3/go.mod h1:jDAZ0A0St1zVRkyvhAPRySOFfhC+4SQtO5VYyeFotgA=
|
||||||
github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4=
|
github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4=
|
||||||
|
@ -2,15 +2,14 @@ package route
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
dnsOutbound "github.com/sagernet/sing-box/protocol/dns"
|
dnsOutbound "github.com/sagernet/sing-box/protocol/dns"
|
||||||
|
R "github.com/sagernet/sing-box/route/rule"
|
||||||
"github.com/sagernet/sing-dns"
|
"github.com/sagernet/sing-dns"
|
||||||
"github.com/sagernet/sing-tun"
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
@ -58,7 +57,7 @@ func (r *Router) hijackDNSPacket(ctx context.Context, conn N.PacketConn, packetB
|
|||||||
|
|
||||||
func ExchangeDNSPacket(ctx context.Context, router *Router, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext, destination M.Socksaddr) {
|
func ExchangeDNSPacket(ctx context.Context, router *Router, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext, destination M.Socksaddr) {
|
||||||
err := exchangeDNSPacket(ctx, router, conn, buffer, metadata, destination)
|
err := exchangeDNSPacket(ctx, router, conn, buffer, metadata, destination)
|
||||||
if err != nil && !errors.Is(err, tun.ErrDrop) && !E.IsClosedOrCanceled(err) {
|
if err != nil && !R.IsRejected(err) && !E.IsClosedOrCanceled(err) {
|
||||||
router.dnsLogger.ErrorContext(ctx, E.Cause(err, "process packet connection"))
|
router.dnsLogger.ErrorContext(ctx, E.Cause(err, "process packet connection"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/common/sniff"
|
"github.com/sagernet/sing-box/common/sniff"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/route/rule"
|
R "github.com/sagernet/sing-box/route/rule"
|
||||||
"github.com/sagernet/sing-dns"
|
"github.com/sagernet/sing-dns"
|
||||||
"github.com/sagernet/sing-mux"
|
"github.com/sagernet/sing-mux"
|
||||||
"github.com/sagernet/sing-vmess"
|
"github.com/sagernet/sing-vmess"
|
||||||
@ -51,7 +51,7 @@ func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata
|
|||||||
err := r.routeConnection(ctx, conn, metadata, onClose)
|
err := r.routeConnection(ctx, conn, metadata, onClose)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
N.CloseOnHandshakeFailure(conn, onClose, err)
|
N.CloseOnHandshakeFailure(conn, onClose, err)
|
||||||
if E.IsClosedOrCanceled(err) {
|
if E.IsClosedOrCanceled(err) || R.IsRejected(err) {
|
||||||
r.logger.DebugContext(ctx, "connection closed: ", err)
|
r.logger.DebugContext(ctx, "connection closed: ", err)
|
||||||
} else {
|
} else {
|
||||||
r.logger.ErrorContext(ctx, err)
|
r.logger.ErrorContext(ctx, err)
|
||||||
@ -101,7 +101,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
|
|||||||
var selectedOutbound adapter.Outbound
|
var selectedOutbound adapter.Outbound
|
||||||
if selectedRule != nil {
|
if selectedRule != nil {
|
||||||
switch action := selectedRule.Action().(type) {
|
switch action := selectedRule.Action().(type) {
|
||||||
case *rule.RuleActionRoute:
|
case *R.RuleActionRoute:
|
||||||
var loaded bool
|
var loaded bool
|
||||||
selectedOutbound, loaded = r.outbound.Outbound(action.Outbound)
|
selectedOutbound, loaded = r.outbound.Outbound(action.Outbound)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
@ -112,11 +112,11 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
|
|||||||
buf.ReleaseMulti(buffers)
|
buf.ReleaseMulti(buffers)
|
||||||
return E.New("TCP is not supported by outbound: ", selectedOutbound.Tag())
|
return E.New("TCP is not supported by outbound: ", selectedOutbound.Tag())
|
||||||
}
|
}
|
||||||
case *rule.RuleActionReject:
|
case *R.RuleActionReject:
|
||||||
buf.ReleaseMulti(buffers)
|
buf.ReleaseMulti(buffers)
|
||||||
N.CloseOnHandshakeFailure(conn, onClose, action.Error(ctx))
|
N.CloseOnHandshakeFailure(conn, onClose, action.Error(ctx))
|
||||||
return nil
|
return nil
|
||||||
case *rule.RuleActionHijackDNS:
|
case *R.RuleActionHijackDNS:
|
||||||
for _, buffer := range buffers {
|
for _, buffer := range buffers {
|
||||||
conn = bufio.NewCachedConn(conn, buffer)
|
conn = bufio.NewCachedConn(conn, buffer)
|
||||||
}
|
}
|
||||||
@ -154,7 +154,7 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
|
|||||||
}))
|
}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
if E.IsClosedOrCanceled(err) {
|
if E.IsClosedOrCanceled(err) || R.IsRejected(err) {
|
||||||
r.logger.DebugContext(ctx, "connection closed: ", err)
|
r.logger.DebugContext(ctx, "connection closed: ", err)
|
||||||
} else {
|
} else {
|
||||||
r.logger.ErrorContext(ctx, err)
|
r.logger.ErrorContext(ctx, err)
|
||||||
@ -171,7 +171,7 @@ func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn,
|
|||||||
err := r.routePacketConnection(ctx, conn, metadata, onClose)
|
err := r.routePacketConnection(ctx, conn, metadata, onClose)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
N.CloseOnHandshakeFailure(conn, onClose, err)
|
N.CloseOnHandshakeFailure(conn, onClose, err)
|
||||||
if E.IsClosedOrCanceled(err) {
|
if E.IsClosedOrCanceled(err) || R.IsRejected(err) {
|
||||||
r.logger.DebugContext(ctx, "connection closed: ", err)
|
r.logger.DebugContext(ctx, "connection closed: ", err)
|
||||||
} else {
|
} else {
|
||||||
r.logger.ErrorContext(ctx, err)
|
r.logger.ErrorContext(ctx, err)
|
||||||
@ -217,7 +217,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
|
|||||||
var selectReturn bool
|
var selectReturn bool
|
||||||
if selectedRule != nil {
|
if selectedRule != nil {
|
||||||
switch action := selectedRule.Action().(type) {
|
switch action := selectedRule.Action().(type) {
|
||||||
case *rule.RuleActionRoute:
|
case *R.RuleActionRoute:
|
||||||
var loaded bool
|
var loaded bool
|
||||||
selectedOutbound, loaded = r.outbound.Outbound(action.Outbound)
|
selectedOutbound, loaded = r.outbound.Outbound(action.Outbound)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
@ -228,11 +228,11 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
|
|||||||
N.ReleaseMultiPacketBuffer(packetBuffers)
|
N.ReleaseMultiPacketBuffer(packetBuffers)
|
||||||
return E.New("UDP is not supported by outbound: ", selectedOutbound.Tag())
|
return E.New("UDP is not supported by outbound: ", selectedOutbound.Tag())
|
||||||
}
|
}
|
||||||
case *rule.RuleActionReject:
|
case *R.RuleActionReject:
|
||||||
N.ReleaseMultiPacketBuffer(packetBuffers)
|
N.ReleaseMultiPacketBuffer(packetBuffers)
|
||||||
N.CloseOnHandshakeFailure(conn, onClose, action.Error(ctx))
|
N.CloseOnHandshakeFailure(conn, onClose, action.Error(ctx))
|
||||||
return nil
|
return nil
|
||||||
case *rule.RuleActionHijackDNS:
|
case *R.RuleActionHijackDNS:
|
||||||
r.hijackDNSPacket(ctx, conn, packetBuffers, metadata, onClose)
|
r.hijackDNSPacket(ctx, conn, packetBuffers, metadata, onClose)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -271,7 +271,7 @@ func (r *Router) PreMatch(metadata adapter.InboundContext) error {
|
|||||||
if selectedRule == nil {
|
if selectedRule == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
rejectAction, isReject := selectedRule.Action().(*rule.RuleActionReject)
|
rejectAction, isReject := selectedRule.Action().(*R.RuleActionReject)
|
||||||
if !isReject {
|
if !isReject {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -346,7 +346,7 @@ func (r *Router) matchRule(
|
|||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
if metadata.InboundOptions != common.DefaultValue[option.InboundOptions]() {
|
if metadata.InboundOptions != common.DefaultValue[option.InboundOptions]() {
|
||||||
if !preMatch && metadata.InboundOptions.SniffEnabled {
|
if !preMatch && metadata.InboundOptions.SniffEnabled {
|
||||||
newBuffer, newPackerBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{
|
newBuffer, newPackerBuffers, newErr := r.actionSniff(ctx, metadata, &R.RuleActionSniff{
|
||||||
OverrideDestination: metadata.InboundOptions.SniffOverrideDestination,
|
OverrideDestination: metadata.InboundOptions.SniffOverrideDestination,
|
||||||
Timeout: time.Duration(metadata.InboundOptions.SniffTimeout),
|
Timeout: time.Duration(metadata.InboundOptions.SniffTimeout),
|
||||||
}, inputConn, inputPacketConn, nil)
|
}, inputConn, inputPacketConn, nil)
|
||||||
@ -361,7 +361,7 @@ func (r *Router) matchRule(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) != dns.DomainStrategyAsIS {
|
if dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) != dns.DomainStrategyAsIS {
|
||||||
fatalErr = r.actionResolve(ctx, metadata, &rule.RuleActionResolve{
|
fatalErr = r.actionResolve(ctx, metadata, &R.RuleActionResolve{
|
||||||
Strategy: dns.DomainStrategy(metadata.InboundOptions.DomainStrategy),
|
Strategy: dns.DomainStrategy(metadata.InboundOptions.DomainStrategy),
|
||||||
})
|
})
|
||||||
if fatalErr != nil {
|
if fatalErr != nil {
|
||||||
@ -398,11 +398,11 @@ match:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var routeOptions *rule.RuleActionRouteOptions
|
var routeOptions *R.RuleActionRouteOptions
|
||||||
switch action := currentRule.Action().(type) {
|
switch action := currentRule.Action().(type) {
|
||||||
case *rule.RuleActionRoute:
|
case *R.RuleActionRoute:
|
||||||
routeOptions = &action.RuleActionRouteOptions
|
routeOptions = &action.RuleActionRouteOptions
|
||||||
case *rule.RuleActionRouteOptions:
|
case *R.RuleActionRouteOptions:
|
||||||
routeOptions = action
|
routeOptions = action
|
||||||
}
|
}
|
||||||
if routeOptions != nil {
|
if routeOptions != nil {
|
||||||
@ -448,7 +448,7 @@ match:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch action := currentRule.Action().(type) {
|
switch action := currentRule.Action().(type) {
|
||||||
case *rule.RuleActionSniff:
|
case *R.RuleActionSniff:
|
||||||
if !preMatch {
|
if !preMatch {
|
||||||
newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn, buffers)
|
newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn, buffers)
|
||||||
if newErr != nil {
|
if newErr != nil {
|
||||||
@ -465,7 +465,7 @@ match:
|
|||||||
selectedRuleIndex = currentRuleIndex
|
selectedRuleIndex = currentRuleIndex
|
||||||
break match
|
break match
|
||||||
}
|
}
|
||||||
case *rule.RuleActionResolve:
|
case *R.RuleActionResolve:
|
||||||
fatalErr = r.actionResolve(ctx, metadata, action)
|
fatalErr = r.actionResolve(ctx, metadata, action)
|
||||||
if fatalErr != nil {
|
if fatalErr != nil {
|
||||||
return
|
return
|
||||||
@ -485,7 +485,7 @@ match:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) actionSniff(
|
func (r *Router) actionSniff(
|
||||||
ctx context.Context, metadata *adapter.InboundContext, action *rule.RuleActionSniff,
|
ctx context.Context, metadata *adapter.InboundContext, action *R.RuleActionSniff,
|
||||||
inputConn net.Conn, inputPacketConn N.PacketConn, inputBuffers []*buf.Buffer,
|
inputConn net.Conn, inputPacketConn N.PacketConn, inputBuffers []*buf.Buffer,
|
||||||
) (buffer *buf.Buffer, packetBuffers []*N.PacketBuffer, fatalErr error) {
|
) (buffer *buf.Buffer, packetBuffers []*N.PacketBuffer, fatalErr error) {
|
||||||
if sniff.Skip(metadata) {
|
if sniff.Skip(metadata) {
|
||||||
@ -645,7 +645,7 @@ func (r *Router) actionSniff(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) actionResolve(ctx context.Context, metadata *adapter.InboundContext, action *rule.RuleActionResolve) error {
|
func (r *Router) actionResolve(ctx context.Context, metadata *adapter.InboundContext, action *R.RuleActionResolve) error {
|
||||||
if metadata.Destination.IsFqdn() {
|
if metadata.Destination.IsFqdn() {
|
||||||
metadata.DNSServer = action.Server
|
metadata.DNSServer = action.Server
|
||||||
addresses, err := r.Lookup(adapter.WithContext(ctx, metadata), metadata.Destination.Fqdn, action.Strategy)
|
addresses, err := r.Lookup(adapter.WithContext(ctx, metadata), metadata.Destination.Fqdn, action.Strategy)
|
||||||
|
@ -170,7 +170,7 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er
|
|||||||
Question: []mDNS.Question{message.Question[0]},
|
Question: []mDNS.Question{message.Question[0]},
|
||||||
}, nil
|
}, nil
|
||||||
case C.RuleActionRejectMethodDrop:
|
case C.RuleActionRejectMethodDrop:
|
||||||
return nil, tun.ErrDrop
|
return nil, &R.RejectedError{Cause: tun.ErrDrop}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -289,7 +289,7 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS
|
|||||||
case C.RuleActionRejectMethodDefault:
|
case C.RuleActionRejectMethodDefault:
|
||||||
return nil, nil
|
return nil, nil
|
||||||
case C.RuleActionRejectMethodDrop:
|
case C.RuleActionRejectMethodDrop:
|
||||||
return nil, tun.ErrDrop
|
return nil, &R.RejectedError{Cause: tun.ErrDrop}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package rule
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -250,6 +251,23 @@ func (r *RuleActionDirect) String() string {
|
|||||||
return "direct" + r.description
|
return "direct" + r.description
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RejectedError struct {
|
||||||
|
Cause error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RejectedError) Error() string {
|
||||||
|
return "rejected"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RejectedError) Unwrap() error {
|
||||||
|
return r.Cause
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsRejected(err error) bool {
|
||||||
|
var rejected *RejectedError
|
||||||
|
return errors.As(err, &rejected)
|
||||||
|
}
|
||||||
|
|
||||||
type RuleActionReject struct {
|
type RuleActionReject struct {
|
||||||
Method string
|
Method string
|
||||||
NoDrop bool
|
NoDrop bool
|
||||||
@ -273,9 +291,9 @@ func (r *RuleActionReject) Error(ctx context.Context) error {
|
|||||||
var returnErr error
|
var returnErr error
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case C.RuleActionRejectMethodDefault:
|
case C.RuleActionRejectMethodDefault:
|
||||||
returnErr = syscall.ECONNREFUSED
|
returnErr = &RejectedError{syscall.ECONNREFUSED}
|
||||||
case C.RuleActionRejectMethodDrop:
|
case C.RuleActionRejectMethodDrop:
|
||||||
return tun.ErrDrop
|
return &RejectedError{tun.ErrDrop}
|
||||||
default:
|
default:
|
||||||
panic(F.ToString("unknown reject method: ", r.Method))
|
panic(F.ToString("unknown reject method: ", r.Method))
|
||||||
}
|
}
|
||||||
@ -293,7 +311,7 @@ func (r *RuleActionReject) Error(ctx context.Context) error {
|
|||||||
if ctx != nil {
|
if ctx != nil {
|
||||||
r.logger.DebugContext(ctx, "dropped due to flooding")
|
r.logger.DebugContext(ctx, "dropped due to flooding")
|
||||||
}
|
}
|
||||||
return tun.ErrDrop
|
return &RejectedError{tun.ErrDrop}
|
||||||
}
|
}
|
||||||
return returnErr
|
return returnErr
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user