diff --git a/constant/proxy.go b/constant/proxy.go index 007179b431..65c2654e49 100644 --- a/constant/proxy.go +++ b/constant/proxy.go @@ -17,6 +17,7 @@ const ( TypeTrojan = "trojan" TypeNaive = "naive" TypeWireGuard = "wireguard" + TypeLegacyWireGuard = "wireguard_legacy" //H TypeHysteria = "hysteria" TypeTor = "tor" TypeSSH = "ssh" diff --git a/include/registry.go b/include/registry.go index 82dbf03a0d..3ec27574ab 100644 --- a/include/registry.go +++ b/include/registry.go @@ -47,6 +47,7 @@ import ( "github.com/sagernet/sing-box/protocol/tunnel" "github.com/sagernet/sing-box/protocol/vless" "github.com/sagernet/sing-box/protocol/vmess" + legacyWireguard "github.com/sagernet/sing-box/protocol/wireguard" "github.com/sagernet/sing-box/service/api" originca "github.com/sagernet/sing-box/service/origin_ca" "github.com/sagernet/sing-box/service/resolved" @@ -120,6 +121,7 @@ func OutboundRegistry() *outbound.Registry { gooserelay.RegisterOutbound(registry) balancer.RegisterLoadBalance(registry) registerMASQUEOutbound(registry) + legacyWireguard.RegisterOutbound(registry) //H registerQUICOutbounds(registry) registerStubForRemovedOutbounds(registry) diff --git a/protocol/cloudflare/inbound.go b/protocol/cloudflare/inbound.go index f445ab956b..dcbe0f910a 100644 --- a/protocol/cloudflare/inbound.go +++ b/protocol/cloudflare/inbound.go @@ -5,6 +5,7 @@ package cloudflare import ( "context" "net" + "net/netip" "time" "github.com/sagernet/sing-box/adapter" @@ -13,7 +14,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-box/route/rule" cloudflared "github.com/sagernet/sing-cloudflared" tun "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common/bufio" @@ -129,32 +129,36 @@ type icmpRouterHandler struct { tag string } -func (h *icmpRouterHandler) RouteICMPConnection(ctx context.Context, session tun.DirectRouteSession, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { +func (h *icmpRouterHandler) RouteICMPFlow(source netip.Addr, destination netip.Addr) (tun.Port, error) { var ipVersion uint8 - if session.Destination.Is4() { + if destination.Is4() { ipVersion = 4 } else { ipVersion = 6 } - destination := M.SocksaddrFrom(session.Destination, 0) - routeDestination, err := h.router.PreMatch(adapter.InboundContext{ + destinationAddr := M.SocksaddrFrom(destination, 0) + result := h.router.PreMatch(adapter.InboundContext{ Inbound: h.tag, InboundType: C.TypeCloudflared, IPVersion: ipVersion, Network: N.NetworkICMP, - Source: M.SocksaddrFrom(session.Source, 0), - Destination: destination, - OriginDestination: destination, - }, routeContext, timeout, false) - if err != nil { - switch { - case rule.IsBypassed(err): - err = nil - case rule.IsRejected(err): - h.logger.Trace("reject ICMP connection from ", session.Source, " to ", session.Destination) - default: - h.logger.Warn(E.Cause(err, "link ICMP connection from ", session.Source, " to ", session.Destination)) + Source: M.SocksaddrFrom(source, 0), + Destination: destinationAddr, + OriginDestination: destinationAddr, + }, nil) + switch result.Action { + case adapter.PreMatchFlow: + port, isPort := result.Outbound.(tun.Port) + if !isPort { + return nil, E.New("outbound does not support ICMP flow routing") } + return port, nil + case adapter.PreMatchReject: + h.logger.Trace("reject ICMP connection from ", source, " to ", destination) + return nil, E.New("rejected") + case adapter.PreMatchBypass: + return nil, E.New("bypassed") + default: + return nil, E.New("no route for ICMP connection from ", source, " to ", destination) } - return routeDestination, err } diff --git a/protocol/tailscale/endpoint.go b/protocol/tailscale/endpoint.go index 3a742c165f..777984b75b 100644 --- a/protocol/tailscale/endpoint.go +++ b/protocol/tailscale/endpoint.go @@ -16,7 +16,6 @@ import ( "runtime" "strings" "sync/atomic" - "syscall" "time" "github.com/sagernet/gvisor/pkg/tcpip" @@ -49,7 +48,6 @@ import ( "github.com/sagernet/tailscale/ipn" tsDNS "github.com/sagernet/tailscale/net/dns" "github.com/sagernet/tailscale/net/netmon" - "github.com/sagernet/tailscale/net/netns" "github.com/sagernet/tailscale/net/tsaddr" tsTUN "github.com/sagernet/tailscale/net/tstun" "github.com/sagernet/tailscale/tailcfg" @@ -68,7 +66,7 @@ import ( var ( _ adapter.OutboundWithPreferredRoutes = (*Endpoint)(nil) - _ adapter.DirectRouteOutbound = (*Endpoint)(nil) + _ adapter.FlowOutbound = (*Endpoint)(nil) _ dialer.PacketDialerWithDestination = (*Endpoint)(nil) ) @@ -119,6 +117,7 @@ type Endpoint struct { systemTun tun.Tun systemDialer *dialer.DefaultDialer fallbackTCPCloser func() + icmpPort *ping.Port } func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TailscaleEndpointOptions) (adapter.Endpoint, error) { @@ -232,7 +231,9 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL func (t *Endpoint) Start(stage adapter.StartStage) error { switch stage { case adapter.StartStateInitialize: - t.server.PeerDNSQueryHandler = (*peerDNSQueryHandler)(t) + // PeerDNSQueryHandler hook was removed from tsnet.Server upstream; + // peer DNS queries no longer route through dnsRouter. See + // peerDNSQueryHandler below, kept unused pending a replacement hook. case adapter.StartStateStart: return t.start() case adapter.StartStatePostStart: @@ -311,19 +312,16 @@ func (t *Endpoint) start() error { t.systemDialer = systemDialer t.server.TunDevice = wgTunDevice } - if mark := t.network.AutoRedirectOutputMark(); mark > 0 { - controlFunc := t.network.AutoRedirectOutputMarkFunc() - if bindFunc := t.network.AutoDetectInterfaceFunc(); bindFunc != nil { - controlFunc = control.Append(controlFunc, bindFunc) - } - netns.SetControlFunc(controlFunc) - } else if runtime.GOOS == "android" && t.platformInterface != nil { - netns.SetControlFunc(func(network, address string, c syscall.RawConn) error { - return control.Raw(c, func(fd uintptr) error { - return t.platformInterface.AutoDetectInterfaceControl(int(fd)) - }) + if runtime.GOOS == "android" && t.platformInterface != nil { + setAndroidProtectFunc(func(fd int) error { + return t.platformInterface.AutoDetectInterfaceControl(fd) }) } + // Auto-redirect output-mark control (t.network.AutoRedirectOutputMark/ + // AutoRedirectOutputMarkFunc/AutoDetectInterfaceFunc) has no equivalent + // hook in the current netns package (netns.SetControlFunc was removed); + // non-Android auto-detect-interface binding for Tailscale's own sockets + // is not currently applied. return nil } @@ -357,7 +355,7 @@ func (t *Endpoint) postStart() error { if gErr != nil { return gonet.TranslateNetstackError(gErr) } - icmpForwarder := tun.NewICMPForwarder(t.ctx, ipStack, t, t.udpTimeout) + icmpForwarder := tun.NewICMPForwarder(ipStack, t, t.logger) ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket) ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket) t.stack = ipStack @@ -538,7 +536,9 @@ func (t *Endpoint) Close() error { t.serverStarted = false } netmon.RegisterInterfaceGetter(nil) - netns.SetControlFunc(nil) + if runtime.GOOS == "android" { + setAndroidProtectFunc(nil) + } if t.fallbackTCPCloser != nil { t.fallbackTCPCloser() t.fallbackTCPCloser = nil @@ -685,60 +685,98 @@ func (t *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (n return packetConn, nil } -func (t *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { - if !t.started.Load() { - return nil, E.New("Tailscale is not ready yet") +// JudgeFlow implements tun.Handler for traffic arriving from Tailscale peers +// via the gVisor ICMP forwarder, checking Tailscale's own ACL filter before +// falling through to the sing-box router. +func (t *Endpoint) JudgeFlow(network uint8, source netip.AddrPort, destination netip.AddrPort, firstPacket []byte) tun.FlowVerdict { + inet4Address, inet6Address := t.server.TailscaleIPs() + if destination.Addr() == inet4Address || destination.Addr() == inet6Address { + return tun.FlowVerdict{Action: tun.ActionAccept} } tsFilter := t.filter.Load() if tsFilter != nil { - var ipProto ipproto.Proto - switch N.NetworkName(network) { - case N.NetworkTCP: + var ( + ipProto ipproto.Proto + destinationPort uint16 + ) + switch network { + case uint8(header.TCPProtocolNumber): ipProto = ipproto.TCP - case N.NetworkUDP: + destinationPort = destination.Port() + case uint8(header.UDPProtocolNumber): ipProto = ipproto.UDP - case N.NetworkICMP: - if !destination.IsIPv6() { - ipProto = ipproto.ICMPv4 - } else { - ipProto = ipproto.ICMPv6 - } + destinationPort = destination.Port() + case uint8(header.ICMPv4ProtocolNumber): + ipProto = ipproto.ICMPv4 + case uint8(header.ICMPv6ProtocolNumber): + ipProto = ipproto.ICMPv6 } - response := tsFilter.Check(source.Addr, destination.Addr, destination.Port, ipProto) - switch response { + switch tsFilter.Check(source.Addr(), destination.Addr(), destinationPort, ipProto) { case filter.Drop: - return nil, syscall.ECONNREFUSED + return tun.FlowVerdict{Action: tun.ActionReject} case filter.DropSilently: - return nil, tun.ErrDrop + return tun.FlowVerdict{Action: tun.ActionDrop} } } - var ipVersion uint8 - if !destination.IsIPv6() { - ipVersion = 4 - } else { - ipVersion = 6 - } - routeDestination, err := t.router.PreMatch(adapter.InboundContext{ - Inbound: t.Tag(), - InboundType: t.Type(), - IPVersion: ipVersion, - Network: network, - Source: source, - Destination: destination, - }, routeContext, timeout, false) - if err != nil { - switch { - case R.IsBypassed(err): - err = nil - case R.IsRejected(err): - t.logger.Trace("reject ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()) - default: - if network == N.NetworkICMP { - t.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString())) - } + return adapter.JudgeFlow(t.router, t.Tag(), t.Type(), network, source, destination, firstPacket) +} + +// PreMatchFlow reports whether this endpoint accepts a direct-route (tun.Port) +// flow for the given network/destination, checking Tailscale's own ACL +// filter. Only ICMP is supported (see icmpFlowPort); other networks fall +// through to normal outbound dialing. +func (t *Endpoint) PreMatchFlow(network string, destination netip.Addr) adapter.PreMatchAction { + if !t.started.Load() { + return adapter.PreMatchReject + } + if network != N.NetworkICMP || t.systemDialer == nil { + return adapter.PreMatchContinue + } + tsFilter := t.filter.Load() + if tsFilter != nil { + var ipProto ipproto.Proto + if !destination.Is6() { + ipProto = ipproto.ICMPv4 + } else { + ipProto = ipproto.ICMPv6 + } + switch tsFilter.Check(destination, destination, 0, ipProto) { + case filter.Drop: + return adapter.PreMatchReject + case filter.DropSilently: + return adapter.PreMatchDrop } } - return routeDestination, err + return adapter.PreMatchFlow +} + +func (t *Endpoint) icmpFlowPort() *ping.Port { + if t.icmpPort == nil { + t.icmpPort = ping.NewPort(t.ctx, t.logger, func(destination netip.Addr) control.Func { + return t.systemDialer.DialerForICMPDestination(destination).Control + }, t.udpTimeout) + } + return t.icmpPort +} + +func (t *Endpoint) PortAddresses() (netip.Addr, netip.Addr) { + return t.icmpFlowPort().PortAddresses() +} + +func (t *Endpoint) PortMTU() uint32 { + return t.icmpFlowPort().PortMTU() +} + +func (t *Endpoint) AttachReturn(returnPath tun.Return) error { + return t.icmpFlowPort().AttachReturn(returnPath) +} + +func (t *Endpoint) DetachReturn(returnPath tun.Return) error { + return t.icmpFlowPort().DetachReturn(returnPath) +} + +func (t *Endpoint) WritePackets(packets [][]byte) error { + return t.icmpFlowPort().WritePackets(packets) } func (t *Endpoint) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { @@ -781,41 +819,7 @@ func (t *Endpoint) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, t.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } -func (t *Endpoint) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { - if !t.started.Load() { - return nil, E.New("Tailscale is not ready yet") - } - ctx := log.ContextWithNewID(t.ctx) - var destination tun.DirectRouteDestination - var err error - if t.systemDialer != nil { - destination, err = ping.ConnectDestination( - ctx, t.logger, - t.systemDialer.DialerForICMPDestination(metadata.Destination.Addr).Control, - metadata.Destination.Addr, routeContext, timeout, - ) - } else { - inet4Address, inet6Address := t.server.TailscaleIPs() - if metadata.Destination.Addr.Is4() && !inet4Address.IsValid() || metadata.Destination.Addr.Is6() && !inet6Address.IsValid() { - return nil, E.New("Tailscale is not ready yet") - } - destination, err = ping.ConnectGVisor( - ctx, t.logger, - metadata.Source.Addr, metadata.Destination.Addr, - routeContext, - t.stack, - inet4Address, inet6Address, - timeout, - ) - } - if err != nil { - return nil, err - } - t.logger.InfoContext(ctx, "linked ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString()) - return destination, nil -} - -func (t *Endpoint) PreferredDomain(domain string) bool { +func (t *Endpoint) PreferredDomain(metadata *adapter.InboundContext, domain string) bool { routeDomains := t.routeDomains.Load() if routeDomains == nil { return false @@ -823,7 +827,7 @@ func (t *Endpoint) PreferredDomain(domain string) bool { return routeDomains[strings.ToLower(domain)] } -func (t *Endpoint) PreferredAddress(address netip.Addr) bool { +func (t *Endpoint) PreferredAddress(metadata *adapter.InboundContext, address netip.Addr) bool { routePrefixes := t.routePrefixes.Load() if routePrefixes == nil { return false @@ -842,15 +846,6 @@ func (t *Endpoint) onReconfig(cfg *wgcfg.Config, routerCfg *router.Config, dnsCf if (t.cfg != nil && reflect.DeepEqual(t.cfg, cfg)) && (t.dnsCfg != nil && reflect.DeepEqual(t.dnsCfg, dnsCfg)) { return } - var inet4Address, inet6Address netip.Addr - for _, address := range cfg.Addresses { - if address.Addr().Is4() { - inet4Address = address.Addr() - } else if address.Addr().Is6() { - inet6Address = address.Addr() - } - } - t.icmpForwarder.SetLocalAddresses(inet4Address, inet6Address) t.cfg = cfg t.dnsCfg = dnsCfg diff --git a/protocol/tailscale/netns_android.go b/protocol/tailscale/netns_android.go new file mode 100644 index 0000000000..bd69293fa5 --- /dev/null +++ b/protocol/tailscale/netns_android.go @@ -0,0 +1,11 @@ +//go:build with_gvisor && android + +package tailscale + +import ( + "github.com/sagernet/tailscale/net/netns" +) + +func setAndroidProtectFunc(f func(fd int) error) { + netns.SetAndroidProtectFunc(f) +} diff --git a/protocol/tailscale/netns_stub.go b/protocol/tailscale/netns_stub.go new file mode 100644 index 0000000000..e7a845b091 --- /dev/null +++ b/protocol/tailscale/netns_stub.go @@ -0,0 +1,6 @@ +//go:build with_gvisor && !android + +package tailscale + +func setAndroidProtectFunc(f func(fd int) error) { +} diff --git a/protocol/tailscale/port.go b/protocol/tailscale/port.go deleted file mode 100644 index cfeb5eca8f..0000000000 --- a/protocol/tailscale/port.go +++ /dev/null @@ -1,127 +0,0 @@ -//go:build with_gvisor - -package tailscale - -import ( - "net/netip" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-tun" - "github.com/sagernet/sing-tun/gtcpip/header" - E "github.com/sagernet/sing/common/exceptions" - tsTUN "github.com/sagernet/tailscale/net/tstun" - "github.com/sagernet/tailscale/types/ipproto" - "github.com/sagernet/tailscale/wgengine/filter" -) - -func (t *Endpoint) PreMatchFlow(network string, destination netip.Addr) adapter.PreMatchAction { - return adapter.PreMatchFlow -} - -func (t *Endpoint) PortAddresses() (netip.Addr, netip.Addr) { - if !t.started.Load() { - return netip.Addr{}, netip.Addr{} - } - return t.server.TailscaleIPs() -} - -func (t *Endpoint) PortMTU() uint32 { - if t.systemInterface { - return t.systemInterfaceMTU - } - return uint32(tsTUN.DefaultTUNMTU()) -} - -func (t *Endpoint) JudgeFlow(network uint8, source netip.AddrPort, destination netip.AddrPort, firstPacket []byte) tun.FlowVerdict { - inet4Address, inet6Address := t.PortAddresses() - if destination.Addr() == inet4Address || destination.Addr() == inet6Address { - return tun.FlowVerdict{Action: tun.ActionAccept} - } - if t.filter != nil { - tsFilter := t.filter.Load() - if tsFilter != nil { - var ( - ipProto ipproto.Proto - destinationPort uint16 - ) - switch network { - case uint8(header.TCPProtocolNumber): - ipProto = ipproto.TCP - destinationPort = destination.Port() - case uint8(header.UDPProtocolNumber): - ipProto = ipproto.UDP - destinationPort = destination.Port() - case uint8(header.ICMPv4ProtocolNumber): - ipProto = ipproto.ICMPv4 - case uint8(header.ICMPv6ProtocolNumber): - ipProto = ipproto.ICMPv6 - } - switch tsFilter.Check(source.Addr(), destination.Addr(), destinationPort, ipProto) { - case filter.Drop: - return tun.FlowVerdict{Action: tun.ActionReject} - case filter.DropSilently: - return tun.FlowVerdict{Action: tun.ActionDrop} - } - } - } - return adapter.JudgeFlow(t.router, t.Tag(), t.Type(), network, source, destination, firstPacket) -} - -func (t *Endpoint) AttachReturn(returnPath tun.Return) error { - t.returnAccess.Lock() - defer t.returnAccess.Unlock() - if t.returnPath == returnPath { - return nil - } - if t.returnPath != nil { - return E.New("return path already attached") - } - err := t.wgEngine.SetReturnPath(returnPath) - if err != nil { - return err - } - t.returnPath = returnPath - return nil -} - -func (t *Endpoint) DetachReturn(returnPath tun.Return) error { - t.returnAccess.Lock() - defer t.returnAccess.Unlock() - if t.returnPath == returnPath { - t.returnPath = nil - } - return nil -} - -func (t *Endpoint) WritePackets(packets [][]byte) error { - if !t.started.Load() { - return E.New("Tailscale is not ready yet") - } - unmatched, err := t.wgEngine.InputPackets(packets) - if err != nil || len(unmatched) == 0 { - return err - } - t.returnAccess.Lock() - returnPath := t.returnPath - t.returnAccess.Unlock() - if returnPath == nil { - return nil - } - headroom := returnPath.ReturnHeadroom() - inet4Address, inet6Address := t.PortAddresses() - var replies [][]byte - for _, packet := range unmatched { - source := inet4Address - if header.IPVersion(packet) == header.IPv6Version { - source = inet6Address - } - reply, replyOk := tun.BuildUnreachable(packet, source, headroom) - if replyOk { - replies = append(replies, reply) - } - } - if len(replies) > 0 { - returnPath.ReturnPackets(replies) - } - return nil -} diff --git a/protocol/tailscale/status.go b/protocol/tailscale/status.go index 7441d1761a..959f682401 100644 --- a/protocol/tailscale/status.go +++ b/protocol/tailscale/status.go @@ -7,12 +7,21 @@ import ( "slices" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/tailscale/ipn" "github.com/sagernet/tailscale/ipn/ipnstate" ) var _ adapter.TailscaleEndpoint = (*Endpoint)(nil) +func (t *Endpoint) Logout(ctx context.Context) error { + if !t.started.Load() { + return E.New("Tailscale is not ready yet") + } + return common.Must1(t.server.LocalClient()).Logout(ctx) +} + func (t *Endpoint) SubscribeTailscaleStatus(ctx context.Context, fn func(*adapter.TailscaleEndpointStatus)) error { localBackend := t.server.ExportLocalBackend() sendStatus := func() { diff --git a/protocol/tailscale/tailssh/server.go b/protocol/tailscale/tailssh/server.go index 660f892ef9..fc73004be9 100644 --- a/protocol/tailscale/tailssh/server.go +++ b/protocol/tailscale/tailssh/server.go @@ -217,8 +217,9 @@ func (s *Server) Start() error { maps.Copy(sshServer.SubsystemHandlers, gliderssh.DefaultSubsystemHandlers) sshServer.AddHostKey(s.hostSigner) s.server = sshServer - hostKeyPublic := strings.TrimSpace(string(gossh.MarshalAuthorizedKey(s.hostSigner.PublicKey()))) - s.tsnetServer.ExportLocalBackend().SetExternalSSHHostKeys([]string{hostKeyPublic}) + // LocalBackend.SetExternalSSHHostKeys was removed upstream; peer status + // (SSH_HostKeys) now only reflects the system/auto-generated host keys, + // not this custom SSH server's key. go func() { err := sshServer.Serve(listener) if err != nil && !errors.Is(err, gliderssh.ErrServerClosed) { diff --git a/protocol/wireguard/outbound.go b/protocol/wireguard/outbound.go index 0a2c5e7d5d..972058e075 100644 --- a/protocol/wireguard/outbound.go +++ b/protocol/wireguard/outbound.go @@ -23,7 +23,7 @@ import ( var _ adapter.OutboundWithPreferredRoutes = (*Outbound)(nil) func RegisterOutbound(registry *outbound.Registry) { - outbound.Register[option.LegacyWireGuardOutboundOptions](registry, C.TypeWireGuard, NewOutbound) + outbound.Register[option.LegacyWireGuardOutboundOptions](registry, C.TypeLegacyWireGuard, NewOutbound) } type Outbound struct { diff --git a/replace/tailscale b/replace/tailscale index 788aa623ed..3c22883997 160000 --- a/replace/tailscale +++ b/replace/tailscale @@ -1 +1 @@ -Subproject commit 788aa623edebf3e9918919cee4c590b177c61ec4 +Subproject commit 3c228839979c50545078beee946dcd992a092246 diff --git a/transport/masque/device_stack.go b/transport/masque/device_stack.go index 0d926dcaac..d26b92225f 100644 --- a/transport/masque/device_stack.go +++ b/transport/masque/device_stack.go @@ -87,7 +87,7 @@ func newStackDevice(options DeviceOptions) (*stackDevice, error) { if options.Handler != nil { 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) - icmpForwarder := tun.NewICMPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout) + icmpForwarder := tun.NewICMPForwarder(ipStack, options.Handler, options.Logger) ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket) ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket) }