ping: Fix stale flows kept alive by unrelated ICMP traffic
Unconnected raw ICMP sockets receive every ICMP packet arriving at the host, so any ICMP traffic refreshed the read deadline of every flow and stale flows (with their raw sockets and goroutines) accumulated forever, making per-ping cost grow linearly with uptime. Expire flows based on their own activity only, and on Linux attach a classic BPF ident filter to each raw socket so other flows' packets are dropped in the kernel instead of waking every flow.
This commit is contained in:
parent
b0188bc3f1
commit
23a39c59be
3 changed files with 16 additions and 4 deletions
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-tun/gtcpip/header"
|
"github.com/sagernet/sing-tun/gtcpip/header"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
"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"
|
||||||
|
|
@ -30,6 +31,7 @@ type Destination struct {
|
||||||
destination netip.Addr
|
destination netip.Addr
|
||||||
writer PacketWriter
|
writer PacketWriter
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
|
lastActive common.TypedValue[time.Time]
|
||||||
requestAccess sync.Mutex
|
requestAccess sync.Mutex
|
||||||
requests map[pingRequest]time.Time
|
requests map[pingRequest]time.Time
|
||||||
}
|
}
|
||||||
|
|
@ -74,6 +76,7 @@ func ConnectDestination(
|
||||||
timeout: timeout,
|
timeout: timeout,
|
||||||
requests: make(map[pingRequest]time.Time),
|
requests: make(map[pingRequest]time.Time),
|
||||||
}
|
}
|
||||||
|
d.lastActive.Store(time.Now())
|
||||||
go d.loopRead()
|
go d.loopRead()
|
||||||
return d, nil
|
return d, nil
|
||||||
}
|
}
|
||||||
|
|
@ -81,14 +84,21 @@ func ConnectDestination(
|
||||||
func (d *Destination) loopRead() {
|
func (d *Destination) loopRead() {
|
||||||
defer d.Close()
|
defer d.Close()
|
||||||
for {
|
for {
|
||||||
buffer := buf.NewSize(maxICMPPacketSize)
|
deadline := d.lastActive.Load().Add(d.timeout)
|
||||||
err := d.conn.SetReadDeadline(time.Now().Add(d.timeout))
|
if !time.Now().Before(deadline) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := d.conn.SetReadDeadline(deadline)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.logger.ErrorContext(d.ctx, E.Cause(err, "set read deadline for ICMP conn"))
|
d.logger.ErrorContext(d.ctx, E.Cause(err, "set read deadline for ICMP conn"))
|
||||||
}
|
}
|
||||||
|
buffer := buf.NewSize(maxICMPPacketSize)
|
||||||
err = d.conn.ReadIP(buffer)
|
err = d.conn.ReadIP(buffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
buffer.Release()
|
buffer.Release()
|
||||||
|
if E.IsTimeout(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if !E.IsClosed(err) {
|
if !E.IsClosed(err) {
|
||||||
d.logger.ErrorContext(d.ctx, E.Cause(err, "receive ICMP echo reply"))
|
d.logger.ErrorContext(d.ctx, E.Cause(err, "receive ICMP echo reply"))
|
||||||
}
|
}
|
||||||
|
|
@ -159,6 +169,7 @@ func (d *Destination) loopRead() {
|
||||||
}
|
}
|
||||||
d.logger.TraceContext(d.ctx, "read ICMPv6 echo reply from ", ipHdr.SourceAddr(), " to ", ipHdr.DestinationAddr(), " id ", icmpHdr.Ident(), " seq ", icmpHdr.Sequence())
|
d.logger.TraceContext(d.ctx, "read ICMPv6 echo reply from ", ipHdr.SourceAddr(), " to ", ipHdr.DestinationAddr(), " id ", icmpHdr.Ident(), " seq ", icmpHdr.Sequence())
|
||||||
}
|
}
|
||||||
|
d.lastActive.Store(time.Now())
|
||||||
err = d.writer.WritePacket(buffer.Bytes())
|
err = d.writer.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"))
|
||||||
|
|
@ -168,6 +179,7 @@ func (d *Destination) loopRead() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Destination) WritePacket(packet *buf.Buffer) error {
|
func (d *Destination) WritePacket(packet *buf.Buffer) error {
|
||||||
|
d.lastActive.Store(time.Now())
|
||||||
if !d.destination.Is6() {
|
if !d.destination.Is6() {
|
||||||
ipHdr := header.IPv4(packet.Bytes())
|
ipHdr := header.IPv4(packet.Bytes())
|
||||||
if !ipHdr.IsValid(packet.Len()) {
|
if !ipHdr.IsValid(packet.Len()) {
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-tun/internal/gtcpip/header"
|
"github.com/sagernet/sing-tun/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/sagernet/sing/common/logger"
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/sagernet/sing-tun/internal/gtcpip/header"
|
"github.com/sagernet/sing-tun/gtcpip/header"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue