ping: Add timeout to destinations

This commit is contained in:
世界 2025-08-24 17:46:03 +08:00
parent 8f6cc9f62e
commit 737ebf01c4
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
6 changed files with 81 additions and 19 deletions

View file

@ -6,6 +6,7 @@ import (
"net/netip" "net/netip"
"os" "os"
"runtime" "runtime"
"time"
"github.com/sagernet/sing-tun" "github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
@ -17,13 +18,21 @@ import (
var _ tun.DirectRouteDestination = (*Destination)(nil) var _ tun.DirectRouteDestination = (*Destination)(nil)
type Destination struct { type Destination struct {
conn *Conn
ctx context.Context ctx context.Context
logger logger.ContextLogger logger logger.ContextLogger
routeContext tun.DirectRouteContext routeContext tun.DirectRouteContext
conn *Conn timeout time.Duration
} }
func ConnectDestination(ctx context.Context, logger logger.ContextLogger, controlFunc control.Func, address netip.Addr, routeContext tun.DirectRouteContext) (tun.DirectRouteDestination, error) { func ConnectDestination(
ctx context.Context,
logger logger.ContextLogger,
controlFunc control.Func,
address netip.Addr,
routeContext tun.DirectRouteContext,
timeout time.Duration,
) (tun.DirectRouteDestination, error) {
var ( var (
conn *Conn conn *Conn
err error err error
@ -41,19 +50,25 @@ func ConnectDestination(ctx context.Context, logger logger.ContextLogger, contro
return nil, err return nil, err
} }
d := &Destination{ d := &Destination{
conn: conn,
ctx: ctx, ctx: ctx,
logger: logger, logger: logger,
routeContext: routeContext, routeContext: routeContext,
conn: conn, timeout: timeout,
} }
go d.loopRead() go d.loopRead()
return d, nil return d, nil
} }
func (d *Destination) loopRead() { func (d *Destination) loopRead() {
defer d.Close()
for { for {
buffer := buf.NewPacket() buffer := buf.NewPacket()
err := d.conn.ReadIP(buffer) err := d.conn.SetReadDeadline(time.Now().Add(d.timeout))
if err != nil {
d.logger.ErrorContext(d.ctx, E.Cause(err, "set read deadline for ICMP conn"))
}
err = d.conn.ReadIP(buffer)
if err != nil { if err != nil {
buffer.Release() buffer.Release()
if !E.IsClosed(err) { if !E.IsClosed(err) {
@ -76,3 +91,7 @@ func (d *Destination) WritePacket(packet *buf.Buffer) error {
func (d *Destination) Close() error { func (d *Destination) Close() error {
return d.conn.Close() return d.conn.Close()
} }
func (d *Destination) IsClosed() bool {
return d.conn.IsClosed()
}

View file

@ -5,11 +5,13 @@ package ping
import ( import (
"context" "context"
"net/netip" "net/netip"
"time"
"github.com/sagernet/gvisor/pkg/tcpip" "github.com/sagernet/gvisor/pkg/tcpip"
"github.com/sagernet/gvisor/pkg/tcpip/adapters/gonet" "github.com/sagernet/gvisor/pkg/tcpip/adapters/gonet"
"github.com/sagernet/gvisor/pkg/tcpip/header" "github.com/sagernet/gvisor/pkg/tcpip/header"
"github.com/sagernet/gvisor/pkg/tcpip/stack" "github.com/sagernet/gvisor/pkg/tcpip/stack"
"github.com/sagernet/gvisor/pkg/tcpip/transport"
"github.com/sagernet/gvisor/pkg/waiter" "github.com/sagernet/gvisor/pkg/waiter"
"github.com/sagernet/sing-tun" "github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
@ -23,8 +25,10 @@ var _ tun.DirectRouteDestination = (*GVisorDestination)(nil)
type GVisorDestination struct { type GVisorDestination struct {
ctx context.Context ctx context.Context
logger logger.ContextLogger logger logger.ContextLogger
endpoint tcpip.Endpoint
conn *gonet.TCPConn conn *gonet.TCPConn
rewriter *Rewriter rewriter *Rewriter
timeout time.Duration
} }
func ConnectGVisor( func ConnectGVisor(
@ -33,6 +37,7 @@ func ConnectGVisor(
routeContext tun.DirectRouteContext, routeContext tun.DirectRouteContext,
stack *stack.Stack, stack *stack.Stack,
bindAddress4, bindAddress6 netip.Addr, bindAddress4, bindAddress6 netip.Addr,
timeout time.Duration,
) (*GVisorDestination, error) { ) (*GVisorDestination, error) {
var ( var (
bindAddress tcpip.Address bindAddress tcpip.Address
@ -76,16 +81,23 @@ func ConnectGVisor(
destination := &GVisorDestination{ destination := &GVisorDestination{
ctx: ctx, ctx: ctx,
logger: logger, logger: logger,
endpoint: endpoint,
conn: gonet.NewTCPConn(&wq, endpoint), conn: gonet.NewTCPConn(&wq, endpoint),
rewriter: rewriter, rewriter: rewriter,
timeout: timeout,
} }
go destination.loopRead() go destination.loopRead()
return destination, nil return destination, nil
} }
func (d *GVisorDestination) loopRead() { func (d *GVisorDestination) loopRead() {
defer d.endpoint.Close()
for { for {
buffer := buf.NewPacket() buffer := buf.NewPacket()
err := d.conn.SetReadDeadline(time.Now().Add(d.timeout))
if err != nil {
d.logger.ErrorContext(d.ctx, E.Cause(err, "set read deadline for ICMP conn"))
}
n, err := d.conn.Read(buffer.FreeBytes()) n, err := d.conn.Read(buffer.FreeBytes())
if err != nil { if err != nil {
buffer.Release() buffer.Release()
@ -111,3 +123,7 @@ func (d *GVisorDestination) WritePacket(packet *buf.Buffer) error {
func (d *GVisorDestination) Close() error { func (d *GVisorDestination) Close() error {
return d.conn.Close() return d.conn.Close()
} }
func (d *GVisorDestination) IsClosed() bool {
return transport.DatagramEndpointState(d.endpoint.State()) == transport.DatagramEndpointStateClosed
}

24
ping/destination_test.go Normal file
View file

@ -0,0 +1,24 @@
package ping_test
import (
"context"
"net/netip"
"testing"
"time"
"github.com/sagernet/sing-tun/ping"
"github.com/sagernet/sing/common/logger"
"github.com/stretchr/testify/require"
)
func TestIsClosed(t *testing.T) {
t.Parallel()
destination, err := ping.ConnectDestination(context.Background(), logger.NOP(), nil, netip.MustParseAddr("1.1.1.1"), nil, 30*time.Second)
require.NoError(t, err)
defer destination.Close()
time.Sleep(1 * time.Second)
require.False(t, destination.IsClosed())
destination.Close()
require.True(t, destination.IsClosed())
}

View file

@ -29,6 +29,7 @@ type Conn struct {
conn net.Conn conn net.Conn
destination netip.Addr destination netip.Addr
source atomic.TypedValue[netip.Addr] source atomic.TypedValue[netip.Addr]
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, logger logger.ContextLogger, privileged bool, controlFunc control.Func, destination netip.Addr) (*Conn, error) {
@ -230,5 +231,10 @@ func (c *Conn) SetReadDeadline(t time.Time) error {
} }
func (c *Conn) Close() error { func (c *Conn) Close() error {
defer c.closed.Store(true)
return c.conn.Close() return c.conn.Close()
} }
func (c *Conn) IsClosed() bool {
return c.closed.Load()
}

View file

@ -16,13 +16,12 @@ import (
) )
type UnprivilegedConn struct { type UnprivilegedConn struct {
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
controlFunc control.Func controlFunc control.Func
destination netip.Addr destination netip.Addr
receiveChan chan *unprivilegedResponse receiveChan chan *unprivilegedResponse
readDeadline atomic.TypedValue[time.Time] readDeadline atomic.TypedValue[time.Time]
writeDeadline atomic.TypedValue[time.Time]
} }
type unprivilegedResponse struct { type unprivilegedResponse struct {
@ -89,9 +88,6 @@ func (c *UnprivilegedConn) Write(b []byte) (n int, err error) {
if readDeadline := c.readDeadline.Load(); !readDeadline.IsZero() { if readDeadline := c.readDeadline.Load(); !readDeadline.IsZero() {
conn.SetReadDeadline(readDeadline) conn.SetReadDeadline(readDeadline)
} }
if writeDeadline := c.writeDeadline.Load(); !writeDeadline.IsZero() {
conn.SetWriteDeadline(writeDeadline)
}
n, err = conn.Write(b) n, err = conn.Write(b)
if err != nil { if err != nil {
conn.Close() conn.Close()
@ -157,9 +153,7 @@ func (c *UnprivilegedConn) RemoteAddr() net.Addr {
} }
func (c *UnprivilegedConn) SetDeadline(t time.Time) error { func (c *UnprivilegedConn) SetDeadline(t time.Time) error {
c.readDeadline.Store(t) return os.ErrInvalid
c.writeDeadline.Store(t)
return nil
} }
func (c *UnprivilegedConn) SetReadDeadline(t time.Time) error { func (c *UnprivilegedConn) SetReadDeadline(t time.Time) error {
@ -168,6 +162,5 @@ func (c *UnprivilegedConn) SetReadDeadline(t time.Time) error {
} }
func (c *UnprivilegedConn) SetWriteDeadline(t time.Time) error { func (c *UnprivilegedConn) SetWriteDeadline(t time.Time) error {
c.writeDeadline.Store(t) return os.ErrInvalid
return nil
} }

View file

@ -13,6 +13,7 @@ import (
type DirectRouteDestination interface { type DirectRouteDestination interface {
WritePacket(packet *buf.Buffer) error WritePacket(packet *buf.Buffer) error
Close() error Close() error
IsClosed() bool
} }
type DirectRouteSession struct { type DirectRouteSession struct {
@ -28,6 +29,9 @@ type DirectRouteMapping struct {
func NewDirectRouteMapping(timeout time.Duration) *DirectRouteMapping { func NewDirectRouteMapping(timeout time.Duration) *DirectRouteMapping {
mapping := common.Must1(freelru.NewSharded[DirectRouteSession, DirectRouteDestination](1024, maphash.NewHasher[DirectRouteSession]().Hash32)) mapping := common.Must1(freelru.NewSharded[DirectRouteSession, DirectRouteDestination](1024, maphash.NewHasher[DirectRouteSession]().Hash32))
mapping.SetHealthCheck(func(session DirectRouteSession, destination DirectRouteDestination) bool {
return !destination.IsClosed()
})
mapping.SetOnEvict(func(session DirectRouteSession, action DirectRouteDestination) { mapping.SetOnEvict(func(session DirectRouteSession, action DirectRouteDestination) {
action.Close() action.Close()
}) })