]>
Commit | Line | Data |
---|---|---|
acddc0ed | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2d59836a | 2 | /* |
3 | * OSPF Neighbor functions. | |
4 | * Copyright (C) 1999, 2000 Toshiaki Takada | |
2d59836a | 5 | */ |
6 | ||
7 | #include <zebra.h> | |
8 | ||
659f4e40 | 9 | #include "lib/bfd.h" |
2d59836a | 10 | #include "linklist.h" |
11 | #include "prefix.h" | |
12 | #include "memory.h" | |
13 | #include "command.h" | |
14 | #include "thread.h" | |
15 | #include "stream.h" | |
16 | #include "table.h" | |
17 | #include "log.h" | |
68fe91d6 | 18 | #include "json.h" |
2d59836a | 19 | |
20 | #include "ospfd/ospfd.h" | |
21 | #include "ospfd/ospf_interface.h" | |
22 | #include "ospfd/ospf_asbr.h" | |
23 | #include "ospfd/ospf_lsa.h" | |
24 | #include "ospfd/ospf_lsdb.h" | |
25 | #include "ospfd/ospf_neighbor.h" | |
26 | #include "ospfd/ospf_nsm.h" | |
27 | #include "ospfd/ospf_packet.h" | |
28 | #include "ospfd/ospf_network.h" | |
29 | #include "ospfd/ospf_flood.h" | |
30 | #include "ospfd/ospf_dump.h" | |
68fe91d6 | 31 | #include "ospfd/ospf_bfd.h" |
cd52c44c | 32 | #include "ospfd/ospf_gr.h" |
2d59836a | 33 | |
478aab98 PJ |
34 | /* Fill in the the 'key' as appropriate to retrieve the entry for nbr |
35 | * from the ospf_interface's nbrs table. Indexed by interface address | |
0ab4a2d6 JT |
36 | * for all cases except Virtual-link and PointToPoint interfaces, where |
37 | * neighbours are indexed by router-ID instead. | |
478aab98 | 38 | */ |
d62a17ae | 39 | static void ospf_nbr_key(struct ospf_interface *oi, struct ospf_neighbor *nbr, |
40 | struct prefix *key) | |
478aab98 | 41 | { |
d62a17ae | 42 | key->family = AF_INET; |
43 | key->prefixlen = IPV4_MAX_BITLEN; | |
44 | ||
45 | /* vlinks are indexed by router-id */ | |
46 | if (oi->type == OSPF_IFTYPE_VIRTUALLINK | |
47 | || oi->type == OSPF_IFTYPE_POINTOPOINT) | |
48 | key->u.prefix4 = nbr->router_id; | |
49 | else | |
50 | key->u.prefix4 = nbr->src; | |
51 | return; | |
478aab98 PJ |
52 | } |
53 | ||
d62a17ae | 54 | struct ospf_neighbor *ospf_nbr_new(struct ospf_interface *oi) |
2d59836a | 55 | { |
d62a17ae | 56 | struct ospf_neighbor *nbr; |
2d59836a | 57 | |
d62a17ae | 58 | /* Allcate new neighbor. */ |
59 | nbr = XCALLOC(MTYPE_OSPF_NEIGHBOR, sizeof(struct ospf_neighbor)); | |
2d59836a | 60 | |
d62a17ae | 61 | /* Relate neighbor to the interface. */ |
62 | nbr->oi = oi; | |
2d59836a | 63 | |
d62a17ae | 64 | /* Set default values. */ |
65 | nbr->state = NSM_Down; | |
2d59836a | 66 | |
d62a17ae | 67 | /* Set inheritance values. */ |
68 | nbr->v_inactivity = OSPF_IF_PARAM(oi, v_wait); | |
69 | nbr->v_db_desc = OSPF_IF_PARAM(oi, retransmit_interval); | |
70 | nbr->v_ls_req = OSPF_IF_PARAM(oi, retransmit_interval); | |
71 | nbr->v_ls_upd = OSPF_IF_PARAM(oi, retransmit_interval); | |
72 | nbr->priority = -1; | |
2d59836a | 73 | |
d62a17ae | 74 | /* DD flags. */ |
75 | nbr->dd_flags = OSPF_DD_FLAG_MS | OSPF_DD_FLAG_M | OSPF_DD_FLAG_I; | |
2d59836a | 76 | |
d62a17ae | 77 | /* Last received and sent DD. */ |
78 | nbr->last_send = NULL; | |
2d59836a | 79 | |
d62a17ae | 80 | nbr->nbr_nbma = NULL; |
2d59836a | 81 | |
d62a17ae | 82 | ospf_lsdb_init(&nbr->db_sum); |
83 | ospf_lsdb_init(&nbr->ls_rxmt); | |
84 | ospf_lsdb_init(&nbr->ls_req); | |
2d59836a | 85 | |
d62a17ae | 86 | nbr->crypt_seqnum = 0; |
2d59836a | 87 | |
06bc3110 | 88 | /* Initialize GR Helper info*/ |
89 | nbr->gr_helper_info.recvd_grace_period = 0; | |
90 | nbr->gr_helper_info.actual_grace_period = 0; | |
91 | nbr->gr_helper_info.gr_helper_status = OSPF_GR_NOT_HELPER; | |
92 | nbr->gr_helper_info.helper_exit_reason = OSPF_GR_HELPER_EXIT_NONE; | |
93 | nbr->gr_helper_info.gr_restart_reason = OSPF_GR_UNKNOWN_RESTART; | |
94 | ||
d62a17ae | 95 | return nbr; |
2d59836a | 96 | } |
97 | ||
d62a17ae | 98 | void ospf_nbr_free(struct ospf_neighbor *nbr) |
2d59836a | 99 | { |
d62a17ae | 100 | /* Free DB summary list. */ |
101 | if (ospf_db_summary_count(nbr)) | |
102 | ospf_db_summary_clear(nbr); | |
103 | /* ospf_db_summary_delete_all (nbr); */ | |
104 | ||
105 | /* Free ls request list. */ | |
106 | if (ospf_ls_request_count(nbr)) | |
107 | ospf_ls_request_delete_all(nbr); | |
108 | ||
109 | /* Free retransmit list. */ | |
110 | if (ospf_ls_retransmit_count(nbr)) | |
111 | ospf_ls_retransmit_clear(nbr); | |
112 | ||
113 | /* Cleanup LSDBs. */ | |
114 | ospf_lsdb_cleanup(&nbr->db_sum); | |
115 | ospf_lsdb_cleanup(&nbr->ls_req); | |
116 | ospf_lsdb_cleanup(&nbr->ls_rxmt); | |
117 | ||
118 | /* Clear last send packet. */ | |
119 | if (nbr->last_send) | |
120 | ospf_packet_free(nbr->last_send); | |
121 | ||
122 | if (nbr->nbr_nbma) { | |
123 | nbr->nbr_nbma->nbr = NULL; | |
124 | nbr->nbr_nbma = NULL; | |
125 | } | |
126 | ||
127 | /* Cancel all timers. */ | |
cccd44f3 DS |
128 | THREAD_OFF(nbr->t_inactivity); |
129 | THREAD_OFF(nbr->t_db_desc); | |
130 | THREAD_OFF(nbr->t_ls_req); | |
131 | THREAD_OFF(nbr->t_ls_upd); | |
d62a17ae | 132 | |
133 | /* Cancel all events. */ /* Thread lookup cost would be negligible. */ | |
134 | thread_cancel_event(master, nbr); | |
135 | ||
659f4e40 | 136 | bfd_sess_free(&nbr->bfd_session); |
45559c4d | 137 | |
cccd44f3 | 138 | THREAD_OFF(nbr->gr_helper_info.t_grace_timer); |
06bc3110 | 139 | |
45559c4d | 140 | nbr->oi = NULL; |
d62a17ae | 141 | XFREE(MTYPE_OSPF_NEIGHBOR, nbr); |
2d59836a | 142 | } |
143 | ||
144 | /* Delete specified OSPF neighbor from interface. */ | |
d62a17ae | 145 | void ospf_nbr_delete(struct ospf_neighbor *nbr) |
2d59836a | 146 | { |
d62a17ae | 147 | struct ospf_interface *oi; |
148 | struct route_node *rn; | |
149 | struct prefix p; | |
150 | ||
151 | oi = nbr->oi; | |
152 | ||
153 | /* get appropriate prefix 'key' */ | |
154 | ospf_nbr_key(oi, nbr, &p); | |
155 | ||
156 | rn = route_node_lookup(oi->nbrs, &p); | |
157 | if (rn) { | |
158 | /* If lookup for a NBR succeeds, the leaf route_node could | |
159 | * only exist because there is (or was) a nbr there. | |
160 | * If the nbr was deleted, the leaf route_node should have | |
161 | * lost its last refcount too, and be deleted. | |
162 | * Therefore a looked-up leaf route_node in nbrs table | |
163 | * should never have NULL info. | |
164 | */ | |
165 | assert(rn->info); | |
166 | ||
167 | if (rn->info) { | |
168 | rn->info = NULL; | |
169 | route_unlock_node(rn); | |
170 | } else | |
96b663a3 MS |
171 | zlog_info("Can't find neighbor %pI4 in the interface %s", |
172 | &nbr->src, IF_NAME(oi)); | |
d62a17ae | 173 | |
174 | route_unlock_node(rn); | |
175 | } else { | |
176 | /* | |
177 | * This neighbor was not found, but before we move on and | |
178 | * free the neighbor structre, make sure that it was not | |
179 | * indexed incorrectly and ended up in the "worng" place | |
180 | */ | |
181 | ||
182 | /* Reverse the lookup rules */ | |
183 | if (oi->type == OSPF_IFTYPE_VIRTUALLINK | |
184 | || oi->type == OSPF_IFTYPE_POINTOPOINT) | |
185 | p.u.prefix4 = nbr->src; | |
186 | else | |
187 | p.u.prefix4 = nbr->router_id; | |
188 | ||
189 | rn = route_node_lookup(oi->nbrs, &p); | |
190 | if (rn) { | |
191 | /* We found the neighbor! | |
192 | * Now make sure it is not the exact same neighbor | |
193 | * structure that we are about to free | |
194 | */ | |
195 | if (nbr == rn->info) { | |
196 | /* Same neighbor, drop the reference to it */ | |
197 | rn->info = NULL; | |
198 | route_unlock_node(rn); | |
199 | } | |
200 | route_unlock_node(rn); | |
201 | } | |
2d59836a | 202 | } |
2d59836a | 203 | |
d62a17ae | 204 | /* Free ospf_neighbor structure. */ |
205 | ospf_nbr_free(nbr); | |
2d59836a | 206 | } |
207 | ||
208 | /* Check myself is in the neighbor list. */ | |
d62a17ae | 209 | int ospf_nbr_bidirectional(struct in_addr *router_id, struct in_addr *neighbors, |
210 | int size) | |
2d59836a | 211 | { |
d62a17ae | 212 | int i; |
213 | int max; | |
2d59836a | 214 | |
d62a17ae | 215 | max = size / sizeof(struct in_addr); |
2d59836a | 216 | |
d62a17ae | 217 | for (i = 0; i < max; i++) |
218 | if (IPV4_ADDR_SAME(router_id, &neighbors[i])) | |
219 | return 1; | |
2d59836a | 220 | |
d62a17ae | 221 | return 0; |
2d59836a | 222 | } |
223 | ||
cdd0c849 | 224 | /* reset nbr_self */ |
d62a17ae | 225 | void ospf_nbr_self_reset(struct ospf_interface *oi, struct in_addr router_id) |
cdd0c849 | 226 | { |
d62a17ae | 227 | if (oi->nbr_self) |
228 | ospf_nbr_delete(oi->nbr_self); | |
ecea0cb0 | 229 | |
d62a17ae | 230 | oi->nbr_self = ospf_nbr_new(oi); |
231 | ospf_nbr_add_self(oi, router_id); | |
cdd0c849 PJ |
232 | } |
233 | ||
2d59836a | 234 | /* Add self to nbr list. */ |
d62a17ae | 235 | void ospf_nbr_add_self(struct ospf_interface *oi, struct in_addr router_id) |
2d59836a | 236 | { |
d62a17ae | 237 | struct prefix p; |
238 | struct route_node *rn; | |
239 | ||
240 | if (!oi->nbr_self) | |
241 | oi->nbr_self = ospf_nbr_new(oi); | |
242 | ||
243 | /* Initial state */ | |
244 | oi->nbr_self->address = *oi->address; | |
245 | oi->nbr_self->priority = OSPF_IF_PARAM(oi, priority); | |
246 | oi->nbr_self->router_id = router_id; | |
247 | oi->nbr_self->src = oi->address->u.prefix4; | |
248 | oi->nbr_self->state = NSM_TwoWay; | |
249 | ||
250 | switch (oi->area->external_routing) { | |
251 | case OSPF_AREA_DEFAULT: | |
252 | SET_FLAG(oi->nbr_self->options, OSPF_OPTION_E); | |
253 | break; | |
254 | case OSPF_AREA_STUB: | |
255 | UNSET_FLAG(oi->nbr_self->options, OSPF_OPTION_E); | |
256 | break; | |
257 | case OSPF_AREA_NSSA: | |
258 | UNSET_FLAG(oi->nbr_self->options, OSPF_OPTION_E); | |
259 | SET_FLAG(oi->nbr_self->options, OSPF_OPTION_NP); | |
260 | break; | |
261 | } | |
262 | ||
263 | /* Add nbr_self to nbrs table */ | |
264 | ospf_nbr_key(oi, oi->nbr_self, &p); | |
265 | ||
266 | rn = route_node_get(oi->nbrs, &p); | |
267 | if (rn->info) { | |
268 | /* There is already pseudo neighbor. */ | |
ed59abd5 DS |
269 | if (IS_DEBUG_OSPF_EVENT) |
270 | zlog_debug( | |
96b663a3 MS |
271 | "router_id %pI4 already present in neighbor table. node refcount %u", |
272 | &router_id, route_node_get_lock_count(rn)); | |
d62a17ae | 273 | route_unlock_node(rn); |
274 | } else | |
275 | rn->info = oi->nbr_self; | |
2d59836a | 276 | } |
277 | ||
278 | /* Get neighbor count by status. | |
279 | Specify status = 0, get all neighbor other than myself. */ | |
d62a17ae | 280 | int ospf_nbr_count(struct ospf_interface *oi, int state) |
2d59836a | 281 | { |
d62a17ae | 282 | struct ospf_neighbor *nbr; |
283 | struct route_node *rn; | |
284 | int count = 0; | |
285 | ||
286 | for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) | |
287 | if ((nbr = rn->info)) | |
288 | if (!IPV4_ADDR_SAME(&nbr->router_id, | |
289 | &oi->ospf->router_id)) | |
290 | if (state == 0 || nbr->state == state) | |
291 | count++; | |
292 | ||
293 | return count; | |
2d59836a | 294 | } |
295 | ||
d62a17ae | 296 | int ospf_nbr_count_opaque_capable(struct ospf_interface *oi) |
2d59836a | 297 | { |
d62a17ae | 298 | struct ospf_neighbor *nbr; |
299 | struct route_node *rn; | |
300 | int count = 0; | |
301 | ||
302 | for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) | |
303 | if ((nbr = rn->info)) | |
304 | if (!IPV4_ADDR_SAME(&nbr->router_id, | |
305 | &oi->ospf->router_id)) | |
306 | if (nbr->state == NSM_Full) | |
307 | if (CHECK_FLAG(nbr->options, | |
308 | OSPF_OPTION_O)) | |
309 | count++; | |
310 | ||
311 | return count; | |
2d59836a | 312 | } |
2d59836a | 313 | |
d3f0d621 | 314 | /* lookup nbr by address - use this only if you know you must |
0ab4a2d6 JT |
315 | * otherwise use the ospf_nbr_lookup() wrapper, which deals |
316 | * with virtual link and PointToPoint neighbours | |
d3f0d621 | 317 | */ |
d62a17ae | 318 | struct ospf_neighbor *ospf_nbr_lookup_by_addr(struct route_table *nbrs, |
319 | struct in_addr *addr) | |
320 | { | |
321 | struct prefix p; | |
322 | struct route_node *rn; | |
323 | struct ospf_neighbor *nbr; | |
324 | ||
325 | p.family = AF_INET; | |
326 | p.prefixlen = IPV4_MAX_BITLEN; | |
327 | p.u.prefix4 = *addr; | |
328 | ||
329 | rn = route_node_lookup(nbrs, &p); | |
330 | if (!rn) | |
331 | return NULL; | |
332 | ||
333 | /* See comment in ospf_nbr_delete */ | |
334 | assert(rn->info); | |
335 | ||
336 | if (rn->info == NULL) { | |
337 | route_unlock_node(rn); | |
338 | return NULL; | |
339 | } | |
340 | ||
341 | nbr = (struct ospf_neighbor *)rn->info; | |
342 | route_unlock_node(rn); | |
343 | ||
344 | return nbr; | |
345 | } | |
346 | ||
347 | struct ospf_neighbor *ospf_nbr_lookup_by_routerid(struct route_table *nbrs, | |
348 | struct in_addr *id) | |
2d59836a | 349 | { |
d62a17ae | 350 | struct route_node *rn; |
351 | struct ospf_neighbor *nbr; | |
352 | ||
353 | for (rn = route_top(nbrs); rn; rn = route_next(rn)) | |
354 | if ((nbr = rn->info) != NULL) | |
355 | if (IPV4_ADDR_SAME(&nbr->router_id, id)) { | |
356 | route_unlock_node(rn); | |
357 | return nbr; | |
358 | } | |
359 | ||
360 | return NULL; | |
2d59836a | 361 | } |
362 | ||
d62a17ae | 363 | void ospf_renegotiate_optional_capabilities(struct ospf *top) |
2d59836a | 364 | { |
d62a17ae | 365 | struct listnode *node; |
366 | struct ospf_interface *oi; | |
367 | struct route_table *nbrs; | |
368 | struct route_node *rn; | |
369 | struct ospf_neighbor *nbr; | |
a1638c7c | 370 | uint8_t shutdown_save = top->inst_shutdown; |
d62a17ae | 371 | |
372 | /* At first, flush self-originated LSAs from routing domain. */ | |
373 | ospf_flush_self_originated_lsas_now(top); | |
374 | ||
a1638c7c YY |
375 | /* ospf_flush_self_originated_lsas_now is primarily intended for shut |
376 | * down scenarios. Reset the inst_shutdown flag that it sets. We are | |
377 | * just changing configuration, and the flag can change the scheduling | |
378 | * of when maxage LSAs are sent. */ | |
379 | top->inst_shutdown = shutdown_save; | |
380 | ||
d62a17ae | 381 | /* Revert all neighbor status to ExStart. */ |
382 | for (ALL_LIST_ELEMENTS_RO(top->oiflist, node, oi)) { | |
383 | if ((nbrs = oi->nbrs) == NULL) | |
384 | continue; | |
385 | ||
386 | for (rn = route_top(nbrs); rn; rn = route_next(rn)) { | |
387 | if ((nbr = rn->info) == NULL || nbr == oi->nbr_self) | |
388 | continue; | |
389 | ||
390 | if (nbr->state < NSM_ExStart) | |
391 | continue; | |
392 | ||
393 | if (IS_DEBUG_OSPF_EVENT) | |
394 | zlog_debug( | |
96b663a3 MS |
395 | "Renegotiate optional capabilities with neighbor(%pI4)", |
396 | &nbr->router_id); | |
d62a17ae | 397 | |
398 | OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_SeqNumberMismatch); | |
399 | } | |
2d59836a | 400 | } |
401 | ||
a4d9009d | 402 | /* Refresh/Re-originate external LSAs (Type-7 and Type-5).*/ |
403 | ospf_external_lsa_rid_change(top); | |
404 | ||
d62a17ae | 405 | return; |
2d59836a | 406 | } |
407 | ||
d62a17ae | 408 | |
409 | struct ospf_neighbor *ospf_nbr_lookup(struct ospf_interface *oi, struct ip *iph, | |
410 | struct ospf_header *ospfh) | |
2d59836a | 411 | { |
9e030550 MS |
412 | struct in_addr srcaddr = iph->ip_src; |
413 | ||
d62a17ae | 414 | if (oi->type == OSPF_IFTYPE_VIRTUALLINK |
415 | || oi->type == OSPF_IFTYPE_POINTOPOINT) | |
416 | return (ospf_nbr_lookup_by_routerid(oi->nbrs, | |
417 | &ospfh->router_id)); | |
418 | else | |
9e030550 | 419 | return (ospf_nbr_lookup_by_addr(oi->nbrs, &srcaddr)); |
d62a17ae | 420 | } |
2d59836a | 421 | |
d62a17ae | 422 | static struct ospf_neighbor *ospf_nbr_add(struct ospf_interface *oi, |
423 | struct ospf_header *ospfh, | |
424 | struct prefix *p) | |
425 | { | |
426 | struct ospf_neighbor *nbr; | |
2d59836a | 427 | |
d62a17ae | 428 | nbr = ospf_nbr_new(oi); |
429 | nbr->state = NSM_Down; | |
430 | nbr->src = p->u.prefix4; | |
431 | memcpy(&nbr->address, p, sizeof(struct prefix)); | |
2d59836a | 432 | |
d62a17ae | 433 | nbr->nbr_nbma = NULL; |
434 | if (oi->type == OSPF_IFTYPE_NBMA) { | |
435 | struct ospf_nbr_nbma *nbr_nbma; | |
436 | struct listnode *node; | |
2d59836a | 437 | |
d62a17ae | 438 | for (ALL_LIST_ELEMENTS_RO(oi->nbr_nbma, node, nbr_nbma)) { |
439 | if (IPV4_ADDR_SAME(&nbr_nbma->addr, &nbr->src)) { | |
440 | nbr_nbma->nbr = nbr; | |
441 | nbr->nbr_nbma = nbr_nbma; | |
2d59836a | 442 | |
d62a17ae | 443 | if (nbr_nbma->t_poll) |
cccd44f3 | 444 | THREAD_OFF(nbr_nbma->t_poll); |
2d59836a | 445 | |
d62a17ae | 446 | nbr->state_change = nbr_nbma->state_change + 1; |
447 | } | |
448 | } | |
449 | } | |
2d59836a | 450 | |
d62a17ae | 451 | /* New nbr, save the crypto sequence number if necessary */ |
452 | if (ntohs(ospfh->auth_type) == OSPF_AUTH_CRYPTOGRAPHIC) | |
453 | nbr->crypt_seqnum = ospfh->u.crypt.crypt_seqnum; | |
d3f0d621 | 454 | |
659f4e40 RZ |
455 | /* Configure BFD if interface has it. */ |
456 | ospf_neighbor_bfd_apply(nbr); | |
457 | ||
d62a17ae | 458 | if (IS_DEBUG_OSPF_EVENT) |
96b663a3 MS |
459 | zlog_debug("NSM[%s:%pI4]: start", IF_NAME(oi), |
460 | &nbr->router_id); | |
d3f0d621 | 461 | |
d62a17ae | 462 | return nbr; |
d3f0d621 | 463 | } |
464 | ||
d62a17ae | 465 | struct ospf_neighbor *ospf_nbr_get(struct ospf_interface *oi, |
466 | struct ospf_header *ospfh, struct ip *iph, | |
467 | struct prefix *p) | |
d3f0d621 | 468 | { |
d62a17ae | 469 | struct route_node *rn; |
470 | struct prefix key; | |
471 | struct ospf_neighbor *nbr; | |
472 | ||
473 | key.family = AF_INET; | |
474 | key.prefixlen = IPV4_MAX_BITLEN; | |
475 | ||
476 | if (oi->type == OSPF_IFTYPE_VIRTUALLINK | |
477 | || oi->type == OSPF_IFTYPE_POINTOPOINT) | |
996c9314 LB |
478 | key.u.prefix4 = ospfh->router_id; /* index vlink and ptp nbrs by |
479 | router-id */ | |
d62a17ae | 480 | else |
481 | key.u.prefix4 = iph->ip_src; | |
482 | ||
483 | rn = route_node_get(oi->nbrs, &key); | |
484 | if (rn->info) { | |
485 | route_unlock_node(rn); | |
486 | nbr = rn->info; | |
487 | ||
488 | if (oi->type == OSPF_IFTYPE_NBMA && nbr->state == NSM_Attempt) { | |
489 | nbr->src = iph->ip_src; | |
490 | memcpy(&nbr->address, p, sizeof(struct prefix)); | |
491 | } | |
492 | } else { | |
493 | rn->info = nbr = ospf_nbr_add(oi, ospfh, p); | |
494 | } | |
d3f0d621 | 495 | |
d62a17ae | 496 | nbr->router_id = ospfh->router_id; |
497 | ||
498 | return nbr; | |
d3f0d621 | 499 | } |