cleanup route handling for local backend

This commit is contained in:
Christoph 2025-10-11 17:56:22 +02:00
parent c00b34ac31
commit e8965bb65e
4 changed files with 97 additions and 37 deletions

View File

@ -658,7 +658,12 @@ func (c LocalController) SetRoutes(
if err != nil { if err != nil {
return fmt.Errorf("failed to get or create routing table and fwmark for %s: %w", interfaceId, err) return fmt.Errorf("failed to get or create routing table and fwmark for %s: %w", interfaceId, err)
} }
if realFwMark != fwMark { wgDev, err := c.wg.Device(string(interfaceId))
if err != nil {
return fmt.Errorf("failed to get wg device for %s: %w", interfaceId, err)
}
currentFwMark := wgDev.FirewallMark
if int(realFwMark) != currentFwMark {
slog.Debug("updating fwmark for interface", "interface", interfaceId, "oldFwMark", fwMark, slog.Debug("updating fwmark for interface", "interface", interfaceId, "oldFwMark", fwMark,
"newFwMark", realFwMark, "oldTable", table, "newTable", realTable) "newFwMark", realFwMark, "oldTable", table, "newTable", realTable)
if err := c.updateFwMarkOnInterface(interfaceId, int(realFwMark)); err != nil { if err := c.updateFwMarkOnInterface(interfaceId, int(realFwMark)); err != nil {
@ -878,9 +883,26 @@ func (c LocalController) RemoveRoutes(
) error { ) error {
slog.Debug("removing linux routes", "interface", interfaceId, "table", table, "fwMark", fwMark, "cidrs", oldCidrs) slog.Debug("removing linux routes", "interface", interfaceId, "table", table, "fwMark", fwMark, "cidrs", oldCidrs)
wgDev, err := c.wg.Device(string(interfaceId))
if err != nil {
slog.Debug("wg device already removed, route cleanup might be incomplete", "interface", interfaceId)
wgDev = nil
}
link, err := c.nl.LinkByName(string(interfaceId)) link, err := c.nl.LinkByName(string(interfaceId))
if err != nil { if err != nil {
return fmt.Errorf("failed to find physical link for %s: %w", interfaceId, err) slog.Debug("physical link already removed, route cleanup might be incomplete", "interface", interfaceId)
link = nil
}
if wgDev != nil && fwMark == 0 {
fwMark = uint32(wgDev.FirewallMark)
}
if wgDev != nil && table == 0 {
table = wgDev.FirewallMark // use the fwMark as table, this is the default behavior
}
linkIndex := -1
if link != nil {
linkIndex = link.Attrs().Index
} }
cidrsV4, cidrsV6 := domain.CidrsPerFamily(oldCidrs) cidrsV4, cidrsV6 := domain.CidrsPerFamily(oldCidrs)
@ -889,6 +911,7 @@ func (c LocalController) RemoveRoutes(
return fmt.Errorf("failed to get or create routing table and fwmark for %s: %w", interfaceId, err) return fmt.Errorf("failed to get or create routing table and fwmark for %s: %w", interfaceId, err)
} }
if linkIndex > 0 {
err = c.removeRoutesForFamily(interfaceId, link, netlink.FAMILY_V4, realTable, realFwMark, cidrsV4) err = c.removeRoutesForFamily(interfaceId, link, netlink.FAMILY_V4, realTable, realFwMark, cidrsV4)
if err != nil { if err != nil {
return fmt.Errorf("failed to remove v4 routes: %w", err) return fmt.Errorf("failed to remove v4 routes: %w", err)
@ -897,6 +920,18 @@ func (c LocalController) RemoveRoutes(
if err != nil { if err != nil {
return fmt.Errorf("failed to remove v6 routes: %w", err) return fmt.Errorf("failed to remove v6 routes: %w", err)
} }
}
if table > 0 {
err = c.removeRouteRulesForTable(netlink.FAMILY_V4, realTable)
if err != nil {
return fmt.Errorf("failed to remove v4 route rules for %s: %w", interfaceId, err)
}
err = c.removeRouteRulesForTable(netlink.FAMILY_V6, realTable)
if err != nil {
return fmt.Errorf("failed to remove v6 route rules for %s: %w", interfaceId, err)
}
}
return nil return nil
} }
@ -958,6 +993,25 @@ func (c LocalController) removeRoutesForFamily(
return nil return nil
} }
func (c LocalController) removeRouteRulesForTable(
family int,
table int,
) error {
existingRules, err := c.nl.RuleList(family)
if err != nil {
return fmt.Errorf("failed to get existing route rules for family-id %d: %w", family, err)
}
for _, existingRule := range existingRules {
if existingRule.Table == table {
err := c.nl.RuleDel(&existingRule)
if err != nil {
return fmt.Errorf("failed to delete old rule for table %d and family-id %d: %w", table, family, err)
}
}
}
return nil
}
// endregion routing-related // endregion routing-related
// region statistics-related // region statistics-related

View File

@ -462,12 +462,17 @@ func (m Manager) DeleteInterface(ctx context.Context, id domain.InterfaceIdentif
return fmt.Errorf("deletion not allowed: %w", err) return fmt.Errorf("deletion not allowed: %w", err)
} }
m.bus.Publish(app.TopicRouteRemove, domain.RoutingTableInfo{
Interface: *existingInterface,
AllowedIps: existingInterface.GetAllowedIPs(existingPeers),
FwMark: existingInterface.FirewallMark,
Table: existingInterface.GetRoutingTable(),
})
now := time.Now() now := time.Now()
existingInterface.Disabled = &now // simulate a disabled interface existingInterface.Disabled = &now // simulate a disabled interface
existingInterface.DisabledReason = domain.DisabledReasonDeleted existingInterface.DisabledReason = domain.DisabledReasonDeleted
physicalInterface, _ := m.wg.GetController(*existingInterface).GetInterface(ctx, id)
if err := m.handleInterfacePreSaveHooks(ctx, existingInterface, !existingInterface.IsDisabled(), if err := m.handleInterfacePreSaveHooks(ctx, existingInterface, !existingInterface.IsDisabled(),
false); err != nil { false); err != nil {
return fmt.Errorf("pre-delete hooks failed: %w", err) return fmt.Errorf("pre-delete hooks failed: %w", err)
@ -489,17 +494,6 @@ func (m Manager) DeleteInterface(ctx context.Context, id domain.InterfaceIdentif
return fmt.Errorf("deletion failure: %w", err) return fmt.Errorf("deletion failure: %w", err)
} }
fwMark := existingInterface.FirewallMark
if physicalInterface != nil && fwMark == 0 {
fwMark = physicalInterface.FirewallMark
}
m.bus.Publish(app.TopicRouteRemove, domain.RoutingTableInfo{
Interface: *existingInterface,
AllowedIps: existingInterface.GetAllowedIPs(existingPeers),
FwMark: fwMark,
Table: existingInterface.GetRoutingTable(),
})
if err := m.handleInterfacePostSaveHooks( if err := m.handleInterfacePostSaveHooks(
ctx, ctx,
existingInterface, existingInterface,
@ -577,15 +571,10 @@ func (m Manager) saveInterface(ctx context.Context, iface *domain.Interface) (
} }
if iface.IsDisabled() { if iface.IsDisabled() {
physicalInterface, _ := m.wg.GetController(*iface).GetInterface(ctx, iface.Identifier)
fwMark := iface.FirewallMark
if physicalInterface != nil && fwMark == 0 {
fwMark = physicalInterface.FirewallMark
}
m.bus.Publish(app.TopicRouteRemove, domain.RoutingTableInfo{ m.bus.Publish(app.TopicRouteRemove, domain.RoutingTableInfo{
Interface: *iface, Interface: *iface,
AllowedIps: iface.GetAllowedIPs(peers), AllowedIps: iface.GetAllowedIPs(peers),
FwMark: fwMark, FwMark: iface.FirewallMark,
Table: iface.GetRoutingTable(), Table: iface.GetRoutingTable(),
}) })
} else { } else {

View File

@ -449,7 +449,6 @@ func (m Manager) GetUserPeerStats(ctx context.Context, id domain.UserIdentifier)
func (m Manager) savePeers(ctx context.Context, peers ...*domain.Peer) error { func (m Manager) savePeers(ctx context.Context, peers ...*domain.Peer) error {
interfaces := make(map[domain.InterfaceIdentifier]domain.Interface) interfaces := make(map[domain.InterfaceIdentifier]domain.Interface)
interfacePeers := make(map[domain.InterfaceIdentifier][]domain.Peer)
for _, peer := range peers { for _, peer := range peers {
// get interface from db if it is not yet in the map // get interface from db if it is not yet in the map
@ -462,7 +461,6 @@ func (m Manager) savePeers(ctx context.Context, peers ...*domain.Peer) error {
} }
iface := interfaces[peer.InterfaceIdentifier] iface := interfaces[peer.InterfaceIdentifier]
interfacePeers[iface.Identifier] = append(interfacePeers[iface.Identifier], *peer)
// Always save the peer to the backend, regardless of disabled/expired state // Always save the peer to the backend, regardless of disabled/expired state
// The backend will handle the disabled state appropriately // The backend will handle the disabled state appropriately
@ -497,9 +495,14 @@ func (m Manager) savePeers(ctx context.Context, peers ...*domain.Peer) error {
// Update routes after peers have changed // Update routes after peers have changed
for id, iface := range interfaces { for id, iface := range interfaces {
interfacePeers, err := m.db.GetInterfacePeers(ctx, id)
if err != nil {
return fmt.Errorf("failed to re-load peers for interface %s: %w", id, err)
}
m.bus.Publish(app.TopicRouteUpdate, domain.RoutingTableInfo{ m.bus.Publish(app.TopicRouteUpdate, domain.RoutingTableInfo{
Interface: iface, Interface: iface,
AllowedIps: iface.GetAllowedIPs(interfacePeers[id]), AllowedIps: iface.GetAllowedIPs(interfacePeers),
FwMark: iface.FirewallMark, FwMark: iface.FirewallMark,
Table: iface.GetRoutingTable(), Table: iface.GetRoutingTable(),
}) })

View File

@ -132,9 +132,14 @@ func (i *Interface) GetConfigFileName() string {
return filename return filename
} }
// GetAllowedIPs returns the allowed IPs for the interface depending on the interface type and peers.
// For example, if the interface type is Server, the allowed IPs are the IPs of the peers.
// If the interface type is Client, the allowed IPs correspond to the AllowedIPsStr of the peers.
func (i *Interface) GetAllowedIPs(peers []Peer) []Cidr { func (i *Interface) GetAllowedIPs(peers []Peer) []Cidr {
var allowedCidrs []Cidr var allowedCidrs []Cidr
switch i.Type {
case InterfaceTypeServer, InterfaceTypeAny:
for _, peer := range peers { for _, peer := range peers {
for _, ip := range peer.Interface.Addresses { for _, ip := range peer.Interface.Addresses {
allowedCidrs = append(allowedCidrs, ip.HostAddr()) allowedCidrs = append(allowedCidrs, ip.HostAddr())
@ -146,6 +151,14 @@ func (i *Interface) GetAllowedIPs(peers []Peer) []Cidr {
} }
} }
} }
case InterfaceTypeClient:
for _, peer := range peers {
allowedIPs, err := CidrsFromString(peer.AllowedIPsStr.GetValue())
if err == nil {
allowedCidrs = append(allowedCidrs, allowedIPs...)
}
}
}
return allowedCidrs return allowedCidrs
} }
@ -315,7 +328,8 @@ type RoutingTableInfo struct {
} }
func (r RoutingTableInfo) String() string { func (r RoutingTableInfo) String() string {
return fmt.Sprintf("%s: %d -> %d", r.Interface.Identifier, r.FwMark, r.Table) v4, v6 := CidrsPerFamily(r.AllowedIps)
return fmt.Sprintf("%s: fwmark=%d; table=%d; routes_4=%d; routes_6=%d", r.Interface.Identifier, r.FwMark, r.Table, len(v4), len(v6))
} }
func (r RoutingTableInfo) ManagementEnabled() bool { func (r RoutingTableInfo) ManagementEnabled() bool {