]> git.sommitrealweird.co.uk Git - quagga-debian.git/blob - nhrpd/nhrp_shortcut.c
New upstream release and new maintainer
[quagga-debian.git] / nhrpd / nhrp_shortcut.c
1 /* NHRP shortcut related functions
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 "nhrpd.h"
11 #include "table.h"
12 #include "memory.h"
13 #include "thread.h"
14 #include "log.h"
15 #include "nhrp_protocol.h"
16
17 static struct route_table *shortcut_rib[AFI_MAX];
18
19 static int nhrp_shortcut_do_purge(struct thread *t);
20 static void nhrp_shortcut_delete(struct nhrp_shortcut *s);
21 static void nhrp_shortcut_send_resolution_req(struct nhrp_shortcut *s);
22
23 static void nhrp_shortcut_check_use(struct nhrp_shortcut *s)
24 {
25         char buf[PREFIX_STRLEN];
26
27         if (s->expiring && s->cache && s->cache->used) {
28                 debugf(NHRP_DEBUG_ROUTE, "Shortcut %s used and expiring",
29                         prefix2str(s->p, buf, sizeof buf));
30                 nhrp_shortcut_send_resolution_req(s);
31         }
32 }
33
34 static int nhrp_shortcut_do_expire(struct thread *t)
35 {
36         struct nhrp_shortcut *s = THREAD_ARG(t);
37
38         s->t_timer = NULL;
39         THREAD_TIMER_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, s->holding_time/3);
40         s->expiring = 1;
41         nhrp_shortcut_check_use(s);
42
43         return 0;
44 }
45
46 static void nhrp_shortcut_cache_notify(struct notifier_block *n, unsigned long cmd)
47 {
48         struct nhrp_shortcut *s = container_of(n, struct nhrp_shortcut, cache_notifier);
49
50         switch (cmd) {
51         case NOTIFY_CACHE_UP:
52                 if (!s->route_installed) {
53                         nhrp_route_announce(1, s->type, s->p, NULL, &s->cache->remote_addr, 0);
54                         s->route_installed = 1;
55                 }
56                 break;
57         case NOTIFY_CACHE_USED:
58                 nhrp_shortcut_check_use(s);
59                 break;
60         case NOTIFY_CACHE_DOWN:
61         case NOTIFY_CACHE_DELETE:
62                 if (s->route_installed) {
63                         nhrp_route_announce(0, NHRP_CACHE_INVALID, s->p, NULL, NULL, 0);
64                         s->route_installed = 0;
65                 }
66                 if (cmd == NOTIFY_CACHE_DELETE)
67                         nhrp_shortcut_delete(s);
68                 break;
69         }
70 }
71
72 static void nhrp_shortcut_update_binding(struct nhrp_shortcut *s, enum nhrp_cache_type type, struct nhrp_cache *c, int holding_time)
73 {
74         s->type = type;
75         if (c != s->cache) {
76                 if (s->cache) {
77                         nhrp_cache_notify_del(s->cache, &s->cache_notifier);
78                         s->cache = NULL;
79                 }
80                 s->cache = c;
81                 if (s->cache) {
82                         nhrp_cache_notify_add(s->cache, &s->cache_notifier, nhrp_shortcut_cache_notify);
83                         if (s->cache->route_installed) {
84                                 /* Force renewal of Zebra announce on prefix change */
85                                 s->route_installed = 0;
86                                 nhrp_shortcut_cache_notify(&s->cache_notifier, NOTIFY_CACHE_UP);
87                         }
88                 }
89                 if (!s->cache || !s->cache->route_installed)
90                         nhrp_shortcut_cache_notify(&s->cache_notifier, NOTIFY_CACHE_DOWN);
91         }
92         if (s->type == NHRP_CACHE_NEGATIVE && !s->route_installed) {
93                 nhrp_route_announce(1, s->type, s->p, NULL, NULL, 0);
94                 s->route_installed = 1;
95         } else if (s->type == NHRP_CACHE_INVALID && s->route_installed) {
96                 nhrp_route_announce(0, NHRP_CACHE_INVALID, s->p, NULL, NULL, 0);
97                 s->route_installed = 0;
98         }
99
100         THREAD_OFF(s->t_timer);
101         if (holding_time) {
102                 s->expiring = 0;
103                 s->holding_time = holding_time;
104                 THREAD_TIMER_ON(master, s->t_timer, nhrp_shortcut_do_expire, s, 2*holding_time/3);
105         }
106 }
107
108 static void nhrp_shortcut_delete(struct nhrp_shortcut *s)
109 {
110         struct route_node *rn;
111         afi_t afi = family2afi(PREFIX_FAMILY(s->p));
112         char buf[PREFIX_STRLEN];
113
114         THREAD_OFF(s->t_timer);
115         nhrp_reqid_free(&nhrp_packet_reqid, &s->reqid);
116
117         debugf(NHRP_DEBUG_ROUTE, "Shortcut %s purged",
118                 prefix2str(s->p, buf, sizeof buf));
119
120         nhrp_shortcut_update_binding(s, NHRP_CACHE_INVALID, NULL, 0);
121
122         /* Delete node */
123         rn = route_node_lookup(shortcut_rib[afi], s->p);
124         if (rn) {
125                 XFREE(MTYPE_NHRP_SHORTCUT, rn->info);
126                 rn->info = NULL;
127                 route_unlock_node(rn);
128                 route_unlock_node(rn);
129         }
130 }
131
132 static int nhrp_shortcut_do_purge(struct thread *t)
133 {
134         struct nhrp_shortcut *s = THREAD_ARG(t);
135         s->t_timer = NULL;
136         nhrp_shortcut_delete(s);
137         return 0;
138 }
139
140 static struct nhrp_shortcut *nhrp_shortcut_get(struct prefix *p)
141 {
142         struct nhrp_shortcut *s;
143         struct route_node *rn;
144         char buf[PREFIX_STRLEN];
145         afi_t afi = family2afi(PREFIX_FAMILY(p));
146
147         if (!shortcut_rib[afi])
148                 return 0;
149
150         rn = route_node_get(shortcut_rib[afi], p);
151         if (!rn->info) {
152                 s = rn->info = XCALLOC(MTYPE_NHRP_SHORTCUT, sizeof(struct nhrp_shortcut));
153                 s->type = NHRP_CACHE_INVALID;
154                 s->p = &rn->p;
155
156                 debugf(NHRP_DEBUG_ROUTE, "Shortcut %s created",
157                         prefix2str(s->p, buf, sizeof buf));
158         } else {
159                 s = rn->info;
160                 route_unlock_node(rn);
161         }
162         return s;
163 }
164
165 static void nhrp_shortcut_recv_resolution_rep(struct nhrp_reqid *reqid, void *arg)
166 {
167         struct nhrp_packet_parser *pp = arg;
168         struct nhrp_shortcut *s = container_of(reqid, struct nhrp_shortcut, reqid);
169         struct nhrp_shortcut *ps;
170         struct nhrp_extension_header *ext;
171         struct nhrp_cie_header *cie;
172         struct nhrp_cache *c = NULL;
173         union sockunion *proto, cie_proto, *nbma, *nbma_natoa, cie_nbma, nat_nbma;
174         struct prefix prefix, route_prefix;
175         struct zbuf extpl;
176         char bufp[PREFIX_STRLEN], buf[3][SU_ADDRSTRLEN];
177         int holding_time = pp->if_ad->holdtime;
178
179         nhrp_reqid_free(&nhrp_packet_reqid, &s->reqid);
180         THREAD_OFF(s->t_timer);
181         THREAD_TIMER_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, 1);
182
183         if (pp->hdr->type != NHRP_PACKET_RESOLUTION_REPLY) {
184                 if (pp->hdr->type == NHRP_PACKET_ERROR_INDICATION &&
185                     pp->hdr->u.error.code == NHRP_ERROR_PROTOCOL_ADDRESS_UNREACHABLE) {
186                         debugf(NHRP_DEBUG_COMMON, "Shortcut: Resolution: Protocol address unreachable");
187                         nhrp_shortcut_update_binding(s, NHRP_CACHE_NEGATIVE, NULL, holding_time);
188                 } else {
189                         debugf(NHRP_DEBUG_COMMON, "Shortcut: Resolution failed");
190                 }
191                 return;
192         }
193
194         /* Parse extensions */
195         memset(&nat_nbma, 0, sizeof nat_nbma);
196         while ((ext = nhrp_ext_pull(&pp->extensions, &extpl)) != NULL) {
197                 switch (htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY) {
198                 case NHRP_EXTENSION_NAT_ADDRESS:
199                         nhrp_cie_pull(&extpl, pp->hdr, &nat_nbma, &cie_proto);
200                         break;
201                 }
202         }
203
204         /* Minor sanity check */
205         prefix2sockunion(s->p, &cie_proto);
206         if (!sockunion_same(&cie_proto, &pp->dst_proto)) {
207                 debugf(NHRP_DEBUG_COMMON, "Shortcut: Warning dst_proto altered from %s to %s",
208                         sockunion2str(&cie_proto, buf[0], sizeof buf[0]),
209                         sockunion2str(&pp->dst_proto, buf[1], sizeof buf[1]));
210         }
211
212         /* One or more CIEs should be given as reply, we support only one */
213         cie = nhrp_cie_pull(&pp->payload, pp->hdr, &cie_nbma, &cie_proto);
214         if (!cie || cie->code != NHRP_CODE_SUCCESS) {
215                 debugf(NHRP_DEBUG_COMMON, "Shortcut: CIE code %d", cie ? cie->code : -1);
216                 return;
217         }
218
219         proto = sockunion_family(&cie_proto) != AF_UNSPEC ? &cie_proto : &pp->dst_proto;
220         if (cie->holding_time)
221                 holding_time = htons(cie->holding_time);
222
223         prefix = *s->p;
224         prefix.prefixlen = cie->prefix_length;
225
226         /* Sanity check prefix length */
227         if (prefix.prefixlen >= 8*prefix_blen(&prefix) || prefix.prefixlen == 0) {
228                 prefix.prefixlen = 8*prefix_blen(&prefix);
229         } else if (nhrp_route_address(NULL, &pp->dst_proto, &route_prefix, NULL) == NHRP_ROUTE_NBMA_NEXTHOP) {
230                 if (prefix.prefixlen < route_prefix.prefixlen)
231                         prefix.prefixlen = route_prefix.prefixlen;
232         }
233
234         debugf(NHRP_DEBUG_COMMON, "Shortcut: %s is at proto %s cie-nbma %s nat-nbma %s cie-holdtime %d",
235                 prefix2str(&prefix, bufp, sizeof bufp),
236                 sockunion2str(proto, buf[0], sizeof buf[0]),
237                 sockunion2str(&cie_nbma, buf[1], sizeof buf[1]),
238                 sockunion2str(&nat_nbma, buf[2], sizeof buf[2]),
239                 htons(cie->holding_time));
240
241         /* Update cache entry for the protocol to nbma binding */
242         if (sockunion_family(&nat_nbma) != AF_UNSPEC) {
243                 nbma = &nat_nbma;
244                 nbma_natoa = &cie_nbma;
245         } else {
246                 nbma = &cie_nbma;
247                 nbma_natoa = NULL;
248         }
249         if (sockunion_family(nbma)) {
250                 c = nhrp_cache_get(pp->ifp, proto, 1);
251                 if (c) {
252                         nhrp_cache_update_binding(
253                                         c, NHRP_CACHE_CACHED, holding_time,
254                                         nhrp_peer_get(pp->ifp, nbma),
255                                         htons(cie->mtu), nbma_natoa);
256                 }
257         }
258
259         /* Update shortcut entry for subnet to protocol gw binding */
260         if (c && !sockunion_same(proto, &pp->dst_proto)) {
261                 ps = nhrp_shortcut_get(&prefix);
262                 if (ps) {
263                         ps->addr = s->addr;
264                         nhrp_shortcut_update_binding(ps, NHRP_CACHE_CACHED, c, holding_time);
265                 }
266         }
267
268         debugf(NHRP_DEBUG_COMMON, "Shortcut: Resolution reply handled");
269 }
270
271 static void nhrp_shortcut_send_resolution_req(struct nhrp_shortcut *s)
272 {
273         struct zbuf *zb;
274         struct nhrp_packet_header *hdr;
275         struct interface *ifp;
276         struct nhrp_interface *nifp;
277         struct nhrp_peer *peer;
278
279         if (nhrp_route_address(NULL, &s->addr, NULL, &peer) != NHRP_ROUTE_NBMA_NEXTHOP)
280                 return;
281
282         if (s->type == NHRP_CACHE_INVALID || s->type == NHRP_CACHE_NEGATIVE)
283                 s->type = NHRP_CACHE_INCOMPLETE;
284
285         ifp = peer->ifp;
286         nifp = ifp->info;
287
288         /* Create request */
289         zb = zbuf_alloc(1500);
290         hdr = nhrp_packet_push(zb, NHRP_PACKET_RESOLUTION_REQUEST,
291                 &nifp->nbma, &nifp->afi[family2afi(sockunion_family(&s->addr))].addr, &s->addr);
292         hdr->u.request_id = htonl(nhrp_reqid_alloc(&nhrp_packet_reqid, &s->reqid, nhrp_shortcut_recv_resolution_rep));
293         hdr->flags = htons(NHRP_FLAG_RESOLUTION_SOURCE_IS_ROUTER |
294                            NHRP_FLAG_RESOLUTION_AUTHORATIVE |
295                            NHRP_FLAG_RESOLUTION_SOURCE_STABLE);
296
297         /* RFC2332 - One or zero CIEs, if CIE is present contains:
298          *  - Prefix length: widest acceptable prefix we accept (if U set, 0xff)
299          *  - MTU: MTU of the source station
300          *  - Holding Time: Max time to cache the source information
301          * */
302         /* FIXME: Send holding time, and MTU */
303
304         nhrp_ext_request(zb, hdr, ifp);
305
306         /* Cisco NAT detection extension */
307         hdr->flags |= htons(NHRP_FLAG_RESOLUTION_NAT);
308         nhrp_ext_push(zb, hdr, NHRP_EXTENSION_NAT_ADDRESS);
309
310         nhrp_packet_complete(zb, hdr);
311
312         nhrp_peer_send(peer, zb);
313         nhrp_peer_unref(peer);
314         zbuf_free(zb);
315 }
316
317 void nhrp_shortcut_initiate(union sockunion *addr)
318 {
319         struct prefix p;
320         struct nhrp_shortcut *s;
321
322         sockunion2hostprefix(addr, &p);
323         s = nhrp_shortcut_get(&p);
324         if (s && s->type != NHRP_CACHE_INCOMPLETE) {
325                 s->addr = *addr;
326                 THREAD_OFF(s->t_timer);
327                 THREAD_TIMER_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, 30);
328                 nhrp_shortcut_send_resolution_req(s);
329         }
330 }
331
332 void nhrp_shortcut_init(void)
333 {
334         shortcut_rib[AFI_IP] = route_table_init();
335         shortcut_rib[AFI_IP6] = route_table_init();
336 }
337
338 void nhrp_shortcut_terminate(void)
339 {
340         route_table_finish(shortcut_rib[AFI_IP]);
341         route_table_finish(shortcut_rib[AFI_IP6]);
342 }
343
344 void nhrp_shortcut_foreach(afi_t afi, void (*cb)(struct nhrp_shortcut *, void *), void *ctx)
345 {
346         struct route_table *rt = shortcut_rib[afi];
347         struct route_node *rn;
348         route_table_iter_t iter;
349
350         if (!rt) return;
351
352         route_table_iter_init(&iter, rt);
353         while ((rn = route_table_iter_next(&iter)) != NULL) {
354                 if (rn->info) cb(rn->info, ctx);
355         }
356         route_table_iter_cleanup(&iter);
357 }
358
359 struct purge_ctx {
360         const struct prefix *p;
361         int deleted;
362 };
363
364 void nhrp_shortcut_purge(struct nhrp_shortcut *s, int force)
365 {
366         THREAD_OFF(s->t_timer);
367         nhrp_reqid_free(&nhrp_packet_reqid, &s->reqid);
368
369         if (force) {
370                 /* Immediate purge on route with draw or pending shortcut */
371                 THREAD_TIMER_MSEC_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, 5);
372         } else {
373                 /* Soft expire - force immediate renewal, but purge
374                  * in few seconds to make sure stale route is not
375                  * used too long. In practice most purges are caused
376                  * by hub bgp change, but target usually stays same.
377                  * This allows to keep nhrp route up, and to not
378                  * cause temporary rerouting via hubs causing latency
379                  * jitter. */
380                 THREAD_TIMER_MSEC_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, 3000);
381                 s->expiring = 1;
382                 nhrp_shortcut_check_use(s);
383         }
384 }
385
386 static void nhrp_shortcut_purge_prefix(struct nhrp_shortcut *s, void *ctx)
387 {
388         struct purge_ctx *pctx = ctx;
389
390         if (prefix_match(pctx->p, s->p))
391                 nhrp_shortcut_purge(s, pctx->deleted || !s->cache);
392 }
393
394 void nhrp_shortcut_prefix_change(const struct prefix *p, int deleted)
395 {
396         struct purge_ctx pctx = {
397                 .p = p,
398                 .deleted = deleted,
399         };
400         nhrp_shortcut_foreach(family2afi(PREFIX_FAMILY(p)), nhrp_shortcut_purge_prefix, &pctx);
401 }
402