[NetworkInformation] Support network IP/status events
[mono.git] / support / nl.c
1 #include <config.h>
2 #include "nl.h"
3
4 #if defined(HAVE_LINUX_NETLINK_H) && defined(HAVE_LINUX_RTNETLINK_H)
5
6 #include <errno.h>
7 #include <unistd.h>
8 #include <fcntl.h>
9 #include <sys/types.h>
10 #include <sys/socket.h>
11 #include <arpa/inet.h>
12 #include <linux/netlink.h>
13 #include <linux/rtnetlink.h>
14
15 #undef NL_DEBUG
16 #define NL_DEBUG_PRINT(...)
17 /*
18 #define NL_DEBUG 1
19 #define NL_DEBUG_PRINT(...) g_message(__VA_ARGS__)
20 */
21
22 enum event_type {
23         EVT_NONE = 0,
24 #define EVT_NONE 0
25         EVT_AVAILABILITY = 1 << 0,
26 #define EVT_AVAILABILITY (1 << 0)
27         EVT_ADDRESS = 1 << 1,
28 #define EVT_ADDRESS (1 << 1)
29         EVT_ALL = EVT_AVAILABILITY | EVT_ADDRESS
30 #define EVT_ALL (EVT_AVAILABILITY | EVT_ADDRESS)
31 };
32
33 #ifdef NL_DEBUG
34 typedef struct {
35         int value;
36         const char *name;
37 } value2name_t;
38
39 #define INIT(x) { x, #x }
40 #define FIND_NAME(a, b) value_to_name (a, b)
41
42 #define FIND_RT_TYPE_NAME(b) FIND_NAME (rt_types, b)
43 static value2name_t rt_types [] = {
44         INIT (RTM_NEWROUTE),
45         INIT (RTM_DELROUTE),
46         INIT (RTM_GETROUTE),
47         INIT (RTM_NEWADDR),
48         INIT (RTM_DELADDR),
49         INIT (RTM_GETADDR),
50         INIT (RTM_NEWLINK),
51         INIT (RTM_GETLINK),
52         INIT (RTM_DELLINK),
53         INIT (RTM_NEWNEIGH),
54         INIT (RTM_GETNEIGH),
55         INIT (RTM_DELNEIGH),
56         {0, NULL}
57 };
58
59 #define FIND_RTM_TYPE_NAME(b) FIND_NAME (rtm_types, b)
60 static value2name_t rtm_types [] = {
61         INIT (RTN_UNSPEC),
62         INIT (RTN_UNICAST),
63         INIT (RTN_LOCAL),
64         INIT (RTN_BROADCAST),
65         INIT (RTN_ANYCAST),
66         INIT (RTN_MULTICAST),
67         INIT (RTN_BLACKHOLE),
68         INIT (RTN_UNREACHABLE),
69         INIT (RTN_PROHIBIT),
70         INIT (RTN_THROW),
71         INIT (RTN_NAT),
72         INIT (RTN_XRESOLVE),
73         {0, NULL}
74 };
75
76 #define FIND_RTM_PROTO_NAME(b) FIND_NAME (rtm_protocols, b)
77 static value2name_t rtm_protocols[] = {
78         INIT (RTPROT_UNSPEC),
79         INIT (RTPROT_REDIRECT),
80         INIT (RTPROT_KERNEL),
81         INIT (RTPROT_BOOT),
82         INIT (RTPROT_STATIC),
83         {0, NULL}
84 };
85
86 #define FIND_RTM_SCOPE_NAME(b) FIND_NAME (rtm_scopes, b)
87 static value2name_t rtm_scopes [] = {
88         INIT (RT_SCOPE_UNIVERSE),
89         INIT (RT_SCOPE_SITE),
90         INIT (RT_SCOPE_LINK),
91         INIT (RT_SCOPE_HOST),
92         INIT (RT_SCOPE_NOWHERE),
93         {0, NULL}
94 };
95
96 #define FIND_RTM_ATTRS_NAME(b) FIND_NAME (rtm_attrs, b)
97 static value2name_t rtm_attrs [] = {
98         INIT (RTA_UNSPEC),
99         INIT (RTA_DST),
100         INIT (RTA_SRC),
101         INIT (RTA_IIF),
102         INIT (RTA_OIF),
103         INIT (RTA_GATEWAY),
104         INIT (RTA_PRIORITY),
105         INIT (RTA_PREFSRC),
106         INIT (RTA_METRICS),
107         INIT (RTA_MULTIPATH),
108         INIT (RTA_PROTOINFO),
109         INIT (RTA_FLOW),
110         INIT (RTA_CACHEINFO),
111         INIT (RTA_SESSION),
112         INIT (RTA_MP_ALGO),
113         INIT (RTA_TABLE),
114         {0, NULL}
115 };
116
117 #define FIND_RT_TABLE_NAME(b) FIND_NAME (rtm_tables, b)
118 static value2name_t rtm_tables [] = {
119         INIT (RT_TABLE_UNSPEC),
120         INIT (RT_TABLE_COMPAT),
121         INIT (RT_TABLE_DEFAULT),
122         INIT (RT_TABLE_MAIN),
123         INIT (RT_TABLE_LOCAL),
124         {0,0}
125 };
126
127 static const char *
128 value_to_name (value2name_t *tbl, int value)
129 {
130         static char auto_name [16];
131
132         while (tbl->name) {
133                 if (tbl->value == value)
134                         return tbl->name;
135                 tbl++;
136         }
137         snprintf (auto_name, sizeof (auto_name), "#%d", value);
138         return auto_name;
139 }
140 #endif /* NL_DEBUG */
141
142 int
143 CreateNLSocket (void)
144 {
145         int sock;
146         struct sockaddr_nl sa;
147         int ret;
148         
149         sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
150
151         ret = fcntl (sock, F_GETFL, 0);
152 #ifdef O_NONBLOCK
153         if (ret != -1) {
154                 ret |= O_NONBLOCK;
155                 ret = fcntl (sock, F_SETFL, ret);
156                 if (ret < 0)
157                         return -1;
158         }
159 #endif
160
161         memset (&sa, 0, sizeof (sa));
162         if (sock < 0)
163                 return -1;
164         sa.nl_family = AF_NETLINK;
165         sa.nl_pid = getpid ();
166         sa.nl_groups = RTMGRP_IPV4_ROUTE | RTMGRP_IPV6_ROUTE | RTMGRP_NOTIFY;
167         /* RTNLGRP_IPV4_IFADDR | RTNLGRP_IPV6_IFADDR
168          * RTMGRP_LINK */
169
170         if (bind (sock, (struct sockaddr *) &sa, sizeof (sa)) < 0)
171                 return -1;
172
173         return sock;
174 }
175
176 int
177 ReadEvents (gpointer sock, gpointer buffer, gint32 count, gint32 size)
178 {
179         struct nlmsghdr *nlp;
180         struct rtmsg *rtp;
181         int rtl;
182         struct rtattr *rtap;
183         int result;
184         int s;
185
186         NL_DEBUG_PRINT ("ENTER ReadEvents()");
187         result = EVT_NONE;
188         s = GPOINTER_TO_INT (sock);
189         /* This socket is not found by IO layer, so we do everything here */
190         if (count == 0) {
191                 while ((count = recv (s, buffer, size, 0)) == -1 && errno == EINTR);
192                 if (count <= 0) {
193                         NL_DEBUG_PRINT ("EXIT ReadEvents()");
194                         return result;
195                 }
196         }
197         for (nlp = (struct nlmsghdr *) buffer; NLMSG_OK (nlp, count); nlp = NLMSG_NEXT (nlp, count)) {
198                 int family;
199                 int addr_length;
200                 int msg_type;
201                 int table;
202                 int protocol;
203                 int scope;
204                 int rtm_type;
205                 gboolean have_dst;
206                 gboolean have_src;
207                 gboolean have_pref_src;
208                 gboolean have_gw;
209 #ifdef AF_INET6
210                 char dst [16];
211                 char src [16];
212                 char pref_src [16];
213                 char gw [16];
214 #else
215                 char dst [4];
216                 char src [4];
217                 char pref_src [4];
218                 char gw [4];
219 #endif
220
221                 msg_type = nlp->nlmsg_type;
222                 NL_DEBUG_PRINT ("TYPE: %d %s", msg_type, FIND_RT_TYPE_NAME (msg_type));
223                 if (msg_type != RTM_NEWROUTE && msg_type != RTM_DELROUTE)
224                         continue;
225
226                 rtp = (struct rtmsg *) NLMSG_DATA (nlp);
227                 family = rtp->rtm_family;
228 #ifdef AF_INET6
229                 addr_length = (family == AF_INET) ? 4 : 16;
230 #else
231                 addr_length = 4;
232 #endif
233                 table = rtp->rtm_table;
234                 protocol = rtp->rtm_protocol;
235                 scope = rtp->rtm_scope;
236                 rtm_type = rtp->rtm_type;
237                 NL_DEBUG_PRINT ("\tRTMSG table: %d %s", table, FIND_RT_TABLE_NAME (table));
238                 if (table != RT_TABLE_MAIN && table != RT_TABLE_LOCAL)
239                         continue;
240
241                 NL_DEBUG_PRINT ("\tRTMSG protocol: %d %s", protocol, FIND_RTM_PROTO_NAME (protocol));
242                 NL_DEBUG_PRINT ("\tRTMSG scope: %d %s", scope, FIND_RTM_SCOPE_NAME (scope));
243                 NL_DEBUG_PRINT ("\tRTMSG type: %d %s", rtm_type, FIND_RTM_TYPE_NAME (rtm_type));
244
245                 rtap = (struct rtattr *) RTM_RTA (rtp);
246                 rtl = RTM_PAYLOAD (nlp);
247                 // loop & get every attribute
248                 //
249                 // 
250                 // NEW_ROUTE
251                 //      table = RT_TABLE_LOCAL, Scope = HOST + pref.src == src  + type=LOCAL -> new if addr
252                 //      RT_TABLE_MAIN, Scope = Universe, unicast, gateway exists -> NEW default route
253                 // DEL_ROUTE
254                 //      table = RT_TABLE_LOCAL, Scope = HOST, perfsrc = dst  + type=LOCAL -> if addr deleted
255                 //      RT_TABLE_MAIN - DELROUTE + unicast -> event (gw down?)
256                 have_dst = have_src = have_pref_src = have_gw = FALSE;
257                 for(; RTA_OK (rtap, rtl); rtap = RTA_NEXT(rtap, rtl)) {
258                         char *data;
259 #ifdef NL_DEBUG
260 #ifdef AF_INET6
261                         char ip [INET6_ADDRSTRLEN];
262                         int ip_length = INET6_ADDRSTRLEN;
263 #else
264                         char ip [INET_ADDRSTRLEN];
265                         int ip_length = INET_ADDRSTRLEN;
266 #endif
267 #endif
268
269                         NL_DEBUG_PRINT ("\tAttribute: %d %d (%s)", rtap->rta_len, rtap->rta_type, FIND_RTM_ATTRS_NAME (rtap->rta_type));
270                         data = RTA_DATA (rtap);
271                         switch (rtap->rta_type) {
272                         case RTA_DST:
273                                 have_dst = TRUE;
274                                 memcpy (dst, data, addr_length);
275 #ifdef NL_DEBUG
276                                 *ip = 0;
277                                 inet_ntop (family, RTA_DATA (rtap), ip, ip_length);
278                                 NL_DEBUG_PRINT ("\t\tDst: %s", ip);
279 #endif
280                                 break;
281                         case RTA_PREFSRC:
282                                 have_pref_src = TRUE;
283                                 memcpy (pref_src, data, addr_length);
284 #ifdef NL_DEBUG
285                                 *ip = 0;
286                                 inet_ntop (family, RTA_DATA (rtap), ip, ip_length);
287                                 NL_DEBUG_PRINT ("\t\tPref. Src.: %s", ip);
288 #endif
289                                 break;
290                         case RTA_SRC:
291                                 have_src = TRUE;
292                                 memcpy (src, data, addr_length);
293 #ifdef NL_DEBUG
294                                 *ip = 0;
295                                 inet_ntop (family, RTA_DATA (rtap), ip, ip_length);
296                                 NL_DEBUG_PRINT ("\tSrc: %s", ip);
297 #endif
298                                 break;
299                         case RTA_GATEWAY:
300                                 have_gw = TRUE;
301                                 memcpy (gw, data, addr_length);
302 #ifdef NL_DEBUG
303                                 *ip = 0;
304                                 inet_ntop (family, RTA_DATA (rtap), ip, ip_length);
305                                 NL_DEBUG_PRINT ("\t\tGateway: %s", ip);
306 #endif
307                                 break;
308                         default:
309                                 break;
310                         }
311                 }
312                 if (msg_type == RTM_NEWROUTE) {
313                         if (table == RT_TABLE_MAIN) {
314                                 NL_DEBUG_PRINT ("NEWROUTE: Availability changed");
315                                 result |= EVT_AVAILABILITY;
316                         } else if (table == RT_TABLE_LOCAL) {
317                                 NL_DEBUG_PRINT ("NEWROUTE: new IP");
318                                 if (have_dst && have_pref_src && memcmp (dst, pref_src, addr_length) == 0)
319                                         result |= EVT_ADDRESS;
320                         }
321                 } else if (msg_type == RTM_DELROUTE) {
322                         if (table == RT_TABLE_MAIN) {
323                                 if (rtm_type == RTN_UNICAST && (have_dst || have_pref_src)) {
324                                         result |= EVT_AVAILABILITY;
325                                         NL_DEBUG_PRINT ("DELROUTE: Availability changed");
326                                 }
327                         } else if (table == RT_TABLE_LOCAL) {
328                                 if (have_dst && have_pref_src && memcmp (dst, pref_src, addr_length) == 0) {
329                                         result |= EVT_ADDRESS;
330                                         NL_DEBUG_PRINT ("DELROUTE: deleted IP");
331                                 }
332                         }
333                 }
334                 while ((count = recv (s, buffer, size, 0)) == -1 && errno == EINTR);
335                 if (count <= 0) {
336                         NL_DEBUG_PRINT ("EXIT ReadEvents() -> %d", result);
337                         return result;
338                 }
339                 nlp = (struct nlmsghdr *) buffer;
340         }
341         NL_DEBUG_PRINT ("EXIT ReadEvents() -> %d", result);
342         return result;
343 }
344
345 int
346 CloseNLSocket (gpointer sock)
347 {
348         return close (GPOINTER_TO_INT (sock));
349 }
350 #else
351 int
352 GetNLSocket (void)
353 {
354         return -1;
355 }
356
357 int
358 ReadEvents (gpointer sock, gpointer buffer, gint32 count, gint32 size)
359 {
360         return 0;
361 }
362
363 int
364 CloseNLSocket (gpointer sock)
365 {
366         return -1;
367 }
368 #endif /* linux/netlink.h + linux/rtnetlink.h */
369