blob: e3ccbfbac3a1d3b87cd279063ae20742bc1bb27b [file] [log] [blame]
paul718e3742002-12-13 20:15:29 +00001/* BGP flap dampening
2 Copyright (C) 2001 IP Infusion Inc.
3
4This file is part of GNU Zebra.
5
6GNU Zebra is free software; you can redistribute it and/or modify it
7under the terms of the GNU General Public License as published by the
8Free Software Foundation; either version 2, or (at your option) any
9later version.
10
11GNU Zebra is distributed in the hope that it will be useful, but
12WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14General Public License for more details.
15
16You should have received a copy of the GNU General Public License
17along with GNU Zebra; see the file COPYING. If not, write to the Free
18Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
1902111-1307, USA. */
20
21#include <zebra.h>
22#include <math.h>
23
24#include "prefix.h"
25#include "memory.h"
26#include "command.h"
27#include "log.h"
28#include "thread.h"
29
30#include "bgpd/bgpd.h"
31#include "bgpd/bgp_damp.h"
32#include "bgpd/bgp_table.h"
33#include "bgpd/bgp_route.h"
34#include "bgpd/bgp_attr.h"
35#include "bgpd/bgp_advertise.h"
36
37/* Global variable to access damping configuration */
38struct bgp_damp_config bgp_damp_cfg;
39struct bgp_damp_config *damp = &bgp_damp_cfg;
40
41/* Utility macro to add and delete BGP dampening information to no
42 used list. */
43#define BGP_DAMP_LIST_ADD(N,A) BGP_INFO_ADD(N,A,no_reuse_list)
44#define BGP_DAMP_LIST_DEL(N,A) BGP_INFO_DEL(N,A,no_reuse_list)
45
46/* Calculate reuse list index by penalty value. */
47static int
48bgp_reuse_index (int penalty)
49{
paulfd79ac92004-10-13 05:06:08 +000050 unsigned int i;
paul718e3742002-12-13 20:15:29 +000051 int index;
52
53 i = (int)(((double) penalty / damp->reuse_limit - 1.0) * damp->scale_factor);
54
55 if ( i >= damp->reuse_index_size )
56 i = damp->reuse_index_size - 1;
57
58 index = damp->reuse_index[i] - damp->reuse_index[0];
59
60 return (damp->reuse_offset + index) % damp->reuse_list_size;
61}
62
63/* Add BGP dampening information to reuse list. */
64static void
65bgp_reuse_list_add (struct bgp_damp_info *bdi)
66{
67 int index;
68
69 index = bdi->index = bgp_reuse_index (bdi->penalty);
70
71 bdi->prev = NULL;
72 bdi->next = damp->reuse_list[index];
73 if (damp->reuse_list[index])
74 damp->reuse_list[index]->prev = bdi;
75 damp->reuse_list[index] = bdi;
76}
77
78/* Delete BGP dampening information from reuse list. */
79static void
80bgp_reuse_list_delete (struct bgp_damp_info *bdi)
81{
82 if (bdi->next)
83 bdi->next->prev = bdi->prev;
84 if (bdi->prev)
85 bdi->prev->next = bdi->next;
86 else
87 damp->reuse_list[bdi->index] = bdi->next;
88}
89
90/* Return decayed penalty value. */
91int
92bgp_damp_decay (time_t tdiff, int penalty)
93{
paulfd79ac92004-10-13 05:06:08 +000094 unsigned int i;
paul718e3742002-12-13 20:15:29 +000095
96 i = (int) ((double) tdiff / DELTA_T);
97
98 if (i == 0)
99 return penalty;
100
101 if (i >= damp->decay_array_size)
102 return 0;
103
104 return (int) (penalty * damp->decay_array[i]);
105}
106
107/* Handler of reuse timer event. Each route in the current reuse-list
108 is evaluated. RFC2439 Section 4.8.7. */
109int
110bgp_reuse_timer (struct thread *t)
111{
112 struct bgp_damp_info *bdi;
113 struct bgp_damp_info *next;
114 time_t t_now, t_diff;
115 struct bgp *bgp;
116 int bgp_process (struct bgp *, struct bgp_node *, afi_t, safi_t);
117
118 damp->t_reuse = NULL;
119 damp->t_reuse =
120 thread_add_timer (master, bgp_reuse_timer, NULL, DELTA_REUSE);
121
122 bgp = bgp_get_default ();
123 if (! bgp)
124 return 0;
125
126 t_now = time (NULL);
127
128 /* 1. save a pointer to the current zeroth queue head and zero the
129 list head entry. */
130 bdi = damp->reuse_list[damp->reuse_offset];
131 damp->reuse_list[damp->reuse_offset] = NULL;
132
133 /* 2. set offset = modulo reuse-list-size ( offset + 1 ), thereby
134 rotating the circular queue of list-heads. */
135 damp->reuse_offset = (damp->reuse_offset + 1) % damp->reuse_list_size;
136
137 /* 3. if ( the saved list head pointer is non-empty ) */
138 for (; bdi; bdi = next)
139 {
140 next = bdi->next;
141
142 /* Set t-diff = t-now - t-updated. */
143 t_diff = t_now - bdi->t_updated;
144
145 /* Set figure-of-merit = figure-of-merit * decay-array-ok [t-diff] */
146 bdi->penalty = bgp_damp_decay (t_diff, bdi->penalty);
147
148 /* Set t-updated = t-now. */
149 bdi->t_updated = t_now;
150
151 /* if (figure-of-merit < reuse). */
152 if (bdi->penalty < damp->reuse_limit)
153 {
154 /* Reuse the route. */
155 UNSET_FLAG (bdi->binfo->flags, BGP_INFO_DAMPED);
156 bdi->suppress_time = 0;
157
158 if (bdi->lastrecord == BGP_RECORD_UPDATE)
159 {
160 UNSET_FLAG (bdi->binfo->flags, BGP_INFO_HISTORY);
161 bgp_aggregate_increment (bgp, &bdi->rn->p, bdi->binfo,
162 bdi->afi, bdi->safi);
163 bgp_process (bgp, bdi->rn, bdi->afi, bdi->safi);
164 }
165
166 if (bdi->penalty <= damp->reuse_limit / 2.0)
167 bgp_damp_info_free (bdi, 1);
168 else
169 BGP_DAMP_LIST_ADD (damp, bdi);
170 }
171 else
172 /* Re-insert into another list (See RFC2439 Section 4.8.6). */
173 bgp_reuse_list_add (bdi);
174 }
175
176 return 0;
177}
178
179/* A route becomes unreachable (RFC2439 Section 4.8.2). */
180int
181bgp_damp_withdraw (struct bgp_info *binfo, struct bgp_node *rn,
182 afi_t afi, safi_t safi, int attr_change)
183{
184 time_t t_now;
185 struct bgp_damp_info *bdi;
186 double last_penalty = 0;
187
188 t_now = time (NULL);
189
190 /* Processing Unreachable Messages. */
191 bdi = binfo->damp_info;
192
193 if (bdi == NULL)
194 {
195 /* If there is no previous stability history. */
196
197 /* RFC2439 said:
198 1. allocate a damping structure.
199 2. set figure-of-merit = 1.
200 3. withdraw the route. */
201
202 bdi = XCALLOC (MTYPE_BGP_DAMP_INFO, sizeof (struct bgp_damp_info));
203 bdi->binfo = binfo;
204 bdi->rn = rn;
205 bdi->penalty = (attr_change ? DEFAULT_PENALTY / 2 : DEFAULT_PENALTY);
206 bdi->flap = 1;
207 bdi->start_time = t_now;
208 bdi->suppress_time = 0;
209 bdi->index = -1;
210 bdi->afi = afi;
211 bdi->safi = safi;
212 binfo->damp_info = bdi;
213 BGP_DAMP_LIST_ADD (damp, bdi);
214 }
215 else
216 {
217 last_penalty = bdi->penalty;
218
219 /* 1. Set t-diff = t-now - t-updated. */
220 bdi->penalty =
221 (bgp_damp_decay (t_now - bdi->t_updated, bdi->penalty)
222 + (attr_change ? DEFAULT_PENALTY / 2 : DEFAULT_PENALTY));
223
224 if (bdi->penalty > damp->ceiling)
225 bdi->penalty = damp->ceiling;
226
227 bdi->flap++;
228 }
229
230 bdi->lastrecord = BGP_RECORD_WITHDRAW;
231 bdi->t_updated = t_now;
232
233 /* Make this route as historical status. */
234 SET_FLAG (binfo->flags, BGP_INFO_HISTORY);
235
236 /* Remove the route from a reuse list if it is on one. */
237 if (CHECK_FLAG (bdi->binfo->flags, BGP_INFO_DAMPED))
238 {
239 /* If decay rate isn't equal to 0, reinsert brn. */
240 if (bdi->penalty != last_penalty)
241 {
242 bgp_reuse_list_delete (bdi);
243 bgp_reuse_list_add (bdi);
244 }
245 return BGP_DAMP_SUPPRESSED;
246 }
247
248 /* If not suppressed before, do annonunce this withdraw and
249 insert into reuse_list. */
250 if (bdi->penalty >= damp->suppress_value)
251 {
252 SET_FLAG (bdi->binfo->flags, BGP_INFO_DAMPED);
253 bdi->suppress_time = t_now;
254 BGP_DAMP_LIST_DEL (damp, bdi);
255 bgp_reuse_list_add (bdi);
256 }
257
258 return BGP_DAMP_USED;
259}
260
261int
262bgp_damp_update (struct bgp_info *binfo, struct bgp_node *rn,
263 afi_t afi, safi_t safi)
264{
265 time_t t_now;
266 struct bgp_damp_info *bdi;
267 int status;
268
269 bdi = binfo->damp_info;
270 if (! bdi)
271 return BGP_DAMP_USED;
272
273 t_now = time (NULL);
274 UNSET_FLAG (binfo->flags, BGP_INFO_HISTORY);
275
276 bdi->lastrecord = BGP_RECORD_UPDATE;
277 bdi->penalty = bgp_damp_decay (t_now - bdi->t_updated, bdi->penalty);
278
279 if (! CHECK_FLAG (bdi->binfo->flags, BGP_INFO_DAMPED)
280 && (bdi->penalty < damp->suppress_value))
281 status = BGP_DAMP_USED;
282 else if (CHECK_FLAG (bdi->binfo->flags, BGP_INFO_DAMPED)
283 && (bdi->penalty < damp->reuse_limit) )
284 {
285 UNSET_FLAG (bdi->binfo->flags, BGP_INFO_DAMPED);
286 bgp_reuse_list_delete (bdi);
287 BGP_DAMP_LIST_ADD (damp, bdi);
288 bdi->suppress_time = 0;
289 status = BGP_DAMP_USED;
290 }
291 else
292 status = BGP_DAMP_SUPPRESSED;
293
294 if (bdi->penalty > damp->reuse_limit / 2.0)
295 bdi->t_updated = t_now;
296 else
297 bgp_damp_info_free (bdi, 0);
298
299 return status;
300}
301
302/* Remove dampening information and history route. */
303int
304bgp_damp_scan (struct bgp_info *binfo, afi_t afi, safi_t safi)
305{
306 time_t t_now, t_diff;
307 struct bgp_damp_info *bdi;
308
309 t_now = time (NULL);
310 bdi = binfo->damp_info;
311
312 if (CHECK_FLAG (binfo->flags, BGP_INFO_DAMPED))
313 {
314 t_diff = t_now - bdi->suppress_time;
315
316 if (t_diff >= damp->max_suppress_time)
317 {
318 UNSET_FLAG (binfo->flags, BGP_INFO_DAMPED);
319 bgp_reuse_list_delete (bdi);
320 BGP_DAMP_LIST_ADD (damp, bdi);
321 bdi->penalty = damp->reuse_limit;
322 bdi->suppress_time = 0;
323 bdi->t_updated = t_now;
324
325 /* Need to announce UPDATE once this binfo is usable again. */
326 if (bdi->lastrecord == BGP_RECORD_UPDATE)
327 return 1;
328 else
329 return 0;
330 }
331 }
332 else
333 {
334 t_diff = t_now - bdi->t_updated;
335 bdi->penalty = bgp_damp_decay (t_diff, bdi->penalty);
336
337 if (bdi->penalty <= damp->reuse_limit / 2.0)
338 {
339 /* release the bdi, bdi->binfo. */
340 bgp_damp_info_free (bdi, 1);
341 return 0;
342 }
343 else
344 bdi->t_updated = t_now;
345 }
346 return 0;
347}
348
349void
350bgp_damp_info_free (struct bgp_damp_info *bdi, int withdraw)
351{
352 struct bgp_info *binfo;
paul718e3742002-12-13 20:15:29 +0000353
354 if (! bdi)
355 return;
356
357 binfo = bdi->binfo;
358 binfo->damp_info = NULL;
359
360 if (CHECK_FLAG (binfo->flags, BGP_INFO_DAMPED))
361 bgp_reuse_list_delete (bdi);
362 else
363 BGP_DAMP_LIST_DEL (damp, bdi);
364
365 UNSET_FLAG (binfo->flags, BGP_INFO_DAMPED);
366 UNSET_FLAG (binfo->flags, BGP_INFO_HISTORY);
367
368 if (bdi->lastrecord == BGP_RECORD_WITHDRAW && withdraw)
paul200df112005-06-01 11:17:05 +0000369 bgp_info_delete (bdi->rn, binfo);
370
paul718e3742002-12-13 20:15:29 +0000371 XFREE (MTYPE_BGP_DAMP_INFO, bdi);
372}
373
374void
375bgp_damp_parameter_set (int hlife, int reuse, int sup, int maxsup)
376{
377 double reuse_max_ratio;
paulfd79ac92004-10-13 05:06:08 +0000378 unsigned int i;
paul718e3742002-12-13 20:15:29 +0000379 double j;
380
381 damp->suppress_value = sup;
382 damp->half_life = hlife;
383 damp->reuse_limit = reuse;
384 damp->max_suppress_time = maxsup;
385
386 /* Initialize params per bgp_damp_config. */
387 damp->reuse_index_size = REUSE_ARRAY_SIZE;
388
389 damp->ceiling = (int)(damp->reuse_limit * (pow(2, (double)damp->max_suppress_time/damp->half_life)));
390
391 /* Decay-array computations */
392 damp->decay_array_size = ceil ((double) damp->max_suppress_time / DELTA_T);
393 damp->decay_array = XMALLOC (MTYPE_BGP_DAMP_ARRAY,
394 sizeof(double) * (damp->decay_array_size));
395 damp->decay_array[0] = 1.0;
396 damp->decay_array[1] = exp ((1.0/((double)damp->half_life/DELTA_T)) * log(0.5));
397
398 /* Calculate decay values for all possible times */
399 for (i = 2; i < damp->decay_array_size; i++)
400 damp->decay_array[i] = damp->decay_array[i-1] * damp->decay_array[1];
401
402 /* Reuse-list computations */
403 i = ceil ((double)damp->max_suppress_time / DELTA_REUSE) + 1;
404 if (i > REUSE_LIST_SIZE || i == 0)
405 i = REUSE_LIST_SIZE;
406 damp->reuse_list_size = i;
407
408 damp->reuse_list = XCALLOC (MTYPE_BGP_DAMP_ARRAY,
409 damp->reuse_list_size
410 * sizeof (struct bgp_reuse_node *));
411 memset (damp->reuse_list, 0x00,
412 damp->reuse_list_size * sizeof (struct bgp_reuse_node *));
413
414 /* Reuse-array computations */
415 damp->reuse_index = XMALLOC (MTYPE_BGP_DAMP_ARRAY,
416 sizeof(int) * damp->reuse_index_size);
417 memset (damp->reuse_index, 0x00,
418 damp->reuse_list_size * sizeof (int));
419
420 reuse_max_ratio = (double)damp->ceiling/damp->reuse_limit;
421 j = (exp((double)damp->max_suppress_time/damp->half_life) * log10(2.0));
422 if ( reuse_max_ratio > j && j != 0 )
423 reuse_max_ratio = j;
424
425 damp->scale_factor = (double)damp->reuse_index_size/(reuse_max_ratio - 1);
426
427 for (i = 0; i < damp->reuse_index_size; i++)
428 {
429 damp->reuse_index[i] =
430 (int)(((double)damp->half_life / DELTA_REUSE)
431 * log10 (1.0 / (damp->reuse_limit * ( 1.0 + ((double)i/damp->scale_factor)))) / log10(0.5));
432 }
433}
434
435int
paulfd79ac92004-10-13 05:06:08 +0000436bgp_damp_enable (struct bgp *bgp, afi_t afi, safi_t safi, time_t half,
437 unsigned int reuse, unsigned int suppress, time_t max)
paul718e3742002-12-13 20:15:29 +0000438{
439 if (CHECK_FLAG (bgp->af_flags[afi][safi], BGP_CONFIG_DAMPENING))
440 {
441 if (damp->half_life == half
442 && damp->reuse_limit == reuse
443 && damp->suppress_value == suppress
444 && damp->max_suppress_time == max)
445 return 0;
446 bgp_damp_disable (bgp, afi, safi);
447 }
448
449 SET_FLAG (bgp->af_flags[afi][safi], BGP_CONFIG_DAMPENING);
450 bgp_damp_parameter_set (half, reuse, suppress, max);
451
452 /* Register reuse timer. */
453 if (! damp->t_reuse)
454 damp->t_reuse =
455 thread_add_timer (master, bgp_reuse_timer, NULL, DELTA_REUSE);
456
457 return 0;
458}
459
460void
461bgp_damp_config_clean (struct bgp_damp_config *damp)
462{
463 /* Free decay array */
464 XFREE (MTYPE_BGP_DAMP_ARRAY, damp->decay_array);
465
466 /* Free reuse index array */
467 XFREE (MTYPE_BGP_DAMP_ARRAY, damp->reuse_index);
468
469 /* Free reuse list array. */
470 XFREE (MTYPE_BGP_DAMP_ARRAY, damp->reuse_list);
471}
472
473/* Clean all the bgp_damp_info stored in reuse_list. */
474void
475bgp_damp_info_clean ()
476{
paulfd79ac92004-10-13 05:06:08 +0000477 unsigned int i;
paul718e3742002-12-13 20:15:29 +0000478 struct bgp_damp_info *bdi, *next;
479
480 damp->reuse_offset = 0;
481
482 for (i = 0; i < damp->reuse_list_size; i++)
483 {
484 if (! damp->reuse_list[i])
485 continue;
486
487 for (bdi = damp->reuse_list[i]; bdi; bdi = next)
488 {
489 next = bdi->next;
490 bgp_damp_info_free (bdi, 1);
491 }
492 damp->reuse_list[i] = NULL;
493 }
494
495 for (bdi = damp->no_reuse_list; bdi; bdi = next)
496 {
497 next = bdi->next;
498 bgp_damp_info_free (bdi, 1);
499 }
500 damp->no_reuse_list = NULL;
501}
502
503int
504bgp_damp_disable (struct bgp *bgp, afi_t afi, safi_t safi)
505{
506 /* Cancel reuse thread. */
507 if (damp->t_reuse )
508 thread_cancel (damp->t_reuse);
509 damp->t_reuse = NULL;
510
511 /* Clean BGP dampening information. */
512 bgp_damp_info_clean ();
513
514 /* Clear configuration */
515 bgp_damp_config_clean (&bgp_damp_cfg);
516
517 UNSET_FLAG (bgp->af_flags[afi][safi], BGP_CONFIG_DAMPENING);
518 return 0;
519}
520
521int
522bgp_config_write_damp (struct vty *vty)
523{
524 if (&bgp_damp_cfg)
525 {
526 if (bgp_damp_cfg.half_life == DEFAULT_HALF_LIFE*60
527 && bgp_damp_cfg.reuse_limit == DEFAULT_REUSE
528 && bgp_damp_cfg.suppress_value == DEFAULT_SUPPRESS
529 && bgp_damp_cfg.max_suppress_time == bgp_damp_cfg.half_life*4)
530 vty_out (vty, " bgp dampening%s", VTY_NEWLINE);
531 else if (bgp_damp_cfg.half_life != DEFAULT_HALF_LIFE*60
532 && bgp_damp_cfg.reuse_limit == DEFAULT_REUSE
533 && bgp_damp_cfg.suppress_value == DEFAULT_SUPPRESS
534 && bgp_damp_cfg.max_suppress_time == bgp_damp_cfg.half_life*4)
paulfd79ac92004-10-13 05:06:08 +0000535 vty_out (vty, " bgp dampening %ld%s",
paul718e3742002-12-13 20:15:29 +0000536 bgp_damp_cfg.half_life/60,
537 VTY_NEWLINE);
538 else
paulfd79ac92004-10-13 05:06:08 +0000539 vty_out (vty, " bgp dampening %ld %d %d %ld%s",
paul718e3742002-12-13 20:15:29 +0000540 bgp_damp_cfg.half_life/60,
541 bgp_damp_cfg.reuse_limit,
542 bgp_damp_cfg.suppress_value,
543 bgp_damp_cfg.max_suppress_time/60,
544 VTY_NEWLINE);
545 return 1;
546 }
547 return 0;
548}
549
550#define BGP_UPTIME_LEN 25
551
552char *
paulfd79ac92004-10-13 05:06:08 +0000553bgp_get_reuse_time (unsigned int penalty, char *buf, size_t len)
paul718e3742002-12-13 20:15:29 +0000554{
555 time_t reuse_time = 0;
556 struct tm *tm = NULL;
557
558 if (penalty > damp->reuse_limit)
559 {
560 reuse_time = (int) (DELTA_T * ((log((double)damp->reuse_limit/penalty))/(log(damp->decay_array[1]))));
561
562 if (reuse_time > damp->max_suppress_time)
563 reuse_time = damp->max_suppress_time;
564
565 tm = gmtime (&reuse_time);
566 }
567 else
568 reuse_time = 0;
569
570 /* Making formatted timer strings. */
571#define ONE_DAY_SECOND 60*60*24
572#define ONE_WEEK_SECOND 60*60*24*7
573 if (reuse_time == 0)
574 snprintf (buf, len, "00:00:00");
575 else if (reuse_time < ONE_DAY_SECOND)
576 snprintf (buf, len, "%02d:%02d:%02d",
577 tm->tm_hour, tm->tm_min, tm->tm_sec);
578 else if (reuse_time < ONE_WEEK_SECOND)
579 snprintf (buf, len, "%dd%02dh%02dm",
580 tm->tm_yday, tm->tm_hour, tm->tm_min);
581 else
582 snprintf (buf, len, "%02dw%dd%02dh",
583 tm->tm_yday/7, tm->tm_yday - ((tm->tm_yday/7) * 7), tm->tm_hour);
584
585 return buf;
586}
587
588void
589bgp_damp_info_vty (struct vty *vty, struct bgp_info *binfo)
590{
591 struct bgp_damp_info *bdi;
592 time_t t_now, t_diff;
593 char timebuf[BGP_UPTIME_LEN];
594 int penalty;
595
596 /* BGP dampening information. */
597 bdi = binfo->damp_info;
598
599 /* If dampening is not enabled or there is no dampening information,
600 return immediately. */
601 if (! damp || ! bdi)
602 return;
603
604 /* Calculate new penalty. */
605 t_now = time (NULL);
606 t_diff = t_now - bdi->t_updated;
607 penalty = bgp_damp_decay (t_diff, bdi->penalty);
608
609 vty_out (vty, " Dampinfo: penalty %d, flapped %d times in %s",
610 penalty, bdi->flap,
611 peer_uptime (bdi->start_time, timebuf, BGP_UPTIME_LEN));
612
613 if (CHECK_FLAG (binfo->flags, BGP_INFO_DAMPED)
614 && ! CHECK_FLAG (binfo->flags, BGP_INFO_HISTORY))
615 vty_out (vty, ", reuse in %s",
616 bgp_get_reuse_time (penalty, timebuf, BGP_UPTIME_LEN));
617
618 vty_out (vty, "%s", VTY_NEWLINE);
619}
620
621char *
622bgp_damp_reuse_time_vty (struct vty *vty, struct bgp_info *binfo)
623{
624 struct bgp_damp_info *bdi;
625 time_t t_now, t_diff;
626 char timebuf[BGP_UPTIME_LEN];
627 int penalty;
628
629 /* BGP dampening information. */
630 bdi = binfo->damp_info;
631
632 /* If dampening is not enabled or there is no dampening information,
633 return immediately. */
634 if (! damp || ! bdi)
635 return NULL;
636
637 /* Calculate new penalty. */
638 t_now = time (NULL);
639 t_diff = t_now - bdi->t_updated;
640 penalty = bgp_damp_decay (t_diff, bdi->penalty);
641
642 return bgp_get_reuse_time (penalty, timebuf, BGP_UPTIME_LEN);
643}