@@ -45,6 +45,12 @@ import (
4545 "golang.org/x/sys/unix"
4646)
4747
48+ const (
49+ // Cap async DNS handling to avoid unbounded goroutine growth under
50+ // sustained high-rate DNS traffic.
51+ maxAsyncDnsInFlight = 512
52+ )
53+
4854type ControlPlane struct {
4955 log * logrus.Logger
5056
@@ -58,6 +64,7 @@ type ControlPlane struct {
5864
5965 dnsController * DnsController
6066 onceNetworkReady sync.Once
67+ dnsAsyncSem chan struct {}
6168
6269 dialMode consts.DialMode
6370
@@ -390,6 +397,7 @@ func NewControlPlane(
390397 outbounds : outbounds ,
391398 dnsController : nil ,
392399 onceNetworkReady : sync.Once {},
400+ dnsAsyncSem : make (chan struct {}, maxAsyncDnsInFlight ),
393401 dialMode : dialMode ,
394402 routingMatcher : routingMatcher ,
395403 ctx : ctx ,
@@ -833,20 +841,33 @@ func (c *ControlPlane) Serve(readyChan chan<- bool, listener *Listener) (err err
833841 // DNS packets must not block the per-src serial task queue:
834842 // a single slow upstream (up to DefaultDialTimeout=8s) would
835843 // stall all subsequent packets from the same source IP.
836- // DNS is stateless; parallel handling is safe.
844+ // DNS is stateless; parallel handling is safe. Use a bounded
845+ // semaphore to avoid unbounded goroutine growth.
837846 if pktDst .Port () == 53 || pktDst .Port () == 5353 {
838- // Transfer buffer ownership to the goroutine; clear defers.
839- gData := data
840- gOob := oob
841- data = nil
842- oob = nil
843- go func () {
844- defer gData .Put ()
845- defer gOob .Put ()
846- if e := c .handlePkt (udpConn , gData , convergeSrc , common .ConvergeAddrPort (pktDst ), common .ConvergeAddrPort (realDst ), routingResult , false ); e != nil {
847+ select {
848+ case c .dnsAsyncSem <- struct {}{}:
849+ // Transfer buffer ownership to the goroutine; clear defers.
850+ gData := data
851+ gOob := oob
852+ data = nil
853+ oob = nil
854+ go func () {
855+ defer func () {
856+ <- c .dnsAsyncSem
857+ }()
858+ defer gData .Put ()
859+ defer gOob .Put ()
860+ if e := c .handlePkt (udpConn , gData , convergeSrc , common .ConvergeAddrPort (pktDst ), common .ConvergeAddrPort (realDst ), routingResult , false ); e != nil {
861+ c .log .Warnln ("handlePkt(dns):" , e )
862+ }
863+ }()
864+ default :
865+ // Semaphore saturated: fall back to sync handling here to
866+ // apply backpressure instead of spawning unbounded goroutines.
867+ if e := c .handlePkt (udpConn , data , convergeSrc , common .ConvergeAddrPort (pktDst ), common .ConvergeAddrPort (realDst ), routingResult , false ); e != nil {
847868 c .log .Warnln ("handlePkt(dns):" , e )
848869 }
849- }()
870+ }
850871 return
851872 }
852873 if e := c .handlePkt (udpConn , data , convergeSrc , common .ConvergeAddrPort (pktDst ), common .ConvergeAddrPort (realDst ), routingResult , false ); e != nil {
0 commit comments