@@ -26,14 +26,25 @@ type UDPProxy struct {
2626 running bool
2727 stopChan chan struct {}
2828 wg sync.WaitGroup
29+
30+ // fwdConn is a shared unbound UDP connection used by all forwarders.
31+ // Because it is not bound to a specific destination, WriteToUDP can
32+ // send to any backend without opening a new socket per packet.
33+ fwdConn * net.UDPConn
34+
35+ // addrCache maps "ip:port" to a resolved *net.UDPAddr so we don't
36+ // call ResolveUDPAddr on every packet.
37+ addrCache map [string ]* net.UDPAddr
38+ addrCacheMu sync.RWMutex
2939}
3040
3141// NewUDPProxy creates a new UDP proxy.
3242func NewUDPProxy (config * ProxyConfig , lb * LoadBalancer ) * UDPProxy {
3343 return & UDPProxy {
34- config : config ,
35- lb : lb ,
36- stopChan : make (chan struct {}),
44+ config : config ,
45+ lb : lb ,
46+ stopChan : make (chan struct {}),
47+ addrCache : make (map [string ]* net.UDPAddr ),
3748 }
3849}
3950
@@ -49,6 +60,14 @@ func (p *UDPProxy) Start(ctx context.Context) {
4960
5061 log .LoggerWContext (ctx ).Info ("Starting UDP proxy" )
5162
63+ // Open a single unbound UDP socket for all outbound forwarding.
64+ fwd , err := net .ListenUDP ("udp" , nil )
65+ if err != nil {
66+ log .LoggerWContext (ctx ).Error (fmt .Sprintf ("Failed to open forwarding socket: %s" , err .Error ()))
67+ return
68+ }
69+ p .fwdConn = fwd
70+
5271 for _ , port := range p .config .Ports {
5372 p .wg .Add (1 )
5473 go func (port int ) {
@@ -76,6 +95,11 @@ func (p *UDPProxy) Stop(ctx context.Context) {
7695 }
7796 }
7897
98+ // Close the shared forwarding socket
99+ if p .fwdConn != nil {
100+ p .fwdConn .Close ()
101+ }
102+
79103 // Wait for all goroutines to finish
80104 done := make (chan struct {})
81105 go func () {
@@ -106,6 +130,11 @@ func (p *UDPProxy) UpdateConfig(ctx context.Context, newConfig *ProxyConfig) {
106130 }
107131
108132 p .config = newConfig
133+
134+ // Flush the address cache so stale entries don't survive a backend change.
135+ p .addrCacheMu .Lock ()
136+ p .addrCache = make (map [string ]* net.UDPAddr )
137+ p .addrCacheMu .Unlock ()
109138}
110139
111140// listenAndForward listens on VIP:port and forwards packets to healthy backends.
@@ -168,6 +197,27 @@ func (p *UDPProxy) listenAndForward(ctx context.Context, port int) {
168197 }
169198}
170199
200+ // resolveAddr returns a cached *net.UDPAddr for the given key (ip:port),
201+ // resolving and caching it on the first call.
202+ func (p * UDPProxy ) resolveAddr (key string ) (* net.UDPAddr , error ) {
203+ p .addrCacheMu .RLock ()
204+ addr , ok := p .addrCache [key ]
205+ p .addrCacheMu .RUnlock ()
206+ if ok {
207+ return addr , nil
208+ }
209+
210+ addr , err := net .ResolveUDPAddr ("udp" , key )
211+ if err != nil {
212+ return nil , err
213+ }
214+
215+ p .addrCacheMu .Lock ()
216+ p .addrCache [key ] = addr
217+ p .addrCacheMu .Unlock ()
218+ return addr , nil
219+ }
220+
171221// forwardPacket forwards a UDP packet to the primary healthy backend.
172222func (p * UDPProxy ) forwardPacket (ctx context.Context , data []byte , srcAddr * net.UDPAddr , port int ) {
173223 backend := p .lb .GetPrimary ()
@@ -176,32 +226,23 @@ func (p *UDPProxy) forwardPacket(ctx context.Context, data []byte, srcAddr *net.
176226 return
177227 }
178228
179- // Create destination address using backend's management IP and same port
180- dstAddr := fmt .Sprintf ("%s:%d" , backend .ManagementIP , port )
181- udpDstAddr , err := net . ResolveUDPAddr ( "udp" , dstAddr )
229+ // Resolve (or retrieve from cache) the destination address
230+ dstKey := fmt .Sprintf ("%s:%d" , backend .ManagementIP , port )
231+ udpDstAddr , err := p . resolveAddr ( dstKey )
182232 if err != nil {
183233 log .LoggerWContext (ctx ).Error (fmt .Sprintf ("Failed to resolve destination address %s: %s" ,
184- dstAddr , err .Error ()))
185- return
186- }
187-
188- // Create a new UDP connection for forwarding
189- // Using a new connection each time since NetFlow/sFlow are fire-and-forget
190- conn , err := net .DialUDP ("udp" , nil , udpDstAddr )
191- if err != nil {
192- log .LoggerWContext (ctx ).Error (fmt .Sprintf ("Failed to connect to backend %s: %s" ,
193- dstAddr , err .Error ()))
234+ dstKey , err .Error ()))
194235 return
195236 }
196- defer conn .Close ()
197237
198- _ , err = conn .Write (data )
238+ // Use the shared unbound socket to send to the backend
239+ _ , err = p .fwdConn .WriteToUDP (data , udpDstAddr )
199240 if err != nil {
200241 log .LoggerWContext (ctx ).Error (fmt .Sprintf ("Failed to forward packet to %s: %s" ,
201- dstAddr , err .Error ()))
242+ dstKey , err .Error ()))
202243 return
203244 }
204245
205246 log .LoggerWContext (ctx ).Debug (fmt .Sprintf ("Forwarded %d bytes from %s to %s" ,
206- len (data ), srcAddr .String (), dstAddr ))
247+ len (data ), srcAddr .String (), dstKey ))
207248}
0 commit comments