ping: Add timeout to destinations
This commit is contained in:
parent
8f6cc9f62e
commit
737ebf01c4
6 changed files with 81 additions and 19 deletions
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
24
ping/destination_test.go
Normal 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())
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue