]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-network/ndisc-router.c
Merge pull request #8417 from brauner/2018-03-09/add_bind_mount_fallback_to_private_d...
[thirdparty/systemd.git] / src / libsystemd-network / ndisc-router.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
1e7a0e21
LP
2/***
3 This file is part of systemd.
4
5 Copyright (C) 2014 Intel Corporation. All rights reserved.
1e7a0e21
LP
6***/
7
8#include <netinet/icmp6.h>
9
10#include "sd-ndisc.h"
11
12#include "alloc-util.h"
13#include "dns-domain.h"
14#include "hostname-util.h"
15#include "missing.h"
16#include "ndisc-internal.h"
17#include "ndisc-router.h"
18#include "strv.h"
19
20_public_ sd_ndisc_router* sd_ndisc_router_ref(sd_ndisc_router *rt) {
21 if (!rt)
22 return NULL;
23
24 assert(rt->n_ref > 0);
25 rt->n_ref++;
26
27 return rt;
28}
29
30_public_ sd_ndisc_router* sd_ndisc_router_unref(sd_ndisc_router *rt) {
31 if (!rt)
32 return NULL;
33
34 assert(rt->n_ref > 0);
35 rt->n_ref--;
36
37 if (rt->n_ref > 0)
38 return NULL;
39
6b430fdb 40 return mfree(rt);
1e7a0e21
LP
41}
42
43sd_ndisc_router *ndisc_router_new(size_t raw_size) {
44 sd_ndisc_router *rt;
45
46 rt = malloc0(ALIGN(sizeof(sd_ndisc_router)) + raw_size);
47 if (!rt)
48 return NULL;
49
50 rt->raw_size = raw_size;
51 rt->n_ref = 1;
52
53 return rt;
54}
55
56_public_ int sd_ndisc_router_from_raw(sd_ndisc_router **ret, const void *raw, size_t raw_size) {
57 _cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL;
58 int r;
59
60 assert_return(ret, -EINVAL);
61 assert_return(raw || raw_size <= 0, -EINVAL);
62
63 rt = ndisc_router_new(raw_size);
64 if (!rt)
65 return -ENOMEM;
66
67 memcpy(NDISC_ROUTER_RAW(rt), raw, raw_size);
68 r = ndisc_router_parse(rt);
69 if (r < 0)
70 return r;
71
1cc6c93a 72 *ret = TAKE_PTR(rt);
1e7a0e21
LP
73
74 return r;
75}
76
77_public_ int sd_ndisc_router_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr) {
78 assert_return(rt, -EINVAL);
79 assert_return(ret_addr, -EINVAL);
80
fdedbe26 81 if (IN6_IS_ADDR_UNSPECIFIED(&rt->address))
1e7a0e21
LP
82 return -ENODATA;
83
84 *ret_addr = rt->address;
85 return 0;
86}
87
88_public_ int sd_ndisc_router_get_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret) {
89 assert_return(rt, -EINVAL);
90 assert_return(TRIPLE_TIMESTAMP_HAS_CLOCK(clock), -EOPNOTSUPP);
91 assert_return(clock_supported(clock), -EOPNOTSUPP);
92 assert_return(ret, -EINVAL);
93
94 if (!triple_timestamp_is_set(&rt->timestamp))
95 return -ENODATA;
96
97 *ret = triple_timestamp_by_clock(&rt->timestamp, clock);
98 return 0;
99}
100
101_public_ int sd_ndisc_router_get_raw(sd_ndisc_router *rt, const void **ret, size_t *size) {
102 assert_return(rt, -EINVAL);
103 assert_return(ret, -EINVAL);
104 assert_return(size, -EINVAL);
105
106 *ret = NDISC_ROUTER_RAW(rt);
107 *size = rt->raw_size;
108
109 return 0;
110}
111
112int ndisc_router_parse(sd_ndisc_router *rt) {
113 struct nd_router_advert *a;
114 const uint8_t *p;
115 bool has_mtu = false, has_flag_extension = false;
116 size_t left;
117
118 assert(rt);
119
120 if (rt->raw_size < sizeof(struct nd_router_advert)) {
121 log_ndisc("Too small to be a router advertisement, ignoring.");
122 return -EBADMSG;
123 }
124
125 /* Router advertisement packets are neatly aligned to 64bit boundaries, hence we can access them directly */
126 a = NDISC_ROUTER_RAW(rt);
127
128 if (a->nd_ra_type != ND_ROUTER_ADVERT) {
129 log_ndisc("Received ND packet that is not a router advertisement, ignoring.");
130 return -EBADMSG;
131 }
132
133 if (a->nd_ra_code != 0) {
134 log_ndisc("Received ND packet with wrong RA code, ignoring.");
135 return -EBADMSG;
136 }
137
138 rt->hop_limit = a->nd_ra_curhoplimit;
139 rt->flags = a->nd_ra_flags_reserved; /* the first 8bit */
140 rt->lifetime = be16toh(a->nd_ra_router_lifetime);
141
142 rt->preference = (rt->flags >> 3) & 3;
143 if (!IN_SET(rt->preference, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_HIGH))
144 rt->preference = SD_NDISC_PREFERENCE_MEDIUM;
145
146 p = (const uint8_t*) NDISC_ROUTER_RAW(rt) + sizeof(struct nd_router_advert);
147 left = rt->raw_size - sizeof(struct nd_router_advert);
148
149 for (;;) {
150 uint8_t type;
151 size_t length;
152
153 if (left == 0)
154 break;
155
156 if (left < 2) {
157 log_ndisc("Option lacks header, ignoring datagram.");
158 return -EBADMSG;
159 }
160
161 type = p[0];
162 length = p[1] * 8;
163
164 if (length == 0) {
165 log_ndisc("Zero-length option, ignoring datagram.");
166 return -EBADMSG;
167 }
168 if (left < length) {
169 log_ndisc("Option truncated, ignoring datagram.");
170 return -EBADMSG;
171 }
172
173 switch (type) {
174
175 case SD_NDISC_OPTION_PREFIX_INFORMATION:
176
177 if (length != 4*8) {
178 log_ndisc("Prefix option of invalid size, ignoring datagram.");
179 return -EBADMSG;
180 }
181
182 if (p[2] > 128) {
183 log_ndisc("Bad prefix length, ignoring datagram.");
184 return -EBADMSG;
185 }
186
187 break;
188
189 case SD_NDISC_OPTION_MTU: {
190 uint32_t m;
191
192 if (has_mtu) {
193 log_ndisc("MTU option specified twice, ignoring.");
194 continue;
195 }
196
197 if (length != 8) {
198 log_ndisc("MTU option of invalid size, ignoring datagram.");
199 return -EBADMSG;
200 }
201
202 m = be32toh(*(uint32_t*) (p + 4));
203 if (m >= IPV6_MIN_MTU) /* ignore invalidly small MTUs */
204 rt->mtu = m;
205
206 has_mtu = true;
207 break;
208 }
209
210 case SD_NDISC_OPTION_ROUTE_INFORMATION:
211 if (length < 1*8 || length > 3*8) {
212 log_ndisc("Route information option of invalid size, ignoring datagram.");
213 return -EBADMSG;
214 }
215
216 if (p[2] > 128) {
217 log_ndisc("Bad route prefix length, ignoring datagram.");
218 return -EBADMSG;
219 }
220
221 break;
222
223 case SD_NDISC_OPTION_RDNSS:
224 if (length < 3*8 || (length % (2*8)) != 1*8) {
225 log_ndisc("RDNSS option has invalid size.");
226 return -EBADMSG;
227 }
228
229 break;
230
231 case SD_NDISC_OPTION_FLAGS_EXTENSION:
232
233 if (has_flag_extension) {
234 log_ndisc("Flags extension option specified twice, ignoring.");
235 continue;
236 }
237
238 if (length < 1*8) {
239 log_ndisc("Flags extension option has invalid size.");
240 return -EBADMSG;
241 }
242
243 /* Add in the additional flags bits */
244 rt->flags |=
245 ((uint64_t) p[2] << 8) |
246 ((uint64_t) p[3] << 16) |
247 ((uint64_t) p[4] << 24) |
248 ((uint64_t) p[5] << 32) |
249 ((uint64_t) p[6] << 40) |
250 ((uint64_t) p[7] << 48);
251
252 has_flag_extension = true;
253 break;
254
255 case SD_NDISC_OPTION_DNSSL:
256 if (length < 2*8) {
257 log_ndisc("DNSSL option has invalid size.");
258 return -EBADMSG;
259 }
260
261 break;
262 }
263
264 p += length, left -= length;
265 }
266
267 rt->rindex = sizeof(struct nd_router_advert);
268 return 0;
269}
270
271_public_ int sd_ndisc_router_get_hop_limit(sd_ndisc_router *rt, uint8_t *ret) {
272 assert_return(rt, -EINVAL);
273 assert_return(ret, -EINVAL);
274
275 *ret = rt->hop_limit;
276 return 0;
277}
278
279_public_ int sd_ndisc_router_get_flags(sd_ndisc_router *rt, uint64_t *ret_flags) {
280 assert_return(rt, -EINVAL);
281 assert_return(ret_flags, -EINVAL);
282
283 *ret_flags = rt->flags;
284 return 0;
285}
286
287_public_ int sd_ndisc_router_get_lifetime(sd_ndisc_router *rt, uint16_t *ret_lifetime) {
288 assert_return(rt, -EINVAL);
289 assert_return(ret_lifetime, -EINVAL);
290
291 *ret_lifetime = rt->lifetime;
292 return 0;
293}
294
295_public_ int sd_ndisc_router_get_preference(sd_ndisc_router *rt, unsigned *ret) {
296 assert_return(rt, -EINVAL);
297 assert_return(ret, -EINVAL);
298
299 *ret = rt->preference;
300 return 0;
301}
302
303_public_ int sd_ndisc_router_get_mtu(sd_ndisc_router *rt, uint32_t *ret) {
304 assert_return(rt, -EINVAL);
305 assert_return(ret, -EINVAL);
306
307 if (rt->mtu <= 0)
308 return -ENODATA;
309
310 *ret = rt->mtu;
311 return 0;
312}
313
314_public_ int sd_ndisc_router_option_rewind(sd_ndisc_router *rt) {
315 assert_return(rt, -EINVAL);
316
317 assert(rt->raw_size >= sizeof(struct nd_router_advert));
318 rt->rindex = sizeof(struct nd_router_advert);
319
320 return rt->rindex < rt->raw_size;
321}
322
323_public_ int sd_ndisc_router_option_next(sd_ndisc_router *rt) {
324 size_t length;
325
326 assert_return(rt, -EINVAL);
327
328 if (rt->rindex == rt->raw_size) /* EOF */
329 return -ESPIPE;
330
331 if (rt->rindex + 2 > rt->raw_size) /* Truncated message */
332 return -EBADMSG;
333
334 length = NDISC_ROUTER_OPTION_LENGTH(rt);
335 if (rt->rindex + length > rt->raw_size)
336 return -EBADMSG;
337
338 rt->rindex += length;
339 return rt->rindex < rt->raw_size;
340}
341
342_public_ int sd_ndisc_router_option_get_type(sd_ndisc_router *rt, uint8_t *ret) {
343 assert_return(rt, -EINVAL);
344 assert_return(ret, -EINVAL);
345
346 if (rt->rindex == rt->raw_size) /* EOF */
347 return -ESPIPE;
348
349 if (rt->rindex + 2 > rt->raw_size) /* Truncated message */
350 return -EBADMSG;
351
352 *ret = NDISC_ROUTER_OPTION_TYPE(rt);
353 return 0;
354}
355
356_public_ int sd_ndisc_router_option_is_type(sd_ndisc_router *rt, uint8_t type) {
357 uint8_t k;
358 int r;
359
360 assert_return(rt, -EINVAL);
361
362 r = sd_ndisc_router_option_get_type(rt, &k);
363 if (r < 0)
364 return r;
365
366 return type == k;
367}
368
369_public_ int sd_ndisc_router_option_get_raw(sd_ndisc_router *rt, const void **ret, size_t *size) {
370 size_t length;
371
372 assert_return(rt, -EINVAL);
373 assert_return(ret, -EINVAL);
374 assert_return(size, -EINVAL);
375
376 /* Note that this returns the full option, including the option header */
377
378 if (rt->rindex + 2 > rt->raw_size)
379 return -EBADMSG;
380
381 length = NDISC_ROUTER_OPTION_LENGTH(rt);
382 if (rt->rindex + length > rt->raw_size)
383 return -EBADMSG;
384
385 *ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex;
386 *size = length;
387
388 return 0;
389}
390
391static int get_prefix_info(sd_ndisc_router *rt, struct nd_opt_prefix_info **ret) {
392 struct nd_opt_prefix_info *ri;
393 size_t length;
394 int r;
395
396 assert(rt);
397 assert(ret);
398
399 r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_PREFIX_INFORMATION);
400 if (r < 0)
401 return r;
402 if (r == 0)
403 return -EMEDIUMTYPE;
404
405 length = NDISC_ROUTER_OPTION_LENGTH(rt);
406 if (length != sizeof(struct nd_opt_prefix_info))
407 return -EBADMSG;
408
409 ri = (struct nd_opt_prefix_info*) ((uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex);
410 if (ri->nd_opt_pi_prefix_len > 128)
411 return -EBADMSG;
412
413 *ret = ri;
414 return 0;
415}
416
417_public_ int sd_ndisc_router_prefix_get_valid_lifetime(sd_ndisc_router *rt, uint32_t *ret) {
418 struct nd_opt_prefix_info *ri;
419 int r;
420
421 assert_return(rt, -EINVAL);
422 assert_return(ret, -EINVAL);
423
424 r = get_prefix_info(rt, &ri);
425 if (r < 0)
426 return r;
427
428 *ret = be32toh(ri->nd_opt_pi_valid_time);
429 return 0;
430}
431
432_public_ int sd_ndisc_router_prefix_get_preferred_lifetime(sd_ndisc_router *rt, uint32_t *ret) {
433 struct nd_opt_prefix_info *pi;
434 int r;
435
436 assert_return(rt, -EINVAL);
437 assert_return(ret, -EINVAL);
438
439 r = get_prefix_info(rt, &pi);
440 if (r < 0)
441 return r;
442
443 *ret = be32toh(pi->nd_opt_pi_preferred_time);
444 return 0;
445}
446
447_public_ int sd_ndisc_router_prefix_get_flags(sd_ndisc_router *rt, uint8_t *ret) {
448 struct nd_opt_prefix_info *pi;
9f702d00 449 uint8_t flags;
1e7a0e21
LP
450 int r;
451
452 assert_return(rt, -EINVAL);
453 assert_return(ret, -EINVAL);
454
455 r = get_prefix_info(rt, &pi);
456 if (r < 0)
457 return r;
458
9f702d00
JT
459 flags = pi->nd_opt_pi_flags_reserved;
460
461 if ((flags & ND_OPT_PI_FLAG_AUTO) && (pi->nd_opt_pi_prefix_len != 64)) {
462 log_ndisc("Invalid prefix length, ignoring prefix for stateless autoconfiguration.");
463 flags &= ~ND_OPT_PI_FLAG_AUTO;
464 }
465
466 *ret = flags;
1e7a0e21
LP
467 return 0;
468}
469
470_public_ int sd_ndisc_router_prefix_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr) {
471 struct nd_opt_prefix_info *pi;
472 int r;
473
474 assert_return(rt, -EINVAL);
475 assert_return(ret_addr, -EINVAL);
476
477 r = get_prefix_info(rt, &pi);
478 if (r < 0)
479 return r;
480
481 *ret_addr = pi->nd_opt_pi_prefix;
482 return 0;
483}
484
485_public_ int sd_ndisc_router_prefix_get_prefixlen(sd_ndisc_router *rt, unsigned *ret) {
486 struct nd_opt_prefix_info *pi;
487 int r;
488
489 assert_return(rt, -EINVAL);
490 assert_return(ret, -EINVAL);
491
492 r = get_prefix_info(rt, &pi);
493 if (r < 0)
494 return r;
495
496 if (pi->nd_opt_pi_prefix_len > 128)
497 return -EBADMSG;
498
499 *ret = pi->nd_opt_pi_prefix_len;
500 return 0;
501}
502
503static int get_route_info(sd_ndisc_router *rt, uint8_t **ret) {
504 uint8_t *ri;
505 size_t length;
506 int r;
507
508 assert(rt);
509 assert(ret);
510
511 r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_ROUTE_INFORMATION);
512 if (r < 0)
513 return r;
514 if (r == 0)
515 return -EMEDIUMTYPE;
516
517 length = NDISC_ROUTER_OPTION_LENGTH(rt);
518 if (length < 1*8 || length > 3*8)
519 return -EBADMSG;
520
521 ri = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex;
522
523 if (ri[2] > 128)
524 return -EBADMSG;
525
526 *ret = ri;
527 return 0;
528}
529
530_public_ int sd_ndisc_router_route_get_lifetime(sd_ndisc_router *rt, uint32_t *ret) {
531 uint8_t *ri;
532 int r;
533
534 assert_return(rt, -EINVAL);
535 assert_return(ret, -EINVAL);
536
537 r = get_route_info(rt, &ri);
538 if (r < 0)
539 return r;
540
541 *ret = be32toh(*(uint32_t*) (ri + 4));
542 return 0;
543}
544
545_public_ int sd_ndisc_router_route_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr) {
546 uint8_t *ri;
547 int r;
548
549 assert_return(rt, -EINVAL);
550 assert_return(ret_addr, -EINVAL);
551
552 r = get_route_info(rt, &ri);
553 if (r < 0)
554 return r;
555
556 zero(*ret_addr);
557 memcpy(ret_addr, ri + 8, NDISC_ROUTER_OPTION_LENGTH(rt) - 8);
558
559 return 0;
560}
561
562_public_ int sd_ndisc_router_route_get_prefixlen(sd_ndisc_router *rt, unsigned *ret) {
563 uint8_t *ri;
564 int r;
565
566 assert_return(rt, -EINVAL);
567 assert_return(ret, -EINVAL);
568
569 r = get_route_info(rt, &ri);
570 if (r < 0)
571 return r;
572
573 *ret = ri[2];
574 return 0;
575}
576
577_public_ int sd_ndisc_router_route_get_preference(sd_ndisc_router *rt, unsigned *ret) {
578 uint8_t *ri;
579 int r;
580
581 assert_return(rt, -EINVAL);
582 assert_return(ret, -EINVAL);
583
584 r = get_route_info(rt, &ri);
585 if (r < 0)
586 return r;
587
588 *ret = (ri[3] >> 3) & 3;
589 if (!IN_SET(*ret, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_HIGH))
590 *ret = SD_NDISC_PREFERENCE_MEDIUM;
591
592 return 0;
593}
594
595static int get_rdnss_info(sd_ndisc_router *rt, uint8_t **ret) {
596 size_t length;
597 int r;
598
599 assert(rt);
600 assert(ret);
601
602 r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_RDNSS);
603 if (r < 0)
604 return r;
605 if (r == 0)
606 return -EMEDIUMTYPE;
607
608 length = NDISC_ROUTER_OPTION_LENGTH(rt);
609 if (length < 3*8 || (length % (2*8)) != 1*8)
610 return -EBADMSG;
611
612 *ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex;
613 return 0;
614}
615
616_public_ int sd_ndisc_router_rdnss_get_addresses(sd_ndisc_router *rt, const struct in6_addr **ret) {
617 uint8_t *ri;
618 int r;
619
620 assert_return(rt, -EINVAL);
621 assert_return(ret, -EINVAL);
622
623 r = get_rdnss_info(rt, &ri);
624 if (r < 0)
625 return r;
626
627 *ret = (const struct in6_addr*) (ri + 8);
628 return (NDISC_ROUTER_OPTION_LENGTH(rt) - 8) / 16;
629}
630
631_public_ int sd_ndisc_router_rdnss_get_lifetime(sd_ndisc_router *rt, uint32_t *ret) {
632 uint8_t *ri;
633 int r;
634
635 assert_return(rt, -EINVAL);
636 assert_return(ret, -EINVAL);
637
638 r = get_rdnss_info(rt, &ri);
639 if (r < 0)
640 return r;
641
642 *ret = be32toh(*(uint32_t*) (ri + 4));
643 return 0;
644}
645
646static int get_dnssl_info(sd_ndisc_router *rt, uint8_t **ret) {
647 size_t length;
648 int r;
649
650 assert(rt);
651 assert(ret);
652
653 r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_DNSSL);
654 if (r < 0)
655 return r;
656 if (r == 0)
657 return -EMEDIUMTYPE;
658
659 length = NDISC_ROUTER_OPTION_LENGTH(rt);
660 if (length < 2*8)
661 return -EBADMSG;
662
663 *ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex;
664 return 0;
665}
666
667_public_ int sd_ndisc_router_dnssl_get_domains(sd_ndisc_router *rt, char ***ret) {
668 _cleanup_strv_free_ char **l = NULL;
669 _cleanup_free_ char *e = NULL;
670 size_t allocated = 0, n = 0, left;
671 uint8_t *ri, *p;
672 bool first = true;
673 int r;
674 unsigned k = 0;
675
676 assert_return(rt, -EINVAL);
677 assert_return(ret, -EINVAL);
678
679 r = get_dnssl_info(rt, &ri);
680 if (r < 0)
681 return r;
682
683 p = ri + 8;
684 left = NDISC_ROUTER_OPTION_LENGTH(rt) - 8;
685
686 for (;;) {
687 if (left == 0) {
688
689 if (n > 0) /* Not properly NUL terminated */
690 return -EBADMSG;
691
692 break;
693 }
694
695 if (*p == 0) {
696 /* Found NUL termination */
697
698 if (n > 0) {
699 _cleanup_free_ char *normalized = NULL;
700
701 e[n] = 0;
702 r = dns_name_normalize(e, &normalized);
703 if (r < 0)
704 return r;
705
706 /* Ignore the root domain name or "localhost" and friends */
707 if (!is_localhost(normalized) &&
708 !dns_name_is_root(normalized)) {
709
710 if (strv_push(&l, normalized) < 0)
711 return -ENOMEM;
712
713 normalized = NULL;
714 k++;
715 }
716 }
717
718 n = 0;
719 first = true;
720 p++, left--;
721 continue;
722 }
723
724 /* Check for compression (which is not allowed) */
725 if (*p > 63)
726 return -EBADMSG;
727
728 if (1U + *p + 1U > left)
729 return -EBADMSG;
730
731 if (!GREEDY_REALLOC(e, allocated, n + !first + DNS_LABEL_ESCAPED_MAX + 1U))
732 return -ENOMEM;
733
734 if (first)
735 first = false;
736 else
737 e[n++] = '.';
738
739 r = dns_label_escape((char*) p+1, *p, e + n, DNS_LABEL_ESCAPED_MAX);
740 if (r < 0)
741 return r;
742
743 n += r;
744
745 left -= 1 + *p;
746 p += 1 + *p;
747 }
748
749 if (strv_isempty(l)) {
750 *ret = NULL;
751 return 0;
752 }
753
1cc6c93a 754 *ret = TAKE_PTR(l);
1e7a0e21
LP
755
756 return k;
757}
758
759_public_ int sd_ndisc_router_dnssl_get_lifetime(sd_ndisc_router *rt, uint32_t *ret_sec) {
760 uint8_t *ri;
761 int r;
762
763 assert_return(rt, -EINVAL);
764 assert_return(ret_sec, -EINVAL);
765
766 r = get_dnssl_info(rt, &ri);
767 if (r < 0)
768 return r;
769
770 *ret_sec = be32toh(*(uint32_t*) (ri + 4));
771 return 0;
772}