Event Manager
The event manager acts somewhat like a buffer manager and message interpretor for the listener. The interface also behaves somewhat like an iterator where an event object is produced on each request (if available). Part of the reason for this is that a single Netlink read can produce multiple Netlink events.
As the example in the source below demonstrates. The Netlink data buffer: pkt is cast as an nlmsghdr message, with netlink macros NLMSG_OK, and NLMSG_NEXT providing help in moving through the Netlink events found in buffer pkt.
const struct nlmsghdr* mh; for (mh = reinterpret_cast(pkt); NLMSG_OK(mh, ps); mh = NLMSG_NEXT(const_cast (mh), ps)) { parse_msg(mh); }
Inside this loop control of a single nlmsghdr data structure is passed to parse_msg() for further netlink interpretation fun.
The parse_msg() pulls data from the nlmsghdr data structure but this data is dependent on the type of message received. For this implementation we are only interested in 4 types of Netlink messages: RTM_NEWLINK, RTM_DELLINK, RTM_NEWADDR, RTM_DELADDR.
switch (nlHdr->nlmsg_type) { case RTM_NEWLINK: case RTM_DELLINK: case RTM_NEWADDR: case RTM_DELADDR:
From this point parse_msg() does the dirty work of pulling specific values from the netlink data structure. This implementation doesn't access the full set of data made available for the interface (only the portions I was interested in cracking open).
Once the relevant bits of data have been parsed, a NetlinkEvent object is created via the constructor (see below). And the event object is then pushed into an stl vector collection. The collection holds onto the event object until it is popped off via a request from the listener object. Processing is done until parse() has iterated through all of the netlink messages and each NetlinkEvent object has been created.
NetlinkEvent e(nlHdr->nlmsg_type, iface, mtu, mac, enabled, running, addr, broadcast, mask_len, index); e.set_ifinfomsg(ifInfo); _coll.push_back(e);
The event object itself just contains accessors for data, and stream manipulators for ease of dumping data from the object.
Now, that's essentially it. The main entry point is our next and last stop (but of course main can be replaced by library call(s)).
Main
The main entry point just declares a sender and listener object. And initializes the listener, and as a bonus returns the socket back. Normally, the sock should be encapsulated in the listener object, but in this case the socket is reused by the sender since the sender only has a limited use in this implementation. Work to do on my part would be to wrap this in an object so that it is not directly exposed.
NetlinkSend nl_send; NetlinkListener nl_listener; int sock = nl_listener.init();
And below we see the listener will return a valid event object if TRUE is returned. The initial startup of the code allows for a single send out the sender object, and the goal of this is to request a netlink dump of interface data.
Finally, our familiar netlink message types are seen below. These are used so that we know what type of event we are working with and can formulate our /presentation/printing as necessary.
while (true) { // cout << "test_netlink: now entering listening mode: " << endl; NetlinkEvent nl_event; if (nl_listener.process(nl_event) == true) { if (send_request) { if (nl_send.send(sock, RTM_GETADDR) != 0) {Snippet of presentation below:
if (nl_event.get_type() == RTM_DELLINK || nl_event.get_type() == RTM_NEWLINK) { cout << " type: " << string(nl_event.get_type()==RTM_DELLINK?"DELLINK":"NEWLINK") << endl; cout << " state: " << string(nl_event.is_link_up()?"UP":"DOWN") << endl;And that's it! Source code follows. main.cc
#include <stdlib.h> #include <iostream> #include <stdio.h> #include <string> #include <linux/types.h> #include <sys/socket.h> #include <linux/rtnetlink.h> #include "netlink_send.hh" #include "netlink_listener.hh" #include "netlink_event.hh" using namespace std; /** * * **/ void usage() { fprintf(stdout, "-s start with sending netlink request\n"); fprintf(stdout, "-h help message\n"); } /** * * **/ int main(int argc, char* const argv[]) { int ch; bool send_request = false; bool debug = false; cout << "netlink_main()" << endl; while ((ch = getopt(argc, argv, "sdh")) != -1) { switch (ch) { case 's': send_request = true; break; case 'd': debug = true; break; case 'h': usage(); exit(0); } } NetlinkSend nl_send; NetlinkListener nl_listener; int sock = nl_listener.init(); if (sock <= 0) { cerr << "test_netlink(), bad voodoo. exiting.." << endl; exit(1); } if (send_request) { cout << "sending initial netlink request" << endl; nl_listener.set_multipart(true); if (nl_send.send(sock, RTM_GETLINK) != 0) { cerr << "test_netlink(), error sending" << endl; exit(1); } } while (true) { // cout << "test_netlink: now entering listening mode: " << endl; NetlinkEvent nl_event; if (nl_listener.process(nl_event) == true) { if (send_request) { if (nl_send.send(sock, RTM_GETADDR) != 0) { cerr << "test_netlink(), error sending" << endl; exit(1); } send_request = false; } else { nl_listener.set_multipart(false); } char buf[20]; sprintf(buf, "%d", nl_event.get_index()); cout << "results for " << nl_event.get_iface() << "(" << string(buf) << ")" << endl; cout << " running: " << string(nl_event.get_running() ? "yes" : "no") << endl; cout << " enabled: " << string(nl_event.get_enabled() ? "yes" : "no") << endl; if (debug) { cout << " ifinfomsg: " << nl_event.get_ifinfomsg() << endl; } if (nl_event.get_type() == RTM_DELLINK || nl_event.get_type() == RTM_NEWLINK) { cout << " type: " << string(nl_event.get_type()==RTM_DELLINK?"DELLINK":"NEWLINK") << endl; cout << " state: " << string(nl_event.is_link_up()?"UP":"DOWN") << endl; sprintf(buf, "%d", nl_event.get_mtu()); cout << " mtu: " << string(buf) << endl; cout << " mac: " << nl_event.get_mac_str() << endl; } else if (nl_event.get_type() == RTM_DELADDR || nl_event.get_type() == RTM_NEWADDR) { cout << " type: " << string(nl_event.get_type()==RTM_DELADDR?"DELADDR":"NEWADDR") << endl; cout << " addr: " << nl_event.get_addr().str().c_str() << endl; cout << " broadcast: " << nl_event.get_broadcast().str().c_str() << endl; char buf[20]; sprintf(buf, "%d", nl_event.get_mask_len()); cout << " mask length: " << string(buf) << endl; } cout << endl; } else { // cout << "didn't receive a message, sleeping for 1 second" << endl; sleep(1); } } exit(0); }netlink_event.cc
#include <linux/types.h> #include <sys/socket.h> #include <linux/rtnetlink.h> #include <net/if.h> #include <netinet/in.h> #include <arpa/inet.h> #include <syslog.h> #include <iostream> #include <string> #include "netlink_event.hh" using namespace std; /** * * **/ NetlinkEvent::NetlinkEvent(int type, std::string iface, int mtu, unsigned char *mac, bool enabled, bool running, IPv4 addr, IPv4 broadcast, int mask_len, int index) : _type(type), _iface(iface), _vif(false), _mtu(mtu), _enabled(enabled), _running(running), _addr(addr), _broadcast(broadcast), _mask_len(mask_len), _index(index) { memcpy(_mac, mac, 6); if (_iface.find(".") != string::npos) { _vif = true; } } /** * * **/ NetlinkEvent::~NetlinkEvent() { } /** * * **/ void NetlinkEvent::log() { syslog(LOG_USER | LOG_INFO, "NetlinkEvent::log(): type: %d, iface: %s, mtu: %d, addr: %s, bc: %s, mask: %d, index: %d", _type, _iface.c_str(), _mtu, _addr.str().c_str(), _broadcast.str().c_str(), _mask_len, _index); } /** * * **/ NetlinkEventManager::NetlinkEventManager() { } /** * * * **/ NetlinkEventManager::~NetlinkEventManager() { } /** * * * **/ void NetlinkEventManager::process(unsigned char* pkt, int size) { if (size <= 0) { return; } size_t ps = size_t(size); const struct nlmsghdr* mh; for (mh = reinterpret_castnetlink_listener.cc(pkt); NLMSG_OK(mh, ps); mh = NLMSG_NEXT(const_cast (mh), ps)) { parse_msg(mh); } } /** * * **/ bool NetlinkEventManager::pop(NetlinkEvent &e) { char buf[20]; sprintf(buf, "%d", _coll.size()); NLEventIter iter = _coll.begin(); if (iter != _coll.end()) { e = *iter; _coll.erase(iter); return true; } return false; } /** * * * **/ void NetlinkEventManager::parse_msg(const struct nlmsghdr *nlHdr) { bool enabled; bool running; string iface; int mtu = -1; int index = -1; unsigned char mac[6]; IPv4 addr, broadcast; int mask_len = -1; bzero(mac, 6); struct ifinfomsg* ifInfo = (struct ifinfomsg *)NLMSG_DATA(nlHdr); //link state flag enabled = ifInfo->ifi_flags & IFF_UP; running = ifInfo->ifi_flags & IFF_RUNNING; index = ifInfo->ifi_index; struct rtattr* rtAttr = (struct rtattr *)IFLA_RTA(ifInfo); int rtLen = IFLA_PAYLOAD(nlHdr); switch (nlHdr->nlmsg_type) { case RTM_NEWLINK: case RTM_DELLINK: case RTM_NEWADDR: case RTM_DELADDR: for(;RTA_OK(rtAttr,rtLen);rtAttr = RTA_NEXT(rtAttr,rtLen)){ if (nlHdr->nlmsg_type == RTM_NEWLINK || nlHdr->nlmsg_type == RTM_DELLINK) { switch(rtAttr->rta_type) { case IFLA_IFNAME: iface = string((char*)RTA_DATA(rtAttr)); break; case IFLA_ADDRESS: memcpy(mac, RTA_DATA(rtAttr), 6); break; case IFLA_MTU: mtu = *((unsigned int *)RTA_DATA(rtAttr)); break; default: break; } } else if (nlHdr->nlmsg_type == RTM_NEWADDR || nlHdr->nlmsg_type == RTM_DELADDR) { uint32_t address; struct ifaddrmsg *ifAddrs; ifAddrs = (struct ifaddrmsg *)NLMSG_DATA(nlHdr); mask_len = ifAddrs->ifa_prefixlen; switch(rtAttr->rta_type) { case IFA_LOCAL: address = *(uint32_t *)RTA_DATA(rtAttr); addr = IPv4(address); break; case IFA_LABEL: iface = string((char*)RTA_DATA(rtAttr)); break; case IFA_BROADCAST: address = *(uint32_t *)RTA_DATA(rtAttr); broadcast = IPv4(address); break; default: break; } } } { NetlinkEvent e(nlHdr->nlmsg_type, iface, mtu, mac, enabled, running, addr, broadcast, mask_len, index); e.set_ifinfomsg(ifInfo); e.log(); _coll.push_back(e); } break; case NLMSG_ERROR: cerr << "netlink message of type ERROR received" << endl; break; case NLMSG_DONE: cerr << "netlink message of type DONE received" << endl; break; case NLMSG_NOOP: cerr << "netlink message of type NOOP received" << endl; break; default: cerr << "unknown netlink message type received" << endl; break; } } /** * * * **/ typedef struct { unsigned int iff_flag; char *name; } iff_flags_name; string NetlinkEvent::get_ifinfomsg() { string ret; char buf[40]; sprintf(buf, "%uc", _ifinfo.ifi_family); ret = "ifi_family: " + string(buf) + ", "; sprintf(buf, "%us", _ifinfo.ifi_type); ret += "ifi_type: " + string(buf) + ", "; sprintf(buf, "%d", _ifinfo.ifi_index); ret += "ifi_index: " + string(buf) + ", "; sprintf(buf, "%ud", _ifinfo.ifi_flags); ret += "ifi_flags: " + string(buf) + ", "; sprintf(buf, "%ud", _ifinfo.ifi_change); ret += "ifi_change: " + string(buf); return ret; } /* string NetlinkEvent::operator<<(const ostream &o) { UNUSED(o); return (""); } */ std::ostream & operator <<(std::ostream & Stream, const NetlinkEvent & instance) { // Stream << ... fields from instance const NetlinkEvent foo = instance; return Stream; }
#include <errno.h> #include <linux/types.h> #include <sys/types.h> #include <sys/socket.h> #include <linux/rtnetlink.h> #include <vector> #include <string> #include <iostream> #include "netlink_event.hh" #include "netlink_listener.hh" using namespace std; #define SO_RCV_BUF_SIZE_MAX (256*1024) // Desired socket buffer size #define SO_RCV_BUF_SIZE_MIN (48*1024) // Min. socket buffer size #define NLSOCK_BYTES (8*1024) /** * * **/ NetlinkListener::NetlinkListener() : _fd(-1), _is_multipart_message_read(false) { } /** * * **/ NetlinkListener::~NetlinkListener() { close(_fd); } /** * * **/ int NetlinkListener::init() { struct sockaddr_nl snl; socklen_t snl_len; if (_fd >= 0) { cerr << "socket cannot be initialized" << endl; return _fd; } _fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (_fd < 0) { cerr << string("Could not open netlink socket: ") << strerror(errno) << endl; return _fd; } comm_sock_set_rcvbuf(_fd, SO_RCV_BUF_SIZE_MAX, SO_RCV_BUF_SIZE_MIN); memset(&snl, 0, sizeof(snl)); snl.nl_family = AF_NETLINK; snl.nl_pid = getpid(); // Let the kernel assign the pid to the socket snl.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR;//_nl_groups; if (bind(_fd, reinterpret_castnetlink_send.cc(&snl), sizeof(snl)) < 0) { cerr << string("bind(AF_NETLINK) failed: ") << strerror(errno) << endl; close(_fd); _fd = -1; return _fd; } snl_len = sizeof(snl); if (getsockname(_fd, reinterpret_cast (&snl), &snl_len) < 0) { cerr << string("getsockname(AF_NETLINK) failed: ") << strerror(errno) << endl; close(_fd); _fd = -1; return _fd; } if (snl_len != sizeof(snl)) { cerr << string("Wrong address length of AF_NETLINK socket: ") << endl; close(_fd); _fd = -1; return _fd; } if (snl.nl_family != AF_NETLINK) { cerr << string("Wrong address family of AF_NETLINK socket: ") << endl; close(_fd); _fd = -1; return _fd; } return _fd; } /** * * **/ bool NetlinkListener::process(NetlinkEvent &e) { if (_fd <= 0) { return false; } vector message; vector buffer(NLSOCK_BYTES); size_t last_mh_off = 0; size_t off = 0; ssize_t got = -1; char buf[20]; for ( ; ; ) { //don't block on recv do { got = recv(_fd, &buffer[0], buffer.size(), MSG_DONTWAIT | MSG_PEEK); if ((got < 0) && (errno == EINTR)) continue; // XXX: the receive was interrupted by a signal if ((got < 0) || (got < (ssize_t)buffer.size())) break; // The buffer is big enough buffer.resize(buffer.size() + NLSOCK_BYTES); } while (true); got = recv(_fd, &buffer[0], buffer.size(), MSG_DONTWAIT); // got = read(_fd, &buffer[0], buffer.size()); if (got < 0) { if (errno == EINTR) continue; // cerr << "Netlink socket read error: " << endl; break; } message.resize(message.size() + got); memcpy(&message[off], &buffer[0], got); off += got; if ((off - last_mh_off) < (ssize_t)sizeof(struct nlmsghdr)) { cerr << string("Netlink socket recvfrom failed: message truncated: ") << endl; break; } // // If this is a multipart message, it must be terminated by NLMSG_DONE // bool is_end_of_message = false; size_t new_size = off - last_mh_off; const struct nlmsghdr* mh; for (mh = reinterpret_cast (&buffer[last_mh_off]); NLMSG_OK(mh, new_size); mh = NLMSG_NEXT(const_cast (mh), new_size)) { if ((mh->nlmsg_flags & NLM_F_MULTI) || _is_multipart_message_read) { sprintf(buf, "%d", mh->nlmsg_type); is_end_of_message = false; if (mh->nlmsg_type == NLMSG_DONE) { is_end_of_message = true; } } } last_mh_off = reinterpret_cast (mh) - reinterpret_cast (&buffer[0]); if (is_end_of_message) { break; } } _nl_event_mgr.process(&message[0], off); return _nl_event_mgr.pop(e); } /** * * **/ int NetlinkListener::comm_sock_set_rcvbuf(int sock, int desired_bufsize, int min_bufsize) { int delta = desired_bufsize / 2; /* * Set the socket buffer size. If we can't set it as large as we * want, search around to try to find the highest acceptable * value. The highest acceptable value being smaller than * minsize is a fatal error. */ if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &desired_bufsize, sizeof(desired_bufsize)) < 0) { desired_bufsize -= delta; while (1) { if (delta > 1) delta /= 2; if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &desired_bufsize, sizeof(desired_bufsize)) < 0) { desired_bufsize -= delta; if (desired_bufsize <= 0) break; } else { if (delta < 1024) { break; } desired_bufsize += delta; } } if (desired_bufsize < min_bufsize) { cerr << "Cannot set receiving buffer size of socket" << endl; return -1; } } return (desired_bufsize); }
#include <errno.h> #include <string.h> #include <linux/types.h> #include <sys/types.h> #include <sys/socket.h> #include <linux/rtnetlink.h> #include <vector> #include <string> #include <iostream> #include "netlink_send.hh" using namespace std; NetlinkSend::NetlinkSend() { } NetlinkSend::~NetlinkSend() { } int NetlinkSend::send(int sock, int type) { int ret; struct sockaddr_nl snl; struct { struct nlmsghdr nlh; struct rtgenmsg g; } req; /* Check netlink socket. */ if (sock < 0) { cout << "sock is not active, exiting" << endl; return -1; } memset (&snl, 0, sizeof snl); snl.nl_family = AF_NETLINK; req.nlh.nlmsg_len = sizeof req; req.nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST; req.nlh.nlmsg_pid = getpid(); req.nlh.nlmsg_type = type; req.nlh.nlmsg_seq = time(NULL); req.g.rtgen_family = AF_UNSPEC; ret = sendto (sock, (void*) &req, sizeof req, 0, (struct sockaddr*) &snl, sizeof snl); if (ret < 0) { cout << "netlink_send failed on send " << endl; return -1; } return 0; }netlink_event.hh
#ifndef __NETLINK_EVENT_HH__ #define __NETLINK_EVENT_HH__ #include <string.h> #include <string> #include <vector> #include <ostream> #include <linux/rtnetlink.h> #include <stdio.h> #include <net/if.h> #include "netlink_types.hh" /** * * **/ class NetlinkEvent { public: NetlinkEvent(int type, std::string iface, int mtu, unsigned char *mac, bool enabled, bool running, IPv4 addr, IPv4 broadcast, int mask_len, int index); NetlinkEvent() : _type(-1), _mtu(-1), _enabled(false), _running(false), _mask_len(-1), _index(-1) {} ~NetlinkEvent(); std::string get_iface() const {return _iface;} int get_mtu() const {return _mtu;} std::string get_mac_str() const { char buf[18]; sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X", _mac[0], _mac[1], _mac[2], _mac[3], _mac[4], _mac[5] ); return std::string(buf); } int get_type() const {return _type;} bool get_enabled() const {return _enabled;} bool get_running() const {return _running;} IPv4 get_addr() const {return _addr;} IPv4 get_broadcast() const {return _broadcast;} int get_mask_len() const {return _mask_len;} bool is_link_up() const {return _enabled;} int get_index() const {return _index;} bool is_vif() const {return _vif;} void log(); void set_ifinfomsg(ifinfomsg *ifinfo) {_ifinfo = *ifinfo;} std::string get_ifinfomsg(); private: int _type; std::string _iface; bool _vif; int _mtu; unsigned char _mac[6]; bool _enabled; bool _running; IPv4 _addr; IPv4 _broadcast; int _mask_len; int _index; //debug struct ifinfomsg _ifinfo; }; /** * * **/ class NetlinkEventManager { public: typedef std::vectornetlink_listener.hhNLEventColl; typedef std::vector ::iterator NLEventIter; public: //methods friend std::ostream & operator<< (std::ostream &, const NetlinkEvent &); NetlinkEventManager(); ~NetlinkEventManager(); void process(unsigned char *pkt, int size); bool pop(NetlinkEvent &e); private: //methods void parse_msg(const struct nlmsghdr*); private: //variables NLEventColl _coll; }; #endif // __NETLINK_EVENT_HH__
#ifndef __NETLINK_LISTENER_HH__ #define __NETLINK_LISTENER_HH__ #include "netlink_event.hh" #includenetlink_send.hhclass NetlinkListener { public: //methods NetlinkListener(); ~NetlinkListener(); /* * returns socket fd */ int init(); bool process(NetlinkEvent &e); int get_sock() {return _fd;} void set_multipart(bool state) {_is_multipart_message_read = state;} private: //methods int comm_sock_set_rcvbuf(int sock, int desired_bufsize, int min_bufsize); private: //vraiables int _fd; bool _is_multipart_message_read; NetlinkEventManager _nl_event_mgr; }; #endif //__NETLINK_LISTENER_HH__
#ifndef __NETLINK_SEND_HH__ #define __NETLINK_SEND_HH__ class NetlinkSend { public: NetlinkSend(); ~NetlinkSend(); int send(int sock, int type); }; #endif //__NETLINK_SEND_HH__netlink_types.hh
#ifndef __NETLINK_TYPES_HH__ #define __NETLINK_TYPES_HH__ #include <stdint.h> #include <sys/types.h> #include <net/ethernet.h> #include <netinet/ether.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> class IPv4 { public: IPv4() : _addr(0) {} IPv4(uint32_t addr) : _addr(addr) {} std::string str() { struct in_addr a; a.s_addr = _addr; return std::string(inet_ntoa(a)); } private: uint32_t _addr; }; class Mac { public: Mac() {} Mac(struct ether_addr mac) { _mac = mac; } private: struct ether_addr _mac; }; #endif //__NETLINK_TYPES_HH__
Hi Michael,
ReplyDeletegreat work!
I was wondering if you forgot to mention the part of
#include "netlink_types.hh".
It is included in netlink_event.hh. Can you publish the missing part.
Thanks in advance
Phil
Thanks Phil--added the missing header. Thanks for the ping--also fixed up the include statements so that the html processor wasn't eating the angle brackets too.
DeleteMike
hi if we have an interface eth0 and its alias eth0:1 is there any way to know which ip is for which eth0
DeleteThe nlmsg structure will contain different IDs per interface (actual and vif)--you can see this below:
Delete# ip add show dev eth0.123
7: eth0.123@eth0: mtu 1500 qdisc noqueue state UP
link/ether 08:00:27:14:53:0d brd ff:ff:ff:ff:ff:ff
inet6 fe80::a00:27ff:fe14:530d/64 scope link
valid_lft forever preferred_lft forever
[edit]
# ip add show dev eth0
2: eth0: mtu 1500 qdisc pfifo_fast state UP qlen 1000
link/ether 08:00:27:14:53:0d brd ff:ff:ff:ff:ff:ff
inet 10.3.0.146/24 brd 10.3.0.255 scope global eth0
inet6 fe80::a00:27ff:fe14:530d/64 scope link
valid_lft forever preferred_lft forever