Difference between revisions of "Libnetstack"

From dankwiki
 
(One intermediate revision by the same user not shown)
Line 3: Line 3:
 
Code lives at https://github.com/dankamongmen/libnetstack.
 
Code lives at https://github.com/dankamongmen/libnetstack.
  
{{#github:README.md|dankamongmen/libnetstack}}.
+
==rtnetlink==
 +
<tt>rtnetlink(7)</tt> (originally implemented AFAIK by Alexey Kuznetsov, the Mad Russian, whom I haven't seen post to [[LKML]] in many years, and miss) provides the <tt>NETLINK_ROUTE</tt> protocol for the <tt>AF_NETLINK</tt> family of sockets. According to <tt>netlink(7)</tt>,<blockquote>Netlink  is  a datagram-oriented service. Both <tt>SOCK_RAW</tt> and <tt>SOCK_DGRAM</tt> are valid values for socket_type. However, the netlink  protocol  does not distinguish between datagram and raw sockets."—<tt>netlink(7)</tt>, Linux man pages 5.03</blockquote> Creating and using such a socket does not require any special permissions, though <tt>CAP_NET_ADMIN</tt> is needed for many control messages (verified kernel-side, of course). Once established,
 +
We can directly request dumps of networking stack state with the <tt>RTM_GET*</tt> set of messages, and/or simply subscribe to the appropriate multicast groups, sit back, and let new events roll to us.
 +
 
 +
{|class="wikitable"
 +
! Group !! Messages
 +
|-
 +
| <tt>RTNLGRP_LINK</tt> || <tt>RTM_NEWLINK</tt>, <tt>RTM_DELLINK</tt>
 +
|-
 +
| <tt>RTNLGRP_IPV4_IFADDR</tt> || <tt>RTM_NEWADDR</tt>, <tt>RTM_DELADDR</tt> (AF_INET only)
 +
|-
 +
| <tt>RTNLGRP_IPV6_IFADDR</tt> || <tt>RTM_NEWADDR</tt>, <tt>RTM_DELADDR</tt> (AF_INET6 only)
 +
|-
 +
| <tt>RTNLGRP_IPV4_ROUTE</tt> || <tt>RTM_NEWROUTE</tt>, <tt>RTM_DELROUTE</tt> (AF_INET only)
 +
|-
 +
| <tt>RTNLGRP_IPV6_ROUTE</tt> || <tt>RTM_NEWROUTE</tt>, <tt>RTM_DELROUTE</tt> (AF_INET6 only)
 +
|-
 +
| <tt>RTNLGRP_NEIGH</tt> || <tt>RTM_NEWNEIGH</tt>, <tt>RTM_DELNEIGH</tt>
 +
|-
 +
|}
 +
 
 +
{{#github:README.md|dankamongmen/libnetstack}}
 +
 
 +
==See also==
 +
* "[https://inai.de/documents/Netlink_Protocol.pdf The Netlink protocol: Mysteries Uncovered]", Jan Engelhardt 2010-10-30
  
 
[[CATEGORY: Projects]]
 
[[CATEGORY: Projects]]

Latest revision as of 18:24, 4 November 2019

AF_NETLINK sockets allow one to enumerate networking stack elements, and subscribe to events regarding changes, additions, and deletions thereof. Netlink is kind of a pain in the ass to work with directly, though. My libnetlink enumerates all existing networking stack elements, subscribes to events, and makes all of this available to the user via queries and/or realtime callbacks. libnetstack has been designed to provide responsive service even in the presence of millions of routes with rapid churning of the route tables.

Code lives at https://github.com/dankamongmen/libnetstack.

rtnetlink

rtnetlink(7) (originally implemented AFAIK by Alexey Kuznetsov, the Mad Russian, whom I haven't seen post to LKML in many years, and miss) provides the NETLINK_ROUTE protocol for the AF_NETLINK family of sockets. According to netlink(7),

Netlink is a datagram-oriented service. Both SOCK_RAW and SOCK_DGRAM are valid values for socket_type. However, the netlink protocol does not distinguish between datagram and raw sockets."—netlink(7), Linux man pages 5.03

Creating and using such a socket does not require any special permissions, though CAP_NET_ADMIN is needed for many control messages (verified kernel-side, of course). Once established,

We can directly request dumps of networking stack state with the RTM_GET* set of messages, and/or simply subscribe to the appropriate multicast groups, sit back, and let new events roll to us.

Group Messages
RTNLGRP_LINK RTM_NEWLINK, RTM_DELLINK
RTNLGRP_IPV4_IFADDR RTM_NEWADDR, RTM_DELADDR (AF_INET only)
RTNLGRP_IPV6_IFADDR RTM_NEWADDR, RTM_DELADDR (AF_INET6 only)
RTNLGRP_IPV4_ROUTE RTM_NEWROUTE, RTM_DELROUTE (AF_INET only)
RTNLGRP_IPV6_ROUTE RTM_NEWROUTE, RTM_DELROUTE (AF_INET6 only)
RTNLGRP_NEIGH RTM_NEWNEIGH, RTM_DELNEIGH

libnetstack

A small C library for tracking and querying the local networking stack

by Nick Black dankamongmen@gmail.com

Build Status

libnetstack.jpg

libnetstack allows netstack objects to be created, queried, and destroyed. When created, a netstack discovers all networking elements in its network namespace (interfaces, routes, addresses, neighbors, etc.—see CLONE_NET), and registers for netlink messages announcing any changes. These changes can be passed back to the user via callbacks. It furthermore keeps a cache of these elements up-to-date based on netlink, and the user can query this cache at any time. Design goals included minimal footprints for both memory and compute, while supporting fast lookups in the presence of millions of routes.

Why not just use libnl-route?

Feel free to use libnl-route. I wasn't enamored of some of its API decisions. Both libraries are solving the same general problem, both only support Linux, and both use libnl. I intend to support FreeBSD in the future.

I believe libnetstack to be more performant on the very complex networking stacks present in certain environments, and to better serve heavily parallel access. The typical user is unlikely to see a meaningful performance difference.

Libnetstack is Apache-licensed, whereas libnl-route is LGPL.

Why not just use ioctl()s, as Stevens taught us?

UNIX Network Programming's third and most recent edition was 2003. Much has happened since then. The various ioctl() mechanisms require polling, and are incomplete compared to rtnetlink(7).

Why not just use netlink(3) directly?

It's a tremendous pain in the ass, I assure you.

Getting libnetstack

Packages

libnetstack is present in the AUR.

Debian Unstable packages are available from DSSCAW.

Requirements

  • Core library: CMake, a C11 compiler, and libnl 3.4.0+
  • Tests: a C++14 compiler and GoogleTest 1.9.0+

Building

mkdir build && cd build && cmake .. && make && make test && sudo make install

You know the drill.

Use

A struct netstack must first be created using netstack_create(). This accepts a netstack_opts structure for configuration, including specification of callbacks. NULL is returned on failure. A program may have an many netstacks as it likes, though I don't personally see much point in more than one in a process. This does not require any special privileges.

c struct netstack* netstack_create(const netstack_opts* opts); int netstack_destroy(struct netstack* ns);

Once a netstack is no longer needed, call netstack_destroy() to release its resources and perform consistency checks. On failure, non-zero is returned, but this can usually be ignored by the caller.

The caller now interacts with the library in two ways: its registered callbacks will be invoked for each event processed, and it can at any time access the libnetstack cache. Multiple threads might invoke callbacks at once (though this does not happen in the current implementation, it might in the future). Ordering between different objects is not necessarily preserved, but events for the same object ("same" meaning "same lookup key", see below) are serialized.

Initial enumeration events

By default, upon creation of a netstack all objects will be enumerated, resulting in a slew of events. This behavior can be changed with the initial_events field in network_opts:

  • NETSTACK_INITIAL_EVENTS_ASYNC: The default. Upon creation, objects will be enumerated, but netstack_create() will return after sending the necessary requests. Events might arrive before or after netstack_create() returns.
  • NETSTACK_INITIAL_EVENTS_BLOCK: Don't return from netstack_create() until all objects have been enumerated. If used, the cache may be safely interrogated once netstack_create() returns. Otherwise, existing objects might not show up for a short time.
  • NETSTACK_INITIAL_EVENTS_NONE: Don't perform the initial enumeration.

Object types

Four object types are currently supported:

  • ifaces, corresponding to network devices both physical and virtual. There is a one-to-one correspondence to elements in sysfs's /class/net node, and also to the outputs of ip link list.

The remaining objects are all associated with a single iface, but multiple ifaces might each lay claim to overlapping objects. For instance, it is possible (though usually pathological) to have the same address on two different interfaces. This will result in two address objects, each reachable through a different iface.

  • addresses, corresponding to local layer 3 addresses. An address might have a corresponding broadcast address.
  • neighbors, corresponding to l3 addresses thought to be reachable via direct transmission. A neighbor might have a corresponding link address. In IPv4, these objects are largely a function of ARP. In IPv6, they primarily result from NDP.
  • routes, corresponding to a destination l3 network. routes specify an associated source address. This source address will typically correspond to a known local address, but this cannot be assumed (to construct an example from the command line, add an IP to an interface, add a route specifying that source IP, and remove the address).

In general, objects correspond to rtnetlink(7) message type families. Multicast support is planned.

Options

Usually, the caller will want to at least configure some callbacks using the netstack_opts structure passed to netstack_create(). A callback and a curry may be configured for each different kind of object. If the callback is NULL, the curry must also be NULL.

```c typedef enum { NETSTACKMOD, // a non-destructive event about an object NETSTACKDEL, // an object that is going away } netstackevente;

// Callback types for various events. Even though routes, addresses etc. can be // reached through a netstackiface, they each get their own type of callback. typedef void (*netstackifacecb)(const struct netstackiface*, netstackevente, void); typedef void (netstackaddrcb)(const struct netstackaddr*, netstackevente, void*); typedef void (*netstackroutecb)(const struct netstackroute*, netstackevente, void); typedef void (netstackneighcb)(const struct netstackneigh*, netstackevent_e, void*);

// Policy for initial object dump. ASYNC will cause events for existing // objects, but netstackcreate() may return before they've been received. // BLOCK blocks netstackcreate() from returning until all initial enumeration // events have been received. NONE inhibits initial enumeration. typedef enum { NETSTACKINITIALEVENTSASYNC, NETSTACKINITIALEVENTSBLOCK, NETSTACKINITIALEVENTSNONE, } netstackinitiale;

// The default for all members is false or the appropriate zero representation. // It is invalid to supply a non-NULL curry together with a NULL callback for // any type. It is invalid to supply no callbacks together with all notracks. typedef struct netstackopts { // a given curry may be non-NULL only if the corresponding cb is also NULL. netstackifacecb ifacecb; void* ifacecurry; netstackaddrcb addrcb; void* addrcurry; netstackroutecb routecb; void* routecurry; netstackneighcb neighcb; void* neighcurry; // If set, do not cache the corresponding type of object bool ifacenotrack, addrnotrack, routenotrack, neighnotrack; netstackinitiale initialevents; // policy for initial object enumeration } netstack_opts; ```

Accessing cached objects

Since events can arrive at any time, invalidating the object cache, it is necessary that the caller either:

  • increment a reference counter, yielding a pointer to an immutable object which must be referenced down, or
  • deep-copy objects out upon access, yielding a mutable object which must be destroyed when no longer needed.

Both mechanisms are supported. Each mechanism takes place while locking at least part of the netstack internals, possibly blocking other threads (including those of the netstack itself, potentially causing kernel events to be dropped). Once the object is obtained, see "Querying objects" below for the API to access it.

It's generally recommended to use the reference-counter approach, aka "sharing". While the object is held, it cannot be destroyed by the netstack, but it might be replaced. It is thus possible for multiple objects in this situation to share the same key, something that never happens in the real world (or in the netstack's cache). Failing to down the reference counter is effectively a memory leak.

c // Take a reference on some netstack iface for read-only use in the client. // There is no copy, but the object still needs to be freed by a call to // netstack_iface_abandon(). const struct netstack_iface* netstack_iface_share_byname(struct netstack* ns, const char* name); const struct netstack_iface* netstack_iface_share_byidx(struct netstack* ns, int idx);

The second mechanism, a deep copy, is only rarely useful. It leaves no residue in the netstack, and can only explicitly be shared with other threads. This could be important for certain control flows and memory architectures.

c // Copy out a netstack iface for arbitrary use in the client. This is a // heavyweight copy, and must be freed using netstack_iface_destroy(). You // would usually be better served by netstack_iface_share_*(). struct netstack_iface* netstack_iface_copy_byname(struct netstack* ns, const char* name); struct netstack_iface* netstack_iface_copy_byidx(struct netstack* ns, int idx);

Shares and copies can occur from within a callback. If you want to use the object that was provided in the callback, this can be done without a lookup or taking any additional locks:

c // Copy/share a netstack_iface to which we already have a handle, for // instance directly from the callback context. This is faster than the // alternatives, as it needn't perform a lookup. const struct netstack_iface* netstack_iface_share(const struct netstack_iface* ni); struct netstack_iface* netstack_iface_copy(const struct netstack_iface* ni);

Whether deep-copied or shared, the object can and should be abandoned via netstack_iface_abandon(). This should be done even if the netstack is destroyed, with the implication that both shared and copied netstack_ifaces remains valid after a call to netstack_destroy().

c // Release a netstack_iface acquired from the netstack through either a copy or // a share operation. Note that while the signature claims constness, ns will // actually presumably be mutated (via alias). It is thus imperative that the // passed object not be used again by the caller! void netstack_iface_abandon(const struct netstack_iface* ni);

Enumerating cached objects

It is possible to get all the cached objects of a type via enumeration. This requires providing a (possibly large) buffer into which data will be copied. If the buffer is not large enough to hold all the objects, another call can be made to get the next batch (it is technically possible to enumerate the objects one-by-one using this method), but this is not guaranteed to be an atomic view of the object class.

The number of objects currently cached can be queried, though this is no guarantee that the number won't have changed by the time a subsequent enumeration is requested:

c // Count of interfaces in the active store, and bytes used to represent them in // total. If iface_notrack is set, these will always return 0. unsigned netstack_iface_count(const struct netstack* ns); uint64_t netstack_iface_bytes(const netstack* ns);

Enumeration currently always takes the form of a copy, never a share (shared enumerations will be added if a compelling reason for them is found). Two buffers must be provided for an enumeration request of up to N objects:

  • offsets, an array of N uint32_ts, and
  • objs, a character buffer of some size (obytes).

No more than N objects will be enumerated. If objs becomes exhausted, or N objects do not exist, fewer than N will be enumerated. The number of objects enumerated is returned, or -1 on error.

```c // State for streaming enumerations (enumerations taking place over several // calls). It's exposed in this header so that callers can easily define one on // their stacks. Don't mess with it. Zero it out to start a new enumeration. typedef struct netstackenumerator { uint32t nonce; uint32t slot; struct netstackiface* hnext; } netstack_enumerator;

// Enumerate up to n netstackifaces via copy. offsets must have space for at // least n elements, which will serve as offsets into objs. objs is a flat // array of size obytes. streamer ought point to a zero-initialized // netstackenumerator to begin an enumeration operation. If // netstackifaceenumerate() is called again using this same streamer, the // enumeration picks up where it left off. A NULL streamer is interpreted as a // request for atomic enumeration; if there is not sufficient space to copy all // objects, it is an error, and the copying will be aborted as soon as // possible. Unlike other errors, n and obytes will be updated in this case to // reflect the current necessary values. // // Returns -1 on error, due to invalid parameters, insufficient space for an // atomic enumeraion, or failure to resume an enumeration (this can happen if // too much has changed since the previous call--enumerations aren't really // suitable for highly dynamic environments). No parameters are modified in // this case (save the atomic case, as noted above). Otherwise, the number of objects // copied r is returned, r <= the original n. n is set to the number of // objects remaining. obytes is set to the bytes required to copy the remaning // objects. streamer is updated, if appropriate. The first r values of offsets // give valid byte offsets into objs, and a (suitably-aligned) network_iface is // present at each such offset. Their associated buffers are also present in // objs. The pointers and bookkeeping within the netstack_ifaces have been // updated so that the resulting objects can be used with the standard // netstack_iface API. There is no need to call netstack_iface_abandon() on // these objects. // // An enumeration operation is thus successfully terminated iff a non-negative // number is returned, and *n and *obytes have both been set to 0. Note that // a 0 could be returned without completion if objs is too small to copy the // next object; in this case, neither *n nor *obytes would be 0. int netstack_iface_enumerate(const struct netstack ns, uint32t* offsets, int* n, void* objs, sizet* obytes, netstack_enumerator* streamer); ```

The streamer parameter is used to stream through the objects. It must be zeroed out prior to the first call of an enumeration sequence, and should not be modified by the caller. Repeating a call with a streamer that has already completed is not an error (0 will be returned, and n and obytes will both be set to 0). An enumeration returning an error should not be retried with the same streamer.

For a positive return value r, the r values returned in offsets index into objs. Each one is a (suitably-aligned) struct netstack_iface. These netstack_ifaces do not need to be fed to netstack_iface_abandon().

Querying objects

Interfaces

Interfaces are described by the opaque netstack_iface object. Interfaces correspond to physical devices (there can sometimes be multiple interfaces per physical device, either by configuration or by default) and virtual devices. Interfaces can be up or down, might or might not have carrier, might have a broadcast domain or might be point-to-point, might have multiple link-layer addresses, might have a link-layer broadcast address, will have a queueing discipline, might be in promiscuous aka "sniffing" mode, and will have a name of fewer than IFNAMSIZ characters.

There are almost always more than one interface on a modern machine, one of which is almost always a loopback device with addresses of 127.0.0.1/8 (IPv4) and ::1/128 (IPv6).

There are a great many types of interface beyond loopback and Ethernet (802.11 WiFi devices look like Ethernet at Layer 2).

```c // name must be at least IFNAMSIZ bytes. returns NULL if no name was reported, // or the name was greater than IFNAMSIZ-1 bytes (should never happen). char* netstackifacename(const struct netstack_iface* ni, char* name);

unsigned netstackifacetype(const struct netstackiface* ni); unsigned netstackifacefamily(const struct netstackiface* ni); int netstackifaceindex(const struct netstackiface* ni); unsigned netstackifaceflags(const struct netstackiface* ni);

static inline bool netstackifaceup(const struct netstackiface* ni){ return netstackifaceflags(ni) & IFFUP; }

// Has a valid broadcast address been configured? static inline bool netstackifacebroadcast(const struct netstackiface* ni){ return netstackifaceflags(ni) & IFFBROADCAST; }

// Is this a loopback device? static inline bool netstackifaceloopback(const struct netstackiface* ni){ return netstackifaceflags(ni) & IFFLOOPBACK; }

// Is this a point-to-point link? static inline bool netstackifacepointtopoint(const struct netstackiface* ni){ return netstackifaceflags(ni) & IFFPOINTOPOINT; }

// Does this link lack ARP? static inline bool netstackifacenoarp(const struct netstackiface* ni){ return netstackifaceflags(ni) & IFFNOARP; }

// Is the interface in promiscuious mode? static inline bool netstackifacepromisc(const struct netstackiface* ni){ return netstackifaceflags(ni) & IFFPROMISC; }

// pass in the maximum number of bytes available for copying the link-layer // address. if this is sufficient, the actual number of bytes copied will be // stored to this variable. otherwise, NULL will be returned. static inline void* netstackifacel2addr(const struct netstackiface* ni, void* buf, sizet* len){ const struct rtattr* rta = netstackifaceattr(ni, IFLAADDRESS); return netstackrtattrcpy(rta, buf, len) ? buf : NULL; }

// same deal as netstackifacel2addr(), but for the broadcast link-layer // address (if one exists). static inline void* netstackifacel2broadcast(const struct netstackiface* ni, void* buf, sizet* len){ const struct rtattr* rta = netstackifaceattr(ni, IFLABROADCAST); return netstackrtattrcpy(rta, buf, len) ? buf : NULL; }

// Returns the MTU as reported by netlink, or 0 if none was reported. static inline uint32t netstackifacemtu(const struct netstackiface* ni){ const struct rtattr* rta = netstackifaceattr(ni, IFLAMTU); uint32t ret; return netstackrtattrcpyexact(rta, &ret, sizeof(ret)) ? ret : 0; }

// Returns the link type (as opposed to the device type, as returned by // netstackifacetype static inline int netstackifacelink(const struct netstackiface* ni){ const struct rtattr* rta = netstackifaceattr(ni, IFLALINK); int ret; return netstackrtattrcpyexact(rta, &ret, sizeof(ret)) ? ret : 0; }

// Returns the queuing discipline, or NULL if none was reported. The return is // heap-allocated, and must be free()d by the caller. char* netstackifaceqdisc(const struct netstack_iface* ni);

// Returns interface stats if they were reported, filling in the stats object // and returning 0. Returns -1 if there were no stats. static inline bool netstackifacestats(const struct netstackiface* ni, struct rtnllinkstats* stats){ const struct rtattr* rta = netstackifaceattr(ni, IFLASTATS); return netstackrtattrcpyexact(rta, stats, sizeof(*stats)); } ```

Addresses

Addresses are described by the opaque netstack_addr object. Addresses can be configured by any number of automatic and manual means, and there are often multiple valid L3 addresses on a single interface.

```c const struct rtattr* netstackaddrattr(const struct netstackaddr* na, int attridx); unsigned netstackaddrfamily(const struct netstackaddr* na); unsigned netstackaddrprefixlen(const struct netstackaddr* na); unsigned netstackaddrflags(const struct netstackaddr* na); unsigned netstackaddrscope(const struct netstackaddr* na); int netstackaddrindex(const struct netstackaddr* na);

// Returns true iff there is an IFAADDRESS layer 3 address associated with this // entry, *and* it can be transformed into a presentable string, *and* buf is // large enough to hold the result. buflen ought be at least INET6ADDRSTRLEN. // family will hold the result of netstackaddrfamily() (assuming that an // IFAADDRESS rtattr was indeed present). static inline char* netstackaddraddressstr(const struct netstackaddr* na, char* buf, sizet buflen, unsigned* family){ const struct rtattr* narta = netstackaddrattr(na, IFAADDRESS); if(narta == NULL){ return NULL; } family = netstack_addr_family(na); if(!netstack_rtattr_l3addrstr(family, narta, buf, buflen)){ return NULL; } return buf; }

// Returns true iff there is an IFALOCAL layer 3 address associated with this // entry, *and* it can be transformed into a presentable string, *and* buf is // large enough to hold the result. buflen ought be at least INET6ADDRSTRLEN. // family will hold the result of netstackaddrfamily() (assuming that an // IFALOCAL rtattr was indeed present). static inline char* netstackaddrlocalstr(const struct netstackaddr* na, char* buf, sizet buflen, unsigned* family){ const struct rtattr* narta = netstackaddrattr(na, IFALOCAL); if(narta == NULL){ return NULL; } family = netstack_addr_family(na); if(!netstack_rtattr_l3addrstr(family, narta, buf, buflen)){ return NULL; } return buf; }

// Returns the address label, or NULL if none was reported. The return is // heap-allocated, and must be free()d by the caller. char* netstackaddrlabel(const struct netstack_addr* na);

// Returns address cacheinfo if they were reported, filling in the cinfo object // and returning 0. Returns -1 if there was no such info. static inline bool netstackaddrcacheinfo(const struct netstackaddr* na, struct ifacacheinfo* cinfo){ const struct rtattr* rta = netstackaddrattr(na, IFACACHEINFO); return netstackrtattrcpy_exact(rta, cinfo, sizeof(*cinfo)); } ```

Routes

Routes are described by the opaque netstack_route object. Routes can be configured by the administrator, a DHCP client, a routing server, IPv6 advertisements, and other means.

```c const struct rtattr* netstackrouteattr(const struct netstackroute* nr, int attridx); unsigned netstackroutefamily(const struct netstackroute* nr); unsigned netstackroutedstlen(const struct netstackroute* nr); unsigned netstackroutesrclen(const struct netstackroute* nr); unsigned netstackroutetos(const struct netstackroute* nr); // Routing tables are indexed 0-255 unsigned netstackroutetable(const struct netstackroute* nr); unsigned netstackrouteprotocol(const struct netstackroute* nr); unsigned netstackroutescope(const struct netstackroute* nr); unsigned netstackroutetype(const struct netstackroute* nr); unsigned netstackrouteflags(const struct netstackroute* nr);

static inline bool netstackroutenotify(const struct netstackroute* nr){ return netstackrouteflags(nr) & RTMF_NOTIFY; }

// Was the route cloned from another route? static inline bool netstackroutecloned(const struct netstackroute* nr){ return netstackrouteflags(nr) & RTMF_CLONED; }

static inline bool netstackrouteequalize(const struct netstackroute* nr){ return netstackrouteflags(nr) & RTMF_EQUALIZE; }

static inline bool netstackroutestr(const struct netstackroute* nr, int attr, char* buf, sizet buflen, unsigned* family){ const struct rtattr* nrrta = netstackrouteattr(nr, attr); if(nrrta == NULL){ return false; } family = netstack_route_family(nr); if(!netstack_rtattr_l3addrstr(family, nrrta, buf, buflen)){ return false; } return true; }

static inline bool netstackroutedststr(const struct netstackroute* nr, char* buf, sizet buflen, unsigned* family){ return netstackroutestr(nr, RTA_DST, buf, buflen, family); }

static inline bool netstackroutesrcstr(const struct netstackroute* nr, char* buf, sizet buflen, unsigned* family){ return netstackroutestr(nr, RTA_SRC, buf, buflen, family); }

static inline bool netstackroutegatewaystr(const struct netstackroute* nr, char* buf, sizet buflen, unsigned* family){ return netstackroutestr(nr, RTA_GATEWAY, buf, buflen, family); }

static inline int netstackrouteintattr(const struct netstackroute* nr, int attr){ const struct rtattr* rt = netstackrouteattr(nr, attr); int ret = 0; if(rt && RTAPAYLOAD(rt) == sizeof(ret)){ memcpy(&ret, RTADATA(rt), RTAPAYLOAD(rt)); } return ret; }

static inline int netstackrouteiif(const struct netstackroute* nr){ return netstackrouteintattr(nr, RTAIIF); }

static inline int netstackrouteoif(const struct netstackroute* nr){ return netstackrouteintattr(nr, RTAOIF); }

static inline int netstackroutepriority(const struct netstackroute* nr){ return netstackrouteintattr(nr, RTAPRIORITY); }

static inline int netstackroutemetric(const struct netstackroute* nr){ return netstackrouteintattr(nr, RTAMETRICS); }

static inline const char* netstackroutetypestr(unsigned rtype){ switch(rtype){ case RTNUNSPEC: return "none"; case RTNUNICAST: return "unicast"; case RTNLOCAL: return "local"; case RTNBROADCAST: return "broadcast"; case RTNANYCAST: return "anycast"; case RTNMULTICAST: return "multicast"; case RTNBLACKHOLE: return "blackhole"; case RTNUNREACHABLE: return "unreachable"; case RTNPROHIBIT: return "prohibit"; case RTNTHROW: return "throw"; case RTNNAT: return "nat"; case RTNXRESOLVE: return "xresolve"; default: return "?"; } }

static inline const char* netstackroutescopestr(unsigned scope){ switch(scope){ case RTSCOPEUNIVERSE: return "global"; // global route case RTSCOPESITE: return "site"; // interior route in the local AS case RTSCOPELINK: return "link"; // route on this link case RTSCOPEHOST: return "host"; // route on the local host case RTSCOPENOWHERE: return "nowhere"; // destination doesn't exist default: return "?"; } }

static inline const char* netstackrouteprotstr(unsigned proto){ switch(proto){ case RTPROTUNSPEC: return "unknown"; case RTPROTREDIRECT: return "icmp"; case RTPROTKERNEL: return "kernel"; case RTPROTBOOT: return "boot"; case RTPROTSTATIC: return "admin"; case RTPROTGATED: return "gated"; case RTPROTRA: return "rdisc/nd"; case RTPROTMRT: return "meritmrt"; case RTPROTZEBRA: return "zebra"; case RTPROTBIRD: return "bird"; case RTPROTDNROUTED: return "decnet"; case RTPROTXORP: return "xdrp"; case RTPROTNTK: return "netsukuku"; case RTPROTDHCP: return "dhcp"; case RTPROTMROUTED: return "mcastd"; case RTPROTBABEL: return "babeld"; case RTPROTBGP: return "bgp"; case RTPROTISIS: return "isis"; case RTPROTOSPF: return "ospf"; case RTPROTRIP: return "rip"; case RTPROT_EIGRP: return "eigrp"; default: return "?"; } } ```

Neigbors

Neighbors are described by the opaque netstack_neigh object. Neighbors can be configured by the administrator or proxy ARP servers, but more typically they follow a natural periodic discovery state machine. Many link types do not have a concept of neighbors.

```c const struct rtattr* netstackneighattr(const struct netstackneigh* nn, int attridx); int netstackneighindex(const struct netstackneigh* nn); int netstackneighfamily(const struct netstackneigh* nn); // always AFUNSPEC unsigned netstackneighflags(const struct netstack_neigh* nn);

static inline bool netstackneighproxyarp(const struct netstackneigh* nn){ return netstackneighflags(nn) & NTFPROXY; }

static inline bool netstackneighipv6router(const struct netstackneigh* nn){ return netstackneighflags(nn) & NTFROUTER; }

unsigned netstackneightype(const struct netstackneigh* nn); // A bitmask of NUD{INCOMPLETE, REACHABLE, STALE, DELAY, PROBE, FAILED, // NOARP, PERMANENT} unsigned netstackneighstate(const struct netstack_neigh* nn);

// Returns true iff there is an NDADST layer 3 address associated with this // entry, *and* it can be transformed into a presentable string, *and* buf is // large enough to hold the result. buflen ought be at least INET6ADDRSTRLEN. // family will hold the result of netstackneighfamily() (assuming that an // NDADST rtattr was indeed present). static inline bool netstackneighl3addrstr(const struct netstackneigh* nn, char* buf, sizet buflen, unsigned* family){ const struct rtattr* nnrta = netstackneighattr(nn, NDADST); if(nnrta == NULL){ return false; } family = netstack_neigh_family(nn); if(!l3addrstr(family, nnrta, buf, buflen)){ return false; } return true; }

// Returns true iff there is an NDALLADDR layer 2 address associated with this // entry, *and* buf is large enough to hold it. buflen ought generally be at // least ETHALEN bytes. static inline bool netstackneighl2addr(const struct netstackneigh* nn, void* buf, sizet buflen){ const struct rtattr* l2rta = netstackneighattr(nn, NDALLADDR); if(l2rta == NULL){ return false; } if(buflen < RTAPAYLOAD(l2rta)){ return false; } memcpy(buf, RTADATA(l2rta), RTAPAYLOAD(l2rta)); return true; }

// Returns non-NULL iff there is an NDALLADDR layer 2 address associated with // this entry, *and* it can be transformed into a presentable string, *and* // memory is successfully allocated to hold the result. The result must in that // case be free()d by the caller. static inline char* netstackneighl2addrstr(const struct netstackneigh* nn){ const struct rtattr* l2rta = netstackneighattr(nn, NDALLADDR); if(l2rta == NULL){ return NULL; } char* llstr = netstackl2addrstr(netstackneightype(nn), RTAPAYLOAD(l2rta), RTADATA(l2rta)); return llstr; }

// Returns true if an NDACACHEINFO rtattr is present, in which case cinfo will // be filled in with the cache statistics for this entry. static inline bool netstackneighcachestats(const struct netstackneigh* nn, struct ndacacheinfo* cinfo){ const struct rtattr* rta = netstackneighattr(nn, NDACACHEINFO); if(rta == NULL){ return false; } memcpy(cinfo, RTADATA(rta), RTAPAYLOAD(rta)); return true; } ```

Statistics

libnetstack maintains some statistics about each netstack. They can be retrieved with netstack_sample_stats(). Note that this call does not necessarily sample the stats in an atomic fashion.

c typedef struct netstack_stats { // Current counts of each object class unsigned ifaces, addrs, routes, neighs; // Events for each object class (dumps + creations + changes + deletions) uintmax_t iface_events, addr_events, route_events, neigh_events; // The number of times a lookup + share or lookup + copy succeeded uintmax_t lookup_shares, lookup_copies; // Number of shares which have been invalidated but not destroyed uintmax_t zombie_shares; // The number of times the user looked up a key and it didn't exist uintmax_t lookup_failures; uintmax_t netlink_errors; // number of nlmsgerrs received from netlink uintmax_t user_callbacks_total; // number of times we've called back } netstack_stats;

// Acquire the current statistics, atomically. netstackstats* netstacksamplestats(const struct netstack* ns, netstackstats* stats); ```

Examples


See also