rtmon_linux.c revision f0c792e78a8bf77c28c7814441e514bea7c5362b
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync/* -*- indent-tabs-mode: nil; -*- */
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync#include "proxy.h"
5b281ba489ca18f0380d7efc7a5108b606cce449vboxsync
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync#include <sys/types.h> /* must come before linux/netlink */
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync#include <sys/socket.h>
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync
1c94c0a63ba68be1a7b2c640e70d7a06464e4fcavboxsync#include <asm/types.h>
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync#include <linux/netlink.h>
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync#include <linux/rtnetlink.h>
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync#include <errno.h>
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync#include <string.h>
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync#include <unistd.h>
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsyncstatic int rtmon_check_defaults(const void *buf, size_t len);
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync/**
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync * Read IPv6 routing table - Linux rtnetlink version.
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync *
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync * XXX: TODO: To avoid re-reading the table we should subscribe to
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync * updates by binding a monitoring NETLINK_ROUTE socket to
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync * sockaddr_nl::nl_groups = RTMGRP_IPV6_ROUTE.
1c94c0a63ba68be1a7b2c640e70d7a06464e4fcavboxsync *
1c94c0a63ba68be1a7b2c640e70d7a06464e4fcavboxsync * But that will provide updates only. Documentation is scarce, but
1c94c0a63ba68be1a7b2c640e70d7a06464e4fcavboxsync * from what I've seen it seems that to get accurate routing info the
1c94c0a63ba68be1a7b2c640e70d7a06464e4fcavboxsync * monitoring socket needs to be created first, then full routing
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync * table requested (easier to do via spearate socket), then monitoring
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync * socket polled for input. The first update(s) of the monitoring
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync * socket may happen before full table is returned, so we can't just
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync * count the defaults, we need to keep track of their { oif, gw } to
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync * correctly ignore updates that are reported via monitoring socket,
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync * but that are already reflected in the full routing table returned
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync * in response to our request.
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync */
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsyncint
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsyncrtmon_get_defaults(void)
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync{
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync int rtsock;
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync ssize_t nsent, ssize;
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync int ndefrts;
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync char *buf = NULL;
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync size_t bufsize;
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync struct {
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync struct nlmsghdr nh;
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync struct rtmsg rtm;
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync char attrbuf[512];
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync } rtreq;
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync memset(&rtreq, 0, sizeof(rtreq));
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync rtreq.nh.nlmsg_type = RTM_GETROUTE;
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync rtreq.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync rtreq.rtm.rtm_family = AF_INET6;
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync rtreq.rtm.rtm_table = RT_TABLE_MAIN;
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync rtreq.rtm.rtm_protocol = RTPROT_UNSPEC;
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync rtreq.nh.nlmsg_len = NLMSG_SPACE(sizeof(rtreq.rtm));
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync bufsize = 1024;
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync ssize = bufsize;
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync for (;;) {
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync char *newbuf;
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync int recverr;
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync newbuf = (char *)realloc(buf, ssize);
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync if (newbuf == NULL) {
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync DPRINTF0(("rtmon: failed to %sallocate buffer\n",
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync buf == NULL ? "" : "re"));
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync free(buf);
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync return -1;
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync }
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync buf = newbuf;
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync bufsize = ssize;
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync /* it's easier to reopen than to flush */
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync rtsock = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync if (rtsock < 0) {
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync DPRINTF0(("rtmon: failed to create netlink socket: %s", strerror(errno)));
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync free(buf);
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync return -1;
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync }
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync nsent = send(rtsock, &rtreq, rtreq.nh.nlmsg_len, 0);
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync if (nsent < 0) {
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync DPRINTF0(("rtmon: RTM_GETROUTE failed: %s", strerror(errno)));
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync close (rtsock);
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync free(buf);
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync return -1;
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync }
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync ssize = recv(rtsock, buf, bufsize, MSG_TRUNC);
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync recverr = errno;
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync close (rtsock);
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync if (ssize < 0) {
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync DPRINTF(("rtmon: failed to read RTM_GETROUTE response: %s",
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync strerror(recverr)));
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync free(buf);
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync return -1;
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync }
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync if ((size_t)ssize <= bufsize) {
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync DPRINTF2(("rtmon: RTM_GETROUTE: %lu bytes\n",
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync (unsigned long)ssize));
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync break;
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync }
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync DPRINTF2(("rtmon: RTM_GETROUTE: truncated %lu to %lu bytes, retrying\n",
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync (unsigned long)ssize, (unsigned long)bufsize));
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync /* try again with larger buffer */
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync }
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync ndefrts = rtmon_check_defaults(buf, (size_t)ssize);
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync free(buf);
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync if (ndefrts == 0) {
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync DPRINTF(("rtmon: no IPv6 default routes found\n"));
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync }
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync else {
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync DPRINTF(("rtmon: %d IPv6 default route%s found\n",
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync ndefrts,
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync ndefrts == 1 || ndefrts == -1 ? "" : "s"));
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync }
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync return ndefrts;
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync}
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync/**
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync * Scan netlink message in the buffer for IPv6 default route changes.
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsync */
5e91fc5e5ea9cccb7a40636f73253d489fbe340bvboxsyncstatic int
rtmon_check_defaults(const void *buf, size_t len)
{
struct nlmsghdr *nh;
int dfltdiff = 0;
for (nh = (struct nlmsghdr *)buf;
NLMSG_OK(nh, len);
nh = NLMSG_NEXT(nh, len))
{
struct rtmsg *rtm;
struct rtattr *rta;
int attrlen;
int delta = 0;
const void *gwbuf;
size_t gwlen;
int oif;
DPRINTF2(("nlmsg type %d flags 0x%x\n",
nh->nlmsg_seq, nh->nlmsg_type, nh->nlmsg_flags));
if (nh->nlmsg_type == NLMSG_DONE) {
break;
}
if (nh->nlmsg_type == NLMSG_ERROR) {
struct nlmsgerr *ne = (struct nlmsgerr *)NLMSG_DATA(nh);
DPRINTF2(("> error %d\n", ne->error));
LWIP_UNUSED_ARG(ne);
break;
}
if (nh->nlmsg_type < RTM_BASE || RTM_MAX <= nh->nlmsg_type) {
/* shouldn't happen */
DPRINTF2(("> not an RTM message!\n"));
continue;
}
rtm = (struct rtmsg *)NLMSG_DATA(nh);
attrlen = RTM_PAYLOAD(nh);
if (nh->nlmsg_type == RTM_NEWROUTE) {
delta = +1;
}
else if (nh->nlmsg_type == RTM_DELROUTE) {
delta = -1;
}
else {
/* shouldn't happen */
continue;
}
/*
* Is this an IPv6 default route in the main table? (Local
* table always has ::/0 reject route, hence the last check).
*/
if (rtm->rtm_family == AF_INET6 /* should always be true */
&& rtm->rtm_dst_len == 0
&& rtm->rtm_table == RT_TABLE_MAIN)
{
dfltdiff += delta;
}
else {
/* some other route change */
continue;
}
gwbuf = NULL;
gwlen = 0;
oif = -1;
for (rta = RTM_RTA(rtm);
RTA_OK(rta, attrlen);
rta = RTA_NEXT(rta, attrlen))
{
if (rta->rta_type == RTA_GATEWAY) {
gwbuf = RTA_DATA(rta);
gwlen = RTA_PAYLOAD(rta);
}
else if (rta->rta_type == RTA_OIF) {
/* assert RTA_PAYLOAD(rta) == 4 */
memcpy(&oif, RTA_DATA(rta), sizeof(oif));
}
}
/* XXX: TODO: note that { oif, gw } was added/removed */
LWIP_UNUSED_ARG(gwbuf);
LWIP_UNUSED_ARG(gwlen);
LWIP_UNUSED_ARG(oif);
}
return dfltdiff;
}