ping: Add filter to destination

This commit is contained in:
世界 2025-08-25 20:51:03 +08:00
parent d0ff7b6f6c
commit 144683d882
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
3 changed files with 108 additions and 31 deletions

View file

@ -6,9 +6,11 @@ import (
"net/netip" "net/netip"
"os" "os"
"runtime" "runtime"
"sync"
"time" "time"
"github.com/sagernet/sing-tun" "github.com/sagernet/sing-tun"
"github.com/sagernet/sing-tun/internal/gtcpip/header"
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/control" "github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
@ -18,18 +20,28 @@ import (
var _ tun.DirectRouteDestination = (*Destination)(nil) var _ tun.DirectRouteDestination = (*Destination)(nil)
type Destination struct { type Destination struct {
conn *Conn conn *Conn
ctx context.Context ctx context.Context
logger logger.ContextLogger logger logger.ContextLogger
routeContext tun.DirectRouteContext destination netip.Addr
timeout time.Duration routeContext tun.DirectRouteContext
timeout time.Duration
requestAccess sync.Mutex
requests map[pingRequest]bool
}
type pingRequest struct {
Source netip.Addr
Destination netip.Addr
Identifier uint16
Sequence uint16
} }
func ConnectDestination( func ConnectDestination(
ctx context.Context, ctx context.Context,
logger logger.ContextLogger, logger logger.ContextLogger,
controlFunc control.Func, controlFunc control.Func,
address netip.Addr, destination netip.Addr,
routeContext tun.DirectRouteContext, routeContext tun.DirectRouteContext,
timeout time.Duration, timeout time.Duration,
) (tun.DirectRouteDestination, error) { ) (tun.DirectRouteDestination, error) {
@ -39,11 +51,11 @@ func ConnectDestination(
) )
switch runtime.GOOS { switch runtime.GOOS {
case "darwin", "ios", "windows": case "darwin", "ios", "windows":
conn, err = Connect(ctx, logger, false, controlFunc, address) conn, err = Connect(ctx, false, controlFunc, destination)
default: default:
conn, err = Connect(ctx, logger, true, controlFunc, address) conn, err = Connect(ctx, true, controlFunc, destination)
if errors.Is(err, os.ErrPermission) { if errors.Is(err, os.ErrPermission) {
conn, err = Connect(ctx, logger, false, controlFunc, address) conn, err = Connect(ctx, false, controlFunc, destination)
} }
} }
if err != nil { if err != nil {
@ -53,8 +65,10 @@ func ConnectDestination(
conn: conn, conn: conn,
ctx: ctx, ctx: ctx,
logger: logger, logger: logger,
destination: destination,
routeContext: routeContext, routeContext: routeContext,
timeout: timeout, timeout: timeout,
requests: make(map[pingRequest]bool),
} }
go d.loopRead() go d.loopRead()
return d, nil return d, nil
@ -76,6 +90,59 @@ func (d *Destination) loopRead() {
} }
return return
} }
if !d.destination.Is6() {
ipHdr := header.IPv4(buffer.Bytes())
if !ipHdr.IsValid(buffer.Len()) {
d.logger.ErrorContext(d.ctx, E.New("invalid IPv4 header received"))
continue
}
if ipHdr.PayloadLength() < header.ICMPv4MinimumSize {
d.logger.ErrorContext(d.ctx, E.New("invalid ICMPv4 header received"))
continue
}
icmpHdr := header.ICMPv4(ipHdr.Payload())
if icmpHdr.Type() != header.ICMPv4EchoReply {
continue
}
var requestExists bool
request := pingRequest{Source: ipHdr.DestinationAddr(), Destination: ipHdr.SourceAddr(), Identifier: icmpHdr.Ident(), Sequence: icmpHdr.Sequence()}
d.requestAccess.Lock()
if d.requests[request] {
requestExists = true
delete(d.requests, request)
}
d.requestAccess.Unlock()
if !requestExists {
continue
}
d.logger.TraceContext(d.ctx, "read ICMPv4 echo reply from ", ipHdr.SourceAddr(), " to ", ipHdr.DestinationAddr(), " id ", icmpHdr.Ident(), " seq ", icmpHdr.Sequence())
} else {
ipHdr := header.IPv6(buffer.Bytes())
if !ipHdr.IsValid(buffer.Len()) {
d.logger.ErrorContext(d.ctx, E.New("invalid IPv6 header received"))
continue
}
if ipHdr.PayloadLength() < header.ICMPv6MinimumSize {
d.logger.ErrorContext(d.ctx, E.New("invalid ICMPv6 header received"))
continue
}
icmpHdr := header.ICMPv6(ipHdr.Payload())
if icmpHdr.Type() != header.ICMPv6EchoReply {
continue
}
var requestExists bool
request := pingRequest{Source: ipHdr.DestinationAddr(), Destination: ipHdr.SourceAddr(), Identifier: icmpHdr.Ident(), Sequence: icmpHdr.Sequence()}
d.requestAccess.Lock()
if d.requests[request] {
requestExists = true
delete(d.requests, request)
}
d.requestAccess.Unlock()
if !requestExists {
continue
}
d.logger.TraceContext(d.ctx, "read ICMPv6 echo reply from ", ipHdr.SourceAddr(), " to ", ipHdr.DestinationAddr(), " id ", icmpHdr.Ident(), " seq ", icmpHdr.Sequence())
}
err = d.routeContext.WritePacket(buffer.Bytes()) err = d.routeContext.WritePacket(buffer.Bytes())
if err != nil { if err != nil {
d.logger.ErrorContext(d.ctx, E.Cause(err, "write ICMP echo reply")) d.logger.ErrorContext(d.ctx, E.Cause(err, "write ICMP echo reply"))
@ -85,6 +152,33 @@ func (d *Destination) loopRead() {
} }
func (d *Destination) WritePacket(packet *buf.Buffer) error { func (d *Destination) WritePacket(packet *buf.Buffer) error {
if !d.destination.Is6() {
ipHdr := header.IPv4(packet.Bytes())
if !ipHdr.IsValid(packet.Len()) {
return E.New("invalid IPv4 header")
}
if ipHdr.PayloadLength() < header.ICMPv4MinimumSize {
return E.New("invalid ICMPv4 header")
}
icmpHdr := header.ICMPv4(ipHdr.Payload())
d.requestAccess.Lock()
d.requests[pingRequest{Source: ipHdr.SourceAddr(), Destination: ipHdr.DestinationAddr(), Identifier: icmpHdr.Ident(), Sequence: icmpHdr.Sequence()}] = true
d.requestAccess.Unlock()
d.logger.TraceContext(d.ctx, "write ICMPv4 echo request from ", ipHdr.SourceAddr(), " to ", ipHdr.DestinationAddr(), " id ", icmpHdr.Ident(), " seq ", icmpHdr.Sequence())
} else {
ipHdr := header.IPv6(packet.Bytes())
if !ipHdr.IsValid(packet.Len()) {
return E.New("invalid IPv6 header")
}
if ipHdr.PayloadLength() < header.ICMPv6MinimumSize {
return E.New("invalid ICMPv6 header")
}
icmpHdr := header.ICMPv6(ipHdr.Payload())
d.requestAccess.Lock()
d.requests[pingRequest{Source: ipHdr.SourceAddr(), Destination: ipHdr.DestinationAddr(), Identifier: icmpHdr.Ident(), Sequence: icmpHdr.Sequence()}] = true
d.requestAccess.Unlock()
d.logger.TraceContext(d.ctx, "write ICMPv6 echo request from ", ipHdr.SourceAddr(), " to ", ipHdr.DestinationAddr(), " id ", icmpHdr.Ident(), " seq ", icmpHdr.Sequence())
}
return d.conn.WriteIP(packet) return d.conn.WriteIP(packet)
} }

View file

@ -15,7 +15,6 @@ import (
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/control" "github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
"golang.org/x/net/ipv4" "golang.org/x/net/ipv4"
@ -24,7 +23,6 @@ import (
type Conn struct { type Conn struct {
ctx context.Context ctx context.Context
logger logger.ContextLogger
privileged bool privileged bool
conn net.Conn conn net.Conn
destination netip.Addr destination netip.Addr
@ -32,10 +30,9 @@ type Conn struct {
closed atomic.Bool closed atomic.Bool
} }
func Connect(ctx context.Context, logger logger.ContextLogger, privileged bool, controlFunc control.Func, destination netip.Addr) (*Conn, error) { func Connect(ctx context.Context, privileged bool, controlFunc control.Func, destination netip.Addr) (*Conn, error) {
c := &Conn{ c := &Conn{
ctx: ctx, ctx: ctx,
logger: logger,
privileged: privileged, privileged: privileged,
destination: destination, destination: destination,
} }
@ -123,7 +120,6 @@ func (c *Conn) ReadIP(buffer *buf.Buffer) error {
TotalLength: uint16(buffer.Len()), TotalLength: uint16(buffer.Len()),
}) })
ipHdr.SetChecksum(^ipHdr.CalculateChecksum()) ipHdr.SetChecksum(^ipHdr.CalculateChecksum())
c.logger.TraceContext(c.ctx, "read icmpv4 echo reply from ", ipHdr.SourceAddr(), " to ", ipHdr.DestinationAddr())
} else { } else {
oob := make([]byte, 1024) oob := make([]byte, 1024)
buffer.Advance(header.IPv6MinimumSize) buffer.Advance(header.IPv6MinimumSize)
@ -164,7 +160,6 @@ func (c *Conn) ReadIP(buffer *buf.Buffer) error {
SrcAddr: addr, SrcAddr: addr,
DstAddr: c.source.Load(), DstAddr: c.source.Load(),
}) })
c.logger.TraceContext(c.ctx, "read icmpv6 echo reply from ", ipHdr.SourceAddr(), " to ", ipHdr.DestinationAddr())
} }
} else { } else {
_, err := buffer.ReadOnceFrom(c.conn) _, err := buffer.ReadOnceFrom(c.conn)
@ -192,7 +187,6 @@ func (c *Conn) ReadIP(buffer *buf.Buffer) error {
} }
icmpHdr.SetChecksum(0) icmpHdr.SetChecksum(0)
icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr[:header.ICMPv4MinimumSize], checksum.Checksum(icmpHdr.Payload(), 0))) icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr[:header.ICMPv4MinimumSize], checksum.Checksum(icmpHdr.Payload(), 0)))
c.logger.TraceContext(c.ctx, "read icmpv4 echo reply from ", ipHdr.SourceAddr(), " to ", ipHdr.DestinationAddr())
} else { } else {
ipHdr := header.IPv6(buffer.Bytes()) ipHdr := header.IPv6(buffer.Bytes())
if !ipHdr.IsValid(buffer.Len()) { if !ipHdr.IsValid(buffer.Len()) {
@ -209,7 +203,6 @@ func (c *Conn) ReadIP(buffer *buf.Buffer) error {
Src: ipHdr.SourceAddressSlice(), Src: ipHdr.SourceAddressSlice(),
Dst: ipHdr.DestinationAddressSlice(), Dst: ipHdr.DestinationAddressSlice(),
})) }))
c.logger.TraceContext(c.ctx, "read icmpv6 echo reply from ", ipHdr.SourceAddr(), " to ", ipHdr.DestinationAddr())
} }
} }
return nil return nil
@ -254,7 +247,6 @@ func (c *Conn) WriteIP(buffer *buf.Buffer) error {
icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr[:header.ICMPv4MinimumSize], checksum.Checksum(icmpHdr.Payload(), 0))) icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr[:header.ICMPv4MinimumSize], checksum.Checksum(icmpHdr.Payload(), 0)))
} }
c.source.Store(M.AddrFromIP(ipHdr.SourceAddressSlice())) c.source.Store(M.AddrFromIP(ipHdr.SourceAddressSlice()))
c.logger.TraceContext(c.ctx, "write icmpv4 echo request from ", ipHdr.SourceAddr(), " to ", ipHdr.DestinationAddr())
return common.Error(c.conn.Write(ipHdr.Payload())) return common.Error(c.conn.Write(ipHdr.Payload()))
} else { } else {
ipHdr := header.IPv6(buffer.Bytes()) ipHdr := header.IPv6(buffer.Bytes())
@ -269,7 +261,6 @@ func (c *Conn) WriteIP(buffer *buf.Buffer) error {
})) }))
} }
c.source.Store(M.AddrFromIP(ipHdr.SourceAddressSlice())) c.source.Store(M.AddrFromIP(ipHdr.SourceAddressSlice()))
c.logger.TraceContext(c.ctx, "write icmpv6 echo request from ", ipHdr.SourceAddr(), " to ", ipHdr.DestinationAddr())
return common.Error(c.conn.Write(ipHdr.Payload())) return common.Error(c.conn.Write(ipHdr.Payload()))
} }
} }
@ -282,7 +273,6 @@ func (c *Conn) WriteICMP(buffer *buf.Buffer) error {
icmpHdr.SetIdent(^icmpHdr.Ident()) icmpHdr.SetIdent(^icmpHdr.Ident())
icmpHdr.SetChecksum(0) icmpHdr.SetChecksum(0)
icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr[:header.ICMPv4MinimumSize], checksum.Checksum(icmpHdr.Payload(), 0))) icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr[:header.ICMPv4MinimumSize], checksum.Checksum(icmpHdr.Payload(), 0)))
c.logger.TraceContext(c.ctx, "write icmpv4 echo request to ", c.destination)
} else { } else {
icmpHdr := header.ICMPv6(buffer.Bytes()) icmpHdr := header.ICMPv6(buffer.Bytes())
icmpHdr.SetIdent(^icmpHdr.Ident()) icmpHdr.SetIdent(^icmpHdr.Ident())
@ -294,11 +284,6 @@ func (c *Conn) WriteICMP(buffer *buf.Buffer) error {
})) }))
} }
} }
if !c.destination.Is6() {
c.logger.TraceContext(c.ctx, "write icmpv4 echo request to ", c.destination)
} else {
c.logger.TraceContext(c.ctx, "write icmpv6 echo request to ", c.destination)
}
return common.Error(c.conn.Write(buffer.Bytes())) return common.Error(c.conn.Write(buffer.Bytes()))
} }

View file

@ -12,8 +12,6 @@ import (
"github.com/sagernet/sing-tun/internal/gtcpip/header" "github.com/sagernet/sing-tun/internal/gtcpip/header"
"github.com/sagernet/sing-tun/ping" "github.com/sagernet/sing-tun/ping"
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/logger"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -73,7 +71,7 @@ func TestPing(t *testing.T) {
} }
func testPingIPv4ReadIP(t *testing.T, privileged bool, addr string) { func testPingIPv4ReadIP(t *testing.T, privileged bool, addr string) {
conn, err := ping.Connect(context.Background(), logger.NOP(), privileged, nil, netip.MustParseAddr(addr)) conn, err := ping.Connect(context.Background(), privileged, nil, netip.MustParseAddr(addr))
if runtime.GOOS == "linux" && err != nil && err.Error() == "socket(): permission denied" { if runtime.GOOS == "linux" && err != nil && err.Error() == "socket(): permission denied" {
t.SkipNow() t.SkipNow()
} }
@ -106,7 +104,7 @@ func testPingIPv4ReadIP(t *testing.T, privileged bool, addr string) {
} }
func testPingIPv4ReadICMP(t *testing.T, privileged bool, addr string) { func testPingIPv4ReadICMP(t *testing.T, privileged bool, addr string) {
conn, err := ping.Connect(context.Background(), logger.NOP(), privileged, nil, netip.MustParseAddr(addr)) conn, err := ping.Connect(context.Background(), privileged, nil, netip.MustParseAddr(addr))
if runtime.GOOS == "linux" && err != nil && err.Error() == "socket(): permission denied" { if runtime.GOOS == "linux" && err != nil && err.Error() == "socket(): permission denied" {
t.SkipNow() t.SkipNow()
} }
@ -138,7 +136,7 @@ func testPingIPv4ReadICMP(t *testing.T, privileged bool, addr string) {
} }
func testPingIPv6ReadIP(t *testing.T, privileged bool, addr string) { func testPingIPv6ReadIP(t *testing.T, privileged bool, addr string) {
conn, err := ping.Connect(context.Background(), logger.NOP(), privileged, nil, netip.MustParseAddr(addr)) conn, err := ping.Connect(context.Background(), privileged, nil, netip.MustParseAddr(addr))
if runtime.GOOS == "linux" && err != nil && err.Error() == "socket(): permission denied" { if runtime.GOOS == "linux" && err != nil && err.Error() == "socket(): permission denied" {
t.SkipNow() t.SkipNow()
} }
@ -170,7 +168,7 @@ func testPingIPv6ReadIP(t *testing.T, privileged bool, addr string) {
} }
func testPingIPv6ReadICMP(t *testing.T, privileged bool, addr string) { func testPingIPv6ReadICMP(t *testing.T, privileged bool, addr string) {
conn, err := ping.Connect(context.Background(), logger.NOP(), privileged, nil, netip.MustParseAddr(addr)) conn, err := ping.Connect(context.Background(), privileged, nil, netip.MustParseAddr(addr))
if runtime.GOOS == "linux" && err != nil && err.Error() == "socket(): permission denied" { if runtime.GOOS == "linux" && err != nil && err.Error() == "socket(): permission denied" {
t.SkipNow() t.SkipNow()
} }