diff --git a/ping/destination.go b/ping/destination.go index dc20112..cf197a0 100644 --- a/ping/destination.go +++ b/ping/destination.go @@ -6,9 +6,11 @@ import ( "net/netip" "os" "runtime" + "sync" "time" "github.com/sagernet/sing-tun" + "github.com/sagernet/sing-tun/internal/gtcpip/header" "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" @@ -18,18 +20,28 @@ import ( var _ tun.DirectRouteDestination = (*Destination)(nil) type Destination struct { - conn *Conn - ctx context.Context - logger logger.ContextLogger - routeContext tun.DirectRouteContext - timeout time.Duration + conn *Conn + ctx context.Context + logger logger.ContextLogger + destination netip.Addr + 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( ctx context.Context, logger logger.ContextLogger, controlFunc control.Func, - address netip.Addr, + destination netip.Addr, routeContext tun.DirectRouteContext, timeout time.Duration, ) (tun.DirectRouteDestination, error) { @@ -39,11 +51,11 @@ func ConnectDestination( ) switch runtime.GOOS { case "darwin", "ios", "windows": - conn, err = Connect(ctx, logger, false, controlFunc, address) + conn, err = Connect(ctx, false, controlFunc, destination) default: - conn, err = Connect(ctx, logger, true, controlFunc, address) + conn, err = Connect(ctx, true, controlFunc, destination) if errors.Is(err, os.ErrPermission) { - conn, err = Connect(ctx, logger, false, controlFunc, address) + conn, err = Connect(ctx, false, controlFunc, destination) } } if err != nil { @@ -53,8 +65,10 @@ func ConnectDestination( conn: conn, ctx: ctx, logger: logger, + destination: destination, routeContext: routeContext, timeout: timeout, + requests: make(map[pingRequest]bool), } go d.loopRead() return d, nil @@ -76,6 +90,59 @@ func (d *Destination) loopRead() { } 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()) if err != nil { 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 { + 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) } diff --git a/ping/ping.go b/ping/ping.go index 4c2d98e..d651852 100644 --- a/ping/ping.go +++ b/ping/ping.go @@ -15,7 +15,6 @@ import ( "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" "golang.org/x/net/ipv4" @@ -24,7 +23,6 @@ import ( type Conn struct { ctx context.Context - logger logger.ContextLogger privileged bool conn net.Conn destination netip.Addr @@ -32,10 +30,9 @@ type Conn struct { 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{ ctx: ctx, - logger: logger, privileged: privileged, destination: destination, } @@ -123,7 +120,6 @@ func (c *Conn) ReadIP(buffer *buf.Buffer) error { TotalLength: uint16(buffer.Len()), }) ipHdr.SetChecksum(^ipHdr.CalculateChecksum()) - c.logger.TraceContext(c.ctx, "read icmpv4 echo reply from ", ipHdr.SourceAddr(), " to ", ipHdr.DestinationAddr()) } else { oob := make([]byte, 1024) buffer.Advance(header.IPv6MinimumSize) @@ -164,7 +160,6 @@ func (c *Conn) ReadIP(buffer *buf.Buffer) error { SrcAddr: addr, DstAddr: c.source.Load(), }) - c.logger.TraceContext(c.ctx, "read icmpv6 echo reply from ", ipHdr.SourceAddr(), " to ", ipHdr.DestinationAddr()) } } else { _, err := buffer.ReadOnceFrom(c.conn) @@ -192,7 +187,6 @@ func (c *Conn) ReadIP(buffer *buf.Buffer) error { } icmpHdr.SetChecksum(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 { ipHdr := header.IPv6(buffer.Bytes()) if !ipHdr.IsValid(buffer.Len()) { @@ -209,7 +203,6 @@ func (c *Conn) ReadIP(buffer *buf.Buffer) error { Src: ipHdr.SourceAddressSlice(), Dst: ipHdr.DestinationAddressSlice(), })) - c.logger.TraceContext(c.ctx, "read icmpv6 echo reply from ", ipHdr.SourceAddr(), " to ", ipHdr.DestinationAddr()) } } 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))) } 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())) } else { 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.logger.TraceContext(c.ctx, "write icmpv6 echo request from ", ipHdr.SourceAddr(), " to ", ipHdr.DestinationAddr()) 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.SetChecksum(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 { icmpHdr := header.ICMPv6(buffer.Bytes()) 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())) } diff --git a/ping/ping_test.go b/ping/ping_test.go index 3091a0a..73a56b2 100644 --- a/ping/ping_test.go +++ b/ping/ping_test.go @@ -12,8 +12,6 @@ import ( "github.com/sagernet/sing-tun/internal/gtcpip/header" "github.com/sagernet/sing-tun/ping" "github.com/sagernet/sing/common/buf" - "github.com/sagernet/sing/common/logger" - "github.com/stretchr/testify/require" ) @@ -73,7 +71,7 @@ func TestPing(t *testing.T) { } 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" { t.SkipNow() } @@ -106,7 +104,7 @@ func testPingIPv4ReadIP(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" { t.SkipNow() } @@ -138,7 +136,7 @@ func testPingIPv4ReadICMP(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" { t.SkipNow() } @@ -170,7 +168,7 @@ func testPingIPv6ReadIP(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" { t.SkipNow() }