Import Upstream version 1.2.2
[quagga-debian.git] / nhrpd / nhrp_cache.c
1 /* NHRP cache
2  * Copyright (c) 2014-2015 Timo Teräs
3  *
4  * This file is free software: you may copy, redistribute and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 2 of the License, or
7  * (at your option) any later version.
8  */
9
10 #include "zebra.h"
11 #include "memory.h"
12 #include "thread.h"
13 #include "hash.h"
14 #include "nhrpd.h"
15
16 #include "netlink.h"
17
18 unsigned long nhrp_cache_counts[NHRP_CACHE_NUM_TYPES];
19
20 const char * const nhrp_cache_type_str[] = {
21         [NHRP_CACHE_INVALID]    = "invalid",
22         [NHRP_CACHE_INCOMPLETE] = "incomplete",
23         [NHRP_CACHE_NEGATIVE]   = "negative",
24         [NHRP_CACHE_CACHED]     = "cached",
25         [NHRP_CACHE_DYNAMIC]    = "dynamic",
26         [NHRP_CACHE_NHS]        = "nhs",
27         [NHRP_CACHE_STATIC]     = "static",
28         [NHRP_CACHE_LOCAL]      = "local",
29 };
30
31 static unsigned int nhrp_cache_protocol_key(void *peer_data)
32 {
33         struct nhrp_cache *p = peer_data;
34         return sockunion_hash(&p->remote_addr);
35 }
36
37 static int nhrp_cache_protocol_cmp(const void *cache_data, const void *key_data)
38 {
39         const struct nhrp_cache *a = cache_data;
40         const struct nhrp_cache *b = key_data;
41         return sockunion_same(&a->remote_addr, &b->remote_addr);
42 }
43
44 static void *nhrp_cache_alloc(void *data)
45 {
46         struct nhrp_cache *p, *key = data;
47
48         p = XMALLOC(MTYPE_NHRP_CACHE, sizeof(struct nhrp_cache));
49         if (p) {
50                 *p = (struct nhrp_cache) {
51                         .cur.type = NHRP_CACHE_INVALID,
52                         .new.type = NHRP_CACHE_INVALID,
53                         .remote_addr = key->remote_addr,
54                         .ifp = key->ifp,
55                         .notifier_list = NOTIFIER_LIST_INITIALIZER(&p->notifier_list),
56                 };
57                 nhrp_cache_counts[p->cur.type]++;
58         }
59
60         return p;
61 }
62
63 static void nhrp_cache_free(struct nhrp_cache *c)
64 {
65         struct nhrp_interface *nifp = c->ifp->info;
66
67         zassert(c->cur.type == NHRP_CACHE_INVALID && c->cur.peer == NULL);
68         zassert(c->new.type == NHRP_CACHE_INVALID && c->new.peer == NULL);
69         nhrp_cache_counts[c->cur.type]--;
70         notifier_call(&c->notifier_list, NOTIFY_CACHE_DELETE);
71         zassert(!notifier_active(&c->notifier_list));
72         hash_release(nifp->cache_hash, c);
73         XFREE(MTYPE_NHRP_CACHE, c);
74 }
75
76 struct nhrp_cache *nhrp_cache_get(struct interface *ifp, union sockunion *remote_addr, int create)
77 {
78         struct nhrp_interface *nifp = ifp->info;
79         struct nhrp_cache key;
80
81         if (!nifp->cache_hash) {
82                 nifp->cache_hash = hash_create(nhrp_cache_protocol_key, nhrp_cache_protocol_cmp);
83                 if (!nifp->cache_hash)
84                         return NULL;
85         }
86
87         key.remote_addr = *remote_addr;
88         key.ifp = ifp;
89
90         return hash_get(nifp->cache_hash, &key, create ? nhrp_cache_alloc : NULL);
91 }
92
93 static int nhrp_cache_do_free(struct thread *t)
94 {
95         struct nhrp_cache *c = THREAD_ARG(t);
96         c->t_timeout = NULL;
97         nhrp_cache_free(c);
98         return 0;
99 }
100
101 static int nhrp_cache_do_timeout(struct thread *t)
102 {
103         struct nhrp_cache *c = THREAD_ARG(t);
104         c->t_timeout = NULL;
105         if (c->cur.type != NHRP_CACHE_INVALID)
106                 nhrp_cache_update_binding(c, c->cur.type, -1, NULL, 0, NULL);
107         return 0;
108 }
109
110 static void nhrp_cache_update_route(struct nhrp_cache *c)
111 {
112         struct prefix pfx;
113         struct nhrp_peer *p = c->cur.peer;
114
115         sockunion2hostprefix(&c->remote_addr, &pfx);
116
117         if (p && nhrp_peer_check(p, 1)) {
118                 netlink_update_binding(p->ifp, &c->remote_addr, &p->vc->remote.nbma);
119                 nhrp_route_announce(1, c->cur.type, &pfx, c->ifp, NULL, c->cur.mtu);
120                 if (c->cur.type >= NHRP_CACHE_DYNAMIC) {
121                         nhrp_route_update_nhrp(&pfx, c->ifp);
122                         c->nhrp_route_installed = 1;
123                 } else if (c->nhrp_route_installed) {
124                         nhrp_route_update_nhrp(&pfx, NULL);
125                         c->nhrp_route_installed = 0;
126                 }
127                 if (!c->route_installed) {
128                         notifier_call(&c->notifier_list, NOTIFY_CACHE_UP);
129                         c->route_installed = 1;
130                 }
131         } else {
132                 if (c->nhrp_route_installed) {
133                         nhrp_route_update_nhrp(&pfx, NULL);
134                         c->nhrp_route_installed = 0;
135                 }
136                 if (c->route_installed) {
137                         sockunion2hostprefix(&c->remote_addr, &pfx);
138                         notifier_call(&c->notifier_list, NOTIFY_CACHE_DOWN);
139                         nhrp_route_announce(0, c->cur.type, &pfx, NULL, NULL, 0);
140                         c->route_installed = 0;
141                 }
142         }
143 }
144
145 static void nhrp_cache_peer_notifier(struct notifier_block *n, unsigned long cmd)
146 {
147         struct nhrp_cache *c = container_of(n, struct nhrp_cache, peer_notifier);
148
149         switch (cmd) {
150         case NOTIFY_PEER_UP:
151                 nhrp_cache_update_route(c);
152                 break;
153         case NOTIFY_PEER_DOWN:
154         case NOTIFY_PEER_IFCONFIG_CHANGED:
155                 notifier_call(&c->notifier_list, NOTIFY_CACHE_DOWN);
156                 nhrp_cache_update_binding(c, c->cur.type, -1, NULL, 0, NULL);
157                 break;
158         case NOTIFY_PEER_NBMA_CHANGING:
159                 if (c->cur.type == NHRP_CACHE_DYNAMIC)
160                         c->cur.peer->vc->abort_migration = 1;
161                 break;
162         }
163 }
164
165 static void nhrp_cache_reset_new(struct nhrp_cache *c)
166 {
167         THREAD_OFF(c->t_auth);
168         if (list_hashed(&c->newpeer_notifier.notifier_entry))
169                 nhrp_peer_notify_del(c->new.peer, &c->newpeer_notifier);
170         nhrp_peer_unref(c->new.peer);
171         memset(&c->new, 0, sizeof(c->new));
172         c->new.type = NHRP_CACHE_INVALID;
173 }
174
175 static void nhrp_cache_update_timers(struct nhrp_cache *c)
176 {
177         THREAD_OFF(c->t_timeout);
178
179         switch (c->cur.type) {
180         case NHRP_CACHE_INVALID:
181                 if (!c->t_auth)
182                         THREAD_TIMER_MSEC_ON(master, c->t_timeout, nhrp_cache_do_free, c, 10);
183                 break;
184         default:
185                 if (c->cur.expires)
186                         THREAD_TIMER_ON(master, c->t_timeout, nhrp_cache_do_timeout, c, c->cur.expires - recent_relative_time().tv_sec);
187                 break;
188         }
189 }
190
191 static void nhrp_cache_authorize_binding(struct nhrp_reqid *r, void *arg)
192 {
193         struct nhrp_cache *c = container_of(r, struct nhrp_cache, eventid);
194         char buf[SU_ADDRSTRLEN];
195
196         debugf(NHRP_DEBUG_COMMON, "cache: %s %s: %s",
197                 c->ifp->name, sockunion2str(&c->remote_addr, buf, sizeof buf),
198                 (const char *) arg);
199
200         nhrp_reqid_free(&nhrp_event_reqid, r);
201
202         if (arg && strcmp(arg, "accept") == 0) {
203                 if (c->cur.peer) {
204                         netlink_update_binding(c->cur.peer->ifp, &c->remote_addr, NULL);
205                         nhrp_peer_notify_del(c->cur.peer, &c->peer_notifier);
206                         nhrp_peer_unref(c->cur.peer);
207                 }
208                 nhrp_cache_counts[c->cur.type]--;
209                 nhrp_cache_counts[c->new.type]++;
210                 c->cur = c->new;
211                 c->cur.peer = nhrp_peer_ref(c->cur.peer);
212                 nhrp_cache_reset_new(c);
213                 if (c->cur.peer)
214                         nhrp_peer_notify_add(c->cur.peer, &c->peer_notifier, nhrp_cache_peer_notifier);
215                 nhrp_cache_update_route(c);
216                 notifier_call(&c->notifier_list, NOTIFY_CACHE_BINDING_CHANGE);
217         } else {
218                 nhrp_cache_reset_new(c);
219         }
220
221         nhrp_cache_update_timers(c);
222 }
223
224 static int nhrp_cache_do_auth_timeout(struct thread *t)
225 {
226         struct nhrp_cache *c = THREAD_ARG(t);
227         c->t_auth = NULL;
228         nhrp_cache_authorize_binding(&c->eventid, (void *) "timeout");
229         return 0;
230 }
231
232 static void nhrp_cache_newpeer_notifier(struct notifier_block *n, unsigned long cmd)
233 {
234         struct nhrp_cache *c = container_of(n, struct nhrp_cache, newpeer_notifier);
235
236         switch (cmd) {
237         case NOTIFY_PEER_UP:
238                 if (nhrp_peer_check(c->new.peer, 1)) {
239                         evmgr_notify("authorize-binding", c, nhrp_cache_authorize_binding);
240                         THREAD_TIMER_ON(master, c->t_auth, nhrp_cache_do_auth_timeout, c, 10);
241                 }
242                 break;
243         case NOTIFY_PEER_DOWN:
244         case NOTIFY_PEER_IFCONFIG_CHANGED:
245                 nhrp_cache_reset_new(c);
246                 break;
247         }
248 }
249
250 int nhrp_cache_update_binding(struct nhrp_cache *c, enum nhrp_cache_type type, int holding_time, struct nhrp_peer *p, uint32_t mtu, union sockunion *nbma_oa)
251 {
252         if (c->cur.type > type || c->new.type > type) {
253                 nhrp_peer_unref(p);
254                 return 0;
255         }
256
257         /* Sanitize MTU */
258         switch (sockunion_family(&c->remote_addr)) {
259         case AF_INET:
260                 if (mtu < 576 || mtu >= 1500)
261                         mtu = 0;
262                 /* Opennhrp announces nbma mtu, but we use protocol mtu.
263                  * This heuristic tries to fix up it. */
264                 if (mtu > 1420) mtu = (mtu & -16) - 80;
265                 break;
266         default:
267                 mtu = 0;
268                 break;
269         }
270
271         nhrp_cache_reset_new(c);
272         if (c->cur.type == type && c->cur.peer == p && c->cur.mtu == mtu) {
273                 if (holding_time > 0) c->cur.expires = recent_relative_time().tv_sec + holding_time;
274                 if (nbma_oa) c->cur.remote_nbma_natoa = *nbma_oa;
275                 else memset(&c->cur.remote_nbma_natoa, 0, sizeof c->cur.remote_nbma_natoa);
276                 nhrp_peer_unref(p);
277         } else {
278                 c->new.type = type;
279                 c->new.peer = p;
280                 c->new.mtu = mtu;
281                 if (nbma_oa) c->new.remote_nbma_natoa = *nbma_oa;
282
283                 if (holding_time > 0)
284                         c->new.expires = recent_relative_time().tv_sec + holding_time;
285                 else if (holding_time < 0)
286                         c->new.type = NHRP_CACHE_INVALID;
287
288                 if (c->new.type == NHRP_CACHE_INVALID ||
289                     c->new.type >= NHRP_CACHE_STATIC ||
290                     c->map) {
291                         nhrp_cache_authorize_binding(&c->eventid, (void *) "accept");
292                 } else {
293                         nhrp_peer_notify_add(c->new.peer, &c->newpeer_notifier, nhrp_cache_newpeer_notifier);
294                         nhrp_cache_newpeer_notifier(&c->newpeer_notifier, NOTIFY_PEER_UP);
295                         THREAD_TIMER_ON(master, c->t_auth, nhrp_cache_do_auth_timeout, c, 60);
296                 }
297         }
298         nhrp_cache_update_timers(c);
299
300         return 1;
301 }
302
303 void nhrp_cache_set_used(struct nhrp_cache *c, int used)
304 {
305         c->used = used;
306         if (c->used)
307                 notifier_call(&c->notifier_list, NOTIFY_CACHE_USED);
308 }
309
310 struct nhrp_cache_iterator_ctx {
311         void (*cb)(struct nhrp_cache *, void *);
312         void *ctx;
313 };
314
315 static void nhrp_cache_iterator(struct hash_backet *b, void *ctx)
316 {
317         struct nhrp_cache_iterator_ctx *ic = ctx;
318         ic->cb(b->data, ic->ctx);
319 }
320
321 void nhrp_cache_foreach(struct interface *ifp, void (*cb)(struct nhrp_cache *, void *), void *ctx)
322 {
323         struct nhrp_interface *nifp = ifp->info;
324         struct nhrp_cache_iterator_ctx ic = {
325                 .cb = cb,
326                 .ctx = ctx,
327         };
328
329         if (nifp->cache_hash)
330                 hash_iterate(nifp->cache_hash, nhrp_cache_iterator, &ic);
331 }
332
333 void nhrp_cache_notify_add(struct nhrp_cache *c, struct notifier_block *n, notifier_fn_t fn)
334 {
335         notifier_add(n, &c->notifier_list, fn);
336 }
337
338 void nhrp_cache_notify_del(struct nhrp_cache *c, struct notifier_block *n)
339 {
340         notifier_del(n);
341 }