package daemon import ( "context" "fmt" "strconv" "strings" "sync" ) const tapPoolPrefix = "tap-pool-" // tapPool owns the idle TAP interface cache plus the monotonic index used to // name new pool entries. All access goes through mu. type tapPool struct { mu sync.Mutex entries []string next int } // initializeTapPool seeds the monotonic pool index from the set of // tap names already in use by running/stopped VMs, so newly warmed // pool entries don't collide with existing ones. Callers (Daemon.Open) // enumerate used taps from the handle cache and pass them in. func (n *HostNetwork) initializeTapPool(usedTaps []string) { if n.config.TapPoolSize <= 0 { return } next := 0 for _, tapName := range usedTaps { if index, ok := parseTapPoolIndex(tapName); ok && index >= next { next = index + 1 } } n.tapPool.mu.Lock() n.tapPool.next = next n.tapPool.mu.Unlock() } func (n *HostNetwork) ensureTapPool(ctx context.Context) { if n.config.TapPoolSize <= 0 { return } for { select { case <-ctx.Done(): return case <-n.closing: return default: } n.tapPool.mu.Lock() if len(n.tapPool.entries) >= n.config.TapPoolSize { n.tapPool.mu.Unlock() return } tapName := fmt.Sprintf("%s%d", tapPoolPrefix, n.tapPool.next) n.tapPool.next++ n.tapPool.mu.Unlock() if err := n.createTap(ctx, tapName); err != nil { if n.logger != nil { n.logger.Warn("tap pool warmup failed", "tap_device", tapName, "error", err.Error()) } return } n.tapPool.mu.Lock() n.tapPool.entries = append(n.tapPool.entries, tapName) n.tapPool.mu.Unlock() if n.logger != nil { n.logger.Debug("tap added to idle pool", "tap_device", tapName) } } } func (n *HostNetwork) acquireTap(ctx context.Context, fallbackName string) (string, error) { n.tapPool.mu.Lock() if count := len(n.tapPool.entries); count > 0 { tapName := n.tapPool.entries[count-1] n.tapPool.entries = n.tapPool.entries[:count-1] n.tapPool.mu.Unlock() return tapName, nil } n.tapPool.mu.Unlock() if err := n.createTap(ctx, fallbackName); err != nil { return "", err } return fallbackName, nil } func (n *HostNetwork) releaseTap(ctx context.Context, tapName string) error { tapName = strings.TrimSpace(tapName) if tapName == "" { return nil } if isTapPoolName(tapName) { n.tapPool.mu.Lock() if len(n.tapPool.entries) < n.config.TapPoolSize { n.tapPool.entries = append(n.tapPool.entries, tapName) n.tapPool.mu.Unlock() return nil } n.tapPool.mu.Unlock() } err := n.privOps().DeleteTap(ctx, tapName) if err == nil { go n.ensureTapPool(context.Background()) } return err } func isTapPoolName(tapName string) bool { return strings.HasPrefix(strings.TrimSpace(tapName), tapPoolPrefix) } func parseTapPoolIndex(tapName string) (int, bool) { if !isTapPoolName(tapName) { return 0, false } value, err := strconv.Atoi(strings.TrimPrefix(strings.TrimSpace(tapName), tapPoolPrefix)) if err != nil { return 0, false } return value, true }