package vlan import ( "context" "fmt" "github.com/vishvananda/netlink" "go.uber.org/zap" "github.com/guildhouse-co/kedge/internal/config" ) // Manager manages VLAN interfaces on the node for underlay mode. type Manager struct { cfg config.VLANConfig log *zap.SugaredLogger } // NewManager creates a new VLAN interface manager. func NewManager(cfg config.VLANConfig, log *zap.SugaredLogger) *Manager { return &Manager{cfg: cfg, log: log} } // Run starts the VLAN manager. It ensures all configured VLAN interfaces // and bridges exist on the node. func (m *Manager) Run(ctx context.Context) error { if !m.cfg.Enabled { m.log.Info("underlay mode disabled, vlan manager idle") <-ctx.Done() return nil } // Ensure all configured VLANs exist. for _, v := range m.cfg.VLANs { if err := m.ensureVLAN(v); err != nil { m.log.Warnw("failed to ensure VLAN", "vlan_id", v.ID, "error", err) } } m.log.Infof("vlan manager started, managing %d VLANs", len(m.cfg.VLANs)) <-ctx.Done() return nil } // VLANCount returns the number of managed VLAN interfaces. func (m *Manager) VLANCount() int { return len(m.cfg.VLANs) } // ensureVLAN creates a VLAN interface and bridge if they don't already exist. func (m *Manager) ensureVLAN(v config.VLANEntry) error { bridgeName := fmt.Sprintf("vlan%d", v.ID) // Check if bridge already exists. if _, err := netlink.LinkByName(bridgeName); err == nil { m.log.Debugw("bridge already exists", "name", bridgeName) return nil } // Get the parent interface. parent, err := netlink.LinkByName(v.ParentInterface) if err != nil { return fmt.Errorf("parent interface %s not found: %w", v.ParentInterface, err) } // Create VLAN sub-interface. vlanLink := &netlink.Vlan{ LinkAttrs: netlink.LinkAttrs{ Name: fmt.Sprintf("%s.%d", v.ParentInterface, v.ID), ParentIndex: parent.Attrs().Index, }, VlanId: v.ID, } if err := netlink.LinkAdd(vlanLink); err != nil { return fmt.Errorf("failed to create VLAN %d: %w", v.ID, err) } if err := netlink.LinkSetUp(vlanLink); err != nil { return fmt.Errorf("failed to bring up VLAN %d: %w", v.ID, err) } // Create bridge for the VLAN. bridge := &netlink.Bridge{ LinkAttrs: netlink.LinkAttrs{Name: bridgeName}, } if err := netlink.LinkAdd(bridge); err != nil { return fmt.Errorf("failed to create bridge %s: %w", bridgeName, err) } if err := netlink.LinkSetUp(bridge); err != nil { return fmt.Errorf("failed to bring up bridge %s: %w", bridgeName, err) } // Add VLAN interface to bridge. if err := netlink.LinkSetMaster(vlanLink, bridge); err != nil { return fmt.Errorf("failed to add VLAN to bridge: %w", err) } m.log.Infow("VLAN created", "id", v.ID, "bridge", bridgeName, "parent", v.ParentInterface) return nil }