(root)/
glibc-2.38/
inet/
inet6_option.c
       1  /* Copyright (C) 2003-2023 Free Software Foundation, Inc.
       2     This file is part of the GNU C Library.
       3  
       4     The GNU C Library is free software; you can redistribute it and/or
       5     modify it under the terms of the GNU Lesser General Public
       6     License as published by the Free Software Foundation; either
       7     version 2.1 of the License, or (at your option) any later version.
       8  
       9     The GNU C Library is distributed in the hope that it will be useful,
      10     but WITHOUT ANY WARRANTY; without even the implied warranty of
      11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      12     Lesser General Public License for more details.
      13  
      14     You should have received a copy of the GNU Lesser General Public
      15     License along with the GNU C Library; if not, see
      16     <https://www.gnu.org/licenses/>.  */
      17  
      18  #include <assert.h>
      19  #include <string.h>
      20  #include <netinet/in.h>
      21  #include <netinet/ip6.h>
      22  #include <sys/param.h>
      23  
      24  
      25  static void
      26  add_pad (struct cmsghdr *cmsg, int len)
      27  {
      28    unsigned char *p = CMSG_DATA (cmsg) + cmsg->cmsg_len - CMSG_LEN (0);
      29  
      30    if (len == 1)
      31      /* Special handling for 1, a one-byte solution.  */
      32      *p++ = IP6OPT_PAD1;
      33    else if (len != 0)
      34      {
      35        /* Multibyte padding.  */
      36        *p++ = IP6OPT_PADN;
      37        *p++ = len - 2;	/* Discount the two header bytes.  */
      38        /* The rest is filled with zero.  */
      39        memset (p, '\0', len - 2);
      40        p += len - 2;
      41      }
      42  
      43    /* Account for the bytes.  */
      44    cmsg->cmsg_len += len;
      45  }
      46  
      47  
      48  static int
      49  get_opt_end (const uint8_t **result, const uint8_t *startp,
      50  	     const uint8_t *endp)
      51  {
      52    if (startp >= endp)
      53      /* Out of bounds.  */
      54      return -1;
      55  
      56    if (*startp == IP6OPT_PAD1)
      57      {
      58        /* Just this one byte.  */
      59        *result = startp + 1;
      60        return 0;
      61      }
      62  
      63    /* Now we know there must be at least two bytes.  */
      64    if (startp + 2 > endp
      65        /* Now we can get the length byte.  */
      66        || startp + startp[1] + 2 > endp)
      67      return -1;
      68  
      69    *result = startp + startp[1] + 2;
      70  
      71    return 0;
      72  }
      73  
      74  
      75  static uint8_t *option_alloc (struct cmsghdr *cmsg, int datalen, int multx,
      76  			      int plusy);
      77  
      78  
      79  /* RFC 2292, 6.3.1
      80  
      81     This function returns the number of bytes required to hold an option
      82     when it is stored as ancillary data, including the cmsghdr structure
      83     at the beginning, and any padding at the end (to make its size a
      84     multiple of 8 bytes).  The argument is the size of the structure
      85     defining the option, which must include any pad bytes at the
      86     beginning (the value y in the alignment term "xn + y"), the type
      87     byte, the length byte, and the option data.  */
      88  int
      89  inet6_option_space (int nbytes)
      90  {
      91    /* Add room for the extension header.  */
      92    nbytes += sizeof (struct ip6_ext);
      93  
      94    return CMSG_SPACE (roundup (nbytes, 8));
      95  }
      96  link_warning (inet6_option_space,
      97  	      "inet6_option_space is obsolete, use the RFC 3542 interfaces")
      98  
      99  
     100  /* RFC 2292, 6.3.2
     101  
     102     This function is called once per ancillary data object that will
     103     contain either Hop-by-Hop or Destination options.  It returns 0 on
     104     success or -1 on an error.  */
     105  int
     106  inet6_option_init (void *bp, struct cmsghdr **cmsgp, int type)
     107  {
     108    /* Only Hop-by-Hop or Destination options allowed.  */
     109    if (type != IPV6_HOPOPTS && type != IPV6_DSTOPTS)
     110      return -1;
     111  
     112    /* BP is a pointer to the previously allocated space.  */
     113    struct cmsghdr *newp = (struct cmsghdr *) bp;
     114  
     115    /* Initialize the message header.
     116  
     117       Length: No data yet, only the cmsghdr struct.  */
     118    newp->cmsg_len = CMSG_LEN (0);
     119    /* Originating protocol: obviously IPv6.  */
     120    newp->cmsg_level = IPPROTO_IPV6;
     121    /* Message type.  */
     122    newp->cmsg_type = type;
     123  
     124    /* Pass up the result.  */
     125    *cmsgp = newp;
     126  
     127    return 0;
     128  }
     129  link_warning (inet6_option_init,
     130  	      "inet6_option_init is obsolete, use the RFC 3542 interfaces")
     131  
     132  
     133  /* RFC 2292, 6.3.3
     134  
     135     This function appends a Hop-by-Hop option or a Destination option
     136     into an ancillary data object that has been initialized by
     137     inet6_option_init().  This function returns 0 if it succeeds or -1 on
     138     an error.  */
     139  int
     140  inet6_option_append (struct cmsghdr *cmsg, const uint8_t *typep, int multx,
     141  		     int plusy)
     142  {
     143    /* typep is a pointer to the 8-bit option type.  It is assumed that this
     144       field is immediately followed by the 8-bit option data length field,
     145       which is then followed immediately by the option data.
     146  
     147       The option types IP6OPT_PAD1 and IP6OPT_PADN also must be handled.  */
     148    int len = typep[0] == IP6OPT_PAD1 ? 1 : typep[1] + 2;
     149  
     150    /* Get the pointer to the space in the message.  */
     151    uint8_t *ptr = option_alloc (cmsg, len, multx, plusy);
     152    if (ptr == NULL)
     153      /* Some problem with the parameters.  */
     154      return -1;
     155  
     156    /* Copy the content.  */
     157    memcpy (ptr, typep, len);
     158  
     159    return 0;
     160  }
     161  link_warning (inet6_option_append,
     162  	      "inet6_option_append is obsolete, use the RFC 3542 interfaces")
     163  
     164  
     165  /* RFC 2292, 6.3.4
     166  
     167     This function appends a Hop-by-Hop option or a Destination option
     168     into an ancillary data object that has been initialized by
     169     inet6_option_init().  This function returns a pointer to the 8-bit
     170     option type field that starts the option on success, or NULL on an
     171     error.  */
     172  static uint8_t *
     173  option_alloc (struct cmsghdr *cmsg, int datalen, int multx, int plusy)
     174  {
     175    /* The RFC limits the value of the alignment values.  */
     176    if ((multx != 1 && multx != 2 && multx != 4 && multx != 8)
     177        || ! (plusy >= 0 && plusy <= 7))
     178      return NULL;
     179  
     180    /* Current data size.  */
     181    int dsize = cmsg->cmsg_len - CMSG_LEN (0);
     182  
     183    /* The first two bytes of the option are for the extended header.  */
     184    if (__glibc_unlikely (dsize == 0))
     185      {
     186        cmsg->cmsg_len += sizeof (struct ip6_ext);
     187        dsize = sizeof (struct ip6_ext);
     188      }
     189  
     190    /* First add padding.  */
     191    add_pad (cmsg, ((multx - (dsize & (multx - 1))) & (multx - 1)) + plusy);
     192  
     193    /* Return the pointer to the start of the option space.  */
     194    uint8_t *result = CMSG_DATA (cmsg) + cmsg->cmsg_len - CMSG_LEN (0);
     195    cmsg->cmsg_len += datalen;
     196  
     197    /* The extended option header length is measured in 8-byte groups.
     198       To represent the current length we might have to add padding.  */
     199    dsize = cmsg->cmsg_len - CMSG_LEN (0);
     200    add_pad (cmsg, (8 - (dsize & (8 - 1))) & (8 - 1));
     201  
     202    /* Record the new length of the option.  */
     203    assert (((cmsg->cmsg_len - CMSG_LEN (0)) % 8) == 0);
     204    int len8b = (cmsg->cmsg_len - CMSG_LEN (0)) / 8 - 1;
     205    if (len8b >= 256)
     206      /* Too long.  */
     207      return NULL;
     208  
     209    struct ip6_ext *ie = (void *) CMSG_DATA (cmsg);
     210    ie->ip6e_len = len8b;
     211  
     212    return result;
     213  }
     214  
     215  
     216  uint8_t *
     217  inet6_option_alloc (struct cmsghdr *cmsg, int datalen, int multx, int plusy)
     218  {
     219    return option_alloc (cmsg, datalen, multx, plusy);
     220  }
     221  link_warning (inet6_option_alloc,
     222  	      "inet6_option_alloc is obsolete, use the RFC 3542 interfaces")
     223  
     224  
     225  /* RFC 2292, 6.3.5
     226  
     227     This function processes the next Hop-by-Hop option or Destination
     228     option in an ancillary data object.  If another option remains to be
     229     processed, the return value of the function is 0 and *tptrp points to
     230     the 8-bit option type field (which is followed by the 8-bit option
     231     data length, followed by the option data).  If no more options remain
     232     to be processed, the return value is -1 and *tptrp is NULL.  If an
     233     error occurs, the return value is -1 and *tptrp is not NULL.  */
     234  int
     235  inet6_option_next (const struct cmsghdr *cmsg, uint8_t **tptrp)
     236  {
     237    /* Make sure it is an option of the right type.  */
     238    if (cmsg->cmsg_level != IPPROTO_IPV6
     239        || (cmsg->cmsg_type != IPV6_HOPOPTS && cmsg->cmsg_type != IPV6_DSTOPTS))
     240      return -1;
     241  
     242    /* Pointer to the extension header.  We only compute the address, we
     243       don't access anything yet.  */
     244    const struct ip6_ext *ip6e = (const struct ip6_ext *) CMSG_DATA (cmsg);
     245  
     246    /* Make sure the message is long enough.  */
     247    if (cmsg->cmsg_len < CMSG_LEN (sizeof (struct ip6_ext))
     248        /* Now we can access the extension header.  */
     249        || cmsg->cmsg_len < CMSG_LEN ((ip6e->ip6e_len + 1) * 8))
     250      /* Too small.  */
     251      return -1;
     252  
     253    /* Determine the address of the byte past the message.  */
     254    const uint8_t *endp = CMSG_DATA (cmsg) + (ip6e->ip6e_len + 1) * 8;
     255  
     256    const uint8_t *result;
     257    if (*tptrp == NULL)
     258      /* This is the first call, return the first option if there is one.  */
     259      result = (const uint8_t *) (ip6e + 1);
     260    else
     261      {
     262        /* Make sure *TPTRP points to a beginning of a new option in
     263  	 the message.  The upper limit is checked in get_opt_end.  */
     264        if (*tptrp < (const uint8_t *) (ip6e + 1))
     265  	return -1;
     266  
     267        /* Get the beginning of the next option.  */
     268        if (get_opt_end (&result, *tptrp, endp) != 0)
     269  	return -1;
     270      }
     271  
     272    /* We know where the next option starts.  */
     273    *tptrp = (uint8_t *) result;
     274  
     275    /* Check the option is fully represented in the message.  */
     276    return get_opt_end (&result, result, endp);
     277  }
     278  link_warning (inet6_option_next,
     279  	      "inet6_option_next is obsolete, use the RFC 3542 interfaces")
     280  
     281  
     282  /* RFC 2292, 6.3.6
     283  
     284     This function is similar to the previously described
     285     inet6_option_next() function, except this function lets the caller
     286     specify the option type to be searched for, instead of always
     287     returning the next option in the ancillary data object.  cmsg is a
     288     pointer to cmsghdr structure of which cmsg_level equals IPPROTO_IPV6
     289     and cmsg_type equals either IPV6_HOPOPTS or IPV6_DSTOPTS.  */
     290  int
     291  inet6_option_find (const struct cmsghdr *cmsg, uint8_t **tptrp, int type)
     292  {
     293    /* Make sure it is an option of the right type.  */
     294    if (cmsg->cmsg_level != IPPROTO_IPV6
     295        || (cmsg->cmsg_type != IPV6_HOPOPTS && cmsg->cmsg_type != IPV6_DSTOPTS))
     296      return -1;
     297  
     298    /* Pointer to the extension header.  We only compute the address, we
     299       don't access anything yet.  */
     300    const struct ip6_ext *ip6e = (const struct ip6_ext *) CMSG_DATA (cmsg);
     301  
     302    /* Make sure the message is long enough.  */
     303    if (cmsg->cmsg_len < CMSG_LEN (sizeof (struct ip6_ext))
     304        /* Now we can access the extension header.  */
     305        || cmsg->cmsg_len < CMSG_LEN ((ip6e->ip6e_len + 1) * 8))
     306      /* Too small.  */
     307      return -1;
     308  
     309    /* Determine the address of the byte past the message.  */
     310    const uint8_t *endp = CMSG_DATA (cmsg) + (ip6e->ip6e_len + 1) * 8;
     311  
     312    const uint8_t *next;
     313    if (*tptrp == NULL)
     314      /* This is the first call, return the first option if there is one.  */
     315      next = (const uint8_t *) (ip6e + 1);
     316    else
     317      {
     318        /* Make sure *TPTRP points to a beginning of a new option in
     319  	 the message.  The upper limit is checked in get_opt_end.  */
     320        if (*tptrp < (const uint8_t *) (ip6e + 1))
     321  	return -1;
     322  
     323        /* Get the beginning of the next option.  */
     324        if (get_opt_end (&next, *tptrp, endp) != 0)
     325  	return -1;
     326      }
     327  
     328    /* Now search for the appropriate typed entry.  */
     329    const uint8_t *result;
     330    do
     331      {
     332        result = next;
     333  
     334        /* Get the end of this entry.  */
     335        if (get_opt_end (&next, result, endp) != 0)
     336  	return -1;
     337      }
     338    while (*result != type);
     339  
     340    /* We know where the next option starts.  */
     341    *tptrp = (uint8_t *) result;
     342  
     343    /* Success.  */
     344    return 0;
     345  }
     346  link_warning (inet6_option_find,
     347  	      "inet6_option_find is obsolete, use the RFC 3542 interfaces")