ef583813eb
Former-commit-id: 943baa9f16a098c33e129777827f3a9d20da00d6
387 lines
9.0 KiB
C
387 lines
9.0 KiB
C
/*
|
|
* Network availability and change notifications for linux.
|
|
*
|
|
* Authors:
|
|
* Gonzalo Paniagua Javier (gonzalo@novell.com)
|
|
*
|
|
* Copyright (c) Novell, Inc. 2011
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include "nl.h"
|
|
|
|
#if defined(HAVE_LINUX_NETLINK_H) && defined(HAVE_LINUX_RTNETLINK_H)
|
|
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <arpa/inet.h>
|
|
#include <linux/netlink.h>
|
|
#include <linux/rtnetlink.h>
|
|
|
|
#undef NL_DEBUG
|
|
#define NL_DEBUG_STMT(a) do { } while (0)
|
|
#define NL_DEBUG_PRINT(...)
|
|
|
|
/*
|
|
#define NL_DEBUG 1
|
|
#define NL_DEBUG_STMT(a) do { a } while (0)
|
|
#define NL_DEBUG_PRINT(...) g_message(__VA_ARGS__)
|
|
*/
|
|
|
|
|
|
#ifdef AF_INET6
|
|
#define ADDR_BYTE_LENGTH 16
|
|
#define ADDR_STR_LENGTH INET6_ADDRSTRLEN
|
|
#else
|
|
#define ADDR_LENGTH 4
|
|
#define ADDR_STR_LENGTH INET_ADDRSTRLEN
|
|
#endif
|
|
|
|
enum event_type {
|
|
EVT_NONE = 0,
|
|
#define EVT_NONE 0
|
|
EVT_AVAILABILITY = 1 << 0,
|
|
#define EVT_AVAILABILITY (1 << 0)
|
|
EVT_ADDRESS = 1 << 1,
|
|
#define EVT_ADDRESS (1 << 1)
|
|
EVT_ALL = EVT_AVAILABILITY | EVT_ADDRESS
|
|
#define EVT_ALL (EVT_AVAILABILITY | EVT_ADDRESS)
|
|
};
|
|
|
|
#ifdef NL_DEBUG
|
|
typedef struct {
|
|
int value;
|
|
const char *name;
|
|
} value2name_t;
|
|
|
|
#define INIT(x) { x, #x }
|
|
#define FIND_NAME(a, b) value_to_name (a, b)
|
|
|
|
#define FIND_RT_TYPE_NAME(b) FIND_NAME (rt_types, b)
|
|
static value2name_t rt_types [] = {
|
|
INIT (RTM_NEWROUTE),
|
|
INIT (RTM_DELROUTE),
|
|
INIT (RTM_GETROUTE),
|
|
INIT (RTM_NEWADDR),
|
|
INIT (RTM_DELADDR),
|
|
INIT (RTM_GETADDR),
|
|
INIT (RTM_NEWLINK),
|
|
INIT (RTM_GETLINK),
|
|
INIT (RTM_DELLINK),
|
|
INIT (RTM_NEWNEIGH),
|
|
INIT (RTM_GETNEIGH),
|
|
INIT (RTM_DELNEIGH),
|
|
{0, NULL}
|
|
};
|
|
|
|
#define FIND_RTM_TYPE_NAME(b) FIND_NAME (rtm_types, b)
|
|
static value2name_t rtm_types [] = {
|
|
INIT (RTN_UNSPEC),
|
|
INIT (RTN_UNICAST),
|
|
INIT (RTN_LOCAL),
|
|
INIT (RTN_BROADCAST),
|
|
INIT (RTN_ANYCAST),
|
|
INIT (RTN_MULTICAST),
|
|
INIT (RTN_BLACKHOLE),
|
|
INIT (RTN_UNREACHABLE),
|
|
INIT (RTN_PROHIBIT),
|
|
INIT (RTN_THROW),
|
|
INIT (RTN_NAT),
|
|
INIT (RTN_XRESOLVE),
|
|
{0, NULL}
|
|
};
|
|
|
|
#define FIND_RTM_PROTO_NAME(b) FIND_NAME (rtm_protocols, b)
|
|
static value2name_t rtm_protocols[] = {
|
|
INIT (RTPROT_UNSPEC),
|
|
INIT (RTPROT_REDIRECT),
|
|
INIT (RTPROT_KERNEL),
|
|
INIT (RTPROT_BOOT),
|
|
INIT (RTPROT_STATIC),
|
|
{0, NULL}
|
|
};
|
|
|
|
#define FIND_RTM_SCOPE_NAME(b) FIND_NAME (rtm_scopes, b)
|
|
static value2name_t rtm_scopes [] = {
|
|
INIT (RT_SCOPE_UNIVERSE),
|
|
INIT (RT_SCOPE_SITE),
|
|
INIT (RT_SCOPE_LINK),
|
|
INIT (RT_SCOPE_HOST),
|
|
INIT (RT_SCOPE_NOWHERE),
|
|
{0, NULL}
|
|
};
|
|
|
|
#define FIND_RTM_ATTRS_NAME(b) FIND_NAME (rtm_attrs, b)
|
|
static value2name_t rtm_attrs [] = {
|
|
INIT (RTA_UNSPEC),
|
|
INIT (RTA_DST),
|
|
INIT (RTA_SRC),
|
|
INIT (RTA_IIF),
|
|
INIT (RTA_OIF),
|
|
INIT (RTA_GATEWAY),
|
|
INIT (RTA_PRIORITY),
|
|
INIT (RTA_PREFSRC),
|
|
INIT (RTA_METRICS),
|
|
INIT (RTA_MULTIPATH),
|
|
INIT (RTA_PROTOINFO),
|
|
INIT (RTA_FLOW),
|
|
INIT (RTA_CACHEINFO),
|
|
INIT (RTA_SESSION),
|
|
INIT (RTA_MP_ALGO),
|
|
INIT (RTA_TABLE),
|
|
{0, NULL}
|
|
};
|
|
|
|
#define FIND_RT_TABLE_NAME(b) FIND_NAME (rtm_tables, b)
|
|
static value2name_t rtm_tables [] = {
|
|
INIT (RT_TABLE_UNSPEC),
|
|
INIT (RT_TABLE_COMPAT),
|
|
INIT (RT_TABLE_DEFAULT),
|
|
INIT (RT_TABLE_MAIN),
|
|
INIT (RT_TABLE_LOCAL),
|
|
{0,0}
|
|
};
|
|
|
|
static const char *
|
|
value_to_name (value2name_t *tbl, int value)
|
|
{
|
|
static char auto_name [16];
|
|
|
|
while (tbl->name) {
|
|
if (tbl->value == value)
|
|
return tbl->name;
|
|
tbl++;
|
|
}
|
|
snprintf (auto_name, sizeof (auto_name), "#%d", value);
|
|
return auto_name;
|
|
}
|
|
#endif /* NL_DEBUG */
|
|
|
|
gpointer
|
|
CreateNLSocket (void)
|
|
{
|
|
int sock;
|
|
struct sockaddr_nl sa;
|
|
int ret;
|
|
|
|
sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
|
|
|
|
ret = fcntl (sock, F_GETFL, 0);
|
|
if (ret != -1) {
|
|
ret |= O_NONBLOCK;
|
|
ret = fcntl (sock, F_SETFL, ret);
|
|
if (ret < 0)
|
|
return GINT_TO_POINTER (-1);
|
|
}
|
|
|
|
memset (&sa, 0, sizeof (sa));
|
|
if (sock < 0)
|
|
return GINT_TO_POINTER (-1);
|
|
sa.nl_family = AF_NETLINK;
|
|
sa.nl_pid = getpid ();
|
|
sa.nl_groups = RTMGRP_IPV4_ROUTE | RTMGRP_IPV6_ROUTE | RTMGRP_NOTIFY;
|
|
/* RTNLGRP_IPV4_IFADDR | RTNLGRP_IPV6_IFADDR
|
|
* RTMGRP_LINK */
|
|
|
|
if (bind (sock, (struct sockaddr *) &sa, sizeof (sa)) < 0)
|
|
return GINT_TO_POINTER (-1);
|
|
|
|
return GINT_TO_POINTER (sock);
|
|
}
|
|
|
|
int
|
|
ReadEvents (gpointer sock, gpointer buffer, gint32 count, gint32 size)
|
|
{
|
|
struct nlmsghdr *nlp;
|
|
struct rtmsg *rtp;
|
|
int rtl;
|
|
struct rtattr *rtap;
|
|
int result;
|
|
int s;
|
|
|
|
NL_DEBUG_PRINT ("ENTER ReadEvents()");
|
|
result = EVT_NONE;
|
|
s = GPOINTER_TO_INT (sock);
|
|
/* This socket is not found by IO layer, so we do everything here */
|
|
if (count == 0) {
|
|
while ((count = recv (s, buffer, size, 0)) == -1 && errno == EINTR);
|
|
if (count <= 0) {
|
|
NL_DEBUG_PRINT ("EXIT ReadEvents()");
|
|
return result;
|
|
}
|
|
}
|
|
for (nlp = (struct nlmsghdr *) buffer; NLMSG_OK (nlp, count); nlp = NLMSG_NEXT (nlp, count)) {
|
|
int family;
|
|
int addr_length;
|
|
int msg_type;
|
|
int table;
|
|
#ifdef NL_DEBUG
|
|
int protocol;
|
|
int scope;
|
|
#endif
|
|
int rtm_type;
|
|
gboolean have_dst;
|
|
gboolean have_src;
|
|
gboolean have_pref_src;
|
|
gboolean have_gw;
|
|
char dst [ADDR_BYTE_LENGTH];
|
|
char src [ADDR_BYTE_LENGTH];
|
|
char pref_src [ADDR_BYTE_LENGTH];
|
|
char gw [ADDR_BYTE_LENGTH];
|
|
|
|
msg_type = nlp->nlmsg_type;
|
|
NL_DEBUG_PRINT ("TYPE: %d %s", msg_type, FIND_RT_TYPE_NAME (msg_type));
|
|
if (msg_type != RTM_NEWROUTE && msg_type != RTM_DELROUTE)
|
|
continue;
|
|
|
|
rtp = (struct rtmsg *) NLMSG_DATA (nlp);
|
|
family = rtp->rtm_family;
|
|
#ifdef AF_INET6
|
|
if (family != AF_INET && family != AF_INET6) {
|
|
#else
|
|
if (family != AF_INET) {
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
addr_length = (family == AF_INET) ? 4 : 16;
|
|
table = rtp->rtm_table;
|
|
#ifdef NL_DEBUG
|
|
protocol = rtp->rtm_protocol;
|
|
scope = rtp->rtm_scope;
|
|
#endif
|
|
rtm_type = rtp->rtm_type;
|
|
NL_DEBUG_PRINT ("\tRTMSG table: %d %s", table, FIND_RT_TABLE_NAME (table));
|
|
if (table != RT_TABLE_MAIN && table != RT_TABLE_LOCAL)
|
|
continue;
|
|
|
|
NL_DEBUG_PRINT ("\tRTMSG protocol: %d %s", protocol, FIND_RTM_PROTO_NAME (protocol));
|
|
NL_DEBUG_PRINT ("\tRTMSG scope: %d %s", scope, FIND_RTM_SCOPE_NAME (scope));
|
|
NL_DEBUG_PRINT ("\tRTMSG type: %d %s", rtm_type, FIND_RTM_TYPE_NAME (rtm_type));
|
|
|
|
rtap = (struct rtattr *) RTM_RTA (rtp);
|
|
rtl = RTM_PAYLOAD (nlp);
|
|
// loop & get every attribute
|
|
//
|
|
//
|
|
// NEW_ROUTE
|
|
// table = RT_TABLE_LOCAL, Scope = HOST + pref.src == src + type=LOCAL -> new if addr
|
|
// RT_TABLE_MAIN, Scope = Universe, unicast, gateway exists -> NEW default route
|
|
// DEL_ROUTE
|
|
// table = RT_TABLE_LOCAL, Scope = HOST, perfsrc = dst + type=LOCAL -> if addr deleted
|
|
// RT_TABLE_MAIN - DELROUTE + unicast -> event (gw down?)
|
|
have_dst = have_src = have_pref_src = have_gw = FALSE;
|
|
for(; RTA_OK (rtap, rtl); rtap = RTA_NEXT(rtap, rtl)) {
|
|
char *data;
|
|
#ifdef NL_DEBUG
|
|
char ip [ADDR_STR_LENGTH];
|
|
#endif
|
|
|
|
NL_DEBUG_PRINT ("\tAttribute: %d %d (%s)", rtap->rta_len, rtap->rta_type, FIND_RTM_ATTRS_NAME (rtap->rta_type));
|
|
data = RTA_DATA (rtap);
|
|
switch (rtap->rta_type) {
|
|
case RTA_DST:
|
|
have_dst = TRUE;
|
|
memcpy (dst, data, addr_length);
|
|
NL_DEBUG_STMT (
|
|
*ip = 0;
|
|
inet_ntop (family, RTA_DATA (rtap), ip, sizeof (ip));
|
|
NL_DEBUG_PRINT ("\t\tDst: %s", ip);
|
|
);
|
|
break;
|
|
case RTA_PREFSRC:
|
|
have_pref_src = TRUE;
|
|
memcpy (pref_src, data, addr_length);
|
|
NL_DEBUG_STMT (
|
|
*ip = 0;
|
|
inet_ntop (family, RTA_DATA (rtap), ip, sizeof (ip));
|
|
NL_DEBUG_PRINT ("\t\tPref. Src.: %s", ip);
|
|
);
|
|
break;
|
|
case RTA_SRC:
|
|
have_src = TRUE;
|
|
memcpy (src, data, addr_length);
|
|
NL_DEBUG_STMT (
|
|
*ip = 0;
|
|
inet_ntop (family, RTA_DATA (rtap), ip, sizeof (ip));
|
|
NL_DEBUG_PRINT ("\tSrc: %s", ip);
|
|
);
|
|
break;
|
|
case RTA_GATEWAY:
|
|
have_gw = TRUE;
|
|
memcpy (gw, data, addr_length);
|
|
NL_DEBUG_STMT (
|
|
*ip = 0;
|
|
inet_ntop (family, RTA_DATA (rtap), ip, sizeof (ip));
|
|
NL_DEBUG_PRINT ("\t\tGateway: %s", ip);
|
|
);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if (msg_type == RTM_NEWROUTE) {
|
|
if (table == RT_TABLE_MAIN) {
|
|
NL_DEBUG_PRINT ("NEWROUTE: Availability changed");
|
|
result |= EVT_AVAILABILITY;
|
|
} else if (table == RT_TABLE_LOCAL) {
|
|
NL_DEBUG_PRINT ("NEWROUTE: new IP");
|
|
if (have_dst && have_pref_src && memcmp (dst, pref_src, addr_length) == 0)
|
|
result |= EVT_ADDRESS;
|
|
}
|
|
} else if (msg_type == RTM_DELROUTE) {
|
|
if (table == RT_TABLE_MAIN) {
|
|
if (rtm_type == RTN_UNICAST && (have_dst || have_pref_src)) {
|
|
result |= EVT_AVAILABILITY;
|
|
NL_DEBUG_PRINT ("DELROUTE: Availability changed");
|
|
}
|
|
} else if (table == RT_TABLE_LOCAL) {
|
|
if (have_dst && have_pref_src && memcmp (dst, pref_src, addr_length) == 0) {
|
|
result |= EVT_ADDRESS;
|
|
NL_DEBUG_PRINT ("DELROUTE: deleted IP");
|
|
}
|
|
}
|
|
}
|
|
while ((count = recv (s, buffer, size, 0)) == -1 && errno == EINTR);
|
|
if (count <= 0) {
|
|
NL_DEBUG_PRINT ("EXIT ReadEvents() -> %d", result);
|
|
return result;
|
|
}
|
|
nlp = (struct nlmsghdr *) buffer;
|
|
}
|
|
NL_DEBUG_PRINT ("EXIT ReadEvents() -> %d", result);
|
|
return result;
|
|
}
|
|
|
|
gpointer
|
|
CloseNLSocket (gpointer sock)
|
|
{
|
|
return GINT_TO_POINTER (close (GPOINTER_TO_INT (sock)));
|
|
}
|
|
#else
|
|
int
|
|
ReadEvents (gpointer sock, gpointer buffer, gint32 count, gint32 size)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
gpointer
|
|
CreateNLSocket (void)
|
|
{
|
|
return GINT_TO_POINTER (-1);
|
|
}
|
|
|
|
gpointer
|
|
CloseNLSocket (gpointer sock)
|
|
{
|
|
return GINT_TO_POINTER (-1);
|
|
}
|
|
#endif /* linux/netlink.h + linux/rtnetlink.h */
|
|
|