From 7f65ab81660e9b0d454be5fbb04528fb131c65cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 21 Oct 2024 23:38:34 +0800 Subject: [PATCH 01/39] Crazy sekai overturns the small pond --- adapter/conn_router.go | 104 ---- adapter/handler.go | 26 + adapter/inbound.go | 35 +- adapter/outbound.go | 5 - adapter/router.go | 35 +- adapter/rule.go | 38 ++ adapter/upstream.go | 177 ++++-- adapter/upstream_legacy.go | 216 +++++++ adapter/v2ray.go | 4 +- cmd/sing-box/cmd_rule_set_match.go | 4 +- .../convertor/adguard/convertor_test.go | 8 +- common/mux/router.go | 17 +- common/mux/v2ray_legacy.go | 32 - common/sniff/sniff.go | 2 +- common/uot/router.go | 39 +- constant/rule.go | 15 + experimental/clashapi/rules.go | 3 +- .../clashapi/trafficontrol/tracker.go | 35 +- go.mod | 14 +- go.sum | 28 +- inbound/default.go | 51 +- inbound/default_tcp.go | 18 +- inbound/default_udp.go | 87 +-- inbound/direct.go | 49 +- inbound/http.go | 39 +- inbound/mixed.go | 26 +- inbound/naive.go | 26 +- inbound/redirect.go | 9 +- inbound/shadowsocks.go | 39 +- inbound/shadowsocks_multi.go | 33 +- inbound/shadowsocks_relay.go | 31 +- inbound/shadowtls.go | 9 + inbound/socks.go | 19 +- inbound/tproxy.go | 43 +- inbound/trojan.go | 26 +- inbound/tun.go | 88 +-- inbound/vless.go | 26 +- inbound/vmess.go | 26 +- include/quic_stub.go | 3 +- option/inbound.go | 1 + option/rule.go | 48 +- option/rule_action.go | 166 +++++ option/rule_dns.go | 57 +- option/types.go | 26 +- outbound/block.go | 2 + outbound/default.go | 10 +- outbound/direct.go | 14 +- outbound/dns.go | 2 + outbound/http.go | 8 - outbound/hysteria.go | 8 - outbound/hysteria2.go | 8 - outbound/proxy.go | 3 + outbound/selector.go | 16 +- outbound/shadowsocks.go | 8 - outbound/shadowtls.go | 8 - outbound/socks.go | 4 + outbound/ssh.go | 8 - outbound/tor.go | 8 - outbound/trojan.go | 8 - outbound/tuic.go | 8 - outbound/urltest.go | 4 + outbound/vless.go | 8 - outbound/vmess.go | 8 - outbound/wireguard.go | 4 + ...uter_geo_resources.go => geo_resources.go} | 3 +- route/route.go | 583 ++++++++++++++++++ route/{router_dns.go => route_dns.go} | 105 ++-- route/router.go | 394 +----------- route/{ => rule}/rule_abstract.go | 20 +- route/rule/rule_action.go | 228 +++++++ route/{ => rule}/rule_default.go | 48 +- route/{ => rule}/rule_dns.go | 63 +- route/{ => rule}/rule_headless.go | 2 +- route/{ => rule}/rule_item_adguard.go | 2 +- route/{ => rule}/rule_item_auth_user.go | 2 +- route/{ => rule}/rule_item_cidr.go | 2 +- route/{ => rule}/rule_item_clash_mode.go | 2 +- route/{ => rule}/rule_item_client.go | 2 +- route/{ => rule}/rule_item_domain.go | 2 +- route/{ => rule}/rule_item_domain_keyword.go | 2 +- route/{ => rule}/rule_item_domain_regex.go | 2 +- route/{ => rule}/rule_item_geoip.go | 2 +- route/{ => rule}/rule_item_geosite.go | 2 +- route/{ => rule}/rule_item_inbound.go | 2 +- route/{ => rule}/rule_item_ip_is_private.go | 2 +- route/{ => rule}/rule_item_ipversion.go | 2 +- route/{ => rule}/rule_item_network.go | 2 +- route/{ => rule}/rule_item_outbound.go | 2 +- route/{ => rule}/rule_item_package_name.go | 2 +- route/{ => rule}/rule_item_port.go | 2 +- route/{ => rule}/rule_item_port_range.go | 2 +- route/{ => rule}/rule_item_process_name.go | 2 +- route/{ => rule}/rule_item_process_path.go | 2 +- .../rule_item_process_path_regex.go | 2 +- route/{ => rule}/rule_item_protocol.go | 2 +- route/{ => rule}/rule_item_query_type.go | 2 +- route/{ => rule}/rule_item_rule_set.go | 2 +- route/{ => rule}/rule_item_user.go | 2 +- route/{ => rule}/rule_item_user_id.go | 2 +- route/{ => rule}/rule_item_wifi_bssid.go | 2 +- route/{ => rule}/rule_item_wifi_ssid.go | 2 +- route/{ => rule}/rule_set.go | 30 +- route/{ => rule}/rule_set_local.go | 2 +- route/{ => rule}/rule_set_remote.go | 2 +- route/{router_rule.go => rule_conds.go} | 24 - test/brutal_test.go | 52 +- test/clash_test.go | 12 +- test/direct_test.go | 13 +- test/docker_test.go | 9 +- test/domain_inbound_test.go | 13 +- test/ech_test.go | 38 +- test/go.mod | 85 ++- test/go.sum | 189 +++--- test/http_test.go | 13 +- test/hysteria2_test.go | 12 +- test/hysteria_test.go | 13 +- test/inbound_detour_test.go | 13 +- test/mux_cool_test.go | 13 +- test/mux_test.go | 26 +- test/shadowsocks_test.go | 39 +- test/shadowtls_test.go | 63 +- test/tfo_test.go | 13 +- test/tls_test.go | 13 +- test/trojan_test.go | 26 +- test/tuic_test.go | 13 +- test/v2ray_transport_test.go | 52 +- test/vmess_test.go | 12 +- transport/v2ray/grpc.go | 7 +- transport/v2ray/grpc_lite.go | 5 +- transport/v2ray/quic.go | 5 +- transport/v2ray/transport.go | 15 +- transport/v2raygrpc/client.go | 2 +- transport/v2raygrpc/conn.go | 10 +- transport/v2raygrpc/server.go | 25 +- transport/v2raygrpclite/server.go | 32 +- transport/v2rayhttp/server.go | 21 +- transport/v2rayhttpupgrade/server.go | 14 +- transport/v2rayquic/server.go | 12 +- transport/v2raywebsocket/server.go | 18 +- transport/wireguard/device_stack.go | 12 + 140 files changed, 2855 insertions(+), 1545 deletions(-) delete mode 100644 adapter/conn_router.go create mode 100644 adapter/rule.go create mode 100644 adapter/upstream_legacy.go delete mode 100644 common/mux/v2ray_legacy.go create mode 100644 option/rule_action.go rename route/{router_geo_resources.go => geo_resources.go} (98%) create mode 100644 route/route.go rename route/{router_dns.go => route_dns.go} (75%) rename route/{ => rule}/rule_abstract.go (94%) create mode 100644 route/rule/rule_action.go rename route/{ => rule}/rule_default.go (88%) rename route/{ => rule}/rule_dns.go (89%) rename route/{ => rule}/rule_headless.go (99%) rename route/{ => rule}/rule_item_adguard.go (98%) rename route/{ => rule}/rule_item_auth_user.go (98%) rename route/{ => rule}/rule_item_cidr.go (99%) rename route/{ => rule}/rule_item_clash_mode.go (97%) rename route/{ => rule}/rule_item_client.go (98%) rename route/{ => rule}/rule_item_domain.go (99%) rename route/{ => rule}/rule_item_domain_keyword.go (98%) rename route/{ => rule}/rule_item_domain_regex.go (99%) rename route/{ => rule}/rule_item_geoip.go (99%) rename route/{ => rule}/rule_item_geosite.go (98%) rename route/{ => rule}/rule_item_inbound.go (98%) rename route/{ => rule}/rule_item_ip_is_private.go (98%) rename route/{ => rule}/rule_item_ipversion.go (97%) rename route/{ => rule}/rule_item_network.go (98%) rename route/{ => rule}/rule_item_outbound.go (98%) rename route/{ => rule}/rule_item_package_name.go (98%) rename route/{ => rule}/rule_item_port.go (98%) rename route/{ => rule}/rule_item_port_range.go (99%) rename route/{ => rule}/rule_item_process_name.go (98%) rename route/{ => rule}/rule_item_process_path.go (98%) rename route/{ => rule}/rule_item_process_path_regex.go (98%) rename route/{ => rule}/rule_item_protocol.go (98%) rename route/{ => rule}/rule_item_query_type.go (98%) rename route/{ => rule}/rule_item_rule_set.go (99%) rename route/{ => rule}/rule_item_user.go (98%) rename route/{ => rule}/rule_item_user_id.go (98%) rename route/{ => rule}/rule_item_wifi_bssid.go (98%) rename route/{ => rule}/rule_item_wifi_ssid.go (98%) rename route/{ => rule}/rule_set.go (60%) rename route/{ => rule}/rule_set_local.go (99%) rename route/{ => rule}/rule_set_remote.go (99%) rename route/{router_rule.go => rule_conds.go} (78%) diff --git a/adapter/conn_router.go b/adapter/conn_router.go deleted file mode 100644 index a87c45e8c..000000000 --- a/adapter/conn_router.go +++ /dev/null @@ -1,104 +0,0 @@ -package adapter - -import ( - "context" - "net" - - "github.com/sagernet/sing/common/logger" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -type ConnectionRouter interface { - RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error - RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error -} - -func NewRouteHandler( - metadata InboundContext, - router ConnectionRouter, - logger logger.ContextLogger, -) UpstreamHandlerAdapter { - return &routeHandlerWrapper{ - metadata: metadata, - router: router, - logger: logger, - } -} - -func NewRouteContextHandler( - router ConnectionRouter, - logger logger.ContextLogger, -) UpstreamHandlerAdapter { - return &routeContextHandlerWrapper{ - router: router, - logger: logger, - } -} - -var _ UpstreamHandlerAdapter = (*routeHandlerWrapper)(nil) - -type routeHandlerWrapper struct { - metadata InboundContext - router ConnectionRouter - logger logger.ContextLogger -} - -func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { - myMetadata := w.metadata - if metadata.Source.IsValid() { - myMetadata.Source = metadata.Source - } - if metadata.Destination.IsValid() { - myMetadata.Destination = metadata.Destination - } - return w.router.RouteConnection(ctx, conn, myMetadata) -} - -func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { - myMetadata := w.metadata - if metadata.Source.IsValid() { - myMetadata.Source = metadata.Source - } - if metadata.Destination.IsValid() { - myMetadata.Destination = metadata.Destination - } - return w.router.RoutePacketConnection(ctx, conn, myMetadata) -} - -func (w *routeHandlerWrapper) NewError(ctx context.Context, err error) { - w.logger.ErrorContext(ctx, err) -} - -var _ UpstreamHandlerAdapter = (*routeContextHandlerWrapper)(nil) - -type routeContextHandlerWrapper struct { - router ConnectionRouter - logger logger.ContextLogger -} - -func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { - myMetadata := ContextFrom(ctx) - if metadata.Source.IsValid() { - myMetadata.Source = metadata.Source - } - if metadata.Destination.IsValid() { - myMetadata.Destination = metadata.Destination - } - return w.router.RouteConnection(ctx, conn, *myMetadata) -} - -func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { - myMetadata := ContextFrom(ctx) - if metadata.Source.IsValid() { - myMetadata.Source = metadata.Source - } - if metadata.Destination.IsValid() { - myMetadata.Destination = metadata.Destination - } - return w.router.RoutePacketConnection(ctx, conn, *myMetadata) -} - -func (w *routeContextHandlerWrapper) NewError(ctx context.Context, err error) { - w.logger.ErrorContext(ctx, err) -} diff --git a/adapter/handler.go b/adapter/handler.go index bc5bcfbb0..6da158ab2 100644 --- a/adapter/handler.go +++ b/adapter/handler.go @@ -6,27 +6,53 @@ import ( "github.com/sagernet/sing/common/buf" E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) +// Deprecated type ConnectionHandler interface { NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error } +type ConnectionHandlerEx interface { + NewConnectionEx(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc) +} + +// Deprecated: use PacketHandlerEx instead type PacketHandler interface { NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata InboundContext) error } +type PacketHandlerEx interface { + NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) +} + +// Deprecated: use OOBPacketHandlerEx instead type OOBPacketHandler interface { NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, oob []byte, metadata InboundContext) error } +type OOBPacketHandlerEx interface { + NewPacketEx(buffer *buf.Buffer, oob []byte, source M.Socksaddr) +} + +// Deprecated type PacketConnectionHandler interface { NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error } +type PacketConnectionHandlerEx interface { + NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc) +} + type UpstreamHandlerAdapter interface { N.TCPConnectionHandler N.UDPConnectionHandler E.Handler } + +type UpstreamHandlerAdapterEx interface { + N.TCPConnectionHandlerEx + N.UDPConnectionHandlerEx +} diff --git a/adapter/inbound.go b/adapter/inbound.go index 82909d011..f4d5802f3 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -2,13 +2,11 @@ package adapter import ( "context" - "net" "net/netip" "github.com/sagernet/sing-box/common/process" "github.com/sagernet/sing-box/option" M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" ) type Inbound interface { @@ -17,11 +15,14 @@ type Inbound interface { Tag() string } -type InjectableInbound interface { +type TCPInjectableInbound interface { Inbound - Network() []string - NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error - NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error + ConnectionHandlerEx +} + +type UDPInjectableInbound interface { + Inbound + PacketConnectionHandlerEx } type InboundContext struct { @@ -43,16 +44,18 @@ type InboundContext struct { // cache - InboundDetour string - LastInbound string - OriginDestination M.Socksaddr - InboundOptions option.InboundOptions - DestinationAddresses []netip.Addr - SourceGeoIPCode string - GeoIPCode string - ProcessInfo *process.Info - QueryType uint16 - FakeIP bool + InboundDetour string + LastInbound string + OriginDestination M.Socksaddr + // Deprecated + InboundOptions option.InboundOptions + UDPDisableDomainUnmapping bool + DestinationAddresses []netip.Addr + SourceGeoIPCode string + GeoIPCode string + ProcessInfo *process.Info + QueryType uint16 + FakeIP bool // rule cache diff --git a/adapter/outbound.go b/adapter/outbound.go index b6980fb97..312cdf3ed 100644 --- a/adapter/outbound.go +++ b/adapter/outbound.go @@ -1,9 +1,6 @@ package adapter import ( - "context" - "net" - N "github.com/sagernet/sing/common/network" ) @@ -15,6 +12,4 @@ type Outbound interface { Network() []string Dependencies() []string N.Dialer - NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error - NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error } diff --git a/adapter/router.go b/adapter/router.go index 5dc4de538..134c94420 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -34,6 +34,7 @@ type Router interface { FakeIPStore() FakeIPStore ConnectionRouter + ConnectionRouterEx GeoIPReader() *geoip.Reader LoadGeosite(code string) (Rule, error) @@ -70,34 +71,24 @@ type Router interface { ResetNetwork() error } -func ContextWithRouter(ctx context.Context, router Router) context.Context { - return service.ContextWith(ctx, router) +// Deprecated: Use ConnectionRouterEx instead. +type ConnectionRouter interface { + RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error + RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error } -func RouterFromContext(ctx context.Context) Router { - return service.FromContext[Router](ctx) -} - -type HeadlessRule interface { - Match(metadata *InboundContext) bool - String() string +type ConnectionRouterEx interface { + ConnectionRouter + RouteConnectionEx(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc) + RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc) } -type Rule interface { - HeadlessRule - Service - Type() string - UpdateGeosite() error - Outbound() string +func ContextWithRouter(ctx context.Context, router Router) context.Context { + return service.ContextWith(ctx, router) } -type DNSRule interface { - Rule - DisableCache() bool - RewriteTTL() *uint32 - ClientSubnet() *netip.Prefix - WithAddressLimit() bool - MatchAddressLimit(metadata *InboundContext) bool +func RouterFromContext(ctx context.Context) Router { + return service.FromContext[Router](ctx) } type RuleSet interface { diff --git a/adapter/rule.go b/adapter/rule.go new file mode 100644 index 000000000..f3737a25d --- /dev/null +++ b/adapter/rule.go @@ -0,0 +1,38 @@ +package adapter + +import ( + C "github.com/sagernet/sing-box/constant" +) + +type HeadlessRule interface { + Match(metadata *InboundContext) bool + String() string +} + +type Rule interface { + HeadlessRule + Service + Type() string + UpdateGeosite() error + Action() RuleAction +} + +type DNSRule interface { + Rule + WithAddressLimit() bool + MatchAddressLimit(metadata *InboundContext) bool +} + +type RuleAction interface { + Type() string + String() string +} + +func IsFinalAction(action RuleAction) bool { + switch action.Type() { + case C.RuleActionTypeSniff, C.RuleActionTypeResolve: + return false + default: + return true + } +} diff --git a/adapter/upstream.go b/adapter/upstream.go index 95dd98ea1..8b0d7c035 100644 --- a/adapter/upstream.go +++ b/adapter/upstream.go @@ -4,112 +4,165 @@ import ( "context" "net" - E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) type ( - ConnectionHandlerFunc = func(ctx context.Context, conn net.Conn, metadata InboundContext) error - PacketConnectionHandlerFunc = func(ctx context.Context, conn N.PacketConn, metadata InboundContext) error + ConnectionHandlerFuncEx = func(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc) + PacketConnectionHandlerFuncEx = func(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc) ) -func NewUpstreamHandler( +func NewUpstreamHandlerEx( metadata InboundContext, - connectionHandler ConnectionHandlerFunc, - packetHandler PacketConnectionHandlerFunc, - errorHandler E.Handler, -) UpstreamHandlerAdapter { - return &myUpstreamHandlerWrapper{ + connectionHandler ConnectionHandlerFuncEx, + packetHandler PacketConnectionHandlerFuncEx, +) UpstreamHandlerAdapterEx { + return &myUpstreamHandlerWrapperEx{ metadata: metadata, connectionHandler: connectionHandler, packetHandler: packetHandler, - errorHandler: errorHandler, } } -var _ UpstreamHandlerAdapter = (*myUpstreamHandlerWrapper)(nil) +var _ UpstreamHandlerAdapterEx = (*myUpstreamHandlerWrapperEx)(nil) -type myUpstreamHandlerWrapper struct { +type myUpstreamHandlerWrapperEx struct { metadata InboundContext - connectionHandler ConnectionHandlerFunc - packetHandler PacketConnectionHandlerFunc - errorHandler E.Handler + connectionHandler ConnectionHandlerFuncEx + packetHandler PacketConnectionHandlerFuncEx } -func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { +func (w *myUpstreamHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { myMetadata := w.metadata - if metadata.Source.IsValid() { - myMetadata.Source = metadata.Source + if source.IsValid() { + myMetadata.Source = source } - if metadata.Destination.IsValid() { - myMetadata.Destination = metadata.Destination + if destination.IsValid() { + myMetadata.Destination = destination } - return w.connectionHandler(ctx, conn, myMetadata) + w.connectionHandler(ctx, conn, myMetadata, onClose) } -func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { +func (w *myUpstreamHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { myMetadata := w.metadata - if metadata.Source.IsValid() { - myMetadata.Source = metadata.Source + if source.IsValid() { + myMetadata.Source = source } - if metadata.Destination.IsValid() { - myMetadata.Destination = metadata.Destination + if destination.IsValid() { + myMetadata.Destination = destination } - return w.packetHandler(ctx, conn, myMetadata) + w.packetHandler(ctx, conn, myMetadata, onClose) } -func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) { - w.errorHandler.NewError(ctx, err) -} - -func UpstreamMetadata(metadata InboundContext) M.Metadata { - return M.Metadata{ - Source: metadata.Source, - Destination: metadata.Destination, - } -} +var _ UpstreamHandlerAdapterEx = (*myUpstreamContextHandlerWrapperEx)(nil) -type myUpstreamContextHandlerWrapper struct { - connectionHandler ConnectionHandlerFunc - packetHandler PacketConnectionHandlerFunc - errorHandler E.Handler +type myUpstreamContextHandlerWrapperEx struct { + connectionHandler ConnectionHandlerFuncEx + packetHandler PacketConnectionHandlerFuncEx } -func NewUpstreamContextHandler( - connectionHandler ConnectionHandlerFunc, - packetHandler PacketConnectionHandlerFunc, - errorHandler E.Handler, -) UpstreamHandlerAdapter { - return &myUpstreamContextHandlerWrapper{ +func NewUpstreamContextHandlerEx( + connectionHandler ConnectionHandlerFuncEx, + packetHandler PacketConnectionHandlerFuncEx, +) UpstreamHandlerAdapterEx { + return &myUpstreamContextHandlerWrapperEx{ connectionHandler: connectionHandler, packetHandler: packetHandler, - errorHandler: errorHandler, } } -func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { +func (w *myUpstreamContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { myMetadata := ContextFrom(ctx) - if metadata.Source.IsValid() { - myMetadata.Source = metadata.Source + if source.IsValid() { + myMetadata.Source = source } - if metadata.Destination.IsValid() { - myMetadata.Destination = metadata.Destination + if destination.IsValid() { + myMetadata.Destination = destination } - return w.connectionHandler(ctx, conn, *myMetadata) + w.connectionHandler(ctx, conn, *myMetadata, onClose) } -func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { +func (w *myUpstreamContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { myMetadata := ContextFrom(ctx) - if metadata.Source.IsValid() { - myMetadata.Source = metadata.Source + if source.IsValid() { + myMetadata.Source = source + } + if destination.IsValid() { + myMetadata.Destination = destination + } + w.packetHandler(ctx, conn, *myMetadata, onClose) +} + +func NewRouteHandlerEx( + metadata InboundContext, + router ConnectionRouterEx, +) UpstreamHandlerAdapterEx { + return &routeHandlerWrapperEx{ + metadata: metadata, + router: router, + } +} + +var _ UpstreamHandlerAdapterEx = (*routeHandlerWrapperEx)(nil) + +type routeHandlerWrapperEx struct { + metadata InboundContext + router ConnectionRouterEx +} + +func (r *routeHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + if source.IsValid() { + r.metadata.Source = source } - if metadata.Destination.IsValid() { - myMetadata.Destination = metadata.Destination + if destination.IsValid() { + r.metadata.Destination = destination } - return w.packetHandler(ctx, conn, *myMetadata) + r.router.RouteConnectionEx(ctx, conn, r.metadata, onClose) } -func (w *myUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) { - w.errorHandler.NewError(ctx, err) +func (r *routeHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + if source.IsValid() { + r.metadata.Source = source + } + if destination.IsValid() { + r.metadata.Destination = destination + } + r.router.RoutePacketConnectionEx(ctx, conn, r.metadata, onClose) +} + +func NewRouteContextHandlerEx( + router ConnectionRouterEx, +) UpstreamHandlerAdapterEx { + return &routeContextHandlerWrapperEx{ + router: router, + } +} + +var _ UpstreamHandlerAdapterEx = (*routeContextHandlerWrapperEx)(nil) + +type routeContextHandlerWrapperEx struct { + router ConnectionRouterEx +} + +func (r *routeContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + metadata := ContextFrom(ctx) + if source.IsValid() { + metadata.Source = source + } + if destination.IsValid() { + metadata.Destination = destination + } + r.router.RouteConnectionEx(ctx, conn, *metadata, onClose) +} + +func (r *routeContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + metadata := ContextFrom(ctx) + if source.IsValid() { + metadata.Source = source + } + if destination.IsValid() { + metadata.Destination = destination + } + r.router.RoutePacketConnectionEx(ctx, conn, *metadata, onClose) } diff --git a/adapter/upstream_legacy.go b/adapter/upstream_legacy.go new file mode 100644 index 000000000..1c6c15c01 --- /dev/null +++ b/adapter/upstream_legacy.go @@ -0,0 +1,216 @@ +package adapter + +import ( + "context" + "net" + + 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" +) + +type ( + // Deprecated + ConnectionHandlerFunc = func(ctx context.Context, conn net.Conn, metadata InboundContext) error + // Deprecated + PacketConnectionHandlerFunc = func(ctx context.Context, conn N.PacketConn, metadata InboundContext) error +) + +// Deprecated +func NewUpstreamHandler( + metadata InboundContext, + connectionHandler ConnectionHandlerFunc, + packetHandler PacketConnectionHandlerFunc, + errorHandler E.Handler, +) UpstreamHandlerAdapter { + return &myUpstreamHandlerWrapper{ + metadata: metadata, + connectionHandler: connectionHandler, + packetHandler: packetHandler, + errorHandler: errorHandler, + } +} + +var _ UpstreamHandlerAdapter = (*myUpstreamHandlerWrapper)(nil) + +// Deprecated +type myUpstreamHandlerWrapper struct { + metadata InboundContext + connectionHandler ConnectionHandlerFunc + packetHandler PacketConnectionHandlerFunc + errorHandler E.Handler +} + +func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { + myMetadata := w.metadata + if metadata.Source.IsValid() { + myMetadata.Source = metadata.Source + } + if metadata.Destination.IsValid() { + myMetadata.Destination = metadata.Destination + } + return w.connectionHandler(ctx, conn, myMetadata) +} + +func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { + myMetadata := w.metadata + if metadata.Source.IsValid() { + myMetadata.Source = metadata.Source + } + if metadata.Destination.IsValid() { + myMetadata.Destination = metadata.Destination + } + return w.packetHandler(ctx, conn, myMetadata) +} + +func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) { + w.errorHandler.NewError(ctx, err) +} + +// Deprecated +func UpstreamMetadata(metadata InboundContext) M.Metadata { + return M.Metadata{ + Source: metadata.Source, + Destination: metadata.Destination, + } +} + +// Deprecated +type myUpstreamContextHandlerWrapper struct { + connectionHandler ConnectionHandlerFunc + packetHandler PacketConnectionHandlerFunc + errorHandler E.Handler +} + +// Deprecated +func NewUpstreamContextHandler( + connectionHandler ConnectionHandlerFunc, + packetHandler PacketConnectionHandlerFunc, + errorHandler E.Handler, +) UpstreamHandlerAdapter { + return &myUpstreamContextHandlerWrapper{ + connectionHandler: connectionHandler, + packetHandler: packetHandler, + errorHandler: errorHandler, + } +} + +func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { + myMetadata := ContextFrom(ctx) + if metadata.Source.IsValid() { + myMetadata.Source = metadata.Source + } + if metadata.Destination.IsValid() { + myMetadata.Destination = metadata.Destination + } + return w.connectionHandler(ctx, conn, *myMetadata) +} + +func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { + myMetadata := ContextFrom(ctx) + if metadata.Source.IsValid() { + myMetadata.Source = metadata.Source + } + if metadata.Destination.IsValid() { + myMetadata.Destination = metadata.Destination + } + return w.packetHandler(ctx, conn, *myMetadata) +} + +func (w *myUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) { + w.errorHandler.NewError(ctx, err) +} + +// Deprecated: Use ConnectionRouterEx instead. +func NewRouteHandler( + metadata InboundContext, + router ConnectionRouter, + logger logger.ContextLogger, +) UpstreamHandlerAdapter { + return &routeHandlerWrapper{ + metadata: metadata, + router: router, + logger: logger, + } +} + +// Deprecated: Use ConnectionRouterEx instead. +func NewRouteContextHandler( + router ConnectionRouter, + logger logger.ContextLogger, +) UpstreamHandlerAdapter { + return &routeContextHandlerWrapper{ + router: router, + logger: logger, + } +} + +var _ UpstreamHandlerAdapter = (*routeHandlerWrapper)(nil) + +// Deprecated: Use ConnectionRouterEx instead. +type routeHandlerWrapper struct { + metadata InboundContext + router ConnectionRouter + logger logger.ContextLogger +} + +func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { + myMetadata := w.metadata + if metadata.Source.IsValid() { + myMetadata.Source = metadata.Source + } + if metadata.Destination.IsValid() { + myMetadata.Destination = metadata.Destination + } + return w.router.RouteConnection(ctx, conn, myMetadata) +} + +func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { + myMetadata := w.metadata + if metadata.Source.IsValid() { + myMetadata.Source = metadata.Source + } + if metadata.Destination.IsValid() { + myMetadata.Destination = metadata.Destination + } + return w.router.RoutePacketConnection(ctx, conn, myMetadata) +} + +func (w *routeHandlerWrapper) NewError(ctx context.Context, err error) { + w.logger.ErrorContext(ctx, err) +} + +var _ UpstreamHandlerAdapter = (*routeContextHandlerWrapper)(nil) + +// Deprecated: Use ConnectionRouterEx instead. +type routeContextHandlerWrapper struct { + router ConnectionRouter + logger logger.ContextLogger +} + +func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { + myMetadata := ContextFrom(ctx) + if metadata.Source.IsValid() { + myMetadata.Source = metadata.Source + } + if metadata.Destination.IsValid() { + myMetadata.Destination = metadata.Destination + } + return w.router.RouteConnection(ctx, conn, *myMetadata) +} + +func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { + myMetadata := ContextFrom(ctx) + if metadata.Source.IsValid() { + myMetadata.Source = metadata.Source + } + if metadata.Destination.IsValid() { + myMetadata.Destination = metadata.Destination + } + return w.router.RoutePacketConnection(ctx, conn, *myMetadata) +} + +func (w *routeContextHandlerWrapper) NewError(ctx context.Context, err error) { + w.logger.ErrorContext(ctx, err) +} diff --git a/adapter/v2ray.go b/adapter/v2ray.go index 5a98d2e55..d9370807f 100644 --- a/adapter/v2ray.go +++ b/adapter/v2ray.go @@ -4,7 +4,6 @@ import ( "context" "net" - E "github.com/sagernet/sing/common/exceptions" N "github.com/sagernet/sing/common/network" ) @@ -16,8 +15,7 @@ type V2RayServerTransport interface { } type V2RayServerTransportHandler interface { - N.TCPConnectionHandler - E.Handler + N.TCPConnectionHandlerEx } type V2RayClientTransport interface { diff --git a/cmd/sing-box/cmd_rule_set_match.go b/cmd/sing-box/cmd_rule_set_match.go index 937458f2c..bfa340cf4 100644 --- a/cmd/sing-box/cmd_rule_set_match.go +++ b/cmd/sing-box/cmd_rule_set_match.go @@ -10,7 +10,7 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-box/route" + "github.com/sagernet/sing-box/route/rule" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json" @@ -84,7 +84,7 @@ func ruleSetMatch(sourcePath string, domain string) error { } for i, ruleOptions := range plainRuleSet.Rules { var currentRule adapter.HeadlessRule - currentRule, err = route.NewHeadlessRule(nil, ruleOptions) + currentRule, err = rule.NewHeadlessRule(nil, ruleOptions) if err != nil { return E.Cause(err, "parse rule_set.rules.[", i, "]") } diff --git a/cmd/sing-box/internal/convertor/adguard/convertor_test.go b/cmd/sing-box/internal/convertor/adguard/convertor_test.go index 7da8a2284..6098485cf 100644 --- a/cmd/sing-box/internal/convertor/adguard/convertor_test.go +++ b/cmd/sing-box/internal/convertor/adguard/convertor_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/route" + "github.com/sagernet/sing-box/route/rule" "github.com/stretchr/testify/require" ) @@ -26,7 +26,7 @@ example.arpa `)) require.NoError(t, err) require.Len(t, rules, 1) - rule, err := route.NewHeadlessRule(nil, rules[0]) + rule, err := rule.NewHeadlessRule(nil, rules[0]) require.NoError(t, err) matchDomain := []string{ "example.org", @@ -85,7 +85,7 @@ func TestHosts(t *testing.T) { `)) require.NoError(t, err) require.Len(t, rules, 1) - rule, err := route.NewHeadlessRule(nil, rules[0]) + rule, err := rule.NewHeadlessRule(nil, rules[0]) require.NoError(t, err) matchDomain := []string{ "google.com", @@ -115,7 +115,7 @@ www.example.org `)) require.NoError(t, err) require.Len(t, rules, 1) - rule, err := route.NewHeadlessRule(nil, rules[0]) + rule, err := rule.NewHeadlessRule(nil, rules[0]) require.NoError(t, err) matchDomain := []string{ "example.com", diff --git a/common/mux/router.go b/common/mux/router.go index 8a2296852..2d4e7705d 100644 --- a/common/mux/router.go +++ b/common/mux/router.go @@ -15,11 +15,11 @@ import ( ) type Router struct { - router adapter.ConnectionRouter + router adapter.ConnectionRouterEx service *mux.Service } -func NewRouterWithOptions(router adapter.ConnectionRouter, logger logger.ContextLogger, options option.InboundMultiplexOptions) (adapter.ConnectionRouter, error) { +func NewRouterWithOptions(router adapter.ConnectionRouterEx, logger logger.ContextLogger, options option.InboundMultiplexOptions) (adapter.ConnectionRouterEx, error) { if !options.Enabled { return router, nil } @@ -54,6 +54,7 @@ func NewRouterWithOptions(router adapter.ConnectionRouter, logger logger.Context func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { if metadata.Destination == mux.Destination { + // TODO: check if WithContext is necessary return r.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, adapter.UpstreamMetadata(metadata)) } else { return r.router.RouteConnection(ctx, conn, metadata) @@ -63,3 +64,15 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { return r.router.RoutePacketConnection(ctx, conn, metadata) } + +func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + if metadata.Destination == mux.Destination { + r.service.NewConnectionEx(adapter.WithContext(ctx, &metadata), conn, metadata.Source, metadata.Destination, onClose) + return + } + r.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + r.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} diff --git a/common/mux/v2ray_legacy.go b/common/mux/v2ray_legacy.go deleted file mode 100644 index f53aff2db..000000000 --- a/common/mux/v2ray_legacy.go +++ /dev/null @@ -1,32 +0,0 @@ -package mux - -import ( - "context" - "net" - - "github.com/sagernet/sing-box/adapter" - vmess "github.com/sagernet/sing-vmess" - "github.com/sagernet/sing/common/logger" - N "github.com/sagernet/sing/common/network" -) - -type V2RayLegacyRouter struct { - router adapter.ConnectionRouter - logger logger.ContextLogger -} - -func NewV2RayLegacyRouter(router adapter.ConnectionRouter, logger logger.ContextLogger) adapter.ConnectionRouter { - return &V2RayLegacyRouter{router, logger} -} - -func (r *V2RayLegacyRouter) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - if metadata.Destination.Fqdn == vmess.MuxDestination.Fqdn { - r.logger.InfoContext(ctx, "inbound legacy multiplex connection") - return vmess.HandleMuxConnection(ctx, conn, adapter.NewRouteHandler(metadata, r.router, r.logger)) - } - return r.router.RouteConnection(ctx, conn, metadata) -} - -func (r *V2RayLegacyRouter) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return r.router.RoutePacketConnection(ctx, conn, metadata) -} diff --git a/common/sniff/sniff.go b/common/sniff/sniff.go index 80bb2984f..81fc0a279 100644 --- a/common/sniff/sniff.go +++ b/common/sniff/sniff.go @@ -18,7 +18,7 @@ type ( PacketSniffer = func(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error ) -func Skip(metadata adapter.InboundContext) bool { +func Skip(metadata *adapter.InboundContext) bool { // skip server first protocols switch metadata.Destination.Port { case 25, 465, 587: diff --git a/common/uot/router.go b/common/uot/router.go index fb2d23d55..98c6d608f 100644 --- a/common/uot/router.go +++ b/common/uot/router.go @@ -13,14 +13,14 @@ import ( "github.com/sagernet/sing/common/uot" ) -var _ adapter.ConnectionRouter = (*Router)(nil) +var _ adapter.ConnectionRouterEx = (*Router)(nil) type Router struct { - router adapter.ConnectionRouter + router adapter.ConnectionRouterEx logger logger.ContextLogger } -func NewRouter(router adapter.ConnectionRouter, logger logger.ContextLogger) *Router { +func NewRouter(router adapter.ConnectionRouterEx, logger logger.ContextLogger) *Router { return &Router{router, logger} } @@ -51,3 +51,36 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { return r.router.RoutePacketConnection(ctx, conn, metadata) } + +func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + switch metadata.Destination.Fqdn { + case uot.MagicAddress: + request, err := uot.ReadRequest(conn) + if err != nil { + err = E.Cause(err, "UoT read request") + r.logger.ErrorContext(ctx, "process connection from ", metadata.Source, ": ", err) + N.CloseOnHandshakeFailure(conn, onClose, err) + return + } + if request.IsConnect { + r.logger.InfoContext(ctx, "inbound UoT connect connection to ", request.Destination) + } else { + r.logger.InfoContext(ctx, "inbound UoT connection to ", request.Destination) + } + metadata.Domain = metadata.Destination.Fqdn + metadata.Destination = request.Destination + r.router.RoutePacketConnectionEx(ctx, uot.NewConn(conn, *request), metadata, onClose) + return + case uot.LegacyMagicAddress: + r.logger.InfoContext(ctx, "inbound legacy UoT connection") + metadata.Domain = metadata.Destination.Fqdn + metadata.Destination = M.Socksaddr{Addr: netip.IPv4Unspecified()} + r.RoutePacketConnectionEx(ctx, uot.NewConn(conn, uot.Request{}), metadata, onClose) + return + } + r.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + r.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} diff --git a/constant/rule.go b/constant/rule.go index 718e79a5e..02e03d8af 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -22,3 +22,18 @@ const ( RuleSetVersion1 = 1 + iota RuleSetVersion2 ) + +const ( + RuleActionTypeRoute = "route" + RuleActionTypeReturn = "return" + RuleActionTypeReject = "reject" + RuleActionTypeHijackDNS = "hijack-dns" + RuleActionTypeSniff = "sniff" + RuleActionTypeResolve = "resolve" +) + +const ( + RuleActionRejectMethodDefault = "default" + RuleActionRejectMethodPortUnreachable = "port-unreachable" + RuleActionRejectMethodDrop = "drop" +) diff --git a/experimental/clashapi/rules.go b/experimental/clashapi/rules.go index 6ab5dda10..bc8fbb2bb 100644 --- a/experimental/clashapi/rules.go +++ b/experimental/clashapi/rules.go @@ -30,10 +30,9 @@ func getRules(router adapter.Router) func(w http.ResponseWriter, r *http.Request rules = append(rules, Rule{ Type: rule.Type(), Payload: rule.String(), - Proxy: rule.Outbound(), + Proxy: rule.Action().String(), }) } - render.JSON(w, r, render.M{ "rules": rules, }) diff --git a/experimental/clashapi/trafficontrol/tracker.go b/experimental/clashapi/trafficontrol/tracker.go index 73c28e69b..9c18abebf 100644 --- a/experimental/clashapi/trafficontrol/tracker.go +++ b/experimental/clashapi/trafficontrol/tracker.go @@ -5,6 +5,7 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + R "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/atomic" "github.com/sagernet/sing/common/bufio" @@ -60,7 +61,7 @@ func (t TrackerMetadata) MarshalJSON() ([]byte, error) { } var rule string if t.Rule != nil { - rule = F.ToString(t.Rule, " => ", t.Rule.Outbound()) + rule = F.ToString(t.Rule, " => ", t.Rule.Action()) } else { rule = "final" } @@ -131,19 +132,21 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundCont outbound string outboundType string ) - if rule == nil { - if defaultOutbound, err := router.DefaultOutbound(N.NetworkTCP); err == nil { - next = defaultOutbound.Tag() - } - } else { - next = rule.Outbound() + var action adapter.RuleAction + if rule != nil { + action = rule.Action() + } + if routeAction, isRouteAction := action.(*R.RuleActionRoute); isRouteAction { + next = routeAction.Outbound + } else if defaultOutbound, err := router.DefaultOutbound(N.NetworkTCP); err == nil { + next = defaultOutbound.Tag() } for { - chain = append(chain, next) detour, loaded := router.Outbound(next) if !loaded { break } + chain = append(chain, next) outbound = detour.Tag() outboundType = detour.Type() group, isGroup := detour.(adapter.OutboundGroup) @@ -218,19 +221,21 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.Inbound outbound string outboundType string ) - if rule == nil { - if defaultOutbound, err := router.DefaultOutbound(N.NetworkUDP); err == nil { - next = defaultOutbound.Tag() - } - } else { - next = rule.Outbound() + var action adapter.RuleAction + if rule != nil { + action = rule.Action() + } + if routeAction, isRouteAction := action.(*R.RuleActionRoute); isRouteAction { + next = routeAction.Outbound + } else if defaultOutbound, err := router.DefaultOutbound(N.NetworkUDP); err == nil { + next = defaultOutbound.Tag() } for { - chain = append(chain, next) detour, loaded := router.Outbound(next) if !loaded { break } + chain = append(chain, next) outbound = detour.Tag() outboundType = detour.Type() group, isGroup := detour.(adapter.OutboundGroup) diff --git a/go.mod b/go.mod index 99b22b040..70cc85772 100644 --- a/go.mod +++ b/go.mod @@ -24,17 +24,17 @@ require ( github.com/sagernet/cors v1.2.1 github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.4 - github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f + github.com/sagernet/gvisor v0.0.0-20241021032506-a4324256e4a3 github.com/sagernet/quic-go v0.48.1-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.5.0 - github.com/sagernet/sing-dns v0.3.0 - github.com/sagernet/sing-mux v0.2.1 + github.com/sagernet/sing v0.6.0-alpha.3 + github.com/sagernet/sing-dns v0.4.0-alpha.1 + github.com/sagernet/sing-mux v0.3.0-alpha.1 github.com/sagernet/sing-quic v0.3.0-rc.2 github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.1.4 - github.com/sagernet/sing-tun v0.4.0-rc.5 + github.com/sagernet/sing-tun v0.6.0-alpha.3 github.com/sagernet/sing-vmess v0.1.12 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/utls v1.6.7 @@ -66,7 +66,7 @@ require ( github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect - github.com/google/btree v1.1.2 // indirect + github.com/google/btree v1.1.3 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect github.com/hashicorp/yamux v0.1.2 // indirect @@ -92,7 +92,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/sync v0.9.0 // indirect golang.org/x/text v0.20.0 // indirect - golang.org/x/time v0.5.0 // indirect + golang.org/x/time v0.7.0 // indirect golang.org/x/tools v0.24.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect diff --git a/go.sum b/go.sum index 01313fa65..ad5c611c1 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk= github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= -github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk= @@ -104,8 +104,8 @@ github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQ github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/gomobile v0.1.4 h1:WzX9ka+iHdupMgy2Vdich+OAt7TM8C2cZbIbzNjBrJY= github.com/sagernet/gomobile v0.1.4/go.mod h1:Pqq2+ZVvs10U7xK+UwJgwYWUykewi8H6vlslAO73n9E= -github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f h1:NkhuupzH5ch7b/Y/6ZHJWrnNLoiNnSJaow6DPb8VW2I= -github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f/go.mod h1:KXmw+ouSJNOsuRpg4wgwwCQuunrGz4yoAqQjsLjc6N0= +github.com/sagernet/gvisor v0.0.0-20241021032506-a4324256e4a3 h1:RxEz7LhPNiF/gX/Hg+OXr5lqsM9iVAgmaK1L1vzlDRM= +github.com/sagernet/gvisor v0.0.0-20241021032506-a4324256e4a3/go.mod h1:ehZwnT2UpmOWAHFL48XdBhnd4Qu4hN2O3Ji0us3ZHMw= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= @@ -115,12 +115,12 @@ github.com/sagernet/quic-go v0.48.1-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= -github.com/sagernet/sing v0.5.0 h1:soo2wVwLcieKWWKIksFNK6CCAojUgAppqQVwyRYGkEM= -github.com/sagernet/sing v0.5.0/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing-dns v0.3.0 h1:uHCIlbCwBxALJwXcEK1d75d7t3vzCSVEQsPfZR1cxQE= -github.com/sagernet/sing-dns v0.3.0/go.mod h1:TqLIelI+FAbVEdiTRolhGLOwvhVjY7oT+wezlOJUQ7M= -github.com/sagernet/sing-mux v0.2.1 h1:N/3MHymfnFZRd29tE3TaXwPUVVgKvxhtOkiCMLp9HVo= -github.com/sagernet/sing-mux v0.2.1/go.mod h1:dm3BWL6NvES9pbib7llpylrq7Gq+LjlzG+0RacdxcyE= +github.com/sagernet/sing v0.6.0-alpha.3 h1:GLp9d6Gbt+Ioeplauuzojz1nY2J6moceVGYIOv/h5gA= +github.com/sagernet/sing v0.6.0-alpha.3/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing-dns v0.4.0-alpha.1 h1:2KlP8DeqtGkULFiZtvG2r7SuoJP6orANFzJwC5vDKvg= +github.com/sagernet/sing-dns v0.4.0-alpha.1/go.mod h1:vgHATsm4wdymwpvBZPei8RY+546iGXS6hlWv2x6YKcM= +github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg= +github.com/sagernet/sing-mux v0.3.0-alpha.1/go.mod h1:FTcImmdfW38Lz7b+HQ+mxxOth1lz4ao8uEnz+MwIJQE= github.com/sagernet/sing-quic v0.3.0-rc.2 h1:7vcC4bdS1GBJzHZhfmJiH0CfzQ4mYLUW51Z2RNHcGwc= github.com/sagernet/sing-quic v0.3.0-rc.2/go.mod h1:3UOq51WVqzra7eCgod7t4hqnTaOiZzFUci9avMrtOqs= github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8= @@ -129,8 +129,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= -github.com/sagernet/sing-tun v0.4.0-rc.5 h1:d4o36GbVZtsucpt5On7CjTFSx4zx82DL8OlGR7rX7vY= -github.com/sagernet/sing-tun v0.4.0-rc.5/go.mod h1:rWu1ZC2lj4+UOOSNiUWjsY0y2fJcOHnLTMGna2n5P2o= +github.com/sagernet/sing-tun v0.6.0-alpha.3 h1:KddmFF9ZYC1n+HZzpAZ1StMC5v38ir6hH0PJ7uGpAGE= +github.com/sagernet/sing-tun v0.6.0-alpha.3/go.mod h1:R/UaKB1oFIvAeMIH2btQCaDVsfCNomEjfYBSCSA94sw= github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg= github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= @@ -198,8 +198,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= +golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= diff --git a/inbound/default.go b/inbound/default.go index 44c580deb..880dd26f6 100644 --- a/inbound/default.go +++ b/inbound/default.go @@ -22,13 +22,13 @@ type myInboundAdapter struct { protocol string network []string ctx context.Context - router adapter.ConnectionRouter + router adapter.ConnectionRouterEx logger log.ContextLogger tag string listenOptions option.ListenOptions - connHandler adapter.ConnectionHandler - packetHandler adapter.PacketHandler - oobPacketHandler adapter.OOBPacketHandler + connHandler adapter.ConnectionHandlerEx + packetHandler adapter.PacketHandlerEx + oobPacketHandler adapter.OOBPacketHandlerEx packetUpstream any // http mixed @@ -55,10 +55,6 @@ func (a *myInboundAdapter) Tag() string { return a.tag } -func (a *myInboundAdapter) Network() []string { - return a.network -} - func (a *myInboundAdapter) Start() error { var err error if common.Contains(a.network, N.NetworkTCP) { @@ -150,6 +146,31 @@ func (a *myInboundAdapter) newPacketConnection(ctx context.Context, conn N.Packe return a.router.RoutePacketConnection(ctx, conn, metadata) } +func (a *myInboundAdapter) upstreamHandlerEx(metadata adapter.InboundContext) adapter.UpstreamHandlerAdapterEx { + return adapter.NewUpstreamHandlerEx(metadata, a.newConnectionEx, a.streamPacketConnectionEx) +} + +func (a *myInboundAdapter) upstreamContextHandlerEx() adapter.UpstreamHandlerAdapterEx { + return adapter.NewUpstreamContextHandlerEx(a.newConnectionEx, a.newPacketConnectionEx) +} + +func (a *myInboundAdapter) newConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + a.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + a.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (a *myInboundAdapter) newPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + ctx = log.ContextWithNewID(ctx) + a.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) + a.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) + a.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} + +func (a *myInboundAdapter) streamPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + a.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) + a.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} + func (a *myInboundAdapter) createMetadata(conn net.Conn, metadata adapter.InboundContext) adapter.InboundContext { metadata.Inbound = a.tag metadata.InboundType = a.protocol @@ -167,25 +188,17 @@ func (a *myInboundAdapter) createMetadata(conn net.Conn, metadata adapter.Inboun return metadata } -func (a *myInboundAdapter) createPacketMetadata(conn N.PacketConn, metadata adapter.InboundContext) adapter.InboundContext { - metadata.Inbound = a.tag - metadata.InboundType = a.protocol - metadata.InboundDetour = a.listenOptions.Detour - metadata.InboundOptions = a.listenOptions.InboundOptions - if !metadata.Destination.IsValid() { - metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap() - } - return metadata -} - +// Deprecated: don't use func (a *myInboundAdapter) newError(err error) { a.logger.Error(err) } +// Deprecated: don't use func (a *myInboundAdapter) NewError(ctx context.Context, err error) { NewError(a.logger, ctx, err) } +// Deprecated: don't use func NewError(logger log.ContextLogger, ctx context.Context, err error) { common.Close(err) if E.IsClosedOrCanceled(err) { diff --git a/inbound/default_tcp.go b/inbound/default_tcp.go index d680c6951..d38f96fe6 100644 --- a/inbound/default_tcp.go +++ b/inbound/default_tcp.go @@ -71,18 +71,14 @@ func (a *myInboundAdapter) injectTCP(conn net.Conn, metadata adapter.InboundCont ctx := log.ContextWithNewID(a.ctx) metadata = a.createMetadata(conn, metadata) a.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) - hErr := a.connHandler.NewConnection(ctx, conn, metadata) - if hErr != nil { - conn.Close() - a.NewError(ctx, E.Cause(hErr, "process connection from ", metadata.Source)) - } + a.connHandler.NewConnectionEx(ctx, conn, metadata, nil) } -func (a *myInboundAdapter) routeTCP(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) { +func (a *myInboundAdapter) routeTCP(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + metadata := a.createMetadata(conn, adapter.InboundContext{ + Source: source, + Destination: destination, + }) a.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) - hErr := a.newConnection(ctx, conn, metadata) - if hErr != nil { - conn.Close() - a.NewError(ctx, E.Cause(hErr, "process connection from ", metadata.Source)) - } + a.connHandler.NewConnectionEx(ctx, conn, metadata, onClose) } diff --git a/inbound/default_udp.go b/inbound/default_udp.go index 8e39471ac..6bcde79da 100644 --- a/inbound/default_udp.go +++ b/inbound/default_udp.go @@ -42,7 +42,6 @@ func (a *myInboundAdapter) loopUDPIn() { defer buffer.Release() buffer.IncRef() defer buffer.DecRef() - packetService := (*myInboundPacketAdapter)(a) for { buffer.Reset() n, addr, err := a.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes()) @@ -50,16 +49,7 @@ func (a *myInboundAdapter) loopUDPIn() { return } buffer.Truncate(n) - var metadata adapter.InboundContext - metadata.Inbound = a.tag - metadata.InboundType = a.protocol - metadata.InboundOptions = a.listenOptions.InboundOptions - metadata.Source = M.SocksaddrFromNetIP(addr).Unwrap() - metadata.OriginDestination = a.udpAddr - err = a.packetHandler.NewPacket(a.ctx, packetService, buffer, metadata) - if err != nil { - a.newError(E.Cause(err, "process packet from ", metadata.Source)) - } + a.packetHandler.NewPacketEx(buffer, M.SocksaddrFromNetIP(addr).Unwrap()) } } @@ -69,7 +59,6 @@ func (a *myInboundAdapter) loopUDPOOBIn() { defer buffer.Release() buffer.IncRef() defer buffer.DecRef() - packetService := (*myInboundPacketAdapter)(a) oob := make([]byte, 1024) for { buffer.Reset() @@ -78,22 +67,12 @@ func (a *myInboundAdapter) loopUDPOOBIn() { return } buffer.Truncate(n) - var metadata adapter.InboundContext - metadata.Inbound = a.tag - metadata.InboundType = a.protocol - metadata.InboundOptions = a.listenOptions.InboundOptions - metadata.Source = M.SocksaddrFromNetIP(addr).Unwrap() - metadata.OriginDestination = a.udpAddr - err = a.oobPacketHandler.NewPacket(a.ctx, packetService, buffer, oob[:oobN], metadata) - if err != nil { - a.newError(E.Cause(err, "process packet from ", metadata.Source)) - } + a.oobPacketHandler.NewPacketEx(buffer, oob[:oobN], M.SocksaddrFromNetIP(addr).Unwrap()) } } func (a *myInboundAdapter) loopUDPInThreadSafe() { defer close(a.packetOutboundClosed) - packetService := (*myInboundPacketAdapter)(a) for { buffer := buf.NewPacket() n, addr, err := a.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes()) @@ -102,23 +81,12 @@ func (a *myInboundAdapter) loopUDPInThreadSafe() { return } buffer.Truncate(n) - var metadata adapter.InboundContext - metadata.Inbound = a.tag - metadata.InboundType = a.protocol - metadata.InboundOptions = a.listenOptions.InboundOptions - metadata.Source = M.SocksaddrFromNetIP(addr).Unwrap() - metadata.OriginDestination = a.udpAddr - err = a.packetHandler.NewPacket(a.ctx, packetService, buffer, metadata) - if err != nil { - buffer.Release() - a.newError(E.Cause(err, "process packet from ", metadata.Source)) - } + a.packetHandler.NewPacketEx(buffer, M.SocksaddrFromNetIP(addr).Unwrap()) } } func (a *myInboundAdapter) loopUDPOOBInThreadSafe() { defer close(a.packetOutboundClosed) - packetService := (*myInboundPacketAdapter)(a) oob := make([]byte, 1024) for { buffer := buf.NewPacket() @@ -128,17 +96,7 @@ func (a *myInboundAdapter) loopUDPOOBInThreadSafe() { return } buffer.Truncate(n) - var metadata adapter.InboundContext - metadata.Inbound = a.tag - metadata.InboundType = a.protocol - metadata.InboundOptions = a.listenOptions.InboundOptions - metadata.Source = M.SocksaddrFromNetIP(addr).Unwrap() - metadata.OriginDestination = a.udpAddr - err = a.oobPacketHandler.NewPacket(a.ctx, packetService, buffer, oob[:oobN], metadata) - if err != nil { - buffer.Release() - a.newError(E.Cause(err, "process packet from ", metadata.Source)) - } + a.oobPacketHandler.NewPacketEx(buffer, oob[:oobN], M.SocksaddrFromNetIP(addr).Unwrap()) } } @@ -148,7 +106,7 @@ func (a *myInboundAdapter) loopUDPOut() { case packet := <-a.packetOutbound: err := a.writePacket(packet.buffer, packet.destination) if err != nil && !E.IsClosed(err) { - a.newError(E.New("write back udp: ", err)) + a.logger.Error(E.New("write back udp: ", err)) } continue case <-a.packetOutboundClosed: @@ -164,15 +122,36 @@ func (a *myInboundAdapter) loopUDPOut() { } } +func (a *myInboundAdapter) packetConn() N.PacketConn { + return (*myInboundPacketAdapter)(a) +} + +func (a *myInboundAdapter) createPacketMetadata(conn N.PacketConn, metadata adapter.InboundContext) adapter.InboundContext { + metadata.Inbound = a.tag + metadata.InboundType = a.protocol + metadata.InboundDetour = a.listenOptions.Detour + metadata.InboundOptions = a.listenOptions.InboundOptions + if !metadata.Destination.IsValid() { + metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap() + } + metadata.OriginDestination = a.udpAddr + return metadata +} + +func (a *myInboundAdapter) createPacketMetadataEx(source M.Socksaddr, destination M.Socksaddr) adapter.InboundContext { + var metadata adapter.InboundContext + metadata.Inbound = a.tag + metadata.InboundType = a.protocol + metadata.InboundDetour = a.listenOptions.Detour + metadata.InboundOptions = a.listenOptions.InboundOptions + metadata.Source = source + metadata.Destination = destination + metadata.OriginDestination = a.udpAddr + return metadata +} + func (a *myInboundAdapter) writePacket(buffer *buf.Buffer, destination M.Socksaddr) error { defer buffer.Release() - if destination.IsFqdn() { - udpAddr, err := net.ResolveUDPAddr(N.NetworkUDP, destination.String()) - if err != nil { - return err - } - return common.Error(a.udpConn.WriteTo(buffer.Bytes(), udpAddr)) - } return common.Error(a.udpConn.WriteToUDPAddrPort(buffer.Bytes(), destination.AddrPort())) } diff --git a/inbound/direct.go b/inbound/direct.go index 7079a9f24..b9a99f12c 100644 --- a/inbound/direct.go +++ b/inbound/direct.go @@ -3,7 +3,6 @@ package inbound import ( "context" "net" - "net/netip" "time" "github.com/sagernet/sing-box/adapter" @@ -13,14 +12,14 @@ import ( "github.com/sagernet/sing/common/buf" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/udpnat" + "github.com/sagernet/sing/common/udpnat2" ) var _ adapter.Inbound = (*Direct)(nil) type Direct struct { myInboundAdapter - udpNat *udpnat.Service[netip.AddrPort] + udpNat *udpnat.Service overrideOption int overrideDestination M.Socksaddr } @@ -54,10 +53,9 @@ func NewDirect(ctx context.Context, router adapter.Router, logger log.ContextLog } else { udpTimeout = C.UDPTimeout } - inbound.udpNat = udpnat.New[netip.AddrPort](int64(udpTimeout.Seconds()), adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound)) + inbound.udpNat = udpnat.New(inbound, inbound.preparePacketConnection, udpTimeout, false) inbound.connHandler = inbound inbound.packetHandler = inbound - inbound.packetUpstream = inbound.udpNat return inbound } @@ -76,29 +74,38 @@ func (d *Direct) NewConnection(ctx context.Context, conn net.Conn, metadata adap return d.router.RouteConnection(ctx, conn, metadata) } -func (d *Direct) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext) error { +func (d *Direct) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { + var destination M.Socksaddr switch d.overrideOption { case 1: - metadata.Destination = d.overrideDestination + destination = d.overrideDestination case 2: - destination := d.overrideDestination - destination.Port = metadata.Destination.Port - metadata.Destination = destination + destination = d.overrideDestination + destination.Port = source.Port case 3: - metadata.Destination.Port = d.overrideDestination.Port + destination = source + destination.Port = d.overrideDestination.Port } - d.udpNat.NewContextPacket(ctx, metadata.Source.AddrPort(), buffer, adapter.UpstreamMetadata(metadata), func(natConn N.PacketConn) (context.Context, N.PacketWriter) { - return adapter.WithContext(log.ContextWithNewID(ctx), &metadata), &udpnat.DirectBackWriter{Source: conn, Nat: natConn} - }) - return nil + d.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, destination, nil) } -func (d *Direct) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return d.router.RouteConnection(ctx, conn, metadata) +func (d *Direct) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + d.newConnectionEx(ctx, conn, metadata, onClose) +} + +func (d *Direct) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + d.newPacketConnectionEx(ctx, conn, d.createPacketMetadataEx(source, destination), onClose) +} + +func (d *Direct) preparePacketConnection(source M.Socksaddr, destination M.Socksaddr, userData any) (bool, context.Context, N.PacketWriter, N.CloseHandlerFunc) { + return true, d.ctx, &directPacketWriter{d.packetConn(), source}, nil +} + +type directPacketWriter struct { + writer N.PacketWriter + source M.Socksaddr } -func (d *Direct) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - ctx = log.ContextWithNewID(ctx) - d.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) - return d.router.RoutePacketConnection(ctx, conn, metadata) +func (w *directPacketWriter) WritePacket(buffer *buf.Buffer, addr M.Socksaddr) error { + return w.writer.WritePacket(buffer, w.source) } diff --git a/inbound/http.go b/inbound/http.go index b46633199..20c8f6903 100644 --- a/inbound/http.go +++ b/inbound/http.go @@ -4,7 +4,6 @@ import ( std_bufio "bufio" "context" "net" - "os" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/tls" @@ -20,8 +19,8 @@ import ( ) var ( - _ adapter.Inbound = (*HTTP)(nil) - _ adapter.InjectableInbound = (*HTTP)(nil) + _ adapter.Inbound = (*HTTP)(nil) + _ adapter.TCPInjectableInbound = (*HTTP)(nil) ) type HTTP struct { @@ -72,7 +71,15 @@ func (h *HTTP) Close() error { ) } -func (h *HTTP) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *HTTP) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := h.newConnection(ctx, conn, metadata, onClose) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } +} + +func (h *HTTP) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { var err error if h.tlsConfig != nil { conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig) @@ -80,35 +87,33 @@ func (h *HTTP) NewConnection(ctx context.Context, conn net.Conn, metadata adapte return err } } - return http.HandleConnection(ctx, conn, std_bufio.NewReader(conn), h.authenticator, h.upstreamUserHandler(metadata), adapter.UpstreamMetadata(metadata)) -} - -func (h *HTTP) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid + return http.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, nil, h.upstreamUserHandlerEx(metadata), metadata.Source, onClose) } -func (a *myInboundAdapter) upstreamUserHandler(metadata adapter.InboundContext) adapter.UpstreamHandlerAdapter { - return adapter.NewUpstreamHandler(metadata, a.newUserConnection, a.streamUserPacketConnection, a) +func (a *myInboundAdapter) upstreamUserHandlerEx(metadata adapter.InboundContext) adapter.UpstreamHandlerAdapterEx { + return adapter.NewUpstreamHandlerEx(metadata, a.newUserConnection, a.streamUserPacketConnection) } -func (a *myInboundAdapter) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (a *myInboundAdapter) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { user, loaded := auth.UserFromContext[string](ctx) if !loaded { a.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) - return a.router.RouteConnection(ctx, conn, metadata) + a.router.RouteConnectionEx(ctx, conn, metadata, onClose) + return } metadata.User = user a.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) - return a.router.RouteConnection(ctx, conn, metadata) + a.router.RouteConnectionEx(ctx, conn, metadata, onClose) } -func (a *myInboundAdapter) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (a *myInboundAdapter) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { user, loaded := auth.UserFromContext[string](ctx) if !loaded { a.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) - return a.router.RoutePacketConnection(ctx, conn, metadata) + a.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) + return } metadata.User = user a.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) - return a.router.RoutePacketConnection(ctx, conn, metadata) + a.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } diff --git a/inbound/mixed.go b/inbound/mixed.go index 3933f7af0..81f6a43a0 100644 --- a/inbound/mixed.go +++ b/inbound/mixed.go @@ -4,7 +4,6 @@ import ( std_bufio "bufio" "context" "net" - "os" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/uot" @@ -12,6 +11,7 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common/auth" + E "github.com/sagernet/sing/common/exceptions" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/protocol/http" "github.com/sagernet/sing/protocol/socks" @@ -20,8 +20,8 @@ import ( ) var ( - _ adapter.Inbound = (*Mixed)(nil) - _ adapter.InjectableInbound = (*Mixed)(nil) + _ adapter.Inbound = (*Mixed)(nil) + _ adapter.TCPInjectableInbound = (*Mixed)(nil) ) type Mixed struct { @@ -47,20 +47,24 @@ func NewMixed(ctx context.Context, router adapter.Router, logger log.ContextLogg return inbound } -func (h *Mixed) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Mixed) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := h.newConnection(ctx, conn, metadata, onClose) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } +} + +func (h *Mixed) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { reader := std_bufio.NewReader(conn) headerBytes, err := reader.Peek(1) if err != nil { - return err + return E.Cause(err, "peek first byte") } switch headerBytes[0] { case socks4.Version, socks5.Version: - return socks.HandleConnection0(ctx, conn, reader, h.authenticator, h.upstreamUserHandler(metadata), adapter.UpstreamMetadata(metadata)) + return socks.HandleConnectionEx(ctx, conn, reader, h.authenticator, nil, h.upstreamUserHandlerEx(metadata), metadata.Source, metadata.Destination, onClose) default: - return http.HandleConnection(ctx, conn, reader, h.authenticator, h.upstreamUserHandler(metadata), adapter.UpstreamMetadata(metadata)) + return http.HandleConnectionEx(ctx, conn, reader, h.authenticator, nil, h.upstreamUserHandlerEx(metadata), metadata.Source, onClose) } } - -func (h *Mixed) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid -} diff --git a/inbound/naive.go b/inbound/naive.go index 07328c09f..498e823ce 100644 --- a/inbound/naive.go +++ b/inbound/naive.go @@ -17,6 +17,7 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/transport/v2rayhttp" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/auth" "github.com/sagernet/sing/common/buf" @@ -164,13 +165,13 @@ func (n *Naive) ServeHTTP(writer http.ResponseWriter, request *http.Request) { n.badRequest(ctx, request, E.New("hijack failed")) return } - n.newConnection(ctx, &naiveH1Conn{Conn: conn}, userName, source, destination) + n.newConnection(ctx, false, &naiveH1Conn{Conn: conn}, userName, source, destination) } else { - n.newConnection(ctx, &naiveH2Conn{reader: request.Body, writer: writer, flusher: writer.(http.Flusher)}, userName, source, destination) + n.newConnection(ctx, true, &naiveH2Conn{reader: request.Body, writer: writer, flusher: writer.(http.Flusher)}, userName, source, destination) } } -func (n *Naive) newConnection(ctx context.Context, conn net.Conn, userName string, source, destination M.Socksaddr) { +func (n *Naive) newConnection(ctx context.Context, waitForClose bool, conn net.Conn, userName string, source M.Socksaddr, destination M.Socksaddr) { if userName != "" { n.logger.InfoContext(ctx, "[", userName, "] inbound connection from ", source) n.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", destination) @@ -178,19 +179,26 @@ func (n *Naive) newConnection(ctx context.Context, conn net.Conn, userName strin n.logger.InfoContext(ctx, "inbound connection from ", source) n.logger.InfoContext(ctx, "inbound connection to ", destination) } - hErr := n.router.RouteConnection(ctx, conn, n.createMetadata(conn, adapter.InboundContext{ + metadata := n.createMetadata(conn, adapter.InboundContext{ Source: source, Destination: destination, User: userName, - })) - if hErr != nil { - conn.Close() - n.NewError(ctx, E.Cause(hErr, "process connection from ", source)) + }) + if !waitForClose { + n.router.RouteConnectionEx(ctx, conn, metadata, nil) + } else { + done := make(chan struct{}) + wrapper := v2rayhttp.NewHTTP2Wrapper(conn) + n.router.RouteConnectionEx(ctx, conn, metadata, N.OnceClose(func(it error) { + close(done) + })) + <-done + wrapper.CloseWrapper() } } func (n *Naive) badRequest(ctx context.Context, request *http.Request, err error) { - n.NewError(ctx, E.Cause(err, "process connection from ", request.RemoteAddr)) + n.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", request.RemoteAddr)) } func rejectHTTP(writer http.ResponseWriter, statusCode int) { diff --git a/inbound/redirect.go b/inbound/redirect.go index 4c7cf1d58..c4c6faf34 100644 --- a/inbound/redirect.go +++ b/inbound/redirect.go @@ -9,7 +9,6 @@ import ( 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" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) @@ -34,11 +33,13 @@ func NewRedirect(ctx context.Context, router adapter.Router, logger log.ContextL return redirect } -func (r *Redirect) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (r *Redirect) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { destination, err := redir.GetOriginalDestination(conn) if err != nil { - return E.Cause(err, "get redirect destination") + conn.Close() + r.logger.ErrorContext(ctx, "process connection from ", conn.RemoteAddr(), ": get redirect destination: ", err) + return } metadata.Destination = M.SocksaddrFromNetIP(destination) - return r.newConnection(ctx, conn, metadata) + r.newConnectionEx(ctx, conn, metadata, onClose) } diff --git a/inbound/shadowsocks.go b/inbound/shadowsocks.go index ca15b8d8e..3fff231d8 100644 --- a/inbound/shadowsocks.go +++ b/inbound/shadowsocks.go @@ -3,7 +3,6 @@ package inbound import ( "context" "net" - "os" "time" "github.com/sagernet/sing-box/adapter" @@ -18,6 +17,7 @@ import ( "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" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/ntp" ) @@ -36,8 +36,8 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte } var ( - _ adapter.Inbound = (*Shadowsocks)(nil) - _ adapter.InjectableInbound = (*Shadowsocks)(nil) + _ adapter.Inbound = (*Shadowsocks)(nil) + _ adapter.TCPInjectableInbound = (*Shadowsocks)(nil) ) type Shadowsocks struct { @@ -74,11 +74,11 @@ func newShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte } switch { case options.Method == shadowsocks.MethodNone: - inbound.service = shadowsocks.NewNoneService(int64(udpTimeout.Seconds()), inbound.upstreamContextHandler()) + inbound.service = shadowsocks.NewNoneService(int64(udpTimeout.Seconds()), adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound)) case common.Contains(shadowaead.List, options.Method): - inbound.service, err = shadowaead.NewService(options.Method, nil, options.Password, int64(udpTimeout.Seconds()), inbound.upstreamContextHandler()) + inbound.service, err = shadowaead.NewService(options.Method, nil, options.Password, int64(udpTimeout.Seconds()), adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound)) case common.Contains(shadowaead_2022.List, options.Method): - inbound.service, err = shadowaead_2022.NewServiceWithPassword(options.Method, options.Password, int64(udpTimeout.Seconds()), inbound.upstreamContextHandler(), ntp.TimeFuncFromContext(ctx)) + inbound.service, err = shadowaead_2022.NewServiceWithPassword(options.Method, options.Password, int64(udpTimeout.Seconds()), adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound), ntp.TimeFuncFromContext(ctx)) default: err = E.New("unsupported method: ", options.Method) } @@ -86,14 +86,29 @@ func newShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte return inbound, err } -func (h *Shadowsocks) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata)) +func (h *Shadowsocks) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata)) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } +} + +func (h *Shadowsocks) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { + err := h.service.NewPacket(h.ctx, h.packetConn(), buffer, M.Metadata{Source: source}) + if err != nil { + h.logger.Error(E.Cause(err, "process packet from ", source)) + } } -func (h *Shadowsocks) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext) error { - return h.service.NewPacket(adapter.WithContext(ctx, &metadata), conn, buffer, adapter.UpstreamMetadata(metadata)) +func (h *Shadowsocks) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + return h.router.RouteConnection(ctx, conn, h.createMetadata(conn, metadata)) } -func (h *Shadowsocks) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid +func (h *Shadowsocks) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + ctx = log.ContextWithNewID(ctx) + h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) + h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) + return h.router.RoutePacketConnection(ctx, conn, h.createPacketMetadata(conn, metadata)) } diff --git a/inbound/shadowsocks_multi.go b/inbound/shadowsocks_multi.go index a291af4ac..295341949 100644 --- a/inbound/shadowsocks_multi.go +++ b/inbound/shadowsocks_multi.go @@ -20,13 +20,14 @@ import ( "github.com/sagernet/sing/common/buf" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" + M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/ntp" ) var ( - _ adapter.Inbound = (*ShadowsocksMulti)(nil) - _ adapter.InjectableInbound = (*ShadowsocksMulti)(nil) + _ adapter.Inbound = (*ShadowsocksMulti)(nil) + _ adapter.TCPInjectableInbound = (*ShadowsocksMulti)(nil) ) type ShadowsocksMulti struct { @@ -66,14 +67,15 @@ func newShadowsocksMulti(ctx context.Context, router adapter.Router, logger log. options.Method, options.Password, int64(udpTimeout.Seconds()), - adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound), + adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound), ntp.TimeFuncFromContext(ctx), ) } else if common.Contains(shadowaead.List, options.Method) { service, err = shadowaead.NewMultiService[int]( options.Method, int64(udpTimeout.Seconds()), - adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound)) + adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound), + ) } else { return nil, E.New("unsupported method: " + options.Method) } @@ -94,16 +96,19 @@ func newShadowsocksMulti(ctx context.Context, router adapter.Router, logger log. return inbound, err } -func (h *ShadowsocksMulti) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata)) -} - -func (h *ShadowsocksMulti) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext) error { - return h.service.NewPacket(adapter.WithContext(ctx, &metadata), conn, buffer, adapter.UpstreamMetadata(metadata)) +func (h *ShadowsocksMulti) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata)) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } } -func (h *ShadowsocksMulti) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid +func (h *ShadowsocksMulti) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { + err := h.service.NewPacket(h.ctx, h.packetConn(), buffer, M.Metadata{Source: source}) + if err != nil { + h.logger.Error(E.Cause(err, "process packet from ", source)) + } } func (h *ShadowsocksMulti) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { @@ -118,7 +123,7 @@ func (h *ShadowsocksMulti) newConnection(ctx context.Context, conn net.Conn, met metadata.User = user } h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) - return h.router.RouteConnection(ctx, conn, metadata) + return h.router.RouteConnection(ctx, conn, h.createMetadata(conn, metadata)) } func (h *ShadowsocksMulti) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { @@ -135,5 +140,5 @@ func (h *ShadowsocksMulti) newPacketConnection(ctx context.Context, conn N.Packe ctx = log.ContextWithNewID(ctx) h.logger.InfoContext(ctx, "[", user, "] inbound packet connection from ", metadata.Source) h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) - return h.router.RoutePacketConnection(ctx, conn, metadata) + return h.router.RoutePacketConnection(ctx, conn, h.createPacketMetadata(conn, metadata)) } diff --git a/inbound/shadowsocks_relay.go b/inbound/shadowsocks_relay.go index fbc2838ad..02246a3f3 100644 --- a/inbound/shadowsocks_relay.go +++ b/inbound/shadowsocks_relay.go @@ -16,13 +16,15 @@ import ( "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/auth" "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" + M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) var ( - _ adapter.Inbound = (*ShadowsocksRelay)(nil) - _ adapter.InjectableInbound = (*ShadowsocksRelay)(nil) + _ adapter.Inbound = (*ShadowsocksRelay)(nil) + _ adapter.TCPInjectableInbound = (*ShadowsocksRelay)(nil) ) type ShadowsocksRelay struct { @@ -61,7 +63,7 @@ func newShadowsocksRelay(ctx context.Context, router adapter.Router, logger log. options.Method, options.Password, int64(udpTimeout.Seconds()), - adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound), + adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound), ) if err != nil { return nil, err @@ -79,16 +81,19 @@ func newShadowsocksRelay(ctx context.Context, router adapter.Router, logger log. return inbound, err } -func (h *ShadowsocksRelay) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata)) -} - -func (h *ShadowsocksRelay) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext) error { - return h.service.NewPacket(adapter.WithContext(ctx, &metadata), conn, buffer, adapter.UpstreamMetadata(metadata)) +func (h *ShadowsocksRelay) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata)) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } } -func (h *ShadowsocksRelay) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid +func (h *ShadowsocksRelay) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { + err := h.service.NewPacket(h.ctx, h.packetConn(), buffer, M.Metadata{Source: source}) + if err != nil { + h.logger.Error(E.Cause(err, "process packet from ", source)) + } } func (h *ShadowsocksRelay) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { @@ -103,7 +108,7 @@ func (h *ShadowsocksRelay) newConnection(ctx context.Context, conn net.Conn, met metadata.User = destination } h.logger.InfoContext(ctx, "[", destination, "] inbound connection to ", metadata.Destination) - return h.router.RouteConnection(ctx, conn, metadata) + return h.router.RouteConnection(ctx, conn, h.createMetadata(conn, metadata)) } func (h *ShadowsocksRelay) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { @@ -120,5 +125,5 @@ func (h *ShadowsocksRelay) newPacketConnection(ctx context.Context, conn N.Packe ctx = log.ContextWithNewID(ctx) h.logger.InfoContext(ctx, "[", destination, "] inbound packet connection from ", metadata.Source) h.logger.InfoContext(ctx, "[", destination, "] inbound packet connection to ", metadata.Destination) - return h.router.RoutePacketConnection(ctx, conn, metadata) + return h.router.RoutePacketConnection(ctx, conn, h.createPacketMetadata(conn, metadata)) } diff --git a/inbound/shadowtls.go b/inbound/shadowtls.go index 358564f22..ca1422863 100644 --- a/inbound/shadowtls.go +++ b/inbound/shadowtls.go @@ -12,6 +12,7 @@ import ( "github.com/sagernet/sing-shadowtls" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/auth" + E "github.com/sagernet/sing/common/exceptions" N "github.com/sagernet/sing/common/network" ) @@ -91,3 +92,11 @@ func (h *ShadowTLS) newConnection(ctx context.Context, conn net.Conn, metadata a } return h.router.RouteConnection(ctx, conn, metadata) } + +func (h *ShadowTLS) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := h.NewConnection(ctx, conn, metadata) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } +} diff --git a/inbound/socks.go b/inbound/socks.go index 7c5183c9f..04b0a77dc 100644 --- a/inbound/socks.go +++ b/inbound/socks.go @@ -1,9 +1,9 @@ package inbound import ( + std_bufio "bufio" "context" "net" - "os" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/uot" @@ -11,13 +11,14 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common/auth" + E "github.com/sagernet/sing/common/exceptions" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/protocol/socks" ) var ( - _ adapter.Inbound = (*Socks)(nil) - _ adapter.InjectableInbound = (*Socks)(nil) + _ adapter.Inbound = (*Socks)(nil) + _ adapter.TCPInjectableInbound = (*Socks)(nil) ) type Socks struct { @@ -42,10 +43,10 @@ func NewSocks(ctx context.Context, router adapter.Router, logger log.ContextLogg return inbound } -func (h *Socks) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return socks.HandleConnection(ctx, conn, h.authenticator, h.upstreamUserHandler(metadata), adapter.UpstreamMetadata(metadata)) -} - -func (h *Socks) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid +func (h *Socks) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := socks.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, nil, h.upstreamUserHandlerEx(metadata), metadata.Source, metadata.Destination, onClose) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } } diff --git a/inbound/tproxy.go b/inbound/tproxy.go index a074eb495..40653c797 100644 --- a/inbound/tproxy.go +++ b/inbound/tproxy.go @@ -18,12 +18,12 @@ import ( E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/udpnat" + "github.com/sagernet/sing/common/udpnat2" ) type TProxy struct { myInboundAdapter - udpNat *udpnat.Service[netip.AddrPort] + udpNat *udpnat.Service } func NewTProxy(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TProxyInboundOptions) *TProxy { @@ -46,8 +46,7 @@ func NewTProxy(ctx context.Context, router adapter.Router, logger log.ContextLog } tproxy.connHandler = tproxy tproxy.oobPacketHandler = tproxy - tproxy.udpNat = udpnat.New[netip.AddrPort](int64(udpTimeout.Seconds()), tproxy.upstreamContextHandler()) - tproxy.packetUpstream = tproxy.udpNat + tproxy.udpNat = udpnat.New(tproxy, tproxy.preparePacketConnection, udpTimeout, false) return tproxy } @@ -75,35 +74,43 @@ func (t *TProxy) Start() error { return nil } -func (t *TProxy) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (t *TProxy) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap() - return t.newConnection(ctx, conn, metadata) + t.newConnectionEx(ctx, conn, metadata, onClose) } -func (t *TProxy) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, oob []byte, metadata adapter.InboundContext) error { +func (t *TProxy) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + t.newPacketConnectionEx(ctx, conn, t.createPacketMetadataEx(source, destination), onClose) +} + +func (t *TProxy) NewPacketEx(buffer *buf.Buffer, oob []byte, source M.Socksaddr) { destination, err := redir.GetOriginalDestinationFromOOB(oob) if err != nil { - return E.Cause(err, "get tproxy destination") + t.logger.Warn("process packet from ", source, ": get tproxy destination: ", err) + return } - metadata.Destination = M.SocksaddrFromNetIP(destination).Unwrap() - t.udpNat.NewContextPacket(ctx, metadata.Source.AddrPort(), buffer, adapter.UpstreamMetadata(metadata), func(natConn N.PacketConn) (context.Context, N.PacketWriter) { - return adapter.WithContext(log.ContextWithNewID(ctx), &metadata), &tproxyPacketWriter{ctx: ctx, source: natConn, destination: metadata.Destination} - }) - return nil + t.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, M.SocksaddrFromNetIP(destination), nil) } type tproxyPacketWriter struct { ctx context.Context - source N.PacketConn + source netip.AddrPort destination M.Socksaddr conn *net.UDPConn } +func (t *TProxy) preparePacketConnection(source M.Socksaddr, destination M.Socksaddr, userData any) (bool, context.Context, N.PacketWriter, N.CloseHandlerFunc) { + writer := &tproxyPacketWriter{ctx: t.ctx, source: source.AddrPort(), destination: destination} + return true, t.ctx, writer, func(it error) { + common.Close(common.PtrOrNil(writer.conn)) + } +} + func (w *tproxyPacketWriter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { defer buffer.Release() conn := w.conn if w.destination == destination && conn != nil { - _, err := conn.WriteToUDPAddrPort(buffer.Bytes(), M.AddrPortFromNet(w.source.LocalAddr())) + _, err := conn.WriteToUDPAddrPort(buffer.Bytes(), w.source) if err != nil { w.conn = nil } @@ -122,9 +129,5 @@ func (w *tproxyPacketWriter) WritePacket(buffer *buf.Buffer, destination M.Socks } else { defer udpConn.Close() } - return common.Error(udpConn.WriteToUDPAddrPort(buffer.Bytes(), M.AddrPortFromNet(w.source.LocalAddr()))) -} - -func (w *tproxyPacketWriter) Close() error { - return common.Close(common.PtrOrNil(w.conn)) + return common.Error(udpConn.WriteToUDPAddrPort(buffer.Bytes(), w.source)) } diff --git a/inbound/trojan.go b/inbound/trojan.go index 203b6d392..ce003dda7 100644 --- a/inbound/trojan.go +++ b/inbound/trojan.go @@ -22,8 +22,8 @@ import ( ) var ( - _ adapter.Inbound = (*Trojan)(nil) - _ adapter.InjectableInbound = (*Trojan)(nil) + _ adapter.Inbound = (*Trojan)(nil) + _ adapter.TCPInjectableInbound = (*Trojan)(nil) ) type Trojan struct { @@ -90,7 +90,7 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog return nil, err } if options.Transport != nil { - inbound.transport, err = v2ray.NewServerTransport(ctx, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*trojanTransportHandler)(inbound)) + inbound.transport, err = v2ray.NewServerTransport(ctx, logger, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*trojanTransportHandler)(inbound)) if err != nil { return nil, E.Cause(err, "create server transport: ", options.Transport.Type) } @@ -149,11 +149,6 @@ func (h *Trojan) Close() error { ) } -func (h *Trojan) newTransportConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - h.injectTCP(conn, metadata) - return nil -} - func (h *Trojan) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { var err error if h.tlsConfig != nil && h.transport == nil { @@ -165,8 +160,12 @@ func (h *Trojan) NewConnection(ctx context.Context, conn net.Conn, metadata adap return h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, adapter.UpstreamMetadata(metadata)) } -func (h *Trojan) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid +func (h *Trojan) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := h.NewConnection(ctx, conn, metadata) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } } func (h *Trojan) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { @@ -226,9 +225,6 @@ var _ adapter.V2RayServerTransportHandler = (*trojanTransportHandler)(nil) type trojanTransportHandler Trojan -func (t *trojanTransportHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { - return (*Trojan)(t).newTransportConnection(ctx, conn, adapter.InboundContext{ - Source: metadata.Source, - Destination: metadata.Destination, - }) +func (t *trojanTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + (*Trojan)(t).routeTCP(ctx, conn, source, destination, onClose) } diff --git a/inbound/tun.go b/inbound/tun.go index 721112d77..0d856419e 100644 --- a/inbound/tun.go +++ b/inbound/tun.go @@ -28,17 +28,18 @@ import ( "go4.org/netipx" ) -var _ adapter.Inbound = (*Tun)(nil) +var _ adapter.Inbound = (*TUN)(nil) -type Tun struct { - tag string - ctx context.Context - router adapter.Router - logger log.ContextLogger +type TUN struct { + tag string + ctx context.Context + router adapter.Router + logger log.ContextLogger + // Deprecated inboundOptions option.InboundOptions tunOptions tun.Options endpointIndependentNat bool - udpTimeout int64 + udpTimeout time.Duration stack string tunIf tun.Tun tunStack tun.Stack @@ -53,7 +54,7 @@ type Tun struct { routeExcludeAddressSet []*netipx.IPSet } -func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions, platformInterface platform.Interface) (*Tun, error) { +func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions, platformInterface platform.Interface) (*TUN, error) { address := options.Address var deprecatedAddressUsed bool //nolint:staticcheck @@ -162,7 +163,7 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger outputMark = tun.DefaultAutoRedirectOutputMark } - inbound := &Tun{ + inbound := &TUN{ tag: tag, ctx: ctx, router: router, @@ -194,7 +195,7 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger InterfaceMonitor: router.InterfaceMonitor(), }, endpointIndependentNat: options.EndpointIndependentNat, - udpTimeout: int64(udpTimeout.Seconds()), + udpTimeout: udpTimeout, stack: options.Stack, platformInterface: platformInterface, platformOptions: common.PtrValueOrDefault(options.Platform), @@ -207,7 +208,7 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger inbound.autoRedirect, err = tun.NewAutoRedirect(tun.AutoRedirectOptions{ TunOptions: &inbound.tunOptions, Context: ctx, - Handler: inbound, + Handler: (*autoRedirectHandler)(inbound), Logger: logger, NetworkMonitor: router.NetworkMonitor(), InterfaceFinder: router.InterfaceFinder(), @@ -283,17 +284,17 @@ func parseRange(uidRanges []ranges.Range[uint32], rangeList []string) ([]ranges. return uidRanges, nil } -func (t *Tun) Type() string { +func (t *TUN) Type() string { return C.TypeTun } -func (t *Tun) Tag() string { +func (t *TUN) Tag() string { return t.tag } -func (t *Tun) Start() error { +func (t *TUN) Start() error { if C.IsAndroid && t.platformInterface == nil { - t.tunOptions.BuildAndroidRules(t.router.PackageManager(), t) + t.tunOptions.BuildAndroidRules(t.router.PackageManager()) } if t.tunOptions.Name == "" { t.tunOptions.Name = tun.CalculateInterfaceName("") @@ -327,7 +328,6 @@ func (t *Tun) Start() error { Context: t.ctx, Tun: tunInterface, TunOptions: t.tunOptions, - EndpointIndependentNat: t.endpointIndependentNat, UDPTimeout: t.udpTimeout, Handler: t, Logger: t.logger, @@ -349,7 +349,7 @@ func (t *Tun) Start() error { return nil } -func (t *Tun) PostStart() error { +func (t *TUN) PostStart() error { monitor := taskmonitor.New(t.logger, C.StartTimeout) if t.autoRedirect != nil { t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet) @@ -388,7 +388,7 @@ func (t *Tun) PostStart() error { return nil } -func (t *Tun) updateRouteAddressSet(it adapter.RuleSet) { +func (t *TUN) updateRouteAddressSet(it adapter.RuleSet) { t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet) t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet) t.autoRedirect.UpdateRouteAddressSet() @@ -396,7 +396,7 @@ func (t *Tun) updateRouteAddressSet(it adapter.RuleSet) { t.routeExcludeAddressSet = nil } -func (t *Tun) Close() error { +func (t *TUN) Close() error { return common.Close( t.tunStack, t.tunIf, @@ -404,44 +404,48 @@ func (t *Tun) Close() error { ) } -func (t *Tun) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata M.Metadata) error { +func (t *TUN) PrepareConnection(source M.Socksaddr, destination M.Socksaddr) error { + // TODO: implement rejects + return nil +} + +func (t *TUN) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { ctx = log.ContextWithNewID(ctx) var metadata adapter.InboundContext metadata.Inbound = t.tag metadata.InboundType = C.TypeTun - metadata.Source = upstreamMetadata.Source - metadata.Destination = upstreamMetadata.Destination + metadata.Source = source + metadata.Destination = destination metadata.InboundOptions = t.inboundOptions - if upstreamMetadata.Protocol != "" { - t.logger.InfoContext(ctx, "inbound ", upstreamMetadata.Protocol, " connection from ", metadata.Source) - } else { - t.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) - } + t.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) - err := t.router.RouteConnection(ctx, conn, metadata) - if err != nil { - t.NewError(ctx, err) - } - return nil + t.router.RouteConnectionEx(ctx, conn, metadata, onClose) } -func (t *Tun) NewPacketConnection(ctx context.Context, conn N.PacketConn, upstreamMetadata M.Metadata) error { +func (t *TUN) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { ctx = log.ContextWithNewID(ctx) var metadata adapter.InboundContext metadata.Inbound = t.tag metadata.InboundType = C.TypeTun - metadata.Source = upstreamMetadata.Source - metadata.Destination = upstreamMetadata.Destination + metadata.Source = source + metadata.Destination = destination metadata.InboundOptions = t.inboundOptions t.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) t.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) - err := t.router.RoutePacketConnection(ctx, conn, metadata) - if err != nil { - t.NewError(ctx, err) - } - return nil + t.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } -func (t *Tun) NewError(ctx context.Context, err error) { - NewError(t.logger, ctx, err) +type autoRedirectHandler TUN + +func (t *autoRedirectHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + ctx = log.ContextWithNewID(ctx) + var metadata adapter.InboundContext + metadata.Inbound = t.tag + metadata.InboundType = C.TypeTun + metadata.Source = source + metadata.Destination = destination + metadata.InboundOptions = t.inboundOptions + t.logger.InfoContext(ctx, "inbound redirect connection from ", metadata.Source) + t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + t.router.RouteConnectionEx(ctx, conn, metadata, onClose) } diff --git a/inbound/vless.go b/inbound/vless.go index 567475647..ec26bd885 100644 --- a/inbound/vless.go +++ b/inbound/vless.go @@ -25,8 +25,8 @@ import ( ) var ( - _ adapter.Inbound = (*VLESS)(nil) - _ adapter.InjectableInbound = (*VLESS)(nil) + _ adapter.Inbound = (*VLESS)(nil) + _ adapter.TCPInjectableInbound = (*VLESS)(nil) ) type VLESS struct { @@ -73,7 +73,7 @@ func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogg } } if options.Transport != nil { - inbound.transport, err = v2ray.NewServerTransport(ctx, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*vlessTransportHandler)(inbound)) + inbound.transport, err = v2ray.NewServerTransport(ctx, logger, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*vlessTransportHandler)(inbound)) if err != nil { return nil, E.Cause(err, "create server transport: ", options.Transport.Type) } @@ -128,11 +128,6 @@ func (h *VLESS) Close() error { ) } -func (h *VLESS) newTransportConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - h.injectTCP(conn, metadata) - return nil -} - func (h *VLESS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { var err error if h.tlsConfig != nil && h.transport == nil { @@ -144,8 +139,12 @@ func (h *VLESS) NewConnection(ctx context.Context, conn net.Conn, metadata adapt return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata)) } -func (h *VLESS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid +func (h *VLESS) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := h.NewConnection(ctx, conn, metadata) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } } func (h *VLESS) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { @@ -188,9 +187,6 @@ var _ adapter.V2RayServerTransportHandler = (*vlessTransportHandler)(nil) type vlessTransportHandler VLESS -func (t *vlessTransportHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { - return (*VLESS)(t).newTransportConnection(ctx, conn, adapter.InboundContext{ - Source: metadata.Source, - Destination: metadata.Destination, - }) +func (t *vlessTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + t.routeTCP(ctx, conn, source, destination, onClose) } diff --git a/inbound/vmess.go b/inbound/vmess.go index 154512751..9099bd629 100644 --- a/inbound/vmess.go +++ b/inbound/vmess.go @@ -25,8 +25,8 @@ import ( ) var ( - _ adapter.Inbound = (*VMess)(nil) - _ adapter.InjectableInbound = (*VMess)(nil) + _ adapter.Inbound = (*VMess)(nil) + _ adapter.TCPInjectableInbound = (*VMess)(nil) ) type VMess struct { @@ -83,7 +83,7 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg } } if options.Transport != nil { - inbound.transport, err = v2ray.NewServerTransport(ctx, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*vmessTransportHandler)(inbound)) + inbound.transport, err = v2ray.NewServerTransport(ctx, logger, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*vmessTransportHandler)(inbound)) if err != nil { return nil, E.Cause(err, "create server transport: ", options.Transport.Type) } @@ -142,11 +142,6 @@ func (h *VMess) Close() error { ) } -func (h *VMess) newTransportConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - h.injectTCP(conn, metadata) - return nil -} - func (h *VMess) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { var err error if h.tlsConfig != nil && h.transport == nil { @@ -158,8 +153,12 @@ func (h *VMess) NewConnection(ctx context.Context, conn net.Conn, metadata adapt return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata)) } -func (h *VMess) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid +func (h *VMess) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := h.NewConnection(ctx, conn, metadata) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } } func (h *VMess) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { @@ -202,9 +201,6 @@ var _ adapter.V2RayServerTransportHandler = (*vmessTransportHandler)(nil) type vmessTransportHandler VMess -func (t *vmessTransportHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { - return (*VMess)(t).newTransportConnection(ctx, conn, adapter.InboundContext{ - Source: metadata.Source, - Destination: metadata.Destination, - }) +func (t *vmessTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + (*VMess)(t).routeTCP(ctx, conn, source, destination, onClose) } diff --git a/include/quic_stub.go b/include/quic_stub.go index ddf9723f9..43aa58d99 100644 --- a/include/quic_stub.go +++ b/include/quic_stub.go @@ -11,6 +11,7 @@ import ( "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/transport/v2ray" "github.com/sagernet/sing-dns" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) @@ -20,7 +21,7 @@ func init() { return nil, C.ErrQUICNotIncluded }) v2ray.RegisterQUICConstructor( - func(ctx context.Context, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { + func(ctx context.Context, logger logger.ContextLogger, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { return nil, C.ErrQUICNotIncluded }, func(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayQUICOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) { diff --git a/option/inbound.go b/option/inbound.go index 54e8bab84..d38799048 100644 --- a/option/inbound.go +++ b/option/inbound.go @@ -98,6 +98,7 @@ func (h *Inbound) UnmarshalJSON(bytes []byte) error { return nil } +// Deprecated: Use rule action instead type InboundOptions struct { SniffEnabled bool `json:"sniff,omitempty"` SniffOverrideDestination bool `json:"sniff_override_destination,omitempty"` diff --git a/option/rule.go b/option/rule.go index 0c66ab1d5..0b11cbdda 100644 --- a/option/rule.go +++ b/option/rule.go @@ -64,7 +64,7 @@ func (r Rule) IsValid() bool { } } -type DefaultRule struct { +type RawDefaultRule struct { Inbound Listable[string] `json:"inbound,omitempty"` IPVersion int `json:"ip_version,omitempty"` Network Listable[string] `json:"network,omitempty"` @@ -98,26 +98,58 @@ type DefaultRule struct { RuleSet Listable[string] `json:"rule_set,omitempty"` RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` Invert bool `json:"invert,omitempty"` - Outbound string `json:"outbound,omitempty"` // Deprecated: renamed to rule_set_ip_cidr_match_source Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"` } +type DefaultRule struct { + RawDefaultRule + RuleAction +} + +func (r *DefaultRule) MarshalJSON() ([]byte, error) { + return MarshallObjects(r.RawDefaultRule, r.RuleAction) +} + +func (r *DefaultRule) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, &r.RawDefaultRule) + if err != nil { + return err + } + return UnmarshallExcluded(data, &r.RawDefaultRule, &r.RuleAction) +} + func (r *DefaultRule) IsValid() bool { var defaultValue DefaultRule defaultValue.Invert = r.Invert - defaultValue.Outbound = r.Outbound + defaultValue.Action = r.Action return !reflect.DeepEqual(r, defaultValue) } +type _LogicalRule struct { + Mode string `json:"mode"` + Rules []Rule `json:"rules,omitempty"` + Invert bool `json:"invert,omitempty"` +} + type LogicalRule struct { - Mode string `json:"mode"` - Rules []Rule `json:"rules,omitempty"` - Invert bool `json:"invert,omitempty"` - Outbound string `json:"outbound,omitempty"` + _LogicalRule + RuleAction +} + +func (r *LogicalRule) MarshalJSON() ([]byte, error) { + return MarshallObjects(r._LogicalRule, r.RuleAction) +} + +func (r *LogicalRule) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, &r._LogicalRule) + if err != nil { + return err + } + return UnmarshallExcluded(data, &r._LogicalRule, &r.RuleAction) } -func (r LogicalRule) IsValid() bool { +func (r *LogicalRule) IsValid() bool { return len(r.Rules) > 0 && common.All(r.Rules, Rule.IsValid) } diff --git a/option/rule_action.go b/option/rule_action.go new file mode 100644 index 000000000..4f0ec177b --- /dev/null +++ b/option/rule_action.go @@ -0,0 +1,166 @@ +package option + +import ( + C "github.com/sagernet/sing-box/constant" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json" +) + +type _RuleAction struct { + Action string `json:"action,omitempty"` + RouteOptions RouteActionOptions `json:"-"` + RejectOptions RejectActionOptions `json:"-"` + SniffOptions RouteActionSniff `json:"-"` + ResolveOptions RouteActionResolve `json:"-"` +} + +type RuleAction _RuleAction + +func (r RuleAction) MarshalJSON() ([]byte, error) { + var v any + switch r.Action { + case C.RuleActionTypeRoute: + r.Action = "" + v = r.RouteOptions + case C.RuleActionTypeReturn: + v = nil + case C.RuleActionTypeReject: + v = r.RejectOptions + case C.RuleActionTypeHijackDNS: + v = nil + case C.RuleActionTypeSniff: + v = r.SniffOptions + case C.RuleActionTypeResolve: + v = r.ResolveOptions + default: + return nil, E.New("unknown rule action: " + r.Action) + } + if v == nil { + return MarshallObjects((_RuleAction)(r)) + } + return MarshallObjects((_RuleAction)(r), v) +} + +func (r *RuleAction) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, (*_RuleAction)(r)) + if err != nil { + return err + } + var v any + switch r.Action { + case "", C.RuleActionTypeRoute: + r.Action = C.RuleActionTypeRoute + v = &r.RouteOptions + case C.RuleActionTypeReturn: + v = nil + case C.RuleActionTypeReject: + v = &r.RejectOptions + case C.RuleActionTypeHijackDNS: + v = nil + case C.RuleActionTypeSniff: + v = &r.SniffOptions + case C.RuleActionTypeResolve: + v = &r.ResolveOptions + default: + return E.New("unknown rule action: " + r.Action) + } + if v == nil { + // check unknown fields + return json.UnmarshalDisallowUnknownFields(data, &_RuleAction{}) + } + return UnmarshallExcluded(data, (*_RuleAction)(r), v) +} + +type _DNSRuleAction struct { + Action string `json:"action,omitempty"` + RouteOptions DNSRouteActionOptions `json:"-"` + RejectOptions RejectActionOptions `json:"-"` + SniffOptions RouteActionSniff `json:"-"` + ResolveOptions RouteActionResolve `json:"-"` +} + +type DNSRuleAction _DNSRuleAction + +func (r DNSRuleAction) MarshalJSON() ([]byte, error) { + var v any + switch r.Action { + case C.RuleActionTypeRoute: + r.Action = "" + v = r.RouteOptions + case C.RuleActionTypeReturn: + v = nil + case C.RuleActionTypeReject: + v = r.RejectOptions + default: + return nil, E.New("unknown DNS rule action: " + r.Action) + } + if v == nil { + return MarshallObjects((_DNSRuleAction)(r)) + } + return MarshallObjects((_DNSRuleAction)(r), v) +} + +func (r *DNSRuleAction) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, (*_DNSRuleAction)(r)) + if err != nil { + return err + } + var v any + switch r.Action { + case "", C.RuleActionTypeRoute: + r.Action = C.RuleActionTypeRoute + v = &r.RouteOptions + case C.RuleActionTypeReturn: + v = nil + case C.RuleActionTypeReject: + v = &r.RejectOptions + default: + return E.New("unknown DNS rule action: " + r.Action) + } + if v == nil { + // check unknown fields + return json.UnmarshalDisallowUnknownFields(data, &_DNSRuleAction{}) + } + return UnmarshallExcluded(data, (*_DNSRuleAction)(r), v) +} + +type RouteActionOptions struct { + Outbound string `json:"outbound"` + UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` +} + +type DNSRouteActionOptions struct { + Server string `json:"server"` + DisableCache bool `json:"disable_cache,omitempty"` + RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` + ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` +} + +type RejectActionOptions struct { + Method RejectMethod `json:"method,omitempty"` +} + +type RejectMethod string + +func (m *RejectMethod) UnmarshalJSON(bytes []byte) error { + err := json.Unmarshal(bytes, (*string)(m)) + if err != nil { + return err + } + switch *m { + case C.RuleActionRejectMethodDefault, C.RuleActionRejectMethodPortUnreachable, C.RuleActionRejectMethodDrop: + return nil + default: + return E.New("unknown reject method: " + *m) + } +} + +type RouteActionSniff struct { + Sniffer Listable[string] `json:"sniffer,omitempty"` + Timeout Duration `json:"timeout,omitempty"` +} + +type RouteActionResolve struct { + Strategy DomainStrategy `json:"strategy,omitempty"` + Server string `json:"server,omitempty"` +} diff --git a/option/rule_dns.go b/option/rule_dns.go index 2e52d6c56..b328c45c0 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -64,7 +64,7 @@ func (r DNSRule) IsValid() bool { } } -type DefaultDNSRule struct { +type RawDefaultDNSRule struct { Inbound Listable[string] `json:"inbound,omitempty"` IPVersion int `json:"ip_version,omitempty"` QueryType Listable[DNSQueryType] `json:"query_type,omitempty"` @@ -100,35 +100,58 @@ type DefaultDNSRule struct { RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty"` Invert bool `json:"invert,omitempty"` - Server string `json:"server,omitempty"` - DisableCache bool `json:"disable_cache,omitempty"` - RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` - ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` // Deprecated: renamed to rule_set_ip_cidr_match_source Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"` } +type DefaultDNSRule struct { + RawDefaultDNSRule + DNSRuleAction +} + +func (r *DefaultDNSRule) MarshalJSON() ([]byte, error) { + return MarshallObjects(r.RawDefaultDNSRule, r.DNSRuleAction) +} + +func (r *DefaultDNSRule) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, &r.RawDefaultDNSRule) + if err != nil { + return err + } + return UnmarshallExcluded(data, &r.RawDefaultDNSRule, &r.DNSRuleAction) +} + func (r *DefaultDNSRule) IsValid() bool { var defaultValue DefaultDNSRule defaultValue.Invert = r.Invert - defaultValue.Server = r.Server - defaultValue.DisableCache = r.DisableCache - defaultValue.RewriteTTL = r.RewriteTTL - defaultValue.ClientSubnet = r.ClientSubnet + defaultValue.DNSRuleAction = r.DNSRuleAction return !reflect.DeepEqual(r, defaultValue) } +type _LogicalDNSRule struct { + Mode string `json:"mode"` + Rules []DNSRule `json:"rules,omitempty"` + Invert bool `json:"invert,omitempty"` +} + type LogicalDNSRule struct { - Mode string `json:"mode"` - Rules []DNSRule `json:"rules,omitempty"` - Invert bool `json:"invert,omitempty"` - Server string `json:"server,omitempty"` - DisableCache bool `json:"disable_cache,omitempty"` - RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` - ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` + _LogicalDNSRule + DNSRuleAction +} + +func (r *LogicalDNSRule) MarshalJSON() ([]byte, error) { + return MarshallObjects(r._LogicalDNSRule, r.DNSRuleAction) +} + +func (r *LogicalDNSRule) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, &r._LogicalDNSRule) + if err != nil { + return err + } + return UnmarshallExcluded(data, &r._LogicalDNSRule, &r.DNSRuleAction) } -func (r LogicalDNSRule) IsValid() bool { +func (r *LogicalDNSRule) IsValid() bool { return len(r.Rules) > 0 && common.All(r.Rules, DNSRule.IsValid) } diff --git a/option/types.go b/option/types.go index b17f22224..bb6485491 100644 --- a/option/types.go +++ b/option/types.go @@ -81,8 +81,11 @@ func (a *AddrPrefix) UnmarshalJSON(content []byte) error { return prefixErr } -func (a AddrPrefix) Build() netip.Prefix { - return netip.Prefix(a) +func (a *AddrPrefix) Build() netip.Prefix { + if a == nil { + return netip.Prefix{} + } + return netip.Prefix(*a) } type NetworkList string @@ -143,12 +146,29 @@ func (l *Listable[T]) UnmarshalJSON(content []byte) error { type DomainStrategy dns.DomainStrategy +func (s DomainStrategy) String() string { + switch dns.DomainStrategy(s) { + case dns.DomainStrategyAsIS: + return "" + case dns.DomainStrategyPreferIPv4: + return "prefer_ipv4" + case dns.DomainStrategyPreferIPv6: + return "prefer_ipv6" + case dns.DomainStrategyUseIPv4: + return "ipv4_only" + case dns.DomainStrategyUseIPv6: + return "ipv6_only" + default: + panic(E.New("unknown domain strategy: ", s)) + } +} + func (s DomainStrategy) MarshalJSON() ([]byte, error) { var value string switch dns.DomainStrategy(s) { case dns.DomainStrategyAsIS: value = "" - // value = "AsIS" + // value = "as_is" case dns.DomainStrategyPreferIPv4: value = "prefer_ipv4" case dns.DomainStrategyPreferIPv6: diff --git a/outbound/block.go b/outbound/block.go index e73c44228..b6ccefe2b 100644 --- a/outbound/block.go +++ b/outbound/block.go @@ -39,12 +39,14 @@ func (h *Block) ListenPacket(ctx context.Context, destination M.Socksaddr) (net. return nil, io.EOF } +// Deprecated func (h *Block) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { conn.Close() h.logger.InfoContext(ctx, "blocked connection to ", metadata.Destination) return nil } +// Deprecated func (h *Block) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { conn.Close() h.logger.InfoContext(ctx, "blocked packet connection to ", metadata.Destination) diff --git a/outbound/default.go b/outbound/default.go index 972aca947..a34ac97af 100644 --- a/outbound/default.go +++ b/outbound/default.go @@ -69,7 +69,7 @@ func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata a if err != nil { return N.ReportHandshakeFailure(conn, err) } - err = N.ReportHandshakeSuccess(conn) + err = N.ReportConnHandshakeSuccess(conn, outConn) if err != nil { outConn.Close() return err @@ -96,7 +96,7 @@ func NewDirectConnection(ctx context.Context, router adapter.Router, this N.Dial if err != nil { return N.ReportHandshakeFailure(conn, err) } - err = N.ReportHandshakeSuccess(conn) + err = N.ReportConnHandshakeSuccess(conn, outConn) if err != nil { outConn.Close() return err @@ -117,14 +117,14 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, if err != nil { return N.ReportHandshakeFailure(conn, err) } - err = N.ReportHandshakeSuccess(conn) + err = N.ReportPacketConnHandshakeSuccess(conn, outConn) if err != nil { outConn.Close() return err } if destinationAddress.IsValid() { if metadata.Destination.IsFqdn() { - if metadata.InboundOptions.UDPDisableDomainUnmapping { + if metadata.UDPDisableDomainUnmapping { outConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination) } else { outConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination) @@ -165,7 +165,7 @@ func NewDirectPacketConnection(ctx context.Context, router adapter.Router, this if err != nil { return N.ReportHandshakeFailure(conn, err) } - err = N.ReportHandshakeSuccess(conn) + err = N.ReportPacketConnHandshakeSuccess(conn, outConn) if err != nil { outConn.Close() return err diff --git a/outbound/direct.go b/outbound/direct.go index c873941c1..415a72f37 100644 --- a/outbound/direct.go +++ b/outbound/direct.go @@ -30,7 +30,7 @@ type Direct struct { fallbackDelay time.Duration overrideOption int overrideDestination M.Socksaddr - loopBack *loopBackDetector + // loopBack *loopBackDetector } func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, options option.DirectOutboundOptions) (*Direct, error) { @@ -51,7 +51,7 @@ func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, opti domainStrategy: dns.DomainStrategy(options.DomainStrategy), fallbackDelay: time.Duration(options.FallbackDelay), dialer: outboundDialer, - loopBack: newLoopBackDetector(router), + // loopBack: newLoopBackDetector(router), } if options.ProxyProtocol != 0 { return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0") @@ -90,11 +90,12 @@ func (h *Direct) DialContext(ctx context.Context, network string, destination M. case N.NetworkUDP: h.logger.InfoContext(ctx, "outbound packet connection to ", destination) } - conn, err := h.dialer.DialContext(ctx, network, destination) + /*conn, err := h.dialer.DialContext(ctx, network, destination) if err != nil { return nil, err } - return h.loopBack.NewConn(conn), nil + return h.loopBack.NewConn(conn), nil*/ + return h.dialer.DialContext(ctx, network, destination) } func (h *Direct) DialParallel(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr) (net.Conn, error) { @@ -148,14 +149,14 @@ func (h *Direct) ListenPacket(ctx context.Context, destination M.Socksaddr) (net if err != nil { return nil, err } - conn = h.loopBack.NewPacketConn(bufio.NewPacketConn(conn), destination) + // conn = h.loopBack.NewPacketConn(bufio.NewPacketConn(conn), destination) if originDestination != destination { conn = bufio.NewNATPacketConn(bufio.NewPacketConn(conn), destination, originDestination) } return conn, nil } -func (h *Direct) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +/*func (h *Direct) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { if h.loopBack.CheckConn(metadata.Source.AddrPort(), M.AddrPortFromNet(conn.LocalAddr())) { return E.New("reject loopback connection to ", metadata.Destination) } @@ -168,3 +169,4 @@ func (h *Direct) NewPacketConnection(ctx context.Context, conn N.PacketConn, met } return NewPacketConnection(ctx, h, conn, metadata) } +*/ diff --git a/outbound/dns.go b/outbound/dns.go index 6ad4a0f8c..08661a99a 100644 --- a/outbound/dns.go +++ b/outbound/dns.go @@ -45,6 +45,7 @@ func (d *DNS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.Pa return nil, os.ErrInvalid } +// Deprecated func (d *DNS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { metadata.Destination = M.Socksaddr{} defer conn.Close() @@ -97,6 +98,7 @@ func (d *DNS) handleConnection(ctx context.Context, conn net.Conn, metadata adap return nil } +// Deprecated func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { metadata.Destination = M.Socksaddr{} var reader N.PacketReader = conn diff --git a/outbound/http.go b/outbound/http.go index cbcbce075..6f15afb5d 100644 --- a/outbound/http.go +++ b/outbound/http.go @@ -64,11 +64,3 @@ func (h *HTTP) DialContext(ctx context.Context, network string, destination M.So func (h *HTTP) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { return nil, os.ErrInvalid } - -func (h *HTTP) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, h, conn, metadata) -} - -func (h *HTTP) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid -} diff --git a/outbound/hysteria.go b/outbound/hysteria.go index cf7f7fed1..f513cf64e 100644 --- a/outbound/hysteria.go +++ b/outbound/hysteria.go @@ -122,14 +122,6 @@ func (h *Hysteria) ListenPacket(ctx context.Context, destination M.Socksaddr) (n return h.client.ListenPacket(ctx, destination) } -func (h *Hysteria) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, h, conn, metadata) -} - -func (h *Hysteria) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return NewPacketConnection(ctx, h, conn, metadata) -} - func (h *Hysteria) InterfaceUpdated() { h.client.CloseWithError(E.New("network changed")) } diff --git a/outbound/hysteria2.go b/outbound/hysteria2.go index 9079e4030..5e46f6a86 100644 --- a/outbound/hysteria2.go +++ b/outbound/hysteria2.go @@ -108,14 +108,6 @@ func (h *Hysteria2) ListenPacket(ctx context.Context, destination M.Socksaddr) ( return h.client.ListenPacket(ctx) } -func (h *Hysteria2) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, h, conn, metadata) -} - -func (h *Hysteria2) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return NewPacketConnection(ctx, h, conn, metadata) -} - func (h *Hysteria2) InterfaceUpdated() { h.client.CloseWithError(E.New("network changed")) } diff --git a/outbound/proxy.go b/outbound/proxy.go index fbc484818..38c184539 100644 --- a/outbound/proxy.go +++ b/outbound/proxy.go @@ -94,6 +94,9 @@ func (l *ProxyListener) acceptLoop() { } } +// TODO: migrate to new api +// +//nolint:staticcheck func (l *ProxyListener) accept(ctx context.Context, conn *net.TCPConn) error { return socks.HandleConnection(ctx, conn, l.authenticator, l, M.Metadata{}) } diff --git a/outbound/selector.go b/outbound/selector.go index e801daead..59e940dfa 100644 --- a/outbound/selector.go +++ b/outbound/selector.go @@ -142,14 +142,26 @@ func (s *Selector) ListenPacket(ctx context.Context, destination M.Socksaddr) (n return s.interruptGroup.NewPacketConn(conn, interrupt.IsExternalConnectionFromContext(ctx)), nil } +// TODO +// Deprecated func (s *Selector) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { ctx = interrupt.ContextWithIsExternalConnection(ctx) - return s.selected.NewConnection(ctx, conn, metadata) + if legacyHandler, ok := s.selected.(adapter.ConnectionHandler); ok { + return legacyHandler.NewConnection(ctx, conn, metadata) + } else { + return NewConnection(ctx, s.selected, conn, metadata) + } } +// TODO +// Deprecated func (s *Selector) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { ctx = interrupt.ContextWithIsExternalConnection(ctx) - return s.selected.NewPacketConnection(ctx, conn, metadata) + if legacyHandler, ok := s.selected.(adapter.PacketConnectionHandler); ok { + return legacyHandler.NewPacketConnection(ctx, conn, metadata) + } else { + return NewPacketConnection(ctx, s.selected, conn, metadata) + } } func RealTag(detour adapter.Outbound) string { diff --git a/outbound/shadowsocks.go b/outbound/shadowsocks.go index 9f8c1cbdb..153542742 100644 --- a/outbound/shadowsocks.go +++ b/outbound/shadowsocks.go @@ -125,14 +125,6 @@ func (h *Shadowsocks) ListenPacket(ctx context.Context, destination M.Socksaddr) } } -func (h *Shadowsocks) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, h, conn, metadata) -} - -func (h *Shadowsocks) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return NewPacketConnection(ctx, h, conn, metadata) -} - func (h *Shadowsocks) InterfaceUpdated() { if h.multiplexDialer != nil { h.multiplexDialer.Reset() diff --git a/outbound/shadowtls.go b/outbound/shadowtls.go index 38de8c0ba..ff1b9d6c4 100644 --- a/outbound/shadowtls.go +++ b/outbound/shadowtls.go @@ -106,11 +106,3 @@ func (h *ShadowTLS) DialContext(ctx context.Context, network string, destination func (h *ShadowTLS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { return nil, os.ErrInvalid } - -func (h *ShadowTLS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, h, conn, metadata) -} - -func (h *ShadowTLS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid -} diff --git a/outbound/socks.go b/outbound/socks.go index f4757467c..575d6eb3d 100644 --- a/outbound/socks.go +++ b/outbound/socks.go @@ -113,6 +113,8 @@ func (h *Socks) ListenPacket(ctx context.Context, destination M.Socksaddr) (net. return h.client.ListenPacket(ctx, destination) } +// TODO +// Deprecated func (h *Socks) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { if h.resolve { return NewDirectConnection(ctx, h.router, h, conn, metadata, dns.DomainStrategyUseIPv4) @@ -121,6 +123,8 @@ func (h *Socks) NewConnection(ctx context.Context, conn net.Conn, metadata adapt } } +// TODO +// Deprecated func (h *Socks) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { if h.resolve { return NewDirectPacketConnection(ctx, h.router, h, conn, metadata, dns.DomainStrategyUseIPv4) diff --git a/outbound/ssh.go b/outbound/ssh.go index ce62b0048..28abe9a5e 100644 --- a/outbound/ssh.go +++ b/outbound/ssh.go @@ -199,11 +199,3 @@ func (s *SSH) DialContext(ctx context.Context, network string, destination M.Soc func (s *SSH) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { return nil, os.ErrInvalid } - -func (s *SSH) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, s, conn, metadata) -} - -func (s *SSH) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid -} diff --git a/outbound/tor.go b/outbound/tor.go index 8ae73a66f..ccc0c0cfe 100644 --- a/outbound/tor.go +++ b/outbound/tor.go @@ -211,11 +211,3 @@ func (t *Tor) DialContext(ctx context.Context, network string, destination M.Soc func (t *Tor) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { return nil, os.ErrInvalid } - -func (t *Tor) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, t, conn, metadata) -} - -func (t *Tor) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid -} diff --git a/outbound/trojan.go b/outbound/trojan.go index bde251d00..ee0b2a4b9 100644 --- a/outbound/trojan.go +++ b/outbound/trojan.go @@ -99,14 +99,6 @@ func (h *Trojan) ListenPacket(ctx context.Context, destination M.Socksaddr) (net } } -func (h *Trojan) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, h, conn, metadata) -} - -func (h *Trojan) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return NewPacketConnection(ctx, h, conn, metadata) -} - func (h *Trojan) InterfaceUpdated() { if h.transport != nil { h.transport.Close() diff --git a/outbound/tuic.go b/outbound/tuic.go index c09833237..aaf998b16 100644 --- a/outbound/tuic.go +++ b/outbound/tuic.go @@ -136,14 +136,6 @@ func (h *TUIC) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.P } } -func (h *TUIC) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, h, conn, metadata) -} - -func (h *TUIC) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return NewPacketConnection(ctx, h, conn, metadata) -} - func (h *TUIC) InterfaceUpdated() { _ = h.client.CloseWithError(E.New("network changed")) } diff --git a/outbound/urltest.go b/outbound/urltest.go index c6e38ec5a..564a0ddc3 100644 --- a/outbound/urltest.go +++ b/outbound/urltest.go @@ -167,11 +167,15 @@ func (s *URLTest) ListenPacket(ctx context.Context, destination M.Socksaddr) (ne return nil, err } +// TODO +// Deprecated func (s *URLTest) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { ctx = interrupt.ContextWithIsExternalConnection(ctx) return NewConnection(ctx, s, conn, metadata) } +// TODO +// Deprecated func (s *URLTest) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { ctx = interrupt.ContextWithIsExternalConnection(ctx) return NewPacketConnection(ctx, s, conn, metadata) diff --git a/outbound/vless.go b/outbound/vless.go index a81678f01..536a1e8fb 100644 --- a/outbound/vless.go +++ b/outbound/vless.go @@ -118,14 +118,6 @@ func (h *VLESS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net. } } -func (h *VLESS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, h, conn, metadata) -} - -func (h *VLESS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return NewPacketConnection(ctx, h, conn, metadata) -} - func (h *VLESS) InterfaceUpdated() { if h.transport != nil { h.transport.Close() diff --git a/outbound/vmess.go b/outbound/vmess.go index 3149729c0..126d2fd05 100644 --- a/outbound/vmess.go +++ b/outbound/vmess.go @@ -146,14 +146,6 @@ func (h *VMess) ListenPacket(ctx context.Context, destination M.Socksaddr) (net. } } -func (h *VMess) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, h, conn, metadata) -} - -func (h *VMess) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return NewPacketConnection(ctx, h, conn, metadata) -} - type vmessDialer VMess func (h *vmessDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { diff --git a/outbound/wireguard.go b/outbound/wireguard.go index 3ae3f63b3..8eb043f47 100644 --- a/outbound/wireguard.go +++ b/outbound/wireguard.go @@ -234,10 +234,14 @@ func (w *WireGuard) ListenPacket(ctx context.Context, destination M.Socksaddr) ( return w.tunDevice.ListenPacket(ctx, destination) } +// TODO +// Deprecated func (w *WireGuard) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { return NewDirectConnection(ctx, w.router, w, conn, metadata, dns.DomainStrategyAsIS) } +// TODO +// Deprecated func (w *WireGuard) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { return NewDirectPacketConnection(ctx, w.router, w, conn, metadata, dns.DomainStrategyAsIS) } diff --git a/route/router_geo_resources.go b/route/geo_resources.go similarity index 98% rename from route/router_geo_resources.go rename to route/geo_resources.go index d4f65cc80..91c06796b 100644 --- a/route/router_geo_resources.go +++ b/route/geo_resources.go @@ -13,6 +13,7 @@ import ( "github.com/sagernet/sing-box/common/geosite" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/experimental/deprecated" + R "github.com/sagernet/sing-box/route/rule" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/common/rw" @@ -32,7 +33,7 @@ func (r *Router) LoadGeosite(code string) (adapter.Rule, error) { if err != nil { return nil, err } - rule, err = NewDefaultRule(r.ctx, r, nil, geosite.Compile(items)) + rule, err = R.NewDefaultRule(r.ctx, r, nil, geosite.Compile(items)) if err != nil { return nil, err } diff --git a/route/route.go b/route/route.go new file mode 100644 index 000000000..86d4d95cc --- /dev/null +++ b/route/route.go @@ -0,0 +1,583 @@ +package route + +import ( + "context" + "errors" + "net" + "net/netip" + "os" + "os/user" + "strings" + "syscall" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/conntrack" + "github.com/sagernet/sing-box/common/process" + "github.com/sagernet/sing-box/common/sniff" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/outbound" + "github.com/sagernet/sing-box/route/rule" + "github.com/sagernet/sing-dns" + "github.com/sagernet/sing-mux" + "github.com/sagernet/sing-tun" + "github.com/sagernet/sing-vmess" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/bufio" + "github.com/sagernet/sing/common/bufio/deadline" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/uot" +) + +// Deprecated: use RouteConnectionEx instead. +func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + return r.routeConnection(ctx, conn, metadata, nil) +} + +func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := r.routeConnection(ctx, conn, metadata, onClose) + if err != nil { + N.CloseOnHandshakeFailure(conn, onClose, err) + if E.IsClosedOrCanceled(err) { + r.logger.DebugContext(ctx, "connection closed: ", err) + } else { + r.logger.ErrorContext(ctx, err) + } + } +} + +func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { + if r.pauseManager.IsDevicePaused() { + return E.New("reject connection to ", metadata.Destination, " while device paused") + } + + if metadata.InboundDetour != "" { + if metadata.LastInbound == metadata.InboundDetour { + return E.New("routing loop on detour: ", metadata.InboundDetour) + } + detour := r.inboundByTag[metadata.InboundDetour] + if detour == nil { + return E.New("inbound detour not found: ", metadata.InboundDetour) + } + injectable, isInjectable := detour.(adapter.TCPInjectableInbound) + if !isInjectable { + return E.New("inbound detour is not TCP injectable: ", metadata.InboundDetour) + } + metadata.LastInbound = metadata.Inbound + metadata.Inbound = metadata.InboundDetour + metadata.InboundDetour = "" + injectable.NewConnectionEx(ctx, conn, metadata, onClose) + return nil + } + conntrack.KillerCheck() + metadata.Network = N.NetworkTCP + switch metadata.Destination.Fqdn { + case mux.Destination.Fqdn: + return E.New("global multiplex is deprecated since sing-box v1.7.0, enable multiplex in inbound options instead.") + case vmess.MuxDestination.Fqdn: + return E.New("global multiplex (v2ray legacy) not supported since sing-box v1.7.0.") + case uot.MagicAddress: + return E.New("global UoT not supported since sing-box v1.7.0.") + case uot.LegacyMagicAddress: + return E.New("global UoT (legacy) not supported since sing-box v1.7.0.") + } + if deadline.NeedAdditionalReadDeadline(conn) { + conn = deadline.NewConn(conn) + } + selectedRule, _, buffers, err := r.matchRule(ctx, &metadata, conn, nil, -1) + if err != nil { + return err + } + var selectedOutbound adapter.Outbound + var selectReturn bool + if selectedRule != nil { + switch action := selectedRule.Action().(type) { + case *rule.RuleActionRoute: + var loaded bool + selectedOutbound, loaded = r.Outbound(action.Outbound) + if !loaded { + buf.ReleaseMulti(buffers) + return E.New("outbound not found: ", action.Outbound) + } + case *rule.RuleActionReturn: + selectReturn = true + case *rule.RuleActionReject: + buf.ReleaseMulti(buffers) + var rejectErr error + switch action.Method { + case C.RuleActionRejectMethodDefault: + rejectErr = os.ErrClosed + case C.RuleActionRejectMethodPortUnreachable: + rejectErr = syscall.ECONNREFUSED + case C.RuleActionRejectMethodDrop: + rejectErr = tun.ErrDrop + } + N.CloseOnHandshakeFailure(conn, onClose, rejectErr) + return nil + } + } + if selectedRule == nil || selectReturn { + if r.defaultOutboundForConnection == nil { + buf.ReleaseMulti(buffers) + return E.New("missing default outbound with TCP support") + } + selectedOutbound = r.defaultOutboundForConnection + } + if !common.Contains(selectedOutbound.Network(), N.NetworkTCP) { + buf.ReleaseMulti(buffers) + return E.New("TCP is not supported by outbound: ", selectedOutbound.Tag()) + } + for _, buffer := range buffers { + conn = bufio.NewCachedConn(conn, buffer) + } + if r.clashServer != nil { + trackerConn, tracker := r.clashServer.RoutedConnection(ctx, conn, metadata, selectedRule) + defer tracker.Leave() + conn = trackerConn + } + if r.v2rayServer != nil { + if statsService := r.v2rayServer.StatsService(); statsService != nil { + conn = statsService.RoutedConnection(metadata.Inbound, selectedOutbound.Tag(), metadata.User, conn) + } + } + legacyOutbound, isLegacy := selectedOutbound.(adapter.ConnectionHandler) + if isLegacy { + err = legacyOutbound.NewConnection(ctx, conn, metadata) + if err != nil { + conn.Close() + if onClose != nil { + onClose(err) + } + return E.Cause(err, "outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]") + } else { + if onClose != nil { + onClose(nil) + } + } + return nil + } + // TODO + err = outbound.NewConnection(ctx, selectedOutbound, conn, metadata) + if err != nil { + conn.Close() + if onClose != nil { + onClose(err) + } + return E.Cause(err, "outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]") + } else { + if onClose != nil { + onClose(nil) + } + } + return nil +} + +func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + err := r.routePacketConnection(ctx, conn, metadata, nil) + if err != nil { + conn.Close() + if E.IsClosedOrCanceled(err) { + r.logger.DebugContext(ctx, "connection closed: ", err) + } else { + r.logger.ErrorContext(ctx, err) + } + } + return nil +} + +func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := r.routePacketConnection(ctx, conn, metadata, onClose) + if err != nil { + N.CloseOnHandshakeFailure(conn, onClose, err) + if E.IsClosedOrCanceled(err) { + r.logger.DebugContext(ctx, "connection closed: ", err) + } else { + r.logger.ErrorContext(ctx, err) + } + } else if onClose != nil { + onClose(nil) + } +} + +func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { + if r.pauseManager.IsDevicePaused() { + return E.New("reject packet connection to ", metadata.Destination, " while device paused") + } + if metadata.InboundDetour != "" { + if metadata.LastInbound == metadata.InboundDetour { + return E.New("routing loop on detour: ", metadata.InboundDetour) + } + detour := r.inboundByTag[metadata.InboundDetour] + if detour == nil { + return E.New("inbound detour not found: ", metadata.InboundDetour) + } + injectable, isInjectable := detour.(adapter.UDPInjectableInbound) + if !isInjectable { + return E.New("inbound detour is not UDP injectable: ", metadata.InboundDetour) + } + metadata.LastInbound = metadata.Inbound + metadata.Inbound = metadata.InboundDetour + metadata.InboundDetour = "" + injectable.NewPacketConnectionEx(ctx, conn, metadata, onClose) + return nil + } + conntrack.KillerCheck() + + // TODO: move to UoT + metadata.Network = N.NetworkUDP + + // Currently we don't have deadline usages for UDP connections + /*if deadline.NeedAdditionalReadDeadline(conn) { + conn = deadline.NewPacketConn(bufio.NewNetPacketConn(conn)) + }*/ + + selectedRule, _, buffers, err := r.matchRule(ctx, &metadata, nil, conn, -1) + if err != nil { + return err + } + var selectedOutbound adapter.Outbound + var selectReturn bool + if selectedRule != nil { + switch action := selectedRule.Action().(type) { + case *rule.RuleActionRoute: + var loaded bool + selectedOutbound, loaded = r.Outbound(action.Outbound) + if !loaded { + buf.ReleaseMulti(buffers) + return E.New("outbound not found: ", action.Outbound) + } + metadata.UDPDisableDomainUnmapping = action.UDPDisableDomainUnmapping + case *rule.RuleActionReturn: + selectReturn = true + case *rule.RuleActionReject: + buf.ReleaseMulti(buffers) + N.CloseOnHandshakeFailure(conn, onClose, syscall.ECONNREFUSED) + return nil + } + } + if selectedRule == nil || selectReturn { + if r.defaultOutboundForPacketConnection == nil { + buf.ReleaseMulti(buffers) + return E.New("missing default outbound with UDP support") + } + selectedOutbound = r.defaultOutboundForPacketConnection + } + if !common.Contains(selectedOutbound.Network(), N.NetworkUDP) { + buf.ReleaseMulti(buffers) + return E.New("UDP is not supported by outbound: ", selectedOutbound.Tag()) + } + for _, buffer := range buffers { + // TODO: check if metadata.Destination == packet destination + conn = bufio.NewCachedPacketConn(conn, buffer, metadata.Destination) + } + if r.clashServer != nil { + trackerConn, tracker := r.clashServer.RoutedPacketConnection(ctx, conn, metadata, selectedRule) + defer tracker.Leave() + conn = trackerConn + } + if r.v2rayServer != nil { + if statsService := r.v2rayServer.StatsService(); statsService != nil { + conn = statsService.RoutedPacketConnection(metadata.Inbound, selectedOutbound.Tag(), metadata.User, conn) + } + } + if metadata.FakeIP { + conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, metadata.Destination) + } + legacyOutbound, isLegacy := selectedOutbound.(adapter.PacketConnectionHandler) + if isLegacy { + err = legacyOutbound.NewPacketConnection(ctx, conn, metadata) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + return E.Cause(err, "outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]") + } + return nil + } + // TODO + err = outbound.NewPacketConnection(ctx, selectedOutbound, conn, metadata) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + return E.Cause(err, "outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]") + } + return nil +} + +func (r *Router) matchRule( + ctx context.Context, metadata *adapter.InboundContext, + inputConn net.Conn, inputPacketConn N.PacketConn, ruleIndex int, +) (selectedRule adapter.Rule, selectedRuleIndex int, buffers []*buf.Buffer, fatalErr error) { + if r.processSearcher != nil && metadata.ProcessInfo == nil { + var originDestination netip.AddrPort + if metadata.OriginDestination.IsValid() { + originDestination = metadata.OriginDestination.AddrPort() + } else if metadata.Destination.IsIP() { + originDestination = metadata.Destination.AddrPort() + } + processInfo, fErr := process.FindProcessInfo(r.processSearcher, ctx, metadata.Network, metadata.Source.AddrPort(), originDestination) + if fErr != nil { + r.logger.InfoContext(ctx, "failed to search process: ", fErr) + } else { + if processInfo.ProcessPath != "" { + r.logger.InfoContext(ctx, "found process path: ", processInfo.ProcessPath) + } else if processInfo.PackageName != "" { + r.logger.InfoContext(ctx, "found package name: ", processInfo.PackageName) + } else if processInfo.UserId != -1 { + if /*needUserName &&*/ true { + osUser, _ := user.LookupId(F.ToString(processInfo.UserId)) + if osUser != nil { + processInfo.User = osUser.Username + } + } + if processInfo.User != "" { + r.logger.InfoContext(ctx, "found user: ", processInfo.User) + } else { + r.logger.InfoContext(ctx, "found user id: ", processInfo.UserId) + } + } + metadata.ProcessInfo = processInfo + } + } + if r.fakeIPStore != nil && r.fakeIPStore.Contains(metadata.Destination.Addr) { + domain, loaded := r.fakeIPStore.Lookup(metadata.Destination.Addr) + if !loaded { + fatalErr = E.New("missing fakeip record, try to configure experimental.cache_file") + return + } + metadata.OriginDestination = metadata.Destination + metadata.Destination = M.Socksaddr{ + Fqdn: domain, + Port: metadata.Destination.Port, + } + metadata.FakeIP = true + r.logger.DebugContext(ctx, "found fakeip domain: ", domain) + } + if r.dnsReverseMapping != nil && metadata.Domain == "" { + domain, loaded := r.dnsReverseMapping.Query(metadata.Destination.Addr) + if loaded { + metadata.Domain = domain + r.logger.DebugContext(ctx, "found reserve mapped domain: ", metadata.Domain) + } + } + if metadata.Destination.IsIPv4() { + metadata.IPVersion = 4 + } else if metadata.Destination.IsIPv6() { + metadata.IPVersion = 6 + } + + //nolint:staticcheck + if metadata.InboundOptions != common.DefaultValue[option.InboundOptions]() { + if metadata.InboundOptions.SniffEnabled { + newBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{ + OverrideDestination: metadata.InboundOptions.SniffOverrideDestination, + Timeout: time.Duration(metadata.InboundOptions.SniffTimeout), + }, inputConn, inputPacketConn) + if newErr != nil { + fatalErr = newErr + return + } + buffers = append(buffers, newBuffers...) + } + if dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) != dns.DomainStrategyAsIS { + fatalErr = r.actionResolve(ctx, metadata, &rule.RuleActionResolve{ + Strategy: dns.DomainStrategy(metadata.InboundOptions.DomainStrategy), + }) + if fatalErr != nil { + return + } + } + if metadata.InboundOptions.UDPDisableDomainUnmapping { + metadata.UDPDisableDomainUnmapping = true + } + metadata.InboundOptions = option.InboundOptions{} + } + +match: + for ruleIndex < len(r.rules) { + rules := r.rules + if ruleIndex != -1 { + rules = rules[ruleIndex+1:] + } + var ( + currentRule adapter.Rule + currentRuleIndex int + matched bool + ) + for currentRuleIndex, currentRule = range rules { + if currentRule.Match(metadata) { + matched = true + break + } + } + if !matched { + break + } + r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] ", currentRule, " => ", currentRule.Action()) + switch action := currentRule.Action().(type) { + case *rule.RuleActionSniff: + newBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn) + if newErr != nil { + fatalErr = newErr + return + } + buffers = append(buffers, newBuffers...) + case *rule.RuleActionResolve: + fatalErr = r.actionResolve(ctx, metadata, action) + if fatalErr != nil { + return + } + default: + selectedRule = currentRule + selectedRuleIndex = currentRuleIndex + break match + } + ruleIndex = currentRuleIndex + } + if metadata.Destination.Addr.IsUnspecified() { + newBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{}, inputConn, inputPacketConn) + if newErr != nil { + fatalErr = newErr + return + } + buffers = append(buffers, newBuffers...) + } + return +} + +func (r *Router) actionSniff( + ctx context.Context, metadata *adapter.InboundContext, action *rule.RuleActionSniff, + inputConn net.Conn, inputPacketConn N.PacketConn, +) (buffers []*buf.Buffer, fatalErr error) { + if sniff.Skip(metadata) { + return + } else if inputConn != nil && len(action.StreamSniffers) > 0 { + buffer := buf.NewPacket() + err := sniff.PeekStream( + ctx, + metadata, + inputConn, + buffer, + action.Timeout, + action.StreamSniffers..., + ) + if err == nil { + //goland:noinspection GoDeprecation + if action.OverrideDestination && M.IsDomainName(metadata.Domain) { + metadata.Destination = M.Socksaddr{ + Fqdn: metadata.Domain, + Port: metadata.Destination.Port, + } + } + if metadata.Domain != "" && metadata.Client != "" { + r.logger.DebugContext(ctx, "sniffed protocol: ", metadata.Protocol, ", domain: ", metadata.Domain, ", client: ", metadata.Client) + } else if metadata.Domain != "" { + r.logger.DebugContext(ctx, "sniffed protocol: ", metadata.Protocol, ", domain: ", metadata.Domain) + } else { + r.logger.DebugContext(ctx, "sniffed protocol: ", metadata.Protocol) + } + } + if !buffer.IsEmpty() { + buffers = append(buffers, buffer) + } else { + buffer.Release() + } + } else if inputPacketConn != nil && len(action.PacketSniffers) > 0 { + for { + var ( + buffer = buf.NewPacket() + destination M.Socksaddr + done = make(chan struct{}) + err error + ) + go func() { + sniffTimeout := C.ReadPayloadTimeout + if action.Timeout > 0 { + sniffTimeout = action.Timeout + } + inputPacketConn.SetReadDeadline(time.Now().Add(sniffTimeout)) + destination, err = inputPacketConn.ReadPacket(buffer) + inputPacketConn.SetReadDeadline(time.Time{}) + close(done) + }() + select { + case <-done: + case <-ctx.Done(): + inputPacketConn.Close() + fatalErr = ctx.Err() + return + } + if err != nil { + buffer.Release() + if !errors.Is(err, os.ErrDeadlineExceeded) { + fatalErr = err + return + } + } else { + // TODO: maybe always override destination + if metadata.Destination.Addr.IsUnspecified() { + metadata.Destination = destination + } + if len(buffers) > 0 { + err = sniff.PeekPacket( + ctx, + metadata, + buffer.Bytes(), + sniff.QUICClientHello, + ) + } else { + err = sniff.PeekPacket( + ctx, metadata, + buffer.Bytes(), + action.PacketSniffers..., + ) + } + buffers = append(buffers, buffer) + if E.IsMulti(err, sniff.ErrClientHelloFragmented) && len(buffers) == 0 { + r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello") + continue + } + if metadata.Protocol != "" { + //goland:noinspection GoDeprecation + if action.OverrideDestination && M.IsDomainName(metadata.Domain) { + metadata.Destination = M.Socksaddr{ + Fqdn: metadata.Domain, + Port: metadata.Destination.Port, + } + } + if metadata.Domain != "" && metadata.Client != "" { + r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain, ", client: ", metadata.Client) + } else if metadata.Domain != "" { + r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain) + } else if metadata.Client != "" { + r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", client: ", metadata.Client) + } else { + r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol) + } + } + } + break + } + } + return +} + +func (r *Router) actionResolve(ctx context.Context, metadata *adapter.InboundContext, action *rule.RuleActionResolve) error { + if metadata.Destination.IsFqdn() { + // TODO: check if WithContext is necessary + addresses, err := r.Lookup(adapter.WithContext(ctx, metadata), metadata.Destination.Fqdn, action.Strategy) + if err != nil { + return err + } + metadata.DestinationAddresses = addresses + r.dnsLogger.DebugContext(ctx, "resolved [", strings.Join(F.MapToString(metadata.DestinationAddresses), " "), "]") + if metadata.Destination.IsIPv4() { + metadata.IPVersion = 4 + } else if metadata.Destination.IsIPv6() { + metadata.IPVersion = 6 + } + } + return nil +} diff --git a/route/router_dns.go b/route/route_dns.go similarity index 75% rename from route/router_dns.go rename to route/route_dns.go index ead8c2894..43eb61e6d 100644 --- a/route/router_dns.go +++ b/route/route_dns.go @@ -8,6 +8,7 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + R "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common/cache" E "github.com/sagernet/sing/common/exceptions" @@ -36,15 +37,16 @@ func (m *DNSReverseMapping) Query(address netip.Addr) (string, bool) { return domain, loaded } -func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, index int, isAddressQuery bool) (context.Context, dns.Transport, dns.DomainStrategy, adapter.DNSRule, int) { +func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int, isAddressQuery bool) (dns.Transport, dns.QueryOptions, adapter.DNSRule, int) { metadata := adapter.ContextFrom(ctx) if metadata == nil { panic("no context") } - if index < len(r.dnsRules) { + var options dns.QueryOptions + if ruleIndex < len(r.dnsRules) { dnsRules := r.dnsRules - if index != -1 { - dnsRules = dnsRules[index+1:] + if ruleIndex != -1 { + dnsRules = dnsRules[ruleIndex+1:] } for currentRuleIndex, rule := range dnsRules { if rule.WithAddressLimit() && !isAddressQuery { @@ -52,43 +54,42 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, index int, isAd } metadata.ResetRuleCache() if rule.Match(metadata) { - detour := rule.Outbound() - transport, loaded := r.transportMap[detour] - if !loaded { - r.dnsLogger.ErrorContext(ctx, "transport not found: ", detour) - continue + displayRuleIndex := currentRuleIndex + if displayRuleIndex != -1 { + displayRuleIndex += displayRuleIndex + 1 } - _, isFakeIP := transport.(adapter.FakeIPTransport) - if isFakeIP && !allowFakeIP { - continue - } - ruleIndex := currentRuleIndex - if index != -1 { - ruleIndex += index + 1 - } - r.dnsLogger.DebugContext(ctx, "match[", ruleIndex, "] ", rule.String(), " => ", detour) - if isFakeIP || rule.DisableCache() { - ctx = dns.ContextWithDisableCache(ctx, true) - } - if rewriteTTL := rule.RewriteTTL(); rewriteTTL != nil { - ctx = dns.ContextWithRewriteTTL(ctx, *rewriteTTL) - } - if clientSubnet := rule.ClientSubnet(); clientSubnet != nil { - ctx = dns.ContextWithClientSubnet(ctx, *clientSubnet) - } - if domainStrategy, dsLoaded := r.transportDomainStrategy[transport]; dsLoaded { - return ctx, transport, domainStrategy, rule, ruleIndex + if routeAction, isRoute := rule.Action().(*R.RuleActionDNSRoute); isRoute { + transport, loaded := r.transportMap[routeAction.Server] + if !loaded { + r.dnsLogger.ErrorContext(ctx, "transport not found: ", routeAction.Server) + continue + } + _, isFakeIP := transport.(adapter.FakeIPTransport) + if isFakeIP && !allowFakeIP { + continue + } + options.DisableCache = isFakeIP || routeAction.DisableCache + options.RewriteTTL = routeAction.RewriteTTL + options.ClientSubnet = routeAction.ClientSubnet + if domainStrategy, dsLoaded := r.transportDomainStrategy[transport]; dsLoaded { + options.Strategy = domainStrategy + } else { + options.Strategy = r.defaultDomainStrategy + } + r.dnsLogger.DebugContext(ctx, "match[", displayRuleIndex, "] ", rule.String(), " => ", rule.Action()) + return transport, options, rule, currentRuleIndex } else { - return ctx, transport, r.defaultDomainStrategy, rule, ruleIndex + return nil, options, rule, currentRuleIndex } } } } if domainStrategy, dsLoaded := r.transportDomainStrategy[r.defaultTransport]; dsLoaded { - return ctx, r.defaultTransport, domainStrategy, nil, -1 + options.Strategy = domainStrategy } else { - return ctx, r.defaultTransport, r.defaultDomainStrategy, nil, -1 + options.Strategy = r.defaultDomainStrategy } + return r.defaultTransport, options, nil, -1 } func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { @@ -117,21 +118,18 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er metadata.Domain = fqdnToDomain(message.Question[0].Name) } var ( - strategy dns.DomainStrategy + options dns.QueryOptions rule adapter.DNSRule ruleIndex int ) ruleIndex = -1 for { - var ( - dnsCtx context.Context - addressLimit bool - ) - dnsCtx, transport, strategy, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex, isAddressQuery(message)) - dnsCtx = adapter.OverrideContext(dnsCtx) + dnsCtx := adapter.OverrideContext(ctx) + var addressLimit bool + transport, options, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex, isAddressQuery(message)) if rule != nil && rule.WithAddressLimit() { addressLimit = true - response, err = r.dnsClient.ExchangeWithResponseCheck(dnsCtx, transport, message, strategy, func(response *mDNS.Msg) bool { + response, err = r.dnsClient.ExchangeWithResponseCheck(dnsCtx, transport, message, options, func(response *mDNS.Msg) bool { addresses, addrErr := dns.MessageToAddresses(response) if addrErr != nil { return false @@ -141,7 +139,7 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er }) } else { addressLimit = false - response, err = r.dnsClient.Exchange(dnsCtx, transport, message, strategy) + response, err = r.dnsClient.Exchange(dnsCtx, transport, message, options) } var rejected bool if err != nil { @@ -199,31 +197,28 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS metadata.Destination = M.Socksaddr{} metadata.Domain = domain var ( - transport dns.Transport - transportStrategy dns.DomainStrategy - rule adapter.DNSRule - ruleIndex int + transport dns.Transport + options dns.QueryOptions + rule adapter.DNSRule + ruleIndex int ) ruleIndex = -1 for { - var ( - dnsCtx context.Context - addressLimit bool - ) - dnsCtx, transport, transportStrategy, rule, ruleIndex = r.matchDNS(ctx, false, ruleIndex, true) - dnsCtx = adapter.OverrideContext(dnsCtx) - if strategy == dns.DomainStrategyAsIS { - strategy = transportStrategy + dnsCtx := adapter.OverrideContext(ctx) + var addressLimit bool + transport, options, rule, ruleIndex = r.matchDNS(ctx, false, ruleIndex, true) + if strategy != dns.DomainStrategyAsIS { + options.Strategy = strategy } if rule != nil && rule.WithAddressLimit() { addressLimit = true - responseAddrs, err = r.dnsClient.LookupWithResponseCheck(dnsCtx, transport, domain, strategy, func(responseAddrs []netip.Addr) bool { + responseAddrs, err = r.dnsClient.LookupWithResponseCheck(dnsCtx, transport, domain, options, func(responseAddrs []netip.Addr) bool { metadata.DestinationAddresses = responseAddrs return rule.MatchAddressLimit(metadata) }) } else { addressLimit = false - responseAddrs, err = r.dnsClient.Lookup(dnsCtx, transport, domain, strategy) + responseAddrs, err = r.dnsClient.Lookup(dnsCtx, transport, domain, options) } if err != nil { if errors.Is(err, dns.ErrResponseRejectedCached) { diff --git a/route/router.go b/route/router.go index 1fc9aa778..6308127fd 100644 --- a/route/router.go +++ b/route/router.go @@ -3,11 +3,9 @@ package route import ( "context" "errors" - "net" "net/netip" "net/url" "os" - "os/user" "runtime" "strings" "syscall" @@ -19,22 +17,16 @@ import ( "github.com/sagernet/sing-box/common/geoip" "github.com/sagernet/sing-box/common/geosite" "github.com/sagernet/sing-box/common/process" - "github.com/sagernet/sing-box/common/sniff" "github.com/sagernet/sing-box/common/taskmonitor" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-box/outbound" + R "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing-box/transport/fakeip" "github.com/sagernet/sing-dns" - "github.com/sagernet/sing-mux" "github.com/sagernet/sing-tun" - "github.com/sagernet/sing-vmess" "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/buf" - "github.com/sagernet/sing/common/bufio" - "github.com/sagernet/sing/common/bufio/deadline" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" @@ -42,7 +34,6 @@ import ( N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/ntp" "github.com/sagernet/sing/common/task" - "github.com/sagernet/sing/common/uot" "github.com/sagernet/sing/common/winpowrprof" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/pause" @@ -154,14 +145,14 @@ func NewRouter( Logger: router.dnsLogger, }) for i, ruleOptions := range options.Rules { - routeRule, err := NewRule(ctx, router, router.logger, ruleOptions, true) + routeRule, err := R.NewRule(ctx, router, router.logger, ruleOptions, true) if err != nil { return nil, E.Cause(err, "parse rule[", i, "]") } router.rules = append(router.rules, routeRule) } for i, dnsRuleOptions := range dnsOptions.Rules { - dnsRule, err := NewDNSRule(ctx, router, router.logger, dnsRuleOptions, true) + dnsRule, err := R.NewDNSRule(ctx, router, router.logger, dnsRuleOptions, true) if err != nil { return nil, E.Cause(err, "parse dns rule[", i, "]") } @@ -171,7 +162,7 @@ func NewRouter( if _, exists := router.ruleSetMap[ruleSetOptions.Tag]; exists { return nil, E.New("duplicate rule-set tag: ", ruleSetOptions.Tag) } - ruleSet, err := NewRuleSet(ctx, router, router.logger, ruleSetOptions) + ruleSet, err := R.NewRuleSet(ctx, router, router.logger, ruleSetOptions) if err != nil { return nil, E.Cause(err, "parse rule-set[", i, "]") } @@ -440,8 +431,12 @@ func (r *Router) Initialize(inbounds []adapter.Inbound, outbounds []adapter.Outb r.defaultOutboundForPacketConnection = defaultOutboundForPacketConnection r.outboundByTag = outboundByTag for i, rule := range r.rules { - if _, loaded := outboundByTag[rule.Outbound()]; !loaded { - return E.New("outbound not found for rule[", i, "]: ", rule.Outbound()) + routeAction, isRoute := rule.Action().(*R.RuleActionRoute) + if !isRoute { + continue + } + if _, loaded := outboundByTag[routeAction.Outbound]; !loaded { + return E.New("outbound not found for rule[", i, "]: ", routeAction.Outbound) } } return nil @@ -807,375 +802,6 @@ func (r *Router) NeedWIFIState() bool { return r.needWIFIState } -func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - if r.pauseManager.IsDevicePaused() { - return E.New("reject connection to ", metadata.Destination, " while device paused") - } - - if metadata.InboundDetour != "" { - if metadata.LastInbound == metadata.InboundDetour { - return E.New("routing loop on detour: ", metadata.InboundDetour) - } - detour := r.inboundByTag[metadata.InboundDetour] - if detour == nil { - return E.New("inbound detour not found: ", metadata.InboundDetour) - } - injectable, isInjectable := detour.(adapter.InjectableInbound) - if !isInjectable { - return E.New("inbound detour is not injectable: ", metadata.InboundDetour) - } - if !common.Contains(injectable.Network(), N.NetworkTCP) { - return E.New("inject: TCP unsupported") - } - metadata.LastInbound = metadata.Inbound - metadata.Inbound = metadata.InboundDetour - metadata.InboundDetour = "" - err := injectable.NewConnection(ctx, conn, metadata) - if err != nil { - return E.Cause(err, "inject ", detour.Tag()) - } - return nil - } - conntrack.KillerCheck() - metadata.Network = N.NetworkTCP - switch metadata.Destination.Fqdn { - case mux.Destination.Fqdn: - return E.New("global multiplex is deprecated since sing-box v1.7.0, enable multiplex in inbound options instead.") - case vmess.MuxDestination.Fqdn: - return E.New("global multiplex (v2ray legacy) not supported since sing-box v1.7.0.") - case uot.MagicAddress: - return E.New("global UoT not supported since sing-box v1.7.0.") - case uot.LegacyMagicAddress: - return E.New("global UoT (legacy) not supported since sing-box v1.7.0.") - } - - if r.fakeIPStore != nil && r.fakeIPStore.Contains(metadata.Destination.Addr) { - domain, loaded := r.fakeIPStore.Lookup(metadata.Destination.Addr) - if !loaded { - return E.New("missing fakeip context") - } - metadata.OriginDestination = metadata.Destination - metadata.Destination = M.Socksaddr{ - Fqdn: domain, - Port: metadata.Destination.Port, - } - metadata.FakeIP = true - r.logger.DebugContext(ctx, "found fakeip domain: ", domain) - } - - if deadline.NeedAdditionalReadDeadline(conn) { - conn = deadline.NewConn(conn) - } - - if metadata.InboundOptions.SniffEnabled && !sniff.Skip(metadata) { - buffer := buf.NewPacket() - err := sniff.PeekStream( - ctx, - &metadata, - conn, - buffer, - time.Duration(metadata.InboundOptions.SniffTimeout), - sniff.TLSClientHello, - sniff.HTTPHost, - sniff.StreamDomainNameQuery, - sniff.SSH, - sniff.BitTorrent, - ) - if err == nil { - if metadata.InboundOptions.SniffOverrideDestination && M.IsDomainName(metadata.Domain) { - metadata.Destination = M.Socksaddr{ - Fqdn: metadata.Domain, - Port: metadata.Destination.Port, - } - } - if metadata.Domain != "" { - r.logger.DebugContext(ctx, "sniffed protocol: ", metadata.Protocol, ", domain: ", metadata.Domain) - } else { - r.logger.DebugContext(ctx, "sniffed protocol: ", metadata.Protocol) - } - } - if !buffer.IsEmpty() { - conn = bufio.NewCachedConn(conn, buffer) - } else { - buffer.Release() - } - } - - if r.dnsReverseMapping != nil && metadata.Domain == "" { - domain, loaded := r.dnsReverseMapping.Query(metadata.Destination.Addr) - if loaded { - metadata.Domain = domain - r.logger.DebugContext(ctx, "found reserve mapped domain: ", metadata.Domain) - } - } - - if metadata.Destination.IsFqdn() && dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) != dns.DomainStrategyAsIS { - addresses, err := r.Lookup(adapter.WithContext(ctx, &metadata), metadata.Destination.Fqdn, dns.DomainStrategy(metadata.InboundOptions.DomainStrategy)) - if err != nil { - return err - } - metadata.DestinationAddresses = addresses - r.dnsLogger.DebugContext(ctx, "resolved [", strings.Join(F.MapToString(metadata.DestinationAddresses), " "), "]") - } - if metadata.Destination.IsIPv4() { - metadata.IPVersion = 4 - } else if metadata.Destination.IsIPv6() { - metadata.IPVersion = 6 - } - ctx, matchedRule, detour, err := r.match(ctx, &metadata, r.defaultOutboundForConnection) - if err != nil { - return err - } - if !common.Contains(detour.Network(), N.NetworkTCP) { - return E.New("missing supported outbound, closing connection") - } - if r.clashServer != nil { - trackerConn, tracker := r.clashServer.RoutedConnection(ctx, conn, metadata, matchedRule) - defer tracker.Leave() - conn = trackerConn - } - if r.v2rayServer != nil { - if statsService := r.v2rayServer.StatsService(); statsService != nil { - conn = statsService.RoutedConnection(metadata.Inbound, detour.Tag(), metadata.User, conn) - } - } - return detour.NewConnection(ctx, conn, metadata) -} - -func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - if r.pauseManager.IsDevicePaused() { - return E.New("reject packet connection to ", metadata.Destination, " while device paused") - } - if metadata.InboundDetour != "" { - if metadata.LastInbound == metadata.InboundDetour { - return E.New("routing loop on detour: ", metadata.InboundDetour) - } - detour := r.inboundByTag[metadata.InboundDetour] - if detour == nil { - return E.New("inbound detour not found: ", metadata.InboundDetour) - } - injectable, isInjectable := detour.(adapter.InjectableInbound) - if !isInjectable { - return E.New("inbound detour is not injectable: ", metadata.InboundDetour) - } - if !common.Contains(injectable.Network(), N.NetworkUDP) { - return E.New("inject: UDP unsupported") - } - metadata.LastInbound = metadata.Inbound - metadata.Inbound = metadata.InboundDetour - metadata.InboundDetour = "" - err := injectable.NewPacketConnection(ctx, conn, metadata) - if err != nil { - return E.Cause(err, "inject ", detour.Tag()) - } - return nil - } - conntrack.KillerCheck() - metadata.Network = N.NetworkUDP - - if r.fakeIPStore != nil && r.fakeIPStore.Contains(metadata.Destination.Addr) { - domain, loaded := r.fakeIPStore.Lookup(metadata.Destination.Addr) - if !loaded { - return E.New("missing fakeip context") - } - metadata.OriginDestination = metadata.Destination - metadata.Destination = M.Socksaddr{ - Fqdn: domain, - Port: metadata.Destination.Port, - } - metadata.FakeIP = true - r.logger.DebugContext(ctx, "found fakeip domain: ", domain) - } - - // Currently we don't have deadline usages for UDP connections - /*if deadline.NeedAdditionalReadDeadline(conn) { - conn = deadline.NewPacketConn(bufio.NewNetPacketConn(conn)) - }*/ - - if metadata.InboundOptions.SniffEnabled || metadata.Destination.Addr.IsUnspecified() { - var bufferList []*buf.Buffer - for { - var ( - buffer = buf.NewPacket() - destination M.Socksaddr - done = make(chan struct{}) - err error - ) - go func() { - sniffTimeout := C.ReadPayloadTimeout - if metadata.InboundOptions.SniffTimeout > 0 { - sniffTimeout = time.Duration(metadata.InboundOptions.SniffTimeout) - } - conn.SetReadDeadline(time.Now().Add(sniffTimeout)) - destination, err = conn.ReadPacket(buffer) - conn.SetReadDeadline(time.Time{}) - close(done) - }() - select { - case <-done: - case <-ctx.Done(): - conn.Close() - return ctx.Err() - } - if err != nil { - buffer.Release() - if !errors.Is(err, os.ErrDeadlineExceeded) { - return err - } - } else { - if metadata.Destination.Addr.IsUnspecified() { - metadata.Destination = destination - } - if metadata.InboundOptions.SniffEnabled { - if len(bufferList) > 0 { - err = sniff.PeekPacket( - ctx, - &metadata, - buffer.Bytes(), - sniff.QUICClientHello, - ) - } else { - err = sniff.PeekPacket( - ctx, &metadata, - buffer.Bytes(), - sniff.DomainNameQuery, - sniff.QUICClientHello, - sniff.STUNMessage, - sniff.UTP, - sniff.UDPTracker, - sniff.DTLSRecord) - } - if E.IsMulti(err, sniff.ErrClientHelloFragmented) && len(bufferList) == 0 { - bufferList = append(bufferList, buffer) - r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello") - continue - } - if metadata.Protocol != "" { - if metadata.InboundOptions.SniffOverrideDestination && M.IsDomainName(metadata.Domain) { - metadata.Destination = M.Socksaddr{ - Fqdn: metadata.Domain, - Port: metadata.Destination.Port, - } - } - if metadata.Domain != "" && metadata.Client != "" { - r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain, ", client: ", metadata.Client) - } else if metadata.Domain != "" { - r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain) - } else if metadata.Client != "" { - r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", client: ", metadata.Client) - } else { - r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol) - } - } - } - conn = bufio.NewCachedPacketConn(conn, buffer, destination) - } - for _, cachedBuffer := range common.Reverse(bufferList) { - conn = bufio.NewCachedPacketConn(conn, cachedBuffer, destination) - } - break - } - } - if r.dnsReverseMapping != nil && metadata.Domain == "" { - domain, loaded := r.dnsReverseMapping.Query(metadata.Destination.Addr) - if loaded { - metadata.Domain = domain - r.logger.DebugContext(ctx, "found reserve mapped domain: ", metadata.Domain) - } - } - if metadata.Destination.IsFqdn() && dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) != dns.DomainStrategyAsIS { - addresses, err := r.Lookup(adapter.WithContext(ctx, &metadata), metadata.Destination.Fqdn, dns.DomainStrategy(metadata.InboundOptions.DomainStrategy)) - if err != nil { - return err - } - metadata.DestinationAddresses = addresses - r.dnsLogger.DebugContext(ctx, "resolved [", strings.Join(F.MapToString(metadata.DestinationAddresses), " "), "]") - } - if metadata.Destination.IsIPv4() { - metadata.IPVersion = 4 - } else if metadata.Destination.IsIPv6() { - metadata.IPVersion = 6 - } - ctx, matchedRule, detour, err := r.match(ctx, &metadata, r.defaultOutboundForPacketConnection) - if err != nil { - return err - } - if !common.Contains(detour.Network(), N.NetworkUDP) { - return E.New("missing supported outbound, closing packet connection") - } - if r.clashServer != nil { - trackerConn, tracker := r.clashServer.RoutedPacketConnection(ctx, conn, metadata, matchedRule) - defer tracker.Leave() - conn = trackerConn - } - if r.v2rayServer != nil { - if statsService := r.v2rayServer.StatsService(); statsService != nil { - conn = statsService.RoutedPacketConnection(metadata.Inbound, detour.Tag(), metadata.User, conn) - } - } - if metadata.FakeIP { - conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, metadata.Destination) - } - return detour.NewPacketConnection(ctx, conn, metadata) -} - -func (r *Router) match(ctx context.Context, metadata *adapter.InboundContext, defaultOutbound adapter.Outbound) (context.Context, adapter.Rule, adapter.Outbound, error) { - matchRule, matchOutbound := r.match0(ctx, metadata, defaultOutbound) - if contextOutbound, loaded := outbound.TagFromContext(ctx); loaded { - if contextOutbound == matchOutbound.Tag() { - return nil, nil, nil, E.New("connection loopback in outbound/", matchOutbound.Type(), "[", matchOutbound.Tag(), "]") - } - } - ctx = outbound.ContextWithTag(ctx, matchOutbound.Tag()) - return ctx, matchRule, matchOutbound, nil -} - -func (r *Router) match0(ctx context.Context, metadata *adapter.InboundContext, defaultOutbound adapter.Outbound) (adapter.Rule, adapter.Outbound) { - if r.processSearcher != nil { - var originDestination netip.AddrPort - if metadata.OriginDestination.IsValid() { - originDestination = metadata.OriginDestination.AddrPort() - } else if metadata.Destination.IsIP() { - originDestination = metadata.Destination.AddrPort() - } - processInfo, err := process.FindProcessInfo(r.processSearcher, ctx, metadata.Network, metadata.Source.AddrPort(), originDestination) - if err != nil { - r.logger.InfoContext(ctx, "failed to search process: ", err) - } else { - if processInfo.ProcessPath != "" { - r.logger.InfoContext(ctx, "found process path: ", processInfo.ProcessPath) - } else if processInfo.PackageName != "" { - r.logger.InfoContext(ctx, "found package name: ", processInfo.PackageName) - } else if processInfo.UserId != -1 { - if /*needUserName &&*/ true { - osUser, _ := user.LookupId(F.ToString(processInfo.UserId)) - if osUser != nil { - processInfo.User = osUser.Username - } - } - if processInfo.User != "" { - r.logger.InfoContext(ctx, "found user: ", processInfo.User) - } else { - r.logger.InfoContext(ctx, "found user id: ", processInfo.UserId) - } - } - metadata.ProcessInfo = processInfo - } - } - for i, rule := range r.rules { - metadata.ResetRuleCache() - if rule.Match(metadata) { - detour := rule.Outbound() - r.logger.DebugContext(ctx, "match[", i, "] ", rule.String(), " => ", detour) - if outbound, loaded := r.Outbound(detour); loaded { - return rule, outbound - } - r.logger.ErrorContext(ctx, "outbound not found: ", detour) - } - } - return nil, defaultOutbound -} - func (r *Router) InterfaceFinder() control.InterfaceFinder { return r.interfaceFinder } diff --git a/route/rule_abstract.go b/route/rule/rule_abstract.go similarity index 94% rename from route/rule_abstract.go rename to route/rule/rule_abstract.go index 9ef2e9327..6a5693418 100644 --- a/route/rule_abstract.go +++ b/route/rule/rule_abstract.go @@ -1,4 +1,4 @@ -package route +package rule import ( "io" @@ -20,7 +20,7 @@ type abstractDefaultRule struct { allItems []RuleItem ruleSetItem RuleItem invert bool - outbound string + action adapter.RuleAction } func (r *abstractDefaultRule) Type() string { @@ -150,8 +150,8 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool { return !r.invert } -func (r *abstractDefaultRule) Outbound() string { - return r.outbound +func (r *abstractDefaultRule) Action() adapter.RuleAction { + return r.action } func (r *abstractDefaultRule) String() string { @@ -163,10 +163,10 @@ func (r *abstractDefaultRule) String() string { } type abstractLogicalRule struct { - rules []adapter.HeadlessRule - mode string - invert bool - outbound string + rules []adapter.HeadlessRule + mode string + invert bool + action adapter.RuleAction } func (r *abstractLogicalRule) Type() string { @@ -231,8 +231,8 @@ func (r *abstractLogicalRule) Match(metadata *adapter.InboundContext) bool { } } -func (r *abstractLogicalRule) Outbound() string { - return r.outbound +func (r *abstractLogicalRule) Action() adapter.RuleAction { + return r.action } func (r *abstractLogicalRule) String() string { diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go new file mode 100644 index 000000000..e85fc7638 --- /dev/null +++ b/route/rule/rule_action.go @@ -0,0 +1,228 @@ +package rule + +import ( + "net/netip" + "strings" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/sniff" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-dns" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" +) + +func NewRuleAction(action option.RuleAction) (adapter.RuleAction, error) { + switch action.Action { + case C.RuleActionTypeRoute: + return &RuleActionRoute{ + Outbound: action.RouteOptions.Outbound, + UDPDisableDomainUnmapping: action.RouteOptions.UDPDisableDomainUnmapping, + }, nil + case C.RuleActionTypeReturn: + return &RuleActionReject{}, nil + case C.RuleActionTypeReject: + return &RuleActionReject{ + Method: string(action.RejectOptions.Method), + }, nil + case C.RuleActionTypeHijackDNS: + return &RuleActionHijackDNS{}, nil + case C.RuleActionTypeSniff: + sniffAction := &RuleActionSniff{ + snifferNames: action.SniffOptions.Sniffer, + Timeout: time.Duration(action.SniffOptions.Timeout), + } + return sniffAction, sniffAction.build() + case C.RuleActionTypeResolve: + return &RuleActionResolve{ + Strategy: dns.DomainStrategy(action.ResolveOptions.Strategy), + Server: action.ResolveOptions.Server, + }, nil + default: + panic(F.ToString("unknown rule action: ", action.Action)) + } +} + +func NewDNSRuleAction(action option.DNSRuleAction) adapter.RuleAction { + switch action.Action { + case C.RuleActionTypeRoute: + return &RuleActionDNSRoute{ + Server: action.RouteOptions.Server, + DisableCache: action.RouteOptions.DisableCache, + RewriteTTL: action.RouteOptions.RewriteTTL, + ClientSubnet: action.RouteOptions.ClientSubnet.Build(), + } + case C.RuleActionTypeReturn: + return &RuleActionReturn{} + case C.RuleActionTypeReject: + return &RuleActionReject{ + Method: string(action.RejectOptions.Method), + } + default: + panic(F.ToString("unknown rule action: ", action.Action)) + } +} + +type RuleActionRoute struct { + Outbound string + UDPDisableDomainUnmapping bool +} + +func (r *RuleActionRoute) Type() string { + return C.RuleActionTypeRoute +} + +func (r *RuleActionRoute) String() string { + return F.ToString("route(", r.Outbound, ")") +} + +type RuleActionDNSRoute struct { + Server string + DisableCache bool + RewriteTTL *uint32 + ClientSubnet netip.Prefix +} + +func (r *RuleActionDNSRoute) Type() string { + return C.RuleActionTypeRoute +} + +func (r *RuleActionDNSRoute) String() string { + return F.ToString("route(", r.Server, ")") +} + +type RuleActionReturn struct{} + +func (r *RuleActionReturn) Type() string { + return C.RuleActionTypeReturn +} + +func (r *RuleActionReturn) String() string { + return "return" +} + +type RuleActionReject struct { + Method string +} + +func (r *RuleActionReject) Type() string { + return C.RuleActionTypeReject +} + +func (r *RuleActionReject) String() string { + if r.Method == C.RuleActionRejectMethodDefault { + return "reject" + } + return F.ToString("reject(", r.Method, ")") +} + +type RuleActionHijackDNS struct{} + +func (r *RuleActionHijackDNS) Type() string { + return C.RuleActionTypeHijackDNS +} + +func (r *RuleActionHijackDNS) String() string { + return "hijack-dns" +} + +type RuleActionSniff struct { + snifferNames []string + StreamSniffers []sniff.StreamSniffer + PacketSniffers []sniff.PacketSniffer + Timeout time.Duration + // Deprecated + OverrideDestination bool +} + +func (r *RuleActionSniff) Type() string { + return C.RuleActionTypeSniff +} + +func (r *RuleActionSniff) build() error { + if len(r.StreamSniffers) > 0 || len(r.PacketSniffers) > 0 { + return nil + } + if len(r.snifferNames) > 0 { + for _, name := range r.snifferNames { + switch name { + case C.ProtocolTLS: + r.StreamSniffers = append(r.StreamSniffers, sniff.TLSClientHello) + case C.ProtocolHTTP: + r.StreamSniffers = append(r.StreamSniffers, sniff.HTTPHost) + case C.ProtocolQUIC: + r.PacketSniffers = append(r.PacketSniffers, sniff.QUICClientHello) + case C.ProtocolDNS: + r.StreamSniffers = append(r.StreamSniffers, sniff.StreamDomainNameQuery) + r.PacketSniffers = append(r.PacketSniffers, sniff.DomainNameQuery) + case C.ProtocolSTUN: + r.PacketSniffers = append(r.PacketSniffers, sniff.STUNMessage) + case C.ProtocolBitTorrent: + r.StreamSniffers = append(r.StreamSniffers, sniff.BitTorrent) + r.PacketSniffers = append(r.PacketSniffers, sniff.UTP) + r.PacketSniffers = append(r.PacketSniffers, sniff.UDPTracker) + case C.ProtocolDTLS: + r.PacketSniffers = append(r.PacketSniffers, sniff.DTLSRecord) + case C.ProtocolSSH: + r.StreamSniffers = append(r.StreamSniffers, sniff.SSH) + case C.ProtocolRDP: + r.StreamSniffers = append(r.StreamSniffers, sniff.RDP) + default: + return E.New("unknown sniffer: ", name) + } + } + } else { + r.StreamSniffers = []sniff.StreamSniffer{ + sniff.TLSClientHello, + sniff.HTTPHost, + sniff.StreamDomainNameQuery, + sniff.BitTorrent, + sniff.SSH, + sniff.RDP, + } + r.PacketSniffers = []sniff.PacketSniffer{ + sniff.DomainNameQuery, + sniff.QUICClientHello, + sniff.STUNMessage, + sniff.UTP, + sniff.UDPTracker, + sniff.DTLSRecord, + } + } + return nil +} + +func (r *RuleActionSniff) String() string { + if len(r.snifferNames) == 0 && r.Timeout == 0 { + return "sniff" + } else if len(r.snifferNames) > 0 && r.Timeout == 0 { + return F.ToString("sniff(", strings.Join(r.snifferNames, ","), ")") + } else if len(r.snifferNames) == 0 && r.Timeout > 0 { + return F.ToString("sniff(", r.Timeout.String(), ")") + } else { + return F.ToString("sniff(", strings.Join(r.snifferNames, ","), ",", r.Timeout.String(), ")") + } +} + +type RuleActionResolve struct { + Strategy dns.DomainStrategy + Server string +} + +func (r *RuleActionResolve) Type() string { + return C.RuleActionTypeResolve +} + +func (r *RuleActionResolve) String() string { + if r.Strategy == dns.DomainStrategyAsIS && r.Server == "" { + return F.ToString("resolve") + } else if r.Strategy != dns.DomainStrategyAsIS && r.Server == "" { + return F.ToString("resolve(", option.DomainStrategy(r.Strategy).String(), ")") + } else if r.Strategy == dns.DomainStrategyAsIS && r.Server != "" { + return F.ToString("resolve(", r.Server, ")") + } else { + return F.ToString("resolve(", option.DomainStrategy(r.Strategy).String(), ",", r.Server, ")") + } +} diff --git a/route/rule_default.go b/route/rule/rule_default.go similarity index 88% rename from route/rule_default.go rename to route/rule/rule_default.go index fb4f6d82b..4f5d1e8a3 100644 --- a/route/rule_default.go +++ b/route/rule/rule_default.go @@ -1,4 +1,4 @@ -package route +package rule import ( "context" @@ -17,16 +17,22 @@ func NewRule(ctx context.Context, router adapter.Router, logger log.ContextLogge if !options.DefaultOptions.IsValid() { return nil, E.New("missing conditions") } - if options.DefaultOptions.Outbound == "" && checkOutbound { - return nil, E.New("missing outbound field") + switch options.DefaultOptions.Action { + case "", C.RuleActionTypeRoute: + if options.DefaultOptions.RouteOptions.Outbound == "" && checkOutbound { + return nil, E.New("missing outbound field") + } } return NewDefaultRule(ctx, router, logger, options.DefaultOptions) case C.RuleTypeLogical: if !options.LogicalOptions.IsValid() { return nil, E.New("missing conditions") } - if options.LogicalOptions.Outbound == "" && checkOutbound { - return nil, E.New("missing outbound field") + switch options.LogicalOptions.Action { + case "", C.RuleActionTypeRoute: + if options.LogicalOptions.RouteOptions.Outbound == "" && checkOutbound { + return nil, E.New("missing outbound field") + } } return NewLogicalRule(ctx, router, logger, options.LogicalOptions) default: @@ -46,10 +52,14 @@ type RuleItem interface { } func NewDefaultRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.DefaultRule) (*DefaultRule, error) { + action, err := NewRuleAction(options.RuleAction) + if err != nil { + return nil, E.Cause(err, "action") + } rule := &DefaultRule{ abstractDefaultRule{ - invert: options.Invert, - outbound: options.Outbound, + invert: options.Invert, + action: action, }, } if len(options.Inbound) > 0 { @@ -244,27 +254,31 @@ type LogicalRule struct { } func NewLogicalRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.LogicalRule) (*LogicalRule, error) { - r := &LogicalRule{ + action, err := NewRuleAction(options.RuleAction) + if err != nil { + return nil, E.Cause(err, "action") + } + rule := &LogicalRule{ abstractLogicalRule{ - rules: make([]adapter.HeadlessRule, len(options.Rules)), - invert: options.Invert, - outbound: options.Outbound, + rules: make([]adapter.HeadlessRule, len(options.Rules)), + invert: options.Invert, + action: action, }, } switch options.Mode { case C.LogicalTypeAnd: - r.mode = C.LogicalTypeAnd + rule.mode = C.LogicalTypeAnd case C.LogicalTypeOr: - r.mode = C.LogicalTypeOr + rule.mode = C.LogicalTypeOr default: return nil, E.New("unknown logical mode: ", options.Mode) } - for i, subRule := range options.Rules { - rule, err := NewRule(ctx, router, logger, subRule, false) + for i, subOptions := range options.Rules { + subRule, err := NewRule(ctx, router, logger, subOptions, false) if err != nil { return nil, E.Cause(err, "sub rule[", i, "]") } - r.rules[i] = rule + rule.rules[i] = subRule } - return r, nil + return rule, nil } diff --git a/route/rule_dns.go b/route/rule/rule_dns.go similarity index 89% rename from route/rule_dns.go rename to route/rule/rule_dns.go index 4740488e3..6e57633d1 100644 --- a/route/rule_dns.go +++ b/route/rule/rule_dns.go @@ -1,8 +1,7 @@ -package route +package rule import ( "context" - "net/netip" "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" @@ -19,16 +18,22 @@ func NewDNSRule(ctx context.Context, router adapter.Router, logger log.ContextLo if !options.DefaultOptions.IsValid() { return nil, E.New("missing conditions") } - if options.DefaultOptions.Server == "" && checkServer { - return nil, E.New("missing server field") + switch options.DefaultOptions.Action { + case "", C.RuleActionTypeRoute: + if options.DefaultOptions.RouteOptions.Server == "" && checkServer { + return nil, E.New("missing server field") + } } return NewDefaultDNSRule(ctx, router, logger, options.DefaultOptions) case C.RuleTypeLogical: if !options.LogicalOptions.IsValid() { return nil, E.New("missing conditions") } - if options.LogicalOptions.Server == "" && checkServer { - return nil, E.New("missing server field") + switch options.LogicalOptions.Action { + case "", C.RuleActionTypeRoute: + if options.LogicalOptions.RouteOptions.Server == "" && checkServer { + return nil, E.New("missing server field") + } } return NewLogicalDNSRule(ctx, router, logger, options.LogicalOptions) default: @@ -40,20 +45,14 @@ var _ adapter.DNSRule = (*DefaultDNSRule)(nil) type DefaultDNSRule struct { abstractDefaultRule - disableCache bool - rewriteTTL *uint32 - clientSubnet *netip.Prefix } func NewDefaultDNSRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.DefaultDNSRule) (*DefaultDNSRule, error) { rule := &DefaultDNSRule{ abstractDefaultRule: abstractDefaultRule{ - invert: options.Invert, - outbound: options.Server, + invert: options.Invert, + action: NewDNSRuleAction(options.DNSRuleAction), }, - disableCache: options.DisableCache, - rewriteTTL: options.RewriteTTL, - clientSubnet: (*netip.Prefix)(options.ClientSubnet), } if len(options.Inbound) > 0 { item := NewInboundRule(options.Inbound) @@ -245,16 +244,8 @@ func NewDefaultDNSRule(ctx context.Context, router adapter.Router, logger log.Co return rule, nil } -func (r *DefaultDNSRule) DisableCache() bool { - return r.disableCache -} - -func (r *DefaultDNSRule) RewriteTTL() *uint32 { - return r.rewriteTTL -} - -func (r *DefaultDNSRule) ClientSubnet() *netip.Prefix { - return r.clientSubnet +func (r *DefaultDNSRule) Action() adapter.RuleAction { + return r.action } func (r *DefaultDNSRule) WithAddressLimit() bool { @@ -289,21 +280,15 @@ var _ adapter.DNSRule = (*LogicalDNSRule)(nil) type LogicalDNSRule struct { abstractLogicalRule - disableCache bool - rewriteTTL *uint32 - clientSubnet *netip.Prefix } func NewLogicalDNSRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.LogicalDNSRule) (*LogicalDNSRule, error) { r := &LogicalDNSRule{ abstractLogicalRule: abstractLogicalRule{ - rules: make([]adapter.HeadlessRule, len(options.Rules)), - invert: options.Invert, - outbound: options.Server, + rules: make([]adapter.HeadlessRule, len(options.Rules)), + invert: options.Invert, + action: NewDNSRuleAction(options.DNSRuleAction), }, - disableCache: options.DisableCache, - rewriteTTL: options.RewriteTTL, - clientSubnet: (*netip.Prefix)(options.ClientSubnet), } switch options.Mode { case C.LogicalTypeAnd: @@ -323,16 +308,8 @@ func NewLogicalDNSRule(ctx context.Context, router adapter.Router, logger log.Co return r, nil } -func (r *LogicalDNSRule) DisableCache() bool { - return r.disableCache -} - -func (r *LogicalDNSRule) RewriteTTL() *uint32 { - return r.rewriteTTL -} - -func (r *LogicalDNSRule) ClientSubnet() *netip.Prefix { - return r.clientSubnet +func (r *LogicalDNSRule) Action() adapter.RuleAction { + return r.action } func (r *LogicalDNSRule) WithAddressLimit() bool { diff --git a/route/rule_headless.go b/route/rule/rule_headless.go similarity index 99% rename from route/rule_headless.go rename to route/rule/rule_headless.go index 23a98c723..9ea357af8 100644 --- a/route/rule_headless.go +++ b/route/rule/rule_headless.go @@ -1,4 +1,4 @@ -package route +package rule import ( "github.com/sagernet/sing-box/adapter" diff --git a/route/rule_item_adguard.go b/route/rule/rule_item_adguard.go similarity index 98% rename from route/rule_item_adguard.go rename to route/rule/rule_item_adguard.go index bdbb3b75f..84252e606 100644 --- a/route/rule_item_adguard.go +++ b/route/rule/rule_item_adguard.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_auth_user.go b/route/rule/rule_item_auth_user.go similarity index 98% rename from route/rule_item_auth_user.go rename to route/rule/rule_item_auth_user.go index fbe053e6b..5799e3c71 100644 --- a/route/rule_item_auth_user.go +++ b/route/rule/rule_item_auth_user.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_cidr.go b/route/rule/rule_item_cidr.go similarity index 99% rename from route/rule_item_cidr.go rename to route/rule/rule_item_cidr.go index be0bb1369..c823dcf30 100644 --- a/route/rule_item_cidr.go +++ b/route/rule/rule_item_cidr.go @@ -1,4 +1,4 @@ -package route +package rule import ( "net/netip" diff --git a/route/rule_item_clash_mode.go b/route/rule/rule_item_clash_mode.go similarity index 97% rename from route/rule_item_clash_mode.go rename to route/rule/rule_item_clash_mode.go index 70141f111..aa5126cbd 100644 --- a/route/rule_item_clash_mode.go +++ b/route/rule/rule_item_clash_mode.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_client.go b/route/rule/rule_item_client.go similarity index 98% rename from route/rule_item_client.go rename to route/rule/rule_item_client.go index eeab44024..63ff41035 100644 --- a/route/rule_item_client.go +++ b/route/rule/rule_item_client.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_domain.go b/route/rule/rule_item_domain.go similarity index 99% rename from route/rule_item_domain.go rename to route/rule/rule_item_domain.go index c77890df2..b7655a794 100644 --- a/route/rule_item_domain.go +++ b/route/rule/rule_item_domain.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_domain_keyword.go b/route/rule/rule_item_domain_keyword.go similarity index 98% rename from route/rule_item_domain_keyword.go rename to route/rule/rule_item_domain_keyword.go index c6ca1e8c2..6e19a10cc 100644 --- a/route/rule_item_domain_keyword.go +++ b/route/rule/rule_item_domain_keyword.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_domain_regex.go b/route/rule/rule_item_domain_regex.go similarity index 99% rename from route/rule_item_domain_regex.go rename to route/rule/rule_item_domain_regex.go index b3555168a..b9752a45a 100644 --- a/route/rule_item_domain_regex.go +++ b/route/rule/rule_item_domain_regex.go @@ -1,4 +1,4 @@ -package route +package rule import ( "regexp" diff --git a/route/rule_item_geoip.go b/route/rule/rule_item_geoip.go similarity index 99% rename from route/rule_item_geoip.go rename to route/rule/rule_item_geoip.go index 3611613a4..3c967fec5 100644 --- a/route/rule_item_geoip.go +++ b/route/rule/rule_item_geoip.go @@ -1,4 +1,4 @@ -package route +package rule import ( "net/netip" diff --git a/route/rule_item_geosite.go b/route/rule/rule_item_geosite.go similarity index 98% rename from route/rule_item_geosite.go rename to route/rule/rule_item_geosite.go index 5fdbfe596..9e5e03c81 100644 --- a/route/rule_item_geosite.go +++ b/route/rule/rule_item_geosite.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_inbound.go b/route/rule/rule_item_inbound.go similarity index 98% rename from route/rule_item_inbound.go rename to route/rule/rule_item_inbound.go index 7e28781f0..87e84740c 100644 --- a/route/rule_item_inbound.go +++ b/route/rule/rule_item_inbound.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_ip_is_private.go b/route/rule/rule_item_ip_is_private.go similarity index 98% rename from route/rule_item_ip_is_private.go rename to route/rule/rule_item_ip_is_private.go index 6592a9d3f..e185db1db 100644 --- a/route/rule_item_ip_is_private.go +++ b/route/rule/rule_item_ip_is_private.go @@ -1,4 +1,4 @@ -package route +package rule import ( "net/netip" diff --git a/route/rule_item_ipversion.go b/route/rule/rule_item_ipversion.go similarity index 97% rename from route/rule_item_ipversion.go rename to route/rule/rule_item_ipversion.go index 3d8762b41..8ab649427 100644 --- a/route/rule_item_ipversion.go +++ b/route/rule/rule_item_ipversion.go @@ -1,4 +1,4 @@ -package route +package rule import ( "github.com/sagernet/sing-box/adapter" diff --git a/route/rule_item_network.go b/route/rule/rule_item_network.go similarity index 98% rename from route/rule_item_network.go rename to route/rule/rule_item_network.go index fc54f425d..bfb334d31 100644 --- a/route/rule_item_network.go +++ b/route/rule/rule_item_network.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_outbound.go b/route/rule/rule_item_outbound.go similarity index 98% rename from route/rule_item_outbound.go rename to route/rule/rule_item_outbound.go index 4b3e16fc6..3f37dee7f 100644 --- a/route/rule_item_outbound.go +++ b/route/rule/rule_item_outbound.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_package_name.go b/route/rule/rule_item_package_name.go similarity index 98% rename from route/rule_item_package_name.go rename to route/rule/rule_item_package_name.go index d1ca09eb2..0066735c3 100644 --- a/route/rule_item_package_name.go +++ b/route/rule/rule_item_package_name.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_port.go b/route/rule/rule_item_port.go similarity index 98% rename from route/rule_item_port.go rename to route/rule/rule_item_port.go index 624789336..af166ee64 100644 --- a/route/rule_item_port.go +++ b/route/rule/rule_item_port.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_port_range.go b/route/rule/rule_item_port_range.go similarity index 99% rename from route/rule_item_port_range.go rename to route/rule/rule_item_port_range.go index f87575f2c..980f7d239 100644 --- a/route/rule_item_port_range.go +++ b/route/rule/rule_item_port_range.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strconv" diff --git a/route/rule_item_process_name.go b/route/rule/rule_item_process_name.go similarity index 98% rename from route/rule_item_process_name.go rename to route/rule/rule_item_process_name.go index ce051666d..fa0f71651 100644 --- a/route/rule_item_process_name.go +++ b/route/rule/rule_item_process_name.go @@ -1,4 +1,4 @@ -package route +package rule import ( "path/filepath" diff --git a/route/rule_item_process_path.go b/route/rule/rule_item_process_path.go similarity index 98% rename from route/rule_item_process_path.go rename to route/rule/rule_item_process_path.go index feae4b275..75dee476d 100644 --- a/route/rule_item_process_path.go +++ b/route/rule/rule_item_process_path.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_process_path_regex.go b/route/rule/rule_item_process_path_regex.go similarity index 98% rename from route/rule_item_process_path_regex.go rename to route/rule/rule_item_process_path_regex.go index 01b2723cc..76cf67b9f 100644 --- a/route/rule_item_process_path_regex.go +++ b/route/rule/rule_item_process_path_regex.go @@ -1,4 +1,4 @@ -package route +package rule import ( "regexp" diff --git a/route/rule_item_protocol.go b/route/rule/rule_item_protocol.go similarity index 98% rename from route/rule_item_protocol.go rename to route/rule/rule_item_protocol.go index 1988f8ade..319b81d50 100644 --- a/route/rule_item_protocol.go +++ b/route/rule/rule_item_protocol.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_query_type.go b/route/rule/rule_item_query_type.go similarity index 98% rename from route/rule_item_query_type.go rename to route/rule/rule_item_query_type.go index 7b6efdd04..36b615f3a 100644 --- a/route/rule_item_query_type.go +++ b/route/rule/rule_item_query_type.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_rule_set.go b/route/rule/rule_item_rule_set.go similarity index 99% rename from route/rule_item_rule_set.go rename to route/rule/rule_item_rule_set.go index b80fca995..a0115a044 100644 --- a/route/rule_item_rule_set.go +++ b/route/rule/rule_item_rule_set.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_user.go b/route/rule/rule_item_user.go similarity index 98% rename from route/rule_item_user.go rename to route/rule/rule_item_user.go index bed97fbaa..d635fa165 100644 --- a/route/rule_item_user.go +++ b/route/rule/rule_item_user.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_user_id.go b/route/rule/rule_item_user_id.go similarity index 98% rename from route/rule_item_user_id.go rename to route/rule/rule_item_user_id.go index 43ab704e3..57372de00 100644 --- a/route/rule_item_user_id.go +++ b/route/rule/rule_item_user_id.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_wifi_bssid.go b/route/rule/rule_item_wifi_bssid.go similarity index 98% rename from route/rule_item_wifi_bssid.go rename to route/rule/rule_item_wifi_bssid.go index 3b1ff9c85..ae94bd6df 100644 --- a/route/rule_item_wifi_bssid.go +++ b/route/rule/rule_item_wifi_bssid.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_wifi_ssid.go b/route/rule/rule_item_wifi_ssid.go similarity index 98% rename from route/rule_item_wifi_ssid.go rename to route/rule/rule_item_wifi_ssid.go index 62cf935eb..3a928f77e 100644 --- a/route/rule_item_wifi_ssid.go +++ b/route/rule/rule_item_wifi_ssid.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_set.go b/route/rule/rule_set.go similarity index 60% rename from route/rule_set.go rename to route/rule/rule_set.go index a2c6d0c19..cdd0fc0af 100644 --- a/route/rule_set.go +++ b/route/rule/rule_set.go @@ -1,4 +1,4 @@ -package route +package rule import ( "context" @@ -41,3 +41,31 @@ func extractIPSetFromRule(rawRule adapter.HeadlessRule) []*netipx.IPSet { panic("unexpected rule type") } } + +func hasHeadlessRule(rules []option.HeadlessRule, cond func(rule option.DefaultHeadlessRule) bool) bool { + for _, rule := range rules { + switch rule.Type { + case C.RuleTypeDefault: + if cond(rule.DefaultOptions) { + return true + } + case C.RuleTypeLogical: + if hasHeadlessRule(rule.LogicalOptions.Rules, cond) { + return true + } + } + } + return false +} + +func isProcessHeadlessRule(rule option.DefaultHeadlessRule) bool { + return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 +} + +func isWIFIHeadlessRule(rule option.DefaultHeadlessRule) bool { + return len(rule.WIFISSID) > 0 || len(rule.WIFIBSSID) > 0 +} + +func isIPCIDRHeadlessRule(rule option.DefaultHeadlessRule) bool { + return len(rule.IPCIDR) > 0 || rule.IPSet != nil +} diff --git a/route/rule_set_local.go b/route/rule/rule_set_local.go similarity index 99% rename from route/rule_set_local.go rename to route/rule/rule_set_local.go index 4485fbad4..900c8b242 100644 --- a/route/rule_set_local.go +++ b/route/rule/rule_set_local.go @@ -1,4 +1,4 @@ -package route +package rule import ( "context" diff --git a/route/rule_set_remote.go b/route/rule/rule_set_remote.go similarity index 99% rename from route/rule_set_remote.go rename to route/rule/rule_set_remote.go index 115416092..bdafa656a 100644 --- a/route/rule_set_remote.go +++ b/route/rule/rule_set_remote.go @@ -1,4 +1,4 @@ -package route +package rule import ( "bytes" diff --git a/route/router_rule.go b/route/rule_conds.go similarity index 78% rename from route/router_rule.go rename to route/rule_conds.go index 4a99a31cc..76ed84a2e 100644 --- a/route/router_rule.go +++ b/route/rule_conds.go @@ -38,22 +38,6 @@ func hasDNSRule(rules []option.DNSRule, cond func(rule option.DefaultDNSRule) bo return false } -func hasHeadlessRule(rules []option.HeadlessRule, cond func(rule option.DefaultHeadlessRule) bool) bool { - for _, rule := range rules { - switch rule.Type { - case C.RuleTypeDefault: - if cond(rule.DefaultOptions) { - return true - } - case C.RuleTypeLogical: - if hasHeadlessRule(rule.LogicalOptions.Rules, cond) { - return true - } - } - } - return false -} - func isGeoIPRule(rule option.DefaultRule) bool { return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode) || len(rule.GeoIP) > 0 && common.Any(rule.GeoIP, notPrivateNode) } @@ -93,11 +77,3 @@ func isWIFIRule(rule option.DefaultRule) bool { func isWIFIDNSRule(rule option.DefaultDNSRule) bool { return len(rule.WIFISSID) > 0 || len(rule.WIFIBSSID) > 0 } - -func isWIFIHeadlessRule(rule option.DefaultHeadlessRule) bool { - return len(rule.WIFISSID) > 0 || len(rule.WIFIBSSID) > 0 -} - -func isIPCIDRHeadlessRule(rule option.DefaultHeadlessRule) bool { - return len(rule.IPCIDR) > 0 || rule.IPSet != nil -} diff --git a/test/brutal_test.go b/test/brutal_test.go index bfe4d1fc0..18aae2e20 100644 --- a/test/brutal_test.go +++ b/test/brutal_test.go @@ -76,9 +76,18 @@ func TestBrutalShadowsocks(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "ss-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, }, }, }, @@ -165,9 +174,18 @@ func TestBrutalTrojan(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "ss-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, }, }, }, @@ -238,9 +256,18 @@ func TestBrutalVMess(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "ss-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, }, }, }, @@ -342,9 +369,18 @@ func TestBrutalVLESS(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "ss-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, }, }, }, diff --git a/test/clash_test.go b/test/clash_test.go index ffd3e10c7..bba7f3beb 100644 --- a/test/clash_test.go +++ b/test/clash_test.go @@ -17,7 +17,7 @@ import ( "github.com/sagernet/sing/common/control" F "github.com/sagernet/sing/common/format" - "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/image" "github.com/docker/docker/client" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -68,7 +68,7 @@ func init() { } defer dockerClient.Close() - list, err := dockerClient.ImageList(context.Background(), types.ImageListOptions{All: true}) + list, err := dockerClient.ImageList(context.Background(), image.ListOptions{All: true}) if err != nil { log.Warn(err) return @@ -85,13 +85,13 @@ func init() { return false } - for _, image := range allImages { - if imageExist(image) { + for _, i := range allImages { + if imageExist(i) { continue } - log.Info("pulling image: ", image) - imageStream, err := dockerClient.ImagePull(context.Background(), image, types.ImagePullOptions{}) + log.Info("pulling image: ", i) + imageStream, err := dockerClient.ImagePull(context.Background(), i, image.PullOptions{}) if err != nil { panic(err) } diff --git a/test/direct_test.go b/test/direct_test.go index ec3cf88c0..1dbf1de1b 100644 --- a/test/direct_test.go +++ b/test/direct_test.go @@ -50,9 +50,18 @@ func _TestProxyProtocol(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "proxy-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "proxy-out", + }, + }, }, }, }, diff --git a/test/docker_test.go b/test/docker_test.go index ade813d75..a85dd12cd 100644 --- a/test/docker_test.go +++ b/test/docker_test.go @@ -11,7 +11,6 @@ import ( F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/rw" - "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/client" "github.com/docker/docker/pkg/stdcopy" @@ -85,10 +84,10 @@ func startDockerContainer(t *testing.T, options DockerOptions) { cleanContainer(dockerContainer.ID) }) - require.NoError(t, dockerClient.ContainerStart(context.Background(), dockerContainer.ID, types.ContainerStartOptions{})) + require.NoError(t, dockerClient.ContainerStart(context.Background(), dockerContainer.ID, container.StartOptions{})) if writeStdin { - stdinAttach, err := dockerClient.ContainerAttach(context.Background(), dockerContainer.ID, types.ContainerAttachOptions{ + stdinAttach, err := dockerClient.ContainerAttach(context.Background(), dockerContainer.ID, container.AttachOptions{ Stdin: writeStdin, Stream: true, }) @@ -98,7 +97,7 @@ func startDockerContainer(t *testing.T, options DockerOptions) { stdinAttach.Close() } if debug.Enabled { - attach, err := dockerClient.ContainerAttach(context.Background(), dockerContainer.ID, types.ContainerAttachOptions{ + attach, err := dockerClient.ContainerAttach(context.Background(), dockerContainer.ID, container.AttachOptions{ Stdout: true, Stderr: true, Logs: true, @@ -118,5 +117,5 @@ func cleanContainer(id string) error { return err } defer dockerClient.Close() - return dockerClient.ContainerRemove(context.Background(), id, types.ContainerRemoveOptions{Force: true}) + return dockerClient.ContainerRemove(context.Background(), id, container.RemoveOptions{Force: true}) } diff --git a/test/domain_inbound_test.go b/test/domain_inbound_test.go index f22fe2498..1ca2121da 100644 --- a/test/domain_inbound_test.go +++ b/test/domain_inbound_test.go @@ -75,9 +75,18 @@ func TestTUICDomainUDP(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "tuic-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "tuic-out", + }, + }, }, }, }, diff --git a/test/ech_test.go b/test/ech_test.go index 35d5d8915..90eae1f48 100644 --- a/test/ech_test.go +++ b/test/ech_test.go @@ -85,9 +85,18 @@ func TestECH(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "trojan-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "trojan-out", + }, + }, }, }, }, @@ -166,9 +175,18 @@ func TestECHQUIC(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "tuic-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "tuic-out", + }, + }, }, }, }, @@ -249,8 +267,16 @@ func TestECHHysteria2(t *testing.T) { { Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "hy2-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "hy2-out", + }, + }, }, }, }, diff --git a/test/go.mod b/test/go.mod index 6caf6240d..f9eefa162 100644 --- a/test/go.mod +++ b/test/go.mod @@ -1,25 +1,27 @@ module test -go 1.20 +go 1.23 + +toolchain go1.23.2 require github.com/sagernet/sing-box v0.0.0 replace github.com/sagernet/sing-box => ../ require ( - github.com/docker/docker v24.0.7+incompatible - github.com/docker/go-connections v0.4.0 - github.com/gofrs/uuid/v5 v5.2.0 - github.com/sagernet/quic-go v0.45.1-beta.2 - github.com/sagernet/sing v0.4.2 - github.com/sagernet/sing-dns v0.2.3 - github.com/sagernet/sing-quic v0.2.0-beta.12 + github.com/docker/docker v27.3.1+incompatible + github.com/docker/go-connections v0.5.0 + github.com/gofrs/uuid/v5 v5.3.0 + github.com/sagernet/quic-go v0.48.0-beta.1 + github.com/sagernet/sing v0.5.0-rc.4.0.20241022031908-cd17884118cb + github.com/sagernet/sing-dns v0.3.0-rc.2.0.20241021154031-a59e0fbba3ce + github.com/sagernet/sing-quic v0.3.0-rc.1 github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 - github.com/spyzhov/ajson v0.9.0 + github.com/spyzhov/ajson v0.9.4 github.com/stretchr/testify v1.9.0 go.uber.org/goleak v1.3.0 - golang.org/x/net v0.25.0 + golang.org/x/net v0.30.0 ) require ( @@ -28,30 +30,38 @@ require ( github.com/andybalholm/brotli v1.0.6 // indirect github.com/caddyserver/certmagic v0.20.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect + github.com/containerd/log v0.1.0 // indirect github.com/cretz/bine v0.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.5.0 // indirect - github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/gaukas/godicttls v0.0.4 // indirect - github.com/go-chi/chi/v5 v5.0.12 // indirect + github.com/go-chi/chi/v5 v5.1.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/btree v1.1.2 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect - github.com/hashicorp/yamux v0.1.1 // indirect + github.com/hashicorp/yamux v0.1.2 // indirect + github.com/josharian/native v1.1.0 // indirect github.com/klauspost/compress v1.17.4 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/libdns/alidns v1.0.3 // indirect github.com/libdns/cloudflare v0.1.1 // indirect github.com/libdns/libdns v0.2.2 // indirect github.com/logrusorgru/aurora v2.0.3+incompatible // indirect + github.com/mdlayher/netlink v1.7.2 // indirect + github.com/mdlayher/socket v0.4.1 // indirect + github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa // indirect github.com/mholt/acmez v1.2.0 // indirect - github.com/miekg/dns v1.1.59 // indirect + github.com/miekg/dns v1.1.62 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/term v0.5.0 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/onsi/ginkgo/v2 v2.9.7 // indirect @@ -65,34 +75,41 @@ require ( github.com/quic-go/qtls-go1-20 v0.4.1 // indirect github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 // indirect - github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f // indirect - github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba // indirect + github.com/sagernet/fswatch v0.1.1 // indirect + github.com/sagernet/gvisor v0.0.0-20241021032506-a4324256e4a3 // indirect + github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect + github.com/sagernet/nftables v0.3.0-beta.4 // indirect github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect - github.com/sagernet/sing-mux v0.2.0 // indirect + github.com/sagernet/sing-mux v0.2.1-0.20241020175909-fe6153f7a9ec // indirect github.com/sagernet/sing-shadowtls v0.1.4 // indirect - github.com/sagernet/sing-tun v0.3.2 // indirect + github.com/sagernet/sing-tun v0.4.0-rc.4.0.20241021153919-9ae45181180d // indirect github.com/sagernet/sing-vmess v0.1.12 // indirect github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect - github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6 // indirect - github.com/sagernet/utls v1.5.4 // indirect + github.com/sagernet/utls v1.6.7 // indirect github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 // indirect github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect - github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect + github.com/vishvananda/netns v0.0.4 // indirect github.com/zeebo/blake3 v0.2.3 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect + go.opentelemetry.io/otel v1.31.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 // indirect + go.opentelemetry.io/otel/metric v1.31.0 // indirect + go.opentelemetry.io/otel/sdk v1.31.0 // indirect + go.opentelemetry.io/otel/trace v1.31.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect - golang.org/x/crypto v0.23.0 // indirect - golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect - golang.org/x/mod v0.18.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.21.0 // indirect - golang.org/x/text v0.16.0 // indirect - golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/grpc v1.63.2 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect + golang.org/x/time v0.7.0 // indirect + golang.org/x/tools v0.24.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect + google.golang.org/grpc v1.67.1 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.1 // indirect lukechampine.com/blake3 v1.3.0 // indirect diff --git a/test/go.sum b/test/go.sum index f482438e3..e851ebd39 100644 --- a/test/go.sum +++ b/test/go.sum @@ -1,14 +1,19 @@ berty.tech/go-libtor v1.0.385 h1:RWK94C3hZj6Z2GdvePpHJLnWYobFr3bY/OdUJ5aoEXw= berty.tech/go-libtor v1.0.385/go.mod h1:9swOOQVb+kmvuAlsgWUK/4c52pm69AdbJsxLzk+fJEw= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc= github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw= github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo= github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI= @@ -17,21 +22,23 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= -github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= -github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI= +github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk= -github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= -github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= -github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= +github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= @@ -40,18 +47,26 @@ github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gofrs/uuid/v5 v5.2.0 h1:qw1GMx6/y8vhVsx626ImfKMuS5CvJmhIKKtuyvfajMM= -github.com/gofrs/uuid/v5 v5.2.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= +github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk= +github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= -github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk= github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= -github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= -github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= +github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= +github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= +github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= @@ -60,6 +75,7 @@ github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuOb github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/libdns/alidns v1.0.3 h1:LFHuGnbseq5+HCeGa1aW8awyX/4M2psB9962fdD2+yQ= github.com/libdns/alidns v1.0.3/go.mod h1:e18uAG6GanfRhcJj6/tps2rCMzQJaYVcGKT+ELjdjGE= github.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn054= @@ -69,18 +85,28 @@ github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s= github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= +github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= +github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= +github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= +github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa h1:9mcjV+RGZVC3reJBNDjjNPyS8PmFG97zq56X7WNaFO4= +github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa/go.mod h1:4tLB5c8U0CxpkFM+AJJB77jEaVDbLH5XQvy42vAGsWw= github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE= -github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs= -github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk= +github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= +github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss= github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0= github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= +github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4= github.com/ooni/go-libtor v1.1.8 h1:Wo3V3DVTxl5vZdxtQakqYP+DAHx7pPtAFSl1bnAa08w= github.com/ooni/go-libtor v1.1.8/go.mod h1:q1YyLwRD9GeMyeerVvwc0vJ2YgwDLTp2bdVcrh/JXyI= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -101,53 +127,57 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 h1:YbmpqPQEMdlk9oFSKYWRqVuu9qzNiOayIonKmv1gCXY= github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1/go.mod h1:J2yAxTFPDjrDPhuAi9aWFz2L3ox9it4qAluBBbN0H5k= -github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f h1:NkhuupzH5ch7b/Y/6ZHJWrnNLoiNnSJaow6DPb8VW2I= -github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f/go.mod h1:KXmw+ouSJNOsuRpg4wgwwCQuunrGz4yoAqQjsLjc6N0= -github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba h1:EY5AS7CCtfmARNv2zXUOrsEMPFDGYxaw65JzA2p51Vk= -github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= -github.com/sagernet/quic-go v0.45.1-beta.2 h1:zkEeCbhdFFkrxKcuIRBtXNKci/1t2J/39QSG/sPvlmc= -github.com/sagernet/quic-go v0.45.1-beta.2/go.mod h1:+N3FqM9DAzOWfe64uxXuBejVJwX7DeW7BslzLO6N/xI= +github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= +github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= +github.com/sagernet/gvisor v0.0.0-20241021032506-a4324256e4a3 h1:RxEz7LhPNiF/gX/Hg+OXr5lqsM9iVAgmaK1L1vzlDRM= +github.com/sagernet/gvisor v0.0.0-20241021032506-a4324256e4a3/go.mod h1:ehZwnT2UpmOWAHFL48XdBhnd4Qu4hN2O3Ji0us3ZHMw= +github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= +github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= +github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= +github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= +github.com/sagernet/quic-go v0.48.0-beta.1 h1:86hQZrmuoARI3BpDRkQaP0iAVpywA4YsRrzJPYuPKWg= +github.com/sagernet/quic-go v0.48.0-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/+or9YMLaG5VeTk4k= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= -github.com/sagernet/sing v0.4.2 h1:jzGNJdZVRI0xlAfFugsIQUPvyB9SuWvbJK7zQCXc4QM= -github.com/sagernet/sing v0.4.2/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls= -github.com/sagernet/sing-dns v0.2.3 h1:YzeBUn2tR38F7HtvGEQ0kLRLmZWMEgi/+7wqa4Twb1k= -github.com/sagernet/sing-dns v0.2.3/go.mod h1:BJpJv6XLnrUbSyIntOT6DG9FW0f4fETmPAHvNjOprLg= -github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo= -github.com/sagernet/sing-mux v0.2.0/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ= -github.com/sagernet/sing-quic v0.2.0-beta.12 h1:BhvA5mmrDFEyDUQB5eeu+9UhF+ieyuNJ5Rsb0dAG3QY= -github.com/sagernet/sing-quic v0.2.0-beta.12/go.mod h1:YVpLfVi8BvYM7NMrjmnvcRm3E8iMETf1gFQmTQDN9jI= +github.com/sagernet/sing v0.5.0-rc.4.0.20241022031908-cd17884118cb h1:3IhGq2UmcbQfAcuqyE8RYKFapqEEa3eItS/MrZr+5l8= +github.com/sagernet/sing v0.5.0-rc.4.0.20241022031908-cd17884118cb/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing-dns v0.3.0-rc.2.0.20241021154031-a59e0fbba3ce h1:OfpxE5qnXMyU/9LtNgX4M7bGP11lJx4s+KZ3Sijb0HE= +github.com/sagernet/sing-dns v0.3.0-rc.2.0.20241021154031-a59e0fbba3ce/go.mod h1:TqLIelI+FAbVEdiTRolhGLOwvhVjY7oT+wezlOJUQ7M= +github.com/sagernet/sing-mux v0.2.1-0.20241020175909-fe6153f7a9ec h1:6Fd/VsEsw9qIjaGi1IBTZSb4b4v5JYtNcoiBtGsQC48= +github.com/sagernet/sing-mux v0.2.1-0.20241020175909-fe6153f7a9ec/go.mod h1:RSwqqHwbtTOX3vs6ms8vMtBGH/0ZNyLm/uwt6TlmR84= +github.com/sagernet/sing-quic v0.3.0-rc.1 h1:SlzL1yfEAKJyRduub8vzOVtbyTLAX7RZEEBZxO5utts= +github.com/sagernet/sing-quic v0.3.0-rc.1/go.mod h1:uX+aUHA0fgIN6U3WViseDpSdTQriViZ7qz0Wbsf1mNQ= github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8= github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE= github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg= github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= -github.com/sagernet/sing-tun v0.3.2 h1:z0bLUT/YXH9RrJS9DsIpB0Bb9afl2hVJOmHd0zA3HJY= -github.com/sagernet/sing-tun v0.3.2/go.mod h1:DxLIyhjWU/HwGYoX0vNGg2c5QgTQIakphU1MuERR5tQ= +github.com/sagernet/sing-tun v0.4.0-rc.4.0.20241021153919-9ae45181180d h1:zWcIQM3eAKJGzy7zhqkO7zm7ZI890OdR4vSwx2mevS0= +github.com/sagernet/sing-tun v0.4.0-rc.4.0.20241021153919-9ae45181180d/go.mod h1:Xhv+Mz2nE7HZTwResni8EtTa7AMJz/S6uQLT5lV23M8= github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg= github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo= -github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6 h1:z3SJQhVyU63FT26Wn/UByW6b7q8QKB0ZkPqsyqcz2PI= -github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6/go.mod h1:73xRZuxwkFk4aiLw28hG8W6o9cr2UPrGL9pdY2UTbvY= -github.com/sagernet/utls v1.5.4 h1:KmsEGbB2dKUtCNC+44NwAdNAqnqQ6GA4pTO0Yik56co= -github.com/sagernet/utls v1.5.4/go.mod h1:CTGxPWExIloRipK3XFpYv0OVyhO8kk3XCGW/ieyTh1s= +github.com/sagernet/utls v1.6.7 h1:Ep3+aJ8FUGGta+II2IEVNUc3EDhaRCZINWkj/LloIA8= +github.com/sagernet/utls v1.6.7/go.mod h1:Uua1TKO/FFuAhLr9rkaVnnrTmmiItzDjv1BUb2+ERwM= github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 h1:R0OMYAScomNAVpTfbHFpxqJpvwuhxSRi+g6z7gZhABs= github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8/go.mod h1:K4J7/npM+VAMUeUmTa2JaA02JmyheP0GpRBOUvn3ecc= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA= -github.com/spyzhov/ajson v0.9.0 h1:tF46gJGOenYVj+k9K1U1XpCxVWhmiyY5PsVCAs1+OJ0= -github.com/spyzhov/ajson v0.9.0/go.mod h1:a6oSw0MMb7Z5aD2tPoPO+jq11ETKgXUr2XktHdT8Wt8= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spyzhov/ajson v0.9.4 h1:MVibcTCgO7DY4IlskdqIlCmDOsUOZ9P7oKj8ifdcf84= +github.com/spyzhov/ajson v0.9.4/go.mod h1:a6oSw0MMb7Z5aD2tPoPO+jq11ETKgXUr2XktHdT8Wt8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= -github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= +github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= @@ -156,6 +186,22 @@ github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= +go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= +go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 h1:lUsI2TYsQw2r1IASwoROaCnjdj2cvC2+Jbxvk6nHnWU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0/go.mod h1:2HpZxxQurfGxJlJDblybejHB6RX6pmExPNe517hREw4= +go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= +go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= +go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= +go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= +go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= +go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -169,31 +215,30 @@ golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaE golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= -golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= -golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -201,35 +246,39 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= +golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= -google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= -google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg= +google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/http_test.go b/test/http_test.go index 4b5fe70fc..88385c275 100644 --- a/test/http_test.go +++ b/test/http_test.go @@ -49,9 +49,18 @@ func TestHTTPSelf(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "http-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "http-out", + }, + }, }, }, }, diff --git a/test/hysteria2_test.go b/test/hysteria2_test.go index f54944281..9ca2f5d38 100644 --- a/test/hysteria2_test.go +++ b/test/hysteria2_test.go @@ -94,8 +94,16 @@ func testHysteria2Self(t *testing.T, salamanderPassword string) { { Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "hy2-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "hy2-out", + }, + }, }, }, }, diff --git a/test/hysteria_test.go b/test/hysteria_test.go index 90ff62dd1..bde1b9fa7 100644 --- a/test/hysteria_test.go +++ b/test/hysteria_test.go @@ -75,9 +75,18 @@ func TestHysteriaSelf(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "hy-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "hy-out", + }, + }, }, }, }, diff --git a/test/inbound_detour_test.go b/test/inbound_detour_test.go index c2ef57a56..9505c217f 100644 --- a/test/inbound_detour_test.go +++ b/test/inbound_detour_test.go @@ -80,9 +80,18 @@ func TestChainedInbound(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "ss-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, }, }, }, diff --git a/test/mux_cool_test.go b/test/mux_cool_test.go index ef47695a5..81130fad9 100644 --- a/test/mux_cool_test.go +++ b/test/mux_cool_test.go @@ -159,9 +159,18 @@ func TestMuxCoolSelf(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "vmess-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "vmess-out", + }, + }, }, }, }, diff --git a/test/mux_test.go b/test/mux_test.go index c02f27087..8d7551855 100644 --- a/test/mux_test.go +++ b/test/mux_test.go @@ -102,9 +102,18 @@ func testShadowsocksMux(t *testing.T, options option.OutboundMultiplexOptions) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "ss-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, }, }, }, @@ -166,9 +175,18 @@ func testVMessMux(t *testing.T, options option.OutboundMultiplexOptions) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "vmess-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "vmess-out", + }, + }, }, }, }, diff --git a/test/shadowsocks_test.go b/test/shadowsocks_test.go index 7d063d9a9..4ef1ee9d9 100644 --- a/test/shadowsocks_test.go +++ b/test/shadowsocks_test.go @@ -197,9 +197,18 @@ func testShadowsocksSelf(t *testing.T, method string, password string) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "ss-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, }, }, }, @@ -258,9 +267,18 @@ func TestShadowsocksUoT(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "ss-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, }, }, }, @@ -319,9 +337,18 @@ func testShadowsocks2022EIH(t *testing.T, method string, password string) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "ss-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, }, }, }, diff --git a/test/shadowtls_test.go b/test/shadowtls_test.go index 2f53d46ad..6f9ee1e56 100644 --- a/test/shadowtls_test.go +++ b/test/shadowtls_test.go @@ -118,12 +118,23 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool) }, }, Route: &option.RouteOptions{ - Rules: []option.Rule{{ - DefaultOptions: option.DefaultRule{ - Inbound: []string{"detour"}, - Outbound: "direct", + Rules: []option.Rule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"detour"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "direct", + }, + }, + }, }, - }}, + }, }, }) testTCP(t, clientPort, testPort) @@ -239,12 +250,23 @@ func TestShadowTLSInbound(t *testing.T) { }, }, Route: &option.RouteOptions{ - Rules: []option.Rule{{ - DefaultOptions: option.DefaultRule{ - Inbound: []string{"in"}, - Outbound: "out", + Rules: []option.Rule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "out", + }, + }, + }, }, - }}, + }, }, }) testTCP(t, clientPort, testPort) @@ -319,12 +341,23 @@ func TestShadowTLSOutbound(t *testing.T) { }, }, Route: &option.RouteOptions{ - Rules: []option.Rule{{ - DefaultOptions: option.DefaultRule{ - Inbound: []string{"detour"}, - Outbound: "direct", + Rules: []option.Rule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"detour"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "direct", + }, + }, + }, }, - }}, + }, }, }) testTCP(t, clientPort, testPort) diff --git a/test/tfo_test.go b/test/tfo_test.go index cc97e189d..7bd34e2db 100644 --- a/test/tfo_test.go +++ b/test/tfo_test.go @@ -60,9 +60,18 @@ func TestTCPSlowOpen(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "ss-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, }, }, }, diff --git a/test/tls_test.go b/test/tls_test.go index da55faf5b..cfc6c1a50 100644 --- a/test/tls_test.go +++ b/test/tls_test.go @@ -76,9 +76,18 @@ func TestUTLS(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "trojan-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "trojan-out", + }, + }, }, }, }, diff --git a/test/trojan_test.go b/test/trojan_test.go index d8659b2e4..f88ec8850 100644 --- a/test/trojan_test.go +++ b/test/trojan_test.go @@ -118,9 +118,18 @@ func TestTrojanSelf(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "trojan-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "trojan-out", + }, + }, }, }, }, @@ -177,9 +186,18 @@ func TestTrojanPlainSelf(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "trojan-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "trojan-out", + }, + }, }, }, }, diff --git a/test/tuic_test.go b/test/tuic_test.go index c2b71111f..5b838f22f 100644 --- a/test/tuic_test.go +++ b/test/tuic_test.go @@ -90,9 +90,18 @@ func testTUICSelf(t *testing.T, udpStream bool, zeroRTTHandshake bool) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "tuic-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "tuic-out", + }, + }, }, }, }, diff --git a/test/v2ray_transport_test.go b/test/v2ray_transport_test.go index 2f39d18ae..c7362f345 100644 --- a/test/v2ray_transport_test.go +++ b/test/v2ray_transport_test.go @@ -108,9 +108,18 @@ func testVMessTransportSelf(t *testing.T, server *option.V2RayTransportOptions, Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "vmess-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "vmess-out", + }, + }, }, }, }, @@ -187,9 +196,18 @@ func testTrojanTransportSelf(t *testing.T, server *option.V2RayTransportOptions, Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "vmess-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "vmess-out", + }, + }, }, }, }, @@ -270,9 +288,18 @@ func TestVMessQUICSelf(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "vmess-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "vmess-out", + }, + }, }, }, }, @@ -334,9 +361,18 @@ func testV2RayTransportNOTLSSelf(t *testing.T, transport *option.V2RayTransportO Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "vmess-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "vmess-out", + }, + }, }, }, }, diff --git a/test/vmess_test.go b/test/vmess_test.go index cc7879ab8..fcf7bf8f4 100644 --- a/test/vmess_test.go +++ b/test/vmess_test.go @@ -315,9 +315,17 @@ func testVMessSelf(t *testing.T, security string, alterId int, globalPadding boo Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "vmess-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.RouteActionOptions{ + Outbound: "vmess-out", + }, + }, }, }, }, diff --git a/transport/v2ray/grpc.go b/transport/v2ray/grpc.go index 05bc5a2a4..1b4250add 100644 --- a/transport/v2ray/grpc.go +++ b/transport/v2ray/grpc.go @@ -10,15 +10,16 @@ import ( "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/transport/v2raygrpc" "github.com/sagernet/sing-box/transport/v2raygrpclite" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) -func NewGRPCServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { +func NewGRPCServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { if options.ForceLite { - return v2raygrpclite.NewServer(ctx, options, tlsConfig, handler) + return v2raygrpclite.NewServer(ctx, logger, options, tlsConfig, handler) } - return v2raygrpc.NewServer(ctx, options, tlsConfig, handler) + return v2raygrpc.NewServer(ctx, logger, options, tlsConfig, handler) } func NewGRPCClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayGRPCOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) { diff --git a/transport/v2ray/grpc_lite.go b/transport/v2ray/grpc_lite.go index 94f6fad13..4f2814a72 100644 --- a/transport/v2ray/grpc_lite.go +++ b/transport/v2ray/grpc_lite.go @@ -9,12 +9,13 @@ import ( "github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/transport/v2raygrpclite" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) -func NewGRPCServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { - return v2raygrpclite.NewServer(ctx, options, tlsConfig, handler) +func NewGRPCServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { + return v2raygrpclite.NewServer(ctx, logger, options, tlsConfig, handler) } func NewGRPCClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayGRPCOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) { diff --git a/transport/v2ray/quic.go b/transport/v2ray/quic.go index 5471157af..4d3cdc6f8 100644 --- a/transport/v2ray/quic.go +++ b/transport/v2ray/quic.go @@ -7,6 +7,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) @@ -21,11 +22,11 @@ func RegisterQUICConstructor(server ServerConstructor[option.V2RayQUICOptions], quicClientConstructor = client } -func NewQUICServer(ctx context.Context, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { +func NewQUICServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { if quicServerConstructor == nil { return nil, os.ErrInvalid } - return quicServerConstructor(ctx, options, tlsConfig, handler) + return quicServerConstructor(ctx, logger, options, tlsConfig, handler) } func NewQUICClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayQUICOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) { diff --git a/transport/v2ray/transport.go b/transport/v2ray/transport.go index deb8a7f0e..ab52f55e8 100644 --- a/transport/v2ray/transport.go +++ b/transport/v2ray/transport.go @@ -11,33 +11,34 @@ import ( "github.com/sagernet/sing-box/transport/v2rayhttpupgrade" "github.com/sagernet/sing-box/transport/v2raywebsocket" 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" ) type ( - ServerConstructor[O any] func(ctx context.Context, options O, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) + ServerConstructor[O any] func(ctx context.Context, logger logger.ContextLogger, options O, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) ClientConstructor[O any] func(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options O, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) ) -func NewServerTransport(ctx context.Context, options option.V2RayTransportOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { +func NewServerTransport(ctx context.Context, logger logger.ContextLogger, options option.V2RayTransportOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { if options.Type == "" { return nil, nil } switch options.Type { case C.V2RayTransportTypeHTTP: - return v2rayhttp.NewServer(ctx, options.HTTPOptions, tlsConfig, handler) + return v2rayhttp.NewServer(ctx, logger, options.HTTPOptions, tlsConfig, handler) case C.V2RayTransportTypeWebsocket: - return v2raywebsocket.NewServer(ctx, options.WebsocketOptions, tlsConfig, handler) + return v2raywebsocket.NewServer(ctx, logger, options.WebsocketOptions, tlsConfig, handler) case C.V2RayTransportTypeQUIC: if tlsConfig == nil { return nil, C.ErrTLSRequired } - return NewQUICServer(ctx, options.QUICOptions, tlsConfig, handler) + return NewQUICServer(ctx, logger, options.QUICOptions, tlsConfig, handler) case C.V2RayTransportTypeGRPC: - return NewGRPCServer(ctx, options.GRPCOptions, tlsConfig, handler) + return NewGRPCServer(ctx, logger, options.GRPCOptions, tlsConfig, handler) case C.V2RayTransportTypeHTTPUpgrade: - return v2rayhttpupgrade.NewServer(ctx, options.HTTPUpgradeOptions, tlsConfig, handler) + return v2rayhttpupgrade.NewServer(ctx, logger, options.HTTPUpgradeOptions, tlsConfig, handler) default: return nil, E.New("unknown transport type: " + options.Type) } diff --git a/transport/v2raygrpc/client.go b/transport/v2raygrpc/client.go index 1e72040a1..af922b45c 100644 --- a/transport/v2raygrpc/client.go +++ b/transport/v2raygrpc/client.go @@ -105,7 +105,7 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) { cancel(err) return nil, err } - return NewGRPCConn(stream, cancel), nil + return NewGRPCConn(stream), nil } func (c *Client) Close() error { diff --git a/transport/v2raygrpc/conn.go b/transport/v2raygrpc/conn.go index bc78f91e3..0a0a627f7 100644 --- a/transport/v2raygrpc/conn.go +++ b/transport/v2raygrpc/conn.go @@ -5,7 +5,6 @@ import ( "os" "time" - "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/baderror" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" @@ -15,17 +14,15 @@ var _ net.Conn = (*GRPCConn)(nil) type GRPCConn struct { GunService - cancel common.ContextCancelCauseFunc - cache []byte + cache []byte } -func NewGRPCConn(service GunService, cancel common.ContextCancelCauseFunc) *GRPCConn { +func NewGRPCConn(service GunService) *GRPCConn { if client, isClient := service.(GunService_TunClient); isClient { service = &clientConnWrapper{client} } return &GRPCConn{ GunService: service, - cancel: cancel, } } @@ -38,7 +35,6 @@ func (c *GRPCConn) Read(b []byte) (n int, err error) { hunk, err := c.Recv() err = baderror.WrapGRPC(err) if err != nil { - c.cancel(err) return } n = copy(b, hunk.Data) @@ -51,14 +47,12 @@ func (c *GRPCConn) Read(b []byte) (n int, err error) { func (c *GRPCConn) Write(b []byte) (n int, err error) { err = baderror.WrapGRPC(c.Send(&Hunk{Data: b})) if err != nil { - c.cancel(err) return } return len(b), nil } func (c *GRPCConn) Close() error { - c.cancel(net.ErrClosed) return nil } diff --git a/transport/v2raygrpc/server.go b/transport/v2raygrpc/server.go index 15088b268..b6b13f829 100644 --- a/transport/v2raygrpc/server.go +++ b/transport/v2raygrpc/server.go @@ -9,8 +9,10 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" @@ -25,11 +27,12 @@ var _ adapter.V2RayServerTransport = (*Server)(nil) type Server struct { ctx context.Context - handler N.TCPConnectionHandler + logger logger.ContextLogger + handler adapter.V2RayServerTransportHandler server *grpc.Server } -func NewServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler N.TCPConnectionHandler) (*Server, error) { +func NewServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { var serverOptions []grpc.ServerOption if tlsConfig != nil { if !common.Contains(tlsConfig.NextProtos(), http2.NextProtoTLS) { @@ -43,17 +46,16 @@ func NewServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig t Timeout: time.Duration(options.PingTimeout), })) } - server := &Server{ctx, handler, grpc.NewServer(serverOptions...)} + server := &Server{ctx, logger, handler, grpc.NewServer(serverOptions...)} RegisterGunServiceCustomNameServer(server.server, server, options.ServiceName) return server, nil } func (s *Server) Tun(server GunService_TunServer) error { - ctx, cancel := common.ContextWithCancelCause(s.ctx) - conn := NewGRPCConn(server, cancel) - var metadata M.Metadata + conn := NewGRPCConn(server) + var source M.Socksaddr if remotePeer, loaded := peer.FromContext(server.Context()); loaded { - metadata.Source = M.SocksaddrFromNet(remotePeer.Addr) + source = M.SocksaddrFromNet(remotePeer.Addr) } if grpcMetadata, loaded := gM.FromIncomingContext(server.Context()); loaded { forwardFrom := strings.Join(grpcMetadata.Get("X-Forwarded-For"), ",") @@ -61,13 +63,16 @@ func (s *Server) Tun(server GunService_TunServer) error { for _, from := range strings.Split(forwardFrom, ",") { originAddr := M.ParseSocksaddr(from) if originAddr.IsValid() { - metadata.Source = originAddr.Unwrap() + source = originAddr.Unwrap() } } } } - go s.handler.NewConnection(ctx, conn, metadata) - <-ctx.Done() + done := make(chan struct{}) + go s.handler.NewConnectionEx(log.ContextWithNewID(s.ctx), conn, source, M.Socksaddr{}, N.OnceClose(func(it error) { + close(done) + })) + <-done return nil } diff --git a/transport/v2raygrpclite/server.go b/transport/v2raygrpclite/server.go index 6d3e42ebe..622d785ab 100644 --- a/transport/v2raygrpclite/server.go +++ b/transport/v2raygrpclite/server.go @@ -10,10 +10,12 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/transport/v2rayhttp" "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" aTLS "github.com/sagernet/sing/common/tls" @@ -26,18 +28,19 @@ import ( var _ adapter.V2RayServerTransport = (*Server)(nil) type Server struct { - tlsConfig tls.ServerConfig - handler adapter.V2RayServerTransportHandler - errorHandler E.Handler - httpServer *http.Server - h2Server *http2.Server - h2cHandler http.Handler - path string + tlsConfig tls.ServerConfig + logger logger.ContextLogger + handler adapter.V2RayServerTransportHandler + httpServer *http.Server + h2Server *http2.Server + h2cHandler http.Handler + path string } -func NewServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { +func NewServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { server := &Server{ tlsConfig: tlsConfig, + logger: logger, handler: handler, path: "/" + options.ServiceName + "/Tun", h2Server: &http2.Server{ @@ -49,6 +52,9 @@ func NewServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig t BaseContext: func(net.Listener) context.Context { return ctx }, + ConnContext: func(ctx context.Context, c net.Conn) context.Context { + return log.ContextWithNewID(ctx) + }, } server.h2cHandler = h2c.NewHandler(server, server.h2Server) return server, nil @@ -74,10 +80,12 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) { writer.Header().Set("Content-Type", "application/grpc") writer.Header().Set("TE", "trailers") writer.WriteHeader(http.StatusOK) - var metadata M.Metadata - metadata.Source = sHttp.SourceAddress(request) + done := make(chan struct{}) conn := v2rayhttp.NewHTTP2Wrapper(newGunConn(request.Body, writer, writer.(http.Flusher))) - s.handler.NewConnection(request.Context(), conn, metadata) + s.handler.NewConnectionEx(request.Context(), conn, sHttp.SourceAddress(request), M.Socksaddr{}, N.OnceClose(func(it error) { + close(done) + })) + <-done conn.CloseWrapper() } @@ -85,7 +93,7 @@ func (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Reques if statusCode > 0 { writer.WriteHeader(statusCode) } - s.handler.NewError(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr)) + s.logger.ErrorContext(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr)) } func (s *Server) Network() []string { diff --git a/transport/v2rayhttp/server.go b/transport/v2rayhttp/server.go index cad7d906b..e0ee42a77 100644 --- a/transport/v2rayhttp/server.go +++ b/transport/v2rayhttp/server.go @@ -11,11 +11,13 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" 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" aTLS "github.com/sagernet/sing/common/tls" @@ -29,6 +31,7 @@ var _ adapter.V2RayServerTransport = (*Server)(nil) type Server struct { ctx context.Context + logger logger.ContextLogger tlsConfig tls.ServerConfig handler adapter.V2RayServerTransportHandler httpServer *http.Server @@ -40,7 +43,7 @@ type Server struct { headers http.Header } -func NewServer(ctx context.Context, options option.V2RayHTTPOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { +func NewServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayHTTPOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { server := &Server{ ctx: ctx, tlsConfig: tlsConfig, @@ -63,6 +66,9 @@ func NewServer(ctx context.Context, options option.V2RayHTTPOptions, tlsConfig t BaseContext: func(net.Listener) context.Context { return ctx }, + ConnContext: func(ctx context.Context, c net.Conn) context.Context { + return log.ContextWithNewID(ctx) + }, } server.h2cHandler = h2c.NewHandler(server, server.h2Server) return server, nil @@ -95,8 +101,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) { } } - var metadata M.Metadata - metadata.Source = sHttp.SourceAddress(request) + source := sHttp.SourceAddress(request) if h, ok := writer.(http.Hijacker); ok { var requestBody *buf.Buffer if contentLength := int(request.ContentLength); contentLength > 0 { @@ -127,14 +132,18 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) { if requestBody != nil { conn = bufio.NewCachedConn(conn, requestBody) } - s.handler.NewConnection(request.Context(), conn, metadata) + s.handler.NewConnectionEx(request.Context(), conn, source, M.Socksaddr{}, nil) } else { writer.WriteHeader(http.StatusOK) + done := make(chan struct{}) conn := NewHTTP2Wrapper(&ServerHTTPConn{ NewHTTPConn(request.Body, writer), writer.(http.Flusher), }) - s.handler.NewConnection(request.Context(), conn, metadata) + s.handler.NewConnectionEx(request.Context(), conn, source, M.Socksaddr{}, N.OnceClose(func(it error) { + close(done) + })) + <-done conn.CloseWrapper() } } @@ -143,7 +152,7 @@ func (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Reques if statusCode > 0 { writer.WriteHeader(statusCode) } - s.handler.NewError(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr)) + s.logger.ErrorContext(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr)) } func (s *Server) Network() []string { diff --git a/transport/v2rayhttpupgrade/server.go b/transport/v2rayhttpupgrade/server.go index a3b5d23ed..6a42912e9 100644 --- a/transport/v2rayhttpupgrade/server.go +++ b/transport/v2rayhttpupgrade/server.go @@ -10,9 +10,11 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/tls" 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" aTLS "github.com/sagernet/sing/common/tls" @@ -23,6 +25,7 @@ var _ adapter.V2RayServerTransport = (*Server)(nil) type Server struct { ctx context.Context + logger logger.ContextLogger tlsConfig tls.ServerConfig handler adapter.V2RayServerTransportHandler httpServer *http.Server @@ -31,7 +34,7 @@ type Server struct { headers http.Header } -func NewServer(ctx context.Context, options option.V2RayHTTPUpgradeOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { +func NewServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayHTTPUpgradeOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { server := &Server{ ctx: ctx, tlsConfig: tlsConfig, @@ -50,6 +53,9 @@ func NewServer(ctx context.Context, options option.V2RayHTTPUpgradeOptions, tlsC BaseContext: func(net.Listener) context.Context { return ctx }, + ConnContext: func(ctx context.Context, c net.Conn) context.Context { + return log.ContextWithNewID(ctx) + }, TLSNextProto: make(map[string]func(*http.Server, *tls.STDConn, http.Handler)), } return server, nil @@ -104,16 +110,14 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) { s.invalidRequest(writer, request, http.StatusInternalServerError, E.Cause(err, "hijack failed")) return } - var metadata M.Metadata - metadata.Source = sHttp.SourceAddress(request) - s.handler.NewConnection(request.Context(), conn, metadata) + s.handler.NewConnectionEx(request.Context(), conn, sHttp.SourceAddress(request), M.Socksaddr{}, nil) } func (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Request, statusCode int, err error) { if statusCode > 0 { writer.WriteHeader(statusCode) } - s.handler.NewError(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr)) + s.logger.ErrorContext(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr)) } func (s *Server) Network() []string { diff --git a/transport/v2rayquic/server.go b/transport/v2rayquic/server.go index f77210301..4c4397e6b 100644 --- a/transport/v2rayquic/server.go +++ b/transport/v2rayquic/server.go @@ -15,6 +15,8 @@ import ( "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-quic" "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" ) @@ -23,6 +25,7 @@ var _ adapter.V2RayServerTransport = (*Server)(nil) type Server struct { ctx context.Context + logger logger.ContextLogger tlsConfig tls.ServerConfig quicConfig *quic.Config handler adapter.V2RayServerTransportHandler @@ -30,7 +33,7 @@ type Server struct { quicListener qtls.Listener } -func NewServer(ctx context.Context, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { +func NewServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { quicConfig := &quic.Config{ DisablePathMTUDiscovery: !C.IsLinux && !C.IsWindows, } @@ -39,6 +42,7 @@ func NewServer(ctx context.Context, options option.V2RayQUICOptions, tlsConfig t } server := &Server{ ctx: ctx, + logger: logger, tlsConfig: tlsConfig, quicConfig: quicConfig, handler: handler, @@ -73,8 +77,8 @@ func (s *Server) acceptLoop() { } go func() { hErr := s.streamAcceptLoop(conn) - if hErr != nil { - s.handler.NewError(conn.Context(), hErr) + if hErr != nil && !E.IsClosedOrCanceled(hErr) { + s.logger.ErrorContext(conn.Context(), hErr) } }() } @@ -86,7 +90,7 @@ func (s *Server) streamAcceptLoop(conn quic.Connection) error { if err != nil { return err } - go s.handler.NewConnection(conn.Context(), &StreamWrapper{Conn: conn, Stream: stream}, M.Metadata{}) + go s.handler.NewConnectionEx(conn.Context(), &StreamWrapper{Conn: conn, Stream: stream}, M.SocksaddrFromNet(conn.RemoteAddr()), M.Socksaddr{}, nil) } } diff --git a/transport/v2raywebsocket/server.go b/transport/v2raywebsocket/server.go index 86f2de9cd..ccabf086a 100644 --- a/transport/v2raywebsocket/server.go +++ b/transport/v2raywebsocket/server.go @@ -11,11 +11,13 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" 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" aTLS "github.com/sagernet/sing/common/tls" @@ -27,6 +29,7 @@ var _ adapter.V2RayServerTransport = (*Server)(nil) type Server struct { ctx context.Context + logger logger.ContextLogger tlsConfig tls.ServerConfig handler adapter.V2RayServerTransportHandler httpServer *http.Server @@ -36,9 +39,10 @@ type Server struct { upgrader ws.HTTPUpgrader } -func NewServer(ctx context.Context, options option.V2RayWebsocketOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { +func NewServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayWebsocketOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { server := &Server{ ctx: ctx, + logger: logger, tlsConfig: tlsConfig, handler: handler, path: options.Path, @@ -59,6 +63,9 @@ func NewServer(ctx context.Context, options option.V2RayWebsocketOptions, tlsCon BaseContext: func(net.Listener) context.Context { return ctx }, + ConnContext: func(ctx context.Context, c net.Conn) context.Context { + return log.ContextWithNewID(ctx) + }, } return server, nil } @@ -102,20 +109,19 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) { s.invalidRequest(writer, request, 0, E.Cause(err, "upgrade websocket connection")) return } - var metadata M.Metadata - metadata.Source = sHttp.SourceAddress(request) - conn = NewConn(wsConn, metadata.Source.TCPAddr(), ws.StateServerSide) + source := sHttp.SourceAddress(request) + conn = NewConn(wsConn, source, ws.StateServerSide) if len(earlyData) > 0 { conn = bufio.NewCachedConn(conn, buf.As(earlyData)) } - s.handler.NewConnection(request.Context(), conn, metadata) + s.handler.NewConnectionEx(request.Context(), conn, source, M.Socksaddr{}, nil) } func (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Request, statusCode int, err error) { if statusCode > 0 { writer.WriteHeader(statusCode) } - s.handler.NewError(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr)) + s.logger.ErrorContext(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr)) } func (s *Server) Network() []string { diff --git a/transport/wireguard/device_stack.go b/transport/wireguard/device_stack.go index d5770419e..61286e6a9 100644 --- a/transport/wireguard/device_stack.go +++ b/transport/wireguard/device_stack.go @@ -252,6 +252,9 @@ func (ep *wireEndpoint) MTU() uint32 { return ep.mtu } +func (ep *wireEndpoint) SetMTU(mtu uint32) { +} + func (ep *wireEndpoint) MaxHeaderLength() uint16 { return 0 } @@ -260,6 +263,9 @@ func (ep *wireEndpoint) LinkAddress() tcpip.LinkAddress { return "" } +func (ep *wireEndpoint) SetLinkAddress(addr tcpip.LinkAddress) { +} + func (ep *wireEndpoint) Capabilities() stack.LinkEndpointCapabilities { return stack.CapabilityRXChecksumOffload } @@ -297,3 +303,9 @@ func (ep *wireEndpoint) WritePackets(list stack.PacketBufferList) (int, tcpip.Er } return list.Len(), nil } + +func (ep *wireEndpoint) Close() { +} + +func (ep *wireEndpoint) SetOnCloseAction(f func()) { +} From c2b833a2287190c871512974f5c33479df9724df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 22 Oct 2024 21:28:22 +0800 Subject: [PATCH 02/39] Implement TCP and ICMP rejects --- adapter/router.go | 1 + constant/rule.go | 9 ++++-- inbound/tun.go | 12 ++++++-- option/rule_action.go | 24 +++++++++------ route/route.go | 62 +++++++++++++++++++++++++-------------- route/rule/rule_action.go | 26 ++++++++++++++-- 6 files changed, 94 insertions(+), 40 deletions(-) diff --git a/adapter/router.go b/adapter/router.go index 134c94420..c9cd46e91 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -34,6 +34,7 @@ type Router interface { FakeIPStore() FakeIPStore ConnectionRouter + PreMatch(metadata InboundContext) error ConnectionRouterEx GeoIPReader() *geoip.Reader diff --git a/constant/rule.go b/constant/rule.go index 02e03d8af..e662cd5ce 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -33,7 +33,10 @@ const ( ) const ( - RuleActionRejectMethodDefault = "default" - RuleActionRejectMethodPortUnreachable = "port-unreachable" - RuleActionRejectMethodDrop = "drop" + RuleActionRejectMethodDefault = "default" + RuleActionRejectMethodReset = "reset" + RuleActionRejectMethodNetworkUnreachable = "network-unreachable" + RuleActionRejectMethodHostUnreachable = "host-unreachable" + RuleActionRejectMethodPortUnreachable = "port-unreachable" + RuleActionRejectMethodDrop = "drop" ) diff --git a/inbound/tun.go b/inbound/tun.go index 0d856419e..f04ae71b3 100644 --- a/inbound/tun.go +++ b/inbound/tun.go @@ -404,9 +404,15 @@ func (t *TUN) Close() error { ) } -func (t *TUN) PrepareConnection(source M.Socksaddr, destination M.Socksaddr) error { - // TODO: implement rejects - return nil +func (t *TUN) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr) error { + return t.router.PreMatch(adapter.InboundContext{ + Inbound: t.tag, + InboundType: C.TypeTun, + Network: network, + Source: source, + Destination: destination, + InboundOptions: t.inboundOptions, + }) } func (t *TUN) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { diff --git a/option/rule_action.go b/option/rule_action.go index 4f0ec177b..f446d81d5 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -136,23 +136,29 @@ type DNSRouteActionOptions struct { ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` } -type RejectActionOptions struct { - Method RejectMethod `json:"method,omitempty"` +type _RejectActionOptions struct { + Method string `json:"method,omitempty"` } -type RejectMethod string +type RejectActionOptions _RejectActionOptions -func (m *RejectMethod) UnmarshalJSON(bytes []byte) error { - err := json.Unmarshal(bytes, (*string)(m)) +func (r *RejectActionOptions) UnmarshalJSON(bytes []byte) error { + err := json.Unmarshal(bytes, (*_RejectActionOptions)(r)) if err != nil { return err } - switch *m { - case C.RuleActionRejectMethodDefault, C.RuleActionRejectMethodPortUnreachable, C.RuleActionRejectMethodDrop: - return nil + switch r.Method { + case "", C.RuleActionRejectMethodDefault: + r.Method = C.RuleActionRejectMethodDefault + case C.RuleActionRejectMethodReset, + C.RuleActionRejectMethodNetworkUnreachable, + C.RuleActionRejectMethodHostUnreachable, + C.RuleActionRejectMethodPortUnreachable, + C.RuleActionRejectMethodDrop: default: - return E.New("unknown reject method: " + *m) + return E.New("unknown reject method: " + r.Method) } + return nil } type RouteActionSniff struct { diff --git a/route/route.go b/route/route.go index 86d4d95cc..cecd0f2ae 100644 --- a/route/route.go +++ b/route/route.go @@ -21,7 +21,6 @@ import ( "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing-dns" "github.com/sagernet/sing-mux" - "github.com/sagernet/sing-tun" "github.com/sagernet/sing-vmess" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" @@ -89,7 +88,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad if deadline.NeedAdditionalReadDeadline(conn) { conn = deadline.NewConn(conn) } - selectedRule, _, buffers, err := r.matchRule(ctx, &metadata, conn, nil, -1) + selectedRule, _, buffers, err := r.matchRule(ctx, &metadata, false, conn, nil, -1) if err != nil { return err } @@ -108,16 +107,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad selectReturn = true case *rule.RuleActionReject: buf.ReleaseMulti(buffers) - var rejectErr error - switch action.Method { - case C.RuleActionRejectMethodDefault: - rejectErr = os.ErrClosed - case C.RuleActionRejectMethodPortUnreachable: - rejectErr = syscall.ECONNREFUSED - case C.RuleActionRejectMethodDrop: - rejectErr = tun.ErrDrop - } - N.CloseOnHandshakeFailure(conn, onClose, rejectErr) + N.CloseOnHandshakeFailure(conn, onClose, action.Error()) return nil } } @@ -236,7 +226,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m conn = deadline.NewPacketConn(bufio.NewNetPacketConn(conn)) }*/ - selectedRule, _, buffers, err := r.matchRule(ctx, &metadata, nil, conn, -1) + selectedRule, _, buffers, err := r.matchRule(ctx, &metadata, false, nil, conn, -1) if err != nil { return err } @@ -306,8 +296,23 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m return nil } +func (r *Router) PreMatch(metadata adapter.InboundContext) error { + selectedRule, _, _, err := r.matchRule(r.ctx, &metadata, true, nil, nil, -1) + if err != nil { + return err + } + if selectedRule == nil { + return nil + } + rejectAction, isReject := selectedRule.Action().(*rule.RuleActionReject) + if !isReject { + return nil + } + return rejectAction.Error() +} + func (r *Router) matchRule( - ctx context.Context, metadata *adapter.InboundContext, + ctx context.Context, metadata *adapter.InboundContext, preMatch bool, inputConn net.Conn, inputPacketConn N.PacketConn, ruleIndex int, ) (selectedRule adapter.Rule, selectedRuleIndex int, buffers []*buf.Buffer, fatalErr error) { if r.processSearcher != nil && metadata.ProcessInfo == nil { @@ -370,7 +375,7 @@ func (r *Router) matchRule( //nolint:staticcheck if metadata.InboundOptions != common.DefaultValue[option.InboundOptions]() { - if metadata.InboundOptions.SniffEnabled { + if !preMatch && metadata.InboundOptions.SniffEnabled { newBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{ OverrideDestination: metadata.InboundOptions.SniffOverrideDestination, Timeout: time.Duration(metadata.InboundOptions.SniffTimeout), @@ -415,15 +420,28 @@ match: if !matched { break } - r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] ", currentRule, " => ", currentRule.Action()) + if !preMatch { + r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] ", currentRule, " => ", currentRule.Action()) + } else { + switch currentRule.Action().Type() { + case C.RuleActionTypeReject, C.RuleActionTypeResolve: + r.logger.DebugContext(ctx, "pre-match[", currentRuleIndex, "] ", currentRule, " => ", currentRule.Action()) + } + } switch action := currentRule.Action().(type) { case *rule.RuleActionSniff: - newBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn) - if newErr != nil { - fatalErr = newErr - return + if !preMatch { + newBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn) + if newErr != nil { + fatalErr = newErr + return + } + buffers = append(buffers, newBuffers...) + } else { + selectedRule = currentRule + selectedRuleIndex = currentRuleIndex + break match } - buffers = append(buffers, newBuffers...) case *rule.RuleActionResolve: fatalErr = r.actionResolve(ctx, metadata, action) if fatalErr != nil { @@ -436,7 +454,7 @@ match: } ruleIndex = currentRuleIndex } - if metadata.Destination.Addr.IsUnspecified() { + if !preMatch && metadata.Destination.Addr.IsUnspecified() { newBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{}, inputConn, inputPacketConn) if newErr != nil { fatalErr = newErr diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index e85fc7638..a157e94e5 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -2,7 +2,9 @@ package rule import ( "net/netip" + "os" "strings" + "syscall" "time" "github.com/sagernet/sing-box/adapter" @@ -10,6 +12,7 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-dns" + "github.com/sagernet/sing-tun" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" ) @@ -22,10 +25,10 @@ func NewRuleAction(action option.RuleAction) (adapter.RuleAction, error) { UDPDisableDomainUnmapping: action.RouteOptions.UDPDisableDomainUnmapping, }, nil case C.RuleActionTypeReturn: - return &RuleActionReject{}, nil + return &RuleActionReturn{}, nil case C.RuleActionTypeReject: return &RuleActionReject{ - Method: string(action.RejectOptions.Method), + Method: action.RejectOptions.Method, }, nil case C.RuleActionTypeHijackDNS: return &RuleActionHijackDNS{}, nil @@ -58,7 +61,7 @@ func NewDNSRuleAction(action option.DNSRuleAction) adapter.RuleAction { return &RuleActionReturn{} case C.RuleActionTypeReject: return &RuleActionReject{ - Method: string(action.RejectOptions.Method), + Method: action.RejectOptions.Method, } default: panic(F.ToString("unknown rule action: ", action.Action)) @@ -118,6 +121,23 @@ func (r *RuleActionReject) String() string { return F.ToString("reject(", r.Method, ")") } +func (r *RuleActionReject) Error() error { + switch r.Method { + case C.RuleActionRejectMethodReset: + return os.ErrClosed + case C.RuleActionRejectMethodNetworkUnreachable: + return syscall.ENETUNREACH + case C.RuleActionRejectMethodHostUnreachable: + return syscall.EHOSTUNREACH + case C.RuleActionRejectMethodDefault, C.RuleActionRejectMethodPortUnreachable: + return syscall.ECONNREFUSED + case C.RuleActionRejectMethodDrop: + return tun.ErrDrop + default: + panic(F.ToString("unknown reject method: ", r.Method)) + } +} + type RuleActionHijackDNS struct{} func (r *RuleActionHijackDNS) Type() string { From 5eb85222059cd06214b21a58122f96c222ef9867 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 22 Oct 2024 22:01:28 +0800 Subject: [PATCH 03/39] Implement resolve(server) --- adapter/inbound.go | 14 ++++---- route/route.go | 2 +- route/route_dns.go | 87 ++++++++++++++++++++++++++++------------------ 3 files changed, 62 insertions(+), 41 deletions(-) diff --git a/adapter/inbound.go b/adapter/inbound.go index f4d5802f3..300f57e3d 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -50,12 +50,14 @@ type InboundContext struct { // Deprecated InboundOptions option.InboundOptions UDPDisableDomainUnmapping bool - DestinationAddresses []netip.Addr - SourceGeoIPCode string - GeoIPCode string - ProcessInfo *process.Info - QueryType uint16 - FakeIP bool + DNSServer string + + DestinationAddresses []netip.Addr + SourceGeoIPCode string + GeoIPCode string + ProcessInfo *process.Info + QueryType uint16 + FakeIP bool // rule cache diff --git a/route/route.go b/route/route.go index cecd0f2ae..56493bd17 100644 --- a/route/route.go +++ b/route/route.go @@ -584,7 +584,7 @@ func (r *Router) actionSniff( func (r *Router) actionResolve(ctx context.Context, metadata *adapter.InboundContext, action *rule.RuleActionResolve) error { if metadata.Destination.IsFqdn() { - // TODO: check if WithContext is necessary + metadata.DNSServer = action.Server addresses, err := r.Lookup(adapter.WithContext(ctx, metadata), metadata.Destination.Fqdn, action.Strategy) if err != nil { return err diff --git a/route/route_dns.go b/route/route_dns.go index 43eb61e6d..60aff6a9f 100644 --- a/route/route_dns.go +++ b/route/route_dns.go @@ -185,6 +185,20 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS cached bool err error ) + printResult := func() { + if err != nil { + if errors.Is(err, dns.ErrResponseRejectedCached) { + r.dnsLogger.DebugContext(ctx, "response rejected for ", domain, " (cached)") + } else if errors.Is(err, dns.ErrResponseRejected) { + r.dnsLogger.DebugContext(ctx, "response rejected for ", domain) + } else { + r.dnsLogger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain)) + } + } else if len(responseAddrs) == 0 { + r.dnsLogger.ErrorContext(ctx, "lookup failed for ", domain, ": empty result") + err = dns.RCodeNameError + } + } responseAddrs, cached = r.dnsClient.LookupCache(ctx, domain, strategy) if cached { if len(responseAddrs) == 0 { @@ -196,46 +210,51 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS ctx, metadata := adapter.ExtendContext(ctx) metadata.Destination = M.Socksaddr{} metadata.Domain = domain - var ( - transport dns.Transport - options dns.QueryOptions - rule adapter.DNSRule - ruleIndex int - ) - ruleIndex = -1 - for { - dnsCtx := adapter.OverrideContext(ctx) - var addressLimit bool - transport, options, rule, ruleIndex = r.matchDNS(ctx, false, ruleIndex, true) - if strategy != dns.DomainStrategyAsIS { - options.Strategy = strategy - } - if rule != nil && rule.WithAddressLimit() { - addressLimit = true - responseAddrs, err = r.dnsClient.LookupWithResponseCheck(dnsCtx, transport, domain, options, func(responseAddrs []netip.Addr) bool { - metadata.DestinationAddresses = responseAddrs - return rule.MatchAddressLimit(metadata) - }) - } else { - addressLimit = false - responseAddrs, err = r.dnsClient.Lookup(dnsCtx, transport, domain, options) + if metadata.DNSServer != "" { + transport, loaded := r.transportMap[metadata.DNSServer] + if !loaded { + return nil, E.New("transport not found: ", metadata.DNSServer) } - if err != nil { - if errors.Is(err, dns.ErrResponseRejectedCached) { - r.dnsLogger.DebugContext(ctx, "response rejected for ", domain, " (cached)") - } else if errors.Is(err, dns.ErrResponseRejected) { - r.dnsLogger.DebugContext(ctx, "response rejected for ", domain) + if strategy == dns.DomainStrategyAsIS { + if transportDomainStrategy, loaded := r.transportDomainStrategy[transport]; loaded { + strategy = transportDomainStrategy } else { - r.dnsLogger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain)) + strategy = r.defaultDomainStrategy } - } else if len(responseAddrs) == 0 { - r.dnsLogger.ErrorContext(ctx, "lookup failed for ", domain, ": empty result") - err = dns.RCodeNameError } - if !addressLimit || err == nil { - break + responseAddrs, err = r.dnsClient.Lookup(ctx, transport, domain, dns.QueryOptions{Strategy: strategy}) + } else { + var ( + transport dns.Transport + options dns.QueryOptions + rule adapter.DNSRule + ruleIndex int + ) + ruleIndex = -1 + for { + dnsCtx := adapter.OverrideContext(ctx) + var addressLimit bool + transport, options, rule, ruleIndex = r.matchDNS(ctx, false, ruleIndex, true) + if strategy != dns.DomainStrategyAsIS { + options.Strategy = strategy + } + if rule != nil && rule.WithAddressLimit() { + addressLimit = true + responseAddrs, err = r.dnsClient.LookupWithResponseCheck(dnsCtx, transport, domain, options, func(responseAddrs []netip.Addr) bool { + metadata.DestinationAddresses = responseAddrs + return rule.MatchAddressLimit(metadata) + }) + } else { + addressLimit = false + responseAddrs, err = r.dnsClient.Lookup(dnsCtx, transport, domain, options) + } + if !addressLimit || err == nil { + break + } + printResult() } } + printResult() if len(responseAddrs) > 0 { r.dnsLogger.InfoContext(ctx, "lookup succeed for ", domain, ": ", strings.Join(F.MapToString(responseAddrs), " ")) } From e45763d5ba368ca80d487e7c7d77f8975dbc1447 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 23 Oct 2024 13:44:08 +0800 Subject: [PATCH 04/39] Implement dns-hijack --- cmd/sing-box/cmd_tools.go | 7 +- inbound/tun.go | 5 +- outbound/dns.go | 25 ++++--- route/dns.go | 91 +++++++++++++++++++++++++ route/route.go | 139 ++++++++++++++++++++++++++++---------- route/rule/rule_action.go | 72 +++++++------------- 6 files changed, 243 insertions(+), 96 deletions(-) create mode 100644 route/dns.go diff --git a/cmd/sing-box/cmd_tools.go b/cmd/sing-box/cmd_tools.go index c45f58557..86b9302e0 100644 --- a/cmd/sing-box/cmd_tools.go +++ b/cmd/sing-box/cmd_tools.go @@ -1,6 +1,9 @@ package main import ( + "errors" + "os" + "github.com/sagernet/sing-box" E "github.com/sagernet/sing/common/exceptions" N "github.com/sagernet/sing/common/network" @@ -23,7 +26,9 @@ func init() { func createPreStartedClient() (*box.Box, error) { options, err := readConfigAndMerge() if err != nil { - return nil, err + if !(errors.Is(err, os.ErrNotExist) && len(configDirectories) == 0 && len(configPaths) == 1) || configPaths[0] != "config.json" { + return nil, err + } } instance, err := box.New(box.Options{Options: options}) if err != nil { diff --git a/inbound/tun.go b/inbound/tun.go index f04ae71b3..11b16428b 100644 --- a/inbound/tun.go +++ b/inbound/tun.go @@ -36,8 +36,9 @@ type TUN struct { router adapter.Router logger log.ContextLogger // Deprecated - inboundOptions option.InboundOptions - tunOptions tun.Options + inboundOptions option.InboundOptions + tunOptions tun.Options + // Deprecated endpointIndependentNat bool udpTimeout time.Duration stack string diff --git a/outbound/dns.go b/outbound/dns.go index 08661a99a..d9c92f19e 100644 --- a/outbound/dns.go +++ b/outbound/dns.go @@ -5,6 +5,7 @@ import ( "encoding/binary" "net" "os" + "time" "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" @@ -50,14 +51,15 @@ func (d *DNS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter metadata.Destination = M.Socksaddr{} defer conn.Close() for { - err := d.handleConnection(ctx, conn, metadata) + conn.SetReadDeadline(time.Now().Add(C.DNSTimeout)) + err := HandleStreamDNSRequest(ctx, d.router, conn, metadata) if err != nil { return err } } } -func (d *DNS) handleConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func HandleStreamDNSRequest(ctx context.Context, router adapter.Router, conn net.Conn, metadata adapter.InboundContext) error { var queryLength uint16 err := binary.Read(conn, binary.BigEndian, &queryLength) if err != nil { @@ -79,7 +81,7 @@ func (d *DNS) handleConnection(ctx context.Context, conn net.Conn, metadata adap } metadataInQuery := metadata go func() error { - response, err := d.router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message) + response, err := router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message) if err != nil { return err } @@ -100,10 +102,14 @@ func (d *DNS) handleConnection(ctx context.Context, conn net.Conn, metadata adap // Deprecated func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + return NewDNSPacketConnection(ctx, d.router, conn, nil, metadata) +} + +func NewDNSPacketConnection(ctx context.Context, router adapter.Router, conn N.PacketConn, cachedPackets []*N.PacketBuffer, metadata adapter.InboundContext) error { metadata.Destination = M.Socksaddr{} var reader N.PacketReader = conn var counters []N.CountFunc - var cachedPackets []*N.PacketBuffer + cachedPackets = common.Reverse(cachedPackets) for { reader, counters = N.UnwrapCountPacketReader(reader, counters) if cachedReader, isCached := reader.(N.CachedPacketReader); isCached { @@ -115,7 +121,7 @@ func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metada } if readWaiter, created := bufio.CreatePacketReadWaiter(reader); created { readWaiter.InitializeReadWaiter(N.ReadWaitOptions{}) - return d.newPacketConnection(ctx, conn, readWaiter, counters, cachedPackets, metadata) + return newDNSPacketConnection(ctx, router, conn, readWaiter, counters, cachedPackets, metadata) } break } @@ -161,7 +167,7 @@ func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metada } metadataInQuery := metadata go func() error { - response, err := d.router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message) + response, err := router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message) if err != nil { cancel(err) return err @@ -186,7 +192,7 @@ func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metada return group.Run(fastClose) } -func (d *DNS) newPacketConnection(ctx context.Context, conn N.PacketConn, readWaiter N.PacketReadWaiter, readCounters []N.CountFunc, cached []*N.PacketBuffer, metadata adapter.InboundContext) error { +func newDNSPacketConnection(ctx context.Context, router adapter.Router, conn N.PacketConn, readWaiter N.PacketReadWaiter, readCounters []N.CountFunc, cached []*N.PacketBuffer, metadata adapter.InboundContext) error { fastClose, cancel := common.ContextWithCancelCause(ctx) timeout := canceler.New(fastClose, cancel, C.DNSTimeout) var group task.Group @@ -206,11 +212,12 @@ func (d *DNS) newPacketConnection(ctx context.Context, conn N.PacketConn, readWa } err = message.Unpack(packet.Buffer.Bytes()) packet.Buffer.Release() + destination = packet.Destination + N.PutPacketBuffer(packet) if err != nil { cancel(err) return err } - destination = packet.Destination } else { buffer, destination, err = readWaiter.WaitReadPacket() if err != nil { @@ -230,7 +237,7 @@ func (d *DNS) newPacketConnection(ctx context.Context, conn N.PacketConn, readWa } metadataInQuery := metadata go func() error { - response, err := d.router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message) + response, err := router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message) if err != nil { cancel(err) return err diff --git a/route/dns.go b/route/dns.go new file mode 100644 index 000000000..34299ebfd --- /dev/null +++ b/route/dns.go @@ -0,0 +1,91 @@ +package route + +import ( + "context" + "net" + "time" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/outbound" + "github.com/sagernet/sing-dns" + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/udpnat2" + + mDNS "github.com/miekg/dns" +) + +func (r *Router) hijackDNSStream(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + metadata.Destination = M.Socksaddr{} + for { + conn.SetReadDeadline(time.Now().Add(C.DNSTimeout)) + err := outbound.HandleStreamDNSRequest(ctx, r, conn, metadata) + if err != nil { + return err + } + } +} + +func (r *Router) hijackDNSPacket(ctx context.Context, conn N.PacketConn, packetBuffers []*N.PacketBuffer, metadata adapter.InboundContext) { + if uConn, isUDPNAT2 := conn.(*udpnat.Conn); isUDPNAT2 { + metadata.Destination = M.Socksaddr{} + for _, packet := range packetBuffers { + buffer := packet.Buffer + destination := packet.Destination + N.PutPacketBuffer(packet) + go ExchangeDNSPacket(ctx, r, uConn, buffer, metadata, destination) + } + uConn.SetHandler(&dnsHijacker{ + router: r, + conn: conn, + ctx: ctx, + metadata: metadata, + }) + return + } + err := outbound.NewDNSPacketConnection(ctx, r, conn, packetBuffers, metadata) + if err != nil && !E.IsClosedOrCanceled(err) { + r.dnsLogger.ErrorContext(ctx, E.Cause(err, "process packet connection")) + } +} + +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) + if err != nil && !E.IsClosedOrCanceled(err) { + router.dnsLogger.ErrorContext(ctx, E.Cause(err, "process packet connection")) + } +} + +func exchangeDNSPacket(ctx context.Context, router *Router, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext, destination M.Socksaddr) error { + var message mDNS.Msg + err := message.Unpack(buffer.Bytes()) + buffer.Release() + if err != nil { + return E.Cause(err, "unpack request") + } + response, err := router.Exchange(adapter.WithContext(ctx, &metadata), &message) + if err != nil { + return err + } + responseBuffer, err := dns.TruncateDNSMessage(&message, response, 1024) + if err != nil { + return err + } + err = conn.WritePacket(responseBuffer, destination) + responseBuffer.Release() + return err +} + +type dnsHijacker struct { + router *Router + conn N.PacketConn + ctx context.Context + metadata adapter.InboundContext +} + +func (h *dnsHijacker) NewPacketEx(buffer *buf.Buffer, destination M.Socksaddr) { + go ExchangeDNSPacket(h.ctx, h.router, h.conn, buffer, h.metadata, destination) +} diff --git a/route/route.go b/route/route.go index 56493bd17..ebffddddb 100644 --- a/route/route.go +++ b/route/route.go @@ -88,7 +88,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad if deadline.NeedAdditionalReadDeadline(conn) { conn = deadline.NewConn(conn) } - selectedRule, _, buffers, err := r.matchRule(ctx, &metadata, false, conn, nil, -1) + selectedRule, _, buffers, _, err := r.matchRule(ctx, &metadata, false, conn, nil, -1) if err != nil { return err } @@ -109,6 +109,12 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad buf.ReleaseMulti(buffers) N.CloseOnHandshakeFailure(conn, onClose, action.Error()) return nil + case *rule.RuleActionHijackDNS: + for _, buffer := range buffers { + conn = bufio.NewCachedConn(conn, buffer) + } + r.hijackDNSStream(ctx, conn, metadata) + return nil } } if selectedRule == nil || selectReturn { @@ -226,7 +232,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m conn = deadline.NewPacketConn(bufio.NewNetPacketConn(conn)) }*/ - selectedRule, _, buffers, err := r.matchRule(ctx, &metadata, false, nil, conn, -1) + selectedRule, _, _, packetBuffers, err := r.matchRule(ctx, &metadata, false, nil, conn, -1) if err != nil { return err } @@ -238,32 +244,35 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m var loaded bool selectedOutbound, loaded = r.Outbound(action.Outbound) if !loaded { - buf.ReleaseMulti(buffers) + N.ReleaseMultiPacketBuffer(packetBuffers) return E.New("outbound not found: ", action.Outbound) } metadata.UDPDisableDomainUnmapping = action.UDPDisableDomainUnmapping case *rule.RuleActionReturn: selectReturn = true case *rule.RuleActionReject: - buf.ReleaseMulti(buffers) + N.ReleaseMultiPacketBuffer(packetBuffers) N.CloseOnHandshakeFailure(conn, onClose, syscall.ECONNREFUSED) return nil + case *rule.RuleActionHijackDNS: + r.hijackDNSPacket(ctx, conn, packetBuffers, metadata) + return nil } } if selectedRule == nil || selectReturn { if r.defaultOutboundForPacketConnection == nil { - buf.ReleaseMulti(buffers) + N.ReleaseMultiPacketBuffer(packetBuffers) return E.New("missing default outbound with UDP support") } selectedOutbound = r.defaultOutboundForPacketConnection } if !common.Contains(selectedOutbound.Network(), N.NetworkUDP) { - buf.ReleaseMulti(buffers) + N.ReleaseMultiPacketBuffer(packetBuffers) return E.New("UDP is not supported by outbound: ", selectedOutbound.Tag()) } - for _, buffer := range buffers { - // TODO: check if metadata.Destination == packet destination - conn = bufio.NewCachedPacketConn(conn, buffer, metadata.Destination) + for _, buffer := range packetBuffers { + conn = bufio.NewCachedPacketConn(conn, buffer.Buffer, buffer.Destination) + N.PutPacketBuffer(buffer) } if r.clashServer != nil { trackerConn, tracker := r.clashServer.RoutedPacketConnection(ctx, conn, metadata, selectedRule) @@ -297,7 +306,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m } func (r *Router) PreMatch(metadata adapter.InboundContext) error { - selectedRule, _, _, err := r.matchRule(r.ctx, &metadata, true, nil, nil, -1) + selectedRule, _, _, _, err := r.matchRule(r.ctx, &metadata, true, nil, nil, -1) if err != nil { return err } @@ -314,7 +323,10 @@ func (r *Router) PreMatch(metadata adapter.InboundContext) error { func (r *Router) matchRule( ctx context.Context, metadata *adapter.InboundContext, preMatch bool, inputConn net.Conn, inputPacketConn N.PacketConn, ruleIndex int, -) (selectedRule adapter.Rule, selectedRuleIndex int, buffers []*buf.Buffer, fatalErr error) { +) ( + selectedRule adapter.Rule, selectedRuleIndex int, + buffers []*buf.Buffer, packetBuffers []*N.PacketBuffer, fatalErr error, +) { if r.processSearcher != nil && metadata.ProcessInfo == nil { var originDestination netip.AddrPort if metadata.OriginDestination.IsValid() { @@ -376,7 +388,7 @@ func (r *Router) matchRule( //nolint:staticcheck if metadata.InboundOptions != common.DefaultValue[option.InboundOptions]() { if !preMatch && metadata.InboundOptions.SniffEnabled { - newBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{ + newBuffer, newPackerBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{ OverrideDestination: metadata.InboundOptions.SniffOverrideDestination, Timeout: time.Duration(metadata.InboundOptions.SniffTimeout), }, inputConn, inputPacketConn) @@ -384,7 +396,11 @@ func (r *Router) matchRule( fatalErr = newErr return } - buffers = append(buffers, newBuffers...) + if newBuffer != nil { + buffers = []*buf.Buffer{newBuffer} + } else if len(newPackerBuffers) > 0 { + packetBuffers = newPackerBuffers + } } if dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) != dns.DomainStrategyAsIS { fatalErr = r.actionResolve(ctx, metadata, &rule.RuleActionResolve{ @@ -421,22 +437,36 @@ match: break } if !preMatch { - r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] ", currentRule, " => ", currentRule.Action()) + ruleDescription := currentRule.String() + if ruleDescription != "" { + r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] ", currentRule, " => ", currentRule.Action()) + } else { + r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] => ", currentRule.Action()) + } } else { switch currentRule.Action().Type() { case C.RuleActionTypeReject, C.RuleActionTypeResolve: - r.logger.DebugContext(ctx, "pre-match[", currentRuleIndex, "] ", currentRule, " => ", currentRule.Action()) + ruleDescription := currentRule.String() + if ruleDescription != "" { + r.logger.DebugContext(ctx, "pre-match[", currentRuleIndex, "] ", currentRule, " => ", currentRule.Action()) + } else { + r.logger.DebugContext(ctx, "pre-match[", currentRuleIndex, "] => ", currentRule.Action()) + } } } switch action := currentRule.Action().(type) { case *rule.RuleActionSniff: if !preMatch { - newBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn) + newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn) if newErr != nil { fatalErr = newErr return } - buffers = append(buffers, newBuffers...) + if newBuffer != nil { + buffers = append(buffers, newBuffer) + } else if len(newPacketBuffers) > 0 { + packetBuffers = append(packetBuffers, newPacketBuffers...) + } } else { selectedRule = currentRule selectedRuleIndex = currentRuleIndex @@ -455,12 +485,16 @@ match: ruleIndex = currentRuleIndex } if !preMatch && metadata.Destination.Addr.IsUnspecified() { - newBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{}, inputConn, inputPacketConn) + newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{}, inputConn, inputPacketConn) if newErr != nil { fatalErr = newErr return } - buffers = append(buffers, newBuffers...) + if newBuffer != nil { + buffers = append(buffers, newBuffer) + } else if len(newPacketBuffers) > 0 { + packetBuffers = append(packetBuffers, newPacketBuffers...) + } } return } @@ -468,18 +502,31 @@ match: func (r *Router) actionSniff( ctx context.Context, metadata *adapter.InboundContext, action *rule.RuleActionSniff, inputConn net.Conn, inputPacketConn N.PacketConn, -) (buffers []*buf.Buffer, fatalErr error) { +) (buffer *buf.Buffer, packetBuffers []*N.PacketBuffer, fatalErr error) { if sniff.Skip(metadata) { return - } else if inputConn != nil && len(action.StreamSniffers) > 0 { - buffer := buf.NewPacket() + } else if inputConn != nil { + sniffBuffer := buf.NewPacket() + var streamSniffers []sniff.StreamSniffer + if len(action.StreamSniffers) > 0 { + streamSniffers = action.StreamSniffers + } else { + streamSniffers = []sniff.StreamSniffer{ + sniff.TLSClientHello, + sniff.HTTPHost, + sniff.StreamDomainNameQuery, + sniff.BitTorrent, + sniff.SSH, + sniff.RDP, + } + } err := sniff.PeekStream( ctx, metadata, inputConn, - buffer, + sniffBuffer, action.Timeout, - action.StreamSniffers..., + streamSniffers..., ) if err == nil { //goland:noinspection GoDeprecation @@ -497,15 +544,15 @@ func (r *Router) actionSniff( r.logger.DebugContext(ctx, "sniffed protocol: ", metadata.Protocol) } } - if !buffer.IsEmpty() { - buffers = append(buffers, buffer) + if !sniffBuffer.IsEmpty() { + buffer = sniffBuffer } else { - buffer.Release() + sniffBuffer.Release() } - } else if inputPacketConn != nil && len(action.PacketSniffers) > 0 { + } else if inputPacketConn != nil { for { var ( - buffer = buf.NewPacket() + sniffBuffer = buf.NewPacket() destination M.Socksaddr done = make(chan struct{}) err error @@ -516,7 +563,7 @@ func (r *Router) actionSniff( sniffTimeout = action.Timeout } inputPacketConn.SetReadDeadline(time.Now().Add(sniffTimeout)) - destination, err = inputPacketConn.ReadPacket(buffer) + destination, err = inputPacketConn.ReadPacket(sniffBuffer) inputPacketConn.SetReadDeadline(time.Time{}) close(done) }() @@ -528,7 +575,7 @@ func (r *Router) actionSniff( return } if err != nil { - buffer.Release() + sniffBuffer.Release() if !errors.Is(err, os.ErrDeadlineExceeded) { fatalErr = err return @@ -538,22 +585,40 @@ func (r *Router) actionSniff( if metadata.Destination.Addr.IsUnspecified() { metadata.Destination = destination } - if len(buffers) > 0 { + if len(packetBuffers) > 0 { err = sniff.PeekPacket( ctx, metadata, - buffer.Bytes(), + sniffBuffer.Bytes(), sniff.QUICClientHello, ) } else { + var packetSniffers []sniff.PacketSniffer + if len(action.PacketSniffers) > 0 { + packetSniffers = action.PacketSniffers + } else { + packetSniffers = []sniff.PacketSniffer{ + sniff.DomainNameQuery, + sniff.QUICClientHello, + sniff.STUNMessage, + sniff.UTP, + sniff.UDPTracker, + sniff.DTLSRecord, + } + } err = sniff.PeekPacket( ctx, metadata, - buffer.Bytes(), - action.PacketSniffers..., + sniffBuffer.Bytes(), + packetSniffers..., ) } - buffers = append(buffers, buffer) - if E.IsMulti(err, sniff.ErrClientHelloFragmented) && len(buffers) == 0 { + packetBuffer := N.NewPacketBuffer() + *packetBuffer = N.PacketBuffer{ + Buffer: sniffBuffer, + Destination: destination, + } + packetBuffers = append(packetBuffers, packetBuffer) + if E.IsMulti(err, sniff.ErrClientHelloFragmented) && len(packetBuffers) == 0 { r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello") continue } diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index a157e94e5..57b736479 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -162,53 +162,31 @@ func (r *RuleActionSniff) Type() string { } func (r *RuleActionSniff) build() error { - if len(r.StreamSniffers) > 0 || len(r.PacketSniffers) > 0 { - return nil - } - if len(r.snifferNames) > 0 { - for _, name := range r.snifferNames { - switch name { - case C.ProtocolTLS: - r.StreamSniffers = append(r.StreamSniffers, sniff.TLSClientHello) - case C.ProtocolHTTP: - r.StreamSniffers = append(r.StreamSniffers, sniff.HTTPHost) - case C.ProtocolQUIC: - r.PacketSniffers = append(r.PacketSniffers, sniff.QUICClientHello) - case C.ProtocolDNS: - r.StreamSniffers = append(r.StreamSniffers, sniff.StreamDomainNameQuery) - r.PacketSniffers = append(r.PacketSniffers, sniff.DomainNameQuery) - case C.ProtocolSTUN: - r.PacketSniffers = append(r.PacketSniffers, sniff.STUNMessage) - case C.ProtocolBitTorrent: - r.StreamSniffers = append(r.StreamSniffers, sniff.BitTorrent) - r.PacketSniffers = append(r.PacketSniffers, sniff.UTP) - r.PacketSniffers = append(r.PacketSniffers, sniff.UDPTracker) - case C.ProtocolDTLS: - r.PacketSniffers = append(r.PacketSniffers, sniff.DTLSRecord) - case C.ProtocolSSH: - r.StreamSniffers = append(r.StreamSniffers, sniff.SSH) - case C.ProtocolRDP: - r.StreamSniffers = append(r.StreamSniffers, sniff.RDP) - default: - return E.New("unknown sniffer: ", name) - } - } - } else { - r.StreamSniffers = []sniff.StreamSniffer{ - sniff.TLSClientHello, - sniff.HTTPHost, - sniff.StreamDomainNameQuery, - sniff.BitTorrent, - sniff.SSH, - sniff.RDP, - } - r.PacketSniffers = []sniff.PacketSniffer{ - sniff.DomainNameQuery, - sniff.QUICClientHello, - sniff.STUNMessage, - sniff.UTP, - sniff.UDPTracker, - sniff.DTLSRecord, + for _, name := range r.snifferNames { + switch name { + case C.ProtocolTLS: + r.StreamSniffers = append(r.StreamSniffers, sniff.TLSClientHello) + case C.ProtocolHTTP: + r.StreamSniffers = append(r.StreamSniffers, sniff.HTTPHost) + case C.ProtocolQUIC: + r.PacketSniffers = append(r.PacketSniffers, sniff.QUICClientHello) + case C.ProtocolDNS: + r.StreamSniffers = append(r.StreamSniffers, sniff.StreamDomainNameQuery) + r.PacketSniffers = append(r.PacketSniffers, sniff.DomainNameQuery) + case C.ProtocolSTUN: + r.PacketSniffers = append(r.PacketSniffers, sniff.STUNMessage) + case C.ProtocolBitTorrent: + r.StreamSniffers = append(r.StreamSniffers, sniff.BitTorrent) + r.PacketSniffers = append(r.PacketSniffers, sniff.UTP) + r.PacketSniffers = append(r.PacketSniffers, sniff.UDPTracker) + case C.ProtocolDTLS: + r.PacketSniffers = append(r.PacketSniffers, sniff.DTLSRecord) + case C.ProtocolSSH: + r.StreamSniffers = append(r.StreamSniffers, sniff.SSH) + case C.ProtocolRDP: + r.StreamSniffers = append(r.StreamSniffers, sniff.RDP) + default: + return E.New("unknown sniffer: ", name) } } return nil From 776052de2078eb34708ba386dd130d95915aa5a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 2 Nov 2024 00:39:02 +0800 Subject: [PATCH 05/39] refactor: Modular inbounds/outbounds --- adapter/inbound.go | 7 + adapter/inbound/adapter.go | 21 ++ adapter/inbound/registry.go | 68 +++++ adapter/outbound.go | 9 + adapter/outbound/adapter.go | 45 ++++ {outbound => adapter/outbound}/default.go | 47 ---- adapter/outbound/registry.go | 68 +++++ box.go | 91 +++++-- cmd/sing-box/cmd.go | 4 +- cmd/sing-box/cmd_format.go | 3 +- cmd/sing-box/cmd_merge.go | 23 +- cmd/sing-box/cmd_run.go | 6 +- common/dialer/default.go | 2 +- common/dialer/wireguard.go | 4 + common/dialer/wireguard_control.go | 11 - common/dialer/wiregurad_stub.go | 9 - common/listener/listener.go | 136 ++++++++++ .../listener/listener_go121.go | 2 +- common/listener/listener_go123.go | 16 ++ .../listener/listener_nongo121.go | 2 +- common/listener/listener_nongo123.go | 15 ++ common/listener/listener_tcp.go | 85 ++++++ common/listener/listener_udp.go | 154 +++++++++++ experimental/clashapi/api_meta_group.go | 10 +- experimental/clashapi/proxies.go | 6 +- experimental/libbox/command_group.go | 22 +- experimental/libbox/command_select.go | 4 +- experimental/libbox/command_urltest.go | 4 +- experimental/libbox/config.go | 19 +- experimental/libbox/service.go | 8 +- experimental/libbox/setup.go | 1 - go.mod | 2 - go.sum | 8 - inbound/builder.go | 54 ---- inbound/default.go | 209 --------------- inbound/default_tcp.go | 84 ------ inbound/default_tcp_go1.20.go | 18 -- inbound/default_tcp_nongo1.20.go | 15 -- inbound/default_udp.go | 208 --------------- inbound/direct.go | 111 -------- inbound/http.go | 119 --------- inbound/hysteria_stub.go | 20 -- inbound/mixed.go | 70 ----- inbound/naive_quic.go | 47 ---- inbound/naive_quic_stub.go | 11 - inbound/redirect.go | 45 ---- inbound/shadowsocks.go | 114 -------- inbound/socks.go | 52 ---- inbound/tuic_stub.go | 16 -- include/quic.go | 18 ++ include/quic_stub.go | 34 +++ include/registry.go | 95 +++++++ include/wireguard.go | 12 + include/wireguard_stub.go | 20 ++ option/inbound.go | 122 +++------ option/inbound_legacy.go | 98 +++++++ option/json.go | 71 ----- option/{config.go => options.go} | 12 +- option/outbound.go | 103 ++------ option/outbound_legacy.go | 103 ++++++++ option/rule.go | 13 +- option/rule_action.go | 13 +- option/rule_dns.go | 13 +- option/rule_set.go | 13 +- option/simple.go | 2 +- option/tls_acme.go | 5 +- option/v2ray_transport.go | 5 +- outbound/block.go | 54 ---- outbound/builder.go | 65 ----- outbound/hysteria_stub.go | 20 -- outbound/lookback.go | 14 - outbound/shadowsocksr.go | 18 -- outbound/shadowsocksr_stub.go | 16 -- outbound/tor_embed.go | 15 -- outbound/tor_embed_mobile.go | 15 -- outbound/tor_external.go | 9 - outbound/tuic_stub.go | 16 -- outbound/wireguard_stub.go | 16 -- protocol/block/outbound.go | 42 +++ protocol/direct/inbound.go | 139 ++++++++++ .../direct/loopback_detect.go | 2 +- .../direct.go => protocol/direct/outbound.go | 48 ++-- outbound/dns.go => protocol/dns/handle.go | 47 +--- protocol/dns/outbound.go | 61 +++++ {outbound => protocol/group}/selector.go | 41 +-- {outbound => protocol/group}/urltest.go | 29 +- protocol/http/inbound.go | 122 +++++++++ outbound/http.go => protocol/http/outbound.go | 35 ++- .../hysteria/inbound.go | 61 +++-- .../hysteria/outbound.go | 44 ++-- .../hysteria2/inbound.go | 61 +++-- .../hysteria2/outbound.go | 44 ++-- protocol/mixed/inbound.go | 109 ++++++++ protocol/naive/inbound.go | 248 ++++++++++++++++++ .../naive/inbound_conn.go | 219 +--------------- protocol/naive/quic/inbound_init.go | 52 ++++ protocol/redirect/redirect.go | 65 +++++ {inbound => protocol/redirect}/tproxy.go | 79 ++++-- protocol/shadowsocks/inbound.go | 179 +++++++++++++ .../shadowsocks/inbound_multi.go | 83 +++--- .../shadowsocks/inbound_relay.go | 79 ++++-- .../shadowsocks/outbound.go | 45 ++-- .../shadowtls/inbound.go | 58 ++-- .../shadowtls/outbound.go | 30 +-- protocol/socks/inbound.go | 91 +++++++ .../socks.go => protocol/socks/outbound.go | 51 ++-- outbound/ssh.go => protocol/ssh/outbound.go | 42 ++- outbound/tor.go => protocol/tor/outbound.go | 41 ++- {outbound => protocol/tor}/proxy.go | 7 +- .../trojan.go => protocol/trojan/inbound.go | 88 ++++--- .../trojan.go => protocol/trojan/outbound.go | 39 ++- inbound/tuic.go => protocol/tuic/inbound.go | 61 +++-- outbound/tuic.go => protocol/tuic/outbound.go | 42 ++- inbound/tun.go => protocol/tun/inbound.go | 36 +-- inbound/vless.go => protocol/vless/inbound.go | 101 ++++--- .../vless.go => protocol/vless/outbound.go | 41 ++- inbound/vmess.go => protocol/vmess/inbound.go | 101 ++++--- .../vmess.go => protocol/vmess/outbound.go | 41 ++- protocol/wireguard/init.go | 10 + .../wireguard/outbound.go | 62 +++-- route/dns.go | 6 +- route/route.go | 2 +- route/router.go | 22 +- test/brutal_test.go | 16 +- test/direct_test.go | 4 +- test/domain_inbound_test.go | 6 +- test/ech_test.go | 12 +- test/http_test.go | 4 +- test/hysteria2_test.go | 10 +- test/hysteria_test.go | 10 +- test/inbound_detour_test.go | 4 +- test/mux_cool_test.go | 10 +- test/mux_test.go | 8 +- test/naive_test.go | 6 +- test/shadowsocks_legacy_test.go | 4 +- test/shadowsocks_test.go | 18 +- test/shadowtls_test.go | 14 +- test/ss_plugin_test.go | 4 +- test/tfo_test.go | 4 +- test/tls_test.go | 4 +- test/trojan_test.go | 12 +- test/tuic_test.go | 10 +- test/v2ray_api_test.go | 4 +- test/v2ray_grpc_test.go | 6 +- test/v2ray_transport_test.go | 16 +- test/v2ray_ws_test.go | 6 +- test/vmess_test.go | 10 +- test/wireguard_test.go | 4 +- test/wrapper_test.go | 2 +- transport/trojan/mux.go | 9 +- transport/trojan/service.go | 8 +- transport/wireguard/client_bind.go | 11 +- transport/wireguard/resolve.go | 2 +- 153 files changed, 3339 insertions(+), 2920 deletions(-) create mode 100644 adapter/inbound/adapter.go create mode 100644 adapter/inbound/registry.go create mode 100644 adapter/outbound/adapter.go rename {outbound => adapter/outbound}/default.go (87%) create mode 100644 adapter/outbound/registry.go delete mode 100644 common/dialer/wireguard_control.go delete mode 100644 common/dialer/wiregurad_stub.go create mode 100644 common/listener/listener.go rename inbound/default_tcp_go1.21.go => common/listener/listener_go121.go (90%) create mode 100644 common/listener/listener_go123.go rename inbound/default_tcp_nongo1.21.go => common/listener/listener_nongo121.go (87%) create mode 100644 common/listener/listener_nongo123.go create mode 100644 common/listener/listener_tcp.go create mode 100644 common/listener/listener_udp.go delete mode 100644 inbound/builder.go delete mode 100644 inbound/default.go delete mode 100644 inbound/default_tcp.go delete mode 100644 inbound/default_tcp_go1.20.go delete mode 100644 inbound/default_tcp_nongo1.20.go delete mode 100644 inbound/default_udp.go delete mode 100644 inbound/direct.go delete mode 100644 inbound/http.go delete mode 100644 inbound/hysteria_stub.go delete mode 100644 inbound/mixed.go delete mode 100644 inbound/naive_quic.go delete mode 100644 inbound/naive_quic_stub.go delete mode 100644 inbound/redirect.go delete mode 100644 inbound/shadowsocks.go delete mode 100644 inbound/socks.go delete mode 100644 inbound/tuic_stub.go create mode 100644 include/registry.go create mode 100644 include/wireguard.go create mode 100644 include/wireguard_stub.go create mode 100644 option/inbound_legacy.go delete mode 100644 option/json.go rename option/{config.go => options.go} (74%) create mode 100644 option/outbound_legacy.go delete mode 100644 outbound/block.go delete mode 100644 outbound/builder.go delete mode 100644 outbound/hysteria_stub.go delete mode 100644 outbound/lookback.go delete mode 100644 outbound/shadowsocksr.go delete mode 100644 outbound/shadowsocksr_stub.go delete mode 100644 outbound/tor_embed.go delete mode 100644 outbound/tor_embed_mobile.go delete mode 100644 outbound/tor_external.go delete mode 100644 outbound/tuic_stub.go delete mode 100644 outbound/wireguard_stub.go create mode 100644 protocol/block/outbound.go create mode 100644 protocol/direct/inbound.go rename outbound/direct_loopback_detect.go => protocol/direct/loopback_detect.go (99%) rename outbound/direct.go => protocol/direct/outbound.go (76%) rename outbound/dns.go => protocol/dns/handle.go (83%) create mode 100644 protocol/dns/outbound.go rename {outbound => protocol/group}/selector.go (82%) rename {outbound => protocol/group}/urltest.go (94%) create mode 100644 protocol/http/inbound.go rename outbound/http.go => protocol/http/outbound.go (56%) rename inbound/hysteria.go => protocol/hysteria/inbound.go (71%) rename outbound/hysteria.go => protocol/hysteria/outbound.go (76%) rename inbound/hysteria2.go => protocol/hysteria2/inbound.go (74%) rename outbound/hysteria2.go => protocol/hysteria2/outbound.go (69%) create mode 100644 protocol/mixed/inbound.go create mode 100644 protocol/naive/inbound.go rename inbound/naive.go => protocol/naive/inbound_conn.go (58%) create mode 100644 protocol/naive/quic/inbound_init.go create mode 100644 protocol/redirect/redirect.go rename {inbound => protocol/redirect}/tproxy.go (60%) create mode 100644 protocol/shadowsocks/inbound.go rename inbound/shadowsocks_multi.go => protocol/shadowsocks/inbound_multi.go (59%) rename inbound/shadowsocks_relay.go => protocol/shadowsocks/inbound_relay.go (57%) rename outbound/shadowsocks.go => protocol/shadowsocks/outbound.go (80%) rename inbound/shadowtls.go => protocol/shadowtls/inbound.go (62%) rename outbound/shadowtls.go => protocol/shadowtls/outbound.go (74%) create mode 100644 protocol/socks/inbound.go rename outbound/socks.go => protocol/socks/outbound.go (65%) rename outbound/ssh.go => protocol/ssh/outbound.go (80%) rename outbound/tor.go => protocol/tor/outbound.go (83%) rename {outbound => protocol/tor}/proxy.go (94%) rename inbound/trojan.go => protocol/trojan/inbound.go (68%) rename outbound/trojan.go => protocol/trojan/outbound.go (79%) rename inbound/tuic.go => protocol/tuic/inbound.go (68%) rename outbound/tuic.go => protocol/tuic/outbound.go (76%) rename inbound/tun.go => protocol/tun/inbound.go (92%) rename inbound/vless.go => protocol/vless/inbound.go (60%) rename outbound/vless.go => protocol/vless/outbound.go (84%) rename inbound/vmess.go => protocol/vmess/inbound.go (62%) rename outbound/vmess.go => protocol/vmess/outbound.go (84%) create mode 100644 protocol/wireguard/init.go rename outbound/wireguard.go => protocol/wireguard/outbound.go (74%) diff --git a/adapter/inbound.go b/adapter/inbound.go index 300f57e3d..ca3e9e590 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -5,6 +5,7 @@ import ( "net/netip" "github.com/sagernet/sing-box/common/process" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" M "github.com/sagernet/sing/common/metadata" ) @@ -25,6 +26,11 @@ type UDPInjectableInbound interface { PacketConnectionHandlerEx } +type InboundRegistry interface { + option.InboundOptionsRegistry + CreateInbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Inbound, error) +} + type InboundContext struct { Inbound string InboundType string @@ -44,6 +50,7 @@ type InboundContext struct { // cache + // Deprecated: implement in rule action InboundDetour string LastInbound string OriginDestination M.Socksaddr diff --git a/adapter/inbound/adapter.go b/adapter/inbound/adapter.go new file mode 100644 index 000000000..1426104af --- /dev/null +++ b/adapter/inbound/adapter.go @@ -0,0 +1,21 @@ +package inbound + +type Adapter struct { + inboundType string + inboundTag string +} + +func NewAdapter(inboundType string, inboundTag string) Adapter { + return Adapter{ + inboundType: inboundType, + inboundTag: inboundTag, + } +} + +func (a *Adapter) Type() string { + return a.inboundType +} + +func (a *Adapter) Tag() string { + return a.inboundTag +} diff --git a/adapter/inbound/registry.go b/adapter/inbound/registry.go new file mode 100644 index 000000000..9f678c90e --- /dev/null +++ b/adapter/inbound/registry.go @@ -0,0 +1,68 @@ +package inbound + +import ( + "context" + "sync" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" +) + +type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Inbound, error) + +func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) { + registry.register(outboundType, func() any { + return new(Options) + }, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Inbound, error) { + return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options.(*Options))) + }) +} + +var _ adapter.InboundRegistry = (*Registry)(nil) + +type ( + optionsConstructorFunc func() any + constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Inbound, error) +) + +type Registry struct { + access sync.Mutex + optionsType map[string]optionsConstructorFunc + constructors map[string]constructorFunc +} + +func NewRegistry() *Registry { + return &Registry{ + optionsType: make(map[string]optionsConstructorFunc), + constructors: make(map[string]constructorFunc), + } +} + +func (r *Registry) CreateOptions(outboundType string) (any, bool) { + r.access.Lock() + defer r.access.Unlock() + optionsConstructor, loaded := r.optionsType[outboundType] + if !loaded { + return nil, false + } + return optionsConstructor(), true +} + +func (r *Registry) CreateInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Inbound, error) { + r.access.Lock() + defer r.access.Unlock() + constructor, loaded := r.constructors[outboundType] + if !loaded { + return nil, E.New("outbound type not found: " + outboundType) + } + return constructor(ctx, router, logger, tag, options) +} + +func (r *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) { + r.access.Lock() + defer r.access.Unlock() + r.optionsType[outboundType] = optionsConstructor + r.constructors[outboundType] = constructor +} diff --git a/adapter/outbound.go b/adapter/outbound.go index 312cdf3ed..df11ed613 100644 --- a/adapter/outbound.go +++ b/adapter/outbound.go @@ -1,6 +1,10 @@ package adapter import ( + "context" + + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" N "github.com/sagernet/sing/common/network" ) @@ -13,3 +17,8 @@ type Outbound interface { Dependencies() []string N.Dialer } + +type OutboundRegistry interface { + option.OutboundOptionsRegistry + CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error) +} diff --git a/adapter/outbound/adapter.go b/adapter/outbound/adapter.go new file mode 100644 index 000000000..481bb6197 --- /dev/null +++ b/adapter/outbound/adapter.go @@ -0,0 +1,45 @@ +package outbound + +import ( + "github.com/sagernet/sing-box/option" +) + +type Adapter struct { + protocol string + network []string + tag string + dependencies []string +} + +func NewAdapter(protocol string, network []string, tag string, dependencies []string) Adapter { + return Adapter{ + protocol: protocol, + network: network, + tag: tag, + dependencies: dependencies, + } +} + +func NewAdapterWithDialerOptions(protocol string, network []string, tag string, dialOptions option.DialerOptions) Adapter { + var dependencies []string + if dialOptions.Detour != "" { + dependencies = []string{dialOptions.Detour} + } + return NewAdapter(protocol, network, tag, dependencies) +} + +func (a *Adapter) Type() string { + return a.protocol +} + +func (a *Adapter) Tag() string { + return a.tag +} + +func (a *Adapter) Network() []string { + return a.network +} + +func (a *Adapter) Dependencies() []string { + return a.dependencies +} diff --git a/outbound/default.go b/adapter/outbound/default.go similarity index 87% rename from outbound/default.go rename to adapter/outbound/default.go index a34ac97af..78b9bfd8d 100644 --- a/outbound/default.go +++ b/adapter/outbound/default.go @@ -9,8 +9,6 @@ import ( "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" @@ -21,42 +19,6 @@ import ( N "github.com/sagernet/sing/common/network" ) -type myOutboundAdapter struct { - protocol string - network []string - router adapter.Router - logger log.ContextLogger - tag string - dependencies []string -} - -func (a *myOutboundAdapter) Type() string { - return a.protocol -} - -func (a *myOutboundAdapter) Tag() string { - return a.tag -} - -func (a *myOutboundAdapter) Network() []string { - return a.network -} - -func (a *myOutboundAdapter) Dependencies() []string { - return a.dependencies -} - -func (a *myOutboundAdapter) NewError(ctx context.Context, err error) { - NewError(a.logger, ctx, err) -} - -func withDialerDependency(options option.DialerOptions) []string { - if options.Detour != "" { - return []string{options.Detour} - } - return nil -} - func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext) error { ctx = adapter.WithContext(ctx, &metadata) var outConn net.Conn @@ -233,12 +195,3 @@ func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) erro } return bufio.CopyConn(ctx, conn, serverConn) } - -func NewError(logger log.ContextLogger, ctx context.Context, err error) { - common.Close(err) - if E.IsClosedOrCanceled(err) { - logger.DebugContext(ctx, "connection closed: ", err) - return - } - logger.ErrorContext(ctx, err) -} diff --git a/adapter/outbound/registry.go b/adapter/outbound/registry.go new file mode 100644 index 000000000..f25631cf1 --- /dev/null +++ b/adapter/outbound/registry.go @@ -0,0 +1,68 @@ +package outbound + +import ( + "context" + "sync" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" +) + +type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Outbound, error) + +func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) { + registry.register(outboundType, func() any { + return new(Options) + }, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Outbound, error) { + return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options.(*Options))) + }) +} + +var _ adapter.OutboundRegistry = (*Registry)(nil) + +type ( + optionsConstructorFunc func() any + constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Outbound, error) +) + +type Registry struct { + access sync.Mutex + optionsType map[string]optionsConstructorFunc + constructors map[string]constructorFunc +} + +func NewRegistry() *Registry { + return &Registry{ + optionsType: make(map[string]optionsConstructorFunc), + constructors: make(map[string]constructorFunc), + } +} + +func (r *Registry) CreateOptions(outboundType string) (any, bool) { + r.access.Lock() + defer r.access.Unlock() + optionsConstructor, loaded := r.optionsType[outboundType] + if !loaded { + return nil, false + } + return optionsConstructor(), true +} + +func (r *Registry) CreateOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Outbound, error) { + r.access.Lock() + defer r.access.Unlock() + constructor, loaded := r.constructors[outboundType] + if !loaded { + return nil, E.New("outbound type not found: " + outboundType) + } + return constructor(ctx, router, logger, tag, options) +} + +func (r *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) { + r.access.Lock() + defer r.access.Unlock() + r.optionsType[outboundType] = optionsConstructor + r.constructors[outboundType] = constructor +} diff --git a/box.go b/box.go index 716b1b093..84da77c04 100644 --- a/box.go +++ b/box.go @@ -14,10 +14,9 @@ import ( "github.com/sagernet/sing-box/experimental" "github.com/sagernet/sing-box/experimental/cachefile" "github.com/sagernet/sing-box/experimental/libbox/platform" - "github.com/sagernet/sing-box/inbound" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-box/outbound" + "github.com/sagernet/sing-box/protocol/direct" "github.com/sagernet/sing-box/route" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" @@ -44,16 +43,37 @@ type Box struct { type Options struct { option.Options Context context.Context - PlatformInterface platform.Interface PlatformLogWriter log.PlatformWriter } +func Context(ctx context.Context, inboundRegistry adapter.InboundRegistry, outboundRegistry adapter.OutboundRegistry) context.Context { + if service.FromContext[option.InboundOptionsRegistry](ctx) == nil || + service.FromContext[adapter.InboundRegistry](ctx) == nil { + ctx = service.ContextWith[option.InboundOptionsRegistry](ctx, inboundRegistry) + ctx = service.ContextWith[adapter.InboundRegistry](ctx, inboundRegistry) + } + if service.FromContext[option.OutboundOptionsRegistry](ctx) == nil || + service.FromContext[adapter.OutboundRegistry](ctx) == nil { + ctx = service.ContextWith[option.OutboundOptionsRegistry](ctx, outboundRegistry) + ctx = service.ContextWith[adapter.OutboundRegistry](ctx, outboundRegistry) + } + return ctx +} + func New(options Options) (*Box, error) { createdAt := time.Now() ctx := options.Context if ctx == nil { ctx = context.Background() } + inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx) + if inboundRegistry == nil { + return nil, E.New("missing inbound registry in context") + } + outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx) + if outboundRegistry == nil { + return nil, E.New("missing outbound registry in context") + } ctx = service.ContextWithDefaultRegistry(ctx) ctx = pause.WithDefaultManager(ctx) experimentalOptions := common.PtrValueOrDefault(options.Experimental) @@ -70,8 +90,9 @@ func New(options Options) (*Box, error) { if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" { needV2RayAPI = true } + platformInterface := service.FromContext[platform.Interface](ctx) var defaultLogWriter io.Writer - if options.PlatformInterface != nil { + if platformInterface != nil { defaultLogWriter = io.Discard } logFactory, err := log.New(log.Options{ @@ -92,64 +113,92 @@ func New(options Options) (*Box, error) { common.PtrValueOrDefault(options.DNS), common.PtrValueOrDefault(options.NTP), options.Inbounds, - options.PlatformInterface, ) if err != nil { return nil, E.Cause(err, "parse route options") } + //nolint:staticcheck + if len(options.LegacyInbounds) > 0 { + for _, legacyInbound := range options.LegacyInbounds { + options.Inbounds = append(options.Inbounds, option.Inbound{ + Type: legacyInbound.Type, + Tag: legacyInbound.Tag, + Options: common.Must1(legacyInbound.RawOptions()), + }) + } + } inbounds := make([]adapter.Inbound, 0, len(options.Inbounds)) + //nolint:staticcheck + if len(options.LegacyOutbounds) > 0 { + for _, legacyOutbound := range options.LegacyOutbounds { + options.Outbounds = append(options.Outbounds, option.Outbound{ + Type: legacyOutbound.Type, + Tag: legacyOutbound.Tag, + Options: common.Must1(legacyOutbound.RawOptions()), + }) + } + } outbounds := make([]adapter.Outbound, 0, len(options.Outbounds)) for i, inboundOptions := range options.Inbounds { - var in adapter.Inbound + var currentInbound adapter.Inbound var tag string if inboundOptions.Tag != "" { tag = inboundOptions.Tag } else { tag = F.ToString(i) } - in, err = inbound.New( + currentInbound, err = inboundRegistry.CreateInbound( ctx, router, logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")), tag, - inboundOptions, - options.PlatformInterface, + inboundOptions.Type, + inboundOptions.Options, ) if err != nil { return nil, E.Cause(err, "parse inbound[", i, "]") } - inbounds = append(inbounds, in) + inbounds = append(inbounds, currentInbound) } for i, outboundOptions := range options.Outbounds { - var out adapter.Outbound + var currentOutbound adapter.Outbound var tag string if outboundOptions.Tag != "" { tag = outboundOptions.Tag } else { tag = F.ToString(i) } - out, err = outbound.New( - ctx, + outboundCtx := ctx + if tag != "" { + // TODO: remove this + outboundCtx = adapter.WithContext(outboundCtx, &adapter.InboundContext{ + Outbound: tag, + }) + } + currentOutbound, err = outboundRegistry.CreateOutbound( + outboundCtx, router, logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")), tag, - outboundOptions) + outboundOptions.Type, + outboundOptions.Options, + ) if err != nil { return nil, E.Cause(err, "parse outbound[", i, "]") } - outbounds = append(outbounds, out) + outbounds = append(outbounds, currentOutbound) } err = router.Initialize(inbounds, outbounds, func() adapter.Outbound { - out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.Outbound{Type: "direct", Tag: "default"}) - common.Must(oErr) - outbounds = append(outbounds, out) - return out + defaultOutbound, cErr := direct.NewOutbound(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.DirectOutboundOptions{}) + common.Must(cErr) + outbounds = append(outbounds, defaultOutbound) + return defaultOutbound }) if err != nil { return nil, err } - if options.PlatformInterface != nil { - err = options.PlatformInterface.Initialize(ctx, router) + if platformInterface != nil { + err = platformInterface.Initialize(ctx, router) if err != nil { return nil, E.Cause(err, "initialize platform interface") } diff --git a/cmd/sing-box/cmd.go b/cmd/sing-box/cmd.go index a2d4a00b3..dc7a83096 100644 --- a/cmd/sing-box/cmd.go +++ b/cmd/sing-box/cmd.go @@ -7,8 +7,9 @@ import ( "strconv" "time" + "github.com/sagernet/sing-box" "github.com/sagernet/sing-box/experimental/deprecated" - _ "github.com/sagernet/sing-box/include" + "github.com/sagernet/sing-box/include" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/filemanager" @@ -68,4 +69,5 @@ func preRun(cmd *cobra.Command, args []string) { configPaths = append(configPaths, "config.json") } globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger())) + globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry()) } diff --git a/cmd/sing-box/cmd_format.go b/cmd/sing-box/cmd_format.go index fc47c5a8d..9856c7633 100644 --- a/cmd/sing-box/cmd_format.go +++ b/cmd/sing-box/cmd_format.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "context" "os" "path/filepath" @@ -38,7 +39,7 @@ func format() error { return err } for _, optionsEntry := range optionsList { - optionsEntry.options, err = badjson.Omitempty(optionsEntry.options) + optionsEntry.options, err = badjson.Omitempty(context.TODO(), optionsEntry.options) if err != nil { return err } diff --git a/cmd/sing-box/cmd_merge.go b/cmd/sing-box/cmd_merge.go index 10dd38a1d..fa194ed34 100644 --- a/cmd/sing-box/cmd_merge.go +++ b/cmd/sing-box/cmd_merge.go @@ -68,29 +68,19 @@ func merge(outputPath string) error { } func mergePathResources(options *option.Options) error { - for index, inbound := range options.Inbounds { - rawOptions, err := inbound.RawOptions() - if err != nil { - return err - } - if tlsOptions, containsTLSOptions := rawOptions.(option.InboundTLSOptionsWrapper); containsTLSOptions { + for _, inbound := range options.Inbounds { + if tlsOptions, containsTLSOptions := inbound.Options.(option.InboundTLSOptionsWrapper); containsTLSOptions { tlsOptions.ReplaceInboundTLSOptions(mergeTLSInboundOptions(tlsOptions.TakeInboundTLSOptions())) } - options.Inbounds[index] = inbound } - for index, outbound := range options.Outbounds { - rawOptions, err := outbound.RawOptions() - if err != nil { - return err - } + for _, outbound := range options.Outbounds { switch outbound.Type { case C.TypeSSH: - outbound.SSHOptions = mergeSSHOutboundOptions(outbound.SSHOptions) + mergeSSHOutboundOptions(outbound.Options.(*option.SSHOutboundOptions)) } - if tlsOptions, containsTLSOptions := rawOptions.(option.OutboundTLSOptionsWrapper); containsTLSOptions { + if tlsOptions, containsTLSOptions := outbound.Options.(option.OutboundTLSOptionsWrapper); containsTLSOptions { tlsOptions.ReplaceOutboundTLSOptions(mergeTLSOutboundOptions(tlsOptions.TakeOutboundTLSOptions())) } - options.Outbounds[index] = outbound } return nil } @@ -138,13 +128,12 @@ func mergeTLSOutboundOptions(options *option.OutboundTLSOptions) *option.Outboun return options } -func mergeSSHOutboundOptions(options option.SSHOutboundOptions) option.SSHOutboundOptions { +func mergeSSHOutboundOptions(options *option.SSHOutboundOptions) { if options.PrivateKeyPath != "" { if content, err := os.ReadFile(os.ExpandEnv(options.PrivateKeyPath)); err == nil { options.PrivateKey = trimStringArray(strings.Split(string(content), "\n")) } } - return options } func trimStringArray(array []string) []string { diff --git a/cmd/sing-box/cmd_run.go b/cmd/sing-box/cmd_run.go index 6850cd19e..f31db9dc8 100644 --- a/cmd/sing-box/cmd_run.go +++ b/cmd/sing-box/cmd_run.go @@ -57,7 +57,7 @@ func readConfigAt(path string) (*OptionsEntry, error) { if err != nil { return nil, E.Cause(err, "read config at ", path) } - options, err := json.UnmarshalExtended[option.Options](configContent) + options, err := json.UnmarshalExtendedContext[option.Options](globalCtx, configContent) if err != nil { return nil, E.Cause(err, "decode config at ", path) } @@ -109,13 +109,13 @@ func readConfigAndMerge() (option.Options, error) { } var mergedMessage json.RawMessage for _, options := range optionsList { - mergedMessage, err = badjson.MergeJSON(options.options.RawMessage, mergedMessage, false) + mergedMessage, err = badjson.MergeJSON(globalCtx, options.options.RawMessage, mergedMessage, false) if err != nil { return option.Options{}, E.Cause(err, "merge config at ", options.path) } } var mergedOptions option.Options - err = mergedOptions.UnmarshalJSON(mergedMessage) + err = mergedOptions.UnmarshalJSONContext(globalCtx, mergedMessage) if err != nil { return option.Options{}, E.Cause(err, "unmarshal merged config") } diff --git a/common/dialer/default.go b/common/dialer/default.go index b4bca55e9..b8a0d5f45 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -125,7 +125,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi setMultiPathTCP(&dialer4) } if options.IsWireGuardListener { - for _, controlFn := range wgControlFns { + for _, controlFn := range WgControlFns { listener.Control = control.Append(listener.Control, controlFn) } } diff --git a/common/dialer/wireguard.go b/common/dialer/wireguard.go index 195133c65..fbd323d80 100644 --- a/common/dialer/wireguard.go +++ b/common/dialer/wireguard.go @@ -2,8 +2,12 @@ package dialer import ( "net" + + "github.com/sagernet/sing/common/control" ) type WireGuardListener interface { ListenPacketCompat(network, address string) (net.PacketConn, error) } + +var WgControlFns []control.Func diff --git a/common/dialer/wireguard_control.go b/common/dialer/wireguard_control.go deleted file mode 100644 index def86411a..000000000 --- a/common/dialer/wireguard_control.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build with_wireguard - -package dialer - -import ( - "github.com/sagernet/wireguard-go/conn" -) - -var _ WireGuardListener = (conn.Listener)(nil) - -var wgControlFns = conn.ControlFns diff --git a/common/dialer/wiregurad_stub.go b/common/dialer/wiregurad_stub.go deleted file mode 100644 index d30c223a9..000000000 --- a/common/dialer/wiregurad_stub.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build !with_wireguard - -package dialer - -import ( - "github.com/sagernet/sing/common/control" -) - -var wgControlFns []control.Func diff --git a/common/listener/listener.go b/common/listener/listener.go new file mode 100644 index 000000000..b42b0434b --- /dev/null +++ b/common/listener/listener.go @@ -0,0 +1,136 @@ +package listener + +import ( + "context" + "net" + "sync/atomic" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/settings" + "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" +) + +type Listener struct { + ctx context.Context + logger logger.ContextLogger + network []string + listenOptions option.ListenOptions + connHandler adapter.ConnectionHandlerEx + packetHandler adapter.PacketHandlerEx + oobPacketHandler adapter.OOBPacketHandlerEx + threadUnsafePacketWriter bool + disablePacketOutput bool + setSystemProxy bool + systemProxySOCKS bool + + tcpListener net.Listener + systemProxy settings.SystemProxy + udpConn *net.UDPConn + udpAddr M.Socksaddr + packetOutbound chan *N.PacketBuffer + packetOutboundClosed chan struct{} + shutdown atomic.Bool +} + +type Options struct { + Context context.Context + Logger logger.ContextLogger + Network []string + Listen option.ListenOptions + ConnectionHandler adapter.ConnectionHandlerEx + PacketHandler adapter.PacketHandlerEx + OOBPacketHandler adapter.OOBPacketHandlerEx + ThreadUnsafePacketWriter bool + DisablePacketOutput bool + SetSystemProxy bool + SystemProxySOCKS bool +} + +func New( + options Options, +) *Listener { + return &Listener{ + ctx: options.Context, + logger: options.Logger, + network: options.Network, + listenOptions: options.Listen, + connHandler: options.ConnectionHandler, + packetHandler: options.PacketHandler, + oobPacketHandler: options.OOBPacketHandler, + threadUnsafePacketWriter: options.ThreadUnsafePacketWriter, + disablePacketOutput: options.DisablePacketOutput, + setSystemProxy: options.SetSystemProxy, + systemProxySOCKS: options.SystemProxySOCKS, + } +} + +func (l *Listener) Start() error { + if common.Contains(l.network, N.NetworkTCP) { + _, err := l.ListenTCP() + if err != nil { + return err + } + go l.loopTCPIn() + } + if common.Contains(l.network, N.NetworkUDP) { + _, err := l.ListenUDP() + if err != nil { + return err + } + l.packetOutboundClosed = make(chan struct{}) + l.packetOutbound = make(chan *N.PacketBuffer, 64) + go l.loopUDPIn() + if !l.disablePacketOutput { + go l.loopUDPOut() + } + } + if l.setSystemProxy { + listenPort := M.SocksaddrFromNet(l.tcpListener.Addr()).Port + var listenAddrString string + listenAddr := l.listenOptions.Listen.Build() + if listenAddr.IsUnspecified() { + listenAddrString = "127.0.0.1" + } else { + listenAddrString = listenAddr.String() + } + systemProxy, err := settings.NewSystemProxy(l.ctx, M.ParseSocksaddrHostPort(listenAddrString, listenPort), l.systemProxySOCKS) + if err != nil { + return E.Cause(err, "initialize system proxy") + } + err = systemProxy.Enable() + if err != nil { + return E.Cause(err, "set system proxy") + } + l.systemProxy = systemProxy + } + return nil +} + +func (l *Listener) Close() error { + l.shutdown.Store(true) + var err error + if l.systemProxy != nil && l.systemProxy.IsEnabled() { + err = l.systemProxy.Disable() + } + return E.Errors(err, common.Close( + l.tcpListener, + common.PtrOrNil(l.udpConn), + )) +} + +func (l *Listener) TCPListener() net.Listener { + return l.tcpListener +} + +func (l *Listener) UDPConn() *net.UDPConn { + return l.udpConn +} + +func (l *Listener) ListenOptions() option.ListenOptions { + return l.listenOptions +} diff --git a/inbound/default_tcp_go1.21.go b/common/listener/listener_go121.go similarity index 90% rename from inbound/default_tcp_go1.21.go rename to common/listener/listener_go121.go index 906818cbe..5af1b05a3 100644 --- a/inbound/default_tcp_go1.21.go +++ b/common/listener/listener_go121.go @@ -1,6 +1,6 @@ //go:build go1.21 -package inbound +package listener import "net" diff --git a/common/listener/listener_go123.go b/common/listener/listener_go123.go new file mode 100644 index 000000000..2e1f4cf43 --- /dev/null +++ b/common/listener/listener_go123.go @@ -0,0 +1,16 @@ +//go:build go1.23 + +package listener + +import ( + "net" + "time" +) + +func setKeepAliveConfig(listener *net.ListenConfig, idle time.Duration, interval time.Duration) { + listener.KeepAliveConfig = net.KeepAliveConfig{ + Enable: true, + Idle: idle, + Interval: interval, + } +} diff --git a/inbound/default_tcp_nongo1.21.go b/common/listener/listener_nongo121.go similarity index 87% rename from inbound/default_tcp_nongo1.21.go rename to common/listener/listener_nongo121.go index d19adb195..36073afe3 100644 --- a/inbound/default_tcp_nongo1.21.go +++ b/common/listener/listener_nongo121.go @@ -1,6 +1,6 @@ //go:build !go1.21 -package inbound +package listener import "net" diff --git a/common/listener/listener_nongo123.go b/common/listener/listener_nongo123.go new file mode 100644 index 000000000..e1582981c --- /dev/null +++ b/common/listener/listener_nongo123.go @@ -0,0 +1,15 @@ +//go:build !go1.23 + +package listener + +import ( + "net" + "time" + + "github.com/sagernet/sing/common/control" +) + +func setKeepAliveConfig(listener *net.ListenConfig, idle time.Duration, interval time.Duration) { + listener.KeepAlive = idle + listener.Control = control.Append(listener.Control, control.SetKeepAlivePeriod(idle, interval)) +} diff --git a/common/listener/listener_tcp.go b/common/listener/listener_tcp.go new file mode 100644 index 000000000..02cef3f04 --- /dev/null +++ b/common/listener/listener_tcp.go @@ -0,0 +1,85 @@ +package listener + +import ( + "net" + "time" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + + "github.com/metacubex/tfo-go" +) + +func (l *Listener) ListenTCP() (net.Listener, error) { + var err error + bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(), l.listenOptions.ListenPort) + var tcpListener net.Listener + var listenConfig net.ListenConfig + if l.listenOptions.TCPKeepAlive >= 0 { + keepIdle := time.Duration(l.listenOptions.TCPKeepAlive) + if keepIdle == 0 { + keepIdle = C.TCPKeepAliveInitial + } + keepInterval := time.Duration(l.listenOptions.TCPKeepAliveInterval) + if keepInterval == 0 { + keepInterval = C.TCPKeepAliveInterval + } + setKeepAliveConfig(&listenConfig, keepIdle, keepInterval) + } + if l.listenOptions.TCPMultiPath { + if !go121Available { + return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.") + } + setMultiPathTCP(&listenConfig) + } + if l.listenOptions.TCPFastOpen { + var tfoConfig tfo.ListenConfig + tfoConfig.ListenConfig = listenConfig + tcpListener, err = tfoConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String()) + } else { + tcpListener, err = listenConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String()) + } + if err == nil { + l.logger.Info("tcp server started at ", tcpListener.Addr()) + } + //nolint:staticcheck + if l.listenOptions.ProxyProtocol || l.listenOptions.ProxyProtocolAcceptNoHeader { + return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0") + } + l.tcpListener = tcpListener + return tcpListener, err +} + +func (l *Listener) loopTCPIn() { + tcpListener := l.tcpListener + var metadata adapter.InboundContext + for { + conn, err := tcpListener.Accept() + if err != nil { + //nolint:staticcheck + if netError, isNetError := err.(net.Error); isNetError && netError.Temporary() { + l.logger.Error(err) + continue + } + if l.shutdown.Load() && E.IsClosed(err) { + return + } + l.tcpListener.Close() + l.logger.Error("tcp listener closed: ", err) + continue + } + //nolint:staticcheck + metadata.InboundDetour = l.listenOptions.Detour + //nolint:staticcheck + metadata.InboundOptions = l.listenOptions.InboundOptions + metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr()).Unwrap() + metadata.OriginDestination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap() + ctx := log.ContextWithNewID(l.ctx) + l.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) + go l.connHandler.NewConnectionEx(ctx, conn, metadata, nil) + } +} diff --git a/common/listener/listener_udp.go b/common/listener/listener_udp.go new file mode 100644 index 000000000..d871c6726 --- /dev/null +++ b/common/listener/listener_udp.go @@ -0,0 +1,154 @@ +package listener + +import ( + "net" + "os" + "time" + + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/control" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +func (l *Listener) ListenUDP() (net.PacketConn, error) { + bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(), l.listenOptions.ListenPort) + var lc net.ListenConfig + var udpFragment bool + if l.listenOptions.UDPFragment != nil { + udpFragment = *l.listenOptions.UDPFragment + } else { + udpFragment = l.listenOptions.UDPFragmentDefault + } + if !udpFragment { + lc.Control = control.Append(lc.Control, control.DisableUDPFragment()) + } + udpConn, err := lc.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String()) + if err != nil { + return nil, err + } + l.udpConn = udpConn.(*net.UDPConn) + l.udpAddr = bindAddr + l.logger.Info("udp server started at ", udpConn.LocalAddr()) + return udpConn, err +} + +func (l *Listener) UDPAddr() M.Socksaddr { + return l.udpAddr +} + +func (l *Listener) PacketWriter() N.PacketWriter { + return (*packetWriter)(l) +} + +func (l *Listener) loopUDPIn() { + defer close(l.packetOutboundClosed) + var buffer *buf.Buffer + if !l.threadUnsafePacketWriter { + buffer = buf.NewPacket() + defer buffer.Release() + buffer.IncRef() + defer buffer.DecRef() + } + if l.oobPacketHandler != nil { + oob := make([]byte, 1024) + for { + if l.threadUnsafePacketWriter { + buffer = buf.NewPacket() + } else { + buffer.Reset() + } + n, oobN, _, addr, err := l.udpConn.ReadMsgUDPAddrPort(buffer.FreeBytes(), oob) + if err != nil { + if l.threadUnsafePacketWriter { + buffer.Release() + } + if l.shutdown.Load() && E.IsClosed(err) { + return + } + l.udpConn.Close() + l.logger.Error("udp listener closed: ", err) + return + } + buffer.Truncate(n) + l.oobPacketHandler.NewPacketEx(buffer, oob[:oobN], M.SocksaddrFromNetIP(addr).Unwrap()) + } + } else { + for { + if l.threadUnsafePacketWriter { + buffer = buf.NewPacket() + } else { + buffer.Reset() + } + n, addr, err := l.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes()) + if err != nil { + if l.threadUnsafePacketWriter { + buffer.Release() + } + if l.shutdown.Load() && E.IsClosed(err) { + return + } + l.udpConn.Close() + l.logger.Error("udp listener closed: ", err) + return + } + buffer.Truncate(n) + l.packetHandler.NewPacketEx(buffer, M.SocksaddrFromNetIP(addr).Unwrap()) + } + } +} + +func (l *Listener) loopUDPOut() { + for { + select { + case packet := <-l.packetOutbound: + destination := packet.Destination.AddrPort() + _, err := l.udpConn.WriteToUDPAddrPort(packet.Buffer.Bytes(), destination) + packet.Buffer.Release() + N.PutPacketBuffer(packet) + if err != nil { + if l.shutdown.Load() && E.IsClosed(err) { + return + } + l.udpConn.Close() + l.logger.Error("udp listener write back: ", destination, ": ", err) + return + } + continue + case <-l.packetOutboundClosed: + } + for { + select { + case packet := <-l.packetOutbound: + packet.Buffer.Release() + N.PutPacketBuffer(packet) + case <-time.After(time.Second): + return + } + } + } +} + +type packetWriter Listener + +func (w *packetWriter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { + packet := N.NewPacketBuffer() + packet.Buffer = buffer + packet.Destination = destination + select { + case w.packetOutbound <- packet: + return nil + default: + buffer.Release() + N.PutPacketBuffer(packet) + if w.shutdown.Load() { + return os.ErrClosed + } + w.logger.Trace("dropped packet to ", destination) + return nil + } +} + +func (w *packetWriter) WriteIsThreadUnsafe() { +} diff --git a/experimental/clashapi/api_meta_group.go b/experimental/clashapi/api_meta_group.go index 396dee7f6..531311f4f 100644 --- a/experimental/clashapi/api_meta_group.go +++ b/experimental/clashapi/api_meta_group.go @@ -10,7 +10,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/urltest" - "github.com/sagernet/sing-box/outbound" + "github.com/sagernet/sing-box/protocol/group" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/batch" "github.com/sagernet/sing/common/json/badjson" @@ -59,7 +59,7 @@ func getGroup(server *Server) func(w http.ResponseWriter, r *http.Request) { func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound) - group, ok := proxy.(adapter.OutboundGroup) + outboundGroup, ok := proxy.(adapter.OutboundGroup) if !ok { render.Status(r, http.StatusNotFound) render.JSON(w, r, ErrNotFound) @@ -82,10 +82,10 @@ func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) defer cancel() var result map[string]uint16 - if urlTestGroup, isURLTestGroup := group.(adapter.URLTestGroup); isURLTestGroup { + if urlTestGroup, isURLTestGroup := outboundGroup.(adapter.URLTestGroup); isURLTestGroup { result, err = urlTestGroup.URLTest(ctx) } else { - outbounds := common.FilterNotNil(common.Map(group.All(), func(it string) adapter.Outbound { + outbounds := common.FilterNotNil(common.Map(outboundGroup.All(), func(it string) adapter.Outbound { itOutbound, _ := server.router.Outbound(it) return itOutbound })) @@ -95,7 +95,7 @@ func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) var resultAccess sync.Mutex for _, detour := range outbounds { tag := detour.Tag() - realTag := outbound.RealTag(detour) + realTag := group.RealTag(detour) if checked[realTag] { continue } diff --git a/experimental/clashapi/proxies.go b/experimental/clashapi/proxies.go index 7a807c1fa..4a9564ee8 100644 --- a/experimental/clashapi/proxies.go +++ b/experimental/clashapi/proxies.go @@ -11,7 +11,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/urltest" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/outbound" + "github.com/sagernet/sing-box/protocol/group" "github.com/sagernet/sing/common" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json/badjson" @@ -168,7 +168,7 @@ func updateProxy(w http.ResponseWriter, r *http.Request) { } proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound) - selector, ok := proxy.(*outbound.Selector) + selector, ok := proxy.(*group.Selector) if !ok { render.Status(r, http.StatusBadRequest) render.JSON(w, r, newError("Must be a Selector")) @@ -204,7 +204,7 @@ func getProxyDelay(server *Server) func(w http.ResponseWriter, r *http.Request) delay, err := urltest.URLTest(ctx, url, proxy) defer func() { - realTag := outbound.RealTag(proxy) + realTag := group.RealTag(proxy) if err != nil { server.urlTestHistory.DeleteURLTestHistory(realTag) } else { diff --git a/experimental/libbox/command_group.go b/experimental/libbox/command_group.go index 3a8d2a074..0915f56b8 100644 --- a/experimental/libbox/command_group.go +++ b/experimental/libbox/command_group.go @@ -9,7 +9,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/urltest" - "github.com/sagernet/sing-box/outbound" + "github.com/sagernet/sing-box/protocol/group" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/varbin" "github.com/sagernet/sing/service" @@ -118,14 +118,14 @@ func writeGroups(writer io.Writer, boxService *BoxService) error { } var groups []OutboundGroup for _, iGroup := range iGroups { - var group OutboundGroup - group.Tag = iGroup.Tag() - group.Type = iGroup.Type() - _, group.Selectable = iGroup.(*outbound.Selector) - group.Selected = iGroup.Now() + var outboundGroup OutboundGroup + outboundGroup.Tag = iGroup.Tag() + outboundGroup.Type = iGroup.Type() + _, outboundGroup.Selectable = iGroup.(*group.Selector) + outboundGroup.Selected = iGroup.Now() if cacheFile != nil { - if isExpand, loaded := cacheFile.LoadGroupExpand(group.Tag); loaded { - group.IsExpand = isExpand + if isExpand, loaded := cacheFile.LoadGroupExpand(outboundGroup.Tag); loaded { + outboundGroup.IsExpand = isExpand } } @@ -142,12 +142,12 @@ func writeGroups(writer io.Writer, boxService *BoxService) error { item.URLTestTime = history.Time.Unix() item.URLTestDelay = int32(history.Delay) } - group.ItemList = append(group.ItemList, &item) + outboundGroup.ItemList = append(outboundGroup.ItemList, &item) } - if len(group.ItemList) < 2 { + if len(outboundGroup.ItemList) < 2 { continue } - groups = append(groups, group) + groups = append(groups, outboundGroup) } return varbin.Write(writer, binary.BigEndian, groups) } diff --git a/experimental/libbox/command_select.go b/experimental/libbox/command_select.go index e1e67e606..f352005d1 100644 --- a/experimental/libbox/command_select.go +++ b/experimental/libbox/command_select.go @@ -4,7 +4,7 @@ import ( "encoding/binary" "net" - "github.com/sagernet/sing-box/outbound" + "github.com/sagernet/sing-box/protocol/group" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/varbin" ) @@ -47,7 +47,7 @@ func (s *CommandServer) handleSelectOutbound(conn net.Conn) error { if !isLoaded { return writeError(conn, E.New("selector not found: ", groupTag)) } - selector, isSelector := outboundGroup.(*outbound.Selector) + selector, isSelector := outboundGroup.(*group.Selector) if !isSelector { return writeError(conn, E.New("outbound is not a selector: ", groupTag)) } diff --git a/experimental/libbox/command_urltest.go b/experimental/libbox/command_urltest.go index 6feda3f8e..c72ded8a3 100644 --- a/experimental/libbox/command_urltest.go +++ b/experimental/libbox/command_urltest.go @@ -7,7 +7,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/urltest" - "github.com/sagernet/sing-box/outbound" + "github.com/sagernet/sing-box/protocol/group" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/batch" E "github.com/sagernet/sing/common/exceptions" @@ -49,7 +49,7 @@ func (s *CommandServer) handleURLTest(conn net.Conn) error { if !isOutboundGroup { return writeError(conn, E.New("outbound is not a group: ", groupTag)) } - urlTest, isURLTest := abstractOutboundGroup.(*outbound.URLTest) + urlTest, isURLTest := abstractOutboundGroup.(*group.URLTest) if isURLTest { go urlTest.CheckOutbounds() } else { diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index 56c561898..53889d300 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -10,6 +10,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/process" "github.com/sagernet/sing-box/experimental/libbox/platform" + "github.com/sagernet/sing-box/include" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common/control" @@ -17,10 +18,11 @@ import ( "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/x/list" + "github.com/sagernet/sing/service" ) -func parseConfig(configContent string) (option.Options, error) { - options, err := json.UnmarshalExtended[option.Options]([]byte(configContent)) +func parseConfig(ctx context.Context, configContent string) (option.Options, error) { + options, err := json.UnmarshalExtendedContext[option.Options](ctx, []byte(configContent)) if err != nil { return option.Options{}, E.Cause(err, "decode config") } @@ -28,16 +30,17 @@ func parseConfig(configContent string) (option.Options, error) { } func CheckConfig(configContent string) error { - options, err := parseConfig(configContent) + ctx := box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry()) + options, err := parseConfig(ctx, configContent) if err != nil { return err } - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(ctx) defer cancel() + ctx = service.ContextWith[platform.Interface](ctx, (*platformInterfaceStub)(nil)) instance, err := box.New(box.Options{ - Context: ctx, - Options: options, - PlatformInterface: (*platformInterfaceStub)(nil), + Context: ctx, + Options: options, }) if err == nil { instance.Close() @@ -140,7 +143,7 @@ func (s *platformInterfaceStub) SendNotification(notification *platform.Notifica } func FormatConfig(configContent string) (string, error) { - options, err := parseConfig(configContent) + options, err := parseConfig(box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry()), configContent) if err != nil { return "", err } diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 20395e95d..eadd4c276 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -17,6 +17,7 @@ import ( "github.com/sagernet/sing-box/experimental/deprecated" "github.com/sagernet/sing-box/experimental/libbox/internal/procfs" "github.com/sagernet/sing-box/experimental/libbox/platform" + "github.com/sagernet/sing-box/include" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-tun" @@ -41,21 +42,22 @@ type BoxService struct { } func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) { - options, err := parseConfig(configContent) + ctx := box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry()) + options, err := parseConfig(ctx, configContent) if err != nil { return nil, err } runtimeDebug.FreeOSMemory() - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(ctx) ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID) urlTestHistoryStorage := urltest.NewHistoryStorage() ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage) ctx = service.ContextWith[deprecated.Manager](ctx, new(deprecatedManager)) platformWrapper := &platformInterfaceWrapper{iif: platformInterface, useProcFS: platformInterface.UseProcFS()} + ctx = service.ContextWith[platform.Interface](ctx, platformWrapper) instance, err := box.New(box.Options{ Context: ctx, Options: options, - PlatformInterface: platformWrapper, PlatformLogWriter: platformWrapper, }) if err != nil { diff --git a/experimental/libbox/setup.go b/experimental/libbox/setup.go index eb5d7c4e3..daafecca8 100644 --- a/experimental/libbox/setup.go +++ b/experimental/libbox/setup.go @@ -9,7 +9,6 @@ import ( "github.com/sagernet/sing-box/common/humanize" C "github.com/sagernet/sing-box/constant" - _ "github.com/sagernet/sing-box/include" "github.com/sagernet/sing-box/log" ) diff --git a/go.mod b/go.mod index 70cc85772..bf88f1d33 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/sagernet/sing-box go 1.20 require ( - berty.tech/go-libtor v1.0.385 github.com/caddyserver/certmagic v0.20.0 github.com/cloudflare/circl v1.3.7 github.com/cretz/bine v0.2.0 @@ -17,7 +16,6 @@ require ( github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa github.com/mholt/acmez v1.2.0 github.com/miekg/dns v1.1.62 - github.com/ooni/go-libtor v1.1.8 github.com/oschwald/maxminddb-golang v1.12.0 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 diff --git a/go.sum b/go.sum index ad5c611c1..3b789c9b4 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -berty.tech/go-libtor v1.0.385 h1:RWK94C3hZj6Z2GdvePpHJLnWYobFr3bY/OdUJ5aoEXw= -berty.tech/go-libtor v1.0.385/go.mod h1:9swOOQVb+kmvuAlsgWUK/4c52pm69AdbJsxLzk+fJEw= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= @@ -9,7 +7,6 @@ github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NY github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw= github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo= github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -81,8 +78,6 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss= github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0= github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= -github.com/ooni/go-libtor v1.1.8 h1:Wo3V3DVTxl5vZdxtQakqYP+DAHx7pPtAFSl1bnAa08w= -github.com/ooni/go-libtor v1.1.8/go.mod h1:q1YyLwRD9GeMyeerVvwc0vJ2YgwDLTp2bdVcrh/JXyI= github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs= github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY= github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE= @@ -146,7 +141,6 @@ github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3k github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= @@ -168,7 +162,6 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= -golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= @@ -182,7 +175,6 @@ golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/inbound/builder.go b/inbound/builder.go deleted file mode 100644 index ddfd361db..000000000 --- a/inbound/builder.go +++ /dev/null @@ -1,54 +0,0 @@ -package inbound - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/experimental/libbox/platform" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - E "github.com/sagernet/sing/common/exceptions" -) - -func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Inbound, platformInterface platform.Interface) (adapter.Inbound, error) { - if options.Type == "" { - return nil, E.New("missing inbound type") - } - switch options.Type { - case C.TypeTun: - return NewTun(ctx, router, logger, tag, options.TunOptions, platformInterface) - case C.TypeRedirect: - return NewRedirect(ctx, router, logger, tag, options.RedirectOptions), nil - case C.TypeTProxy: - return NewTProxy(ctx, router, logger, tag, options.TProxyOptions), nil - case C.TypeDirect: - return NewDirect(ctx, router, logger, tag, options.DirectOptions), nil - case C.TypeSOCKS: - return NewSocks(ctx, router, logger, tag, options.SocksOptions), nil - case C.TypeHTTP: - return NewHTTP(ctx, router, logger, tag, options.HTTPOptions) - case C.TypeMixed: - return NewMixed(ctx, router, logger, tag, options.MixedOptions), nil - case C.TypeShadowsocks: - return NewShadowsocks(ctx, router, logger, tag, options.ShadowsocksOptions) - case C.TypeVMess: - return NewVMess(ctx, router, logger, tag, options.VMessOptions) - case C.TypeTrojan: - return NewTrojan(ctx, router, logger, tag, options.TrojanOptions) - case C.TypeNaive: - return NewNaive(ctx, router, logger, tag, options.NaiveOptions) - case C.TypeHysteria: - return NewHysteria(ctx, router, logger, tag, options.HysteriaOptions) - case C.TypeShadowTLS: - return NewShadowTLS(ctx, router, logger, tag, options.ShadowTLSOptions) - case C.TypeVLESS: - return NewVLESS(ctx, router, logger, tag, options.VLESSOptions) - case C.TypeTUIC: - return NewTUIC(ctx, router, logger, tag, options.TUICOptions) - case C.TypeHysteria2: - return NewHysteria2(ctx, router, logger, tag, options.Hysteria2Options) - default: - return nil, E.New("unknown inbound type: ", options.Type) - } -} diff --git a/inbound/default.go b/inbound/default.go deleted file mode 100644 index 880dd26f6..000000000 --- a/inbound/default.go +++ /dev/null @@ -1,209 +0,0 @@ -package inbound - -import ( - "context" - "net" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/settings" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/atomic" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -var _ adapter.Inbound = (*myInboundAdapter)(nil) - -type myInboundAdapter struct { - protocol string - network []string - ctx context.Context - router adapter.ConnectionRouterEx - logger log.ContextLogger - tag string - listenOptions option.ListenOptions - connHandler adapter.ConnectionHandlerEx - packetHandler adapter.PacketHandlerEx - oobPacketHandler adapter.OOBPacketHandlerEx - packetUpstream any - - // http mixed - - setSystemProxy bool - systemProxy settings.SystemProxy - - // internal - - tcpListener net.Listener - udpConn *net.UDPConn - udpAddr M.Socksaddr - packetOutboundClosed chan struct{} - packetOutbound chan *myInboundPacket - - inShutdown atomic.Bool -} - -func (a *myInboundAdapter) Type() string { - return a.protocol -} - -func (a *myInboundAdapter) Tag() string { - return a.tag -} - -func (a *myInboundAdapter) Start() error { - var err error - if common.Contains(a.network, N.NetworkTCP) { - _, err = a.ListenTCP() - if err != nil { - return err - } - go a.loopTCPIn() - } - if common.Contains(a.network, N.NetworkUDP) { - _, err = a.ListenUDP() - if err != nil { - return err - } - a.packetOutboundClosed = make(chan struct{}) - a.packetOutbound = make(chan *myInboundPacket) - if a.oobPacketHandler != nil { - if _, threadUnsafeHandler := common.Cast[N.ThreadUnsafeWriter](a.packetUpstream); !threadUnsafeHandler { - go a.loopUDPOOBIn() - } else { - go a.loopUDPOOBInThreadSafe() - } - } else { - if _, threadUnsafeHandler := common.Cast[N.ThreadUnsafeWriter](a.packetUpstream); !threadUnsafeHandler { - go a.loopUDPIn() - } else { - go a.loopUDPInThreadSafe() - } - go a.loopUDPOut() - } - } - if a.setSystemProxy { - listenPort := M.SocksaddrFromNet(a.tcpListener.Addr()).Port - var listenAddrString string - listenAddr := a.listenOptions.Listen.Build() - if listenAddr.IsUnspecified() { - listenAddrString = "127.0.0.1" - } else { - listenAddrString = listenAddr.String() - } - var systemProxy settings.SystemProxy - systemProxy, err = settings.NewSystemProxy(a.ctx, M.ParseSocksaddrHostPort(listenAddrString, listenPort), a.protocol == C.TypeMixed) - if err != nil { - return E.Cause(err, "initialize system proxy") - } - err = systemProxy.Enable() - if err != nil { - return E.Cause(err, "set system proxy") - } - a.systemProxy = systemProxy - } - return nil -} - -func (a *myInboundAdapter) Close() error { - a.inShutdown.Store(true) - var err error - if a.systemProxy != nil && a.systemProxy.IsEnabled() { - err = a.systemProxy.Disable() - } - return E.Errors(err, common.Close( - a.tcpListener, - common.PtrOrNil(a.udpConn), - )) -} - -func (a *myInboundAdapter) upstreamHandler(metadata adapter.InboundContext) adapter.UpstreamHandlerAdapter { - return adapter.NewUpstreamHandler(metadata, a.newConnection, a.streamPacketConnection, a) -} - -func (a *myInboundAdapter) upstreamContextHandler() adapter.UpstreamHandlerAdapter { - return adapter.NewUpstreamContextHandler(a.newConnection, a.newPacketConnection, a) -} - -func (a *myInboundAdapter) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - a.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) - return a.router.RouteConnection(ctx, conn, metadata) -} - -func (a *myInboundAdapter) streamPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - a.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) - return a.router.RoutePacketConnection(ctx, conn, metadata) -} - -func (a *myInboundAdapter) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - ctx = log.ContextWithNewID(ctx) - a.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) - a.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) - return a.router.RoutePacketConnection(ctx, conn, metadata) -} - -func (a *myInboundAdapter) upstreamHandlerEx(metadata adapter.InboundContext) adapter.UpstreamHandlerAdapterEx { - return adapter.NewUpstreamHandlerEx(metadata, a.newConnectionEx, a.streamPacketConnectionEx) -} - -func (a *myInboundAdapter) upstreamContextHandlerEx() adapter.UpstreamHandlerAdapterEx { - return adapter.NewUpstreamContextHandlerEx(a.newConnectionEx, a.newPacketConnectionEx) -} - -func (a *myInboundAdapter) newConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - a.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) - a.router.RouteConnectionEx(ctx, conn, metadata, onClose) -} - -func (a *myInboundAdapter) newPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - ctx = log.ContextWithNewID(ctx) - a.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) - a.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) - a.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) -} - -func (a *myInboundAdapter) streamPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - a.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) - a.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) -} - -func (a *myInboundAdapter) createMetadata(conn net.Conn, metadata adapter.InboundContext) adapter.InboundContext { - metadata.Inbound = a.tag - metadata.InboundType = a.protocol - metadata.InboundDetour = a.listenOptions.Detour - metadata.InboundOptions = a.listenOptions.InboundOptions - if !metadata.Source.IsValid() { - metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr()).Unwrap() - } - if !metadata.Destination.IsValid() { - metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap() - } - if tcpConn, isTCP := common.Cast[*net.TCPConn](conn); isTCP { - metadata.OriginDestination = M.SocksaddrFromNet(tcpConn.LocalAddr()).Unwrap() - } - return metadata -} - -// Deprecated: don't use -func (a *myInboundAdapter) newError(err error) { - a.logger.Error(err) -} - -// Deprecated: don't use -func (a *myInboundAdapter) NewError(ctx context.Context, err error) { - NewError(a.logger, ctx, err) -} - -// Deprecated: don't use -func NewError(logger log.ContextLogger, ctx context.Context, err error) { - common.Close(err) - if E.IsClosedOrCanceled(err) { - logger.DebugContext(ctx, "connection closed: ", err) - return - } - logger.ErrorContext(ctx, err) -} diff --git a/inbound/default_tcp.go b/inbound/default_tcp.go deleted file mode 100644 index d38f96fe6..000000000 --- a/inbound/default_tcp.go +++ /dev/null @@ -1,84 +0,0 @@ -package inbound - -import ( - "context" - "net" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing/common/control" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -func (a *myInboundAdapter) ListenTCP() (net.Listener, error) { - var err error - bindAddr := M.SocksaddrFrom(a.listenOptions.Listen.Build(), a.listenOptions.ListenPort) - var tcpListener net.Listener - var listenConfig net.ListenConfig - // TODO: Add an option to customize the keep alive period - listenConfig.KeepAlive = C.TCPKeepAliveInitial - listenConfig.Control = control.Append(listenConfig.Control, control.SetKeepAlivePeriod(C.TCPKeepAliveInitial, C.TCPKeepAliveInterval)) - if a.listenOptions.TCPMultiPath { - if !go121Available { - return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.") - } - setMultiPathTCP(&listenConfig) - } - if a.listenOptions.TCPFastOpen { - if !go120Available { - return nil, E.New("TCP Fast Open requires go1.20, please recompile your binary.") - } - tcpListener, err = listenTFO(listenConfig, a.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String()) - } else { - tcpListener, err = listenConfig.Listen(a.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String()) - } - if err == nil { - a.logger.Info("tcp server started at ", tcpListener.Addr()) - } - if a.listenOptions.ProxyProtocol || a.listenOptions.ProxyProtocolAcceptNoHeader { - return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0") - } - a.tcpListener = tcpListener - return tcpListener, err -} - -func (a *myInboundAdapter) loopTCPIn() { - tcpListener := a.tcpListener - for { - conn, err := tcpListener.Accept() - if err != nil { - //goland:noinspection GoDeprecation - //nolint:staticcheck - if netError, isNetError := err.(net.Error); isNetError && netError.Temporary() { - a.logger.Error(err) - continue - } - if a.inShutdown.Load() && E.IsClosed(err) { - return - } - a.tcpListener.Close() - a.logger.Error("serve error: ", err) - continue - } - go a.injectTCP(conn, adapter.InboundContext{}) - } -} - -func (a *myInboundAdapter) injectTCP(conn net.Conn, metadata adapter.InboundContext) { - ctx := log.ContextWithNewID(a.ctx) - metadata = a.createMetadata(conn, metadata) - a.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) - a.connHandler.NewConnectionEx(ctx, conn, metadata, nil) -} - -func (a *myInboundAdapter) routeTCP(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { - metadata := a.createMetadata(conn, adapter.InboundContext{ - Source: source, - Destination: destination, - }) - a.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) - a.connHandler.NewConnectionEx(ctx, conn, metadata, onClose) -} diff --git a/inbound/default_tcp_go1.20.go b/inbound/default_tcp_go1.20.go deleted file mode 100644 index 23949b067..000000000 --- a/inbound/default_tcp_go1.20.go +++ /dev/null @@ -1,18 +0,0 @@ -//go:build go1.20 - -package inbound - -import ( - "context" - "net" - - "github.com/metacubex/tfo-go" -) - -const go120Available = true - -func listenTFO(listenConfig net.ListenConfig, ctx context.Context, network string, address string) (net.Listener, error) { - var tfoConfig tfo.ListenConfig - tfoConfig.ListenConfig = listenConfig - return tfoConfig.Listen(ctx, network, address) -} diff --git a/inbound/default_tcp_nongo1.20.go b/inbound/default_tcp_nongo1.20.go deleted file mode 100644 index e7a026bc8..000000000 --- a/inbound/default_tcp_nongo1.20.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build !go1.20 - -package inbound - -import ( - "context" - "net" - "os" -) - -const go120Available = false - -func listenTFO(listenConfig net.ListenConfig, ctx context.Context, network string, address string) (net.Listener, error) { - return nil, os.ErrInvalid -} diff --git a/inbound/default_udp.go b/inbound/default_udp.go deleted file mode 100644 index 6bcde79da..000000000 --- a/inbound/default_udp.go +++ /dev/null @@ -1,208 +0,0 @@ -package inbound - -import ( - "net" - "os" - "time" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/buf" - "github.com/sagernet/sing/common/control" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -func (a *myInboundAdapter) ListenUDP() (net.PacketConn, error) { - bindAddr := M.SocksaddrFrom(a.listenOptions.Listen.Build(), a.listenOptions.ListenPort) - var lc net.ListenConfig - var udpFragment bool - if a.listenOptions.UDPFragment != nil { - udpFragment = *a.listenOptions.UDPFragment - } else { - udpFragment = a.listenOptions.UDPFragmentDefault - } - if !udpFragment { - lc.Control = control.Append(lc.Control, control.DisableUDPFragment()) - } - udpConn, err := lc.ListenPacket(a.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String()) - if err != nil { - return nil, err - } - a.udpConn = udpConn.(*net.UDPConn) - a.udpAddr = bindAddr - a.logger.Info("udp server started at ", udpConn.LocalAddr()) - return udpConn, err -} - -func (a *myInboundAdapter) loopUDPIn() { - defer close(a.packetOutboundClosed) - buffer := buf.NewPacket() - defer buffer.Release() - buffer.IncRef() - defer buffer.DecRef() - for { - buffer.Reset() - n, addr, err := a.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes()) - if err != nil { - return - } - buffer.Truncate(n) - a.packetHandler.NewPacketEx(buffer, M.SocksaddrFromNetIP(addr).Unwrap()) - } -} - -func (a *myInboundAdapter) loopUDPOOBIn() { - defer close(a.packetOutboundClosed) - buffer := buf.NewPacket() - defer buffer.Release() - buffer.IncRef() - defer buffer.DecRef() - oob := make([]byte, 1024) - for { - buffer.Reset() - n, oobN, _, addr, err := a.udpConn.ReadMsgUDPAddrPort(buffer.FreeBytes(), oob) - if err != nil { - return - } - buffer.Truncate(n) - a.oobPacketHandler.NewPacketEx(buffer, oob[:oobN], M.SocksaddrFromNetIP(addr).Unwrap()) - } -} - -func (a *myInboundAdapter) loopUDPInThreadSafe() { - defer close(a.packetOutboundClosed) - for { - buffer := buf.NewPacket() - n, addr, err := a.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes()) - if err != nil { - buffer.Release() - return - } - buffer.Truncate(n) - a.packetHandler.NewPacketEx(buffer, M.SocksaddrFromNetIP(addr).Unwrap()) - } -} - -func (a *myInboundAdapter) loopUDPOOBInThreadSafe() { - defer close(a.packetOutboundClosed) - oob := make([]byte, 1024) - for { - buffer := buf.NewPacket() - n, oobN, _, addr, err := a.udpConn.ReadMsgUDPAddrPort(buffer.FreeBytes(), oob) - if err != nil { - buffer.Release() - return - } - buffer.Truncate(n) - a.oobPacketHandler.NewPacketEx(buffer, oob[:oobN], M.SocksaddrFromNetIP(addr).Unwrap()) - } -} - -func (a *myInboundAdapter) loopUDPOut() { - for { - select { - case packet := <-a.packetOutbound: - err := a.writePacket(packet.buffer, packet.destination) - if err != nil && !E.IsClosed(err) { - a.logger.Error(E.New("write back udp: ", err)) - } - continue - case <-a.packetOutboundClosed: - } - for { - select { - case packet := <-a.packetOutbound: - packet.buffer.Release() - default: - return - } - } - } -} - -func (a *myInboundAdapter) packetConn() N.PacketConn { - return (*myInboundPacketAdapter)(a) -} - -func (a *myInboundAdapter) createPacketMetadata(conn N.PacketConn, metadata adapter.InboundContext) adapter.InboundContext { - metadata.Inbound = a.tag - metadata.InboundType = a.protocol - metadata.InboundDetour = a.listenOptions.Detour - metadata.InboundOptions = a.listenOptions.InboundOptions - if !metadata.Destination.IsValid() { - metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap() - } - metadata.OriginDestination = a.udpAddr - return metadata -} - -func (a *myInboundAdapter) createPacketMetadataEx(source M.Socksaddr, destination M.Socksaddr) adapter.InboundContext { - var metadata adapter.InboundContext - metadata.Inbound = a.tag - metadata.InboundType = a.protocol - metadata.InboundDetour = a.listenOptions.Detour - metadata.InboundOptions = a.listenOptions.InboundOptions - metadata.Source = source - metadata.Destination = destination - metadata.OriginDestination = a.udpAddr - return metadata -} - -func (a *myInboundAdapter) writePacket(buffer *buf.Buffer, destination M.Socksaddr) error { - defer buffer.Release() - return common.Error(a.udpConn.WriteToUDPAddrPort(buffer.Bytes(), destination.AddrPort())) -} - -type myInboundPacketAdapter myInboundAdapter - -func (s *myInboundPacketAdapter) ReadPacket(buffer *buf.Buffer) (M.Socksaddr, error) { - n, addr, err := s.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes()) - if err != nil { - return M.Socksaddr{}, err - } - buffer.Truncate(n) - return M.SocksaddrFromNetIP(addr), nil -} - -func (s *myInboundPacketAdapter) WriteIsThreadUnsafe() { -} - -type myInboundPacket struct { - buffer *buf.Buffer - destination M.Socksaddr -} - -func (s *myInboundPacketAdapter) Upstream() any { - return s.udpConn -} - -func (s *myInboundPacketAdapter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { - select { - case s.packetOutbound <- &myInboundPacket{buffer, destination}: - return nil - case <-s.packetOutboundClosed: - return os.ErrClosed - } -} - -func (s *myInboundPacketAdapter) Close() error { - return s.udpConn.Close() -} - -func (s *myInboundPacketAdapter) LocalAddr() net.Addr { - return s.udpConn.LocalAddr() -} - -func (s *myInboundPacketAdapter) SetDeadline(t time.Time) error { - return s.udpConn.SetDeadline(t) -} - -func (s *myInboundPacketAdapter) SetReadDeadline(t time.Time) error { - return s.udpConn.SetReadDeadline(t) -} - -func (s *myInboundPacketAdapter) SetWriteDeadline(t time.Time) error { - return s.udpConn.SetWriteDeadline(t) -} diff --git a/inbound/direct.go b/inbound/direct.go deleted file mode 100644 index b9a99f12c..000000000 --- a/inbound/direct.go +++ /dev/null @@ -1,111 +0,0 @@ -package inbound - -import ( - "context" - "net" - "time" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common/buf" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/udpnat2" -) - -var _ adapter.Inbound = (*Direct)(nil) - -type Direct struct { - myInboundAdapter - udpNat *udpnat.Service - overrideOption int - overrideDestination M.Socksaddr -} - -func NewDirect(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.DirectInboundOptions) *Direct { - options.UDPFragmentDefault = true - inbound := &Direct{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeDirect, - network: options.Network.Build(), - ctx: ctx, - router: router, - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - } - if options.OverrideAddress != "" && options.OverridePort != 0 { - inbound.overrideOption = 1 - inbound.overrideDestination = M.ParseSocksaddrHostPort(options.OverrideAddress, options.OverridePort) - } else if options.OverrideAddress != "" { - inbound.overrideOption = 2 - inbound.overrideDestination = M.ParseSocksaddrHostPort(options.OverrideAddress, options.OverridePort) - } else if options.OverridePort != 0 { - inbound.overrideOption = 3 - inbound.overrideDestination = M.Socksaddr{Port: options.OverridePort} - } - var udpTimeout time.Duration - if options.UDPTimeout != 0 { - udpTimeout = time.Duration(options.UDPTimeout) - } else { - udpTimeout = C.UDPTimeout - } - inbound.udpNat = udpnat.New(inbound, inbound.preparePacketConnection, udpTimeout, false) - inbound.connHandler = inbound - inbound.packetHandler = inbound - return inbound -} - -func (d *Direct) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - switch d.overrideOption { - case 1: - metadata.Destination = d.overrideDestination - case 2: - destination := d.overrideDestination - destination.Port = metadata.Destination.Port - metadata.Destination = destination - case 3: - metadata.Destination.Port = d.overrideDestination.Port - } - d.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) - return d.router.RouteConnection(ctx, conn, metadata) -} - -func (d *Direct) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { - var destination M.Socksaddr - switch d.overrideOption { - case 1: - destination = d.overrideDestination - case 2: - destination = d.overrideDestination - destination.Port = source.Port - case 3: - destination = source - destination.Port = d.overrideDestination.Port - } - d.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, destination, nil) -} - -func (d *Direct) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - d.newConnectionEx(ctx, conn, metadata, onClose) -} - -func (d *Direct) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { - d.newPacketConnectionEx(ctx, conn, d.createPacketMetadataEx(source, destination), onClose) -} - -func (d *Direct) preparePacketConnection(source M.Socksaddr, destination M.Socksaddr, userData any) (bool, context.Context, N.PacketWriter, N.CloseHandlerFunc) { - return true, d.ctx, &directPacketWriter{d.packetConn(), source}, nil -} - -type directPacketWriter struct { - writer N.PacketWriter - source M.Socksaddr -} - -func (w *directPacketWriter) WritePacket(buffer *buf.Buffer, addr M.Socksaddr) error { - return w.writer.WritePacket(buffer, w.source) -} diff --git a/inbound/http.go b/inbound/http.go deleted file mode 100644 index 20c8f6903..000000000 --- a/inbound/http.go +++ /dev/null @@ -1,119 +0,0 @@ -package inbound - -import ( - std_bufio "bufio" - "context" - "net" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/tls" - "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" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/auth" - E "github.com/sagernet/sing/common/exceptions" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/protocol/http" -) - -var ( - _ adapter.Inbound = (*HTTP)(nil) - _ adapter.TCPInjectableInbound = (*HTTP)(nil) -) - -type HTTP struct { - myInboundAdapter - authenticator *auth.Authenticator - tlsConfig tls.ServerConfig -} - -func NewHTTP(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPMixedInboundOptions) (*HTTP, error) { - inbound := &HTTP{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeHTTP, - network: []string{N.NetworkTCP}, - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - setSystemProxy: options.SetSystemProxy, - }, - authenticator: auth.NewAuthenticator(options.Users), - } - if options.TLS != nil { - tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) - if err != nil { - return nil, err - } - inbound.tlsConfig = tlsConfig - } - inbound.connHandler = inbound - return inbound, nil -} - -func (h *HTTP) Start() error { - if h.tlsConfig != nil { - err := h.tlsConfig.Start() - if err != nil { - return E.Cause(err, "create TLS config") - } - } - return h.myInboundAdapter.Start() -} - -func (h *HTTP) Close() error { - return common.Close( - &h.myInboundAdapter, - h.tlsConfig, - ) -} - -func (h *HTTP) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - err := h.newConnection(ctx, conn, metadata, onClose) - N.CloseOnHandshakeFailure(conn, onClose, err) - if err != nil { - h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) - } -} - -func (h *HTTP) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { - var err error - if h.tlsConfig != nil { - conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig) - if err != nil { - return err - } - } - return http.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, nil, h.upstreamUserHandlerEx(metadata), metadata.Source, onClose) -} - -func (a *myInboundAdapter) upstreamUserHandlerEx(metadata adapter.InboundContext) adapter.UpstreamHandlerAdapterEx { - return adapter.NewUpstreamHandlerEx(metadata, a.newUserConnection, a.streamUserPacketConnection) -} - -func (a *myInboundAdapter) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - user, loaded := auth.UserFromContext[string](ctx) - if !loaded { - a.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) - a.router.RouteConnectionEx(ctx, conn, metadata, onClose) - return - } - metadata.User = user - a.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) - a.router.RouteConnectionEx(ctx, conn, metadata, onClose) -} - -func (a *myInboundAdapter) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - user, loaded := auth.UserFromContext[string](ctx) - if !loaded { - a.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) - a.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) - return - } - metadata.User = user - a.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) - a.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) -} diff --git a/inbound/hysteria_stub.go b/inbound/hysteria_stub.go deleted file mode 100644 index fab86bb56..000000000 --- a/inbound/hysteria_stub.go +++ /dev/null @@ -1,20 +0,0 @@ -//go:build !with_quic - -package inbound - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" -) - -func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (adapter.Inbound, error) { - return nil, C.ErrQUICNotIncluded -} - -func NewHysteria2(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2InboundOptions) (adapter.Inbound, error) { - return nil, C.ErrQUICNotIncluded -} diff --git a/inbound/mixed.go b/inbound/mixed.go deleted file mode 100644 index 81f6a43a0..000000000 --- a/inbound/mixed.go +++ /dev/null @@ -1,70 +0,0 @@ -package inbound - -import ( - std_bufio "bufio" - "context" - "net" - - "github.com/sagernet/sing-box/adapter" - "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" - "github.com/sagernet/sing/common/auth" - E "github.com/sagernet/sing/common/exceptions" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/protocol/http" - "github.com/sagernet/sing/protocol/socks" - "github.com/sagernet/sing/protocol/socks/socks4" - "github.com/sagernet/sing/protocol/socks/socks5" -) - -var ( - _ adapter.Inbound = (*Mixed)(nil) - _ adapter.TCPInjectableInbound = (*Mixed)(nil) -) - -type Mixed struct { - myInboundAdapter - authenticator *auth.Authenticator -} - -func NewMixed(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPMixedInboundOptions) *Mixed { - inbound := &Mixed{ - myInboundAdapter{ - protocol: C.TypeMixed, - network: []string{N.NetworkTCP}, - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - setSystemProxy: options.SetSystemProxy, - }, - auth.NewAuthenticator(options.Users), - } - inbound.connHandler = inbound - return inbound -} - -func (h *Mixed) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - err := h.newConnection(ctx, conn, metadata, onClose) - N.CloseOnHandshakeFailure(conn, onClose, err) - if err != nil { - h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) - } -} - -func (h *Mixed) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { - reader := std_bufio.NewReader(conn) - headerBytes, err := reader.Peek(1) - if err != nil { - return E.Cause(err, "peek first byte") - } - switch headerBytes[0] { - case socks4.Version, socks5.Version: - return socks.HandleConnectionEx(ctx, conn, reader, h.authenticator, nil, h.upstreamUserHandlerEx(metadata), metadata.Source, metadata.Destination, onClose) - default: - return http.HandleConnectionEx(ctx, conn, reader, h.authenticator, nil, h.upstreamUserHandlerEx(metadata), metadata.Source, onClose) - } -} diff --git a/inbound/naive_quic.go b/inbound/naive_quic.go deleted file mode 100644 index 9f99bf278..000000000 --- a/inbound/naive_quic.go +++ /dev/null @@ -1,47 +0,0 @@ -//go:build with_quic - -package inbound - -import ( - "github.com/sagernet/quic-go" - "github.com/sagernet/quic-go/http3" - "github.com/sagernet/sing-quic" - E "github.com/sagernet/sing/common/exceptions" -) - -func (n *Naive) configureHTTP3Listener() error { - err := qtls.ConfigureHTTP3(n.tlsConfig) - if err != nil { - return err - } - - udpConn, err := n.ListenUDP() - if err != nil { - return err - } - - quicListener, err := qtls.ListenEarly(udpConn, n.tlsConfig, &quic.Config{ - MaxIncomingStreams: 1 << 60, - Allow0RTT: true, - }) - if err != nil { - udpConn.Close() - return err - } - - h3Server := &http3.Server{ - Port: int(n.listenOptions.ListenPort), - Handler: n, - } - - go func() { - sErr := h3Server.ServeListener(quicListener) - udpConn.Close() - if sErr != nil && !E.IsClosedOrCanceled(sErr) { - n.logger.Error("http3 server serve error: ", sErr) - } - }() - - n.h3Server = h3Server - return nil -} diff --git a/inbound/naive_quic_stub.go b/inbound/naive_quic_stub.go deleted file mode 100644 index 90f697e46..000000000 --- a/inbound/naive_quic_stub.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build !with_quic - -package inbound - -import ( - C "github.com/sagernet/sing-box/constant" -) - -func (n *Naive) configureHTTP3Listener() error { - return C.ErrQUICNotIncluded -} diff --git a/inbound/redirect.go b/inbound/redirect.go deleted file mode 100644 index c4c6faf34..000000000 --- a/inbound/redirect.go +++ /dev/null @@ -1,45 +0,0 @@ -package inbound - -import ( - "context" - "net" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/redir" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -type Redirect struct { - myInboundAdapter -} - -func NewRedirect(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.RedirectInboundOptions) *Redirect { - redirect := &Redirect{ - myInboundAdapter{ - protocol: C.TypeRedirect, - network: []string{N.NetworkTCP}, - ctx: ctx, - router: router, - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - } - redirect.connHandler = redirect - return redirect -} - -func (r *Redirect) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - destination, err := redir.GetOriginalDestination(conn) - if err != nil { - conn.Close() - r.logger.ErrorContext(ctx, "process connection from ", conn.RemoteAddr(), ": get redirect destination: ", err) - return - } - metadata.Destination = M.SocksaddrFromNetIP(destination) - r.newConnectionEx(ctx, conn, metadata, onClose) -} diff --git a/inbound/shadowsocks.go b/inbound/shadowsocks.go deleted file mode 100644 index 3fff231d8..000000000 --- a/inbound/shadowsocks.go +++ /dev/null @@ -1,114 +0,0 @@ -package inbound - -import ( - "context" - "net" - "time" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/mux" - "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" - "github.com/sagernet/sing-shadowsocks" - "github.com/sagernet/sing-shadowsocks/shadowaead" - "github.com/sagernet/sing-shadowsocks/shadowaead_2022" - "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" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/ntp" -) - -func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (adapter.Inbound, error) { - if len(options.Users) > 0 && len(options.Destinations) > 0 { - return nil, E.New("users and destinations options must not be combined") - } - if len(options.Users) > 0 { - return newShadowsocksMulti(ctx, router, logger, tag, options) - } else if len(options.Destinations) > 0 { - return newShadowsocksRelay(ctx, router, logger, tag, options) - } else { - return newShadowsocks(ctx, router, logger, tag, options) - } -} - -var ( - _ adapter.Inbound = (*Shadowsocks)(nil) - _ adapter.TCPInjectableInbound = (*Shadowsocks)(nil) -) - -type Shadowsocks struct { - myInboundAdapter - service shadowsocks.Service -} - -func newShadowsocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*Shadowsocks, error) { - inbound := &Shadowsocks{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeShadowsocks, - network: options.Network.Build(), - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - } - - inbound.connHandler = inbound - inbound.packetHandler = inbound - var err error - inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex)) - if err != nil { - return nil, err - } - - var udpTimeout time.Duration - if options.UDPTimeout != 0 { - udpTimeout = time.Duration(options.UDPTimeout) - } else { - udpTimeout = C.UDPTimeout - } - switch { - case options.Method == shadowsocks.MethodNone: - inbound.service = shadowsocks.NewNoneService(int64(udpTimeout.Seconds()), adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound)) - case common.Contains(shadowaead.List, options.Method): - inbound.service, err = shadowaead.NewService(options.Method, nil, options.Password, int64(udpTimeout.Seconds()), adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound)) - case common.Contains(shadowaead_2022.List, options.Method): - inbound.service, err = shadowaead_2022.NewServiceWithPassword(options.Method, options.Password, int64(udpTimeout.Seconds()), adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound), ntp.TimeFuncFromContext(ctx)) - default: - err = E.New("unsupported method: ", options.Method) - } - inbound.packetUpstream = inbound.service - return inbound, err -} - -func (h *Shadowsocks) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata)) - N.CloseOnHandshakeFailure(conn, onClose, err) - if err != nil { - h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) - } -} - -func (h *Shadowsocks) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { - err := h.service.NewPacket(h.ctx, h.packetConn(), buffer, M.Metadata{Source: source}) - if err != nil { - h.logger.Error(E.Cause(err, "process packet from ", source)) - } -} - -func (h *Shadowsocks) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) - return h.router.RouteConnection(ctx, conn, h.createMetadata(conn, metadata)) -} - -func (h *Shadowsocks) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - ctx = log.ContextWithNewID(ctx) - h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) - h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) - return h.router.RoutePacketConnection(ctx, conn, h.createPacketMetadata(conn, metadata)) -} diff --git a/inbound/socks.go b/inbound/socks.go deleted file mode 100644 index 04b0a77dc..000000000 --- a/inbound/socks.go +++ /dev/null @@ -1,52 +0,0 @@ -package inbound - -import ( - std_bufio "bufio" - "context" - "net" - - "github.com/sagernet/sing-box/adapter" - "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" - "github.com/sagernet/sing/common/auth" - E "github.com/sagernet/sing/common/exceptions" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/protocol/socks" -) - -var ( - _ adapter.Inbound = (*Socks)(nil) - _ adapter.TCPInjectableInbound = (*Socks)(nil) -) - -type Socks struct { - myInboundAdapter - authenticator *auth.Authenticator -} - -func NewSocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SocksInboundOptions) *Socks { - inbound := &Socks{ - myInboundAdapter{ - protocol: C.TypeSOCKS, - network: []string{N.NetworkTCP}, - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - auth.NewAuthenticator(options.Users), - } - inbound.connHandler = inbound - return inbound -} - -func (h *Socks) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - err := socks.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, nil, h.upstreamUserHandlerEx(metadata), metadata.Source, metadata.Destination, onClose) - N.CloseOnHandshakeFailure(conn, onClose, err) - if err != nil { - h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) - } -} diff --git a/inbound/tuic_stub.go b/inbound/tuic_stub.go deleted file mode 100644 index bfd402ab4..000000000 --- a/inbound/tuic_stub.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build !with_quic - -package inbound - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" -) - -func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICInboundOptions) (adapter.Inbound, error) { - return nil, C.ErrQUICNotIncluded -} diff --git a/include/quic.go b/include/quic.go index 1bcc0fbc9..980b45811 100644 --- a/include/quic.go +++ b/include/quic.go @@ -3,6 +3,24 @@ package include import ( + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/protocol/hysteria" + "github.com/sagernet/sing-box/protocol/hysteria2" + _ "github.com/sagernet/sing-box/protocol/naive/quic" + "github.com/sagernet/sing-box/protocol/tuic" _ "github.com/sagernet/sing-box/transport/v2rayquic" _ "github.com/sagernet/sing-dns/quic" ) + +func registerQUICInbounds(registry *inbound.Registry) { + hysteria.RegisterInbound(registry) + tuic.RegisterInbound(registry) + hysteria2.RegisterInbound(registry) +} + +func registerQUICOutbounds(registry *outbound.Registry) { + hysteria.RegisterOutbound(registry) + tuic.RegisterOutbound(registry) + hysteria2.RegisterOutbound(registry) +} diff --git a/include/quic_stub.go b/include/quic_stub.go index 43aa58d99..66c085903 100644 --- a/include/quic_stub.go +++ b/include/quic_stub.go @@ -4,11 +4,18 @@ package include import ( "context" + "io" + "net/http" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/protocol/naive" "github.com/sagernet/sing-box/transport/v2ray" "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common/logger" @@ -29,3 +36,30 @@ func init() { }, ) } + +func registerQUICInbounds(registry *inbound.Registry) { + inbound.Register[option.HysteriaInboundOptions](registry, C.TypeHysteria, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (adapter.Inbound, error) { + return nil, C.ErrQUICNotIncluded + }) + inbound.Register[option.TUICInboundOptions](registry, C.TypeTUIC, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICInboundOptions) (adapter.Inbound, error) { + return nil, C.ErrQUICNotIncluded + }) + inbound.Register[option.Hysteria2InboundOptions](registry, C.TypeHysteria2, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2InboundOptions) (adapter.Inbound, error) { + return nil, C.ErrQUICNotIncluded + }) + naive.ConfigureHTTP3ListenerFunc = func(listener *listener.Listener, handler http.Handler, tlsConfig tls.ServerConfig, logger logger.Logger) (io.Closer, error) { + return nil, C.ErrQUICNotIncluded + } +} + +func registerQUICOutbounds(registry *outbound.Registry) { + outbound.Register[option.HysteriaOutboundOptions](registry, C.TypeHysteria, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaOutboundOptions) (adapter.Outbound, error) { + return nil, C.ErrQUICNotIncluded + }) + outbound.Register[option.TUICOutboundOptions](registry, C.TypeTUIC, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICOutboundOptions) (adapter.Outbound, error) { + return nil, C.ErrQUICNotIncluded + }) + outbound.Register[option.Hysteria2OutboundOptions](registry, C.TypeHysteria2, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2OutboundOptions) (adapter.Outbound, error) { + return nil, C.ErrQUICNotIncluded + }) +} diff --git a/include/registry.go b/include/registry.go new file mode 100644 index 000000000..03fb33f24 --- /dev/null +++ b/include/registry.go @@ -0,0 +1,95 @@ +package include + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "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-box/protocol/block" + "github.com/sagernet/sing-box/protocol/direct" + "github.com/sagernet/sing-box/protocol/dns" + "github.com/sagernet/sing-box/protocol/group" + "github.com/sagernet/sing-box/protocol/http" + "github.com/sagernet/sing-box/protocol/mixed" + "github.com/sagernet/sing-box/protocol/naive" + "github.com/sagernet/sing-box/protocol/redirect" + "github.com/sagernet/sing-box/protocol/shadowsocks" + "github.com/sagernet/sing-box/protocol/shadowtls" + "github.com/sagernet/sing-box/protocol/socks" + "github.com/sagernet/sing-box/protocol/ssh" + "github.com/sagernet/sing-box/protocol/tor" + "github.com/sagernet/sing-box/protocol/trojan" + "github.com/sagernet/sing-box/protocol/tun" + "github.com/sagernet/sing-box/protocol/vless" + "github.com/sagernet/sing-box/protocol/vmess" + E "github.com/sagernet/sing/common/exceptions" +) + +func InboundRegistry() *inbound.Registry { + registry := inbound.NewRegistry() + + tun.RegisterInbound(registry) + redirect.RegisterRedirect(registry) + redirect.RegisterTProxy(registry) + direct.RegisterInbound(registry) + + socks.RegisterInbound(registry) + http.RegisterInbound(registry) + mixed.RegisterInbound(registry) + + shadowsocks.RegisterInbound(registry) + vmess.RegisterInbound(registry) + trojan.RegisterInbound(registry) + naive.RegisterInbound(registry) + shadowtls.RegisterInbound(registry) + vless.RegisterInbound(registry) + + registerQUICInbounds(registry) + registerStubForRemovedInbounds(registry) + + return registry +} + +func OutboundRegistry() *outbound.Registry { + registry := outbound.NewRegistry() + + direct.RegisterOutbound(registry) + + block.RegisterOutbound(registry) + dns.RegisterOutbound(registry) + + group.RegisterSelector(registry) + group.RegisterURLTest(registry) + + socks.RegisterOutbound(registry) + http.RegisterOutbound(registry) + shadowsocks.RegisterOutbound(registry) + vmess.RegisterOutbound(registry) + trojan.RegisterOutbound(registry) + tor.RegisterOutbound(registry) + ssh.RegisterOutbound(registry) + shadowtls.RegisterOutbound(registry) + vless.RegisterOutbound(registry) + + registerQUICOutbounds(registry) + registerWireGuardOutbound(registry) + registerStubForRemovedOutbounds(registry) + + return registry +} + +func registerStubForRemovedInbounds(registry *inbound.Registry) { + inbound.Register[option.ShadowsocksInboundOptions](registry, C.TypeShadowsocksR, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (adapter.Inbound, error) { + return nil, E.New("ShadowsocksR is deprecated and removed in sing-box 1.6.0") + }) +} + +func registerStubForRemovedOutbounds(registry *outbound.Registry) { + outbound.Register[option.ShadowsocksROutboundOptions](registry, C.TypeShadowsocksR, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksROutboundOptions) (adapter.Outbound, error) { + return nil, E.New("ShadowsocksR is deprecated and removed in sing-box 1.6.0") + }) +} diff --git a/include/wireguard.go b/include/wireguard.go new file mode 100644 index 000000000..dfc3a242a --- /dev/null +++ b/include/wireguard.go @@ -0,0 +1,12 @@ +//go:build with_wireguard + +package include + +import ( + "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/protocol/wireguard" +) + +func registerWireGuardOutbound(registry *outbound.Registry) { + wireguard.RegisterOutbound(registry) +} diff --git a/include/wireguard_stub.go b/include/wireguard_stub.go new file mode 100644 index 000000000..a9e84522b --- /dev/null +++ b/include/wireguard_stub.go @@ -0,0 +1,20 @@ +//go:build !with_wireguard + +package include + +import ( + "context" + + "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" + E "github.com/sagernet/sing/common/exceptions" +) + +func registerWireGuardOutbound(registry *outbound.Registry) { + outbound.Register[option.WireGuardOutboundOptions](registry, C.TypeWireGuard, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardOutboundOptions) (adapter.Outbound, error) { + return nil, E.New(`WireGuard is not included in this build, rebuild with -tags with_wireguard`) + }) +} diff --git a/option/inbound.go b/option/inbound.go index d38799048..651d02846 100644 --- a/option/inbound.go +++ b/option/inbound.go @@ -1,100 +1,49 @@ package option import ( + "context" "time" - C "github.com/sagernet/sing-box/constant" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/service" ) +type InboundOptionsRegistry interface { + CreateOptions(outboundType string) (any, bool) +} + type _Inbound struct { - Type string `json:"type"` - Tag string `json:"tag,omitempty"` - TunOptions TunInboundOptions `json:"-"` - RedirectOptions RedirectInboundOptions `json:"-"` - TProxyOptions TProxyInboundOptions `json:"-"` - DirectOptions DirectInboundOptions `json:"-"` - SocksOptions SocksInboundOptions `json:"-"` - HTTPOptions HTTPMixedInboundOptions `json:"-"` - MixedOptions HTTPMixedInboundOptions `json:"-"` - ShadowsocksOptions ShadowsocksInboundOptions `json:"-"` - VMessOptions VMessInboundOptions `json:"-"` - TrojanOptions TrojanInboundOptions `json:"-"` - NaiveOptions NaiveInboundOptions `json:"-"` - HysteriaOptions HysteriaInboundOptions `json:"-"` - ShadowTLSOptions ShadowTLSInboundOptions `json:"-"` - VLESSOptions VLESSInboundOptions `json:"-"` - TUICOptions TUICInboundOptions `json:"-"` - Hysteria2Options Hysteria2InboundOptions `json:"-"` + Type string `json:"type"` + Tag string `json:"tag,omitempty"` + Options any `json:"-"` } type Inbound _Inbound -func (h *Inbound) RawOptions() (any, error) { - var rawOptionsPtr any - switch h.Type { - case C.TypeTun: - rawOptionsPtr = &h.TunOptions - case C.TypeRedirect: - rawOptionsPtr = &h.RedirectOptions - case C.TypeTProxy: - rawOptionsPtr = &h.TProxyOptions - case C.TypeDirect: - rawOptionsPtr = &h.DirectOptions - case C.TypeSOCKS: - rawOptionsPtr = &h.SocksOptions - case C.TypeHTTP: - rawOptionsPtr = &h.HTTPOptions - case C.TypeMixed: - rawOptionsPtr = &h.MixedOptions - case C.TypeShadowsocks: - rawOptionsPtr = &h.ShadowsocksOptions - case C.TypeVMess: - rawOptionsPtr = &h.VMessOptions - case C.TypeTrojan: - rawOptionsPtr = &h.TrojanOptions - case C.TypeNaive: - rawOptionsPtr = &h.NaiveOptions - case C.TypeHysteria: - rawOptionsPtr = &h.HysteriaOptions - case C.TypeShadowTLS: - rawOptionsPtr = &h.ShadowTLSOptions - case C.TypeVLESS: - rawOptionsPtr = &h.VLESSOptions - case C.TypeTUIC: - rawOptionsPtr = &h.TUICOptions - case C.TypeHysteria2: - rawOptionsPtr = &h.Hysteria2Options - case "": - return nil, E.New("missing inbound type") - default: - return nil, E.New("unknown inbound type: ", h.Type) - } - return rawOptionsPtr, nil +func (h *Inbound) MarshalJSONContext(ctx context.Context) ([]byte, error) { + return badjson.MarshallObjectsContext(ctx, (*_Inbound)(h), h.Options) } -func (h Inbound) MarshalJSON() ([]byte, error) { - rawOptions, err := h.RawOptions() - if err != nil { - return nil, err - } - return MarshallObjects((_Inbound)(h), rawOptions) -} - -func (h *Inbound) UnmarshalJSON(bytes []byte) error { - err := json.Unmarshal(bytes, (*_Inbound)(h)) +func (h *Inbound) UnmarshalJSONContext(ctx context.Context, content []byte) error { + err := json.Unmarshal(content, (*_Inbound)(h)) if err != nil { return err } - rawOptions, err := h.RawOptions() - if err != nil { - return err + registry := service.FromContext[InboundOptionsRegistry](ctx) + if registry == nil { + return E.New("missing inbound options registry in context") } - err = UnmarshallExcluded(bytes, (*_Inbound)(h), rawOptions) + options, loaded := registry.CreateOptions(h.Type) + if !loaded { + return E.New("unknown inbound type: ", h.Type) + } + err = badjson.UnmarshallExcludedContext(ctx, content, (*_Inbound)(h), options) if err != nil { return err } + h.Options = options return nil } @@ -105,19 +54,24 @@ type InboundOptions struct { SniffTimeout Duration `json:"sniff_timeout,omitempty"` DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` + Detour string `json:"detour,omitempty"` } type ListenOptions struct { - Listen *ListenAddress `json:"listen,omitempty"` - ListenPort uint16 `json:"listen_port,omitempty"` - TCPFastOpen bool `json:"tcp_fast_open,omitempty"` - TCPMultiPath bool `json:"tcp_multi_path,omitempty"` - UDPFragment *bool `json:"udp_fragment,omitempty"` - UDPFragmentDefault bool `json:"-"` - UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` - ProxyProtocol bool `json:"proxy_protocol,omitempty"` - ProxyProtocolAcceptNoHeader bool `json:"proxy_protocol_accept_no_header,omitempty"` - Detour string `json:"detour,omitempty"` + Listen *ListenAddress `json:"listen,omitempty"` + ListenPort uint16 `json:"listen_port,omitempty"` + TCPKeepAlive Duration `json:"tcp_keep_alive,omitempty"` + TCPKeepAliveInterval Duration `json:"tcp_keep_alive_interval,omitempty"` + TCPFastOpen bool `json:"tcp_fast_open,omitempty"` + TCPMultiPath bool `json:"tcp_multi_path,omitempty"` + UDPFragment *bool `json:"udp_fragment,omitempty"` + UDPFragmentDefault bool `json:"-"` + UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` + + // Deprecated: removed + ProxyProtocol bool `json:"proxy_protocol,omitempty"` + // Deprecated: removed + ProxyProtocolAcceptNoHeader bool `json:"proxy_protocol_accept_no_header,omitempty"` InboundOptions } diff --git a/option/inbound_legacy.go b/option/inbound_legacy.go new file mode 100644 index 000000000..e7f346785 --- /dev/null +++ b/option/inbound_legacy.go @@ -0,0 +1,98 @@ +package option + +import ( + C "github.com/sagernet/sing-box/constant" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" +) + +type _LegacyInbound struct { + Type string `json:"type"` + Tag string `json:"tag,omitempty"` + TunOptions TunInboundOptions `json:"-"` + RedirectOptions RedirectInboundOptions `json:"-"` + TProxyOptions TProxyInboundOptions `json:"-"` + DirectOptions DirectInboundOptions `json:"-"` + SocksOptions SocksInboundOptions `json:"-"` + HTTPOptions HTTPMixedInboundOptions `json:"-"` + MixedOptions HTTPMixedInboundOptions `json:"-"` + ShadowsocksOptions ShadowsocksInboundOptions `json:"-"` + VMessOptions VMessInboundOptions `json:"-"` + TrojanOptions TrojanInboundOptions `json:"-"` + NaiveOptions NaiveInboundOptions `json:"-"` + HysteriaOptions HysteriaInboundOptions `json:"-"` + ShadowTLSOptions ShadowTLSInboundOptions `json:"-"` + VLESSOptions VLESSInboundOptions `json:"-"` + TUICOptions TUICInboundOptions `json:"-"` + Hysteria2Options Hysteria2InboundOptions `json:"-"` +} + +type LegacyInbound _LegacyInbound + +func (h *LegacyInbound) RawOptions() (any, error) { + var rawOptionsPtr any + switch h.Type { + case C.TypeTun: + rawOptionsPtr = &h.TunOptions + case C.TypeRedirect: + rawOptionsPtr = &h.RedirectOptions + case C.TypeTProxy: + rawOptionsPtr = &h.TProxyOptions + case C.TypeDirect: + rawOptionsPtr = &h.DirectOptions + case C.TypeSOCKS: + rawOptionsPtr = &h.SocksOptions + case C.TypeHTTP: + rawOptionsPtr = &h.HTTPOptions + case C.TypeMixed: + rawOptionsPtr = &h.MixedOptions + case C.TypeShadowsocks: + rawOptionsPtr = &h.ShadowsocksOptions + case C.TypeVMess: + rawOptionsPtr = &h.VMessOptions + case C.TypeTrojan: + rawOptionsPtr = &h.TrojanOptions + case C.TypeNaive: + rawOptionsPtr = &h.NaiveOptions + case C.TypeHysteria: + rawOptionsPtr = &h.HysteriaOptions + case C.TypeShadowTLS: + rawOptionsPtr = &h.ShadowTLSOptions + case C.TypeVLESS: + rawOptionsPtr = &h.VLESSOptions + case C.TypeTUIC: + rawOptionsPtr = &h.TUICOptions + case C.TypeHysteria2: + rawOptionsPtr = &h.Hysteria2Options + case "": + return nil, E.New("missing inbound type") + default: + return nil, E.New("unknown inbound type: ", h.Type) + } + return rawOptionsPtr, nil +} + +func (h LegacyInbound) MarshalJSON() ([]byte, error) { + rawOptions, err := h.RawOptions() + if err != nil { + return nil, err + } + return badjson.MarshallObjects((_LegacyInbound)(h), rawOptions) +} + +func (h *LegacyInbound) UnmarshalJSON(bytes []byte) error { + err := json.Unmarshal(bytes, (*_LegacyInbound)(h)) + if err != nil { + return err + } + rawOptions, err := h.RawOptions() + if err != nil { + return err + } + err = badjson.UnmarshallExcluded(bytes, (*_LegacyInbound)(h), rawOptions) + if err != nil { + return err + } + return nil +} diff --git a/option/json.go b/option/json.go deleted file mode 100644 index 775141d5a..000000000 --- a/option/json.go +++ /dev/null @@ -1,71 +0,0 @@ -package option - -import ( - "github.com/sagernet/sing/common" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/json" - "github.com/sagernet/sing/common/json/badjson" -) - -func ToMap(v any) (*badjson.JSONObject, error) { - inputContent, err := json.Marshal(v) - if err != nil { - return nil, err - } - var content badjson.JSONObject - err = content.UnmarshalJSON(inputContent) - if err != nil { - return nil, err - } - return &content, nil -} - -func MergeObjects(objects ...any) (*badjson.JSONObject, error) { - var content badjson.JSONObject - for _, object := range objects { - objectMap, err := ToMap(object) - if err != nil { - return nil, err - } - content.PutAll(objectMap) - } - return &content, nil -} - -func MarshallObjects(objects ...any) ([]byte, error) { - objects = common.FilterNotNil(objects) - if len(objects) == 1 { - return json.Marshal(objects[0]) - } - content, err := MergeObjects(objects...) - if err != nil { - return nil, err - } - return content.MarshalJSON() -} - -func UnmarshallExcluded(inputContent []byte, parentObject any, object any) error { - parentContent, err := ToMap(parentObject) - if err != nil { - return err - } - var content badjson.JSONObject - err = content.UnmarshalJSON(inputContent) - if err != nil { - return err - } - for _, key := range parentContent.Keys() { - content.Remove(key) - } - if object == nil { - if content.IsEmpty() { - return nil - } - return E.New("unexpected key: ", content.Keys()[0]) - } - inputContent, err = content.MarshalJSON() - if err != nil { - return err - } - return json.UnmarshalDisallowUnknownFields(inputContent, object) -} diff --git a/option/config.go b/option/options.go similarity index 74% rename from option/config.go rename to option/options.go index 3f5d7602c..c4811fa99 100644 --- a/option/config.go +++ b/option/options.go @@ -2,6 +2,7 @@ package option import ( "bytes" + "context" "github.com/sagernet/sing/common/json" ) @@ -16,12 +17,17 @@ type _Options struct { Outbounds []Outbound `json:"outbounds,omitempty"` Route *RouteOptions `json:"route,omitempty"` Experimental *ExperimentalOptions `json:"experimental,omitempty"` + + // Deprecated: use Inbounds instead + LegacyInbounds []LegacyInbound `json:"inbound,omitempty"` + // Deprecated: use Outbounds instead + LegacyOutbounds []LegacyOutbound `json:"_"` } type Options _Options -func (o *Options) UnmarshalJSON(content []byte) error { - decoder := json.NewDecoder(bytes.NewReader(content)) +func (o *Options) UnmarshalJSONContext(ctx context.Context, content []byte) error { + decoder := json.NewDecoderContext(ctx, bytes.NewReader(content)) decoder.DisallowUnknownFields() err := decoder.Decode((*_Options)(o)) if err != nil { @@ -38,3 +44,5 @@ type LogOptions struct { Timestamp bool `json:"timestamp,omitempty"` DisableColor bool `json:"-"` } + +type StubOptions struct{} diff --git a/option/outbound.go b/option/outbound.go index 6c943cd9a..00a20aa57 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -1,104 +1,49 @@ package option import ( - C "github.com/sagernet/sing-box/constant" + "context" + E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/service" ) +type OutboundOptionsRegistry interface { + CreateOptions(outboundType string) (any, bool) +} + type _Outbound struct { - Type string `json:"type"` - Tag string `json:"tag,omitempty"` - DirectOptions DirectOutboundOptions `json:"-"` - SocksOptions SocksOutboundOptions `json:"-"` - HTTPOptions HTTPOutboundOptions `json:"-"` - ShadowsocksOptions ShadowsocksOutboundOptions `json:"-"` - VMessOptions VMessOutboundOptions `json:"-"` - TrojanOptions TrojanOutboundOptions `json:"-"` - WireGuardOptions WireGuardOutboundOptions `json:"-"` - HysteriaOptions HysteriaOutboundOptions `json:"-"` - TorOptions TorOutboundOptions `json:"-"` - SSHOptions SSHOutboundOptions `json:"-"` - ShadowTLSOptions ShadowTLSOutboundOptions `json:"-"` - ShadowsocksROptions ShadowsocksROutboundOptions `json:"-"` - VLESSOptions VLESSOutboundOptions `json:"-"` - TUICOptions TUICOutboundOptions `json:"-"` - Hysteria2Options Hysteria2OutboundOptions `json:"-"` - SelectorOptions SelectorOutboundOptions `json:"-"` - URLTestOptions URLTestOutboundOptions `json:"-"` + Type string `json:"type"` + Tag string `json:"tag,omitempty"` + Options any `json:"-"` } type Outbound _Outbound -func (h *Outbound) RawOptions() (any, error) { - var rawOptionsPtr any - switch h.Type { - case C.TypeDirect: - rawOptionsPtr = &h.DirectOptions - case C.TypeBlock, C.TypeDNS: - rawOptionsPtr = nil - case C.TypeSOCKS: - rawOptionsPtr = &h.SocksOptions - case C.TypeHTTP: - rawOptionsPtr = &h.HTTPOptions - case C.TypeShadowsocks: - rawOptionsPtr = &h.ShadowsocksOptions - case C.TypeVMess: - rawOptionsPtr = &h.VMessOptions - case C.TypeTrojan: - rawOptionsPtr = &h.TrojanOptions - case C.TypeWireGuard: - rawOptionsPtr = &h.WireGuardOptions - case C.TypeHysteria: - rawOptionsPtr = &h.HysteriaOptions - case C.TypeTor: - rawOptionsPtr = &h.TorOptions - case C.TypeSSH: - rawOptionsPtr = &h.SSHOptions - case C.TypeShadowTLS: - rawOptionsPtr = &h.ShadowTLSOptions - case C.TypeShadowsocksR: - rawOptionsPtr = &h.ShadowsocksROptions - case C.TypeVLESS: - rawOptionsPtr = &h.VLESSOptions - case C.TypeTUIC: - rawOptionsPtr = &h.TUICOptions - case C.TypeHysteria2: - rawOptionsPtr = &h.Hysteria2Options - case C.TypeSelector: - rawOptionsPtr = &h.SelectorOptions - case C.TypeURLTest: - rawOptionsPtr = &h.URLTestOptions - case "": - return nil, E.New("missing outbound type") - default: - return nil, E.New("unknown outbound type: ", h.Type) - } - return rawOptionsPtr, nil +func (h *Outbound) MarshalJSONContext(ctx context.Context) ([]byte, error) { + return badjson.MarshallObjectsContext(ctx, (*_Outbound)(h), h.Options) } -func (h *Outbound) MarshalJSON() ([]byte, error) { - rawOptions, err := h.RawOptions() - if err != nil { - return nil, err - } - return MarshallObjects((*_Outbound)(h), rawOptions) -} - -func (h *Outbound) UnmarshalJSON(bytes []byte) error { - err := json.Unmarshal(bytes, (*_Outbound)(h)) +func (h *Outbound) UnmarshalJSONContext(ctx context.Context, content []byte) error { + err := json.Unmarshal(content, (*_Outbound)(h)) if err != nil { return err } - rawOptions, err := h.RawOptions() - if err != nil { - return err + registry := service.FromContext[OutboundOptionsRegistry](ctx) + if registry == nil { + return E.New("missing outbound options registry in context") + } + options, loaded := registry.CreateOptions(h.Type) + if !loaded { + return E.New("unknown outbound type: ", h.Type) } - err = UnmarshallExcluded(bytes, (*_Outbound)(h), rawOptions) + err = badjson.UnmarshallExcludedContext(ctx, content, (*_Outbound)(h), options) if err != nil { return err } + h.Options = options return nil } diff --git a/option/outbound_legacy.go b/option/outbound_legacy.go new file mode 100644 index 000000000..d840884b7 --- /dev/null +++ b/option/outbound_legacy.go @@ -0,0 +1,103 @@ +package option + +import ( + C "github.com/sagernet/sing-box/constant" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" +) + +type _LegacyOutbound struct { + Type string `json:"type"` + Tag string `json:"tag,omitempty"` + DirectOptions DirectOutboundOptions `json:"-"` + SocksOptions SOCKSOutboundOptions `json:"-"` + HTTPOptions HTTPOutboundOptions `json:"-"` + ShadowsocksOptions ShadowsocksOutboundOptions `json:"-"` + VMessOptions VMessOutboundOptions `json:"-"` + TrojanOptions TrojanOutboundOptions `json:"-"` + WireGuardOptions WireGuardOutboundOptions `json:"-"` + HysteriaOptions HysteriaOutboundOptions `json:"-"` + TorOptions TorOutboundOptions `json:"-"` + SSHOptions SSHOutboundOptions `json:"-"` + ShadowTLSOptions ShadowTLSOutboundOptions `json:"-"` + ShadowsocksROptions ShadowsocksROutboundOptions `json:"-"` + VLESSOptions VLESSOutboundOptions `json:"-"` + TUICOptions TUICOutboundOptions `json:"-"` + Hysteria2Options Hysteria2OutboundOptions `json:"-"` + SelectorOptions SelectorOutboundOptions `json:"-"` + URLTestOptions URLTestOutboundOptions `json:"-"` +} + +type LegacyOutbound _LegacyOutbound + +func (h *LegacyOutbound) RawOptions() (any, error) { + var rawOptionsPtr any + switch h.Type { + case C.TypeDirect: + rawOptionsPtr = &h.DirectOptions + case C.TypeBlock, C.TypeDNS: + rawOptionsPtr = new(StubOptions) + case C.TypeSOCKS: + rawOptionsPtr = &h.SocksOptions + case C.TypeHTTP: + rawOptionsPtr = &h.HTTPOptions + case C.TypeShadowsocks: + rawOptionsPtr = &h.ShadowsocksOptions + case C.TypeVMess: + rawOptionsPtr = &h.VMessOptions + case C.TypeTrojan: + rawOptionsPtr = &h.TrojanOptions + case C.TypeWireGuard: + rawOptionsPtr = &h.WireGuardOptions + case C.TypeHysteria: + rawOptionsPtr = &h.HysteriaOptions + case C.TypeTor: + rawOptionsPtr = &h.TorOptions + case C.TypeSSH: + rawOptionsPtr = &h.SSHOptions + case C.TypeShadowTLS: + rawOptionsPtr = &h.ShadowTLSOptions + case C.TypeShadowsocksR: + rawOptionsPtr = &h.ShadowsocksROptions + case C.TypeVLESS: + rawOptionsPtr = &h.VLESSOptions + case C.TypeTUIC: + rawOptionsPtr = &h.TUICOptions + case C.TypeHysteria2: + rawOptionsPtr = &h.Hysteria2Options + case C.TypeSelector: + rawOptionsPtr = &h.SelectorOptions + case C.TypeURLTest: + rawOptionsPtr = &h.URLTestOptions + case "": + return nil, E.New("missing outbound type") + default: + return nil, E.New("unknown outbound type: ", h.Type) + } + return rawOptionsPtr, nil +} + +func (h *LegacyOutbound) MarshalJSON() ([]byte, error) { + rawOptions, err := h.RawOptions() + if err != nil { + return nil, err + } + return badjson.MarshallObjects((*_LegacyOutbound)(h), rawOptions) +} + +func (h *LegacyOutbound) UnmarshalJSON(bytes []byte) error { + err := json.Unmarshal(bytes, (*_LegacyOutbound)(h)) + if err != nil { + return err + } + rawOptions, err := h.RawOptions() + if err != nil { + return err + } + err = badjson.UnmarshallExcluded(bytes, (*_LegacyOutbound)(h), rawOptions) + if err != nil { + return err + } + return nil +} diff --git a/option/rule.go b/option/rule.go index 0b11cbdda..07e6ddbec 100644 --- a/option/rule.go +++ b/option/rule.go @@ -7,6 +7,7 @@ import ( "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" ) type _Rule struct { @@ -28,7 +29,7 @@ func (r Rule) MarshalJSON() ([]byte, error) { default: return nil, E.New("unknown rule type: " + r.Type) } - return MarshallObjects((_Rule)(r), v) + return badjson.MarshallObjects((_Rule)(r), v) } func (r *Rule) UnmarshalJSON(bytes []byte) error { @@ -46,7 +47,7 @@ func (r *Rule) UnmarshalJSON(bytes []byte) error { default: return E.New("unknown rule type: " + r.Type) } - err = UnmarshallExcluded(bytes, (*_Rule)(r), v) + err = badjson.UnmarshallExcluded(bytes, (*_Rule)(r), v) if err != nil { return err } @@ -109,7 +110,7 @@ type DefaultRule struct { } func (r *DefaultRule) MarshalJSON() ([]byte, error) { - return MarshallObjects(r.RawDefaultRule, r.RuleAction) + return badjson.MarshallObjects(r.RawDefaultRule, r.RuleAction) } func (r *DefaultRule) UnmarshalJSON(data []byte) error { @@ -117,7 +118,7 @@ func (r *DefaultRule) UnmarshalJSON(data []byte) error { if err != nil { return err } - return UnmarshallExcluded(data, &r.RawDefaultRule, &r.RuleAction) + return badjson.UnmarshallExcluded(data, &r.RawDefaultRule, &r.RuleAction) } func (r *DefaultRule) IsValid() bool { @@ -139,7 +140,7 @@ type LogicalRule struct { } func (r *LogicalRule) MarshalJSON() ([]byte, error) { - return MarshallObjects(r._LogicalRule, r.RuleAction) + return badjson.MarshallObjects(r._LogicalRule, r.RuleAction) } func (r *LogicalRule) UnmarshalJSON(data []byte) error { @@ -147,7 +148,7 @@ func (r *LogicalRule) UnmarshalJSON(data []byte) error { if err != nil { return err } - return UnmarshallExcluded(data, &r._LogicalRule, &r.RuleAction) + return badjson.UnmarshallExcluded(data, &r._LogicalRule, &r.RuleAction) } func (r *LogicalRule) IsValid() bool { diff --git a/option/rule_action.go b/option/rule_action.go index f446d81d5..e752a2bea 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -4,6 +4,7 @@ import ( C "github.com/sagernet/sing-box/constant" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" ) type _RuleAction struct { @@ -36,9 +37,9 @@ func (r RuleAction) MarshalJSON() ([]byte, error) { return nil, E.New("unknown rule action: " + r.Action) } if v == nil { - return MarshallObjects((_RuleAction)(r)) + return badjson.MarshallObjects((_RuleAction)(r)) } - return MarshallObjects((_RuleAction)(r), v) + return badjson.MarshallObjects((_RuleAction)(r), v) } func (r *RuleAction) UnmarshalJSON(data []byte) error { @@ -68,7 +69,7 @@ func (r *RuleAction) UnmarshalJSON(data []byte) error { // check unknown fields return json.UnmarshalDisallowUnknownFields(data, &_RuleAction{}) } - return UnmarshallExcluded(data, (*_RuleAction)(r), v) + return badjson.UnmarshallExcluded(data, (*_RuleAction)(r), v) } type _DNSRuleAction struct { @@ -95,9 +96,9 @@ func (r DNSRuleAction) MarshalJSON() ([]byte, error) { return nil, E.New("unknown DNS rule action: " + r.Action) } if v == nil { - return MarshallObjects((_DNSRuleAction)(r)) + return badjson.MarshallObjects((_DNSRuleAction)(r)) } - return MarshallObjects((_DNSRuleAction)(r), v) + return badjson.MarshallObjects((_DNSRuleAction)(r), v) } func (r *DNSRuleAction) UnmarshalJSON(data []byte) error { @@ -121,7 +122,7 @@ func (r *DNSRuleAction) UnmarshalJSON(data []byte) error { // check unknown fields return json.UnmarshalDisallowUnknownFields(data, &_DNSRuleAction{}) } - return UnmarshallExcluded(data, (*_DNSRuleAction)(r), v) + return badjson.UnmarshallExcluded(data, (*_DNSRuleAction)(r), v) } type RouteActionOptions struct { diff --git a/option/rule_dns.go b/option/rule_dns.go index b328c45c0..8c4b6ab83 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -7,6 +7,7 @@ import ( "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" ) type _DNSRule struct { @@ -28,7 +29,7 @@ func (r DNSRule) MarshalJSON() ([]byte, error) { default: return nil, E.New("unknown rule type: " + r.Type) } - return MarshallObjects((_DNSRule)(r), v) + return badjson.MarshallObjects((_DNSRule)(r), v) } func (r *DNSRule) UnmarshalJSON(bytes []byte) error { @@ -46,7 +47,7 @@ func (r *DNSRule) UnmarshalJSON(bytes []byte) error { default: return E.New("unknown rule type: " + r.Type) } - err = UnmarshallExcluded(bytes, (*_DNSRule)(r), v) + err = badjson.UnmarshallExcluded(bytes, (*_DNSRule)(r), v) if err != nil { return err } @@ -111,7 +112,7 @@ type DefaultDNSRule struct { } func (r *DefaultDNSRule) MarshalJSON() ([]byte, error) { - return MarshallObjects(r.RawDefaultDNSRule, r.DNSRuleAction) + return badjson.MarshallObjects(r.RawDefaultDNSRule, r.DNSRuleAction) } func (r *DefaultDNSRule) UnmarshalJSON(data []byte) error { @@ -119,7 +120,7 @@ func (r *DefaultDNSRule) UnmarshalJSON(data []byte) error { if err != nil { return err } - return UnmarshallExcluded(data, &r.RawDefaultDNSRule, &r.DNSRuleAction) + return badjson.UnmarshallExcluded(data, &r.RawDefaultDNSRule, &r.DNSRuleAction) } func (r *DefaultDNSRule) IsValid() bool { @@ -141,7 +142,7 @@ type LogicalDNSRule struct { } func (r *LogicalDNSRule) MarshalJSON() ([]byte, error) { - return MarshallObjects(r._LogicalDNSRule, r.DNSRuleAction) + return badjson.MarshallObjects(r._LogicalDNSRule, r.DNSRuleAction) } func (r *LogicalDNSRule) UnmarshalJSON(data []byte) error { @@ -149,7 +150,7 @@ func (r *LogicalDNSRule) UnmarshalJSON(data []byte) error { if err != nil { return err } - return UnmarshallExcluded(data, &r._LogicalDNSRule, &r.DNSRuleAction) + return badjson.UnmarshallExcluded(data, &r._LogicalDNSRule, &r.DNSRuleAction) } func (r *LogicalDNSRule) IsValid() bool { diff --git a/option/rule_set.go b/option/rule_set.go index d4368de38..358821def 100644 --- a/option/rule_set.go +++ b/option/rule_set.go @@ -9,6 +9,7 @@ import ( E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" "go4.org/netipx" ) @@ -37,7 +38,7 @@ func (r RuleSet) MarshalJSON() ([]byte, error) { default: return nil, E.New("unknown rule-set type: " + r.Type) } - return MarshallObjects((_RuleSet)(r), v) + return badjson.MarshallObjects((_RuleSet)(r), v) } func (r *RuleSet) UnmarshalJSON(bytes []byte) error { @@ -71,7 +72,7 @@ func (r *RuleSet) UnmarshalJSON(bytes []byte) error { } else { r.Format = "" } - err = UnmarshallExcluded(bytes, (*_RuleSet)(r), v) + err = badjson.UnmarshallExcluded(bytes, (*_RuleSet)(r), v) if err != nil { return err } @@ -107,7 +108,7 @@ func (r HeadlessRule) MarshalJSON() ([]byte, error) { default: return nil, E.New("unknown rule type: " + r.Type) } - return MarshallObjects((_HeadlessRule)(r), v) + return badjson.MarshallObjects((_HeadlessRule)(r), v) } func (r *HeadlessRule) UnmarshalJSON(bytes []byte) error { @@ -125,7 +126,7 @@ func (r *HeadlessRule) UnmarshalJSON(bytes []byte) error { default: return E.New("unknown rule type: " + r.Type) } - err = UnmarshallExcluded(bytes, (*_HeadlessRule)(r), v) + err = badjson.UnmarshallExcluded(bytes, (*_HeadlessRule)(r), v) if err != nil { return err } @@ -203,7 +204,7 @@ func (r PlainRuleSetCompat) MarshalJSON() ([]byte, error) { default: return nil, E.New("unknown rule-set version: ", r.Version) } - return MarshallObjects((_PlainRuleSetCompat)(r), v) + return badjson.MarshallObjects((_PlainRuleSetCompat)(r), v) } func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error { @@ -220,7 +221,7 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error { default: return E.New("unknown rule-set version: ", r.Version) } - err = UnmarshallExcluded(bytes, (*_PlainRuleSetCompat)(r), v) + err = badjson.UnmarshallExcluded(bytes, (*_PlainRuleSetCompat)(r), v) if err != nil { return err } diff --git a/option/simple.go b/option/simple.go index ba9d6bf19..78171ce4c 100644 --- a/option/simple.go +++ b/option/simple.go @@ -14,7 +14,7 @@ type HTTPMixedInboundOptions struct { InboundTLSOptionsContainer } -type SocksOutboundOptions struct { +type SOCKSOutboundOptions struct { DialerOptions ServerOptions Version string `json:"version,omitempty"` diff --git a/option/tls_acme.go b/option/tls_acme.go index 17d515e20..9c2e081fa 100644 --- a/option/tls_acme.go +++ b/option/tls_acme.go @@ -4,6 +4,7 @@ import ( C "github.com/sagernet/sing-box/constant" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" ) type InboundACMEOptions struct { @@ -45,7 +46,7 @@ func (o ACMEDNS01ChallengeOptions) MarshalJSON() ([]byte, error) { default: return nil, E.New("unknown provider type: " + o.Provider) } - return MarshallObjects((_ACMEDNS01ChallengeOptions)(o), v) + return badjson.MarshallObjects((_ACMEDNS01ChallengeOptions)(o), v) } func (o *ACMEDNS01ChallengeOptions) UnmarshalJSON(bytes []byte) error { @@ -62,7 +63,7 @@ func (o *ACMEDNS01ChallengeOptions) UnmarshalJSON(bytes []byte) error { default: return E.New("unknown provider type: " + o.Provider) } - err = UnmarshallExcluded(bytes, (*_ACMEDNS01ChallengeOptions)(o), v) + err = badjson.UnmarshallExcluded(bytes, (*_ACMEDNS01ChallengeOptions)(o), v) if err != nil { return err } diff --git a/option/v2ray_transport.go b/option/v2ray_transport.go index fcd81f946..f87b175d3 100644 --- a/option/v2ray_transport.go +++ b/option/v2ray_transport.go @@ -4,6 +4,7 @@ import ( C "github.com/sagernet/sing-box/constant" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" ) type _V2RayTransportOptions struct { @@ -35,7 +36,7 @@ func (o V2RayTransportOptions) MarshalJSON() ([]byte, error) { default: return nil, E.New("unknown transport type: " + o.Type) } - return MarshallObjects((_V2RayTransportOptions)(o), v) + return badjson.MarshallObjects((_V2RayTransportOptions)(o), v) } func (o *V2RayTransportOptions) UnmarshalJSON(bytes []byte) error { @@ -58,7 +59,7 @@ func (o *V2RayTransportOptions) UnmarshalJSON(bytes []byte) error { default: return E.New("unknown transport type: " + o.Type) } - err = UnmarshallExcluded(bytes, (*_V2RayTransportOptions)(o), v) + err = badjson.UnmarshallExcluded(bytes, (*_V2RayTransportOptions)(o), v) if err != nil { return err } diff --git a/outbound/block.go b/outbound/block.go deleted file mode 100644 index b6ccefe2b..000000000 --- a/outbound/block.go +++ /dev/null @@ -1,54 +0,0 @@ -package outbound - -import ( - "context" - "io" - "net" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -var _ adapter.Outbound = (*Block)(nil) - -type Block struct { - myOutboundAdapter -} - -func NewBlock(logger log.ContextLogger, tag string) *Block { - return &Block{ - myOutboundAdapter{ - protocol: C.TypeBlock, - network: []string{N.NetworkTCP, N.NetworkUDP}, - logger: logger, - tag: tag, - }, - } -} - -func (h *Block) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - h.logger.InfoContext(ctx, "blocked connection to ", destination) - return nil, io.EOF -} - -func (h *Block) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - h.logger.InfoContext(ctx, "blocked packet connection to ", destination) - return nil, io.EOF -} - -// Deprecated -func (h *Block) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - conn.Close() - h.logger.InfoContext(ctx, "blocked connection to ", metadata.Destination) - return nil -} - -// Deprecated -func (h *Block) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - conn.Close() - h.logger.InfoContext(ctx, "blocked packet connection to ", metadata.Destination) - return nil -} diff --git a/outbound/builder.go b/outbound/builder.go deleted file mode 100644 index d895b56dd..000000000 --- a/outbound/builder.go +++ /dev/null @@ -1,65 +0,0 @@ -package outbound - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - 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" -) - -func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Outbound) (adapter.Outbound, error) { - if tag != "" { - ctx = adapter.WithContext(ctx, &adapter.InboundContext{ - Outbound: tag, - }) - } - if options.Type == "" { - return nil, E.New("missing outbound type") - } - ctx = ContextWithTag(ctx, tag) - switch options.Type { - case C.TypeDirect: - return NewDirect(router, logger, tag, options.DirectOptions) - case C.TypeBlock: - return NewBlock(logger, tag), nil - case C.TypeDNS: - return NewDNS(router, tag), nil - case C.TypeSOCKS: - return NewSocks(router, logger, tag, options.SocksOptions) - case C.TypeHTTP: - return NewHTTP(ctx, router, logger, tag, options.HTTPOptions) - case C.TypeShadowsocks: - return NewShadowsocks(ctx, router, logger, tag, options.ShadowsocksOptions) - case C.TypeVMess: - return NewVMess(ctx, router, logger, tag, options.VMessOptions) - case C.TypeTrojan: - return NewTrojan(ctx, router, logger, tag, options.TrojanOptions) - case C.TypeWireGuard: - return NewWireGuard(ctx, router, logger, tag, options.WireGuardOptions) - case C.TypeHysteria: - return NewHysteria(ctx, router, logger, tag, options.HysteriaOptions) - case C.TypeTor: - return NewTor(ctx, router, logger, tag, options.TorOptions) - case C.TypeSSH: - return NewSSH(ctx, router, logger, tag, options.SSHOptions) - case C.TypeShadowTLS: - return NewShadowTLS(ctx, router, logger, tag, options.ShadowTLSOptions) - case C.TypeShadowsocksR: - return NewShadowsocksR(ctx, router, logger, tag, options.ShadowsocksROptions) - case C.TypeVLESS: - return NewVLESS(ctx, router, logger, tag, options.VLESSOptions) - case C.TypeTUIC: - return NewTUIC(ctx, router, logger, tag, options.TUICOptions) - case C.TypeHysteria2: - return NewHysteria2(ctx, router, logger, tag, options.Hysteria2Options) - case C.TypeSelector: - return NewSelector(ctx, router, logger, tag, options.SelectorOptions) - case C.TypeURLTest: - return NewURLTest(ctx, router, logger, tag, options.URLTestOptions) - default: - return nil, E.New("unknown outbound type: ", options.Type) - } -} diff --git a/outbound/hysteria_stub.go b/outbound/hysteria_stub.go deleted file mode 100644 index 84db5305b..000000000 --- a/outbound/hysteria_stub.go +++ /dev/null @@ -1,20 +0,0 @@ -//go:build !with_quic - -package outbound - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" -) - -func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaOutboundOptions) (adapter.Outbound, error) { - return nil, C.ErrQUICNotIncluded -} - -func NewHysteria2(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2OutboundOptions) (adapter.Outbound, error) { - return nil, C.ErrQUICNotIncluded -} diff --git a/outbound/lookback.go b/outbound/lookback.go deleted file mode 100644 index aeb7451db..000000000 --- a/outbound/lookback.go +++ /dev/null @@ -1,14 +0,0 @@ -package outbound - -import "context" - -type outboundTagKey struct{} - -func ContextWithTag(ctx context.Context, outboundTag string) context.Context { - return context.WithValue(ctx, outboundTagKey{}, outboundTag) -} - -func TagFromContext(ctx context.Context) (string, bool) { - value, loaded := ctx.Value(outboundTagKey{}).(string) - return value, loaded -} diff --git a/outbound/shadowsocksr.go b/outbound/shadowsocksr.go deleted file mode 100644 index 615a71e4e..000000000 --- a/outbound/shadowsocksr.go +++ /dev/null @@ -1,18 +0,0 @@ -//go:build with_shadowsocksr - -package outbound - -import ( - "context" - "os" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" -) - -var _ int = "ShadowsocksR is deprecated and removed in sing-box 1.6.0" - -func NewShadowsocksR(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksROutboundOptions) (adapter.Outbound, error) { - return nil, os.ErrInvalid -} diff --git a/outbound/shadowsocksr_stub.go b/outbound/shadowsocksr_stub.go deleted file mode 100644 index 94971da0b..000000000 --- a/outbound/shadowsocksr_stub.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build !with_shadowsocksr - -package outbound - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - E "github.com/sagernet/sing/common/exceptions" -) - -func NewShadowsocksR(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksROutboundOptions) (adapter.Outbound, error) { - return nil, E.New("ShadowsocksR is deprecated and removed in sing-box 1.6.0") -} diff --git a/outbound/tor_embed.go b/outbound/tor_embed.go deleted file mode 100644 index d80b49ae6..000000000 --- a/outbound/tor_embed.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build with_embedded_tor && !(android || ios) - -package outbound - -import ( - "berty.tech/go-libtor" - "github.com/cretz/bine/tor" -) - -func newConfig() tor.StartConf { - return tor.StartConf{ - ProcessCreator: libtor.Creator, - UseEmbeddedControlConn: true, - } -} diff --git a/outbound/tor_embed_mobile.go b/outbound/tor_embed_mobile.go deleted file mode 100644 index 0900d8c93..000000000 --- a/outbound/tor_embed_mobile.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build with_embedded_tor && (android || ios) - -package outbound - -import ( - "github.com/cretz/bine/tor" - "github.com/ooni/go-libtor" -) - -func newConfig() tor.StartConf { - return tor.StartConf{ - ProcessCreator: libtor.Creator, - UseEmbeddedControlConn: true, - } -} diff --git a/outbound/tor_external.go b/outbound/tor_external.go deleted file mode 100644 index 6bce95d1d..000000000 --- a/outbound/tor_external.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build !with_embedded_tor - -package outbound - -import "github.com/cretz/bine/tor" - -func newConfig() tor.StartConf { - return tor.StartConf{} -} diff --git a/outbound/tuic_stub.go b/outbound/tuic_stub.go deleted file mode 100644 index a6372c9ee..000000000 --- a/outbound/tuic_stub.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build !with_quic - -package outbound - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" -) - -func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICOutboundOptions) (adapter.Outbound, error) { - return nil, C.ErrQUICNotIncluded -} diff --git a/outbound/wireguard_stub.go b/outbound/wireguard_stub.go deleted file mode 100644 index 3a8b0e87a..000000000 --- a/outbound/wireguard_stub.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build !with_wireguard - -package outbound - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - E "github.com/sagernet/sing/common/exceptions" -) - -func NewWireGuard(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardOutboundOptions) (adapter.Outbound, error) { - return nil, E.New(`WireGuard is not included in this build, rebuild with -tags with_wireguard`) -} diff --git a/protocol/block/outbound.go b/protocol/block/outbound.go new file mode 100644 index 000000000..75bc7797e --- /dev/null +++ b/protocol/block/outbound.go @@ -0,0 +1,42 @@ +package block + +import ( + "context" + "net" + "syscall" + + "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/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.StubOptions](registry, C.TypeBlock, New) +} + +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger +} + +func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, _ option.StubOptions) (adapter.Outbound, error) { + return &Outbound{ + Adapter: outbound.NewAdapter(C.TypeBlock, []string{N.NetworkTCP, N.NetworkUDP}, tag, nil), + logger: logger, + }, nil +} + +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + h.logger.InfoContext(ctx, "blocked connection to ", destination) + return nil, syscall.EPERM +} + +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + h.logger.InfoContext(ctx, "blocked packet connection to ", destination) + return nil, syscall.EPERM +} diff --git a/protocol/direct/inbound.go b/protocol/direct/inbound.go new file mode 100644 index 000000000..568a72cbd --- /dev/null +++ b/protocol/direct/inbound.go @@ -0,0 +1,139 @@ +package direct + +import ( + "context" + "net" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/buf" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/udpnat2" +) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.DirectInboundOptions](registry, C.TypeDirect, NewInbound) +} + +type Inbound struct { + inbound.Adapter + ctx context.Context + router adapter.ConnectionRouterEx + logger log.ContextLogger + listener *listener.Listener + udpNat *udpnat.Service + overrideOption int + overrideDestination M.Socksaddr +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.DirectInboundOptions) (adapter.Inbound, error) { + options.UDPFragmentDefault = true + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeDirect, tag), + ctx: ctx, + router: router, + logger: logger, + } + if options.OverrideAddress != "" && options.OverridePort != 0 { + inbound.overrideOption = 1 + inbound.overrideDestination = M.ParseSocksaddrHostPort(options.OverrideAddress, options.OverridePort) + } else if options.OverrideAddress != "" { + inbound.overrideOption = 2 + inbound.overrideDestination = M.ParseSocksaddrHostPort(options.OverrideAddress, options.OverridePort) + } else if options.OverridePort != 0 { + inbound.overrideOption = 3 + inbound.overrideDestination = M.Socksaddr{Port: options.OverridePort} + } + var udpTimeout time.Duration + if options.UDPTimeout != 0 { + udpTimeout = time.Duration(options.UDPTimeout) + } else { + udpTimeout = C.UDPTimeout + } + inbound.udpNat = udpnat.New(inbound, inbound.preparePacketConnection, udpTimeout, false) + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: options.Network.Build(), + Listen: options.ListenOptions, + ConnectionHandler: inbound, + PacketHandler: inbound, + }) + return inbound, nil +} + +func (i *Inbound) Start() error { + return i.listener.Start() +} + +func (i *Inbound) Close() error { + return i.listener.Close() +} + +func (i *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + switch i.overrideOption { + case 1: + metadata.Destination = i.overrideDestination + case 2: + destination := i.overrideDestination + destination.Port = metadata.Destination.Port + metadata.Destination = destination + case 3: + metadata.Destination.Port = i.overrideDestination.Port + } + i.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + return i.router.RouteConnection(ctx, conn, metadata) +} + +func (i *Inbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { + var destination M.Socksaddr + switch i.overrideOption { + case 1: + destination = i.overrideDestination + case 2: + destination = i.overrideDestination + destination.Port = source.Port + case 3: + destination = source + destination.Port = i.overrideDestination.Port + } + i.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, destination, nil) +} + +func (i *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + i.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + metadata.Inbound = i.Tag() + metadata.InboundType = i.Type() + i.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (i *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + i.logger.InfoContext(ctx, "inbound packet connection from ", source) + i.logger.InfoContext(ctx, "inbound packet connection to ", destination) + var metadata adapter.InboundContext + metadata.Inbound = i.Tag() + metadata.InboundType = i.Type() + metadata.Source = source + metadata.Destination = destination + metadata.OriginDestination = i.listener.UDPAddr() + i.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} + +func (i *Inbound) preparePacketConnection(source M.Socksaddr, destination M.Socksaddr, userData any) (bool, context.Context, N.PacketWriter, N.CloseHandlerFunc) { + return true, log.ContextWithNewID(i.ctx), &directPacketWriter{i.listener.PacketWriter(), source}, nil +} + +type directPacketWriter struct { + writer N.PacketWriter + source M.Socksaddr +} + +func (w *directPacketWriter) WritePacket(buffer *buf.Buffer, addr M.Socksaddr) error { + return w.writer.WritePacket(buffer, w.source) +} diff --git a/outbound/direct_loopback_detect.go b/protocol/direct/loopback_detect.go similarity index 99% rename from outbound/direct_loopback_detect.go rename to protocol/direct/loopback_detect.go index 1469b9d01..5a184e692 100644 --- a/outbound/direct_loopback_detect.go +++ b/protocol/direct/loopback_detect.go @@ -1,4 +1,4 @@ -package outbound +package direct import ( "net" diff --git a/outbound/direct.go b/protocol/direct/outbound.go similarity index 76% rename from outbound/direct.go rename to protocol/direct/outbound.go index 415a72f37..32c1ed8fd 100644 --- a/outbound/direct.go +++ b/protocol/direct/outbound.go @@ -1,4 +1,4 @@ -package outbound +package direct import ( "context" @@ -7,6 +7,7 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" @@ -14,17 +15,20 @@ import ( "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common/bufio" 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" ) -var ( - _ adapter.Outbound = (*Direct)(nil) - _ N.ParallelDialer = (*Direct)(nil) -) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.DirectOutboundOptions](registry, C.TypeDirect, NewOutbound) +} + +var _ N.ParallelDialer = (*Outbound)(nil) -type Direct struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger dialer N.Dialer domainStrategy dns.DomainStrategy fallbackDelay time.Duration @@ -33,21 +37,15 @@ type Direct struct { // loopBack *loopBackDetector } -func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, options option.DirectOutboundOptions) (*Direct, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.DirectOutboundOptions) (adapter.Outbound, error) { options.UDPFragmentDefault = true outboundDialer, err := dialer.New(router, options.DialerOptions) if err != nil { return nil, err } - outbound := &Direct{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeDirect, - network: []string{N.NetworkTCP, N.NetworkUDP}, - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, []string{N.NetworkTCP, N.NetworkUDP}, tag, options.DialerOptions), + logger: logger, domainStrategy: dns.DomainStrategy(options.DomainStrategy), fallbackDelay: time.Duration(options.FallbackDelay), dialer: outboundDialer, @@ -69,9 +67,9 @@ func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, opti return outbound, nil } -func (h *Direct) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination switch h.overrideOption { case 1: @@ -98,9 +96,9 @@ func (h *Direct) DialContext(ctx context.Context, network string, destination M. return h.dialer.DialContext(ctx, network, destination) } -func (h *Direct) DialParallel(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr) (net.Conn, error) { +func (h *Outbound) DialParallel(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination switch h.overrideOption { case 1, 2: @@ -125,9 +123,9 @@ func (h *Direct) DialParallel(ctx context.Context, network string, destination M return N.DialParallel(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, h.fallbackDelay) } -func (h *Direct) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination originDestination := destination switch h.overrideOption { @@ -156,14 +154,14 @@ func (h *Direct) ListenPacket(ctx context.Context, destination M.Socksaddr) (net return conn, nil } -/*func (h *Direct) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +/*func (h *Outbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { if h.loopBack.CheckConn(metadata.Source.AddrPort(), M.AddrPortFromNet(conn.LocalAddr())) { return E.New("reject loopback connection to ", metadata.Destination) } return NewConnection(ctx, h, conn, metadata) } -func (h *Direct) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *Outbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { if h.loopBack.CheckPacketConn(metadata.Source.AddrPort(), M.AddrPortFromNet(conn.LocalAddr())) { return E.New("reject loopback packet connection to ", metadata.Destination) } diff --git a/outbound/dns.go b/protocol/dns/handle.go similarity index 83% rename from outbound/dns.go rename to protocol/dns/handle.go index d9c92f19e..23ed1c0c7 100644 --- a/outbound/dns.go +++ b/protocol/dns/handle.go @@ -1,11 +1,9 @@ -package outbound +package dns import ( "context" "encoding/binary" "net" - "os" - "time" "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" @@ -21,44 +19,6 @@ import ( mDNS "github.com/miekg/dns" ) -var _ adapter.Outbound = (*DNS)(nil) - -type DNS struct { - myOutboundAdapter -} - -func NewDNS(router adapter.Router, tag string) *DNS { - return &DNS{ - myOutboundAdapter{ - protocol: C.TypeDNS, - network: []string{N.NetworkTCP, N.NetworkUDP}, - router: router, - tag: tag, - }, - } -} - -func (d *DNS) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - return nil, os.ErrInvalid -} - -func (d *DNS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - return nil, os.ErrInvalid -} - -// Deprecated -func (d *DNS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - metadata.Destination = M.Socksaddr{} - defer conn.Close() - for { - conn.SetReadDeadline(time.Now().Add(C.DNSTimeout)) - err := HandleStreamDNSRequest(ctx, d.router, conn, metadata) - if err != nil { - return err - } - } -} - func HandleStreamDNSRequest(ctx context.Context, router adapter.Router, conn net.Conn, metadata adapter.InboundContext) error { var queryLength uint16 err := binary.Read(conn, binary.BigEndian, &queryLength) @@ -100,11 +60,6 @@ func HandleStreamDNSRequest(ctx context.Context, router adapter.Router, conn net return nil } -// Deprecated -func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return NewDNSPacketConnection(ctx, d.router, conn, nil, metadata) -} - func NewDNSPacketConnection(ctx context.Context, router adapter.Router, conn N.PacketConn, cachedPackets []*N.PacketBuffer, metadata adapter.InboundContext) error { metadata.Destination = M.Socksaddr{} var reader N.PacketReader = conn diff --git a/protocol/dns/outbound.go b/protocol/dns/outbound.go new file mode 100644 index 000000000..7ce9fde2f --- /dev/null +++ b/protocol/dns/outbound.go @@ -0,0 +1,61 @@ +package dns + +import ( + "context" + "net" + "os" + "time" + + "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/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.StubOptions](registry, C.TypeDNS, NewOutbound) +} + +type Outbound struct { + outbound.Adapter + router adapter.Router + logger logger.ContextLogger +} + +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.StubOptions) (adapter.Outbound, error) { + return &Outbound{ + Adapter: outbound.NewAdapter(C.TypeDNS, []string{N.NetworkTCP, N.NetworkUDP}, tag, nil), + router: router, + logger: logger, + }, nil +} + +func (d *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + return nil, os.ErrInvalid +} + +func (d *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + return nil, os.ErrInvalid +} + +// Deprecated +func (d *Outbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + metadata.Destination = M.Socksaddr{} + defer conn.Close() + for { + conn.SetReadDeadline(time.Now().Add(C.DNSTimeout)) + err := HandleStreamDNSRequest(ctx, d.router, conn, metadata) + if err != nil { + return err + } + } +} + +// Deprecated +func (d *Outbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + return NewDNSPacketConnection(ctx, d.router, conn, nil, metadata) +} diff --git a/outbound/selector.go b/protocol/group/selector.go similarity index 82% rename from outbound/selector.go rename to protocol/group/selector.go index 59e940dfa..32ab8b2a4 100644 --- a/outbound/selector.go +++ b/protocol/group/selector.go @@ -1,28 +1,33 @@ -package outbound +package group import ( "context" "net" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/interrupt" 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" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/service" ) -var ( - _ adapter.Outbound = (*Selector)(nil) - _ adapter.OutboundGroup = (*Selector)(nil) -) +func RegisterSelector(registry *outbound.Registry) { + outbound.Register[option.SelectorOutboundOptions](registry, C.TypeSelector, NewSelector) +} + +var _ adapter.OutboundGroup = (*Selector)(nil) type Selector struct { - myOutboundAdapter + outbound.Adapter ctx context.Context + router adapter.Router + logger logger.ContextLogger tags []string defaultTag string outbounds map[string]adapter.Outbound @@ -31,16 +36,12 @@ type Selector struct { interruptExternalConnections bool } -func NewSelector(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SelectorOutboundOptions) (*Selector, error) { +func NewSelector(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SelectorOutboundOptions) (adapter.Outbound, error) { outbound := &Selector{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeSelector, - router: router, - logger: logger, - tag: tag, - dependencies: options.Outbounds, - }, + Adapter: outbound.NewAdapter(C.TypeSelector, nil, tag, options.Outbounds), ctx: ctx, + router: router, + logger: logger, tags: options.Outbounds, defaultTag: options.Default, outbounds: make(map[string]adapter.Outbound), @@ -69,10 +70,10 @@ func (s *Selector) Start() error { s.outbounds[tag] = detour } - if s.tag != "" { + if s.Tag() != "" { cacheFile := service.FromContext[adapter.CacheFile](s.ctx) if cacheFile != nil { - selected := cacheFile.LoadSelected(s.tag) + selected := cacheFile.LoadSelected(s.Tag()) if selected != "" { detour, loaded := s.outbounds[selected] if loaded { @@ -113,10 +114,10 @@ func (s *Selector) SelectOutbound(tag string) bool { return true } s.selected = detour - if s.tag != "" { + if s.Tag() != "" { cacheFile := service.FromContext[adapter.CacheFile](s.ctx) if cacheFile != nil { - err := cacheFile.StoreSelected(s.tag, tag) + err := cacheFile.StoreSelected(s.Tag(), tag) if err != nil { s.logger.Error("store selected: ", err) } @@ -149,7 +150,7 @@ func (s *Selector) NewConnection(ctx context.Context, conn net.Conn, metadata ad if legacyHandler, ok := s.selected.(adapter.ConnectionHandler); ok { return legacyHandler.NewConnection(ctx, conn, metadata) } else { - return NewConnection(ctx, s.selected, conn, metadata) + return outbound.NewConnection(ctx, s.selected, conn, metadata) } } @@ -160,7 +161,7 @@ func (s *Selector) NewPacketConnection(ctx context.Context, conn N.PacketConn, m if legacyHandler, ok := s.selected.(adapter.PacketConnectionHandler); ok { return legacyHandler.NewPacketConnection(ctx, conn, metadata) } else { - return NewPacketConnection(ctx, s.selected, conn, metadata) + return outbound.NewPacketConnection(ctx, s.selected, conn, metadata) } } diff --git a/outbound/urltest.go b/protocol/group/urltest.go similarity index 94% rename from outbound/urltest.go rename to protocol/group/urltest.go index 564a0ddc3..ccdf809d9 100644 --- a/outbound/urltest.go +++ b/protocol/group/urltest.go @@ -1,4 +1,4 @@ -package outbound +package group import ( "context" @@ -7,6 +7,7 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/interrupt" "github.com/sagernet/sing-box/common/urltest" C "github.com/sagernet/sing-box/constant" @@ -22,15 +23,20 @@ import ( "github.com/sagernet/sing/service/pause" ) +func RegisterURLTest(registry *outbound.Registry) { + outbound.Register[option.URLTestOutboundOptions](registry, C.TypeURLTest, NewURLTest) +} + var ( - _ adapter.Outbound = (*URLTest)(nil) _ adapter.OutboundGroup = (*URLTest)(nil) _ adapter.InterfaceUpdateListener = (*URLTest)(nil) ) type URLTest struct { - myOutboundAdapter + outbound.Adapter ctx context.Context + router adapter.Router + logger log.ContextLogger tags []string link string interval time.Duration @@ -40,17 +46,12 @@ type URLTest struct { interruptExternalConnections bool } -func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.URLTestOutboundOptions) (*URLTest, error) { +func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.URLTestOutboundOptions) (adapter.Outbound, error) { outbound := &URLTest{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeURLTest, - network: []string{N.NetworkTCP, N.NetworkUDP}, - router: router, - logger: logger, - tag: tag, - dependencies: options.Outbounds, - }, + Adapter: outbound.NewAdapter(C.TypeURLTest, []string{N.NetworkTCP, N.NetworkUDP}, tag, options.Outbounds), ctx: ctx, + router: router, + logger: logger, tags: options.Outbounds, link: options.URL, interval: time.Duration(options.Interval), @@ -171,14 +172,14 @@ func (s *URLTest) ListenPacket(ctx context.Context, destination M.Socksaddr) (ne // Deprecated func (s *URLTest) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { ctx = interrupt.ContextWithIsExternalConnection(ctx) - return NewConnection(ctx, s, conn, metadata) + return outbound.NewConnection(ctx, s, conn, metadata) } // TODO // Deprecated func (s *URLTest) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { ctx = interrupt.ContextWithIsExternalConnection(ctx) - return NewPacketConnection(ctx, s, conn, metadata) + return outbound.NewPacketConnection(ctx, s, conn, metadata) } func (s *URLTest) InterfaceUpdated() { diff --git a/protocol/http/inbound.go b/protocol/http/inbound.go new file mode 100644 index 000000000..87ed9a108 --- /dev/null +++ b/protocol/http/inbound.go @@ -0,0 +1,122 @@ +package http + +import ( + std_bufio "bufio" + "context" + "net" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/tls" + "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" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/auth" + E "github.com/sagernet/sing/common/exceptions" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/protocol/http" +) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.HTTPMixedInboundOptions](registry, C.TypeHTTP, NewInbound) +} + +var _ adapter.TCPInjectableInbound = (*Inbound)(nil) + +type Inbound struct { + inbound.Adapter + router adapter.ConnectionRouterEx + logger log.ContextLogger + listener *listener.Listener + authenticator *auth.Authenticator + tlsConfig tls.ServerConfig +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPMixedInboundOptions) (adapter.Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeHTTP, tag), + router: uot.NewRouter(router, logger), + logger: logger, + authenticator: auth.NewAuthenticator(options.Users), + } + if options.TLS != nil { + tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) + if err != nil { + return nil, err + } + inbound.tlsConfig = tlsConfig + } + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: inbound, + SetSystemProxy: options.SetSystemProxy, + SystemProxySOCKS: false, + }) + return inbound, nil +} + +func (h *Inbound) Start() error { + if h.tlsConfig != nil { + err := h.tlsConfig.Start() + if err != nil { + return E.Cause(err, "create TLS config") + } + } + return h.listener.Start() +} + +func (h *Inbound) Close() error { + return common.Close( + &h.listener, + h.tlsConfig, + ) +} + +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := h.newConnection(ctx, conn, metadata, onClose) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } +} + +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { + var err error + if h.tlsConfig != nil { + conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig) + if err != nil { + return err + } + } + return http.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, nil, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose) +} + +func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + user, loaded := auth.UserFromContext[string](ctx) + if !loaded { + h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) + return + } + metadata.User = user + h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + user, loaded := auth.UserFromContext[string](ctx) + if !loaded { + h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) + return + } + metadata.User = user + h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} diff --git a/outbound/http.go b/protocol/http/outbound.go similarity index 56% rename from outbound/http.go rename to protocol/http/outbound.go index 6f15afb5d..4c930591b 100644 --- a/outbound/http.go +++ b/protocol/http/outbound.go @@ -1,4 +1,4 @@ -package outbound +package http import ( "context" @@ -6,25 +6,30 @@ import ( "os" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" sHTTP "github.com/sagernet/sing/protocol/http" ) -var _ adapter.Outbound = (*HTTP)(nil) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.HTTPOutboundOptions](registry, C.TypeHTTP, NewOutbound) +} -type HTTP struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger client *sHTTP.Client } -func NewHTTP(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPOutboundOptions) (*HTTP, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPOutboundOptions) (adapter.Outbound, error) { outboundDialer, err := dialer.New(router, options.DialerOptions) if err != nil { return nil, err @@ -33,16 +38,10 @@ func NewHTTP(ctx context.Context, router adapter.Router, logger log.ContextLogge if err != nil { return nil, err } - return &HTTP{ - myOutboundAdapter{ - protocol: C.TypeHTTP, - network: []string{N.NetworkTCP}, - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, - sHTTP.NewClient(sHTTP.Options{ + return &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeHTTP, []string{N.NetworkTCP}, tag, options.DialerOptions), + logger: logger, + client: sHTTP.NewClient(sHTTP.Options{ Dialer: detour, Server: options.ServerOptions.Build(), Username: options.Username, @@ -53,14 +52,14 @@ func NewHTTP(ctx context.Context, router adapter.Router, logger log.ContextLogge }, nil } -func (h *HTTP) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination h.logger.InfoContext(ctx, "outbound connection to ", destination) return h.client.DialContext(ctx, network, destination) } -func (h *HTTP) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { return nil, os.ErrInvalid } diff --git a/inbound/hysteria.go b/protocol/hysteria/inbound.go similarity index 71% rename from inbound/hysteria.go rename to protocol/hysteria/inbound.go index e415d5701..8127106b8 100644 --- a/inbound/hysteria.go +++ b/protocol/hysteria/inbound.go @@ -1,6 +1,4 @@ -//go:build with_quic - -package inbound +package hysteria import ( "context" @@ -8,7 +6,9 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" "github.com/sagernet/sing-box/common/humanize" + "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" @@ -20,16 +20,21 @@ import ( N "github.com/sagernet/sing/common/network" ) -var _ adapter.Inbound = (*Hysteria)(nil) +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.HysteriaInboundOptions](registry, C.TypeHysteria, NewInbound) +} -type Hysteria struct { - myInboundAdapter +type Inbound struct { + inbound.Adapter + router adapter.Router + logger log.ContextLogger + listener *listener.Listener tlsConfig tls.ServerConfig service *hysteria.Service[int] userNameList []string } -func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (*Hysteria, error) { +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (adapter.Inbound, error) { options.UDPFragmentDefault = true if options.TLS == nil || !options.TLS.Enabled { return nil, C.ErrTLSRequired @@ -38,16 +43,15 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL if err != nil { return nil, err } - inbound := &Hysteria{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeHysteria, - network: []string{N.NetworkUDP}, - ctx: ctx, - router: router, - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeHysteria, tag), + router: router, + logger: logger, + listener: listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Listen: options.ListenOptions, + }), tlsConfig: tlsConfig, } var sendBps, receiveBps uint64 @@ -113,9 +117,12 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL return inbound, nil } -func (h *Hysteria) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { ctx = log.ContextWithNewID(ctx) - metadata = h.createMetadata(conn, metadata) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) userID, _ := auth.UserFromContext[int](ctx) if userName := h.userNameList[userID]; userName != "" { @@ -127,9 +134,13 @@ func (h *Hysteria) newConnection(ctx context.Context, conn net.Conn, metadata ad return h.router.RouteConnection(ctx, conn, metadata) } -func (h *Hysteria) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { ctx = log.ContextWithNewID(ctx) - metadata = h.createPacketMetadata(conn, metadata) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + metadata.OriginDestination = h.listener.UDPAddr() h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) userID, _ := auth.UserFromContext[int](ctx) if userName := h.userNameList[userID]; userName != "" { @@ -141,23 +152,23 @@ func (h *Hysteria) newPacketConnection(ctx context.Context, conn N.PacketConn, m return h.router.RoutePacketConnection(ctx, conn, metadata) } -func (h *Hysteria) Start() error { +func (h *Inbound) Start() error { if h.tlsConfig != nil { err := h.tlsConfig.Start() if err != nil { return err } } - packetConn, err := h.myInboundAdapter.ListenUDP() + packetConn, err := h.listener.ListenUDP() if err != nil { return err } return h.service.Start(packetConn) } -func (h *Hysteria) Close() error { +func (h *Inbound) Close() error { return common.Close( - &h.myInboundAdapter, + &h.listener, h.tlsConfig, common.PtrOrNil(h.service), ) diff --git a/outbound/hysteria.go b/protocol/hysteria/outbound.go similarity index 76% rename from outbound/hysteria.go rename to protocol/hysteria/outbound.go index f513cf64e..4722f4f06 100644 --- a/outbound/hysteria.go +++ b/protocol/hysteria/outbound.go @@ -1,6 +1,4 @@ -//go:build with_quic - -package outbound +package hysteria import ( "context" @@ -8,31 +6,39 @@ import ( "os" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/humanize" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/protocol/tuic" "github.com/sagernet/sing-quic/hysteria" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" 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" ) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.HysteriaOutboundOptions](registry, C.TypeHysteria, NewOutbound) +} + var ( - _ adapter.Outbound = (*TUIC)(nil) - _ adapter.InterfaceUpdateListener = (*TUIC)(nil) + _ adapter.Outbound = (*tuic.Outbound)(nil) + _ adapter.InterfaceUpdateListener = (*tuic.Outbound)(nil) ) -type Hysteria struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger client *hysteria.Client } -func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaOutboundOptions) (*Hysteria, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaOutboundOptions) (adapter.Outbound, error) { options.UDPFragmentDefault = true if options.TLS == nil || !options.TLS.Enabled { return nil, C.ErrTLSRequired @@ -88,20 +94,14 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL if err != nil { return nil, err } - return &Hysteria{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeHysteria, - network: networkList, - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, - client: client, + return &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeHysteria, networkList, tag, options.DialerOptions), + logger: logger, + client: client, }, nil } -func (h *Hysteria) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { switch N.NetworkName(network) { case N.NetworkTCP: h.logger.InfoContext(ctx, "outbound connection to ", destination) @@ -117,15 +117,15 @@ func (h *Hysteria) DialContext(ctx context.Context, network string, destination } } -func (h *Hysteria) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { h.logger.InfoContext(ctx, "outbound packet connection to ", destination) return h.client.ListenPacket(ctx, destination) } -func (h *Hysteria) InterfaceUpdated() { +func (h *Outbound) InterfaceUpdated() { h.client.CloseWithError(E.New("network changed")) } -func (h *Hysteria) Close() error { +func (h *Outbound) Close() error { return h.client.CloseWithError(os.ErrClosed) } diff --git a/inbound/hysteria2.go b/protocol/hysteria2/inbound.go similarity index 74% rename from inbound/hysteria2.go rename to protocol/hysteria2/inbound.go index c13e9531f..cbf811097 100644 --- a/inbound/hysteria2.go +++ b/protocol/hysteria2/inbound.go @@ -1,6 +1,4 @@ -//go:build with_quic - -package inbound +package hysteria2 import ( "context" @@ -11,6 +9,8 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" @@ -23,16 +23,21 @@ import ( N "github.com/sagernet/sing/common/network" ) -var _ adapter.Inbound = (*Hysteria2)(nil) +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.Hysteria2InboundOptions](registry, C.TypeHysteria2, NewInbound) +} -type Hysteria2 struct { - myInboundAdapter +type Inbound struct { + inbound.Adapter + router adapter.Router + logger log.ContextLogger + listener *listener.Listener tlsConfig tls.ServerConfig service *hysteria2.Service[int] userNameList []string } -func NewHysteria2(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2InboundOptions) (*Hysteria2, error) { +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2InboundOptions) (adapter.Inbound, error) { options.UDPFragmentDefault = true if options.TLS == nil || !options.TLS.Enabled { return nil, C.ErrTLSRequired @@ -76,16 +81,15 @@ func NewHysteria2(ctx context.Context, router adapter.Router, logger log.Context return nil, E.New("unknown masquerade URL scheme: ", masqueradeURL.Scheme) } } - inbound := &Hysteria2{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeHysteria2, - network: []string{N.NetworkUDP}, - ctx: ctx, - router: router, - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeHysteria2, tag), + router: router, + logger: logger, + listener: listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Listen: options.ListenOptions, + }), tlsConfig: tlsConfig, } var udpTimeout time.Duration @@ -124,9 +128,12 @@ func NewHysteria2(ctx context.Context, router adapter.Router, logger log.Context return inbound, nil } -func (h *Hysteria2) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { ctx = log.ContextWithNewID(ctx) - metadata = h.createMetadata(conn, metadata) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) userID, _ := auth.UserFromContext[int](ctx) if userName := h.userNameList[userID]; userName != "" { @@ -138,9 +145,13 @@ func (h *Hysteria2) newConnection(ctx context.Context, conn net.Conn, metadata a return h.router.RouteConnection(ctx, conn, metadata) } -func (h *Hysteria2) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { ctx = log.ContextWithNewID(ctx) - metadata = h.createPacketMetadata(conn, metadata) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + metadata.OriginDestination = h.listener.UDPAddr() h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) userID, _ := auth.UserFromContext[int](ctx) if userName := h.userNameList[userID]; userName != "" { @@ -152,23 +163,23 @@ func (h *Hysteria2) newPacketConnection(ctx context.Context, conn N.PacketConn, return h.router.RoutePacketConnection(ctx, conn, metadata) } -func (h *Hysteria2) Start() error { +func (h *Inbound) Start() error { if h.tlsConfig != nil { err := h.tlsConfig.Start() if err != nil { return err } } - packetConn, err := h.myInboundAdapter.ListenUDP() + packetConn, err := h.listener.ListenUDP() if err != nil { return err } return h.service.Start(packetConn) } -func (h *Hysteria2) Close() error { +func (h *Inbound) Close() error { return common.Close( - &h.myInboundAdapter, + &h.listener, h.tlsConfig, common.PtrOrNil(h.service), ) diff --git a/outbound/hysteria2.go b/protocol/hysteria2/outbound.go similarity index 69% rename from outbound/hysteria2.go rename to protocol/hysteria2/outbound.go index 5e46f6a86..5ebc6c91f 100644 --- a/outbound/hysteria2.go +++ b/protocol/hysteria2/outbound.go @@ -1,6 +1,4 @@ -//go:build with_quic - -package outbound +package hysteria2 import ( "context" @@ -8,31 +6,39 @@ import ( "os" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/protocol/tuic" "github.com/sagernet/sing-quic/hysteria" "github.com/sagernet/sing-quic/hysteria2" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" 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" ) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.Hysteria2OutboundOptions](registry, C.TypeHysteria2, NewOutbound) +} + var ( - _ adapter.Outbound = (*TUIC)(nil) - _ adapter.InterfaceUpdateListener = (*TUIC)(nil) + _ adapter.Outbound = (*tuic.Outbound)(nil) + _ adapter.InterfaceUpdateListener = (*tuic.Outbound)(nil) ) -type Hysteria2 struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger client *hysteria2.Client } -func NewHysteria2(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2OutboundOptions) (*Hysteria2, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2OutboundOptions) (adapter.Outbound, error) { options.UDPFragmentDefault = true if options.TLS == nil || !options.TLS.Enabled { return nil, C.ErrTLSRequired @@ -74,20 +80,14 @@ func NewHysteria2(ctx context.Context, router adapter.Router, logger log.Context if err != nil { return nil, err } - return &Hysteria2{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeHysteria2, - network: networkList, - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, - client: client, + return &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeHysteria2, networkList, tag, options.DialerOptions), + logger: logger, + client: client, }, nil } -func (h *Hysteria2) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { switch N.NetworkName(network) { case N.NetworkTCP: h.logger.InfoContext(ctx, "outbound connection to ", destination) @@ -103,15 +103,15 @@ func (h *Hysteria2) DialContext(ctx context.Context, network string, destination } } -func (h *Hysteria2) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { h.logger.InfoContext(ctx, "outbound packet connection to ", destination) return h.client.ListenPacket(ctx) } -func (h *Hysteria2) InterfaceUpdated() { +func (h *Outbound) InterfaceUpdated() { h.client.CloseWithError(E.New("network changed")) } -func (h *Hysteria2) Close() error { +func (h *Outbound) Close() error { return h.client.CloseWithError(os.ErrClosed) } diff --git a/protocol/mixed/inbound.go b/protocol/mixed/inbound.go new file mode 100644 index 000000000..e57b791fe --- /dev/null +++ b/protocol/mixed/inbound.go @@ -0,0 +1,109 @@ +package mixed + +import ( + std_bufio "bufio" + "context" + "net" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "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" + "github.com/sagernet/sing/common/auth" + E "github.com/sagernet/sing/common/exceptions" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/protocol/http" + "github.com/sagernet/sing/protocol/socks" + "github.com/sagernet/sing/protocol/socks/socks4" + "github.com/sagernet/sing/protocol/socks/socks5" +) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.HTTPMixedInboundOptions](registry, C.TypeMixed, NewInbound) +} + +var _ adapter.TCPInjectableInbound = (*Inbound)(nil) + +type Inbound struct { + inbound.Adapter + router adapter.ConnectionRouterEx + logger log.ContextLogger + listener *listener.Listener + authenticator *auth.Authenticator +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPMixedInboundOptions) (adapter.Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeMixed, tag), + router: uot.NewRouter(router, logger), + logger: logger, + authenticator: auth.NewAuthenticator(options.Users), + } + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: inbound, + SetSystemProxy: options.SetSystemProxy, + SystemProxySOCKS: true, + }) + return inbound, nil +} + +func (h *Inbound) Start() error { + return h.listener.Start() +} + +func (h *Inbound) Close() error { + return h.listener.Close() +} + +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := h.newConnection(ctx, conn, metadata, onClose) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } +} + +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { + reader := std_bufio.NewReader(conn) + headerBytes, err := reader.Peek(1) + if err != nil { + return E.Cause(err, "peek first byte") + } + switch headerBytes[0] { + case socks4.Version, socks5.Version: + return socks.HandleConnectionEx(ctx, conn, reader, h.authenticator, nil, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, metadata.Destination, onClose) + default: + return http.HandleConnectionEx(ctx, conn, reader, h.authenticator, nil, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose) + } +} + +func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + user, loaded := auth.UserFromContext[string](ctx) + if !loaded { + h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) + return + } + metadata.User = user + h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + user, loaded := auth.UserFromContext[string](ctx) + if !loaded { + h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) + return + } + metadata.User = user + h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} diff --git a/protocol/naive/inbound.go b/protocol/naive/inbound.go new file mode 100644 index 000000000..1a561aeaf --- /dev/null +++ b/protocol/naive/inbound.go @@ -0,0 +1,248 @@ +package naive + +import ( + "context" + "io" + "math/rand" + "net" + "net/http" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/tls" + "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" + "github.com/sagernet/sing-box/transport/v2rayhttp" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/auth" + 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" + sHttp "github.com/sagernet/sing/protocol/http" +) + +var ConfigureHTTP3ListenerFunc func(listener *listener.Listener, handler http.Handler, tlsConfig tls.ServerConfig, logger logger.Logger) (io.Closer, error) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.NaiveInboundOptions](registry, C.TypeNaive, NewInbound) +} + +type Inbound struct { + inbound.Adapter + ctx context.Context + router adapter.ConnectionRouterEx + logger logger.ContextLogger + listener *listener.Listener + network []string + networkIsDefault bool + authenticator *auth.Authenticator + tlsConfig tls.ServerConfig + httpServer *http.Server + h3Server io.Closer +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.NaiveInboundOptions) (adapter.Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeNaive, tag), + ctx: ctx, + router: uot.NewRouter(router, logger), + logger: logger, + listener: listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Listen: options.ListenOptions, + }), + networkIsDefault: options.Network == "", + network: options.Network.Build(), + authenticator: auth.NewAuthenticator(options.Users), + } + if common.Contains(inbound.network, N.NetworkUDP) { + if options.TLS == nil || !options.TLS.Enabled { + return nil, E.New("TLS is required for QUIC server") + } + } + if len(options.Users) == 0 { + return nil, E.New("missing users") + } + if options.TLS != nil { + tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) + if err != nil { + return nil, err + } + inbound.tlsConfig = tlsConfig + } + return inbound, nil +} + +func (n *Inbound) Start() error { + var tlsConfig *tls.STDConfig + if n.tlsConfig != nil { + err := n.tlsConfig.Start() + if err != nil { + return E.Cause(err, "create TLS config") + } + tlsConfig, err = n.tlsConfig.Config() + if err != nil { + return err + } + } + if common.Contains(n.network, N.NetworkTCP) { + tcpListener, err := n.listener.ListenTCP() + if err != nil { + return err + } + n.httpServer = &http.Server{ + Handler: n, + TLSConfig: tlsConfig, + BaseContext: func(listener net.Listener) context.Context { + return n.ctx + }, + } + go func() { + var sErr error + if tlsConfig != nil { + sErr = n.httpServer.ServeTLS(tcpListener, "", "") + } else { + sErr = n.httpServer.Serve(tcpListener) + } + if sErr != nil && !E.IsClosedOrCanceled(sErr) { + n.logger.Error("http server serve error: ", sErr) + } + }() + } + + if common.Contains(n.network, N.NetworkUDP) { + http3Server, err := ConfigureHTTP3ListenerFunc(n.listener, n, n.tlsConfig, n.logger) + if err == nil { + n.h3Server = http3Server + } else if len(n.network) > 1 { + n.logger.Warn(E.Cause(err, "naive http3 disabled")) + } else { + return err + } + } + + return nil +} + +func (n *Inbound) Close() error { + return common.Close( + &n.listener, + common.PtrOrNil(n.httpServer), + n.h3Server, + n.tlsConfig, + ) +} + +func (n *Inbound) ServeHTTP(writer http.ResponseWriter, request *http.Request) { + ctx := log.ContextWithNewID(request.Context()) + if request.Method != "CONNECT" { + rejectHTTP(writer, http.StatusBadRequest) + n.badRequest(ctx, request, E.New("not CONNECT request")) + return + } else if request.Header.Get("Padding") == "" { + rejectHTTP(writer, http.StatusBadRequest) + n.badRequest(ctx, request, E.New("missing naive padding")) + return + } + userName, password, authOk := sHttp.ParseBasicAuth(request.Header.Get("Proxy-Authorization")) + if authOk { + authOk = n.authenticator.Verify(userName, password) + } + if !authOk { + rejectHTTP(writer, http.StatusProxyAuthRequired) + n.badRequest(ctx, request, E.New("authorization failed")) + return + } + writer.Header().Set("Padding", generateNaivePaddingHeader()) + writer.WriteHeader(http.StatusOK) + writer.(http.Flusher).Flush() + + hostPort := request.URL.Host + if hostPort == "" { + hostPort = request.Host + } + source := sHttp.SourceAddress(request) + destination := M.ParseSocksaddr(hostPort) + + if hijacker, isHijacker := writer.(http.Hijacker); isHijacker { + conn, _, err := hijacker.Hijack() + if err != nil { + n.badRequest(ctx, request, E.New("hijack failed")) + return + } + n.newConnection(ctx, false, &naiveH1Conn{Conn: conn}, userName, source, destination) + } else { + n.newConnection(ctx, true, &naiveH2Conn{reader: request.Body, writer: writer, flusher: writer.(http.Flusher)}, userName, source, destination) + } +} + +func (n *Inbound) newConnection(ctx context.Context, waitForClose bool, conn net.Conn, userName string, source M.Socksaddr, destination M.Socksaddr) { + if userName != "" { + n.logger.InfoContext(ctx, "[", userName, "] inbound connection from ", source) + n.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", destination) + } else { + n.logger.InfoContext(ctx, "inbound connection from ", source) + n.logger.InfoContext(ctx, "inbound connection to ", destination) + } + var metadata adapter.InboundContext + metadata.Inbound = n.Tag() + metadata.InboundType = n.Type() + metadata.InboundDetour = n.listener.ListenOptions().Detour + metadata.InboundOptions = n.listener.ListenOptions().InboundOptions + metadata.Source = source + metadata.Destination = destination + metadata.OriginDestination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap() + metadata.User = userName + if !waitForClose { + n.router.RouteConnectionEx(ctx, conn, metadata, nil) + } else { + done := make(chan struct{}) + wrapper := v2rayhttp.NewHTTP2Wrapper(conn) + n.router.RouteConnectionEx(ctx, conn, metadata, N.OnceClose(func(it error) { + close(done) + })) + <-done + wrapper.CloseWrapper() + } +} + +func (n *Inbound) badRequest(ctx context.Context, request *http.Request, err error) { + n.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", request.RemoteAddr)) +} + +func rejectHTTP(writer http.ResponseWriter, statusCode int) { + hijacker, ok := writer.(http.Hijacker) + if !ok { + writer.WriteHeader(statusCode) + return + } + conn, _, err := hijacker.Hijack() + if err != nil { + writer.WriteHeader(statusCode) + return + } + if tcpConn, isTCP := common.Cast[*net.TCPConn](conn); isTCP { + tcpConn.SetLinger(0) + } + conn.Close() +} + +func generateNaivePaddingHeader() string { + paddingLen := rand.Intn(32) + 30 + padding := make([]byte, paddingLen) + bits := rand.Uint64() + for i := 0; i < 16; i++ { + // Codes that won't be Huffman coded. + padding[i] = "!#$()+<>?@[]^`{}"[bits&15] + bits >>= 4 + } + for i := 16; i < paddingLen; i++ { + padding[i] = '~' + } + return string(padding) +} diff --git a/inbound/naive.go b/protocol/naive/inbound_conn.go similarity index 58% rename from inbound/naive.go rename to protocol/naive/inbound_conn.go index 498e823ce..16944cbaf 100644 --- a/inbound/naive.go +++ b/protocol/naive/inbound_conn.go @@ -1,7 +1,6 @@ -package inbound +package naive import ( - "context" "encoding/binary" "io" "math/rand" @@ -11,228 +10,12 @@ import ( "strings" "time" - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/tls" - "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" - "github.com/sagernet/sing-box/transport/v2rayhttp" "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/auth" "github.com/sagernet/sing/common/buf" - E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/rw" - sHttp "github.com/sagernet/sing/protocol/http" ) -var _ adapter.Inbound = (*Naive)(nil) - -type Naive struct { - myInboundAdapter - authenticator *auth.Authenticator - tlsConfig tls.ServerConfig - httpServer *http.Server - h3Server any -} - -func NewNaive(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.NaiveInboundOptions) (*Naive, error) { - inbound := &Naive{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeNaive, - network: options.Network.Build(), - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - authenticator: auth.NewAuthenticator(options.Users), - } - if common.Contains(inbound.network, N.NetworkUDP) { - if options.TLS == nil || !options.TLS.Enabled { - return nil, E.New("TLS is required for QUIC server") - } - } - if len(options.Users) == 0 { - return nil, E.New("missing users") - } - if options.TLS != nil { - tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) - if err != nil { - return nil, err - } - inbound.tlsConfig = tlsConfig - } - return inbound, nil -} - -func (n *Naive) Start() error { - var tlsConfig *tls.STDConfig - if n.tlsConfig != nil { - err := n.tlsConfig.Start() - if err != nil { - return E.Cause(err, "create TLS config") - } - tlsConfig, err = n.tlsConfig.Config() - if err != nil { - return err - } - } - - if common.Contains(n.network, N.NetworkTCP) { - tcpListener, err := n.ListenTCP() - if err != nil { - return err - } - n.httpServer = &http.Server{ - Handler: n, - TLSConfig: tlsConfig, - BaseContext: func(listener net.Listener) context.Context { - return n.ctx - }, - } - go func() { - var sErr error - if tlsConfig != nil { - sErr = n.httpServer.ServeTLS(tcpListener, "", "") - } else { - sErr = n.httpServer.Serve(tcpListener) - } - if sErr != nil && !E.IsClosedOrCanceled(sErr) { - n.logger.Error("http server serve error: ", sErr) - } - }() - } - - if common.Contains(n.network, N.NetworkUDP) { - err := n.configureHTTP3Listener() - if !C.WithQUIC && len(n.network) > 1 { - n.logger.Warn(E.Cause(err, "naive http3 disabled")) - } else if err != nil { - return err - } - } - - return nil -} - -func (n *Naive) Close() error { - return common.Close( - &n.myInboundAdapter, - common.PtrOrNil(n.httpServer), - n.h3Server, - n.tlsConfig, - ) -} - -func (n *Naive) ServeHTTP(writer http.ResponseWriter, request *http.Request) { - ctx := log.ContextWithNewID(request.Context()) - if request.Method != "CONNECT" { - rejectHTTP(writer, http.StatusBadRequest) - n.badRequest(ctx, request, E.New("not CONNECT request")) - return - } else if request.Header.Get("Padding") == "" { - rejectHTTP(writer, http.StatusBadRequest) - n.badRequest(ctx, request, E.New("missing naive padding")) - return - } - userName, password, authOk := sHttp.ParseBasicAuth(request.Header.Get("Proxy-Authorization")) - if authOk { - authOk = n.authenticator.Verify(userName, password) - } - if !authOk { - rejectHTTP(writer, http.StatusProxyAuthRequired) - n.badRequest(ctx, request, E.New("authorization failed")) - return - } - writer.Header().Set("Padding", generateNaivePaddingHeader()) - writer.WriteHeader(http.StatusOK) - writer.(http.Flusher).Flush() - - hostPort := request.URL.Host - if hostPort == "" { - hostPort = request.Host - } - source := sHttp.SourceAddress(request) - destination := M.ParseSocksaddr(hostPort) - - if hijacker, isHijacker := writer.(http.Hijacker); isHijacker { - conn, _, err := hijacker.Hijack() - if err != nil { - n.badRequest(ctx, request, E.New("hijack failed")) - return - } - n.newConnection(ctx, false, &naiveH1Conn{Conn: conn}, userName, source, destination) - } else { - n.newConnection(ctx, true, &naiveH2Conn{reader: request.Body, writer: writer, flusher: writer.(http.Flusher)}, userName, source, destination) - } -} - -func (n *Naive) newConnection(ctx context.Context, waitForClose bool, conn net.Conn, userName string, source M.Socksaddr, destination M.Socksaddr) { - if userName != "" { - n.logger.InfoContext(ctx, "[", userName, "] inbound connection from ", source) - n.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", destination) - } else { - n.logger.InfoContext(ctx, "inbound connection from ", source) - n.logger.InfoContext(ctx, "inbound connection to ", destination) - } - metadata := n.createMetadata(conn, adapter.InboundContext{ - Source: source, - Destination: destination, - User: userName, - }) - if !waitForClose { - n.router.RouteConnectionEx(ctx, conn, metadata, nil) - } else { - done := make(chan struct{}) - wrapper := v2rayhttp.NewHTTP2Wrapper(conn) - n.router.RouteConnectionEx(ctx, conn, metadata, N.OnceClose(func(it error) { - close(done) - })) - <-done - wrapper.CloseWrapper() - } -} - -func (n *Naive) badRequest(ctx context.Context, request *http.Request, err error) { - n.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", request.RemoteAddr)) -} - -func rejectHTTP(writer http.ResponseWriter, statusCode int) { - hijacker, ok := writer.(http.Hijacker) - if !ok { - writer.WriteHeader(statusCode) - return - } - conn, _, err := hijacker.Hijack() - if err != nil { - writer.WriteHeader(statusCode) - return - } - if tcpConn, isTCP := common.Cast[*net.TCPConn](conn); isTCP { - tcpConn.SetLinger(0) - } - conn.Close() -} - -func generateNaivePaddingHeader() string { - paddingLen := rand.Intn(32) + 30 - padding := make([]byte, paddingLen) - bits := rand.Uint64() - for i := 0; i < 16; i++ { - // Codes that won't be Huffman coded. - padding[i] = "!#$()+<>?@[]^`{}"[bits&15] - bits >>= 4 - } - for i := 16; i < paddingLen; i++ { - padding[i] = '~' - } - return string(padding) -} - const kFirstPaddings = 8 type naiveH1Conn struct { diff --git a/protocol/naive/quic/inbound_init.go b/protocol/naive/quic/inbound_init.go new file mode 100644 index 000000000..f495c860f --- /dev/null +++ b/protocol/naive/quic/inbound_init.go @@ -0,0 +1,52 @@ +package quic + +import ( + "io" + "net/http" + + "github.com/sagernet/quic-go" + "github.com/sagernet/quic-go/http3" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/protocol/naive" + "github.com/sagernet/sing-quic" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" +) + +func init() { + naive.ConfigureHTTP3ListenerFunc = func(listener *listener.Listener, handler http.Handler, tlsConfig tls.ServerConfig, logger logger.Logger) (io.Closer, error) { + err := qtls.ConfigureHTTP3(tlsConfig) + if err != nil { + return nil, err + } + + udpConn, err := listener.ListenUDP() + if err != nil { + return nil, err + } + + quicListener, err := qtls.ListenEarly(udpConn, tlsConfig, &quic.Config{ + MaxIncomingStreams: 1 << 60, + Allow0RTT: true, + }) + if err != nil { + udpConn.Close() + return nil, err + } + + h3Server := &http3.Server{ + Handler: handler, + } + + go func() { + sErr := h3Server.ServeListener(quicListener) + udpConn.Close() + if sErr != nil && !E.IsClosedOrCanceled(sErr) { + logger.Error("http3 server closed: ", sErr) + } + }() + + return quicListener, nil + } +} diff --git a/protocol/redirect/redirect.go b/protocol/redirect/redirect.go new file mode 100644 index 000000000..71e1fced1 --- /dev/null +++ b/protocol/redirect/redirect.go @@ -0,0 +1,65 @@ +package redirect + +import ( + "context" + "net" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/redir" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +func RegisterRedirect(registry *inbound.Registry) { + inbound.Register[option.RedirectInboundOptions](registry, C.TypeRedirect, NewRedirect) +} + +type Redirect struct { + inbound.Adapter + router adapter.Router + logger log.ContextLogger + listener *listener.Listener +} + +func NewRedirect(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.RedirectInboundOptions) (adapter.Inbound, error) { + redirect := &Redirect{ + Adapter: inbound.NewAdapter(C.TypeRedirect, tag), + router: router, + logger: logger, + } + redirect.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: redirect, + }) + return redirect, nil +} + +func (h *Redirect) Start() error { + return h.listener.Start() +} + +func (h *Redirect) Close() error { + return h.listener.Close() +} + +func (h *Redirect) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + destination, err := redir.GetOriginalDestination(conn) + if err != nil { + conn.Close() + h.logger.ErrorContext(ctx, "process connection from ", conn.RemoteAddr(), ": get redirect destination: ", err) + return + } + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.Destination = M.SocksaddrFromNetIP(destination) + h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} diff --git a/inbound/tproxy.go b/protocol/redirect/tproxy.go similarity index 60% rename from inbound/tproxy.go rename to protocol/redirect/tproxy.go index 40653c797..dee40ec5a 100644 --- a/inbound/tproxy.go +++ b/protocol/redirect/tproxy.go @@ -1,4 +1,4 @@ -package inbound +package redirect import ( "context" @@ -8,6 +8,8 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/redir" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" @@ -21,22 +23,25 @@ import ( "github.com/sagernet/sing/common/udpnat2" ) +func RegisterTProxy(registry *inbound.Registry) { + inbound.Register[option.TProxyInboundOptions](registry, C.TypeTProxy, NewTProxy) +} + type TProxy struct { - myInboundAdapter - udpNat *udpnat.Service + inbound.Adapter + ctx context.Context + router adapter.Router + logger log.ContextLogger + listener *listener.Listener + udpNat *udpnat.Service } -func NewTProxy(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TProxyInboundOptions) *TProxy { +func NewTProxy(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TProxyInboundOptions) (adapter.Inbound, error) { tproxy := &TProxy{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeTProxy, - network: options.Network.Build(), - ctx: ctx, - router: router, - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, + Adapter: inbound.NewAdapter(C.TypeTProxy, tag), + ctx: ctx, + router: router, + logger: logger, } var udpTimeout time.Duration if options.UDPTimeout != 0 { @@ -44,28 +49,34 @@ func NewTProxy(ctx context.Context, router adapter.Router, logger log.ContextLog } else { udpTimeout = C.UDPTimeout } - tproxy.connHandler = tproxy - tproxy.oobPacketHandler = tproxy tproxy.udpNat = udpnat.New(tproxy, tproxy.preparePacketConnection, udpTimeout, false) - return tproxy + tproxy.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: options.Network.Build(), + Listen: options.ListenOptions, + ConnectionHandler: tproxy, + OOBPacketHandler: tproxy, + }) + return tproxy, nil } func (t *TProxy) Start() error { - err := t.myInboundAdapter.Start() + err := t.listener.Start() if err != nil { return err } - if t.tcpListener != nil { - err = control.Conn(common.MustCast[syscall.Conn](t.tcpListener), func(fd uintptr) error { - return redir.TProxy(fd, M.SocksaddrFromNet(t.tcpListener.Addr()).Addr.Is6()) + if listener := t.listener.TCPListener(); listener != nil { + err = control.Conn(common.MustCast[syscall.Conn](listener), func(fd uintptr) error { + return redir.TProxy(fd, M.SocksaddrFromNet(listener.Addr()).Addr.Is6()) }) if err != nil { return E.Cause(err, "configure tproxy TCP listener") } } - if t.udpConn != nil { - err = control.Conn(t.udpConn, func(fd uintptr) error { - return redir.TProxy(fd, M.SocksaddrFromNet(t.udpConn.LocalAddr()).Addr.Is6()) + if conn := t.listener.UDPConn(); conn != nil { + err = control.Conn(conn, func(fd uintptr) error { + return redir.TProxy(fd, M.SocksaddrFromNet(conn.LocalAddr()).Addr.Is6()) }) if err != nil { return E.Cause(err, "configure tproxy UDP listener") @@ -74,13 +85,26 @@ func (t *TProxy) Start() error { return nil } +func (t *TProxy) Close() error { + return t.listener.Close() +} + func (t *TProxy) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap() - t.newConnectionEx(ctx, conn, metadata, onClose) + t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + t.router.RouteConnectionEx(ctx, conn, metadata, onClose) } func (t *TProxy) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { - t.newPacketConnectionEx(ctx, conn, t.createPacketMetadataEx(source, destination), onClose) + t.logger.InfoContext(ctx, "inbound packet connection from ", source) + t.logger.InfoContext(ctx, "inbound packet connection to ", destination) + var metadata adapter.InboundContext + metadata.Inbound = t.Tag() + metadata.InboundType = t.Type() + metadata.Source = source + metadata.Destination = destination + metadata.OriginDestination = t.listener.UDPAddr() + t.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } func (t *TProxy) NewPacketEx(buffer *buf.Buffer, oob []byte, source M.Socksaddr) { @@ -100,8 +124,9 @@ type tproxyPacketWriter struct { } func (t *TProxy) preparePacketConnection(source M.Socksaddr, destination M.Socksaddr, userData any) (bool, context.Context, N.PacketWriter, N.CloseHandlerFunc) { - writer := &tproxyPacketWriter{ctx: t.ctx, source: source.AddrPort(), destination: destination} - return true, t.ctx, writer, func(it error) { + ctx := log.ContextWithNewID(t.ctx) + writer := &tproxyPacketWriter{ctx: ctx, source: source.AddrPort(), destination: destination} + return true, ctx, writer, func(it error) { common.Close(common.PtrOrNil(writer.conn)) } } diff --git a/protocol/shadowsocks/inbound.go b/protocol/shadowsocks/inbound.go new file mode 100644 index 000000000..b23516d99 --- /dev/null +++ b/protocol/shadowsocks/inbound.go @@ -0,0 +1,179 @@ +package shadowsocks + +import ( + "context" + "net" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/mux" + "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" + "github.com/sagernet/sing-shadowsocks" + "github.com/sagernet/sing-shadowsocks/shadowaead" + "github.com/sagernet/sing-shadowsocks/shadowaead_2022" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + 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/ntp" +) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.ShadowsocksInboundOptions](registry, C.TypeShadowsocks, NewInbound) +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (adapter.Inbound, error) { + if len(options.Users) > 0 && len(options.Destinations) > 0 { + return nil, E.New("users and destinations options must not be combined") + } + if len(options.Users) > 0 { + return newMultiInbound(ctx, router, logger, tag, options) + } else if len(options.Destinations) > 0 { + return newRelayInbound(ctx, router, logger, tag, options) + } else { + return newInbound(ctx, router, logger, tag, options) + } +} + +var _ adapter.TCPInjectableInbound = (*Inbound)(nil) + +type Inbound struct { + inbound.Adapter + ctx context.Context + router adapter.ConnectionRouterEx + logger logger.ContextLogger + listener *listener.Listener + service shadowsocks.Service +} + +func newInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeShadowsocks, tag), + ctx: ctx, + router: uot.NewRouter(router, logger), + logger: logger, + } + var err error + inbound.router, err = mux.NewRouterWithOptions(router, logger, common.PtrValueOrDefault(options.Multiplex)) + if err != nil { + return nil, err + } + var udpTimeout time.Duration + if options.UDPTimeout != 0 { + udpTimeout = time.Duration(options.UDPTimeout) + } else { + udpTimeout = C.UDPTimeout + } + switch { + case options.Method == shadowsocks.MethodNone: + inbound.service = shadowsocks.NewNoneService(int64(udpTimeout.Seconds()), adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound)) + case common.Contains(shadowaead.List, options.Method): + inbound.service, err = shadowaead.NewService(options.Method, nil, options.Password, int64(udpTimeout.Seconds()), adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound)) + case common.Contains(shadowaead_2022.List, options.Method): + inbound.service, err = shadowaead_2022.NewServiceWithPassword(options.Method, options.Password, int64(udpTimeout.Seconds()), adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound), ntp.TimeFuncFromContext(ctx)) + default: + err = E.New("unsupported method: ", options.Method) + } + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: options.Network.Build(), + Listen: options.ListenOptions, + ConnectionHandler: inbound, + PacketHandler: inbound, + ThreadUnsafePacketWriter: true, + }) + return inbound, err +} + +func (h *Inbound) Start() error { + return h.listener.Start() +} + +func (h *Inbound) Close() error { + return h.listener.Close() +} + +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata)) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } +} + +func (h *Inbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { + err := h.service.NewPacket(h.ctx, &stubPacketConn{h.listener.PacketWriter()}, buffer, M.Metadata{Source: source}) + if err != nil { + h.logger.Error(E.Cause(err, "process packet from ", source)) + } +} + +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + return h.router.RouteConnection(ctx, conn, metadata) +} + +func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + ctx = log.ContextWithNewID(ctx) + h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) + h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + return h.router.RoutePacketConnection(ctx, conn, metadata) +} + +var _ N.PacketConn = (*stubPacketConn)(nil) + +type stubPacketConn struct { + N.PacketWriter +} + +func (c *stubPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) { + panic("stub!") +} + +func (c *stubPacketConn) Close() error { + return nil +} + +func (c *stubPacketConn) LocalAddr() net.Addr { + panic("stub!") +} + +func (c *stubPacketConn) SetDeadline(t time.Time) error { + panic("stub!") +} + +func (c *stubPacketConn) SetReadDeadline(t time.Time) error { + panic("stub!") +} + +func (c *stubPacketConn) SetWriteDeadline(t time.Time) error { + panic("stub!") +} + +func (h *Inbound) NewError(ctx context.Context, err error) { + NewError(h.logger, ctx, err) +} + +// Deprecated: remove +func NewError(logger logger.ContextLogger, ctx context.Context, err error) { + common.Close(err) + if E.IsClosedOrCanceled(err) { + logger.DebugContext(ctx, "connection closed: ", err) + return + } + logger.ErrorContext(ctx, err) +} diff --git a/inbound/shadowsocks_multi.go b/protocol/shadowsocks/inbound_multi.go similarity index 59% rename from inbound/shadowsocks_multi.go rename to protocol/shadowsocks/inbound_multi.go index 295341949..0e1efedfc 100644 --- a/inbound/shadowsocks_multi.go +++ b/protocol/shadowsocks/inbound_multi.go @@ -1,4 +1,4 @@ -package inbound +package shadowsocks import ( "context" @@ -7,6 +7,8 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/uot" C "github.com/sagernet/sing-box/constant" @@ -20,36 +22,31 @@ import ( "github.com/sagernet/sing/common/buf" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" + "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/ntp" ) -var ( - _ adapter.Inbound = (*ShadowsocksMulti)(nil) - _ adapter.TCPInjectableInbound = (*ShadowsocksMulti)(nil) -) +var _ adapter.TCPInjectableInbound = (*MultiInbound)(nil) -type ShadowsocksMulti struct { - myInboundAdapter - service shadowsocks.MultiService[int] - users []option.ShadowsocksUser +type MultiInbound struct { + inbound.Adapter + ctx context.Context + router adapter.ConnectionRouterEx + logger logger.ContextLogger + listener *listener.Listener + service shadowsocks.MultiService[int] + users []option.ShadowsocksUser } -func newShadowsocksMulti(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*ShadowsocksMulti, error) { - inbound := &ShadowsocksMulti{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeShadowsocks, - network: options.Network.Build(), - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, +func newMultiInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*MultiInbound, error) { + inbound := &MultiInbound{ + Adapter: inbound.NewAdapter(C.TypeShadowsocks, tag), + ctx: ctx, + router: uot.NewRouter(router, logger), + logger: logger, } - inbound.connHandler = inbound - inbound.packetHandler = inbound var err error inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex)) if err != nil { @@ -91,12 +88,28 @@ func newShadowsocksMulti(ctx context.Context, router adapter.Router, logger log. return nil, err } inbound.service = service - inbound.packetUpstream = service inbound.users = options.Users + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: options.Network.Build(), + Listen: options.ListenOptions, + ConnectionHandler: inbound, + PacketHandler: inbound, + ThreadUnsafePacketWriter: true, + }) return inbound, err } -func (h *ShadowsocksMulti) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { +func (h *MultiInbound) Start() error { + return h.listener.Start() +} + +func (h *MultiInbound) Close() error { + return h.listener.Close() +} + +func (h *MultiInbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata)) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { @@ -104,14 +117,14 @@ func (h *ShadowsocksMulti) NewConnectionEx(ctx context.Context, conn net.Conn, m } } -func (h *ShadowsocksMulti) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { - err := h.service.NewPacket(h.ctx, h.packetConn(), buffer, M.Metadata{Source: source}) +func (h *MultiInbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { + err := h.service.NewPacket(h.ctx, &stubPacketConn{h.listener.PacketWriter()}, buffer, M.Metadata{Source: source}) if err != nil { h.logger.Error(E.Cause(err, "process packet from ", source)) } } -func (h *ShadowsocksMulti) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *MultiInbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -123,10 +136,12 @@ func (h *ShadowsocksMulti) newConnection(ctx context.Context, conn net.Conn, met metadata.User = user } h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) - return h.router.RouteConnection(ctx, conn, h.createMetadata(conn, metadata)) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + return h.router.RouteConnection(ctx, conn, metadata) } -func (h *ShadowsocksMulti) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *MultiInbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -140,5 +155,13 @@ func (h *ShadowsocksMulti) newPacketConnection(ctx context.Context, conn N.Packe ctx = log.ContextWithNewID(ctx) h.logger.InfoContext(ctx, "[", user, "] inbound packet connection from ", metadata.Source) h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) - return h.router.RoutePacketConnection(ctx, conn, h.createPacketMetadata(conn, metadata)) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + return h.router.RoutePacketConnection(ctx, conn, metadata) +} + +func (h *MultiInbound) NewError(ctx context.Context, err error) { + NewError(h.logger, ctx, err) } diff --git a/inbound/shadowsocks_relay.go b/protocol/shadowsocks/inbound_relay.go similarity index 57% rename from inbound/shadowsocks_relay.go rename to protocol/shadowsocks/inbound_relay.go index 02246a3f3..5818ca29f 100644 --- a/inbound/shadowsocks_relay.go +++ b/protocol/shadowsocks/inbound_relay.go @@ -1,4 +1,4 @@ -package inbound +package shadowsocks import ( "context" @@ -7,6 +7,8 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/uot" C "github.com/sagernet/sing-box/constant" @@ -18,36 +20,31 @@ import ( "github.com/sagernet/sing/common/buf" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) -var ( - _ adapter.Inbound = (*ShadowsocksRelay)(nil) - _ adapter.TCPInjectableInbound = (*ShadowsocksRelay)(nil) -) +var _ adapter.TCPInjectableInbound = (*RelayInbound)(nil) -type ShadowsocksRelay struct { - myInboundAdapter +type RelayInbound struct { + inbound.Adapter + ctx context.Context + router adapter.ConnectionRouterEx + logger logger.ContextLogger + listener *listener.Listener service *shadowaead_2022.RelayService[int] destinations []option.ShadowsocksDestination } -func newShadowsocksRelay(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*ShadowsocksRelay, error) { - inbound := &ShadowsocksRelay{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeShadowsocks, - network: options.Network.Build(), - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, +func newRelayInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*RelayInbound, error) { + inbound := &RelayInbound{ + Adapter: inbound.NewAdapter(C.TypeShadowsocks, tag), + ctx: ctx, + router: uot.NewRouter(router, logger), + logger: logger, destinations: options.Destinations, } - inbound.connHandler = inbound - inbound.packetHandler = inbound var err error inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex)) if err != nil { @@ -77,11 +74,27 @@ func newShadowsocksRelay(ctx context.Context, router adapter.Router, logger log. return nil, err } inbound.service = service - inbound.packetUpstream = service + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: options.Network.Build(), + Listen: options.ListenOptions, + ConnectionHandler: inbound, + PacketHandler: inbound, + ThreadUnsafePacketWriter: true, + }) return inbound, err } -func (h *ShadowsocksRelay) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { +func (h *RelayInbound) Start() error { + return h.listener.Start() +} + +func (h *RelayInbound) Close() error { + return h.listener.Close() +} + +func (h *RelayInbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata)) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { @@ -89,14 +102,14 @@ func (h *ShadowsocksRelay) NewConnectionEx(ctx context.Context, conn net.Conn, m } } -func (h *ShadowsocksRelay) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { - err := h.service.NewPacket(h.ctx, h.packetConn(), buffer, M.Metadata{Source: source}) +func (h *RelayInbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { + err := h.service.NewPacket(h.ctx, &stubPacketConn{h.listener.PacketWriter()}, buffer, M.Metadata{Source: source}) if err != nil { h.logger.Error(E.Cause(err, "process packet from ", source)) } } -func (h *ShadowsocksRelay) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *RelayInbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { destinationIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -108,10 +121,12 @@ func (h *ShadowsocksRelay) newConnection(ctx context.Context, conn net.Conn, met metadata.User = destination } h.logger.InfoContext(ctx, "[", destination, "] inbound connection to ", metadata.Destination) - return h.router.RouteConnection(ctx, conn, h.createMetadata(conn, metadata)) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + return h.router.RouteConnection(ctx, conn, metadata) } -func (h *ShadowsocksRelay) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *RelayInbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { destinationIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -125,5 +140,13 @@ func (h *ShadowsocksRelay) newPacketConnection(ctx context.Context, conn N.Packe ctx = log.ContextWithNewID(ctx) h.logger.InfoContext(ctx, "[", destination, "] inbound packet connection from ", metadata.Source) h.logger.InfoContext(ctx, "[", destination, "] inbound packet connection to ", metadata.Destination) - return h.router.RoutePacketConnection(ctx, conn, h.createPacketMetadata(conn, metadata)) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + return h.router.RoutePacketConnection(ctx, conn, metadata) +} + +func (h *RelayInbound) NewError(ctx context.Context, err error) { + NewError(h.logger, ctx, err) } diff --git a/outbound/shadowsocks.go b/protocol/shadowsocks/outbound.go similarity index 80% rename from outbound/shadowsocks.go rename to protocol/shadowsocks/outbound.go index 153542742..73b383850 100644 --- a/outbound/shadowsocks.go +++ b/protocol/shadowsocks/outbound.go @@ -1,10 +1,11 @@ -package outbound +package shadowsocks import ( "context" "net" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/mux" C "github.com/sagernet/sing-box/constant" @@ -15,15 +16,19 @@ import ( "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" 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" ) -var _ adapter.Outbound = (*Shadowsocks)(nil) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.ShadowsocksOutboundOptions](registry, C.TypeShadowsocks, NewOutbound) +} -type Shadowsocks struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger dialer N.Dialer method shadowsocks.Method serverAddr M.Socksaddr @@ -32,7 +37,7 @@ type Shadowsocks struct { multiplexDialer *mux.Client } -func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksOutboundOptions) (*Shadowsocks, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksOutboundOptions) (adapter.Outbound, error) { method, err := shadowsocks.CreateMethod(ctx, options.Method, shadowsocks.MethodOptions{ Password: options.Password, }) @@ -43,15 +48,9 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte if err != nil { return nil, err } - outbound := &Shadowsocks{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeShadowsocks, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeShadowsocks, options.Network.Build(), tag, options.DialerOptions), + logger: logger, dialer: outboundDialer, method: method, serverAddr: options.ServerOptions.Build(), @@ -78,9 +77,9 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte return outbound, nil } -func (h *Shadowsocks) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination if h.multiplexDialer == nil { switch N.NetworkName(network) { @@ -106,9 +105,9 @@ func (h *Shadowsocks) DialContext(ctx context.Context, network string, destinati } } -func (h *Shadowsocks) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination if h.multiplexDialer == nil { if h.uotClient != nil { @@ -125,24 +124,24 @@ func (h *Shadowsocks) ListenPacket(ctx context.Context, destination M.Socksaddr) } } -func (h *Shadowsocks) InterfaceUpdated() { +func (h *Outbound) InterfaceUpdated() { if h.multiplexDialer != nil { h.multiplexDialer.Reset() } return } -func (h *Shadowsocks) Close() error { +func (h *Outbound) Close() error { return common.Close(common.PtrOrNil(h.multiplexDialer)) } var _ N.Dialer = (*shadowsocksDialer)(nil) -type shadowsocksDialer Shadowsocks +type shadowsocksDialer Outbound func (h *shadowsocksDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination switch N.NetworkName(network) { case N.NetworkTCP: @@ -170,7 +169,7 @@ func (h *shadowsocksDialer) DialContext(ctx context.Context, network string, des func (h *shadowsocksDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination outConn, err := h.dialer.DialContext(ctx, N.NetworkUDP, h.serverAddr) if err != nil { diff --git a/inbound/shadowtls.go b/protocol/shadowtls/inbound.go similarity index 62% rename from inbound/shadowtls.go rename to protocol/shadowtls/inbound.go index ca1422863..6887e838a 100644 --- a/inbound/shadowtls.go +++ b/protocol/shadowtls/inbound.go @@ -1,11 +1,13 @@ -package inbound +package shadowtls import ( "context" "net" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/listener" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" @@ -13,25 +15,27 @@ import ( "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/auth" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" N "github.com/sagernet/sing/common/network" ) -type ShadowTLS struct { - myInboundAdapter - service *shadowtls.Service +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.ShadowTLSInboundOptions](registry, C.TypeShadowTLS, NewInbound) } -func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSInboundOptions) (*ShadowTLS, error) { - inbound := &ShadowTLS{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeShadowTLS, - network: []string{N.NetworkTCP}, - ctx: ctx, - router: router, - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, +type Inbound struct { + inbound.Adapter + router adapter.Router + logger logger.ContextLogger + listener *listener.Listener + service *shadowtls.Service +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSInboundOptions) (adapter.Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeShadowTLS, tag), + router: router, + logger: logger, } if options.Version == 0 { @@ -68,22 +72,36 @@ func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.Context }, HandshakeForServerName: handshakeForServerName, StrictMode: options.StrictMode, - Handler: adapter.NewUpstreamContextHandler(inbound.newConnection, nil, inbound), + Handler: adapter.NewUpstreamContextHandler(inbound.newConnection, nil, nil), Logger: logger, }) if err != nil { return nil, err } inbound.service = service - inbound.connHandler = inbound + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: inbound, + }) return inbound, nil } -func (h *ShadowTLS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) Start() error { + return h.listener.Start() +} + +func (h *Inbound) Close() error { + return h.listener.Close() +} + +func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata)) } -func (h *ShadowTLS) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { if userName, _ := auth.UserFromContext[string](ctx); userName != "" { metadata.User = userName h.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", metadata.Destination) @@ -93,7 +111,7 @@ func (h *ShadowTLS) newConnection(ctx context.Context, conn net.Conn, metadata a return h.router.RouteConnection(ctx, conn, metadata) } -func (h *ShadowTLS) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { err := h.NewConnection(ctx, conn, metadata) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { diff --git a/outbound/shadowtls.go b/protocol/shadowtls/outbound.go similarity index 74% rename from outbound/shadowtls.go rename to protocol/shadowtls/outbound.go index ff1b9d6c4..7d46a8f68 100644 --- a/outbound/shadowtls.go +++ b/protocol/shadowtls/outbound.go @@ -1,4 +1,4 @@ -package outbound +package shadowtls import ( "context" @@ -6,6 +6,7 @@ import ( "os" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" @@ -17,23 +18,18 @@ import ( N "github.com/sagernet/sing/common/network" ) -var _ adapter.Outbound = (*ShadowTLS)(nil) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.ShadowTLSOutboundOptions](registry, C.TypeShadowTLS, NewOutbound) +} -type ShadowTLS struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter client *shadowtls.Client } -func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSOutboundOptions) (*ShadowTLS, error) { - outbound := &ShadowTLS{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeShadowTLS, - network: []string{N.NetworkTCP}, - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSOutboundOptions) (adapter.Outbound, error) { + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeShadowTLS, []string{N.NetworkTCP}, tag, options.DialerOptions), } if options.TLS == nil || !options.TLS.Enabled { return nil, C.ErrTLSRequired @@ -91,9 +87,9 @@ func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.Context return outbound, nil } -func (h *ShadowTLS) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination switch N.NetworkName(network) { case N.NetworkTCP: @@ -103,6 +99,6 @@ func (h *ShadowTLS) DialContext(ctx context.Context, network string, destination } } -func (h *ShadowTLS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { return nil, os.ErrInvalid } diff --git a/protocol/socks/inbound.go b/protocol/socks/inbound.go new file mode 100644 index 000000000..29649a88d --- /dev/null +++ b/protocol/socks/inbound.go @@ -0,0 +1,91 @@ +package socks + +import ( + std_bufio "bufio" + "context" + "net" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "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" + "github.com/sagernet/sing/common/auth" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/protocol/socks" +) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.SocksInboundOptions](registry, C.TypeSOCKS, NewInbound) +} + +var _ adapter.TCPInjectableInbound = (*Inbound)(nil) + +type Inbound struct { + inbound.Adapter + router adapter.ConnectionRouterEx + logger logger.ContextLogger + listener *listener.Listener + authenticator *auth.Authenticator +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SocksInboundOptions) (adapter.Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeSOCKS, tag), + router: uot.NewRouter(router, logger), + logger: logger, + authenticator: auth.NewAuthenticator(options.Users), + } + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: inbound, + }) + return inbound, nil +} + +func (h *Inbound) Start() error { + return h.listener.Start() +} + +func (h *Inbound) Close() error { + return h.listener.Close() +} + +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := socks.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, nil, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, metadata.Destination, onClose) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } +} + +func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + user, loaded := auth.UserFromContext[string](ctx) + if !loaded { + h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) + return + } + metadata.User = user + h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + user, loaded := auth.UserFromContext[string](ctx) + if !loaded { + h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) + return + } + metadata.User = user + h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} diff --git a/outbound/socks.go b/protocol/socks/outbound.go similarity index 65% rename from outbound/socks.go rename to protocol/socks/outbound.go index 575d6eb3d..0194800a7 100644 --- a/outbound/socks.go +++ b/protocol/socks/outbound.go @@ -1,10 +1,11 @@ -package outbound +package socks import ( "context" "net" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" @@ -12,22 +13,29 @@ import ( "github.com/sagernet/sing-dns" "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/protocol/socks" ) -var _ adapter.Outbound = (*Socks)(nil) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.SOCKSOutboundOptions](registry, C.TypeSOCKS, NewOutbound) +} + +var _ adapter.Outbound = (*Outbound)(nil) -type Socks struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter + router adapter.Router + logger logger.ContextLogger client *socks.Client resolve bool uotClient *uot.Client } -func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, options option.SocksOutboundOptions) (*Socks, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SOCKSOutboundOptions) (adapter.Outbound, error) { var version socks.Version var err error if options.Version != "" { @@ -42,15 +50,10 @@ func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, optio if err != nil { return nil, err } - outbound := &Socks{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeSOCKS, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeSOCKS, options.Network.Build(), tag, options.DialerOptions), + router: router, + logger: logger, client: socks.NewClient(outboundDialer, options.ServerOptions.Build(), version, options.Username, options.Password), resolve: version == socks.Version4, } @@ -64,9 +67,9 @@ func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, optio return outbound, nil } -func (h *Socks) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination switch N.NetworkName(network) { case N.NetworkTCP: @@ -90,9 +93,9 @@ func (h *Socks) DialContext(ctx context.Context, network string, destination M.S return h.client.DialContext(ctx, network, destination) } -func (h *Socks) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination if h.uotClient != nil { h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination) @@ -115,20 +118,20 @@ func (h *Socks) ListenPacket(ctx context.Context, destination M.Socksaddr) (net. // TODO // Deprecated -func (h *Socks) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Outbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { if h.resolve { - return NewDirectConnection(ctx, h.router, h, conn, metadata, dns.DomainStrategyUseIPv4) + return outbound.NewDirectConnection(ctx, h.router, h, conn, metadata, dns.DomainStrategyUseIPv4) } else { - return NewConnection(ctx, h, conn, metadata) + return outbound.NewConnection(ctx, h, conn, metadata) } } // TODO // Deprecated -func (h *Socks) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *Outbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { if h.resolve { - return NewDirectPacketConnection(ctx, h.router, h, conn, metadata, dns.DomainStrategyUseIPv4) + return outbound.NewDirectPacketConnection(ctx, h.router, h, conn, metadata, dns.DomainStrategyUseIPv4) } else { - return NewPacketConnection(ctx, h, conn, metadata) + return outbound.NewPacketConnection(ctx, h, conn, metadata) } } diff --git a/outbound/ssh.go b/protocol/ssh/outbound.go similarity index 80% rename from outbound/ssh.go rename to protocol/ssh/outbound.go index 28abe9a5e..62a2a8d9b 100644 --- a/outbound/ssh.go +++ b/protocol/ssh/outbound.go @@ -1,4 +1,4 @@ -package outbound +package ssh import ( "bytes" @@ -12,26 +12,30 @@ import ( "sync" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" 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" "golang.org/x/crypto/ssh" ) -var ( - _ adapter.Outbound = (*SSH)(nil) - _ adapter.InterfaceUpdateListener = (*SSH)(nil) -) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.SSHOutboundOptions](registry, C.TypeSSH, NewOutbound) +} -type SSH struct { - myOutboundAdapter +var _ adapter.InterfaceUpdateListener = (*Outbound)(nil) + +type Outbound struct { + outbound.Adapter ctx context.Context + logger logger.ContextLogger dialer N.Dialer serverAddr M.Socksaddr user string @@ -44,21 +48,15 @@ type SSH struct { client *ssh.Client } -func NewSSH(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SSHOutboundOptions) (*SSH, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SSHOutboundOptions) (adapter.Outbound, error) { outboundDialer, err := dialer.New(router, options.DialerOptions) if err != nil { return nil, err } - outbound := &SSH{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeSSH, - network: []string{N.NetworkTCP}, - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeSSH, []string{N.NetworkTCP}, tag, options.DialerOptions), ctx: ctx, + logger: logger, dialer: outboundDialer, serverAddr: options.ServerOptions.Build(), user: options.User, @@ -122,7 +120,7 @@ func randomVersion() string { return version } -func (s *SSH) connect() (*ssh.Client, error) { +func (s *Outbound) connect() (*ssh.Client, error) { if s.client != nil { return s.client, nil } @@ -179,16 +177,16 @@ func (s *SSH) connect() (*ssh.Client, error) { return client, nil } -func (s *SSH) InterfaceUpdated() { +func (s *Outbound) InterfaceUpdated() { common.Close(s.clientConn) return } -func (s *SSH) Close() error { +func (s *Outbound) Close() error { return common.Close(s.clientConn) } -func (s *SSH) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (s *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { client, err := s.connect() if err != nil { return nil, err @@ -196,6 +194,6 @@ func (s *SSH) DialContext(ctx context.Context, network string, destination M.Soc return client.Dial(network, destination.String()) } -func (s *SSH) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (s *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { return nil, os.ErrInvalid } diff --git a/outbound/tor.go b/protocol/tor/outbound.go similarity index 83% rename from outbound/tor.go rename to protocol/tor/outbound.go index ccc0c0cfe..89a295b82 100644 --- a/outbound/tor.go +++ b/protocol/tor/outbound.go @@ -1,4 +1,4 @@ -package outbound +package tor import ( "context" @@ -8,6 +8,7 @@ import ( "strings" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" @@ -15,6 +16,7 @@ import ( "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" + "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/rw" @@ -24,11 +26,14 @@ import ( "github.com/cretz/bine/tor" ) -var _ adapter.Outbound = (*Tor)(nil) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.TorOutboundOptions](registry, C.TypeTor, NewOutbound) +} -type Tor struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter ctx context.Context + logger logger.ContextLogger proxy *ProxyListener startConf *tor.StartConf options map[string]string @@ -37,8 +42,8 @@ type Tor struct { socksClient *socks.Client } -func NewTor(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TorOutboundOptions) (*Tor, error) { - startConf := newConfig() +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TorOutboundOptions) (adapter.Outbound, error) { + var startConf tor.StartConf startConf.DataDir = os.ExpandEnv(options.DataDirectory) startConf.TempDataDirBase = os.TempDir() startConf.ExtraArgs = options.ExtraArgs @@ -74,23 +79,17 @@ func NewTor(ctx context.Context, router adapter.Router, logger log.ContextLogger if err != nil { return nil, err } - return &Tor{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeTor, - network: []string{N.NetworkTCP}, - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + return &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeTor, []string{N.NetworkTCP}, tag, options.DialerOptions), ctx: ctx, + logger: logger, proxy: NewProxyListener(ctx, logger, outboundDialer), startConf: &startConf, options: options.Options, }, nil } -func (t *Tor) Start() error { +func (t *Outbound) Start() error { err := t.start() if err != nil { t.Close() @@ -106,7 +105,7 @@ var torLogEvents = []control.EventCode{ control.EventCodeLogWarn, } -func (t *Tor) start() error { +func (t *Outbound) start() error { torInstance, err := tor.Start(t.ctx, t.startConf) if err != nil { return E.New(strings.ToLower(err.Error())) @@ -168,7 +167,7 @@ func (t *Tor) start() error { return nil } -func (t *Tor) recvLoop() { +func (t *Outbound) recvLoop() { for rawEvent := range t.events { switch event := rawEvent.(type) { case *control.LogEvent: @@ -191,7 +190,7 @@ func (t *Tor) recvLoop() { } } -func (t *Tor) Close() error { +func (t *Outbound) Close() error { err := common.Close( common.PtrOrNil(t.proxy), common.PtrOrNil(t.instance), @@ -203,11 +202,11 @@ func (t *Tor) Close() error { return err } -func (t *Tor) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (t *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { t.logger.InfoContext(ctx, "outbound connection to ", destination) return t.socksClient.DialContext(ctx, network, destination) } -func (t *Tor) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (t *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { return nil, os.ErrInvalid } diff --git a/outbound/proxy.go b/protocol/tor/proxy.go similarity index 94% rename from outbound/proxy.go rename to protocol/tor/proxy.go index 38c184539..ef60bd1f7 100644 --- a/outbound/proxy.go +++ b/protocol/tor/proxy.go @@ -1,4 +1,4 @@ -package outbound +package tor import ( "context" @@ -7,6 +7,7 @@ import ( "net" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/auth" @@ -106,7 +107,7 @@ func (l *ProxyListener) NewConnection(ctx context.Context, conn net.Conn, upstre metadata.Network = N.NetworkTCP metadata.Destination = upstreamMetadata.Destination l.logger.InfoContext(ctx, "proxy connection to ", metadata.Destination) - return NewConnection(ctx, l.dialer, conn, metadata) + return outbound.NewConnection(ctx, l.dialer, conn, metadata) } func (l *ProxyListener) NewPacketConnection(ctx context.Context, conn N.PacketConn, upstreamMetadata M.Metadata) error { @@ -114,5 +115,5 @@ func (l *ProxyListener) NewPacketConnection(ctx context.Context, conn N.PacketCo metadata.Network = N.NetworkUDP metadata.Destination = upstreamMetadata.Destination l.logger.InfoContext(ctx, "proxy packet connection to ", metadata.Destination) - return NewPacketConnection(ctx, l.dialer, conn, metadata) + return outbound.NewPacketConnection(ctx, l.dialer, conn, metadata) } diff --git a/inbound/trojan.go b/protocol/trojan/inbound.go similarity index 68% rename from inbound/trojan.go rename to protocol/trojan/inbound.go index ce003dda7..010ae8baf 100644 --- a/inbound/trojan.go +++ b/protocol/trojan/inbound.go @@ -1,4 +1,4 @@ -package inbound +package trojan import ( "context" @@ -6,6 +6,8 @@ import ( "os" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" @@ -21,13 +23,17 @@ import ( N "github.com/sagernet/sing/common/network" ) -var ( - _ adapter.Inbound = (*Trojan)(nil) - _ adapter.TCPInjectableInbound = (*Trojan)(nil) -) +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.TrojanInboundOptions](registry, C.TypeTrojan, NewInbound) +} + +var _ adapter.TCPInjectableInbound = (*Inbound)(nil) -type Trojan struct { - myInboundAdapter +type Inbound struct { + inbound.Adapter + router adapter.ConnectionRouterEx + logger log.ContextLogger + listener *listener.Listener service *trojan.Service[int] users []option.TrojanUser tlsConfig tls.ServerConfig @@ -36,18 +42,12 @@ type Trojan struct { transport adapter.V2RayServerTransport } -func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanInboundOptions) (*Trojan, error) { - inbound := &Trojan{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeTrojan, - network: []string{N.NetworkTCP}, - ctx: ctx, - router: router, - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - users: options.Users, +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanInboundOptions) (adapter.Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeTrojan, tag), + router: router, + logger: logger, + users: options.Users, } if options.TLS != nil { tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) @@ -80,7 +80,7 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog } fallbackHandler = adapter.NewUpstreamContextHandler(inbound.fallbackConnection, nil, nil) } - service := trojan.NewService[int](adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound), fallbackHandler) + service := trojan.NewService[int](adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, nil), fallbackHandler, logger) err := service.UpdateUsers(common.MapIndexed(options.Users, func(index int, it option.TrojanUser) int { return index }), common.Map(options.Users, func(it option.TrojanUser) string { @@ -90,7 +90,7 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog return nil, err } if options.Transport != nil { - inbound.transport, err = v2ray.NewServerTransport(ctx, logger, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*trojanTransportHandler)(inbound)) + inbound.transport, err = v2ray.NewServerTransport(ctx, logger, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*inboundTransportHandler)(inbound)) if err != nil { return nil, E.Cause(err, "create server transport: ", options.Transport.Type) } @@ -100,11 +100,17 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog return nil, err } inbound.service = service - inbound.connHandler = inbound + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: inbound, + }) return inbound, nil } -func (h *Trojan) Start() error { +func (h *Inbound) Start() error { if h.tlsConfig != nil { err := h.tlsConfig.Start() if err != nil { @@ -112,10 +118,10 @@ func (h *Trojan) Start() error { } } if h.transport == nil { - return h.myInboundAdapter.Start() + return h.listener.Start() } if common.Contains(h.transport.Network(), N.NetworkTCP) { - tcpListener, err := h.myInboundAdapter.ListenTCP() + tcpListener, err := h.listener.ListenTCP() if err != nil { return err } @@ -127,7 +133,7 @@ func (h *Trojan) Start() error { }() } if common.Contains(h.transport.Network(), N.NetworkUDP) { - udpConn, err := h.myInboundAdapter.ListenUDP() + udpConn, err := h.listener.ListenUDP() if err != nil { return err } @@ -141,15 +147,15 @@ func (h *Trojan) Start() error { return nil } -func (h *Trojan) Close() error { +func (h *Inbound) Close() error { return common.Close( - &h.myInboundAdapter, + &h.listener, h.tlsConfig, h.transport, ) } -func (h *Trojan) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { var err error if h.tlsConfig != nil && h.transport == nil { conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig) @@ -160,7 +166,7 @@ func (h *Trojan) NewConnection(ctx context.Context, conn net.Conn, metadata adap return h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, adapter.UpstreamMetadata(metadata)) } -func (h *Trojan) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { err := h.NewConnection(ctx, conn, metadata) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { @@ -168,7 +174,7 @@ func (h *Trojan) NewConnectionEx(ctx context.Context, conn net.Conn, metadata ad } } -func (h *Trojan) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -183,7 +189,7 @@ func (h *Trojan) newConnection(ctx context.Context, conn net.Conn, metadata adap return h.router.RouteConnection(ctx, conn, metadata) } -func (h *Trojan) fallbackConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) fallbackConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { var fallbackAddr M.Socksaddr if len(h.fallbackAddrTLSNextProto) > 0 { if tlsConn, loaded := common.Cast[tls.Conn](conn); loaded { @@ -206,7 +212,7 @@ func (h *Trojan) fallbackConnection(ctx context.Context, conn net.Conn, metadata return h.router.RouteConnection(ctx, conn, metadata) } -func (h *Trojan) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -221,10 +227,18 @@ func (h *Trojan) newPacketConnection(ctx context.Context, conn N.PacketConn, met return h.router.RoutePacketConnection(ctx, conn, metadata) } -var _ adapter.V2RayServerTransportHandler = (*trojanTransportHandler)(nil) +var _ adapter.V2RayServerTransportHandler = (*inboundTransportHandler)(nil) -type trojanTransportHandler Trojan +type inboundTransportHandler Inbound -func (t *trojanTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { - (*Trojan)(t).routeTCP(ctx, conn, source, destination, onClose) +func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + var metadata adapter.InboundContext + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + metadata.Source = source + metadata.Destination = destination + h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) + (*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose) } diff --git a/outbound/trojan.go b/protocol/trojan/outbound.go similarity index 79% rename from outbound/trojan.go rename to protocol/trojan/outbound.go index ee0b2a4b9..f64c48c3c 100644 --- a/outbound/trojan.go +++ b/protocol/trojan/outbound.go @@ -1,10 +1,11 @@ -package outbound +package trojan import ( "context" "net" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/tls" @@ -16,14 +17,18 @@ import ( "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" 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" ) -var _ adapter.Outbound = (*Trojan)(nil) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.TrojanOutboundOptions](registry, C.TypeTrojan, NewOutbound) +} -type Trojan struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger dialer N.Dialer serverAddr M.Socksaddr key [56]byte @@ -32,20 +37,14 @@ type Trojan struct { transport adapter.V2RayClientTransport } -func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanOutboundOptions) (*Trojan, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanOutboundOptions) (adapter.Outbound, error) { outboundDialer, err := dialer.New(router, options.DialerOptions) if err != nil { return nil, err } - outbound := &Trojan{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeTrojan, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeTrojan, options.Network.Build(), tag, options.DialerOptions), + logger: logger, dialer: outboundDialer, serverAddr: options.ServerOptions.Build(), key: trojan.Key(options.Password), @@ -69,7 +68,7 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog return outbound, nil } -func (h *Trojan) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { if h.multiplexDialer == nil { switch N.NetworkName(network) { case N.NetworkTCP: @@ -89,7 +88,7 @@ func (h *Trojan) DialContext(ctx context.Context, network string, destination M. } } -func (h *Trojan) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { if h.multiplexDialer == nil { h.logger.InfoContext(ctx, "outbound packet connection to ", destination) return (*trojanDialer)(h).ListenPacket(ctx, destination) @@ -99,7 +98,7 @@ func (h *Trojan) ListenPacket(ctx context.Context, destination M.Socksaddr) (net } } -func (h *Trojan) InterfaceUpdated() { +func (h *Outbound) InterfaceUpdated() { if h.transport != nil { h.transport.Close() } @@ -109,15 +108,15 @@ func (h *Trojan) InterfaceUpdated() { return } -func (h *Trojan) Close() error { +func (h *Outbound) Close() error { return common.Close(common.PtrOrNil(h.multiplexDialer), h.transport) } -type trojanDialer Trojan +type trojanDialer Outbound func (h *trojanDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination var conn net.Conn var err error diff --git a/inbound/tuic.go b/protocol/tuic/inbound.go similarity index 68% rename from inbound/tuic.go rename to protocol/tuic/inbound.go index b067c43cd..33de10d5c 100644 --- a/inbound/tuic.go +++ b/protocol/tuic/inbound.go @@ -1,6 +1,4 @@ -//go:build with_quic - -package inbound +package tuic import ( "context" @@ -8,6 +6,8 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/uot" C "github.com/sagernet/sing-box/constant" @@ -22,16 +22,21 @@ import ( "github.com/gofrs/uuid/v5" ) -var _ adapter.Inbound = (*TUIC)(nil) +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.TUICInboundOptions](registry, C.TypeTUIC, NewInbound) +} -type TUIC struct { - myInboundAdapter +type Inbound struct { + inbound.Adapter + router adapter.ConnectionRouterEx + logger log.ContextLogger + listener *listener.Listener tlsConfig tls.ServerConfig server *tuic.Service[int] userNameList []string } -func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICInboundOptions) (*TUIC, error) { +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICInboundOptions) (adapter.Inbound, error) { options.UDPFragmentDefault = true if options.TLS == nil || !options.TLS.Enabled { return nil, C.ErrTLSRequired @@ -40,16 +45,15 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge if err != nil { return nil, err } - inbound := &TUIC{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeTUIC, - network: []string{N.NetworkUDP}, - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeTUIC, tag), + router: uot.NewRouter(router, logger), + logger: logger, + listener: listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Listen: options.ListenOptions, + }), tlsConfig: tlsConfig, } var udpTimeout time.Duration @@ -95,9 +99,12 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge return inbound, nil } -func (h *TUIC) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { ctx = log.ContextWithNewID(ctx) - metadata = h.createMetadata(conn, metadata) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) userID, _ := auth.UserFromContext[int](ctx) if userName := h.userNameList[userID]; userName != "" { @@ -109,9 +116,13 @@ func (h *TUIC) newConnection(ctx context.Context, conn net.Conn, metadata adapte return h.router.RouteConnection(ctx, conn, metadata) } -func (h *TUIC) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { ctx = log.ContextWithNewID(ctx) - metadata = h.createPacketMetadata(conn, metadata) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + metadata.OriginDestination = h.listener.UDPAddr() h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) userID, _ := auth.UserFromContext[int](ctx) if userName := h.userNameList[userID]; userName != "" { @@ -123,23 +134,23 @@ func (h *TUIC) newPacketConnection(ctx context.Context, conn N.PacketConn, metad return h.router.RoutePacketConnection(ctx, conn, metadata) } -func (h *TUIC) Start() error { +func (h *Inbound) Start() error { if h.tlsConfig != nil { err := h.tlsConfig.Start() if err != nil { return err } } - packetConn, err := h.myInboundAdapter.ListenUDP() + packetConn, err := h.listener.ListenUDP() if err != nil { return err } return h.server.Start(packetConn) } -func (h *TUIC) Close() error { +func (h *Inbound) Close() error { return common.Close( - &h.myInboundAdapter, + &h.listener, h.tlsConfig, common.PtrOrNil(h.server), ) diff --git a/outbound/tuic.go b/protocol/tuic/outbound.go similarity index 76% rename from outbound/tuic.go rename to protocol/tuic/outbound.go index aaf998b16..691d1658d 100644 --- a/outbound/tuic.go +++ b/protocol/tuic/outbound.go @@ -1,6 +1,4 @@ -//go:build with_quic - -package outbound +package tuic import ( "context" @@ -9,6 +7,7 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" @@ -18,6 +17,7 @@ import ( "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" 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" @@ -25,18 +25,20 @@ import ( "github.com/gofrs/uuid/v5" ) -var ( - _ adapter.Outbound = (*TUIC)(nil) - _ adapter.InterfaceUpdateListener = (*TUIC)(nil) -) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.TUICOutboundOptions](registry, C.TypeTUIC, NewOutbound) +} + +var _ adapter.InterfaceUpdateListener = (*Outbound)(nil) -type TUIC struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger client *tuic.Client udpStream bool } -func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICOutboundOptions) (*TUIC, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICOutboundOptions) (adapter.Outbound, error) { options.UDPFragmentDefault = true if options.TLS == nil || !options.TLS.Enabled { return nil, C.ErrTLSRequired @@ -77,21 +79,15 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge if err != nil { return nil, err } - return &TUIC{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeTUIC, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + return &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeTUIC, options.Network.Build(), tag, options.DialerOptions), + logger: logger, client: client, udpStream: options.UDPOverStream, }, nil } -func (h *TUIC) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { switch N.NetworkName(network) { case N.NetworkTCP: h.logger.InfoContext(ctx, "outbound connection to ", destination) @@ -119,7 +115,7 @@ func (h *TUIC) DialContext(ctx context.Context, network string, destination M.So } } -func (h *TUIC) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { if h.udpStream { h.logger.InfoContext(ctx, "outbound stream packet connection to ", destination) streamConn, err := h.client.DialConn(ctx, uot.RequestDestination(uot.Version)) @@ -136,10 +132,10 @@ func (h *TUIC) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.P } } -func (h *TUIC) InterfaceUpdated() { +func (h *Outbound) InterfaceUpdated() { _ = h.client.CloseWithError(E.New("network changed")) } -func (h *TUIC) Close() error { +func (h *Outbound) Close() error { return h.client.CloseWithError(os.ErrClosed) } diff --git a/inbound/tun.go b/protocol/tun/inbound.go similarity index 92% rename from inbound/tun.go rename to protocol/tun/inbound.go index 11b16428b..ff679c8ec 100644 --- a/inbound/tun.go +++ b/protocol/tun/inbound.go @@ -1,4 +1,4 @@ -package inbound +package tun import ( "context" @@ -11,6 +11,7 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" "github.com/sagernet/sing-box/common/taskmonitor" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/experimental/deprecated" @@ -24,13 +25,16 @@ import ( N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/ranges" "github.com/sagernet/sing/common/x/list" + "github.com/sagernet/sing/service" "go4.org/netipx" ) -var _ adapter.Inbound = (*TUN)(nil) +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.TunInboundOptions](registry, C.TypeTun, NewInbound) +} -type TUN struct { +type Inbound struct { tag string ctx context.Context router adapter.Router @@ -55,7 +59,7 @@ type TUN struct { routeExcludeAddressSet []*netipx.IPSet } -func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions, platformInterface platform.Interface) (*TUN, error) { +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions) (adapter.Inbound, error) { address := options.Address var deprecatedAddressUsed bool //nolint:staticcheck @@ -164,7 +168,7 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger outputMark = tun.DefaultAutoRedirectOutputMark } - inbound := &TUN{ + inbound := &Inbound{ tag: tag, ctx: ctx, router: router, @@ -198,7 +202,7 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger endpointIndependentNat: options.EndpointIndependentNat, udpTimeout: udpTimeout, stack: options.Stack, - platformInterface: platformInterface, + platformInterface: service.FromContext[platform.Interface](ctx), platformOptions: common.PtrValueOrDefault(options.Platform), } if options.AutoRedirect { @@ -285,15 +289,15 @@ func parseRange(uidRanges []ranges.Range[uint32], rangeList []string) ([]ranges. return uidRanges, nil } -func (t *TUN) Type() string { +func (t *Inbound) Type() string { return C.TypeTun } -func (t *TUN) Tag() string { +func (t *Inbound) Tag() string { return t.tag } -func (t *TUN) Start() error { +func (t *Inbound) Start() error { if C.IsAndroid && t.platformInterface == nil { t.tunOptions.BuildAndroidRules(t.router.PackageManager()) } @@ -350,7 +354,7 @@ func (t *TUN) Start() error { return nil } -func (t *TUN) PostStart() error { +func (t *Inbound) PostStart() error { monitor := taskmonitor.New(t.logger, C.StartTimeout) if t.autoRedirect != nil { t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet) @@ -389,7 +393,7 @@ func (t *TUN) PostStart() error { return nil } -func (t *TUN) updateRouteAddressSet(it adapter.RuleSet) { +func (t *Inbound) updateRouteAddressSet(it adapter.RuleSet) { t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet) t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet) t.autoRedirect.UpdateRouteAddressSet() @@ -397,7 +401,7 @@ func (t *TUN) updateRouteAddressSet(it adapter.RuleSet) { t.routeExcludeAddressSet = nil } -func (t *TUN) Close() error { +func (t *Inbound) Close() error { return common.Close( t.tunStack, t.tunIf, @@ -405,7 +409,7 @@ func (t *TUN) Close() error { ) } -func (t *TUN) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr) error { +func (t *Inbound) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr) error { return t.router.PreMatch(adapter.InboundContext{ Inbound: t.tag, InboundType: C.TypeTun, @@ -416,7 +420,7 @@ func (t *TUN) PrepareConnection(network string, source M.Socksaddr, destination }) } -func (t *TUN) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { +func (t *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { ctx = log.ContextWithNewID(ctx) var metadata adapter.InboundContext metadata.Inbound = t.tag @@ -429,7 +433,7 @@ func (t *TUN) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socks t.router.RouteConnectionEx(ctx, conn, metadata, onClose) } -func (t *TUN) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { +func (t *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { ctx = log.ContextWithNewID(ctx) var metadata adapter.InboundContext metadata.Inbound = t.tag @@ -442,7 +446,7 @@ func (t *TUN) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, sour t.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } -type autoRedirectHandler TUN +type autoRedirectHandler Inbound func (t *autoRedirectHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { ctx = log.ContextWithNewID(ctx) diff --git a/inbound/vless.go b/protocol/vless/inbound.go similarity index 60% rename from inbound/vless.go rename to protocol/vless/inbound.go index ec26bd885..0641549b0 100644 --- a/inbound/vless.go +++ b/protocol/vless/inbound.go @@ -1,4 +1,4 @@ -package inbound +package vless import ( "context" @@ -6,6 +6,8 @@ import ( "os" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/uot" @@ -20,37 +22,36 @@ import ( "github.com/sagernet/sing/common/auth" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) -var ( - _ adapter.Inbound = (*VLESS)(nil) - _ adapter.TCPInjectableInbound = (*VLESS)(nil) -) +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.VLESSInboundOptions](registry, C.TypeVLESS, NewInbound) +} -type VLESS struct { - myInboundAdapter +var _ adapter.TCPInjectableInbound = (*Inbound)(nil) + +type Inbound struct { + inbound.Adapter ctx context.Context + router adapter.ConnectionRouterEx + logger logger.ContextLogger + listener *listener.Listener users []option.VLESSUser service *vless.Service[int] tlsConfig tls.ServerConfig transport adapter.V2RayServerTransport } -func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSInboundOptions) (*VLESS, error) { - inbound := &VLESS{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeVLESS, - network: []string{N.NetworkTCP}, - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - ctx: ctx, - users: options.Users, +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSInboundOptions) (adapter.Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeVLESS, tag), + ctx: ctx, + router: uot.NewRouter(router, logger), + logger: logger, + users: options.Users, } var err error inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex)) @@ -73,16 +74,22 @@ func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogg } } if options.Transport != nil { - inbound.transport, err = v2ray.NewServerTransport(ctx, logger, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*vlessTransportHandler)(inbound)) + inbound.transport, err = v2ray.NewServerTransport(ctx, logger, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*inboundTransportHandler)(inbound)) if err != nil { return nil, E.Cause(err, "create server transport: ", options.Transport.Type) } } - inbound.connHandler = inbound + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: inbound, + }) return inbound, nil } -func (h *VLESS) Start() error { +func (h *Inbound) Start() error { if h.tlsConfig != nil { err := h.tlsConfig.Start() if err != nil { @@ -90,10 +97,10 @@ func (h *VLESS) Start() error { } } if h.transport == nil { - return h.myInboundAdapter.Start() + return h.listener.Start() } if common.Contains(h.transport.Network(), N.NetworkTCP) { - tcpListener, err := h.myInboundAdapter.ListenTCP() + tcpListener, err := h.listener.ListenTCP() if err != nil { return err } @@ -105,7 +112,7 @@ func (h *VLESS) Start() error { }() } if common.Contains(h.transport.Network(), N.NetworkUDP) { - udpConn, err := h.myInboundAdapter.ListenUDP() + udpConn, err := h.listener.ListenUDP() if err != nil { return err } @@ -119,16 +126,16 @@ func (h *VLESS) Start() error { return nil } -func (h *VLESS) Close() error { +func (h *Inbound) Close() error { return common.Close( h.service, - &h.myInboundAdapter, + &h.listener, h.tlsConfig, h.transport, ) } -func (h *VLESS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { var err error if h.tlsConfig != nil && h.transport == nil { conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig) @@ -139,7 +146,7 @@ func (h *VLESS) NewConnection(ctx context.Context, conn net.Conn, metadata adapt return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata)) } -func (h *VLESS) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { err := h.NewConnection(ctx, conn, metadata) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { @@ -147,7 +154,7 @@ func (h *VLESS) NewConnectionEx(ctx context.Context, conn net.Conn, metadata ada } } -func (h *VLESS) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -162,7 +169,7 @@ func (h *VLESS) newConnection(ctx context.Context, conn net.Conn, metadata adapt return h.router.RouteConnection(ctx, conn, metadata) } -func (h *VLESS) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -183,10 +190,32 @@ func (h *VLESS) newPacketConnection(ctx context.Context, conn N.PacketConn, meta return h.router.RoutePacketConnection(ctx, conn, metadata) } -var _ adapter.V2RayServerTransportHandler = (*vlessTransportHandler)(nil) +var _ adapter.V2RayServerTransportHandler = (*inboundTransportHandler)(nil) + +type inboundTransportHandler Inbound -type vlessTransportHandler VLESS +func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + var metadata adapter.InboundContext + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + metadata.Source = source + metadata.Destination = destination + h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) + (*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose) +} + +func (h *Inbound) NewError(ctx context.Context, err error) { + NewError(h.logger, ctx, err) +} -func (t *vlessTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { - t.routeTCP(ctx, conn, source, destination, onClose) +// Deprecated: remove +func NewError(logger logger.ContextLogger, ctx context.Context, err error) { + common.Close(err) + if E.IsClosedOrCanceled(err) { + logger.DebugContext(ctx, "connection closed: ", err) + return + } + logger.ErrorContext(ctx, err) } diff --git a/outbound/vless.go b/protocol/vless/outbound.go similarity index 84% rename from outbound/vless.go rename to protocol/vless/outbound.go index 536a1e8fb..1074549e8 100644 --- a/outbound/vless.go +++ b/protocol/vless/outbound.go @@ -1,10 +1,11 @@ -package outbound +package vless import ( "context" "net" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/tls" @@ -17,14 +18,18 @@ import ( "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" 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" ) -var _ adapter.Outbound = (*VLESS)(nil) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.VLESSOutboundOptions](registry, C.TypeVLESS, NewOutbound) +} -type VLESS struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger dialer N.Dialer client *vless.Client serverAddr M.Socksaddr @@ -35,20 +40,14 @@ type VLESS struct { xudp bool } -func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSOutboundOptions) (*VLESS, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSOutboundOptions) (adapter.Outbound, error) { outboundDialer, err := dialer.New(router, options.DialerOptions) if err != nil { return nil, err } - outbound := &VLESS{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeVLESS, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeVLESS, options.Network.Build(), tag, options.DialerOptions), + logger: logger, dialer: outboundDialer, serverAddr: options.ServerOptions.Build(), } @@ -88,7 +87,7 @@ func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogg return outbound, nil } -func (h *VLESS) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { if h.multiplexDialer == nil { switch N.NetworkName(network) { case N.NetworkTCP: @@ -108,7 +107,7 @@ func (h *VLESS) DialContext(ctx context.Context, network string, destination M.S } } -func (h *VLESS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { if h.multiplexDialer == nil { h.logger.InfoContext(ctx, "outbound packet connection to ", destination) return (*vlessDialer)(h).ListenPacket(ctx, destination) @@ -118,7 +117,7 @@ func (h *VLESS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net. } } -func (h *VLESS) InterfaceUpdated() { +func (h *Outbound) InterfaceUpdated() { if h.transport != nil { h.transport.Close() } @@ -128,15 +127,15 @@ func (h *VLESS) InterfaceUpdated() { return } -func (h *VLESS) Close() error { +func (h *Outbound) Close() error { return common.Close(common.PtrOrNil(h.multiplexDialer), h.transport) } -type vlessDialer VLESS +type vlessDialer Outbound func (h *vlessDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination var conn net.Conn var err error @@ -179,7 +178,7 @@ func (h *vlessDialer) DialContext(ctx context.Context, network string, destinati func (h *vlessDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { h.logger.InfoContext(ctx, "outbound packet connection to ", destination) ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination var conn net.Conn var err error diff --git a/inbound/vmess.go b/protocol/vmess/inbound.go similarity index 62% rename from inbound/vmess.go rename to protocol/vmess/inbound.go index 9099bd629..1c80f376e 100644 --- a/inbound/vmess.go +++ b/protocol/vmess/inbound.go @@ -1,4 +1,4 @@ -package inbound +package vmess import ( "context" @@ -6,6 +6,8 @@ import ( "os" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/uot" @@ -19,38 +21,37 @@ import ( "github.com/sagernet/sing/common/auth" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" + "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/ntp" ) -var ( - _ adapter.Inbound = (*VMess)(nil) - _ adapter.TCPInjectableInbound = (*VMess)(nil) -) +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.VMessInboundOptions](registry, C.TypeVMess, NewInbound) +} -type VMess struct { - myInboundAdapter +var _ adapter.TCPInjectableInbound = (*Inbound)(nil) + +type Inbound struct { + inbound.Adapter ctx context.Context + router adapter.ConnectionRouterEx + logger logger.ContextLogger + listener *listener.Listener service *vmess.Service[int] users []option.VMessUser tlsConfig tls.ServerConfig transport adapter.V2RayServerTransport } -func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessInboundOptions) (*VMess, error) { - inbound := &VMess{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeVMess, - network: []string{N.NetworkTCP}, - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - ctx: ctx, - users: options.Users, +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessInboundOptions) (adapter.Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeVMess, tag), + ctx: ctx, + router: uot.NewRouter(router, logger), + logger: logger, + users: options.Users, } var err error inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex)) @@ -83,16 +84,22 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg } } if options.Transport != nil { - inbound.transport, err = v2ray.NewServerTransport(ctx, logger, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*vmessTransportHandler)(inbound)) + inbound.transport, err = v2ray.NewServerTransport(ctx, logger, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*inboundTransportHandler)(inbound)) if err != nil { return nil, E.Cause(err, "create server transport: ", options.Transport.Type) } } - inbound.connHandler = inbound + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: inbound, + }) return inbound, nil } -func (h *VMess) Start() error { +func (h *Inbound) Start() error { err := h.service.Start() if err != nil { return err @@ -104,10 +111,10 @@ func (h *VMess) Start() error { } } if h.transport == nil { - return h.myInboundAdapter.Start() + return h.listener.Start() } if common.Contains(h.transport.Network(), N.NetworkTCP) { - tcpListener, err := h.myInboundAdapter.ListenTCP() + tcpListener, err := h.listener.ListenTCP() if err != nil { return err } @@ -119,7 +126,7 @@ func (h *VMess) Start() error { }() } if common.Contains(h.transport.Network(), N.NetworkUDP) { - udpConn, err := h.myInboundAdapter.ListenUDP() + udpConn, err := h.listener.ListenUDP() if err != nil { return err } @@ -133,16 +140,16 @@ func (h *VMess) Start() error { return nil } -func (h *VMess) Close() error { +func (h *Inbound) Close() error { return common.Close( h.service, - &h.myInboundAdapter, + &h.listener, h.tlsConfig, h.transport, ) } -func (h *VMess) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { var err error if h.tlsConfig != nil && h.transport == nil { conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig) @@ -153,7 +160,7 @@ func (h *VMess) NewConnection(ctx context.Context, conn net.Conn, metadata adapt return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata)) } -func (h *VMess) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { err := h.NewConnection(ctx, conn, metadata) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { @@ -161,7 +168,7 @@ func (h *VMess) NewConnectionEx(ctx context.Context, conn net.Conn, metadata ada } } -func (h *VMess) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -176,7 +183,7 @@ func (h *VMess) newConnection(ctx context.Context, conn net.Conn, metadata adapt return h.router.RouteConnection(ctx, conn, metadata) } -func (h *VMess) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -197,10 +204,32 @@ func (h *VMess) newPacketConnection(ctx context.Context, conn N.PacketConn, meta return h.router.RoutePacketConnection(ctx, conn, metadata) } -var _ adapter.V2RayServerTransportHandler = (*vmessTransportHandler)(nil) +var _ adapter.V2RayServerTransportHandler = (*inboundTransportHandler)(nil) + +type inboundTransportHandler Inbound -type vmessTransportHandler VMess +func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + var metadata adapter.InboundContext + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + metadata.Source = source + metadata.Destination = destination + h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) + (*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose) +} + +func (h *Inbound) NewError(ctx context.Context, err error) { + NewError(h.logger, ctx, err) +} -func (t *vmessTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { - (*VMess)(t).routeTCP(ctx, conn, source, destination, onClose) +// Deprecated: remove +func NewError(logger logger.ContextLogger, ctx context.Context, err error) { + common.Close(err) + if E.IsClosedOrCanceled(err) { + logger.DebugContext(ctx, "connection closed: ", err) + return + } + logger.ErrorContext(ctx, err) } diff --git a/outbound/vmess.go b/protocol/vmess/outbound.go similarity index 84% rename from outbound/vmess.go rename to protocol/vmess/outbound.go index 126d2fd05..759ea8baa 100644 --- a/outbound/vmess.go +++ b/protocol/vmess/outbound.go @@ -1,10 +1,11 @@ -package outbound +package vmess import ( "context" "net" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/tls" @@ -16,15 +17,19 @@ import ( "github.com/sagernet/sing-vmess/packetaddr" "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/ntp" ) -var _ adapter.Outbound = (*VMess)(nil) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.VMessOutboundOptions](registry, C.TypeVMess, NewOutbound) +} -type VMess struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger dialer N.Dialer client *vmess.Client serverAddr M.Socksaddr @@ -35,20 +40,14 @@ type VMess struct { xudp bool } -func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessOutboundOptions) (*VMess, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessOutboundOptions) (adapter.Outbound, error) { outboundDialer, err := dialer.New(router, options.DialerOptions) if err != nil { return nil, err } - outbound := &VMess{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeVMess, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeVMess, options.Network.Build(), tag, options.DialerOptions), + logger: logger, dialer: outboundDialer, serverAddr: options.ServerOptions.Build(), } @@ -102,7 +101,7 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg return outbound, nil } -func (h *VMess) InterfaceUpdated() { +func (h *Outbound) InterfaceUpdated() { if h.transport != nil { h.transport.Close() } @@ -112,11 +111,11 @@ func (h *VMess) InterfaceUpdated() { return } -func (h *VMess) Close() error { +func (h *Outbound) Close() error { return common.Close(common.PtrOrNil(h.multiplexDialer), h.transport) } -func (h *VMess) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { if h.multiplexDialer == nil { switch N.NetworkName(network) { case N.NetworkTCP: @@ -136,7 +135,7 @@ func (h *VMess) DialContext(ctx context.Context, network string, destination M.S } } -func (h *VMess) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { if h.multiplexDialer == nil { h.logger.InfoContext(ctx, "outbound packet connection to ", destination) return (*vmessDialer)(h).ListenPacket(ctx, destination) @@ -146,11 +145,11 @@ func (h *VMess) ListenPacket(ctx context.Context, destination M.Socksaddr) (net. } } -type vmessDialer VMess +type vmessDialer Outbound func (h *vmessDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination var conn net.Conn var err error @@ -178,7 +177,7 @@ func (h *vmessDialer) DialContext(ctx context.Context, network string, destinati func (h *vmessDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination var conn net.Conn var err error diff --git a/protocol/wireguard/init.go b/protocol/wireguard/init.go new file mode 100644 index 000000000..848c113be --- /dev/null +++ b/protocol/wireguard/init.go @@ -0,0 +1,10 @@ +package wireguard + +import ( + "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/wireguard-go/conn" +) + +func init() { + dialer.WgControlFns = conn.ControlFns +} diff --git a/outbound/wireguard.go b/protocol/wireguard/outbound.go similarity index 74% rename from outbound/wireguard.go rename to protocol/wireguard/outbound.go index 8eb043f47..7251de9e6 100644 --- a/outbound/wireguard.go +++ b/protocol/wireguard/outbound.go @@ -1,6 +1,4 @@ -//go:build with_wireguard - -package outbound +package wireguard import ( "context" @@ -12,6 +10,7 @@ import ( "strings" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" @@ -21,6 +20,7 @@ import ( "github.com/sagernet/sing-tun" "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/x/list" @@ -30,14 +30,17 @@ import ( "github.com/sagernet/wireguard-go/device" ) -var ( - _ adapter.Outbound = (*WireGuard)(nil) - _ adapter.InterfaceUpdateListener = (*WireGuard)(nil) -) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.WireGuardOutboundOptions](registry, C.TypeWireGuard, NewOutbound) +} -type WireGuard struct { - myOutboundAdapter +var _ adapter.InterfaceUpdateListener = (*Outbound)(nil) + +type Outbound struct { + outbound.Adapter ctx context.Context + router adapter.Router + logger logger.ContextLogger workers int peers []wireguard.PeerConfig useStdNetBind bool @@ -51,17 +54,12 @@ type WireGuard struct { tunDevice wireguard.Device } -func NewWireGuard(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardOutboundOptions) (*WireGuard, error) { - outbound := &WireGuard{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeWireGuard, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardOutboundOptions) (adapter.Outbound, error) { + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeWireGuard, options.Network.Build(), tag, options.DialerOptions), ctx: ctx, + router: router, + logger: logger, workers: options.Workers, pauseManager: service.FromContext[pause.Manager](ctx), } @@ -111,7 +109,7 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context return outbound, nil } -func (w *WireGuard) Start() error { +func (w *Outbound) Start() error { if common.Any(w.peers, func(peer wireguard.PeerConfig) bool { return !peer.Endpoint.IsValid() }) { @@ -121,7 +119,7 @@ func (w *WireGuard) Start() error { return w.start() } -func (w *WireGuard) PostStart() error { +func (w *Outbound) PostStart() error { if common.All(w.peers, func(peer wireguard.PeerConfig) bool { return peer.Endpoint.IsValid() }) { @@ -130,7 +128,7 @@ func (w *WireGuard) PostStart() error { return w.start() } -func (w *WireGuard) start() error { +func (w *Outbound) start() error { err := wireguard.ResolvePeers(w.ctx, w.router, w.peers) if err != nil { return err @@ -150,7 +148,7 @@ func (w *WireGuard) start() error { connectAddr = w.peers[0].Endpoint reserved = w.peers[0].Reserved } - bind = wireguard.NewClientBind(w.ctx, w, w.listener, isConnect, connectAddr, reserved) + bind = wireguard.NewClientBind(w.ctx, w.logger, w.listener, isConnect, connectAddr, reserved) } err = w.tunDevice.Start() if err != nil { @@ -177,7 +175,7 @@ func (w *WireGuard) start() error { return nil } -func (w *WireGuard) Close() error { +func (w *Outbound) Close() error { if w.device != nil { w.device.Close() } @@ -187,12 +185,12 @@ func (w *WireGuard) Close() error { return nil } -func (w *WireGuard) InterfaceUpdated() { +func (w *Outbound) InterfaceUpdated() { w.device.BindUpdate() return } -func (w *WireGuard) onPauseUpdated(event int) { +func (w *Outbound) onPauseUpdated(event int) { switch event { case pause.EventDevicePaused: w.device.Down() @@ -201,7 +199,7 @@ func (w *WireGuard) onPauseUpdated(event int) { } } -func (w *WireGuard) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (w *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { switch network { case N.NetworkTCP: w.logger.InfoContext(ctx, "outbound connection to ", destination) @@ -218,7 +216,7 @@ func (w *WireGuard) DialContext(ctx context.Context, network string, destination return w.tunDevice.DialContext(ctx, network, destination) } -func (w *WireGuard) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (w *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { w.logger.InfoContext(ctx, "outbound packet connection to ", destination) if destination.IsFqdn() { destinationAddresses, err := w.router.LookupDefault(ctx, destination.Fqdn) @@ -236,12 +234,12 @@ func (w *WireGuard) ListenPacket(ctx context.Context, destination M.Socksaddr) ( // TODO // Deprecated -func (w *WireGuard) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewDirectConnection(ctx, w.router, w, conn, metadata, dns.DomainStrategyAsIS) +func (w *Outbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + return outbound.NewDirectConnection(ctx, w.router, w, conn, metadata, dns.DomainStrategyAsIS) } // TODO // Deprecated -func (w *WireGuard) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return NewDirectPacketConnection(ctx, w.router, w, conn, metadata, dns.DomainStrategyAsIS) +func (w *Outbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + return outbound.NewDirectPacketConnection(ctx, w.router, w, conn, metadata, dns.DomainStrategyAsIS) } diff --git a/route/dns.go b/route/dns.go index 34299ebfd..a0c376c2f 100644 --- a/route/dns.go +++ b/route/dns.go @@ -7,7 +7,7 @@ import ( "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/outbound" + dnsOutbound "github.com/sagernet/sing-box/protocol/dns" "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common/buf" E "github.com/sagernet/sing/common/exceptions" @@ -22,7 +22,7 @@ func (r *Router) hijackDNSStream(ctx context.Context, conn net.Conn, metadata ad metadata.Destination = M.Socksaddr{} for { conn.SetReadDeadline(time.Now().Add(C.DNSTimeout)) - err := outbound.HandleStreamDNSRequest(ctx, r, conn, metadata) + err := dnsOutbound.HandleStreamDNSRequest(ctx, r, conn, metadata) if err != nil { return err } @@ -46,7 +46,7 @@ func (r *Router) hijackDNSPacket(ctx context.Context, conn N.PacketConn, packetB }) return } - err := outbound.NewDNSPacketConnection(ctx, r, conn, packetBuffers, metadata) + err := dnsOutbound.NewDNSPacketConnection(ctx, r, conn, packetBuffers, metadata) if err != nil && !E.IsClosedOrCanceled(err) { r.dnsLogger.ErrorContext(ctx, E.Cause(err, "process packet connection")) } diff --git a/route/route.go b/route/route.go index ebffddddb..6c68cf79c 100644 --- a/route/route.go +++ b/route/route.go @@ -12,12 +12,12 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/conntrack" "github.com/sagernet/sing-box/common/process" "github.com/sagernet/sing-box/common/sniff" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-box/outbound" "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing-dns" "github.com/sagernet/sing-mux" diff --git a/route/router.go b/route/router.go index 6308127fd..ce4807490 100644 --- a/route/router.go +++ b/route/router.go @@ -99,7 +99,6 @@ func NewRouter( dnsOptions option.DNSOptions, ntpOptions option.NTPOptions, inbounds []option.Inbound, - platformInterface platform.Interface, ) (*Router, error) { router := &Router{ ctx: ctx, @@ -122,10 +121,13 @@ func NewRouter( defaultInterface: options.DefaultInterface, defaultMark: options.DefaultMark, pauseManager: service.FromContext[pause.Manager](ctx), - platformInterface: platformInterface, + platformInterface: service.FromContext[platform.Interface](ctx), needWIFIState: hasRule(options.Rules, isWIFIRule) || hasDNSRule(dnsOptions.Rules, isWIFIDNSRule), needPackageManager: common.Any(inbounds, func(inbound option.Inbound) bool { - return len(inbound.TunOptions.IncludePackage) > 0 || len(inbound.TunOptions.ExcludePackage) > 0 + if tunOptions, isTUN := inbound.Options.(*option.TunInboundOptions); isTUN && tunOptions.AutoRoute { + return true + } + return false }), } router.dnsClient = dns.NewClient(dns.ClientOptions{ @@ -324,9 +326,15 @@ func NewRouter( router.fakeIPStore = fakeip.NewStore(ctx, router.logger, inet4Range, inet6Range) } - usePlatformDefaultInterfaceMonitor := platformInterface != nil && platformInterface.UsePlatformDefaultInterfaceMonitor() + usePlatformDefaultInterfaceMonitor := router.platformInterface != nil && router.platformInterface.UsePlatformDefaultInterfaceMonitor() needInterfaceMonitor := options.AutoDetectInterface || common.Any(inbounds, func(inbound option.Inbound) bool { - return inbound.HTTPOptions.SetSystemProxy || inbound.MixedOptions.SetSystemProxy || inbound.TunOptions.AutoRoute + if httpMixedOptions, isHTTPMixed := inbound.Options.(*option.HTTPMixedInboundOptions); isHTTPMixed && httpMixedOptions.SetSystemProxy { + return true + } + if tunOptions, isTUN := inbound.Options.(*option.TunInboundOptions); isTUN && tunOptions.AutoRoute { + return true + } + return false }) if !usePlatformDefaultInterfaceMonitor { @@ -342,7 +350,7 @@ func NewRouter( interfaceMonitor, err := tun.NewDefaultInterfaceMonitor(router.networkMonitor, router.logger, tun.DefaultInterfaceMonitorOptions{ InterfaceFinder: router.interfaceFinder, OverrideAndroidVPN: options.OverrideAndroidVPN, - UnderNetworkExtension: platformInterface != nil && platformInterface.UnderNetworkExtension(), + UnderNetworkExtension: router.platformInterface != nil && router.platformInterface.UnderNetworkExtension(), }) if err != nil { return nil, E.New("auto_detect_interface unsupported on current platform") @@ -351,7 +359,7 @@ func NewRouter( router.interfaceMonitor = interfaceMonitor } } else { - interfaceMonitor := platformInterface.CreateDefaultInterfaceMonitor(router.logger) + interfaceMonitor := router.platformInterface.CreateDefaultInterfaceMonitor(router.logger) interfaceMonitor.RegisterCallback(router.notifyNetworkUpdate) router.interfaceMonitor = interfaceMonitor } diff --git a/test/brutal_test.go b/test/brutal_test.go index 18aae2e20..ce1d2c2a8 100644 --- a/test/brutal_test.go +++ b/test/brutal_test.go @@ -15,7 +15,7 @@ func TestBrutalShadowsocks(t *testing.T) { method := shadowaead_2022.List[0] password := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -46,7 +46,7 @@ func TestBrutalShadowsocks(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -100,7 +100,7 @@ func TestBrutalTrojan(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") password := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -138,7 +138,7 @@ func TestBrutalTrojan(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -197,7 +197,7 @@ func TestBrutalTrojan(t *testing.T) { func TestBrutalVMess(t *testing.T) { user, _ := uuid.NewV4() startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -227,7 +227,7 @@ func TestBrutalVMess(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -279,7 +279,7 @@ func TestBrutalVMess(t *testing.T) { func TestBrutalVLESS(t *testing.T) { user, _ := uuid.NewV4() startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -326,7 +326,7 @@ func TestBrutalVLESS(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/direct_test.go b/test/direct_test.go index 1dbf1de1b..c4fd8c5ea 100644 --- a/test/direct_test.go +++ b/test/direct_test.go @@ -11,7 +11,7 @@ import ( // Since this is a feature one-off added by outsiders, I won't address these anymore. func _TestProxyProtocol(t *testing.T) { startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -33,7 +33,7 @@ func _TestProxyProtocol(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/domain_inbound_test.go b/test/domain_inbound_test.go index 1ca2121da..c82b0d297 100644 --- a/test/domain_inbound_test.go +++ b/test/domain_inbound_test.go @@ -6,7 +6,7 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" - dns "github.com/sagernet/sing-dns" + "github.com/sagernet/sing-dns" "github.com/gofrs/uuid/v5" ) @@ -14,7 +14,7 @@ import ( func TestTUICDomainUDP(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -49,7 +49,7 @@ func TestTUICDomainUDP(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/ech_test.go b/test/ech_test.go index 90eae1f48..eeac1acb7 100644 --- a/test/ech_test.go +++ b/test/ech_test.go @@ -16,7 +16,7 @@ func TestECH(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org", false)) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -55,7 +55,7 @@ func TestECH(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -109,7 +109,7 @@ func TestECHQUIC(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org", false)) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -145,7 +145,7 @@ func TestECHQUIC(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -199,7 +199,7 @@ func TestECHHysteria2(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org", false)) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -235,7 +235,7 @@ func TestECHHysteria2(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/http_test.go b/test/http_test.go index 88385c275..7e7240053 100644 --- a/test/http_test.go +++ b/test/http_test.go @@ -10,7 +10,7 @@ import ( func TestHTTPSelf(t *testing.T) { startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -31,7 +31,7 @@ func TestHTTPSelf(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/hysteria2_test.go b/test/hysteria2_test.go index 9ca2f5d38..665da552c 100644 --- a/test/hysteria2_test.go +++ b/test/hysteria2_test.go @@ -28,7 +28,7 @@ func testHysteria2Self(t *testing.T, salamanderPassword string) { } } startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -63,7 +63,7 @@ func testHysteria2Self(t *testing.T, salamanderPassword string) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -115,7 +115,7 @@ func testHysteria2Self(t *testing.T, salamanderPassword string) { func TestHysteria2Inbound(t *testing.T) { caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeHysteria2, Hysteria2Options: option.Hysteria2InboundOptions{ @@ -167,7 +167,7 @@ func TestHysteria2Outbound(t *testing.T) { }, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -178,7 +178,7 @@ func TestHysteria2Outbound(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeHysteria2, Hysteria2Options: option.Hysteria2OutboundOptions{ diff --git a/test/hysteria_test.go b/test/hysteria_test.go index bde1b9fa7..dce00390a 100644 --- a/test/hysteria_test.go +++ b/test/hysteria_test.go @@ -11,7 +11,7 @@ import ( func TestHysteriaSelf(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -46,7 +46,7 @@ func TestHysteriaSelf(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -98,7 +98,7 @@ func TestHysteriaSelf(t *testing.T) { func TestHysteriaInbound(t *testing.T) { caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeHysteria, HysteriaOptions: option.HysteriaInboundOptions{ @@ -149,7 +149,7 @@ func TestHysteriaOutbound(t *testing.T) { }, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -160,7 +160,7 @@ func TestHysteriaOutbound(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeHysteria, HysteriaOptions: option.HysteriaOutboundOptions{ diff --git a/test/inbound_detour_test.go b/test/inbound_detour_test.go index 9505c217f..c26c81a79 100644 --- a/test/inbound_detour_test.go +++ b/test/inbound_detour_test.go @@ -13,7 +13,7 @@ func TestChainedInbound(t *testing.T) { method := shadowaead_2022.List[0] password := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -49,7 +49,7 @@ func TestChainedInbound(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/mux_cool_test.go b/test/mux_cool_test.go index 81130fad9..e72f244fc 100644 --- a/test/mux_cool_test.go +++ b/test/mux_cool_test.go @@ -37,7 +37,7 @@ func TestMuxCoolServer(t *testing.T) { }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeVMess, VMessOptions: option.VMessInboundOptions{ @@ -81,7 +81,7 @@ func TestMuxCoolClient(t *testing.T) { }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -92,7 +92,7 @@ func TestMuxCoolClient(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeVMess, VMessOptions: option.VMessOutboundOptions{ @@ -112,7 +112,7 @@ func TestMuxCoolClient(t *testing.T) { func TestMuxCoolSelf(t *testing.T) { user := newUUID() startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -139,7 +139,7 @@ func TestMuxCoolSelf(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/mux_test.go b/test/mux_test.go index 8d7551855..335def2e9 100644 --- a/test/mux_test.go +++ b/test/mux_test.go @@ -55,7 +55,7 @@ func testShadowsocksMux(t *testing.T, options option.OutboundMultiplexOptions) { method := shadowaead_2022.List[0] password := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -81,7 +81,7 @@ func testShadowsocksMux(t *testing.T, options option.OutboundMultiplexOptions) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -125,7 +125,7 @@ func testShadowsocksMux(t *testing.T, options option.OutboundMultiplexOptions) { func testVMessMux(t *testing.T, options option.OutboundMultiplexOptions) { user, _ := uuid.NewV4() startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -154,7 +154,7 @@ func testVMessMux(t *testing.T, options option.OutboundMultiplexOptions) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/naive_test.go b/test/naive_test.go index 1a1547da1..fe3e7dce9 100644 --- a/test/naive_test.go +++ b/test/naive_test.go @@ -13,7 +13,7 @@ import ( func TestNaiveInboundWithNginx(t *testing.T) { caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeNaive, NaiveOptions: option.NaiveInboundOptions{ @@ -59,7 +59,7 @@ func TestNaiveInboundWithNginx(t *testing.T) { func TestNaiveInbound(t *testing.T) { caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeNaive, NaiveOptions: option.NaiveInboundOptions{ @@ -103,7 +103,7 @@ func TestNaiveInbound(t *testing.T) { func TestNaiveHTTP3Inbound(t *testing.T) { caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeNaive, NaiveOptions: option.NaiveInboundOptions{ diff --git a/test/shadowsocks_legacy_test.go b/test/shadowsocks_legacy_test.go index 8075a7df2..ae6f38e40 100644 --- a/test/shadowsocks_legacy_test.go +++ b/test/shadowsocks_legacy_test.go @@ -24,7 +24,7 @@ func testShadowsocksLegacy(t *testing.T, method string) { }, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -35,7 +35,7 @@ func testShadowsocksLegacy(t *testing.T, method string) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeShadowsocks, ShadowsocksOptions: option.ShadowsocksOutboundOptions{ diff --git a/test/shadowsocks_test.go b/test/shadowsocks_test.go index 4ef1ee9d9..0f7af7650 100644 --- a/test/shadowsocks_test.go +++ b/test/shadowsocks_test.go @@ -99,7 +99,7 @@ func testShadowsocksInboundWithShadowsocksRust(t *testing.T, method string, pass Cmd: []string{"-s", F.ToString("127.0.0.1:", serverPort), "-b", F.ToString("0.0.0.0:", clientPort), "-m", method, "-k", password, "-U"}, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeShadowsocks, ShadowsocksOptions: option.ShadowsocksInboundOptions{ @@ -124,7 +124,7 @@ func testShadowsocksOutboundWithShadowsocksRust(t *testing.T, method string, pas Cmd: []string{"-s", F.ToString("0.0.0.0:", serverPort), "-m", method, "-k", password, "-U"}, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -135,7 +135,7 @@ func testShadowsocksOutboundWithShadowsocksRust(t *testing.T, method string, pas }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeShadowsocks, ShadowsocksOptions: option.ShadowsocksOutboundOptions{ @@ -154,7 +154,7 @@ func testShadowsocksOutboundWithShadowsocksRust(t *testing.T, method string, pas func testShadowsocksSelf(t *testing.T, method string, password string) { startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -177,7 +177,7 @@ func testShadowsocksSelf(t *testing.T, method string, password string) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -221,7 +221,7 @@ func TestShadowsocksUoT(t *testing.T) { method := shadowaead_2022.List[0] password := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -244,7 +244,7 @@ func TestShadowsocksUoT(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -289,7 +289,7 @@ func TestShadowsocksUoT(t *testing.T) { func testShadowsocks2022EIH(t *testing.T, method string, password string) { startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -317,7 +317,7 @@ func testShadowsocks2022EIH(t *testing.T, method string, password string) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/shadowtls_test.go b/test/shadowtls_test.go index 6f9ee1e56..71e8d9fa2 100644 --- a/test/shadowtls_test.go +++ b/test/shadowtls_test.go @@ -37,7 +37,7 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool) method := shadowaead_2022.List[0] ssPassword := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -80,7 +80,7 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool) }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeShadowsocks, ShadowsocksOptions: option.ShadowsocksOutboundOptions{ @@ -142,7 +142,7 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool) func TestShadowTLSFallback(t *testing.T) { startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeShadowTLS, ShadowTLSOptions: option.ShadowTLSInboundOptions{ @@ -189,7 +189,7 @@ func TestShadowTLSInbound(t *testing.T) { Cmd: []string{"--v3", "--threads", "1", "client", "--listen", "0.0.0.0:" + F.ToString(otherPort), "--server", "127.0.0.1:" + F.ToString(serverPort), "--sni", "google.com", "--password", password}, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "in", @@ -232,7 +232,7 @@ func TestShadowTLSInbound(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -283,7 +283,7 @@ func TestShadowTLSOutbound(t *testing.T) { Env: []string{"RUST_LOG=trace"}, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -306,7 +306,7 @@ func TestShadowTLSOutbound(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeShadowsocks, ShadowsocksOptions: option.ShadowsocksOutboundOptions{ diff --git a/test/ss_plugin_test.go b/test/ss_plugin_test.go index 94606b70f..3f837b4e9 100644 --- a/test/ss_plugin_test.go +++ b/test/ss_plugin_test.go @@ -33,7 +33,7 @@ func testShadowsocksPlugin(t *testing.T, name string, opts string, args string) }, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -44,7 +44,7 @@ func testShadowsocksPlugin(t *testing.T, name string, opts string, args string) }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeShadowsocks, ShadowsocksOptions: option.ShadowsocksOutboundOptions{ diff --git a/test/tfo_test.go b/test/tfo_test.go index 7bd34e2db..458a936d6 100644 --- a/test/tfo_test.go +++ b/test/tfo_test.go @@ -13,7 +13,7 @@ func TestTCPSlowOpen(t *testing.T) { method := shadowaead.List[0] password := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -37,7 +37,7 @@ func TestTCPSlowOpen(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/tls_test.go b/test/tls_test.go index cfc6c1a50..b42d924f4 100644 --- a/test/tls_test.go +++ b/test/tls_test.go @@ -11,7 +11,7 @@ import ( func TestUTLS(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -46,7 +46,7 @@ func TestUTLS(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/trojan_test.go b/test/trojan_test.go index f88ec8850..1a206c66a 100644 --- a/test/trojan_test.go +++ b/test/trojan_test.go @@ -20,7 +20,7 @@ func TestTrojanOutbound(t *testing.T) { }, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -31,7 +31,7 @@ func TestTrojanOutbound(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeTrojan, TrojanOptions: option.TrojanOutboundOptions{ @@ -57,7 +57,7 @@ func TestTrojanOutbound(t *testing.T) { func TestTrojanSelf(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -92,7 +92,7 @@ func TestTrojanSelf(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -140,7 +140,7 @@ func TestTrojanSelf(t *testing.T) { func TestTrojanPlainSelf(t *testing.T) { startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -167,7 +167,7 @@ func TestTrojanPlainSelf(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/tuic_test.go b/test/tuic_test.go index 5b838f22f..41fb75999 100644 --- a/test/tuic_test.go +++ b/test/tuic_test.go @@ -29,7 +29,7 @@ func testTUICSelf(t *testing.T, udpStream bool, zeroRTTHandshake bool) { udpRelayMode = "quic" } startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -62,7 +62,7 @@ func testTUICSelf(t *testing.T, udpStream bool, zeroRTTHandshake bool) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -113,7 +113,7 @@ func testTUICSelf(t *testing.T, udpStream bool, zeroRTTHandshake bool) { func TestTUICInbound(t *testing.T) { caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeTUIC, TUICOptions: option.TUICInboundOptions{ @@ -160,7 +160,7 @@ func TestTUICOutbound(t *testing.T) { }, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -171,7 +171,7 @@ func TestTUICOutbound(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeTUIC, TUICOptions: option.TUICOutboundOptions{ diff --git a/test/v2ray_api_test.go b/test/v2ray_api_test.go index 1bea41a67..cd7ae2c44 100644 --- a/test/v2ray_api_test.go +++ b/test/v2ray_api_test.go @@ -14,7 +14,7 @@ import ( func TestV2RayAPI(t *testing.T) { i := startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "in", @@ -26,7 +26,7 @@ func TestV2RayAPI(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, Tag: "out", diff --git a/test/v2ray_grpc_test.go b/test/v2ray_grpc_test.go index fa43f753a..5cf875435 100644 --- a/test/v2ray_grpc_test.go +++ b/test/v2ray_grpc_test.go @@ -27,7 +27,7 @@ func testV2RayGRPCInbound(t *testing.T, forceLite bool) { require.NoError(t, err) _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeVMess, VMessOptions: option.VMessInboundOptions{ @@ -126,7 +126,7 @@ func testV2RayGRPCOutbound(t *testing.T, forceLite bool) { }, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -138,7 +138,7 @@ func testV2RayGRPCOutbound(t *testing.T, forceLite bool) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeVMess, Tag: "vmess-out", diff --git a/test/v2ray_transport_test.go b/test/v2ray_transport_test.go index c7362f345..27074e78e 100644 --- a/test/v2ray_transport_test.go +++ b/test/v2ray_transport_test.go @@ -44,7 +44,7 @@ func testVMessTransportSelf(t *testing.T, server *option.V2RayTransportOptions, require.NoError(t, err) _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -80,7 +80,7 @@ func testVMessTransportSelf(t *testing.T, server *option.V2RayTransportOptions, }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -133,7 +133,7 @@ func testTrojanTransportSelf(t *testing.T, server *option.V2RayTransportOptions, require.NoError(t, err) _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -169,7 +169,7 @@ func testTrojanTransportSelf(t *testing.T, server *option.V2RayTransportOptions, }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -224,7 +224,7 @@ func TestVMessQUICSelf(t *testing.T) { require.NoError(t, err) _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -260,7 +260,7 @@ func TestVMessQUICSelf(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -312,7 +312,7 @@ func testV2RayTransportNOTLSSelf(t *testing.T, transport *option.V2RayTransportO user, err := uuid.DefaultGenerator.NewV4() require.NoError(t, err) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -340,7 +340,7 @@ func testV2RayTransportNOTLSSelf(t *testing.T, transport *option.V2RayTransportO }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/v2ray_ws_test.go b/test/v2ray_ws_test.go index 0e238c28b..de8d4bdce 100644 --- a/test/v2ray_ws_test.go +++ b/test/v2ray_ws_test.go @@ -61,7 +61,7 @@ func testV2RayWebsocketInbound(t *testing.T, maxEarlyData uint32, earlyDataHeade require.NoError(t, err) _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeVMess, VMessOptions: option.VMessInboundOptions{ @@ -158,7 +158,7 @@ func testV2RayWebsocketOutbound(t *testing.T, maxEarlyData uint32, earlyDataHead }, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -170,7 +170,7 @@ func testV2RayWebsocketOutbound(t *testing.T, maxEarlyData uint32, earlyDataHead }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeVMess, Tag: "vmess-out", diff --git a/test/vmess_test.go b/test/vmess_test.go index fcf7bf8f4..9f81d9a01 100644 --- a/test/vmess_test.go +++ b/test/vmess_test.go @@ -181,7 +181,7 @@ func testVMessInboundWithV2Ray(t *testing.T, security string, alterId int, authe }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeVMess, VMessOptions: option.VMessInboundOptions{ @@ -229,7 +229,7 @@ func testVMessOutboundWithV2Ray(t *testing.T, security string, globalPadding boo }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -240,7 +240,7 @@ func testVMessOutboundWithV2Ray(t *testing.T, security string, globalPadding boo }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeVMess, VMessOptions: option.VMessOutboundOptions{ @@ -263,7 +263,7 @@ func testVMessOutboundWithV2Ray(t *testing.T, security string, globalPadding boo func testVMessSelf(t *testing.T, security string, alterId int, globalPadding bool, authenticatedLength bool, packetAddr bool) { user := newUUID() startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -291,7 +291,7 @@ func testVMessSelf(t *testing.T, security string, alterId int, globalPadding boo }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/wireguard_test.go b/test/wireguard_test.go index 50e87ee04..70c0e5a55 100644 --- a/test/wireguard_test.go +++ b/test/wireguard_test.go @@ -21,7 +21,7 @@ func _TestWireGuard(t *testing.T) { }) time.Sleep(5 * time.Second) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -32,7 +32,7 @@ func _TestWireGuard(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeWireGuard, WireGuardOptions: option.WireGuardOutboundOptions{ diff --git a/test/wrapper_test.go b/test/wrapper_test.go index d2b6b9ffc..a7c23f335 100644 --- a/test/wrapper_test.go +++ b/test/wrapper_test.go @@ -10,7 +10,7 @@ import ( ) func TestOptionsWrapper(t *testing.T) { - inbound := option.Inbound{ + inbound := option.LegacyInbound{ Type: C.TypeHTTP, HTTPOptions: option.HTTPMixedInboundOptions{ InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ diff --git a/transport/trojan/mux.go b/transport/trojan/mux.go index b1cc9985c..0329bd40f 100644 --- a/transport/trojan/mux.go +++ b/transport/trojan/mux.go @@ -8,12 +8,13 @@ import ( "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/common/task" "github.com/sagernet/smux" ) -func HandleMuxConnection(ctx context.Context, conn net.Conn, metadata M.Metadata, handler Handler) error { +func HandleMuxConnection(ctx context.Context, conn net.Conn, metadata M.Metadata, handler Handler, logger logger.ContextLogger) error { session, err := smux.Server(conn, smuxConfig()) if err != nil { return err @@ -26,7 +27,7 @@ func HandleMuxConnection(ctx context.Context, conn net.Conn, metadata M.Metadata if err != nil { return err } - go newMuxConnection(ctx, stream, metadata, handler) + go newMuxConnection(ctx, stream, metadata, handler, logger) } }) group.Cleanup(func() { @@ -35,10 +36,10 @@ func HandleMuxConnection(ctx context.Context, conn net.Conn, metadata M.Metadata return group.Run(ctx) } -func newMuxConnection(ctx context.Context, conn net.Conn, metadata M.Metadata, handler Handler) { +func newMuxConnection(ctx context.Context, conn net.Conn, metadata M.Metadata, handler Handler, logger logger.ContextLogger) { err := newMuxConnection0(ctx, conn, metadata, handler) if err != nil { - handler.NewError(ctx, E.Cause(err, "process trojan-go multiplex connection")) + logger.ErrorContext(ctx, E.Cause(err, "process trojan-go multiplex connection")) } } diff --git a/transport/trojan/service.go b/transport/trojan/service.go index 97f674ab9..978d737fe 100644 --- a/transport/trojan/service.go +++ b/transport/trojan/service.go @@ -9,6 +9,7 @@ import ( "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" 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/rw" @@ -17,7 +18,6 @@ import ( type Handler interface { N.TCPConnectionHandler N.UDPConnectionHandler - E.Handler } type Service[K comparable] struct { @@ -25,14 +25,16 @@ type Service[K comparable] struct { keys map[[56]byte]K handler Handler fallbackHandler N.TCPConnectionHandler + logger logger.ContextLogger } -func NewService[K comparable](handler Handler, fallbackHandler N.TCPConnectionHandler) *Service[K] { +func NewService[K comparable](handler Handler, fallbackHandler N.TCPConnectionHandler, logger logger.ContextLogger) *Service[K] { return &Service[K]{ users: make(map[K][56]byte), keys: make(map[[56]byte]K), handler: handler, fallbackHandler: fallbackHandler, + logger: logger, } } @@ -110,7 +112,7 @@ func (s *Service[K]) NewConnection(ctx context.Context, conn net.Conn, metadata return s.handler.NewPacketConnection(ctx, &PacketConn{Conn: conn}, metadata) // case CommandMux: default: - return HandleMuxConnection(ctx, conn, metadata, s.handler) + return HandleMuxConnection(ctx, conn, metadata, s.handler, s.logger) } } diff --git a/transport/wireguard/client_bind.go b/transport/wireguard/client_bind.go index 6c534532a..20e7c0790 100644 --- a/transport/wireguard/client_bind.go +++ b/transport/wireguard/client_bind.go @@ -10,6 +10,7 @@ import ( "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" 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/service" @@ -21,10 +22,10 @@ var _ conn.Bind = (*ClientBind)(nil) type ClientBind struct { ctx context.Context + logger logger.Logger pauseManager pause.Manager bindCtx context.Context bindDone context.CancelFunc - errorHandler E.Handler dialer N.Dialer reservedForEndpoint map[netip.AddrPort][3]uint8 connAccess sync.Mutex @@ -35,11 +36,11 @@ type ClientBind struct { reserved [3]uint8 } -func NewClientBind(ctx context.Context, errorHandler E.Handler, dialer N.Dialer, isConnect bool, connectAddr netip.AddrPort, reserved [3]uint8) *ClientBind { +func NewClientBind(ctx context.Context, logger logger.Logger, dialer N.Dialer, isConnect bool, connectAddr netip.AddrPort, reserved [3]uint8) *ClientBind { return &ClientBind{ ctx: ctx, + logger: logger, pauseManager: service.FromContext[pause.Manager](ctx), - errorHandler: errorHandler, dialer: dialer, reservedForEndpoint: make(map[netip.AddrPort][3]uint8), done: make(chan struct{}), @@ -115,7 +116,7 @@ func (c *ClientBind) receive(packets [][]byte, sizes []int, eps []conn.Endpoint) return default: } - c.errorHandler.NewError(context.Background(), E.Cause(err, "connect to server")) + c.logger.Error(E.Cause(err, "connect to server")) err = nil c.pauseManager.WaitActive() time.Sleep(time.Second) @@ -127,7 +128,7 @@ func (c *ClientBind) receive(packets [][]byte, sizes []int, eps []conn.Endpoint) select { case <-c.done: default: - c.errorHandler.NewError(context.Background(), E.Cause(err, "read packet")) + c.logger.Error(context.Background(), E.Cause(err, "read packet")) err = nil } return diff --git a/transport/wireguard/resolve.go b/transport/wireguard/resolve.go index 5b4124d20..d7a1d19c0 100644 --- a/transport/wireguard/resolve.go +++ b/transport/wireguard/resolve.go @@ -8,7 +8,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/option" - dns "github.com/sagernet/sing-dns" + "github.com/sagernet/sing-dns" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" ) From 866be4acbd1610ba39842952096a77e862206944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 6 Nov 2024 17:23:00 +0800 Subject: [PATCH 06/39] Remove unused reject methods --- cmd/sing-box/cmd_format.go | 3 +-- constant/rule.go | 8 ++----- option/options.go | 4 ++-- option/rule_action.go | 18 +++++++------- protocol/tun/inbound.go | 18 +++++++++----- route/route.go | 7 +++--- route/rule/rule_action.go | 49 +++++++++++++++++++++++++++----------- route/rule/rule_default.go | 4 ++-- route/rule/rule_dns.go | 4 ++-- 9 files changed, 67 insertions(+), 48 deletions(-) diff --git a/cmd/sing-box/cmd_format.go b/cmd/sing-box/cmd_format.go index 9856c7633..ab59c9ae5 100644 --- a/cmd/sing-box/cmd_format.go +++ b/cmd/sing-box/cmd_format.go @@ -2,7 +2,6 @@ package main import ( "bytes" - "context" "os" "path/filepath" @@ -39,7 +38,7 @@ func format() error { return err } for _, optionsEntry := range optionsList { - optionsEntry.options, err = badjson.Omitempty(context.TODO(), optionsEntry.options) + optionsEntry.options, err = badjson.Omitempty(globalCtx, optionsEntry.options) if err != nil { return err } diff --git a/constant/rule.go b/constant/rule.go index e662cd5ce..9a095555c 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -33,10 +33,6 @@ const ( ) const ( - RuleActionRejectMethodDefault = "default" - RuleActionRejectMethodReset = "reset" - RuleActionRejectMethodNetworkUnreachable = "network-unreachable" - RuleActionRejectMethodHostUnreachable = "host-unreachable" - RuleActionRejectMethodPortUnreachable = "port-unreachable" - RuleActionRejectMethodDrop = "drop" + RuleActionRejectMethodDefault = "default" + RuleActionRejectMethodDrop = "drop" ) diff --git a/option/options.go b/option/options.go index c4811fa99..d28b0ebc0 100644 --- a/option/options.go +++ b/option/options.go @@ -19,9 +19,9 @@ type _Options struct { Experimental *ExperimentalOptions `json:"experimental,omitempty"` // Deprecated: use Inbounds instead - LegacyInbounds []LegacyInbound `json:"inbound,omitempty"` + LegacyInbounds []LegacyInbound `json:"-"` // Deprecated: use Outbounds instead - LegacyOutbounds []LegacyOutbound `json:"_"` + LegacyOutbounds []LegacyOutbound `json:"-"` } type Options _Options diff --git a/option/rule_action.go b/option/rule_action.go index e752a2bea..3a40e1c06 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -73,11 +73,9 @@ func (r *RuleAction) UnmarshalJSON(data []byte) error { } type _DNSRuleAction struct { - Action string `json:"action,omitempty"` - RouteOptions DNSRouteActionOptions `json:"-"` - RejectOptions RejectActionOptions `json:"-"` - SniffOptions RouteActionSniff `json:"-"` - ResolveOptions RouteActionResolve `json:"-"` + Action string `json:"action,omitempty"` + RouteOptions DNSRouteActionOptions `json:"-"` + RejectOptions RejectActionOptions `json:"-"` } type DNSRuleAction _DNSRuleAction @@ -139,6 +137,7 @@ type DNSRouteActionOptions struct { type _RejectActionOptions struct { Method string `json:"method,omitempty"` + NoDrop bool `json:"no_drop,omitempty"` } type RejectActionOptions _RejectActionOptions @@ -151,14 +150,13 @@ func (r *RejectActionOptions) UnmarshalJSON(bytes []byte) error { switch r.Method { case "", C.RuleActionRejectMethodDefault: r.Method = C.RuleActionRejectMethodDefault - case C.RuleActionRejectMethodReset, - C.RuleActionRejectMethodNetworkUnreachable, - C.RuleActionRejectMethodHostUnreachable, - C.RuleActionRejectMethodPortUnreachable, - C.RuleActionRejectMethodDrop: + case C.RuleActionRejectMethodDrop: default: return E.New("unknown reject method: " + r.Method) } + if r.Method == C.RuleActionRejectMethodDrop && r.NoDrop { + return E.New("no_drop is not allowed when method is drop") + } return nil } diff --git a/protocol/tun/inbound.go b/protocol/tun/inbound.go index ff679c8ec..4be30d61b 100644 --- a/protocol/tun/inbound.go +++ b/protocol/tun/inbound.go @@ -343,19 +343,25 @@ func (t *Inbound) Start() error { if err != nil { return err } - monitor.Start("initiating tun stack") - err = tunStack.Start() - monitor.Finish() t.tunStack = tunStack - if err != nil { - return err - } t.logger.Info("started at ", t.tunOptions.Name) return nil } func (t *Inbound) PostStart() error { monitor := taskmonitor.New(t.logger, C.StartTimeout) + monitor.Start("starting tun stack") + err := t.tunStack.Start() + monitor.Finish() + if err != nil { + return E.Cause(err, "starting tun stack") + } + monitor.Start("starting tun interface") + err = t.tunIf.Start() + monitor.Finish() + if err != nil { + return E.Cause(err, "starting TUN interface") + } if t.autoRedirect != nil { t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet) for _, routeRuleSet := range t.routeRuleSet { diff --git a/route/route.go b/route/route.go index 6c68cf79c..2c199a2d2 100644 --- a/route/route.go +++ b/route/route.go @@ -8,7 +8,6 @@ import ( "os" "os/user" "strings" - "syscall" "time" "github.com/sagernet/sing-box/adapter" @@ -107,7 +106,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad selectReturn = true case *rule.RuleActionReject: buf.ReleaseMulti(buffers) - N.CloseOnHandshakeFailure(conn, onClose, action.Error()) + N.CloseOnHandshakeFailure(conn, onClose, action.Error(ctx)) return nil case *rule.RuleActionHijackDNS: for _, buffer := range buffers { @@ -252,7 +251,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m selectReturn = true case *rule.RuleActionReject: N.ReleaseMultiPacketBuffer(packetBuffers) - N.CloseOnHandshakeFailure(conn, onClose, syscall.ECONNREFUSED) + N.CloseOnHandshakeFailure(conn, onClose, action.Error(ctx)) return nil case *rule.RuleActionHijackDNS: r.hijackDNSPacket(ctx, conn, packetBuffers, metadata) @@ -317,7 +316,7 @@ func (r *Router) PreMatch(metadata adapter.InboundContext) error { if !isReject { return nil } - return rejectAction.Error() + return rejectAction.Error(nil) } func (r *Router) matchRule( diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index 57b736479..031f181c7 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -1,10 +1,10 @@ package rule import ( + "context" "net/netip" - "os" "strings" - "syscall" + "sync" "time" "github.com/sagernet/sing-box/adapter" @@ -13,11 +13,15 @@ import ( "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-dns" "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/logger" + + "golang.org/x/sys/unix" ) -func NewRuleAction(action option.RuleAction) (adapter.RuleAction, error) { +func NewRuleAction(logger logger.ContextLogger, action option.RuleAction) (adapter.RuleAction, error) { switch action.Action { case C.RuleActionTypeRoute: return &RuleActionRoute{ @@ -29,6 +33,8 @@ func NewRuleAction(action option.RuleAction) (adapter.RuleAction, error) { case C.RuleActionTypeReject: return &RuleActionReject{ Method: action.RejectOptions.Method, + NoDrop: action.RejectOptions.NoDrop, + logger: logger, }, nil case C.RuleActionTypeHijackDNS: return &RuleActionHijackDNS{}, nil @@ -48,7 +54,7 @@ func NewRuleAction(action option.RuleAction) (adapter.RuleAction, error) { } } -func NewDNSRuleAction(action option.DNSRuleAction) adapter.RuleAction { +func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction) adapter.RuleAction { switch action.Action { case C.RuleActionTypeRoute: return &RuleActionDNSRoute{ @@ -62,6 +68,8 @@ func NewDNSRuleAction(action option.DNSRuleAction) adapter.RuleAction { case C.RuleActionTypeReject: return &RuleActionReject{ Method: action.RejectOptions.Method, + NoDrop: action.RejectOptions.NoDrop, + logger: logger, } default: panic(F.ToString("unknown rule action: ", action.Action)) @@ -107,7 +115,11 @@ func (r *RuleActionReturn) String() string { } type RuleActionReject struct { - Method string + Method string + NoDrop bool + logger logger.ContextLogger + dropAccess sync.Mutex + dropCounter []time.Time } func (r *RuleActionReject) Type() string { @@ -121,21 +133,30 @@ func (r *RuleActionReject) String() string { return F.ToString("reject(", r.Method, ")") } -func (r *RuleActionReject) Error() error { +func (r *RuleActionReject) Error(ctx context.Context) error { + var returnErr error switch r.Method { - case C.RuleActionRejectMethodReset: - return os.ErrClosed - case C.RuleActionRejectMethodNetworkUnreachable: - return syscall.ENETUNREACH - case C.RuleActionRejectMethodHostUnreachable: - return syscall.EHOSTUNREACH - case C.RuleActionRejectMethodDefault, C.RuleActionRejectMethodPortUnreachable: - return syscall.ECONNREFUSED + case C.RuleActionRejectMethodDefault: + returnErr = unix.ECONNREFUSED case C.RuleActionRejectMethodDrop: return tun.ErrDrop default: panic(F.ToString("unknown reject method: ", r.Method)) } + r.dropAccess.Lock() + defer r.dropAccess.Unlock() + timeNow := time.Now() + r.dropCounter = common.Filter(r.dropCounter, func(t time.Time) bool { + return timeNow.Sub(t) <= 30*time.Second + }) + r.dropCounter = append(r.dropCounter, timeNow) + if len(r.dropCounter) > 50 { + if ctx != nil { + r.logger.DebugContext(ctx, "dropped due to flooding") + } + return tun.ErrDrop + } + return returnErr } type RuleActionHijackDNS struct{} diff --git a/route/rule/rule_default.go b/route/rule/rule_default.go index 4f5d1e8a3..a337c19f2 100644 --- a/route/rule/rule_default.go +++ b/route/rule/rule_default.go @@ -52,7 +52,7 @@ type RuleItem interface { } func NewDefaultRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.DefaultRule) (*DefaultRule, error) { - action, err := NewRuleAction(options.RuleAction) + action, err := NewRuleAction(logger, options.RuleAction) if err != nil { return nil, E.Cause(err, "action") } @@ -254,7 +254,7 @@ type LogicalRule struct { } func NewLogicalRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.LogicalRule) (*LogicalRule, error) { - action, err := NewRuleAction(options.RuleAction) + action, err := NewRuleAction(logger, options.RuleAction) if err != nil { return nil, E.Cause(err, "action") } diff --git a/route/rule/rule_dns.go b/route/rule/rule_dns.go index 6e57633d1..2218f6a30 100644 --- a/route/rule/rule_dns.go +++ b/route/rule/rule_dns.go @@ -51,7 +51,7 @@ func NewDefaultDNSRule(ctx context.Context, router adapter.Router, logger log.Co rule := &DefaultDNSRule{ abstractDefaultRule: abstractDefaultRule{ invert: options.Invert, - action: NewDNSRuleAction(options.DNSRuleAction), + action: NewDNSRuleAction(logger, options.DNSRuleAction), }, } if len(options.Inbound) > 0 { @@ -287,7 +287,7 @@ func NewLogicalDNSRule(ctx context.Context, router adapter.Router, logger log.Co abstractLogicalRule: abstractLogicalRule{ rules: make([]adapter.HeadlessRule, len(options.Rules)), invert: options.Invert, - action: NewDNSRuleAction(options.DNSRuleAction), + action: NewDNSRuleAction(logger, options.DNSRuleAction), }, } switch options.Mode { From 3a3ad11cb3aa78908d04992bd1b02def42d59feb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 6 Nov 2024 17:30:40 +0800 Subject: [PATCH 07/39] Improve rule actions --- adapter/inbound.go | 4 +- constant/rule.go | 13 +-- option/rule.go | 14 +-- option/rule_action.go | 182 ++++++++++++++++++++++++++++++++----- option/rule_dns.go | 14 +-- protocol/dns/handle.go | 1 + route/route.go | 89 +++++++++++------- route/route_dns.go | 75 ++++++++++++--- route/rule/rule_action.go | 111 ++++++++++++++++++---- route/rule/rule_default.go | 4 +- 10 files changed, 395 insertions(+), 112 deletions(-) diff --git a/adapter/inbound.go b/adapter/inbound.go index ca3e9e590..f9ed17085 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -57,7 +57,9 @@ type InboundContext struct { // Deprecated InboundOptions option.InboundOptions UDPDisableDomainUnmapping bool - DNSServer string + UDPConnect bool + + DNSServer string DestinationAddresses []netip.Addr SourceGeoIPCode string diff --git a/constant/rule.go b/constant/rule.go index 9a095555c..095c0a586 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -24,12 +24,13 @@ const ( ) const ( - RuleActionTypeRoute = "route" - RuleActionTypeReturn = "return" - RuleActionTypeReject = "reject" - RuleActionTypeHijackDNS = "hijack-dns" - RuleActionTypeSniff = "sniff" - RuleActionTypeResolve = "resolve" + RuleActionTypeRoute = "route" + RuleActionTypeRouteOptions = "route-options" + RuleActionTypeDirect = "direct" + RuleActionTypeReject = "reject" + RuleActionTypeHijackDNS = "hijack-dns" + RuleActionTypeSniff = "sniff" + RuleActionTypeResolve = "resolve" ) const ( diff --git a/option/rule.go b/option/rule.go index 07e6ddbec..952afa614 100644 --- a/option/rule.go +++ b/option/rule.go @@ -109,7 +109,7 @@ type DefaultRule struct { RuleAction } -func (r *DefaultRule) MarshalJSON() ([]byte, error) { +func (r DefaultRule) MarshalJSON() ([]byte, error) { return badjson.MarshallObjects(r.RawDefaultRule, r.RuleAction) } @@ -128,27 +128,27 @@ func (r *DefaultRule) IsValid() bool { return !reflect.DeepEqual(r, defaultValue) } -type _LogicalRule struct { +type RawLogicalRule struct { Mode string `json:"mode"` Rules []Rule `json:"rules,omitempty"` Invert bool `json:"invert,omitempty"` } type LogicalRule struct { - _LogicalRule + RawLogicalRule RuleAction } -func (r *LogicalRule) MarshalJSON() ([]byte, error) { - return badjson.MarshallObjects(r._LogicalRule, r.RuleAction) +func (r LogicalRule) MarshalJSON() ([]byte, error) { + return badjson.MarshallObjects(r.RawLogicalRule, r.RuleAction) } func (r *LogicalRule) UnmarshalJSON(data []byte) error { - err := json.Unmarshal(data, &r._LogicalRule) + err := json.Unmarshal(data, &r.RawLogicalRule) if err != nil { return err } - return badjson.UnmarshallExcluded(data, &r._LogicalRule, &r.RuleAction) + return badjson.UnmarshallExcluded(data, &r.RawLogicalRule, &r.RuleAction) } func (r *LogicalRule) IsValid() bool { diff --git a/option/rule_action.go b/option/rule_action.go index 3a40e1c06..edc197de0 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -1,30 +1,41 @@ package option import ( + "fmt" + "time" + C "github.com/sagernet/sing-box/constant" + dns "github.com/sagernet/sing-dns" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" ) type _RuleAction struct { - Action string `json:"action,omitempty"` - RouteOptions RouteActionOptions `json:"-"` - RejectOptions RejectActionOptions `json:"-"` - SniffOptions RouteActionSniff `json:"-"` - ResolveOptions RouteActionResolve `json:"-"` + Action string `json:"action,omitempty"` + RouteOptions RouteActionOptions `json:"-"` + RouteOptionsOptions RouteOptionsActionOptions `json:"-"` + DirectOptions DirectActionOptions `json:"-"` + RejectOptions RejectActionOptions `json:"-"` + SniffOptions RouteActionSniff `json:"-"` + ResolveOptions RouteActionResolve `json:"-"` } type RuleAction _RuleAction func (r RuleAction) MarshalJSON() ([]byte, error) { + if r.Action == "" { + return json.Marshal(struct{}{}) + } var v any switch r.Action { case C.RuleActionTypeRoute: r.Action = "" v = r.RouteOptions - case C.RuleActionTypeReturn: - v = nil + case C.RuleActionTypeRouteOptions: + v = r.RouteOptionsOptions + case C.RuleActionTypeDirect: + v = r.DirectOptions case C.RuleActionTypeReject: v = r.RejectOptions case C.RuleActionTypeHijackDNS: @@ -52,8 +63,10 @@ func (r *RuleAction) UnmarshalJSON(data []byte) error { case "", C.RuleActionTypeRoute: r.Action = C.RuleActionTypeRoute v = &r.RouteOptions - case C.RuleActionTypeReturn: - v = nil + case C.RuleActionTypeRouteOptions: + v = &r.RouteOptionsOptions + case C.RuleActionTypeDirect: + v = &r.DirectOptions case C.RuleActionTypeReject: v = &r.RejectOptions case C.RuleActionTypeHijackDNS: @@ -73,29 +86,30 @@ func (r *RuleAction) UnmarshalJSON(data []byte) error { } type _DNSRuleAction struct { - Action string `json:"action,omitempty"` - RouteOptions DNSRouteActionOptions `json:"-"` - RejectOptions RejectActionOptions `json:"-"` + Action string `json:"action,omitempty"` + RouteOptions DNSRouteActionOptions `json:"-"` + RouteOptionsOptions DNSRouteOptionsActionOptions `json:"-"` + RejectOptions RejectActionOptions `json:"-"` } type DNSRuleAction _DNSRuleAction func (r DNSRuleAction) MarshalJSON() ([]byte, error) { + if r.Action == "" { + return json.Marshal(struct{}{}) + } var v any switch r.Action { case C.RuleActionTypeRoute: r.Action = "" v = r.RouteOptions - case C.RuleActionTypeReturn: - v = nil + case C.RuleActionTypeRouteOptions: + v = r.RouteOptionsOptions case C.RuleActionTypeReject: v = r.RejectOptions default: return nil, E.New("unknown DNS rule action: " + r.Action) } - if v == nil { - return badjson.MarshallObjects((_DNSRuleAction)(r)) - } return badjson.MarshallObjects((_DNSRuleAction)(r), v) } @@ -109,8 +123,8 @@ func (r *DNSRuleAction) UnmarshalJSON(data []byte) error { case "", C.RuleActionTypeRoute: r.Action = C.RuleActionTypeRoute v = &r.RouteOptions - case C.RuleActionTypeReturn: - v = nil + case C.RuleActionTypeRouteOptions: + v = &r.RouteOptionsOptions case C.RuleActionTypeReject: v = &r.RejectOptions default: @@ -123,18 +137,136 @@ func (r *DNSRuleAction) UnmarshalJSON(data []byte) error { return badjson.UnmarshallExcluded(data, (*_DNSRuleAction)(r), v) } -type RouteActionOptions struct { - Outbound string `json:"outbound"` - UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` +type _RouteActionOptions struct { + Outbound string `json:"outbound,omitempty"` +} + +type RouteActionOptions _RouteActionOptions + +func (r *RouteActionOptions) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, (*_RouteActionOptions)(r)) + if err != nil { + return err + } + if r.Outbound == "" { + return E.New("missing outbound") + } + return nil +} + +type _RouteOptionsActionOptions struct { + UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` + UDPConnect bool `json:"udp_connect,omitempty"` +} + +type RouteOptionsActionOptions _RouteOptionsActionOptions + +func (r *RouteOptionsActionOptions) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, (*_RouteOptionsActionOptions)(r)) + if err != nil { + return err + } + if *r == (RouteOptionsActionOptions{}) { + return E.New("empty route option action") + } + return nil +} + +type _DNSRouteActionOptions struct { + Server string `json:"server,omitempty"` + // Deprecated: Use DNSRouteOptionsActionOptions instead. + DisableCache bool `json:"disable_cache,omitempty"` + // Deprecated: Use DNSRouteOptionsActionOptions instead. + RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` + // Deprecated: Use DNSRouteOptionsActionOptions instead. + ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` +} + +type DNSRouteActionOptions _DNSRouteActionOptions + +func (r *DNSRouteActionOptions) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, (*_DNSRouteActionOptions)(r)) + if err != nil { + return err + } + if r.Server == "" { + return E.New("missing server") + } + return nil } -type DNSRouteActionOptions struct { - Server string `json:"server"` +type _DNSRouteOptionsActionOptions struct { DisableCache bool `json:"disable_cache,omitempty"` RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` } +type DNSRouteOptionsActionOptions _DNSRouteOptionsActionOptions + +func (r *DNSRouteOptionsActionOptions) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, (*_DNSRouteOptionsActionOptions)(r)) + if err != nil { + return err + } + if *r == (DNSRouteOptionsActionOptions{}) { + return E.New("empty DNS route option action") + } + return nil +} + +type _DirectActionOptions DialerOptions + +type DirectActionOptions _DirectActionOptions + +func (d DirectActionOptions) Descriptions() []string { + var descriptions []string + if d.BindInterface != "" { + descriptions = append(descriptions, "bind_interface="+d.BindInterface) + } + if d.Inet4BindAddress != nil { + descriptions = append(descriptions, "inet4_bind_address="+d.Inet4BindAddress.Build().String()) + } + if d.Inet6BindAddress != nil { + descriptions = append(descriptions, "inet6_bind_address="+d.Inet6BindAddress.Build().String()) + } + if d.RoutingMark != 0 { + descriptions = append(descriptions, "routing_mark="+fmt.Sprintf("0x%x", d.RoutingMark)) + } + if d.ReuseAddr { + descriptions = append(descriptions, "reuse_addr") + } + if d.ConnectTimeout != 0 { + descriptions = append(descriptions, "connect_timeout="+time.Duration(d.ConnectTimeout).String()) + } + if d.TCPFastOpen { + descriptions = append(descriptions, "tcp_fast_open") + } + if d.TCPMultiPath { + descriptions = append(descriptions, "tcp_multi_path") + } + if d.UDPFragment != nil { + descriptions = append(descriptions, "udp_fragment="+fmt.Sprint(*d.UDPFragment)) + } + if d.DomainStrategy != DomainStrategy(dns.DomainStrategyAsIS) { + descriptions = append(descriptions, "domain_strategy="+d.DomainStrategy.String()) + } + if d.FallbackDelay != 0 { + descriptions = append(descriptions, "fallback_delay="+time.Duration(d.FallbackDelay).String()) + } + return descriptions +} + +func (d *DirectActionOptions) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, (*_DirectActionOptions)(d)) + if err != nil { + return err + } + if d.Detour != "" { + return E.New("detour is not available in the current context") + } + return nil +} + type _RejectActionOptions struct { Method string `json:"method,omitempty"` NoDrop bool `json:"no_drop,omitempty"` @@ -155,7 +287,7 @@ func (r *RejectActionOptions) UnmarshalJSON(bytes []byte) error { return E.New("unknown reject method: " + r.Method) } if r.Method == C.RuleActionRejectMethodDrop && r.NoDrop { - return E.New("no_drop is not allowed when method is drop") + return E.New("no_drop is not available in current context") } return nil } diff --git a/option/rule_dns.go b/option/rule_dns.go index 8c4b6ab83..949104c94 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -111,7 +111,7 @@ type DefaultDNSRule struct { DNSRuleAction } -func (r *DefaultDNSRule) MarshalJSON() ([]byte, error) { +func (r DefaultDNSRule) MarshalJSON() ([]byte, error) { return badjson.MarshallObjects(r.RawDefaultDNSRule, r.DNSRuleAction) } @@ -123,34 +123,34 @@ func (r *DefaultDNSRule) UnmarshalJSON(data []byte) error { return badjson.UnmarshallExcluded(data, &r.RawDefaultDNSRule, &r.DNSRuleAction) } -func (r *DefaultDNSRule) IsValid() bool { +func (r DefaultDNSRule) IsValid() bool { var defaultValue DefaultDNSRule defaultValue.Invert = r.Invert defaultValue.DNSRuleAction = r.DNSRuleAction return !reflect.DeepEqual(r, defaultValue) } -type _LogicalDNSRule struct { +type RawLogicalDNSRule struct { Mode string `json:"mode"` Rules []DNSRule `json:"rules,omitempty"` Invert bool `json:"invert,omitempty"` } type LogicalDNSRule struct { - _LogicalDNSRule + RawLogicalDNSRule DNSRuleAction } func (r *LogicalDNSRule) MarshalJSON() ([]byte, error) { - return badjson.MarshallObjects(r._LogicalDNSRule, r.DNSRuleAction) + return badjson.MarshallObjects(r.RawLogicalDNSRule, r.DNSRuleAction) } func (r *LogicalDNSRule) UnmarshalJSON(data []byte) error { - err := json.Unmarshal(data, &r._LogicalDNSRule) + err := json.Unmarshal(data, &r.RawLogicalDNSRule) if err != nil { return err } - return badjson.UnmarshallExcluded(data, &r._LogicalDNSRule, &r.DNSRuleAction) + return badjson.UnmarshallExcluded(data, &r.RawLogicalDNSRule, &r.DNSRuleAction) } func (r *LogicalDNSRule) IsValid() bool { diff --git a/protocol/dns/handle.go b/protocol/dns/handle.go index 23ed1c0c7..bc58d9e20 100644 --- a/protocol/dns/handle.go +++ b/protocol/dns/handle.go @@ -43,6 +43,7 @@ func HandleStreamDNSRequest(ctx context.Context, router adapter.Router, conn net go func() error { response, err := router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message) if err != nil { + conn.Close() return err } responseBuffer := buf.NewPacket() diff --git a/route/route.go b/route/route.go index 2c199a2d2..e6f0b37ce 100644 --- a/route/route.go +++ b/route/route.go @@ -91,19 +91,30 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad if err != nil { return err } - var selectedOutbound adapter.Outbound - var selectReturn bool + var ( + // selectedOutbound adapter.Outbound + selectedDialer N.Dialer + selectedTag string + selectedDescription string + ) if selectedRule != nil { switch action := selectedRule.Action().(type) { case *rule.RuleActionRoute: - var loaded bool - selectedOutbound, loaded = r.Outbound(action.Outbound) + selectedOutbound, loaded := r.Outbound(action.Outbound) if !loaded { buf.ReleaseMulti(buffers) return E.New("outbound not found: ", action.Outbound) } - case *rule.RuleActionReturn: - selectReturn = true + if !common.Contains(selectedOutbound.Network(), N.NetworkTCP) { + buf.ReleaseMulti(buffers) + return E.New("TCP is not supported by outbound: ", selectedOutbound.Tag()) + } + selectedDialer = selectedOutbound + selectedTag = selectedOutbound.Tag() + selectedDescription = F.ToString("outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]") + case *rule.RuleActionDirect: + selectedDialer = action.Dialer + selectedDescription = action.String() case *rule.RuleActionReject: buf.ReleaseMulti(buffers) N.CloseOnHandshakeFailure(conn, onClose, action.Error(ctx)) @@ -116,17 +127,16 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad return nil } } - if selectedRule == nil || selectReturn { + if selectedRule == nil { if r.defaultOutboundForConnection == nil { buf.ReleaseMulti(buffers) return E.New("missing default outbound with TCP support") } - selectedOutbound = r.defaultOutboundForConnection - } - if !common.Contains(selectedOutbound.Network(), N.NetworkTCP) { - buf.ReleaseMulti(buffers) - return E.New("TCP is not supported by outbound: ", selectedOutbound.Tag()) + selectedDialer = r.defaultOutboundForConnection + selectedTag = r.defaultOutboundForConnection.Tag() + selectedDescription = F.ToString("outbound/", r.defaultOutboundForConnection.Type(), "[", r.defaultOutboundForConnection.Tag(), "]") } + for _, buffer := range buffers { conn = bufio.NewCachedConn(conn, buffer) } @@ -137,10 +147,10 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad } if r.v2rayServer != nil { if statsService := r.v2rayServer.StatsService(); statsService != nil { - conn = statsService.RoutedConnection(metadata.Inbound, selectedOutbound.Tag(), metadata.User, conn) + conn = statsService.RoutedConnection(metadata.Inbound, selectedTag, metadata.User, conn) } } - legacyOutbound, isLegacy := selectedOutbound.(adapter.ConnectionHandler) + legacyOutbound, isLegacy := selectedDialer.(adapter.ConnectionHandler) if isLegacy { err = legacyOutbound.NewConnection(ctx, conn, metadata) if err != nil { @@ -148,7 +158,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad if onClose != nil { onClose(err) } - return E.Cause(err, "outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]") + return E.Cause(err, selectedDescription) } else { if onClose != nil { onClose(nil) @@ -157,13 +167,13 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad return nil } // TODO - err = outbound.NewConnection(ctx, selectedOutbound, conn, metadata) + err = outbound.NewConnection(ctx, selectedDialer, conn, metadata) if err != nil { conn.Close() if onClose != nil { onClose(err) } - return E.Cause(err, "outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]") + return E.Cause(err, selectedDescription) } else { if onClose != nil { onClose(nil) @@ -235,20 +245,30 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m if err != nil { return err } - var selectedOutbound adapter.Outbound + var ( + selectedDialer N.Dialer + selectedTag string + selectedDescription string + ) var selectReturn bool if selectedRule != nil { switch action := selectedRule.Action().(type) { case *rule.RuleActionRoute: - var loaded bool - selectedOutbound, loaded = r.Outbound(action.Outbound) + selectedOutbound, loaded := r.Outbound(action.Outbound) if !loaded { N.ReleaseMultiPacketBuffer(packetBuffers) return E.New("outbound not found: ", action.Outbound) } - metadata.UDPDisableDomainUnmapping = action.UDPDisableDomainUnmapping - case *rule.RuleActionReturn: - selectReturn = true + if !common.Contains(selectedOutbound.Network(), N.NetworkUDP) { + N.ReleaseMultiPacketBuffer(packetBuffers) + return E.New("UDP is not supported by outbound: ", selectedOutbound.Tag()) + } + selectedDialer = selectedOutbound + selectedTag = selectedOutbound.Tag() + selectedDescription = F.ToString("outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]") + case *rule.RuleActionDirect: + selectedDialer = action.Dialer + selectedDescription = action.String() case *rule.RuleActionReject: N.ReleaseMultiPacketBuffer(packetBuffers) N.CloseOnHandshakeFailure(conn, onClose, action.Error(ctx)) @@ -263,11 +283,9 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m N.ReleaseMultiPacketBuffer(packetBuffers) return E.New("missing default outbound with UDP support") } - selectedOutbound = r.defaultOutboundForPacketConnection - } - if !common.Contains(selectedOutbound.Network(), N.NetworkUDP) { - N.ReleaseMultiPacketBuffer(packetBuffers) - return E.New("UDP is not supported by outbound: ", selectedOutbound.Tag()) + selectedDialer = r.defaultOutboundForPacketConnection + selectedTag = r.defaultOutboundForPacketConnection.Tag() + selectedDescription = F.ToString("outbound/", r.defaultOutboundForPacketConnection.Type(), "[", r.defaultOutboundForPacketConnection.Tag(), "]") } for _, buffer := range packetBuffers { conn = bufio.NewCachedPacketConn(conn, buffer.Buffer, buffer.Destination) @@ -280,26 +298,26 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m } if r.v2rayServer != nil { if statsService := r.v2rayServer.StatsService(); statsService != nil { - conn = statsService.RoutedPacketConnection(metadata.Inbound, selectedOutbound.Tag(), metadata.User, conn) + conn = statsService.RoutedPacketConnection(metadata.Inbound, selectedTag, metadata.User, conn) } } if metadata.FakeIP { conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, metadata.Destination) } - legacyOutbound, isLegacy := selectedOutbound.(adapter.PacketConnectionHandler) + legacyOutbound, isLegacy := selectedDialer.(adapter.PacketConnectionHandler) if isLegacy { err = legacyOutbound.NewPacketConnection(ctx, conn, metadata) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { - return E.Cause(err, "outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]") + return E.Cause(err, selectedDescription) } return nil } // TODO - err = outbound.NewPacketConnection(ctx, selectedOutbound, conn, metadata) + err = outbound.NewPacketConnection(ctx, selectedDialer, conn, metadata) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { - return E.Cause(err, "outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]") + return E.Cause(err, selectedDescription) } return nil } @@ -444,7 +462,7 @@ match: } } else { switch currentRule.Action().Type() { - case C.RuleActionTypeReject, C.RuleActionTypeResolve: + case C.RuleActionTypeReject: ruleDescription := currentRule.String() if ruleDescription != "" { r.logger.DebugContext(ctx, "pre-match[", currentRuleIndex, "] ", currentRule, " => ", currentRule.Action()) @@ -454,6 +472,9 @@ match: } } switch action := currentRule.Action().(type) { + case *rule.RuleActionRouteOptions: + metadata.UDPDisableDomainUnmapping = action.UDPDisableDomainUnmapping + metadata.UDPConnect = action.UDPConnect case *rule.RuleActionSniff: if !preMatch { newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn) diff --git a/route/route_dns.go b/route/route_dns.go index 60aff6a9f..cd5dee1a9 100644 --- a/route/route_dns.go +++ b/route/route_dns.go @@ -8,8 +8,10 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" R "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing-dns" + tun "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common/cache" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" @@ -48,38 +50,63 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int, if ruleIndex != -1 { dnsRules = dnsRules[ruleIndex+1:] } - for currentRuleIndex, rule := range dnsRules { - if rule.WithAddressLimit() && !isAddressQuery { + for currentRuleIndex, currentRule := range dnsRules { + if currentRule.WithAddressLimit() && !isAddressQuery { continue } metadata.ResetRuleCache() - if rule.Match(metadata) { + if currentRule.Match(metadata) { displayRuleIndex := currentRuleIndex if displayRuleIndex != -1 { displayRuleIndex += displayRuleIndex + 1 } - if routeAction, isRoute := rule.Action().(*R.RuleActionDNSRoute); isRoute { - transport, loaded := r.transportMap[routeAction.Server] + ruleDescription := currentRule.String() + if ruleDescription != "" { + r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] ", currentRule, " => ", currentRule.Action()) + } else { + r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action()) + } + switch action := currentRule.Action().(type) { + case *R.RuleActionDNSRoute: + transport, loaded := r.transportMap[action.Server] if !loaded { - r.dnsLogger.ErrorContext(ctx, "transport not found: ", routeAction.Server) + r.dnsLogger.ErrorContext(ctx, "transport not found: ", action.Server) continue } _, isFakeIP := transport.(adapter.FakeIPTransport) if isFakeIP && !allowFakeIP { continue } - options.DisableCache = isFakeIP || routeAction.DisableCache - options.RewriteTTL = routeAction.RewriteTTL - options.ClientSubnet = routeAction.ClientSubnet + if isFakeIP || action.DisableCache { + options.DisableCache = true + } + if action.RewriteTTL != nil { + options.RewriteTTL = action.RewriteTTL + } + if action.ClientSubnet.IsValid() { + options.ClientSubnet = action.ClientSubnet + } if domainStrategy, dsLoaded := r.transportDomainStrategy[transport]; dsLoaded { options.Strategy = domainStrategy } else { options.Strategy = r.defaultDomainStrategy } - r.dnsLogger.DebugContext(ctx, "match[", displayRuleIndex, "] ", rule.String(), " => ", rule.Action()) - return transport, options, rule, currentRuleIndex - } else { - return nil, options, rule, currentRuleIndex + r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action()) + return transport, options, currentRule, currentRuleIndex + case *R.RuleActionDNSRouteOptions: + if action.DisableCache { + options.DisableCache = true + } + if action.RewriteTTL != nil { + options.RewriteTTL = action.RewriteTTL + } + if action.ClientSubnet.IsValid() { + options.ClientSubnet = action.ClientSubnet + } + r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action()) + case *R.RuleActionReject: + r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action()) + return nil, options, currentRule, currentRuleIndex } } } @@ -127,6 +154,17 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er dnsCtx := adapter.OverrideContext(ctx) var addressLimit bool transport, options, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex, isAddressQuery(message)) + if rule != nil { + switch action := rule.Action().(type) { + case *R.RuleActionReject: + switch action.Method { + case C.RuleActionRejectMethodDefault: + return dns.FixedResponse(message.Id, message.Question[0], nil, 0), nil + case C.RuleActionRejectMethodDrop: + return nil, tun.ErrDrop + } + } + } if rule != nil && rule.WithAddressLimit() { addressLimit = true response, err = r.dnsClient.ExchangeWithResponseCheck(dnsCtx, transport, message, options, func(response *mDNS.Msg) bool { @@ -238,6 +276,17 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS if strategy != dns.DomainStrategyAsIS { options.Strategy = strategy } + if rule != nil { + switch action := rule.Action().(type) { + case *R.RuleActionReject: + switch action.Method { + case C.RuleActionRejectMethodDefault: + return nil, nil + case C.RuleActionRejectMethodDrop: + return nil, tun.ErrDrop + } + } + } if rule != nil && rule.WithAddressLimit() { addressLimit = true responseAddrs, err = r.dnsClient.LookupWithResponseCheck(dnsCtx, transport, domain, options, func(responseAddrs []netip.Addr) bool { diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index 031f181c7..620260d07 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -5,9 +5,11 @@ import ( "net/netip" "strings" "sync" + "syscall" "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/sniff" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" @@ -17,19 +19,42 @@ import ( E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/logger" - - "golang.org/x/sys/unix" + N "github.com/sagernet/sing/common/network" ) -func NewRuleAction(logger logger.ContextLogger, action option.RuleAction) (adapter.RuleAction, error) { +func NewRuleAction(router adapter.Router, logger logger.ContextLogger, action option.RuleAction) (adapter.RuleAction, error) { switch action.Action { + case "": + return nil, nil case C.RuleActionTypeRoute: return &RuleActionRoute{ - Outbound: action.RouteOptions.Outbound, - UDPDisableDomainUnmapping: action.RouteOptions.UDPDisableDomainUnmapping, + Outbound: action.RouteOptions.Outbound, + }, nil + case C.RuleActionTypeRouteOptions: + return &RuleActionRouteOptions{ + UDPDisableDomainUnmapping: action.RouteOptionsOptions.UDPDisableDomainUnmapping, + UDPConnect: action.RouteOptionsOptions.UDPConnect, + }, nil + case C.RuleActionTypeDirect: + directDialer, err := dialer.New(router, option.DialerOptions(action.DirectOptions)) + if err != nil { + return nil, err + } + var description string + descriptions := action.DirectOptions.Descriptions() + switch len(descriptions) { + case 0: + case 1: + description = F.ToString("(", descriptions[0], ")") + case 2: + description = F.ToString("(", descriptions[0], ",", descriptions[1], ")") + default: + description = F.ToString("(", descriptions[0], ",", descriptions[1], ",...)") + } + return &RuleActionDirect{ + Dialer: directDialer, + description: description, }, nil - case C.RuleActionTypeReturn: - return &RuleActionReturn{}, nil case C.RuleActionTypeReject: return &RuleActionReject{ Method: action.RejectOptions.Method, @@ -56,6 +81,8 @@ func NewRuleAction(logger logger.ContextLogger, action option.RuleAction) (adapt func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction) adapter.RuleAction { switch action.Action { + case "": + return nil case C.RuleActionTypeRoute: return &RuleActionDNSRoute{ Server: action.RouteOptions.Server, @@ -63,8 +90,12 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction) RewriteTTL: action.RouteOptions.RewriteTTL, ClientSubnet: action.RouteOptions.ClientSubnet.Build(), } - case C.RuleActionTypeReturn: - return &RuleActionReturn{} + case C.RuleActionTypeRouteOptions: + return &RuleActionDNSRouteOptions{ + DisableCache: action.RouteOptionsOptions.DisableCache, + RewriteTTL: action.RouteOptionsOptions.RewriteTTL, + ClientSubnet: action.RouteOptionsOptions.ClientSubnet.Build(), + } case C.RuleActionTypeReject: return &RuleActionReject{ Method: action.RejectOptions.Method, @@ -77,8 +108,7 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction) } type RuleActionRoute struct { - Outbound string - UDPDisableDomainUnmapping bool + Outbound string } func (r *RuleActionRoute) Type() string { @@ -89,6 +119,26 @@ func (r *RuleActionRoute) String() string { return F.ToString("route(", r.Outbound, ")") } +type RuleActionRouteOptions struct { + UDPDisableDomainUnmapping bool + UDPConnect bool +} + +func (r *RuleActionRouteOptions) Type() string { + return C.RuleActionTypeRouteOptions +} + +func (r *RuleActionRouteOptions) String() string { + var descriptions []string + if r.UDPDisableDomainUnmapping { + descriptions = append(descriptions, "udp-disable-domain-unmapping") + } + if r.UDPConnect { + descriptions = append(descriptions, "udp-connect") + } + return F.ToString("route-options(", strings.Join(descriptions, ","), ")") +} + type RuleActionDNSRoute struct { Server string DisableCache bool @@ -104,14 +154,41 @@ func (r *RuleActionDNSRoute) String() string { return F.ToString("route(", r.Server, ")") } -type RuleActionReturn struct{} +type RuleActionDNSRouteOptions struct { + DisableCache bool + RewriteTTL *uint32 + ClientSubnet netip.Prefix +} + +func (r *RuleActionDNSRouteOptions) Type() string { + return C.RuleActionTypeRouteOptions +} + +func (r *RuleActionDNSRouteOptions) String() string { + var descriptions []string + if r.DisableCache { + descriptions = append(descriptions, "disable-cache") + } + if r.RewriteTTL != nil { + descriptions = append(descriptions, F.ToString("rewrite-ttl(", *r.RewriteTTL, ")")) + } + if r.ClientSubnet.IsValid() { + descriptions = append(descriptions, F.ToString("client-subnet(", r.ClientSubnet, ")")) + } + return F.ToString("route-options(", strings.Join(descriptions, ","), ")") +} + +type RuleActionDirect struct { + Dialer N.Dialer + description string +} -func (r *RuleActionReturn) Type() string { - return C.RuleActionTypeReturn +func (r *RuleActionDirect) Type() string { + return C.RuleActionTypeDirect } -func (r *RuleActionReturn) String() string { - return "return" +func (r *RuleActionDirect) String() string { + return "direct" + r.description } type RuleActionReject struct { @@ -137,7 +214,7 @@ func (r *RuleActionReject) Error(ctx context.Context) error { var returnErr error switch r.Method { case C.RuleActionRejectMethodDefault: - returnErr = unix.ECONNREFUSED + returnErr = syscall.ECONNREFUSED case C.RuleActionRejectMethodDrop: return tun.ErrDrop default: diff --git a/route/rule/rule_default.go b/route/rule/rule_default.go index a337c19f2..566c816e1 100644 --- a/route/rule/rule_default.go +++ b/route/rule/rule_default.go @@ -52,7 +52,7 @@ type RuleItem interface { } func NewDefaultRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.DefaultRule) (*DefaultRule, error) { - action, err := NewRuleAction(logger, options.RuleAction) + action, err := NewRuleAction(router, logger, options.RuleAction) if err != nil { return nil, E.Cause(err, "action") } @@ -254,7 +254,7 @@ type LogicalRule struct { } func NewLogicalRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.LogicalRule) (*LogicalRule, error) { - action, err := NewRuleAction(logger, options.RuleAction) + action, err := NewRuleAction(router, logger, options.RuleAction) if err != nil { return nil, E.Cause(err, "action") } From b75dbc8a26ae17509abc2939aebbc72e35f0ad79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 7 Nov 2024 12:02:36 +0800 Subject: [PATCH 08/39] Implement new deprecated warnings --- experimental/deprecated/constants.go | 40 +++++++++++++++++++++++++--- experimental/deprecated/manager.go | 1 - experimental/libbox/service.go | 4 +-- option/outbound.go | 11 ++++++++ option/rule_action.go | 15 ++++++----- option/rule_dns.go | 13 ++++----- 6 files changed, 65 insertions(+), 19 deletions(-) diff --git a/experimental/deprecated/constants.go b/experimental/deprecated/constants.go index 44918857d..7830452f2 100644 --- a/experimental/deprecated/constants.go +++ b/experimental/deprecated/constants.go @@ -1,6 +1,7 @@ package deprecated import ( + "github.com/sagernet/sing-box/common/badversion" C "github.com/sagernet/sing-box/constant" F "github.com/sagernet/sing/common/format" @@ -23,11 +24,12 @@ func (n Note) Impending() bool { if !semver.IsValid("v" + C.Version) { return false } - versionMinor := semver.Compare(semver.MajorMinor("v"+C.Version), "v"+n.ScheduledVersion) - if semver.Prerelease("v"+C.Version) == "" && versionMinor > 0 { + versionCurrent := badversion.Parse(C.Version) + versionMinor := badversion.Parse(n.ScheduledVersion).Minor - versionCurrent.Minor + if versionCurrent.PreReleaseIdentifier == "" && versionMinor < 0 { panic("invalid deprecated note: " + n.Name) } - return versionMinor >= -1 + return versionMinor <= 1 } func (n Note) Message() string { @@ -49,6 +51,7 @@ var OptionBadMatchSource = Note{ Description: "legacy match source rule item", DeprecatedVersion: "1.10.0", ScheduledVersion: "1.11.0", + EnvName: "BAD_MATCH_SOURCE", MigrationLink: "https://sing-box.sagernet.org/deprecated/#match-source-rule-items-are-renamed", } @@ -75,12 +78,43 @@ var OptionTUNAddressX = Note{ Description: "legacy tun address fields", DeprecatedVersion: "1.10.0", ScheduledVersion: "1.12.0", + EnvName: "TUN_ADDRESS_X", MigrationLink: "https://sing-box.sagernet.org/migration/#tun-address-fields-are-merged", } +var OptionSpecialOutbounds = Note{ + Name: "special-outbounds", + Description: "legacy special outbounds", + DeprecatedVersion: "1.11.0", + ScheduledVersion: "1.13.0", + EnvName: "SPECIAL_OUTBOUNDS", + MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-legacy-special-outbounds-to-rule-actions", +} + +var OptionInboundOptions = Note{ + Name: "inbound-options", + Description: "legacy inbound fields", + DeprecatedVersion: "1.11.0", + ScheduledVersion: "1.13.0", + EnvName: "INBOUND_OPTIONS", + MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-legacy-special-outbounds-to-rule-actions", +} + +var OptionLegacyDNSRouteOptions = Note{ + Name: "legacy-dns-route-options", + Description: "legacy dns route options", + DeprecatedVersion: "1.11.0", + ScheduledVersion: "1.12.0", + EnvName: "LEGACY_DNS_ROUTE_OPTIONS", + MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-legacy-dns-route-options-to-rule-actions", +} + var Options = []Note{ OptionBadMatchSource, OptionGEOIP, OptionGEOSITE, OptionTUNAddressX, + OptionSpecialOutbounds, + OptionInboundOptions, + OptionLegacyDNSRouteOptions, } diff --git a/experimental/deprecated/manager.go b/experimental/deprecated/manager.go index d12acf48f..d7cae1797 100644 --- a/experimental/deprecated/manager.go +++ b/experimental/deprecated/manager.go @@ -2,7 +2,6 @@ package deprecated import ( "context" - "github.com/sagernet/sing/service" ) diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index eadd4c276..dcb0370f3 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -43,16 +43,16 @@ type BoxService struct { func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) { ctx := box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry()) + ctx = service.ContextWith[deprecated.Manager](ctx, new(deprecatedManager)) + ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID) options, err := parseConfig(ctx, configContent) if err != nil { return nil, err } runtimeDebug.FreeOSMemory() ctx, cancel := context.WithCancel(ctx) - ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID) urlTestHistoryStorage := urltest.NewHistoryStorage() ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage) - ctx = service.ContextWith[deprecated.Manager](ctx, new(deprecatedManager)) platformWrapper := &platformInterfaceWrapper{iif: platformInterface, useProcFS: platformInterface.UseProcFS()} ctx = service.ContextWith[platform.Interface](ctx, platformWrapper) instance, err := box.New(box.Options{ diff --git a/option/outbound.go b/option/outbound.go index 00a20aa57..1dddb3548 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -3,6 +3,8 @@ package option import ( "context" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/experimental/deprecated" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" @@ -35,6 +37,10 @@ func (h *Outbound) UnmarshalJSONContext(ctx context.Context, content []byte) err if registry == nil { return E.New("missing outbound options registry in context") } + switch h.Type { + case C.TypeBlock, C.TypeDNS: + deprecated.Report(ctx, deprecated.OptionSpecialOutbounds) + } options, loaded := registry.CreateOptions(h.Type) if !loaded { return E.New("unknown outbound type: ", h.Type) @@ -43,6 +49,11 @@ func (h *Outbound) UnmarshalJSONContext(ctx context.Context, content []byte) err if err != nil { return err } + if listenWrapper, isListen := options.(ListenOptionsWrapper); isListen { + if listenWrapper.TakeListenOptions().InboundOptions != (InboundOptions{}) { + deprecated.Report(ctx, deprecated.OptionInboundOptions) + } + } h.Options = options return nil } diff --git a/option/rule_action.go b/option/rule_action.go index edc197de0..a7244e179 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -1,10 +1,12 @@ package option import ( + "context" "fmt" "time" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/experimental/deprecated" dns "github.com/sagernet/sing-dns" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" @@ -113,7 +115,7 @@ func (r DNSRuleAction) MarshalJSON() ([]byte, error) { return badjson.MarshallObjects((_DNSRuleAction)(r), v) } -func (r *DNSRuleAction) UnmarshalJSON(data []byte) error { +func (r *DNSRuleAction) UnmarshalJSONContext(ctx context.Context, data []byte) error { err := json.Unmarshal(data, (*_DNSRuleAction)(r)) if err != nil { return err @@ -130,11 +132,7 @@ func (r *DNSRuleAction) UnmarshalJSON(data []byte) error { default: return E.New("unknown DNS rule action: " + r.Action) } - if v == nil { - // check unknown fields - return json.UnmarshalDisallowUnknownFields(data, &_DNSRuleAction{}) - } - return badjson.UnmarshallExcluded(data, (*_DNSRuleAction)(r), v) + return badjson.UnmarshallExcludedContext(ctx, data, (*_DNSRuleAction)(r), v) } type _RouteActionOptions struct { @@ -184,7 +182,7 @@ type _DNSRouteActionOptions struct { type DNSRouteActionOptions _DNSRouteActionOptions -func (r *DNSRouteActionOptions) UnmarshalJSON(data []byte) error { +func (r *DNSRouteActionOptions) UnmarshalJSONContext(ctx context.Context, data []byte) error { err := json.Unmarshal(data, (*_DNSRouteActionOptions)(r)) if err != nil { return err @@ -192,6 +190,9 @@ func (r *DNSRouteActionOptions) UnmarshalJSON(data []byte) error { if r.Server == "" { return E.New("missing server") } + if r.DisableCache || r.RewriteTTL != nil || r.ClientSubnet != nil { + deprecated.Report(ctx, deprecated.OptionLegacyDNSRouteOptions) + } return nil } diff --git a/option/rule_dns.go b/option/rule_dns.go index 949104c94..894e95d34 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -1,6 +1,7 @@ package option import ( + "context" "reflect" C "github.com/sagernet/sing-box/constant" @@ -32,7 +33,7 @@ func (r DNSRule) MarshalJSON() ([]byte, error) { return badjson.MarshallObjects((_DNSRule)(r), v) } -func (r *DNSRule) UnmarshalJSON(bytes []byte) error { +func (r *DNSRule) UnmarshalJSONContext(ctx context.Context, bytes []byte) error { err := json.Unmarshal(bytes, (*_DNSRule)(r)) if err != nil { return err @@ -47,7 +48,7 @@ func (r *DNSRule) UnmarshalJSON(bytes []byte) error { default: return E.New("unknown rule type: " + r.Type) } - err = badjson.UnmarshallExcluded(bytes, (*_DNSRule)(r), v) + err = badjson.UnmarshallExcludedContext(ctx, bytes, (*_DNSRule)(r), v) if err != nil { return err } @@ -115,12 +116,12 @@ func (r DefaultDNSRule) MarshalJSON() ([]byte, error) { return badjson.MarshallObjects(r.RawDefaultDNSRule, r.DNSRuleAction) } -func (r *DefaultDNSRule) UnmarshalJSON(data []byte) error { +func (r *DefaultDNSRule) UnmarshalJSONContext(ctx context.Context, data []byte) error { err := json.Unmarshal(data, &r.RawDefaultDNSRule) if err != nil { return err } - return badjson.UnmarshallExcluded(data, &r.RawDefaultDNSRule, &r.DNSRuleAction) + return badjson.UnmarshallExcludedContext(ctx, data, &r.RawDefaultDNSRule, &r.DNSRuleAction) } func (r DefaultDNSRule) IsValid() bool { @@ -145,12 +146,12 @@ func (r *LogicalDNSRule) MarshalJSON() ([]byte, error) { return badjson.MarshallObjects(r.RawLogicalDNSRule, r.DNSRuleAction) } -func (r *LogicalDNSRule) UnmarshalJSON(data []byte) error { +func (r *LogicalDNSRule) UnmarshalJSONContext(ctx context.Context, data []byte) error { err := json.Unmarshal(data, &r.RawLogicalDNSRule) if err != nil { return err } - return badjson.UnmarshallExcluded(data, &r.RawLogicalDNSRule, &r.DNSRuleAction) + return badjson.UnmarshallExcludedContext(ctx, data, &r.RawLogicalDNSRule, &r.DNSRuleAction) } func (r *LogicalDNSRule) IsValid() bool { From 85f634d0cb51064f19f7657a79123f1bc5cc1035 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 7 Nov 2024 13:44:00 +0800 Subject: [PATCH 09/39] Implement udp connect --- adapter/outbound/default.go | 110 ++++++++++++++++++++++++++---------- 1 file changed, 79 insertions(+), 31 deletions(-) diff --git a/adapter/outbound/default.go b/adapter/outbound/default.go index 78b9bfd8d..bb58ff54d 100644 --- a/adapter/outbound/default.go +++ b/adapter/outbound/default.go @@ -20,6 +20,7 @@ import ( ) func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext) error { + defer conn.Close() ctx = adapter.WithContext(ctx, &metadata) var outConn net.Conn var err error @@ -40,6 +41,7 @@ func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata a } func NewDirectConnection(ctx context.Context, router adapter.Router, this N.Dialer, conn net.Conn, metadata adapter.InboundContext, domainStrategy dns.DomainStrategy) error { + defer conn.Close() ctx = adapter.WithContext(ctx, &metadata) var outConn net.Conn var err error @@ -67,29 +69,49 @@ func NewDirectConnection(ctx context.Context, router adapter.Router, this N.Dial } func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata adapter.InboundContext) error { + defer conn.Close() ctx = adapter.WithContext(ctx, &metadata) - var outConn net.PacketConn - var destinationAddress netip.Addr - var err error - if len(metadata.DestinationAddresses) > 0 { - outConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses) + var ( + outPacketConn net.PacketConn + outConn net.Conn + destinationAddress netip.Addr + err error + ) + if metadata.UDPConnect { + if len(metadata.DestinationAddresses) > 0 { + outConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses) + } else { + outConn, err = this.DialContext(ctx, N.NetworkUDP, metadata.Destination) + } + if err != nil { + return N.ReportHandshakeFailure(conn, err) + } + outPacketConn = bufio.NewUnbindPacketConn(outConn) + connRemoteAddr := M.AddrFromNet(outConn.RemoteAddr()) + if connRemoteAddr != metadata.Destination.Addr { + destinationAddress = connRemoteAddr + } } else { - outConn, err = this.ListenPacket(ctx, metadata.Destination) - } - if err != nil { - return N.ReportHandshakeFailure(conn, err) + if len(metadata.DestinationAddresses) > 0 { + outPacketConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses) + } else { + outPacketConn, err = this.ListenPacket(ctx, metadata.Destination) + } + if err != nil { + return N.ReportHandshakeFailure(conn, err) + } } - err = N.ReportPacketConnHandshakeSuccess(conn, outConn) + err = N.ReportPacketConnHandshakeSuccess(conn, outPacketConn) if err != nil { - outConn.Close() + outPacketConn.Close() return err } if destinationAddress.IsValid() { if metadata.Destination.IsFqdn() { if metadata.UDPDisableDomainUnmapping { - outConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination) + outPacketConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination) } else { - outConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination) + outPacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination) } } if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded { @@ -104,37 +126,63 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, case C.ProtocolDNS: ctx, conn = canceler.NewPacketConn(ctx, conn, C.DNSTimeout) } - return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outConn)) + return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outPacketConn)) } func NewDirectPacketConnection(ctx context.Context, router adapter.Router, this N.Dialer, conn N.PacketConn, metadata adapter.InboundContext, domainStrategy dns.DomainStrategy) error { + defer conn.Close() ctx = adapter.WithContext(ctx, &metadata) - var outConn net.PacketConn - var destinationAddress netip.Addr - var err error - if len(metadata.DestinationAddresses) > 0 { - outConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses) - } else if metadata.Destination.IsFqdn() { - var destinationAddresses []netip.Addr - destinationAddresses, err = router.Lookup(ctx, metadata.Destination.Fqdn, domainStrategy) + var ( + outPacketConn net.PacketConn + outConn net.Conn + destinationAddress netip.Addr + err error + ) + if metadata.UDPConnect { + if len(metadata.DestinationAddresses) > 0 { + outConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses) + } else if metadata.Destination.IsFqdn() { + var destinationAddresses []netip.Addr + destinationAddresses, err = router.Lookup(ctx, metadata.Destination.Fqdn, domainStrategy) + if err != nil { + return N.ReportHandshakeFailure(conn, err) + } + outConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, destinationAddresses) + } else { + outConn, err = this.DialContext(ctx, N.NetworkUDP, metadata.Destination) + } if err != nil { return N.ReportHandshakeFailure(conn, err) } - outConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, destinationAddresses) + connRemoteAddr := M.AddrFromNet(outConn.RemoteAddr()) + if connRemoteAddr != metadata.Destination.Addr { + destinationAddress = connRemoteAddr + } } else { - outConn, err = this.ListenPacket(ctx, metadata.Destination) - } - if err != nil { - return N.ReportHandshakeFailure(conn, err) + if len(metadata.DestinationAddresses) > 0 { + outPacketConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses) + } else if metadata.Destination.IsFqdn() { + var destinationAddresses []netip.Addr + destinationAddresses, err = router.Lookup(ctx, metadata.Destination.Fqdn, domainStrategy) + if err != nil { + return N.ReportHandshakeFailure(conn, err) + } + outPacketConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, destinationAddresses) + } else { + outPacketConn, err = this.ListenPacket(ctx, metadata.Destination) + } + if err != nil { + return N.ReportHandshakeFailure(conn, err) + } } - err = N.ReportPacketConnHandshakeSuccess(conn, outConn) + err = N.ReportPacketConnHandshakeSuccess(conn, outPacketConn) if err != nil { - outConn.Close() + outPacketConn.Close() return err } if destinationAddress.IsValid() { if metadata.Destination.IsFqdn() { - outConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination) + outPacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination) } if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded { natConn.UpdateDestination(destinationAddress) @@ -148,7 +196,7 @@ func NewDirectPacketConnection(ctx context.Context, router adapter.Router, this case C.ProtocolDNS: ctx, conn = canceler.NewPacketConn(ctx, conn, C.DNSTimeout) } - return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outConn)) + return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outPacketConn)) } func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) error { From 1a230bda5d07f652d2d23ec01b4e6fb69ddb29fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 7 Nov 2024 21:44:04 +0800 Subject: [PATCH 10/39] Migrate bad options to library --- common/dialer/default.go | 5 +- common/listener/listener.go | 3 +- common/listener/listener_tcp.go | 3 +- common/listener/listener_udp.go | 3 +- experimental/deprecated/manager.go | 1 + option/dns.go | 32 ++-- option/experimental.go | 32 ++-- option/group.go | 14 +- option/inbound.go | 35 ++--- option/ntp.go | 8 +- option/outbound.go | 31 ++-- option/platform.go | 7 +- option/rule.go | 67 ++++----- option/rule_action.go | 18 ++- option/rule_dns.go | 71 ++++----- option/rule_set.go | 49 ++++--- option/simple.go | 9 +- option/ssh.go | 18 ++- option/time_unit.go | 226 ----------------------------- option/tls.go | 80 +++++----- option/tls_acme.go | 3 +- option/tuic.go | 28 ++-- option/tun.go | 69 ++++----- option/tun_platform.go | 6 +- option/types.go | 132 ----------------- option/v2ray_transport.go | 37 ++--- option/wireguard.go | 26 ++-- protocol/tun/inbound.go | 3 +- route/router.go | 4 +- route/rule/rule_action.go | 4 +- test/brutal_test.go | 26 ++-- test/direct_test.go | 8 +- test/domain_inbound_test.go | 8 +- test/ech_test.go | 19 +-- test/go.mod | 18 +-- test/go.sum | 51 ++----- test/http_test.go | 8 +- test/hysteria2_test.go | 16 +- test/hysteria_test.go | 16 +- test/inbound_detour_test.go | 14 +- test/mux_cool_test.go | 16 +- test/mux_test.go | 14 +- test/naive_test.go | 14 +- test/shadowsocks_legacy_test.go | 6 +- test/shadowsocks_test.go | 28 ++-- test/shadowtls_test.go | 37 +++-- test/ss_plugin_test.go | 6 +- test/tfo_test.go | 8 +- test/tls_test.go | 8 +- test/trojan_test.go | 18 ++- test/tuic_test.go | 16 +- test/v2ray_api_test.go | 6 +- test/v2ray_grpc_test.go | 10 +- test/v2ray_transport_test.go | 26 ++-- test/v2ray_ws_test.go | 10 +- test/vmess_test.go | 16 +- test/wireguard_test.go | 6 +- transport/sip003/v2ray.go | 3 +- 58 files changed, 579 insertions(+), 877 deletions(-) delete mode 100644 option/time_unit.go diff --git a/common/dialer/default.go b/common/dialer/default.go index b8a0d5f45..fcc5e60ec 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -3,6 +3,7 @@ package dialer import ( "context" "net" + "net/netip" "time" "github.com/sagernet/sing-box/adapter" @@ -102,7 +103,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi udpAddr4 string ) if options.Inet4BindAddress != nil { - bindAddr := options.Inet4BindAddress.Build() + bindAddr := options.Inet4BindAddress.Build(netip.IPv4Unspecified()) dialer4.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()} udpDialer4.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()} udpAddr4 = M.SocksaddrFrom(bindAddr, 0).String() @@ -113,7 +114,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi udpAddr6 string ) if options.Inet6BindAddress != nil { - bindAddr := options.Inet6BindAddress.Build() + bindAddr := options.Inet6BindAddress.Build(netip.IPv6Unspecified()) dialer6.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()} udpDialer6.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()} udpAddr6 = M.SocksaddrFrom(bindAddr, 0).String() diff --git a/common/listener/listener.go b/common/listener/listener.go index b42b0434b..289f15c51 100644 --- a/common/listener/listener.go +++ b/common/listener/listener.go @@ -3,6 +3,7 @@ package listener import ( "context" "net" + "net/netip" "sync/atomic" "github.com/sagernet/sing-box/adapter" @@ -92,7 +93,7 @@ func (l *Listener) Start() error { if l.setSystemProxy { listenPort := M.SocksaddrFromNet(l.tcpListener.Addr()).Port var listenAddrString string - listenAddr := l.listenOptions.Listen.Build() + listenAddr := l.listenOptions.Listen.Build(netip.IPv4Unspecified()) if listenAddr.IsUnspecified() { listenAddrString = "127.0.0.1" } else { diff --git a/common/listener/listener_tcp.go b/common/listener/listener_tcp.go index 02cef3f04..646d40179 100644 --- a/common/listener/listener_tcp.go +++ b/common/listener/listener_tcp.go @@ -2,6 +2,7 @@ package listener import ( "net" + "net/netip" "time" "github.com/sagernet/sing-box/adapter" @@ -16,7 +17,7 @@ import ( func (l *Listener) ListenTCP() (net.Listener, error) { var err error - bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(), l.listenOptions.ListenPort) + bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort) var tcpListener net.Listener var listenConfig net.ListenConfig if l.listenOptions.TCPKeepAlive >= 0 { diff --git a/common/listener/listener_udp.go b/common/listener/listener_udp.go index d871c6726..9be232130 100644 --- a/common/listener/listener_udp.go +++ b/common/listener/listener_udp.go @@ -2,6 +2,7 @@ package listener import ( "net" + "net/netip" "os" "time" @@ -13,7 +14,7 @@ import ( ) func (l *Listener) ListenUDP() (net.PacketConn, error) { - bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(), l.listenOptions.ListenPort) + bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort) var lc net.ListenConfig var udpFragment bool if l.listenOptions.UDPFragment != nil { diff --git a/experimental/deprecated/manager.go b/experimental/deprecated/manager.go index d7cae1797..d12acf48f 100644 --- a/experimental/deprecated/manager.go +++ b/experimental/deprecated/manager.go @@ -2,6 +2,7 @@ package deprecated import ( "context" + "github.com/sagernet/sing/service" ) diff --git a/option/dns.go b/option/dns.go index be947583a..32c1ac2e1 100644 --- a/option/dns.go +++ b/option/dns.go @@ -1,6 +1,10 @@ package option -import "net/netip" +import ( + "net/netip" + + "github.com/sagernet/sing/common/json/badoption" +) type DNSOptions struct { Servers []DNSServerOptions `json:"servers,omitempty"` @@ -12,22 +16,22 @@ type DNSOptions struct { } type DNSServerOptions struct { - Tag string `json:"tag,omitempty"` - Address string `json:"address"` - AddressResolver string `json:"address_resolver,omitempty"` - AddressStrategy DomainStrategy `json:"address_strategy,omitempty"` - AddressFallbackDelay Duration `json:"address_fallback_delay,omitempty"` - Strategy DomainStrategy `json:"strategy,omitempty"` - Detour string `json:"detour,omitempty"` - ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` + Tag string `json:"tag,omitempty"` + Address string `json:"address"` + AddressResolver string `json:"address_resolver,omitempty"` + AddressStrategy DomainStrategy `json:"address_strategy,omitempty"` + AddressFallbackDelay badoption.Duration `json:"address_fallback_delay,omitempty"` + Strategy DomainStrategy `json:"strategy,omitempty"` + Detour string `json:"detour,omitempty"` + ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"` } type DNSClientOptions struct { - Strategy DomainStrategy `json:"strategy,omitempty"` - DisableCache bool `json:"disable_cache,omitempty"` - DisableExpire bool `json:"disable_expire,omitempty"` - IndependentCache bool `json:"independent_cache,omitempty"` - ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` + Strategy DomainStrategy `json:"strategy,omitempty"` + DisableCache bool `json:"disable_cache,omitempty"` + DisableExpire bool `json:"disable_expire,omitempty"` + IndependentCache bool `json:"independent_cache,omitempty"` + ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"` } type DNSFakeIPOptions struct { diff --git a/option/experimental.go b/option/experimental.go index 6ab663855..bf0df9e78 100644 --- a/option/experimental.go +++ b/option/experimental.go @@ -1,5 +1,7 @@ package option +import "github.com/sagernet/sing/common/json/badoption" + type ExperimentalOptions struct { CacheFile *CacheFileOptions `json:"cache_file,omitempty"` ClashAPI *ClashAPIOptions `json:"clash_api,omitempty"` @@ -8,24 +10,24 @@ type ExperimentalOptions struct { } type CacheFileOptions struct { - Enabled bool `json:"enabled,omitempty"` - Path string `json:"path,omitempty"` - CacheID string `json:"cache_id,omitempty"` - StoreFakeIP bool `json:"store_fakeip,omitempty"` - StoreRDRC bool `json:"store_rdrc,omitempty"` - RDRCTimeout Duration `json:"rdrc_timeout,omitempty"` + Enabled bool `json:"enabled,omitempty"` + Path string `json:"path,omitempty"` + CacheID string `json:"cache_id,omitempty"` + StoreFakeIP bool `json:"store_fakeip,omitempty"` + StoreRDRC bool `json:"store_rdrc,omitempty"` + RDRCTimeout badoption.Duration `json:"rdrc_timeout,omitempty"` } type ClashAPIOptions struct { - ExternalController string `json:"external_controller,omitempty"` - ExternalUI string `json:"external_ui,omitempty"` - ExternalUIDownloadURL string `json:"external_ui_download_url,omitempty"` - ExternalUIDownloadDetour string `json:"external_ui_download_detour,omitempty"` - Secret string `json:"secret,omitempty"` - DefaultMode string `json:"default_mode,omitempty"` - ModeList []string `json:"-"` - AccessControlAllowOrigin Listable[string] `json:"access_control_allow_origin,omitempty"` - AccessControlAllowPrivateNetwork bool `json:"access_control_allow_private_network,omitempty"` + ExternalController string `json:"external_controller,omitempty"` + ExternalUI string `json:"external_ui,omitempty"` + ExternalUIDownloadURL string `json:"external_ui_download_url,omitempty"` + ExternalUIDownloadDetour string `json:"external_ui_download_detour,omitempty"` + Secret string `json:"secret,omitempty"` + DefaultMode string `json:"default_mode,omitempty"` + ModeList []string `json:"-"` + AccessControlAllowOrigin badoption.Listable[string] `json:"access_control_allow_origin,omitempty"` + AccessControlAllowPrivateNetwork bool `json:"access_control_allow_private_network,omitempty"` // Deprecated: migrated to global cache file CacheFile string `json:"cache_file,omitempty"` diff --git a/option/group.go b/option/group.go index 72a0f6370..02b3a5ecb 100644 --- a/option/group.go +++ b/option/group.go @@ -1,5 +1,7 @@ package option +import "github.com/sagernet/sing/common/json/badoption" + type SelectorOutboundOptions struct { Outbounds []string `json:"outbounds"` Default string `json:"default,omitempty"` @@ -7,10 +9,10 @@ type SelectorOutboundOptions struct { } type URLTestOutboundOptions struct { - Outbounds []string `json:"outbounds"` - URL string `json:"url,omitempty"` - Interval Duration `json:"interval,omitempty"` - Tolerance uint16 `json:"tolerance,omitempty"` - IdleTimeout Duration `json:"idle_timeout,omitempty"` - InterruptExistConnections bool `json:"interrupt_exist_connections,omitempty"` + Outbounds []string `json:"outbounds"` + URL string `json:"url,omitempty"` + Interval badoption.Duration `json:"interval,omitempty"` + Tolerance uint16 `json:"tolerance,omitempty"` + IdleTimeout badoption.Duration `json:"idle_timeout,omitempty"` + InterruptExistConnections bool `json:"interrupt_exist_connections,omitempty"` } diff --git a/option/inbound.go b/option/inbound.go index 651d02846..a67719fa0 100644 --- a/option/inbound.go +++ b/option/inbound.go @@ -7,6 +7,7 @@ import ( E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" "github.com/sagernet/sing/service" ) @@ -49,24 +50,24 @@ func (h *Inbound) UnmarshalJSONContext(ctx context.Context, content []byte) erro // Deprecated: Use rule action instead type InboundOptions struct { - SniffEnabled bool `json:"sniff,omitempty"` - SniffOverrideDestination bool `json:"sniff_override_destination,omitempty"` - SniffTimeout Duration `json:"sniff_timeout,omitempty"` - DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` - UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` - Detour string `json:"detour,omitempty"` + SniffEnabled bool `json:"sniff,omitempty"` + SniffOverrideDestination bool `json:"sniff_override_destination,omitempty"` + SniffTimeout badoption.Duration `json:"sniff_timeout,omitempty"` + DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` + UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` + Detour string `json:"detour,omitempty"` } type ListenOptions struct { - Listen *ListenAddress `json:"listen,omitempty"` - ListenPort uint16 `json:"listen_port,omitempty"` - TCPKeepAlive Duration `json:"tcp_keep_alive,omitempty"` - TCPKeepAliveInterval Duration `json:"tcp_keep_alive_interval,omitempty"` - TCPFastOpen bool `json:"tcp_fast_open,omitempty"` - TCPMultiPath bool `json:"tcp_multi_path,omitempty"` - UDPFragment *bool `json:"udp_fragment,omitempty"` - UDPFragmentDefault bool `json:"-"` - UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` + Listen *badoption.Addr `json:"listen,omitempty"` + ListenPort uint16 `json:"listen_port,omitempty"` + TCPKeepAlive badoption.Duration `json:"tcp_keep_alive,omitempty"` + TCPKeepAliveInterval badoption.Duration `json:"tcp_keep_alive_interval,omitempty"` + TCPFastOpen bool `json:"tcp_fast_open,omitempty"` + TCPMultiPath bool `json:"tcp_multi_path,omitempty"` + UDPFragment *bool `json:"udp_fragment,omitempty"` + UDPFragmentDefault bool `json:"-"` + UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` // Deprecated: removed ProxyProtocol bool `json:"proxy_protocol,omitempty"` @@ -75,7 +76,7 @@ type ListenOptions struct { InboundOptions } -type UDPTimeoutCompat Duration +type UDPTimeoutCompat badoption.Duration func (c UDPTimeoutCompat) MarshalJSON() ([]byte, error) { return json.Marshal((time.Duration)(c).String()) @@ -88,7 +89,7 @@ func (c *UDPTimeoutCompat) UnmarshalJSON(data []byte) error { *c = UDPTimeoutCompat(time.Second * time.Duration(valueNumber)) return nil } - return json.Unmarshal(data, (*Duration)(c)) + return json.Unmarshal(data, (*badoption.Duration)(c)) } type ListenOptionsWrapper interface { diff --git a/option/ntp.go b/option/ntp.go index 0bd2489ac..d441d95e8 100644 --- a/option/ntp.go +++ b/option/ntp.go @@ -1,9 +1,11 @@ package option +import "github.com/sagernet/sing/common/json/badoption" + type NTPOptions struct { - Enabled bool `json:"enabled,omitempty"` - Interval Duration `json:"interval,omitempty"` - WriteToSystem bool `json:"write_to_system,omitempty"` + Enabled bool `json:"enabled,omitempty"` + Interval badoption.Duration `json:"interval,omitempty"` + WriteToSystem bool `json:"write_to_system,omitempty"` ServerOptions DialerOptions } diff --git a/option/outbound.go b/option/outbound.go index 1dddb3548..0e2d5874d 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -8,6 +8,7 @@ import ( E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/service" ) @@ -64,21 +65,21 @@ type DialerOptionsWrapper interface { } type DialerOptions struct { - Detour string `json:"detour,omitempty"` - BindInterface string `json:"bind_interface,omitempty"` - Inet4BindAddress *ListenAddress `json:"inet4_bind_address,omitempty"` - Inet6BindAddress *ListenAddress `json:"inet6_bind_address,omitempty"` - ProtectPath string `json:"protect_path,omitempty"` - RoutingMark uint32 `json:"routing_mark,omitempty"` - ReuseAddr bool `json:"reuse_addr,omitempty"` - ConnectTimeout Duration `json:"connect_timeout,omitempty"` - TCPFastOpen bool `json:"tcp_fast_open,omitempty"` - TCPMultiPath bool `json:"tcp_multi_path,omitempty"` - UDPFragment *bool `json:"udp_fragment,omitempty"` - UDPFragmentDefault bool `json:"-"` - DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` - FallbackDelay Duration `json:"fallback_delay,omitempty"` - IsWireGuardListener bool `json:"-"` + Detour string `json:"detour,omitempty"` + BindInterface string `json:"bind_interface,omitempty"` + Inet4BindAddress *badoption.Addr `json:"inet4_bind_address,omitempty"` + Inet6BindAddress *badoption.Addr `json:"inet6_bind_address,omitempty"` + ProtectPath string `json:"protect_path,omitempty"` + RoutingMark uint32 `json:"routing_mark,omitempty"` + ReuseAddr bool `json:"reuse_addr,omitempty"` + ConnectTimeout badoption.Duration `json:"connect_timeout,omitempty"` + TCPFastOpen bool `json:"tcp_fast_open,omitempty"` + TCPMultiPath bool `json:"tcp_multi_path,omitempty"` + UDPFragment *bool `json:"udp_fragment,omitempty"` + UDPFragmentDefault bool `json:"-"` + DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` + FallbackDelay badoption.Duration `json:"fallback_delay,omitempty"` + IsWireGuardListener bool `json:"-"` } func (o *DialerOptions) TakeDialerOptions() DialerOptions { diff --git a/option/platform.go b/option/platform.go index a43cbf230..e4ecd6fa1 100644 --- a/option/platform.go +++ b/option/platform.go @@ -3,6 +3,7 @@ package option import ( E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badoption" ) type OnDemandOptions struct { @@ -12,10 +13,10 @@ type OnDemandOptions struct { type OnDemandRule struct { Action *OnDemandRuleAction `json:"action,omitempty"` - DNSSearchDomainMatch Listable[string] `json:"dns_search_domain_match,omitempty"` - DNSServerAddressMatch Listable[string] `json:"dns_server_address_match,omitempty"` + DNSSearchDomainMatch badoption.Listable[string] `json:"dns_search_domain_match,omitempty"` + DNSServerAddressMatch badoption.Listable[string] `json:"dns_server_address_match,omitempty"` InterfaceTypeMatch *OnDemandRuleInterfaceType `json:"interface_type_match,omitempty"` - SSIDMatch Listable[string] `json:"ssid_match,omitempty"` + SSIDMatch badoption.Listable[string] `json:"ssid_match,omitempty"` ProbeURL string `json:"probe_url,omitempty"` } diff --git a/option/rule.go b/option/rule.go index 952afa614..d5ff9925a 100644 --- a/option/rule.go +++ b/option/rule.go @@ -8,6 +8,7 @@ import ( E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" ) type _Rule struct { @@ -66,39 +67,39 @@ func (r Rule) IsValid() bool { } type RawDefaultRule struct { - Inbound Listable[string] `json:"inbound,omitempty"` - IPVersion int `json:"ip_version,omitempty"` - Network Listable[string] `json:"network,omitempty"` - AuthUser Listable[string] `json:"auth_user,omitempty"` - Protocol Listable[string] `json:"protocol,omitempty"` - Client Listable[string] `json:"client,omitempty"` - Domain Listable[string] `json:"domain,omitempty"` - DomainSuffix Listable[string] `json:"domain_suffix,omitempty"` - DomainKeyword Listable[string] `json:"domain_keyword,omitempty"` - DomainRegex Listable[string] `json:"domain_regex,omitempty"` - Geosite Listable[string] `json:"geosite,omitempty"` - SourceGeoIP Listable[string] `json:"source_geoip,omitempty"` - GeoIP Listable[string] `json:"geoip,omitempty"` - SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"` - SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` - IPCIDR Listable[string] `json:"ip_cidr,omitempty"` - IPIsPrivate bool `json:"ip_is_private,omitempty"` - SourcePort Listable[uint16] `json:"source_port,omitempty"` - SourcePortRange Listable[string] `json:"source_port_range,omitempty"` - Port Listable[uint16] `json:"port,omitempty"` - PortRange Listable[string] `json:"port_range,omitempty"` - ProcessName Listable[string] `json:"process_name,omitempty"` - ProcessPath Listable[string] `json:"process_path,omitempty"` - ProcessPathRegex Listable[string] `json:"process_path_regex,omitempty"` - PackageName Listable[string] `json:"package_name,omitempty"` - User Listable[string] `json:"user,omitempty"` - UserID Listable[int32] `json:"user_id,omitempty"` - ClashMode string `json:"clash_mode,omitempty"` - WIFISSID Listable[string] `json:"wifi_ssid,omitempty"` - WIFIBSSID Listable[string] `json:"wifi_bssid,omitempty"` - RuleSet Listable[string] `json:"rule_set,omitempty"` - RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` - Invert bool `json:"invert,omitempty"` + Inbound badoption.Listable[string] `json:"inbound,omitempty"` + IPVersion int `json:"ip_version,omitempty"` + Network badoption.Listable[string] `json:"network,omitempty"` + AuthUser badoption.Listable[string] `json:"auth_user,omitempty"` + Protocol badoption.Listable[string] `json:"protocol,omitempty"` + Client badoption.Listable[string] `json:"client,omitempty"` + Domain badoption.Listable[string] `json:"domain,omitempty"` + DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` + DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` + DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` + Geosite badoption.Listable[string] `json:"geosite,omitempty"` + SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"` + GeoIP badoption.Listable[string] `json:"geoip,omitempty"` + SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` + SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` + IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` + IPIsPrivate bool `json:"ip_is_private,omitempty"` + SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` + SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` + Port badoption.Listable[uint16] `json:"port,omitempty"` + PortRange badoption.Listable[string] `json:"port_range,omitempty"` + ProcessName badoption.Listable[string] `json:"process_name,omitempty"` + ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` + ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` + PackageName badoption.Listable[string] `json:"package_name,omitempty"` + User badoption.Listable[string] `json:"user,omitempty"` + UserID badoption.Listable[int32] `json:"user_id,omitempty"` + ClashMode string `json:"clash_mode,omitempty"` + WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` + WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` + RuleSet badoption.Listable[string] `json:"rule_set,omitempty"` + RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` + Invert bool `json:"invert,omitempty"` // Deprecated: renamed to rule_set_ip_cidr_match_source Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"` diff --git a/option/rule_action.go b/option/rule_action.go index a7244e179..7a76391ca 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -3,6 +3,7 @@ package option import ( "context" "fmt" + "net/netip" "time" C "github.com/sagernet/sing-box/constant" @@ -11,6 +12,7 @@ import ( E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" ) type _RuleAction struct { @@ -177,7 +179,7 @@ type _DNSRouteActionOptions struct { // Deprecated: Use DNSRouteOptionsActionOptions instead. RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` // Deprecated: Use DNSRouteOptionsActionOptions instead. - ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` + ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"` } type DNSRouteActionOptions _DNSRouteActionOptions @@ -197,9 +199,9 @@ func (r *DNSRouteActionOptions) UnmarshalJSONContext(ctx context.Context, data [ } type _DNSRouteOptionsActionOptions struct { - DisableCache bool `json:"disable_cache,omitempty"` - RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` - ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` + DisableCache bool `json:"disable_cache,omitempty"` + RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` + ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"` } type DNSRouteOptionsActionOptions _DNSRouteOptionsActionOptions @@ -225,10 +227,10 @@ func (d DirectActionOptions) Descriptions() []string { descriptions = append(descriptions, "bind_interface="+d.BindInterface) } if d.Inet4BindAddress != nil { - descriptions = append(descriptions, "inet4_bind_address="+d.Inet4BindAddress.Build().String()) + descriptions = append(descriptions, "inet4_bind_address="+d.Inet4BindAddress.Build(netip.IPv4Unspecified()).String()) } if d.Inet6BindAddress != nil { - descriptions = append(descriptions, "inet6_bind_address="+d.Inet6BindAddress.Build().String()) + descriptions = append(descriptions, "inet6_bind_address="+d.Inet6BindAddress.Build(netip.IPv6Unspecified()).String()) } if d.RoutingMark != 0 { descriptions = append(descriptions, "routing_mark="+fmt.Sprintf("0x%x", d.RoutingMark)) @@ -294,8 +296,8 @@ func (r *RejectActionOptions) UnmarshalJSON(bytes []byte) error { } type RouteActionSniff struct { - Sniffer Listable[string] `json:"sniffer,omitempty"` - Timeout Duration `json:"timeout,omitempty"` + Sniffer badoption.Listable[string] `json:"sniffer,omitempty"` + Timeout badoption.Duration `json:"timeout,omitempty"` } type RouteActionResolve struct { diff --git a/option/rule_dns.go b/option/rule_dns.go index 894e95d34..e758488be 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -9,6 +9,7 @@ import ( E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" ) type _DNSRule struct { @@ -67,41 +68,41 @@ func (r DNSRule) IsValid() bool { } type RawDefaultDNSRule struct { - Inbound Listable[string] `json:"inbound,omitempty"` - IPVersion int `json:"ip_version,omitempty"` - QueryType Listable[DNSQueryType] `json:"query_type,omitempty"` - Network Listable[string] `json:"network,omitempty"` - AuthUser Listable[string] `json:"auth_user,omitempty"` - Protocol Listable[string] `json:"protocol,omitempty"` - Domain Listable[string] `json:"domain,omitempty"` - DomainSuffix Listable[string] `json:"domain_suffix,omitempty"` - DomainKeyword Listable[string] `json:"domain_keyword,omitempty"` - DomainRegex Listable[string] `json:"domain_regex,omitempty"` - Geosite Listable[string] `json:"geosite,omitempty"` - SourceGeoIP Listable[string] `json:"source_geoip,omitempty"` - GeoIP Listable[string] `json:"geoip,omitempty"` - IPCIDR Listable[string] `json:"ip_cidr,omitempty"` - IPIsPrivate bool `json:"ip_is_private,omitempty"` - SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"` - SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` - SourcePort Listable[uint16] `json:"source_port,omitempty"` - SourcePortRange Listable[string] `json:"source_port_range,omitempty"` - Port Listable[uint16] `json:"port,omitempty"` - PortRange Listable[string] `json:"port_range,omitempty"` - ProcessName Listable[string] `json:"process_name,omitempty"` - ProcessPath Listable[string] `json:"process_path,omitempty"` - ProcessPathRegex Listable[string] `json:"process_path_regex,omitempty"` - PackageName Listable[string] `json:"package_name,omitempty"` - User Listable[string] `json:"user,omitempty"` - UserID Listable[int32] `json:"user_id,omitempty"` - Outbound Listable[string] `json:"outbound,omitempty"` - ClashMode string `json:"clash_mode,omitempty"` - WIFISSID Listable[string] `json:"wifi_ssid,omitempty"` - WIFIBSSID Listable[string] `json:"wifi_bssid,omitempty"` - RuleSet Listable[string] `json:"rule_set,omitempty"` - RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` - RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty"` - Invert bool `json:"invert,omitempty"` + Inbound badoption.Listable[string] `json:"inbound,omitempty"` + IPVersion int `json:"ip_version,omitempty"` + QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"` + Network badoption.Listable[string] `json:"network,omitempty"` + AuthUser badoption.Listable[string] `json:"auth_user,omitempty"` + Protocol badoption.Listable[string] `json:"protocol,omitempty"` + Domain badoption.Listable[string] `json:"domain,omitempty"` + DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` + DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` + DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` + Geosite badoption.Listable[string] `json:"geosite,omitempty"` + SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"` + GeoIP badoption.Listable[string] `json:"geoip,omitempty"` + IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` + IPIsPrivate bool `json:"ip_is_private,omitempty"` + SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` + SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` + SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` + SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` + Port badoption.Listable[uint16] `json:"port,omitempty"` + PortRange badoption.Listable[string] `json:"port_range,omitempty"` + ProcessName badoption.Listable[string] `json:"process_name,omitempty"` + ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` + ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` + PackageName badoption.Listable[string] `json:"package_name,omitempty"` + User badoption.Listable[string] `json:"user,omitempty"` + UserID badoption.Listable[int32] `json:"user_id,omitempty"` + Outbound badoption.Listable[string] `json:"outbound,omitempty"` + ClashMode string `json:"clash_mode,omitempty"` + WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` + WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` + RuleSet badoption.Listable[string] `json:"rule_set,omitempty"` + RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` + RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty"` + Invert bool `json:"invert,omitempty"` // Deprecated: renamed to rule_set_ip_cidr_match_source Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"` diff --git a/option/rule_set.go b/option/rule_set.go index 358821def..f0d96bb6e 100644 --- a/option/rule_set.go +++ b/option/rule_set.go @@ -10,6 +10,7 @@ import ( F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" "go4.org/netipx" ) @@ -84,9 +85,9 @@ type LocalRuleSet struct { } type RemoteRuleSet struct { - URL string `json:"url"` - DownloadDetour string `json:"download_detour,omitempty"` - UpdateInterval Duration `json:"update_interval,omitempty"` + URL string `json:"url"` + DownloadDetour string `json:"download_detour,omitempty"` + UpdateInterval badoption.Duration `json:"update_interval,omitempty"` } type _HeadlessRule struct { @@ -145,32 +146,32 @@ func (r HeadlessRule) IsValid() bool { } type DefaultHeadlessRule struct { - QueryType Listable[DNSQueryType] `json:"query_type,omitempty"` - Network Listable[string] `json:"network,omitempty"` - Domain Listable[string] `json:"domain,omitempty"` - DomainSuffix Listable[string] `json:"domain_suffix,omitempty"` - DomainKeyword Listable[string] `json:"domain_keyword,omitempty"` - DomainRegex Listable[string] `json:"domain_regex,omitempty"` - SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"` - IPCIDR Listable[string] `json:"ip_cidr,omitempty"` - SourcePort Listable[uint16] `json:"source_port,omitempty"` - SourcePortRange Listable[string] `json:"source_port_range,omitempty"` - Port Listable[uint16] `json:"port,omitempty"` - PortRange Listable[string] `json:"port_range,omitempty"` - ProcessName Listable[string] `json:"process_name,omitempty"` - ProcessPath Listable[string] `json:"process_path,omitempty"` - ProcessPathRegex Listable[string] `json:"process_path_regex,omitempty"` - PackageName Listable[string] `json:"package_name,omitempty"` - WIFISSID Listable[string] `json:"wifi_ssid,omitempty"` - WIFIBSSID Listable[string] `json:"wifi_bssid,omitempty"` - Invert bool `json:"invert,omitempty"` + QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"` + Network badoption.Listable[string] `json:"network,omitempty"` + Domain badoption.Listable[string] `json:"domain,omitempty"` + DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` + DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` + DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` + SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` + IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` + SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` + SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` + Port badoption.Listable[uint16] `json:"port,omitempty"` + PortRange badoption.Listable[string] `json:"port_range,omitempty"` + ProcessName badoption.Listable[string] `json:"process_name,omitempty"` + ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` + ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` + PackageName badoption.Listable[string] `json:"package_name,omitempty"` + WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` + WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` + Invert bool `json:"invert,omitempty"` DomainMatcher *domain.Matcher `json:"-"` SourceIPSet *netipx.IPSet `json:"-"` IPSet *netipx.IPSet `json:"-"` - AdGuardDomain Listable[string] `json:"-"` - AdGuardDomainMatcher *domain.AdGuardMatcher `json:"-"` + AdGuardDomain badoption.Listable[string] `json:"-"` + AdGuardDomainMatcher *domain.AdGuardMatcher `json:"-"` } func (r DefaultHeadlessRule) IsValid() bool { diff --git a/option/simple.go b/option/simple.go index 78171ce4c..5fda30efe 100644 --- a/option/simple.go +++ b/option/simple.go @@ -1,6 +1,9 @@ package option -import "github.com/sagernet/sing/common/auth" +import ( + "github.com/sagernet/sing/common/auth" + "github.com/sagernet/sing/common/json/badoption" +) type SocksInboundOptions struct { ListenOptions @@ -30,6 +33,6 @@ type HTTPOutboundOptions struct { Username string `json:"username,omitempty"` Password string `json:"password,omitempty"` OutboundTLSOptionsContainer - Path string `json:"path,omitempty"` - Headers HTTPHeader `json:"headers,omitempty"` + Path string `json:"path,omitempty"` + Headers badoption.HTTPHeader `json:"headers,omitempty"` } diff --git a/option/ssh.go b/option/ssh.go index d0bfbf742..1c6ca6bb9 100644 --- a/option/ssh.go +++ b/option/ssh.go @@ -1,14 +1,16 @@ package option +import "github.com/sagernet/sing/common/json/badoption" + type SSHOutboundOptions struct { DialerOptions ServerOptions - User string `json:"user,omitempty"` - Password string `json:"password,omitempty"` - PrivateKey Listable[string] `json:"private_key,omitempty"` - PrivateKeyPath string `json:"private_key_path,omitempty"` - PrivateKeyPassphrase string `json:"private_key_passphrase,omitempty"` - HostKey Listable[string] `json:"host_key,omitempty"` - HostKeyAlgorithms Listable[string] `json:"host_key_algorithms,omitempty"` - ClientVersion string `json:"client_version,omitempty"` + User string `json:"user,omitempty"` + Password string `json:"password,omitempty"` + PrivateKey badoption.Listable[string] `json:"private_key,omitempty"` + PrivateKeyPath string `json:"private_key_path,omitempty"` + PrivateKeyPassphrase string `json:"private_key_passphrase,omitempty"` + HostKey badoption.Listable[string] `json:"host_key,omitempty"` + HostKeyAlgorithms badoption.Listable[string] `json:"host_key_algorithms,omitempty"` + ClientVersion string `json:"client_version,omitempty"` } diff --git a/option/time_unit.go b/option/time_unit.go deleted file mode 100644 index 5e531dadf..000000000 --- a/option/time_unit.go +++ /dev/null @@ -1,226 +0,0 @@ -package option - -import ( - "errors" - "time" -) - -// Copyright 2010 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. - -const durationDay = 24 * time.Hour - -var unitMap = map[string]uint64{ - "ns": uint64(time.Nanosecond), - "us": uint64(time.Microsecond), - "µs": uint64(time.Microsecond), // U+00B5 = micro symbol - "μs": uint64(time.Microsecond), // U+03BC = Greek letter mu - "ms": uint64(time.Millisecond), - "s": uint64(time.Second), - "m": uint64(time.Minute), - "h": uint64(time.Hour), - "d": uint64(durationDay), -} - -// ParseDuration parses a duration string. -// A duration string is a possibly signed sequence of -// decimal numbers, each with optional fraction and a unit suffix, -// such as "300ms", "-1.5h" or "2h45m". -// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". -func ParseDuration(s string) (Duration, error) { - // [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+ - orig := s - var d uint64 - neg := false - - // Consume [-+]? - if s != "" { - c := s[0] - if c == '-' || c == '+' { - neg = c == '-' - s = s[1:] - } - } - // Special case: if all that is left is "0", this is zero. - if s == "0" { - return 0, nil - } - if s == "" { - return 0, errors.New("time: invalid duration " + quote(orig)) - } - for s != "" { - var ( - v, f uint64 // integers before, after decimal point - scale float64 = 1 // value = v + f/scale - ) - - var err error - - // The next character must be [0-9.] - if !(s[0] == '.' || '0' <= s[0] && s[0] <= '9') { - return 0, errors.New("time: invalid duration " + quote(orig)) - } - // Consume [0-9]* - pl := len(s) - v, s, err = leadingInt(s) - if err != nil { - return 0, errors.New("time: invalid duration " + quote(orig)) - } - pre := pl != len(s) // whether we consumed anything before a period - - // Consume (\.[0-9]*)? - post := false - if s != "" && s[0] == '.' { - s = s[1:] - pl := len(s) - f, scale, s = leadingFraction(s) - post = pl != len(s) - } - if !pre && !post { - // no digits (e.g. ".s" or "-.s") - return 0, errors.New("time: invalid duration " + quote(orig)) - } - - // Consume unit. - i := 0 - for ; i < len(s); i++ { - c := s[i] - if c == '.' || '0' <= c && c <= '9' { - break - } - } - if i == 0 { - return 0, errors.New("time: missing unit in duration " + quote(orig)) - } - u := s[:i] - s = s[i:] - unit, ok := unitMap[u] - if !ok { - return 0, errors.New("time: unknown unit " + quote(u) + " in duration " + quote(orig)) - } - if v > 1<<63/unit { - // overflow - return 0, errors.New("time: invalid duration " + quote(orig)) - } - v *= unit - if f > 0 { - // float64 is needed to be nanosecond accurate for fractions of hours. - // v >= 0 && (f*unit/scale) <= 3.6e+12 (ns/h, h is the largest unit) - v += uint64(float64(f) * (float64(unit) / scale)) - if v > 1<<63 { - // overflow - return 0, errors.New("time: invalid duration " + quote(orig)) - } - } - d += v - if d > 1<<63 { - return 0, errors.New("time: invalid duration " + quote(orig)) - } - } - if neg { - return -Duration(d), nil - } - if d > 1<<63-1 { - return 0, errors.New("time: invalid duration " + quote(orig)) - } - return Duration(d), nil -} - -var errLeadingInt = errors.New("time: bad [0-9]*") // never printed - -// leadingInt consumes the leading [0-9]* from s. -func leadingInt[bytes []byte | string](s bytes) (x uint64, rem bytes, err error) { - i := 0 - for ; i < len(s); i++ { - c := s[i] - if c < '0' || c > '9' { - break - } - if x > 1<<63/10 { - // overflow - return 0, rem, errLeadingInt - } - x = x*10 + uint64(c) - '0' - if x > 1<<63 { - // overflow - return 0, rem, errLeadingInt - } - } - return x, s[i:], nil -} - -// leadingFraction consumes the leading [0-9]* from s. -// It is used only for fractions, so does not return an error on overflow, -// it just stops accumulating precision. -func leadingFraction(s string) (x uint64, scale float64, rem string) { - i := 0 - scale = 1 - overflow := false - for ; i < len(s); i++ { - c := s[i] - if c < '0' || c > '9' { - break - } - if overflow { - continue - } - if x > (1<<63-1)/10 { - // It's possible for overflow to give a positive number, so take care. - overflow = true - continue - } - y := x*10 + uint64(c) - '0' - if y > 1<<63 { - overflow = true - continue - } - x = y - scale *= 10 - } - return x, scale, s[i:] -} - -// These are borrowed from unicode/utf8 and strconv and replicate behavior in -// that package, since we can't take a dependency on either. -const ( - lowerhex = "0123456789abcdef" - runeSelf = 0x80 - runeError = '\uFFFD' -) - -func quote(s string) string { - buf := make([]byte, 1, len(s)+2) // slice will be at least len(s) + quotes - buf[0] = '"' - for i, c := range s { - if c >= runeSelf || c < ' ' { - // This means you are asking us to parse a time.Duration or - // time.Location with unprintable or non-ASCII characters in it. - // We don't expect to hit this case very often. We could try to - // reproduce strconv.Quote's behavior with full fidelity but - // given how rarely we expect to hit these edge cases, speed and - // conciseness are better. - var width int - if c == runeError { - width = 1 - if i+2 < len(s) && s[i:i+3] == string(runeError) { - width = 3 - } - } else { - width = len(string(c)) - } - for j := 0; j < width; j++ { - buf = append(buf, `\x`...) - buf = append(buf, lowerhex[s[i+j]>>4]) - buf = append(buf, lowerhex[s[i+j]&0xF]) - } - } else { - if c == '"' || c == '\\' { - buf = append(buf, '\\') - } - buf = append(buf, string(c)...) - } - } - buf = append(buf, '"') - return string(buf) -} diff --git a/option/tls.go b/option/tls.go index 3680afe05..72aaaef1a 100644 --- a/option/tls.go +++ b/option/tls.go @@ -1,20 +1,22 @@ package option +import "github.com/sagernet/sing/common/json/badoption" + type InboundTLSOptions struct { - Enabled bool `json:"enabled,omitempty"` - ServerName string `json:"server_name,omitempty"` - Insecure bool `json:"insecure,omitempty"` - ALPN Listable[string] `json:"alpn,omitempty"` - MinVersion string `json:"min_version,omitempty"` - MaxVersion string `json:"max_version,omitempty"` - CipherSuites Listable[string] `json:"cipher_suites,omitempty"` - Certificate Listable[string] `json:"certificate,omitempty"` - CertificatePath string `json:"certificate_path,omitempty"` - Key Listable[string] `json:"key,omitempty"` - KeyPath string `json:"key_path,omitempty"` - ACME *InboundACMEOptions `json:"acme,omitempty"` - ECH *InboundECHOptions `json:"ech,omitempty"` - Reality *InboundRealityOptions `json:"reality,omitempty"` + Enabled bool `json:"enabled,omitempty"` + ServerName string `json:"server_name,omitempty"` + Insecure bool `json:"insecure,omitempty"` + ALPN badoption.Listable[string] `json:"alpn,omitempty"` + MinVersion string `json:"min_version,omitempty"` + MaxVersion string `json:"max_version,omitempty"` + CipherSuites badoption.Listable[string] `json:"cipher_suites,omitempty"` + Certificate badoption.Listable[string] `json:"certificate,omitempty"` + CertificatePath string `json:"certificate_path,omitempty"` + Key badoption.Listable[string] `json:"key,omitempty"` + KeyPath string `json:"key_path,omitempty"` + ACME *InboundACMEOptions `json:"acme,omitempty"` + ECH *InboundECHOptions `json:"ech,omitempty"` + Reality *InboundRealityOptions `json:"reality,omitempty"` } type InboundTLSOptionsContainer struct { @@ -35,19 +37,19 @@ func (o *InboundTLSOptionsContainer) ReplaceInboundTLSOptions(options *InboundTL } type OutboundTLSOptions struct { - Enabled bool `json:"enabled,omitempty"` - DisableSNI bool `json:"disable_sni,omitempty"` - ServerName string `json:"server_name,omitempty"` - Insecure bool `json:"insecure,omitempty"` - ALPN Listable[string] `json:"alpn,omitempty"` - MinVersion string `json:"min_version,omitempty"` - MaxVersion string `json:"max_version,omitempty"` - CipherSuites Listable[string] `json:"cipher_suites,omitempty"` - Certificate Listable[string] `json:"certificate,omitempty"` - CertificatePath string `json:"certificate_path,omitempty"` - ECH *OutboundECHOptions `json:"ech,omitempty"` - UTLS *OutboundUTLSOptions `json:"utls,omitempty"` - Reality *OutboundRealityOptions `json:"reality,omitempty"` + Enabled bool `json:"enabled,omitempty"` + DisableSNI bool `json:"disable_sni,omitempty"` + ServerName string `json:"server_name,omitempty"` + Insecure bool `json:"insecure,omitempty"` + ALPN badoption.Listable[string] `json:"alpn,omitempty"` + MinVersion string `json:"min_version,omitempty"` + MaxVersion string `json:"max_version,omitempty"` + CipherSuites badoption.Listable[string] `json:"cipher_suites,omitempty"` + Certificate badoption.Listable[string] `json:"certificate,omitempty"` + CertificatePath string `json:"certificate_path,omitempty"` + ECH *OutboundECHOptions `json:"ech,omitempty"` + UTLS *OutboundUTLSOptions `json:"utls,omitempty"` + Reality *OutboundRealityOptions `json:"reality,omitempty"` } type OutboundTLSOptionsContainer struct { @@ -71,8 +73,8 @@ type InboundRealityOptions struct { Enabled bool `json:"enabled,omitempty"` Handshake InboundRealityHandshakeOptions `json:"handshake,omitempty"` PrivateKey string `json:"private_key,omitempty"` - ShortID Listable[string] `json:"short_id,omitempty"` - MaxTimeDifference Duration `json:"max_time_difference,omitempty"` + ShortID badoption.Listable[string] `json:"short_id,omitempty"` + MaxTimeDifference badoption.Duration `json:"max_time_difference,omitempty"` } type InboundRealityHandshakeOptions struct { @@ -81,19 +83,19 @@ type InboundRealityHandshakeOptions struct { } type InboundECHOptions struct { - Enabled bool `json:"enabled,omitempty"` - PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"` - DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,omitempty"` - Key Listable[string] `json:"key,omitempty"` - KeyPath string `json:"key_path,omitempty"` + Enabled bool `json:"enabled,omitempty"` + PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"` + DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,omitempty"` + Key badoption.Listable[string] `json:"key,omitempty"` + KeyPath string `json:"key_path,omitempty"` } type OutboundECHOptions struct { - Enabled bool `json:"enabled,omitempty"` - PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"` - DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,omitempty"` - Config Listable[string] `json:"config,omitempty"` - ConfigPath string `json:"config_path,omitempty"` + Enabled bool `json:"enabled,omitempty"` + PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"` + DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,omitempty"` + Config badoption.Listable[string] `json:"config,omitempty"` + ConfigPath string `json:"config_path,omitempty"` } type OutboundUTLSOptions struct { diff --git a/option/tls_acme.go b/option/tls_acme.go index 9c2e081fa..502706073 100644 --- a/option/tls_acme.go +++ b/option/tls_acme.go @@ -5,10 +5,11 @@ import ( E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" ) type InboundACMEOptions struct { - Domain Listable[string] `json:"domain,omitempty"` + Domain badoption.Listable[string] `json:"domain,omitempty"` DataDirectory string `json:"data_directory,omitempty"` DefaultServerName string `json:"default_server_name,omitempty"` Email string `json:"email,omitempty"` diff --git a/option/tuic.go b/option/tuic.go index 736d5a660..a9b739ec6 100644 --- a/option/tuic.go +++ b/option/tuic.go @@ -1,12 +1,14 @@ package option +import "github.com/sagernet/sing/common/json/badoption" + type TUICInboundOptions struct { ListenOptions - Users []TUICUser `json:"users,omitempty"` - CongestionControl string `json:"congestion_control,omitempty"` - AuthTimeout Duration `json:"auth_timeout,omitempty"` - ZeroRTTHandshake bool `json:"zero_rtt_handshake,omitempty"` - Heartbeat Duration `json:"heartbeat,omitempty"` + Users []TUICUser `json:"users,omitempty"` + CongestionControl string `json:"congestion_control,omitempty"` + AuthTimeout badoption.Duration `json:"auth_timeout,omitempty"` + ZeroRTTHandshake bool `json:"zero_rtt_handshake,omitempty"` + Heartbeat badoption.Duration `json:"heartbeat,omitempty"` InboundTLSOptionsContainer } @@ -19,13 +21,13 @@ type TUICUser struct { type TUICOutboundOptions struct { DialerOptions ServerOptions - UUID string `json:"uuid,omitempty"` - Password string `json:"password,omitempty"` - CongestionControl string `json:"congestion_control,omitempty"` - UDPRelayMode string `json:"udp_relay_mode,omitempty"` - UDPOverStream bool `json:"udp_over_stream,omitempty"` - ZeroRTTHandshake bool `json:"zero_rtt_handshake,omitempty"` - Heartbeat Duration `json:"heartbeat,omitempty"` - Network NetworkList `json:"network,omitempty"` + UUID string `json:"uuid,omitempty"` + Password string `json:"password,omitempty"` + CongestionControl string `json:"congestion_control,omitempty"` + UDPRelayMode string `json:"udp_relay_mode,omitempty"` + UDPOverStream bool `json:"udp_over_stream,omitempty"` + ZeroRTTHandshake bool `json:"zero_rtt_handshake,omitempty"` + Heartbeat badoption.Duration `json:"heartbeat,omitempty"` + Network NetworkList `json:"network,omitempty"` OutboundTLSOptionsContainer } diff --git a/option/tun.go b/option/tun.go index dbb1bfeae..a7a0f6bc0 100644 --- a/option/tun.go +++ b/option/tun.go @@ -7,51 +7,52 @@ import ( E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badoption" ) type TunInboundOptions struct { - InterfaceName string `json:"interface_name,omitempty"` - MTU uint32 `json:"mtu,omitempty"` - GSO bool `json:"gso,omitempty"` - Address Listable[netip.Prefix] `json:"address,omitempty"` - AutoRoute bool `json:"auto_route,omitempty"` - IPRoute2TableIndex int `json:"iproute2_table_index,omitempty"` - IPRoute2RuleIndex int `json:"iproute2_rule_index,omitempty"` - AutoRedirect bool `json:"auto_redirect,omitempty"` - AutoRedirectInputMark FwMark `json:"auto_redirect_input_mark,omitempty"` - AutoRedirectOutputMark FwMark `json:"auto_redirect_output_mark,omitempty"` - StrictRoute bool `json:"strict_route,omitempty"` - RouteAddress Listable[netip.Prefix] `json:"route_address,omitempty"` - RouteAddressSet Listable[string] `json:"route_address_set,omitempty"` - RouteExcludeAddress Listable[netip.Prefix] `json:"route_exclude_address,omitempty"` - RouteExcludeAddressSet Listable[string] `json:"route_exclude_address_set,omitempty"` - IncludeInterface Listable[string] `json:"include_interface,omitempty"` - ExcludeInterface Listable[string] `json:"exclude_interface,omitempty"` - IncludeUID Listable[uint32] `json:"include_uid,omitempty"` - IncludeUIDRange Listable[string] `json:"include_uid_range,omitempty"` - ExcludeUID Listable[uint32] `json:"exclude_uid,omitempty"` - ExcludeUIDRange Listable[string] `json:"exclude_uid_range,omitempty"` - IncludeAndroidUser Listable[int] `json:"include_android_user,omitempty"` - IncludePackage Listable[string] `json:"include_package,omitempty"` - ExcludePackage Listable[string] `json:"exclude_package,omitempty"` - EndpointIndependentNat bool `json:"endpoint_independent_nat,omitempty"` - UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` - Stack string `json:"stack,omitempty"` - Platform *TunPlatformOptions `json:"platform,omitempty"` + InterfaceName string `json:"interface_name,omitempty"` + MTU uint32 `json:"mtu,omitempty"` + GSO bool `json:"gso,omitempty"` + Address badoption.Listable[netip.Prefix] `json:"address,omitempty"` + AutoRoute bool `json:"auto_route,omitempty"` + IPRoute2TableIndex int `json:"iproute2_table_index,omitempty"` + IPRoute2RuleIndex int `json:"iproute2_rule_index,omitempty"` + AutoRedirect bool `json:"auto_redirect,omitempty"` + AutoRedirectInputMark FwMark `json:"auto_redirect_input_mark,omitempty"` + AutoRedirectOutputMark FwMark `json:"auto_redirect_output_mark,omitempty"` + StrictRoute bool `json:"strict_route,omitempty"` + RouteAddress badoption.Listable[netip.Prefix] `json:"route_address,omitempty"` + RouteAddressSet badoption.Listable[string] `json:"route_address_set,omitempty"` + RouteExcludeAddress badoption.Listable[netip.Prefix] `json:"route_exclude_address,omitempty"` + RouteExcludeAddressSet badoption.Listable[string] `json:"route_exclude_address_set,omitempty"` + IncludeInterface badoption.Listable[string] `json:"include_interface,omitempty"` + ExcludeInterface badoption.Listable[string] `json:"exclude_interface,omitempty"` + IncludeUID badoption.Listable[uint32] `json:"include_uid,omitempty"` + IncludeUIDRange badoption.Listable[string] `json:"include_uid_range,omitempty"` + ExcludeUID badoption.Listable[uint32] `json:"exclude_uid,omitempty"` + ExcludeUIDRange badoption.Listable[string] `json:"exclude_uid_range,omitempty"` + IncludeAndroidUser badoption.Listable[int] `json:"include_android_user,omitempty"` + IncludePackage badoption.Listable[string] `json:"include_package,omitempty"` + ExcludePackage badoption.Listable[string] `json:"exclude_package,omitempty"` + EndpointIndependentNat bool `json:"endpoint_independent_nat,omitempty"` + UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` + Stack string `json:"stack,omitempty"` + Platform *TunPlatformOptions `json:"platform,omitempty"` InboundOptions // Deprecated: merged to Address - Inet4Address Listable[netip.Prefix] `json:"inet4_address,omitempty"` + Inet4Address badoption.Listable[netip.Prefix] `json:"inet4_address,omitempty"` // Deprecated: merged to Address - Inet6Address Listable[netip.Prefix] `json:"inet6_address,omitempty"` + Inet6Address badoption.Listable[netip.Prefix] `json:"inet6_address,omitempty"` // Deprecated: merged to RouteAddress - Inet4RouteAddress Listable[netip.Prefix] `json:"inet4_route_address,omitempty"` + Inet4RouteAddress badoption.Listable[netip.Prefix] `json:"inet4_route_address,omitempty"` // Deprecated: merged to RouteAddress - Inet6RouteAddress Listable[netip.Prefix] `json:"inet6_route_address,omitempty"` + Inet6RouteAddress badoption.Listable[netip.Prefix] `json:"inet6_route_address,omitempty"` // Deprecated: merged to RouteExcludeAddress - Inet4RouteExcludeAddress Listable[netip.Prefix] `json:"inet4_route_exclude_address,omitempty"` + Inet4RouteExcludeAddress badoption.Listable[netip.Prefix] `json:"inet4_route_exclude_address,omitempty"` // Deprecated: merged to RouteExcludeAddress - Inet6RouteExcludeAddress Listable[netip.Prefix] `json:"inet6_route_exclude_address,omitempty"` + Inet6RouteExcludeAddress badoption.Listable[netip.Prefix] `json:"inet6_route_exclude_address,omitempty"` } type FwMark uint32 diff --git a/option/tun_platform.go b/option/tun_platform.go index a0a54eed0..b42f6894f 100644 --- a/option/tun_platform.go +++ b/option/tun_platform.go @@ -1,5 +1,7 @@ package option +import "github.com/sagernet/sing/common/json/badoption" + type TunPlatformOptions struct { HTTPProxy *HTTPProxyOptions `json:"http_proxy,omitempty"` } @@ -7,6 +9,6 @@ type TunPlatformOptions struct { type HTTPProxyOptions struct { Enabled bool `json:"enabled,omitempty"` ServerOptions - BypassDomain Listable[string] `json:"bypass_domain,omitempty"` - MatchDomain Listable[string] `json:"match_domain,omitempty"` + BypassDomain badoption.Listable[string] `json:"bypass_domain,omitempty"` + MatchDomain badoption.Listable[string] `json:"match_domain,omitempty"` } diff --git a/option/types.go b/option/types.go index bb6485491..04e3f10e2 100644 --- a/option/types.go +++ b/option/types.go @@ -1,10 +1,7 @@ package option import ( - "net/http" - "net/netip" "strings" - "time" "github.com/sagernet/sing-dns" E "github.com/sagernet/sing/common/exceptions" @@ -15,79 +12,6 @@ import ( mDNS "github.com/miekg/dns" ) -type ListenAddress netip.Addr - -func NewListenAddress(addr netip.Addr) *ListenAddress { - address := ListenAddress(addr) - return &address -} - -func (a ListenAddress) MarshalJSON() ([]byte, error) { - addr := netip.Addr(a) - if !addr.IsValid() { - return nil, nil - } - return json.Marshal(addr.String()) -} - -func (a *ListenAddress) UnmarshalJSON(content []byte) error { - var value string - err := json.Unmarshal(content, &value) - if err != nil { - return err - } - addr, err := netip.ParseAddr(value) - if err != nil { - return err - } - *a = ListenAddress(addr) - return nil -} - -func (a *ListenAddress) Build() netip.Addr { - if a == nil { - return netip.AddrFrom4([4]byte{127, 0, 0, 1}) - } - return (netip.Addr)(*a) -} - -type AddrPrefix netip.Prefix - -func (a AddrPrefix) MarshalJSON() ([]byte, error) { - prefix := netip.Prefix(a) - if prefix.Bits() == prefix.Addr().BitLen() { - return json.Marshal(prefix.Addr().String()) - } else { - return json.Marshal(prefix.String()) - } -} - -func (a *AddrPrefix) UnmarshalJSON(content []byte) error { - var value string - err := json.Unmarshal(content, &value) - if err != nil { - return err - } - prefix, prefixErr := netip.ParsePrefix(value) - if prefixErr == nil { - *a = AddrPrefix(prefix) - return nil - } - addr, addrErr := netip.ParseAddr(value) - if addrErr == nil { - *a = AddrPrefix(netip.PrefixFrom(addr, addr.BitLen())) - return nil - } - return prefixErr -} - -func (a *AddrPrefix) Build() netip.Prefix { - if a == nil { - return netip.Prefix{} - } - return netip.Prefix(*a) -} - type NetworkList string func (v *NetworkList) UnmarshalJSON(content []byte) error { @@ -120,30 +44,6 @@ func (v NetworkList) Build() []string { return strings.Split(string(v), "\n") } -type Listable[T any] []T - -func (l Listable[T]) MarshalJSON() ([]byte, error) { - arrayList := []T(l) - if len(arrayList) == 1 { - return json.Marshal(arrayList[0]) - } - return json.Marshal(arrayList) -} - -func (l *Listable[T]) UnmarshalJSON(content []byte) error { - err := json.UnmarshalDisallowUnknownFields(content, (*[]T)(l)) - if err == nil { - return nil - } - var singleItem T - newError := json.UnmarshalDisallowUnknownFields(content, &singleItem) - if newError != nil { - return E.Errors(err, newError) - } - *l = []T{singleItem} - return nil -} - type DomainStrategy dns.DomainStrategy func (s DomainStrategy) String() string { @@ -206,26 +106,6 @@ func (s *DomainStrategy) UnmarshalJSON(bytes []byte) error { return nil } -type Duration time.Duration - -func (d Duration) MarshalJSON() ([]byte, error) { - return json.Marshal((time.Duration)(d).String()) -} - -func (d *Duration) UnmarshalJSON(bytes []byte) error { - var value string - err := json.Unmarshal(bytes, &value) - if err != nil { - return err - } - duration, err := ParseDuration(value) - if err != nil { - return err - } - *d = Duration(duration) - return nil -} - type DNSQueryType uint16 func (t DNSQueryType) String() string { @@ -270,15 +150,3 @@ func DNSQueryTypeToString(queryType uint16) string { } return F.ToString(queryType) } - -type HTTPHeader map[string]Listable[string] - -func (h HTTPHeader) Build() http.Header { - header := make(http.Header) - for name, values := range h { - for _, value := range values { - header.Add(name, value) - } - } - return header -} diff --git a/option/v2ray_transport.go b/option/v2ray_transport.go index f87b175d3..68c238581 100644 --- a/option/v2ray_transport.go +++ b/option/v2ray_transport.go @@ -5,6 +5,7 @@ import ( E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" ) type _V2RayTransportOptions struct { @@ -67,33 +68,33 @@ func (o *V2RayTransportOptions) UnmarshalJSON(bytes []byte) error { } type V2RayHTTPOptions struct { - Host Listable[string] `json:"host,omitempty"` - Path string `json:"path,omitempty"` - Method string `json:"method,omitempty"` - Headers HTTPHeader `json:"headers,omitempty"` - IdleTimeout Duration `json:"idle_timeout,omitempty"` - PingTimeout Duration `json:"ping_timeout,omitempty"` + Host badoption.Listable[string] `json:"host,omitempty"` + Path string `json:"path,omitempty"` + Method string `json:"method,omitempty"` + Headers badoption.HTTPHeader `json:"headers,omitempty"` + IdleTimeout badoption.Duration `json:"idle_timeout,omitempty"` + PingTimeout badoption.Duration `json:"ping_timeout,omitempty"` } type V2RayWebsocketOptions struct { - Path string `json:"path,omitempty"` - Headers HTTPHeader `json:"headers,omitempty"` - MaxEarlyData uint32 `json:"max_early_data,omitempty"` - EarlyDataHeaderName string `json:"early_data_header_name,omitempty"` + Path string `json:"path,omitempty"` + Headers badoption.HTTPHeader `json:"headers,omitempty"` + MaxEarlyData uint32 `json:"max_early_data,omitempty"` + EarlyDataHeaderName string `json:"early_data_header_name,omitempty"` } type V2RayQUICOptions struct{} type V2RayGRPCOptions struct { - ServiceName string `json:"service_name,omitempty"` - IdleTimeout Duration `json:"idle_timeout,omitempty"` - PingTimeout Duration `json:"ping_timeout,omitempty"` - PermitWithoutStream bool `json:"permit_without_stream,omitempty"` - ForceLite bool `json:"-"` // for test + ServiceName string `json:"service_name,omitempty"` + IdleTimeout badoption.Duration `json:"idle_timeout,omitempty"` + PingTimeout badoption.Duration `json:"ping_timeout,omitempty"` + PermitWithoutStream bool `json:"permit_without_stream,omitempty"` + ForceLite bool `json:"-"` // for test } type V2RayHTTPUpgradeOptions struct { - Host string `json:"host,omitempty"` - Path string `json:"path,omitempty"` - Headers HTTPHeader `json:"headers,omitempty"` + Host string `json:"host,omitempty"` + Path string `json:"path,omitempty"` + Headers badoption.HTTPHeader `json:"headers,omitempty"` } diff --git a/option/wireguard.go b/option/wireguard.go index 65bfad206..ebdf159fd 100644 --- a/option/wireguard.go +++ b/option/wireguard.go @@ -1,15 +1,19 @@ package option -import "net/netip" +import ( + "net/netip" + + "github.com/sagernet/sing/common/json/badoption" +) type WireGuardOutboundOptions struct { DialerOptions - SystemInterface bool `json:"system_interface,omitempty"` - GSO bool `json:"gso,omitempty"` - InterfaceName string `json:"interface_name,omitempty"` - LocalAddress Listable[netip.Prefix] `json:"local_address"` - PrivateKey string `json:"private_key"` - Peers []WireGuardPeer `json:"peers,omitempty"` + SystemInterface bool `json:"system_interface,omitempty"` + GSO bool `json:"gso,omitempty"` + InterfaceName string `json:"interface_name,omitempty"` + LocalAddress badoption.Listable[netip.Prefix] `json:"local_address"` + PrivateKey string `json:"private_key"` + Peers []WireGuardPeer `json:"peers,omitempty"` ServerOptions PeerPublicKey string `json:"peer_public_key"` PreSharedKey string `json:"pre_shared_key,omitempty"` @@ -21,8 +25,8 @@ type WireGuardOutboundOptions struct { type WireGuardPeer struct { ServerOptions - PublicKey string `json:"public_key,omitempty"` - PreSharedKey string `json:"pre_shared_key,omitempty"` - AllowedIPs Listable[string] `json:"allowed_ips,omitempty"` - Reserved []uint8 `json:"reserved,omitempty"` + PublicKey string `json:"public_key,omitempty"` + PreSharedKey string `json:"pre_shared_key,omitempty"` + AllowedIPs badoption.Listable[string] `json:"allowed_ips,omitempty"` + Reserved []uint8 `json:"reserved,omitempty"` } diff --git a/protocol/tun/inbound.go b/protocol/tun/inbound.go index 4be30d61b..f2476223c 100644 --- a/protocol/tun/inbound.go +++ b/protocol/tun/inbound.go @@ -21,6 +21,7 @@ import ( "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json/badoption" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/ranges" @@ -257,7 +258,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo return inbound, nil } -func uidToRange(uidList option.Listable[uint32]) []ranges.Range[uint32] { +func uidToRange(uidList badoption.Listable[uint32]) []ranges.Range[uint32] { return common.Map(uidList, func(uid uint32) ranges.Range[uint32] { return ranges.NewSingle(uid) }) diff --git a/route/router.go b/route/router.go index ce4807490..9a57328a4 100644 --- a/route/router.go +++ b/route/router.go @@ -239,9 +239,9 @@ func NewRouter( } var clientSubnet netip.Prefix if server.ClientSubnet != nil { - clientSubnet = server.ClientSubnet.Build() + clientSubnet = netip.Prefix(common.PtrValueOrDefault(server.ClientSubnet)) } else if dnsOptions.ClientSubnet != nil { - clientSubnet = dnsOptions.ClientSubnet.Build() + clientSubnet = netip.Prefix(common.PtrValueOrDefault(dnsOptions.ClientSubnet)) } if serverProtocol == "" { serverProtocol = "transport" diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index 620260d07..0bf45ba25 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -88,13 +88,13 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction) Server: action.RouteOptions.Server, DisableCache: action.RouteOptions.DisableCache, RewriteTTL: action.RouteOptions.RewriteTTL, - ClientSubnet: action.RouteOptions.ClientSubnet.Build(), + ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptions.ClientSubnet)), } case C.RuleActionTypeRouteOptions: return &RuleActionDNSRouteOptions{ DisableCache: action.RouteOptionsOptions.DisableCache, RewriteTTL: action.RouteOptionsOptions.RewriteTTL, - ClientSubnet: action.RouteOptionsOptions.ClientSubnet.Build(), + ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptionsOptions.ClientSubnet)), } case C.RuleActionTypeReject: return &RuleActionReject{ diff --git a/test/brutal_test.go b/test/brutal_test.go index ce1d2c2a8..cea0ad073 100644 --- a/test/brutal_test.go +++ b/test/brutal_test.go @@ -7,6 +7,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-shadowsocks/shadowaead_2022" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" "github.com/gofrs/uuid/v5" ) @@ -15,13 +17,13 @@ func TestBrutalShadowsocks(t *testing.T) { method := shadowaead_2022.List[0] password := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -30,7 +32,7 @@ func TestBrutalShadowsocks(t *testing.T) { Type: C.TypeShadowsocks, ShadowsocksOptions: option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Method: method, @@ -100,13 +102,13 @@ func TestBrutalTrojan(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") password := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -115,7 +117,7 @@ func TestBrutalTrojan(t *testing.T) { Type: C.TypeTrojan, TrojanOptions: option.TrojanInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.TrojanUser{{Password: password}}, @@ -197,13 +199,13 @@ func TestBrutalTrojan(t *testing.T) { func TestBrutalVMess(t *testing.T) { user, _ := uuid.NewV4() startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -212,7 +214,7 @@ func TestBrutalVMess(t *testing.T) { Type: C.TypeVMess, VMessOptions: option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{{UUID: user.String()}}, @@ -279,13 +281,13 @@ func TestBrutalVMess(t *testing.T) { func TestBrutalVLESS(t *testing.T) { user, _ := uuid.NewV4() startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -294,7 +296,7 @@ func TestBrutalVLESS(t *testing.T) { Type: C.TypeVLESS, VLESSOptions: option.VLESSInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VLESSUser{{UUID: user.String()}}, diff --git a/test/direct_test.go b/test/direct_test.go index c4fd8c5ea..7e7740e52 100644 --- a/test/direct_test.go +++ b/test/direct_test.go @@ -6,18 +6,20 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" ) // Since this is a feature one-off added by outsiders, I won't address these anymore. func _TestProxyProtocol(t *testing.T) { startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -26,7 +28,7 @@ func _TestProxyProtocol(t *testing.T) { Type: C.TypeDirect, DirectOptions: option.DirectInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, ProxyProtocol: true, }, diff --git a/test/domain_inbound_test.go b/test/domain_inbound_test.go index c82b0d297..032b5f59b 100644 --- a/test/domain_inbound_test.go +++ b/test/domain_inbound_test.go @@ -7,6 +7,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-dns" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" "github.com/gofrs/uuid/v5" ) @@ -14,13 +16,13 @@ import ( func TestTUICDomainUDP(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -29,7 +31,7 @@ func TestTUICDomainUDP(t *testing.T) { Type: C.TypeTUIC, TUICOptions: option.TUICInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, InboundOptions: option.InboundOptions{ DomainStrategy: option.DomainStrategy(dns.DomainStrategyUseIPv6), diff --git a/test/ech_test.go b/test/ech_test.go index eeac1acb7..5dac5a0a1 100644 --- a/test/ech_test.go +++ b/test/ech_test.go @@ -8,6 +8,7 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" "github.com/gofrs/uuid/v5" ) @@ -16,13 +17,13 @@ func TestECH(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org", false)) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -31,7 +32,7 @@ func TestECH(t *testing.T) { Type: C.TypeTrojan, TrojanOptions: option.TrojanInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.TrojanUser{ @@ -109,13 +110,13 @@ func TestECHQUIC(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org", false)) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -124,7 +125,7 @@ func TestECHQUIC(t *testing.T) { Type: C.TypeTUIC, TUICOptions: option.TUICInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.TUICUser{{ @@ -199,13 +200,13 @@ func TestECHHysteria2(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org", false)) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -214,7 +215,7 @@ func TestECHHysteria2(t *testing.T) { Type: C.TypeHysteria2, Hysteria2Options: option.Hysteria2InboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.Hysteria2User{{ diff --git a/test/go.mod b/test/go.mod index f9eefa162..72d593ffb 100644 --- a/test/go.mod +++ b/test/go.mod @@ -12,10 +12,10 @@ require ( github.com/docker/docker v27.3.1+incompatible github.com/docker/go-connections v0.5.0 github.com/gofrs/uuid/v5 v5.3.0 - github.com/sagernet/quic-go v0.48.0-beta.1 - github.com/sagernet/sing v0.5.0-rc.4.0.20241022031908-cd17884118cb - github.com/sagernet/sing-dns v0.3.0-rc.2.0.20241021154031-a59e0fbba3ce - github.com/sagernet/sing-quic v0.3.0-rc.1 + github.com/sagernet/quic-go v0.48.1-beta.1 + github.com/sagernet/sing v0.5.1-0.20241107131656-6e1285b5d82f + github.com/sagernet/sing-dns v0.3.1-0.20241105104342-1914f319ddab + github.com/sagernet/sing-quic v0.3.0-rc.2 github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/spyzhov/ajson v0.9.4 @@ -25,13 +25,11 @@ require ( ) require ( - berty.tech/go-libtor v1.0.385 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/andybalholm/brotli v1.0.6 // indirect github.com/caddyserver/certmagic v0.20.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cretz/bine v0.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect @@ -42,8 +40,6 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect - github.com/gobwas/httphead v0.1.0 // indirect - github.com/gobwas/pool v0.2.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/go-cmp v0.6.0 // indirect @@ -65,7 +61,6 @@ require ( github.com/moby/term v0.5.0 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/onsi/ginkgo/v2 v2.9.7 // indirect - github.com/ooni/go-libtor v1.1.8 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect github.com/oschwald/maxminddb-golang v1.12.0 // indirect @@ -81,13 +76,10 @@ require ( github.com/sagernet/nftables v0.3.0-beta.4 // indirect github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect github.com/sagernet/sing-mux v0.2.1-0.20241020175909-fe6153f7a9ec // indirect - github.com/sagernet/sing-shadowtls v0.1.4 // indirect - github.com/sagernet/sing-tun v0.4.0-rc.4.0.20241021153919-9ae45181180d // indirect + github.com/sagernet/sing-tun v0.4.0-rc.5.0.20241107062822-5a91eb99c90f // indirect github.com/sagernet/sing-vmess v0.1.12 // indirect github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect github.com/sagernet/utls v1.6.7 // indirect - github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 // indirect - github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect github.com/vishvananda/netns v0.0.4 // indirect github.com/zeebo/blake3 v0.2.3 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect diff --git a/test/go.sum b/test/go.sum index e851ebd39..7cb24e5f6 100644 --- a/test/go.sum +++ b/test/go.sum @@ -1,5 +1,3 @@ -berty.tech/go-libtor v1.0.385 h1:RWK94C3hZj6Z2GdvePpHJLnWYobFr3bY/OdUJ5aoEXw= -berty.tech/go-libtor v1.0.385/go.mod h1:9swOOQVb+kmvuAlsgWUK/4c52pm69AdbJsxLzk+fJEw= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= @@ -14,9 +12,6 @@ github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vc github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw= -github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo= -github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -43,10 +38,6 @@ github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= -github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= -github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= -github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= -github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk= github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -107,8 +98,6 @@ github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss= github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0= github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4= -github.com/ooni/go-libtor v1.1.8 h1:Wo3V3DVTxl5vZdxtQakqYP+DAHx7pPtAFSl1bnAa08w= -github.com/ooni/go-libtor v1.1.8/go.mod h1:q1YyLwRD9GeMyeerVvwc0vJ2YgwDLTp2bdVcrh/JXyI= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= @@ -135,45 +124,37 @@ github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZN github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= -github.com/sagernet/quic-go v0.48.0-beta.1 h1:86hQZrmuoARI3BpDRkQaP0iAVpywA4YsRrzJPYuPKWg= -github.com/sagernet/quic-go v0.48.0-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/+or9YMLaG5VeTk4k= +github.com/sagernet/quic-go v0.48.1-beta.1 h1:ElPaV5yzlXIKZpqFMAcUGax6vddi3zt4AEpT94Z0vwo= +github.com/sagernet/quic-go v0.48.1-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/+or9YMLaG5VeTk4k= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= -github.com/sagernet/sing v0.5.0-rc.4.0.20241022031908-cd17884118cb h1:3IhGq2UmcbQfAcuqyE8RYKFapqEEa3eItS/MrZr+5l8= -github.com/sagernet/sing v0.5.0-rc.4.0.20241022031908-cd17884118cb/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing-dns v0.3.0-rc.2.0.20241021154031-a59e0fbba3ce h1:OfpxE5qnXMyU/9LtNgX4M7bGP11lJx4s+KZ3Sijb0HE= -github.com/sagernet/sing-dns v0.3.0-rc.2.0.20241021154031-a59e0fbba3ce/go.mod h1:TqLIelI+FAbVEdiTRolhGLOwvhVjY7oT+wezlOJUQ7M= +github.com/sagernet/sing v0.5.1-0.20241107131656-6e1285b5d82f h1:A6+OeV5P1mok0eEEbLh4PidymZ6VZnTZ2uHwfapXgdU= +github.com/sagernet/sing v0.5.1-0.20241107131656-6e1285b5d82f/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing-dns v0.3.1-0.20241105104342-1914f319ddab h1:djP4EY/KM5T62xscormLflVi7eDlHv6p7md1FHMSArE= +github.com/sagernet/sing-dns v0.3.1-0.20241105104342-1914f319ddab/go.mod h1:TqLIelI+FAbVEdiTRolhGLOwvhVjY7oT+wezlOJUQ7M= github.com/sagernet/sing-mux v0.2.1-0.20241020175909-fe6153f7a9ec h1:6Fd/VsEsw9qIjaGi1IBTZSb4b4v5JYtNcoiBtGsQC48= github.com/sagernet/sing-mux v0.2.1-0.20241020175909-fe6153f7a9ec/go.mod h1:RSwqqHwbtTOX3vs6ms8vMtBGH/0ZNyLm/uwt6TlmR84= -github.com/sagernet/sing-quic v0.3.0-rc.1 h1:SlzL1yfEAKJyRduub8vzOVtbyTLAX7RZEEBZxO5utts= -github.com/sagernet/sing-quic v0.3.0-rc.1/go.mod h1:uX+aUHA0fgIN6U3WViseDpSdTQriViZ7qz0Wbsf1mNQ= +github.com/sagernet/sing-quic v0.3.0-rc.2 h1:7vcC4bdS1GBJzHZhfmJiH0CfzQ4mYLUW51Z2RNHcGwc= +github.com/sagernet/sing-quic v0.3.0-rc.2/go.mod h1:3UOq51WVqzra7eCgod7t4hqnTaOiZzFUci9avMrtOqs= github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8= github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE= github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg= github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= -github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= -github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= -github.com/sagernet/sing-tun v0.4.0-rc.4.0.20241021153919-9ae45181180d h1:zWcIQM3eAKJGzy7zhqkO7zm7ZI890OdR4vSwx2mevS0= -github.com/sagernet/sing-tun v0.4.0-rc.4.0.20241021153919-9ae45181180d/go.mod h1:Xhv+Mz2nE7HZTwResni8EtTa7AMJz/S6uQLT5lV23M8= +github.com/sagernet/sing-tun v0.4.0-rc.5.0.20241107062822-5a91eb99c90f h1:gQwTgN/E4oHe3VlseD3/RhPs866cWcTsPG4dP6a8f8o= +github.com/sagernet/sing-tun v0.4.0-rc.5.0.20241107062822-5a91eb99c90f/go.mod h1:Ehs5mZ3T8tTgV3H1Tx4Va5ixvyKjTAUPJ3G11dq7B/g= github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg= github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo= github.com/sagernet/utls v1.6.7 h1:Ep3+aJ8FUGGta+II2IEVNUc3EDhaRCZINWkj/LloIA8= github.com/sagernet/utls v1.6.7/go.mod h1:Uua1TKO/FFuAhLr9rkaVnnrTmmiItzDjv1BUb2+ERwM= -github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 h1:R0OMYAScomNAVpTfbHFpxqJpvwuhxSRi+g6z7gZhABs= -github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8/go.mod h1:K4J7/npM+VAMUeUmTa2JaA02JmyheP0GpRBOUvn3ecc= -github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= -github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spyzhov/ajson v0.9.4 h1:MVibcTCgO7DY4IlskdqIlCmDOsUOZ9P7oKj8ifdcf84= github.com/spyzhov/ajson v0.9.4/go.mod h1:a6oSw0MMb7Z5aD2tPoPO+jq11ETKgXUr2XktHdT8Wt8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= @@ -211,10 +192,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= @@ -227,8 +206,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -237,23 +214,15 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= -golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= diff --git a/test/http_test.go b/test/http_test.go index 7e7240053..dbf8b3bcd 100644 --- a/test/http_test.go +++ b/test/http_test.go @@ -6,17 +6,19 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" ) func TestHTTPSelf(t *testing.T) { startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -25,7 +27,7 @@ func TestHTTPSelf(t *testing.T) { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, }, diff --git a/test/hysteria2_test.go b/test/hysteria2_test.go index 665da552c..1e031f1c1 100644 --- a/test/hysteria2_test.go +++ b/test/hysteria2_test.go @@ -7,6 +7,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-quic/hysteria2" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" ) func TestHysteria2Self(t *testing.T) { @@ -28,13 +30,13 @@ func testHysteria2Self(t *testing.T, salamanderPassword string) { } } startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -43,7 +45,7 @@ func testHysteria2Self(t *testing.T, salamanderPassword string) { Type: C.TypeHysteria2, Hysteria2Options: option.Hysteria2InboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, UpMbps: 100, @@ -115,12 +117,12 @@ func testHysteria2Self(t *testing.T, salamanderPassword string) { func TestHysteria2Inbound(t *testing.T) { caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeHysteria2, Hysteria2Options: option.Hysteria2InboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Obfs: &option.Hysteria2Obfs{ @@ -167,12 +169,12 @@ func TestHysteria2Outbound(t *testing.T) { }, }) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, diff --git a/test/hysteria_test.go b/test/hysteria_test.go index dce00390a..e9467e188 100644 --- a/test/hysteria_test.go +++ b/test/hysteria_test.go @@ -6,18 +6,20 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" ) func TestHysteriaSelf(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -26,7 +28,7 @@ func TestHysteriaSelf(t *testing.T) { Type: C.TypeHysteria, HysteriaOptions: option.HysteriaInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, UpMbps: 100, @@ -98,12 +100,12 @@ func TestHysteriaSelf(t *testing.T) { func TestHysteriaInbound(t *testing.T) { caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeHysteria, HysteriaOptions: option.HysteriaInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, UpMbps: 100, @@ -149,12 +151,12 @@ func TestHysteriaOutbound(t *testing.T) { }, }) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, diff --git a/test/inbound_detour_test.go b/test/inbound_detour_test.go index c26c81a79..4ae82bc91 100644 --- a/test/inbound_detour_test.go +++ b/test/inbound_detour_test.go @@ -7,19 +7,21 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-shadowsocks/shadowaead_2022" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" ) func TestChainedInbound(t *testing.T) { method := shadowaead_2022.List[0] password := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -28,9 +30,11 @@ func TestChainedInbound(t *testing.T) { Type: C.TypeShadowsocks, ShadowsocksOptions: option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, - Detour: "detour", + InboundOptions: option.InboundOptions{ + Detour: "detour", + }, }, Method: method, Password: password, @@ -41,7 +45,7 @@ func TestChainedInbound(t *testing.T) { Tag: "detour", ShadowsocksOptions: option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: otherPort, }, Method: method, diff --git a/test/mux_cool_test.go b/test/mux_cool_test.go index e72f244fc..2dceeb545 100644 --- a/test/mux_cool_test.go +++ b/test/mux_cool_test.go @@ -7,6 +7,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" "github.com/spyzhov/ajson" "github.com/stretchr/testify/require" @@ -37,12 +39,12 @@ func TestMuxCoolServer(t *testing.T) { }) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeVMess, VMessOptions: option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{ @@ -81,12 +83,12 @@ func TestMuxCoolClient(t *testing.T) { }) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -112,13 +114,13 @@ func TestMuxCoolClient(t *testing.T) { func TestMuxCoolSelf(t *testing.T) { user := newUUID() startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -127,7 +129,7 @@ func TestMuxCoolSelf(t *testing.T) { Type: C.TypeVMess, VMessOptions: option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{ diff --git a/test/mux_test.go b/test/mux_test.go index 335def2e9..27c6b914e 100644 --- a/test/mux_test.go +++ b/test/mux_test.go @@ -7,6 +7,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-shadowsocks/shadowaead_2022" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" "github.com/gofrs/uuid/v5" ) @@ -55,13 +57,13 @@ func testShadowsocksMux(t *testing.T, options option.OutboundMultiplexOptions) { method := shadowaead_2022.List[0] password := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -70,7 +72,7 @@ func testShadowsocksMux(t *testing.T, options option.OutboundMultiplexOptions) { Type: C.TypeShadowsocks, ShadowsocksOptions: option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Method: method, @@ -125,13 +127,13 @@ func testShadowsocksMux(t *testing.T, options option.OutboundMultiplexOptions) { func testVMessMux(t *testing.T, options option.OutboundMultiplexOptions) { user, _ := uuid.NewV4() startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -140,7 +142,7 @@ func testVMessMux(t *testing.T, options option.OutboundMultiplexOptions) { Type: C.TypeVMess, VMessOptions: option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{ diff --git a/test/naive_test.go b/test/naive_test.go index fe3e7dce9..d3cb395e5 100644 --- a/test/naive_test.go +++ b/test/naive_test.go @@ -6,19 +6,21 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/auth" + "github.com/sagernet/sing/common/json/badoption" "github.com/sagernet/sing/common/network" ) func TestNaiveInboundWithNginx(t *testing.T) { caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeNaive, NaiveOptions: option.NaiveInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: otherPort, }, Users: []auth.User{ @@ -59,12 +61,12 @@ func TestNaiveInboundWithNginx(t *testing.T) { func TestNaiveInbound(t *testing.T) { caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeNaive, NaiveOptions: option.NaiveInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []auth.User{ @@ -103,12 +105,12 @@ func TestNaiveInbound(t *testing.T) { func TestNaiveHTTP3Inbound(t *testing.T) { caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeNaive, NaiveOptions: option.NaiveInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []auth.User{ diff --git a/test/shadowsocks_legacy_test.go b/test/shadowsocks_legacy_test.go index ae6f38e40..bbdbbde85 100644 --- a/test/shadowsocks_legacy_test.go +++ b/test/shadowsocks_legacy_test.go @@ -7,7 +7,9 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-shadowsocks2/shadowstream" + "github.com/sagernet/sing/common" F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/json/badoption" ) func TestShadowsocksLegacy(t *testing.T) { @@ -24,12 +26,12 @@ func testShadowsocksLegacy(t *testing.T, method string) { }, }) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, diff --git a/test/shadowsocks_test.go b/test/shadowsocks_test.go index 0f7af7650..c83734e4d 100644 --- a/test/shadowsocks_test.go +++ b/test/shadowsocks_test.go @@ -9,7 +9,9 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-shadowsocks/shadowaead_2022" + "github.com/sagernet/sing/common" F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/json/badoption" "github.com/stretchr/testify/require" ) @@ -99,12 +101,12 @@ func testShadowsocksInboundWithShadowsocksRust(t *testing.T, method string, pass Cmd: []string{"-s", F.ToString("127.0.0.1:", serverPort), "-b", F.ToString("0.0.0.0:", clientPort), "-m", method, "-k", password, "-U"}, }) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeShadowsocks, ShadowsocksOptions: option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Method: method, @@ -124,12 +126,12 @@ func testShadowsocksOutboundWithShadowsocksRust(t *testing.T, method string, pas Cmd: []string{"-s", F.ToString("0.0.0.0:", serverPort), "-m", method, "-k", password, "-U"}, }) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -154,13 +156,13 @@ func testShadowsocksOutboundWithShadowsocksRust(t *testing.T, method string, pas func testShadowsocksSelf(t *testing.T, method string, password string) { startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -169,7 +171,7 @@ func testShadowsocksSelf(t *testing.T, method string, password string) { Type: C.TypeShadowsocks, ShadowsocksOptions: option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Method: method, @@ -221,13 +223,13 @@ func TestShadowsocksUoT(t *testing.T) { method := shadowaead_2022.List[0] password := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -236,7 +238,7 @@ func TestShadowsocksUoT(t *testing.T) { Type: C.TypeShadowsocks, ShadowsocksOptions: option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Method: method, @@ -289,13 +291,13 @@ func TestShadowsocksUoT(t *testing.T) { func testShadowsocks2022EIH(t *testing.T, method string, password string) { startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -304,7 +306,7 @@ func testShadowsocks2022EIH(t *testing.T, method string, password string) { Type: C.TypeShadowsocks, ShadowsocksOptions: option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Method: method, diff --git a/test/shadowtls_test.go b/test/shadowtls_test.go index 71e8d9fa2..026c6f55a 100644 --- a/test/shadowtls_test.go +++ b/test/shadowtls_test.go @@ -10,7 +10,9 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-shadowsocks/shadowaead_2022" + "github.com/sagernet/sing/common" F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/json/badoption" "github.com/stretchr/testify/require" ) @@ -37,12 +39,12 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool) method := shadowaead_2022.List[0] ssPassword := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -52,9 +54,12 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool) Tag: "in", ShadowTLSOptions: option.ShadowTLSInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, - Detour: "detour", + + InboundOptions: option.InboundOptions{ + Detour: "detour", + }, }, Handshake: option.ShadowTLSHandshakeOptions{ ServerOptions: option.ServerOptions{ @@ -72,7 +77,7 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool) Tag: "detour", ShadowsocksOptions: option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: otherPort, }, Method: method, @@ -142,12 +147,12 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool) func TestShadowTLSFallback(t *testing.T) { startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeShadowTLS, ShadowTLSOptions: option.ShadowTLSInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Handshake: option.ShadowTLSHandshakeOptions{ @@ -189,13 +194,13 @@ func TestShadowTLSInbound(t *testing.T) { Cmd: []string{"--v3", "--threads", "1", "client", "--listen", "0.0.0.0:" + F.ToString(otherPort), "--server", "127.0.0.1:" + F.ToString(serverPort), "--sni", "google.com", "--password", password}, }) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -204,9 +209,11 @@ func TestShadowTLSInbound(t *testing.T) { Type: C.TypeShadowTLS, ShadowTLSOptions: option.ShadowTLSInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, - Detour: "detour", + InboundOptions: option.InboundOptions{ + Detour: "detour", + }, }, Handshake: option.ShadowTLSHandshakeOptions{ ServerOptions: option.ServerOptions{ @@ -225,7 +232,7 @@ func TestShadowTLSInbound(t *testing.T) { Tag: "detour", ShadowsocksOptions: option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), }, Method: method, Password: password, @@ -283,12 +290,12 @@ func TestShadowTLSOutbound(t *testing.T) { Env: []string{"RUST_LOG=trace"}, }) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -298,7 +305,7 @@ func TestShadowTLSOutbound(t *testing.T) { Tag: "detour", ShadowsocksOptions: option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: otherPort, }, Method: method, diff --git a/test/ss_plugin_test.go b/test/ss_plugin_test.go index 3f837b4e9..b74c13561 100644 --- a/test/ss_plugin_test.go +++ b/test/ss_plugin_test.go @@ -6,6 +6,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" ) func TestShadowsocksObfs(t *testing.T) { @@ -33,12 +35,12 @@ func testShadowsocksPlugin(t *testing.T, name string, opts string, args string) }, }) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, diff --git a/test/tfo_test.go b/test/tfo_test.go index 458a936d6..2587bc2cc 100644 --- a/test/tfo_test.go +++ b/test/tfo_test.go @@ -7,19 +7,21 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-shadowsocks/shadowaead" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" ) func TestTCPSlowOpen(t *testing.T) { method := shadowaead.List[0] password := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -28,7 +30,7 @@ func TestTCPSlowOpen(t *testing.T) { Type: C.TypeShadowsocks, ShadowsocksOptions: option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, TCPFastOpen: true, }, diff --git a/test/tls_test.go b/test/tls_test.go index b42d924f4..fb0c7a1bc 100644 --- a/test/tls_test.go +++ b/test/tls_test.go @@ -6,18 +6,20 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" ) func TestUTLS(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -26,7 +28,7 @@ func TestUTLS(t *testing.T) { Type: C.TypeTrojan, TrojanOptions: option.TrojanInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.TrojanUser{ diff --git a/test/trojan_test.go b/test/trojan_test.go index 1a206c66a..08e3b4d88 100644 --- a/test/trojan_test.go +++ b/test/trojan_test.go @@ -6,6 +6,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" ) func TestTrojanOutbound(t *testing.T) { @@ -20,12 +22,12 @@ func TestTrojanOutbound(t *testing.T) { }, }) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -57,13 +59,13 @@ func TestTrojanOutbound(t *testing.T) { func TestTrojanSelf(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -72,7 +74,7 @@ func TestTrojanSelf(t *testing.T) { Type: C.TypeTrojan, TrojanOptions: option.TrojanInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.TrojanUser{ @@ -140,13 +142,13 @@ func TestTrojanSelf(t *testing.T) { func TestTrojanPlainSelf(t *testing.T) { startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -155,7 +157,7 @@ func TestTrojanPlainSelf(t *testing.T) { Type: C.TypeTrojan, TrojanOptions: option.TrojanInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.TrojanUser{ diff --git a/test/tuic_test.go b/test/tuic_test.go index 41fb75999..0c5f277dc 100644 --- a/test/tuic_test.go +++ b/test/tuic_test.go @@ -6,6 +6,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" "github.com/gofrs/uuid/v5" ) @@ -29,13 +31,13 @@ func testTUICSelf(t *testing.T, udpStream bool, zeroRTTHandshake bool) { udpRelayMode = "quic" } startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -44,7 +46,7 @@ func testTUICSelf(t *testing.T, udpStream bool, zeroRTTHandshake bool) { Type: C.TypeTUIC, TUICOptions: option.TUICInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.TUICUser{{ @@ -113,12 +115,12 @@ func testTUICSelf(t *testing.T, udpStream bool, zeroRTTHandshake bool) { func TestTUICInbound(t *testing.T) { caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeTUIC, TUICOptions: option.TUICInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.TUICUser{{ @@ -160,12 +162,12 @@ func TestTUICOutbound(t *testing.T) { }, }) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, diff --git a/test/v2ray_api_test.go b/test/v2ray_api_test.go index cd7ae2c44..72e97b3ff 100644 --- a/test/v2ray_api_test.go +++ b/test/v2ray_api_test.go @@ -8,19 +8,21 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/experimental/v2rayapi" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" "github.com/stretchr/testify/require" ) func TestV2RayAPI(t *testing.T) { i := startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, diff --git a/test/v2ray_grpc_test.go b/test/v2ray_grpc_test.go index 5cf875435..30cb4bd5b 100644 --- a/test/v2ray_grpc_test.go +++ b/test/v2ray_grpc_test.go @@ -7,6 +7,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" "github.com/gofrs/uuid/v5" "github.com/spyzhov/ajson" @@ -27,12 +29,12 @@ func testV2RayGRPCInbound(t *testing.T, forceLite bool) { require.NoError(t, err) _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeVMess, VMessOptions: option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{ @@ -126,13 +128,13 @@ func testV2RayGRPCOutbound(t *testing.T, forceLite bool) { }, }) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, diff --git a/test/v2ray_transport_test.go b/test/v2ray_transport_test.go index 27074e78e..bc1a3157b 100644 --- a/test/v2ray_transport_test.go +++ b/test/v2ray_transport_test.go @@ -6,6 +6,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" "github.com/gofrs/uuid/v5" "github.com/stretchr/testify/require" @@ -44,13 +46,13 @@ func testVMessTransportSelf(t *testing.T, server *option.V2RayTransportOptions, require.NoError(t, err) _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -59,7 +61,7 @@ func testVMessTransportSelf(t *testing.T, server *option.V2RayTransportOptions, Type: C.TypeVMess, VMessOptions: option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{ @@ -133,13 +135,13 @@ func testTrojanTransportSelf(t *testing.T, server *option.V2RayTransportOptions, require.NoError(t, err) _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -148,7 +150,7 @@ func testTrojanTransportSelf(t *testing.T, server *option.V2RayTransportOptions, Type: C.TypeTrojan, TrojanOptions: option.TrojanInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.TrojanUser{ @@ -224,13 +226,13 @@ func TestVMessQUICSelf(t *testing.T) { require.NoError(t, err) _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -239,7 +241,7 @@ func TestVMessQUICSelf(t *testing.T) { Type: C.TypeVMess, VMessOptions: option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{ @@ -312,13 +314,13 @@ func testV2RayTransportNOTLSSelf(t *testing.T, transport *option.V2RayTransportO user, err := uuid.DefaultGenerator.NewV4() require.NoError(t, err) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -327,7 +329,7 @@ func testV2RayTransportNOTLSSelf(t *testing.T, transport *option.V2RayTransportO Type: C.TypeVMess, VMessOptions: option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{ diff --git a/test/v2ray_ws_test.go b/test/v2ray_ws_test.go index de8d4bdce..35f7a7c5e 100644 --- a/test/v2ray_ws_test.go +++ b/test/v2ray_ws_test.go @@ -7,6 +7,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" "github.com/gofrs/uuid/v5" "github.com/spyzhov/ajson" @@ -61,12 +63,12 @@ func testV2RayWebsocketInbound(t *testing.T, maxEarlyData uint32, earlyDataHeade require.NoError(t, err) _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeVMess, VMessOptions: option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{ @@ -158,13 +160,13 @@ func testV2RayWebsocketOutbound(t *testing.T, maxEarlyData uint32, earlyDataHead }, }) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, diff --git a/test/vmess_test.go b/test/vmess_test.go index 9f81d9a01..e0fd6549c 100644 --- a/test/vmess_test.go +++ b/test/vmess_test.go @@ -7,6 +7,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" "github.com/gofrs/uuid/v5" "github.com/spyzhov/ajson" @@ -181,12 +183,12 @@ func testVMessInboundWithV2Ray(t *testing.T, security string, alterId int, authe }) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeVMess, VMessOptions: option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{ @@ -229,12 +231,12 @@ func testVMessOutboundWithV2Ray(t *testing.T, security string, globalPadding boo }) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -263,13 +265,13 @@ func testVMessOutboundWithV2Ray(t *testing.T, security string, globalPadding boo func testVMessSelf(t *testing.T, security string, alterId int, globalPadding bool, authenticatedLength bool, packetAddr bool) { user := newUUID() startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -278,7 +280,7 @@ func testVMessSelf(t *testing.T, security string, alterId int, globalPadding boo Type: C.TypeVMess, VMessOptions: option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{ diff --git a/test/wireguard_test.go b/test/wireguard_test.go index 70c0e5a55..a30d520ee 100644 --- a/test/wireguard_test.go +++ b/test/wireguard_test.go @@ -7,6 +7,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" ) func _TestWireGuard(t *testing.T) { @@ -21,12 +23,12 @@ func _TestWireGuard(t *testing.T) { }) time.Sleep(5 * time.Second) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + LegacyInbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, diff --git a/transport/sip003/v2ray.go b/transport/sip003/v2ray.go index c142180bb..d7b752f6c 100644 --- a/transport/sip003/v2ray.go +++ b/transport/sip003/v2ray.go @@ -12,6 +12,7 @@ import ( "github.com/sagernet/sing-box/transport/v2ray" "github.com/sagernet/sing-vmess" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json/badoption" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) @@ -67,7 +68,7 @@ func newV2RayPlugin(ctx context.Context, pluginOpts Args, router adapter.Router, transportOptions = option.V2RayTransportOptions{ Type: C.V2RayTransportTypeWebsocket, WebsocketOptions: option.V2RayWebsocketOptions{ - Headers: map[string]option.Listable[string]{ + Headers: map[string]badoption.Listable[string]{ "Host": []string{host}, }, Path: path, From 24496d89b11939b280a7ad8b1e88ab90069071e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 6 Nov 2024 19:05:34 +0800 Subject: [PATCH 11/39] documentation: Remove outdated icons --- docs/configuration/dns/index.md | 4 ---- docs/configuration/dns/index.zh.md | 4 ---- docs/configuration/dns/server.md | 4 ---- docs/configuration/dns/server.zh.md | 4 ---- docs/configuration/experimental/cache-file.md | 4 ---- docs/configuration/experimental/cache-file.zh.md | 4 ---- 6 files changed, 24 deletions(-) diff --git a/docs/configuration/dns/index.md b/docs/configuration/dns/index.md index c0eafccc3..f37c93a4b 100644 --- a/docs/configuration/dns/index.md +++ b/docs/configuration/dns/index.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! quote "Changes in sing-box 1.9.0" :material-plus: [client_subnet](#client_subnet) diff --git a/docs/configuration/dns/index.zh.md b/docs/configuration/dns/index.zh.md index ba390cef1..c845ae0aa 100644 --- a/docs/configuration/dns/index.zh.md +++ b/docs/configuration/dns/index.zh.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! quote "sing-box 1.9.0 中的更改" :material-plus: [client_subnet](#client_subnet) diff --git a/docs/configuration/dns/server.md b/docs/configuration/dns/server.md index 3c5245812..5ec75faa5 100644 --- a/docs/configuration/dns/server.md +++ b/docs/configuration/dns/server.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! quote "Changes in sing-box 1.9.0" :material-plus: [client_subnet](#client_subnet) diff --git a/docs/configuration/dns/server.zh.md b/docs/configuration/dns/server.zh.md index baa117510..9f4705416 100644 --- a/docs/configuration/dns/server.zh.md +++ b/docs/configuration/dns/server.zh.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! quote "sing-box 1.9.0 中的更改" :material-plus: [client_subnet](#client_subnet) diff --git a/docs/configuration/experimental/cache-file.md b/docs/configuration/experimental/cache-file.md index b30538e59..18c430d97 100644 --- a/docs/configuration/experimental/cache-file.md +++ b/docs/configuration/experimental/cache-file.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! question "Since sing-box 1.8.0" !!! quote "Changes in sing-box 1.9.0" diff --git a/docs/configuration/experimental/cache-file.zh.md b/docs/configuration/experimental/cache-file.zh.md index 6d86dc842..656d53c4f 100644 --- a/docs/configuration/experimental/cache-file.zh.md +++ b/docs/configuration/experimental/cache-file.zh.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! question "自 sing-box 1.8.0 起" !!! quote "sing-box 1.9.0 中的更改" From b8613de6736d8cbc15f519f5cd1db692edf934a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 6 Nov 2024 19:10:26 +0800 Subject: [PATCH 12/39] documentation: Update the scheduled removal time of deprecated features --- docs/configuration/dns/rule.md | 4 ++-- docs/configuration/route/geoip.md | 2 +- docs/configuration/route/geoip.zh.md | 2 +- docs/configuration/route/geosite.md | 2 +- docs/configuration/route/geosite.zh.md | 2 +- docs/configuration/route/rule.md | 6 +++--- docs/deprecated.md | 6 +++--- docs/deprecated.zh.md | 4 ++-- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/configuration/dns/rule.md b/docs/configuration/dns/rule.md index 03ca66bcc..715c3b7fd 100644 --- a/docs/configuration/dns/rule.md +++ b/docs/configuration/dns/rule.md @@ -218,7 +218,7 @@ Match domain using regular expression. !!! failure "Deprecated in sing-box 1.8.0" - Geosite is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geosite-to-rule-sets). + Geosite is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geosite-to-rule-sets). Match geosite. @@ -226,7 +226,7 @@ Match geosite. !!! failure "Deprecated in sing-box 1.8.0" - GeoIP is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geoip-to-rule-sets). + GeoIP is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets). Match source geoip. diff --git a/docs/configuration/route/geoip.md b/docs/configuration/route/geoip.md index 8a2ed1d4a..a045574aa 100644 --- a/docs/configuration/route/geoip.md +++ b/docs/configuration/route/geoip.md @@ -4,7 +4,7 @@ icon: material/delete-clock !!! failure "Deprecated in sing-box 1.8.0" - GeoIP is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geoip-to-rule-sets). + GeoIP is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets). ### Structure diff --git a/docs/configuration/route/geoip.zh.md b/docs/configuration/route/geoip.zh.md index fb2481e22..eb7bbe2d1 100644 --- a/docs/configuration/route/geoip.zh.md +++ b/docs/configuration/route/geoip.zh.md @@ -4,7 +4,7 @@ icon: material/delete-clock !!! failure "已在 sing-box 1.8.0 废弃" - GeoIP 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geoip)。 + GeoIP 已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geoip)。 ### 结构 diff --git a/docs/configuration/route/geosite.md b/docs/configuration/route/geosite.md index 046305715..9a1b9dce0 100644 --- a/docs/configuration/route/geosite.md +++ b/docs/configuration/route/geosite.md @@ -4,7 +4,7 @@ icon: material/delete-clock !!! failure "Deprecated in sing-box 1.8.0" - Geosite is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geosite-to-rule-sets). + Geosite is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geosite-to-rule-sets). ### Structure diff --git a/docs/configuration/route/geosite.zh.md b/docs/configuration/route/geosite.zh.md index eeee38ff1..7cec5b208 100644 --- a/docs/configuration/route/geosite.zh.md +++ b/docs/configuration/route/geosite.zh.md @@ -4,7 +4,7 @@ icon: material/delete-clock !!! failure "已在 sing-box 1.8.0 废弃" - Geosite 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geosite)。 + Geosite 已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geosite)。 ### 结构 diff --git a/docs/configuration/route/rule.md b/docs/configuration/route/rule.md index b5d17f215..5e86560c6 100644 --- a/docs/configuration/route/rule.md +++ b/docs/configuration/route/rule.md @@ -209,7 +209,7 @@ Match domain using regular expression. !!! failure "Deprecated in sing-box 1.8.0" - Geosite is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geosite-to-rule-sets). + Geosite is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geosite-to-rule-sets). Match geosite. @@ -217,7 +217,7 @@ Match geosite. !!! failure "Deprecated in sing-box 1.8.0" - GeoIP is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geoip-to-rule-sets). + GeoIP is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets). Match source geoip. @@ -225,7 +225,7 @@ Match source geoip. !!! failure "Deprecated in sing-box 1.8.0" - GeoIP is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geoip-to-rule-sets). + GeoIP is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets). Match geoip. diff --git a/docs/deprecated.md b/docs/deprecated.md index 9cb717cf2..604806f0f 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -12,7 +12,7 @@ icon: material/delete-alert `inet4_route_address` and `inet6_route_address` are merged into `route_address`, `inet4_route_exclude_address` and `inet6_route_exclude_address` are merged into `route_exclude_address`. -Old fields are deprecated and will be removed in sing-box 1.11.0. +Old fields are deprecated and will be removed in sing-box 1.12.0. #### Match source rule items are renamed @@ -32,7 +32,7 @@ check [Migration](/migration/#migrate-cache-file-from-clash-api-to-independent-o #### GeoIP -GeoIP is deprecated and may be removed in the future. +GeoIP is deprecated and may be removed in sing-box 1.12.0. The maxmind GeoIP National Database, as an IP classification database, is not entirely suitable for traffic bypassing, @@ -43,7 +43,7 @@ check [Migration](/migration/#migrate-geoip-to-rule-sets). #### Geosite -Geosite is deprecated and may be removed in the future. +Geosite is deprecated and will be removed in sing-box 1.12.0. Geosite, the `domain-list-community` project maintained by V2Ray as an early traffic bypassing solution, suffers from a number of problems, including lack of maintenance, inaccurate rules, and difficult management. diff --git a/docs/deprecated.zh.md b/docs/deprecated.zh.md index 5bd2ea458..64e155d19 100644 --- a/docs/deprecated.zh.md +++ b/docs/deprecated.zh.md @@ -32,7 +32,7 @@ Clash API 中的 `cache_file` 及相关功能已废弃且已迁移到独立的 ` #### GeoIP -GeoIP 已废弃且可能在不久的将来移除。 +GeoIP 已废弃且将在 sing-box 1.12.0 中被移除。 maxmind GeoIP 国家数据库作为 IP 分类数据库,不完全适合流量绕过, 且现有的实现均存在内存使用大与管理困难的问题。 @@ -42,7 +42,7 @@ sing-box 1.8.0 引入了[规则集](/configuration/rule-set/), #### Geosite -Geosite 已废弃且可能在不久的将来移除。 +Geosite 已废弃且将在 sing-box 1.12.0 中被移除。 Geosite,即由 V2Ray 维护的 domain-list-community 项目,作为早期流量绕过解决方案, 存在着包括缺少维护、规则不准确和管理困难内的大量问题。 From 44560f0c206aee3ccd5cf7a922829b6cdb7a21e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 6 Nov 2024 19:02:55 +0800 Subject: [PATCH 13/39] documentation: Add rule action --- docs/configuration/dns/rule.md | 48 +++-- docs/configuration/dns/rule.zh.md | 2 +- docs/configuration/dns/rule_action.md | 85 +++++++++ docs/configuration/dns/rule_action.zh.md | 86 +++++++++ docs/configuration/outbound/block.md | 10 +- docs/configuration/outbound/block.zh.md | 10 +- docs/configuration/outbound/dns.md | 8 + docs/configuration/outbound/dns.zh.md | 8 + docs/configuration/route/rule.md | 19 +- docs/configuration/route/rule.zh.md | 19 +- docs/configuration/route/rule_action.md | 139 ++++++++++++++ docs/configuration/route/rule_action.zh.md | 136 ++++++++++++++ docs/configuration/shared/listen.md | 34 +++- docs/configuration/shared/listen.zh.md | 32 ++++ docs/deprecated.md | 30 ++- docs/deprecated.zh.md | 25 ++- docs/migration.md | 208 ++++++++++++++++++++- docs/migration.zh.md | 206 ++++++++++++++++++++ mkdocs.yml | 4 + option/inbound.go | 2 +- option/rule_action.go | 6 - route/route.go | 2 +- 22 files changed, 1076 insertions(+), 43 deletions(-) create mode 100644 docs/configuration/dns/rule_action.md create mode 100644 docs/configuration/dns/rule_action.zh.md create mode 100644 docs/configuration/route/rule_action.md create mode 100644 docs/configuration/route/rule_action.zh.md diff --git a/docs/configuration/dns/rule.md b/docs/configuration/dns/rule.md index 715c3b7fd..5cb24e819 100644 --- a/docs/configuration/dns/rule.md +++ b/docs/configuration/dns/rule.md @@ -2,6 +2,14 @@ icon: material/new-box --- +!!! quote "Changes in sing-box 1.11.0" + + :material-plus: [action](#action) + :material-alert: [server](#server) + :material-alert: [disable_cache](#disable_cache) + :material-alert: [rewrite_ttl](#rewrite_ttl) + :material-alert: [client_subnet](#client_subnet) + !!! quote "Changes in sing-box 1.10.0" :material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) @@ -14,7 +22,7 @@ icon: material/new-box :material-plus: [geoip](#geoip) :material-plus: [ip_cidr](#ip_cidr) :material-plus: [ip_is_private](#ip_is_private) - :material-plus: [client_subnet](#client_subnet) + :material-plus: [client_subnet](#client_subnet) :material-plus: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) !!! quote "Changes in sing-box 1.8.0" @@ -135,19 +143,15 @@ icon: material/new-box "outbound": [ "direct" ], - "server": "local", - "disable_cache": false, - "rewrite_ttl": 100, - "client_subnet": "127.0.0.1/24" + "action": "route", + "server": "local" }, { "type": "logical", "mode": "and", "rules": [], - "server": "local", - "disable_cache": false, - "rewrite_ttl": 100, - "client_subnet": "127.0.0.1/24" + "action": "route", + "server": "local" } ] } @@ -354,29 +358,35 @@ Match outbound. `any` can be used as a value to match any outbound. -#### server +#### action ==Required== -Tag of the target dns server. +See [DNS Rule Actions](../rule_action/) for details. + +#### server + +!!! failure "Deprecated in sing-box 1.11.0" + + Moved to [DNS Rule Action](../rule_action#route). #### disable_cache -Disable cache and save cache in this query. +!!! failure "Deprecated in sing-box 1.11.0" -#### rewrite_ttl + Moved to [DNS Rule Action](../rule_action#route). -Rewrite TTL in DNS responses. +#### rewrite_ttl -#### client_subnet +!!! failure "Deprecated in sing-box 1.11.0" -!!! question "Since sing-box 1.9.0" + Moved to [DNS Rule Action](../rule_action#route). -Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default. +#### client_subnet -If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically. +!!! failure "Deprecated in sing-box 1.11.0" -Will overrides `dns.client_subnet` and `servers.[].client_subnet`. + Moved to [DNS Rule Action](../rule_action#route). ### Address Filter Fields diff --git a/docs/configuration/dns/rule.zh.md b/docs/configuration/dns/rule.zh.md index b484cbed6..205b01ae0 100644 --- a/docs/configuration/dns/rule.zh.md +++ b/docs/configuration/dns/rule.zh.md @@ -14,7 +14,7 @@ icon: material/new-box :material-plus: [geoip](#geoip) :material-plus: [ip_cidr](#ip_cidr) :material-plus: [ip_is_private](#ip_is_private) - :material-plus: [client_subnet](#client_subnet) + :material-plus: [client_subnet](#client_subnet) :material-plus: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) !!! quote "sing-box 1.8.0 中的更改" diff --git a/docs/configuration/dns/rule_action.md b/docs/configuration/dns/rule_action.md new file mode 100644 index 000000000..8943b6535 --- /dev/null +++ b/docs/configuration/dns/rule_action.md @@ -0,0 +1,85 @@ +--- +icon: material/new-box +--- + +# DNS Rule Action + +!!! question "Since sing-box 1.11.0" + +### route + +```json +{ + "action": "route", // default + "server": "", + + // for compatibility + "disable_cache": false, + "rewrite_ttl": 0, + "client_subnet": null +} +``` + +`route` inherits the classic rule behavior of routing DNS requests to the specified server. + +#### server + +==Required== + +Tag of target server. + +#### disable_cache/rewrite_ttl/client_subnet + +!!! failure "Deprecated in sing-box 1.11.0" + + Legacy route options is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-legacy-dns-route-options-to-rule-actions). + +### route-options + +```json +{ + "action": "route-options", + "disable_cache": false, + "rewrite_ttl": null, + "client_subnet": null +} +``` + +#### disable_cache + +Disable cache and save cache in this query. + +#### rewrite_ttl + +Rewrite TTL in DNS responses. + +#### client_subnet + +Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default. + +If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically. + +Will overrides `dns.client_subnet` and `servers.[].client_subnet`. + +### reject + +```json +{ + "action": "reject", + "method": "default", // default + "no_drop": false +} +``` + +`reject` reject DNS requests. + +#### method + +- `default`: Reply with NXDOMAIN. +- `drop`: Drop the request. + +#### no_drop + +If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s. + +Not available when `method` is set to drop. diff --git a/docs/configuration/dns/rule_action.zh.md b/docs/configuration/dns/rule_action.zh.md new file mode 100644 index 000000000..8a9dc07ee --- /dev/null +++ b/docs/configuration/dns/rule_action.zh.md @@ -0,0 +1,86 @@ +--- +icon: material/new-box +--- + +# DNS 规则动作 + +!!! question "自 sing-box 1.11.0 起" + +### route + +```json +{ + "action": "route", // 默认 + "server": "", + + // 兼容性 + "disable_cache": false, + "rewrite_ttl": 0, + "client_subnet": null +} +``` + +`route` 继承了将 DNS 请求 路由到指定服务器的经典规则动作。 + +#### server + +==必填== + +目标 DNS 服务器的标签。 + +#### disable_cache/rewrite_ttl/client_subnet + +!!! failure "自 sing-box 1.11.0 起" + + 旧的路由选项已弃用,且将在 sing-box 1.12.0 中移除,参阅 [迁移指南](/migration/#migrate-legacy-dns-route-options-to-rule-actions). + +### route-options + +```json +{ + "action": "route-options", + "disable_cache": false, + "rewrite_ttl": null, + "client_subnet": null +} +``` + + +#### disable_cache + +在此查询中禁用缓存。 + +#### rewrite_ttl + +重写 DNS 回应中的 TTL。 + +#### client_subnet + +默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。 + +如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。 + +将覆盖 `dns.client_subnet` 与 `servers.[].client_subnet`。 + +### reject + +```json +{ + "action": "reject", + "method": "default", // default + "no_drop": false +} +``` + +`reject` 拒绝 DNS 请求。 + +#### method + +- `default`: 返回 NXDOMAIN。 +- `drop`: 丢弃请求。 + +#### no_drop + +如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`。 + +当 `method` 设为 `drop` 时不可用。 diff --git a/docs/configuration/outbound/block.md b/docs/configuration/outbound/block.md index e27a4b3ef..f29120ccf 100644 --- a/docs/configuration/outbound/block.md +++ b/docs/configuration/outbound/block.md @@ -1,8 +1,14 @@ -`block` outbound closes all incoming requests. +--- +icon: material/delete-clock +--- + +!!! failure "Deprecated in sing-box 1.11.0" + + Legacy special outbounds are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-special-outbounds-to-rule-actions). ### Structure -```json +```json F { "type": "block", "tag": "block" diff --git a/docs/configuration/outbound/block.zh.md b/docs/configuration/outbound/block.zh.md index bc0762e3a..822478cea 100644 --- a/docs/configuration/outbound/block.zh.md +++ b/docs/configuration/outbound/block.zh.md @@ -1,3 +1,11 @@ +--- +icon: material/delete-clock +--- + +!!! failure "已在 sing-box 1.11.0 废弃" + + 旧的特殊出站已被弃用,且将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-legacy-special-outbounds-to-rule-actions). + `block` 出站关闭所有传入请求。 ### 结构 @@ -11,4 +19,4 @@ ### 字段 -无字段。 \ No newline at end of file +无字段。 diff --git a/docs/configuration/outbound/dns.md b/docs/configuration/outbound/dns.md index 1f8c54770..d73360413 100644 --- a/docs/configuration/outbound/dns.md +++ b/docs/configuration/outbound/dns.md @@ -1,3 +1,11 @@ +--- +icon: material/delete-clock +--- + +!!! failure "Deprecated in sing-box 1.11.0" + + Legacy special outbounds are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-special-outbounds-to-rule-actions). + `dns` outbound is a internal DNS server. ### Structure diff --git a/docs/configuration/outbound/dns.zh.md b/docs/configuration/outbound/dns.zh.md index 67538f6e0..3db2fefb0 100644 --- a/docs/configuration/outbound/dns.zh.md +++ b/docs/configuration/outbound/dns.zh.md @@ -1,3 +1,11 @@ +--- +icon: material/delete-clock +--- + +!!! failure "已在 sing-box 1.11.0 废弃" + + 旧的特殊出站已被弃用,且将在 sing-box 1.13.0 中被移除, 参阅 [迁移指南](/migration/#migrate-legacy-special-outbounds-to-rule-actions). + `dns` 出站是一个内部 DNS 服务器。 ### 结构 diff --git a/docs/configuration/route/rule.md b/docs/configuration/route/rule.md index 5e86560c6..fe40d5651 100644 --- a/docs/configuration/route/rule.md +++ b/docs/configuration/route/rule.md @@ -1,7 +1,12 @@ --- -icon: material/alert-decagram +icon: material/new-box --- +!!! quote "Changes in sing-box 1.11.0" + + :material-plus: [action](#action) + :material-alert: [outbound](#outbound) + !!! quote "Changes in sing-box 1.10.0" :material-plus: [client](#client) @@ -129,6 +134,7 @@ icon: material/alert-decagram "rule_set_ipcidr_match_source": false, "rule_set_ip_cidr_match_source": false, "invert": false, + "action": "route", "outbound": "direct" }, { @@ -136,6 +142,7 @@ icon: material/alert-decagram "mode": "and", "rules": [], "invert": false, + "action": "route", "outbound": "direct" } ] @@ -357,11 +364,17 @@ Make `ip_cidr` in rule-sets match the source IP. Invert match result. -#### outbound +#### action ==Required== -Tag of the target outbound. +See [Rule Actions](../rule_action/) for details. + +#### outbound + +!!! failure "Deprecated in sing-box 1.11.0" + + Moved to [Rule Action](../rule_action#route). ### Logical Fields diff --git a/docs/configuration/route/rule.zh.md b/docs/configuration/route/rule.zh.md index a93ce5e53..316339f67 100644 --- a/docs/configuration/route/rule.zh.md +++ b/docs/configuration/route/rule.zh.md @@ -1,7 +1,12 @@ --- -icon: material/alert-decagram +icon: material/new-box --- +!!! quote "sing-box 1.11.0 中的更改" + + :material-plus: [action](#action) + :material-alert: [outbound](#outbound) + !!! quote "sing-box 1.10.0 中的更改" :material-plus: [client](#client) @@ -127,6 +132,7 @@ icon: material/alert-decagram "rule_set_ipcidr_match_source": false, "rule_set_ip_cidr_match_source": false, "invert": false, + "action": "route", "outbound": "direct" }, { @@ -134,6 +140,7 @@ icon: material/alert-decagram "mode": "and", "rules": [], "invert": false, + "action": "route", "outbound": "direct" } ] @@ -355,11 +362,17 @@ icon: material/alert-decagram 反选匹配结果。 -#### outbound +#### action ==必填== -目标出站的标签。 +参阅 [规则行动](../rule_action/)。 + +#### outbound + +!!! failure "已在 sing-box 1.11.0 废弃" + + 已移动到 [规则行动](../rule_action#route). ### 逻辑字段 diff --git a/docs/configuration/route/rule_action.md b/docs/configuration/route/rule_action.md new file mode 100644 index 000000000..843d75636 --- /dev/null +++ b/docs/configuration/route/rule_action.md @@ -0,0 +1,139 @@ +--- +icon: material/new-box +--- + +# Rule Action + +!!! question "Since sing-box 1.11.0" + +## Final actions + +### route + +```json +{ + "action": "route", // default + "outbound": "" +} +``` + +`route` inherits the classic rule behavior of routing connection to the specified outbound. + +#### outbound + +==Required== + +Tag of target outbound. + +### route-options + +```json +{ + "action": "route-options", + "udp_disable_domain_unmapping": false, + "udp_connect": false +} +``` + +`route-options` set options for routing. + +#### udp_disable_domain_unmapping + +If enabled, for UDP proxy requests addressed to a domain, +the original packet address will be sent in the response instead of the mapped domain. + +This option is used for compatibility with clients that +do not support receiving UDP packets with domain addresses, such as Surge. + +#### udp_connect + +If enabled, attempts to connect UDP connection to the destination instead of listen. + +### reject + +```json +{ + "action": "reject", + "method": "default", // default + "no_drop": false +} +``` + +`reject` reject connections + +The specified method is used for reject tun connections if `sniff` action has not been performed yet. + +For non-tun connections and already established connections, will just be closed. + +#### method + +- `default`: Reply with TCP RST for TCP connections, and ICMP port unreachable for UDP packets. +- `drop`: Drop packets. + +#### no_drop + +If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s. + +Not available when `method` is set to drop. + +### hijack-dns + +```json +{ + "action": "hijack-dns" +} +``` + +`hijack-dns` hijack DNS requests to the sing-box DNS module. + +## Non-final actions + +### sniff + +```json +{ + "action": "sniff", + "sniffer": [], + "timeout": "" +} +``` + +`sniff` performs protocol sniffing on connections. + +For deprecated `inbound.sniff` options, it is considered to `sniff()` performed before routing. + +#### sniffer + +Enabled sniffers. + +All sniffers enabled by default. + +Available protocol values an be found on in [Protocol Sniff](../sniff/) + +#### timeout + +Timeout for sniffing. + +`300ms` is used by default. + +### resolve + +```json +{ + "action": "resolve", + "strategy": "", + "server": "" +} +``` + +`resolve` resolve request destination from domain to IP addresses. + +#### strategy + +DNS resolution strategy, available values are: `prefer_ipv4`, `prefer_ipv6`, `ipv4_only`, `ipv6_only`. + +`dns.strategy` will be used by default. + +#### server + +Specifies DNS server tag to use instead of selecting through DNS routing. diff --git a/docs/configuration/route/rule_action.zh.md b/docs/configuration/route/rule_action.zh.md new file mode 100644 index 000000000..ae16d85fd --- /dev/null +++ b/docs/configuration/route/rule_action.zh.md @@ -0,0 +1,136 @@ +--- +icon: material/new-box +--- + +# 规则动作 + +!!! question "自 sing-box 1.11.0 起" + +## 最终动作 + +### route + +```json +{ + "action": "route", // 默认 + "outbound": "", + "udp_disable_domain_unmapping": false +} +``` + +`route` 继承了将连接路由到指定出站的经典规则动作。 + +#### outbound + +==必填== + +目标出站的标签。 + +### route-options + +```json +{ + "action": "route-options", + "udp_disable_domain_unmapping": false, + "udp_connect": false +} +``` + +#### udp_disable_domain_unmapping + +如果启用,对于地址为域的 UDP 代理请求,将在响应中发送原始包地址而不是映射的域。 + +此选项用于兼容不支持接收带有域地址的 UDP 包的客户端,如 Surge。 + +#### udp_connect + +如果启用,将尝试将 UDP 连接 connect 到目标而不是 listen。 + +### reject + +```json +{ + "action": "reject", + "method": "default", // 默认 + "no_drop": false +} +``` + +`reject` 拒绝连接。 + +如果尚未执行 `sniff` 操作,则将使用指定方法拒绝 tun 连接。 + +对于非 tun 连接和已建立的连接,将直接关闭。 + +#### method + +- `default`: 对于 TCP 连接回复 RST,对于 UDP 包回复 ICMP 端口不可达。 +- `drop`: 丢弃数据包。 + +#### no_drop + +如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`。 + +当 `method` 设为 `drop` 时不可用。 + +### hijack-dns + +```json +{ + "action": "hijack-dns" +} +``` + +`hijack-dns` 劫持 DNS 请求至 sing-box DNS 模块。 + +## 非最终动作 + +### sniff + +```json +{ + "action": "sniff", + "sniffer": [], + "timeout": "" +} +``` + +`sniff` 对连接执行协议嗅探。 + +对于已弃用的 `inbound.sniff` 选项,被视为在路由之前执行的 `sniff`。 + +#### sniffer + +启用的探测器。 + +默认启用所有探测器。 + +可用的协议值可以在 [协议嗅探](../sniff/) 中找到。 + +#### timeout + +探测超时时间。 + +默认使用 300ms。 + +### resolve + +```json +{ + "action": "resolve", + "strategy": "", + "server": "" +} +``` + +`resolve` 将请求的目标从域名解析为 IP 地址。 + +#### strategy + +DNS 解析策略,可用值有:`prefer_ipv4`、`prefer_ipv6`、`ipv4_only`、`ipv6_only`。 + +默认使用 `dns.strategy`。 + +#### server + +指定要使用的 DNS 服务器的标签,而不是通过 DNS 路由进行选择。 diff --git a/docs/configuration/shared/listen.md b/docs/configuration/shared/listen.md index ae3ed6a40..fa6a05b97 100644 --- a/docs/configuration/shared/listen.md +++ b/docs/configuration/shared/listen.md @@ -1,3 +1,15 @@ +--- +icon: material/delete-clock +--- + +!!! quote "Changes in sing-box 1.11.0" + + :material-delete-clock: [sniff](#sniff) + :material-delete-clock: [sniff_override_destination](#sniff_override_destination) + :material-delete-clock: [sniff_timeout](#sniff_timeout) + :material-delete-clock: [domain_strategy](#domain_strategy) + :material-delete-clock: [udp_disable_domain_unmapping](#udp_disable_domain_unmapping) + ### Structure ```json @@ -68,24 +80,40 @@ Requires target inbound support, see [Injectable](/configuration/inbound/#fields #### sniff +!!! failure "Deprecated in sing-box 1.11.0" + + Inbound fields are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-inbound-fields-to-rule-actions). + Enable sniffing. See [Protocol Sniff](/configuration/route/sniff/) for details. #### sniff_override_destination +!!! failure "Deprecated in sing-box 1.11.0" + + Inbound fields are deprecated and will be removed in sing-box 1.13.0. + Override the connection destination address with the sniffed domain. If the domain name is invalid (like tor), this will not work. #### sniff_timeout +!!! failure "Deprecated in sing-box 1.11.0" + + Inbound fields are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-inbound-fields-to-rule-actions). + Timeout for sniffing. -300ms is used by default. +`300ms` is used by default. #### domain_strategy +!!! failure "Deprecated in sing-box 1.11.0" + + Inbound fields are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-inbound-fields-to-rule-actions). + One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`. If set, the requested domain name will be resolved to IP before routing. @@ -94,6 +122,10 @@ If `sniff_override_destination` is in effect, its value will be taken as a fallb #### udp_disable_domain_unmapping +!!! failure "Deprecated in sing-box 1.11.0" + + Inbound fields are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-inbound-fields-to-rule-actions). + If enabled, for UDP proxy requests addressed to a domain, the original packet address will be sent in the response instead of the mapped domain. diff --git a/docs/configuration/shared/listen.zh.md b/docs/configuration/shared/listen.zh.md index 398c98c53..3b472c4df 100644 --- a/docs/configuration/shared/listen.zh.md +++ b/docs/configuration/shared/listen.zh.md @@ -1,3 +1,15 @@ +--- +icon: material/delete-clock +--- + +!!! quote "sing-box 1.11.0 中的更改" + + :material-delete-clock: [sniff](#sniff) + :material-delete-clock: [sniff_override_destination](#sniff_override_destination) + :material-delete-clock: [sniff_timeout](#sniff_timeout) + :material-delete-clock: [domain_strategy](#domain_strategy) + :material-delete-clock: [udp_disable_domain_unmapping](#udp_disable_domain_unmapping) + ### 结构 ```json @@ -69,24 +81,40 @@ UDP NAT 过期时间,以秒为单位。 #### sniff +!!! failure "已在 sing-box 1.11.0 废弃" + + 入站字段已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/migration/#migrate-legacy-inbound-fields-to-rule-actions). + 启用协议探测。 参阅 [协议探测](/zh/configuration/route/sniff/) #### sniff_override_destination +!!! failure "已在 sing-box 1.11.0 废弃" + + 入站字段已废弃且将在 sing-box 1.12.0 中被移除。 + 用探测出的域名覆盖连接目标地址。 如果域名无效(如 Tor),将不生效。 #### sniff_timeout +!!! failure "已在 sing-box 1.11.0 废弃" + + 入站字段已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/migration/#migrate-legacy-inbound-fields-to-rule-actions). + 探测超时时间。 默认使用 300ms。 #### domain_strategy +!!! failure "已在 sing-box 1.11.0 废弃" + + 入站字段已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/migration/#migrate-legacy-inbound-fields-to-rule-actions). + 可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。 如果设置,请求的域名将在路由之前解析为 IP。 @@ -95,6 +123,10 @@ UDP NAT 过期时间,以秒为单位。 #### udp_disable_domain_unmapping +!!! failure "已在 sing-box 1.11.0 废弃" + + 入站字段已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/migration/#migrate-legacy-inbound-fields-to-rule-actions). + 如果启用,对于地址为域的 UDP 代理请求,将在响应中发送原始包地址而不是映射的域。 此选项用于兼容不支持接收带有域地址的 UDP 包的客户端,如 Surge。 diff --git a/docs/deprecated.md b/docs/deprecated.md index 604806f0f..f057319ad 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -4,6 +4,32 @@ icon: material/delete-alert # Deprecated Feature List +## 1.11.0 + +#### Legacy special outbounds + +Legacy special outbounds (`block` / `dns`) are deprecated +and can be replaced by rule actions, +check [Migration](../migration/#migrate-legacy-special-outbounds-to-rule-actions). + +Old fields will be removed in sing-box 1.13.0. + +#### Legacy inbound fields + +Legacy inbound fields (`inbound.` are deprecated +and can be replaced by rule actions, +check [Migration](../migration/#migrate-legacy-inbound-fields-to-rule-actions). + +Old fields will be removed in sing-box 1.13.0. + +#### Legacy DNS route options + +Legacy DNS route options (`disable_cache`, `rewrite_ttl`, `client_subnet`) are deprecated +and can be replaced by rule actions, +check [Migration](../migration/#migrate-legacy-dns-route-options-to-rule-actions). + +Old fields will be removed in sing-box 1.12.0. + ## 1.10.0 #### TUN address fields are merged @@ -12,7 +38,7 @@ icon: material/delete-alert `inet4_route_address` and `inet6_route_address` are merged into `route_address`, `inet4_route_exclude_address` and `inet6_route_exclude_address` are merged into `route_exclude_address`. -Old fields are deprecated and will be removed in sing-box 1.12.0. +Old fields will be removed in sing-box 1.12.0. #### Match source rule items are renamed @@ -32,7 +58,7 @@ check [Migration](/migration/#migrate-cache-file-from-clash-api-to-independent-o #### GeoIP -GeoIP is deprecated and may be removed in sing-box 1.12.0. +GeoIP is deprecated and will be removed in sing-box 1.12.0. The maxmind GeoIP National Database, as an IP classification database, is not entirely suitable for traffic bypassing, diff --git a/docs/deprecated.zh.md b/docs/deprecated.zh.md index 64e155d19..2f7b28f7b 100644 --- a/docs/deprecated.zh.md +++ b/docs/deprecated.zh.md @@ -4,6 +4,29 @@ icon: material/delete-alert # 废弃功能列表 +## 1.11.0 + +#### 旧的特殊出站 + +旧的特殊出站(`block` / `dns`)已废弃且可以通过规则动作替代, +参阅 [迁移指南](/migration/#migrate-legacy-special-outbounds-to-rule-actions)。 + +旧字段将在 sing-box 1.13.0 中被移除。 + +#### 旧的入站字段 + +旧的入站字段(`inbound.`)已废弃且可以通过规则动作替代, +参阅 [迁移指南](/migration/#migrate-legacy-inbound-fields-to-rule-actions)。 + +旧字段将在 sing-box 1.13.0 中被移除。 + +#### 旧的 DNS 路由参数 + +旧的 DNS 路由参数(`disable_cache`、`rewrite_ttl`、`client_subnet`)已废弃且可以通过规则动作替代, +参阅 [迁移指南](/migration/#migrate-legacy-dns-route-options-to-rule-actions)。 + +旧字段将在 sing-box 1.12.0 中被移除。 + ## 1.10.0 #### Match source 规则项已重命名 @@ -17,7 +40,7 @@ icon: material/delete-alert `inet4_route_address` 和 `inet6_route_address` 已合并为 `route_address`, `inet4_route_exclude_address` 和 `inet6_route_exclude_address` 已合并为 `route_exclude_address`。 -旧字段已废弃,且将在 sing-box 1.11.0 中被移除。 +旧字段将在 sing-box 1.11.0 中被移除。 #### 移除对 go1.18 和 go1.19 的支持 diff --git a/docs/migration.md b/docs/migration.md index 71b616921..9207db5bc 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -2,6 +2,212 @@ icon: material/arrange-bring-forward --- +## 1.11.0 + +### Migrate legacy special outbounds to rule actions + +Legacy special outbounds are deprecated and can be replaced by rule actions. + +!!! info "References" + + [Rule Action](/configuration/route/rule_action/) / + [Block](/configuration/outbound/block/) / + [DNS](/configuration/outbound/dns) + +=== "Block" + + === ":material-card-remove: Deprecated" + + ```json + { + "outbounds": [ + { + "type": "block", + "tag": "block" + } + ], + "route": { + "rules": [ + { + ..., + + "outbound": "block" + } + ] + } + } + ``` + + === ":material-card-multiple: New" + + ```json + { + "route": { + "rules": [ + { + ..., + + "action": "reject" + } + ] + } + } + ``` + +=== "DNS" + + === ":material-card-remove: Deprecated" + + ```json + { + "inbound": [ + { + ..., + + "sniff": true + } + ], + "outbounds": [ + { + "tag": "dns", + "type": "dns" + } + ], + "route": { + "rules": [ + { + "protocol": "dns", + "outbound": "dns" + } + ] + } + } + ``` + + === ":material-card-multiple: New" + + ```json + { + "route": { + "rules": [ + { + "action": "sniff" + }, + { + "protocol": "dns", + "action": "hijack-dns" + } + ] + } + } + ``` + +### Migrate legacy inbound fields to rule actions + +Inbound fields are deprecated and can be replaced by rule actions. + +!!! info "References" + + [Listen Fields](/configuration/inbound/listen/) / + [Rule](/configuration/route/rule/) / + [Rule Action](/configuration/route/rule_action/) / + [DNS Rule](/configuration/dns/rule/) / + [DNS Rule Action](/configuration/dns/rule_action/) + +=== ":material-card-remove: Deprecated" + + ```json + { + "inbounds": [ + { + "type": "mixed", + "sniff": true, + "sniff_timeout": "1s", + "domain_strategy": "prefer_ipv4" + } + ] + } + ``` + +=== ":material-card-multiple: New" + + ```json + { + "inbounds": [ + { + "type": "mixed", + "tag": "in" + } + ], + "route": { + "rules": [ + { + "inbound": "in", + "action": "resolve", + "strategy": "prefer_ipv4" + }, + { + "inbound": "in", + "action": "sniff", + "timeout": "1s" + } + ] + } + } + ``` + +### Migrate legacy DNS route options to rule actions + +Legacy DNS route options are deprecated and can be replaced by rule actions. + +!!! info "References" + + [DNS Rule](/configuration/dns/rule/) / + [DNS Rule Action](/configuration/dns/rule_action/) + +=== ":material-card-remove: Deprecated" + + ```json + { + "dns": { + "rules": [ + { + ..., + + "server": "local", + "disable_cache": true, + "rewrite_ttl": 600, + "client_subnet": "1.1.1.1/24" + } + ] + } + } + ``` + +=== ":material-card-multiple: New" + + ```json + { + "dns": { + "rules": [ + { + ..., + + "action": "route-options", + "disable_cache": true, + "rewrite_ttl": 600, + "client_subnet": "1.1.1.1/24" + }, + { + ..., + + "server": "local" + } + ] + } + } + ``` + ## 1.10.0 ### TUN address fields are merged @@ -10,8 +216,6 @@ icon: material/arrange-bring-forward `inet4_route_address` and `inet6_route_address` are merged into `route_address`, `inet4_route_exclude_address` and `inet6_route_exclude_address` are merged into `route_exclude_address`. -Old fields are deprecated and will be removed in sing-box 1.11.0. - !!! info "References" [TUN](/configuration/inbound/tun/) diff --git a/docs/migration.zh.md b/docs/migration.zh.md index 62fbe9ede..f51860f72 100644 --- a/docs/migration.zh.md +++ b/docs/migration.zh.md @@ -2,6 +2,212 @@ icon: material/arrange-bring-forward --- +## 1.11.0 + +### 迁移旧的特殊出站到规则动作 + +旧的特殊出站已被弃用,且可以被规则动作替代。 + +!!! info "参考" + + [规则动作](/zh/configuration/route/rule_action/) / + [Block](/zh/configuration/outbound/block/) / + [DNS](/zh/configuration/outbound/dns) + +=== "Block" + + === ":material-card-remove: 弃用的" + + ```json + { + "outbounds": [ + { + "type": "block", + "tag": "block" + } + ], + "route": { + "rules": [ + { + ..., + + "outbound": "block" + } + ] + } + } + ``` + + === ":material-card-multiple: 新的" + + ```json + { + "route": { + "rules": [ + { + ..., + + "action": "reject" + } + ] + } + } + ``` + +=== "DNS" + + === ":material-card-remove: 弃用的" + + ```json + { + "inbound": [ + { + ..., + + "sniff": true + } + ], + "outbounds": [ + { + "tag": "dns", + "type": "dns" + } + ], + "route": { + "rules": [ + { + "protocol": "dns", + "outbound": "dns" + } + ] + } + } + ``` + + === ":material-card-multiple: 新的" + + ```json + { + "route": { + "rules": [ + { + "action": "sniff" + }, + { + "protocol": "dns", + "action": "hijack-dns" + } + ] + } + } + ``` + +### 迁移旧的入站字段到规则动作 + +入站选项已被弃用,且可以被规则动作替代。 + +!!! info "参考" + + [监听字段](/zh/configuration/shared/listen/) / + [规则](/zh/configuration/route/rule/) / + [规则动作](/zh/configuration/route/rule_action/) / + [DNS 规则](/zh/configuration/dns/rule/) / + [DNS 规则动作](/zh/configuration/dns/rule_action/) + +=== ":material-card-remove: 弃用的" + + ```json + { + "inbounds": [ + { + "type": "mixed", + "sniff": true, + "sniff_timeout": "1s", + "domain_strategy": "prefer_ipv4" + } + ] + } + ``` + +=== ":material-card-multiple: New" + + ```json + { + "inbounds": [ + { + "type": "mixed", + "tag": "in" + } + ], + "route": { + "rules": [ + { + "inbound": "in", + "action": "resolve", + "strategy": "prefer_ipv4" + }, + { + "inbound": "in", + "action": "sniff", + "timeout": "1s" + } + ] + } + } + ``` + +### 迁移旧的 DNS 路由选项到规则动作 + +旧的 DNS 路由选项已被弃用,且可以被规则动作替代。 + +!!! info "参考" + + [DNS 规则](/zh/configuration/dns/rule/) / + [DNS 规则动作](/zh/configuration/dns/rule_action/) + +=== ":material-card-remove: 弃用的" + + ```json + { + "dns": { + "rules": [ + { + ..., + + "server": "local", + "disable_cache": true, + "rewrite_ttl": 600, + "client_subnet": "1.1.1.1/24" + } + ] + } + } + ``` + +=== ":material-card-multiple: 新的" + + ```json + { + "dns": { + "rules": [ + { + ..., + + "action": "route-options", + "disable_cache": true, + "rewrite_ttl": 600, + "client_subnet": "1.1.1.1/24" + }, + { + ..., + + "server": "local" + } + ] + } + } + ``` + ## 1.10.0 ### TUN 地址字段已合并 diff --git a/mkdocs.yml b/mkdocs.yml index d5cdcee47..66e8a2e99 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -82,6 +82,7 @@ nav: - configuration/dns/index.md - DNS Server: configuration/dns/server.md - DNS Rule: configuration/dns/rule.md + - DNS Rule Action: configuration/dns/rule_action.md - FakeIP: configuration/dns/fakeip.md - NTP: - configuration/ntp/index.md @@ -90,6 +91,7 @@ nav: - GeoIP: configuration/route/geoip.md - Geosite: configuration/route/geosite.md - Route Rule: configuration/route/rule.md + - Rule Action: configuration/route/rule_action.md - Protocol Sniff: configuration/route/sniff.md - Rule Set: - configuration/rule-set/index.md @@ -218,9 +220,11 @@ plugins: Log: 日志 DNS Server: DNS 服务器 DNS Rule: DNS 规则 + DNS Rule Action: DNS 规则动作 Route: 路由 Route Rule: 路由规则 + Rule Action: 规则动作 Protocol Sniff: 协议探测 Rule Set: 规则集 diff --git a/option/inbound.go b/option/inbound.go index a67719fa0..2cc159894 100644 --- a/option/inbound.go +++ b/option/inbound.go @@ -34,7 +34,7 @@ func (h *Inbound) UnmarshalJSONContext(ctx context.Context, content []byte) erro } registry := service.FromContext[InboundOptionsRegistry](ctx) if registry == nil { - return E.New("missing inbound options registry in context") + return E.New("missing Inbound fields registry in context") } options, loaded := registry.CreateOptions(h.Type) if !loaded { diff --git a/option/rule_action.go b/option/rule_action.go index 7a76391ca..3b4e8edb5 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -148,9 +148,6 @@ func (r *RouteActionOptions) UnmarshalJSON(data []byte) error { if err != nil { return err } - if r.Outbound == "" { - return E.New("missing outbound") - } return nil } @@ -189,9 +186,6 @@ func (r *DNSRouteActionOptions) UnmarshalJSONContext(ctx context.Context, data [ if err != nil { return err } - if r.Server == "" { - return E.New("missing server") - } if r.DisableCache || r.RewriteTTL != nil || r.ClientSubnet != nil { deprecated.Report(ctx, deprecated.OptionLegacyDNSRouteOptions) } diff --git a/route/route.go b/route/route.go index e6f0b37ce..8fcc3f928 100644 --- a/route/route.go +++ b/route/route.go @@ -76,7 +76,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad metadata.Network = N.NetworkTCP switch metadata.Destination.Fqdn { case mux.Destination.Fqdn: - return E.New("global multiplex is deprecated since sing-box v1.7.0, enable multiplex in inbound options instead.") + return E.New("global multiplex is deprecated since sing-box v1.7.0, enable multiplex in Inbound fields instead.") case vmess.MuxDestination.Fqdn: return E.New("global multiplex (v2ray legacy) not supported since sing-box v1.7.0.") case uot.MagicAddress: From 1ee7a4a272610826df25e07ef6c48c88db2c73b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 6 Nov 2024 22:59:02 +0800 Subject: [PATCH 14/39] documentation: Bump version --- docs/changelog.md | 62 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index e70ec08b9..66b374b57 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,66 @@ icon: material/alert-decagram --- +#### 1.11.0-alpha.10 + +* Fixes and improvements + +#### 1.11.0-alpha.9 + +* Improve tun compatibility **1** +* Fixes and improvements + +**1**: + +When `gvisor` tun stack is enabled, even if the request passes routing, +if the outbound connection establishment fails, +the connection still does not need to be established and a TCP RST is replied. + +#### 1.11.0-alpha.7 + +* Introducing rule actions **1** + +**1**: + +New rule actions replace legacy inbound fields and special outbound fields, +and can be used for pre-matching **2**. + +See [Rule](/configuration/route/rule/), +[Rule Action](/configuration/route/rule_action/), +[DNS Rule](/configuration/dns/rule/) and +[DNS Rule Action](/configuration/dns/rule_action/). + +For migration, see +[Migrate legacy special outbounds to rule actions](/migration/#migrate-legacy-special-outbounds-to-rule-actions), +[Migrate legacy inbound fields to rule actions](/migration/#migrate-legacy-inbound-fields-to-rule-actions) +and [Migrate legacy DNS route options to rule actions](/migration/#migrate-legacy-dns-route-options-to-rule-actions). + +**2**: + +Similar to Surge's pre-matching. + +Specifically, the new rule actions allow you to reject connections with +TCP RST (for TCP connections) and ICMP port unreachable (for UDP packets) +before connection established to improve tun's compatibility. + +See [Rule Action](/configuration/route/rule_action/). + +#### 1.11.0-alpha.6 + +* Update quic-go to v0.48.1 +* Set gateway for tun correctly +* Fixes and improvements + +#### 1.11.0-alpha.2 + +* Add warnings for usage of deprecated features +* Fixes and improvements + +#### 1.11.0-alpha.1 + +* Update quic-go to v0.48.0 +* Fixes and improvements + ### 1.10.1 * Fixes and improvements @@ -33,7 +93,7 @@ Important changes since 1.9: The new auto-redirect feature allows TUN to automatically configure connection redirection to improve proxy performance. -When auto-redirect is enabled, new route address set options will allow you to +When auto-redirect is enabled, new route address set options will allow you to automatically configure destination IP CIDR rules from a specified rule set to the firewall. Specified or unspecified destinations will bypass the sing-box routes to get better performance From beaab2e4db0f3df05cd688ad330e4177b02824b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 9 Nov 2024 21:16:11 +0800 Subject: [PATCH 15/39] refactor: Modular inbound/outbound manager --- adapter/experimental.go | 4 +- adapter/inbound.go | 10 +- adapter/inbound/manager.go | 143 +++++ adapter/inbound/registry.go | 36 +- adapter/lifecycle.go | 41 ++ adapter/lifecycle_legacy.go | 33 ++ adapter/outbound.go | 9 + adapter/outbound/manager.go | 265 +++++++++ adapter/prestart.go | 8 - adapter/router.go | 18 +- box.go | 140 +++-- box_outbound.go | 85 --- cmd/sing-box/cmd_tools.go | 6 +- cmd/sing-box/cmd_tools_connect.go | 2 +- cmd/sing-box/cmd_tools_fetch.go | 2 +- cmd/sing-box/cmd_tools_fetch_http3.go | 2 +- cmd/sing-box/cmd_tools_synctime.go | 3 +- common/dialer/detour.go | 16 +- common/dialer/dialer.go | 18 +- common/dialer/router.go | 28 +- common/settings/proxy_darwin.go | 3 +- common/tls/ech_client.go | 3 +- common/tls/reality_server.go | 3 +- experimental/clashapi.go | 6 +- experimental/clashapi/api_meta_group.go | 8 +- experimental/clashapi/proxies.go | 19 +- experimental/clashapi/server.go | 64 +-- experimental/clashapi/server_resources.go | 8 +- .../clashapi/trafficontrol/tracker.go | 16 +- protocol/direct/outbound.go | 4 +- protocol/group/selector.go | 6 +- protocol/group/urltest.go | 10 +- protocol/http/outbound.go | 2 +- protocol/hysteria/outbound.go | 2 +- protocol/hysteria2/outbound.go | 2 +- protocol/shadowsocks/outbound.go | 2 +- protocol/shadowtls/inbound.go | 4 +- protocol/shadowtls/outbound.go | 2 +- protocol/socks/outbound.go | 2 +- protocol/ssh/outbound.go | 2 +- protocol/tor/outbound.go | 2 +- protocol/trojan/outbound.go | 2 +- protocol/tuic/outbound.go | 2 +- protocol/vless/outbound.go | 2 +- protocol/vmess/outbound.go | 2 +- protocol/wireguard/outbound.go | 2 +- route/router.go | 507 ++++++++---------- route/rule/rule_action.go | 4 +- route/rule/rule_default.go | 4 +- route/rule/rule_set_remote.go | 58 +- transport/dhcp/server.go | 3 +- transport/fakeip/server.go | 3 +- 52 files changed, 965 insertions(+), 663 deletions(-) create mode 100644 adapter/inbound/manager.go create mode 100644 adapter/lifecycle.go create mode 100644 adapter/lifecycle_legacy.go create mode 100644 adapter/outbound/manager.go delete mode 100644 box_outbound.go diff --git a/adapter/experimental.go b/adapter/experimental.go index 0cab5ed5a..bee24c4f8 100644 --- a/adapter/experimental.go +++ b/adapter/experimental.go @@ -15,7 +15,7 @@ import ( type ClashServer interface { Service - PreStarter + LegacyPreStarter Mode() string ModeList() []string HistoryStorage() *urltest.HistoryStorage @@ -25,7 +25,7 @@ type ClashServer interface { type CacheFile interface { Service - PreStarter + LegacyPreStarter StoreFakeIP() bool FakeIPStorage diff --git a/adapter/inbound.go b/adapter/inbound.go index f9ed17085..d80e59f7b 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -28,7 +28,15 @@ type UDPInjectableInbound interface { type InboundRegistry interface { option.InboundOptionsRegistry - CreateInbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Inbound, error) + Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, inboundType string, options any) (Inbound, error) +} + +type InboundManager interface { + NewService + Inbounds() []Inbound + Get(tag string) (Inbound, bool) + Remove(tag string) error + Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, inboundType string, options any) error } type InboundContext struct { diff --git a/adapter/inbound/manager.go b/adapter/inbound/manager.go new file mode 100644 index 000000000..d2be4b578 --- /dev/null +++ b/adapter/inbound/manager.go @@ -0,0 +1,143 @@ +package inbound + +import ( + "context" + "os" + "sync" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/taskmonitor" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" +) + +var _ adapter.InboundManager = (*Manager)(nil) + +type Manager struct { + logger log.ContextLogger + registry adapter.InboundRegistry + access sync.Mutex + started bool + stage adapter.StartStage + inbounds []adapter.Inbound + inboundByTag map[string]adapter.Inbound +} + +func NewManager(logger log.ContextLogger, registry adapter.InboundRegistry) *Manager { + return &Manager{ + logger: logger, + registry: registry, + inboundByTag: make(map[string]adapter.Inbound), + } +} + +func (m *Manager) Start(stage adapter.StartStage) error { + m.access.Lock() + defer m.access.Unlock() + if m.started && m.stage >= stage { + panic("already started") + } + m.started = true + m.stage = stage + for _, inbound := range m.inbounds { + err := adapter.LegacyStart(inbound, stage) + if err != nil { + return E.Cause(err, stage.Action(), " inbound/", inbound.Type(), "[", inbound.Tag(), "]") + } + } + return nil +} + +func (m *Manager) Close() error { + m.access.Lock() + if !m.started { + panic("not started") + } + m.started = false + inbounds := m.inbounds + m.inbounds = nil + m.access.Unlock() + monitor := taskmonitor.New(m.logger, C.StopTimeout) + var err error + for _, inbound := range inbounds { + monitor.Start("close inbound/", inbound.Type(), "[", inbound.Tag(), "]") + err = E.Append(err, inbound.Close(), func(err error) error { + return E.Cause(err, "close inbound/", inbound.Type(), "[", inbound.Tag(), "]") + }) + monitor.Finish() + } + return nil +} + +func (m *Manager) Inbounds() []adapter.Inbound { + m.access.Lock() + defer m.access.Unlock() + return m.inbounds +} + +func (m *Manager) Get(tag string) (adapter.Inbound, bool) { + m.access.Lock() + defer m.access.Unlock() + inbound, found := m.inboundByTag[tag] + return inbound, found +} + +func (m *Manager) Remove(tag string) error { + m.access.Lock() + inbound, found := m.inboundByTag[tag] + if !found { + m.access.Unlock() + return os.ErrInvalid + } + delete(m.inboundByTag, tag) + index := common.Index(m.inbounds, func(it adapter.Inbound) bool { + return it == inbound + }) + if index == -1 { + panic("invalid inbound index") + } + m.inbounds = append(m.inbounds[:index], m.inbounds[index+1:]...) + started := m.started + m.access.Unlock() + if started { + return inbound.Close() + } + return nil +} + +func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) error { + inbound, err := m.registry.Create(ctx, router, logger, tag, outboundType, options) + if err != nil { + return err + } + m.access.Lock() + defer m.access.Unlock() + if m.started { + for _, stage := range adapter.ListStartStages { + err = adapter.LegacyStart(inbound, stage) + if err != nil { + return E.Cause(err, stage.Action(), " inbound/", inbound.Type(), "[", inbound.Tag(), "]") + } + } + } + if existsInbound, loaded := m.inboundByTag[tag]; loaded { + if m.started { + err = existsInbound.Close() + if err != nil { + return E.Cause(err, "close inbound/", existsInbound.Type(), "[", existsInbound.Tag(), "]") + } + } + existsIndex := common.Index(m.inbounds, func(it adapter.Inbound) bool { + return it == existsInbound + }) + if existsIndex == -1 { + panic("invalid inbound index") + } + m.inbounds = append(m.inbounds[:existsIndex], m.inbounds[existsIndex+1:]...) + } + m.inbounds = append(m.inbounds, inbound) + m.inboundByTag[tag] = inbound + return nil +} diff --git a/adapter/inbound/registry.go b/adapter/inbound/registry.go index 9f678c90e..622e01c7f 100644 --- a/adapter/inbound/registry.go +++ b/adapter/inbound/registry.go @@ -28,41 +28,41 @@ type ( ) type Registry struct { - access sync.Mutex - optionsType map[string]optionsConstructorFunc - constructors map[string]constructorFunc + access sync.Mutex + optionsType map[string]optionsConstructorFunc + constructor map[string]constructorFunc } func NewRegistry() *Registry { return &Registry{ - optionsType: make(map[string]optionsConstructorFunc), - constructors: make(map[string]constructorFunc), + optionsType: make(map[string]optionsConstructorFunc), + constructor: make(map[string]constructorFunc), } } -func (r *Registry) CreateOptions(outboundType string) (any, bool) { - r.access.Lock() - defer r.access.Unlock() - optionsConstructor, loaded := r.optionsType[outboundType] +func (m *Registry) CreateOptions(outboundType string) (any, bool) { + m.access.Lock() + defer m.access.Unlock() + optionsConstructor, loaded := m.optionsType[outboundType] if !loaded { return nil, false } return optionsConstructor(), true } -func (r *Registry) CreateInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Inbound, error) { - r.access.Lock() - defer r.access.Unlock() - constructor, loaded := r.constructors[outboundType] +func (m *Registry) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Inbound, error) { + m.access.Lock() + defer m.access.Unlock() + constructor, loaded := m.constructor[outboundType] if !loaded { return nil, E.New("outbound type not found: " + outboundType) } return constructor(ctx, router, logger, tag, options) } -func (r *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) { - r.access.Lock() - defer r.access.Unlock() - r.optionsType[outboundType] = optionsConstructor - r.constructors[outboundType] = constructor +func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) { + m.access.Lock() + defer m.access.Unlock() + m.optionsType[outboundType] = optionsConstructor + m.constructor[outboundType] = constructor } diff --git a/adapter/lifecycle.go b/adapter/lifecycle.go new file mode 100644 index 000000000..85de425d5 --- /dev/null +++ b/adapter/lifecycle.go @@ -0,0 +1,41 @@ +package adapter + +type StartStage uint8 + +const ( + StartStateInitialize StartStage = iota + StartStateStart + StartStatePostStart + StartStateStarted +) + +var ListStartStages = []StartStage{ + StartStateInitialize, + StartStateStart, + StartStatePostStart, + StartStateStarted, +} + +func (s StartStage) Action() string { + switch s { + case StartStateInitialize: + return "initialize" + case StartStateStart: + return "start" + case StartStatePostStart: + return "post-start" + case StartStateStarted: + return "start-after-started" + default: + panic("unknown stage") + } +} + +type NewService interface { + NewStarter + Close() error +} + +type NewStarter interface { + Start(stage StartStage) error +} diff --git a/adapter/lifecycle_legacy.go b/adapter/lifecycle_legacy.go new file mode 100644 index 000000000..5968131bb --- /dev/null +++ b/adapter/lifecycle_legacy.go @@ -0,0 +1,33 @@ +package adapter + +type LegacyPreStarter interface { + PreStart() error +} + +type LegacyPostStarter interface { + PostStart() error +} + +func LegacyStart(starter any, stage StartStage) error { + switch stage { + case StartStateInitialize: + if preStarter, isPreStarter := starter.(interface { + PreStart() error + }); isPreStarter { + return preStarter.PreStart() + } + case StartStateStart: + if starter, isStarter := starter.(interface { + Start() error + }); isStarter { + return starter.Start() + } + case StartStatePostStart: + if postStarter, isPostStarter := starter.(interface { + PostStart() error + }); isPostStarter { + return postStarter.PostStart() + } + } + return nil +} diff --git a/adapter/outbound.go b/adapter/outbound.go index df11ed613..b170398a9 100644 --- a/adapter/outbound.go +++ b/adapter/outbound.go @@ -22,3 +22,12 @@ type OutboundRegistry interface { option.OutboundOptionsRegistry CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error) } + +type OutboundManager interface { + NewService + Outbounds() []Outbound + Outbound(tag string) (Outbound, bool) + Default() Outbound + Remove(tag string) error + Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) error +} diff --git a/adapter/outbound/manager.go b/adapter/outbound/manager.go new file mode 100644 index 000000000..b3e1a170b --- /dev/null +++ b/adapter/outbound/manager.go @@ -0,0 +1,265 @@ +package outbound + +import ( + "context" + "io" + "os" + "strings" + "sync" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/taskmonitor" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" +) + +var _ adapter.OutboundManager = (*Manager)(nil) + +type Manager struct { + logger log.ContextLogger + registry adapter.OutboundRegistry + defaultTag string + access sync.Mutex + started bool + stage adapter.StartStage + outbounds []adapter.Outbound + outboundByTag map[string]adapter.Outbound + dependByTag map[string][]string + defaultOutbound adapter.Outbound + defaultOutboundFallback adapter.Outbound +} + +func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry, defaultTag string) *Manager { + return &Manager{ + logger: logger, + registry: registry, + defaultTag: defaultTag, + outboundByTag: make(map[string]adapter.Outbound), + dependByTag: make(map[string][]string), + } +} + +func (m *Manager) Initialize(defaultOutboundFallback adapter.Outbound) { + m.defaultOutboundFallback = defaultOutboundFallback +} + +func (m *Manager) Start(stage adapter.StartStage) error { + m.access.Lock() + defer m.access.Unlock() + if m.started && m.stage >= stage { + panic("already started") + } + m.started = true + m.stage = stage + if stage == adapter.StartStateStart { + m.startOutbounds() + } else { + for _, outbound := range m.outbounds { + err := adapter.LegacyStart(outbound, stage) + if err != nil { + return E.Cause(err, stage.Action(), " outbound/", outbound.Type(), "[", outbound.Tag(), "]") + } + } + } + return nil +} + +func (m *Manager) startOutbounds() error { + monitor := taskmonitor.New(m.logger, C.StartTimeout) + started := make(map[string]bool) + for { + canContinue := false + startOne: + for _, outboundToStart := range m.outbounds { + outboundTag := outboundToStart.Tag() + if started[outboundTag] { + continue + } + dependencies := outboundToStart.Dependencies() + for _, dependency := range dependencies { + if !started[dependency] { + continue startOne + } + } + started[outboundTag] = true + canContinue = true + if starter, isStarter := outboundToStart.(interface { + Start() error + }); isStarter { + monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]") + err := starter.Start() + monitor.Finish() + if err != nil { + return E.Cause(err, "start outbound/", outboundToStart.Type(), "[", outboundTag, "]") + } + } + } + if len(started) == len(m.outbounds) { + break + } + if canContinue { + continue + } + currentOutbound := common.Find(m.outbounds, func(it adapter.Outbound) bool { + return !started[it.Tag()] + }) + var lintOutbound func(oTree []string, oCurrent adapter.Outbound) error + lintOutbound = func(oTree []string, oCurrent adapter.Outbound) error { + problemOutboundTag := common.Find(oCurrent.Dependencies(), func(it string) bool { + return !started[it] + }) + if common.Contains(oTree, problemOutboundTag) { + return E.New("circular outbound dependency: ", strings.Join(oTree, " -> "), " -> ", problemOutboundTag) + } + problemOutbound := m.outboundByTag[problemOutboundTag] + if problemOutbound == nil { + return E.New("dependency[", problemOutboundTag, "] not found for outbound[", oCurrent.Tag(), "]") + } + return lintOutbound(append(oTree, problemOutboundTag), problemOutbound) + } + return lintOutbound([]string{currentOutbound.Tag()}, currentOutbound) + } + return nil +} + +func (m *Manager) Close() error { + monitor := taskmonitor.New(m.logger, C.StopTimeout) + m.access.Lock() + if !m.started { + panic("not started") + } + m.started = false + outbounds := m.outbounds + m.outbounds = nil + m.access.Unlock() + var err error + for _, outbound := range outbounds { + if closer, isCloser := outbound.(io.Closer); isCloser { + monitor.Start("close outbound/", outbound.Type(), "[", outbound.Tag(), "]") + err = E.Append(err, closer.Close(), func(err error) error { + return E.Cause(err, "close outbound/", outbound.Type(), "[", outbound.Tag(), "]") + }) + monitor.Finish() + } + } + return nil +} + +func (m *Manager) Outbounds() []adapter.Outbound { + m.access.Lock() + defer m.access.Unlock() + return m.outbounds +} + +func (m *Manager) Outbound(tag string) (adapter.Outbound, bool) { + m.access.Lock() + defer m.access.Unlock() + outbound, found := m.outboundByTag[tag] + return outbound, found +} + +func (m *Manager) Default() adapter.Outbound { + m.access.Lock() + defer m.access.Unlock() + if m.defaultOutbound != nil { + return m.defaultOutbound + } else { + return m.defaultOutboundFallback + } +} + +func (m *Manager) Remove(tag string) error { + m.access.Lock() + outbound, found := m.outboundByTag[tag] + if !found { + m.access.Unlock() + return os.ErrInvalid + } + delete(m.outboundByTag, tag) + index := common.Index(m.outbounds, func(it adapter.Outbound) bool { + return it == outbound + }) + if index == -1 { + panic("invalid inbound index") + } + m.outbounds = append(m.outbounds[:index], m.outbounds[index+1:]...) + started := m.started + if m.defaultOutbound == outbound { + if len(m.outbounds) > 0 { + m.defaultOutbound = m.outbounds[0] + m.logger.Info("updated default outbound to ", m.defaultOutbound.Tag()) + } else { + m.defaultOutbound = nil + } + } + dependBy := m.dependByTag[tag] + if len(dependBy) > 0 { + return E.New("outbound[", tag, "] is depended by ", strings.Join(dependBy, ", ")) + } + dependencies := outbound.Dependencies() + for _, dependency := range dependencies { + if len(m.dependByTag[dependency]) == 1 { + delete(m.dependByTag, dependency) + } else { + m.dependByTag[dependency] = common.Filter(m.dependByTag[dependency], func(it string) bool { + return it != tag + }) + } + } + m.access.Unlock() + if started { + return common.Close(outbound) + } + return nil +} + +func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, inboundType string, options any) error { + if tag == "" { + return os.ErrInvalid + } + outbound, err := m.registry.CreateOutbound(ctx, router, logger, tag, inboundType, options) + if err != nil { + return err + } + m.access.Lock() + defer m.access.Unlock() + if m.started { + for _, stage := range adapter.ListStartStages { + err = adapter.LegacyStart(outbound, stage) + if err != nil { + return E.Cause(err, stage.Action(), " outbound/", outbound.Type(), "[", outbound.Tag(), "]") + } + } + } + if existsOutbound, loaded := m.outboundByTag[tag]; loaded { + if m.started { + err = common.Close(existsOutbound) + if err != nil { + return E.Cause(err, "close outbound/", existsOutbound.Type(), "[", existsOutbound.Tag(), "]") + } + } + existsIndex := common.Index(m.outbounds, func(it adapter.Outbound) bool { + return it == existsOutbound + }) + if existsIndex == -1 { + panic("invalid inbound index") + } + m.outbounds = append(m.outbounds[:existsIndex], m.outbounds[existsIndex+1:]...) + } + m.outbounds = append(m.outbounds, outbound) + m.outboundByTag[tag] = outbound + dependencies := outbound.Dependencies() + for _, dependency := range dependencies { + m.dependByTag[dependency] = append(m.dependByTag[dependency], tag) + } + if tag == m.defaultTag || (m.defaultTag == "" && m.defaultOutbound == nil) { + m.defaultOutbound = outbound + if m.started { + m.logger.Info("updated default outbound to ", outbound.Tag()) + } + } + return nil +} diff --git a/adapter/prestart.go b/adapter/prestart.go index 6a39aec3f..b8e8da308 100644 --- a/adapter/prestart.go +++ b/adapter/prestart.go @@ -1,9 +1 @@ package adapter - -type PreStarter interface { - PreStart() error -} - -type PostStarter interface { - PostStart() error -} diff --git a/adapter/router.go b/adapter/router.go index c9cd46e91..b8ac51f57 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -15,21 +15,13 @@ import ( M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/x/list" - "github.com/sagernet/sing/service" mdns "github.com/miekg/dns" "go4.org/netipx" ) type Router interface { - Service - PreStarter - PostStarter - Cleanup() error - - Outbounds() []Outbound - Outbound(tag string) (Outbound, bool) - DefaultOutbound(network string) (Outbound, error) + NewService FakeIPStore() FakeIPStore @@ -84,14 +76,6 @@ type ConnectionRouterEx interface { RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc) } -func ContextWithRouter(ctx context.Context, router Router) context.Context { - return service.ContextWith(ctx, router) -} - -func RouterFromContext(ctx context.Context) Router { - return service.FromContext[Router](ctx) -} - type RuleSet interface { Name() string StartContext(ctx context.Context, startContext *HTTPStartContext) error diff --git a/box.go b/box.go index 84da77c04..a4ca530fd 100644 --- a/box.go +++ b/box.go @@ -9,6 +9,8 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/taskmonitor" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/experimental" @@ -30,8 +32,8 @@ var _ adapter.Service = (*Box)(nil) type Box struct { createdAt time.Time router adapter.Router - inbounds []adapter.Inbound - outbounds []adapter.Outbound + inbound *inbound.Manager + outbound *outbound.Manager logFactory log.Factory logger log.ContextLogger preServices1 map[string]adapter.Service @@ -66,6 +68,7 @@ func New(options Options) (*Box, error) { if ctx == nil { ctx = context.Background() } + ctx = service.ContextWithDefaultRegistry(ctx) inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx) if inboundRegistry == nil { return nil, E.New("missing inbound registry in context") @@ -74,7 +77,6 @@ func New(options Options) (*Box, error) { if outboundRegistry == nil { return nil, E.New("missing outbound registry in context") } - ctx = service.ContextWithDefaultRegistry(ctx) ctx = pause.WithDefaultManager(ctx) experimentalOptions := common.PtrValueOrDefault(options.Experimental) applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug)) @@ -106,10 +108,15 @@ func New(options Options) (*Box, error) { if err != nil { return nil, E.Cause(err, "create log factory") } + routeOptions := common.PtrValueOrDefault(options.Route) + inboundManager := inbound.NewManager(logFactory.NewLogger("inbound-manager"), inboundRegistry) + outboundManager := outbound.NewManager(logFactory.NewLogger("outbound-manager"), outboundRegistry, routeOptions.Final) + ctx = service.ContextWith[adapter.InboundManager](ctx, inboundManager) + ctx = service.ContextWith[adapter.OutboundManager](ctx, outboundManager) router, err := route.NewRouter( ctx, logFactory, - common.PtrValueOrDefault(options.Route), + routeOptions, common.PtrValueOrDefault(options.DNS), common.PtrValueOrDefault(options.NTP), options.Inbounds, @@ -127,7 +134,6 @@ func New(options Options) (*Box, error) { }) } } - inbounds := make([]adapter.Inbound, 0, len(options.Inbounds)) //nolint:staticcheck if len(options.LegacyOutbounds) > 0 { for _, legacyOutbound := range options.LegacyOutbounds { @@ -138,17 +144,14 @@ func New(options Options) (*Box, error) { }) } } - outbounds := make([]adapter.Outbound, 0, len(options.Outbounds)) for i, inboundOptions := range options.Inbounds { - var currentInbound adapter.Inbound var tag string if inboundOptions.Tag != "" { tag = inboundOptions.Tag } else { tag = F.ToString(i) } - currentInbound, err = inboundRegistry.CreateInbound( - ctx, + err = inboundManager.Create(ctx, router, logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")), tag, @@ -156,12 +159,10 @@ func New(options Options) (*Box, error) { inboundOptions.Options, ) if err != nil { - return nil, E.Cause(err, "parse inbound[", i, "]") + return nil, E.Cause(err, "initialize inbound[", i, "]") } - inbounds = append(inbounds, currentInbound) } for i, outboundOptions := range options.Outbounds { - var currentOutbound adapter.Outbound var tag string if outboundOptions.Tag != "" { tag = outboundOptions.Tag @@ -175,7 +176,7 @@ func New(options Options) (*Box, error) { Outbound: tag, }) } - currentOutbound, err = outboundRegistry.CreateOutbound( + err = outboundManager.Create( outboundCtx, router, logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")), @@ -184,16 +185,18 @@ func New(options Options) (*Box, error) { outboundOptions.Options, ) if err != nil { - return nil, E.Cause(err, "parse outbound[", i, "]") + return nil, E.Cause(err, "initialize outbound[", i, "]") } - outbounds = append(outbounds, currentOutbound) } - err = router.Initialize(inbounds, outbounds, func() adapter.Outbound { - defaultOutbound, cErr := direct.NewOutbound(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.DirectOutboundOptions{}) - common.Must(cErr) - outbounds = append(outbounds, defaultOutbound) - return defaultOutbound - }) + outboundManager.Initialize(common.Must1( + direct.NewOutbound( + ctx, + router, + logFactory.NewLogger("outbound/direct"), + "direct", + option.DirectOutboundOptions{}, + ), + )) if err != nil { return nil, err } @@ -217,7 +220,7 @@ func New(options Options) (*Box, error) { if needClashAPI { clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI) clashAPIOptions.ModeList = experimental.CalculateClashModeList(options.Options) - clashServer, err := experimental.NewClashServer(ctx, router, logFactory.(log.ObservableFactory), clashAPIOptions) + clashServer, err := experimental.NewClashServer(ctx, logFactory.(log.ObservableFactory), clashAPIOptions) if err != nil { return nil, E.Cause(err, "create clash api server") } @@ -234,8 +237,8 @@ func New(options Options) (*Box, error) { } return &Box{ router: router, - inbounds: inbounds, - outbounds: outbounds, + inbound: inboundManager, + outbound: outboundManager, createdAt: createdAt, logFactory: logFactory, logger: logFactory.Logger(), @@ -293,7 +296,7 @@ func (s *Box) preStart() error { return E.Cause(err, "start logger") } for serviceName, service := range s.preServices1 { - if preService, isPreService := service.(adapter.PreStarter); isPreService { + if preService, isPreService := service.(adapter.LegacyPreStarter); isPreService { monitor.Start("pre-start ", serviceName) err := preService.PreStart() monitor.Finish() @@ -303,7 +306,7 @@ func (s *Box) preStart() error { } } for serviceName, service := range s.preServices2 { - if preService, isPreService := service.(adapter.PreStarter); isPreService { + if preService, isPreService := service.(adapter.LegacyPreStarter); isPreService { monitor.Start("pre-start ", serviceName) err := preService.PreStart() monitor.Finish() @@ -312,15 +315,15 @@ func (s *Box) preStart() error { } } } - err = s.router.PreStart() + err = s.router.Start(adapter.StartStateInitialize) if err != nil { - return E.Cause(err, "pre-start router") + return E.Cause(err, "initialize router") } - err = s.startOutbounds() + err = s.outbound.Start(adapter.StartStateStart) if err != nil { return err } - return s.router.Start() + return s.router.Start(adapter.StartStateStart) } func (s *Box) start() error { @@ -340,52 +343,39 @@ func (s *Box) start() error { return E.Cause(err, "start ", serviceName) } } - for i, in := range s.inbounds { - var tag string - if in.Tag() == "" { - tag = F.ToString(i) - } else { - tag = in.Tag() - } - err = in.Start() - if err != nil { - return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]") - } - } - err = s.postStart() + err = s.inbound.Start(adapter.StartStateStart) if err != nil { return err } - return s.router.Cleanup() -} - -func (s *Box) postStart() error { for serviceName, service := range s.postServices { err := service.Start() if err != nil { return E.Cause(err, "start ", serviceName) } } - // TODO: reorganize ALL start order - for _, out := range s.outbounds { - if lateOutbound, isLateOutbound := out.(adapter.PostStarter); isLateOutbound { - err := lateOutbound.PostStart() - if err != nil { - return E.Cause(err, "post-start outbound/", out.Tag()) - } - } + err = s.outbound.Start(adapter.StartStatePostStart) + if err != nil { + return err } - err := s.router.PostStart() + err = s.router.Start(adapter.StartStatePostStart) if err != nil { return err } - for _, in := range s.inbounds { - if lateInbound, isLateInbound := in.(adapter.PostStarter); isLateInbound { - err = lateInbound.PostStart() - if err != nil { - return E.Cause(err, "post-start inbound/", in.Tag()) - } - } + err = s.inbound.Start(adapter.StartStatePostStart) + if err != nil { + return err + } + err = s.router.Start(adapter.StartStateStarted) + if err != nil { + return err + } + err = s.outbound.Start(adapter.StartStateStarted) + if err != nil { + return err + } + err = s.inbound.Start(adapter.StartStateStarted) + if err != nil { + return err } return nil } @@ -406,20 +396,8 @@ func (s *Box) Close() error { }) monitor.Finish() } - for i, in := range s.inbounds { - monitor.Start("close inbound/", in.Type(), "[", i, "]") - errors = E.Append(errors, in.Close(), func(err error) error { - return E.Cause(err, "close inbound/", in.Type(), "[", i, "]") - }) - monitor.Finish() - } - for i, out := range s.outbounds { - monitor.Start("close outbound/", out.Type(), "[", i, "]") - errors = E.Append(errors, common.Close(out), func(err error) error { - return E.Cause(err, "close outbound/", out.Type(), "[", i, "]") - }) - monitor.Finish() - } + errors = E.Errors(errors, s.inbound.Close()) + errors = E.Errors(errors, s.outbound.Close()) monitor.Start("close router") if err := common.Close(s.router); err != nil { errors = E.Append(errors, err, func(err error) error { @@ -449,6 +427,14 @@ func (s *Box) Close() error { return errors } +func (s *Box) Inbound() adapter.InboundManager { + return s.inbound +} + +func (s *Box) Outbound() adapter.OutboundManager { + return s.outbound +} + func (s *Box) Router() adapter.Router { return s.router } diff --git a/box_outbound.go b/box_outbound.go deleted file mode 100644 index f03f3b7d4..000000000 --- a/box_outbound.go +++ /dev/null @@ -1,85 +0,0 @@ -package box - -import ( - "strings" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/taskmonitor" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing/common" - E "github.com/sagernet/sing/common/exceptions" - F "github.com/sagernet/sing/common/format" -) - -func (s *Box) startOutbounds() error { - monitor := taskmonitor.New(s.logger, C.StartTimeout) - outboundTags := make(map[adapter.Outbound]string) - outbounds := make(map[string]adapter.Outbound) - for i, outboundToStart := range s.outbounds { - var outboundTag string - if outboundToStart.Tag() == "" { - outboundTag = F.ToString(i) - } else { - outboundTag = outboundToStart.Tag() - } - if _, exists := outbounds[outboundTag]; exists { - return E.New("outbound tag ", outboundTag, " duplicated") - } - outboundTags[outboundToStart] = outboundTag - outbounds[outboundTag] = outboundToStart - } - started := make(map[string]bool) - for { - canContinue := false - startOne: - for _, outboundToStart := range s.outbounds { - outboundTag := outboundTags[outboundToStart] - if started[outboundTag] { - continue - } - dependencies := outboundToStart.Dependencies() - for _, dependency := range dependencies { - if !started[dependency] { - continue startOne - } - } - started[outboundTag] = true - canContinue = true - if starter, isStarter := outboundToStart.(interface { - Start() error - }); isStarter { - monitor.Start("initialize outbound/", outboundToStart.Type(), "[", outboundTag, "]") - err := starter.Start() - monitor.Finish() - if err != nil { - return E.Cause(err, "initialize outbound/", outboundToStart.Type(), "[", outboundTag, "]") - } - } - } - if len(started) == len(s.outbounds) { - break - } - if canContinue { - continue - } - currentOutbound := common.Find(s.outbounds, func(it adapter.Outbound) bool { - return !started[outboundTags[it]] - }) - var lintOutbound func(oTree []string, oCurrent adapter.Outbound) error - lintOutbound = func(oTree []string, oCurrent adapter.Outbound) error { - problemOutboundTag := common.Find(oCurrent.Dependencies(), func(it string) bool { - return !started[it] - }) - if common.Contains(oTree, problemOutboundTag) { - return E.New("circular outbound dependency: ", strings.Join(oTree, " -> "), " -> ", problemOutboundTag) - } - problemOutbound := outbounds[problemOutboundTag] - if problemOutbound == nil { - return E.New("dependency[", problemOutboundTag, "] not found for outbound[", outboundTags[oCurrent], "]") - } - return lintOutbound(append(oTree, problemOutboundTag), problemOutbound) - } - return lintOutbound([]string{outboundTags[currentOutbound]}, currentOutbound) - } - return nil -} diff --git a/cmd/sing-box/cmd_tools.go b/cmd/sing-box/cmd_tools.go index 86b9302e0..8f30e0542 100644 --- a/cmd/sing-box/cmd_tools.go +++ b/cmd/sing-box/cmd_tools.go @@ -41,11 +41,11 @@ func createPreStartedClient() (*box.Box, error) { return instance, nil } -func createDialer(instance *box.Box, network string, outboundTag string) (N.Dialer, error) { +func createDialer(instance *box.Box, outboundTag string) (N.Dialer, error) { if outboundTag == "" { - return instance.Router().DefaultOutbound(N.NetworkName(network)) + return instance.Outbound().Default(), nil } else { - outbound, loaded := instance.Router().Outbound(outboundTag) + outbound, loaded := instance.Outbound().Outbound(outboundTag) if !loaded { return nil, E.New("outbound not found: ", outboundTag) } diff --git a/cmd/sing-box/cmd_tools_connect.go b/cmd/sing-box/cmd_tools_connect.go index 3ea04bcd4..d352d533e 100644 --- a/cmd/sing-box/cmd_tools_connect.go +++ b/cmd/sing-box/cmd_tools_connect.go @@ -45,7 +45,7 @@ func connect(address string) error { return err } defer instance.Close() - dialer, err := createDialer(instance, commandConnectFlagNetwork, commandToolsFlagOutbound) + dialer, err := createDialer(instance, commandToolsFlagOutbound) if err != nil { return err } diff --git a/cmd/sing-box/cmd_tools_fetch.go b/cmd/sing-box/cmd_tools_fetch.go index 3f62424a4..5ee3b8751 100644 --- a/cmd/sing-box/cmd_tools_fetch.go +++ b/cmd/sing-box/cmd_tools_fetch.go @@ -48,7 +48,7 @@ func fetch(args []string) error { httpClient = &http.Client{ Transport: &http.Transport{ DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { - dialer, err := createDialer(instance, network, commandToolsFlagOutbound) + dialer, err := createDialer(instance, commandToolsFlagOutbound) if err != nil { return nil, err } diff --git a/cmd/sing-box/cmd_tools_fetch_http3.go b/cmd/sing-box/cmd_tools_fetch_http3.go index 5dc3d9157..d72ed99d5 100644 --- a/cmd/sing-box/cmd_tools_fetch_http3.go +++ b/cmd/sing-box/cmd_tools_fetch_http3.go @@ -16,7 +16,7 @@ import ( ) func initializeHTTP3Client(instance *box.Box) error { - dialer, err := createDialer(instance, N.NetworkUDP, commandToolsFlagOutbound) + dialer, err := createDialer(instance, commandToolsFlagOutbound) if err != nil { return err } diff --git a/cmd/sing-box/cmd_tools_synctime.go b/cmd/sing-box/cmd_tools_synctime.go index 20d73a6d6..38510eb8e 100644 --- a/cmd/sing-box/cmd_tools_synctime.go +++ b/cmd/sing-box/cmd_tools_synctime.go @@ -9,7 +9,6 @@ import ( "github.com/sagernet/sing-box/log" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/ntp" "github.com/spf13/cobra" @@ -45,7 +44,7 @@ func syncTime() error { if err != nil { return err } - dialer, err := createDialer(instance, N.NetworkUDP, commandToolsFlagOutbound) + dialer, err := createDialer(instance, commandToolsFlagOutbound) if err != nil { return err } diff --git a/common/dialer/detour.go b/common/dialer/detour.go index 81600913d..c1d40faa1 100644 --- a/common/dialer/detour.go +++ b/common/dialer/detour.go @@ -12,15 +12,15 @@ import ( ) type DetourDialer struct { - router adapter.Router - detour string - dialer N.Dialer - initOnce sync.Once - initErr error + outboundManager adapter.OutboundManager + detour string + dialer N.Dialer + initOnce sync.Once + initErr error } -func NewDetour(router adapter.Router, detour string) N.Dialer { - return &DetourDialer{router: router, detour: detour} +func NewDetour(outboundManager adapter.OutboundManager, detour string) N.Dialer { + return &DetourDialer{outboundManager: outboundManager, detour: detour} } func (d *DetourDialer) Start() error { @@ -31,7 +31,7 @@ func (d *DetourDialer) Start() error { func (d *DetourDialer) Dialer() (N.Dialer, error) { d.initOnce.Do(func() { var loaded bool - d.dialer, loaded = d.router.Outbound(d.detour) + d.dialer, loaded = d.outboundManager.Outbound(d.detour) if !loaded { d.initErr = E.New("outbound detour not found: ", d.detour) } diff --git a/common/dialer/dialer.go b/common/dialer/dialer.go index 56c5f2ad1..fe4c7c120 100644 --- a/common/dialer/dialer.go +++ b/common/dialer/dialer.go @@ -1,21 +1,22 @@ package dialer import ( + "context" "time" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-dns" + E "github.com/sagernet/sing/common/exceptions" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" ) -func New(router adapter.Router, options option.DialerOptions) (N.Dialer, error) { +func New(ctx context.Context, options option.DialerOptions) (N.Dialer, error) { + router := service.FromContext[adapter.Router](ctx) if options.IsWireGuardListener { return NewDefault(router, options) } - if router == nil { - return NewDefault(nil, options) - } var ( dialer N.Dialer err error @@ -26,7 +27,14 @@ func New(router adapter.Router, options option.DialerOptions) (N.Dialer, error) return nil, err } } else { - dialer = NewDetour(router, options.Detour) + outboundManager := service.FromContext[adapter.OutboundManager](ctx) + if outboundManager == nil { + return nil, E.New("missing outbound manager") + } + dialer = NewDetour(outboundManager, options.Detour) + } + if router == nil { + return NewDefault(router, options) } if options.Detour == "" { dialer = NewResolveDialer( diff --git a/common/dialer/router.go b/common/dialer/router.go index 253160775..3edce65b0 100644 --- a/common/dialer/router.go +++ b/common/dialer/router.go @@ -9,30 +9,22 @@ import ( N "github.com/sagernet/sing/common/network" ) -type RouterDialer struct { - router adapter.Router +type DefaultOutboundDialer struct { + outboundManager adapter.OutboundManager } -func NewRouter(router adapter.Router) N.Dialer { - return &RouterDialer{router: router} +func NewDefaultOutbound(outboundManager adapter.OutboundManager) N.Dialer { + return &DefaultOutboundDialer{outboundManager: outboundManager} } -func (d *RouterDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - dialer, err := d.router.DefaultOutbound(network) - if err != nil { - return nil, err - } - return dialer.DialContext(ctx, network, destination) +func (d *DefaultOutboundDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + return d.outboundManager.Default().DialContext(ctx, network, destination) } -func (d *RouterDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - dialer, err := d.router.DefaultOutbound(N.NetworkUDP) - if err != nil { - return nil, err - } - return dialer.ListenPacket(ctx, destination) +func (d *DefaultOutboundDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + return d.outboundManager.Default().ListenPacket(ctx, destination) } -func (d *RouterDialer) Upstream() any { - return d.router +func (d *DefaultOutboundDialer) Upstream() any { + return d.outboundManager.Default() } diff --git a/common/settings/proxy_darwin.go b/common/settings/proxy_darwin.go index f03658a85..2d86fa2d9 100644 --- a/common/settings/proxy_darwin.go +++ b/common/settings/proxy_darwin.go @@ -12,6 +12,7 @@ import ( M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/common/shell" "github.com/sagernet/sing/common/x/list" + "github.com/sagernet/sing/service" ) type DarwinSystemProxy struct { @@ -24,7 +25,7 @@ type DarwinSystemProxy struct { } func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*DarwinSystemProxy, error) { - interfaceMonitor := adapter.RouterFromContext(ctx).InterfaceMonitor() + interfaceMonitor := service.FromContext[adapter.Router](ctx).InterfaceMonitor() if interfaceMonitor == nil { return nil, E.New("missing interface monitor") } diff --git a/common/tls/ech_client.go b/common/tls/ech_client.go index 7f72b4d8a..0433cebdc 100644 --- a/common/tls/ech_client.go +++ b/common/tls/ech_client.go @@ -19,6 +19,7 @@ import ( "github.com/sagernet/sing-dns" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/ntp" + "github.com/sagernet/sing/service" mDNS "github.com/miekg/dns" ) @@ -213,7 +214,7 @@ func fetchECHClientConfig(ctx context.Context) func(_ context.Context, serverNam }, }, } - response, err := adapter.RouterFromContext(ctx).Exchange(ctx, message) + response, err := service.FromContext[adapter.Router](ctx).Exchange(ctx, message) if err != nil { return nil, err } diff --git a/common/tls/reality_server.go b/common/tls/reality_server.go index a93187983..06501d9e8 100644 --- a/common/tls/reality_server.go +++ b/common/tls/reality_server.go @@ -11,7 +11,6 @@ import ( "time" "github.com/sagernet/reality" - "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" @@ -102,7 +101,7 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb tlsConfig.ShortIds[shortID] = true } - handshakeDialer, err := dialer.New(adapter.RouterFromContext(ctx), options.Reality.Handshake.DialerOptions) + handshakeDialer, err := dialer.New(ctx, options.Reality.Handshake.DialerOptions) if err != nil { return nil, err } diff --git a/experimental/clashapi.go b/experimental/clashapi.go index 872d9b995..4ad07c8b8 100644 --- a/experimental/clashapi.go +++ b/experimental/clashapi.go @@ -12,7 +12,7 @@ import ( "github.com/sagernet/sing/common" ) -type ClashServerConstructor = func(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) +type ClashServerConstructor = func(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) var clashServerConstructor ClashServerConstructor @@ -20,11 +20,11 @@ func RegisterClashServerConstructor(constructor ClashServerConstructor) { clashServerConstructor = constructor } -func NewClashServer(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { +func NewClashServer(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { if clashServerConstructor == nil { return nil, os.ErrInvalid } - return clashServerConstructor(ctx, router, logFactory, options) + return clashServerConstructor(ctx, logFactory, options) } func CalculateClashModeList(options option.Options) []string { diff --git a/experimental/clashapi/api_meta_group.go b/experimental/clashapi/api_meta_group.go index 531311f4f..c5c07ba6a 100644 --- a/experimental/clashapi/api_meta_group.go +++ b/experimental/clashapi/api_meta_group.go @@ -23,7 +23,7 @@ func groupRouter(server *Server) http.Handler { r := chi.NewRouter() r.Get("/", getGroups(server)) r.Route("/{name}", func(r chi.Router) { - r.Use(parseProxyName, findProxyByName(server.router)) + r.Use(parseProxyName, findProxyByName(server)) r.Get("/", getGroup(server)) r.Get("/delay", getGroupDelay(server)) }) @@ -32,7 +32,7 @@ func groupRouter(server *Server) http.Handler { func getGroups(server *Server) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { - groups := common.Map(common.Filter(server.router.Outbounds(), func(it adapter.Outbound) bool { + groups := common.Map(common.Filter(server.outboundManager.Outbounds(), func(it adapter.Outbound) bool { _, isGroup := it.(adapter.OutboundGroup) return isGroup }), func(it adapter.Outbound) *badjson.JSONObject { @@ -86,7 +86,7 @@ func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) result, err = urlTestGroup.URLTest(ctx) } else { outbounds := common.FilterNotNil(common.Map(outboundGroup.All(), func(it string) adapter.Outbound { - itOutbound, _ := server.router.Outbound(it) + itOutbound, _ := server.outboundManager.Outbound(it) return itOutbound })) b, _ := batch.New(ctx, batch.WithConcurrencyNum[any](10)) @@ -100,7 +100,7 @@ func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) continue } checked[realTag] = true - p, loaded := server.router.Outbound(realTag) + p, loaded := server.outboundManager.Outbound(realTag) if !loaded { continue } diff --git a/experimental/clashapi/proxies.go b/experimental/clashapi/proxies.go index 4a9564ee8..8d8ecb384 100644 --- a/experimental/clashapi/proxies.go +++ b/experimental/clashapi/proxies.go @@ -23,10 +23,10 @@ import ( func proxyRouter(server *Server, router adapter.Router) http.Handler { r := chi.NewRouter() - r.Get("/", getProxies(server, router)) + r.Get("/", getProxies(server)) r.Route("/{name}", func(r chi.Router) { - r.Use(parseProxyName, findProxyByName(router)) + r.Use(parseProxyName, findProxyByName(server)) r.Get("/", getProxy(server)) r.Get("/delay", getProxyDelay(server)) r.Put("/", updateProxy) @@ -42,11 +42,11 @@ func parseProxyName(next http.Handler) http.Handler { }) } -func findProxyByName(router adapter.Router) func(next http.Handler) http.Handler { +func findProxyByName(server *Server) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { name := r.Context().Value(CtxKeyProxyName).(string) - proxy, exist := router.Outbound(name) + proxy, exist := server.outboundManager.Outbound(name) if !exist { render.Status(r, http.StatusNotFound) render.JSON(w, r, ErrNotFound) @@ -83,10 +83,10 @@ func proxyInfo(server *Server, detour adapter.Outbound) *badjson.JSONObject { return &info } -func getProxies(server *Server, router adapter.Router) func(w http.ResponseWriter, r *http.Request) { +func getProxies(server *Server) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var proxyMap badjson.JSONObject - outbounds := common.Filter(router.Outbounds(), func(detour adapter.Outbound) bool { + outbounds := common.Filter(server.outboundManager.Outbounds(), func(detour adapter.Outbound) bool { return detour.Tag() != "" }) @@ -100,12 +100,7 @@ func getProxies(server *Server, router adapter.Router) func(w http.ResponseWrite allProxies = append(allProxies, detour.Tag()) } - var defaultTag string - if defaultOutbound, err := router.DefaultOutbound(N.NetworkTCP); err == nil { - defaultTag = defaultOutbound.Tag() - } else { - defaultTag = allProxies[0] - } + defaultTag := server.outboundManager.Default().Tag() sort.SliceStable(allProxies, func(i, j int) bool { return allProxies[i] == defaultTag diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index 889d191e0..ef08a4be1 100644 --- a/experimental/clashapi/server.go +++ b/experimental/clashapi/server.go @@ -40,15 +40,16 @@ func init() { var _ adapter.ClashServer = (*Server)(nil) type Server struct { - ctx context.Context - router adapter.Router - logger log.Logger - httpServer *http.Server - trafficManager *trafficontrol.Manager - urlTestHistory *urltest.HistoryStorage - mode string - modeList []string - modeUpdateHook chan<- struct{} + ctx context.Context + router adapter.Router + outboundManager adapter.OutboundManager + logger log.Logger + httpServer *http.Server + trafficManager *trafficontrol.Manager + urlTestHistory *urltest.HistoryStorage + mode string + modeList []string + modeUpdateHook chan<- struct{} externalController bool externalUI string @@ -56,13 +57,14 @@ type Server struct { externalUIDownloadDetour string } -func NewServer(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { +func NewServer(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { trafficManager := trafficontrol.NewManager() chiRouter := chi.NewRouter() - server := &Server{ - ctx: ctx, - router: router, - logger: logFactory.NewLogger("clash-api"), + s := &Server{ + ctx: ctx, + router: service.FromContext[adapter.Router](ctx), + outboundManager: service.FromContext[adapter.OutboundManager](ctx), + logger: logFactory.NewLogger("clash-api"), httpServer: &http.Server{ Addr: options.ExternalController, Handler: chiRouter, @@ -73,18 +75,18 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ externalUIDownloadURL: options.ExternalUIDownloadURL, externalUIDownloadDetour: options.ExternalUIDownloadDetour, } - server.urlTestHistory = service.PtrFromContext[urltest.HistoryStorage](ctx) - if server.urlTestHistory == nil { - server.urlTestHistory = urltest.NewHistoryStorage() + s.urlTestHistory = service.PtrFromContext[urltest.HistoryStorage](ctx) + if s.urlTestHistory == nil { + s.urlTestHistory = urltest.NewHistoryStorage() } defaultMode := "Rule" if options.DefaultMode != "" { defaultMode = options.DefaultMode } - if !common.Contains(server.modeList, defaultMode) { - server.modeList = append([]string{defaultMode}, server.modeList...) + if !common.Contains(s.modeList, defaultMode) { + s.modeList = append([]string{defaultMode}, s.modeList...) } - server.mode = defaultMode + s.mode = defaultMode //goland:noinspection GoDeprecation //nolint:staticcheck if options.StoreMode || options.StoreSelected || options.StoreFakeIP || options.CacheFile != "" || options.CacheID != "" { @@ -108,30 +110,30 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ r.Get("/logs", getLogs(logFactory)) r.Get("/traffic", traffic(trafficManager)) r.Get("/version", version) - r.Mount("/configs", configRouter(server, logFactory)) - r.Mount("/proxies", proxyRouter(server, router)) - r.Mount("/rules", ruleRouter(router)) - r.Mount("/connections", connectionRouter(router, trafficManager)) + r.Mount("/configs", configRouter(s, logFactory)) + r.Mount("/proxies", proxyRouter(s, s.router)) + r.Mount("/rules", ruleRouter(s.router)) + r.Mount("/connections", connectionRouter(s.router, trafficManager)) r.Mount("/providers/proxies", proxyProviderRouter()) r.Mount("/providers/rules", ruleProviderRouter()) r.Mount("/script", scriptRouter()) r.Mount("/profile", profileRouter()) r.Mount("/cache", cacheRouter(ctx)) - r.Mount("/dns", dnsRouter(router)) + r.Mount("/dns", dnsRouter(s.router)) - server.setupMetaAPI(r) + s.setupMetaAPI(r) }) if options.ExternalUI != "" { - server.externalUI = filemanager.BasePath(ctx, os.ExpandEnv(options.ExternalUI)) + s.externalUI = filemanager.BasePath(ctx, os.ExpandEnv(options.ExternalUI)) chiRouter.Group(func(r chi.Router) { - fs := http.StripPrefix("/ui", http.FileServer(http.Dir(server.externalUI))) + fs := http.StripPrefix("/ui", http.FileServer(http.Dir(s.externalUI))) r.Get("/ui", http.RedirectHandler("/ui/", http.StatusTemporaryRedirect).ServeHTTP) r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) { fs.ServeHTTP(w, r) }) }) } - return server, nil + return s, nil } func (s *Server) PreStart() error { @@ -235,12 +237,12 @@ func (s *Server) TrafficManager() *trafficontrol.Manager { } func (s *Server) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule) (net.Conn, adapter.Tracker) { - tracker := trafficontrol.NewTCPTracker(conn, s.trafficManager, metadata, s.router, matchedRule) + tracker := trafficontrol.NewTCPTracker(conn, s.trafficManager, metadata, s.outboundManager, matchedRule) return tracker, tracker } func (s *Server) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule) (N.PacketConn, adapter.Tracker) { - tracker := trafficontrol.NewUDPTracker(conn, s.trafficManager, metadata, s.router, matchedRule) + tracker := trafficontrol.NewUDPTracker(conn, s.trafficManager, metadata, s.outboundManager, matchedRule) return tracker, tracker } diff --git a/experimental/clashapi/server_resources.go b/experimental/clashapi/server_resources.go index a5c79e0cf..e5b28e300 100644 --- a/experimental/clashapi/server_resources.go +++ b/experimental/clashapi/server_resources.go @@ -15,7 +15,6 @@ import ( "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/service/filemanager" ) @@ -45,16 +44,13 @@ func (s *Server) downloadExternalUI() error { s.logger.Info("downloading external ui") var detour adapter.Outbound if s.externalUIDownloadDetour != "" { - outbound, loaded := s.router.Outbound(s.externalUIDownloadDetour) + outbound, loaded := s.outboundManager.Outbound(s.externalUIDownloadDetour) if !loaded { return E.New("detour outbound not found: ", s.externalUIDownloadDetour) } detour = outbound } else { - outbound, err := s.router.DefaultOutbound(N.NetworkTCP) - if err != nil { - return err - } + outbound := s.outboundManager.Default() detour = outbound } httpClient := &http.Client{ diff --git a/experimental/clashapi/trafficontrol/tracker.go b/experimental/clashapi/trafficontrol/tracker.go index 9c18abebf..df5437faf 100644 --- a/experimental/clashapi/trafficontrol/tracker.go +++ b/experimental/clashapi/trafficontrol/tracker.go @@ -124,7 +124,7 @@ func (tt *TCPConn) WriterReplaceable() bool { return true } -func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundContext, router adapter.Router, rule adapter.Rule) *TCPConn { +func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundContext, outboundManager adapter.OutboundManager, rule adapter.Rule) *TCPConn { id, _ := uuid.NewV4() var ( chain []string @@ -138,11 +138,11 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundCont } if routeAction, isRouteAction := action.(*R.RuleActionRoute); isRouteAction { next = routeAction.Outbound - } else if defaultOutbound, err := router.DefaultOutbound(N.NetworkTCP); err == nil { - next = defaultOutbound.Tag() + } else { + next = outboundManager.Default().Tag() } for { - detour, loaded := router.Outbound(next) + detour, loaded := outboundManager.Outbound(next) if !loaded { break } @@ -213,7 +213,7 @@ func (ut *UDPConn) WriterReplaceable() bool { return true } -func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.InboundContext, router adapter.Router, rule adapter.Rule) *UDPConn { +func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.InboundContext, outboundManager adapter.OutboundManager, rule adapter.Rule) *UDPConn { id, _ := uuid.NewV4() var ( chain []string @@ -227,11 +227,11 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.Inbound } if routeAction, isRouteAction := action.(*R.RuleActionRoute); isRouteAction { next = routeAction.Outbound - } else if defaultOutbound, err := router.DefaultOutbound(N.NetworkUDP); err == nil { - next = defaultOutbound.Tag() + } else { + next = outboundManager.Default().Tag() } for { - detour, loaded := router.Outbound(next) + detour, loaded := outboundManager.Outbound(next) if !loaded { break } diff --git a/protocol/direct/outbound.go b/protocol/direct/outbound.go index 32c1ed8fd..27b334c9a 100644 --- a/protocol/direct/outbound.go +++ b/protocol/direct/outbound.go @@ -12,7 +12,7 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-dns" + dns "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" @@ -39,7 +39,7 @@ type Outbound struct { func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.DirectOutboundOptions) (adapter.Outbound, error) { options.UDPFragmentDefault = true - outboundDialer, err := dialer.New(router, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions) if err != nil { return nil, err } diff --git a/protocol/group/selector.go b/protocol/group/selector.go index 32ab8b2a4..dc0b7cceb 100644 --- a/protocol/group/selector.go +++ b/protocol/group/selector.go @@ -26,7 +26,7 @@ var _ adapter.OutboundGroup = (*Selector)(nil) type Selector struct { outbound.Adapter ctx context.Context - router adapter.Router + outboundManager adapter.OutboundManager logger logger.ContextLogger tags []string defaultTag string @@ -40,7 +40,7 @@ func NewSelector(ctx context.Context, router adapter.Router, logger log.ContextL outbound := &Selector{ Adapter: outbound.NewAdapter(C.TypeSelector, nil, tag, options.Outbounds), ctx: ctx, - router: router, + outboundManager: service.FromContext[adapter.OutboundManager](ctx), logger: logger, tags: options.Outbounds, defaultTag: options.Default, @@ -63,7 +63,7 @@ func (s *Selector) Network() []string { func (s *Selector) Start() error { for i, tag := range s.tags { - detour, loaded := s.router.Outbound(tag) + detour, loaded := s.outboundManager.Outbound(tag) if !loaded { return E.New("outbound ", i, " not found: ", tag) } diff --git a/protocol/group/urltest.go b/protocol/group/urltest.go index ccdf809d9..4d76a31c6 100644 --- a/protocol/group/urltest.go +++ b/protocol/group/urltest.go @@ -36,6 +36,7 @@ type URLTest struct { outbound.Adapter ctx context.Context router adapter.Router + outboundManager adapter.OutboundManager logger log.ContextLogger tags []string link string @@ -51,6 +52,7 @@ func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLo Adapter: outbound.NewAdapter(C.TypeURLTest, []string{N.NetworkTCP, N.NetworkUDP}, tag, options.Outbounds), ctx: ctx, router: router, + outboundManager: service.FromContext[adapter.OutboundManager](ctx), logger: logger, tags: options.Outbounds, link: options.URL, @@ -68,7 +70,7 @@ func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLo func (s *URLTest) Start() error { outbounds := make([]adapter.Outbound, 0, len(s.tags)) for i, tag := range s.tags { - detour, loaded := s.router.Outbound(tag) + detour, loaded := s.outboundManager.Outbound(tag) if !loaded { return E.New("outbound ", i, " not found: ", tag) } @@ -77,6 +79,7 @@ func (s *URLTest) Start() error { group, err := NewURLTestGroup( s.ctx, s.router, + s.outboundManager, s.logger, outbounds, s.link, @@ -190,6 +193,7 @@ func (s *URLTest) InterfaceUpdated() { type URLTestGroup struct { ctx context.Context router adapter.Router + outboundManager adapter.OutboundManager logger log.Logger outbounds []adapter.Outbound link string @@ -214,6 +218,7 @@ type URLTestGroup struct { func NewURLTestGroup( ctx context.Context, router adapter.Router, + outboundManager adapter.OutboundManager, logger log.Logger, outbounds []adapter.Outbound, link string, @@ -244,6 +249,7 @@ func NewURLTestGroup( return &URLTestGroup{ ctx: ctx, router: router, + outboundManager: outboundManager, logger: logger, outbounds: outbounds, link: link, @@ -385,7 +391,7 @@ func (g *URLTestGroup) urlTest(ctx context.Context, force bool) (map[string]uint continue } checked[realTag] = true - p, loaded := g.router.Outbound(realTag) + p, loaded := g.outboundManager.Outbound(realTag) if !loaded { continue } diff --git a/protocol/http/outbound.go b/protocol/http/outbound.go index 4c930591b..81fd02466 100644 --- a/protocol/http/outbound.go +++ b/protocol/http/outbound.go @@ -30,7 +30,7 @@ type Outbound struct { } func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPOutboundOptions) (adapter.Outbound, error) { - outboundDialer, err := dialer.New(router, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions) if err != nil { return nil, err } diff --git a/protocol/hysteria/outbound.go b/protocol/hysteria/outbound.go index 4722f4f06..e4c8775fa 100644 --- a/protocol/hysteria/outbound.go +++ b/protocol/hysteria/outbound.go @@ -47,7 +47,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL if err != nil { return nil, err } - outboundDialer, err := dialer.New(router, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions) if err != nil { return nil, err } diff --git a/protocol/hysteria2/outbound.go b/protocol/hysteria2/outbound.go index 5ebc6c91f..4cabb4751 100644 --- a/protocol/hysteria2/outbound.go +++ b/protocol/hysteria2/outbound.go @@ -59,7 +59,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL return nil, E.New("unknown obfs type: ", options.Obfs.Type) } } - outboundDialer, err := dialer.New(router, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions) if err != nil { return nil, err } diff --git a/protocol/shadowsocks/outbound.go b/protocol/shadowsocks/outbound.go index 73b383850..8771fa8e9 100644 --- a/protocol/shadowsocks/outbound.go +++ b/protocol/shadowsocks/outbound.go @@ -44,7 +44,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL if err != nil { return nil, err } - outboundDialer, err := dialer.New(router, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions) if err != nil { return nil, err } diff --git a/protocol/shadowtls/inbound.go b/protocol/shadowtls/inbound.go index 6887e838a..ce0431a6a 100644 --- a/protocol/shadowtls/inbound.go +++ b/protocol/shadowtls/inbound.go @@ -46,7 +46,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo if options.Version > 1 { handshakeForServerName = make(map[string]shadowtls.HandshakeConfig) for serverName, serverOptions := range options.HandshakeForServerName { - handshakeDialer, err := dialer.New(router, serverOptions.DialerOptions) + handshakeDialer, err := dialer.New(ctx, serverOptions.DialerOptions) if err != nil { return nil, err } @@ -56,7 +56,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo } } } - handshakeDialer, err := dialer.New(router, options.Handshake.DialerOptions) + handshakeDialer, err := dialer.New(ctx, options.Handshake.DialerOptions) if err != nil { return nil, err } diff --git a/protocol/shadowtls/outbound.go b/protocol/shadowtls/outbound.go index 7d46a8f68..e979dba27 100644 --- a/protocol/shadowtls/outbound.go +++ b/protocol/shadowtls/outbound.go @@ -68,7 +68,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL tlsHandshakeFunc = shadowtls.DefaultTLSHandshakeFunc(options.Password, stdTLSConfig) } } - outboundDialer, err := dialer.New(router, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions) if err != nil { return nil, err } diff --git a/protocol/socks/outbound.go b/protocol/socks/outbound.go index 0194800a7..dbb5ab617 100644 --- a/protocol/socks/outbound.go +++ b/protocol/socks/outbound.go @@ -46,7 +46,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL if err != nil { return nil, err } - outboundDialer, err := dialer.New(router, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions) if err != nil { return nil, err } diff --git a/protocol/ssh/outbound.go b/protocol/ssh/outbound.go index 62a2a8d9b..1dfc1f6d6 100644 --- a/protocol/ssh/outbound.go +++ b/protocol/ssh/outbound.go @@ -49,7 +49,7 @@ type Outbound struct { } func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SSHOutboundOptions) (adapter.Outbound, error) { - outboundDialer, err := dialer.New(router, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions) if err != nil { return nil, err } diff --git a/protocol/tor/outbound.go b/protocol/tor/outbound.go index 89a295b82..3d2170115 100644 --- a/protocol/tor/outbound.go +++ b/protocol/tor/outbound.go @@ -75,7 +75,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL } startConf.TorrcFile = torrcFile } - outboundDialer, err := dialer.New(router, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions) if err != nil { return nil, err } diff --git a/protocol/trojan/outbound.go b/protocol/trojan/outbound.go index f64c48c3c..68b006904 100644 --- a/protocol/trojan/outbound.go +++ b/protocol/trojan/outbound.go @@ -38,7 +38,7 @@ type Outbound struct { } func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanOutboundOptions) (adapter.Outbound, error) { - outboundDialer, err := dialer.New(router, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions) if err != nil { return nil, err } diff --git a/protocol/tuic/outbound.go b/protocol/tuic/outbound.go index 691d1658d..177f21fc9 100644 --- a/protocol/tuic/outbound.go +++ b/protocol/tuic/outbound.go @@ -60,7 +60,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL case "quic": tuicUDPStream = true } - outboundDialer, err := dialer.New(router, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions) if err != nil { return nil, err } diff --git a/protocol/vless/outbound.go b/protocol/vless/outbound.go index 1074549e8..de655230e 100644 --- a/protocol/vless/outbound.go +++ b/protocol/vless/outbound.go @@ -41,7 +41,7 @@ type Outbound struct { } func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSOutboundOptions) (adapter.Outbound, error) { - outboundDialer, err := dialer.New(router, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions) if err != nil { return nil, err } diff --git a/protocol/vmess/outbound.go b/protocol/vmess/outbound.go index 759ea8baa..1e84639f4 100644 --- a/protocol/vmess/outbound.go +++ b/protocol/vmess/outbound.go @@ -41,7 +41,7 @@ type Outbound struct { } func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessOutboundOptions) (adapter.Outbound, error) { - outboundDialer, err := dialer.New(router, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions) if err != nil { return nil, err } diff --git a/protocol/wireguard/outbound.go b/protocol/wireguard/outbound.go index 7251de9e6..90f76c1ac 100644 --- a/protocol/wireguard/outbound.go +++ b/protocol/wireguard/outbound.go @@ -78,7 +78,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL options.IsWireGuardListener = true outbound.useStdNetBind = true } - listener, err := dialer.New(router, options.DialerOptions) + listener, err := dialer.New(ctx, options.DialerOptions) if err != nil { return nil, err } diff --git a/route/router.go b/route/router.go index 9a57328a4..557b043ae 100644 --- a/route/router.go +++ b/route/router.go @@ -87,7 +87,7 @@ type Router struct { v2rayServer adapter.V2RayServer platformInterface platform.Interface needWIFIState bool - needPackageManager bool + enforcePackageManager bool wifiState adapter.WIFIState started bool } @@ -123,7 +123,7 @@ func NewRouter( pauseManager: service.FromContext[pause.Manager](ctx), platformInterface: service.FromContext[platform.Interface](ctx), needWIFIState: hasRule(options.Rules, isWIFIRule) || hasDNSRule(dnsOptions.Rules, isWIFIDNSRule), - needPackageManager: common.Any(inbounds, func(inbound option.Inbound) bool { + enforcePackageManager: common.Any(inbounds, func(inbound option.Inbound) bool { if tunOptions, isTUN := inbound.Options.(*option.TunInboundOptions); isTUN && tunOptions.AutoRoute { return true } @@ -191,7 +191,8 @@ func NewRouter( transportTags[i] = tag transportTagMap[tag] = true } - ctx = adapter.ContextWithRouter(ctx, router) + ctx = service.ContextWith[adapter.Router](ctx, router) + outboundManager := service.FromContext[adapter.OutboundManager](ctx) for { lastLen := len(dummyTransportMap) for i, server := range dnsOptions.Servers { @@ -201,9 +202,9 @@ func NewRouter( } var detour N.Dialer if server.Detour == "" { - detour = dialer.NewRouter(router) + detour = dialer.NewDefaultOutbound(outboundManager) } else { - detour = dialer.NewDetour(router, server.Detour) + detour = dialer.NewDetour(outboundManager, server.Detour) } var serverProtocol string switch server.Address { @@ -327,7 +328,7 @@ func NewRouter( } usePlatformDefaultInterfaceMonitor := router.platformInterface != nil && router.platformInterface.UsePlatformDefaultInterfaceMonitor() - needInterfaceMonitor := options.AutoDetectInterface || common.Any(inbounds, func(inbound option.Inbound) bool { + enforceInterfaceMonitor := options.AutoDetectInterface || common.Any(inbounds, func(inbound option.Inbound) bool { if httpMixedOptions, isHTTPMixed := inbound.Options.(*option.HTTPMixedInboundOptions); isHTTPMixed && httpMixedOptions.SetSystemProxy { return true } @@ -339,7 +340,7 @@ func NewRouter( if !usePlatformDefaultInterfaceMonitor { networkMonitor, err := tun.NewNetworkUpdateMonitor(router.logger) - if !((err != nil && !needInterfaceMonitor) || errors.Is(err, os.ErrInvalid)) { + if !((err != nil && !enforceInterfaceMonitor) || errors.Is(err, os.ErrInvalid)) { if err != nil { return nil, err } @@ -365,7 +366,7 @@ func NewRouter( } if ntpOptions.Enabled { - ntpDialer, err := dialer.New(router, ntpOptions.DialerOptions) + ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions) if err != nil { return nil, E.Cause(err, "create NTP service") } @@ -383,73 +384,6 @@ func NewRouter( return router, nil } -func (r *Router) Initialize(inbounds []adapter.Inbound, outbounds []adapter.Outbound, defaultOutbound func() adapter.Outbound) error { - inboundByTag := make(map[string]adapter.Inbound) - for _, inbound := range inbounds { - inboundByTag[inbound.Tag()] = inbound - } - outboundByTag := make(map[string]adapter.Outbound) - for _, detour := range outbounds { - outboundByTag[detour.Tag()] = detour - } - var defaultOutboundForConnection adapter.Outbound - var defaultOutboundForPacketConnection adapter.Outbound - if r.defaultDetour != "" { - detour, loaded := outboundByTag[r.defaultDetour] - if !loaded { - return E.New("default detour not found: ", r.defaultDetour) - } - if common.Contains(detour.Network(), N.NetworkTCP) { - defaultOutboundForConnection = detour - } - if common.Contains(detour.Network(), N.NetworkUDP) { - defaultOutboundForPacketConnection = detour - } - } - if defaultOutboundForConnection == nil { - for _, detour := range outbounds { - if common.Contains(detour.Network(), N.NetworkTCP) { - defaultOutboundForConnection = detour - break - } - } - } - if defaultOutboundForPacketConnection == nil { - for _, detour := range outbounds { - if common.Contains(detour.Network(), N.NetworkUDP) { - defaultOutboundForPacketConnection = detour - break - } - } - } - if defaultOutboundForConnection == nil || defaultOutboundForPacketConnection == nil { - detour := defaultOutbound() - if defaultOutboundForConnection == nil { - defaultOutboundForConnection = detour - } - if defaultOutboundForPacketConnection == nil { - defaultOutboundForPacketConnection = detour - } - outbounds = append(outbounds, detour) - outboundByTag[detour.Tag()] = detour - } - r.inboundByTag = inboundByTag - r.outbounds = outbounds - r.defaultOutboundForConnection = defaultOutboundForConnection - r.defaultOutboundForPacketConnection = defaultOutboundForPacketConnection - r.outboundByTag = outboundByTag - for i, rule := range r.rules { - routeAction, isRoute := rule.Action().(*R.RuleActionRoute) - if !isRoute { - continue - } - if _, loaded := outboundByTag[routeAction.Outbound]; !loaded { - return E.New("outbound not found for rule[", i, "]: ", routeAction.Outbound) - } - } - return nil -} - func (r *Router) Outbounds() []adapter.Outbound { if !r.started { return nil @@ -457,140 +391,240 @@ func (r *Router) Outbounds() []adapter.Outbound { return r.outbounds } -func (r *Router) PreStart() error { +func (r *Router) Start(stage adapter.StartStage) error { monitor := taskmonitor.New(r.logger, C.StartTimeout) - if r.interfaceMonitor != nil { - monitor.Start("initialize interface monitor") - err := r.interfaceMonitor.Start() - monitor.Finish() - if err != nil { - return err - } - } - if r.networkMonitor != nil { - monitor.Start("initialize network monitor") - err := r.networkMonitor.Start() - monitor.Finish() - if err != nil { - return err + switch stage { + case adapter.StartStateInitialize: + if r.interfaceMonitor != nil { + monitor.Start("initialize interface monitor") + err := r.interfaceMonitor.Start() + monitor.Finish() + if err != nil { + return err + } } - } - if r.fakeIPStore != nil { - monitor.Start("initialize fakeip store") - err := r.fakeIPStore.Start() - monitor.Finish() - if err != nil { - return err + if r.networkMonitor != nil { + monitor.Start("initialize network monitor") + err := r.networkMonitor.Start() + monitor.Finish() + if err != nil { + return err + } } - } - return nil -} - -func (r *Router) Start() error { - monitor := taskmonitor.New(r.logger, C.StartTimeout) - if r.needGeoIPDatabase { - monitor.Start("initialize geoip database") - err := r.prepareGeoIPDatabase() - monitor.Finish() - if err != nil { - return err + if r.fakeIPStore != nil { + monitor.Start("initialize fakeip store") + err := r.fakeIPStore.Start() + monitor.Finish() + if err != nil { + return err + } } - } - if r.needGeositeDatabase { - monitor.Start("initialize geosite database") - err := r.prepareGeositeDatabase() - monitor.Finish() - if err != nil { - return err + case adapter.StartStateStart: + if r.needGeoIPDatabase { + monitor.Start("initialize geoip database") + err := r.prepareGeoIPDatabase() + monitor.Finish() + if err != nil { + return err + } } - } - if r.needGeositeDatabase { - for _, rule := range r.rules { - err := rule.UpdateGeosite() + if r.needGeositeDatabase { + monitor.Start("initialize geosite database") + err := r.prepareGeositeDatabase() + monitor.Finish() if err != nil { - r.logger.Error("failed to initialize geosite: ", err) + return err } } - for _, rule := range r.dnsRules { - err := rule.UpdateGeosite() + if r.needGeositeDatabase { + for _, rule := range r.rules { + err := rule.UpdateGeosite() + if err != nil { + r.logger.Error("failed to initialize geosite: ", err) + } + } + for _, rule := range r.dnsRules { + err := rule.UpdateGeosite() + if err != nil { + r.logger.Error("failed to initialize geosite: ", err) + } + } + err := common.Close(r.geositeReader) if err != nil { - r.logger.Error("failed to initialize geosite: ", err) + return err } + r.geositeCache = nil + r.geositeReader = nil } - err := common.Close(r.geositeReader) - if err != nil { - return err + + if runtime.GOOS == "windows" { + powerListener, err := winpowrprof.NewEventListener(r.notifyWindowsPowerEvent) + if err == nil { + r.powerListener = powerListener + } else { + r.logger.Warn("initialize power listener: ", err) + } } - r.geositeCache = nil - r.geositeReader = nil - } - if runtime.GOOS == "windows" { - powerListener, err := winpowrprof.NewEventListener(r.notifyWindowsPowerEvent) - if err == nil { - r.powerListener = powerListener - } else { - r.logger.Warn("initialize power listener: ", err) + if r.powerListener != nil { + monitor.Start("start power listener") + err := r.powerListener.Start() + monitor.Finish() + if err != nil { + return E.Cause(err, "start power listener") + } } - } - if r.powerListener != nil { - monitor.Start("start power listener") - err := r.powerListener.Start() + monitor.Start("initialize DNS client") + r.dnsClient.Start() monitor.Finish() - if err != nil { - return E.Cause(err, "start power listener") - } - } - monitor.Start("initialize DNS client") - r.dnsClient.Start() - monitor.Finish() + if C.IsAndroid && r.platformInterface == nil { + monitor.Start("initialize package manager") + packageManager, err := tun.NewPackageManager(tun.PackageManagerOptions{ + Callback: r, + Logger: r.logger, + }) + monitor.Finish() + if err != nil { + return E.Cause(err, "create package manager") + } + if r.enforcePackageManager { + monitor.Start("start package manager") + err = packageManager.Start() + monitor.Finish() + if err != nil { + return E.Cause(err, "start package manager") + } + } + r.packageManager = packageManager + } - if C.IsAndroid && r.platformInterface == nil { - monitor.Start("initialize package manager") - packageManager, err := tun.NewPackageManager(tun.PackageManagerOptions{ - Callback: r, - Logger: r.logger, - }) - monitor.Finish() - if err != nil { - return E.Cause(err, "create package manager") + for i, rule := range r.dnsRules { + monitor.Start("initialize DNS rule[", i, "]") + err := rule.Start() + monitor.Finish() + if err != nil { + return E.Cause(err, "initialize DNS rule[", i, "]") + } } - if r.needPackageManager { - monitor.Start("start package manager") - err = packageManager.Start() + for i, transport := range r.transports { + monitor.Start("initialize DNS transport[", i, "]") + err := transport.Start() monitor.Finish() if err != nil { - return E.Cause(err, "start package manager") + return E.Cause(err, "initialize DNS server[", i, "]") } } - r.packageManager = packageManager - } - - for i, rule := range r.dnsRules { - monitor.Start("initialize DNS rule[", i, "]") - err := rule.Start() - monitor.Finish() - if err != nil { - return E.Cause(err, "initialize DNS rule[", i, "]") + if r.timeService != nil { + monitor.Start("initialize time service") + err := r.timeService.Start() + monitor.Finish() + if err != nil { + return E.Cause(err, "initialize time service") + } } - } - for i, transport := range r.transports { - monitor.Start("initialize DNS transport[", i, "]") - err := transport.Start() - monitor.Finish() - if err != nil { - return E.Cause(err, "initialize DNS server[", i, "]") + case adapter.StartStatePostStart: + var cacheContext *adapter.HTTPStartContext + if len(r.ruleSets) > 0 { + monitor.Start("initialize rule-set") + cacheContext = adapter.NewHTTPStartContext() + var ruleSetStartGroup task.Group + for i, ruleSet := range r.ruleSets { + ruleSetInPlace := ruleSet + ruleSetStartGroup.Append0(func(ctx context.Context) error { + err := ruleSetInPlace.StartContext(ctx, cacheContext) + if err != nil { + return E.Cause(err, "initialize rule-set[", i, "]") + } + return nil + }) + } + ruleSetStartGroup.Concurrency(5) + ruleSetStartGroup.FastFail() + err := ruleSetStartGroup.Run(r.ctx) + monitor.Finish() + if err != nil { + return err + } } - } - if r.timeService != nil { - monitor.Start("initialize time service") - err := r.timeService.Start() - monitor.Finish() - if err != nil { - return E.Cause(err, "initialize time service") + if cacheContext != nil { + cacheContext.Close() + } + needFindProcess := r.needFindProcess + needWIFIState := r.needWIFIState + for _, ruleSet := range r.ruleSets { + metadata := ruleSet.Metadata() + if metadata.ContainsProcessRule { + needFindProcess = true + } + if metadata.ContainsWIFIRule { + needWIFIState = true + } + } + if C.IsAndroid && r.platformInterface == nil && !r.enforcePackageManager { + if needFindProcess { + monitor.Start("start package manager") + err := r.packageManager.Start() + monitor.Finish() + if err != nil { + return E.Cause(err, "start package manager") + } + } else { + r.packageManager = nil + } } + if needFindProcess { + if r.platformInterface != nil { + r.processSearcher = r.platformInterface + } else { + monitor.Start("initialize process searcher") + searcher, err := process.NewSearcher(process.Config{ + Logger: r.logger, + PackageManager: r.packageManager, + }) + monitor.Finish() + if err != nil { + if err != os.ErrInvalid { + r.logger.Warn(E.Cause(err, "create process searcher")) + } + } else { + r.processSearcher = searcher + } + } + } + if needWIFIState && r.platformInterface != nil { + monitor.Start("initialize WIFI state") + r.needWIFIState = true + r.interfaceMonitor.RegisterCallback(func(_ int) { + r.updateWIFIState() + }) + r.updateWIFIState() + monitor.Finish() + } + for i, rule := range r.rules { + monitor.Start("initialize rule[", i, "]") + err := rule.Start() + monitor.Finish() + if err != nil { + return E.Cause(err, "initialize rule[", i, "]") + } + } + for _, ruleSet := range r.ruleSets { + monitor.Start("post start rule_set[", ruleSet.Name(), "]") + err := ruleSet.PostStart() + monitor.Finish() + if err != nil { + return E.Cause(err, "post start rule_set[", ruleSet.Name(), "]") + } + } + r.started = true + return nil + case adapter.StartStateStarted: + for _, ruleSet := range r.ruleSetMap { + ruleSet.Cleanup() + } + runtime.GC() } return nil } @@ -671,113 +705,6 @@ func (r *Router) Close() error { return err } -func (r *Router) PostStart() error { - monitor := taskmonitor.New(r.logger, C.StopTimeout) - var cacheContext *adapter.HTTPStartContext - if len(r.ruleSets) > 0 { - monitor.Start("initialize rule-set") - cacheContext = adapter.NewHTTPStartContext() - var ruleSetStartGroup task.Group - for i, ruleSet := range r.ruleSets { - ruleSetInPlace := ruleSet - ruleSetStartGroup.Append0(func(ctx context.Context) error { - err := ruleSetInPlace.StartContext(ctx, cacheContext) - if err != nil { - return E.Cause(err, "initialize rule-set[", i, "]") - } - return nil - }) - } - ruleSetStartGroup.Concurrency(5) - ruleSetStartGroup.FastFail() - err := ruleSetStartGroup.Run(r.ctx) - monitor.Finish() - if err != nil { - return err - } - } - if cacheContext != nil { - cacheContext.Close() - } - needFindProcess := r.needFindProcess - needWIFIState := r.needWIFIState - for _, ruleSet := range r.ruleSets { - metadata := ruleSet.Metadata() - if metadata.ContainsProcessRule { - needFindProcess = true - } - if metadata.ContainsWIFIRule { - needWIFIState = true - } - } - if C.IsAndroid && r.platformInterface == nil && !r.needPackageManager { - if needFindProcess { - monitor.Start("start package manager") - err := r.packageManager.Start() - monitor.Finish() - if err != nil { - return E.Cause(err, "start package manager") - } - } else { - r.packageManager = nil - } - } - if needFindProcess { - if r.platformInterface != nil { - r.processSearcher = r.platformInterface - } else { - monitor.Start("initialize process searcher") - searcher, err := process.NewSearcher(process.Config{ - Logger: r.logger, - PackageManager: r.packageManager, - }) - monitor.Finish() - if err != nil { - if err != os.ErrInvalid { - r.logger.Warn(E.Cause(err, "create process searcher")) - } - } else { - r.processSearcher = searcher - } - } - } - if needWIFIState && r.platformInterface != nil { - monitor.Start("initialize WIFI state") - r.needWIFIState = true - r.interfaceMonitor.RegisterCallback(func(_ int) { - r.updateWIFIState() - }) - r.updateWIFIState() - monitor.Finish() - } - for i, rule := range r.rules { - monitor.Start("initialize rule[", i, "]") - err := rule.Start() - monitor.Finish() - if err != nil { - return E.Cause(err, "initialize rule[", i, "]") - } - } - for _, ruleSet := range r.ruleSets { - monitor.Start("post start rule_set[", ruleSet.Name(), "]") - err := ruleSet.PostStart() - monitor.Finish() - if err != nil { - return E.Cause(err, "post start rule_set[", ruleSet.Name(), "]") - } - } - r.started = true - return nil -} - -func (r *Router) Cleanup() error { - for _, ruleSet := range r.ruleSetMap { - ruleSet.Cleanup() - } - runtime.GC() - return nil -} - func (r *Router) Outbound(tag string) (adapter.Outbound, bool) { outbound, loaded := r.outboundByTag[tag] return outbound, loaded diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index 0bf45ba25..00579c18d 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -22,7 +22,7 @@ import ( N "github.com/sagernet/sing/common/network" ) -func NewRuleAction(router adapter.Router, logger logger.ContextLogger, action option.RuleAction) (adapter.RuleAction, error) { +func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action option.RuleAction) (adapter.RuleAction, error) { switch action.Action { case "": return nil, nil @@ -36,7 +36,7 @@ func NewRuleAction(router adapter.Router, logger logger.ContextLogger, action op UDPConnect: action.RouteOptionsOptions.UDPConnect, }, nil case C.RuleActionTypeDirect: - directDialer, err := dialer.New(router, option.DialerOptions(action.DirectOptions)) + directDialer, err := dialer.New(ctx, option.DialerOptions(action.DirectOptions)) if err != nil { return nil, err } diff --git a/route/rule/rule_default.go b/route/rule/rule_default.go index 566c816e1..a12c63ef8 100644 --- a/route/rule/rule_default.go +++ b/route/rule/rule_default.go @@ -52,7 +52,7 @@ type RuleItem interface { } func NewDefaultRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.DefaultRule) (*DefaultRule, error) { - action, err := NewRuleAction(router, logger, options.RuleAction) + action, err := NewRuleAction(ctx, logger, options.RuleAction) if err != nil { return nil, E.Cause(err, "action") } @@ -254,7 +254,7 @@ type LogicalRule struct { } func NewLogicalRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.LogicalRule) (*LogicalRule, error) { - action, err := NewRuleAction(router, logger, options.RuleAction) + action, err := NewRuleAction(ctx, logger, options.RuleAction) if err != nil { return nil, E.Cause(err, "action") } diff --git a/route/rule/rule_set_remote.go b/route/rule/rule_set_remote.go index bdafa656a..55c863e9c 100644 --- a/route/rule/rule_set_remote.go +++ b/route/rule/rule_set_remote.go @@ -33,23 +33,24 @@ import ( var _ adapter.RuleSet = (*RemoteRuleSet)(nil) type RemoteRuleSet struct { - ctx context.Context - cancel context.CancelFunc - router adapter.Router - logger logger.ContextLogger - options option.RuleSet - metadata adapter.RuleSetMetadata - updateInterval time.Duration - dialer N.Dialer - rules []adapter.HeadlessRule - lastUpdated time.Time - lastEtag string - updateTicker *time.Ticker - cacheFile adapter.CacheFile - pauseManager pause.Manager - callbackAccess sync.Mutex - callbacks list.List[adapter.RuleSetUpdateCallback] - refs atomic.Int32 + ctx context.Context + cancel context.CancelFunc + router adapter.Router + outboundManager adapter.OutboundManager + logger logger.ContextLogger + options option.RuleSet + metadata adapter.RuleSetMetadata + updateInterval time.Duration + dialer N.Dialer + rules []adapter.HeadlessRule + lastUpdated time.Time + lastEtag string + updateTicker *time.Ticker + cacheFile adapter.CacheFile + pauseManager pause.Manager + callbackAccess sync.Mutex + callbacks list.List[adapter.RuleSetUpdateCallback] + refs atomic.Int32 } func NewRemoteRuleSet(ctx context.Context, router adapter.Router, logger logger.ContextLogger, options option.RuleSet) *RemoteRuleSet { @@ -61,13 +62,14 @@ func NewRemoteRuleSet(ctx context.Context, router adapter.Router, logger logger. updateInterval = 24 * time.Hour } return &RemoteRuleSet{ - ctx: ctx, - cancel: cancel, - router: router, - logger: logger, - options: options, - updateInterval: updateInterval, - pauseManager: service.FromContext[pause.Manager](ctx), + ctx: ctx, + cancel: cancel, + router: router, + outboundManager: service.FromContext[adapter.OutboundManager](ctx), + logger: logger, + options: options, + updateInterval: updateInterval, + pauseManager: service.FromContext[pause.Manager](ctx), } } @@ -83,17 +85,13 @@ func (s *RemoteRuleSet) StartContext(ctx context.Context, startContext *adapter. s.cacheFile = service.FromContext[adapter.CacheFile](s.ctx) var dialer N.Dialer if s.options.RemoteOptions.DownloadDetour != "" { - outbound, loaded := s.router.Outbound(s.options.RemoteOptions.DownloadDetour) + outbound, loaded := s.outboundManager.Outbound(s.options.RemoteOptions.DownloadDetour) if !loaded { return E.New("download_detour not found: ", s.options.RemoteOptions.DownloadDetour) } dialer = outbound } else { - outbound, err := s.router.DefaultOutbound(N.NetworkTCP) - if err != nil { - return err - } - dialer = outbound + dialer = s.outboundManager.Default() } s.dialer = dialer if s.cacheFile != nil { diff --git a/transport/dhcp/server.go b/transport/dhcp/server.go index 8325c37b2..d5603e9ed 100644 --- a/transport/dhcp/server.go +++ b/transport/dhcp/server.go @@ -23,6 +23,7 @@ import ( E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/task" "github.com/sagernet/sing/common/x/list" + "github.com/sagernet/sing/service" "github.com/insomniacslk/dhcp/dhcpv4" mDNS "github.com/miekg/dns" @@ -53,7 +54,7 @@ func NewTransport(options dns.TransportOptions) (*Transport, error) { if linkURL.Host == "" { return nil, E.New("missing interface name for DHCP") } - router := adapter.RouterFromContext(options.Context) + router := service.FromContext[adapter.Router](options.Context) if router == nil { return nil, E.New("missing router in context") } diff --git a/transport/fakeip/server.go b/transport/fakeip/server.go index 5e0c7eef0..d1bbb2aad 100644 --- a/transport/fakeip/server.go +++ b/transport/fakeip/server.go @@ -9,6 +9,7 @@ import ( "github.com/sagernet/sing-dns" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" + "github.com/sagernet/sing/service" mDNS "github.com/miekg/dns" ) @@ -32,7 +33,7 @@ type Transport struct { } func NewTransport(options dns.TransportOptions) (*Transport, error) { - router := adapter.RouterFromContext(options.Context) + router := service.FromContext[adapter.Router](options.Context) if router == nil { return nil, E.New("missing router in context") } From 1df8dfcadea24834b3f5d904dafbd3b414fae3b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 10 Nov 2024 12:11:21 +0800 Subject: [PATCH 16/39] refactor: Modular network manager --- adapter/inbound/manager.go | 4 +- adapter/network.go | 23 ++ adapter/outbound/manager.go | 20 +- adapter/router.go | 19 +- box.go | 56 +-- cmd/sing-box/cmd_rule_set_match.go | 3 +- common/dialer/default.go | 24 +- common/dialer/dialer.go | 25 +- common/settings/proxy_darwin.go | 2 +- experimental/libbox/command_group.go | 4 +- experimental/libbox/command_select.go | 2 +- experimental/libbox/command_urltest.go | 4 +- experimental/libbox/config.go | 2 +- experimental/libbox/monitor.go | 2 +- experimental/libbox/platform/interface.go | 4 +- experimental/libbox/service.go | 10 +- experimental/libbox/service_pause.go | 2 +- include/clashapi_stub.go | 2 +- protocol/direct/loopback_detect.go | 16 +- protocol/tun/inbound.go | 24 +- protocol/wireguard/outbound.go | 2 +- route/geo_resources.go | 10 +- route/network.go | 337 ++++++++++++++++ route/route.go | 34 +- route/router.go | 459 +++------------------- route/rule/rule_default.go | 19 +- route/rule/rule_dns.go | 19 +- route/rule/rule_headless.go | 24 +- route/rule/rule_item_wifi_bssid.go | 12 +- route/rule/rule_item_wifi_ssid.go | 12 +- route/rule/rule_set.go | 6 +- route/rule/rule_set_local.go | 8 +- route/rule/rule_set_remote.go | 6 +- transport/dhcp/server.go | 25 +- transport/wireguard/device_system.go | 4 +- 35 files changed, 617 insertions(+), 608 deletions(-) create mode 100644 adapter/network.go create mode 100644 route/network.go diff --git a/adapter/inbound/manager.go b/adapter/inbound/manager.go index d2be4b578..69a3ad46b 100644 --- a/adapter/inbound/manager.go +++ b/adapter/inbound/manager.go @@ -52,13 +52,13 @@ func (m *Manager) Start(stage adapter.StartStage) error { func (m *Manager) Close() error { m.access.Lock() + defer m.access.Unlock() if !m.started { - panic("not started") + return nil } m.started = false inbounds := m.inbounds m.inbounds = nil - m.access.Unlock() monitor := taskmonitor.New(m.logger, C.StopTimeout) var err error for _, inbound := range inbounds { diff --git a/adapter/network.go b/adapter/network.go new file mode 100644 index 000000000..0ce274118 --- /dev/null +++ b/adapter/network.go @@ -0,0 +1,23 @@ +package adapter + +import ( + "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common/control" +) + +type NetworkManager interface { + NewService + InterfaceFinder() control.InterfaceFinder + UpdateInterfaces() error + DefaultInterface() string + AutoDetectInterface() bool + AutoDetectInterfaceFunc() control.Func + DefaultMark() uint32 + RegisterAutoRedirectOutputMark(mark uint32) error + AutoRedirectOutputMark() uint32 + NetworkMonitor() tun.NetworkUpdateMonitor + InterfaceMonitor() tun.DefaultInterfaceMonitor + PackageManager() tun.PackageManager + WIFIState() WIFIState + ResetNetwork() +} diff --git a/adapter/outbound/manager.go b/adapter/outbound/manager.go index b3e1a170b..10a89a1c9 100644 --- a/adapter/outbound/manager.go +++ b/adapter/outbound/manager.go @@ -48,16 +48,17 @@ func (m *Manager) Initialize(defaultOutboundFallback adapter.Outbound) { func (m *Manager) Start(stage adapter.StartStage) error { m.access.Lock() - defer m.access.Unlock() if m.started && m.stage >= stage { panic("already started") } m.started = true m.stage = stage + outbounds := m.outbounds + m.access.Unlock() if stage == adapter.StartStateStart { - m.startOutbounds() + return m.startOutbounds(outbounds) } else { - for _, outbound := range m.outbounds { + for _, outbound := range outbounds { err := adapter.LegacyStart(outbound, stage) if err != nil { return E.Cause(err, stage.Action(), " outbound/", outbound.Type(), "[", outbound.Tag(), "]") @@ -67,13 +68,13 @@ func (m *Manager) Start(stage adapter.StartStage) error { return nil } -func (m *Manager) startOutbounds() error { +func (m *Manager) startOutbounds(outbounds []adapter.Outbound) error { monitor := taskmonitor.New(m.logger, C.StartTimeout) started := make(map[string]bool) for { canContinue := false startOne: - for _, outboundToStart := range m.outbounds { + for _, outboundToStart := range outbounds { outboundTag := outboundToStart.Tag() if started[outboundTag] { continue @@ -97,13 +98,13 @@ func (m *Manager) startOutbounds() error { } } } - if len(started) == len(m.outbounds) { + if len(started) == len(outbounds) { break } if canContinue { continue } - currentOutbound := common.Find(m.outbounds, func(it adapter.Outbound) bool { + currentOutbound := common.Find(outbounds, func(it adapter.Outbound) bool { return !started[it.Tag()] }) var lintOutbound func(oTree []string, oCurrent adapter.Outbound) error @@ -114,7 +115,9 @@ func (m *Manager) startOutbounds() error { if common.Contains(oTree, problemOutboundTag) { return E.New("circular outbound dependency: ", strings.Join(oTree, " -> "), " -> ", problemOutboundTag) } + m.access.Lock() problemOutbound := m.outboundByTag[problemOutboundTag] + m.access.Unlock() if problemOutbound == nil { return E.New("dependency[", problemOutboundTag, "] not found for outbound[", oCurrent.Tag(), "]") } @@ -129,7 +132,8 @@ func (m *Manager) Close() error { monitor := taskmonitor.New(m.logger, C.StopTimeout) m.access.Lock() if !m.started { - panic("not started") + m.access.Unlock() + return nil } m.started = false outbounds := m.outbounds diff --git a/adapter/router.go b/adapter/router.go index b8ac51f57..40a461a79 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -10,8 +10,6 @@ import ( "github.com/sagernet/sing-box/common/geoip" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-dns" - "github.com/sagernet/sing-tun" - "github.com/sagernet/sing/common/control" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/x/list" @@ -31,28 +29,13 @@ type Router interface { GeoIPReader() *geoip.Reader LoadGeosite(code string) (Rule, error) - RuleSet(tag string) (RuleSet, bool) - NeedWIFIState() bool Exchange(ctx context.Context, message *mdns.Msg) (*mdns.Msg, error) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error) ClearDNSCache() - - InterfaceFinder() control.InterfaceFinder - UpdateInterfaces() error - DefaultInterface() string - AutoDetectInterface() bool - AutoDetectInterfaceFunc() control.Func - DefaultMark() uint32 - RegisterAutoRedirectOutputMark(mark uint32) error - AutoRedirectOutputMark() uint32 - NetworkMonitor() tun.NetworkUpdateMonitor - InterfaceMonitor() tun.DefaultInterfaceMonitor - PackageManager() tun.PackageManager - WIFIState() WIFIState Rules() []Rule ClashServer() ClashServer @@ -61,7 +44,7 @@ type Router interface { V2RayServer() V2RayServer SetV2RayServer(server V2RayServer) - ResetNetwork() error + ResetNetwork() } // Deprecated: Use ConnectionRouterEx instead. diff --git a/box.go b/box.go index a4ca530fd..d3fe5e46e 100644 --- a/box.go +++ b/box.go @@ -34,6 +34,7 @@ type Box struct { router adapter.Router inbound *inbound.Manager outbound *outbound.Manager + network *route.NetworkManager logFactory log.Factory logger log.ContextLogger preServices1 map[string]adapter.Service @@ -109,20 +110,18 @@ func New(options Options) (*Box, error) { return nil, E.Cause(err, "create log factory") } routeOptions := common.PtrValueOrDefault(options.Route) - inboundManager := inbound.NewManager(logFactory.NewLogger("inbound-manager"), inboundRegistry) - outboundManager := outbound.NewManager(logFactory.NewLogger("outbound-manager"), outboundRegistry, routeOptions.Final) + inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry) + outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, routeOptions.Final) ctx = service.ContextWith[adapter.InboundManager](ctx, inboundManager) ctx = service.ContextWith[adapter.OutboundManager](ctx, outboundManager) - router, err := route.NewRouter( - ctx, - logFactory, - routeOptions, - common.PtrValueOrDefault(options.DNS), - common.PtrValueOrDefault(options.NTP), - options.Inbounds, - ) + networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions) if err != nil { - return nil, E.Cause(err, "parse route options") + return nil, E.Cause(err, "initialize network manager") + } + ctx = service.ContextWith[adapter.NetworkManager](ctx, networkManager) + router, err := route.NewRouter(ctx, logFactory, routeOptions, common.PtrValueOrDefault(options.DNS), common.PtrValueOrDefault(options.NTP)) + if err != nil { + return nil, E.Cause(err, "initialize router") } //nolint:staticcheck if len(options.LegacyInbounds) > 0 { @@ -197,11 +196,8 @@ func New(options Options) (*Box, error) { option.DirectOutboundOptions{}, ), )) - if err != nil { - return nil, err - } if platformInterface != nil { - err = platformInterface.Initialize(ctx, router) + err = platformInterface.Initialize(networkManager) if err != nil { return nil, E.Cause(err, "initialize platform interface") } @@ -239,6 +235,7 @@ func New(options Options) (*Box, error) { router: router, inbound: inboundManager, outbound: outboundManager, + network: networkManager, createdAt: createdAt, logFactory: logFactory, logger: logFactory.Logger(), @@ -315,6 +312,10 @@ func (s *Box) preStart() error { } } } + err = s.network.Start(adapter.StartStateInitialize) + if err != nil { + return E.Cause(err, "initialize network manager") + } err = s.router.Start(adapter.StartStateInitialize) if err != nil { return E.Cause(err, "initialize router") @@ -323,6 +324,10 @@ func (s *Box) preStart() error { if err != nil { return err } + err = s.network.Start(adapter.StartStateStart) + if err != nil { + return err + } return s.router.Start(adapter.StartStateStart) } @@ -357,6 +362,10 @@ func (s *Box) start() error { if err != nil { return err } + err = s.network.Start(adapter.StartStatePostStart) + if err != nil { + return err + } err = s.router.Start(adapter.StartStatePostStart) if err != nil { return err @@ -365,6 +374,10 @@ func (s *Box) start() error { if err != nil { return err } + err = s.network.Start(adapter.StartStateStarted) + if err != nil { + return err + } err = s.router.Start(adapter.StartStateStarted) if err != nil { return err @@ -398,13 +411,8 @@ func (s *Box) Close() error { } errors = E.Errors(errors, s.inbound.Close()) errors = E.Errors(errors, s.outbound.Close()) - monitor.Start("close router") - if err := common.Close(s.router); err != nil { - errors = E.Append(errors, err, func(err error) error { - return E.Cause(err, "close router") - }) - } - monitor.Finish() + errors = E.Errors(errors, s.network.Close()) + errors = E.Errors(errors, s.router.Close()) for serviceName, service := range s.preServices1 { monitor.Start("close ", serviceName) errors = E.Append(errors, service.Close(), func(err error) error { @@ -435,6 +443,10 @@ func (s *Box) Outbound() adapter.OutboundManager { return s.outbound } +func (s *Box) Network() adapter.NetworkManager { + return s.network +} + func (s *Box) Router() adapter.Router { return s.router } diff --git a/cmd/sing-box/cmd_rule_set_match.go b/cmd/sing-box/cmd_rule_set_match.go index bfa340cf4..7a3a04231 100644 --- a/cmd/sing-box/cmd_rule_set_match.go +++ b/cmd/sing-box/cmd_rule_set_match.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "context" "io" "os" @@ -84,7 +85,7 @@ func ruleSetMatch(sourcePath string, domain string) error { } for i, ruleOptions := range plainRuleSet.Rules { var currentRule adapter.HeadlessRule - currentRule, err = rule.NewHeadlessRule(nil, ruleOptions) + currentRule, err = rule.NewHeadlessRule(context.Background(), ruleOptions) if err != nil { return E.Cause(err, "parse rule_set.rules.[", i, "]") } diff --git a/common/dialer/default.go b/common/dialer/default.go index fcc5e60ec..a4e4290e5 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -29,31 +29,31 @@ type DefaultDialer struct { isWireGuardListener bool } -func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDialer, error) { +func NewDefault(networkManager adapter.NetworkManager, options option.DialerOptions) (*DefaultDialer, error) { var dialer net.Dialer var listener net.ListenConfig if options.BindInterface != "" { var interfaceFinder control.InterfaceFinder - if router != nil { - interfaceFinder = router.InterfaceFinder() + if networkManager != nil { + interfaceFinder = networkManager.InterfaceFinder() } else { interfaceFinder = control.NewDefaultInterfaceFinder() } bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1) dialer.Control = control.Append(dialer.Control, bindFunc) listener.Control = control.Append(listener.Control, bindFunc) - } else if router != nil && router.AutoDetectInterface() { - bindFunc := router.AutoDetectInterfaceFunc() + } else if networkManager != nil && networkManager.AutoDetectInterface() { + bindFunc := networkManager.AutoDetectInterfaceFunc() dialer.Control = control.Append(dialer.Control, bindFunc) listener.Control = control.Append(listener.Control, bindFunc) - } else if router != nil && router.DefaultInterface() != "" { - bindFunc := control.BindToInterface(router.InterfaceFinder(), router.DefaultInterface(), -1) + } else if networkManager != nil && networkManager.DefaultInterface() != "" { + bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), networkManager.DefaultInterface(), -1) dialer.Control = control.Append(dialer.Control, bindFunc) listener.Control = control.Append(listener.Control, bindFunc) } var autoRedirectOutputMark uint32 - if router != nil { - autoRedirectOutputMark = router.AutoRedirectOutputMark() + if networkManager != nil { + autoRedirectOutputMark = networkManager.AutoRedirectOutputMark() } if autoRedirectOutputMark > 0 { dialer.Control = control.Append(dialer.Control, control.RoutingMark(autoRedirectOutputMark)) @@ -65,9 +65,9 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi if autoRedirectOutputMark > 0 { return nil, E.New("`auto_redirect` with `route_[_exclude]_address_set is conflict with `routing_mark`") } - } else if router != nil && router.DefaultMark() > 0 { - dialer.Control = control.Append(dialer.Control, control.RoutingMark(router.DefaultMark())) - listener.Control = control.Append(listener.Control, control.RoutingMark(router.DefaultMark())) + } else if networkManager != nil && networkManager.DefaultMark() > 0 { + dialer.Control = control.Append(dialer.Control, control.RoutingMark(networkManager.DefaultMark())) + listener.Control = control.Append(listener.Control, control.RoutingMark(networkManager.DefaultMark())) if autoRedirectOutputMark > 0 { return nil, E.New("`auto_redirect` with `route_[_exclude]_address_set is conflict with `default_mark`") } diff --git a/common/dialer/dialer.go b/common/dialer/dialer.go index fe4c7c120..047a25146 100644 --- a/common/dialer/dialer.go +++ b/common/dialer/dialer.go @@ -13,16 +13,16 @@ import ( ) func New(ctx context.Context, options option.DialerOptions) (N.Dialer, error) { - router := service.FromContext[adapter.Router](ctx) + networkManager := service.FromContext[adapter.NetworkManager](ctx) if options.IsWireGuardListener { - return NewDefault(router, options) + return NewDefault(networkManager, options) } var ( dialer N.Dialer err error ) if options.Detour == "" { - dialer, err = NewDefault(router, options) + dialer, err = NewDefault(networkManager, options) if err != nil { return nil, err } @@ -33,16 +33,19 @@ func New(ctx context.Context, options option.DialerOptions) (N.Dialer, error) { } dialer = NewDetour(outboundManager, options.Detour) } - if router == nil { - return NewDefault(router, options) + if networkManager == nil { + return NewDefault(networkManager, options) } if options.Detour == "" { - dialer = NewResolveDialer( - router, - dialer, - options.Detour == "" && !options.TCPFastOpen, - dns.DomainStrategy(options.DomainStrategy), - time.Duration(options.FallbackDelay)) + router := service.FromContext[adapter.Router](ctx) + if router != nil { + dialer = NewResolveDialer( + router, + dialer, + options.Detour == "" && !options.TCPFastOpen, + dns.DomainStrategy(options.DomainStrategy), + time.Duration(options.FallbackDelay)) + } } return dialer, nil } diff --git a/common/settings/proxy_darwin.go b/common/settings/proxy_darwin.go index 2d86fa2d9..17e82cf59 100644 --- a/common/settings/proxy_darwin.go +++ b/common/settings/proxy_darwin.go @@ -25,7 +25,7 @@ type DarwinSystemProxy struct { } func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*DarwinSystemProxy, error) { - interfaceMonitor := service.FromContext[adapter.Router](ctx).InterfaceMonitor() + interfaceMonitor := service.FromContext[adapter.NetworkManager](ctx).InterfaceMonitor() if interfaceMonitor == nil { return nil, E.New("missing interface monitor") } diff --git a/experimental/libbox/command_group.go b/experimental/libbox/command_group.go index 0915f56b8..684cac62c 100644 --- a/experimental/libbox/command_group.go +++ b/experimental/libbox/command_group.go @@ -109,7 +109,7 @@ func readGroups(reader io.Reader) (OutboundGroupIterator, error) { func writeGroups(writer io.Writer, boxService *BoxService) error { historyStorage := service.PtrFromContext[urltest.HistoryStorage](boxService.ctx) cacheFile := service.FromContext[adapter.CacheFile](boxService.ctx) - outbounds := boxService.instance.Router().Outbounds() + outbounds := boxService.instance.Outbound().Outbounds() var iGroups []adapter.OutboundGroup for _, it := range outbounds { if group, isGroup := it.(adapter.OutboundGroup); isGroup { @@ -130,7 +130,7 @@ func writeGroups(writer io.Writer, boxService *BoxService) error { } for _, itemTag := range iGroup.All() { - itemOutbound, isLoaded := boxService.instance.Router().Outbound(itemTag) + itemOutbound, isLoaded := boxService.instance.Outbound().Outbound(itemTag) if !isLoaded { continue } diff --git a/experimental/libbox/command_select.go b/experimental/libbox/command_select.go index f352005d1..6dd74a2d4 100644 --- a/experimental/libbox/command_select.go +++ b/experimental/libbox/command_select.go @@ -43,7 +43,7 @@ func (s *CommandServer) handleSelectOutbound(conn net.Conn) error { if service == nil { return writeError(conn, E.New("service not ready")) } - outboundGroup, isLoaded := service.instance.Router().Outbound(groupTag) + outboundGroup, isLoaded := service.instance.Outbound().Outbound(groupTag) if !isLoaded { return writeError(conn, E.New("selector not found: ", groupTag)) } diff --git a/experimental/libbox/command_urltest.go b/experimental/libbox/command_urltest.go index c72ded8a3..c30d996e6 100644 --- a/experimental/libbox/command_urltest.go +++ b/experimental/libbox/command_urltest.go @@ -41,7 +41,7 @@ func (s *CommandServer) handleURLTest(conn net.Conn) error { if serviceNow == nil { return nil } - abstractOutboundGroup, isLoaded := serviceNow.instance.Router().Outbound(groupTag) + abstractOutboundGroup, isLoaded := serviceNow.instance.Outbound().Outbound(groupTag) if !isLoaded { return writeError(conn, E.New("outbound group not found: ", groupTag)) } @@ -55,7 +55,7 @@ func (s *CommandServer) handleURLTest(conn net.Conn) error { } else { historyStorage := service.PtrFromContext[urltest.HistoryStorage](serviceNow.ctx) outbounds := common.Filter(common.Map(outboundGroup.All(), func(it string) adapter.Outbound { - itOutbound, _ := serviceNow.instance.Router().Outbound(it) + itOutbound, _ := serviceNow.instance.Outbound().Outbound(it) return itOutbound }), func(it adapter.Outbound) bool { if it == nil { diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index 53889d300..a102a3d89 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -50,7 +50,7 @@ func CheckConfig(configContent string) error { type platformInterfaceStub struct{} -func (s *platformInterfaceStub) Initialize(ctx context.Context, router adapter.Router) error { +func (s *platformInterfaceStub) Initialize(networkManager adapter.NetworkManager) error { return nil } diff --git a/experimental/libbox/monitor.go b/experimental/libbox/monitor.go index 60fc2b8e7..18c8b0092 100644 --- a/experimental/libbox/monitor.go +++ b/experimental/libbox/monitor.go @@ -115,7 +115,7 @@ func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName s err = m.updateInterfaces() } if err == nil { - err = m.router.UpdateInterfaces() + err = m.networkManager.UpdateInterfaces() } if err != nil { m.logger.Error(E.Cause(err, "update interfaces")) diff --git a/experimental/libbox/platform/interface.go b/experimental/libbox/platform/interface.go index 3dc000814..16eab5abc 100644 --- a/experimental/libbox/platform/interface.go +++ b/experimental/libbox/platform/interface.go @@ -1,8 +1,6 @@ package platform import ( - "context" - "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/process" "github.com/sagernet/sing-box/option" @@ -12,7 +10,7 @@ import ( ) type Interface interface { - Initialize(ctx context.Context, router adapter.Router) error + Initialize(networkManager adapter.NetworkManager) error UsePlatformAutoDetectInterfaceControl() bool AutoDetectInterfaceControl(fd int) error OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index dcb0370f3..d1c2b4d2a 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -104,13 +104,13 @@ var ( ) type platformInterfaceWrapper struct { - iif PlatformInterface - useProcFS bool - router adapter.Router + iif PlatformInterface + useProcFS bool + networkManager adapter.NetworkManager } -func (w *platformInterfaceWrapper) Initialize(ctx context.Context, router adapter.Router) error { - w.router = router +func (w *platformInterfaceWrapper) Initialize(networkManager adapter.NetworkManager) error { + w.networkManager = networkManager return nil } diff --git a/experimental/libbox/service_pause.go b/experimental/libbox/service_pause.go index c4aa8daaf..a7599a767 100644 --- a/experimental/libbox/service_pause.go +++ b/experimental/libbox/service_pause.go @@ -29,5 +29,5 @@ func (s *BoxService) Wake() { } func (s *BoxService) ResetNetwork() { - _ = s.instance.Router().ResetNetwork() + s.instance.Router().ResetNetwork() } diff --git a/include/clashapi_stub.go b/include/clashapi_stub.go index 26e199b31..e7d5304fa 100644 --- a/include/clashapi_stub.go +++ b/include/clashapi_stub.go @@ -13,7 +13,7 @@ import ( ) func init() { - experimental.RegisterClashServerConstructor(func(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { + experimental.RegisterClashServerConstructor(func(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { return nil, E.New(`clash api is not included in this build, rebuild with -tags with_clash_api`) }) } diff --git a/protocol/direct/loopback_detect.go b/protocol/direct/loopback_detect.go index 5a184e692..4bc1be3b0 100644 --- a/protocol/direct/loopback_detect.go +++ b/protocol/direct/loopback_detect.go @@ -11,18 +11,18 @@ import ( ) type loopBackDetector struct { - router adapter.Router + networkManager adapter.NetworkManager connAccess sync.RWMutex packetConnAccess sync.RWMutex connMap map[netip.AddrPort]netip.AddrPort packetConnMap map[uint16]uint16 } -func newLoopBackDetector(router adapter.Router) *loopBackDetector { +func newLoopBackDetector(networkManager adapter.NetworkManager) *loopBackDetector { return &loopBackDetector{ - router: router, - connMap: make(map[netip.AddrPort]netip.AddrPort), - packetConnMap: make(map[uint16]uint16), + networkManager: networkManager, + connMap: make(map[netip.AddrPort]netip.AddrPort), + packetConnMap: make(map[uint16]uint16), } } @@ -33,7 +33,7 @@ func (l *loopBackDetector) NewConn(conn net.Conn) net.Conn { } if udpConn, isUDPConn := conn.(abstractUDPConn); isUDPConn { if !source.Addr().IsLoopback() { - _, err := l.router.InterfaceFinder().InterfaceByAddr(source.Addr()) + _, err := l.networkManager.InterfaceFinder().InterfaceByAddr(source.Addr()) if err != nil { return conn } @@ -59,7 +59,7 @@ func (l *loopBackDetector) NewPacketConn(conn N.NetPacketConn, destination M.Soc return conn } if !source.Addr().IsLoopback() { - _, err := l.router.InterfaceFinder().InterfaceByAddr(source.Addr()) + _, err := l.networkManager.InterfaceFinder().InterfaceByAddr(source.Addr()) if err != nil { return conn } @@ -82,7 +82,7 @@ func (l *loopBackDetector) CheckPacketConn(source netip.AddrPort, local netip.Ad return false } if !source.Addr().IsLoopback() { - _, err := l.router.InterfaceFinder().InterfaceByAddr(source.Addr()) + _, err := l.networkManager.InterfaceFinder().InterfaceByAddr(source.Addr()) if err != nil { return false } diff --git a/protocol/tun/inbound.go b/protocol/tun/inbound.go index f2476223c..302afb578 100644 --- a/protocol/tun/inbound.go +++ b/protocol/tun/inbound.go @@ -36,10 +36,11 @@ func RegisterInbound(registry *inbound.Registry) { } type Inbound struct { - tag string - ctx context.Context - router adapter.Router - logger log.ContextLogger + tag string + ctx context.Context + router adapter.Router + networkManager adapter.NetworkManager + logger log.ContextLogger // Deprecated inboundOptions option.InboundOptions tunOptions tun.Options @@ -168,11 +169,12 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo if outputMark == 0 { outputMark = tun.DefaultAutoRedirectOutputMark } - + networkManager := service.FromContext[adapter.NetworkManager](ctx) inbound := &Inbound{ tag: tag, ctx: ctx, router: router, + networkManager: networkManager, logger: logger, inboundOptions: options.InboundOptions, tunOptions: tun.Options{ @@ -198,7 +200,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo IncludeAndroidUser: options.IncludeAndroidUser, IncludePackage: options.IncludePackage, ExcludePackage: options.ExcludePackage, - InterfaceMonitor: router.InterfaceMonitor(), + InterfaceMonitor: networkManager.InterfaceMonitor(), }, endpointIndependentNat: options.EndpointIndependentNat, udpTimeout: udpTimeout, @@ -216,8 +218,8 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo Context: ctx, Handler: (*autoRedirectHandler)(inbound), Logger: logger, - NetworkMonitor: router.NetworkMonitor(), - InterfaceFinder: router.InterfaceFinder(), + NetworkMonitor: networkManager.NetworkMonitor(), + InterfaceFinder: networkManager.InterfaceFinder(), TableName: "sing-box", DisableNFTables: dErr == nil && disableNFTables, RouteAddressSet: &inbound.routeAddressSet, @@ -248,7 +250,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo } if markMode { inbound.tunOptions.AutoRedirectMarkMode = true - err = router.RegisterAutoRedirectOutputMark(inbound.tunOptions.AutoRedirectOutputMark) + err = networkManager.RegisterAutoRedirectOutputMark(inbound.tunOptions.AutoRedirectOutputMark) if err != nil { return nil, err } @@ -300,7 +302,7 @@ func (t *Inbound) Tag() string { func (t *Inbound) Start() error { if C.IsAndroid && t.platformInterface == nil { - t.tunOptions.BuildAndroidRules(t.router.PackageManager()) + t.tunOptions.BuildAndroidRules(t.networkManager.PackageManager()) } if t.tunOptions.Name == "" { t.tunOptions.Name = tun.CalculateInterfaceName("") @@ -338,7 +340,7 @@ func (t *Inbound) Start() error { Handler: t, Logger: t.logger, ForwarderBindInterface: forwarderBindInterface, - InterfaceFinder: t.router.InterfaceFinder(), + InterfaceFinder: t.networkManager.InterfaceFinder(), IncludeAllNetworks: includeAllNetworks, }) if err != nil { diff --git a/protocol/wireguard/outbound.go b/protocol/wireguard/outbound.go index 90f76c1ac..7f33bf607 100644 --- a/protocol/wireguard/outbound.go +++ b/protocol/wireguard/outbound.go @@ -100,7 +100,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL if !options.SystemInterface && tun.WithGVisor { wireTunDevice, err = wireguard.NewStackDevice(options.LocalAddress, mtu) } else { - wireTunDevice, err = wireguard.NewSystemDevice(router, options.InterfaceName, options.LocalAddress, mtu, options.GSO) + wireTunDevice, err = wireguard.NewSystemDevice(service.FromContext[adapter.NetworkManager](ctx), options.InterfaceName, options.LocalAddress, mtu, options.GSO) } if err != nil { return nil, E.Cause(err, "create WireGuard device") diff --git a/route/geo_resources.go b/route/geo_resources.go index 91c06796b..c5a45ffde 100644 --- a/route/geo_resources.go +++ b/route/geo_resources.go @@ -33,7 +33,7 @@ func (r *Router) LoadGeosite(code string) (adapter.Rule, error) { if err != nil { return nil, err } - rule, err = R.NewDefaultRule(r.ctx, r, nil, geosite.Compile(items)) + rule, err = R.NewDefaultRule(r.ctx, nil, geosite.Compile(items)) if err != nil { return nil, err } @@ -145,13 +145,13 @@ func (r *Router) downloadGeoIPDatabase(savePath string) error { r.logger.Info("downloading geoip database") var detour adapter.Outbound if r.geoIPOptions.DownloadDetour != "" { - outbound, loaded := r.Outbound(r.geoIPOptions.DownloadDetour) + outbound, loaded := r.outboundManager.Outbound(r.geoIPOptions.DownloadDetour) if !loaded { return E.New("detour outbound not found: ", r.geoIPOptions.DownloadDetour) } detour = outbound } else { - detour = r.defaultOutboundForConnection + detour = r.outboundManager.Default() } if parentDir := filepath.Dir(savePath); parentDir != "" { @@ -200,13 +200,13 @@ func (r *Router) downloadGeositeDatabase(savePath string) error { r.logger.Info("downloading geosite database") var detour adapter.Outbound if r.geositeOptions.DownloadDetour != "" { - outbound, loaded := r.Outbound(r.geositeOptions.DownloadDetour) + outbound, loaded := r.outboundManager.Outbound(r.geositeOptions.DownloadDetour) if !loaded { return E.New("detour outbound not found: ", r.geositeOptions.DownloadDetour) } detour = outbound } else { - detour = r.defaultOutboundForConnection + detour = r.outboundManager.Default() } if parentDir := filepath.Dir(savePath); parentDir != "" { diff --git a/route/network.go b/route/network.go new file mode 100644 index 000000000..b0caa24c1 --- /dev/null +++ b/route/network.go @@ -0,0 +1,337 @@ +package route + +import ( + "context" + "errors" + "net/netip" + "os" + "runtime" + "syscall" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/conntrack" + "github.com/sagernet/sing-box/common/taskmonitor" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/experimental/libbox/platform" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common/control" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/common/winpowrprof" + "github.com/sagernet/sing/service" + "github.com/sagernet/sing/service/pause" +) + +var _ adapter.NetworkManager = (*NetworkManager)(nil) + +type NetworkManager struct { + logger logger.ContextLogger + interfaceFinder *control.DefaultInterfaceFinder + autoDetectInterface bool + defaultInterface string + defaultMark uint32 + autoRedirectOutputMark uint32 + networkMonitor tun.NetworkUpdateMonitor + interfaceMonitor tun.DefaultInterfaceMonitor + packageManager tun.PackageManager + powerListener winpowrprof.EventListener + pauseManager pause.Manager + platformInterface platform.Interface + outboundManager adapter.OutboundManager + wifiState adapter.WIFIState + started bool +} + +func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOptions option.RouteOptions) (*NetworkManager, error) { + nm := &NetworkManager{ + logger: logger, + interfaceFinder: control.NewDefaultInterfaceFinder(), + autoDetectInterface: routeOptions.AutoDetectInterface, + defaultInterface: routeOptions.DefaultInterface, + defaultMark: routeOptions.DefaultMark, + pauseManager: service.FromContext[pause.Manager](ctx), + platformInterface: service.FromContext[platform.Interface](ctx), + outboundManager: service.FromContext[adapter.OutboundManager](ctx), + } + usePlatformDefaultInterfaceMonitor := nm.platformInterface != nil && nm.platformInterface.UsePlatformDefaultInterfaceMonitor() + enforceInterfaceMonitor := routeOptions.AutoDetectInterface + if !usePlatformDefaultInterfaceMonitor { + networkMonitor, err := tun.NewNetworkUpdateMonitor(logger) + if !((err != nil && !enforceInterfaceMonitor) || errors.Is(err, os.ErrInvalid)) { + if err != nil { + return nil, E.Cause(err, "create network monitor") + } + nm.networkMonitor = networkMonitor + networkMonitor.RegisterCallback(func() { + _ = nm.interfaceFinder.Update() + }) + interfaceMonitor, err := tun.NewDefaultInterfaceMonitor(nm.networkMonitor, logger, tun.DefaultInterfaceMonitorOptions{ + InterfaceFinder: nm.interfaceFinder, + OverrideAndroidVPN: routeOptions.OverrideAndroidVPN, + UnderNetworkExtension: nm.platformInterface != nil && nm.platformInterface.UnderNetworkExtension(), + }) + if err != nil { + return nil, E.New("auto_detect_interface unsupported on current platform") + } + interfaceMonitor.RegisterCallback(nm.notifyNetworkUpdate) + nm.interfaceMonitor = interfaceMonitor + } + } else { + interfaceMonitor := nm.platformInterface.CreateDefaultInterfaceMonitor(logger) + interfaceMonitor.RegisterCallback(nm.notifyNetworkUpdate) + nm.interfaceMonitor = interfaceMonitor + } + return nm, nil +} + +func (r *NetworkManager) Start(stage adapter.StartStage) error { + monitor := taskmonitor.New(r.logger, C.StartTimeout) + switch stage { + case adapter.StartStateInitialize: + if r.interfaceMonitor != nil { + monitor.Start("initialize interface monitor") + err := r.interfaceMonitor.Start() + monitor.Finish() + if err != nil { + return err + } + } + if r.networkMonitor != nil { + monitor.Start("initialize network monitor") + err := r.networkMonitor.Start() + monitor.Finish() + if err != nil { + return err + } + } + case adapter.StartStateStart: + if runtime.GOOS == "windows" { + powerListener, err := winpowrprof.NewEventListener(r.notifyWindowsPowerEvent) + if err == nil { + r.powerListener = powerListener + } else { + r.logger.Warn("initialize power listener: ", err) + } + } + if r.powerListener != nil { + monitor.Start("start power listener") + err := r.powerListener.Start() + monitor.Finish() + if err != nil { + return E.Cause(err, "start power listener") + } + } + if C.IsAndroid && r.platformInterface == nil { + monitor.Start("initialize package manager") + packageManager, err := tun.NewPackageManager(tun.PackageManagerOptions{ + Callback: r, + Logger: r.logger, + }) + monitor.Finish() + if err != nil { + return E.Cause(err, "create package manager") + } + monitor.Start("start package manager") + err = packageManager.Start() + monitor.Finish() + if err != nil { + r.logger.Warn("initialize package manager: ", err) + } else { + r.packageManager = packageManager + } + } + case adapter.StartStatePostStart: + r.started = true + } + return nil +} + +func (r *NetworkManager) Close() error { + monitor := taskmonitor.New(r.logger, C.StopTimeout) + var err error + if r.interfaceMonitor != nil { + monitor.Start("close interface monitor") + err = E.Append(err, r.interfaceMonitor.Close(), func(err error) error { + return E.Cause(err, "close interface monitor") + }) + monitor.Finish() + } + if r.networkMonitor != nil { + monitor.Start("close network monitor") + err = E.Append(err, r.networkMonitor.Close(), func(err error) error { + return E.Cause(err, "close network monitor") + }) + monitor.Finish() + } + if r.packageManager != nil { + monitor.Start("close package manager") + err = E.Append(err, r.packageManager.Close(), func(err error) error { + return E.Cause(err, "close package manager") + }) + monitor.Finish() + } + if r.powerListener != nil { + monitor.Start("close power listener") + err = E.Append(err, r.powerListener.Close(), func(err error) error { + return E.Cause(err, "close power listener") + }) + monitor.Finish() + } + return nil +} + +func (r *NetworkManager) InterfaceFinder() control.InterfaceFinder { + return r.interfaceFinder +} + +func (r *NetworkManager) UpdateInterfaces() error { + if r.platformInterface == nil || !r.platformInterface.UsePlatformInterfaceGetter() { + return r.interfaceFinder.Update() + } else { + interfaces, err := r.platformInterface.Interfaces() + if err != nil { + return err + } + r.interfaceFinder.UpdateInterfaces(interfaces) + return nil + } +} + +func (r *NetworkManager) DefaultInterface() string { + return r.defaultInterface +} + +func (r *NetworkManager) AutoDetectInterface() bool { + return r.autoDetectInterface +} + +func (r *NetworkManager) AutoDetectInterfaceFunc() control.Func { + if r.platformInterface != nil && r.platformInterface.UsePlatformAutoDetectInterfaceControl() { + return func(network, address string, conn syscall.RawConn) error { + return control.Raw(conn, func(fd uintptr) error { + return r.platformInterface.AutoDetectInterfaceControl(int(fd)) + }) + } + } else { + if r.interfaceMonitor == nil { + return nil + } + return control.BindToInterfaceFunc(r.interfaceFinder, func(network string, address string) (interfaceName string, interfaceIndex int, err error) { + remoteAddr := M.ParseSocksaddr(address).Addr + if C.IsLinux { + interfaceName, interfaceIndex = r.interfaceMonitor.DefaultInterface(remoteAddr) + if interfaceIndex == -1 { + err = tun.ErrNoRoute + } + } else { + interfaceIndex = r.interfaceMonitor.DefaultInterfaceIndex(remoteAddr) + if interfaceIndex == -1 { + err = tun.ErrNoRoute + } + } + return + }) + } +} + +func (r *NetworkManager) DefaultMark() uint32 { + return r.defaultMark +} + +func (r *NetworkManager) RegisterAutoRedirectOutputMark(mark uint32) error { + if r.autoRedirectOutputMark > 0 { + return E.New("only one auto-redirect can be configured") + } + r.autoRedirectOutputMark = mark + return nil +} + +func (r *NetworkManager) AutoRedirectOutputMark() uint32 { + return r.autoRedirectOutputMark +} + +func (r *NetworkManager) NetworkMonitor() tun.NetworkUpdateMonitor { + return r.networkMonitor +} + +func (r *NetworkManager) InterfaceMonitor() tun.DefaultInterfaceMonitor { + return r.interfaceMonitor +} + +func (r *NetworkManager) PackageManager() tun.PackageManager { + return r.packageManager +} + +func (r *NetworkManager) WIFIState() adapter.WIFIState { + return r.wifiState +} + +func (r *NetworkManager) ResetNetwork() { + conntrack.Close() + + for _, outbound := range r.outboundManager.Outbounds() { + listener, isListener := outbound.(adapter.InterfaceUpdateListener) + if isListener { + listener.InterfaceUpdated() + } + } +} + +func (r *NetworkManager) notifyNetworkUpdate(event int) { + if event == tun.EventNoRoute { + r.pauseManager.NetworkPause() + r.logger.Error("missing default interface") + } else { + r.pauseManager.NetworkWake() + if C.IsAndroid && r.platformInterface == nil { + var vpnStatus string + if r.interfaceMonitor.AndroidVPNEnabled() { + vpnStatus = "enabled" + } else { + vpnStatus = "disabled" + } + r.logger.Info("updated default interface ", r.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", r.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified()), ", vpn ", vpnStatus) + } else { + r.logger.Info("updated default interface ", r.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", r.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified())) + } + if r.platformInterface != nil { + state := r.platformInterface.ReadWIFIState() + if state != r.wifiState { + r.wifiState = state + if state.SSID == "" && state.BSSID == "" { + r.logger.Info("updated WIFI state: disconnected") + } else { + r.logger.Info("updated WIFI state: SSID=", state.SSID, ", BSSID=", state.BSSID) + } + } + } + } + + if !r.started { + return + } + + r.ResetNetwork() +} + +func (r *NetworkManager) notifyWindowsPowerEvent(event int) { + switch event { + case winpowrprof.EVENT_SUSPEND: + r.pauseManager.DevicePause() + r.ResetNetwork() + case winpowrprof.EVENT_RESUME: + if !r.pauseManager.IsDevicePaused() { + return + } + fallthrough + case winpowrprof.EVENT_RESUME_AUTOMATIC: + r.pauseManager.DeviceWake() + r.ResetNetwork() + } +} + +func (r *NetworkManager) OnPackagesUpdated(packages int, sharedUsers int) { + r.logger.Info("updated packages list: ", packages, " packages, ", sharedUsers, " shared users") +} diff --git a/route/route.go b/route/route.go index 8fcc3f928..3897e4c6d 100644 --- a/route/route.go +++ b/route/route.go @@ -58,8 +58,8 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad if metadata.LastInbound == metadata.InboundDetour { return E.New("routing loop on detour: ", metadata.InboundDetour) } - detour := r.inboundByTag[metadata.InboundDetour] - if detour == nil { + detour, loaded := r.inboundManager.Get(metadata.InboundDetour) + if !loaded { return E.New("inbound detour not found: ", metadata.InboundDetour) } injectable, isInjectable := detour.(adapter.TCPInjectableInbound) @@ -100,7 +100,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad if selectedRule != nil { switch action := selectedRule.Action().(type) { case *rule.RuleActionRoute: - selectedOutbound, loaded := r.Outbound(action.Outbound) + selectedOutbound, loaded := r.outboundManager.Outbound(action.Outbound) if !loaded { buf.ReleaseMulti(buffers) return E.New("outbound not found: ", action.Outbound) @@ -128,13 +128,14 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad } } if selectedRule == nil { - if r.defaultOutboundForConnection == nil { + defaultOutbound := r.outboundManager.Default() + if !common.Contains(defaultOutbound.Network(), N.NetworkTCP) { buf.ReleaseMulti(buffers) - return E.New("missing default outbound with TCP support") + return E.New("TCP is not supported by default outbound: ", defaultOutbound.Tag()) } - selectedDialer = r.defaultOutboundForConnection - selectedTag = r.defaultOutboundForConnection.Tag() - selectedDescription = F.ToString("outbound/", r.defaultOutboundForConnection.Type(), "[", r.defaultOutboundForConnection.Tag(), "]") + selectedDialer = defaultOutbound + selectedTag = defaultOutbound.Tag() + selectedDescription = F.ToString("outbound/", defaultOutbound.Type(), "[", defaultOutbound.Tag(), "]") } for _, buffer := range buffers { @@ -217,8 +218,8 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m if metadata.LastInbound == metadata.InboundDetour { return E.New("routing loop on detour: ", metadata.InboundDetour) } - detour := r.inboundByTag[metadata.InboundDetour] - if detour == nil { + detour, loaded := r.inboundManager.Get(metadata.InboundDetour) + if !loaded { return E.New("inbound detour not found: ", metadata.InboundDetour) } injectable, isInjectable := detour.(adapter.UDPInjectableInbound) @@ -254,7 +255,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m if selectedRule != nil { switch action := selectedRule.Action().(type) { case *rule.RuleActionRoute: - selectedOutbound, loaded := r.Outbound(action.Outbound) + selectedOutbound, loaded := r.outboundManager.Outbound(action.Outbound) if !loaded { N.ReleaseMultiPacketBuffer(packetBuffers) return E.New("outbound not found: ", action.Outbound) @@ -279,13 +280,14 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m } } if selectedRule == nil || selectReturn { - if r.defaultOutboundForPacketConnection == nil { + defaultOutbound := r.outboundManager.Default() + if !common.Contains(defaultOutbound.Network(), N.NetworkUDP) { N.ReleaseMultiPacketBuffer(packetBuffers) - return E.New("missing default outbound with UDP support") + return E.New("UDP is not supported by outbound: ", defaultOutbound.Tag()) } - selectedDialer = r.defaultOutboundForPacketConnection - selectedTag = r.defaultOutboundForPacketConnection.Tag() - selectedDescription = F.ToString("outbound/", r.defaultOutboundForPacketConnection.Type(), "[", r.defaultOutboundForPacketConnection.Tag(), "]") + selectedDialer = defaultOutbound + selectedTag = defaultOutbound.Tag() + selectedDescription = F.ToString("outbound/", defaultOutbound.Type(), "[", defaultOutbound.Tag(), "]") } for _, buffer := range packetBuffers { conn = bufio.NewCachedPacketConn(conn, buffer.Buffer, buffer.Destination) diff --git a/route/router.go b/route/router.go index 557b043ae..62b374473 100644 --- a/route/router.go +++ b/route/router.go @@ -2,17 +2,14 @@ package route import ( "context" - "errors" "net/netip" "net/url" "os" "runtime" "strings" - "syscall" "time" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/conntrack" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/geoip" "github.com/sagernet/sing-box/common/geosite" @@ -25,16 +22,13 @@ import ( R "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing-box/transport/fakeip" "github.com/sagernet/sing-dns" - "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/ntp" "github.com/sagernet/sing/common/task" - "github.com/sagernet/sing/common/winpowrprof" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/pause" ) @@ -42,69 +36,50 @@ import ( var _ adapter.Router = (*Router)(nil) type Router struct { - ctx context.Context - logger log.ContextLogger - dnsLogger log.ContextLogger - inboundByTag map[string]adapter.Inbound - outbounds []adapter.Outbound - outboundByTag map[string]adapter.Outbound - rules []adapter.Rule - defaultDetour string - defaultOutboundForConnection adapter.Outbound - defaultOutboundForPacketConnection adapter.Outbound - needGeoIPDatabase bool - needGeositeDatabase bool - geoIPOptions option.GeoIPOptions - geositeOptions option.GeositeOptions - geoIPReader *geoip.Reader - geositeReader *geosite.Reader - geositeCache map[string]adapter.Rule - needFindProcess bool - dnsClient *dns.Client - defaultDomainStrategy dns.DomainStrategy - dnsRules []adapter.DNSRule - ruleSets []adapter.RuleSet - ruleSetMap map[string]adapter.RuleSet - defaultTransport dns.Transport - transports []dns.Transport - transportMap map[string]dns.Transport - transportDomainStrategy map[dns.Transport]dns.DomainStrategy - dnsReverseMapping *DNSReverseMapping - fakeIPStore adapter.FakeIPStore - interfaceFinder *control.DefaultInterfaceFinder - autoDetectInterface bool - defaultInterface string - defaultMark uint32 - autoRedirectOutputMark uint32 - networkMonitor tun.NetworkUpdateMonitor - interfaceMonitor tun.DefaultInterfaceMonitor - packageManager tun.PackageManager - powerListener winpowrprof.EventListener - processSearcher process.Searcher - timeService *ntp.Service - pauseManager pause.Manager - clashServer adapter.ClashServer - v2rayServer adapter.V2RayServer - platformInterface platform.Interface - needWIFIState bool - enforcePackageManager bool - wifiState adapter.WIFIState - started bool -} - -func NewRouter( - ctx context.Context, - logFactory log.Factory, - options option.RouteOptions, - dnsOptions option.DNSOptions, - ntpOptions option.NTPOptions, - inbounds []option.Inbound, -) (*Router, error) { + ctx context.Context + logger log.ContextLogger + dnsLogger log.ContextLogger + inboundManager adapter.InboundManager + outboundManager adapter.OutboundManager + networkManager adapter.NetworkManager + rules []adapter.Rule + needGeoIPDatabase bool + needGeositeDatabase bool + geoIPOptions option.GeoIPOptions + geositeOptions option.GeositeOptions + geoIPReader *geoip.Reader + geositeReader *geosite.Reader + geositeCache map[string]adapter.Rule + needFindProcess bool + dnsClient *dns.Client + defaultDomainStrategy dns.DomainStrategy + dnsRules []adapter.DNSRule + ruleSets []adapter.RuleSet + ruleSetMap map[string]adapter.RuleSet + defaultTransport dns.Transport + transports []dns.Transport + transportMap map[string]dns.Transport + transportDomainStrategy map[dns.Transport]dns.DomainStrategy + dnsReverseMapping *DNSReverseMapping + fakeIPStore adapter.FakeIPStore + processSearcher process.Searcher + timeService *ntp.Service + pauseManager pause.Manager + clashServer adapter.ClashServer + v2rayServer adapter.V2RayServer + platformInterface platform.Interface + needWIFIState bool + started bool +} + +func NewRouter(ctx context.Context, logFactory log.Factory, options option.RouteOptions, dnsOptions option.DNSOptions, ntpOptions option.NTPOptions) (*Router, error) { router := &Router{ ctx: ctx, logger: logFactory.NewLogger("router"), dnsLogger: logFactory.NewLogger("dns"), - outboundByTag: make(map[string]adapter.Outbound), + inboundManager: service.FromContext[adapter.InboundManager](ctx), + outboundManager: service.FromContext[adapter.OutboundManager](ctx), + networkManager: service.FromContext[adapter.NetworkManager](ctx), rules: make([]adapter.Rule, 0, len(options.Rules)), dnsRules: make([]adapter.DNSRule, 0, len(dnsOptions.Rules)), ruleSetMap: make(map[string]adapter.RuleSet), @@ -114,22 +89,12 @@ func NewRouter( geositeOptions: common.PtrValueOrDefault(options.Geosite), geositeCache: make(map[string]adapter.Rule), needFindProcess: hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess, - defaultDetour: options.Final, defaultDomainStrategy: dns.DomainStrategy(dnsOptions.Strategy), - interfaceFinder: control.NewDefaultInterfaceFinder(), - autoDetectInterface: options.AutoDetectInterface, - defaultInterface: options.DefaultInterface, - defaultMark: options.DefaultMark, pauseManager: service.FromContext[pause.Manager](ctx), platformInterface: service.FromContext[platform.Interface](ctx), needWIFIState: hasRule(options.Rules, isWIFIRule) || hasDNSRule(dnsOptions.Rules, isWIFIDNSRule), - enforcePackageManager: common.Any(inbounds, func(inbound option.Inbound) bool { - if tunOptions, isTUN := inbound.Options.(*option.TunInboundOptions); isTUN && tunOptions.AutoRoute { - return true - } - return false - }), } + ctx = service.ContextWith[adapter.Router](ctx, router) router.dnsClient = dns.NewClient(dns.ClientOptions{ DisableCache: dnsOptions.DNSClientOptions.DisableCache, DisableExpire: dnsOptions.DNSClientOptions.DisableExpire, @@ -147,14 +112,14 @@ func NewRouter( Logger: router.dnsLogger, }) for i, ruleOptions := range options.Rules { - routeRule, err := R.NewRule(ctx, router, router.logger, ruleOptions, true) + routeRule, err := R.NewRule(ctx, router.logger, ruleOptions, true) if err != nil { return nil, E.Cause(err, "parse rule[", i, "]") } router.rules = append(router.rules, routeRule) } for i, dnsRuleOptions := range dnsOptions.Rules { - dnsRule, err := R.NewDNSRule(ctx, router, router.logger, dnsRuleOptions, true) + dnsRule, err := R.NewDNSRule(ctx, router.logger, dnsRuleOptions, true) if err != nil { return nil, E.Cause(err, "parse dns rule[", i, "]") } @@ -164,7 +129,7 @@ func NewRouter( if _, exists := router.ruleSetMap[ruleSetOptions.Tag]; exists { return nil, E.New("duplicate rule-set tag: ", ruleSetOptions.Tag) } - ruleSet, err := R.NewRuleSet(ctx, router, router.logger, ruleSetOptions) + ruleSet, err := R.NewRuleSet(ctx, router.logger, ruleSetOptions) if err != nil { return nil, E.Cause(err, "parse rule-set[", i, "]") } @@ -191,7 +156,6 @@ func NewRouter( transportTags[i] = tag transportTagMap[tag] = true } - ctx = service.ContextWith[adapter.Router](ctx, router) outboundManager := service.FromContext[adapter.OutboundManager](ctx) for { lastLen := len(dummyTransportMap) @@ -298,7 +262,7 @@ func NewRouter( Context: ctx, Name: "local", Address: "local", - Dialer: common.Must1(dialer.NewDefault(router, option.DialerOptions{})), + Dialer: common.Must1(dialer.NewDefault(router.networkManager, option.DialerOptions{})), }))) } defaultTransport = transports[0] @@ -327,44 +291,6 @@ func NewRouter( router.fakeIPStore = fakeip.NewStore(ctx, router.logger, inet4Range, inet6Range) } - usePlatformDefaultInterfaceMonitor := router.platformInterface != nil && router.platformInterface.UsePlatformDefaultInterfaceMonitor() - enforceInterfaceMonitor := options.AutoDetectInterface || common.Any(inbounds, func(inbound option.Inbound) bool { - if httpMixedOptions, isHTTPMixed := inbound.Options.(*option.HTTPMixedInboundOptions); isHTTPMixed && httpMixedOptions.SetSystemProxy { - return true - } - if tunOptions, isTUN := inbound.Options.(*option.TunInboundOptions); isTUN && tunOptions.AutoRoute { - return true - } - return false - }) - - if !usePlatformDefaultInterfaceMonitor { - networkMonitor, err := tun.NewNetworkUpdateMonitor(router.logger) - if !((err != nil && !enforceInterfaceMonitor) || errors.Is(err, os.ErrInvalid)) { - if err != nil { - return nil, err - } - router.networkMonitor = networkMonitor - networkMonitor.RegisterCallback(func() { - _ = router.interfaceFinder.Update() - }) - interfaceMonitor, err := tun.NewDefaultInterfaceMonitor(router.networkMonitor, router.logger, tun.DefaultInterfaceMonitorOptions{ - InterfaceFinder: router.interfaceFinder, - OverrideAndroidVPN: options.OverrideAndroidVPN, - UnderNetworkExtension: router.platformInterface != nil && router.platformInterface.UnderNetworkExtension(), - }) - if err != nil { - return nil, E.New("auto_detect_interface unsupported on current platform") - } - interfaceMonitor.RegisterCallback(router.notifyNetworkUpdate) - router.interfaceMonitor = interfaceMonitor - } - } else { - interfaceMonitor := router.platformInterface.CreateDefaultInterfaceMonitor(router.logger) - interfaceMonitor.RegisterCallback(router.notifyNetworkUpdate) - router.interfaceMonitor = interfaceMonitor - } - if ntpOptions.Enabled { ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions) if err != nil { @@ -384,33 +310,10 @@ func NewRouter( return router, nil } -func (r *Router) Outbounds() []adapter.Outbound { - if !r.started { - return nil - } - return r.outbounds -} - func (r *Router) Start(stage adapter.StartStage) error { monitor := taskmonitor.New(r.logger, C.StartTimeout) switch stage { case adapter.StartStateInitialize: - if r.interfaceMonitor != nil { - monitor.Start("initialize interface monitor") - err := r.interfaceMonitor.Start() - monitor.Finish() - if err != nil { - return err - } - } - if r.networkMonitor != nil { - monitor.Start("initialize network monitor") - err := r.networkMonitor.Start() - monitor.Finish() - if err != nil { - return err - } - } if r.fakeIPStore != nil { monitor.Start("initialize fakeip store") err := r.fakeIPStore.Start() @@ -457,49 +360,10 @@ func (r *Router) Start(stage adapter.StartStage) error { r.geositeReader = nil } - if runtime.GOOS == "windows" { - powerListener, err := winpowrprof.NewEventListener(r.notifyWindowsPowerEvent) - if err == nil { - r.powerListener = powerListener - } else { - r.logger.Warn("initialize power listener: ", err) - } - } - - if r.powerListener != nil { - monitor.Start("start power listener") - err := r.powerListener.Start() - monitor.Finish() - if err != nil { - return E.Cause(err, "start power listener") - } - } - monitor.Start("initialize DNS client") r.dnsClient.Start() monitor.Finish() - if C.IsAndroid && r.platformInterface == nil { - monitor.Start("initialize package manager") - packageManager, err := tun.NewPackageManager(tun.PackageManagerOptions{ - Callback: r, - Logger: r.logger, - }) - monitor.Finish() - if err != nil { - return E.Cause(err, "create package manager") - } - if r.enforcePackageManager { - monitor.Start("start package manager") - err = packageManager.Start() - monitor.Finish() - if err != nil { - return E.Cause(err, "start package manager") - } - } - r.packageManager = packageManager - } - for i, rule := range r.dnsRules { monitor.Start("initialize DNS rule[", i, "]") err := rule.Start() @@ -552,26 +416,13 @@ func (r *Router) Start(stage adapter.StartStage) error { cacheContext.Close() } needFindProcess := r.needFindProcess - needWIFIState := r.needWIFIState for _, ruleSet := range r.ruleSets { metadata := ruleSet.Metadata() if metadata.ContainsProcessRule { needFindProcess = true } if metadata.ContainsWIFIRule { - needWIFIState = true - } - } - if C.IsAndroid && r.platformInterface == nil && !r.enforcePackageManager { - if needFindProcess { - monitor.Start("start package manager") - err := r.packageManager.Start() - monitor.Finish() - if err != nil { - return E.Cause(err, "start package manager") - } - } else { - r.packageManager = nil + r.needWIFIState = true } } if needFindProcess { @@ -581,7 +432,7 @@ func (r *Router) Start(stage adapter.StartStage) error { monitor.Start("initialize process searcher") searcher, err := process.NewSearcher(process.Config{ Logger: r.logger, - PackageManager: r.packageManager, + PackageManager: r.networkManager.PackageManager(), }) monitor.Finish() if err != nil { @@ -593,15 +444,6 @@ func (r *Router) Start(stage adapter.StartStage) error { } } } - if needWIFIState && r.platformInterface != nil { - monitor.Start("initialize WIFI state") - r.needWIFIState = true - r.interfaceMonitor.RegisterCallback(func(_ int) { - r.updateWIFIState() - }) - r.updateWIFIState() - monitor.Finish() - } for i, rule := range r.rules { monitor.Start("initialize rule[", i, "]") err := rule.Start() @@ -660,34 +502,6 @@ func (r *Router) Close() error { }) monitor.Finish() } - if r.interfaceMonitor != nil { - monitor.Start("close interface monitor") - err = E.Append(err, r.interfaceMonitor.Close(), func(err error) error { - return E.Cause(err, "close interface monitor") - }) - monitor.Finish() - } - if r.networkMonitor != nil { - monitor.Start("close network monitor") - err = E.Append(err, r.networkMonitor.Close(), func(err error) error { - return E.Cause(err, "close network monitor") - }) - monitor.Finish() - } - if r.packageManager != nil { - monitor.Start("close package manager") - err = E.Append(err, r.packageManager.Close(), func(err error) error { - return E.Cause(err, "close package manager") - }) - monitor.Finish() - } - if r.powerListener != nil { - monitor.Start("close power listener") - err = E.Append(err, r.powerListener.Close(), func(err error) error { - return E.Cause(err, "close power listener") - }) - monitor.Finish() - } if r.timeService != nil { monitor.Start("close time service") err = E.Append(err, r.timeService.Close(), func(err error) error { @@ -705,25 +519,6 @@ func (r *Router) Close() error { return err } -func (r *Router) Outbound(tag string) (adapter.Outbound, bool) { - outbound, loaded := r.outboundByTag[tag] - return outbound, loaded -} - -func (r *Router) DefaultOutbound(network string) (adapter.Outbound, error) { - if network == N.NetworkTCP { - if r.defaultOutboundForConnection == nil { - return nil, E.New("missing default outbound for TCP connections") - } - return r.defaultOutboundForConnection, nil - } else { - if r.defaultOutboundForPacketConnection == nil { - return nil, E.New("missing default outbound for UDP connections") - } - return r.defaultOutboundForPacketConnection, nil - } -} - func (r *Router) FakeIPStore() adapter.FakeIPStore { return r.fakeIPStore } @@ -737,96 +532,10 @@ func (r *Router) NeedWIFIState() bool { return r.needWIFIState } -func (r *Router) InterfaceFinder() control.InterfaceFinder { - return r.interfaceFinder -} - -func (r *Router) UpdateInterfaces() error { - if r.platformInterface == nil || !r.platformInterface.UsePlatformInterfaceGetter() { - return r.interfaceFinder.Update() - } else { - interfaces, err := r.platformInterface.Interfaces() - if err != nil { - return err - } - r.interfaceFinder.UpdateInterfaces(interfaces) - return nil - } -} - -func (r *Router) AutoDetectInterface() bool { - return r.autoDetectInterface -} - -func (r *Router) AutoDetectInterfaceFunc() control.Func { - if r.platformInterface != nil && r.platformInterface.UsePlatformAutoDetectInterfaceControl() { - return func(network, address string, conn syscall.RawConn) error { - return control.Raw(conn, func(fd uintptr) error { - return r.platformInterface.AutoDetectInterfaceControl(int(fd)) - }) - } - } else { - if r.interfaceMonitor == nil { - return nil - } - return control.BindToInterfaceFunc(r.InterfaceFinder(), func(network string, address string) (interfaceName string, interfaceIndex int, err error) { - remoteAddr := M.ParseSocksaddr(address).Addr - if C.IsLinux { - interfaceName, interfaceIndex = r.InterfaceMonitor().DefaultInterface(remoteAddr) - if interfaceIndex == -1 { - err = tun.ErrNoRoute - } - } else { - interfaceIndex = r.InterfaceMonitor().DefaultInterfaceIndex(remoteAddr) - if interfaceIndex == -1 { - err = tun.ErrNoRoute - } - } - return - }) - } -} - -func (r *Router) RegisterAutoRedirectOutputMark(mark uint32) error { - if r.autoRedirectOutputMark > 0 { - return E.New("only one auto-redirect can be configured") - } - r.autoRedirectOutputMark = mark - return nil -} - -func (r *Router) AutoRedirectOutputMark() uint32 { - return r.autoRedirectOutputMark -} - -func (r *Router) DefaultInterface() string { - return r.defaultInterface -} - -func (r *Router) DefaultMark() uint32 { - return r.defaultMark -} - func (r *Router) Rules() []adapter.Rule { return r.rules } -func (r *Router) WIFIState() adapter.WIFIState { - return r.wifiState -} - -func (r *Router) NetworkMonitor() tun.NetworkUpdateMonitor { - return r.networkMonitor -} - -func (r *Router) InterfaceMonitor() tun.DefaultInterfaceMonitor { - return r.interfaceMonitor -} - -func (r *Router) PackageManager() tun.PackageManager { - return r.packageManager -} - func (r *Router) ClashServer() adapter.ClashServer { return r.clashServer } @@ -843,10 +552,6 @@ func (r *Router) SetV2RayServer(server adapter.V2RayServer) { r.v2rayServer = server } -func (r *Router) OnPackagesUpdated(packages int, sharedUsers int) { - r.logger.Info("updated packages list: ", packages, " packages, ", sharedUsers, " shared users") -} - func (r *Router) NewError(ctx context.Context, err error) { common.Close(err) if E.IsClosedOrCanceled(err) { @@ -856,75 +561,9 @@ func (r *Router) NewError(ctx context.Context, err error) { r.logger.ErrorContext(ctx, err) } -func (r *Router) notifyNetworkUpdate(event int) { - if event == tun.EventNoRoute { - r.pauseManager.NetworkPause() - r.logger.Error("missing default interface") - } else { - r.pauseManager.NetworkWake() - if C.IsAndroid && r.platformInterface == nil { - var vpnStatus string - if r.interfaceMonitor.AndroidVPNEnabled() { - vpnStatus = "enabled" - } else { - vpnStatus = "disabled" - } - r.logger.Info("updated default interface ", r.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", r.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified()), ", vpn ", vpnStatus) - } else { - r.logger.Info("updated default interface ", r.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", r.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified())) - } - } - - if !r.started { - return - } - - _ = r.ResetNetwork() -} - -func (r *Router) ResetNetwork() error { - conntrack.Close() - - for _, outbound := range r.outbounds { - listener, isListener := outbound.(adapter.InterfaceUpdateListener) - if isListener { - listener.InterfaceUpdated() - } - } - +func (r *Router) ResetNetwork() { + r.networkManager.ResetNetwork() for _, transport := range r.transports { transport.Reset() } - return nil -} - -func (r *Router) updateWIFIState() { - if r.platformInterface == nil { - return - } - state := r.platformInterface.ReadWIFIState() - if state != r.wifiState { - r.wifiState = state - if state.SSID == "" && state.BSSID == "" { - r.logger.Info("updated WIFI state: disconnected") - } else { - r.logger.Info("updated WIFI state: SSID=", state.SSID, ", BSSID=", state.BSSID) - } - } -} - -func (r *Router) notifyWindowsPowerEvent(event int) { - switch event { - case winpowrprof.EVENT_SUSPEND: - r.pauseManager.DevicePause() - _ = r.ResetNetwork() - case winpowrprof.EVENT_RESUME: - if !r.pauseManager.IsDevicePaused() { - return - } - fallthrough - case winpowrprof.EVENT_RESUME_AUTOMATIC: - r.pauseManager.DeviceWake() - _ = r.ResetNetwork() - } } diff --git a/route/rule/rule_default.go b/route/rule/rule_default.go index a12c63ef8..33a8e16c3 100644 --- a/route/rule/rule_default.go +++ b/route/rule/rule_default.go @@ -9,9 +9,10 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/service" ) -func NewRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.Rule, checkOutbound bool) (adapter.Rule, error) { +func NewRule(ctx context.Context, logger log.ContextLogger, options option.Rule, checkOutbound bool) (adapter.Rule, error) { switch options.Type { case "", C.RuleTypeDefault: if !options.DefaultOptions.IsValid() { @@ -23,7 +24,7 @@ func NewRule(ctx context.Context, router adapter.Router, logger log.ContextLogge return nil, E.New("missing outbound field") } } - return NewDefaultRule(ctx, router, logger, options.DefaultOptions) + return NewDefaultRule(ctx, logger, options.DefaultOptions) case C.RuleTypeLogical: if !options.LogicalOptions.IsValid() { return nil, E.New("missing conditions") @@ -34,7 +35,7 @@ func NewRule(ctx context.Context, router adapter.Router, logger log.ContextLogge return nil, E.New("missing outbound field") } } - return NewLogicalRule(ctx, router, logger, options.LogicalOptions) + return NewLogicalRule(ctx, logger, options.LogicalOptions) default: return nil, E.New("unknown rule type: ", options.Type) } @@ -51,7 +52,7 @@ type RuleItem interface { String() string } -func NewDefaultRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.DefaultRule) (*DefaultRule, error) { +func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options option.DefaultRule) (*DefaultRule, error) { action, err := NewRuleAction(ctx, logger, options.RuleAction) if err != nil { return nil, E.Cause(err, "action") @@ -62,6 +63,8 @@ func NewDefaultRule(ctx context.Context, router adapter.Router, logger log.Conte action: action, }, } + router := service.FromContext[adapter.Router](ctx) + networkManager := service.FromContext[adapter.NetworkManager](ctx) if len(options.Inbound) > 0 { item := NewInboundRule(options.Inbound) rule.items = append(rule.items, item) @@ -221,12 +224,12 @@ func NewDefaultRule(ctx context.Context, router adapter.Router, logger log.Conte rule.allItems = append(rule.allItems, item) } if len(options.WIFISSID) > 0 { - item := NewWIFISSIDItem(router, options.WIFISSID) + item := NewWIFISSIDItem(networkManager, options.WIFISSID) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } if len(options.WIFIBSSID) > 0 { - item := NewWIFIBSSIDItem(router, options.WIFIBSSID) + item := NewWIFIBSSIDItem(networkManager, options.WIFIBSSID) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } @@ -253,7 +256,7 @@ type LogicalRule struct { abstractLogicalRule } -func NewLogicalRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.LogicalRule) (*LogicalRule, error) { +func NewLogicalRule(ctx context.Context, logger log.ContextLogger, options option.LogicalRule) (*LogicalRule, error) { action, err := NewRuleAction(ctx, logger, options.RuleAction) if err != nil { return nil, E.Cause(err, "action") @@ -274,7 +277,7 @@ func NewLogicalRule(ctx context.Context, router adapter.Router, logger log.Conte return nil, E.New("unknown logical mode: ", options.Mode) } for i, subOptions := range options.Rules { - subRule, err := NewRule(ctx, router, logger, subOptions, false) + subRule, err := NewRule(ctx, logger, subOptions, false) if err != nil { return nil, E.Cause(err, "sub rule[", i, "]") } diff --git a/route/rule/rule_dns.go b/route/rule/rule_dns.go index 2218f6a30..df5f3f335 100644 --- a/route/rule/rule_dns.go +++ b/route/rule/rule_dns.go @@ -10,9 +10,10 @@ import ( "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/service" ) -func NewDNSRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.DNSRule, checkServer bool) (adapter.DNSRule, error) { +func NewDNSRule(ctx context.Context, logger log.ContextLogger, options option.DNSRule, checkServer bool) (adapter.DNSRule, error) { switch options.Type { case "", C.RuleTypeDefault: if !options.DefaultOptions.IsValid() { @@ -24,7 +25,7 @@ func NewDNSRule(ctx context.Context, router adapter.Router, logger log.ContextLo return nil, E.New("missing server field") } } - return NewDefaultDNSRule(ctx, router, logger, options.DefaultOptions) + return NewDefaultDNSRule(ctx, logger, options.DefaultOptions) case C.RuleTypeLogical: if !options.LogicalOptions.IsValid() { return nil, E.New("missing conditions") @@ -35,7 +36,7 @@ func NewDNSRule(ctx context.Context, router adapter.Router, logger log.ContextLo return nil, E.New("missing server field") } } - return NewLogicalDNSRule(ctx, router, logger, options.LogicalOptions) + return NewLogicalDNSRule(ctx, logger, options.LogicalOptions) default: return nil, E.New("unknown rule type: ", options.Type) } @@ -47,7 +48,7 @@ type DefaultDNSRule struct { abstractDefaultRule } -func NewDefaultDNSRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.DefaultDNSRule) (*DefaultDNSRule, error) { +func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options option.DefaultDNSRule) (*DefaultDNSRule, error) { rule := &DefaultDNSRule{ abstractDefaultRule: abstractDefaultRule{ invert: options.Invert, @@ -59,6 +60,8 @@ func NewDefaultDNSRule(ctx context.Context, router adapter.Router, logger log.Co rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } + router := service.FromContext[adapter.Router](ctx) + networkManager := service.FromContext[adapter.NetworkManager](ctx) if options.IPVersion > 0 { switch options.IPVersion { case 4, 6: @@ -218,12 +221,12 @@ func NewDefaultDNSRule(ctx context.Context, router adapter.Router, logger log.Co rule.allItems = append(rule.allItems, item) } if len(options.WIFISSID) > 0 { - item := NewWIFISSIDItem(router, options.WIFISSID) + item := NewWIFISSIDItem(networkManager, options.WIFISSID) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } if len(options.WIFIBSSID) > 0 { - item := NewWIFIBSSIDItem(router, options.WIFIBSSID) + item := NewWIFIBSSIDItem(networkManager, options.WIFIBSSID) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } @@ -282,7 +285,7 @@ type LogicalDNSRule struct { abstractLogicalRule } -func NewLogicalDNSRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.LogicalDNSRule) (*LogicalDNSRule, error) { +func NewLogicalDNSRule(ctx context.Context, logger log.ContextLogger, options option.LogicalDNSRule) (*LogicalDNSRule, error) { r := &LogicalDNSRule{ abstractLogicalRule: abstractLogicalRule{ rules: make([]adapter.HeadlessRule, len(options.Rules)), @@ -299,7 +302,7 @@ func NewLogicalDNSRule(ctx context.Context, router adapter.Router, logger log.Co return nil, E.New("unknown logical mode: ", options.Mode) } for i, subRule := range options.Rules { - rule, err := NewDNSRule(ctx, router, logger, subRule, false) + rule, err := NewDNSRule(ctx, logger, subRule, false) if err != nil { return nil, E.Cause(err, "sub rule[", i, "]") } diff --git a/route/rule/rule_headless.go b/route/rule/rule_headless.go index 9ea357af8..99488b204 100644 --- a/route/rule/rule_headless.go +++ b/route/rule/rule_headless.go @@ -1,24 +1,27 @@ package rule import ( + "context" + "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/service" ) -func NewHeadlessRule(router adapter.Router, options option.HeadlessRule) (adapter.HeadlessRule, error) { +func NewHeadlessRule(ctx context.Context, options option.HeadlessRule) (adapter.HeadlessRule, error) { switch options.Type { case "", C.RuleTypeDefault: if !options.DefaultOptions.IsValid() { return nil, E.New("missing conditions") } - return NewDefaultHeadlessRule(router, options.DefaultOptions) + return NewDefaultHeadlessRule(ctx, options.DefaultOptions) case C.RuleTypeLogical: if !options.LogicalOptions.IsValid() { return nil, E.New("missing conditions") } - return NewLogicalHeadlessRule(router, options.LogicalOptions) + return NewLogicalHeadlessRule(ctx, options.LogicalOptions) default: return nil, E.New("unknown rule type: ", options.Type) } @@ -30,7 +33,8 @@ type DefaultHeadlessRule struct { abstractDefaultRule } -func NewDefaultHeadlessRule(router adapter.Router, options option.DefaultHeadlessRule) (*DefaultHeadlessRule, error) { +func NewDefaultHeadlessRule(ctx context.Context, options option.DefaultHeadlessRule) (*DefaultHeadlessRule, error) { + networkManager := service.FromContext[adapter.NetworkManager](ctx) rule := &DefaultHeadlessRule{ abstractDefaultRule{ invert: options.Invert, @@ -137,15 +141,15 @@ func NewDefaultHeadlessRule(router adapter.Router, options option.DefaultHeadles rule.allItems = append(rule.allItems, item) } if len(options.WIFISSID) > 0 { - if router != nil { - item := NewWIFISSIDItem(router, options.WIFISSID) + if networkManager != nil { + item := NewWIFISSIDItem(networkManager, options.WIFISSID) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } } if len(options.WIFIBSSID) > 0 { - if router != nil { - item := NewWIFIBSSIDItem(router, options.WIFIBSSID) + if networkManager != nil { + item := NewWIFIBSSIDItem(networkManager, options.WIFIBSSID) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } @@ -168,7 +172,7 @@ type LogicalHeadlessRule struct { abstractLogicalRule } -func NewLogicalHeadlessRule(router adapter.Router, options option.LogicalHeadlessRule) (*LogicalHeadlessRule, error) { +func NewLogicalHeadlessRule(ctx context.Context, options option.LogicalHeadlessRule) (*LogicalHeadlessRule, error) { r := &LogicalHeadlessRule{ abstractLogicalRule{ rules: make([]adapter.HeadlessRule, len(options.Rules)), @@ -184,7 +188,7 @@ func NewLogicalHeadlessRule(router adapter.Router, options option.LogicalHeadles return nil, E.New("unknown logical mode: ", options.Mode) } for i, subRule := range options.Rules { - rule, err := NewHeadlessRule(router, subRule) + rule, err := NewHeadlessRule(ctx, subRule) if err != nil { return nil, E.Cause(err, "sub rule[", i, "]") } diff --git a/route/rule/rule_item_wifi_bssid.go b/route/rule/rule_item_wifi_bssid.go index ae94bd6df..8f887322e 100644 --- a/route/rule/rule_item_wifi_bssid.go +++ b/route/rule/rule_item_wifi_bssid.go @@ -10,12 +10,12 @@ import ( var _ RuleItem = (*WIFIBSSIDItem)(nil) type WIFIBSSIDItem struct { - bssidList []string - bssidMap map[string]bool - router adapter.Router + bssidList []string + bssidMap map[string]bool + networkManager adapter.NetworkManager } -func NewWIFIBSSIDItem(router adapter.Router, bssidList []string) *WIFIBSSIDItem { +func NewWIFIBSSIDItem(networkManager adapter.NetworkManager, bssidList []string) *WIFIBSSIDItem { bssidMap := make(map[string]bool) for _, bssid := range bssidList { bssidMap[bssid] = true @@ -23,12 +23,12 @@ func NewWIFIBSSIDItem(router adapter.Router, bssidList []string) *WIFIBSSIDItem return &WIFIBSSIDItem{ bssidList, bssidMap, - router, + networkManager, } } func (r *WIFIBSSIDItem) Match(metadata *adapter.InboundContext) bool { - return r.bssidMap[r.router.WIFIState().BSSID] + return r.bssidMap[r.networkManager.WIFIState().BSSID] } func (r *WIFIBSSIDItem) String() string { diff --git a/route/rule/rule_item_wifi_ssid.go b/route/rule/rule_item_wifi_ssid.go index 3a928f77e..ab9fdd884 100644 --- a/route/rule/rule_item_wifi_ssid.go +++ b/route/rule/rule_item_wifi_ssid.go @@ -10,12 +10,12 @@ import ( var _ RuleItem = (*WIFISSIDItem)(nil) type WIFISSIDItem struct { - ssidList []string - ssidMap map[string]bool - router adapter.Router + ssidList []string + ssidMap map[string]bool + networkManager adapter.NetworkManager } -func NewWIFISSIDItem(router adapter.Router, ssidList []string) *WIFISSIDItem { +func NewWIFISSIDItem(networkManager adapter.NetworkManager, ssidList []string) *WIFISSIDItem { ssidMap := make(map[string]bool) for _, ssid := range ssidList { ssidMap[ssid] = true @@ -23,12 +23,12 @@ func NewWIFISSIDItem(router adapter.Router, ssidList []string) *WIFISSIDItem { return &WIFISSIDItem{ ssidList, ssidMap, - router, + networkManager, } } func (r *WIFISSIDItem) Match(metadata *adapter.InboundContext) bool { - return r.ssidMap[r.router.WIFIState().SSID] + return r.ssidMap[r.networkManager.WIFIState().SSID] } func (r *WIFISSIDItem) String() string { diff --git a/route/rule/rule_set.go b/route/rule/rule_set.go index cdd0fc0af..c0f307b76 100644 --- a/route/rule/rule_set.go +++ b/route/rule/rule_set.go @@ -13,12 +13,12 @@ import ( "go4.org/netipx" ) -func NewRuleSet(ctx context.Context, router adapter.Router, logger logger.ContextLogger, options option.RuleSet) (adapter.RuleSet, error) { +func NewRuleSet(ctx context.Context, logger logger.ContextLogger, options option.RuleSet) (adapter.RuleSet, error) { switch options.Type { case C.RuleSetTypeInline, C.RuleSetTypeLocal, "": - return NewLocalRuleSet(ctx, router, logger, options) + return NewLocalRuleSet(ctx, logger, options) case C.RuleSetTypeRemote: - return NewRemoteRuleSet(ctx, router, logger, options), nil + return NewRemoteRuleSet(ctx, logger, options), nil default: return nil, E.New("unknown rule-set type: ", options.Type) } diff --git a/route/rule/rule_set_local.go b/route/rule/rule_set_local.go index 900c8b242..63678de1c 100644 --- a/route/rule/rule_set_local.go +++ b/route/rule/rule_set_local.go @@ -26,7 +26,7 @@ import ( var _ adapter.RuleSet = (*LocalRuleSet)(nil) type LocalRuleSet struct { - router adapter.Router + ctx context.Context logger logger.Logger tag string rules []adapter.HeadlessRule @@ -36,9 +36,9 @@ type LocalRuleSet struct { refs atomic.Int32 } -func NewLocalRuleSet(ctx context.Context, router adapter.Router, logger logger.Logger, options option.RuleSet) (*LocalRuleSet, error) { +func NewLocalRuleSet(ctx context.Context, logger logger.Logger, options option.RuleSet) (*LocalRuleSet, error) { ruleSet := &LocalRuleSet{ - router: router, + ctx: ctx, logger: logger, tag: options.Tag, fileFormat: options.Format, @@ -129,7 +129,7 @@ func (s *LocalRuleSet) reloadRules(headlessRules []option.HeadlessRule) error { rules := make([]adapter.HeadlessRule, len(headlessRules)) var err error for i, ruleOptions := range headlessRules { - rules[i], err = NewHeadlessRule(s.router, ruleOptions) + rules[i], err = NewHeadlessRule(s.ctx, ruleOptions) if err != nil { return E.Cause(err, "parse rule_set.rules.[", i, "]") } diff --git a/route/rule/rule_set_remote.go b/route/rule/rule_set_remote.go index 55c863e9c..5ecbcbef3 100644 --- a/route/rule/rule_set_remote.go +++ b/route/rule/rule_set_remote.go @@ -35,7 +35,6 @@ var _ adapter.RuleSet = (*RemoteRuleSet)(nil) type RemoteRuleSet struct { ctx context.Context cancel context.CancelFunc - router adapter.Router outboundManager adapter.OutboundManager logger logger.ContextLogger options option.RuleSet @@ -53,7 +52,7 @@ type RemoteRuleSet struct { refs atomic.Int32 } -func NewRemoteRuleSet(ctx context.Context, router adapter.Router, logger logger.ContextLogger, options option.RuleSet) *RemoteRuleSet { +func NewRemoteRuleSet(ctx context.Context, logger logger.ContextLogger, options option.RuleSet) *RemoteRuleSet { ctx, cancel := context.WithCancel(ctx) var updateInterval time.Duration if options.RemoteOptions.UpdateInterval > 0 { @@ -64,7 +63,6 @@ func NewRemoteRuleSet(ctx context.Context, router adapter.Router, logger logger. return &RemoteRuleSet{ ctx: ctx, cancel: cancel, - router: router, outboundManager: service.FromContext[adapter.OutboundManager](ctx), logger: logger, options: options, @@ -181,7 +179,7 @@ func (s *RemoteRuleSet) loadBytes(content []byte) error { } rules := make([]adapter.HeadlessRule, len(plainRuleSet.Rules)) for i, ruleOptions := range plainRuleSet.Rules { - rules[i], err = NewHeadlessRule(s.router, ruleOptions) + rules[i], err = NewHeadlessRule(s.ctx, ruleOptions) if err != nil { return E.Cause(err, "parse rule_set.rules.[", i, "]") } diff --git a/transport/dhcp/server.go b/transport/dhcp/server.go index d5603e9ed..dfe33d86c 100644 --- a/transport/dhcp/server.go +++ b/transport/dhcp/server.go @@ -38,6 +38,7 @@ func init() { type Transport struct { options dns.TransportOptions router adapter.Router + networkManager adapter.NetworkManager interfaceName string autoInterface bool interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback] @@ -54,15 +55,11 @@ func NewTransport(options dns.TransportOptions) (*Transport, error) { if linkURL.Host == "" { return nil, E.New("missing interface name for DHCP") } - router := service.FromContext[adapter.Router](options.Context) - if router == nil { - return nil, E.New("missing router in context") - } transport := &Transport{ - options: options, - router: router, - interfaceName: linkURL.Host, - autoInterface: linkURL.Host == "auto", + options: options, + networkManager: service.FromContext[adapter.NetworkManager](options.Context), + interfaceName: linkURL.Host, + autoInterface: linkURL.Host == "auto", } return transport, nil } @@ -77,7 +74,7 @@ func (t *Transport) Start() error { return err } if t.autoInterface { - t.interfaceCallback = t.router.InterfaceMonitor().RegisterCallback(t.interfaceUpdated) + t.interfaceCallback = t.networkManager.InterfaceMonitor().RegisterCallback(t.interfaceUpdated) } return nil } @@ -93,7 +90,7 @@ func (t *Transport) Close() error { transport.Close() } if t.interfaceCallback != nil { - t.router.InterfaceMonitor().UnregisterCallback(t.interfaceCallback) + t.networkManager.InterfaceMonitor().UnregisterCallback(t.interfaceCallback) } return nil } @@ -125,10 +122,10 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, func (t *Transport) fetchInterface() (*net.Interface, error) { interfaceName := t.interfaceName if t.autoInterface { - if t.router.InterfaceMonitor() == nil { + if t.networkManager.InterfaceMonitor() == nil { return nil, E.New("missing monitor for auto DHCP, set route.auto_detect_interface") } - interfaceName = t.router.InterfaceMonitor().DefaultInterfaceName(netip.Addr{}) + interfaceName = t.networkManager.InterfaceMonitor().DefaultInterfaceName(netip.Addr{}) } if interfaceName == "" { return nil, E.New("missing default interface") @@ -177,7 +174,7 @@ func (t *Transport) interfaceUpdated(int) { func (t *Transport) fetchServers0(ctx context.Context, iface *net.Interface) error { var listener net.ListenConfig - listener.Control = control.Append(listener.Control, control.BindToInterface(t.router.InterfaceFinder(), iface.Name, iface.Index)) + listener.Control = control.Append(listener.Control, control.BindToInterface(t.networkManager.InterfaceFinder(), iface.Name, iface.Index)) listener.Control = control.Append(listener.Control, control.ReuseAddr()) listenAddr := "0.0.0.0:68" if runtime.GOOS == "linux" || runtime.GOOS == "android" { @@ -255,7 +252,7 @@ func (t *Transport) recreateServers(iface *net.Interface, serverAddrs []netip.Ad return it.String() }), ","), "]") } - serverDialer := common.Must1(dialer.NewDefault(t.router, option.DialerOptions{ + serverDialer := common.Must1(dialer.NewDefault(t.networkManager, option.DialerOptions{ BindInterface: iface.Name, UDPFragmentDefault: true, })) diff --git a/transport/wireguard/device_system.go b/transport/wireguard/device_system.go index 36c270f9d..8a54a75ef 100644 --- a/transport/wireguard/device_system.go +++ b/transport/wireguard/device_system.go @@ -34,7 +34,7 @@ type SystemDevice struct { closeOnce sync.Once } -func NewSystemDevice(router adapter.Router, interfaceName string, localPrefixes []netip.Prefix, mtu uint32, gso bool) (*SystemDevice, error) { +func NewSystemDevice(networkManager adapter.NetworkManager, interfaceName string, localPrefixes []netip.Prefix, mtu uint32, gso bool) (*SystemDevice, error) { var inet4Addresses []netip.Prefix var inet6Addresses []netip.Prefix for _, prefixes := range localPrefixes { @@ -49,7 +49,7 @@ func NewSystemDevice(router adapter.Router, interfaceName string, localPrefixes } return &SystemDevice{ - dialer: common.Must1(dialer.NewDefault(router, option.DialerOptions{ + dialer: common.Must1(dialer.NewDefault(networkManager, option.DialerOptions{ BindInterface: interfaceName, })), name: interfaceName, From b4f1c2a596bfff6bcc3fdd248c0dd5c524700f8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 10 Nov 2024 16:46:59 +0800 Subject: [PATCH 17/39] refactor: Extract clash/v2ray/time service form router --- adapter/experimental.go | 30 +- adapter/inbound.go | 2 +- adapter/lifecycle.go | 9 +- adapter/lifecycle_legacy.go | 32 ++- adapter/network.go | 2 +- adapter/outbound.go | 2 +- adapter/router.go | 13 +- box.go | 272 ++++++++---------- experimental/cachefile/cache.go | 21 +- experimental/clashapi/server.go | 81 +++--- .../clashapi/trafficontrol/tracker.go | 34 +-- experimental/libbox/command_clash_mode.go | 14 +- .../libbox/command_close_connection.go | 6 +- experimental/libbox/command_connections.go | 6 +- experimental/libbox/command_server.go | 2 +- experimental/libbox/command_status.go | 12 +- experimental/libbox/service.go | 8 +- experimental/v2rayapi/server.go | 11 +- experimental/v2rayapi/stats.go | 12 +- protocol/group/urltest.go | 29 +- route/route.go | 77 ++--- route/router.go | 66 +---- route/rule/rule_default.go | 3 +- route/rule/rule_dns.go | 3 +- route/rule/rule_item_clash_mode.go | 15 +- 25 files changed, 298 insertions(+), 464 deletions(-) diff --git a/adapter/experimental.go b/adapter/experimental.go index bee24c4f8..f22ff9b22 100644 --- a/adapter/experimental.go +++ b/adapter/experimental.go @@ -4,28 +4,28 @@ import ( "bytes" "context" "encoding/binary" - "net" "time" "github.com/sagernet/sing-box/common/urltest" "github.com/sagernet/sing-dns" - N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/varbin" ) type ClashServer interface { - Service - LegacyPreStarter + LifecycleService + ConnectionTracker Mode() string ModeList() []string HistoryStorage() *urltest.HistoryStorage - RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker) - RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule) (N.PacketConn, Tracker) +} + +type V2RayServer interface { + LifecycleService + StatsService() ConnectionTracker } type CacheFile interface { - Service - LegacyPreStarter + LifecycleService StoreFakeIP() bool FakeIPStorage @@ -94,10 +94,6 @@ func (s *SavedRuleSet) UnmarshalBinary(data []byte) error { return nil } -type Tracker interface { - Leave() -} - type OutboundGroup interface { Outbound Now() string @@ -115,13 +111,3 @@ func OutboundTag(detour Outbound) string { } return detour.Tag() } - -type V2RayServer interface { - Service - StatsService() V2RayStatsService -} - -type V2RayStatsService interface { - RoutedConnection(inbound string, outbound string, user string, conn net.Conn) net.Conn - RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn -} diff --git a/adapter/inbound.go b/adapter/inbound.go index d80e59f7b..7932237d0 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -32,7 +32,7 @@ type InboundRegistry interface { } type InboundManager interface { - NewService + Lifecycle Inbounds() []Inbound Get(tag string) (Inbound, bool) Remove(tag string) error diff --git a/adapter/lifecycle.go b/adapter/lifecycle.go index 85de425d5..7a1639dc4 100644 --- a/adapter/lifecycle.go +++ b/adapter/lifecycle.go @@ -31,11 +31,12 @@ func (s StartStage) Action() string { } } -type NewService interface { - NewStarter +type Lifecycle interface { + Start(stage StartStage) error Close() error } -type NewStarter interface { - Start(stage StartStage) error +type LifecycleService interface { + Name() string + Lifecycle } diff --git a/adapter/lifecycle_legacy.go b/adapter/lifecycle_legacy.go index 5968131bb..f73f92340 100644 --- a/adapter/lifecycle_legacy.go +++ b/adapter/lifecycle_legacy.go @@ -1,13 +1,5 @@ package adapter -type LegacyPreStarter interface { - PreStart() error -} - -type LegacyPostStarter interface { - PostStart() error -} - func LegacyStart(starter any, stage StartStage) error { switch stage { case StartStateInitialize: @@ -31,3 +23,27 @@ func LegacyStart(starter any, stage StartStage) error { } return nil } + +type lifecycleServiceWrapper struct { + Service + name string +} + +func NewLifecycleService(service Service, name string) LifecycleService { + return &lifecycleServiceWrapper{ + Service: service, + name: name, + } +} + +func (l *lifecycleServiceWrapper) Name() string { + return l.name +} + +func (l *lifecycleServiceWrapper) Start(stage StartStage) error { + return LegacyStart(l.Service, stage) +} + +func (l *lifecycleServiceWrapper) Close() error { + return l.Service.Close() +} diff --git a/adapter/network.go b/adapter/network.go index 0ce274118..533bfced2 100644 --- a/adapter/network.go +++ b/adapter/network.go @@ -6,7 +6,7 @@ import ( ) type NetworkManager interface { - NewService + Lifecycle InterfaceFinder() control.InterfaceFinder UpdateInterfaces() error DefaultInterface() string diff --git a/adapter/outbound.go b/adapter/outbound.go index b170398a9..2c2b10910 100644 --- a/adapter/outbound.go +++ b/adapter/outbound.go @@ -24,7 +24,7 @@ type OutboundRegistry interface { } type OutboundManager interface { - NewService + Lifecycle Outbounds() []Outbound Outbound(tag string) (Outbound, bool) Default() Outbound diff --git a/adapter/router.go b/adapter/router.go index 40a461a79..6dd39357b 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -19,7 +19,7 @@ import ( ) type Router interface { - NewService + Lifecycle FakeIPStore() FakeIPStore @@ -38,15 +38,16 @@ type Router interface { ClearDNSCache() Rules() []Rule - ClashServer() ClashServer - SetClashServer(server ClashServer) - - V2RayServer() V2RayServer - SetV2RayServer(server V2RayServer) + SetTracker(tracker ConnectionTracker) ResetNetwork() } +type ConnectionTracker interface { + RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule, matchOutbound Outbound) net.Conn + RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule, matchOutbound Outbound) N.PacketConn +} + // Deprecated: Use ConnectionRouterEx instead. type ConnectionRouter interface { RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error diff --git a/box.go b/box.go index d3fe5e46e..6205a7d91 100644 --- a/box.go +++ b/box.go @@ -11,6 +11,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter/inbound" "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/taskmonitor" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/experimental" @@ -23,6 +24,7 @@ import ( "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/ntp" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/pause" ) @@ -30,17 +32,15 @@ import ( var _ adapter.Service = (*Box)(nil) type Box struct { - createdAt time.Time - router adapter.Router - inbound *inbound.Manager - outbound *outbound.Manager - network *route.NetworkManager - logFactory log.Factory - logger log.ContextLogger - preServices1 map[string]adapter.Service - preServices2 map[string]adapter.Service - postServices map[string]adapter.Service - done chan struct{} + createdAt time.Time + logFactory log.Factory + logger log.ContextLogger + network *route.NetworkManager + router *route.Router + inbound *inbound.Manager + outbound *outbound.Manager + services []adapter.LifecycleService + done chan struct{} } type Options struct { @@ -49,7 +49,11 @@ type Options struct { PlatformLogWriter log.PlatformWriter } -func Context(ctx context.Context, inboundRegistry adapter.InboundRegistry, outboundRegistry adapter.OutboundRegistry) context.Context { +func Context( + ctx context.Context, + inboundRegistry adapter.InboundRegistry, + outboundRegistry adapter.OutboundRegistry, +) context.Context { if service.FromContext[option.InboundOptionsRegistry](ctx) == nil || service.FromContext[adapter.InboundRegistry](ctx) == nil { ctx = service.ContextWith[option.InboundOptionsRegistry](ctx, inboundRegistry) @@ -70,14 +74,17 @@ func New(options Options) (*Box, error) { ctx = context.Background() } ctx = service.ContextWithDefaultRegistry(ctx) + inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx) if inboundRegistry == nil { return nil, E.New("missing inbound registry in context") } + outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx) if outboundRegistry == nil { return nil, E.New("missing outbound registry in context") } + ctx = pause.WithDefaultManager(ctx) experimentalOptions := common.PtrValueOrDefault(options.Experimental) applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug)) @@ -109,17 +116,19 @@ func New(options Options) (*Box, error) { if err != nil { return nil, E.Cause(err, "create log factory") } + routeOptions := common.PtrValueOrDefault(options.Route) inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry) outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, routeOptions.Final) - ctx = service.ContextWith[adapter.InboundManager](ctx, inboundManager) - ctx = service.ContextWith[adapter.OutboundManager](ctx, outboundManager) + service.MustRegister[adapter.InboundManager](ctx, inboundManager) + service.MustRegister[adapter.OutboundManager](ctx, outboundManager) + networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions) if err != nil { return nil, E.Cause(err, "initialize network manager") } - ctx = service.ContextWith[adapter.NetworkManager](ctx, networkManager) - router, err := route.NewRouter(ctx, logFactory, routeOptions, common.PtrValueOrDefault(options.DNS), common.PtrValueOrDefault(options.NTP)) + service.MustRegister[adapter.NetworkManager](ctx, networkManager) + router, err := route.NewRouter(ctx, logFactory, routeOptions, common.PtrValueOrDefault(options.DNS)) if err != nil { return nil, E.Cause(err, "initialize router") } @@ -202,47 +211,61 @@ func New(options Options) (*Box, error) { return nil, E.Cause(err, "initialize platform interface") } } - preServices1 := make(map[string]adapter.Service) - preServices2 := make(map[string]adapter.Service) - postServices := make(map[string]adapter.Service) + var services []adapter.LifecycleService if needCacheFile { - cacheFile := service.FromContext[adapter.CacheFile](ctx) - if cacheFile == nil { - cacheFile = cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile)) - service.MustRegister[adapter.CacheFile](ctx, cacheFile) - } - preServices1["cache file"] = cacheFile + cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile)) + service.MustRegister[adapter.CacheFile](ctx, cacheFile) + services = append(services, cacheFile) } if needClashAPI { clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI) clashAPIOptions.ModeList = experimental.CalculateClashModeList(options.Options) clashServer, err := experimental.NewClashServer(ctx, logFactory.(log.ObservableFactory), clashAPIOptions) if err != nil { - return nil, E.Cause(err, "create clash api server") + return nil, E.Cause(err, "create clash-server") } - router.SetClashServer(clashServer) - preServices2["clash api"] = clashServer + router.SetTracker(clashServer) + service.MustRegister[adapter.ClashServer](ctx, clashServer) + services = append(services, clashServer) } if needV2RayAPI { v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI)) if err != nil { - return nil, E.Cause(err, "create v2ray api server") + return nil, E.Cause(err, "create v2ray-server") + } + if v2rayServer.StatsService() != nil { + router.SetTracker(v2rayServer.StatsService()) + services = append(services, v2rayServer) + service.MustRegister[adapter.V2RayServer](ctx, v2rayServer) + } + } + ntpOptions := common.PtrValueOrDefault(options.NTP) + if ntpOptions.Enabled { + ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions) + if err != nil { + return nil, E.Cause(err, "create NTP service") } - router.SetV2RayServer(v2rayServer) - preServices2["v2ray api"] = v2rayServer + timeService := ntp.NewService(ntp.Options{ + Context: ctx, + Dialer: ntpDialer, + Logger: logFactory.NewLogger("ntp"), + Server: ntpOptions.ServerOptions.Build(), + Interval: time.Duration(ntpOptions.Interval), + WriteToSystem: ntpOptions.WriteToSystem, + }) + service.MustRegister[ntp.TimeService](ctx, timeService) + services = append(services, adapter.NewLifecycleService(timeService, "ntp service")) } return &Box{ - router: router, - inbound: inboundManager, - outbound: outboundManager, - network: networkManager, - createdAt: createdAt, - logFactory: logFactory, - logger: logFactory.Logger(), - preServices1: preServices1, - preServices2: preServices2, - postServices: postServices, - done: make(chan struct{}), + network: networkManager, + router: router, + inbound: inboundManager, + outbound: outboundManager, + createdAt: createdAt, + logFactory: logFactory, + logger: logFactory.Logger(), + services: services, + done: make(chan struct{}), }, nil } @@ -292,43 +315,29 @@ func (s *Box) preStart() error { if err != nil { return E.Cause(err, "start logger") } - for serviceName, service := range s.preServices1 { - if preService, isPreService := service.(adapter.LegacyPreStarter); isPreService { - monitor.Start("pre-start ", serviceName) - err := preService.PreStart() - monitor.Finish() - if err != nil { - return E.Cause(err, "pre-start ", serviceName) - } + for _, lifecycleService := range s.services { + err = lifecycleService.Start(adapter.StartStateInitialize) // cache-file + if err != nil { + return E.Cause(err, "initialize ", lifecycleService.Name()) } } - for serviceName, service := range s.preServices2 { - if preService, isPreService := service.(adapter.LegacyPreStarter); isPreService { - monitor.Start("pre-start ", serviceName) - err := preService.PreStart() - monitor.Finish() - if err != nil { - return E.Cause(err, "pre-start ", serviceName) - } + for _, lifecycle := range []adapter.Lifecycle{ + s.network, s.router, s.outbound, s.inbound, + } { + err = lifecycle.Start(adapter.StartStateInitialize) + if err != nil { + return err } } - err = s.network.Start(adapter.StartStateInitialize) - if err != nil { - return E.Cause(err, "initialize network manager") - } - err = s.router.Start(adapter.StartStateInitialize) - if err != nil { - return E.Cause(err, "initialize router") - } - err = s.outbound.Start(adapter.StartStateStart) - if err != nil { - return err - } - err = s.network.Start(adapter.StartStateStart) - if err != nil { - return err + for _, lifecycle := range []adapter.Lifecycle{ + s.outbound, s.network, s.router, + } { + err = lifecycle.Start(adapter.StartStateStart) + if err != nil { + return err + } } - return s.router.Start(adapter.StartStateStart) + return nil } func (s *Box) start() error { @@ -336,59 +345,31 @@ func (s *Box) start() error { if err != nil { return err } - for serviceName, service := range s.preServices1 { - err = service.Start() - if err != nil { - return E.Cause(err, "start ", serviceName) - } - } - for serviceName, service := range s.preServices2 { - err = service.Start() + for _, lifecycleService := range s.services { + err = lifecycleService.Start(adapter.StartStateStart) if err != nil { - return E.Cause(err, "start ", serviceName) + return E.Cause(err, "initialize ", lifecycleService.Name()) } } err = s.inbound.Start(adapter.StartStateStart) if err != nil { return err } - for serviceName, service := range s.postServices { - err := service.Start() + for _, lifecycleService := range []adapter.Lifecycle{ + s.outbound, s.network, s.router, s.inbound, + } { + err = lifecycleService.Start(adapter.StartStatePostStart) if err != nil { - return E.Cause(err, "start ", serviceName) + return err } } - err = s.outbound.Start(adapter.StartStatePostStart) - if err != nil { - return err - } - err = s.network.Start(adapter.StartStatePostStart) - if err != nil { - return err - } - err = s.router.Start(adapter.StartStatePostStart) - if err != nil { - return err - } - err = s.inbound.Start(adapter.StartStatePostStart) - if err != nil { - return err - } - err = s.network.Start(adapter.StartStateStarted) - if err != nil { - return err - } - err = s.router.Start(adapter.StartStateStarted) - if err != nil { - return err - } - err = s.outbound.Start(adapter.StartStateStarted) - if err != nil { - return err - } - err = s.inbound.Start(adapter.StartStateStarted) - if err != nil { - return err + for _, lifecycleService := range []adapter.Lifecycle{ + s.network, s.router, s.outbound, s.inbound, + } { + err = lifecycleService.Start(adapter.StartStateStarted) + if err != nil { + return err + } } return nil } @@ -400,47 +381,18 @@ func (s *Box) Close() error { default: close(s.done) } - monitor := taskmonitor.New(s.logger, C.StopTimeout) - var errors error - for serviceName, service := range s.postServices { - monitor.Start("close ", serviceName) - errors = E.Append(errors, service.Close(), func(err error) error { - return E.Cause(err, "close ", serviceName) - }) - monitor.Finish() - } - errors = E.Errors(errors, s.inbound.Close()) - errors = E.Errors(errors, s.outbound.Close()) - errors = E.Errors(errors, s.network.Close()) - errors = E.Errors(errors, s.router.Close()) - for serviceName, service := range s.preServices1 { - monitor.Start("close ", serviceName) - errors = E.Append(errors, service.Close(), func(err error) error { - return E.Cause(err, "close ", serviceName) - }) - monitor.Finish() - } - for serviceName, service := range s.preServices2 { - monitor.Start("close ", serviceName) - errors = E.Append(errors, service.Close(), func(err error) error { - return E.Cause(err, "close ", serviceName) + err := common.Close( + s.inbound, s.outbound, s.router, s.network, + ) + for _, lifecycleService := range s.services { + err = E.Append(err, lifecycleService.Close(), func(err error) error { + return E.Cause(err, "close ", lifecycleService.Name()) }) - monitor.Finish() } - if err := common.Close(s.logFactory); err != nil { - errors = E.Append(errors, err, func(err error) error { - return E.Cause(err, "close logger") - }) - } - return errors -} - -func (s *Box) Inbound() adapter.InboundManager { - return s.inbound -} - -func (s *Box) Outbound() adapter.OutboundManager { - return s.outbound + err = E.Append(err, s.logFactory.Close(), func(err error) error { + return E.Cause(err, "close logger") + }) + return err } func (s *Box) Network() adapter.NetworkManager { @@ -450,3 +402,11 @@ func (s *Box) Network() adapter.NetworkManager { func (s *Box) Router() adapter.Router { return s.router } + +func (s *Box) Inbound() adapter.InboundManager { + return s.inbound +} + +func (s *Box) Outbound() adapter.OutboundManager { + return s.outbound +} diff --git a/experimental/cachefile/cache.go b/experimental/cachefile/cache.go index 1027588fc..498b9474f 100644 --- a/experimental/cachefile/cache.go +++ b/experimental/cachefile/cache.go @@ -93,7 +93,18 @@ func New(ctx context.Context, options option.CacheFileOptions) *CacheFile { } } -func (c *CacheFile) start() error { +func (c *CacheFile) Name() string { + return "cache-file" +} + +func (c *CacheFile) Dependencies() []string { + return nil +} + +func (c *CacheFile) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateInitialize { + return nil + } const fileMode = 0o666 options := bbolt.Options{Timeout: time.Second} var ( @@ -151,14 +162,6 @@ func (c *CacheFile) start() error { return nil } -func (c *CacheFile) PreStart() error { - return c.start() -} - -func (c *CacheFile) Start() error { - return nil -} - func (c *CacheFile) Close() error { if c.DB == nil { return nil diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index ef08a4be1..b0f47646f 100644 --- a/experimental/clashapi/server.go +++ b/experimental/clashapi/server.go @@ -136,45 +136,50 @@ func NewServer(ctx context.Context, logFactory log.ObservableFactory, options op return s, nil } -func (s *Server) PreStart() error { - cacheFile := service.FromContext[adapter.CacheFile](s.ctx) - if cacheFile != nil { - mode := cacheFile.LoadMode() - if common.Any(s.modeList, func(it string) bool { - return strings.EqualFold(it, mode) - }) { - s.mode = mode - } - } - return nil +func (s *Server) Name() string { + return "clash server" } -func (s *Server) Start() error { - if s.externalController { - s.checkAndDownloadExternalUI() - var ( - listener net.Listener - err error - ) - for i := 0; i < 3; i++ { - listener, err = net.Listen("tcp", s.httpServer.Addr) - if runtime.GOOS == "android" && errors.Is(err, syscall.EADDRINUSE) { - time.Sleep(100 * time.Millisecond) - continue +func (s *Server) Start(stage adapter.StartStage) error { + switch stage { + case adapter.StartStateStart: + cacheFile := service.FromContext[adapter.CacheFile](s.ctx) + if cacheFile != nil { + mode := cacheFile.LoadMode() + if common.Any(s.modeList, func(it string) bool { + return strings.EqualFold(it, mode) + }) { + s.mode = mode } - break - } - if err != nil { - return E.Cause(err, "external controller listen error") } - s.logger.Info("restful api listening at ", listener.Addr()) - go func() { - err = s.httpServer.Serve(listener) - if err != nil && !errors.Is(err, http.ErrServerClosed) { - s.logger.Error("external controller serve error: ", err) + case adapter.StartStateStarted: + if s.externalController { + s.checkAndDownloadExternalUI() + var ( + listener net.Listener + err error + ) + for i := 0; i < 3; i++ { + listener, err = net.Listen("tcp", s.httpServer.Addr) + if runtime.GOOS == "android" && errors.Is(err, syscall.EADDRINUSE) { + time.Sleep(100 * time.Millisecond) + continue + } + break + } + if err != nil { + return E.Cause(err, "external controller listen error") } - }() + s.logger.Info("restful api listening at ", listener.Addr()) + go func() { + err = s.httpServer.Serve(listener) + if err != nil && !errors.Is(err, http.ErrServerClosed) { + s.logger.Error("external controller serve error: ", err) + } + }() + } } + return nil } @@ -236,14 +241,12 @@ func (s *Server) TrafficManager() *trafficontrol.Manager { return s.trafficManager } -func (s *Server) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule) (net.Conn, adapter.Tracker) { - tracker := trafficontrol.NewTCPTracker(conn, s.trafficManager, metadata, s.outboundManager, matchedRule) - return tracker, tracker +func (s *Server) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) net.Conn { + return trafficontrol.NewTCPTracker(conn, s.trafficManager, metadata, s.outboundManager, matchedRule, matchOutbound) } -func (s *Server) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule) (N.PacketConn, adapter.Tracker) { - tracker := trafficontrol.NewUDPTracker(conn, s.trafficManager, metadata, s.outboundManager, matchedRule) - return tracker, tracker +func (s *Server) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) N.PacketConn { + return trafficontrol.NewUDPTracker(conn, s.trafficManager, metadata, s.outboundManager, matchedRule, matchOutbound) } func authentication(serverSecret string) func(next http.Handler) http.Handler { diff --git a/experimental/clashapi/trafficontrol/tracker.go b/experimental/clashapi/trafficontrol/tracker.go index df5437faf..e324be206 100644 --- a/experimental/clashapi/trafficontrol/tracker.go +++ b/experimental/clashapi/trafficontrol/tracker.go @@ -5,7 +5,6 @@ import ( "time" "github.com/sagernet/sing-box/adapter" - R "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/atomic" "github.com/sagernet/sing/common/bufio" @@ -88,7 +87,6 @@ func (t TrackerMetadata) MarshalJSON() ([]byte, error) { } type Tracker interface { - adapter.Tracker Metadata() TrackerMetadata Close() error } @@ -108,10 +106,6 @@ func (tt *TCPConn) Close() error { return tt.ExtendedConn.Close() } -func (tt *TCPConn) Leave() { - tt.manager.Leave(tt) -} - func (tt *TCPConn) Upstream() any { return tt.ExtendedConn } @@ -124,7 +118,7 @@ func (tt *TCPConn) WriterReplaceable() bool { return true } -func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundContext, outboundManager adapter.OutboundManager, rule adapter.Rule) *TCPConn { +func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundContext, outboundManager adapter.OutboundManager, matchRule adapter.Rule, matchOutbound adapter.Outbound) *TCPConn { id, _ := uuid.NewV4() var ( chain []string @@ -132,12 +126,8 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundCont outbound string outboundType string ) - var action adapter.RuleAction - if rule != nil { - action = rule.Action() - } - if routeAction, isRouteAction := action.(*R.RuleActionRoute); isRouteAction { - next = routeAction.Outbound + if matchOutbound != nil { + next = matchOutbound.Tag() } else { next = outboundManager.Default().Tag() } @@ -172,7 +162,7 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundCont Upload: upload, Download: download, Chain: common.Reverse(chain), - Rule: rule, + Rule: matchRule, Outbound: outbound, OutboundType: outboundType, }, @@ -197,10 +187,6 @@ func (ut *UDPConn) Close() error { return ut.PacketConn.Close() } -func (ut *UDPConn) Leave() { - ut.manager.Leave(ut) -} - func (ut *UDPConn) Upstream() any { return ut.PacketConn } @@ -213,7 +199,7 @@ func (ut *UDPConn) WriterReplaceable() bool { return true } -func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.InboundContext, outboundManager adapter.OutboundManager, rule adapter.Rule) *UDPConn { +func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.InboundContext, outboundManager adapter.OutboundManager, matchRule adapter.Rule, matchOutbound adapter.Outbound) *UDPConn { id, _ := uuid.NewV4() var ( chain []string @@ -221,12 +207,8 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.Inbound outbound string outboundType string ) - var action adapter.RuleAction - if rule != nil { - action = rule.Action() - } - if routeAction, isRouteAction := action.(*R.RuleActionRoute); isRouteAction { - next = routeAction.Outbound + if matchOutbound != nil { + next = matchOutbound.Tag() } else { next = outboundManager.Default().Tag() } @@ -261,7 +243,7 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.Inbound Upload: upload, Download: download, Chain: common.Reverse(chain), - Rule: rule, + Rule: matchRule, Outbound: outbound, OutboundType: outboundType, }, diff --git a/experimental/libbox/command_clash_mode.go b/experimental/libbox/command_clash_mode.go index 1b6eb4708..af69047fd 100644 --- a/experimental/libbox/command_clash_mode.go +++ b/experimental/libbox/command_clash_mode.go @@ -38,11 +38,7 @@ func (s *CommandServer) handleSetClashMode(conn net.Conn) error { if service == nil { return writeError(conn, E.New("service not ready")) } - clashServer := service.instance.Router().ClashServer() - if clashServer == nil { - return writeError(conn, E.New("Clash API disabled")) - } - clashServer.(*clashapi.Server).SetMode(newMode) + service.clashServer.(*clashapi.Server).SetMode(newMode) return writeError(conn, nil) } @@ -69,18 +65,14 @@ func (s *CommandServer) handleModeConn(conn net.Conn) error { return ctx.Err() } } - clashServer := s.service.instance.Router().ClashServer() - if clashServer == nil { - return binary.Write(conn, binary.BigEndian, uint16(0)) - } - err := writeClashModeList(conn, clashServer) + err := writeClashModeList(conn, s.service.clashServer) if err != nil { return err } for { select { case <-s.modeUpdate: - err = varbin.Write(conn, binary.BigEndian, clashServer.Mode()) + err = varbin.Write(conn, binary.BigEndian, s.service.clashServer.Mode()) if err != nil { return err } diff --git a/experimental/libbox/command_close_connection.go b/experimental/libbox/command_close_connection.go index a2b05e561..46f7023fa 100644 --- a/experimental/libbox/command_close_connection.go +++ b/experimental/libbox/command_close_connection.go @@ -45,11 +45,7 @@ func (s *CommandServer) handleCloseConnection(conn net.Conn) error { if service == nil { return writeError(conn, E.New("service not ready")) } - clashServer := service.instance.Router().ClashServer() - if clashServer == nil { - return writeError(conn, E.New("Clash API disabled")) - } - targetConn := clashServer.(*clashapi.Server).TrafficManager().Connection(uuid.FromStringOrNil(connId)) + targetConn := service.clashServer.(*clashapi.Server).TrafficManager().Connection(uuid.FromStringOrNil(connId)) if targetConn == nil { return writeError(conn, E.New("connection already closed")) } diff --git a/experimental/libbox/command_connections.go b/experimental/libbox/command_connections.go index b51c73524..39d9303cb 100644 --- a/experimental/libbox/command_connections.go +++ b/experimental/libbox/command_connections.go @@ -49,11 +49,7 @@ func (s *CommandServer) handleConnectionsConn(conn net.Conn) error { for { service := s.service if service != nil { - clashServer := service.instance.Router().ClashServer() - if clashServer == nil { - return E.New("Clash API disabled") - } - trafficManager = clashServer.(*clashapi.Server).TrafficManager() + trafficManager = service.clashServer.(*clashapi.Server).TrafficManager() break } select { diff --git a/experimental/libbox/command_server.go b/experimental/libbox/command_server.go index 26b4aa79b..798a52bdd 100644 --- a/experimental/libbox/command_server.go +++ b/experimental/libbox/command_server.go @@ -60,7 +60,7 @@ func NewCommandServer(handler CommandServerHandler, maxLines int32) *CommandServ func (s *CommandServer) SetService(newService *BoxService) { if newService != nil { service.PtrFromContext[urltest.HistoryStorage](newService.ctx).SetHook(s.urlTestUpdate) - newService.instance.Router().ClashServer().(*clashapi.Server).SetModeUpdateHook(s.modeUpdate) + newService.clashServer.(*clashapi.Server).SetModeUpdateHook(s.modeUpdate) } s.service = newService s.notifyURLTestUpdate() diff --git a/experimental/libbox/command_status.go b/experimental/libbox/command_status.go index 4ab09d4b2..810b3dcef 100644 --- a/experimental/libbox/command_status.go +++ b/experimental/libbox/command_status.go @@ -31,13 +31,11 @@ func (s *CommandServer) readStatus() StatusMessage { message.ConnectionsOut = int32(conntrack.Count()) if s.service != nil { - if clashServer := s.service.instance.Router().ClashServer(); clashServer != nil { - message.TrafficAvailable = true - trafficManager := clashServer.(*clashapi.Server).TrafficManager() - message.Uplink, message.Downlink = trafficManager.Now() - message.UplinkTotal, message.DownlinkTotal = trafficManager.Total() - message.ConnectionsIn = int32(trafficManager.ConnectionsLen()) - } + message.TrafficAvailable = true + trafficManager := s.service.clashServer.(*clashapi.Server).TrafficManager() + message.Uplink, message.Downlink = trafficManager.Now() + message.UplinkTotal, message.DownlinkTotal = trafficManager.Total() + message.ConnectionsIn = int32(trafficManager.ConnectionsLen()) } return message diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index d1c2b4d2a..26bbbec54 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -34,17 +34,18 @@ import ( type BoxService struct { ctx context.Context cancel context.CancelFunc + urlTestHistoryStorage *urltest.HistoryStorage instance *box.Box + clashServer adapter.ClashServer pauseManager pause.Manager - urlTestHistoryStorage *urltest.HistoryStorage servicePauseFields } func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) { ctx := box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry()) - ctx = service.ContextWith[deprecated.Manager](ctx, new(deprecatedManager)) ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID) + service.MustRegister[deprecated.Manager](ctx, new(deprecatedManager)) options, err := parseConfig(ctx, configContent) if err != nil { return nil, err @@ -54,7 +55,7 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box urlTestHistoryStorage := urltest.NewHistoryStorage() ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage) platformWrapper := &platformInterfaceWrapper{iif: platformInterface, useProcFS: platformInterface.UseProcFS()} - ctx = service.ContextWith[platform.Interface](ctx, platformWrapper) + service.MustRegister[platform.Interface](ctx, platformWrapper) instance, err := box.New(box.Options{ Context: ctx, Options: options, @@ -71,6 +72,7 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box instance: instance, urlTestHistoryStorage: urlTestHistoryStorage, pauseManager: service.FromContext[pause.Manager](ctx), + clashServer: service.FromContext[adapter.ClashServer](ctx), }, nil } diff --git a/experimental/v2rayapi/server.go b/experimental/v2rayapi/server.go index 8b4b43855..8ebae1c4f 100644 --- a/experimental/v2rayapi/server.go +++ b/experimental/v2rayapi/server.go @@ -44,7 +44,14 @@ func NewServer(logger log.Logger, options option.V2RayAPIOptions) (adapter.V2Ray return server, nil } -func (s *Server) Start() error { +func (s *Server) Name() string { + return "v2ray server" +} + +func (s *Server) Start(stage adapter.StartStage) error { + if stage != adapter.StartStatePostStart { + return nil + } listener, err := net.Listen("tcp", s.listen) if err != nil { return err @@ -70,6 +77,6 @@ func (s *Server) Close() error { ) } -func (s *Server) StatsService() adapter.V2RayStatsService { +func (s *Server) StatsService() adapter.ConnectionTracker { return s.statsService } diff --git a/experimental/v2rayapi/stats.go b/experimental/v2rayapi/stats.go index 38b9a301f..6c44518fd 100644 --- a/experimental/v2rayapi/stats.go +++ b/experimental/v2rayapi/stats.go @@ -22,7 +22,7 @@ func init() { } var ( - _ adapter.V2RayStatsService = (*StatsService)(nil) + _ adapter.ConnectionTracker = (*StatsService)(nil) _ StatsServiceServer = (*StatsService)(nil) ) @@ -60,7 +60,10 @@ func NewStatsService(options option.V2RayStatsServiceOptions) *StatsService { } } -func (s *StatsService) RoutedConnection(inbound string, outbound string, user string, conn net.Conn) net.Conn { +func (s *StatsService) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) net.Conn { + inbound := metadata.Inbound + user := metadata.User + outbound := matchOutbound.Tag() var readCounter []*atomic.Int64 var writeCounter []*atomic.Int64 countInbound := inbound != "" && s.inbounds[inbound] @@ -86,7 +89,10 @@ func (s *StatsService) RoutedConnection(inbound string, outbound string, user st return bufio.NewInt64CounterConn(conn, readCounter, writeCounter) } -func (s *StatsService) RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn { +func (s *StatsService) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) N.PacketConn { + inbound := metadata.Inbound + user := metadata.User + outbound := matchOutbound.Tag() var readCounter []*atomic.Int64 var writeCounter []*atomic.Int64 countInbound := inbound != "" && s.inbounds[inbound] diff --git a/protocol/group/urltest.go b/protocol/group/urltest.go index 4d76a31c6..f1a84b504 100644 --- a/protocol/group/urltest.go +++ b/protocol/group/urltest.go @@ -76,18 +76,7 @@ func (s *URLTest) Start() error { } outbounds = append(outbounds, detour) } - group, err := NewURLTestGroup( - s.ctx, - s.router, - s.outboundManager, - s.logger, - outbounds, - s.link, - s.interval, - s.tolerance, - s.idleTimeout, - s.interruptExternalConnections, - ) + group, err := NewURLTestGroup(s.ctx, s.outboundManager, s.logger, outbounds, s.link, s.interval, s.tolerance, s.idleTimeout, s.interruptExternalConnections) if err != nil { return err } @@ -215,18 +204,7 @@ type URLTestGroup struct { lastActive atomic.TypedValue[time.Time] } -func NewURLTestGroup( - ctx context.Context, - router adapter.Router, - outboundManager adapter.OutboundManager, - logger log.Logger, - outbounds []adapter.Outbound, - link string, - interval time.Duration, - tolerance uint16, - idleTimeout time.Duration, - interruptExternalConnections bool, -) (*URLTestGroup, error) { +func NewURLTestGroup(ctx context.Context, outboundManager adapter.OutboundManager, logger log.Logger, outbounds []adapter.Outbound, link string, interval time.Duration, tolerance uint16, idleTimeout time.Duration, interruptExternalConnections bool) (*URLTestGroup, error) { if interval == 0 { interval = C.DefaultURLTestInterval } @@ -241,14 +219,13 @@ func NewURLTestGroup( } var history *urltest.HistoryStorage if history = service.PtrFromContext[urltest.HistoryStorage](ctx); history != nil { - } else if clashServer := router.ClashServer(); clashServer != nil { + } else if clashServer := service.FromContext[adapter.ClashServer](ctx); clashServer != nil { history = clashServer.HistoryStorage() } else { history = urltest.NewHistoryStorage() } return &URLTestGroup{ ctx: ctx, - router: router, outboundManager: outboundManager, logger: logger, outbounds: outbounds, diff --git a/route/route.go b/route/route.go index 3897e4c6d..d44c516ee 100644 --- a/route/route.go +++ b/route/route.go @@ -91,16 +91,12 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad if err != nil { return err } - var ( - // selectedOutbound adapter.Outbound - selectedDialer N.Dialer - selectedTag string - selectedDescription string - ) + var selectedOutbound adapter.Outbound if selectedRule != nil { switch action := selectedRule.Action().(type) { case *rule.RuleActionRoute: - selectedOutbound, loaded := r.outboundManager.Outbound(action.Outbound) + var loaded bool + selectedOutbound, loaded = r.outboundManager.Outbound(action.Outbound) if !loaded { buf.ReleaseMulti(buffers) return E.New("outbound not found: ", action.Outbound) @@ -109,12 +105,6 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad buf.ReleaseMulti(buffers) return E.New("TCP is not supported by outbound: ", selectedOutbound.Tag()) } - selectedDialer = selectedOutbound - selectedTag = selectedOutbound.Tag() - selectedDescription = F.ToString("outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]") - case *rule.RuleActionDirect: - selectedDialer = action.Dialer - selectedDescription = action.String() case *rule.RuleActionReject: buf.ReleaseMulti(buffers) N.CloseOnHandshakeFailure(conn, onClose, action.Error(ctx)) @@ -133,25 +123,16 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad buf.ReleaseMulti(buffers) return E.New("TCP is not supported by default outbound: ", defaultOutbound.Tag()) } - selectedDialer = defaultOutbound - selectedTag = defaultOutbound.Tag() - selectedDescription = F.ToString("outbound/", defaultOutbound.Type(), "[", defaultOutbound.Tag(), "]") + selectedOutbound = defaultOutbound } for _, buffer := range buffers { conn = bufio.NewCachedConn(conn, buffer) } - if r.clashServer != nil { - trackerConn, tracker := r.clashServer.RoutedConnection(ctx, conn, metadata, selectedRule) - defer tracker.Leave() - conn = trackerConn + if r.tracker != nil { + conn = r.tracker.RoutedConnection(ctx, conn, metadata, selectedRule, selectedOutbound) } - if r.v2rayServer != nil { - if statsService := r.v2rayServer.StatsService(); statsService != nil { - conn = statsService.RoutedConnection(metadata.Inbound, selectedTag, metadata.User, conn) - } - } - legacyOutbound, isLegacy := selectedDialer.(adapter.ConnectionHandler) + legacyOutbound, isLegacy := selectedOutbound.(adapter.ConnectionHandler) if isLegacy { err = legacyOutbound.NewConnection(ctx, conn, metadata) if err != nil { @@ -159,7 +140,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad if onClose != nil { onClose(err) } - return E.Cause(err, selectedDescription) + return E.Cause(err, F.ToString("outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]")) } else { if onClose != nil { onClose(nil) @@ -168,13 +149,13 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad return nil } // TODO - err = outbound.NewConnection(ctx, selectedDialer, conn, metadata) + err = outbound.NewConnection(ctx, selectedOutbound, conn, metadata) if err != nil { conn.Close() if onClose != nil { onClose(err) } - return E.Cause(err, selectedDescription) + return E.Cause(err, F.ToString("outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]")) } else { if onClose != nil { onClose(nil) @@ -246,16 +227,13 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m if err != nil { return err } - var ( - selectedDialer N.Dialer - selectedTag string - selectedDescription string - ) + var selectedOutbound adapter.Outbound var selectReturn bool if selectedRule != nil { switch action := selectedRule.Action().(type) { case *rule.RuleActionRoute: - selectedOutbound, loaded := r.outboundManager.Outbound(action.Outbound) + var loaded bool + selectedOutbound, loaded = r.outboundManager.Outbound(action.Outbound) if !loaded { N.ReleaseMultiPacketBuffer(packetBuffers) return E.New("outbound not found: ", action.Outbound) @@ -264,12 +242,6 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m N.ReleaseMultiPacketBuffer(packetBuffers) return E.New("UDP is not supported by outbound: ", selectedOutbound.Tag()) } - selectedDialer = selectedOutbound - selectedTag = selectedOutbound.Tag() - selectedDescription = F.ToString("outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]") - case *rule.RuleActionDirect: - selectedDialer = action.Dialer - selectedDescription = action.String() case *rule.RuleActionReject: N.ReleaseMultiPacketBuffer(packetBuffers) N.CloseOnHandshakeFailure(conn, onClose, action.Error(ctx)) @@ -285,41 +257,32 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m N.ReleaseMultiPacketBuffer(packetBuffers) return E.New("UDP is not supported by outbound: ", defaultOutbound.Tag()) } - selectedDialer = defaultOutbound - selectedTag = defaultOutbound.Tag() - selectedDescription = F.ToString("outbound/", defaultOutbound.Type(), "[", defaultOutbound.Tag(), "]") + selectedOutbound = defaultOutbound } for _, buffer := range packetBuffers { conn = bufio.NewCachedPacketConn(conn, buffer.Buffer, buffer.Destination) N.PutPacketBuffer(buffer) } - if r.clashServer != nil { - trackerConn, tracker := r.clashServer.RoutedPacketConnection(ctx, conn, metadata, selectedRule) - defer tracker.Leave() - conn = trackerConn - } - if r.v2rayServer != nil { - if statsService := r.v2rayServer.StatsService(); statsService != nil { - conn = statsService.RoutedPacketConnection(metadata.Inbound, selectedTag, metadata.User, conn) - } + if r.tracker != nil { + conn = r.tracker.RoutedPacketConnection(ctx, conn, metadata, selectedRule, selectedOutbound) } if metadata.FakeIP { conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, metadata.Destination) } - legacyOutbound, isLegacy := selectedDialer.(adapter.PacketConnectionHandler) + legacyOutbound, isLegacy := selectedOutbound.(adapter.PacketConnectionHandler) if isLegacy { err = legacyOutbound.NewPacketConnection(ctx, conn, metadata) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { - return E.Cause(err, selectedDescription) + return E.Cause(err, F.ToString("outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]")) } return nil } // TODO - err = outbound.NewPacketConnection(ctx, selectedDialer, conn, metadata) + err = outbound.NewPacketConnection(ctx, selectedOutbound, conn, metadata) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { - return E.Cause(err, selectedDescription) + return E.Cause(err, F.ToString("outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]")) } return nil } diff --git a/route/router.go b/route/router.go index 62b374473..1f760a86a 100644 --- a/route/router.go +++ b/route/router.go @@ -27,7 +27,6 @@ import ( F "github.com/sagernet/sing/common/format" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/ntp" "github.com/sagernet/sing/common/task" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/pause" @@ -63,16 +62,14 @@ type Router struct { dnsReverseMapping *DNSReverseMapping fakeIPStore adapter.FakeIPStore processSearcher process.Searcher - timeService *ntp.Service pauseManager pause.Manager - clashServer adapter.ClashServer - v2rayServer adapter.V2RayServer + tracker adapter.ConnectionTracker platformInterface platform.Interface needWIFIState bool started bool } -func NewRouter(ctx context.Context, logFactory log.Factory, options option.RouteOptions, dnsOptions option.DNSOptions, ntpOptions option.NTPOptions) (*Router, error) { +func NewRouter(ctx context.Context, logFactory log.Factory, options option.RouteOptions, dnsOptions option.DNSOptions) (*Router, error) { router := &Router{ ctx: ctx, logger: logFactory.NewLogger("router"), @@ -94,7 +91,7 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.Route platformInterface: service.FromContext[platform.Interface](ctx), needWIFIState: hasRule(options.Rules, isWIFIRule) || hasDNSRule(dnsOptions.Rules, isWIFIDNSRule), } - ctx = service.ContextWith[adapter.Router](ctx, router) + service.MustRegister[adapter.Router](ctx, router) router.dnsClient = dns.NewClient(dns.ClientOptions{ DisableCache: dnsOptions.DNSClientOptions.DisableCache, DisableExpire: dnsOptions.DNSClientOptions.DisableExpire, @@ -290,23 +287,6 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.Route } router.fakeIPStore = fakeip.NewStore(ctx, router.logger, inet4Range, inet6Range) } - - if ntpOptions.Enabled { - ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions) - if err != nil { - return nil, E.Cause(err, "create NTP service") - } - timeService := ntp.NewService(ntp.Options{ - Context: ctx, - Dialer: ntpDialer, - Logger: logFactory.NewLogger("ntp"), - Server: ntpOptions.ServerOptions.Build(), - Interval: time.Duration(ntpOptions.Interval), - WriteToSystem: ntpOptions.WriteToSystem, - }) - service.MustRegister[ntp.TimeService](ctx, timeService) - router.timeService = timeService - } return router, nil } @@ -380,14 +360,6 @@ func (r *Router) Start(stage adapter.StartStage) error { return E.Cause(err, "initialize DNS server[", i, "]") } } - if r.timeService != nil { - monitor.Start("initialize time service") - err := r.timeService.Start() - monitor.Finish() - if err != nil { - return E.Cause(err, "initialize time service") - } - } case adapter.StartStatePostStart: var cacheContext *adapter.HTTPStartContext if len(r.ruleSets) > 0 { @@ -502,13 +474,6 @@ func (r *Router) Close() error { }) monitor.Finish() } - if r.timeService != nil { - monitor.Start("close time service") - err = E.Append(err, r.timeService.Close(), func(err error) error { - return E.Cause(err, "close time service") - }) - monitor.Finish() - } if r.fakeIPStore != nil { monitor.Start("close fakeip store") err = E.Append(err, r.fakeIPStore.Close(), func(err error) error { @@ -536,29 +501,8 @@ func (r *Router) Rules() []adapter.Rule { return r.rules } -func (r *Router) ClashServer() adapter.ClashServer { - return r.clashServer -} - -func (r *Router) SetClashServer(server adapter.ClashServer) { - r.clashServer = server -} - -func (r *Router) V2RayServer() adapter.V2RayServer { - return r.v2rayServer -} - -func (r *Router) SetV2RayServer(server adapter.V2RayServer) { - r.v2rayServer = server -} - -func (r *Router) NewError(ctx context.Context, err error) { - common.Close(err) - if E.IsClosedOrCanceled(err) { - r.logger.DebugContext(ctx, "connection closed: ", err) - return - } - r.logger.ErrorContext(ctx, err) +func (r *Router) SetTracker(tracker adapter.ConnectionTracker) { + r.tracker = tracker } func (r *Router) ResetNetwork() { diff --git a/route/rule/rule_default.go b/route/rule/rule_default.go index 33a8e16c3..d1076e3d9 100644 --- a/route/rule/rule_default.go +++ b/route/rule/rule_default.go @@ -219,7 +219,8 @@ func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options optio rule.allItems = append(rule.allItems, item) } if options.ClashMode != "" { - item := NewClashModeItem(router, options.ClashMode) + clashServer := service.FromContext[adapter.ClashServer](ctx) + item := NewClashModeItem(clashServer, options.ClashMode) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } diff --git a/route/rule/rule_dns.go b/route/rule/rule_dns.go index df5f3f335..70e1417a5 100644 --- a/route/rule/rule_dns.go +++ b/route/rule/rule_dns.go @@ -216,7 +216,8 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op rule.allItems = append(rule.allItems, item) } if options.ClashMode != "" { - item := NewClashModeItem(router, options.ClashMode) + clashServer := service.FromContext[adapter.ClashServer](ctx) + item := NewClashModeItem(clashServer, options.ClashMode) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } diff --git a/route/rule/rule_item_clash_mode.go b/route/rule/rule_item_clash_mode.go index aa5126cbd..4f9bc7eab 100644 --- a/route/rule/rule_item_clash_mode.go +++ b/route/rule/rule_item_clash_mode.go @@ -9,23 +9,22 @@ import ( var _ RuleItem = (*ClashModeItem)(nil) type ClashModeItem struct { - router adapter.Router - mode string + clashServer adapter.ClashServer + mode string } -func NewClashModeItem(router adapter.Router, mode string) *ClashModeItem { +func NewClashModeItem(clashServer adapter.ClashServer, mode string) *ClashModeItem { return &ClashModeItem{ - router: router, - mode: mode, + clashServer: clashServer, + mode: mode, } } func (r *ClashModeItem) Match(metadata *adapter.InboundContext) bool { - clashServer := r.router.ClashServer() - if clashServer == nil { + if r.clashServer == nil { return false } - return strings.EqualFold(clashServer.Mode(), r.mode) + return strings.EqualFold(r.clashServer.Mode(), r.mode) } func (r *ClashModeItem) String() string { From 7daf2d171613e0454bbcd3593114dfa51f946e06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 10 Nov 2024 16:52:11 +0800 Subject: [PATCH 18/39] Fix hijack-dns --- route/dns.go | 1 - 1 file changed, 1 deletion(-) diff --git a/route/dns.go b/route/dns.go index a0c376c2f..5fb8b47f8 100644 --- a/route/dns.go +++ b/route/dns.go @@ -75,7 +75,6 @@ func exchangeDNSPacket(ctx context.Context, router *Router, conn N.PacketConn, b return err } err = conn.WritePacket(responseBuffer, destination) - responseBuffer.Release() return err } From e3ffffc645a05f619af9ff84187f771e25698be6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 10 Nov 2024 16:47:19 +0800 Subject: [PATCH 19/39] documentation: Bump version --- docs/changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index 66b374b57..55892e871 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,7 +2,7 @@ icon: material/alert-decagram --- -#### 1.11.0-alpha.10 +#### 1.11.0-alpha.11 * Fixes and improvements From 1edb80adcc2a37ae09dbbef715da552cfec8b73c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 11 Nov 2024 16:04:27 +0800 Subject: [PATCH 20/39] Fix start stage --- adapter/inbound/manager.go | 4 +-- adapter/lifecycle.go | 26 +++++++++++++-- adapter/lifecycle_legacy.go | 2 +- adapter/outbound/manager.go | 4 +-- box.go | 64 +++++++++++++++---------------------- go.mod | 8 +++-- go.sum | 13 ++++---- 7 files changed, 67 insertions(+), 54 deletions(-) diff --git a/adapter/inbound/manager.go b/adapter/inbound/manager.go index 69a3ad46b..d2be0f365 100644 --- a/adapter/inbound/manager.go +++ b/adapter/inbound/manager.go @@ -44,7 +44,7 @@ func (m *Manager) Start(stage adapter.StartStage) error { for _, inbound := range m.inbounds { err := adapter.LegacyStart(inbound, stage) if err != nil { - return E.Cause(err, stage.Action(), " inbound/", inbound.Type(), "[", inbound.Tag(), "]") + return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]") } } return nil @@ -118,7 +118,7 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log. for _, stage := range adapter.ListStartStages { err = adapter.LegacyStart(inbound, stage) if err != nil { - return E.Cause(err, stage.Action(), " inbound/", inbound.Type(), "[", inbound.Tag(), "]") + return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]") } } } diff --git a/adapter/lifecycle.go b/adapter/lifecycle.go index 7a1639dc4..aff9fadba 100644 --- a/adapter/lifecycle.go +++ b/adapter/lifecycle.go @@ -1,5 +1,7 @@ package adapter +import E "github.com/sagernet/sing/common/exceptions" + type StartStage uint8 const ( @@ -16,7 +18,7 @@ var ListStartStages = []StartStage{ StartStateStarted, } -func (s StartStage) Action() string { +func (s StartStage) String() string { switch s { case StartStateInitialize: return "initialize" @@ -25,7 +27,7 @@ func (s StartStage) Action() string { case StartStatePostStart: return "post-start" case StartStateStarted: - return "start-after-started" + return "finish-start" default: panic("unknown stage") } @@ -40,3 +42,23 @@ type LifecycleService interface { Name() string Lifecycle } + +func Start(stage StartStage, services ...Lifecycle) error { + for _, service := range services { + err := service.Start(stage) + if err != nil { + return err + } + } + return nil +} + +func StartNamed(stage StartStage, services []LifecycleService) error { + for _, service := range services { + err := service.Start(stage) + if err != nil { + return E.Cause(err, stage.String(), " ", service.Name()) + } + } + return nil +} diff --git a/adapter/lifecycle_legacy.go b/adapter/lifecycle_legacy.go index f73f92340..0c8c75dae 100644 --- a/adapter/lifecycle_legacy.go +++ b/adapter/lifecycle_legacy.go @@ -14,7 +14,7 @@ func LegacyStart(starter any, stage StartStage) error { }); isStarter { return starter.Start() } - case StartStatePostStart: + case StartStateStarted: if postStarter, isPostStarter := starter.(interface { PostStart() error }); isPostStarter { diff --git a/adapter/outbound/manager.go b/adapter/outbound/manager.go index 10a89a1c9..84a105c59 100644 --- a/adapter/outbound/manager.go +++ b/adapter/outbound/manager.go @@ -61,7 +61,7 @@ func (m *Manager) Start(stage adapter.StartStage) error { for _, outbound := range outbounds { err := adapter.LegacyStart(outbound, stage) if err != nil { - return E.Cause(err, stage.Action(), " outbound/", outbound.Type(), "[", outbound.Tag(), "]") + return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]") } } } @@ -234,7 +234,7 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log. for _, stage := range adapter.ListStartStages { err = adapter.LegacyStart(outbound, stage) if err != nil { - return E.Cause(err, stage.Action(), " outbound/", outbound.Type(), "[", outbound.Tag(), "]") + return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]") } } } diff --git a/box.go b/box.go index 6205a7d91..d9abe15af 100644 --- a/box.go +++ b/box.go @@ -315,27 +315,17 @@ func (s *Box) preStart() error { if err != nil { return E.Cause(err, "start logger") } - for _, lifecycleService := range s.services { - err = lifecycleService.Start(adapter.StartStateInitialize) // cache-file - if err != nil { - return E.Cause(err, "initialize ", lifecycleService.Name()) - } + err = adapter.StartNamed(adapter.StartStateInitialize, s.services) // cache-file clash-api v2ray-api + if err != nil { + return err } - for _, lifecycle := range []adapter.Lifecycle{ - s.network, s.router, s.outbound, s.inbound, - } { - err = lifecycle.Start(adapter.StartStateInitialize) - if err != nil { - return err - } + err = adapter.Start(adapter.StartStateInitialize, s.network, s.router, s.outbound, s.inbound) + if err != nil { + return err } - for _, lifecycle := range []adapter.Lifecycle{ - s.outbound, s.network, s.router, - } { - err = lifecycle.Start(adapter.StartStateStart) - if err != nil { - return err - } + err = adapter.Start(adapter.StartStateStart, s.outbound, s.network, s.router) + if err != nil { + return err } return nil } @@ -345,31 +335,29 @@ func (s *Box) start() error { if err != nil { return err } - for _, lifecycleService := range s.services { - err = lifecycleService.Start(adapter.StartStateStart) - if err != nil { - return E.Cause(err, "initialize ", lifecycleService.Name()) - } + err = adapter.StartNamed(adapter.StartStateStart, s.services) + if err != nil { + return err } err = s.inbound.Start(adapter.StartStateStart) if err != nil { return err } - for _, lifecycleService := range []adapter.Lifecycle{ - s.outbound, s.network, s.router, s.inbound, - } { - err = lifecycleService.Start(adapter.StartStatePostStart) - if err != nil { - return err - } + err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.router, s.inbound) + if err != nil { + return err } - for _, lifecycleService := range []adapter.Lifecycle{ - s.network, s.router, s.outbound, s.inbound, - } { - err = lifecycleService.Start(adapter.StartStateStarted) - if err != nil { - return err - } + err = adapter.StartNamed(adapter.StartStatePostStart, s.services) + if err != nil { + return err + } + err = adapter.Start(adapter.StartStateStarted, s.network, s.router, s.outbound, s.inbound) + if err != nil { + return err + } + err = adapter.StartNamed(adapter.StartStateStarted, s.services) + if err != nil { + return err } return nil } diff --git a/go.mod b/go.mod index bf88f1d33..0a2f7b0c8 100644 --- a/go.mod +++ b/go.mod @@ -25,14 +25,14 @@ require ( github.com/sagernet/gvisor v0.0.0-20241021032506-a4324256e4a3 github.com/sagernet/quic-go v0.48.1-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.6.0-alpha.3 + github.com/sagernet/sing v0.6.0-alpha.4 github.com/sagernet/sing-dns v0.4.0-alpha.1 github.com/sagernet/sing-mux v0.3.0-alpha.1 github.com/sagernet/sing-quic v0.3.0-rc.2 github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.1.4 - github.com/sagernet/sing-tun v0.6.0-alpha.3 + github.com/sagernet/sing-tun v0.6.0-alpha.4 github.com/sagernet/sing-vmess v0.1.12 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/utls v1.6.7 @@ -53,7 +53,9 @@ require ( howett.net/plist v1.0.1 ) -//replace github.com/sagernet/sing => ../sing +replace github.com/sagernet/sing => ../sing + +replace github.com/sagernet/sing-tun => ../sing-tun require ( github.com/ajg/form v1.5.1 // indirect diff --git a/go.sum b/go.sum index 3b789c9b4..637097528 100644 --- a/go.sum +++ b/go.sum @@ -109,9 +109,6 @@ github.com/sagernet/quic-go v0.48.1-beta.1 h1:ElPaV5yzlXIKZpqFMAcUGax6vddi3zt4AE github.com/sagernet/quic-go v0.48.1-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/+or9YMLaG5VeTk4k= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= -github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= -github.com/sagernet/sing v0.6.0-alpha.3 h1:GLp9d6Gbt+Ioeplauuzojz1nY2J6moceVGYIOv/h5gA= -github.com/sagernet/sing v0.6.0-alpha.3/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-dns v0.4.0-alpha.1 h1:2KlP8DeqtGkULFiZtvG2r7SuoJP6orANFzJwC5vDKvg= github.com/sagernet/sing-dns v0.4.0-alpha.1/go.mod h1:vgHATsm4wdymwpvBZPei8RY+546iGXS6hlWv2x6YKcM= github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg= @@ -124,8 +121,6 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= -github.com/sagernet/sing-tun v0.6.0-alpha.3 h1:KddmFF9ZYC1n+HZzpAZ1StMC5v38ir6hH0PJ7uGpAGE= -github.com/sagernet/sing-tun v0.6.0-alpha.3/go.mod h1:R/UaKB1oFIvAeMIH2btQCaDVsfCNomEjfYBSCSA94sw= github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg= github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= @@ -141,8 +136,14 @@ github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3k github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA= @@ -181,7 +182,7 @@ golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= From b6c940af612b48bb6551e44f1ec0a4fe8e9acdf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 11 Nov 2024 16:06:56 +0800 Subject: [PATCH 21/39] Fix match rules --- route/route.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/route/route.go b/route/route.go index d44c516ee..0e3f8916f 100644 --- a/route/route.go +++ b/route/route.go @@ -467,7 +467,12 @@ match: selectedRuleIndex = currentRuleIndex break match } - ruleIndex = currentRuleIndex + if ruleIndex == -1 { + ruleIndex = currentRuleIndex + } else { + ruleIndex += currentRuleIndex + } + ruleIndex++ } if !preMatch && metadata.Destination.Addr.IsUnspecified() { newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{}, inputConn, inputPacketConn) From 412701d4c505bd0a22065f6f673aba2d36067f5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 11 Nov 2024 16:23:45 +0800 Subject: [PATCH 22/39] refactor: Platform Interfaces --- adapter/network.go | 19 ++ adapter/router.go | 9 - common/settings/proxy_darwin.go | 7 +- constant/network.go | 8 + constant/os.go | 2 +- experimental/libbox/config.go | 18 +- experimental/libbox/link_flags_stub.go | 2 +- ...link_flags_linux.go => link_flags_unix.go} | 2 + experimental/libbox/monitor.go | 155 ++++----------- experimental/libbox/platform.go | 16 +- experimental/libbox/platform/interface.go | 5 +- experimental/libbox/service.go | 55 ++++-- go.mod | 6 +- go.sum | 13 +- protocol/direct/loopback_detect.go | 6 +- route/network.go | 179 +++++++++++++----- transport/dhcp/server.go | 21 +- 17 files changed, 267 insertions(+), 256 deletions(-) create mode 100644 constant/network.go rename experimental/libbox/{link_flags_linux.go => link_flags_unix.go} (97%) diff --git a/adapter/network.go b/adapter/network.go index 533bfced2..6c09a0a3e 100644 --- a/adapter/network.go +++ b/adapter/network.go @@ -9,6 +9,8 @@ type NetworkManager interface { Lifecycle InterfaceFinder() control.InterfaceFinder UpdateInterfaces() error + DefaultNetworkInterface() *NetworkInterface + NetworkInterfaces() []NetworkInterface DefaultInterface() string AutoDetectInterface() bool AutoDetectInterfaceFunc() control.Func @@ -21,3 +23,20 @@ type NetworkManager interface { WIFIState() WIFIState ResetNetwork() } + +type InterfaceUpdateListener interface { + InterfaceUpdated() +} + +type WIFIState struct { + SSID string + BSSID string +} + +type NetworkInterface struct { + control.Interface + Type string + DNSServers []string + Expensive bool + Constrained bool +} diff --git a/adapter/router.go b/adapter/router.go index 6dd39357b..a637e5068 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -119,12 +119,3 @@ func (c *HTTPStartContext) Close() { client.CloseIdleConnections() } } - -type InterfaceUpdateListener interface { - InterfaceUpdated() -} - -type WIFIState struct { - SSID string - BSSID string -} diff --git a/common/settings/proxy_darwin.go b/common/settings/proxy_darwin.go index 17e82cf59..3c06a8536 100644 --- a/common/settings/proxy_darwin.go +++ b/common/settings/proxy_darwin.go @@ -2,7 +2,6 @@ package settings import ( "context" - "net/netip" "strconv" "strings" @@ -77,14 +76,14 @@ func (p *DarwinSystemProxy) update(event int) { } func (p *DarwinSystemProxy) update0() error { - newInterfaceName := p.monitor.DefaultInterfaceName(netip.IPv4Unspecified()) - if p.interfaceName == newInterfaceName { + newInterface := p.monitor.DefaultInterface() + if p.interfaceName == newInterface.Name { return nil } if p.interfaceName != "" { _ = p.Disable() } - p.interfaceName = newInterfaceName + p.interfaceName = newInterface.Name interfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName) if err != nil { return err diff --git a/constant/network.go b/constant/network.go new file mode 100644 index 000000000..f5ac2a4e4 --- /dev/null +++ b/constant/network.go @@ -0,0 +1,8 @@ +package constant + +const ( + InterfaceTypeWIFI = "wifi" + InterfaceTypeCellular = "cellular" + InterfaceTypeEthernet = "ethernet" + InterfaceTypeOther = "other" +) diff --git a/constant/os.go b/constant/os.go index 63c6c41cd..6142767c9 100644 --- a/constant/os.go +++ b/constant/os.go @@ -6,7 +6,7 @@ import ( const IsAndroid = goos.IsAndroid == 1 -const IsDarwin = goos.IsDarwin == 1 +const IsDarwin = goos.IsDarwin == 1 || goos.IsIos == 1 const IsDragonfly = goos.IsDragonfly == 1 diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index a102a3d89..cce45c604 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -74,11 +74,7 @@ func (s *platformInterfaceStub) CreateDefaultInterfaceMonitor(logger logger.Logg return (*interfaceMonitorStub)(nil) } -func (s *platformInterfaceStub) UsePlatformInterfaceGetter() bool { - return true -} - -func (s *platformInterfaceStub) Interfaces() ([]control.Interface, error) { +func (s *platformInterfaceStub) Interfaces() ([]adapter.NetworkInterface, error) { return nil, os.ErrInvalid } @@ -111,16 +107,8 @@ func (s *interfaceMonitorStub) Close() error { return os.ErrInvalid } -func (s *interfaceMonitorStub) DefaultInterfaceName(destination netip.Addr) string { - return "" -} - -func (s *interfaceMonitorStub) DefaultInterfaceIndex(destination netip.Addr) int { - return -1 -} - -func (s *interfaceMonitorStub) DefaultInterface(destination netip.Addr) (string, int) { - return "", -1 +func (s *interfaceMonitorStub) DefaultInterface() *control.Interface { + return nil } func (s *interfaceMonitorStub) OverrideAndroidVPN() bool { diff --git a/experimental/libbox/link_flags_stub.go b/experimental/libbox/link_flags_stub.go index 306754f3f..ce3d1eb80 100644 --- a/experimental/libbox/link_flags_stub.go +++ b/experimental/libbox/link_flags_stub.go @@ -1,4 +1,4 @@ -//go:build !linux +//go:build !unix package libbox diff --git a/experimental/libbox/link_flags_linux.go b/experimental/libbox/link_flags_unix.go similarity index 97% rename from experimental/libbox/link_flags_linux.go rename to experimental/libbox/link_flags_unix.go index aa9ad46d2..04f41d641 100644 --- a/experimental/libbox/link_flags_linux.go +++ b/experimental/libbox/link_flags_unix.go @@ -1,3 +1,5 @@ +//go:build unix + package libbox import ( diff --git a/experimental/libbox/monitor.go b/experimental/libbox/monitor.go index 18c8b0092..8f4019715 100644 --- a/experimental/libbox/monitor.go +++ b/experimental/libbox/monitor.go @@ -1,15 +1,10 @@ package libbox import ( - "net" - "net/netip" - "sync" - "github.com/sagernet/sing-tun" - "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" - M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/common/x/list" ) @@ -20,19 +15,9 @@ var ( type platformDefaultInterfaceMonitor struct { *platformInterfaceWrapper - networkAddresses []networkAddress - defaultInterfaceName string - defaultInterfaceIndex int - element *list.Element[tun.NetworkUpdateCallback] - access sync.Mutex - callbacks list.List[tun.DefaultInterfaceUpdateCallback] - logger logger.Logger -} - -type networkAddress struct { - interfaceName string - interfaceIndex int - addresses []netip.Prefix + element *list.Element[tun.NetworkUpdateCallback] + callbacks list.List[tun.DefaultInterfaceUpdateCallback] + logger logger.Logger } func (m *platformDefaultInterfaceMonitor) Start() error { @@ -43,37 +28,10 @@ func (m *platformDefaultInterfaceMonitor) Close() error { return m.iif.CloseDefaultInterfaceMonitor(m) } -func (m *platformDefaultInterfaceMonitor) DefaultInterfaceName(destination netip.Addr) string { - for _, address := range m.networkAddresses { - for _, prefix := range address.addresses { - if prefix.Contains(destination) { - return address.interfaceName - } - } - } - return m.defaultInterfaceName -} - -func (m *platformDefaultInterfaceMonitor) DefaultInterfaceIndex(destination netip.Addr) int { - for _, address := range m.networkAddresses { - for _, prefix := range address.addresses { - if prefix.Contains(destination) { - return address.interfaceIndex - } - } - } - return m.defaultInterfaceIndex -} - -func (m *platformDefaultInterfaceMonitor) DefaultInterface(destination netip.Addr) (string, int) { - for _, address := range m.networkAddresses { - for _, prefix := range address.addresses { - if prefix.Contains(destination) { - return address.interfaceName, address.interfaceIndex - } - } - } - return m.defaultInterfaceName, m.defaultInterfaceIndex +func (m *platformDefaultInterfaceMonitor) DefaultInterface() *control.Interface { + m.defaultInterfaceAccess.Lock() + defer m.defaultInterfaceAccess.Unlock() + return m.defaultInterface } func (m *platformDefaultInterfaceMonitor) OverrideAndroidVPN() bool { @@ -85,96 +43,49 @@ func (m *platformDefaultInterfaceMonitor) AndroidVPNEnabled() bool { } func (m *platformDefaultInterfaceMonitor) RegisterCallback(callback tun.DefaultInterfaceUpdateCallback) *list.Element[tun.DefaultInterfaceUpdateCallback] { - m.access.Lock() - defer m.access.Unlock() + m.defaultInterfaceAccess.Lock() + defer m.defaultInterfaceAccess.Unlock() return m.callbacks.PushBack(callback) } func (m *platformDefaultInterfaceMonitor) UnregisterCallback(element *list.Element[tun.DefaultInterfaceUpdateCallback]) { - m.access.Lock() - defer m.access.Unlock() + m.defaultInterfaceAccess.Lock() + defer m.defaultInterfaceAccess.Unlock() m.callbacks.Remove(element) } -func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName string, interfaceIndex32 int32) { - if interfaceName == "" || interfaceIndex32 == -1 { - m.defaultInterfaceName = "" - m.defaultInterfaceIndex = -1 - m.access.Lock() +func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName string, interfaceIndex32 int32, isExpensive bool, isConstrained bool) { + m.isExpensive = isExpensive + m.isConstrained = isConstrained + err := m.networkManager.UpdateInterfaces() + if err != nil { + m.logger.Error(E.Cause(err, "update interfaces")) + } + m.defaultInterfaceAccess.Lock() + if interfaceIndex32 == -1 { + m.defaultInterface = nil callbacks := m.callbacks.Array() - m.access.Unlock() + m.defaultInterfaceAccess.Unlock() for _, callback := range callbacks { - callback(tun.EventNoRoute) + callback(tun.EventInterfaceUpdate) } return } - var err error - if m.iif.UsePlatformInterfaceGetter() { - err = m.updateInterfacesPlatform() - } else { - err = m.updateInterfaces() - } - if err == nil { - err = m.networkManager.UpdateInterfaces() - } + oldInterface := m.defaultInterface + newInterface, err := m.networkManager.InterfaceFinder().ByIndex(int(interfaceIndex32)) if err != nil { - m.logger.Error(E.Cause(err, "update interfaces")) + m.defaultInterfaceAccess.Unlock() + m.logger.Error(E.Cause(err, "find updated interface: ", interfaceName)) + return } - interfaceIndex := int(interfaceIndex32) - if m.defaultInterfaceName == interfaceName && m.defaultInterfaceIndex == interfaceIndex { + m.defaultInterface = newInterface + if oldInterface != nil && oldInterface.Name == m.defaultInterface.Name && oldInterface.Index == m.defaultInterface.Index { + m.defaultInterfaceAccess.Unlock() return } - m.defaultInterfaceName = interfaceName - m.defaultInterfaceIndex = interfaceIndex - m.access.Lock() callbacks := m.callbacks.Array() - m.access.Unlock() + m.defaultInterfaceAccess.Unlock() for _, callback := range callbacks { callback(tun.EventInterfaceUpdate) } } - -func (m *platformDefaultInterfaceMonitor) updateInterfaces() error { - interfaces, err := net.Interfaces() - if err != nil { - return err - } - var addresses []networkAddress - for _, iif := range interfaces { - var netAddresses []net.Addr - netAddresses, err = iif.Addrs() - if err != nil { - return err - } - var address networkAddress - address.interfaceName = iif.Name - address.interfaceIndex = iif.Index - address.addresses = common.Map(common.FilterIsInstance(netAddresses, func(it net.Addr) (*net.IPNet, bool) { - value, loaded := it.(*net.IPNet) - return value, loaded - }), func(it *net.IPNet) netip.Prefix { - bits, _ := it.Mask.Size() - return netip.PrefixFrom(M.AddrFromIP(it.IP), bits) - }) - addresses = append(addresses, address) - } - m.networkAddresses = addresses - return nil -} - -func (m *platformDefaultInterfaceMonitor) updateInterfacesPlatform() error { - interfaces, err := m.Interfaces() - if err != nil { - return err - } - var addresses []networkAddress - for _, iif := range interfaces { - var address networkAddress - address.interfaceName = iif.Name - address.interfaceIndex = iif.Index - // address.addresses = common.Map(iif.Addresses, netip.MustParsePrefix) - addresses = append(addresses, address) - } - m.networkAddresses = addresses - return nil -} diff --git a/experimental/libbox/platform.go b/experimental/libbox/platform.go index 1976ad70f..a61243458 100644 --- a/experimental/libbox/platform.go +++ b/experimental/libbox/platform.go @@ -1,6 +1,7 @@ package libbox import ( + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" ) @@ -13,10 +14,8 @@ type PlatformInterface interface { FindConnectionOwner(ipProtocol int32, sourceAddress string, sourcePort int32, destinationAddress string, destinationPort int32) (int32, error) PackageNameByUid(uid int32) (string, error) UIDByPackageName(packageName string) (int32, error) - UsePlatformDefaultInterfaceMonitor() bool StartDefaultInterfaceMonitor(listener InterfaceUpdateListener) error CloseDefaultInterfaceMonitor(listener InterfaceUpdateListener) error - UsePlatformInterfaceGetter() bool GetInterfaces() (NetworkInterfaceIterator, error) UnderNetworkExtension() bool IncludeAllNetworks() bool @@ -31,15 +30,26 @@ type TunInterface interface { } type InterfaceUpdateListener interface { - UpdateDefaultInterface(interfaceName string, interfaceIndex int32) + UpdateDefaultInterface(interfaceName string, interfaceIndex int32, isExpensive bool, isConstrained bool) } +const ( + InterfaceTypeWIFI = C.InterfaceTypeWIFI + InterfaceTypeCellular = C.InterfaceTypeCellular + InterfaceTypeEthernet = C.InterfaceTypeEthernet + InterfaceTypeOther = C.InterfaceTypeOther +) + type NetworkInterface struct { Index int32 MTU int32 Name string Addresses StringIterator Flags int32 + + Type string + DNSServer StringIterator + Metered bool } type WIFIState struct { diff --git a/experimental/libbox/platform/interface.go b/experimental/libbox/platform/interface.go index 16eab5abc..ef37daea2 100644 --- a/experimental/libbox/platform/interface.go +++ b/experimental/libbox/platform/interface.go @@ -5,7 +5,6 @@ import ( "github.com/sagernet/sing-box/common/process" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-tun" - "github.com/sagernet/sing/common/control" "github.com/sagernet/sing/common/logger" ) @@ -14,10 +13,8 @@ type Interface interface { UsePlatformAutoDetectInterfaceControl() bool AutoDetectInterfaceControl(fd int) error OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) - UsePlatformDefaultInterfaceMonitor() bool CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor - UsePlatformInterfaceGetter() bool - Interfaces() ([]control.Interface, error) + Interfaces() ([]adapter.NetworkInterface, error) UnderNetworkExtension() bool IncludeAllNetworks() bool ClearDNSCache() diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 26bbbec54..93274b1f2 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -6,6 +6,7 @@ import ( "os" "runtime" runtimeDebug "runtime/debug" + "sync" "syscall" "time" @@ -54,7 +55,10 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box ctx, cancel := context.WithCancel(ctx) urlTestHistoryStorage := urltest.NewHistoryStorage() ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage) - platformWrapper := &platformInterfaceWrapper{iif: platformInterface, useProcFS: platformInterface.UseProcFS()} + platformWrapper := &platformInterfaceWrapper{ + iif: platformInterface, + useProcFS: platformInterface.UseProcFS(), + } service.MustRegister[platform.Interface](ctx, platformWrapper) instance, err := box.New(box.Options{ Context: ctx, @@ -106,9 +110,14 @@ var ( ) type platformInterfaceWrapper struct { - iif PlatformInterface - useProcFS bool - networkManager adapter.NetworkManager + iif PlatformInterface + useProcFS bool + networkManager adapter.NetworkManager + myTunName string + defaultInterfaceAccess sync.Mutex + defaultInterface *control.Interface + isExpensive bool + isConstrained bool } func (w *platformInterfaceWrapper) Initialize(networkManager adapter.NetworkManager) error { @@ -148,38 +157,42 @@ func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions return nil, E.Cause(err, "dup tun file descriptor") } options.FileDescriptor = dupFd + w.myTunName = options.Name return tun.New(*options) } -func (w *platformInterfaceWrapper) UsePlatformDefaultInterfaceMonitor() bool { - return w.iif.UsePlatformDefaultInterfaceMonitor() -} - func (w *platformInterfaceWrapper) CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor { return &platformDefaultInterfaceMonitor{ platformInterfaceWrapper: w, - defaultInterfaceIndex: -1, logger: logger, } } -func (w *platformInterfaceWrapper) UsePlatformInterfaceGetter() bool { - return w.iif.UsePlatformInterfaceGetter() -} - -func (w *platformInterfaceWrapper) Interfaces() ([]control.Interface, error) { +func (w *platformInterfaceWrapper) Interfaces() ([]adapter.NetworkInterface, error) { interfaceIterator, err := w.iif.GetInterfaces() if err != nil { return nil, err } - var interfaces []control.Interface + var interfaces []adapter.NetworkInterface for _, netInterface := range iteratorToArray[*NetworkInterface](interfaceIterator) { - interfaces = append(interfaces, control.Interface{ - Index: int(netInterface.Index), - MTU: int(netInterface.MTU), - Name: netInterface.Name, - Addresses: common.Map(iteratorToArray[string](netInterface.Addresses), netip.MustParsePrefix), - Flags: linkFlags(uint32(netInterface.Flags)), + if netInterface.Name == w.myTunName { + continue + } + w.defaultInterfaceAccess.Lock() + isDefault := w.defaultInterface != nil && int(netInterface.Index) == w.defaultInterface.Index + w.defaultInterfaceAccess.Unlock() + interfaces = append(interfaces, adapter.NetworkInterface{ + Interface: control.Interface{ + Index: int(netInterface.Index), + MTU: int(netInterface.MTU), + Name: netInterface.Name, + Addresses: common.Map(iteratorToArray[string](netInterface.Addresses), netip.MustParsePrefix), + Flags: linkFlags(uint32(netInterface.Flags)), + }, + Type: netInterface.Type, + DNSServers: iteratorToArray[string](netInterface.DNSServer), + Expensive: netInterface.Metered || isDefault && w.isExpensive, + Constrained: isDefault && w.isConstrained, }) } return interfaces, nil diff --git a/go.mod b/go.mod index 0a2f7b0c8..aaa1b0649 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.1.4 - github.com/sagernet/sing-tun v0.6.0-alpha.4 + github.com/sagernet/sing-tun v0.6.0-alpha.5 github.com/sagernet/sing-vmess v0.1.12 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/utls v1.6.7 @@ -53,10 +53,6 @@ require ( howett.net/plist v1.0.1 ) -replace github.com/sagernet/sing => ../sing - -replace github.com/sagernet/sing-tun => ../sing-tun - require ( github.com/ajg/form v1.5.1 // indirect github.com/andybalholm/brotli v1.0.6 // indirect diff --git a/go.sum b/go.sum index 637097528..44f61a99b 100644 --- a/go.sum +++ b/go.sum @@ -109,6 +109,9 @@ github.com/sagernet/quic-go v0.48.1-beta.1 h1:ElPaV5yzlXIKZpqFMAcUGax6vddi3zt4AE github.com/sagernet/quic-go v0.48.1-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/+or9YMLaG5VeTk4k= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= +github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= +github.com/sagernet/sing v0.6.0-alpha.4 h1:h9oshzhaY0ESPC9HERcXtT9MhK7Oyo/IWXVu1uIiw3Y= +github.com/sagernet/sing v0.6.0-alpha.4/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-dns v0.4.0-alpha.1 h1:2KlP8DeqtGkULFiZtvG2r7SuoJP6orANFzJwC5vDKvg= github.com/sagernet/sing-dns v0.4.0-alpha.1/go.mod h1:vgHATsm4wdymwpvBZPei8RY+546iGXS6hlWv2x6YKcM= github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg= @@ -121,6 +124,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= +github.com/sagernet/sing-tun v0.6.0-alpha.5 h1:N2m+tzpVgeDxsQzc3tq3Bge2uSRhJ72KXy4Zr03U+Cg= +github.com/sagernet/sing-tun v0.6.0-alpha.5/go.mod h1:5FKJNou4ZfW3HhLoSpRRUc8RT+nsdFTvhJc+4MlBrOo= github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg= github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= @@ -136,14 +141,8 @@ github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3k github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA= @@ -182,7 +181,7 @@ golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/protocol/direct/loopback_detect.go b/protocol/direct/loopback_detect.go index 4bc1be3b0..7a62164ef 100644 --- a/protocol/direct/loopback_detect.go +++ b/protocol/direct/loopback_detect.go @@ -33,7 +33,7 @@ func (l *loopBackDetector) NewConn(conn net.Conn) net.Conn { } if udpConn, isUDPConn := conn.(abstractUDPConn); isUDPConn { if !source.Addr().IsLoopback() { - _, err := l.networkManager.InterfaceFinder().InterfaceByAddr(source.Addr()) + _, err := l.networkManager.InterfaceFinder().ByAddr(source.Addr()) if err != nil { return conn } @@ -59,7 +59,7 @@ func (l *loopBackDetector) NewPacketConn(conn N.NetPacketConn, destination M.Soc return conn } if !source.Addr().IsLoopback() { - _, err := l.networkManager.InterfaceFinder().InterfaceByAddr(source.Addr()) + _, err := l.networkManager.InterfaceFinder().ByAddr(source.Addr()) if err != nil { return conn } @@ -82,7 +82,7 @@ func (l *loopBackDetector) CheckPacketConn(source netip.AddrPort, local netip.Ad return false } if !source.Addr().IsLoopback() { - _, err := l.networkManager.InterfaceFinder().InterfaceByAddr(source.Addr()) + _, err := l.networkManager.InterfaceFinder().ByAddr(source.Addr()) if err != nil { return false } diff --git a/route/network.go b/route/network.go index b0caa24c1..c6253b91e 100644 --- a/route/network.go +++ b/route/network.go @@ -3,9 +3,10 @@ package route import ( "context" "errors" - "net/netip" + "net" "os" "runtime" + "strings" "syscall" "github.com/sagernet/sing-box/adapter" @@ -15,33 +16,41 @@ import ( "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/atomic" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/common/winpowrprof" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/pause" + + "golang.org/x/exp/slices" ) var _ adapter.NetworkManager = (*NetworkManager)(nil) type NetworkManager struct { - logger logger.ContextLogger - interfaceFinder *control.DefaultInterfaceFinder + logger logger.ContextLogger + interfaceFinder *control.DefaultInterfaceFinder + networkInterfaces atomic.TypedValue[[]adapter.NetworkInterface] + autoDetectInterface bool defaultInterface string defaultMark uint32 autoRedirectOutputMark uint32 - networkMonitor tun.NetworkUpdateMonitor - interfaceMonitor tun.DefaultInterfaceMonitor - packageManager tun.PackageManager - powerListener winpowrprof.EventListener - pauseManager pause.Manager - platformInterface platform.Interface - outboundManager adapter.OutboundManager - wifiState adapter.WIFIState - started bool + + networkMonitor tun.NetworkUpdateMonitor + interfaceMonitor tun.DefaultInterfaceMonitor + packageManager tun.PackageManager + powerListener winpowrprof.EventListener + pauseManager pause.Manager + platformInterface platform.Interface + outboundManager adapter.OutboundManager + wifiState adapter.WIFIState + started bool } func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOptions option.RouteOptions) (*NetworkManager, error) { @@ -55,7 +64,7 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOp platformInterface: service.FromContext[platform.Interface](ctx), outboundManager: service.FromContext[adapter.OutboundManager](ctx), } - usePlatformDefaultInterfaceMonitor := nm.platformInterface != nil && nm.platformInterface.UsePlatformDefaultInterfaceMonitor() + usePlatformDefaultInterfaceMonitor := nm.platformInterface != nil enforceInterfaceMonitor := routeOptions.AutoDetectInterface if !usePlatformDefaultInterfaceMonitor { networkMonitor, err := tun.NewNetworkUpdateMonitor(logger) @@ -90,17 +99,17 @@ func (r *NetworkManager) Start(stage adapter.StartStage) error { monitor := taskmonitor.New(r.logger, C.StartTimeout) switch stage { case adapter.StartStateInitialize: - if r.interfaceMonitor != nil { - monitor.Start("initialize interface monitor") - err := r.interfaceMonitor.Start() + if r.networkMonitor != nil { + monitor.Start("initialize network monitor") + err := r.networkMonitor.Start() monitor.Finish() if err != nil { return err } } - if r.networkMonitor != nil { - monitor.Start("initialize network monitor") - err := r.networkMonitor.Start() + if r.interfaceMonitor != nil { + monitor.Start("initialize interface monitor") + err := r.interfaceMonitor.Start() monitor.Finish() if err != nil { return err @@ -151,20 +160,6 @@ func (r *NetworkManager) Start(stage adapter.StartStage) error { func (r *NetworkManager) Close() error { monitor := taskmonitor.New(r.logger, C.StopTimeout) var err error - if r.interfaceMonitor != nil { - monitor.Start("close interface monitor") - err = E.Append(err, r.interfaceMonitor.Close(), func(err error) error { - return E.Cause(err, "close interface monitor") - }) - monitor.Finish() - } - if r.networkMonitor != nil { - monitor.Start("close network monitor") - err = E.Append(err, r.networkMonitor.Close(), func(err error) error { - return E.Cause(err, "close network monitor") - }) - monitor.Finish() - } if r.packageManager != nil { monitor.Start("close package manager") err = E.Append(err, r.packageManager.Close(), func(err error) error { @@ -179,6 +174,20 @@ func (r *NetworkManager) Close() error { }) monitor.Finish() } + if r.interfaceMonitor != nil { + monitor.Start("close interface monitor") + err = E.Append(err, r.interfaceMonitor.Close(), func(err error) error { + return E.Cause(err, "close interface monitor") + }) + monitor.Finish() + } + if r.networkMonitor != nil { + monitor.Start("close network monitor") + err = E.Append(err, r.networkMonitor.Close(), func(err error) error { + return E.Cause(err, "close network monitor") + }) + monitor.Finish() + } return nil } @@ -187,18 +196,75 @@ func (r *NetworkManager) InterfaceFinder() control.InterfaceFinder { } func (r *NetworkManager) UpdateInterfaces() error { - if r.platformInterface == nil || !r.platformInterface.UsePlatformInterfaceGetter() { + if r.platformInterface == nil { return r.interfaceFinder.Update() } else { interfaces, err := r.platformInterface.Interfaces() if err != nil { return err } - r.interfaceFinder.UpdateInterfaces(interfaces) + if C.IsDarwin { + err = r.interfaceFinder.Update() + if err != nil { + return err + } + // NEInterface only provides name,index and type + interfaces = common.Map(interfaces, func(it adapter.NetworkInterface) adapter.NetworkInterface { + iif, _ := r.interfaceFinder.ByIndex(it.Index) + if iif != nil { + it.Interface = *iif + } + return it + }) + } else { + r.interfaceFinder.UpdateInterfaces(common.Map(interfaces, func(it adapter.NetworkInterface) control.Interface { return it.Interface })) + } + oldInterfaces := r.networkInterfaces.Load() + newInterfaces := common.Filter(interfaces, func(it adapter.NetworkInterface) bool { + return it.Flags&net.FlagUp != 0 + }) + r.networkInterfaces.Store(newInterfaces) + if !slices.EqualFunc(oldInterfaces, newInterfaces, func(oldInterface adapter.NetworkInterface, newInterface adapter.NetworkInterface) bool { + return oldInterface.Interface.Index == newInterface.Interface.Index && + oldInterface.Interface.Name == newInterface.Interface.Name && + oldInterface.Interface.Flags == newInterface.Interface.Flags && + oldInterface.Type == newInterface.Type && + oldInterface.Expensive == newInterface.Expensive && + oldInterface.Constrained == newInterface.Constrained + }) { + r.logger.Info("updated available networks: ", strings.Join(common.Map(newInterfaces, func(it adapter.NetworkInterface) string { + var options []string + options = append(options, F.ToString(it.Type)) + if it.Expensive { + options = append(options, "expensive") + } + if it.Constrained { + options = append(options, "constrained") + } + return F.ToString(it.Name, " (", strings.Join(options, ", "), ")") + }), ", ")) + } return nil } } +func (r *NetworkManager) DefaultNetworkInterface() *adapter.NetworkInterface { + iif := r.interfaceMonitor.DefaultInterface() + if iif == nil { + return nil + } + for _, it := range r.networkInterfaces.Load() { + if it.Interface.Index == iif.Index { + return &it + } + } + return &adapter.NetworkInterface{Interface: *iif} +} + +func (r *NetworkManager) NetworkInterfaces() []adapter.NetworkInterface { + return r.networkInterfaces.Load() +} + func (r *NetworkManager) DefaultInterface() string { return r.defaultInterface } @@ -220,18 +286,17 @@ func (r *NetworkManager) AutoDetectInterfaceFunc() control.Func { } return control.BindToInterfaceFunc(r.interfaceFinder, func(network string, address string) (interfaceName string, interfaceIndex int, err error) { remoteAddr := M.ParseSocksaddr(address).Addr - if C.IsLinux { - interfaceName, interfaceIndex = r.interfaceMonitor.DefaultInterface(remoteAddr) - if interfaceIndex == -1 { - err = tun.ErrNoRoute - } - } else { - interfaceIndex = r.interfaceMonitor.DefaultInterfaceIndex(remoteAddr) - if interfaceIndex == -1 { - err = tun.ErrNoRoute + if remoteAddr.IsValid() { + iif, err := r.interfaceFinder.ByAddr(remoteAddr) + if err == nil { + return iif.Name, iif.Index, nil } } - return + defaultInterface := r.interfaceMonitor.DefaultInterface() + if defaultInterface == nil { + return "", -1, tun.ErrNoRoute + } + return defaultInterface.Name, defaultInterface.Index, nil }) } } @@ -285,6 +350,12 @@ func (r *NetworkManager) notifyNetworkUpdate(event int) { r.logger.Error("missing default interface") } else { r.pauseManager.NetworkWake() + defaultInterface := r.DefaultNetworkInterface() + if defaultInterface == nil { + panic("invalid interface context") + } + var options []string + options = append(options, F.ToString("index ", defaultInterface.Index)) if C.IsAndroid && r.platformInterface == nil { var vpnStatus string if r.interfaceMonitor.AndroidVPNEnabled() { @@ -292,17 +363,24 @@ func (r *NetworkManager) notifyNetworkUpdate(event int) { } else { vpnStatus = "disabled" } - r.logger.Info("updated default interface ", r.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", r.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified()), ", vpn ", vpnStatus) + options = append(options, "vpn "+vpnStatus) } else { - r.logger.Info("updated default interface ", r.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", r.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified())) + if defaultInterface.Type != "" { + options = append(options, F.ToString("type ", defaultInterface.Type)) + } + if defaultInterface.Expensive { + options = append(options, "expensive") + } + if defaultInterface.Constrained { + options = append(options, "constrained") + } } + r.logger.Info("updated default interface ", defaultInterface.Name, ", ", strings.Join(options, ", ")) if r.platformInterface != nil { state := r.platformInterface.ReadWIFIState() if state != r.wifiState { r.wifiState = state - if state.SSID == "" && state.BSSID == "" { - r.logger.Info("updated WIFI state: disconnected") - } else { + if state.SSID != "" { r.logger.Info("updated WIFI state: SSID=", state.SSID, ", BSSID=", state.BSSID) } } @@ -312,7 +390,6 @@ func (r *NetworkManager) notifyNetworkUpdate(event int) { if !r.started { return } - r.ResetNetwork() } diff --git a/transport/dhcp/server.go b/transport/dhcp/server.go index dfe33d86c..9a06ac171 100644 --- a/transport/dhcp/server.go +++ b/transport/dhcp/server.go @@ -119,18 +119,19 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, return nil, err } -func (t *Transport) fetchInterface() (*net.Interface, error) { - interfaceName := t.interfaceName +func (t *Transport) fetchInterface() (*control.Interface, error) { if t.autoInterface { if t.networkManager.InterfaceMonitor() == nil { return nil, E.New("missing monitor for auto DHCP, set route.auto_detect_interface") } - interfaceName = t.networkManager.InterfaceMonitor().DefaultInterfaceName(netip.Addr{}) - } - if interfaceName == "" { - return nil, E.New("missing default interface") + defaultInterface := t.networkManager.InterfaceMonitor().DefaultInterface() + if defaultInterface == nil { + return nil, E.New("missing default interface") + } + return defaultInterface, nil + } else { + return t.networkManager.InterfaceFinder().ByName(t.interfaceName) } - return net.InterfaceByName(interfaceName) } func (t *Transport) fetchServers() error { @@ -172,7 +173,7 @@ func (t *Transport) interfaceUpdated(int) { } } -func (t *Transport) fetchServers0(ctx context.Context, iface *net.Interface) error { +func (t *Transport) fetchServers0(ctx context.Context, iface *control.Interface) error { var listener net.ListenConfig listener.Control = control.Append(listener.Control, control.BindToInterface(t.networkManager.InterfaceFinder(), iface.Name, iface.Index)) listener.Control = control.Append(listener.Control, control.ReuseAddr()) @@ -206,7 +207,7 @@ func (t *Transport) fetchServers0(ctx context.Context, iface *net.Interface) err return group.Run(ctx) } -func (t *Transport) fetchServersResponse(iface *net.Interface, packetConn net.PacketConn, transactionID dhcpv4.TransactionID) error { +func (t *Transport) fetchServersResponse(iface *control.Interface, packetConn net.PacketConn, transactionID dhcpv4.TransactionID) error { buffer := buf.NewSize(dhcpv4.MaxMessageSize) defer buffer.Release() @@ -246,7 +247,7 @@ func (t *Transport) fetchServersResponse(iface *net.Interface, packetConn net.Pa } } -func (t *Transport) recreateServers(iface *net.Interface, serverAddrs []netip.Addr) error { +func (t *Transport) recreateServers(iface *control.Interface, serverAddrs []netip.Addr) error { if len(serverAddrs) > 0 { t.options.Logger.Info("dhcp: updated DNS servers from ", iface.Name, ": [", strings.Join(common.Map(serverAddrs, func(it netip.Addr) string { return it.String() From 9988144868bd9789317dcb8bffed68bfdf8219c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 11 Nov 2024 16:27:28 +0800 Subject: [PATCH 23/39] Fix decompile rule-set --- cmd/sing-box/cmd_rule_set_compile.go | 7 +---- cmd/sing-box/cmd_rule_set_convert.go | 3 +- cmd/sing-box/cmd_rule_set_decompile.go | 8 +---- cmd/sing-box/cmd_rule_set_match.go | 15 +++++---- common/srs/binary.go | 42 ++++++++++++-------------- constant/rule.go | 1 + route/rule/rule_set_local.go | 15 ++++----- route/rule/rule_set_remote.go | 17 +++++------ 8 files changed, 48 insertions(+), 60 deletions(-) diff --git a/cmd/sing-box/cmd_rule_set_compile.go b/cmd/sing-box/cmd_rule_set_compile.go index 4fae4d99e..0c44a2a1e 100644 --- a/cmd/sing-box/cmd_rule_set_compile.go +++ b/cmd/sing-box/cmd_rule_set_compile.go @@ -6,7 +6,6 @@ import ( "strings" "github.com/sagernet/sing-box/common/srs" - C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common/json" @@ -56,10 +55,6 @@ func compileRuleSet(sourcePath string) error { if err != nil { return err } - ruleSet, err := plainRuleSet.Upgrade() - if err != nil { - return err - } var outputPath string if flagRuleSetCompileOutput == flagRuleSetCompileDefaultOutput { if strings.HasSuffix(sourcePath, ".json") { @@ -74,7 +69,7 @@ func compileRuleSet(sourcePath string) error { if err != nil { return err } - err = srs.Write(outputFile, ruleSet, plainRuleSet.Version == C.RuleSetVersion2) + err = srs.Write(outputFile, plainRuleSet.Options, plainRuleSet.Version) if err != nil { outputFile.Close() os.Remove(outputPath) diff --git a/cmd/sing-box/cmd_rule_set_convert.go b/cmd/sing-box/cmd_rule_set_convert.go index 8d2092ea2..320149cf0 100644 --- a/cmd/sing-box/cmd_rule_set_convert.go +++ b/cmd/sing-box/cmd_rule_set_convert.go @@ -7,6 +7,7 @@ import ( "github.com/sagernet/sing-box/cmd/sing-box/internal/convertor/adguard" "github.com/sagernet/sing-box/common/srs" + 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" @@ -77,7 +78,7 @@ func convertRuleSet(sourcePath string) error { return err } defer outputFile.Close() - err = srs.Write(outputFile, option.PlainRuleSet{Rules: rules}, true) + err = srs.Write(outputFile, option.PlainRuleSet{Rules: rules}, C.RuleSetVersion2) if err != nil { outputFile.Close() os.Remove(outputPath) diff --git a/cmd/sing-box/cmd_rule_set_decompile.go b/cmd/sing-box/cmd_rule_set_decompile.go index 02af03dd6..10a67bcf1 100644 --- a/cmd/sing-box/cmd_rule_set_decompile.go +++ b/cmd/sing-box/cmd_rule_set_decompile.go @@ -6,9 +6,7 @@ import ( "strings" "github.com/sagernet/sing-box/common/srs" - C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common/json" "github.com/spf13/cobra" @@ -48,14 +46,10 @@ func decompileRuleSet(sourcePath string) error { return err } } - plainRuleSet, err := srs.Read(reader, true) + ruleSet, err := srs.Read(reader, true) if err != nil { return err } - ruleSet := option.PlainRuleSetCompat{ - Version: C.RuleSetVersion1, - Options: plainRuleSet, - } var outputPath string if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput { if strings.HasSuffix(sourcePath, ".srs") { diff --git a/cmd/sing-box/cmd_rule_set_match.go b/cmd/sing-box/cmd_rule_set_match.go index 7a3a04231..f5016117d 100644 --- a/cmd/sing-box/cmd_rule_set_match.go +++ b/cmd/sing-box/cmd_rule_set_match.go @@ -56,26 +56,25 @@ func ruleSetMatch(sourcePath string, domain string) error { if err != nil { return E.Cause(err, "read rule-set") } - var plainRuleSet option.PlainRuleSet + var ruleSet option.PlainRuleSetCompat switch flagRuleSetMatchFormat { case C.RuleSetFormatSource: - var compat option.PlainRuleSetCompat - compat, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content) - if err != nil { - return err - } - plainRuleSet, err = compat.Upgrade() + ruleSet, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content) if err != nil { return err } case C.RuleSetFormatBinary: - plainRuleSet, err = srs.Read(bytes.NewReader(content), false) + ruleSet, err = srs.Read(bytes.NewReader(content), false) if err != nil { return err } default: return E.New("unknown rule-set format: ", flagRuleSetMatchFormat) } + plainRuleSet, err := ruleSet.Upgrade() + if err != nil { + return err + } ipAddress := M.ParseAddr(domain) var metadata adapter.InboundContext if ipAddress.IsValid() { diff --git a/common/srs/binary.go b/common/srs/binary.go index 489dc22e2..dd670fc83 100644 --- a/common/srs/binary.go +++ b/common/srs/binary.go @@ -41,7 +41,7 @@ const ( ruleItemFinal uint8 = 0xFF ) -func Read(reader io.Reader, recover bool) (ruleSet option.PlainRuleSet, err error) { +func Read(reader io.Reader, recover bool) (ruleSetCompat option.PlainRuleSetCompat, err error) { var magicBytes [3]byte _, err = io.ReadFull(reader, magicBytes[:]) if err != nil { @@ -54,10 +54,10 @@ func Read(reader io.Reader, recover bool) (ruleSet option.PlainRuleSet, err erro var version uint8 err = binary.Read(reader, binary.BigEndian, &version) if err != nil { - return ruleSet, err + return ruleSetCompat, err } - if version > C.RuleSetVersion2 { - return ruleSet, E.New("unsupported version: ", version) + if version > C.RuleSetVersionCurrent { + return ruleSetCompat, E.New("unsupported version: ", version) } compressReader, err := zlib.NewReader(reader) if err != nil { @@ -68,9 +68,10 @@ func Read(reader io.Reader, recover bool) (ruleSet option.PlainRuleSet, err erro if err != nil { return } - ruleSet.Rules = make([]option.HeadlessRule, length) + ruleSetCompat.Version = version + ruleSetCompat.Options.Rules = make([]option.HeadlessRule, length) for i := uint64(0); i < length; i++ { - ruleSet.Rules[i], err = readRule(bReader, recover) + ruleSetCompat.Options.Rules[i], err = readRule(bReader, recover) if err != nil { err = E.Cause(err, "read rule[", i, "]") return @@ -79,18 +80,12 @@ func Read(reader io.Reader, recover bool) (ruleSet option.PlainRuleSet, err erro return } -func Write(writer io.Writer, ruleSet option.PlainRuleSet, generateUnstable bool) error { +func Write(writer io.Writer, ruleSet option.PlainRuleSet, generateVersion uint8) error { _, err := writer.Write(MagicBytes[:]) if err != nil { return err } - var version uint8 - if generateUnstable { - version = C.RuleSetVersion2 - } else { - version = C.RuleSetVersion1 - } - err = binary.Write(writer, binary.BigEndian, version) + err = binary.Write(writer, binary.BigEndian, generateVersion) if err != nil { return err } @@ -104,7 +99,7 @@ func Write(writer io.Writer, ruleSet option.PlainRuleSet, generateUnstable bool) return err } for _, rule := range ruleSet.Rules { - err = writeRule(bWriter, rule, generateUnstable) + err = writeRule(bWriter, rule, generateVersion) if err != nil { return err } @@ -135,12 +130,12 @@ func readRule(reader varbin.Reader, recover bool) (rule option.HeadlessRule, err return } -func writeRule(writer varbin.Writer, rule option.HeadlessRule, generateUnstable bool) error { +func writeRule(writer varbin.Writer, rule option.HeadlessRule, generateVersion uint8) error { switch rule.Type { case C.RuleTypeDefault: - return writeDefaultRule(writer, rule.DefaultOptions, generateUnstable) + return writeDefaultRule(writer, rule.DefaultOptions, generateVersion) case C.RuleTypeLogical: - return writeLogicalRule(writer, rule.LogicalOptions, generateUnstable) + return writeLogicalRule(writer, rule.LogicalOptions, generateVersion) default: panic("unknown rule type: " + rule.Type) } @@ -240,7 +235,7 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea } } -func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, generateUnstable bool) error { +func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, generateVersion uint8) error { err := binary.Write(writer, binary.BigEndian, uint8(0)) if err != nil { return err @@ -264,7 +259,7 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen if err != nil { return err } - err = domain.NewMatcher(rule.Domain, rule.DomainSuffix, !generateUnstable).Write(writer) + err = domain.NewMatcher(rule.Domain, rule.DomainSuffix, generateVersion == C.RuleSetVersion1).Write(writer) if err != nil { return err } @@ -354,6 +349,9 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen } } if len(rule.AdGuardDomain) > 0 { + if generateVersion < C.RuleSetVersion2 { + return E.New("AdGuard rule items is only supported in version 2 or later") + } err = binary.Write(writer, binary.BigEndian, ruleItemAdGuardDomain) if err != nil { return err @@ -457,7 +455,7 @@ func readLogicalRule(reader varbin.Reader, recovery bool) (logicalRule option.Lo return } -func writeLogicalRule(writer varbin.Writer, logicalRule option.LogicalHeadlessRule, generateUnstable bool) error { +func writeLogicalRule(writer varbin.Writer, logicalRule option.LogicalHeadlessRule, generateVersion uint8) error { err := binary.Write(writer, binary.BigEndian, uint8(1)) if err != nil { return err @@ -478,7 +476,7 @@ func writeLogicalRule(writer varbin.Writer, logicalRule option.LogicalHeadlessRu return err } for _, rule := range logicalRule.Rules { - err = writeRule(writer, rule, generateUnstable) + err = writeRule(writer, rule, generateVersion) if err != nil { return err } diff --git a/constant/rule.go b/constant/rule.go index 095c0a586..b1f91c608 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -21,6 +21,7 @@ const ( const ( RuleSetVersion1 = 1 + iota RuleSetVersion2 + RuleSetVersionCurrent = RuleSetVersion2 ) const ( diff --git a/route/rule/rule_set_local.go b/route/rule/rule_set_local.go index 63678de1c..efbc525ee 100644 --- a/route/rule/rule_set_local.go +++ b/route/rule/rule_set_local.go @@ -95,33 +95,34 @@ func (s *LocalRuleSet) StartContext(ctx context.Context, startContext *adapter.H } func (s *LocalRuleSet) reloadFile(path string) error { - var plainRuleSet option.PlainRuleSet + var ruleSet option.PlainRuleSetCompat switch s.fileFormat { case C.RuleSetFormatSource, "": content, err := os.ReadFile(path) if err != nil { return err } - compat, err := json.UnmarshalExtended[option.PlainRuleSetCompat](content) - if err != nil { - return err - } - plainRuleSet, err = compat.Upgrade() + ruleSet, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content) if err != nil { return err } + case C.RuleSetFormatBinary: setFile, err := os.Open(path) if err != nil { return err } - plainRuleSet, err = srs.Read(setFile, false) + ruleSet, err = srs.Read(setFile, false) if err != nil { return err } default: return E.New("unknown rule-set format: ", s.fileFormat) } + plainRuleSet, err := ruleSet.Upgrade() + if err != nil { + return err + } return s.reloadRules(plainRuleSet.Rules) } diff --git a/route/rule/rule_set_remote.go b/route/rule/rule_set_remote.go index 5ecbcbef3..830e19f7c 100644 --- a/route/rule/rule_set_remote.go +++ b/route/rule/rule_set_remote.go @@ -155,28 +155,27 @@ func (s *RemoteRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSet func (s *RemoteRuleSet) loadBytes(content []byte) error { var ( - plainRuleSet option.PlainRuleSet - err error + ruleSet option.PlainRuleSetCompat + err error ) switch s.options.Format { case C.RuleSetFormatSource: - var compat option.PlainRuleSetCompat - compat, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content) - if err != nil { - return err - } - plainRuleSet, err = compat.Upgrade() + ruleSet, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content) if err != nil { return err } case C.RuleSetFormatBinary: - plainRuleSet, err = srs.Read(bytes.NewReader(content), false) + ruleSet, err = srs.Read(bytes.NewReader(content), false) if err != nil { return err } default: return E.New("unknown rule-set format: ", s.options.Format) } + plainRuleSet, err := ruleSet.Upgrade() + if err != nil { + return err + } rules := make([]adapter.HeadlessRule, len(plainRuleSet.Rules)) for i, ruleOptions := range plainRuleSet.Rules { rules[i], err = NewHeadlessRule(s.ctx, ruleOptions) From ef2a2fdd52a99720501eab121fa8443e576f33ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 11 Nov 2024 16:32:34 +0800 Subject: [PATCH 24/39] Merge route options to route actions --- experimental/deprecated/constants.go | 10 ------ option/rule_action.go | 43 ++++++------------------- route/route.go | 6 ++++ route/rule/rule_action.go | 48 +++++++++++++++++++++------- 4 files changed, 51 insertions(+), 56 deletions(-) diff --git a/experimental/deprecated/constants.go b/experimental/deprecated/constants.go index 7830452f2..bf848ffea 100644 --- a/experimental/deprecated/constants.go +++ b/experimental/deprecated/constants.go @@ -100,15 +100,6 @@ var OptionInboundOptions = Note{ MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-legacy-special-outbounds-to-rule-actions", } -var OptionLegacyDNSRouteOptions = Note{ - Name: "legacy-dns-route-options", - Description: "legacy dns route options", - DeprecatedVersion: "1.11.0", - ScheduledVersion: "1.12.0", - EnvName: "LEGACY_DNS_ROUTE_OPTIONS", - MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-legacy-dns-route-options-to-rule-actions", -} - var Options = []Note{ OptionBadMatchSource, OptionGEOIP, @@ -116,5 +107,4 @@ var Options = []Note{ OptionTUNAddressX, OptionSpecialOutbounds, OptionInboundOptions, - OptionLegacyDNSRouteOptions, } diff --git a/option/rule_action.go b/option/rule_action.go index 3b4e8edb5..9bc130393 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -7,8 +7,7 @@ import ( "time" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/experimental/deprecated" - dns "github.com/sagernet/sing-dns" + "github.com/sagernet/sing-dns" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" @@ -137,18 +136,10 @@ func (r *DNSRuleAction) UnmarshalJSONContext(ctx context.Context, data []byte) e return badjson.UnmarshallExcludedContext(ctx, data, (*_DNSRuleAction)(r), v) } -type _RouteActionOptions struct { - Outbound string `json:"outbound,omitempty"` -} - -type RouteActionOptions _RouteActionOptions - -func (r *RouteActionOptions) UnmarshalJSON(data []byte) error { - err := json.Unmarshal(data, (*_RouteActionOptions)(r)) - if err != nil { - return err - } - return nil +type RouteActionOptions struct { + Outbound string `json:"outbound,omitempty"` + UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` + UDPConnect bool `json:"udp_connect,omitempty"` } type _RouteOptionsActionOptions struct { @@ -169,29 +160,13 @@ func (r *RouteOptionsActionOptions) UnmarshalJSON(data []byte) error { return nil } -type _DNSRouteActionOptions struct { - Server string `json:"server,omitempty"` - // Deprecated: Use DNSRouteOptionsActionOptions instead. - DisableCache bool `json:"disable_cache,omitempty"` - // Deprecated: Use DNSRouteOptionsActionOptions instead. - RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` - // Deprecated: Use DNSRouteOptionsActionOptions instead. +type DNSRouteActionOptions struct { + Server string `json:"server,omitempty"` + DisableCache bool `json:"disable_cache,omitempty"` + RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"` } -type DNSRouteActionOptions _DNSRouteActionOptions - -func (r *DNSRouteActionOptions) UnmarshalJSONContext(ctx context.Context, data []byte) error { - err := json.Unmarshal(data, (*_DNSRouteActionOptions)(r)) - if err != nil { - return err - } - if r.DisableCache || r.RewriteTTL != nil || r.ClientSubnet != nil { - deprecated.Report(ctx, deprecated.OptionLegacyDNSRouteOptions) - } - return nil -} - type _DNSRouteOptionsActionOptions struct { DisableCache bool `json:"disable_cache,omitempty"` RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` diff --git a/route/route.go b/route/route.go index 0e3f8916f..ddf523586 100644 --- a/route/route.go +++ b/route/route.go @@ -437,6 +437,12 @@ match: } } switch action := currentRule.Action().(type) { + case *rule.RuleActionRoute: + metadata.UDPDisableDomainUnmapping = action.UDPDisableDomainUnmapping + metadata.UDPConnect = action.UDPConnect + selectedRule = currentRule + selectedRuleIndex = currentRuleIndex + break match case *rule.RuleActionRouteOptions: metadata.UDPDisableDomainUnmapping = action.UDPDisableDomainUnmapping metadata.UDPConnect = action.UDPConnect diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index 00579c18d..d44e36eea 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -29,6 +29,10 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti case C.RuleActionTypeRoute: return &RuleActionRoute{ Outbound: action.RouteOptions.Outbound, + RuleActionRouteOptions: RuleActionRouteOptions{ + UDPDisableDomainUnmapping: action.RouteOptions.UDPDisableDomainUnmapping, + UDPConnect: action.RouteOptions.UDPConnect, + }, }, nil case C.RuleActionTypeRouteOptions: return &RuleActionRouteOptions{ @@ -85,10 +89,12 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction) return nil case C.RuleActionTypeRoute: return &RuleActionDNSRoute{ - Server: action.RouteOptions.Server, - DisableCache: action.RouteOptions.DisableCache, - RewriteTTL: action.RouteOptions.RewriteTTL, - ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptions.ClientSubnet)), + Server: action.RouteOptions.Server, + RuleActionDNSRouteOptions: RuleActionDNSRouteOptions{ + DisableCache: action.RouteOptions.DisableCache, + RewriteTTL: action.RouteOptions.RewriteTTL, + ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptions.ClientSubnet)), + }, } case C.RuleActionTypeRouteOptions: return &RuleActionDNSRouteOptions{ @@ -109,6 +115,7 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction) type RuleActionRoute struct { Outbound string + RuleActionRouteOptions } func (r *RuleActionRoute) Type() string { @@ -116,7 +123,15 @@ func (r *RuleActionRoute) Type() string { } func (r *RuleActionRoute) String() string { - return F.ToString("route(", r.Outbound, ")") + var descriptions []string + descriptions = append(descriptions, r.Outbound) + if r.UDPDisableDomainUnmapping { + descriptions = append(descriptions, "udp-disable-domain-unmapping") + } + if r.UDPConnect { + descriptions = append(descriptions, "udp-connect") + } + return F.ToString("route(", strings.Join(descriptions, ","), ")") } type RuleActionRouteOptions struct { @@ -140,10 +155,8 @@ func (r *RuleActionRouteOptions) String() string { } type RuleActionDNSRoute struct { - Server string - DisableCache bool - RewriteTTL *uint32 - ClientSubnet netip.Prefix + Server string + RuleActionDNSRouteOptions } func (r *RuleActionDNSRoute) Type() string { @@ -151,7 +164,18 @@ func (r *RuleActionDNSRoute) Type() string { } func (r *RuleActionDNSRoute) String() string { - return F.ToString("route(", r.Server, ")") + var descriptions []string + descriptions = append(descriptions, r.Server) + if r.DisableCache { + descriptions = append(descriptions, "disable-cache") + } + if r.RewriteTTL != nil { + descriptions = append(descriptions, F.ToString("rewrite-ttl=", *r.RewriteTTL)) + } + if r.ClientSubnet.IsValid() { + descriptions = append(descriptions, F.ToString("client-subnet=", r.ClientSubnet)) + } + return F.ToString("route(", strings.Join(descriptions, ","), ")") } type RuleActionDNSRouteOptions struct { @@ -170,10 +194,10 @@ func (r *RuleActionDNSRouteOptions) String() string { descriptions = append(descriptions, "disable-cache") } if r.RewriteTTL != nil { - descriptions = append(descriptions, F.ToString("rewrite-ttl(", *r.RewriteTTL, ")")) + descriptions = append(descriptions, F.ToString("rewrite-ttl=", *r.RewriteTTL)) } if r.ClientSubnet.IsValid() { - descriptions = append(descriptions, F.ToString("client-subnet(", r.ClientSubnet, ")")) + descriptions = append(descriptions, F.ToString("client-subnet=", r.ClientSubnet)) } return F.ToString("route-options(", strings.Join(descriptions, ","), ")") } From aa35ae1736dd5da5ee1145935e88ed2a1c40c832 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 11 Nov 2024 16:30:25 +0800 Subject: [PATCH 25/39] Add `network_[type/is_expensive/is_constrained]` rule items --- .../convertor/adguard/convertor_test.go | 7 +-- common/srs/binary.go | 30 ++++++++++++ constant/rule.go | 3 +- option/rule.go | 3 ++ option/rule_dns.go | 3 ++ option/rule_set.go | 49 ++++++++++--------- route/rule/rule_default.go | 15 ++++++ route/rule/rule_dns.go | 15 ++++++ route/rule/rule_headless.go | 25 ++++++++-- .../rule/rule_item_network_is_constrained.go | 29 +++++++++++ route/rule/rule_item_network_is_expensive.go | 29 +++++++++++ route/rule/rule_item_network_type.go | 39 +++++++++++++++ 12 files changed, 215 insertions(+), 32 deletions(-) create mode 100644 route/rule/rule_item_network_is_constrained.go create mode 100644 route/rule/rule_item_network_is_expensive.go create mode 100644 route/rule/rule_item_network_type.go diff --git a/cmd/sing-box/internal/convertor/adguard/convertor_test.go b/cmd/sing-box/internal/convertor/adguard/convertor_test.go index 6098485cf..212c21703 100644 --- a/cmd/sing-box/internal/convertor/adguard/convertor_test.go +++ b/cmd/sing-box/internal/convertor/adguard/convertor_test.go @@ -1,6 +1,7 @@ package adguard import ( + "context" "strings" "testing" @@ -26,7 +27,7 @@ example.arpa `)) require.NoError(t, err) require.Len(t, rules, 1) - rule, err := rule.NewHeadlessRule(nil, rules[0]) + rule, err := rule.NewHeadlessRule(context.Background(), rules[0]) require.NoError(t, err) matchDomain := []string{ "example.org", @@ -85,7 +86,7 @@ func TestHosts(t *testing.T) { `)) require.NoError(t, err) require.Len(t, rules, 1) - rule, err := rule.NewHeadlessRule(nil, rules[0]) + rule, err := rule.NewHeadlessRule(context.Background(), rules[0]) require.NoError(t, err) matchDomain := []string{ "google.com", @@ -115,7 +116,7 @@ www.example.org `)) require.NoError(t, err) require.Len(t, rules, 1) - rule, err := rule.NewHeadlessRule(nil, rules[0]) + rule, err := rule.NewHeadlessRule(context.Background(), rules[0]) require.NoError(t, err) matchDomain := []string{ "example.com", diff --git a/common/srs/binary.go b/common/srs/binary.go index dd670fc83..fbed78adc 100644 --- a/common/srs/binary.go +++ b/common/srs/binary.go @@ -38,6 +38,9 @@ const ( ruleItemWIFIBSSID ruleItemAdGuardDomain ruleItemProcessPathRegex + ruleItemNetworkType + ruleItemNetworkIsExpensive + ruleItemNetworkIsConstrained ruleItemFinal uint8 = 0xFF ) @@ -222,6 +225,12 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea return } rule.AdGuardDomainMatcher = matcher + case ruleItemNetworkType: + rule.NetworkType, err = readRuleItemString(reader) + case ruleItemNetworkIsExpensive: + rule.NetworkIsExpensive = true + case ruleItemNetworkIsConstrained: + rule.NetworkIsConstrained = true case ruleItemFinal: err = binary.Read(reader, binary.BigEndian, &rule.Invert) return @@ -336,6 +345,27 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen return err } } + if len(rule.NetworkType) > 0 { + if generateVersion < C.RuleSetVersion3 { + return E.New("network_type rule item is only supported in version 3 or later") + } + err = writeRuleItemString(writer, ruleItemNetworkType, rule.NetworkType) + if err != nil { + return err + } + } + if rule.NetworkIsExpensive { + err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsExpensive) + if err != nil { + return err + } + } + if rule.NetworkIsConstrained { + err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsConstrained) + if err != nil { + return err + } + } if len(rule.WIFISSID) > 0 { err = writeRuleItemString(writer, ruleItemWIFISSID, rule.WIFISSID) if err != nil { diff --git a/constant/rule.go b/constant/rule.go index b1f91c608..c4a778384 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -21,7 +21,8 @@ const ( const ( RuleSetVersion1 = 1 + iota RuleSetVersion2 - RuleSetVersionCurrent = RuleSetVersion2 + RuleSetVersion3 + RuleSetVersionCurrent = RuleSetVersion3 ) const ( diff --git a/option/rule.go b/option/rule.go index d5ff9925a..82a53f75a 100644 --- a/option/rule.go +++ b/option/rule.go @@ -95,6 +95,9 @@ type RawDefaultRule struct { User badoption.Listable[string] `json:"user,omitempty"` UserID badoption.Listable[int32] `json:"user_id,omitempty"` ClashMode string `json:"clash_mode,omitempty"` + NetworkType badoption.Listable[string] `json:"network_type,omitempty"` + NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` + NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` RuleSet badoption.Listable[string] `json:"rule_set,omitempty"` diff --git a/option/rule_dns.go b/option/rule_dns.go index e758488be..c49d312ba 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -97,6 +97,9 @@ type RawDefaultDNSRule struct { UserID badoption.Listable[int32] `json:"user_id,omitempty"` Outbound badoption.Listable[string] `json:"outbound,omitempty"` ClashMode string `json:"clash_mode,omitempty"` + NetworkType badoption.Listable[string] `json:"network_type,omitempty"` + NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` + NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` RuleSet badoption.Listable[string] `json:"rule_set,omitempty"` diff --git a/option/rule_set.go b/option/rule_set.go index f0d96bb6e..9ccca4759 100644 --- a/option/rule_set.go +++ b/option/rule_set.go @@ -146,25 +146,28 @@ func (r HeadlessRule) IsValid() bool { } type DefaultHeadlessRule struct { - QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"` - Network badoption.Listable[string] `json:"network,omitempty"` - Domain badoption.Listable[string] `json:"domain,omitempty"` - DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` - DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` - DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` - SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` - IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` - SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` - SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` - Port badoption.Listable[uint16] `json:"port,omitempty"` - PortRange badoption.Listable[string] `json:"port_range,omitempty"` - ProcessName badoption.Listable[string] `json:"process_name,omitempty"` - ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` - ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` - PackageName badoption.Listable[string] `json:"package_name,omitempty"` - WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` - WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` - Invert bool `json:"invert,omitempty"` + QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"` + Network badoption.Listable[string] `json:"network,omitempty"` + Domain badoption.Listable[string] `json:"domain,omitempty"` + DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` + DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` + DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` + SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` + IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` + SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` + SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` + Port badoption.Listable[uint16] `json:"port,omitempty"` + PortRange badoption.Listable[string] `json:"port_range,omitempty"` + ProcessName badoption.Listable[string] `json:"process_name,omitempty"` + ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` + ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` + PackageName badoption.Listable[string] `json:"package_name,omitempty"` + NetworkType badoption.Listable[string] `json:"network_type,omitempty"` + NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` + NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` + WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` + WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` + Invert bool `json:"invert,omitempty"` DomainMatcher *domain.Matcher `json:"-"` SourceIPSet *netipx.IPSet `json:"-"` @@ -191,7 +194,7 @@ func (r LogicalHeadlessRule) IsValid() bool { } type _PlainRuleSetCompat struct { - Version int `json:"version"` + Version uint8 `json:"version"` Options PlainRuleSet `json:"-"` } @@ -200,7 +203,7 @@ type PlainRuleSetCompat _PlainRuleSetCompat func (r PlainRuleSetCompat) MarshalJSON() ([]byte, error) { var v any switch r.Version { - case C.RuleSetVersion1, C.RuleSetVersion2: + case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3: v = r.Options default: return nil, E.New("unknown rule-set version: ", r.Version) @@ -215,7 +218,7 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error { } var v any switch r.Version { - case C.RuleSetVersion1, C.RuleSetVersion2: + case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3: v = &r.Options case 0: return E.New("missing rule-set version") @@ -231,7 +234,7 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error { func (r PlainRuleSetCompat) Upgrade() (PlainRuleSet, error) { switch r.Version { - case C.RuleSetVersion1, C.RuleSetVersion2: + case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3: default: return PlainRuleSet{}, E.New("unknown rule-set version: " + F.ToString(r.Version)) } diff --git a/route/rule/rule_default.go b/route/rule/rule_default.go index d1076e3d9..5c37b415a 100644 --- a/route/rule/rule_default.go +++ b/route/rule/rule_default.go @@ -224,6 +224,21 @@ func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options optio rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } + if len(options.NetworkType) > 0 { + item := NewNetworkTypeItem(networkManager, options.NetworkType) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if options.NetworkIsExpensive { + item := NewNetworkIsExpensiveItem(networkManager) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if options.NetworkIsConstrained { + item := NewNetworkIsConstrainedItem(networkManager) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } if len(options.WIFISSID) > 0 { item := NewWIFISSIDItem(networkManager, options.WIFISSID) rule.items = append(rule.items, item) diff --git a/route/rule/rule_dns.go b/route/rule/rule_dns.go index 70e1417a5..ecb68ece7 100644 --- a/route/rule/rule_dns.go +++ b/route/rule/rule_dns.go @@ -221,6 +221,21 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } + if len(options.NetworkType) > 0 { + item := NewNetworkTypeItem(networkManager, options.NetworkType) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if options.NetworkIsExpensive { + item := NewNetworkIsExpensiveItem(networkManager) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if options.NetworkIsConstrained { + item := NewNetworkIsConstrainedItem(networkManager) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } if len(options.WIFISSID) > 0 { item := NewWIFISSIDItem(networkManager, options.WIFISSID) rule.items = append(rule.items, item) diff --git a/route/rule/rule_headless.go b/route/rule/rule_headless.go index 99488b204..7f2dc5fca 100644 --- a/route/rule/rule_headless.go +++ b/route/rule/rule_headless.go @@ -140,18 +140,33 @@ func NewDefaultHeadlessRule(ctx context.Context, options option.DefaultHeadlessR rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } - if len(options.WIFISSID) > 0 { - if networkManager != nil { + if networkManager != nil { + if len(options.NetworkType) > 0 { + item := NewNetworkTypeItem(networkManager, options.NetworkType) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if options.NetworkIsExpensive { + item := NewNetworkIsExpensiveItem(networkManager) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if options.NetworkIsConstrained { + item := NewNetworkIsConstrainedItem(networkManager) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.WIFISSID) > 0 { item := NewWIFISSIDItem(networkManager, options.WIFISSID) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) + } - } - if len(options.WIFIBSSID) > 0 { - if networkManager != nil { + if len(options.WIFIBSSID) > 0 { item := NewWIFIBSSIDItem(networkManager, options.WIFIBSSID) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) + } } if len(options.AdGuardDomain) > 0 { diff --git a/route/rule/rule_item_network_is_constrained.go b/route/rule/rule_item_network_is_constrained.go new file mode 100644 index 000000000..e0368b753 --- /dev/null +++ b/route/rule/rule_item_network_is_constrained.go @@ -0,0 +1,29 @@ +package rule + +import ( + "github.com/sagernet/sing-box/adapter" +) + +var _ RuleItem = (*NetworkIsConstrainedItem)(nil) + +type NetworkIsConstrainedItem struct { + networkManager adapter.NetworkManager +} + +func NewNetworkIsConstrainedItem(networkManager adapter.NetworkManager) *NetworkIsConstrainedItem { + return &NetworkIsConstrainedItem{ + networkManager: networkManager, + } +} + +func (r *NetworkIsConstrainedItem) Match(metadata *adapter.InboundContext) bool { + networkInterface := r.networkManager.DefaultNetworkInterface() + if networkInterface == nil { + return false + } + return networkInterface.Constrained +} + +func (r *NetworkIsConstrainedItem) String() string { + return "network_is_expensive=true" +} diff --git a/route/rule/rule_item_network_is_expensive.go b/route/rule/rule_item_network_is_expensive.go new file mode 100644 index 000000000..83e4f96f8 --- /dev/null +++ b/route/rule/rule_item_network_is_expensive.go @@ -0,0 +1,29 @@ +package rule + +import ( + "github.com/sagernet/sing-box/adapter" +) + +var _ RuleItem = (*NetworkIsExpensiveItem)(nil) + +type NetworkIsExpensiveItem struct { + networkManager adapter.NetworkManager +} + +func NewNetworkIsExpensiveItem(networkManager adapter.NetworkManager) *NetworkIsExpensiveItem { + return &NetworkIsExpensiveItem{ + networkManager: networkManager, + } +} + +func (r *NetworkIsExpensiveItem) Match(metadata *adapter.InboundContext) bool { + networkInterface := r.networkManager.DefaultNetworkInterface() + if networkInterface == nil { + return false + } + return networkInterface.Expensive +} + +func (r *NetworkIsExpensiveItem) String() string { + return "network_is_expensive=true" +} diff --git a/route/rule/rule_item_network_type.go b/route/rule/rule_item_network_type.go new file mode 100644 index 000000000..8ebdb25e9 --- /dev/null +++ b/route/rule/rule_item_network_type.go @@ -0,0 +1,39 @@ +package rule + +import ( + "strings" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing/common" + F "github.com/sagernet/sing/common/format" +) + +var _ RuleItem = (*NetworkTypeItem)(nil) + +type NetworkTypeItem struct { + networkManager adapter.NetworkManager + networkType []string +} + +func NewNetworkTypeItem(networkManager adapter.NetworkManager, networkType []string) *NetworkTypeItem { + return &NetworkTypeItem{ + networkManager: networkManager, + networkType: networkType, + } +} + +func (r *NetworkTypeItem) Match(metadata *adapter.InboundContext) bool { + networkInterface := r.networkManager.DefaultNetworkInterface() + if networkInterface == nil { + return false + } + return common.Contains(r.networkType, networkInterface.Type) +} + +func (r *NetworkTypeItem) String() string { + if len(r.networkType) == 1 { + return F.ToString("network_type=", r.networkType[0]) + } else { + return F.ToString("network_type=", "["+strings.Join(F.MapToString(r.networkType), " ")+"]") + } +} From c59f282b7d4c4fecd5d57686a0e23a69d6442fd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 11 Nov 2024 17:24:15 +0800 Subject: [PATCH 26/39] documentation: Merge route options to route actions --- docs/configuration/dns/rule_action.md | 32 ++++++------- docs/configuration/dns/rule_action.zh.md | 31 ++++++------- docs/configuration/route/rule_action.md | 28 ++++++------ docs/configuration/route/rule_action.zh.md | 25 ++++++----- docs/deprecated.md | 8 ---- docs/deprecated.zh.md | 7 --- docs/migration.md | 52 ---------------------- docs/migration.zh.md | 52 ---------------------- 8 files changed, 55 insertions(+), 180 deletions(-) diff --git a/docs/configuration/dns/rule_action.md b/docs/configuration/dns/rule_action.md index 8943b6535..cccc13967 100644 --- a/docs/configuration/dns/rule_action.md +++ b/docs/configuration/dns/rule_action.md @@ -12,8 +12,6 @@ icon: material/new-box { "action": "route", // default "server": "", - - // for compatibility "disable_cache": false, "rewrite_ttl": 0, "client_subnet": null @@ -28,23 +26,6 @@ icon: material/new-box Tag of target server. -#### disable_cache/rewrite_ttl/client_subnet - -!!! failure "Deprecated in sing-box 1.11.0" - - Legacy route options is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-legacy-dns-route-options-to-rule-actions). - -### route-options - -```json -{ - "action": "route-options", - "disable_cache": false, - "rewrite_ttl": null, - "client_subnet": null -} -``` - #### disable_cache Disable cache and save cache in this query. @@ -61,6 +42,19 @@ If value is an IP address instead of prefix, `/32` or `/128` will be appended au Will overrides `dns.client_subnet` and `servers.[].client_subnet`. +### route-options + +```json +{ + "action": "route-options", + "disable_cache": false, + "rewrite_ttl": null, + "client_subnet": null +} +``` + +`route-options` set options for routing. + ### reject ```json diff --git a/docs/configuration/dns/rule_action.zh.md b/docs/configuration/dns/rule_action.zh.md index 8a9dc07ee..8fad23816 100644 --- a/docs/configuration/dns/rule_action.zh.md +++ b/docs/configuration/dns/rule_action.zh.md @@ -28,24 +28,6 @@ icon: material/new-box 目标 DNS 服务器的标签。 -#### disable_cache/rewrite_ttl/client_subnet - -!!! failure "自 sing-box 1.11.0 起" - - 旧的路由选项已弃用,且将在 sing-box 1.12.0 中移除,参阅 [迁移指南](/migration/#migrate-legacy-dns-route-options-to-rule-actions). - -### route-options - -```json -{ - "action": "route-options", - "disable_cache": false, - "rewrite_ttl": null, - "client_subnet": null -} -``` - - #### disable_cache 在此查询中禁用缓存。 @@ -62,6 +44,19 @@ icon: material/new-box 将覆盖 `dns.client_subnet` 与 `servers.[].client_subnet`。 +### route-options + +```json +{ + "action": "route-options", + "disable_cache": false, + "rewrite_ttl": null, + "client_subnet": null +} +``` + +`route-options` 为路由设置选项。 + ### reject ```json diff --git a/docs/configuration/route/rule_action.md b/docs/configuration/route/rule_action.md index 843d75636..7804f5869 100644 --- a/docs/configuration/route/rule_action.md +++ b/docs/configuration/route/rule_action.md @@ -13,7 +13,9 @@ icon: material/new-box ```json { "action": "route", // default - "outbound": "" + "outbound": "", + "udp_disable_domain_unmapping": false, + "udp_connect": false } ``` @@ -25,18 +27,6 @@ icon: material/new-box Tag of target outbound. -### route-options - -```json -{ - "action": "route-options", - "udp_disable_domain_unmapping": false, - "udp_connect": false -} -``` - -`route-options` set options for routing. - #### udp_disable_domain_unmapping If enabled, for UDP proxy requests addressed to a domain, @@ -49,6 +39,18 @@ do not support receiving UDP packets with domain addresses, such as Surge. If enabled, attempts to connect UDP connection to the destination instead of listen. +### route-options + +```json +{ + "action": "route-options", + "udp_disable_domain_unmapping": false, + "udp_connect": false +} +``` + +`route-options` set options for routing. + ### reject ```json diff --git a/docs/configuration/route/rule_action.zh.md b/docs/configuration/route/rule_action.zh.md index ae16d85fd..103a04bd5 100644 --- a/docs/configuration/route/rule_action.zh.md +++ b/docs/configuration/route/rule_action.zh.md @@ -14,7 +14,8 @@ icon: material/new-box { "action": "route", // 默认 "outbound": "", - "udp_disable_domain_unmapping": false + "udp_disable_domain_unmapping": false, + "udp_connect": false } ``` @@ -26,16 +27,6 @@ icon: material/new-box 目标出站的标签。 -### route-options - -```json -{ - "action": "route-options", - "udp_disable_domain_unmapping": false, - "udp_connect": false -} -``` - #### udp_disable_domain_unmapping 如果启用,对于地址为域的 UDP 代理请求,将在响应中发送原始包地址而不是映射的域。 @@ -46,6 +37,18 @@ icon: material/new-box 如果启用,将尝试将 UDP 连接 connect 到目标而不是 listen。 +### route-options + +```json +{ + "action": "route-options", + "udp_disable_domain_unmapping": false, + "udp_connect": false +} +``` + +`route-options` 为路由设置选项。 + ### reject ```json diff --git a/docs/deprecated.md b/docs/deprecated.md index f057319ad..d57c93ade 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -22,14 +22,6 @@ check [Migration](../migration/#migrate-legacy-inbound-fields-to-rule-actions). Old fields will be removed in sing-box 1.13.0. -#### Legacy DNS route options - -Legacy DNS route options (`disable_cache`, `rewrite_ttl`, `client_subnet`) are deprecated -and can be replaced by rule actions, -check [Migration](../migration/#migrate-legacy-dns-route-options-to-rule-actions). - -Old fields will be removed in sing-box 1.12.0. - ## 1.10.0 #### TUN address fields are merged diff --git a/docs/deprecated.zh.md b/docs/deprecated.zh.md index 2f7b28f7b..76a426d27 100644 --- a/docs/deprecated.zh.md +++ b/docs/deprecated.zh.md @@ -20,13 +20,6 @@ icon: material/delete-alert 旧字段将在 sing-box 1.13.0 中被移除。 -#### 旧的 DNS 路由参数 - -旧的 DNS 路由参数(`disable_cache`、`rewrite_ttl`、`client_subnet`)已废弃且可以通过规则动作替代, -参阅 [迁移指南](/migration/#migrate-legacy-dns-route-options-to-rule-actions)。 - -旧字段将在 sing-box 1.12.0 中被移除。 - ## 1.10.0 #### Match source 规则项已重命名 diff --git a/docs/migration.md b/docs/migration.md index 9207db5bc..8f01c4f03 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -156,58 +156,6 @@ Inbound fields are deprecated and can be replaced by rule actions. } ``` -### Migrate legacy DNS route options to rule actions - -Legacy DNS route options are deprecated and can be replaced by rule actions. - -!!! info "References" - - [DNS Rule](/configuration/dns/rule/) / - [DNS Rule Action](/configuration/dns/rule_action/) - -=== ":material-card-remove: Deprecated" - - ```json - { - "dns": { - "rules": [ - { - ..., - - "server": "local", - "disable_cache": true, - "rewrite_ttl": 600, - "client_subnet": "1.1.1.1/24" - } - ] - } - } - ``` - -=== ":material-card-multiple: New" - - ```json - { - "dns": { - "rules": [ - { - ..., - - "action": "route-options", - "disable_cache": true, - "rewrite_ttl": 600, - "client_subnet": "1.1.1.1/24" - }, - { - ..., - - "server": "local" - } - ] - } - } - ``` - ## 1.10.0 ### TUN address fields are merged diff --git a/docs/migration.zh.md b/docs/migration.zh.md index f51860f72..dc62f3700 100644 --- a/docs/migration.zh.md +++ b/docs/migration.zh.md @@ -156,58 +156,6 @@ icon: material/arrange-bring-forward } ``` -### 迁移旧的 DNS 路由选项到规则动作 - -旧的 DNS 路由选项已被弃用,且可以被规则动作替代。 - -!!! info "参考" - - [DNS 规则](/zh/configuration/dns/rule/) / - [DNS 规则动作](/zh/configuration/dns/rule_action/) - -=== ":material-card-remove: 弃用的" - - ```json - { - "dns": { - "rules": [ - { - ..., - - "server": "local", - "disable_cache": true, - "rewrite_ttl": 600, - "client_subnet": "1.1.1.1/24" - } - ] - } - } - ``` - -=== ":material-card-multiple: 新的" - - ```json - { - "dns": { - "rules": [ - { - ..., - - "action": "route-options", - "disable_cache": true, - "rewrite_ttl": 600, - "client_subnet": "1.1.1.1/24" - }, - { - ..., - - "server": "local" - } - ] - } - } - ``` - ## 1.10.0 ### TUN 地址字段已合并 From d8e66b9180155d05be2bf64e9ecd8350ada9e238 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 11 Nov 2024 19:33:40 +0800 Subject: [PATCH 27/39] documentation: Add new rule item types --- docs/configuration/dns/rule.md | 43 ++- docs/configuration/dns/rule.zh.md | 91 ++++-- docs/configuration/route/rule.md | 43 ++- docs/configuration/route/rule.zh.md | 50 +++- docs/configuration/rule-set/adguard.zh.md | 70 +++++ docs/configuration/rule-set/headless-rule.md | 48 ++++ .../rule-set/headless-rule.zh.md | 258 ++++++++++++++++++ docs/configuration/rule-set/index.md | 2 +- docs/configuration/rule-set/index.zh.md | 117 ++++++++ docs/configuration/rule-set/source-format.md | 21 +- .../rule-set/source-format.zh.md | 46 ++++ 11 files changed, 753 insertions(+), 36 deletions(-) create mode 100644 docs/configuration/rule-set/adguard.zh.md create mode 100644 docs/configuration/rule-set/headless-rule.zh.md create mode 100644 docs/configuration/rule-set/index.zh.md create mode 100644 docs/configuration/rule-set/source-format.zh.md diff --git a/docs/configuration/dns/rule.md b/docs/configuration/dns/rule.md index 5cb24e819..1f04b2999 100644 --- a/docs/configuration/dns/rule.md +++ b/docs/configuration/dns/rule.md @@ -8,7 +8,10 @@ icon: material/new-box :material-alert: [server](#server) :material-alert: [disable_cache](#disable_cache) :material-alert: [rewrite_ttl](#rewrite_ttl) - :material-alert: [client_subnet](#client_subnet) + :material-alert: [client_subnet](#client_subnet) + :material-plus: [network_type](#network_type) + :material-plus: [network_is_expensive](#network_is_expensive) + :material-plus: [network_is_constrained](#network_is_constrained) !!! quote "Changes in sing-box 1.10.0" @@ -125,6 +128,11 @@ icon: material/new-box 1000 ], "clash_mode": "direct", + "network_type": [ + "wifi" + ], + "network_is_expensive": false, + "network_is_constrained": false, "wifi_ssid": [ "My WIFI" ], @@ -310,6 +318,39 @@ Match user id. Match Clash mode. +#### network_type + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms. + +Match network type. + +Available values: `wifi`, `cellular`, `ethernet` and `other`. + +#### network_is_expensive + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms. + +Match if network is considered Metered (on Android) or considered expensive, +such as Cellular or a Personal Hotspot (on Apple platforms). + +#### network_is_constrained + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Apple platforms. + +Match if network is in Low Data Mode. + #### wifi_ssid !!! quote "" diff --git a/docs/configuration/dns/rule.zh.md b/docs/configuration/dns/rule.zh.md index 205b01ae0..e904f8cd0 100644 --- a/docs/configuration/dns/rule.zh.md +++ b/docs/configuration/dns/rule.zh.md @@ -2,6 +2,17 @@ icon: material/new-box --- +!!! quote "sing-box 1.11.0 中的更改" + + :material-plus: [action](#action) + :material-alert: [server](#server) + :material-alert: [disable_cache](#disable_cache) + :material-alert: [rewrite_ttl](#rewrite_ttl) + :material-alert: [client_subnet](#client_subnet) + :material-plus: [network_type](#network_type) + :material-plus: [network_is_expensive](#network_is_expensive) + :material-plus: [network_is_constrained](#network_is_constrained) + !!! quote "sing-box 1.10.0 中的更改" :material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) @@ -117,6 +128,11 @@ icon: material/new-box 1000 ], "clash_mode": "direct", + "network_type": [ + "wifi" + ], + "network_is_expensive": false, + "network_is_constrained": false, "wifi_ssid": [ "My WIFI" ], @@ -135,17 +151,15 @@ icon: material/new-box "outbound": [ "direct" ], - "server": "local", - "disable_cache": false, - "client_subnet": "127.0.0.1/24" + "action": "route", + "server": "local" }, { "type": "logical", "mode": "and", "rules": [], - "server": "local", - "disable_cache": false, - "client_subnet": "127.0.0.1/24" + "action": "route", + "server": "local" } ] } @@ -304,6 +318,39 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 匹配 Clash 模式。 +#### network_type + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +匹配网络类型。 + +Available values: `wifi`, `cellular`, `ethernet` and `other`. + +#### network_is_expensive + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +匹配如果网络被视为计费 (在 Android) 或被视为昂贵, +像蜂窝网络或个人热点 (在 Apple 平台)。 + +#### network_is_constrained + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Apple 平台图形客户端中支持。 + +匹配如果网络在低数据模式下。 + #### wifi_ssid !!! quote "" @@ -352,29 +399,35 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 `any` 可作为值用于匹配任意出站。 -#### server +#### action ==必填== -目标 DNS 服务器的标签。 +参阅 [规则动作](../rule_action/)。 + +#### server + +!!! failure "已在 sing-box 1.11.0 废弃" + + 已移动到 [DNS 规则动作](../rule_action#route). #### disable_cache -在此查询中禁用缓存。 +!!! failure "已在 sing-box 1.11.0 废弃" + + 已移动到 [DNS 规则动作](../rule_action#route). #### rewrite_ttl -重写 DNS 回应中的 TTL。 +!!! failure "已在 sing-box 1.11.0 废弃" -#### client_subnet + 已移动到 [DNS 规则动作](../rule_action#route). -!!! question "自 sing-box 1.9.0 起" - -默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。 +#### client_subnet -如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。 +!!! failure "已在 sing-box 1.11.0 废弃" -将覆盖 `dns.client_subnet` 与 `servers.[].client_subnet`。 + 已移动到 [DNS 规则动作](../rule_action#route). ### 地址筛选字段 @@ -420,8 +473,12 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 #### mode +==必填== + `and` 或 `or` #### rules -包括的规则。 +==必填== + +包括的规则。 \ No newline at end of file diff --git a/docs/configuration/route/rule.md b/docs/configuration/route/rule.md index fe40d5651..43954a78c 100644 --- a/docs/configuration/route/rule.md +++ b/docs/configuration/route/rule.md @@ -5,7 +5,10 @@ icon: material/new-box !!! quote "Changes in sing-box 1.11.0" :material-plus: [action](#action) - :material-alert: [outbound](#outbound) + :material-alert: [outbound](#outbound) + :material-plus: [network_type](#network_type) + :material-plus: [network_is_expensive](#network_is_expensive) + :material-plus: [network_is_constrained](#network_is_constrained) !!! quote "Changes in sing-box 1.10.0" @@ -120,6 +123,11 @@ icon: material/new-box 1000 ], "clash_mode": "direct", + "network_type": [ + "wifi" + ], + "network_is_expensive": false, + "network_is_constrained": false, "wifi_ssid": [ "My WIFI" ], @@ -322,6 +330,39 @@ Match user id. Match Clash mode. +#### network_type + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms. + +Match network type. + +Available values: `wifi`, `cellular`, `ethernet` and `other`. + +#### network_is_expensive + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms. + +Match if network is considered Metered (on Android) or considered expensive, +such as Cellular or a Personal Hotspot (on Apple platforms). + +#### network_is_constrained + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Apple platforms. + +Match if network is in Low Data Mode. + #### wifi_ssid !!! quote "" diff --git a/docs/configuration/route/rule.zh.md b/docs/configuration/route/rule.zh.md index 316339f67..e5c5f0177 100644 --- a/docs/configuration/route/rule.zh.md +++ b/docs/configuration/route/rule.zh.md @@ -5,7 +5,10 @@ icon: material/new-box !!! quote "sing-box 1.11.0 中的更改" :material-plus: [action](#action) - :material-alert: [outbound](#outbound) + :material-alert: [outbound](#outbound) + :material-plus: [network_type](#network_type) + :material-plus: [network_is_expensive](#network_is_expensive) + :material-plus: [network_is_constrained](#network_is_constrained) !!! quote "sing-box 1.10.0 中的更改" @@ -13,7 +16,6 @@ icon: material/new-box :material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) :material-plus: [process_path_regex](#process_path_regex) - !!! quote "sing-box 1.8.0 中的更改" :material-plus: [rule_set](#rule_set) @@ -118,6 +120,11 @@ icon: material/new-box 1000 ], "clash_mode": "direct", + "network_type": [ + "wifi" + ], + "network_is_expensive": false, + "network_is_constrained": false, "wifi_ssid": [ "My WIFI" ], @@ -153,7 +160,7 @@ icon: material/new-box 当内容只有一项时,可以忽略 JSON 数组 [] 标签。 -### Default Fields +### 默认字段 !!! note "" @@ -320,6 +327,39 @@ icon: material/new-box 匹配 Clash 模式。 +#### network_type + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +匹配网络类型。 + +Available values: `wifi`, `cellular`, `ethernet` and `other`. + +#### network_is_expensive + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +匹配如果网络被视为计费 (在 Android) 或被视为昂贵, +像蜂窝网络或个人热点 (在 Apple 平台)。 + +#### network_is_constrained + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Apple 平台图形客户端中支持。 + +匹配如果网络在低数据模式下。 + #### wifi_ssid !!! quote "" @@ -366,13 +406,13 @@ icon: material/new-box ==必填== -参阅 [规则行动](../rule_action/)。 +参阅 [规则动作](../rule_action/)。 #### outbound !!! failure "已在 sing-box 1.11.0 废弃" - 已移动到 [规则行动](../rule_action#route). + 已移动到 [规则动作](../rule_action#route). ### 逻辑字段 diff --git a/docs/configuration/rule-set/adguard.zh.md b/docs/configuration/rule-set/adguard.zh.md new file mode 100644 index 000000000..b08ab34b4 --- /dev/null +++ b/docs/configuration/rule-set/adguard.zh.md @@ -0,0 +1,70 @@ +--- +icon: material/new-box +--- + +# AdGuard DNS Filter + +!!! question "自 sing-box 1.10.0 起" + +sing-box 支持其他项目的一些规则集格式,这些格式无法完全转换为 sing-box, +目前只有 AdGuard DNS Filter。 + +这些格式不直接作为源格式支持, +而是需要将它们转换为二进制规则集。 + +## 转换 + +使用 `sing-box rule-set convert --type adguard [--output .srs] .txt` 以转换为二进制规则集。 + +## 性能 + +AdGuard 将所有规则保存在内存中并按顺序匹配, +而 sing-box 选择高性能和较小的内存使用量。 +作为权衡,您无法知道匹配了哪个规则项。 + +## 兼容性 + +[AdGuardSDNSFilter](https://github.com/AdguardTeam/AdGuardSDNSFilter) +中的几乎所有规则以及 [adguard-filter-list](https://github.com/ppfeufer/adguard-filter-list) +中列出的规则集中的规则均受支持。 + +## 支持的格式 + +### AdGuard Filter + +#### 基本规则语法 + +| 语法 | 支持 | +|--------|------------------| +| `@@` | :material-check: | +| `\|\|` | :material-check: | +| `\|` | :material-check: | +| `^` | :material-check: | +| `*` | :material-check: | + +#### 主机语法 + +| 语法 | 示例 | 支持 | +|-------------|--------------------------|--------------------------| +| Scheme | `https://` | :material-alert: Ignored | +| Domain Host | `example.org` | :material-check: | +| IP Host | `1.1.1.1`, `10.0.0.` | :material-close: | +| Regexp | `/regexp/` | :material-check: | +| Port | `example.org:80` | :material-close: | +| Path | `example.org/path/ad.js` | :material-close: | + +#### 描述符语法 + +| 描述符 | 支持 | +|-----------------------|--------------------------| +| `$important` | :material-check: | +| `$dnsrewrite=0.0.0.0` | :material-alert: Ignored | +| 任何其他描述符 | :material-close: | + +### Hosts + +只有 IP 地址为 `0.0.0.0` 的条目将被接受。 + +### 简易 + +当所有行都是有效域时,它们被视为简单的逐行域规则, 与 hosts 一样,只匹配完全相同的域。 \ No newline at end of file diff --git a/docs/configuration/rule-set/headless-rule.md b/docs/configuration/rule-set/headless-rule.md index 891b12784..bdad22f0d 100644 --- a/docs/configuration/rule-set/headless-rule.md +++ b/docs/configuration/rule-set/headless-rule.md @@ -1,3 +1,13 @@ +--- +icon: material/new-box +--- + +!!! quote "Changes in sing-box 1.11.0" + + :material-plus: [network_type](#network_type) + :material-plus: [network_is_expensive](#network_is_expensive) + :material-plus: [network_is_constrained](#network_is_constrained) + ### Structure !!! question "Since sing-box 1.8.0" @@ -63,6 +73,11 @@ "package_name": [ "com.termux" ], + "network_type": [ + "wifi" + ], + "network_is_expensive": false, + "network_is_constrained": false, "wifi_ssid": [ "My WIFI" ], @@ -177,6 +192,39 @@ Match process path using regular expression. Match android package name. +#### network_type + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms. + +Match network type. + +Available values: `wifi`, `cellular`, `ethernet` and `other`. + +#### network_is_expensive + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms. + +Match if network is considered Metered (on Android) or considered expensive, +such as Cellular or a Personal Hotspot (on Apple platforms). + +#### network_is_constrained + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Apple platforms. + +Match if network is in Low Data Mode. + #### wifi_ssid !!! quote "" diff --git a/docs/configuration/rule-set/headless-rule.zh.md b/docs/configuration/rule-set/headless-rule.zh.md new file mode 100644 index 000000000..c5281504f --- /dev/null +++ b/docs/configuration/rule-set/headless-rule.zh.md @@ -0,0 +1,258 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.11.0 中的更改" + + :material-plus: [network_type](#network_type) + :material-alert: [network_is_expensive](#network_is_expensive) + :material-alert: [network_is_constrained](#network_is_constrained) + +### 结构 + +!!! question "自 sing-box 1.8.0 起" + +```json +{ + "rules": [ + { + "query_type": [ + "A", + "HTTPS", + 32768 + ], + "network": [ + "tcp" + ], + "domain": [ + "test.com" + ], + "domain_suffix": [ + ".cn" + ], + "domain_keyword": [ + "test" + ], + "domain_regex": [ + "^stun\\..+" + ], + "source_ip_cidr": [ + "10.0.0.0/24", + "192.168.0.1" + ], + "ip_cidr": [ + "10.0.0.0/24", + "192.168.0.1" + ], + "source_port": [ + 12345 + ], + "source_port_range": [ + "1000:2000", + ":3000", + "4000:" + ], + "port": [ + 80, + 443 + ], + "port_range": [ + "1000:2000", + ":3000", + "4000:" + ], + "process_name": [ + "curl" + ], + "process_path": [ + "/usr/bin/curl" + ], + "process_path_regex": [ + "^/usr/bin/.+" + ], + "package_name": [ + "com.termux" + ], + "network_type": [ + "wifi" + ], + "network_is_expensive": false, + "network_is_constrained": false, + "wifi_ssid": [ + "My WIFI" + ], + "wifi_bssid": [ + "00:00:00:00:00:00" + ], + "invert": false + }, + { + "type": "logical", + "mode": "and", + "rules": [], + "invert": false + } + ] +} +``` + +!!! note "" + + 当内容只有一项时,可以忽略 JSON 数组 [] 标签。 + +### Default Fields + +!!! note "" + + 默认规则使用以下匹配逻辑: + (`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `ip_cidr`) && + (`port` || `port_range`) && + (`source_port` || `source_port_range`) && + `other fields` + +#### query_type + +DNS 查询类型。值可以为整数或者类型名称字符串。 + +#### network + +`tcp` 或 `udp`。 + +#### domain + +匹配完整域名。 + +#### domain_suffix + +匹配域名后缀。 + +#### domain_keyword + +匹配域名关键字。 + +#### domain_regex + +匹配域名正则表达式。 + +#### source_ip_cidr + +匹配源 IP CIDR。 + +#### ip_cidr + +匹配 IP CIDR。 + +#### source_port + +匹配源端口。 + +#### source_port_range + +匹配源端口范围。 + +#### port + +匹配端口。 + +#### port_range + +匹配端口范围。 + +#### process_name + +!!! quote "" + + 仅支持 Linux、Windows 和 macOS。 + +匹配进程名称。 + +#### process_path + +!!! quote "" + + 仅支持 Linux、Windows 和 macOS. + +匹配进程路径。 + +#### process_path_regex + +!!! question "自 sing-box 1.10.0 起" + +!!! quote "" + + 仅支持 Linux、Windows 和 macOS. + +使用正则表达式匹配进程路径。 + +#### package_name + +匹配 Android 应用包名。 + +#### network_type + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +匹配网络类型。 + +Available values: `wifi`, `cellular`, `ethernet` and `other`. + +#### network_is_expensive + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +匹配如果网络被视为计费 (在 Android) 或被视为昂贵, +像蜂窝网络或个人热点 (在 Apple 平台)。 + +#### network_is_constrained + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Apple 平台图形客户端中支持。 + +匹配如果网络在低数据模式下。 + +#### wifi_ssid + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +匹配 WiFi SSID。 + +#### wifi_bssid + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +#### invert + +反选匹配结果。 + +### 逻辑字段 + +#### type + +`logical` + +#### mode + +==必填== + +`and` 或 `or` + +#### rules + +==必填== + +包括的规则。 \ No newline at end of file diff --git a/docs/configuration/rule-set/index.md b/docs/configuration/rule-set/index.md index dfe71d9e3..bed3fb546 100644 --- a/docs/configuration/rule-set/index.md +++ b/docs/configuration/rule-set/index.md @@ -74,7 +74,7 @@ Tag of rule-set. ==Required== -List of [Headless Rule](./headless-rule.md/). +List of [Headless Rule](../headless-rule/). ### Local or Remote Fields diff --git a/docs/configuration/rule-set/index.zh.md b/docs/configuration/rule-set/index.zh.md new file mode 100644 index 000000000..083c06bd3 --- /dev/null +++ b/docs/configuration/rule-set/index.zh.md @@ -0,0 +1,117 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.10.0 中的更改" + + :material-plus: `type: inline` + +# 规则集 + +!!! question "自 sing-box 1.8.0 起" + +### 结构 + +=== "内联" + + !!! question "自 sing-box 1.10.0 起" + + ```json + { + "type": "inline", // 可选 + "tag": "", + "rules": [] + } + ``` + +=== "本地文件" + + ```json + { + "type": "local", + "tag": "", + "format": "source", // or binary + "path": "" + } + ``` + +=== "远程文件" + + !!! info "" + + 远程规则集将被缓存如果 `experimental.cache_file.enabled` 已启用。 + + ```json + { + "type": "remote", + "tag": "", + "format": "source", // or binary + "url": "", + "download_detour": "", // 可选 + "update_interval": "" // 可选 + } + ``` + +### 字段 + +#### type + +==必填== + +规则集类型, `local` 或 `remote`。 + +#### tag + +==必填== + +规则集的标签。 + +### 内联字段 + +!!! question "自 sing-box 1.10.0 起" + +#### rules + +==必填== + +一组 [无头规则](../headless-rule/). + +### 本地或远程字段 + +#### format + +==必填== + +规则集格式, `source` 或 `binary`。 + +### 本地字段 + +#### path + +==必填== + +!!! note "" + + 自 sing-box 1.10.0 起,文件更改时将自动重新加载。 + +规则集的文件路径。 + +### 远程字段 + +#### url + +==必填== + +规则集的下载 URL。 + +#### download_detour + +用于下载规则集的出站的标签。 + +如果为空,将使用默认出站。 + +#### update_interval + +规则集的更新间隔。 + +默认使用 `1d`。 diff --git a/docs/configuration/rule-set/source-format.md b/docs/configuration/rule-set/source-format.md index 8c46cbf02..43e8ea453 100644 --- a/docs/configuration/rule-set/source-format.md +++ b/docs/configuration/rule-set/source-format.md @@ -4,6 +4,10 @@ icon: material/new-box # Source Format +!!! quote "Changes in sing-box 1.11.0" + + :material-plus: version `3` + !!! quote "Changes in sing-box 1.10.0" :material-plus: version `2` @@ -14,7 +18,7 @@ icon: material/new-box ```json { - "version": 2, + "version": 3, "rules": [] } ``` @@ -29,19 +33,14 @@ Use `sing-box rule-set compile [--output .srs] .json` to c ==Required== -Version of rule-set, one of `1` or `2`. - -* 1: Initial rule-set version, since sing-box 1.8.0. -* 2: Optimized memory usages of `domain_suffix` rules. - -The new rule-set version `2` does not make any changes to the format, only affecting `binary` rule-sets compiled by command `rule-set compile` - -Since 1.10.0, the optimization is always applied to `source` rule-sets even if version is set to `1`. +Version of rule-set. -It is recommended to upgrade to `2` after sing-box 1.10.0 becomes a stable version. +* 1: sing-box 1.8.0: Initial rule-set version. +* 2: sing-box 1.10.0: Optimized memory usages of `domain_suffix` rules in binary rule-sets. +* 3: sing-box 1.11.0: Added `network_type`, `network_is_expensive` and `network_is_constrainted` rule items. #### rules ==Required== -List of [Headless Rule](./headless-rule.md/). +List of [Headless Rule](../headless-rule/). diff --git a/docs/configuration/rule-set/source-format.zh.md b/docs/configuration/rule-set/source-format.zh.md new file mode 100644 index 000000000..0e8fc0cb9 --- /dev/null +++ b/docs/configuration/rule-set/source-format.zh.md @@ -0,0 +1,46 @@ +--- +icon: material/new-box +--- + +# 源文件格式 + +!!! quote "sing-box 1.11.0 中的更改" + + :material-plus: version `3` + +!!! quote "sing-box 1.10.0 中的更改" + + :material-plus: version `2` + +!!! question "自 sing-box 1.8.0 起" + +### 结构 + +```json +{ + "version": 3, + "rules": [] +} +``` + +### 编译 + +使用 `sing-box rule-set compile [--output .srs] .json` 以编译源文件为二进制规则集。 + +### 字段 + +#### version + +==必填== + +规则集版本。 + +* 1: sing-box 1.8.0: 初始规则集版本。 +* 2: sing-box 1.10.0: 优化了二进制规则集中 `domain_suffix` 规则的内存使用。 +* 3: sing-box 1.11.0: 添加了 `network_type`、 `network_is_expensive` 和 `network_is_constrainted` 规则项。 + +#### rules + +==必填== + +一组 [无头规则](../headless-rule/). From 26064a9fdc53b90612bfb4f0b86aa339a2f3ac6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 11 Nov 2024 19:33:54 +0800 Subject: [PATCH 28/39] documentation: Bump version --- docs/changelog.md | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 55892e871..e4f4523f3 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,10 +2,28 @@ icon: material/alert-decagram --- -#### 1.11.0-alpha.11 +#### 1.11.0-alpha.12 +* Merge route options to route actions **1** +* Add `network_type`, `network_is_expensive` and `network_is_constrainted` rule items **2** * Fixes and improvements +**1**: + +Route options in DNS route actions will no longer be considered deprecated, +see [DNS Route Action](/configuration/dns/rule_action/). + +Also, now `udp_disable_domain_unmapping` and `udp_connect` can also be configured in route action, +see [Route Action](/configuration/route/rule_action/). + +**2**: + +When using in graphical clients, new routing rule items allow you to match on +network type (WIFI, cellular, etc.), whether the network is expensive, and whether Low Data Mode is enabled. + +See [Route Rule](/configuration/route/rule/), [DNS Route Rule](/configuration/dns/rule/) +and [Headless Rule](/configuration/rule-set/headless-rule/). + #### 1.11.0-alpha.9 * Improve tun compatibility **1** @@ -32,7 +50,7 @@ See [Rule](/configuration/route/rule/), [DNS Rule Action](/configuration/dns/rule_action/). For migration, see -[Migrate legacy special outbounds to rule actions](/migration/#migrate-legacy-special-outbounds-to-rule-actions), +[Migrate legacy special outbounds to rule actions](/migration/#migrate-legacy-special-outbounds-to-rule-actions), [Migrate legacy inbound fields to rule actions](/migration/#migrate-legacy-inbound-fields-to-rule-actions) and [Migrate legacy DNS route options to rule actions](/migration/#migrate-legacy-dns-route-options-to-rule-actions). From 0d1b3226cd250b68ed49a3ef89460a5ef36c22d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 12 Nov 2024 11:24:21 +0800 Subject: [PATCH 29/39] Fix match rules --- go.mod | 4 ++-- go.sum | 8 ++++---- route/route.go | 45 +++++++++++++-------------------------------- 3 files changed, 19 insertions(+), 38 deletions(-) diff --git a/go.mod b/go.mod index aaa1b0649..626d2da78 100644 --- a/go.mod +++ b/go.mod @@ -25,14 +25,14 @@ require ( github.com/sagernet/gvisor v0.0.0-20241021032506-a4324256e4a3 github.com/sagernet/quic-go v0.48.1-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.6.0-alpha.4 + github.com/sagernet/sing v0.6.0-alpha.5 github.com/sagernet/sing-dns v0.4.0-alpha.1 github.com/sagernet/sing-mux v0.3.0-alpha.1 github.com/sagernet/sing-quic v0.3.0-rc.2 github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.1.4 - github.com/sagernet/sing-tun v0.6.0-alpha.5 + github.com/sagernet/sing-tun v0.6.0-alpha.6 github.com/sagernet/sing-vmess v0.1.12 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/utls v1.6.7 diff --git a/go.sum b/go.sum index 44f61a99b..88bb91b52 100644 --- a/go.sum +++ b/go.sum @@ -110,8 +110,8 @@ github.com/sagernet/quic-go v0.48.1-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= -github.com/sagernet/sing v0.6.0-alpha.4 h1:h9oshzhaY0ESPC9HERcXtT9MhK7Oyo/IWXVu1uIiw3Y= -github.com/sagernet/sing v0.6.0-alpha.4/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.6.0-alpha.5 h1:AQHBy2It7mSefP9eaic03yymaxPA2IPsPIClLT+IM0I= +github.com/sagernet/sing v0.6.0-alpha.5/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-dns v0.4.0-alpha.1 h1:2KlP8DeqtGkULFiZtvG2r7SuoJP6orANFzJwC5vDKvg= github.com/sagernet/sing-dns v0.4.0-alpha.1/go.mod h1:vgHATsm4wdymwpvBZPei8RY+546iGXS6hlWv2x6YKcM= github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg= @@ -124,8 +124,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= -github.com/sagernet/sing-tun v0.6.0-alpha.5 h1:N2m+tzpVgeDxsQzc3tq3Bge2uSRhJ72KXy4Zr03U+Cg= -github.com/sagernet/sing-tun v0.6.0-alpha.5/go.mod h1:5FKJNou4ZfW3HhLoSpRRUc8RT+nsdFTvhJc+4MlBrOo= +github.com/sagernet/sing-tun v0.6.0-alpha.6 h1:eEWtQlMlTt8cUsPQQKSRTdystdzPnDL9mAPY7K9ctAk= +github.com/sagernet/sing-tun v0.6.0-alpha.6/go.mod h1:FuVNb/9YqhcWRRZOkJtpoCvB+pY1lByaNTJmzyNIB3g= github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg= github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= diff --git a/route/route.go b/route/route.go index ddf523586..c4907663c 100644 --- a/route/route.go +++ b/route/route.go @@ -87,7 +87,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad if deadline.NeedAdditionalReadDeadline(conn) { conn = deadline.NewConn(conn) } - selectedRule, _, buffers, _, err := r.matchRule(ctx, &metadata, false, conn, nil, -1) + selectedRule, _, buffers, _, err := r.matchRule(ctx, &metadata, false, conn, nil) if err != nil { return err } @@ -223,7 +223,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m conn = deadline.NewPacketConn(bufio.NewNetPacketConn(conn)) }*/ - selectedRule, _, _, packetBuffers, err := r.matchRule(ctx, &metadata, false, nil, conn, -1) + selectedRule, _, _, packetBuffers, err := r.matchRule(ctx, &metadata, false, nil, conn) if err != nil { return err } @@ -288,7 +288,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m } func (r *Router) PreMatch(metadata adapter.InboundContext) error { - selectedRule, _, _, _, err := r.matchRule(r.ctx, &metadata, true, nil, nil, -1) + selectedRule, _, _, _, err := r.matchRule(r.ctx, &metadata, true, nil, nil) if err != nil { return err } @@ -304,7 +304,7 @@ func (r *Router) PreMatch(metadata adapter.InboundContext) error { func (r *Router) matchRule( ctx context.Context, metadata *adapter.InboundContext, preMatch bool, - inputConn net.Conn, inputPacketConn N.PacketConn, ruleIndex int, + inputConn net.Conn, inputPacketConn N.PacketConn, ) ( selectedRule adapter.Rule, selectedRuleIndex int, buffers []*buf.Buffer, packetBuffers []*N.PacketBuffer, fatalErr error, @@ -399,24 +399,9 @@ func (r *Router) matchRule( } match: - for ruleIndex < len(r.rules) { - rules := r.rules - if ruleIndex != -1 { - rules = rules[ruleIndex+1:] - } - var ( - currentRule adapter.Rule - currentRuleIndex int - matched bool - ) - for currentRuleIndex, currentRule = range rules { - if currentRule.Match(metadata) { - matched = true - break - } - } - if !matched { - break + for currentRuleIndex, currentRule := range r.rules { + if !currentRule.Match(metadata) { + continue } if !preMatch { ruleDescription := currentRule.String() @@ -440,9 +425,6 @@ match: case *rule.RuleActionRoute: metadata.UDPDisableDomainUnmapping = action.UDPDisableDomainUnmapping metadata.UDPConnect = action.UDPConnect - selectedRule = currentRule - selectedRuleIndex = currentRuleIndex - break match case *rule.RuleActionRouteOptions: metadata.UDPDisableDomainUnmapping = action.UDPDisableDomainUnmapping metadata.UDPConnect = action.UDPConnect @@ -468,17 +450,16 @@ match: if fatalErr != nil { return } - default: + } + actionType := currentRule.Action().Type() + if actionType == C.RuleActionTypeRoute || + actionType == C.RuleActionTypeReject || + actionType == C.RuleActionTypeHijackDNS || + (actionType == C.RuleActionTypeSniff && preMatch) { selectedRule = currentRule selectedRuleIndex = currentRuleIndex break match } - if ruleIndex == -1 { - ruleIndex = currentRuleIndex - } else { - ruleIndex += currentRuleIndex - } - ruleIndex++ } if !preMatch && metadata.Destination.Addr.IsUnspecified() { newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{}, inputConn, inputPacketConn) From c0e48f865eccdf93a82cfe4627a5eba0b6fac6e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 12 Nov 2024 11:25:30 +0800 Subject: [PATCH 30/39] documentation: Bump version --- docs/changelog.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index e4f4523f3..4a3716a01 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,10 @@ icon: material/alert-decagram --- +#### 1.11.0-alpha.13 + +* Fixes and improvements + #### 1.11.0-alpha.12 * Merge route options to route actions **1** From 7f99cab8934165b3a008cb622c6815ccb809fddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 12 Nov 2024 12:56:31 +0800 Subject: [PATCH 31/39] Downgrade NDK to 26.2.11394342 --- cmd/internal/build_shared/sdk.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/internal/build_shared/sdk.go b/cmd/internal/build_shared/sdk.go index e089a5250..a664abee5 100644 --- a/cmd/internal/build_shared/sdk.go +++ b/cmd/internal/build_shared/sdk.go @@ -58,7 +58,7 @@ func FindSDK() { } func findNDK() bool { - const fixedVersion = "26.3.11579264" + const fixedVersion = "26.2.11394342" const versionFile = "source.properties" if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) { androidNDKPath = fixedPath From edf40da07ca79845618c058a9203fe9f9c11d437 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 12 Nov 2024 14:37:14 +0800 Subject: [PATCH 32/39] Fix check interface --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 626d2da78..f2683f84e 100644 --- a/go.mod +++ b/go.mod @@ -25,14 +25,14 @@ require ( github.com/sagernet/gvisor v0.0.0-20241021032506-a4324256e4a3 github.com/sagernet/quic-go v0.48.1-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.6.0-alpha.5 + github.com/sagernet/sing v0.6.0-alpha.6 github.com/sagernet/sing-dns v0.4.0-alpha.1 github.com/sagernet/sing-mux v0.3.0-alpha.1 github.com/sagernet/sing-quic v0.3.0-rc.2 github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.1.4 - github.com/sagernet/sing-tun v0.6.0-alpha.6 + github.com/sagernet/sing-tun v0.6.0-alpha.7 github.com/sagernet/sing-vmess v0.1.12 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/utls v1.6.7 diff --git a/go.sum b/go.sum index 88bb91b52..f1f16aea9 100644 --- a/go.sum +++ b/go.sum @@ -110,8 +110,8 @@ github.com/sagernet/quic-go v0.48.1-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= -github.com/sagernet/sing v0.6.0-alpha.5 h1:AQHBy2It7mSefP9eaic03yymaxPA2IPsPIClLT+IM0I= -github.com/sagernet/sing v0.6.0-alpha.5/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.6.0-alpha.6 h1:R0abM8ZeazyAKo9d3DNxtrgW17g3tZAD8al7O5+ADOw= +github.com/sagernet/sing v0.6.0-alpha.6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-dns v0.4.0-alpha.1 h1:2KlP8DeqtGkULFiZtvG2r7SuoJP6orANFzJwC5vDKvg= github.com/sagernet/sing-dns v0.4.0-alpha.1/go.mod h1:vgHATsm4wdymwpvBZPei8RY+546iGXS6hlWv2x6YKcM= github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg= @@ -124,8 +124,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= -github.com/sagernet/sing-tun v0.6.0-alpha.6 h1:eEWtQlMlTt8cUsPQQKSRTdystdzPnDL9mAPY7K9ctAk= -github.com/sagernet/sing-tun v0.6.0-alpha.6/go.mod h1:FuVNb/9YqhcWRRZOkJtpoCvB+pY1lByaNTJmzyNIB3g= +github.com/sagernet/sing-tun v0.6.0-alpha.7 h1:h5Fqg+H5VggJq/LGdc/hOctNEcYAdkmfKY83lYIDHUg= +github.com/sagernet/sing-tun v0.6.0-alpha.7/go.mod h1:JkgiLLnQUXln1zLGVoJqUwAulJGT0xoiPU4/pYF1fhU= github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg= github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= From 8cb11bf322be37ca0541aacbab65de0c111def01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 13 Nov 2024 10:38:46 +0800 Subject: [PATCH 33/39] Fix rule match --- route/route.go | 1 + 1 file changed, 1 insertion(+) diff --git a/route/route.go b/route/route.go index c4907663c..1c4da4b7a 100644 --- a/route/route.go +++ b/route/route.go @@ -400,6 +400,7 @@ func (r *Router) matchRule( match: for currentRuleIndex, currentRule := range r.rules { + metadata.ResetRuleCache() if !currentRule.Match(metadata) { continue } From f361c838050c27a314328a74da90fe7125940692 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 12 Nov 2024 19:37:10 +0800 Subject: [PATCH 34/39] Add multi network dialing --- adapter/inbound.go | 4 + adapter/network.go | 14 +- adapter/outbound/default.go | 116 ++----------- common/dialer/default.go | 173 +++++++++++++------ common/dialer/default_go1.20.go | 4 + common/dialer/default_nongo1.20.go | 4 + common/dialer/default_parallel_interface.go | 181 ++++++++++++++++++++ common/dialer/default_parallel_network.go | 122 +++++++++++++ common/dialer/dialer.go | 36 ++++ common/dialer/resolve.go | 89 +++++++++- common/settings/proxy_darwin.go | 10 +- constant/network.go | 42 +++++ experimental/libbox/monitor.go | 4 +- go.mod | 4 +- go.sum | 8 +- option/outbound.go | 32 ++-- option/route.go | 24 +-- option/rule_action.go | 14 +- option/types.go | 21 +++ protocol/direct/outbound.go | 131 +++++++++++--- protocol/socks/outbound.go | 21 --- protocol/wireguard/outbound.go | 13 -- route/network.go | 121 +++++++------ route/route.go | 4 + route/rule/rule_action.go | 6 + transport/dhcp/server.go | 2 +- 26 files changed, 887 insertions(+), 313 deletions(-) create mode 100644 common/dialer/default_parallel_interface.go create mode 100644 common/dialer/default_parallel_network.go diff --git a/adapter/inbound.go b/adapter/inbound.go index 7932237d0..33b1b4d16 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -3,8 +3,10 @@ package adapter import ( "context" "net/netip" + "time" "github.com/sagernet/sing-box/common/process" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" M "github.com/sagernet/sing/common/metadata" @@ -66,6 +68,8 @@ type InboundContext struct { InboundOptions option.InboundOptions UDPDisableDomainUnmapping bool UDPConnect bool + NetworkStrategy C.NetworkStrategy + FallbackDelay time.Duration DNSServer string diff --git a/adapter/network.go b/adapter/network.go index 6c09a0a3e..dd924c339 100644 --- a/adapter/network.go +++ b/adapter/network.go @@ -1,6 +1,9 @@ package adapter import ( + "time" + + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common/control" ) @@ -11,10 +14,10 @@ type NetworkManager interface { UpdateInterfaces() error DefaultNetworkInterface() *NetworkInterface NetworkInterfaces() []NetworkInterface - DefaultInterface() string AutoDetectInterface() bool AutoDetectInterfaceFunc() control.Func - DefaultMark() uint32 + ProtectFunc() control.Func + DefaultOptions() NetworkOptions RegisterAutoRedirectOutputMark(mark uint32) error AutoRedirectOutputMark() uint32 NetworkMonitor() tun.NetworkUpdateMonitor @@ -24,6 +27,13 @@ type NetworkManager interface { ResetNetwork() } +type NetworkOptions struct { + DefaultNetworkStrategy C.NetworkStrategy + DefaultFallbackDelay time.Duration + DefaultInterface string + DefaultMark uint32 +} + type InterfaceUpdateListener interface { InterfaceUpdated() } diff --git a/adapter/outbound/default.go b/adapter/outbound/default.go index bb58ff54d..84be8abaf 100644 --- a/adapter/outbound/default.go +++ b/adapter/outbound/default.go @@ -8,8 +8,8 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/dialer" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" @@ -25,35 +25,11 @@ func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata a var outConn net.Conn var err error if len(metadata.DestinationAddresses) > 0 { - outConn, err = N.DialSerial(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses) - } else { - outConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination) - } - if err != nil { - return N.ReportHandshakeFailure(conn, err) - } - err = N.ReportConnHandshakeSuccess(conn, outConn) - if err != nil { - outConn.Close() - return err - } - return CopyEarlyConn(ctx, conn, outConn) -} - -func NewDirectConnection(ctx context.Context, router adapter.Router, this N.Dialer, conn net.Conn, metadata adapter.InboundContext, domainStrategy dns.DomainStrategy) error { - defer conn.Close() - ctx = adapter.WithContext(ctx, &metadata) - var outConn net.Conn - var err error - if len(metadata.DestinationAddresses) > 0 { - outConn, err = N.DialSerial(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses) - } else if metadata.Destination.IsFqdn() { - var destinationAddresses []netip.Addr - destinationAddresses, err = router.Lookup(ctx, metadata.Destination.Fqdn, domainStrategy) - if err != nil { - return N.ReportHandshakeFailure(conn, err) + if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer { + outConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.FallbackDelay) + } else { + outConn, err = N.DialSerial(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses) } - outConn, err = N.DialSerial(ctx, this, N.NetworkTCP, metadata.Destination, destinationAddresses) } else { outConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination) } @@ -79,7 +55,11 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, ) if metadata.UDPConnect { if len(metadata.DestinationAddresses) > 0 { - outConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses) + if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer { + outConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.FallbackDelay) + } else { + outConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses) + } } else { outConn, err = this.DialContext(ctx, N.NetworkUDP, metadata.Destination) } @@ -93,7 +73,11 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, } } else { if len(metadata.DestinationAddresses) > 0 { - outPacketConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses) + if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer { + outPacketConn, destinationAddress, err = dialer.ListenSerialNetworkPacket(ctx, parallelDialer, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.FallbackDelay) + } else { + outPacketConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses) + } } else { outPacketConn, err = this.ListenPacket(ctx, metadata.Destination) } @@ -129,76 +113,6 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outPacketConn)) } -func NewDirectPacketConnection(ctx context.Context, router adapter.Router, this N.Dialer, conn N.PacketConn, metadata adapter.InboundContext, domainStrategy dns.DomainStrategy) error { - defer conn.Close() - ctx = adapter.WithContext(ctx, &metadata) - var ( - outPacketConn net.PacketConn - outConn net.Conn - destinationAddress netip.Addr - err error - ) - if metadata.UDPConnect { - if len(metadata.DestinationAddresses) > 0 { - outConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses) - } else if metadata.Destination.IsFqdn() { - var destinationAddresses []netip.Addr - destinationAddresses, err = router.Lookup(ctx, metadata.Destination.Fqdn, domainStrategy) - if err != nil { - return N.ReportHandshakeFailure(conn, err) - } - outConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, destinationAddresses) - } else { - outConn, err = this.DialContext(ctx, N.NetworkUDP, metadata.Destination) - } - if err != nil { - return N.ReportHandshakeFailure(conn, err) - } - connRemoteAddr := M.AddrFromNet(outConn.RemoteAddr()) - if connRemoteAddr != metadata.Destination.Addr { - destinationAddress = connRemoteAddr - } - } else { - if len(metadata.DestinationAddresses) > 0 { - outPacketConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses) - } else if metadata.Destination.IsFqdn() { - var destinationAddresses []netip.Addr - destinationAddresses, err = router.Lookup(ctx, metadata.Destination.Fqdn, domainStrategy) - if err != nil { - return N.ReportHandshakeFailure(conn, err) - } - outPacketConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, destinationAddresses) - } else { - outPacketConn, err = this.ListenPacket(ctx, metadata.Destination) - } - if err != nil { - return N.ReportHandshakeFailure(conn, err) - } - } - err = N.ReportPacketConnHandshakeSuccess(conn, outPacketConn) - if err != nil { - outPacketConn.Close() - return err - } - if destinationAddress.IsValid() { - if metadata.Destination.IsFqdn() { - outPacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination) - } - if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded { - natConn.UpdateDestination(destinationAddress) - } - } - switch metadata.Protocol { - case C.ProtocolSTUN: - ctx, conn = canceler.NewPacketConn(ctx, conn, C.STUNTimeout) - case C.ProtocolQUIC: - ctx, conn = canceler.NewPacketConn(ctx, conn, C.QUICTimeout) - case C.ProtocolDNS: - ctx, conn = canceler.NewPacketConn(ctx, conn, C.DNSTimeout) - } - return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outPacketConn)) -} - func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) error { if cachedReader, isCached := conn.(N.CachedReader); isCached { payload := cachedReader.ReadCached() diff --git a/common/dialer/default.go b/common/dialer/default.go index a4e4290e5..f59bfbbf2 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -16,60 +16,85 @@ import ( N "github.com/sagernet/sing/common/network" ) -var _ WireGuardListener = (*DefaultDialer)(nil) +var ( + _ ParallelInterfaceDialer = (*DefaultDialer)(nil) + _ WireGuardListener = (*DefaultDialer)(nil) +) type DefaultDialer struct { - dialer4 tcpDialer - dialer6 tcpDialer - udpDialer4 net.Dialer - udpDialer6 net.Dialer - udpListener net.ListenConfig - udpAddr4 string - udpAddr6 string - isWireGuardListener bool + dialer4 tcpDialer + dialer6 tcpDialer + udpDialer4 net.Dialer + udpDialer6 net.Dialer + udpListener net.ListenConfig + udpAddr4 string + udpAddr6 string + isWireGuardListener bool + networkManager adapter.NetworkManager + networkStrategy C.NetworkStrategy + networkFallbackDelay time.Duration } func NewDefault(networkManager adapter.NetworkManager, options option.DialerOptions) (*DefaultDialer, error) { - var dialer net.Dialer - var listener net.ListenConfig + var ( + dialer net.Dialer + listener net.ListenConfig + interfaceFinder control.InterfaceFinder + networkStrategy C.NetworkStrategy + networkFallbackDelay time.Duration + ) + if networkManager != nil { + interfaceFinder = networkManager.InterfaceFinder() + } else { + interfaceFinder = control.NewDefaultInterfaceFinder() + } if options.BindInterface != "" { - var interfaceFinder control.InterfaceFinder - if networkManager != nil { - interfaceFinder = networkManager.InterfaceFinder() - } else { - interfaceFinder = control.NewDefaultInterfaceFinder() - } bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1) dialer.Control = control.Append(dialer.Control, bindFunc) listener.Control = control.Append(listener.Control, bindFunc) - } else if networkManager != nil && networkManager.AutoDetectInterface() { - bindFunc := networkManager.AutoDetectInterfaceFunc() - dialer.Control = control.Append(dialer.Control, bindFunc) - listener.Control = control.Append(listener.Control, bindFunc) - } else if networkManager != nil && networkManager.DefaultInterface() != "" { - bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), networkManager.DefaultInterface(), -1) - dialer.Control = control.Append(dialer.Control, bindFunc) - listener.Control = control.Append(listener.Control, bindFunc) - } - var autoRedirectOutputMark uint32 - if networkManager != nil { - autoRedirectOutputMark = networkManager.AutoRedirectOutputMark() - } - if autoRedirectOutputMark > 0 { - dialer.Control = control.Append(dialer.Control, control.RoutingMark(autoRedirectOutputMark)) - listener.Control = control.Append(listener.Control, control.RoutingMark(autoRedirectOutputMark)) } if options.RoutingMark > 0 { dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark)) listener.Control = control.Append(listener.Control, control.RoutingMark(options.RoutingMark)) + } + if networkManager != nil { + autoRedirectOutputMark := networkManager.AutoRedirectOutputMark() if autoRedirectOutputMark > 0 { - return nil, E.New("`auto_redirect` with `route_[_exclude]_address_set is conflict with `routing_mark`") + if options.RoutingMark > 0 { + return nil, E.New("`routing_mark` is conflict with `tun.auto_redirect` with `tun.route_[_exclude]_address_set") + } + dialer.Control = control.Append(dialer.Control, control.RoutingMark(autoRedirectOutputMark)) + listener.Control = control.Append(listener.Control, control.RoutingMark(autoRedirectOutputMark)) } - } else if networkManager != nil && networkManager.DefaultMark() > 0 { - dialer.Control = control.Append(dialer.Control, control.RoutingMark(networkManager.DefaultMark())) - listener.Control = control.Append(listener.Control, control.RoutingMark(networkManager.DefaultMark())) - if autoRedirectOutputMark > 0 { - return nil, E.New("`auto_redirect` with `route_[_exclude]_address_set is conflict with `default_mark`") + } + if C.NetworkStrategy(options.NetworkStrategy) != C.NetworkStrategyDefault { + if options.BindInterface != "" || options.Inet4BindAddress != nil || options.Inet6BindAddress != nil { + return nil, E.New("`network_strategy` is conflict with `bind_interface`, `inet4_bind_address` and `inet6_bind_address`") + } + networkStrategy = C.NetworkStrategy(options.NetworkStrategy) + networkFallbackDelay = time.Duration(options.NetworkFallbackDelay) + if networkManager == nil || !networkManager.AutoDetectInterface() { + return nil, E.New("`route.auto_detect_interface` is require by `network_strategy`") + } + } + if networkManager != nil && options.BindInterface == "" && options.Inet4BindAddress == nil && options.Inet6BindAddress == nil { + defaultOptions := networkManager.DefaultOptions() + if defaultOptions.DefaultInterface != "" { + bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.DefaultInterface, -1) + dialer.Control = control.Append(dialer.Control, bindFunc) + listener.Control = control.Append(listener.Control, bindFunc) + } else if networkManager.AutoDetectInterface() { + if defaultOptions.DefaultNetworkStrategy != C.NetworkStrategyDefault && C.NetworkStrategy(options.NetworkStrategy) == C.NetworkStrategyDefault { + networkStrategy = defaultOptions.DefaultNetworkStrategy + networkFallbackDelay = defaultOptions.DefaultFallbackDelay + bindFunc := networkManager.ProtectFunc() + dialer.Control = control.Append(dialer.Control, bindFunc) + listener.Control = control.Append(listener.Control, bindFunc) + } else { + bindFunc := networkManager.AutoDetectInterfaceFunc() + dialer.Control = control.Append(dialer.Control, bindFunc) + listener.Control = control.Append(listener.Control, bindFunc) + } } } if options.ReuseAddr { @@ -130,6 +155,9 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti listener.Control = control.Append(listener.Control, controlFn) } } + if networkStrategy != C.NetworkStrategyDefault && options.TCPFastOpen { + return nil, E.New("`tcp_fast_open` is conflict with `network_strategy` or `route.default_network_strategy`") + } tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen) if err != nil { return nil, err @@ -147,6 +175,9 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti udpAddr4, udpAddr6, options.IsWireGuardListener, + networkManager, + networkStrategy, + networkFallbackDelay, }, nil } @@ -154,33 +185,71 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address if !address.IsValid() { return nil, E.New("invalid address") } - switch N.NetworkName(network) { - case N.NetworkUDP: + if d.networkStrategy == C.NetworkStrategyDefault { + switch N.NetworkName(network) { + case N.NetworkUDP: + if !address.IsIPv6() { + return trackConn(d.udpDialer4.DialContext(ctx, network, address.String())) + } else { + return trackConn(d.udpDialer6.DialContext(ctx, network, address.String())) + } + } if !address.IsIPv6() { - return trackConn(d.udpDialer4.DialContext(ctx, network, address.String())) + return trackConn(DialSlowContext(&d.dialer4, ctx, network, address)) } else { - return trackConn(d.udpDialer6.DialContext(ctx, network, address.String())) + return trackConn(DialSlowContext(&d.dialer6, ctx, network, address)) } + } else { + return d.DialParallelInterface(ctx, network, address, d.networkStrategy, d.networkFallbackDelay) + } +} + +func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network string, address M.Socksaddr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) { + if strategy == C.NetworkStrategyDefault { + return d.DialContext(ctx, network, address) } - if !address.IsIPv6() { - return trackConn(DialSlowContext(&d.dialer4, ctx, network, address)) + if !d.networkManager.AutoDetectInterface() { + return nil, E.New("`route.auto_detect_interface` is require by `network_strategy`") + } + var dialer net.Dialer + if N.NetworkName(network) == N.NetworkTCP { + dialer = dialerFromTCPDialer(d.dialer4) } else { - return trackConn(DialSlowContext(&d.dialer6, ctx, network, address)) + dialer = d.udpDialer4 } + return trackConn(d.dialParallelInterface(ctx, dialer, network, address.String(), strategy, fallbackDelay)) } func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - if destination.IsIPv6() { - return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)) - } else if destination.IsIPv4() && !destination.Addr.IsUnspecified() { - return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4)) + if d.networkStrategy == C.NetworkStrategyDefault { + if destination.IsIPv6() { + return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)) + } else if destination.IsIPv4() && !destination.Addr.IsUnspecified() { + return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4)) + } else { + return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4)) + } } else { - return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4)) + return d.ListenSerialInterfacePacket(ctx, destination, d.networkStrategy, d.networkFallbackDelay) + } +} + +func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, error) { + if strategy == C.NetworkStrategyDefault { + return d.ListenPacket(ctx, destination) + } + if !d.networkManager.AutoDetectInterface() { + return nil, E.New("`route.auto_detect_interface` is require by `network_strategy`") + } + network := N.NetworkUDP + if destination.IsIPv4() && !destination.Addr.IsUnspecified() { + network += "4" } + return trackPacketConn(d.listenSerialInterfacePacket(ctx, d.udpListener, network, "", strategy, fallbackDelay)) } func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) { - return trackPacketConn(d.udpListener.ListenPacket(context.Background(), network, address)) + return trackPacketConn(d.listenSerialInterfacePacket(context.Background(), d.udpListener, network, address, d.networkStrategy, d.networkFallbackDelay)) } func trackConn(conn net.Conn, err error) (net.Conn, error) { diff --git a/common/dialer/default_go1.20.go b/common/dialer/default_go1.20.go index a9f7b612b..9dde955f8 100644 --- a/common/dialer/default_go1.20.go +++ b/common/dialer/default_go1.20.go @@ -13,3 +13,7 @@ type tcpDialer = tfo.Dialer func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) { return tfo.Dialer{Dialer: dialer, DisableTFO: !tfoEnabled}, nil } + +func dialerFromTCPDialer(dialer tcpDialer) net.Dialer { + return dialer.Dialer +} diff --git a/common/dialer/default_nongo1.20.go b/common/dialer/default_nongo1.20.go index 215024245..b2e4638d1 100644 --- a/common/dialer/default_nongo1.20.go +++ b/common/dialer/default_nongo1.20.go @@ -16,3 +16,7 @@ func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) { } return dialer, nil } + +func dialerFromTCPDialer(dialer tcpDialer) net.Dialer { + return dialer +} diff --git a/common/dialer/default_parallel_interface.go b/common/dialer/default_parallel_interface.go new file mode 100644 index 000000000..f35811f10 --- /dev/null +++ b/common/dialer/default_parallel_interface.go @@ -0,0 +1,181 @@ +package dialer + +import ( + "context" + "net" + "time" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing/common/control" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" + N "github.com/sagernet/sing/common/network" +) + +func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Dialer, network string, addr string, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) { + primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy) + if len(primaryInterfaces)+len(fallbackInterfaces) == 0 { + return nil, E.New("no available network interface") + } + if fallbackDelay == 0 { + fallbackDelay = N.DefaultFallbackDelay + } + returned := make(chan struct{}) + defer close(returned) + type dialResult struct { + net.Conn + error + primary bool + } + results := make(chan dialResult) // unbuffered + startRacer := func(ctx context.Context, primary bool, iif adapter.NetworkInterface) { + perNetDialer := dialer + perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index)) + conn, err := perNetDialer.DialContext(ctx, network, addr) + if err != nil { + select { + case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Name, ")"), primary: primary}: + case <-returned: + } + } else { + select { + case results <- dialResult{Conn: conn}: + case <-returned: + conn.Close() + } + } + } + primaryCtx, primaryCancel := context.WithCancel(ctx) + defer primaryCancel() + for _, iif := range primaryInterfaces { + go startRacer(primaryCtx, true, iif) + } + var ( + fallbackTimer *time.Timer + fallbackChan <-chan time.Time + ) + if len(fallbackInterfaces) > 0 { + fallbackTimer = time.NewTimer(fallbackDelay) + defer fallbackTimer.Stop() + fallbackChan = fallbackTimer.C + } + var errors []error + for { + select { + case <-fallbackChan: + fallbackCtx, fallbackCancel := context.WithCancel(ctx) + defer fallbackCancel() + for _, iif := range fallbackInterfaces { + go startRacer(fallbackCtx, false, iif) + } + case res := <-results: + if res.error == nil { + return res.Conn, nil + } + errors = append(errors, res.error) + if len(errors) == len(primaryInterfaces)+len(fallbackInterfaces) { + return nil, E.Errors(errors...) + } + if res.primary && fallbackTimer != nil && fallbackTimer.Stop() { + fallbackTimer.Reset(0) + } + } + } +} + +func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listener net.ListenConfig, network string, addr string, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, error) { + primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy) + if len(primaryInterfaces)+len(fallbackInterfaces) == 0 { + return nil, E.New("no available network interface") + } + if fallbackDelay == 0 { + fallbackDelay = N.DefaultFallbackDelay + } + var errors []error + for _, primaryInterface := range primaryInterfaces { + perNetListener := listener + perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, primaryInterface.Name, primaryInterface.Index)) + conn, err := perNetListener.ListenPacket(ctx, network, addr) + if err == nil { + return conn, nil + } + errors = append(errors, E.Cause(err, "listen ", primaryInterface.Name, " (", primaryInterface.Name, ")")) + } + for _, fallbackInterface := range fallbackInterfaces { + perNetListener := listener + perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, fallbackInterface.Name, fallbackInterface.Index)) + conn, err := perNetListener.ListenPacket(ctx, network, addr) + if err == nil { + return conn, nil + } + errors = append(errors, E.Cause(err, "listen ", fallbackInterface.Name, " (", fallbackInterface.Name, ")")) + } + return nil, E.Errors(errors...) +} + +func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkStrategy) (primaryInterfaces []adapter.NetworkInterface, fallbackInterfaces []adapter.NetworkInterface) { + interfaces := networkManager.NetworkInterfaces() + switch strategy { + case C.NetworkStrategyFallback: + defaultIf := networkManager.InterfaceMonitor().DefaultInterface() + if defaultIf != nil { + for _, iif := range interfaces { + if iif.Index == defaultIf.Index { + primaryInterfaces = append(primaryInterfaces, iif) + } else { + fallbackInterfaces = append(fallbackInterfaces, iif) + } + } + } else { + primaryInterfaces = interfaces + } + case C.NetworkStrategyHybrid: + primaryInterfaces = interfaces + case C.NetworkStrategyWIFI: + for _, iif := range interfaces { + if iif.Type == C.InterfaceTypeWIFI { + primaryInterfaces = append(primaryInterfaces, iif) + } else { + fallbackInterfaces = append(fallbackInterfaces, iif) + } + } + case C.NetworkStrategyCellular: + for _, iif := range interfaces { + if iif.Type == C.InterfaceTypeCellular { + primaryInterfaces = append(primaryInterfaces, iif) + } else { + fallbackInterfaces = append(fallbackInterfaces, iif) + } + } + case C.NetworkStrategyEthernet: + for _, iif := range interfaces { + if iif.Type == C.InterfaceTypeEthernet { + primaryInterfaces = append(primaryInterfaces, iif) + } else { + fallbackInterfaces = append(fallbackInterfaces, iif) + } + } + case C.NetworkStrategyWIFIOnly: + for _, iif := range interfaces { + if iif.Type == C.InterfaceTypeWIFI { + primaryInterfaces = append(primaryInterfaces, iif) + } + } + case C.NetworkStrategyCellularOnly: + for _, iif := range interfaces { + if iif.Type == C.InterfaceTypeCellular { + primaryInterfaces = append(primaryInterfaces, iif) + } + } + case C.NetworkStrategyEthernetOnly: + for _, iif := range interfaces { + if iif.Type == C.InterfaceTypeEthernet { + primaryInterfaces = append(primaryInterfaces, iif) + } + } + default: + panic(F.ToString("unknown network strategy: ", strategy)) + } + return primaryInterfaces, fallbackInterfaces +} diff --git a/common/dialer/default_parallel_network.go b/common/dialer/default_parallel_network.go new file mode 100644 index 000000000..f42d9330c --- /dev/null +++ b/common/dialer/default_parallel_network.go @@ -0,0 +1,122 @@ +package dialer + +import ( + "context" + "net" + "net/netip" + "time" + + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +func DialSerialNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) { + if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel { + return parallelDialer.DialParallelNetwork(ctx, network, destination, destinationAddresses, strategy, fallbackDelay) + } + var errors []error + for _, address := range destinationAddresses { + conn, err := dialer.DialParallelInterface(ctx, network, M.SocksaddrFrom(address, destination.Port), strategy, fallbackDelay) + if err == nil { + return conn, nil + } + errors = append(errors, err) + } + return nil, E.Errors(errors...) +} + +func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, preferIPv6 bool, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) { + if fallbackDelay == 0 { + fallbackDelay = N.DefaultFallbackDelay + } + + returned := make(chan struct{}) + defer close(returned) + + addresses4 := common.Filter(destinationAddresses, func(address netip.Addr) bool { + return address.Is4() || address.Is4In6() + }) + addresses6 := common.Filter(destinationAddresses, func(address netip.Addr) bool { + return address.Is6() && !address.Is4In6() + }) + if len(addresses4) == 0 || len(addresses6) == 0 { + return DialSerialNetwork(ctx, dialer, network, destination, destinationAddresses, strategy, fallbackDelay) + } + var primaries, fallbacks []netip.Addr + if preferIPv6 { + primaries = addresses6 + fallbacks = addresses4 + } else { + primaries = addresses4 + fallbacks = addresses6 + } + type dialResult struct { + net.Conn + error + primary bool + done bool + } + results := make(chan dialResult) // unbuffered + startRacer := func(ctx context.Context, primary bool) { + ras := primaries + if !primary { + ras = fallbacks + } + c, err := DialSerialNetwork(ctx, dialer, network, destination, ras, strategy, fallbackDelay) + select { + case results <- dialResult{Conn: c, error: err, primary: primary, done: true}: + case <-returned: + if c != nil { + c.Close() + } + } + } + var primary, fallback dialResult + primaryCtx, primaryCancel := context.WithCancel(ctx) + defer primaryCancel() + go startRacer(primaryCtx, true) + fallbackTimer := time.NewTimer(fallbackDelay) + defer fallbackTimer.Stop() + for { + select { + case <-fallbackTimer.C: + fallbackCtx, fallbackCancel := context.WithCancel(ctx) + defer fallbackCancel() + go startRacer(fallbackCtx, false) + + case res := <-results: + if res.error == nil { + return res.Conn, nil + } + if res.primary { + primary = res + } else { + fallback = res + } + if primary.done && fallback.done { + return nil, primary.error + } + if res.primary && fallbackTimer.Stop() { + fallbackTimer.Reset(0) + } + } + } +} + +func ListenSerialNetworkPacket(ctx context.Context, dialer ParallelInterfaceDialer, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) { + if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel { + return parallelDialer.ListenSerialNetworkPacket(ctx, destination, destinationAddresses, strategy, fallbackDelay) + } + var errors []error + for _, address := range destinationAddresses { + conn, err := dialer.ListenSerialInterfacePacket(ctx, M.SocksaddrFrom(address, destination.Port), strategy, fallbackDelay) + if err == nil { + return conn, address, nil + } + errors = append(errors, err) + } + return nil, netip.Addr{}, E.Errors(errors...) +} diff --git a/common/dialer/dialer.go b/common/dialer/dialer.go index 047a25146..b3305d739 100644 --- a/common/dialer/dialer.go +++ b/common/dialer/dialer.go @@ -2,12 +2,16 @@ package dialer import ( "context" + "net" + "net/netip" "time" "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-dns" E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/service" ) @@ -49,3 +53,35 @@ func New(ctx context.Context, options option.DialerOptions) (N.Dialer, error) { } return dialer, nil } + +func NewDirect(ctx context.Context, options option.DialerOptions) (ParallelInterfaceDialer, error) { + if options.Detour != "" { + return nil, E.New("`detour` is not supported in direct context") + } + networkManager := service.FromContext[adapter.NetworkManager](ctx) + if options.IsWireGuardListener { + return NewDefault(networkManager, options) + } + dialer, err := NewDefault(networkManager, options) + if err != nil { + return nil, err + } + return NewResolveParallelInterfaceDialer( + service.FromContext[adapter.Router](ctx), + dialer, + true, + dns.DomainStrategy(options.DomainStrategy), + time.Duration(options.FallbackDelay), + ), nil +} + +type ParallelInterfaceDialer interface { + N.Dialer + DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) + ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, error) +} + +type ParallelNetworkDialer interface { + DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) + ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) +} diff --git a/common/dialer/resolve.go b/common/dialer/resolve.go index f2ee50db5..ce17923c4 100644 --- a/common/dialer/resolve.go +++ b/common/dialer/resolve.go @@ -7,6 +7,7 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common/bufio" @@ -14,7 +15,12 @@ import ( N "github.com/sagernet/sing/common/network" ) -type ResolveDialer struct { +var ( + _ N.Dialer = (*resolveDialer)(nil) + _ ParallelInterfaceDialer = (*resolveParallelNetworkDialer)(nil) +) + +type resolveDialer struct { dialer N.Dialer parallel bool router adapter.Router @@ -22,8 +28,8 @@ type ResolveDialer struct { fallbackDelay time.Duration } -func NewResolveDialer(router adapter.Router, dialer N.Dialer, parallel bool, strategy dns.DomainStrategy, fallbackDelay time.Duration) *ResolveDialer { - return &ResolveDialer{ +func NewResolveDialer(router adapter.Router, dialer N.Dialer, parallel bool, strategy dns.DomainStrategy, fallbackDelay time.Duration) N.Dialer { + return &resolveDialer{ dialer, parallel, router, @@ -32,7 +38,25 @@ func NewResolveDialer(router adapter.Router, dialer N.Dialer, parallel bool, str } } -func (d *ResolveDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +type resolveParallelNetworkDialer struct { + resolveDialer + dialer ParallelInterfaceDialer +} + +func NewResolveParallelInterfaceDialer(router adapter.Router, dialer ParallelInterfaceDialer, parallel bool, strategy dns.DomainStrategy, fallbackDelay time.Duration) ParallelInterfaceDialer { + return &resolveParallelNetworkDialer{ + resolveDialer{ + dialer, + parallel, + router, + strategy, + fallbackDelay, + }, + dialer, + } +} + +func (d *resolveDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { if !destination.IsFqdn() { return d.dialer.DialContext(ctx, network, destination) } @@ -57,7 +81,7 @@ func (d *ResolveDialer) DialContext(ctx context.Context, network string, destina } } -func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { if !destination.IsFqdn() { return d.dialer.ListenPacket(ctx, destination) } @@ -82,6 +106,59 @@ func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil } -func (d *ResolveDialer) Upstream() any { +func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) { + if !destination.IsFqdn() { + return d.dialer.DialContext(ctx, network, destination) + } + ctx, metadata := adapter.ExtendContext(ctx) + ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug) + metadata.Destination = destination + metadata.Domain = "" + var addresses []netip.Addr + var err error + if d.strategy == dns.DomainStrategyAsIS { + addresses, err = d.router.LookupDefault(ctx, destination.Fqdn) + } else { + addresses, err = d.router.Lookup(ctx, destination.Fqdn, d.strategy) + } + if err != nil { + return nil, err + } + if fallbackDelay == 0 { + fallbackDelay = d.fallbackDelay + } + if d.parallel { + return DialParallelNetwork(ctx, d.dialer, network, destination, addresses, d.strategy == dns.DomainStrategyPreferIPv6, strategy, fallbackDelay) + } else { + return DialSerialNetwork(ctx, d.dialer, network, destination, addresses, strategy, fallbackDelay) + } +} + +func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, error) { + if !destination.IsFqdn() { + return d.dialer.ListenPacket(ctx, destination) + } + ctx, metadata := adapter.ExtendContext(ctx) + ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug) + metadata.Destination = destination + metadata.Domain = "" + var addresses []netip.Addr + var err error + if d.strategy == dns.DomainStrategyAsIS { + addresses, err = d.router.LookupDefault(ctx, destination.Fqdn) + } else { + addresses, err = d.router.Lookup(ctx, destination.Fqdn, d.strategy) + } + if err != nil { + return nil, err + } + conn, destinationAddress, err := ListenSerialNetworkPacket(ctx, d.dialer, destination, addresses, strategy, fallbackDelay) + if err != nil { + return nil, err + } + return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil +} + +func (d *resolveDialer) Upstream() any { return d.dialer } diff --git a/common/settings/proxy_darwin.go b/common/settings/proxy_darwin.go index 3c06a8536..53ed0fe03 100644 --- a/common/settings/proxy_darwin.go +++ b/common/settings/proxy_darwin.go @@ -7,6 +7,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/common/shell" @@ -33,7 +34,7 @@ func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bo serverAddr: serverAddr, supportSOCKS: supportSOCKS, } - proxy.element = interfaceMonitor.RegisterCallback(proxy.update) + proxy.element = interfaceMonitor.RegisterCallback(proxy.routeUpdate) return proxy, nil } @@ -65,11 +66,8 @@ func (p *DarwinSystemProxy) Disable() error { return err } -func (p *DarwinSystemProxy) update(event int) { - if event&tun.EventInterfaceUpdate == 0 { - return - } - if !p.isEnabled { +func (p *DarwinSystemProxy) routeUpdate(defaultInterface *control.Interface, flags int) { + if !p.isEnabled || defaultInterface == nil { return } _ = p.update0() diff --git a/constant/network.go b/constant/network.go index f5ac2a4e4..c026b7b15 100644 --- a/constant/network.go +++ b/constant/network.go @@ -1,8 +1,50 @@ package constant +import ( + "github.com/sagernet/sing/common" + F "github.com/sagernet/sing/common/format" +) + const ( InterfaceTypeWIFI = "wifi" InterfaceTypeCellular = "cellular" InterfaceTypeEthernet = "ethernet" InterfaceTypeOther = "other" ) + +type NetworkStrategy int + +const ( + NetworkStrategyDefault NetworkStrategy = iota + NetworkStrategyFallback + NetworkStrategyHybrid + NetworkStrategyWIFI + NetworkStrategyCellular + NetworkStrategyEthernet + NetworkStrategyWIFIOnly + NetworkStrategyCellularOnly + NetworkStrategyEthernetOnly +) + +var ( + NetworkStrategyToString = map[NetworkStrategy]string{ + NetworkStrategyDefault: "default", + NetworkStrategyFallback: "fallback", + NetworkStrategyHybrid: "hybrid", + NetworkStrategyWIFI: "wifi", + NetworkStrategyCellular: "cellular", + NetworkStrategyEthernet: "ethernet", + NetworkStrategyWIFIOnly: "wifi_only", + NetworkStrategyCellularOnly: "cellular_only", + NetworkStrategyEthernetOnly: "ethernet_only", + } + StringToNetworkStrategy = common.ReverseMap(NetworkStrategyToString) +) + +func (s NetworkStrategy) String() string { + name, loaded := NetworkStrategyToString[s] + if !loaded { + return F.ToString(int(s)) + } + return name +} diff --git a/experimental/libbox/monitor.go b/experimental/libbox/monitor.go index 8f4019715..d237d5348 100644 --- a/experimental/libbox/monitor.go +++ b/experimental/libbox/monitor.go @@ -67,7 +67,7 @@ func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName s callbacks := m.callbacks.Array() m.defaultInterfaceAccess.Unlock() for _, callback := range callbacks { - callback(tun.EventInterfaceUpdate) + callback(nil, 0) } return } @@ -86,6 +86,6 @@ func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName s callbacks := m.callbacks.Array() m.defaultInterfaceAccess.Unlock() for _, callback := range callbacks { - callback(tun.EventInterfaceUpdate) + callback(newInterface, 0) } } diff --git a/go.mod b/go.mod index f2683f84e..95c865f13 100644 --- a/go.mod +++ b/go.mod @@ -25,14 +25,14 @@ require ( github.com/sagernet/gvisor v0.0.0-20241021032506-a4324256e4a3 github.com/sagernet/quic-go v0.48.1-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.6.0-alpha.6 + github.com/sagernet/sing v0.6.0-alpha.7 github.com/sagernet/sing-dns v0.4.0-alpha.1 github.com/sagernet/sing-mux v0.3.0-alpha.1 github.com/sagernet/sing-quic v0.3.0-rc.2 github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.1.4 - github.com/sagernet/sing-tun v0.6.0-alpha.7 + github.com/sagernet/sing-tun v0.6.0-alpha.8 github.com/sagernet/sing-vmess v0.1.12 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/utls v1.6.7 diff --git a/go.sum b/go.sum index f1f16aea9..1e4230876 100644 --- a/go.sum +++ b/go.sum @@ -110,8 +110,8 @@ github.com/sagernet/quic-go v0.48.1-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= -github.com/sagernet/sing v0.6.0-alpha.6 h1:R0abM8ZeazyAKo9d3DNxtrgW17g3tZAD8al7O5+ADOw= -github.com/sagernet/sing v0.6.0-alpha.6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.6.0-alpha.7 h1:C77ZlUxdSJiHLCLXbmWAVvyllFaNdRl0nUkdbWZyFcU= +github.com/sagernet/sing v0.6.0-alpha.7/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-dns v0.4.0-alpha.1 h1:2KlP8DeqtGkULFiZtvG2r7SuoJP6orANFzJwC5vDKvg= github.com/sagernet/sing-dns v0.4.0-alpha.1/go.mod h1:vgHATsm4wdymwpvBZPei8RY+546iGXS6hlWv2x6YKcM= github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg= @@ -124,8 +124,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= -github.com/sagernet/sing-tun v0.6.0-alpha.7 h1:h5Fqg+H5VggJq/LGdc/hOctNEcYAdkmfKY83lYIDHUg= -github.com/sagernet/sing-tun v0.6.0-alpha.7/go.mod h1:JkgiLLnQUXln1zLGVoJqUwAulJGT0xoiPU4/pYF1fhU= +github.com/sagernet/sing-tun v0.6.0-alpha.8 h1:HhXyUvXxtaXgT+IILZMq6kbrAyDbUwbN+Df/XxpL7Vo= +github.com/sagernet/sing-tun v0.6.0-alpha.8/go.mod h1:JkgiLLnQUXln1zLGVoJqUwAulJGT0xoiPU4/pYF1fhU= github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg= github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= diff --git a/option/outbound.go b/option/outbound.go index 0e2d5874d..5791802c9 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -65,21 +65,23 @@ type DialerOptionsWrapper interface { } type DialerOptions struct { - Detour string `json:"detour,omitempty"` - BindInterface string `json:"bind_interface,omitempty"` - Inet4BindAddress *badoption.Addr `json:"inet4_bind_address,omitempty"` - Inet6BindAddress *badoption.Addr `json:"inet6_bind_address,omitempty"` - ProtectPath string `json:"protect_path,omitempty"` - RoutingMark uint32 `json:"routing_mark,omitempty"` - ReuseAddr bool `json:"reuse_addr,omitempty"` - ConnectTimeout badoption.Duration `json:"connect_timeout,omitempty"` - TCPFastOpen bool `json:"tcp_fast_open,omitempty"` - TCPMultiPath bool `json:"tcp_multi_path,omitempty"` - UDPFragment *bool `json:"udp_fragment,omitempty"` - UDPFragmentDefault bool `json:"-"` - DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` - FallbackDelay badoption.Duration `json:"fallback_delay,omitempty"` - IsWireGuardListener bool `json:"-"` + Detour string `json:"detour,omitempty"` + BindInterface string `json:"bind_interface,omitempty"` + Inet4BindAddress *badoption.Addr `json:"inet4_bind_address,omitempty"` + Inet6BindAddress *badoption.Addr `json:"inet6_bind_address,omitempty"` + ProtectPath string `json:"protect_path,omitempty"` + RoutingMark uint32 `json:"routing_mark,omitempty"` + ReuseAddr bool `json:"reuse_addr,omitempty"` + ConnectTimeout badoption.Duration `json:"connect_timeout,omitempty"` + TCPFastOpen bool `json:"tcp_fast_open,omitempty"` + TCPMultiPath bool `json:"tcp_multi_path,omitempty"` + UDPFragment *bool `json:"udp_fragment,omitempty"` + UDPFragmentDefault bool `json:"-"` + DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` + NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"` + FallbackDelay badoption.Duration `json:"fallback_delay,omitempty"` + NetworkFallbackDelay badoption.Duration `json:"network_fallback_delay,omitempty"` + IsWireGuardListener bool `json:"-"` } func (o *DialerOptions) TakeDialerOptions() DialerOptions { diff --git a/option/route.go b/option/route.go index dfd72986f..236e56f7b 100644 --- a/option/route.go +++ b/option/route.go @@ -1,16 +1,20 @@ package option +import "github.com/sagernet/sing/common/json/badoption" + type RouteOptions struct { - GeoIP *GeoIPOptions `json:"geoip,omitempty"` - Geosite *GeositeOptions `json:"geosite,omitempty"` - Rules []Rule `json:"rules,omitempty"` - RuleSet []RuleSet `json:"rule_set,omitempty"` - Final string `json:"final,omitempty"` - FindProcess bool `json:"find_process,omitempty"` - AutoDetectInterface bool `json:"auto_detect_interface,omitempty"` - OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"` - DefaultInterface string `json:"default_interface,omitempty"` - DefaultMark uint32 `json:"default_mark,omitempty"` + GeoIP *GeoIPOptions `json:"geoip,omitempty"` + Geosite *GeositeOptions `json:"geosite,omitempty"` + Rules []Rule `json:"rules,omitempty"` + RuleSet []RuleSet `json:"rule_set,omitempty"` + Final string `json:"final,omitempty"` + FindProcess bool `json:"find_process,omitempty"` + AutoDetectInterface bool `json:"auto_detect_interface,omitempty"` + OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"` + DefaultInterface string `json:"default_interface,omitempty"` + DefaultMark uint32 `json:"default_mark,omitempty"` + DefaultNetworkStrategy NetworkStrategy `json:"default_network_strategy,omitempty"` + DefaultFallbackDelay badoption.Duration `json:"default_fallback_delay,omitempty"` } type GeoIPOptions struct { diff --git a/option/rule_action.go b/option/rule_action.go index 9bc130393..7c31ea7a4 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -137,14 +137,18 @@ func (r *DNSRuleAction) UnmarshalJSONContext(ctx context.Context, data []byte) e } type RouteActionOptions struct { - Outbound string `json:"outbound,omitempty"` - UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` - UDPConnect bool `json:"udp_connect,omitempty"` + Outbound string `json:"outbound,omitempty"` + NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"` + FallbackDelay uint32 `json:"fallback_delay,omitempty"` + UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` + UDPConnect bool `json:"udp_connect,omitempty"` } type _RouteOptionsActionOptions struct { - UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` - UDPConnect bool `json:"udp_connect,omitempty"` + NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"` + FallbackDelay uint32 `json:"fallback_delay,omitempty"` + UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` + UDPConnect bool `json:"udp_connect,omitempty"` } type RouteOptionsActionOptions _RouteOptionsActionOptions diff --git a/option/types.go b/option/types.go index 04e3f10e2..8ed06250b 100644 --- a/option/types.go +++ b/option/types.go @@ -3,6 +3,7 @@ package option import ( "strings" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-dns" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" @@ -150,3 +151,23 @@ func DNSQueryTypeToString(queryType uint16) string { } return F.ToString(queryType) } + +type NetworkStrategy C.NetworkStrategy + +func (n NetworkStrategy) MarshalJSON() ([]byte, error) { + return json.Marshal(C.NetworkStrategy(n).String()) +} + +func (n *NetworkStrategy) UnmarshalJSON(content []byte) error { + var value string + err := json.Unmarshal(content, &value) + if err != nil { + return err + } + strategy, loaded := C.StringToNetworkStrategy[value] + if !loaded { + return E.New("unknown network strategy: ", value) + } + *n = NetworkStrategy(strategy) + return nil +} diff --git a/protocol/direct/outbound.go b/protocol/direct/outbound.go index 27b334c9a..4251c3663 100644 --- a/protocol/direct/outbound.go +++ b/protocol/direct/outbound.go @@ -13,6 +13,7 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" dns "github.com/sagernet/sing-dns" + "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" @@ -24,31 +25,38 @@ func RegisterOutbound(registry *outbound.Registry) { outbound.Register[option.DirectOutboundOptions](registry, C.TypeDirect, NewOutbound) } -var _ N.ParallelDialer = (*Outbound)(nil) +var ( + _ N.ParallelDialer = (*Outbound)(nil) + _ dialer.ParallelNetworkDialer = (*Outbound)(nil) +) type Outbound struct { outbound.Adapter - logger logger.ContextLogger - dialer N.Dialer - domainStrategy dns.DomainStrategy - fallbackDelay time.Duration - overrideOption int - overrideDestination M.Socksaddr + logger logger.ContextLogger + dialer dialer.ParallelInterfaceDialer + domainStrategy dns.DomainStrategy + fallbackDelay time.Duration + networkStrategy C.NetworkStrategy + networkFallbackDelay time.Duration + overrideOption int + overrideDestination M.Socksaddr // loopBack *loopBackDetector } func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.DirectOutboundOptions) (adapter.Outbound, error) { options.UDPFragmentDefault = true - outboundDialer, err := dialer.New(ctx, options.DialerOptions) + outboundDialer, err := dialer.NewDirect(ctx, options.DialerOptions) if err != nil { return nil, err } outbound := &Outbound{ - Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, []string{N.NetworkTCP, N.NetworkUDP}, tag, options.DialerOptions), - logger: logger, - domainStrategy: dns.DomainStrategy(options.DomainStrategy), - fallbackDelay: time.Duration(options.FallbackDelay), - dialer: outboundDialer, + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, []string{N.NetworkTCP, N.NetworkUDP}, tag, options.DialerOptions), + logger: logger, + domainStrategy: dns.DomainStrategy(options.DomainStrategy), + fallbackDelay: time.Duration(options.FallbackDelay), + networkStrategy: C.NetworkStrategy(options.NetworkStrategy), + networkFallbackDelay: time.Duration(options.NetworkFallbackDelay), + dialer: outboundDialer, // loopBack: newLoopBackDetector(router), } if options.ProxyProtocol != 0 { @@ -96,6 +104,37 @@ func (h *Outbound) DialContext(ctx context.Context, network string, destination return h.dialer.DialContext(ctx, network, destination) } +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = h.Tag() + metadata.Destination = destination + originDestination := destination + switch h.overrideOption { + case 1: + destination = h.overrideDestination + case 2: + newDestination := h.overrideDestination + newDestination.Port = destination.Port + destination = newDestination + case 3: + destination.Port = h.overrideDestination.Port + } + if h.overrideOption == 0 { + h.logger.InfoContext(ctx, "outbound packet connection") + } else { + h.logger.InfoContext(ctx, "outbound packet connection to ", destination) + } + conn, err := h.dialer.ListenPacket(ctx, destination) + if err != nil { + return nil, err + } + // conn = h.loopBack.NewPacketConn(bufio.NewPacketConn(conn), destination) + if originDestination != destination { + conn = bufio.NewNATPacketConn(bufio.NewPacketConn(conn), destination, originDestination) + } + return conn, nil +} + func (h *Outbound) DialParallel(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.Tag() @@ -120,14 +159,64 @@ func (h *Outbound) DialParallel(ctx context.Context, network string, destination } else { domainStrategy = dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) } - return N.DialParallel(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, h.fallbackDelay) + switch domainStrategy { + case dns.DomainStrategyUseIPv4: + destinationAddresses = common.Filter(destinationAddresses, netip.Addr.Is4) + if len(destinationAddresses) == 0 { + return nil, E.New("no IPv4 address available for ", destination) + } + case dns.DomainStrategyUseIPv6: + destinationAddresses = common.Filter(destinationAddresses, netip.Addr.Is6) + if len(destinationAddresses) == 0 { + return nil, E.New("no IPv6 address available for ", destination) + } + } + return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, h.networkStrategy, h.fallbackDelay) } -func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) { + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = h.Tag() + metadata.Destination = destination + switch h.overrideOption { + case 1, 2: + // override address + return h.DialContext(ctx, network, destination) + case 3: + destination.Port = h.overrideDestination.Port + } + network = N.NetworkName(network) + switch network { + case N.NetworkTCP: + h.logger.InfoContext(ctx, "outbound connection to ", destination) + case N.NetworkUDP: + h.logger.InfoContext(ctx, "outbound packet connection to ", destination) + } + var domainStrategy dns.DomainStrategy + if h.domainStrategy != dns.DomainStrategyAsIS { + domainStrategy = h.domainStrategy + } else { + domainStrategy = dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) + } + switch domainStrategy { + case dns.DomainStrategyUseIPv4: + destinationAddresses = common.Filter(destinationAddresses, netip.Addr.Is4) + if len(destinationAddresses) == 0 { + return nil, E.New("no IPv4 address available for ", destination) + } + case dns.DomainStrategyUseIPv6: + destinationAddresses = common.Filter(destinationAddresses, netip.Addr.Is6) + if len(destinationAddresses) == 0 { + return nil, E.New("no IPv6 address available for ", destination) + } + } + return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, networkStrategy, fallbackDelay) +} + +func (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) { ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.Tag() metadata.Destination = destination - originDestination := destination switch h.overrideOption { case 1: destination = h.overrideDestination @@ -143,15 +232,11 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n } else { h.logger.InfoContext(ctx, "outbound packet connection to ", destination) } - conn, err := h.dialer.ListenPacket(ctx, destination) + conn, newDestination, err := dialer.ListenSerialNetworkPacket(ctx, h.dialer, destination, destinationAddresses, networkStrategy, fallbackDelay) if err != nil { - return nil, err - } - // conn = h.loopBack.NewPacketConn(bufio.NewPacketConn(conn), destination) - if originDestination != destination { - conn = bufio.NewNATPacketConn(bufio.NewPacketConn(conn), destination, originDestination) + return nil, netip.Addr{}, err } - return conn, nil + return conn, newDestination, nil } /*func (h *Outbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { diff --git a/protocol/socks/outbound.go b/protocol/socks/outbound.go index dbb5ab617..70a5a5eda 100644 --- a/protocol/socks/outbound.go +++ b/protocol/socks/outbound.go @@ -10,7 +10,6 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" @@ -115,23 +114,3 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n h.logger.InfoContext(ctx, "outbound packet connection to ", destination) return h.client.ListenPacket(ctx, destination) } - -// TODO -// Deprecated -func (h *Outbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - if h.resolve { - return outbound.NewDirectConnection(ctx, h.router, h, conn, metadata, dns.DomainStrategyUseIPv4) - } else { - return outbound.NewConnection(ctx, h, conn, metadata) - } -} - -// TODO -// Deprecated -func (h *Outbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - if h.resolve { - return outbound.NewDirectPacketConnection(ctx, h.router, h, conn, metadata, dns.DomainStrategyUseIPv4) - } else { - return outbound.NewPacketConnection(ctx, h, conn, metadata) - } -} diff --git a/protocol/wireguard/outbound.go b/protocol/wireguard/outbound.go index 7f33bf607..7b2f8a6cc 100644 --- a/protocol/wireguard/outbound.go +++ b/protocol/wireguard/outbound.go @@ -16,7 +16,6 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/transport/wireguard" - "github.com/sagernet/sing-dns" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" @@ -231,15 +230,3 @@ func (w *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n } return w.tunDevice.ListenPacket(ctx, destination) } - -// TODO -// Deprecated -func (w *Outbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return outbound.NewDirectConnection(ctx, w.router, w, conn, metadata, dns.DomainStrategyAsIS) -} - -// TODO -// Deprecated -func (w *Outbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return outbound.NewDirectPacketConnection(ctx, w.router, w, conn, metadata, dns.DomainStrategyAsIS) -} diff --git a/route/network.go b/route/network.go index c6253b91e..51fbdf05d 100644 --- a/route/network.go +++ b/route/network.go @@ -8,6 +8,7 @@ import ( "runtime" "strings" "syscall" + "time" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/conntrack" @@ -38,8 +39,7 @@ type NetworkManager struct { networkInterfaces atomic.TypedValue[[]adapter.NetworkInterface] autoDetectInterface bool - defaultInterface string - defaultMark uint32 + defaultOptions adapter.NetworkOptions autoRedirectOutputMark uint32 networkMonitor tun.NetworkUpdateMonitor @@ -58,11 +58,23 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOp logger: logger, interfaceFinder: control.NewDefaultInterfaceFinder(), autoDetectInterface: routeOptions.AutoDetectInterface, - defaultInterface: routeOptions.DefaultInterface, - defaultMark: routeOptions.DefaultMark, - pauseManager: service.FromContext[pause.Manager](ctx), - platformInterface: service.FromContext[platform.Interface](ctx), - outboundManager: service.FromContext[adapter.OutboundManager](ctx), + defaultOptions: adapter.NetworkOptions{ + DefaultInterface: routeOptions.DefaultInterface, + DefaultMark: routeOptions.DefaultMark, + DefaultNetworkStrategy: C.NetworkStrategy(routeOptions.DefaultNetworkStrategy), + DefaultFallbackDelay: time.Duration(routeOptions.DefaultFallbackDelay), + }, + pauseManager: service.FromContext[pause.Manager](ctx), + platformInterface: service.FromContext[platform.Interface](ctx), + outboundManager: service.FromContext[adapter.OutboundManager](ctx), + } + if C.NetworkStrategy(routeOptions.DefaultNetworkStrategy) != C.NetworkStrategyDefault { + if routeOptions.DefaultInterface != "" { + return nil, E.New("`default_network_strategy` is conflict with `default_interface`") + } + if !routeOptions.AutoDetectInterface { + return nil, E.New("`auto_detect_interface` is required by `default_network_strategy`") + } } usePlatformDefaultInterfaceMonitor := nm.platformInterface != nil enforceInterfaceMonitor := routeOptions.AutoDetectInterface @@ -84,12 +96,12 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOp if err != nil { return nil, E.New("auto_detect_interface unsupported on current platform") } - interfaceMonitor.RegisterCallback(nm.notifyNetworkUpdate) + interfaceMonitor.RegisterCallback(nm.notifyInterfaceUpdate) nm.interfaceMonitor = interfaceMonitor } } else { interfaceMonitor := nm.platformInterface.CreateDefaultInterfaceMonitor(logger) - interfaceMonitor.RegisterCallback(nm.notifyNetworkUpdate) + interfaceMonitor.RegisterCallback(nm.notifyInterfaceUpdate) nm.interfaceMonitor = interfaceMonitor } return nm, nil @@ -265,10 +277,6 @@ func (r *NetworkManager) NetworkInterfaces() []adapter.NetworkInterface { return r.networkInterfaces.Load() } -func (r *NetworkManager) DefaultInterface() string { - return r.defaultInterface -} - func (r *NetworkManager) AutoDetectInterface() bool { return r.autoDetectInterface } @@ -301,8 +309,19 @@ func (r *NetworkManager) AutoDetectInterfaceFunc() control.Func { } } -func (r *NetworkManager) DefaultMark() uint32 { - return r.defaultMark +func (r *NetworkManager) ProtectFunc() control.Func { + if r.platformInterface != nil && r.platformInterface.UsePlatformAutoDetectInterfaceControl() { + return func(network, address string, conn syscall.RawConn) error { + return control.Raw(conn, func(fd uintptr) error { + return r.platformInterface.AutoDetectInterfaceControl(int(fd)) + }) + } + } + return nil +} + +func (r *NetworkManager) DefaultOptions() adapter.NetworkOptions { + return r.defaultOptions } func (r *NetworkManager) RegisterAutoRedirectOutputMark(mark uint32) error { @@ -344,45 +363,47 @@ func (r *NetworkManager) ResetNetwork() { } } -func (r *NetworkManager) notifyNetworkUpdate(event int) { - if event == tun.EventNoRoute { +func (r *NetworkManager) notifyInterfaceUpdate(defaultInterface *control.Interface, flags int) { + if defaultInterface == nil { r.pauseManager.NetworkPause() r.logger.Error("missing default interface") - } else { - r.pauseManager.NetworkWake() - defaultInterface := r.DefaultNetworkInterface() - if defaultInterface == nil { - panic("invalid interface context") - } - var options []string - options = append(options, F.ToString("index ", defaultInterface.Index)) - if C.IsAndroid && r.platformInterface == nil { - var vpnStatus string - if r.interfaceMonitor.AndroidVPNEnabled() { - vpnStatus = "enabled" - } else { - vpnStatus = "disabled" - } - options = append(options, "vpn "+vpnStatus) + return + } + + r.pauseManager.NetworkWake() + var options []string + options = append(options, F.ToString("index ", defaultInterface.Index)) + if C.IsAndroid && r.platformInterface == nil { + var vpnStatus string + if r.interfaceMonitor.AndroidVPNEnabled() { + vpnStatus = "enabled" } else { - if defaultInterface.Type != "" { - options = append(options, F.ToString("type ", defaultInterface.Type)) - } - if defaultInterface.Expensive { - options = append(options, "expensive") - } - if defaultInterface.Constrained { - options = append(options, "constrained") - } + vpnStatus = "disabled" } - r.logger.Info("updated default interface ", defaultInterface.Name, ", ", strings.Join(options, ", ")) - if r.platformInterface != nil { - state := r.platformInterface.ReadWIFIState() - if state != r.wifiState { - r.wifiState = state - if state.SSID != "" { - r.logger.Info("updated WIFI state: SSID=", state.SSID, ", BSSID=", state.BSSID) - } + options = append(options, "vpn "+vpnStatus) + } else if r.platformInterface != nil { + networkInterface := common.Find(r.networkInterfaces.Load(), func(it adapter.NetworkInterface) bool { + return it.Interface.Index == defaultInterface.Index + }) + if networkInterface.Type == "" { + // race + return + } + options = append(options, F.ToString("type ", networkInterface.Type)) + if networkInterface.Expensive { + options = append(options, "expensive") + } + if networkInterface.Constrained { + options = append(options, "constrained") + } + } + r.logger.Info("updated default interface ", defaultInterface.Name, ", ", strings.Join(options, ", ")) + if r.platformInterface != nil { + state := r.platformInterface.ReadWIFIState() + if state != r.wifiState { + r.wifiState = state + if state.SSID != "" { + r.logger.Info("updated WIFI state: SSID=", state.SSID, ", BSSID=", state.BSSID) } } } diff --git a/route/route.go b/route/route.go index 1c4da4b7a..051ee403b 100644 --- a/route/route.go +++ b/route/route.go @@ -424,9 +424,13 @@ match: } switch action := currentRule.Action().(type) { case *rule.RuleActionRoute: + metadata.NetworkStrategy = action.NetworkStrategy + metadata.FallbackDelay = action.FallbackDelay metadata.UDPDisableDomainUnmapping = action.UDPDisableDomainUnmapping metadata.UDPConnect = action.UDPConnect case *rule.RuleActionRouteOptions: + metadata.NetworkStrategy = action.NetworkStrategy + metadata.FallbackDelay = action.FallbackDelay metadata.UDPDisableDomainUnmapping = action.UDPDisableDomainUnmapping metadata.UDPConnect = action.UDPConnect case *rule.RuleActionSniff: diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index d44e36eea..f9b2e6410 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -30,12 +30,16 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti return &RuleActionRoute{ Outbound: action.RouteOptions.Outbound, RuleActionRouteOptions: RuleActionRouteOptions{ + NetworkStrategy: C.NetworkStrategy(action.RouteOptions.NetworkStrategy), + FallbackDelay: time.Duration(action.RouteOptions.FallbackDelay), UDPDisableDomainUnmapping: action.RouteOptions.UDPDisableDomainUnmapping, UDPConnect: action.RouteOptions.UDPConnect, }, }, nil case C.RuleActionTypeRouteOptions: return &RuleActionRouteOptions{ + NetworkStrategy: C.NetworkStrategy(action.RouteOptionsOptions.NetworkStrategy), + FallbackDelay: time.Duration(action.RouteOptionsOptions.FallbackDelay), UDPDisableDomainUnmapping: action.RouteOptionsOptions.UDPDisableDomainUnmapping, UDPConnect: action.RouteOptionsOptions.UDPConnect, }, nil @@ -135,6 +139,8 @@ func (r *RuleActionRoute) String() string { } type RuleActionRouteOptions struct { + NetworkStrategy C.NetworkStrategy + FallbackDelay time.Duration UDPDisableDomainUnmapping bool UDPConnect bool } diff --git a/transport/dhcp/server.go b/transport/dhcp/server.go index 9a06ac171..29c6bbe02 100644 --- a/transport/dhcp/server.go +++ b/transport/dhcp/server.go @@ -166,7 +166,7 @@ func (t *Transport) updateServers() error { } } -func (t *Transport) interfaceUpdated(int) { +func (t *Transport) interfaceUpdated(defaultInterface *control.Interface, flags int) { err := t.updateServers() if err != nil { t.options.Logger.Error("update servers: ", err) From 2baf38a531596be69791a8c82652ba6d05d6f28e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 12 Nov 2024 22:49:36 +0800 Subject: [PATCH 35/39] documentation: Remove unused titles --- docs/configuration/dns/fakeip.md | 2 -- docs/configuration/dns/fakeip.zh.md | 2 -- docs/configuration/dns/rule_action.md | 2 -- docs/configuration/dns/rule_action.zh.md | 2 -- docs/configuration/inbound/shadowtls.md | 4 ++-- docs/configuration/rule-set/adguard.md | 2 -- docs/configuration/rule-set/adguard.zh.md | 2 -- docs/configuration/rule-set/source-format.md | 2 -- docs/configuration/rule-set/source-format.zh.md | 2 -- docs/configuration/shared/tls.md | 2 +- docs/configuration/shared/udp-over-tcp.md | 2 -- 11 files changed, 3 insertions(+), 21 deletions(-) diff --git a/docs/configuration/dns/fakeip.md b/docs/configuration/dns/fakeip.md index 51db1f42d..63490ac19 100644 --- a/docs/configuration/dns/fakeip.md +++ b/docs/configuration/dns/fakeip.md @@ -1,5 +1,3 @@ -# FakeIP - ### Structure ```json diff --git a/docs/configuration/dns/fakeip.zh.md b/docs/configuration/dns/fakeip.zh.md index 3d9a814a6..10c6dc68e 100644 --- a/docs/configuration/dns/fakeip.zh.md +++ b/docs/configuration/dns/fakeip.zh.md @@ -1,5 +1,3 @@ -# FakeIP - ### 结构 ```json diff --git a/docs/configuration/dns/rule_action.md b/docs/configuration/dns/rule_action.md index cccc13967..af19131f6 100644 --- a/docs/configuration/dns/rule_action.md +++ b/docs/configuration/dns/rule_action.md @@ -2,8 +2,6 @@ icon: material/new-box --- -# DNS Rule Action - !!! question "Since sing-box 1.11.0" ### route diff --git a/docs/configuration/dns/rule_action.zh.md b/docs/configuration/dns/rule_action.zh.md index 8fad23816..219a5fd75 100644 --- a/docs/configuration/dns/rule_action.zh.md +++ b/docs/configuration/dns/rule_action.zh.md @@ -2,8 +2,6 @@ icon: material/new-box --- -# DNS 规则动作 - !!! question "自 sing-box 1.11.0 起" ### route diff --git a/docs/configuration/inbound/shadowtls.md b/docs/configuration/inbound/shadowtls.md index db010b790..0f6684d20 100644 --- a/docs/configuration/inbound/shadowtls.md +++ b/docs/configuration/inbound/shadowtls.md @@ -66,11 +66,11 @@ Only available in the ShadowTLS protocol 3. ==Required== -Handshake server address and [Dial options](/configuration/shared/dial/). +Handshake server address and [Dial Fields](/configuration/shared/dial/). #### handshake_for_server_name -Handshake server address and [Dial options](/configuration/shared/dial/) for specific server name. +Handshake server address and [Dial Fields](/configuration/shared/dial/) for specific server name. Only available in the ShadowTLS protocol 2/3. diff --git a/docs/configuration/rule-set/adguard.md b/docs/configuration/rule-set/adguard.md index 870972bf2..bda737945 100644 --- a/docs/configuration/rule-set/adguard.md +++ b/docs/configuration/rule-set/adguard.md @@ -2,8 +2,6 @@ icon: material/new-box --- -# AdGuard DNS Filter - !!! question "Since sing-box 1.10.0" sing-box supports some rule-set formats from other projects which cannot be fully translated to sing-box, diff --git a/docs/configuration/rule-set/adguard.zh.md b/docs/configuration/rule-set/adguard.zh.md index b08ab34b4..026f2e0be 100644 --- a/docs/configuration/rule-set/adguard.zh.md +++ b/docs/configuration/rule-set/adguard.zh.md @@ -2,8 +2,6 @@ icon: material/new-box --- -# AdGuard DNS Filter - !!! question "自 sing-box 1.10.0 起" sing-box 支持其他项目的一些规则集格式,这些格式无法完全转换为 sing-box, diff --git a/docs/configuration/rule-set/source-format.md b/docs/configuration/rule-set/source-format.md index 43e8ea453..1dcc1d447 100644 --- a/docs/configuration/rule-set/source-format.md +++ b/docs/configuration/rule-set/source-format.md @@ -2,8 +2,6 @@ icon: material/new-box --- -# Source Format - !!! quote "Changes in sing-box 1.11.0" :material-plus: version `3` diff --git a/docs/configuration/rule-set/source-format.zh.md b/docs/configuration/rule-set/source-format.zh.md index 0e8fc0cb9..3dacaea7c 100644 --- a/docs/configuration/rule-set/source-format.zh.md +++ b/docs/configuration/rule-set/source-format.zh.md @@ -2,8 +2,6 @@ icon: material/new-box --- -# 源文件格式 - !!! quote "sing-box 1.11.0 中的更改" :material-plus: version `3` diff --git a/docs/configuration/shared/tls.md b/docs/configuration/shared/tls.md index 40343e18e..7a6c9d5e0 100644 --- a/docs/configuration/shared/tls.md +++ b/docs/configuration/shared/tls.md @@ -380,7 +380,7 @@ See [DNS01 Challenge Fields](/configuration/shared/dns01_challenge/) for details ==Required== -Handshake server address and [Dial options](/configuration/shared/dial/). +Handshake server address and [Dial Fields](/configuration/shared/dial/). #### private_key diff --git a/docs/configuration/shared/udp-over-tcp.md b/docs/configuration/shared/udp-over-tcp.md index 6bd8a68a9..9c2688b94 100644 --- a/docs/configuration/shared/udp-over-tcp.md +++ b/docs/configuration/shared/udp-over-tcp.md @@ -1,5 +1,3 @@ -# UDP over TCP - !!! warning "" It's a proprietary protocol created by SagerNet, not part of shadowsocks. From 81e49b524849e0a8be55e69f01375cdf460933fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 12 Nov 2024 22:57:15 +0800 Subject: [PATCH 36/39] documentation: Add parallel network dialing --- docs/configuration/route/index.md | 45 +++++++++++++++-- docs/configuration/route/index.zh.md | 30 +++++++++++- docs/configuration/route/rule_action.md | 29 +++++++++-- docs/configuration/route/rule_action.zh.md | 29 +++++++++-- docs/configuration/shared/dial.md | 53 +++++++++++++++++--- docs/configuration/shared/dial.zh.md | 57 +++++++++++++++++----- 6 files changed, 208 insertions(+), 35 deletions(-) diff --git a/docs/configuration/route/index.md b/docs/configuration/route/index.md index 507cb140b..2b035eb42 100644 --- a/docs/configuration/route/index.md +++ b/docs/configuration/route/index.md @@ -1,5 +1,14 @@ +--- +icon: material/new-box +--- + # Route +!!! quote "Changes in sing-box 1.11.0" + + :material-plus: [default_network_strategy](#default_network_strategy) + :material-alert: [default_fallback_delay](#default_fallback_delay) + !!! quote "Changes in sing-box 1.8.0" :material-plus: [rule_set](#rule_set) @@ -18,16 +27,18 @@ "final": "", "auto_detect_interface": false, "override_android_vpn": false, - "default_interface": "en0", - "default_mark": 233 + "default_interface": "", + "default_mark": 0, + "default_network_strategy": "", + "default_fallback_delay": "" } } ``` ### Fields -| Key | Format | -|-----------|----------------------| +| Key | Format | +|-----------|-----------------------| | `geoip` | [GeoIP](./geoip/) | | `geosite` | [Geosite](./geosite/) | @@ -81,4 +92,28 @@ Takes no effect if `auto_detect_interface` is set. Set routing mark by default. -Takes no effect if `outbound.routing_mark` is set. \ No newline at end of file +Takes no effect if `outbound.routing_mark` is set. + +#### default_network_strategy + +!!! quote "" + + Only supported in graphical clients on Android and iOS with `auto_detect_interface` enabled. + +Strategy for selecting network interfaces. + +Takes no effect if `outbound.bind_interface`, `outbound.inet4_bind_address` or `outbound.inet6_bind_address` is set. + +Can be overrides by `outbound.network_strategy`. + +Conflicts with `default_interface`. + +See [Dial Fields](/configuration/shared/dial/#network_strategy) for available values. + +#### default_fallback_delay + +!!! quote "" + + Only supported in graphical clients on Android and iOS with `auto_detect_interface` enabled and `network_strategy` set. + +See [Dial Fields](/configuration/shared/dial/#fallback_delay) for details. \ No newline at end of file diff --git a/docs/configuration/route/index.zh.md b/docs/configuration/route/index.zh.md index 68d4f66d9..b00237c45 100644 --- a/docs/configuration/route/index.zh.md +++ b/docs/configuration/route/index.zh.md @@ -18,8 +18,10 @@ "final": "", "auto_detect_interface": false, "override_android_vpn": false, - "default_interface": "en0", - "default_mark": 233 + "default_interface": "", + "default_mark": 0, + "default_network_strategy": "", + "default_fallback_delay": "" } } ``` @@ -82,3 +84,27 @@ 默认为出站连接设置路由标记。 如果设置了 `outbound.routing_mark` 设置,则不生效。 + +#### network_strategy + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持,并且需要 `auto_detect_interface`。 + +选择网络接口的策略。 + +当 `outbound.bind_interface`, `outbound.inet4_bind_address` 或 `outbound.inet6_bind_address` 已设置时不生效。 + +可以被 `outbound.network_strategy` 覆盖。 + +与 `default_interface` 冲突。 + +可用值请参阅 [拨号字段](/configuration/shared/dial/#network_strategy)。 + +#### fallback_delay + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持,并且需要 `auto_detect_interface` 且 `network_strategy` 已设置。 + +详情请参阅 [拨号字段](/configuration/shared/dial/#fallback_delay)。 diff --git a/docs/configuration/route/rule_action.md b/docs/configuration/route/rule_action.md index 7804f5869..5e9cfbc22 100644 --- a/docs/configuration/route/rule_action.md +++ b/docs/configuration/route/rule_action.md @@ -2,10 +2,6 @@ icon: material/new-box --- -# Rule Action - -!!! question "Since sing-box 1.11.0" - ## Final actions ### route @@ -14,6 +10,8 @@ icon: material/new-box { "action": "route", // default "outbound": "", + "network_strategy": "", + "fallback_delay": "", "udp_disable_domain_unmapping": false, "udp_connect": false } @@ -27,6 +25,27 @@ icon: material/new-box Tag of target outbound. +#### network_strategy + +!!! quote "" + + Only supported in graphical clients on Android and iOS with `auto_detect_interface` enabled. + +Strategy for selecting network interfaces. + +Only take effect if outbound is direct without `outbound.bind_interface`, +`outbound.inet4_bind_address` and `outbound.inet6_bind_address` set. + +See [Dial Fields](/configuration/shared/dial/#network_strategy) for available values. + +#### fallback_delay + +!!! quote "" + + Only supported in graphical clients on Android and iOS with `auto_detect_interface` enabled and `network_strategy` set. + +See [Dial Fields](/configuration/shared/dial/#fallback_delay) for details. + #### udp_disable_domain_unmapping If enabled, for UDP proxy requests addressed to a domain, @@ -44,6 +63,8 @@ If enabled, attempts to connect UDP connection to the destination instead of lis ```json { "action": "route-options", + "network_strategy": "", + "fallback_delay": "", "udp_disable_domain_unmapping": false, "udp_connect": false } diff --git a/docs/configuration/route/rule_action.zh.md b/docs/configuration/route/rule_action.zh.md index 103a04bd5..52965fd24 100644 --- a/docs/configuration/route/rule_action.zh.md +++ b/docs/configuration/route/rule_action.zh.md @@ -2,10 +2,6 @@ icon: material/new-box --- -# 规则动作 - -!!! question "自 sing-box 1.11.0 起" - ## 最终动作 ### route @@ -14,6 +10,8 @@ icon: material/new-box { "action": "route", // 默认 "outbound": "", + "network_strategy": "", + "fallback_delay": "", "udp_disable_domain_unmapping": false, "udp_connect": false } @@ -27,6 +25,27 @@ icon: material/new-box 目标出站的标签。 +#### network_strategy + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持,并且需要 `auto_detect_interface`。 + +选择网络接口的策略。 + +仅当出站为 `direct` 且 `outbound.bind_interface`, `outbound.inet4_bind_address` +且 `outbound.inet6_bind_address` 未设置时生效。 + +可用值参阅 [拨号字段](/configuration/shared/dial/#network_strategy)。 + +#### fallback_delay + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持,并且需要 `auto_detect_interface` 且 `network_strategy` 已设置。 + +详情参阅 [拨号字段](/configuration/shared/dial/#fallback_delay)。 + #### udp_disable_domain_unmapping 如果启用,对于地址为域的 UDP 代理请求,将在响应中发送原始包地址而不是映射的域。 @@ -42,6 +61,8 @@ icon: material/new-box ```json { "action": "route-options", + "network_strategy": "", + "fallback_delay": "", "udp_disable_domain_unmapping": false, "udp_connect": false } diff --git a/docs/configuration/shared/dial.md b/docs/configuration/shared/dial.md index 8139c7518..56467510d 100644 --- a/docs/configuration/shared/dial.md +++ b/docs/configuration/shared/dial.md @@ -1,3 +1,12 @@ +--- +icon: material/new-box +--- + +!!! quote "Changes in sing-box 1.11.0" + + :material-plus: [network_strategy](#network_strategy) + :material-alert: [fallback_delay](#fallback_delay) + ### Structure ```json @@ -13,20 +22,19 @@ "tcp_multi_path": false, "udp_fragment": false, "domain_strategy": "prefer_ipv6", + "network_strategy": "default", "fallback_delay": "300ms" } ``` ### Fields -| Field | Available Context | -|------------------------------------------------------------------------------------------------------------------------------------------|-------------------| -| `bind_interface` /`*bind_address` /`routing_mark` /`reuse_addr` / `tcp_fast_open` / `tcp_multi_path` / `udp_fragment` /`connect_timeout` | `detour` not set | - #### detour The tag of the upstream outbound. +If enabled, all other fields will be ignored. + #### bind_interface The network interface to bind to. @@ -78,7 +86,7 @@ Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". #### domain_strategy -One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`. +Available values: `prefer_ipv4`, `prefer_ipv6`, `ipv4_only`, `ipv6_only`. If set, the requested domain name will be resolved to IP before connect. @@ -87,11 +95,40 @@ If set, the requested domain name will be resolved to IP before connect. | `direct` | Domain in request | Take `inbound.domain_strategy` if not set | | others | Domain in server address | / | +#### network_strategy + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and iOS with `auto_detect_interface` enabled. + +Strategy for selecting network interfaces. + +Available values: + +- `default` (default): Connect to the default interface. +- `fallback`: Try all other interfaces when timeout. +- `hybrid`: Connect to all interfaces concurrently and choose the fastest one. +- `wifi`: Prioritize WIFI, but try all other interfaces when unavailable or timeout. +- `cellular`: Prioritize Cellular, but try all other interfaces when unavailable or timeout. +- `ethernet`: Prioritize Ethernet, but try all other interfaces when unavailable or timeout. +- `wifi_only`: Connect to WIFI only. +- `cellular_only`: Connect to Cellular only. +- `ethernet_only`: Connect to Ethernet only. + +Conflicts with `bind_interface`, `inet4_bind_address` and `inet6_bind_address`. + #### fallback_delay The length of time to wait before spawning a RFC 6555 Fast Fallback connection. -That is, is the amount of time to wait for connection to succeed before assuming + +For `domain_strategy`, is the amount of time to wait for connection to succeed before assuming that IPv4/IPv6 is misconfigured and falling back to other type of addresses. -If zero, a default delay of 300ms is used. -Only take effect when `domain_strategy` is set. \ No newline at end of file +For `network_strategy`, is the amount of time to wait for connection to succeed before falling +back to other interfaces. + +Only take effect when `domain_strategy` or `network_strategy` is set. + +`300ms` is used by default. diff --git a/docs/configuration/shared/dial.zh.md b/docs/configuration/shared/dial.zh.md index ccb61c7a4..c81f95bd5 100644 --- a/docs/configuration/shared/dial.zh.md +++ b/docs/configuration/shared/dial.zh.md @@ -1,3 +1,12 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.11.0 中的更改" + + :material-plus: [network_strategy](#network_strategy) + :material-alert: [fallback_delay](#fallback_delay) + ### 结构 ```json @@ -13,17 +22,13 @@ "tcp_multi_path": false, "udp_fragment": false, "domain_strategy": "prefer_ipv6", + "network_strategy": "", "fallback_delay": "300ms" } ``` ### 字段 -| 字段 | 可用上下文 | -|------------------------------------------------------------------------------------------------------------------------------------------|--------------| -| `bind_interface` /`*bind_address` /`routing_mark` /`reuse_addr` / `tcp_fast_open` / `tcp_mutli_path` / `udp_fragment` /`connect_timeout` | `detour` 未设置 | - - #### detour 上游出站的标签。 @@ -83,15 +88,43 @@ 如果设置,域名将在请求发出之前解析为 IP。 -| 出站 | 受影响的域名 | 默认回退值 | -|----------|--------------------------|-------------------------------------------| -| `direct` | 请求中的域名 | `inbound.domain_strategy` | -| others | 服务器地址中的域名 | / | +| 出站 | 受影响的域名 | 默认回退值 | +|----------|-----------|---------------------------| +| `direct` | 请求中的域名 | `inbound.domain_strategy` | +| others | 服务器地址中的域名 | / | + +#### network_strategy + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Android 与 iOS 平台图形客户端中支持。 + +用于选择网络接口的策略。 + +可用值: + +- `default` (默认): 连接到默认接口, +- `fallback`: 如果超时,尝试所有剩余接口。 +- `hybrid`: 同时尝试所有接口,选择最快的一个。 +- `wifi`: 优先使用 WIFI,但在不可用或超时时尝试所有其他接口。 +- `cellular`: 优先使用蜂窝数据,但在不可用或超时时尝试所有其他接口。 +- `ethernet`: 优先使用以太网,但在不可用或超时时尝试所有其他接口。 +- `wifi_only`: 仅连接到 WIFI。 +- `cellular_only`: 仅连接到蜂窝数据。 +- `ethernet_only`: 仅连接到以太网。 + +与 `bind_interface`, `bind_inet4_address` 和 `bind_inet6_address` 冲突。 #### fallback_delay 在生成 RFC 6555 快速回退连接之前等待的时间长度。 -也就是说,是在假设之前等待 IPv6 成功的时间量如果设置了 "prefer_ipv4",则 IPv6 配置错误并回退到 IPv4。 -如果为零,则使用 300 毫秒的默认延迟。 -仅当 `domain_strategy` 为 `prefer_ipv4` 或 `prefer_ipv6` 时生效。 +对于 `domain_strategy`,是在假设之前等待 IPv6 成功的时间量如果设置了 "prefer_ipv4",则 IPv6 配置错误并回退到 IPv4。 + +对于 `network_strategy`,对于 `network_strategy`,是在回退到其他接口之前等待连接成功的时间。 + +仅当 `domain_strategy` 或 `network_strategy` 已设置时生效。 + +默认使用 `300ms`。 From 353cf6baf8adc9cba9c7831d195b1c1a6c84a738 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 12 Nov 2024 23:08:11 +0800 Subject: [PATCH 37/39] documentation: Bump version --- docs/changelog.md | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 4a3716a01..dd169d525 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,23 @@ icon: material/alert-decagram --- +#### 1.11.0-alpha.14 + +* Add multi network dialing **1** +* Fixes and improvements + +**1**: + +Similar to Surge's strategy. + +New options allow you to connect using multiple network interfaces, +prefer or only use one type of interface, +and configure a timeout to fallback to other interfaces. + +See [Dial Fields](/configuration/shared/dial/#network_strategy), +[Rule Action](/configuration/route/rule_action/#network_strategy) +and [Route](/configuration/route/#default_network_strategy). + #### 1.11.0-alpha.13 * Fixes and improvements @@ -62,7 +79,7 @@ and [Migrate legacy DNS route options to rule actions](/migration/#migrate-legac Similar to Surge's pre-matching. -Specifically, the new rule actions allow you to reject connections with +Specifically, new rule actions allow you to reject connections with TCP RST (for TCP connections) and ICMP port unreachable (for UDP packets) before connection established to improve tun's compatibility. @@ -159,7 +176,7 @@ allows you to write headless rules directly without creating a rule-set file. **8**: -With the new access control options, not only can you allow Clash dashboards +With new access control options, not only can you allow Clash dashboards to access the Clash API on your local network, you can also manually limit the websites that can access the API instead of allowing everyone. From 632782c217af98e3cf2f36016aeecf1dda9bacee Mon Sep 17 00:00:00 2001 From: Sakurado Rin Date: Tue, 12 Nov 2024 15:20:32 +0800 Subject: [PATCH 38/39] documentation: Introduce page `status` --- docs/stylesheets/extra.css | 43 ++++++++++++++++++++++++++++++++++++++ mkdocs.yml | 6 ++++++ 2 files changed, 49 insertions(+) create mode 100644 docs/stylesheets/extra.css diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css new file mode 100644 index 000000000..6f13a2abc --- /dev/null +++ b/docs/stylesheets/extra.css @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2016-2023 Martin Donath + * Alex Voss + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +:root { + --md-status--alert: url('data:image/svg+xml;charset=utf-8,'); + --md-status--deprecated: url('data:image/svg+xml;charset=utf-8,'); + --md-status--new: url('data:image/svg+xml;charset=utf-8,'); +} + +.md-status--alert::after { + mask-image: var(--md-status--alert); + -webkit-mask-image: var(--md-status--alert); +} + +.md-status--deprecated::after { + mask-image: var(--md-status--deprecated); + -webkit-mask-image: var(--md-status--deprecated); +} + +.md-status--new::after { + mask-image: var(--md-status--new); + -webkit-mask-image: var(--md-status--new); +} \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 66e8a2e99..41aac8025 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -181,10 +181,16 @@ markdown_extensions: class: mermaid format: !!python/name:pymdownx.superfences.fence_code_format extra: + status: + alert: "Warning present" + deprecated: "Deprecated content included" + new: "New content available" social: - icon: fontawesome/brands/github link: https://github.com/SagerNet/sing-box generator: false +extra_css: + - stylesheets/extra.css plugins: - search - i18n: From f9f257360d2ffc88e1ae7edca7eab541a0042618 Mon Sep 17 00:00:00 2001 From: Sakurado Rin Date: Wed, 13 Nov 2024 11:07:29 +0800 Subject: [PATCH 39/39] documentation: Replace page `icon` with page `status` --- docs/configuration/dns/rule.md | 2 +- docs/configuration/dns/rule.zh.md | 2 +- docs/configuration/dns/rule_action.md | 2 +- docs/configuration/dns/rule_action.zh.md | 2 +- docs/configuration/experimental/clash-api.md | 2 +- docs/configuration/experimental/clash-api.zh.md | 2 +- docs/configuration/inbound/tun.md | 2 +- docs/configuration/inbound/tun.zh.md | 2 +- docs/configuration/outbound/block.md | 2 +- docs/configuration/outbound/block.zh.md | 2 +- docs/configuration/outbound/dns.md | 2 +- docs/configuration/outbound/dns.zh.md | 2 +- docs/configuration/route/geoip.md | 2 +- docs/configuration/route/geoip.zh.md | 2 +- docs/configuration/route/geosite.md | 2 +- docs/configuration/route/geosite.zh.md | 2 +- docs/configuration/route/index.md | 2 +- docs/configuration/route/rule.md | 2 +- docs/configuration/route/rule.zh.md | 2 +- docs/configuration/route/rule_action.md | 2 +- docs/configuration/route/rule_action.zh.md | 2 +- docs/configuration/route/sniff.md | 2 +- docs/configuration/route/sniff.zh.md | 2 +- docs/configuration/rule-set/adguard.md | 2 +- docs/configuration/rule-set/adguard.zh.md | 2 +- docs/configuration/rule-set/headless-rule.md | 2 +- docs/configuration/rule-set/headless-rule.zh.md | 2 +- docs/configuration/rule-set/index.md | 2 +- docs/configuration/rule-set/index.zh.md | 2 +- docs/configuration/rule-set/source-format.md | 2 +- docs/configuration/rule-set/source-format.zh.md | 2 +- docs/configuration/shared/dial.md | 2 +- docs/configuration/shared/dial.zh.md | 2 +- docs/configuration/shared/listen.md | 2 +- docs/configuration/shared/listen.zh.md | 2 +- docs/configuration/shared/tls.md | 2 +- docs/configuration/shared/tls.zh.md | 2 +- 37 files changed, 37 insertions(+), 37 deletions(-) diff --git a/docs/configuration/dns/rule.md b/docs/configuration/dns/rule.md index 1f04b2999..941301ebc 100644 --- a/docs/configuration/dns/rule.md +++ b/docs/configuration/dns/rule.md @@ -1,5 +1,5 @@ --- -icon: material/new-box +status: new --- !!! quote "Changes in sing-box 1.11.0" diff --git a/docs/configuration/dns/rule.zh.md b/docs/configuration/dns/rule.zh.md index e904f8cd0..10c49bf97 100644 --- a/docs/configuration/dns/rule.zh.md +++ b/docs/configuration/dns/rule.zh.md @@ -1,5 +1,5 @@ --- -icon: material/new-box +status: new --- !!! quote "sing-box 1.11.0 中的更改" diff --git a/docs/configuration/dns/rule_action.md b/docs/configuration/dns/rule_action.md index af19131f6..c8d0c2dd9 100644 --- a/docs/configuration/dns/rule_action.md +++ b/docs/configuration/dns/rule_action.md @@ -1,5 +1,5 @@ --- -icon: material/new-box +status: new --- !!! question "Since sing-box 1.11.0" diff --git a/docs/configuration/dns/rule_action.zh.md b/docs/configuration/dns/rule_action.zh.md index 219a5fd75..25accbcf1 100644 --- a/docs/configuration/dns/rule_action.zh.md +++ b/docs/configuration/dns/rule_action.zh.md @@ -1,5 +1,5 @@ --- -icon: material/new-box +status: new --- !!! question "自 sing-box 1.11.0 起" diff --git a/docs/configuration/experimental/clash-api.md b/docs/configuration/experimental/clash-api.md index 7425143eb..b6cc783c9 100644 --- a/docs/configuration/experimental/clash-api.md +++ b/docs/configuration/experimental/clash-api.md @@ -1,5 +1,5 @@ --- -icon: material/new-box +status: new --- !!! quote "Changes in sing-box 1.10.0" diff --git a/docs/configuration/experimental/clash-api.zh.md b/docs/configuration/experimental/clash-api.zh.md index b3d8aeaf9..4d2455818 100644 --- a/docs/configuration/experimental/clash-api.zh.md +++ b/docs/configuration/experimental/clash-api.zh.md @@ -1,5 +1,5 @@ --- -icon: material/new-box +status: new --- !!! quote "sing-box 1.10.0 中的更改" diff --git a/docs/configuration/inbound/tun.md b/docs/configuration/inbound/tun.md index 420f77a90..836b2887b 100644 --- a/docs/configuration/inbound/tun.md +++ b/docs/configuration/inbound/tun.md @@ -1,5 +1,5 @@ --- -icon: material/new-box +status: new --- !!! quote "Changes in sing-box 1.10.0" diff --git a/docs/configuration/inbound/tun.zh.md b/docs/configuration/inbound/tun.zh.md index e7f69483a..c20534620 100644 --- a/docs/configuration/inbound/tun.zh.md +++ b/docs/configuration/inbound/tun.zh.md @@ -1,5 +1,5 @@ --- -icon: material/new-box +status: new --- !!! quote "Changes in sing-box 1.10.0" diff --git a/docs/configuration/outbound/block.md b/docs/configuration/outbound/block.md index f29120ccf..bcb7260bf 100644 --- a/docs/configuration/outbound/block.md +++ b/docs/configuration/outbound/block.md @@ -1,5 +1,5 @@ --- -icon: material/delete-clock +status: deprecated --- !!! failure "Deprecated in sing-box 1.11.0" diff --git a/docs/configuration/outbound/block.zh.md b/docs/configuration/outbound/block.zh.md index 822478cea..6c80e0957 100644 --- a/docs/configuration/outbound/block.zh.md +++ b/docs/configuration/outbound/block.zh.md @@ -1,5 +1,5 @@ --- -icon: material/delete-clock +status: deprecated --- !!! failure "已在 sing-box 1.11.0 废弃" diff --git a/docs/configuration/outbound/dns.md b/docs/configuration/outbound/dns.md index d73360413..5e8ae6637 100644 --- a/docs/configuration/outbound/dns.md +++ b/docs/configuration/outbound/dns.md @@ -1,5 +1,5 @@ --- -icon: material/delete-clock +status: deprecated --- !!! failure "Deprecated in sing-box 1.11.0" diff --git a/docs/configuration/outbound/dns.zh.md b/docs/configuration/outbound/dns.zh.md index 3db2fefb0..91ad4b2a8 100644 --- a/docs/configuration/outbound/dns.zh.md +++ b/docs/configuration/outbound/dns.zh.md @@ -1,5 +1,5 @@ --- -icon: material/delete-clock +status: deprecated --- !!! failure "已在 sing-box 1.11.0 废弃" diff --git a/docs/configuration/route/geoip.md b/docs/configuration/route/geoip.md index a045574aa..5dc04db7a 100644 --- a/docs/configuration/route/geoip.md +++ b/docs/configuration/route/geoip.md @@ -1,5 +1,5 @@ --- -icon: material/delete-clock +status: deprecated --- !!! failure "Deprecated in sing-box 1.8.0" diff --git a/docs/configuration/route/geoip.zh.md b/docs/configuration/route/geoip.zh.md index eb7bbe2d1..24f482e52 100644 --- a/docs/configuration/route/geoip.zh.md +++ b/docs/configuration/route/geoip.zh.md @@ -1,5 +1,5 @@ --- -icon: material/delete-clock +status: deprecated --- !!! failure "已在 sing-box 1.8.0 废弃" diff --git a/docs/configuration/route/geosite.md b/docs/configuration/route/geosite.md index 9a1b9dce0..77d04e71b 100644 --- a/docs/configuration/route/geosite.md +++ b/docs/configuration/route/geosite.md @@ -1,5 +1,5 @@ --- -icon: material/delete-clock +status: deprecated --- !!! failure "Deprecated in sing-box 1.8.0" diff --git a/docs/configuration/route/geosite.zh.md b/docs/configuration/route/geosite.zh.md index 7cec5b208..fff38e9d7 100644 --- a/docs/configuration/route/geosite.zh.md +++ b/docs/configuration/route/geosite.zh.md @@ -1,5 +1,5 @@ --- -icon: material/delete-clock +status: deprecated --- !!! failure "已在 sing-box 1.8.0 废弃" diff --git a/docs/configuration/route/index.md b/docs/configuration/route/index.md index 2b035eb42..74d7bb0eb 100644 --- a/docs/configuration/route/index.md +++ b/docs/configuration/route/index.md @@ -1,5 +1,5 @@ --- -icon: material/new-box +status: new --- # Route diff --git a/docs/configuration/route/rule.md b/docs/configuration/route/rule.md index 43954a78c..0ba61824b 100644 --- a/docs/configuration/route/rule.md +++ b/docs/configuration/route/rule.md @@ -1,5 +1,5 @@ --- -icon: material/new-box +status: new --- !!! quote "Changes in sing-box 1.11.0" diff --git a/docs/configuration/route/rule.zh.md b/docs/configuration/route/rule.zh.md index e5c5f0177..6c01f8bc0 100644 --- a/docs/configuration/route/rule.zh.md +++ b/docs/configuration/route/rule.zh.md @@ -1,5 +1,5 @@ --- -icon: material/new-box +status: new --- !!! quote "sing-box 1.11.0 中的更改" diff --git a/docs/configuration/route/rule_action.md b/docs/configuration/route/rule_action.md index 5e9cfbc22..62a1361b5 100644 --- a/docs/configuration/route/rule_action.md +++ b/docs/configuration/route/rule_action.md @@ -1,5 +1,5 @@ --- -icon: material/new-box +status: new --- ## Final actions diff --git a/docs/configuration/route/rule_action.zh.md b/docs/configuration/route/rule_action.zh.md index 52965fd24..2b0c1f7f7 100644 --- a/docs/configuration/route/rule_action.zh.md +++ b/docs/configuration/route/rule_action.zh.md @@ -1,5 +1,5 @@ --- -icon: material/new-box +status: new --- ## 最终动作 diff --git a/docs/configuration/route/sniff.md b/docs/configuration/route/sniff.md index 40de038cb..d0784ee5b 100644 --- a/docs/configuration/route/sniff.md +++ b/docs/configuration/route/sniff.md @@ -1,5 +1,5 @@ --- -icon: material/new-box +status: new --- !!! quote "Changes in sing-box 1.10.0" diff --git a/docs/configuration/route/sniff.zh.md b/docs/configuration/route/sniff.zh.md index 4efa45381..85249ad76 100644 --- a/docs/configuration/route/sniff.zh.md +++ b/docs/configuration/route/sniff.zh.md @@ -1,5 +1,5 @@ --- -icon: material/new-box +status: new --- !!! quote "sing-box 1.10.0 中的更改" diff --git a/docs/configuration/rule-set/adguard.md b/docs/configuration/rule-set/adguard.md index bda737945..2f7ccd24d 100644 --- a/docs/configuration/rule-set/adguard.md +++ b/docs/configuration/rule-set/adguard.md @@ -1,5 +1,5 @@ --- -icon: material/new-box +status: new --- !!! question "Since sing-box 1.10.0" diff --git a/docs/configuration/rule-set/adguard.zh.md b/docs/configuration/rule-set/adguard.zh.md index 026f2e0be..bc676e5b8 100644 --- a/docs/configuration/rule-set/adguard.zh.md +++ b/docs/configuration/rule-set/adguard.zh.md @@ -1,5 +1,5 @@ --- -icon: material/new-box +status: new --- !!! question "自 sing-box 1.10.0 起" diff --git a/docs/configuration/rule-set/headless-rule.md b/docs/configuration/rule-set/headless-rule.md index bdad22f0d..99f42a7be 100644 --- a/docs/configuration/rule-set/headless-rule.md +++ b/docs/configuration/rule-set/headless-rule.md @@ -1,5 +1,5 @@ --- -icon: material/new-box +status: new --- !!! quote "Changes in sing-box 1.11.0" diff --git a/docs/configuration/rule-set/headless-rule.zh.md b/docs/configuration/rule-set/headless-rule.zh.md index c5281504f..72931522a 100644 --- a/docs/configuration/rule-set/headless-rule.zh.md +++ b/docs/configuration/rule-set/headless-rule.zh.md @@ -1,5 +1,5 @@ --- -icon: material/new-box +status: new --- !!! quote "sing-box 1.11.0 中的更改" diff --git a/docs/configuration/rule-set/index.md b/docs/configuration/rule-set/index.md index bed3fb546..027fc03a0 100644 --- a/docs/configuration/rule-set/index.md +++ b/docs/configuration/rule-set/index.md @@ -1,5 +1,5 @@ --- -icon: material/new-box +status: new --- !!! quote "Changes in sing-box 1.10.0" diff --git a/docs/configuration/rule-set/index.zh.md b/docs/configuration/rule-set/index.zh.md index 083c06bd3..df9941387 100644 --- a/docs/configuration/rule-set/index.zh.md +++ b/docs/configuration/rule-set/index.zh.md @@ -1,5 +1,5 @@ --- -icon: material/new-box +status: new --- !!! quote "sing-box 1.10.0 中的更改" diff --git a/docs/configuration/rule-set/source-format.md b/docs/configuration/rule-set/source-format.md index 1dcc1d447..18ab094b6 100644 --- a/docs/configuration/rule-set/source-format.md +++ b/docs/configuration/rule-set/source-format.md @@ -1,5 +1,5 @@ --- -icon: material/new-box +status: new --- !!! quote "Changes in sing-box 1.11.0" diff --git a/docs/configuration/rule-set/source-format.zh.md b/docs/configuration/rule-set/source-format.zh.md index 3dacaea7c..08425ac4f 100644 --- a/docs/configuration/rule-set/source-format.zh.md +++ b/docs/configuration/rule-set/source-format.zh.md @@ -1,5 +1,5 @@ --- -icon: material/new-box +status: new --- !!! quote "sing-box 1.11.0 中的更改" diff --git a/docs/configuration/shared/dial.md b/docs/configuration/shared/dial.md index 56467510d..a099f1115 100644 --- a/docs/configuration/shared/dial.md +++ b/docs/configuration/shared/dial.md @@ -1,5 +1,5 @@ --- -icon: material/new-box +status: new --- !!! quote "Changes in sing-box 1.11.0" diff --git a/docs/configuration/shared/dial.zh.md b/docs/configuration/shared/dial.zh.md index c81f95bd5..b2736fbf2 100644 --- a/docs/configuration/shared/dial.zh.md +++ b/docs/configuration/shared/dial.zh.md @@ -1,5 +1,5 @@ --- -icon: material/new-box +status: new --- !!! quote "sing-box 1.11.0 中的更改" diff --git a/docs/configuration/shared/listen.md b/docs/configuration/shared/listen.md index fa6a05b97..08a89193d 100644 --- a/docs/configuration/shared/listen.md +++ b/docs/configuration/shared/listen.md @@ -1,5 +1,5 @@ --- -icon: material/delete-clock +status: deprecated --- !!! quote "Changes in sing-box 1.11.0" diff --git a/docs/configuration/shared/listen.zh.md b/docs/configuration/shared/listen.zh.md index 3b472c4df..0ff83fce8 100644 --- a/docs/configuration/shared/listen.zh.md +++ b/docs/configuration/shared/listen.zh.md @@ -1,5 +1,5 @@ --- -icon: material/delete-clock +status: deprecated --- !!! quote "sing-box 1.11.0 中的更改" diff --git a/docs/configuration/shared/tls.md b/docs/configuration/shared/tls.md index 7a6c9d5e0..544b2ee04 100644 --- a/docs/configuration/shared/tls.md +++ b/docs/configuration/shared/tls.md @@ -1,5 +1,5 @@ --- -icon: material/alert-decagram +status: alert --- !!! quote "Changes in sing-box 1.10.0" diff --git a/docs/configuration/shared/tls.zh.md b/docs/configuration/shared/tls.zh.md index b254e0838..7a13bb17d 100644 --- a/docs/configuration/shared/tls.zh.md +++ b/docs/configuration/shared/tls.zh.md @@ -1,5 +1,5 @@ --- -icon: material/alert-decagram +status: alert --- !!! quote "sing-box 1.10.0 中的更改"