1 /*
2  * lib/cache_mngr.c	Cache Manager
3  *
4  *	This library is free software; you can redistribute it and/or
5  *	modify it under the terms of the GNU Lesser General Public
6  *	License as published by the Free Software Foundation version 2.1
7  *	of the License.
8  *
9  * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
10  */
11 
12 /**
13  * @ingroup cache_mngt
14  * @defgroup cache_mngr Manager
15  * @brief Helps keeping caches up to date.
16  *
17  * The purpose of a cache manager is to keep track of caches and
18  * automatically receive event notifications to keep the caches
19  * up to date with the kernel state. Each manager has exactly one
20  * netlink socket assigned which limits the scope of each manager
21  * to exactly one netlink family. Therefore all caches committed
22  * to a manager must be part of the same netlink family. Due to the
23  * nature of a manager, it is not possible to have a cache maintain
24  * two instances of the same cache type. The socket is subscribed
25  * to the event notification group of each cache and also put into
26  * non-blocking mode. Functions exist to poll() on the socket to
27  * wait for new events to be received.
28  *
29  * @code
30  * App       libnl                        Kernel
31  *        |                            |
32  *            +-----------------+        [ notification, link change ]
33  *        |   |  Cache Manager  |      | [   (IFF_UP | IFF_RUNNING)  ]
34  *            |                 |                |
35  *        |   |   +------------+|      |         |  [ notification, new addr ]
36  *    <-------|---| route/link |<-------(async)--+  [  10.0.1.1/32 dev eth1  ]
37  *        |   |   +------------+|      |                      |
38  *            |   +------------+|                             |
39  *    <---|---|---| route/addr |<------|-(async)--------------+
40  *            |   +------------+|
41  *        |   |   +------------+|      |
42  *    <-------|---| ...        ||
43  *        |   |   +------------+|      |
44  *            +-----------------+
45  *        |                            |
46  * @endcode
47  *
48  * @par 1) Creating a new cache manager
49  * @code
50  * struct nl_cache_mngr *mngr;
51  *
52  * // Allocate a new cache manager for RTNETLINK and automatically
53  * // provide the caches added to the manager.
54  * mngr = nl_cache_mngr_alloc(NETLINK_ROUTE, NL_AUTO_PROVIDE);
55  * @endcode
56  *
57  * @par 2) Keep track of a cache
58  * @code
59  * struct nl_cache *cache;
60  *
61  * // Create a new cache for links/interfaces and ask the manager to
62  * // keep it up to date for us. This will trigger a full dump request
63  * // to initially fill the cache.
64  * cache = nl_cache_mngr_add(mngr, "route/link");
65  * @endcode
66  *
67  * @par 3) Make the manager receive updates
68  * @code
69  * // Give the manager the ability to receive updates, will call poll()
70  * // with a timeout of 5 seconds.
71  * if (nl_cache_mngr_poll(mngr, 5000) > 0) {
72  *         // Manager received at least one update, dump cache?
73  *         nl_cache_dump(cache, ...);
74  * }
75  * @endcode
76  *
77  * @par 4) Release cache manager
78  * @code
79  * nl_cache_mngr_free(mngr);
80  * @endcode
81  * @{
82  */
83 
84 #include <netlink-local.h>
85 #include <netlink/netlink.h>
86 #include <netlink/cache.h>
87 #include <netlink/utils.h>
88 
include_cb(struct nl_object * obj,struct nl_parser_param * p)89 static int include_cb(struct nl_object *obj, struct nl_parser_param *p)
90 {
91 	struct nl_cache_assoc *ca = p->pp_arg;
92 
93 	NL_DBG(2, "Including object %p into cache %p\n", obj, ca->ca_cache);
94 #ifdef NL_DEBUG
95 	if (nl_debug >= 4)
96 		nl_object_dump(obj, &nl_debug_dp);
97 #endif
98 	return nl_cache_include(ca->ca_cache, obj, ca->ca_change, ca->ca_change_data);
99 }
100 
event_input(struct nl_msg * msg,void * arg)101 static int event_input(struct nl_msg *msg, void *arg)
102 {
103 	struct nl_cache_mngr *mngr = arg;
104 	int protocol = nlmsg_get_proto(msg);
105 	int type = nlmsg_hdr(msg)->nlmsg_type;
106 	struct nl_cache_ops *ops;
107 	int i, n;
108 	struct nl_parser_param p = {
109 		.pp_cb = include_cb,
110 	};
111 
112 	NL_DBG(2, "Cache manager %p, handling new message %p as event\n",
113 	       mngr, msg);
114 #ifdef NL_DEBUG
115 	if (nl_debug >= 4)
116 		nl_msg_dump(msg, stderr);
117 #endif
118 
119 	if (mngr->cm_protocol != protocol)
120 		BUG();
121 
122 	for (i = 0; i < mngr->cm_nassocs; i++) {
123 		if (mngr->cm_assocs[i].ca_cache) {
124 			ops = mngr->cm_assocs[i].ca_cache->c_ops;
125 			for (n = 0; ops->co_msgtypes[n].mt_id >= 0; n++)
126 				if (ops->co_msgtypes[n].mt_id == type)
127 					goto found;
128 		}
129 	}
130 
131 	return NL_SKIP;
132 
133 found:
134 	NL_DBG(2, "Associated message %p to cache %p\n",
135 	       msg, mngr->cm_assocs[i].ca_cache);
136 	p.pp_arg = &mngr->cm_assocs[i];
137 
138 	return nl_cache_parse(ops, NULL, nlmsg_hdr(msg), &p);
139 }
140 
141 /**
142  * Allocate new cache manager
143  * @arg sk		Netlink socket.
144  * @arg protocol	Netlink Protocol this manager is used for
145  * @arg flags		Flags
146  *
147  * @return Newly allocated cache manager or NULL on failure.
148  */
nl_cache_mngr_alloc(struct nl_sock * sk,int protocol,int flags,struct nl_cache_mngr ** result)149 int nl_cache_mngr_alloc(struct nl_sock *sk, int protocol, int flags,
150 			struct nl_cache_mngr **result)
151 {
152 	struct nl_cache_mngr *mngr;
153 	int err = -NLE_NOMEM;
154 
155 	if (sk == NULL)
156 		BUG();
157 
158 	mngr = calloc(1, sizeof(*mngr));
159 	if (!mngr)
160 		goto errout;
161 
162 	mngr->cm_handle = sk;
163 	mngr->cm_nassocs = 32;
164 	mngr->cm_protocol = protocol;
165 	mngr->cm_flags = flags;
166 	mngr->cm_assocs = calloc(mngr->cm_nassocs,
167 				 sizeof(struct nl_cache_assoc));
168 	if (!mngr->cm_assocs)
169 		goto errout;
170 
171 	nl_socket_modify_cb(mngr->cm_handle, NL_CB_VALID, NL_CB_CUSTOM,
172 			    event_input, mngr);
173 
174 	/* Required to receive async event notifications */
175 	nl_socket_disable_seq_check(mngr->cm_handle);
176 
177 	if ((err = nl_connect(mngr->cm_handle, protocol) < 0))
178 		goto errout;
179 
180 	if ((err = nl_socket_set_nonblocking(mngr->cm_handle) < 0))
181 		goto errout;
182 
183 	NL_DBG(1, "Allocated cache manager %p, protocol %d, %d caches\n",
184 	       mngr, protocol, mngr->cm_nassocs);
185 
186 	*result = mngr;
187 	return 0;
188 
189 errout:
190 	nl_cache_mngr_free(mngr);
191 	return err;
192 }
193 
194 /**
195  * Add cache responsibility to cache manager
196  * @arg mngr		Cache manager.
197  * @arg name		Name of cache to keep track of
198  * @arg cb		Function to be called upon changes.
199  * @arg result		Pointer to store added cache.
200  *
201  * Allocates a new cache of the specified type and adds it to the manager.
202  * The operation will trigger a full dump request from the kernel to
203  * initially fill the contents of the cache. The manager will subscribe
204  * to the notification group of the cache to keep track of any further
205  * changes.
206  *
207  * @return 0 on success or a negative error code.
208  */
nl_cache_mngr_add(struct nl_cache_mngr * mngr,const char * name,change_func_t cb,void * data,struct nl_cache ** result)209 int nl_cache_mngr_add(struct nl_cache_mngr *mngr, const char *name,
210 		      change_func_t cb, void *data, struct nl_cache **result)
211 {
212 	struct nl_cache_ops *ops;
213 	struct nl_cache *cache;
214 	struct nl_af_group *grp;
215 	int err, i;
216 
217 	ops = nl_cache_ops_lookup(name);
218 	if (!ops)
219 		return -NLE_NOCACHE;
220 
221 	if (ops->co_protocol != mngr->cm_protocol)
222 		return -NLE_PROTO_MISMATCH;
223 
224 	if (ops->co_groups == NULL)
225 		return -NLE_OPNOTSUPP;
226 
227 	for (i = 0; i < mngr->cm_nassocs; i++)
228 		if (mngr->cm_assocs[i].ca_cache &&
229 		    mngr->cm_assocs[i].ca_cache->c_ops == ops)
230 			return -NLE_EXIST;
231 
232 retry:
233 	for (i = 0; i < mngr->cm_nassocs; i++)
234 		if (!mngr->cm_assocs[i].ca_cache)
235 			break;
236 
237 	if (i >= mngr->cm_nassocs) {
238 		mngr->cm_nassocs += 16;
239 		mngr->cm_assocs = realloc(mngr->cm_assocs,
240 					  mngr->cm_nassocs *
241 					  sizeof(struct nl_cache_assoc));
242 		if (mngr->cm_assocs == NULL)
243 			return -NLE_NOMEM;
244 		else {
245 			NL_DBG(1, "Increased capacity of cache manager %p " \
246 				  "to %d\n", mngr, mngr->cm_nassocs);
247 			goto retry;
248 		}
249 	}
250 
251 	cache = nl_cache_alloc(ops);
252 	if (!cache)
253 		return -NLE_NOMEM;
254 
255 	for (grp = ops->co_groups; grp->ag_group; grp++) {
256 		err = nl_socket_add_membership(mngr->cm_handle, grp->ag_group);
257 		if (err < 0)
258 			goto errout_free_cache;
259 	}
260 
261 	err = nl_cache_refill(mngr->cm_handle, cache);
262 	if (err < 0)
263 		goto errout_drop_membership;
264 
265 	mngr->cm_assocs[i].ca_cache = cache;
266 	mngr->cm_assocs[i].ca_change = cb;
267 	mngr->cm_assocs[i].ca_change_data = data;
268 
269 	if (mngr->cm_flags & NL_AUTO_PROVIDE)
270 		nl_cache_mngt_provide(cache);
271 
272 	NL_DBG(1, "Added cache %p <%s> to cache manager %p\n",
273 	       cache, nl_cache_name(cache), mngr);
274 
275 	*result = cache;
276 	return 0;
277 
278 errout_drop_membership:
279 	for (grp = ops->co_groups; grp->ag_group; grp++)
280 		nl_socket_drop_membership(mngr->cm_handle, grp->ag_group);
281 errout_free_cache:
282 	nl_cache_free(cache);
283 
284 	return err;
285 }
286 
287 /**
288  * Get file descriptor
289  * @arg mngr		Cache Manager
290  *
291  * Get the file descriptor of the socket associated to the manager.
292  * This can be used to change socket options or monitor activity
293  * using poll()/select().
294  */
nl_cache_mngr_get_fd(struct nl_cache_mngr * mngr)295 int nl_cache_mngr_get_fd(struct nl_cache_mngr *mngr)
296 {
297 	return nl_socket_get_fd(mngr->cm_handle);
298 }
299 
300 /**
301  * Check for event notifications
302  * @arg mngr		Cache Manager
303  * @arg timeout		Upper limit poll() will block, in milliseconds.
304  *
305  * Causes poll() to be called to check for new event notifications
306  * being available. Automatically receives and handles available
307  * notifications.
308  *
309  * This functionally is ideally called regularly during an idle
310  * period.
311  *
312  * @return A positive value if at least one update was handled, 0
313  *         for none, or a  negative error code.
314  */
nl_cache_mngr_poll(struct nl_cache_mngr * mngr,int timeout)315 int nl_cache_mngr_poll(struct nl_cache_mngr *mngr, int timeout)
316 {
317 	int ret;
318 	struct pollfd fds = {
319 		.fd = nl_socket_get_fd(mngr->cm_handle),
320 		.events = POLLIN,
321 	};
322 
323 	NL_DBG(3, "Cache manager %p, poll() fd %d\n", mngr, fds.fd);
324 	ret = poll(&fds, 1, timeout);
325 	NL_DBG(3, "Cache manager %p, poll() returned %d\n", mngr, ret);
326 	if (ret < 0)
327 		return -nl_syserr2nlerr(errno);
328 
329 	if (ret == 0)
330 		return 0;
331 
332 	return nl_cache_mngr_data_ready(mngr);
333 }
334 
335 /**
336  * Receive available event notifications
337  * @arg mngr		Cache manager
338  *
339  * This function can be called if the socket associated to the manager
340  * contains updates to be received. This function should not be used
341  * if nl_cache_mngr_poll() is used.
342  *
343  * @return A positive value if at least one update was handled, 0
344  *         for none, or a  negative error code.
345  */
nl_cache_mngr_data_ready(struct nl_cache_mngr * mngr)346 int nl_cache_mngr_data_ready(struct nl_cache_mngr *mngr)
347 {
348 	int err;
349 
350 	err = nl_recvmsgs_default(mngr->cm_handle);
351 	if (err < 0)
352 		return err;
353 
354 	return 1;
355 }
356 
357 /**
358  * Free cache manager and all caches.
359  * @arg mngr		Cache manager.
360  *
361  * Release all resources after usage of a cache manager.
362  */
nl_cache_mngr_free(struct nl_cache_mngr * mngr)363 void nl_cache_mngr_free(struct nl_cache_mngr *mngr)
364 {
365 	int i;
366 
367 	if (!mngr)
368 		return;
369 
370 	if (mngr->cm_handle)
371 		nl_close(mngr->cm_handle);
372 
373 	for (i = 0; i < mngr->cm_nassocs; i++)
374 		if (mngr->cm_assocs[i].ca_cache)
375 			nl_cache_free(mngr->cm_assocs[i].ca_cache);
376 
377 	free(mngr->cm_assocs);
378 	free(mngr);
379 
380 	NL_DBG(1, "Cache manager %p freed\n", mngr);
381 }
382 
383 /** @} */
384