mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-05-13 13:57:05 +00:00
feat: DirectRoute support for UDP/TCP traceroute
Add UDP and TCP DirectRoute handling to enable mtr --udp and mtr --tcp through WireGuard and other tunnels. - Add UDP DirectRoute with ICMPForwarder integration in direct outbound - Add TCP DirectRoute for traceroute in route matching - Pass ICMPForwarder to UDP/TCP forwarders in WireGuard stack - Fix safe type assertion for DirectRouteOutbound in PreMatch - Intercept ICMP errors in stackDevice before gVisor delivery - Use import aliases for sing-tun packages
This commit is contained in:
parent
246d6a5f8f
commit
793b81c190
6 changed files with 87 additions and 26 deletions
|
|
@ -13,7 +13,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-tun"
|
||||
tun "github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing-tun/ping"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
|
|
@ -110,7 +110,19 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
|
|||
|
||||
func (h *Outbound) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||
ctx := log.ContextWithNewID(h.ctx)
|
||||
destination, err := ping.ConnectDestination(ctx, h.logger, common.MustCast[*dialer.DefaultDialer](h.dialer).DialerForICMPDestination(metadata.Destination.Addr).Control, metadata.Destination.Addr, routeContext, timeout)
|
||||
controlFunc := common.MustCast[*dialer.DefaultDialer](h.dialer).DialerForICMPDestination(metadata.Destination.Addr).Control
|
||||
var (
|
||||
destination tun.DirectRouteDestination
|
||||
err error
|
||||
)
|
||||
switch metadata.Network {
|
||||
case N.NetworkUDP:
|
||||
destination, err = ping.ConnectUDPDestination(ctx, h.logger, controlFunc, metadata.Destination.Addr, routeContext, timeout)
|
||||
case N.NetworkTCP:
|
||||
destination, err = ping.ConnectTCPDestination(ctx, h.logger, controlFunc, metadata.Destination.Addr, routeContext, timeout)
|
||||
default:
|
||||
destination, err = ping.ConnectDestination(ctx, h.logger, controlFunc, metadata.Destination.Addr, routeContext, timeout)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -181,7 +181,11 @@ func (s *Selector) NewDirectRouteConnection(metadata adapter.InboundContext, rou
|
|||
if !common.Contains(selected.Network(), metadata.Network) {
|
||||
return nil, E.New(metadata.Network, " is not supported by outbound: ", selected.Tag())
|
||||
}
|
||||
return selected.(adapter.DirectRouteOutbound).NewDirectRouteConnection(metadata, routeContext, timeout)
|
||||
directRoute, ok := selected.(adapter.DirectRouteOutbound)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
return directRoute.NewDirectRouteConnection(metadata, routeContext, timeout)
|
||||
}
|
||||
|
||||
func RealTag(detour adapter.Outbound) string {
|
||||
|
|
|
|||
|
|
@ -183,7 +183,11 @@ func (s *URLTest) NewDirectRouteConnection(metadata adapter.InboundContext, rout
|
|||
if !common.Contains(selected.Network(), metadata.Network) {
|
||||
return nil, E.New(metadata.Network, " is not supported by outbound: ", selected.Tag())
|
||||
}
|
||||
return selected.(adapter.DirectRouteOutbound).NewDirectRouteConnection(metadata, routeContext, timeout)
|
||||
directRoute, ok := selected.(adapter.DirectRouteOutbound)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
return directRoute.NewDirectRouteConnection(metadata, routeContext, timeout)
|
||||
}
|
||||
|
||||
type URLTestGroup struct {
|
||||
|
|
|
|||
|
|
@ -12,10 +12,10 @@ import (
|
|||
"github.com/sagernet/sing-box/common/sniff"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
R "github.com/sagernet/sing-box/route/rule"
|
||||
"github.com/sagernet/sing-mux"
|
||||
"github.com/sagernet/sing-tun"
|
||||
mux "github.com/sagernet/sing-mux"
|
||||
tun "github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing-tun/ping"
|
||||
"github.com/sagernet/sing-vmess"
|
||||
vmess "github.com/sagernet/sing-vmess"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
|
|
@ -329,7 +329,11 @@ func (r *Router) PreMatch(metadata adapter.InboundContext, routeContext tun.Dire
|
|||
if !common.Contains(outbound.Network(), metadata.Network) {
|
||||
return nil, E.New(metadata.Network, " is not supported by outbound: ", action.Outbound)
|
||||
}
|
||||
directRouteOutbound = outbound.(adapter.DirectRouteOutbound)
|
||||
var ok bool
|
||||
directRouteOutbound, ok = outbound.(adapter.DirectRouteOutbound)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
case *R.RuleActionRoute:
|
||||
if routeContext == nil {
|
||||
return nil, nil
|
||||
|
|
@ -341,18 +345,26 @@ func (r *Router) PreMatch(metadata adapter.InboundContext, routeContext tun.Dire
|
|||
if !common.Contains(outbound.Network(), metadata.Network) {
|
||||
return nil, E.New(metadata.Network, " is not supported by outbound: ", action.Outbound)
|
||||
}
|
||||
directRouteOutbound = outbound.(adapter.DirectRouteOutbound)
|
||||
var ok bool
|
||||
directRouteOutbound, ok = outbound.(adapter.DirectRouteOutbound)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if directRouteOutbound == nil {
|
||||
if selectedRule != nil || metadata.Network != N.NetworkICMP {
|
||||
if selectedRule != nil || (metadata.Network != N.NetworkICMP && metadata.Network != N.NetworkUDP && metadata.Network != N.NetworkTCP) {
|
||||
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())
|
||||
}
|
||||
directRouteOutbound = defaultOutbound.(adapter.DirectRouteOutbound)
|
||||
var ok bool
|
||||
directRouteOutbound, ok = defaultOutbound.(adapter.DirectRouteOutbound)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
if metadata.Destination.IsDomain() {
|
||||
if len(metadata.DestinationAddresses) == 0 {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import (
|
|||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-tun"
|
||||
tun "github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing-tun/ping"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/buffer"
|
||||
|
|
@ -21,7 +22,7 @@ import (
|
|||
"github.com/sagernet/gvisor/pkg/tcpip/transport/udp"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-tun"
|
||||
tun "github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing-tun/ping"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
|
|
@ -45,6 +46,7 @@ type stackDevice struct {
|
|||
dispatcher stack.NetworkDispatcher
|
||||
inet4Address netip.Addr
|
||||
inet6Address netip.Addr
|
||||
rewriter *ping.SourceRewriter
|
||||
}
|
||||
|
||||
func newStackDevice(options DeviceOptions) (*stackDevice, error) {
|
||||
|
|
@ -88,11 +90,15 @@ func newStackDevice(options DeviceOptions) (*stackDevice, error) {
|
|||
}
|
||||
}
|
||||
tunDevice.stack = ipStack
|
||||
tunDevice.rewriter = ping.NewSourceRewriter(options.Context, options.Logger, inet4Address, inet6Address)
|
||||
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.SetLocalAddresses(inet4Address, inet6Address)
|
||||
icmpForwarder.SetTTLDecrement(inet4Address, inet6Address, 0)
|
||||
tcpHandler := tun.NewTCPForwarder(options.Context, ipStack, options.Handler).HandlePacket
|
||||
udpHandler := tun.NewUDPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout).HandlePacket
|
||||
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.WrapTCPHandlerWithDirectRoute(ipStack, options.Handler, icmpForwarder, options.UDPTimeout, 0, inet4Address, inet6Address, tcpHandler))
|
||||
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.WrapUDPHandlerWithDirectRoute(ipStack, options.Handler, icmpForwarder, options.UDPTimeout, 0, inet4Address, inet6Address, udpHandler))
|
||||
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket)
|
||||
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket)
|
||||
}
|
||||
|
|
@ -210,6 +216,11 @@ func (w *stackDevice) Write(bufs [][]byte, offset int) (count int, err error) {
|
|||
if len(b) == 0 {
|
||||
continue
|
||||
}
|
||||
handled, _ := w.rewriter.WriteBack(b)
|
||||
if handled {
|
||||
count++
|
||||
continue
|
||||
}
|
||||
var networkProtocol tcpip.NetworkProtocolNumber
|
||||
switch header.IPVersion(b) {
|
||||
case header.IPv4Version:
|
||||
|
|
@ -260,19 +271,37 @@ func (w *stackDevice) BatchSize() int {
|
|||
|
||||
func (w *stackDevice) CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||
ctx := log.ContextWithNewID(w.ctx)
|
||||
destination, err := ping.ConnectGVisor(
|
||||
ctx, w.logger,
|
||||
metadata.Source.Addr, metadata.Destination.Addr,
|
||||
routeContext,
|
||||
w.stack,
|
||||
w.inet4Address, w.inet6Address,
|
||||
timeout,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
session := tun.DirectRouteSession{
|
||||
Source: metadata.Source.Addr,
|
||||
Destination: metadata.Destination.Addr,
|
||||
}
|
||||
w.rewriter.CreateSession(session, routeContext)
|
||||
w.logger.InfoContext(ctx, "linked ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString())
|
||||
return destination, nil
|
||||
return &stackNatDestination{device: w, session: session}, nil
|
||||
}
|
||||
|
||||
var _ tun.DirectRouteDestination = (*stackNatDestination)(nil)
|
||||
|
||||
type stackNatDestination struct {
|
||||
device *stackDevice
|
||||
session tun.DirectRouteSession
|
||||
closed atomic.Bool
|
||||
}
|
||||
|
||||
func (d *stackNatDestination) WritePacket(buffer *buf.Buffer) error {
|
||||
d.device.rewriter.RewritePacket(buffer.Bytes())
|
||||
d.device.packetOutbound <- buffer
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *stackNatDestination) Close() error {
|
||||
d.closed.Store(true)
|
||||
d.device.rewriter.DeleteSession(d.session)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *stackNatDestination) IsClosed() bool {
|
||||
return d.closed.Load()
|
||||
}
|
||||
|
||||
var _ stack.LinkEndpoint = (*wireEndpoint)(nil)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue