LCOV - code coverage report
Current view: top level - libsystemd-network - dhcp6-option.c (source / functions) Hit Total Coverage
Test: main_coverage.info Lines: 220 309 71.2 %
Date: 2019-08-22 15:41:25 Functions: 12 13 92.3 %

          Line data    Source code
       1             : /* SPDX-License-Identifier: LGPL-2.1+ */
       2             : /***
       3             :   Copyright © 2014-2015 Intel Corporation. All rights reserved.
       4             : ***/
       5             : 
       6             : #include <errno.h>
       7             : #include <netinet/in.h>
       8             : #include <string.h>
       9             : 
      10             : #include "sd-dhcp6-client.h"
      11             : 
      12             : #include "alloc-util.h"
      13             : #include "dhcp6-internal.h"
      14             : #include "dhcp6-lease-internal.h"
      15             : #include "dhcp6-protocol.h"
      16             : #include "dns-domain.h"
      17             : #include "memory-util.h"
      18             : #include "sparse-endian.h"
      19             : #include "strv.h"
      20             : #include "unaligned.h"
      21             : 
      22             : typedef struct DHCP6StatusOption {
      23             :         struct DHCP6Option option;
      24             :         be16_t status;
      25             :         char msg[];
      26             : } _packed_ DHCP6StatusOption;
      27             : 
      28             : typedef struct DHCP6AddressOption {
      29             :         struct DHCP6Option option;
      30             :         struct iaaddr iaaddr;
      31             :         uint8_t options[];
      32             : } _packed_ DHCP6AddressOption;
      33             : 
      34             : typedef struct DHCP6PDPrefixOption {
      35             :         struct DHCP6Option option;
      36             :         struct iapdprefix iapdprefix;
      37             :         uint8_t options[];
      38             : } _packed_ DHCP6PDPrefixOption;
      39             : 
      40             : #define DHCP6_OPTION_IA_NA_LEN (sizeof(struct ia_na))
      41             : #define DHCP6_OPTION_IA_PD_LEN (sizeof(struct ia_pd))
      42             : #define DHCP6_OPTION_IA_TA_LEN (sizeof(struct ia_ta))
      43             : 
      44          18 : static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode,
      45             :                              size_t optlen) {
      46          18 :         DHCP6Option *option = (DHCP6Option*) *buf;
      47             : 
      48          18 :         assert_return(buf, -EINVAL);
      49          18 :         assert_return(*buf, -EINVAL);
      50          18 :         assert_return(buflen, -EINVAL);
      51             : 
      52          18 :         if (optlen > 0xffff || *buflen < optlen + offsetof(DHCP6Option, data))
      53           0 :                 return -ENOBUFS;
      54             : 
      55          18 :         option->code = htobe16(optcode);
      56          18 :         option->len = htobe16(optlen);
      57             : 
      58          18 :         *buf += offsetof(DHCP6Option, data);
      59          18 :         *buflen -= offsetof(DHCP6Option, data);
      60             : 
      61          18 :         return 0;
      62             : }
      63             : 
      64          15 : int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
      65             :                         size_t optlen, const void *optval) {
      66             :         int r;
      67             : 
      68          15 :         assert_return(optval || optlen == 0, -EINVAL);
      69             : 
      70          15 :         r = option_append_hdr(buf, buflen, code, optlen);
      71          15 :         if (r < 0)
      72           0 :                 return r;
      73             : 
      74          15 :         memcpy_safe(*buf, optval, optlen);
      75             : 
      76          15 :         *buf += optlen;
      77          15 :         *buflen -= optlen;
      78             : 
      79          15 :         return 0;
      80             : }
      81             : 
      82           2 : int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia) {
      83             :         uint16_t len;
      84             :         uint8_t *ia_hdr;
      85           2 :         size_t iaid_offset, ia_buflen, ia_addrlen = 0;
      86             :         DHCP6Address *addr;
      87             :         int r;
      88             : 
      89           2 :         assert_return(buf, -EINVAL);
      90           2 :         assert_return(*buf, -EINVAL);
      91           2 :         assert_return(buflen, -EINVAL);
      92           2 :         assert_return(ia, -EINVAL);
      93             : 
      94           2 :         switch (ia->type) {
      95           2 :         case SD_DHCP6_OPTION_IA_NA:
      96           2 :                 len = DHCP6_OPTION_IA_NA_LEN;
      97           2 :                 iaid_offset = offsetof(DHCP6IA, ia_na);
      98           2 :                 break;
      99             : 
     100           0 :         case SD_DHCP6_OPTION_IA_TA:
     101           0 :                 len = DHCP6_OPTION_IA_TA_LEN;
     102           0 :                 iaid_offset = offsetof(DHCP6IA, ia_ta);
     103           0 :                 break;
     104             : 
     105           0 :         default:
     106           0 :                 return -EINVAL;
     107             :         }
     108             : 
     109           2 :         if (*buflen < offsetof(DHCP6Option, data) + len)
     110           0 :                 return -ENOBUFS;
     111             : 
     112           2 :         ia_hdr = *buf;
     113           2 :         ia_buflen = *buflen;
     114             : 
     115           2 :         *buf += offsetof(DHCP6Option, data);
     116           2 :         *buflen -= offsetof(DHCP6Option, data);
     117             : 
     118           2 :         memcpy(*buf, (char*) ia + iaid_offset, len);
     119             : 
     120           2 :         *buf += len;
     121           2 :         *buflen -= len;
     122             : 
     123           3 :         LIST_FOREACH(addresses, addr, ia->addresses) {
     124           1 :                 r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IAADDR,
     125             :                                       sizeof(addr->iaaddr));
     126           1 :                 if (r < 0)
     127           0 :                         return r;
     128             : 
     129           1 :                 memcpy(*buf, &addr->iaaddr, sizeof(addr->iaaddr));
     130             : 
     131           1 :                 *buf += sizeof(addr->iaaddr);
     132           1 :                 *buflen -= sizeof(addr->iaaddr);
     133             : 
     134           1 :                 ia_addrlen += offsetof(DHCP6Option, data) + sizeof(addr->iaaddr);
     135             :         }
     136             : 
     137           2 :         r = option_append_hdr(&ia_hdr, &ia_buflen, ia->type, len + ia_addrlen);
     138           2 :         if (r < 0)
     139           0 :                 return r;
     140             : 
     141           2 :         return 0;
     142             : }
     143             : 
     144           2 : int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn) {
     145             :         uint8_t buffer[1 + DNS_WIRE_FORMAT_HOSTNAME_MAX];
     146             :         int r;
     147             : 
     148           2 :         assert_return(buf && *buf && buflen && fqdn, -EINVAL);
     149             : 
     150           2 :         buffer[0] = DHCP6_FQDN_FLAG_S; /* Request server to perform AAAA RR DNS updates */
     151             : 
     152             :         /* Store domain name after flags field */
     153           2 :         r = dns_name_to_wire_format(fqdn, buffer + 1, sizeof(buffer) - 1,  false);
     154           2 :         if (r <= 0)
     155           0 :                 return r;
     156             : 
     157             :         /*
     158             :          * According to RFC 4704, chapter 4.2 only add terminating zero-length
     159             :          * label in case a FQDN is provided. Since dns_name_to_wire_format
     160             :          * always adds terminating zero-length label remove if only a hostname
     161             :          * is provided.
     162             :          */
     163           2 :         if (dns_name_is_single_label(fqdn))
     164           0 :                 r--;
     165             : 
     166           2 :         r = dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_FQDN, 1 + r, buffer);
     167             : 
     168           2 :         return r;
     169             : }
     170             : 
     171           0 : int dhcp6_option_append_pd(uint8_t *buf, size_t len, const DHCP6IA *pd) {
     172           0 :         DHCP6Option *option = (DHCP6Option *)buf;
     173           0 :         size_t i = sizeof(*option) + sizeof(pd->ia_pd);
     174             :         DHCP6Address *prefix;
     175             : 
     176           0 :         assert_return(buf, -EINVAL);
     177           0 :         assert_return(pd, -EINVAL);
     178           0 :         assert_return(pd->type == SD_DHCP6_OPTION_IA_PD, -EINVAL);
     179             : 
     180           0 :         if (len < i)
     181           0 :                 return -ENOBUFS;
     182             : 
     183           0 :         option->code = htobe16(SD_DHCP6_OPTION_IA_PD);
     184             : 
     185           0 :         memcpy(&option->data, &pd->ia_pd, sizeof(pd->ia_pd));
     186             : 
     187           0 :         LIST_FOREACH(addresses, prefix, pd->addresses) {
     188             :                 DHCP6PDPrefixOption *prefix_opt;
     189             : 
     190           0 :                 if (len < i + sizeof(*prefix_opt))
     191           0 :                         return -ENOBUFS;
     192             : 
     193           0 :                 prefix_opt = (DHCP6PDPrefixOption *)&buf[i];
     194           0 :                 prefix_opt->option.code = htobe16(SD_DHCP6_OPTION_IA_PD_PREFIX);
     195           0 :                 prefix_opt->option.len = htobe16(sizeof(prefix_opt->iapdprefix));
     196             : 
     197           0 :                 memcpy(&prefix_opt->iapdprefix, &prefix->iapdprefix,
     198             :                        sizeof(struct iapdprefix));
     199             : 
     200           0 :                 i += sizeof(*prefix_opt);
     201             :         }
     202             : 
     203           0 :         option->len = htobe16(i - sizeof(*option));
     204             : 
     205           0 :         return i;
     206             : }
     207             : 
     208           3 : static int option_parse_hdr(uint8_t **buf, size_t *buflen, uint16_t *optcode, size_t *optlen) {
     209           3 :         DHCP6Option *option = (DHCP6Option*) *buf;
     210             :         uint16_t len;
     211             : 
     212           3 :         assert_return(buf, -EINVAL);
     213           3 :         assert_return(optcode, -EINVAL);
     214           3 :         assert_return(optlen, -EINVAL);
     215             : 
     216           3 :         if (*buflen < offsetof(DHCP6Option, data))
     217           1 :                 return -ENOMSG;
     218             : 
     219           2 :         len = be16toh(option->len);
     220             : 
     221           2 :         if (len > *buflen)
     222           0 :                 return -ENOMSG;
     223             : 
     224           2 :         *optcode = be16toh(option->code);
     225           2 :         *optlen = len;
     226             : 
     227           2 :         *buf += 4;
     228           2 :         *buflen -= 4;
     229             : 
     230           2 :         return 0;
     231             : }
     232             : 
     233           3 : int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
     234             :                        size_t *optlen, uint8_t **optvalue) {
     235             :         int r;
     236             : 
     237           3 :         assert_return(buf && buflen && optcode && optlen && optvalue, -EINVAL);
     238             : 
     239           3 :         r = option_parse_hdr(buf, buflen, optcode, optlen);
     240           3 :         if (r < 0)
     241           1 :                 return r;
     242             : 
     243           2 :         if (*optlen > *buflen)
     244           0 :                 return -ENOBUFS;
     245             : 
     246           2 :         *optvalue = *buf;
     247           2 :         *buflen -= *optlen;
     248           2 :         *buf += *optlen;
     249             : 
     250           2 :         return 0;
     251             : }
     252             : 
     253           9 : int dhcp6_option_parse_status(DHCP6Option *option, size_t len) {
     254           9 :         DHCP6StatusOption *statusopt = (DHCP6StatusOption *)option;
     255             : 
     256           9 :         if (len < sizeof(DHCP6StatusOption) ||
     257           9 :             be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(DHCP6StatusOption))
     258           0 :                 return -ENOBUFS;
     259             : 
     260           9 :         return be16toh(statusopt->status);
     261             : }
     262             : 
     263           6 : static int dhcp6_option_parse_address(DHCP6Option *option, DHCP6IA *ia,
     264             :                                       uint32_t *lifetime_valid) {
     265           6 :         DHCP6AddressOption *addr_option = (DHCP6AddressOption *)option;
     266             :         DHCP6Address *addr;
     267             :         uint32_t lt_valid, lt_pref;
     268             :         int r;
     269             : 
     270           6 :         if (be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(*addr_option))
     271           0 :                 return -ENOBUFS;
     272             : 
     273           6 :         lt_valid = be32toh(addr_option->iaaddr.lifetime_valid);
     274           6 :         lt_pref = be32toh(addr_option->iaaddr.lifetime_preferred);
     275             : 
     276           6 :         if (lt_valid == 0 || lt_pref > lt_valid) {
     277           0 :                 log_dhcp6_client(client, "Valid lifetime of an IA address is zero or preferred lifetime %d > valid lifetime %d",
     278             :                                  lt_pref, lt_valid);
     279             : 
     280           0 :                 return 0;
     281             :         }
     282             : 
     283           6 :         if (be16toh(option->len) + offsetof(DHCP6Option, data) > sizeof(*addr_option)) {
     284           2 :                 r = dhcp6_option_parse_status((DHCP6Option *)addr_option->options, be16toh(option->len) + offsetof(DHCP6Option, data) - sizeof(*addr_option));
     285           2 :                 if (r != 0)
     286           1 :                         return r < 0 ? r: 0;
     287             :         }
     288             : 
     289           5 :         addr = new0(DHCP6Address, 1);
     290           5 :         if (!addr)
     291           0 :                 return -ENOMEM;
     292             : 
     293           5 :         LIST_INIT(addresses, addr);
     294           5 :         memcpy(&addr->iaaddr, option->data, sizeof(addr->iaaddr));
     295             : 
     296           5 :         LIST_PREPEND(addresses, ia->addresses, addr);
     297             : 
     298           5 :         *lifetime_valid = be32toh(addr->iaaddr.lifetime_valid);
     299             : 
     300           5 :         return 0;
     301             : }
     302             : 
     303           3 : static int dhcp6_option_parse_pdprefix(DHCP6Option *option, DHCP6IA *ia,
     304             :                                        uint32_t *lifetime_valid) {
     305           3 :         DHCP6PDPrefixOption *pdprefix_option = (DHCP6PDPrefixOption *)option;
     306             :         DHCP6Address *prefix;
     307             :         uint32_t lt_valid, lt_pref;
     308             :         int r;
     309             : 
     310           3 :         if (be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(*pdprefix_option))
     311           0 :                 return -ENOBUFS;
     312             : 
     313           3 :         lt_valid = be32toh(pdprefix_option->iapdprefix.lifetime_valid);
     314           3 :         lt_pref = be32toh(pdprefix_option->iapdprefix.lifetime_preferred);
     315             : 
     316           3 :         if (lt_valid == 0 || lt_pref > lt_valid) {
     317           0 :                 log_dhcp6_client(client, "Valid lifetieme of a PD prefix is zero or preferred lifetime %d > valid lifetime %d",
     318             :                                  lt_pref, lt_valid);
     319             : 
     320           0 :                 return 0;
     321             :         }
     322             : 
     323           3 :         if (be16toh(option->len) + offsetof(DHCP6Option, data) > sizeof(*pdprefix_option)) {
     324           3 :                 r = dhcp6_option_parse_status((DHCP6Option *)pdprefix_option->options, be16toh(option->len) + offsetof(DHCP6Option, data) - sizeof(*pdprefix_option));
     325           3 :                 if (r != 0)
     326           0 :                         return r < 0 ? r: 0;
     327             :         }
     328             : 
     329           3 :         prefix = new0(DHCP6Address, 1);
     330           3 :         if (!prefix)
     331           0 :                 return -ENOMEM;
     332             : 
     333           3 :         LIST_INIT(addresses, prefix);
     334           3 :         memcpy(&prefix->iapdprefix, option->data, sizeof(prefix->iapdprefix));
     335             : 
     336           3 :         LIST_PREPEND(addresses, ia->addresses, prefix);
     337             : 
     338           3 :         *lifetime_valid = be32toh(prefix->iapdprefix.lifetime_valid);
     339             : 
     340           3 :         return 0;
     341             : }
     342             : 
     343          11 : int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) {
     344             :         uint16_t iatype, optlen;
     345             :         size_t i, len;
     346          11 :         int r = 0, status;
     347             :         uint16_t opt;
     348             :         size_t iaaddr_offset;
     349          11 :         uint32_t lt_t1, lt_t2, lt_valid = 0, lt_min = UINT32_MAX;
     350             : 
     351          11 :         assert_return(ia, -EINVAL);
     352          11 :         assert_return(!ia->addresses, -EINVAL);
     353             : 
     354          11 :         iatype = be16toh(iaoption->code);
     355          11 :         len = be16toh(iaoption->len);
     356             : 
     357          11 :         switch (iatype) {
     358           9 :         case SD_DHCP6_OPTION_IA_NA:
     359             : 
     360           9 :                 if (len < DHCP6_OPTION_IA_NA_LEN)
     361           1 :                         return -ENOBUFS;
     362             : 
     363           8 :                 iaaddr_offset = DHCP6_OPTION_IA_NA_LEN;
     364           8 :                 memcpy(&ia->ia_na, iaoption->data, sizeof(ia->ia_na));
     365             : 
     366           8 :                 lt_t1 = be32toh(ia->ia_na.lifetime_t1);
     367           8 :                 lt_t2 = be32toh(ia->ia_na.lifetime_t2);
     368             : 
     369           8 :                 if (lt_t1 && lt_t2 && lt_t1 > lt_t2) {
     370           0 :                         log_dhcp6_client(client, "IA NA T1 %ds > T2 %ds",
     371             :                                          lt_t1, lt_t2);
     372           0 :                         return -EINVAL;
     373             :                 }
     374             : 
     375           8 :                 break;
     376             : 
     377           2 :         case SD_DHCP6_OPTION_IA_PD:
     378             : 
     379           2 :                 if (len < sizeof(ia->ia_pd))
     380           0 :                         return -ENOBUFS;
     381             : 
     382           2 :                 iaaddr_offset = sizeof(ia->ia_pd);
     383           2 :                 memcpy(&ia->ia_pd, iaoption->data, sizeof(ia->ia_pd));
     384             : 
     385           2 :                 lt_t1 = be32toh(ia->ia_pd.lifetime_t1);
     386           2 :                 lt_t2 = be32toh(ia->ia_pd.lifetime_t2);
     387             : 
     388           2 :                 if (lt_t1 && lt_t2 && lt_t1 > lt_t2) {
     389           0 :                         log_dhcp6_client(client, "IA PD T1 %ds > T2 %ds",
     390             :                                          lt_t1, lt_t2);
     391           0 :                         return -EINVAL;
     392             :                 }
     393             : 
     394           2 :                 break;
     395             : 
     396           0 :         case SD_DHCP6_OPTION_IA_TA:
     397           0 :                 if (len < DHCP6_OPTION_IA_TA_LEN)
     398           0 :                         return -ENOBUFS;
     399             : 
     400           0 :                 iaaddr_offset = DHCP6_OPTION_IA_TA_LEN;
     401           0 :                 memcpy(&ia->ia_ta.id, iaoption->data, sizeof(ia->ia_ta));
     402             : 
     403           0 :                 break;
     404             : 
     405           0 :         default:
     406           0 :                 return -ENOMSG;
     407             :         }
     408             : 
     409          10 :         ia->type = iatype;
     410          10 :         i = iaaddr_offset;
     411             : 
     412          22 :         while (i < len) {
     413          14 :                 DHCP6Option *option = (DHCP6Option *)&iaoption->data[i];
     414             : 
     415          14 :                 if (len < i + sizeof(*option) || len < i + sizeof(*option) + be16toh(option->len))
     416           1 :                         return -ENOBUFS;
     417             : 
     418          13 :                 opt = be16toh(option->code);
     419          13 :                 optlen = be16toh(option->len);
     420             : 
     421          13 :                 switch (opt) {
     422           6 :                 case SD_DHCP6_OPTION_IAADDR:
     423             : 
     424           6 :                         if (!IN_SET(ia->type, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_TA)) {
     425           0 :                                 log_dhcp6_client(client, "IA Address option not in IA NA or TA option");
     426           0 :                                 return -EINVAL;
     427             :                         }
     428             : 
     429           6 :                         r = dhcp6_option_parse_address(option, ia, &lt_valid);
     430           6 :                         if (r < 0)
     431           0 :                                 return r;
     432             : 
     433           6 :                         if (lt_valid < lt_min)
     434           6 :                                 lt_min = lt_valid;
     435             : 
     436           6 :                         break;
     437             : 
     438           3 :                 case SD_DHCP6_OPTION_IA_PD_PREFIX:
     439             : 
     440           3 :                         if (!IN_SET(ia->type, SD_DHCP6_OPTION_IA_PD)) {
     441           0 :                                 log_dhcp6_client(client, "IA PD Prefix option not in IA PD option");
     442           0 :                                 return -EINVAL;
     443             :                         }
     444             : 
     445           3 :                         r = dhcp6_option_parse_pdprefix(option, ia, &lt_valid);
     446           3 :                         if (r < 0)
     447           0 :                                 return r;
     448             : 
     449           3 :                         if (lt_valid < lt_min)
     450           2 :                                 lt_min = lt_valid;
     451             : 
     452           3 :                         break;
     453             : 
     454           4 :                 case SD_DHCP6_OPTION_STATUS_CODE:
     455             : 
     456           4 :                         status = dhcp6_option_parse_status(option, optlen + offsetof(DHCP6Option, data));
     457           4 :                         if (status < 0)
     458           0 :                                 return status;
     459           4 :                         if (status > 0) {
     460           1 :                                 log_dhcp6_client(client, "IA status %d",
     461             :                                                  status);
     462             : 
     463           1 :                                 return -EINVAL;
     464             :                         }
     465             : 
     466           3 :                         break;
     467             : 
     468           0 :                 default:
     469           0 :                         log_dhcp6_client(client, "Unknown IA option %d", opt);
     470           0 :                         break;
     471             :                 }
     472             : 
     473          12 :                 i += sizeof(*option) + optlen;
     474             :         }
     475             : 
     476           8 :         switch(iatype) {
     477           6 :         case SD_DHCP6_OPTION_IA_NA:
     478           6 :                 if (!ia->ia_na.lifetime_t1 && !ia->ia_na.lifetime_t2) {
     479           0 :                         lt_t1 = lt_min / 2;
     480           0 :                         lt_t2 = lt_min / 10 * 8;
     481           0 :                         ia->ia_na.lifetime_t1 = htobe32(lt_t1);
     482           0 :                         ia->ia_na.lifetime_t2 = htobe32(lt_t2);
     483             : 
     484           0 :                         log_dhcp6_client(client, "Computed IA NA T1 %ds and T2 %ds as both were zero",
     485             :                                          lt_t1, lt_t2);
     486             :                 }
     487             : 
     488           6 :                 break;
     489             : 
     490           2 :         case SD_DHCP6_OPTION_IA_PD:
     491           2 :                 if (!ia->ia_pd.lifetime_t1 && !ia->ia_pd.lifetime_t2) {
     492           0 :                         lt_t1 = lt_min / 2;
     493           0 :                         lt_t2 = lt_min / 10 * 8;
     494           0 :                         ia->ia_pd.lifetime_t1 = htobe32(lt_t1);
     495           0 :                         ia->ia_pd.lifetime_t2 = htobe32(lt_t2);
     496             : 
     497           0 :                         log_dhcp6_client(client, "Computed IA PD T1 %ds and T2 %ds as both were zero",
     498             :                                          lt_t1, lt_t2);
     499             :                 }
     500             : 
     501           2 :                 break;
     502             : 
     503           0 :         default:
     504           0 :                 break;
     505             :         }
     506             : 
     507           8 :         return 0;
     508             : }
     509             : 
     510           8 : int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen,
     511             :                                 struct in6_addr **addrs, size_t count,
     512             :                                 size_t *allocated) {
     513             : 
     514           8 :         if (optlen == 0 || optlen % sizeof(struct in6_addr) != 0)
     515           0 :                 return -EINVAL;
     516             : 
     517           8 :         if (!GREEDY_REALLOC(*addrs, *allocated,
     518             :                             count * sizeof(struct in6_addr) + optlen))
     519           0 :                 return -ENOMEM;
     520             : 
     521           8 :         memcpy(*addrs + count, optval, optlen);
     522             : 
     523           8 :         count += optlen / sizeof(struct in6_addr);
     524             : 
     525           8 :         return count;
     526             : }
     527             : 
     528           4 : int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char ***str_arr) {
     529           4 :         size_t pos = 0, idx = 0;
     530           4 :         _cleanup_strv_free_ char **names = NULL;
     531             :         int r;
     532             : 
     533           4 :         assert_return(optlen > 1, -ENODATA);
     534           4 :         assert_return(optval[optlen - 1] == '\0', -EINVAL);
     535             : 
     536           8 :         while (pos < optlen) {
     537           4 :                 _cleanup_free_ char *ret = NULL;
     538           4 :                 size_t n = 0, allocated = 0;
     539           4 :                 bool first = true;
     540             : 
     541           8 :                 for (;;) {
     542             :                         const char *label;
     543             :                         uint8_t c;
     544             : 
     545          12 :                         c = optval[pos++];
     546             : 
     547          12 :                         if (c == 0)
     548             :                                 /* End of name */
     549           4 :                                 break;
     550           8 :                         if (c > 63)
     551           0 :                                 return -EBADMSG;
     552             : 
     553             :                         /* Literal label */
     554           8 :                         label = (const char *)&optval[pos];
     555           8 :                         pos += c;
     556           8 :                         if (pos >= optlen)
     557           0 :                                 return -EMSGSIZE;
     558             : 
     559           8 :                         if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX))
     560           0 :                                 return -ENOMEM;
     561             : 
     562           8 :                         if (first)
     563           4 :                                 first = false;
     564             :                         else
     565           4 :                                 ret[n++] = '.';
     566             : 
     567           8 :                         r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX);
     568           8 :                         if (r < 0)
     569           0 :                                 return r;
     570             : 
     571           8 :                         n += r;
     572             :                 }
     573             : 
     574           4 :                 if (n == 0)
     575           0 :                         continue;
     576             : 
     577           4 :                 if (!GREEDY_REALLOC(ret, allocated, n + 1))
     578           0 :                         return -ENOMEM;
     579             : 
     580           4 :                 ret[n] = 0;
     581             : 
     582           4 :                 r = strv_extend(&names, ret);
     583           4 :                 if (r < 0)
     584           0 :                         return r;
     585             : 
     586           4 :                 idx++;
     587             :         }
     588             : 
     589           4 :         *str_arr = TAKE_PTR(names);
     590             : 
     591           4 :         return idx;
     592             : }

Generated by: LCOV version 1.14