Route Manager#

The RouteManager class provides centralized management of the kernel IPv4/IPv6 routing table in Join. It monitors route changes via Linux Netlink, maintains an in-memory cache of all RT_TABLE_MAIN routes, and notifies listeners of any change.

RouteManager is independent of InterfaceManager — the two managers can be used together or separately.


Getting an instance#

RouteManager is a singleton. Use instance() for normal usage. A custom Reactor* can be injected by constructing directly:

#include <join/routemanager.hpp>

using namespace join;

// Normal usage — singleton
RouteManager& manager = RouteManager::instance();

// Custom reactor
Reactor myReactor;
RouteManager manager(&myReactor);

On construction the manager opens a Netlink socket, subscribes to IPv4 and IPv6 route events, registers itself with the reactor, and performs an initial synchronous refresh().

RouteManager is neither copyable nor movable.


Finding routes#

Find by interface index#

IpAddress dest("10.0.0.0");

Route::Ptr route = manager.findByIndex(2, dest, 8);
if (!route)
{
    // Route not found
}

Find by interface name#

Route::Ptr route = manager.findByName("eth0", dest, 8);

Enumerating routes#

All routes#

for (const auto& route : manager.enumerate())
{
    std::cout << route->dest().toString()
              << "/" << route->prefix()
              << " dev " << route->index() << "\n";
}

Routes for a specific interface#

// By interface index
for (const auto& route : manager.enumerate(2))
{
    // ...
}

// By interface name
for (const auto& route : manager.enumerate("eth0"))
{
    // ...
}

Refreshing route data#

refresh() clears the cache and re-dumps the kernel route table synchronously:

if (manager.refresh() == -1)
{
    std::cerr << lastError.message() << "\n";
}

Adding routes#

IpAddress dest("10.0.0.0");
IpAddress gateway("192.168.1.1");
uint32_t  metric = 100;

// By interface index — asynchronous (default)
manager.addRoute(2, dest, 8, gateway, metric);

// By interface index — synchronous
if (manager.addRoute(2, dest, 8, gateway, metric, true) == -1)
{
    std::cerr << lastError.message() << "\n";
}

// By interface name
manager.addRoute("eth0", dest, 8, gateway, metric, true);

// On-link route (no gateway)
manager.addRoute("eth0", IpAddress("192.168.2.0"), 24, {}, 0, true);

Removing routes#

// By interface index
manager.removeRoute(2, dest, 8, true);

// By interface name
manager.removeRoute("eth0", dest, 8, true);

removeRoute() looks up the current gateway in the cache before sending the kernel request, so the route must exist in the cache. Returns -1 with Errc::NotFound if not cached.


Flushing routes#

Removes all non-kernel routes on a given interface:

// By interface index
manager.flushRoutes(2, true);

// By interface name
manager.flushRoutes("eth0", true);

⚠️ Routes with RTPROT_KERNEL are skipped — only user-space routes are removed.


Updating a route#

Use Route::set() to replace a route’s gateway and metric in place:

Route::Ptr route = manager.findByName("eth0", IpAddress("10.0.0.0"), 8);
if (route)
{
    route->set(IpAddress("192.168.1.254"), 200, true);
}

Event notifications#

RouteChangeType flags#

enum class RouteChangeType : uint32_t
{
    Added           = 1 << 0,  // entry did not exist before
    Deleted         = 1 << 1,  // entry was removed
    Modified        = 1 << 2,  // entry was updated
    GatewayChanged  = 1 << 3,
    MetricChanged   = 1 << 4,
    TypeChanged     = 1 << 5,
    ScopeChanged    = 1 << 6,
    ProtocolChanged = 1 << 7,
};

Flags support all bitwise operators (&, |, ^, ~, &=, |=, ^=).

Route listener#

addRouteListener returns a unique uint64_t id used to unregister later:

uint64_t id = manager.addRouteListener([](const RouteInfo& info) {
    if (info.flags & RouteChangeType::Added)
    {
        std::cout << "Route added: "
                  << info.route->dest().toString()
                  << "/" << info.route->prefix();

        if (!info.route->gateway().isWildcard())
            std::cout << " via " << info.route->gateway().toString();

        std::cout << " dev " << info.route->index() << "\n";
    }

    if (info.flags & RouteChangeType::Deleted)
    {
        std::cout << "Route deleted: "
                  << info.route->dest().toString()
                  << "/" << info.route->prefix() << "\n";
    }

    if (info.flags & RouteChangeType::GatewayChanged)
    {
        std::cout << "Gateway changed to "
                  << info.route->gateway().toString() << "\n";
    }
});

// Unregister
manager.removeRouteListener(id);

Reactor integration#

RouteManager registers itself with the reactor on construction and unregisters on destruction. All listener callbacks are invoked from the reactor’s dispatcher thread — keep them short and non-blocking.

With a custom Reactor#

Reactor reactor;
RouteManager manager(&reactor);

ThreadPool pool;
pool.push([&reactor]() {
    Thread::affinity(pthread_self(), 1);
    Thread::priority(pthread_self(), 50);
    reactor.run();
});

// ... application runs ...

reactor.stop();

Information structures#

RouteInfo#

struct RouteInfo
{
    Route::Ptr      route;  // the affected route
    RouteChangeType flags;  // what changed (bitmask)
};

Route cache scope#

Only routes from RT_TABLE_MAIN with family AF_INET or AF_INET6 are tracked. Routes from other tables (local, policy) are silently ignored.


Error handling#

Methods return 0 on success, -1 on failure. Check join::lastError for details:

if (manager.addRoute("eth0", dest, 8, gateway, 0, true) == -1)
{
    std::cerr << "Failed: " << lastError.message() << "\n";
}

Synchronous operations time out after 5 seconds and set Errc::TimedOut.


Best practices#

  • Register listeners before doing anything else to avoid missing early events.
  • Keep callbacks short and non-blocking — they run on the reactor dispatcher thread.
  • Store the returned id from addRouteListener to be able to unregister.
  • Call refresh() after external tools (e.g. ip route) modify the routing table.
  • Use flushRoutes() to clean up all user routes on an interface before removing it.
  • Check return values for all synchronous operations.

Summary#

FeatureSupported
Route enumeration
Route lookup
Add route
Remove route
Update route
Flush interface routes
Route notifications
IPv4 support
IPv6 support
Reactor integration
Custom Reactor
Sync/async operations