]> git.proxmox.com Git - mirror_frr.git/blame - ldpd/l2vpn.c
Merge pull request #688 from opensourcerouting/openbsd-mpls-fixes
[mirror_frr.git] / ldpd / l2vpn.c
CommitLineData
8429abe0
RW
1/* $OpenBSD$ */
2
3/*
4 * Copyright (c) 2015 Renato Westphal <renato@openbsd.org>
5 * Copyright (c) 2009 Michele Marchetto <michele@openbsd.org>
6 * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org>
7 * Copyright (c) 2004, 2005, 2008 Esben Norby <norby@openbsd.org>
8 *
9 * Permission to use, copy, modify, and distribute this software for any
10 * purpose with or without fee is hereby granted, provided that the above
11 * copyright notice and this permission notice appear in all copies.
12 *
13 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
14 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
16 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
18 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
19 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20 */
21
eac6e3f0 22#include <zebra.h>
8429abe0
RW
23
24#include "ldpd.h"
25#include "ldpe.h"
26#include "lde.h"
27#include "log.h"
28
90d7e7bd
RW
29static void l2vpn_pw_fec(struct l2vpn_pw *, struct fec *);
30static __inline int l2vpn_compare(struct l2vpn *, struct l2vpn *);
029c1958 31static __inline int l2vpn_if_compare(struct l2vpn_if *, struct l2vpn_if *);
20bacaeb 32static __inline int l2vpn_pw_compare(struct l2vpn_pw *, struct l2vpn_pw *);
90d7e7bd
RW
33
34RB_GENERATE(l2vpn_head, l2vpn, entry, l2vpn_compare)
029c1958 35RB_GENERATE(l2vpn_if_head, l2vpn_if, entry, l2vpn_if_compare)
20bacaeb 36RB_GENERATE(l2vpn_pw_head, l2vpn_pw, entry, l2vpn_pw_compare)
90d7e7bd
RW
37
38static __inline int
39l2vpn_compare(struct l2vpn *a, struct l2vpn *b)
40{
41 return (strcmp(a->name, b->name));
42}
8429abe0
RW
43
44struct l2vpn *
45l2vpn_new(const char *name)
46{
47 struct l2vpn *l2vpn;
48
49 if ((l2vpn = calloc(1, sizeof(*l2vpn))) == NULL)
50 fatal("l2vpn_new: calloc");
51
52 strlcpy(l2vpn->name, name, sizeof(l2vpn->name));
53
54 /* set default values */
55 l2vpn->mtu = DEFAULT_L2VPN_MTU;
56 l2vpn->pw_type = DEFAULT_PW_TYPE;
57
029c1958 58 RB_INIT(&l2vpn->if_tree);
20bacaeb
RW
59 RB_INIT(&l2vpn->pw_tree);
60 RB_INIT(&l2vpn->pw_inactive_tree);
8429abe0
RW
61
62 return (l2vpn);
63}
64
65struct l2vpn *
66l2vpn_find(struct ldpd_conf *xconf, const char *name)
67{
90d7e7bd
RW
68 struct l2vpn l2vpn;
69 strlcpy(l2vpn.name, name, sizeof(l2vpn.name));
70 return (RB_FIND(l2vpn_head, &xconf->l2vpn_tree, &l2vpn));
8429abe0
RW
71}
72
73void
74l2vpn_del(struct l2vpn *l2vpn)
75{
76 struct l2vpn_if *lif;
77 struct l2vpn_pw *pw;
78
029c1958
RW
79 while ((lif = RB_ROOT(&l2vpn->if_tree)) != NULL) {
80 RB_REMOVE(l2vpn_if_head, &l2vpn->if_tree, lif);
8429abe0
RW
81 free(lif);
82 }
20bacaeb
RW
83 while ((pw = RB_ROOT(&l2vpn->pw_tree)) != NULL) {
84 RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_tree, pw);
8429abe0
RW
85 free(pw);
86 }
20bacaeb
RW
87 while ((pw = RB_ROOT(&l2vpn->pw_inactive_tree)) != NULL) {
88 RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_inactive_tree, pw);
eac6e3f0
RW
89 free(pw);
90 }
8429abe0
RW
91
92 free(l2vpn);
93}
94
95void
96l2vpn_init(struct l2vpn *l2vpn)
97{
98 struct l2vpn_pw *pw;
99
20bacaeb 100 RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_tree)
8429abe0
RW
101 l2vpn_pw_init(pw);
102}
103
104void
105l2vpn_exit(struct l2vpn *l2vpn)
106{
107 struct l2vpn_pw *pw;
108
20bacaeb 109 RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_tree)
8429abe0
RW
110 l2vpn_pw_exit(pw);
111}
112
029c1958
RW
113static __inline int
114l2vpn_if_compare(struct l2vpn_if *a, struct l2vpn_if *b)
115{
116 return (strcmp(a->ifname, b->ifname));
117}
118
8429abe0 119struct l2vpn_if *
52b530fc 120l2vpn_if_new(struct l2vpn *l2vpn, const char *ifname)
8429abe0
RW
121{
122 struct l2vpn_if *lif;
123
124 if ((lif = calloc(1, sizeof(*lif))) == NULL)
125 fatal("l2vpn_if_new: calloc");
126
127 lif->l2vpn = l2vpn;
52b530fc 128 strlcpy(lif->ifname, ifname, sizeof(lif->ifname));
8429abe0
RW
129
130 return (lif);
131}
132
133struct l2vpn_if *
52bd4c23 134l2vpn_if_find(struct l2vpn *l2vpn, const char *ifname)
eac6e3f0 135{
029c1958
RW
136 struct l2vpn_if lif;
137 strlcpy(lif.ifname, ifname, sizeof(lif.ifname));
138 return (RB_FIND(l2vpn_if_head, &l2vpn->if_tree, &lif));
eac6e3f0
RW
139}
140
52b530fc
RW
141void
142l2vpn_if_update_info(struct l2vpn_if *lif, struct kif *kif)
143{
144 lif->ifindex = kif->ifindex;
988ded8d 145 lif->operative = kif->operative;
52b530fc
RW
146 memcpy(lif->mac, kif->mac, sizeof(lif->mac));
147}
148
26519d8c
RW
149void
150l2vpn_if_update(struct l2vpn_if *lif)
151{
152 struct l2vpn *l2vpn = lif->l2vpn;
153 struct l2vpn_pw *pw;
154 struct map fec;
155 struct nbr *nbr;
156
988ded8d 157 if (lif->operative)
26519d8c
RW
158 return;
159
160 RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_tree) {
161 nbr = nbr_find_ldpid(pw->lsr_id.s_addr);
162 if (nbr == NULL)
163 continue;
164
165 memset(&fec, 0, sizeof(fec));
166 fec.type = MAP_TYPE_PWID;
167 fec.fec.pwid.type = l2vpn->pw_type;
168 fec.fec.pwid.group_id = 0;
169 fec.flags |= F_MAP_PW_ID;
170 fec.fec.pwid.pwid = pw->pwid;
171
172 send_mac_withdrawal(nbr, &fec, lif->mac);
173 }
174}
175
20bacaeb
RW
176static __inline int
177l2vpn_pw_compare(struct l2vpn_pw *a, struct l2vpn_pw *b)
178{
179 return (strcmp(a->ifname, b->ifname));
180}
eac6e3f0 181
8429abe0 182struct l2vpn_pw *
52b530fc 183l2vpn_pw_new(struct l2vpn *l2vpn, const char *ifname)
8429abe0
RW
184{
185 struct l2vpn_pw *pw;
186
187 if ((pw = calloc(1, sizeof(*pw))) == NULL)
188 fatal("l2vpn_pw_new: calloc");
189
190 pw->l2vpn = l2vpn;
52b530fc 191 strlcpy(pw->ifname, ifname, sizeof(pw->ifname));
8429abe0
RW
192
193 return (pw);
194}
195
196struct l2vpn_pw *
52bd4c23 197l2vpn_pw_find(struct l2vpn *l2vpn, const char *ifname)
eac6e3f0
RW
198{
199 struct l2vpn_pw *pw;
20bacaeb 200 struct l2vpn_pw s;
eac6e3f0 201
20bacaeb
RW
202 strlcpy(s.ifname, ifname, sizeof(s.ifname));
203 pw = RB_FIND(l2vpn_pw_head, &l2vpn->pw_tree, &s);
204 if (pw)
205 return (pw);
206 return (RB_FIND(l2vpn_pw_head, &l2vpn->pw_inactive_tree, &s));
8429abe0
RW
207}
208
5c3f00af
RW
209struct l2vpn_pw *
210l2vpn_pw_find_active(struct l2vpn *l2vpn, const char *ifname)
211{
212 struct l2vpn_pw s;
213
214 strlcpy(s.ifname, ifname, sizeof(s.ifname));
215 return (RB_FIND(l2vpn_pw_head, &l2vpn->pw_tree, &s));
216}
217
218struct l2vpn_pw *
219l2vpn_pw_find_inactive(struct l2vpn *l2vpn, const char *ifname)
220{
221 struct l2vpn_pw s;
222
223 strlcpy(s.ifname, ifname, sizeof(s.ifname));
224 return (RB_FIND(l2vpn_pw_head, &l2vpn->pw_inactive_tree, &s));
225}
226
52b530fc
RW
227void
228l2vpn_pw_update_info(struct l2vpn_pw *pw, struct kif *kif)
229{
230 pw->ifindex = kif->ifindex;
231}
232
8429abe0
RW
233void
234l2vpn_pw_init(struct l2vpn_pw *pw)
235{
236 struct fec fec;
237
238 l2vpn_pw_reset(pw);
239
240 l2vpn_pw_fec(pw, &fec);
88d88a9c 241 lde_kernel_insert(&fec, AF_INET, (union ldpd_addr*)&pw->lsr_id, 0, 0,
8429abe0 242 0, (void *)pw);
8cb1fc45 243 lde_kernel_update(&fec);
8429abe0
RW
244}
245
246void
247l2vpn_pw_exit(struct l2vpn_pw *pw)
248{
249 struct fec fec;
250
251 l2vpn_pw_fec(pw, &fec);
88d88a9c 252 lde_kernel_remove(&fec, AF_INET, (union ldpd_addr*)&pw->lsr_id, 0, 0);
8cb1fc45 253 lde_kernel_update(&fec);
8429abe0
RW
254}
255
256static void
257l2vpn_pw_fec(struct l2vpn_pw *pw, struct fec *fec)
258{
259 memset(fec, 0, sizeof(*fec));
260 fec->type = FEC_TYPE_PWID;
261 fec->u.pwid.type = pw->l2vpn->pw_type;
262 fec->u.pwid.pwid = pw->pwid;
263 fec->u.pwid.lsr_id = pw->lsr_id;
264}
265
266void
267l2vpn_pw_reset(struct l2vpn_pw *pw)
268{
269 pw->remote_group = 0;
270 pw->remote_mtu = 0;
271 pw->remote_status = 0;
272
273 if (pw->flags & F_PW_CWORD_CONF)
274 pw->flags |= F_PW_CWORD;
275 else
276 pw->flags &= ~F_PW_CWORD;
277
278 if (pw->flags & F_PW_STATUSTLV_CONF)
279 pw->flags |= F_PW_STATUSTLV;
280 else
281 pw->flags &= ~F_PW_STATUSTLV;
282}
283
284int
285l2vpn_pw_ok(struct l2vpn_pw *pw, struct fec_nh *fnh)
286{
8429abe0
RW
287 /* check for a remote label */
288 if (fnh->remote_label == NO_LABEL)
289 return (0);
290
291 /* MTUs must match */
292 if (pw->l2vpn->mtu != pw->remote_mtu)
293 return (0);
294
295 /* check pw status if applicable */
296 if ((pw->flags & F_PW_STATUSTLV) &&
297 pw->remote_status != PW_FORWARDING)
298 return (0);
299
8429abe0
RW
300 return (1);
301}
302
303int
304l2vpn_pw_negotiate(struct lde_nbr *ln, struct fec_node *fn, struct map *map)
305{
306 struct l2vpn_pw *pw;
307 struct status_tlv st;
308
309 /* NOTE: thanks martini & friends for all this mess */
310
311 pw = (struct l2vpn_pw *) fn->data;
312 if (pw == NULL)
313 /*
314 * pseudowire not configured, return and record
315 * the mapping later
316 */
317 return (0);
318
319 /* RFC4447 - Section 6.2: control word negotiation */
320 if (fec_find(&ln->sent_map, &fn->fec)) {
321 if ((map->flags & F_MAP_PW_CWORD) &&
322 !(pw->flags & F_PW_CWORD_CONF)) {
323 /* ignore the received label mapping */
324 return (1);
325 } else if (!(map->flags & F_MAP_PW_CWORD) &&
326 (pw->flags & F_PW_CWORD_CONF)) {
327 /* append a "Wrong C-bit" status code */
328 st.status_code = S_WRONG_CBIT;
329 st.msg_id = map->msg_id;
330 st.msg_type = htons(MSG_TYPE_LABELMAPPING);
0bcc2916 331 lde_send_labelwithdraw(ln, fn, NULL, &st);
8429abe0
RW
332
333 pw->flags &= ~F_PW_CWORD;
334 lde_send_labelmapping(ln, fn, 1);
335 }
336 } else if (map->flags & F_MAP_PW_CWORD) {
337 if (pw->flags & F_PW_CWORD_CONF)
338 pw->flags |= F_PW_CWORD;
339 else
340 /* act as if no label mapping had been received */
341 return (1);
342 } else
343 pw->flags &= ~F_PW_CWORD;
344
345 /* RFC4447 - Section 5.4.3: pseudowire status negotiation */
346 if (fec_find(&ln->recv_map, &fn->fec) == NULL &&
347 !(map->flags & F_MAP_PW_STATUS))
348 pw->flags &= ~F_PW_STATUSTLV;
349
350 return (0);
351}
352
353void
0bcc2916 354l2vpn_send_pw_status(struct lde_nbr *ln, uint32_t status, struct fec *fec)
8429abe0
RW
355{
356 struct notify_msg nm;
357
358 memset(&nm, 0, sizeof(nm));
359 nm.status_code = S_PW_STATUS;
360 nm.pw_status = status;
361 nm.flags |= F_NOTIF_PW_STATUS;
362 lde_fec2map(fec, &nm.fec);
363 nm.flags |= F_NOTIF_FEC;
364
0bcc2916
RW
365 lde_imsg_compose_ldpe(IMSG_NOTIFICATION_SEND, ln->peerid, 0, &nm,
366 sizeof(nm));
367}
368
369void
370l2vpn_send_pw_status_wcard(struct lde_nbr *ln, uint32_t status,
371 uint16_t pw_type, uint32_t group_id)
372{
373 struct notify_msg nm;
374
375 memset(&nm, 0, sizeof(nm));
376 nm.status_code = S_PW_STATUS;
377 nm.pw_status = status;
378 nm.flags |= F_NOTIF_PW_STATUS;
379 nm.fec.type = MAP_TYPE_PWID;
380 nm.fec.fec.pwid.type = pw_type;
381 nm.fec.fec.pwid.group_id = group_id;
382 nm.flags |= F_NOTIF_FEC;
383
384 lde_imsg_compose_ldpe(IMSG_NOTIFICATION_SEND, ln->peerid, 0, &nm,
385 sizeof(nm));
8429abe0
RW
386}
387
388void
389l2vpn_recv_pw_status(struct lde_nbr *ln, struct notify_msg *nm)
390{
391 struct fec fec;
392 struct fec_node *fn;
393 struct fec_nh *fnh;
394 struct l2vpn_pw *pw;
395
aba50a83
RW
396 if (nm->fec.type == MAP_TYPE_TYPED_WCARD ||
397 !(nm->fec.flags & F_MAP_PW_ID)) {
0bcc2916 398 l2vpn_recv_pw_status_wcard(ln, nm);
8429abe0 399 return;
0bcc2916 400 }
8429abe0
RW
401
402 lde_map2fec(&nm->fec, ln->id, &fec);
403 fn = (struct fec_node *)fec_find(&ft, &fec);
404 if (fn == NULL)
405 /* unknown fec */
406 return;
407
408 pw = (struct l2vpn_pw *) fn->data;
409 if (pw == NULL)
410 return;
411
88d88a9c 412 fnh = fec_nh_find(fn, AF_INET, (union ldpd_addr *)&ln->id, 0, 0);
8429abe0
RW
413 if (fnh == NULL)
414 return;
415
416 /* remote status didn't change */
417 if (pw->remote_status == nm->pw_status)
418 return;
8429abe0
RW
419 pw->remote_status = nm->pw_status;
420
421 if (l2vpn_pw_ok(pw, fnh))
422 lde_send_change_klabel(fn, fnh);
423 else
424 lde_send_delete_klabel(fn, fnh);
425}
426
0bcc2916
RW
427/* RFC4447 PWid group wildcard */
428void
429l2vpn_recv_pw_status_wcard(struct lde_nbr *ln, struct notify_msg *nm)
430{
431 struct fec *f;
432 struct fec_node *fn;
433 struct fec_nh *fnh;
434 struct l2vpn_pw *pw;
aba50a83 435 struct map *wcard = &nm->fec;
0bcc2916
RW
436
437 RB_FOREACH(f, fec_tree, &ft) {
438 fn = (struct fec_node *)f;
439 if (fn->fec.type != FEC_TYPE_PWID)
440 continue;
0bcc2916
RW
441
442 pw = (struct l2vpn_pw *) fn->data;
443 if (pw == NULL)
444 continue;
aba50a83
RW
445
446 switch (wcard->type) {
447 case MAP_TYPE_TYPED_WCARD:
448 if (wcard->fec.twcard.u.pw_type != PW_TYPE_WILDCARD &&
449 wcard->fec.twcard.u.pw_type != fn->fec.u.pwid.type)
450 continue;
451 break;
452 case MAP_TYPE_PWID:
453 if (wcard->fec.pwid.type != fn->fec.u.pwid.type)
454 continue;
455 if (wcard->fec.pwid.group_id != pw->remote_group)
456 continue;
457 break;
458 }
0bcc2916
RW
459
460 fnh = fec_nh_find(fn, AF_INET, (union ldpd_addr *)&ln->id,
461 0, 0);
462 if (fnh == NULL)
463 continue;
464
465 /* remote status didn't change */
466 if (pw->remote_status == nm->pw_status)
467 continue;
468 pw->remote_status = nm->pw_status;
469
470 if (l2vpn_pw_ok(pw, fnh))
471 lde_send_change_klabel(fn, fnh);
472 else
473 lde_send_delete_klabel(fn, fnh);
474 }
475}
476
8429abe0
RW
477void
478l2vpn_pw_ctl(pid_t pid)
479{
480 struct l2vpn *l2vpn;
481 struct l2vpn_pw *pw;
482 static struct ctl_pw pwctl;
483
90d7e7bd 484 RB_FOREACH(l2vpn, l2vpn_head, &ldeconf->l2vpn_tree)
20bacaeb 485 RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_tree) {
8429abe0 486 memset(&pwctl, 0, sizeof(pwctl));
eac6e3f0
RW
487 strlcpy(pwctl.l2vpn_name, pw->l2vpn->name,
488 sizeof(pwctl.l2vpn_name));
8429abe0
RW
489 strlcpy(pwctl.ifname, pw->ifname,
490 sizeof(pwctl.ifname));
491 pwctl.pwid = pw->pwid;
492 pwctl.lsr_id = pw->lsr_id;
493 pwctl.status = pw->flags & F_PW_STATUS_UP;
494
495 lde_imsg_compose_ldpe(IMSG_CTL_SHOW_L2VPN_PW, 0,
496 pid, &pwctl, sizeof(pwctl));
497 }
498}
499
500void
501l2vpn_binding_ctl(pid_t pid)
502{
503 struct fec *f;
504 struct fec_node *fn;
505 struct lde_map *me;
506 struct l2vpn_pw *pw;
507 static struct ctl_pw pwctl;
508
509 RB_FOREACH(f, fec_tree, &ft) {
510 if (f->type != FEC_TYPE_PWID)
511 continue;
512
513 fn = (struct fec_node *)f;
514 if (fn->local_label == NO_LABEL &&
d3e1887a 515 RB_EMPTY(&fn->downstream))
8429abe0
RW
516 continue;
517
518 memset(&pwctl, 0, sizeof(pwctl));
519 pwctl.type = f->u.pwid.type;
520 pwctl.pwid = f->u.pwid.pwid;
521 pwctl.lsr_id = f->u.pwid.lsr_id;
522
523 pw = (struct l2vpn_pw *) fn->data;
524 if (pw) {
525 pwctl.local_label = fn->local_label;
526 pwctl.local_gid = 0;
527 pwctl.local_ifmtu = pw->l2vpn->mtu;
eac6e3f0
RW
528 pwctl.local_cword = (pw->flags & F_PW_CWORD_CONF) ?
529 1 : 0;
8429abe0
RW
530 } else
531 pwctl.local_label = NO_LABEL;
532
d3e1887a 533 RB_FOREACH(me, lde_map_head, &fn->downstream)
8429abe0
RW
534 if (f->u.pwid.lsr_id.s_addr == me->nexthop->id.s_addr)
535 break;
536
537 if (me) {
538 pwctl.remote_label = me->map.label;
539 pwctl.remote_gid = me->map.fec.pwid.group_id;
540 if (me->map.flags & F_MAP_PW_IFMTU)
541 pwctl.remote_ifmtu = me->map.fec.pwid.ifmtu;
eac6e3f0
RW
542 if (pw)
543 pwctl.remote_cword = (pw->flags & F_PW_CWORD) ?
544 1 : 0;
8429abe0
RW
545
546 lde_imsg_compose_ldpe(IMSG_CTL_SHOW_L2VPN_BINDING,
547 0, pid, &pwctl, sizeof(pwctl));
548 } else if (pw) {
549 pwctl.remote_label = NO_LABEL;
550
551 lde_imsg_compose_ldpe(IMSG_CTL_SHOW_L2VPN_BINDING,
552 0, pid, &pwctl, sizeof(pwctl));
553 }
554 }
555}
556
557/* ldpe */
558
559void
560ldpe_l2vpn_init(struct l2vpn *l2vpn)
561{
562 struct l2vpn_pw *pw;
563
20bacaeb 564 RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_tree)
8429abe0
RW
565 ldpe_l2vpn_pw_init(pw);
566}
567
568void
569ldpe_l2vpn_exit(struct l2vpn *l2vpn)
570{
571 struct l2vpn_pw *pw;
572
20bacaeb 573 RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_tree)
8429abe0
RW
574 ldpe_l2vpn_pw_exit(pw);
575}
576
577void
578ldpe_l2vpn_pw_init(struct l2vpn_pw *pw)
579{
580 struct tnbr *tnbr;
581
582 tnbr = tnbr_find(leconf, pw->af, &pw->addr);
583 if (tnbr == NULL) {
eac6e3f0 584 tnbr = tnbr_new(pw->af, &pw->addr);
8429abe0 585 tnbr_update(tnbr);
7989cdba 586 RB_INSERT(tnbr_head, &leconf->tnbr_tree, tnbr);
8429abe0
RW
587 }
588
589 tnbr->pw_count++;
590}
591
592void
593ldpe_l2vpn_pw_exit(struct l2vpn_pw *pw)
594{
595 struct tnbr *tnbr;
596
597 tnbr = tnbr_find(leconf, pw->af, &pw->addr);
598 if (tnbr) {
599 tnbr->pw_count--;
7989cdba 600 tnbr_check(leconf, tnbr);
8429abe0
RW
601 }
602}