Add ping support for direct outbound

This commit is contained in:
世界 2025-02-17 22:00:57 +08:00
parent 1d72a6b30e
commit c002b8321e
No known key found for this signature in database
GPG Key ID: CD109927C34A63C4
17 changed files with 513 additions and 52 deletions

View File

@ -6,6 +6,7 @@ import (
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-tun"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
) )
@ -20,10 +21,16 @@ type Outbound interface {
} }
type OutboundWithPreferredRoutes interface { type OutboundWithPreferredRoutes interface {
Outbound
PreferredDomain(domain string) bool PreferredDomain(domain string) bool
PreferredAddress(address netip.Addr) bool PreferredAddress(address netip.Addr) bool
} }
type DirectRouteOutbound interface {
Outbound
NewDirectRouteConnection(metadata InboundContext, routeContext tun.DirectRouteContext) (tun.DirectRouteDestination, error)
}
type OutboundRegistry interface { type OutboundRegistry interface {
option.OutboundOptionsRegistry option.OutboundOptionsRegistry
CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error) CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)

View File

@ -8,6 +8,7 @@ import (
"sync" "sync"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-tun"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/ntp" "github.com/sagernet/sing/common/ntp"
@ -19,7 +20,7 @@ import (
type Router interface { type Router interface {
Lifecycle Lifecycle
ConnectionRouter ConnectionRouter
PreMatch(metadata InboundContext) error PreMatch(metadata InboundContext, context tun.DirectRouteContext) (tun.DirectRouteDestination, error)
ConnectionRouterEx ConnectionRouterEx
RuleSet(tag string) (RuleSet, bool) RuleSet(tag string) (RuleSet, bool)
NeedWIFIState() bool NeedWIFIState() bool

View File

@ -318,6 +318,14 @@ func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksadd
} }
} }
func (d *DefaultDialer) DialerForICMPNetwork(network string) net.Dialer {
if network == N.NetworkICMPv6 {
return dialerFromTCPDialer(d.dialer6)
} else {
return dialerFromTCPDialer(d.dialer4)
}
}
func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) { func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
if strategy == nil { if strategy == nil {
strategy = d.networkStrategy strategy = d.networkStrategy

4
go.mod
View File

@ -25,7 +25,7 @@ require (
github.com/sagernet/cors v1.2.1 github.com/sagernet/cors v1.2.1
github.com/sagernet/fswatch v0.1.1 github.com/sagernet/fswatch v0.1.1
github.com/sagernet/gomobile v0.1.8 github.com/sagernet/gomobile v0.1.8
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237
github.com/sagernet/quic-go v0.52.0-beta.1 github.com/sagernet/quic-go v0.52.0-beta.1
github.com/sagernet/sing v0.7.6-0.20250815070458-d33ece7a184f github.com/sagernet/sing v0.7.6-0.20250815070458-d33ece7a184f
github.com/sagernet/sing-mux v0.3.3 github.com/sagernet/sing-mux v0.3.3
@ -33,7 +33,7 @@ require (
github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks v0.2.8
github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowsocks2 v0.2.1
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
github.com/sagernet/sing-tun v0.7.0-beta.1 github.com/sagernet/sing-tun v0.7.0-beta.1.0.20250822062121-036d61a0aace
github.com/sagernet/sing-vmess v0.2.7 github.com/sagernet/sing-vmess v0.2.7
github.com/sagernet/smux v1.5.34-mod.2 github.com/sagernet/smux v1.5.34-mod.2
github.com/sagernet/tailscale v1.80.3-mod.5 github.com/sagernet/tailscale v1.80.3-mod.5

8
go.sum
View File

@ -158,8 +158,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/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
github.com/sagernet/gomobile v0.1.8 h1:vXgoN0pjsMONAaYCTdsKBX2T1kxuS7sbT/mZ7PElGoo= github.com/sagernet/gomobile v0.1.8 h1:vXgoN0pjsMONAaYCTdsKBX2T1kxuS7sbT/mZ7PElGoo=
github.com/sagernet/gomobile v0.1.8/go.mod h1:A8l3FlHi2D/+mfcd4HHvk5DGFPW/ShFb9jHP5VmSiDY= github.com/sagernet/gomobile v0.1.8/go.mod h1:A8l3FlHi2D/+mfcd4HHvk5DGFPW/ShFb9jHP5VmSiDY=
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb h1:pprQtDqNgqXkRsXn+0E8ikKOemzmum8bODjSfDene38= github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237 h1:SUPFNB+vSP4RBPrSEgNII+HkfqC8hKMpYLodom4o4EU=
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb/go.mod h1:QkkPEJLw59/tfxgapHta14UL5qMUah5NXhO0Kw2Kan4= github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237/go.mod h1:QkkPEJLw59/tfxgapHta14UL5qMUah5NXhO0Kw2Kan4=
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= 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/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 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
@ -179,8 +179,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
github.com/sagernet/sing-tun v0.7.0-beta.1 h1:mBIFXYAnGO5ey/HcCYanqnBx61E7yF8zTFGRZonGYmY= github.com/sagernet/sing-tun v0.7.0-beta.1.0.20250822062121-036d61a0aace h1:whFyDOTkuDqjNwGMUbVCMiIqpV2iwXwBCZ1E/X9ZgJI=
github.com/sagernet/sing-tun v0.7.0-beta.1/go.mod h1:AHJuRrLbNRJuivuFZ2VhXwDj4ViYp14szG5EkkKAqRQ= github.com/sagernet/sing-tun v0.7.0-beta.1.0.20250822062121-036d61a0aace/go.mod h1:kEYEIq6pYBnKfQ1s4rEuiM+HWNYz96waFccPHbdCM9A=
github.com/sagernet/sing-vmess v0.2.7 h1:2ee+9kO0xW5P4mfe6TYVWf9VtY8k1JhNysBqsiYj0sk= github.com/sagernet/sing-vmess v0.2.7 h1:2ee+9kO0xW5P4mfe6TYVWf9VtY8k1JhNysBqsiYj0sk=
github.com/sagernet/sing-vmess v0.2.7/go.mod h1:5aYoOtYksAyS0NXDm0qKeTYW1yoE1bJVcv+XLcVoyJs= github.com/sagernet/sing-vmess v0.2.7/go.mod h1:5aYoOtYksAyS0NXDm0qKeTYW1yoE1bJVcv+XLcVoyJs=
github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4= github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4=

View File

@ -13,6 +13,7 @@ import (
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
tun "github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
@ -29,10 +30,12 @@ var (
_ N.ParallelDialer = (*Outbound)(nil) _ N.ParallelDialer = (*Outbound)(nil)
_ dialer.ParallelNetworkDialer = (*Outbound)(nil) _ dialer.ParallelNetworkDialer = (*Outbound)(nil)
_ dialer.DirectDialer = (*Outbound)(nil) _ dialer.DirectDialer = (*Outbound)(nil)
_ adapter.DirectRouteOutbound = (*Outbound)(nil)
) )
type Outbound struct { type Outbound struct {
outbound.Adapter outbound.Adapter
ctx context.Context
logger logger.ContextLogger logger logger.ContextLogger
dialer dialer.ParallelInterfaceDialer dialer dialer.ParallelInterfaceDialer
domainStrategy C.DomainStrategy domainStrategy C.DomainStrategy
@ -58,7 +61,8 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
return nil, err return nil, err
} }
outbound := &Outbound{ outbound := &Outbound{
Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions), Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMPv4, N.NetworkICMPv6}, options.DialerOptions),
ctx: ctx,
logger: logger, logger: logger,
//nolint:staticcheck //nolint:staticcheck
domainStrategy: C.DomainStrategy(options.DomainStrategy), domainStrategy: C.DomainStrategy(options.DomainStrategy),
@ -146,6 +150,11 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
return conn, nil return conn, nil
} }
func (h *Outbound) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext) (tun.DirectRouteDestination, error) {
h.logger.Info("linked ", metadata.Network, " connection to ", metadata.Destination.AddrString())
return tun.NewICMPDestination(h.ctx, h.logger, common.MustCast[*dialer.DefaultDialer](h.dialer).DialerForICMPNetwork(metadata.Network), metadata.Network, metadata.Destination.Addr, routeContext)
}
func (h *Outbound) 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) ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.Tag() metadata.Outbound = h.Tag()

View File

@ -20,6 +20,7 @@ import (
"github.com/sagernet/gvisor/pkg/tcpip/adapters/gonet" "github.com/sagernet/gvisor/pkg/tcpip/adapters/gonet"
"github.com/sagernet/gvisor/pkg/tcpip/header" "github.com/sagernet/gvisor/pkg/tcpip/header"
"github.com/sagernet/gvisor/pkg/tcpip/stack" "github.com/sagernet/gvisor/pkg/tcpip/stack"
"github.com/sagernet/gvisor/pkg/tcpip/transport/icmp"
"github.com/sagernet/gvisor/pkg/tcpip/transport/tcp" "github.com/sagernet/gvisor/pkg/tcpip/transport/tcp"
"github.com/sagernet/gvisor/pkg/tcpip/transport/udp" "github.com/sagernet/gvisor/pkg/tcpip/transport/udp"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
@ -240,8 +241,10 @@ func (t *Endpoint) Start(stage adapter.StartStage) error {
return gonet.TranslateNetstackError(gErr) return gonet.TranslateNetstackError(gErr)
} }
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(t.ctx, ipStack, t).HandlePacket) ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(t.ctx, ipStack, t).HandlePacket)
udpForwarder := tun.NewUDPForwarder(t.ctx, ipStack, t, t.udpTimeout) ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.NewUDPForwarder(t.ctx, ipStack, t, t.udpTimeout).HandlePacket)
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, udpForwarder.HandlePacket) icmpForwarder := tun.NewICMPForwarder(t.ctx, ipStack, t, t.udpTimeout)
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket)
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket)
t.stack = ipStack t.stack = ipStack
localBackend := t.server.ExportLocalBackend() localBackend := t.server.ExportLocalBackend()
@ -415,7 +418,7 @@ func (t *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
return udpConn, nil return udpConn, nil
} }
func (t *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr) error { func (t *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext) (tun.DirectRouteDestination, error) {
tsFilter := t.filter.Load() tsFilter := t.filter.Load()
if tsFilter != nil { if tsFilter != nil {
var ipProto ipproto.Proto var ipProto ipproto.Proto
@ -428,9 +431,9 @@ func (t *Endpoint) PrepareConnection(network string, source M.Socksaddr, destina
response := tsFilter.Check(source.Addr, destination.Addr, destination.Port, ipProto) response := tsFilter.Check(source.Addr, destination.Addr, destination.Port, ipProto)
switch response { switch response {
case filter.Drop: case filter.Drop:
return syscall.ECONNRESET return nil, syscall.ECONNREFUSED
case filter.DropSilently: case filter.DropSilently:
return tun.ErrDrop return nil, tun.ErrDrop
} }
} }
return t.router.PreMatch(adapter.InboundContext{ return t.router.PreMatch(adapter.InboundContext{
@ -439,7 +442,7 @@ func (t *Endpoint) PrepareConnection(network string, source M.Socksaddr, destina
Network: network, Network: network,
Source: source, Source: source,
Destination: destination, Destination: destination,
}) }, routeContext)
} }
func (t *Endpoint) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { func (t *Endpoint) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {

View File

@ -8,6 +8,7 @@ import (
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"syscall"
"time" "time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
@ -454,15 +455,21 @@ func (t *Inbound) Close() error {
) )
} }
func (t *Inbound) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr) error { func (t *Inbound) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext) (tun.DirectRouteDestination, error) {
return t.router.PreMatch(adapter.InboundContext{ routeDestination, err := t.router.PreMatch(adapter.InboundContext{
Inbound: t.tag, Inbound: t.tag,
InboundType: C.TypeTun, InboundType: C.TypeTun,
Network: network, Network: network,
Source: source, Source: source,
Destination: destination, Destination: destination,
InboundOptions: t.inboundOptions, InboundOptions: t.inboundOptions,
}) }, routeContext)
if err != nil {
if !E.IsMulti(err, tun.ErrDrop, syscall.ECONNREFUSED) {
t.logger.Warn(err)
}
}
return routeDestination, err
} }
func (t *Inbound) 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) {

View File

@ -13,6 +13,7 @@ import (
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/transport/wireguard" "github.com/sagernet/sing-box/transport/wireguard"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
@ -40,7 +41,7 @@ type Endpoint struct {
func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardEndpointOptions) (adapter.Endpoint, error) { func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardEndpointOptions) (adapter.Endpoint, error) {
ep := &Endpoint{ ep := &Endpoint{
Adapter: endpoint.NewAdapterWithDialerOptions(C.TypeWireGuard, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions), Adapter: endpoint.NewAdapterWithDialerOptions(C.TypeWireGuard, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMPv4, N.NetworkICMPv6}, options.DialerOptions),
ctx: ctx, ctx: ctx,
router: router, router: router,
dnsRouter: service.FromContext[adapter.DNSRouter](ctx), dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
@ -124,14 +125,14 @@ func (w *Endpoint) Close() error {
return w.endpoint.Close() return w.endpoint.Close()
} }
func (w *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr) error { func (w *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, context tun.DirectRouteContext) (tun.DirectRouteDestination, error) {
return w.router.PreMatch(adapter.InboundContext{ return w.router.PreMatch(adapter.InboundContext{
Inbound: w.Tag(), Inbound: w.Tag(),
InboundType: w.Type(), InboundType: w.Type(),
Network: network, Network: network,
Source: source, Source: source,
Destination: destination, Destination: destination,
}) }, context)
} }
func (w *Endpoint) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { func (w *Endpoint) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
@ -220,3 +221,12 @@ func (w *Endpoint) PreferredDomain(domain string) bool {
func (w *Endpoint) PreferredAddress(address netip.Addr) bool { func (w *Endpoint) PreferredAddress(address netip.Addr) bool {
return w.endpoint.Lookup(address) != nil return w.endpoint.Lookup(address) != nil
} }
func (w *Endpoint) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext) (tun.DirectRouteDestination, error) {
destination, err := w.endpoint.NewDirectRouteConnection(metadata, routeContext)
if err != nil {
return nil, err
}
w.logger.Info("linked ", metadata.Network, " connection to ", metadata.Destination.AddrString())
return destination, nil
}

View File

@ -17,6 +17,7 @@ import (
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
R "github.com/sagernet/sing-box/route/rule" R "github.com/sagernet/sing-box/route/rule"
"github.com/sagernet/sing-mux" "github.com/sagernet/sing-mux"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing-vmess" "github.com/sagernet/sing-vmess"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
@ -258,19 +259,37 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
return nil return nil
} }
func (r *Router) PreMatch(metadata adapter.InboundContext) error { func (r *Router) PreMatch(metadata adapter.InboundContext, routeContext tun.DirectRouteContext) (tun.DirectRouteDestination, error) {
selectedRule, _, _, _, err := r.matchRule(r.ctx, &metadata, true, nil, nil) selectedRule, _, _, _, err := r.matchRule(r.ctx, &metadata, true, nil, nil)
if err != nil { if err != nil {
return err return nil, err
} }
if selectedRule == nil { if selectedRule != nil {
return nil switch action := selectedRule.Action().(type) {
case *R.RuleActionReject:
return nil, action.Error(context.Background())
case *R.RuleActionRoute:
if routeContext == nil {
return nil, nil
} }
rejectAction, isReject := selectedRule.Action().(*R.RuleActionReject) outbound, loaded := r.outbound.Outbound(action.Outbound)
if !isReject { if !loaded {
return nil return nil, E.New("outbound not found: ", action.Outbound)
} }
return rejectAction.Error(context.Background()) if !common.Contains(outbound.Network(), metadata.Network) {
return nil, E.New(metadata.Network, " is not supported by outbound: ", action.Outbound)
}
return outbound.(adapter.DirectRouteOutbound).NewDirectRouteConnection(metadata, routeContext)
}
}
if metadata.Network != N.NetworkICMPv4 && metadata.Network != N.NetworkICMPv6 {
return nil, nil
}
defaultOutbound := r.outbound.Default()
if !common.Contains(defaultOutbound.Network(), metadata.Network) {
return nil, E.New(metadata.Network, " is not supported by default outbound: ", defaultOutbound.Tag())
}
return defaultOutbound.(adapter.DirectRouteOutbound).NewDirectRouteConnection(metadata, routeContext)
} }
func (r *Router) matchRule( func (r *Router) matchRule(

View File

@ -5,6 +5,7 @@ import (
"net/netip" "net/netip"
"time" "time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-tun" "github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/logger"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
@ -17,6 +18,8 @@ type Device interface {
N.Dialer N.Dialer
Start() error Start() error
SetDevice(device *device.Device) SetDevice(device *device.Device)
Inet4Address() netip.Addr
Inet6Address() netip.Addr
} }
type DeviceOptions struct { type DeviceOptions struct {
@ -41,3 +44,8 @@ func NewDevice(options DeviceOptions) (Device, error) {
return newSystemStackDevice(options) return newSystemStackDevice(options)
} }
} }
type NatDevice interface {
Device
CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext) (tun.DirectRouteDestination, error)
}

View File

@ -0,0 +1,85 @@
package wireguard
import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/buf"
)
var _ Device = (*natDeviceWrapper)(nil)
type natDeviceWrapper struct {
Device
gVisorOutbound
packetOutbound chan *buf.Buffer
mapping *tun.NatMapping
writer *tun.NatWriter
buffer [][]byte
}
func NewNATDevice(upstream Device, ipRewrite bool) NatDevice {
wrapper := &natDeviceWrapper{
Device: upstream,
gVisorOutbound: newGVisorOutbound(),
packetOutbound: make(chan *buf.Buffer, 256),
mapping: tun.NewNatMapping(ipRewrite),
}
if ipRewrite {
wrapper.writer = tun.NewNatWriter(upstream.Inet4Address(), upstream.Inet6Address())
}
return wrapper
}
func (d *natDeviceWrapper) Write(bufs [][]byte, offset int) (int, error) {
for _, buffer := range bufs {
handled, err := d.mapping.WritePacket(buffer[offset:])
if handled {
if err != nil {
return 0, err
}
} else {
d.buffer = append(d.buffer, buffer)
}
}
if len(d.buffer) > 0 {
_, err := d.Device.Write(d.buffer, offset)
if err != nil {
return 0, err
}
d.buffer = d.buffer[:0]
}
return 0, nil
}
func (d *natDeviceWrapper) CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext) (tun.DirectRouteDestination, error) {
session := tun.DirectRouteSession{
Source: metadata.Source.Addr,
Destination: metadata.Destination.Addr,
}
d.mapping.CreateSession(session, routeContext)
return &natDestinationWrapper{d, session}, nil
}
var _ tun.DirectRouteDestination = (*natDestinationWrapper)(nil)
type natDestinationWrapper struct {
device *natDeviceWrapper
session tun.DirectRouteSession
}
func (d *natDestinationWrapper) WritePacket(buffer *buf.Buffer) error {
if d.device.writer != nil {
d.device.writer.RewritePacket(buffer.Bytes())
}
d.device.packetOutbound <- buffer
return nil
}
func (d *natDestinationWrapper) Close() error {
d.device.mapping.DeleteSession(d.session)
return nil
}
func (d *natDestinationWrapper) Timeout() bool {
return false
}

View File

@ -0,0 +1,48 @@
//go:build with_gvisor
package wireguard
import (
"github.com/sagernet/gvisor/pkg/tcpip/stack"
)
type gVisorOutbound struct {
outbound chan *stack.PacketBuffer
}
func newGVisorOutbound() gVisorOutbound {
return gVisorOutbound{
outbound: make(chan *stack.PacketBuffer, 256),
}
}
func (d *natDeviceWrapper) Read(bufs [][]byte, sizes []int, offset int) (n int, err error) {
select {
case packet := <-d.outbound:
defer packet.DecRef()
var copyN int
/*rangeIterate(packet.Data().AsRange(), func(view *buffer.View) {
copyN += copy(bufs[0][offset+copyN:], view.AsSlice())
})*/
for _, view := range packet.AsSlices() {
copyN += copy(bufs[0][offset+copyN:], view)
}
sizes[0] = copyN
return 1, nil
case packet := <-d.packetOutbound:
defer packet.Release()
sizes[0] = copy(bufs[0][offset:], packet.Bytes())
return 1, nil
default:
}
return d.Device.Read(bufs, sizes, offset)
}
func (d *natDestinationWrapper) WritePacketBuffer(packetBuffer *stack.PacketBuffer) error {
println("read from wg")
if d.device.writer != nil {
d.device.writer.RewritePacketBuffer(packetBuffer)
}
d.device.outbound <- packetBuffer
return nil
}

View File

@ -0,0 +1,20 @@
//go:build !with_gvisor
package wireguard
type gVisorOutbound struct{}
func newGVisorOutbound() gVisorOutbound {
return gVisorOutbound{}
}
func (d *natDeviceWrapper) Read(bufs [][]byte, sizes []int, offset int) (n int, err error) {
select {
case packet := <-d.packetOutbound:
defer packet.Release()
sizes[0] = copy(bufs[0][offset:], packet.Bytes())
return 1, nil
default:
}
return d.Device.Read(bufs, sizes, offset)
}

View File

@ -5,6 +5,7 @@ package wireguard
import ( import (
"context" "context"
"net" "net"
"net/netip"
"os" "os"
"github.com/sagernet/gvisor/pkg/buffer" "github.com/sagernet/gvisor/pkg/buffer"
@ -14,9 +15,12 @@ import (
"github.com/sagernet/gvisor/pkg/tcpip/network/ipv4" "github.com/sagernet/gvisor/pkg/tcpip/network/ipv4"
"github.com/sagernet/gvisor/pkg/tcpip/network/ipv6" "github.com/sagernet/gvisor/pkg/tcpip/network/ipv6"
"github.com/sagernet/gvisor/pkg/tcpip/stack" "github.com/sagernet/gvisor/pkg/tcpip/stack"
"github.com/sagernet/gvisor/pkg/tcpip/transport/icmp"
"github.com/sagernet/gvisor/pkg/tcpip/transport/tcp" "github.com/sagernet/gvisor/pkg/tcpip/transport/tcp"
"github.com/sagernet/gvisor/pkg/tcpip/transport/udp" "github.com/sagernet/gvisor/pkg/tcpip/transport/udp"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-tun" "github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
@ -24,17 +28,20 @@ import (
wgTun "github.com/sagernet/wireguard-go/tun" wgTun "github.com/sagernet/wireguard-go/tun"
) )
var _ Device = (*stackDevice)(nil) var _ NatDevice = (*stackDevice)(nil)
type stackDevice struct { type stackDevice struct {
stack *stack.Stack stack *stack.Stack
mtu uint32 mtu uint32
events chan wgTun.Event events chan wgTun.Event
outbound chan *stack.PacketBuffer outbound chan *stack.PacketBuffer
packetOutbound chan *buf.Buffer
done chan struct{} done chan struct{}
dispatcher stack.NetworkDispatcher dispatcher stack.NetworkDispatcher
addr4 tcpip.Address addr4 tcpip.Address
addr6 tcpip.Address addr6 tcpip.Address
mapping *tun.NatMapping
writer *tun.NatWriter
} }
func newStackDevice(options DeviceOptions) (*stackDevice, error) { func newStackDevice(options DeviceOptions) (*stackDevice, error) {
@ -42,7 +49,9 @@ func newStackDevice(options DeviceOptions) (*stackDevice, error) {
mtu: options.MTU, mtu: options.MTU,
events: make(chan wgTun.Event, 1), events: make(chan wgTun.Event, 1),
outbound: make(chan *stack.PacketBuffer, 256), outbound: make(chan *stack.PacketBuffer, 256),
packetOutbound: make(chan *buf.Buffer, 256),
done: make(chan struct{}), done: make(chan struct{}),
mapping: tun.NewNatMapping(true),
} }
ipStack, err := tun.NewGVisorStack((*wireEndpoint)(tunDevice)) ipStack, err := tun.NewGVisorStack((*wireEndpoint)(tunDevice))
if err != nil { if err != nil {
@ -68,10 +77,14 @@ func newStackDevice(options DeviceOptions) (*stackDevice, error) {
return nil, E.New("parse local address ", protoAddr.AddressWithPrefix, ": ", gErr.String()) return nil, E.New("parse local address ", protoAddr.AddressWithPrefix, ": ", gErr.String())
} }
} }
tunDevice.writer = tun.NewNatWriter(tunDevice.Inet4Address(), tunDevice.Inet6Address())
tunDevice.stack = ipStack tunDevice.stack = ipStack
if options.Handler != nil { if options.Handler != nil {
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(options.Context, ipStack, options.Handler).HandlePacket) ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(options.Context, ipStack, options.Handler).HandlePacket)
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.NewUDPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout).HandlePacket) ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.NewUDPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout).HandlePacket)
icmpForwarder := tun.NewICMPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout)
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket)
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket)
} }
return tunDevice, nil return tunDevice, nil
} }
@ -130,6 +143,14 @@ func (w *stackDevice) ListenPacket(ctx context.Context, destination M.Socksaddr)
return udpConn, nil return udpConn, nil
} }
func (w *stackDevice) Inet4Address() netip.Addr {
return netip.AddrFrom4(w.addr4.As4())
}
func (w *stackDevice) Inet6Address() netip.Addr {
return netip.AddrFrom16(w.addr6.As16())
}
func (w *stackDevice) SetDevice(device *device.Device) { func (w *stackDevice) SetDevice(device *device.Device) {
} }
@ -144,20 +165,24 @@ func (w *stackDevice) File() *os.File {
func (w *stackDevice) Read(bufs [][]byte, sizes []int, offset int) (count int, err error) { func (w *stackDevice) Read(bufs [][]byte, sizes []int, offset int) (count int, err error) {
select { select {
case packetBuffer, ok := <-w.outbound: case packet, ok := <-w.outbound:
if !ok { if !ok {
return 0, os.ErrClosed return 0, os.ErrClosed
} }
defer packetBuffer.DecRef() defer packet.DecRef()
p := bufs[0] var copyN int
p = p[offset:] /*rangeIterate(packet.Data().AsRange(), func(view *buffer.View) {
n := 0 copyN += copy(bufs[0][offset+copyN:], view.AsSlice())
for _, slice := range packetBuffer.AsSlices() { })*/
n += copy(p[n:], slice) for _, view := range packet.AsSlices() {
copyN += copy(bufs[0][offset+copyN:], view)
} }
sizes[0] = n sizes[0] = copyN
count = 1 return 1, nil
return case packet := <-w.packetOutbound:
defer packet.Release()
sizes[0] = copy(bufs[0][offset:], packet.Bytes())
return 1, nil
case <-w.done: case <-w.done:
return 0, os.ErrClosed return 0, os.ErrClosed
} }
@ -169,6 +194,14 @@ func (w *stackDevice) Write(bufs [][]byte, offset int) (count int, err error) {
if len(b) == 0 { if len(b) == 0 {
continue continue
} }
handled, err := w.mapping.WritePacket(b)
if handled {
if err != nil {
return count, err
}
count++
continue
}
var networkProtocol tcpip.NetworkProtocolNumber var networkProtocol tcpip.NetworkProtocolNumber
switch header.IPVersion(b) { switch header.IPVersion(b) {
case header.IPv4Version: case header.IPv4Version:
@ -282,3 +315,157 @@ func (ep *wireEndpoint) Close() {
func (ep *wireEndpoint) SetOnCloseAction(f func()) { func (ep *wireEndpoint) SetOnCloseAction(f func()) {
} }
func (w *stackDevice) CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext) (tun.DirectRouteDestination, error) {
/* var wq waiter.Queue
ep, err := raw.NewEndpoint(w.stack, ipv4.ProtocolNumber, icmp.ProtocolNumber4, &wq)
if err != nil {
return nil, E.Cause(gonet.TranslateNetstackError(err), "create endpoint")
}
err = ep.Connect(tcpip.FullAddress{
NIC: tun.DefaultNIC,
Port: metadata.Destination.Port,
Addr: tun.AddressFromAddr(metadata.Destination.Addr),
})
if err != nil {
ep.Close()
return nil, E.Cause(gonet.TranslateNetstackError(err), "ICMP connect ", metadata.Destination)
}
fmt.Println("linked ", metadata.Network, " connection to ", metadata.Destination.AddrString())
destination := &endpointNatDestination{
ep: ep,
wq: &wq,
context: routeContext,
}
go destination.loopRead()
return destination, nil*/
session := tun.DirectRouteSession{
Source: metadata.Source.Addr,
Destination: metadata.Destination.Addr,
}
w.mapping.CreateSession(session, routeContext)
return &stackNatDestination{
device: w,
session: session,
}, nil
}
type stackNatDestination struct {
device *stackDevice
session tun.DirectRouteSession
}
func (d *stackNatDestination) WritePacket(buffer *buf.Buffer) error {
if d.device.writer != nil {
d.device.writer.RewritePacket(buffer.Bytes())
}
d.device.packetOutbound <- buffer
return nil
}
func (d *stackNatDestination) WritePacketBuffer(buffer *stack.PacketBuffer) error {
if d.device.writer != nil {
d.device.writer.RewritePacketBuffer(buffer)
}
d.device.outbound <- buffer
return nil
}
func (d *stackNatDestination) Close() error {
d.device.mapping.DeleteSession(d.session)
return nil
}
func (d *stackNatDestination) Timeout() bool {
return false
}
/*type endpointNatDestination struct {
ep tcpip.Endpoint
wq *waiter.Queue
networkProto tcpip.NetworkProtocolNumber
context tun.DirectRouteContext
done chan struct{}
}
func (d *endpointNatDestination) loopRead() {
for {
println("start read")
buffer, err := commonRead(d.ep, d.wq, d.done)
if err != nil {
log.Error(err)
return
}
println("done read")
ipHdr := header.IPv4(buffer.Bytes())
if ipHdr.TransportProtocol() != header.ICMPv4ProtocolNumber {
buffer.Release()
continue
}
icmpHdr := header.ICMPv4(ipHdr.Payload())
if icmpHdr.Type() != header.ICMPv4EchoReply {
buffer.Release()
continue
}
fmt.Println("read echo reply")
_ = d.context.WritePacket(ipHdr)
buffer.Release()
}
}
func commonRead(ep tcpip.Endpoint, wq *waiter.Queue, done chan struct{}) (*buf.Buffer, error) {
buffer := buf.NewPacket()
result, err := ep.Read(buffer, tcpip.ReadOptions{})
if err != nil {
if _, ok := err.(*tcpip.ErrWouldBlock); ok {
waitEntry, notifyCh := waiter.NewChannelEntry(waiter.ReadableEvents)
wq.EventRegister(&waitEntry)
defer wq.EventUnregister(&waitEntry)
for {
result, err = ep.Read(buffer, tcpip.ReadOptions{})
if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
break
}
select {
case <-notifyCh:
case <-done:
buffer.Release()
return nil, context.DeadlineExceeded
}
}
}
return nil, gonet.TranslateNetstackError(err)
}
buffer.Truncate(result.Count)
return buffer, nil
}
func (d *endpointNatDestination) WritePacket(buffer *buf.Buffer) error {
_, err := d.ep.Write(buffer, tcpip.WriteOptions{})
if err != nil {
return gonet.TranslateNetstackError(err)
}
return nil
}
func (d *endpointNatDestination) WritePacketBuffer(buffer *stack.PacketBuffer) error {
data := buffer.ToView().AsSlice()
println("write echo request buffer :" + fmt.Sprint(data))
_, err := d.ep.Write(bytes.NewReader(data), tcpip.WriteOptions{})
if err != nil {
log.Error(err)
return gonet.TranslateNetstackError(err)
}
return nil
}
func (d *endpointNatDestination) Close() error {
d.ep.Abort()
close(d.done)
return nil
}
func (d *endpointNatDestination) Timeout() bool {
return false
}
*/

View File

@ -28,16 +28,36 @@ type systemDevice struct {
batchDevice tun.LinuxTUN batchDevice tun.LinuxTUN
events chan wgTun.Event events chan wgTun.Event
closeOnce sync.Once closeOnce sync.Once
addr4 netip.Addr
addr6 netip.Addr
} }
func newSystemDevice(options DeviceOptions) (*systemDevice, error) { func newSystemDevice(options DeviceOptions) (*systemDevice, error) {
if options.Name == "" { if options.Name == "" {
options.Name = tun.CalculateInterfaceName("wg") options.Name = tun.CalculateInterfaceName("wg")
} }
var inet4Address netip.Addr
var inet6Address netip.Addr
if len(options.Address) > 0 {
if prefix := common.Find(options.Address, func(it netip.Prefix) bool {
return it.Addr().Is4()
}); prefix.IsValid() {
inet4Address = prefix.Addr()
}
}
if len(options.Address) > 0 {
if prefix := common.Find(options.Address, func(it netip.Prefix) bool {
return it.Addr().Is6()
}); prefix.IsValid() {
inet6Address = prefix.Addr()
}
}
return &systemDevice{ return &systemDevice{
options: options, options: options,
dialer: options.CreateDialer(options.Name), dialer: options.CreateDialer(options.Name),
events: make(chan wgTun.Event, 1), events: make(chan wgTun.Event, 1),
addr4: inet4Address,
addr6: inet6Address,
}, nil }, nil
} }
@ -49,6 +69,14 @@ func (w *systemDevice) ListenPacket(ctx context.Context, destination M.Socksaddr
return w.dialer.ListenPacket(ctx, destination) return w.dialer.ListenPacket(ctx, destination)
} }
func (w *systemDevice) Inet4Address() netip.Addr {
return w.addr4
}
func (w *systemDevice) Inet6Address() netip.Addr {
return w.addr6
}
func (w *systemDevice) SetDevice(device *device.Device) { func (w *systemDevice) SetDevice(device *device.Device) {
} }

View File

@ -12,6 +12,8 @@ import (
"strings" "strings"
"unsafe" "unsafe"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
@ -31,6 +33,7 @@ type Endpoint struct {
ipcConf string ipcConf string
allowedAddress []netip.Prefix allowedAddress []netip.Prefix
tunDevice Device tunDevice Device
natDevice NatDevice
device *device.Device device *device.Device
allowedIPs *device.AllowedIPs allowedIPs *device.AllowedIPs
pause pause.Manager pause pause.Manager
@ -114,12 +117,17 @@ func NewEndpoint(options EndpointOptions) (*Endpoint, error) {
if err != nil { if err != nil {
return nil, E.Cause(err, "create WireGuard device") return nil, E.Cause(err, "create WireGuard device")
} }
natDevice, isNatDevice := tunDevice.(NatDevice)
if !isNatDevice {
natDevice = NewNATDevice(tunDevice, true)
}
return &Endpoint{ return &Endpoint{
options: options, options: options,
peers: peers, peers: peers,
ipcConf: ipcConf, ipcConf: ipcConf,
allowedAddress: allowedAddresses, allowedAddress: allowedAddresses,
tunDevice: tunDevice, tunDevice: tunDevice,
natDevice: natDevice,
}, nil }, nil
} }
@ -179,7 +187,13 @@ func (e *Endpoint) Start(resolve bool) error {
e.options.Logger.Error(fmt.Sprintf(strings.ToLower(format), args...)) e.options.Logger.Error(fmt.Sprintf(strings.ToLower(format), args...))
}, },
} }
wgDevice := device.NewDevice(e.options.Context, e.tunDevice, bind, logger, e.options.Workers) var deviceInput Device
if e.natDevice != nil {
deviceInput = e.natDevice
} else {
deviceInput = e.tunDevice
}
wgDevice := device.NewDevice(e.options.Context, deviceInput, bind, logger, e.options.Workers)
e.tunDevice.SetDevice(wgDevice) e.tunDevice.SetDevice(wgDevice)
ipcConf := e.ipcConf ipcConf := e.ipcConf
for _, peer := range e.peers { for _, peer := range e.peers {
@ -229,6 +243,13 @@ func (e *Endpoint) Lookup(address netip.Addr) *device.Peer {
return e.allowedIPs.Lookup(address.AsSlice()) return e.allowedIPs.Lookup(address.AsSlice())
} }
func (e *Endpoint) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext) (tun.DirectRouteDestination, error) {
if e.natDevice == nil {
return nil, os.ErrInvalid
}
return e.natDevice.CreateDestination(metadata, routeContext)
}
func (e *Endpoint) onPauseUpdated(event int) { func (e *Endpoint) onPauseUpdated(event int) {
switch event { switch event {
case pause.EventDevicePaused, pause.EventNetworkPause: case pause.EventDevicePaused, pause.EventNetworkPause: