InterfaceManager#

The InterfaceManager provides centralized management of network interfaces in Join. It monitors system interfaces, handles creation and deletion, and notifies listeners of network changes through an event-driven architecture.

InterfaceManager is integrated with the Join reactor and automatically tracks all interface, address, and route changes via Linux Netlink.


Creating an instance#

InterfaceManager is a regular class — not a singleton. Construct it directly. By default it uses the global ReactorThread reactor, but a custom Reactor* can be passed:

#include <join/interfacemanager.hpp>

using namespace join;

// default: uses ReactorThread::reactor()
InterfaceManager manager;

// custom reactor
Reactor myReactor;
InterfaceManager manager(&myReactor);

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

InterfaceManager is neither copyable nor movable.


Finding interfaces#

Find by index#

Interface::Ptr eth0 = manager.findByIndex(2);
if (!eth0)
{
    // interface not found
}

Find by name#

Interface::Ptr eth0 = manager.findByName("eth0");
if (!eth0)
{
    // interface not found
}

Enumerate all interfaces#

for (const auto& iface : manager.enumerate())
{
    std::cout << iface->name() << " (" << iface->index() << ")\n";
}

Creating interfaces#

All creation methods default to asynchronous (sync = false). Pass true to block until the kernel confirms the operation.

Dummy interface#

// asynchronous (default)
manager.createDummyInterface("dummy0");

// synchronous
if (manager.createDummyInterface("dummy0", true) == -1)
{
    std::cerr << join::lastError.message() << "\n";
}

Bridge interface#

manager.createBridgeInterface("br0", true);

VLAN interface#

uint16_t vlanId = 100;

// by parent index
manager.createVlanInterface("eth0.100", 2, vlanId, ETH_P_8021Q, true);

// by parent name
manager.createVlanInterface("eth0.100", "eth0", vlanId, ETH_P_8021Q, true);

⚠️ VLAN IDs must be in the range 1–4094. ID 0 is reserved and will return -1.

Virtual Ethernet pair (veth)#

// both ends in current namespace
manager.createVethInterface("veth0", "veth1", nullptr, true);

// peer moved to a different namespace
pid_t containerPid = 12345;
manager.createVethInterface("veth0", "veth1", &containerPid, true);

GRE tunnel#

IpAddress local("192.168.1.1");
IpAddress remote("192.168.2.1");
uint32_t ikey = 1000;
uint32_t okey = 2000;

// by parent index, with keys
manager.createGreInterface("gre0", 2, local, remote, &ikey, &okey, 64, true);

// by parent name, without keys
manager.createGreInterface("gre0", "eth0", local, remote, nullptr, nullptr, 64, true);

⚠️ localAddress and remoteAddress must be the same IP family (both IPv4 or both IPv6).


Deleting interfaces#

// by index
manager.removeInterface(10, true);

// by name
manager.removeInterface("dummy0", true);

Refreshing interface data#

refresh() dumps link, address, and route data from the kernel. The default is synchronous:

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

// asynchronous
manager.refresh(false);

Event notifications#

ChangeType flags#

enum ChangeType
{
    Added               = 1 << 0,
    Deleted             = 1 << 1,
    Modified            = 1 << 2,
    AdminStateChanged   = 1 << 3,   // IFF_UP changed
    OperStateChanged    = 1 << 4,   // IFF_RUNNING changed
    MacChanged          = 1 << 5,
    NameChanged         = 1 << 6,
    MtuChanged          = 1 << 7,
    KindChanged         = 1 << 8,
    MasterChanged       = 1 << 9,   // bridge membership
};

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

manager.addLinkListener([&manager](const LinkInfo& info) {
    Interface::Ptr iface = manager.findByIndex(info.index);

    if (info.flags & ChangeType::Added)
    {
        std::cout << "Interface added: " << iface->name() << "\n";
    }

    if (info.flags & ChangeType::Deleted)
    {
        std::cout << "Interface deleted: index " << info.index << "\n";
    }

    if (info.flags & ChangeType::AdminStateChanged)
    {
        std::cout << iface->name()
                  << (iface->isEnabled() ? " up" : " down") << "\n";
    }
});

⚠️ removeLinkListener matches callbacks by function pointer identity. It works with named functions and stored std::function objects bound to the same target, but not with distinct lambda instances even if they have identical bodies.

Address notifications#

AddressInfo::address is a tuple<IpAddress, uint32_t, IpAddress>:

manager.addAddressListener([&manager](const AddressInfo& info) {
    Interface::Ptr iface = manager.findByIndex(info.index);

    const IpAddress& ip        = std::get<0>(info.address);  // IP address
    uint32_t         prefix    = std::get<1>(info.address);  // prefix length
    const IpAddress& broadcast = std::get<2>(info.address);  // broadcast (IPv4)

    if (info.flags & ChangeType::Added)
    {
        std::cout << "Address added: " << ip.toString()
                  << "/" << prefix << " on " << iface->name() << "\n";
    }

    if (info.flags & ChangeType::Deleted)
    {
        std::cout << "Address removed: " << ip.toString() << "/" << prefix << "\n";
    }
});

Route notifications#

RouteInfo::route is a tuple<IpAddress, uint32_t, IpAddress, uint32_t>:

manager.addRouteListener([&manager](const RouteInfo& info) {
    Interface::Ptr iface = manager.findByIndex(info.index);

    const IpAddress& dest    = std::get<0>(info.route);  // destination network
    uint32_t         prefix  = std::get<1>(info.route);  // prefix length
    const IpAddress& gateway = std::get<2>(info.route);  // gateway address
    uint32_t         metric  = std::get<3>(info.route);  // route metric

    if (info.flags & ChangeType::Added)
    {
        std::cout << "Route added: " << dest.toString() << "/" << prefix;
        if (!gateway.isWildcard())
            std::cout << " via " << gateway.toString();
        std::cout << " on " << iface->name() << "\n";
    }

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

Reactor integration#

InterfaceManager inherits from Netlink::Socket which inherits from EventHandler. It registers itself with the reactor in its constructor and unregisters in its destructor. All listener callbacks are invoked from the reactor’s dispatcher thread — keep them short and non-blocking.

With a custom Reactor and ThreadPool#

Reactor reactor;
InterfaceManager 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#

LinkInfo#

struct LinkInfo
{
    uint32_t   index;   // interface index
    ChangeType flags;   // what changed (bitmask)
};

AddressInfo#

struct AddressInfo : public LinkInfo
{
    Interface::Address address;  // tuple<IpAddress, uint32_t, IpAddress>
                                 //        ip         prefix   broadcast
};

RouteInfo#

struct RouteInfo : public LinkInfo
{
    Interface::Route route;  // tuple<IpAddress, uint32_t, IpAddress, uint32_t>
                             //        dest       prefix   gateway    metric
};

Error handling#

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

if (manager.createDummyInterface("dummy0", true) == -1)
{
    std::cerr << "Failed: " << join::lastError.message() << "\n";
}

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


Best practices#

  • Use synchronous mode when you need confirmation before proceeding
  • Use asynchronous mode for fire-and-forget batch operations
  • Register listeners before doing anything else to avoid missing early events
  • Keep callbacks short and non-blocking — they run on the reactor dispatcher thread
  • Call refresh() after external tools (e.g. ip, ifconfig) modify interfaces
  • Check return values for all synchronous operations
  • Store named callbacks if you need to call removeLinkListener — lambdas cannot be matched by identity

Summary#

FeatureSupported
Interface enumeration
Interface lookup
Dummy interfaces
Bridge interfaces
VLAN interfaces
Veth pairs
GRE tunnels
Interface deletion
Link notifications
Address notifications
Route notifications
Reactor integration
Custom Reactor
Sync/async operations