ping: Fix unprivileged conn leak

This commit is contained in:
世界 2026-05-19 16:25:51 +08:00
parent e5d2fab035
commit 47ac4d08b1
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
4 changed files with 20 additions and 12 deletions

View file

@ -51,11 +51,11 @@ func ConnectDestination(
) )
switch runtime.GOOS { switch runtime.GOOS {
case "darwin", "ios", "windows": case "darwin", "ios", "windows":
conn, err = Connect(ctx, false, controlFunc, destination) conn, err = Connect(ctx, false, controlFunc, destination, timeout)
default: default:
conn, err = Connect(ctx, true, controlFunc, destination) conn, err = Connect(ctx, true, controlFunc, destination, timeout)
if errors.Is(err, os.ErrPermission) { if errors.Is(err, os.ErrPermission) {
conn, err = Connect(ctx, false, controlFunc, destination) conn, err = Connect(ctx, false, controlFunc, destination, timeout)
} }
} }
if err != nil { if err != nil {

View file

@ -30,22 +30,22 @@ type Conn struct {
readMsg func(b, oob []byte) (n, oobn int, addr netip.Addr, err error) readMsg func(b, oob []byte) (n, oobn int, addr netip.Addr, err error)
} }
func Connect(ctx context.Context, privileged bool, controlFunc control.Func, destination netip.Addr) (*Conn, error) { func Connect(ctx context.Context, privileged bool, controlFunc control.Func, destination netip.Addr, idleTimeout time.Duration) (*Conn, error) {
c := &Conn{ c := &Conn{
ctx: ctx, ctx: ctx,
privileged: privileged, privileged: privileged,
destination: destination, destination: destination,
} }
err := c.connect(controlFunc) err := c.connect(controlFunc, idleTimeout)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return c, nil return c, nil
} }
func (c *Conn) connect(controlFunc control.Func) (err error) { func (c *Conn) connect(controlFunc control.Func, idleTimeout time.Duration) (err error) {
if c.isLinuxUnprivileged() { if c.isLinuxUnprivileged() {
c.conn, err = newUnprivilegedConn(c.ctx, controlFunc, c.destination) c.conn, err = newUnprivilegedConn(c.ctx, controlFunc, c.destination, idleTimeout)
} else { } else {
c.conn, err = connect(c.privileged, controlFunc, c.destination) c.conn, err = connect(c.privileged, controlFunc, c.destination)
} }

View file

@ -72,7 +72,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(), privileged, nil, netip.MustParseAddr(addr)) conn, err := ping.Connect(context.Background(), privileged, nil, netip.MustParseAddr(addr), 0)
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()
} }
@ -105,7 +105,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(), privileged, nil, netip.MustParseAddr(addr)) conn, err := ping.Connect(context.Background(), privileged, nil, netip.MustParseAddr(addr), 0)
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()
} }
@ -137,7 +137,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(), privileged, nil, netip.MustParseAddr(addr)) conn, err := ping.Connect(context.Background(), privileged, nil, netip.MustParseAddr(addr), 0)
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()
} }
@ -169,7 +169,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(), privileged, nil, netip.MustParseAddr(addr)) conn, err := ping.Connect(context.Background(), privileged, nil, netip.MustParseAddr(addr), 0)
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()
} }

View file

@ -21,6 +21,7 @@ type UnprivilegedConn struct {
cancel context.CancelFunc cancel context.CancelFunc
controlFunc control.Func controlFunc control.Func
destination netip.Addr destination netip.Addr
idleTimeout time.Duration
receiveChan chan *unprivilegedResponse receiveChan chan *unprivilegedResponse
readDeadline pipe.Deadline readDeadline pipe.Deadline
mappingAccess sync.Mutex mappingAccess sync.Mutex
@ -33,7 +34,7 @@ type unprivilegedResponse struct {
Addr netip.Addr Addr netip.Addr
} }
func newUnprivilegedConn(ctx context.Context, controlFunc control.Func, destination netip.Addr) (net.Conn, error) { func newUnprivilegedConn(ctx context.Context, controlFunc control.Func, destination netip.Addr, idleTimeout time.Duration) (net.Conn, error) {
conn, err := connect(false, controlFunc, destination) conn, err := connect(false, controlFunc, destination)
if err != nil { if err != nil {
return nil, err return nil, err
@ -45,6 +46,7 @@ func newUnprivilegedConn(ctx context.Context, controlFunc control.Func, destinat
cancel: cancel, cancel: cancel,
controlFunc: controlFunc, controlFunc: controlFunc,
destination: destination, destination: destination,
idleTimeout: idleTimeout,
receiveChan: make(chan *unprivilegedResponse), receiveChan: make(chan *unprivilegedResponse),
readDeadline: pipe.MakeDeadline(), readDeadline: pipe.MakeDeadline(),
mapping: make(map[uint16]net.Conn), mapping: make(map[uint16]net.Conn),
@ -116,6 +118,12 @@ func (c *UnprivilegedConn) Write(b []byte) (n int, err error) {
func (c *UnprivilegedConn) fetchResponse(conn *net.UDPConn, identifier uint16) { func (c *UnprivilegedConn) fetchResponse(conn *net.UDPConn, identifier uint16) {
defer c.removeConn(conn, identifier) defer c.removeConn(conn, identifier)
for { for {
if c.idleTimeout > 0 {
err := conn.SetReadDeadline(time.Now().Add(c.idleTimeout))
if err != nil {
return
}
}
buffer := buf.NewPacket() buffer := buf.NewPacket()
cmsgBuffer := buf.NewSize(1024) cmsgBuffer := buf.NewSize(1024)
n, oobN, _, addr, err := conn.ReadMsgUDPAddrPort(buffer.FreeBytes(), cmsgBuffer.FreeBytes()) n, oobN, _, addr, err := conn.ReadMsgUDPAddrPort(buffer.FreeBytes(), cmsgBuffer.FreeBytes())