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:

// libjoin.
#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✅