implement route handling for mikrotik controller

This commit is contained in:
Christoph Haas
2025-10-12 14:01:21 +02:00
parent 04679dba52
commit aef6719817
9 changed files with 435 additions and 61 deletions

View File

@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"log/slog"
"sync"
"github.com/h44z/wg-portal/internal/app"
"github.com/h44z/wg-portal/internal/config"
@@ -29,21 +30,9 @@ type EventBus interface {
type RoutesController interface {
// SetRoutes sets the routes for the given interface. If no routes are provided, the function is a no-op.
SetRoutes(
ctx context.Context,
interfaceId domain.InterfaceIdentifier,
table int,
fwMark uint32,
cidrs []domain.Cidr,
) error
SetRoutes(ctx context.Context, info domain.RoutingTableInfo) error
// RemoveRoutes removes the routes for the given interface. If no routes are provided, the function is a no-op.
RemoveRoutes(
ctx context.Context,
interfaceId domain.InterfaceIdentifier,
table int,
fwMark uint32,
oldCidrs []domain.Cidr,
) error
RemoveRoutes(ctx context.Context, info domain.RoutingTableInfo) error
}
// endregion dependencies
@@ -64,6 +53,8 @@ type Manager struct {
bus EventBus
db InterfaceAndPeerDatabaseRepo
wgController ControllerManager
mux *sync.Mutex
}
// NewRouteManager creates a new route manager instance.
@@ -79,6 +70,7 @@ func NewRouteManager(
db: db,
wgController: wgController,
mux: &sync.Mutex{},
}
m.connectToMessageBus()
@@ -98,6 +90,9 @@ func (m Manager) StartBackgroundJobs(_ context.Context) {
}
func (m Manager) handleRouteUpdateEvent(info domain.RoutingTableInfo) {
m.mux.Lock() // ensure that only one route update is processed at a time
defer m.mux.Unlock()
slog.Debug("handling route update event", "info", info.String())
if !info.ManagementEnabled() {
@@ -115,6 +110,9 @@ func (m Manager) handleRouteUpdateEvent(info domain.RoutingTableInfo) {
}
func (m Manager) handleRouteRemoveEvent(info domain.RoutingTableInfo) {
m.mux.Lock() // ensure that only one route update is processed at a time
defer m.mux.Unlock()
slog.Debug("handling route remove event", "info", info.String())
if !info.ManagementEnabled() {
@@ -144,7 +142,7 @@ func (m Manager) syncRoutes(ctx context.Context, info domain.RoutingTableInfo) e
return nil
}
err := rc.SetRoutes(ctx, info.Interface.Identifier, info.Table, info.FwMark, info.AllowedIps)
err := rc.SetRoutes(ctx, info)
if err != nil {
return fmt.Errorf("failed to set routes for interface %s: %w", info.Interface.Identifier, err)
}
@@ -164,7 +162,7 @@ func (m Manager) removeRoutes(ctx context.Context, info domain.RoutingTableInfo)
return nil
}
err := rc.RemoveRoutes(ctx, info.Interface.Identifier, info.Table, info.FwMark, info.AllowedIps)
err := rc.RemoveRoutes(ctx, info)
if err != nil {
return fmt.Errorf("failed to remove routes for interface %s: %w", info.Interface.Identifier, err)
}

View File

@@ -467,6 +467,8 @@ func (m Manager) DeleteInterface(ctx context.Context, id domain.InterfaceIdentif
AllowedIps: existingInterface.GetAllowedIPs(existingPeers),
FwMark: existingInterface.FirewallMark,
Table: existingInterface.GetRoutingTable(),
TableStr: existingInterface.RoutingTable,
IsDeleted: true,
})
now := time.Now()
@@ -518,7 +520,11 @@ func (m Manager) saveInterface(ctx context.Context, iface *domain.Interface) (
return nil, fmt.Errorf("interface validation failed: %w", err)
}
oldEnabled, newEnabled := m.getInterfaceStateHistory(ctx, iface)
oldEnabled, newEnabled, routeTableChanged := false, !iface.IsDisabled(), false // if the interface did not exist, we assume it was not enabled
oldInterface, err := m.db.GetInterface(ctx, iface.Identifier)
if err == nil {
oldEnabled, newEnabled, routeTableChanged = m.getInterfaceStateHistory(oldInterface, iface)
}
if err := m.handleInterfacePreSaveHooks(ctx, iface, oldEnabled, newEnabled); err != nil {
return nil, fmt.Errorf("pre-save hooks failed: %w", err)
@@ -528,7 +534,7 @@ func (m Manager) saveInterface(ctx context.Context, iface *domain.Interface) (
return nil, fmt.Errorf("pre-save actions failed: %w", err)
}
err := m.db.SaveInterface(ctx, iface.Identifier, func(i *domain.Interface) (*domain.Interface, error) {
err = m.db.SaveInterface(ctx, iface.Identifier, func(i *domain.Interface) (*domain.Interface, error) {
iface.CopyCalculatedAttributes(i)
err := m.wg.GetController(*iface).SaveInterface(ctx, iface.Identifier,
@@ -576,6 +582,7 @@ func (m Manager) saveInterface(ctx context.Context, iface *domain.Interface) (
AllowedIps: iface.GetAllowedIPs(peers),
FwMark: iface.FirewallMark,
Table: iface.GetRoutingTable(),
TableStr: iface.RoutingTable,
})
} else {
m.bus.Publish(app.TopicRouteUpdate, domain.RoutingTableInfo{
@@ -583,7 +590,19 @@ func (m Manager) saveInterface(ctx context.Context, iface *domain.Interface) (
AllowedIps: iface.GetAllowedIPs(peers),
FwMark: iface.FirewallMark,
Table: iface.GetRoutingTable(),
TableStr: iface.RoutingTable,
})
// if the route table changed, ensure that the old entries are remove
if routeTableChanged {
m.bus.Publish(app.TopicRouteRemove, domain.RoutingTableInfo{
Interface: *oldInterface,
AllowedIps: oldInterface.GetAllowedIPs(peers),
FwMark: oldInterface.FirewallMark,
Table: oldInterface.GetRoutingTable(),
TableStr: oldInterface.RoutingTable,
IsDeleted: true, // mark the old entries as deleted
})
}
}
if err := m.handleInterfacePostSaveHooks(ctx, iface, oldEnabled, newEnabled); err != nil {
@@ -622,13 +641,11 @@ func (m Manager) saveInterface(ctx context.Context, iface *domain.Interface) (
return iface, nil
}
func (m Manager) getInterfaceStateHistory(ctx context.Context, iface *domain.Interface) (oldEnabled, newEnabled bool) {
oldInterface, err := m.db.GetInterface(ctx, iface.Identifier)
if err != nil {
return false, !iface.IsDisabled() // if the interface did not exist, we assume it was not enabled
}
return !oldInterface.IsDisabled(), !iface.IsDisabled()
func (m Manager) getInterfaceStateHistory(
oldInterface *domain.Interface,
iface *domain.Interface,
) (oldEnabled, newEnabled, routeTableChanged bool) {
return !oldInterface.IsDisabled(), !iface.IsDisabled(), oldInterface.RoutingTable != iface.RoutingTable
}
func (m Manager) handleInterfacePreSaveActions(ctx context.Context, iface *domain.Interface) error {

View File

@@ -400,6 +400,7 @@ func (m Manager) DeletePeer(ctx context.Context, id domain.PeerIdentifier) error
AllowedIps: iface.GetAllowedIPs(peers),
FwMark: iface.FirewallMark,
Table: iface.GetRoutingTable(),
TableStr: iface.RoutingTable,
})
// Update interface after peers have changed
m.bus.Publish(app.TopicPeerInterfaceUpdated, peer.InterfaceIdentifier)
@@ -505,6 +506,7 @@ func (m Manager) savePeers(ctx context.Context, peers ...*domain.Peer) error {
AllowedIps: iface.GetAllowedIPs(interfacePeers),
FwMark: iface.FirewallMark,
Table: iface.GetRoutingTable(),
TableStr: iface.RoutingTable,
})
}