/* * Network availability and change notifications for linux. * * Authors: * Gonzalo Paniagua Javier (gonzalo@novell.com) * * Copyright (c) Novell, Inc. 2011 */ #ifdef HAVE_CONFIG_H #include #endif #include "nl.h" #if defined(HAVE_LINUX_NETLINK_H) && defined(HAVE_LINUX_RTNETLINK_H) #include #include #include #include #include #include #include #include #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 */ int 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 -1; } memset (&sa, 0, sizeof (sa)); if (sock < 0) return -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 -1; return 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; } int CloseNLSocket (gpointer sock) { return close (GPOINTER_TO_INT (sock)); } #else int GetNLSocket (void) { return -1; } int ReadEvents (gpointer sock, gpointer buffer, gint32 count, gint32 size) { return 0; } int CreateNLSocket (void) { return -1; } int CloseNLSocket (gpointer sock) { return -1; } #endif /* linux/netlink.h + linux/rtnetlink.h */