diff --git a/adapter/router.go b/adapter/router.go index 592882e95..1de092b1e 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -19,6 +19,7 @@ type Router interface { Lifecycle ConnectionRouter PreMatch(metadata InboundContext, firstPacket []byte) PreMatchResult + HijackDNSPacket(ctx context.Context, payload []byte, writer N.PacketWriter, metadata InboundContext) ConnectionRouterEx RuleSet(tag string) (RuleSet, bool) Rules() []Rule @@ -37,6 +38,7 @@ const ( PreMatchReject PreMatchDrop PreMatchBypass + PreMatchHijackDNS ) type PreMatchResult struct { @@ -92,6 +94,8 @@ func JudgeFlow(router Router, inbound string, inboundType string, network uint8, return tun.FlowVerdict{Action: tun.ActionDrop} case PreMatchBypass: return tun.FlowVerdict{Action: tun.ActionBypass} + case PreMatchHijackDNS: + return tun.FlowVerdict{Action: tun.ActionHijackDNS} default: return tun.FlowVerdict{Action: tun.ActionAccept} } diff --git a/go.mod b/go.mod index 5004a6de4..9e0f5fd6c 100644 --- a/go.mod +++ b/go.mod @@ -53,7 +53,7 @@ require ( github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1 github.com/sagernet/sing-snell v0.0.0-20260710094516-a4e97ee24beb - github.com/sagernet/sing-tun v0.8.12-0.20260717024008-39eed1f6361d + github.com/sagernet/sing-tun v0.8.12-0.20260719050255-7e7bd198288c github.com/sagernet/sing-usbip v0.0.0-20260616101517-efb91521eddb github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 github.com/sagernet/smux v1.5.50-sing-box-mod.1 diff --git a/go.sum b/go.sum index 30e2cf36e..d1793bf96 100644 --- a/go.sum +++ b/go.sum @@ -299,8 +299,8 @@ github.com/sagernet/sing-shadowtls v0.2.1 h1:ZiHZdnEnP+YS73NMsxiZmIFCwNd0M4k7PkG github.com/sagernet/sing-shadowtls v0.2.1/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= github.com/sagernet/sing-snell v0.0.0-20260710094516-a4e97ee24beb h1:VvU2/PZqP5tbKTDq0BxkhRO8ZnKI4UJzziakgBiP2Qg= github.com/sagernet/sing-snell v0.0.0-20260710094516-a4e97ee24beb/go.mod h1:PcwzX/Xvqky0EP3kGt8OCjYb3R1pydenPHNQZcPZmXY= -github.com/sagernet/sing-tun v0.8.12-0.20260717024008-39eed1f6361d h1:UXUGxGTwotY+R+QkRGvhj/N148SDnwMh/d9Hefn5X7Y= -github.com/sagernet/sing-tun v0.8.12-0.20260717024008-39eed1f6361d/go.mod h1:F/gRq5VX1WN/OZtsvbN2JjXXuNl2ATJglHMSk1/iN9U= +github.com/sagernet/sing-tun v0.8.12-0.20260719050255-7e7bd198288c h1:jS5eWD9PyXhHY89DueRDgenfcrfpyPOuuZIoNfnBmOs= +github.com/sagernet/sing-tun v0.8.12-0.20260719050255-7e7bd198288c/go.mod h1:F/gRq5VX1WN/OZtsvbN2JjXXuNl2ATJglHMSk1/iN9U= github.com/sagernet/sing-usbip v0.0.0-20260616101517-efb91521eddb h1:KEMbfexD4DvrQGYWwx6r+AwH9Veh8z6cnBZmtCS2G+0= github.com/sagernet/sing-usbip v0.0.0-20260616101517-efb91521eddb/go.mod h1:D4CnJX3MNAAANhbQUxfIRgBdnvlTEaV7h6ojedcs+pw= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o= diff --git a/protocol/openconnect/client.go b/protocol/openconnect/client.go index 553fb9a6b..dd78d72de 100644 --- a/protocol/openconnect/client.go +++ b/protocol/openconnect/client.go @@ -420,6 +420,10 @@ func (e *Endpoint) JudgeFlow(network uint8, source netip.AddrPort, destination n return judgeOpenConnectFlow(e.router, e.Tag(), e.Type(), e.state.Load().localAddresses, network, source, destination, firstPacket) } +func (e *Endpoint) NewDNSPacket(payload []byte, source M.Socksaddr, destination M.Socksaddr, writer N.PacketWriter) { + e.newDNSPacket(log.ContextWithNewID(e.loopContext), e, payload, source, destination, writer) +} + func (e *Endpoint) ready() bool { state := e.state.Load() return state.started && state.tunnelConfigured diff --git a/protocol/openconnect/endpoint.go b/protocol/openconnect/endpoint.go index 62096692d..9cf4021e0 100644 --- a/protocol/openconnect/endpoint.go +++ b/protocol/openconnect/endpoint.go @@ -68,6 +68,18 @@ func (e *endpointBase) newPacketConnection(ctx context.Context, endpoint adapter e.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } +func (e *endpointBase) newDNSPacket(ctx context.Context, endpoint adapter.Endpoint, payload []byte, source M.Socksaddr, destination M.Socksaddr, writer N.PacketWriter) { + var metadata adapter.InboundContext + metadata.Inbound = endpoint.Tag() + metadata.InboundType = endpoint.Type() + metadata.Network = N.NetworkUDP + metadata.Source = source + metadata.Destination = destination + metadata.Protocol = C.ProtocolDNS + e.logger.InfoContext(ctx, "inbound DNS packet from ", source) + e.router.HijackDNSPacket(ctx, payload, writer, metadata) +} + func isEndpointLocalAddress(localAddresses []netip.Prefix, address netip.Addr) bool { for _, localPrefix := range localAddresses { if address == localPrefix.Addr() { diff --git a/protocol/openvpn/client.go b/protocol/openvpn/client.go index 2677d6500..7fb0dd091 100644 --- a/protocol/openvpn/client.go +++ b/protocol/openvpn/client.go @@ -477,6 +477,10 @@ func (c *ClientEndpoint) JudgeFlow(network uint8, source netip.AddrPort, destina return judgeOpenVPNFlow(c.router, c.Tag(), c.Type(), c.state.Load().localAddresses, network, source, destination, firstPacket) } +func (c *ClientEndpoint) NewDNSPacket(payload []byte, source M.Socksaddr, destination M.Socksaddr, writer N.PacketWriter) { + c.newDNSPacket(log.ContextWithNewID(c.ctx), c, payload, source, destination, writer) +} + func (c *ClientEndpoint) ready() bool { state := c.state.Load() return state.started && state.tunnelConfigured diff --git a/protocol/openvpn/endpoint.go b/protocol/openvpn/endpoint.go index 4728ddc23..954d43db5 100644 --- a/protocol/openvpn/endpoint.go +++ b/protocol/openvpn/endpoint.go @@ -72,6 +72,18 @@ func (e *endpointBase) newPacketConnection(ctx context.Context, endpoint adapter e.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } +func (e *endpointBase) newDNSPacket(ctx context.Context, endpoint adapter.Endpoint, payload []byte, source M.Socksaddr, destination M.Socksaddr, writer N.PacketWriter) { + var metadata adapter.InboundContext + metadata.Inbound = endpoint.Tag() + metadata.InboundType = endpoint.Type() + metadata.Network = N.NetworkUDP + metadata.Source = source + metadata.Destination = destination + metadata.Protocol = C.ProtocolDNS + e.logger.InfoContext(ctx, "inbound DNS packet from ", source) + e.router.HijackDNSPacket(ctx, payload, writer, metadata) +} + func isEndpointLocalAddress(localAddresses []netip.Prefix, address netip.Addr) bool { for _, localPrefix := range localAddresses { if address == localPrefix.Addr() { diff --git a/protocol/openvpn/server.go b/protocol/openvpn/server.go index 8c5dc0439..1d0db1646 100644 --- a/protocol/openvpn/server.go +++ b/protocol/openvpn/server.go @@ -484,6 +484,10 @@ func (s *ServerEndpoint) JudgeFlow(network uint8, source netip.AddrPort, destina return judgeOpenVPNFlow(s.router, s.Tag(), s.Type(), s.localAddresses, network, source, destination, firstPacket) } +func (s *ServerEndpoint) NewDNSPacket(payload []byte, source M.Socksaddr, destination M.Socksaddr, writer N.PacketWriter) { + s.newDNSPacket(log.ContextWithNewID(s.ctx), s, payload, source, destination, writer) +} + func (s *ServerEndpoint) WritePackets(packets [][]byte) error { if !s.started.Load() { return E.New("OpenVPN server is not ready yet") diff --git a/protocol/tailscale/port.go b/protocol/tailscale/port.go index cfeb5eca8..ae0c2b9a5 100644 --- a/protocol/tailscale/port.go +++ b/protocol/tailscale/port.go @@ -6,9 +6,13 @@ import ( "net/netip" "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-tun" "github.com/sagernet/sing-tun/gtcpip/header" E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" tsTUN "github.com/sagernet/tailscale/net/tstun" "github.com/sagernet/tailscale/types/ipproto" "github.com/sagernet/tailscale/wgengine/filter" @@ -67,6 +71,19 @@ func (t *Endpoint) JudgeFlow(network uint8, source netip.AddrPort, destination n return adapter.JudgeFlow(t.router, t.Tag(), t.Type(), network, source, destination, firstPacket) } +func (t *Endpoint) NewDNSPacket(payload []byte, source M.Socksaddr, destination M.Socksaddr, writer N.PacketWriter) { + ctx := log.ContextWithNewID(t.ctx) + var metadata adapter.InboundContext + metadata.Inbound = t.Tag() + metadata.InboundType = t.Type() + metadata.Network = N.NetworkUDP + metadata.Source = source + metadata.Destination = destination + metadata.Protocol = C.ProtocolDNS + t.logger.InfoContext(ctx, "inbound DNS packet from ", source) + t.router.HijackDNSPacket(ctx, payload, writer, metadata) +} + func (t *Endpoint) AttachReturn(returnPath tun.Return) error { t.returnAccess.Lock() defer t.returnAccess.Unlock() diff --git a/protocol/tun/inbound.go b/protocol/tun/inbound.go index 284df6836..739ceaa27 100644 --- a/protocol/tun/inbound.go +++ b/protocol/tun/inbound.go @@ -18,6 +18,7 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-tun" + "github.com/sagernet/sing-tun/gtcpip/header" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json/badoption" @@ -516,11 +517,27 @@ func (t *Inbound) Close() error { func (t *Inbound) JudgeFlow(network uint8, source netip.AddrPort, destination netip.AddrPort, firstPacket []byte) tun.FlowVerdict { if slices.Contains(t.dnsHijackAddress, destination.Addr()) { + if network == uint8(header.UDPProtocolNumber) { + return tun.FlowVerdict{Action: tun.ActionHijackDNS} + } return tun.FlowVerdict{Action: tun.ActionAccept} } return adapter.JudgeFlow(t.router, t.tag, C.TypeTun, network, source, destination, firstPacket) } +func (t *Inbound) NewDNSPacket(payload []byte, source M.Socksaddr, destination M.Socksaddr, writer N.PacketWriter) { + ctx := log.ContextWithNewID(t.ctx) + var metadata adapter.InboundContext + metadata.Inbound = t.tag + metadata.InboundType = C.TypeTun + metadata.Network = N.NetworkUDP + metadata.Source = source + metadata.Destination = destination + metadata.Protocol = C.ProtocolDNS + t.logger.InfoContext(ctx, "inbound DNS packet from ", source) + t.router.HijackDNSPacket(ctx, payload, writer, metadata) +} + 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 @@ -591,3 +608,7 @@ func (t *autoRedirectHandler) NewConnectionEx(ctx context.Context, conn net.Conn func (t *autoRedirectHandler) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { panic("unexcepted") } + +func (t *autoRedirectHandler) NewDNSPacket(payload []byte, source M.Socksaddr, destination M.Socksaddr, writer N.PacketWriter) { + (*Inbound)(t).NewDNSPacket(payload, source, destination, writer) +} diff --git a/protocol/wireguard/endpoint.go b/protocol/wireguard/endpoint.go index 4783b3539..d3e8c1a41 100644 --- a/protocol/wireguard/endpoint.go +++ b/protocol/wireguard/endpoint.go @@ -188,6 +188,19 @@ func (w *Endpoint) JudgeFlow(network uint8, source netip.AddrPort, destination n return adapter.JudgeFlow(w.router, w.Tag(), w.Type(), network, source, destination, firstPacket) } +func (w *Endpoint) NewDNSPacket(payload []byte, source M.Socksaddr, destination M.Socksaddr, writer N.PacketWriter) { + ctx := log.ContextWithNewID(w.ctx) + var metadata adapter.InboundContext + metadata.Inbound = w.Tag() + metadata.InboundType = w.Type() + metadata.Network = N.NetworkUDP + metadata.Source = source + metadata.Destination = destination + metadata.Protocol = C.ProtocolDNS + w.logger.InfoContext(ctx, "inbound DNS packet from ", source) + w.router.HijackDNSPacket(ctx, payload, writer, metadata) +} + func (w *Endpoint) WritePackets(packets [][]byte) error { if !w.started.Load() { return E.New("WireGuard is not ready yet") diff --git a/route/dns.go b/route/dns.go index dee4a756a..94f73b51f 100644 --- a/route/dns.go +++ b/route/dns.go @@ -10,12 +10,9 @@ import ( "github.com/sagernet/sing-box/dns" dnsOutbound "github.com/sagernet/sing-box/protocol/dns" R "github.com/sagernet/sing-box/route/rule" - "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/udpnat2" mDNS "github.com/miekg/dns" ) @@ -36,24 +33,6 @@ func (r *Router) hijackDNSStream(ctx context.Context, conn net.Conn, metadata ad } func (r *Router) hijackDNSPacket(ctx context.Context, conn N.PacketConn, packetBuffers []*N.PacketBuffer, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { - if natConn, isNatConn := conn.(udpnat.Conn); isNatConn { - metadata.Destination = M.Socksaddr{} - for _, packet := range packetBuffers { - buffer := packet.Buffer - destination := packet.Destination - N.PutPacketBuffer(packet) - go ExchangeDNSPacket(ctx, r.dns, r.logger, natConn, buffer, metadata, destination) - } - natConn.SetHandler(&dnsHijacker{ - router: r.dns, - logger: r.logger, - conn: conn, - ctx: ctx, - metadata: metadata, - onClose: onClose, - }) - return nil - } err := dnsOutbound.NewDNSPacketConnection(ctx, r.dns, conn, packetBuffers, metadata) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil && !E.IsClosedOrCanceled(err) { @@ -62,48 +41,31 @@ func (r *Router) hijackDNSPacket(ctx context.Context, conn N.PacketConn, packetB return nil } -func ExchangeDNSPacket(ctx context.Context, router adapter.DNSRouter, logger logger.ContextLogger, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext, destination M.Socksaddr) { - err := exchangeDNSPacket(ctx, router, conn, buffer, metadata, destination) - if err != nil && !R.IsRejected(err) && !E.IsClosedOrCanceled(err) { - logger.ErrorContext(ctx, E.Cause(err, "process DNS packet")) - } -} - -func exchangeDNSPacket(ctx context.Context, router adapter.DNSRouter, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext, destination M.Socksaddr) error { +func (r *Router) HijackDNSPacket(ctx context.Context, payload []byte, writer N.PacketWriter, metadata adapter.InboundContext) { var message mDNS.Msg - err := message.Unpack(buffer.Bytes()) - buffer.Release() + err := message.Unpack(payload) if err != nil { - return E.Cause(err, "unpack request") + r.logger.ErrorContext(ctx, E.Cause(err, "process DNS packet: unpack request")) + return } - response, err := router.Exchange(adapter.WithContext(ctx, &metadata), &message, adapter.DNSQueryOptions{}) + destination := metadata.Destination + metadata.Destination = M.Socksaddr{} + go func() { + exchangeErr := r.exchangeDNSPacket(ctx, &message, writer, metadata, destination) + if exchangeErr != nil && !R.IsRejected(exchangeErr) && !E.IsClosedOrCanceled(exchangeErr) { + r.logger.ErrorContext(ctx, E.Cause(exchangeErr, "process DNS packet")) + } + }() +} + +func (r *Router) exchangeDNSPacket(ctx context.Context, message *mDNS.Msg, writer N.PacketWriter, metadata adapter.InboundContext, destination M.Socksaddr) error { + response, err := r.dns.Exchange(adapter.WithContext(ctx, &metadata), message, adapter.DNSQueryOptions{}) if err != nil { return err } - responseBuffer, err := dns.TruncateDNSMessage(&message, response, 1024) + responseBuffer, err := dns.TruncateDNSMessage(message, response, 1024) if err != nil { return err } - err = conn.WritePacket(responseBuffer, destination) - return err -} - -type dnsHijacker struct { - router adapter.DNSRouter - logger logger.ContextLogger - conn N.PacketConn - ctx context.Context - metadata adapter.InboundContext - onClose N.CloseHandlerFunc -} - -func (h *dnsHijacker) NewPacketEx(buffer *buf.Buffer, destination M.Socksaddr) { - go ExchangeDNSPacket(h.ctx, h.router, h.logger, h.conn, buffer, h.metadata, destination) -} - -func (h *dnsHijacker) Close() error { - if h.onClose != nil { - h.onClose(nil) - } - return nil + return writer.WritePacket(responseBuffer, destination) } diff --git a/route/route.go b/route/route.go index 89fb597a3..3d0d5f778 100644 --- a/route/route.go +++ b/route/route.go @@ -391,6 +391,11 @@ func (r *Router) PreMatch(metadata adapter.InboundContext, firstPacket []byte) a return adapter.PreMatchResult{Action: adapter.PreMatchDrop} } return adapter.PreMatchResult{Action: adapter.PreMatchReject} + case *R.RuleActionHijackDNS: + if metadata.Network != N.NetworkUDP { + return continueResult + } + return adapter.PreMatchResult{Action: adapter.PreMatchHijackDNS} case *R.RuleActionResolve: resolveErr := r.actionResolve(adapter.WithContext(ctx, &metadata), &metadata, action) if resolveErr != nil { diff --git a/route/rule/rule_item_rule_set_test.go b/route/rule/rule_item_rule_set_test.go index d6457fbd1..a49980d08 100644 --- a/route/rule/rule_item_rule_set_test.go +++ b/route/rule/rule_item_rule_set_test.go @@ -24,6 +24,9 @@ func (r *ruleSetItemTestRouter) PreMatch(adapter.InboundContext, []byte) adapter return adapter.PreMatchResult{} } +func (r *ruleSetItemTestRouter) HijackDNSPacket(context.Context, []byte, N.PacketWriter, adapter.InboundContext) { +} + func (r *ruleSetItemTestRouter) RouteConnection(context.Context, net.Conn, adapter.InboundContext) error { return nil } diff --git a/test/endpoint_udp_nat_test.go b/test/endpoint_udp_nat_test.go index 7dae4b026..b8851043d 100644 --- a/test/endpoint_udp_nat_test.go +++ b/test/endpoint_udp_nat_test.go @@ -48,6 +48,9 @@ func (h *endpointUDPNATHandler) JudgeFlow(uint8, netip.AddrPort, netip.AddrPort, return tun.FlowVerdict{Action: tun.ActionAccept} } +func (h *endpointUDPNATHandler) NewDNSPacket([]byte, M.Socksaddr, M.Socksaddr, N.PacketWriter) { +} + func (h *endpointUDPNATHandler) NewConnectionEx(_ context.Context, conn net.Conn, _ M.Socksaddr, _ M.Socksaddr, onClose N.CloseHandlerFunc) { err := conn.Close() if onClose != nil { diff --git a/test/go.mod b/test/go.mod index b42fbe5c5..dc56e0206 100644 --- a/test/go.mod +++ b/test/go.mod @@ -16,7 +16,7 @@ require ( github.com/sagernet/sing-quic v0.6.4-0.20260709034545-e23afe1172dc github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 - github.com/sagernet/sing-tun v0.8.12-0.20260717024008-39eed1f6361d + github.com/sagernet/sing-tun v0.8.12-0.20260719050255-7e7bd198288c github.com/spyzhov/ajson v0.9.4 github.com/stretchr/testify v1.11.1 go.uber.org/goleak v1.3.0 diff --git a/test/go.sum b/test/go.sum index 6712ad5f1..43099a4df 100644 --- a/test/go.sum +++ b/test/go.sum @@ -318,8 +318,8 @@ github.com/sagernet/sing-shadowtls v0.2.1 h1:ZiHZdnEnP+YS73NMsxiZmIFCwNd0M4k7PkG github.com/sagernet/sing-shadowtls v0.2.1/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= github.com/sagernet/sing-snell v0.0.0-20260710094516-a4e97ee24beb h1:VvU2/PZqP5tbKTDq0BxkhRO8ZnKI4UJzziakgBiP2Qg= github.com/sagernet/sing-snell v0.0.0-20260710094516-a4e97ee24beb/go.mod h1:PcwzX/Xvqky0EP3kGt8OCjYb3R1pydenPHNQZcPZmXY= -github.com/sagernet/sing-tun v0.8.12-0.20260717024008-39eed1f6361d h1:UXUGxGTwotY+R+QkRGvhj/N148SDnwMh/d9Hefn5X7Y= -github.com/sagernet/sing-tun v0.8.12-0.20260717024008-39eed1f6361d/go.mod h1:F/gRq5VX1WN/OZtsvbN2JjXXuNl2ATJglHMSk1/iN9U= +github.com/sagernet/sing-tun v0.8.12-0.20260719050255-7e7bd198288c h1:jS5eWD9PyXhHY89DueRDgenfcrfpyPOuuZIoNfnBmOs= +github.com/sagernet/sing-tun v0.8.12-0.20260719050255-7e7bd198288c/go.mod h1:F/gRq5VX1WN/OZtsvbN2JjXXuNl2ATJglHMSk1/iN9U= github.com/sagernet/sing-usbip v0.0.0-20260616101517-efb91521eddb h1:KEMbfexD4DvrQGYWwx6r+AwH9Veh8z6cnBZmtCS2G+0= github.com/sagernet/sing-usbip v0.0.0-20260616101517-efb91521eddb/go.mod h1:D4CnJX3MNAAANhbQUxfIRgBdnvlTEaV7h6ojedcs+pw= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o=