55 "fmt"
66 "math"
77 "os"
8+ "path/filepath"
9+ "strings"
810 "time"
911
1012 cnitypes "github.com/containernetworking/cni/pkg/types"
@@ -18,6 +20,10 @@ import (
1820 "github.com/cri-o/cri-o/server/metrics"
1921)
2022
23+ const (
24+ cacheDir = "/var/lib/cni/results"
25+ )
26+
2127// networkStart sets up the sandbox's network and returns the pod IP on success
2228// or an error.
2329func (s * Server ) networkStart (ctx context.Context , sb * sandbox.Sandbox ) (podIPs []string , result cnitypes.Result , retErr error ) {
@@ -185,43 +191,75 @@ func (s *Server) networkStop(ctx context.Context, sb *sandbox.Sandbox) error {
185191 }
186192
187193 // Check if the network namespace file exists and is valid before attempting CNI teardown.
188- // If the file doesn't exist or is invalid, skip CNI teardown and mark network as stopped.
194+ // If the file doesn't exist or is invalid, we should still attempt CNI teardown using cached information
195+ // to prevent IP leaks, but we'll mark the network as stopped regardless of the outcome.
196+ netnsValid := true
197+
189198 if podNetwork .NetNS != "" {
190199 if _ , statErr := os .Stat (podNetwork .NetNS ); statErr != nil {
191- // Network namespace file doesn't exist, mark network as stopped and return success
192- log .Debugf (ctx , "Network namespace file %s does not exist for pod sandbox %s(%s), skipping CNI teardown" ,
200+ // Network namespace file doesn't exist, but we should still attempt CNI teardown
201+ log .Debugf (ctx , "Network namespace file %s does not exist for pod sandbox %s(%s), attempting CNI teardown with cached info " ,
193202 podNetwork .NetNS , sb .Name (), sb .ID ())
194203
195- return sb .SetNetworkStopped (ctx , true )
196- }
197-
198- if validateErr := s .validateNetworkNamespace (podNetwork .NetNS ); validateErr != nil {
204+ netnsValid = false
205+ } else if validateErr := s .validateNetworkNamespace (podNetwork .NetNS ); validateErr != nil {
199206 // Network namespace file exists but is invalid (e.g., corrupted or fake file)
200- log .Warnf (ctx , "Network namespace file %s is invalid for pod sandbox %s(%s): %v, removing and skipping CNI teardown" ,
207+ log .Warnf (ctx , "Network namespace file %s is invalid for pod sandbox %s(%s): %v, removing and attempting CNI teardown with cached info " ,
201208 podNetwork .NetNS , sb .Name (), sb .ID (), validateErr )
202209 s .cleanupNetns (ctx , podNetwork .NetNS , sb )
203210
204- return sb . SetNetworkStopped ( ctx , true )
211+ netnsValid = false
205212 }
206213 }
207214
215+ // Always attempt CNI teardown to prevent IP leaks, even if netns is invalid.
208216 if err := s .config .CNIPlugin ().TearDownPodWithContext (stopCtx , podNetwork ); err != nil {
209217 log .Warnf (ctx , "Failed to destroy network for pod sandbox %s(%s): %v" , sb .Name (), sb .ID (), err )
210218
211219 // If the network namespace exists but CNI teardown failed, try to clean it up.
212- if podNetwork .NetNS != "" {
220+ if podNetwork .NetNS != "" && netnsValid {
213221 if _ , statErr := os .Stat (podNetwork .NetNS ); statErr == nil {
214222 // Clean up the netns file since CNI teardown failed.
215223 s .cleanupNetns (ctx , podNetwork .NetNS , sb )
216224 }
217225 }
218226
227+ // Clean up CNI result files if CNI teardown failed.
228+ s .cleanupCNIResultFiles (ctx , sb .ID ())
229+
230+ // Even if CNI teardown failed, mark network as stopped to prevent retry loops.
231+ if setErr := sb .SetNetworkStopped (ctx , true ); setErr != nil {
232+ log .Warnf (ctx , "Failed to set network stopped for pod sandbox %s(%s): %v" , sb .Name (), sb .ID (), setErr )
233+ }
234+
219235 return fmt .Errorf ("network teardown failed for pod sandbox %s(%s): %w" , sb .Name (), sb .ID (), err )
220236 }
221237
222238 return sb .SetNetworkStopped (ctx , true )
223239}
224240
241+ // cleanupCNIResultFiles removes CNI result files for a given container ID.
242+ // This is called when CNI teardown fails to prevent stale result files from accumulating.
243+ func (s * Server ) cleanupCNIResultFiles (ctx context.Context , containerID string ) {
244+ entries , err := os .ReadDir (cacheDir )
245+ if err != nil {
246+ log .Warnf (ctx , "Failed to read CNI cache directory %s: %v" , cacheDir , err )
247+
248+ return
249+ }
250+
251+ for _ , entry := range entries {
252+ if ! entry .IsDir () && strings .Contains (entry .Name (), containerID ) {
253+ filePath := filepath .Join (cacheDir , entry .Name ())
254+ if err := os .Remove (filePath ); err != nil {
255+ log .Warnf (ctx , "Failed to remove CNI result file %s: %v" , filePath , err )
256+ } else {
257+ log .Infof (ctx , "Cleaned up CNI result file %s for container %s" , entry .Name (), containerID )
258+ }
259+ }
260+ }
261+ }
262+
225263func (s * Server ) newPodNetwork (ctx context.Context , sb * sandbox.Sandbox ) (ocicni.PodNetwork , error ) {
226264 _ , span := log .StartSpan (ctx )
227265 defer span .End ()
0 commit comments