Blame inet/inet6_option.c

Packit 6c4009
/* Copyright (C) 2003-2018 Free Software Foundation, Inc.
Packit 6c4009
   This file is part of the GNU C Library.
Packit 6c4009
   Contributed by Ulrich Drepper <drepper@redhat.com>, 2003.
Packit 6c4009
Packit 6c4009
   The GNU C Library is free software; you can redistribute it and/or
Packit 6c4009
   modify it under the terms of the GNU Lesser General Public
Packit 6c4009
   License as published by the Free Software Foundation; either
Packit 6c4009
   version 2.1 of the License, or (at your option) any later version.
Packit 6c4009
Packit 6c4009
   The GNU C Library is distributed in the hope that it will be useful,
Packit 6c4009
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 6c4009
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 6c4009
   Lesser General Public License for more details.
Packit 6c4009
Packit 6c4009
   You should have received a copy of the GNU Lesser General Public
Packit 6c4009
   License along with the GNU C Library; if not, see
Packit 6c4009
   <http://www.gnu.org/licenses/>.  */
Packit 6c4009
Packit 6c4009
#include <assert.h>
Packit 6c4009
#include <string.h>
Packit 6c4009
#include <netinet/in.h>
Packit 6c4009
#include <netinet/ip6.h>
Packit 6c4009
#include <sys/param.h>
Packit 6c4009
Packit 6c4009
Packit 6c4009
static void
Packit 6c4009
add_pad (struct cmsghdr *cmsg, int len)
Packit 6c4009
{
Packit 6c4009
  unsigned char *p = CMSG_DATA (cmsg) + cmsg->cmsg_len - CMSG_LEN (0);
Packit 6c4009
Packit 6c4009
  if (len == 1)
Packit 6c4009
    /* Special handling for 1, a one-byte solution.  */
Packit 6c4009
    *p++ = IP6OPT_PAD1;
Packit 6c4009
  else if (len != 0)
Packit 6c4009
    {
Packit 6c4009
      /* Multibyte padding.  */
Packit 6c4009
      *p++ = IP6OPT_PADN;
Packit 6c4009
      *p++ = len - 2;	/* Discount the two header bytes.  */
Packit 6c4009
      /* The rest is filled with zero.  */
Packit 6c4009
      memset (p, '\0', len - 2);
Packit 6c4009
      p += len - 2;
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  /* Account for the bytes.  */
Packit 6c4009
  cmsg->cmsg_len += len;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
static int
Packit 6c4009
get_opt_end (const uint8_t **result, const uint8_t *startp,
Packit 6c4009
	     const uint8_t *endp)
Packit 6c4009
{
Packit 6c4009
  if (startp >= endp)
Packit 6c4009
    /* Out of bounds.  */
Packit 6c4009
    return -1;
Packit 6c4009
Packit 6c4009
  if (*startp == IP6OPT_PAD1)
Packit 6c4009
    {
Packit 6c4009
      /* Just this one byte.  */
Packit 6c4009
      *result = startp + 1;
Packit 6c4009
      return 0;
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  /* Now we know there must be at least two bytes.  */
Packit 6c4009
  if (startp + 2 > endp
Packit 6c4009
      /* Now we can get the length byte.  */
Packit 6c4009
      || startp + startp[1] + 2 > endp)
Packit 6c4009
    return -1;
Packit 6c4009
Packit 6c4009
  *result = startp + startp[1] + 2;
Packit 6c4009
Packit 6c4009
  return 0;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
static uint8_t *option_alloc (struct cmsghdr *cmsg, int datalen, int multx,
Packit 6c4009
			      int plusy);
Packit 6c4009
Packit 6c4009
Packit 6c4009
/* RFC 2292, 6.3.1
Packit 6c4009
Packit 6c4009
   This function returns the number of bytes required to hold an option
Packit 6c4009
   when it is stored as ancillary data, including the cmsghdr structure
Packit 6c4009
   at the beginning, and any padding at the end (to make its size a
Packit 6c4009
   multiple of 8 bytes).  The argument is the size of the structure
Packit 6c4009
   defining the option, which must include any pad bytes at the
Packit 6c4009
   beginning (the value y in the alignment term "xn + y"), the type
Packit 6c4009
   byte, the length byte, and the option data.  */
Packit 6c4009
int
Packit 6c4009
inet6_option_space (int nbytes)
Packit 6c4009
{
Packit 6c4009
  /* Add room for the extension header.  */
Packit 6c4009
  nbytes += sizeof (struct ip6_ext);
Packit 6c4009
Packit 6c4009
  return CMSG_SPACE (roundup (nbytes, 8));
Packit 6c4009
}
Packit 6c4009
link_warning (inet6_option_space,
Packit 6c4009
	      "inet6_option_space is obsolete, use the RFC 3542 interfaces")
Packit 6c4009
Packit 6c4009
Packit 6c4009
/* RFC 2292, 6.3.2
Packit 6c4009
Packit 6c4009
   This function is called once per ancillary data object that will
Packit 6c4009
   contain either Hop-by-Hop or Destination options.  It returns 0 on
Packit 6c4009
   success or -1 on an error.  */
Packit 6c4009
int
Packit 6c4009
inet6_option_init (void *bp, struct cmsghdr **cmsgp, int type)
Packit 6c4009
{
Packit 6c4009
  /* Only Hop-by-Hop or Destination options allowed.  */
Packit 6c4009
  if (type != IPV6_HOPOPTS && type != IPV6_DSTOPTS)
Packit 6c4009
    return -1;
Packit 6c4009
Packit 6c4009
  /* BP is a pointer to the previously allocated space.  */
Packit 6c4009
  struct cmsghdr *newp = (struct cmsghdr *) bp;
Packit 6c4009
Packit 6c4009
  /* Initialize the message header.
Packit 6c4009
Packit 6c4009
     Length: No data yet, only the cmsghdr struct.  */
Packit 6c4009
  newp->cmsg_len = CMSG_LEN (0);
Packit 6c4009
  /* Originating protocol: obviously IPv6.  */
Packit 6c4009
  newp->cmsg_level = IPPROTO_IPV6;
Packit 6c4009
  /* Message type.  */
Packit 6c4009
  newp->cmsg_type = type;
Packit 6c4009
Packit 6c4009
  /* Pass up the result.  */
Packit 6c4009
  *cmsgp = newp;
Packit 6c4009
Packit 6c4009
  return 0;
Packit 6c4009
}
Packit 6c4009
link_warning (inet6_option_init,
Packit 6c4009
	      "inet6_option_init is obsolete, use the RFC 3542 interfaces")
Packit 6c4009
Packit 6c4009
Packit 6c4009
/* RFC 2292, 6.3.3
Packit 6c4009
Packit 6c4009
   This function appends a Hop-by-Hop option or a Destination option
Packit 6c4009
   into an ancillary data object that has been initialized by
Packit 6c4009
   inet6_option_init().  This function returns 0 if it succeeds or -1 on
Packit 6c4009
   an error.  */
Packit 6c4009
int
Packit 6c4009
inet6_option_append (struct cmsghdr *cmsg, const uint8_t *typep, int multx,
Packit 6c4009
		     int plusy)
Packit 6c4009
{
Packit 6c4009
  /* typep is a pointer to the 8-bit option type.  It is assumed that this
Packit 6c4009
     field is immediately followed by the 8-bit option data length field,
Packit 6c4009
     which is then followed immediately by the option data.
Packit 6c4009
Packit 6c4009
     The option types IP6OPT_PAD1 and IP6OPT_PADN also must be handled.  */
Packit 6c4009
  int len = typep[0] == IP6OPT_PAD1 ? 1 : typep[1] + 2;
Packit 6c4009
Packit 6c4009
  /* Get the pointer to the space in the message.  */
Packit 6c4009
  uint8_t *ptr = option_alloc (cmsg, len, multx, plusy);
Packit 6c4009
  if (ptr == NULL)
Packit 6c4009
    /* Some problem with the parameters.  */
Packit 6c4009
    return -1;
Packit 6c4009
Packit 6c4009
  /* Copy the content.  */
Packit 6c4009
  memcpy (ptr, typep, len);
Packit 6c4009
Packit 6c4009
  return 0;
Packit 6c4009
}
Packit 6c4009
link_warning (inet6_option_append,
Packit 6c4009
	      "inet6_option_append is obsolete, use the RFC 3542 interfaces")
Packit 6c4009
Packit 6c4009
Packit 6c4009
/* RFC 2292, 6.3.4
Packit 6c4009
Packit 6c4009
   This function appends a Hop-by-Hop option or a Destination option
Packit 6c4009
   into an ancillary data object that has been initialized by
Packit 6c4009
   inet6_option_init().  This function returns a pointer to the 8-bit
Packit 6c4009
   option type field that starts the option on success, or NULL on an
Packit 6c4009
   error.  */
Packit 6c4009
static uint8_t *
Packit 6c4009
option_alloc (struct cmsghdr *cmsg, int datalen, int multx, int plusy)
Packit 6c4009
{
Packit 6c4009
  /* The RFC limits the value of the alignment values.  */
Packit 6c4009
  if ((multx != 1 && multx != 2 && multx != 4 && multx != 8)
Packit 6c4009
      || ! (plusy >= 0 && plusy <= 7))
Packit 6c4009
    return NULL;
Packit 6c4009
Packit 6c4009
  /* Current data size.  */
Packit 6c4009
  int dsize = cmsg->cmsg_len - CMSG_LEN (0);
Packit 6c4009
Packit 6c4009
  /* The first two bytes of the option are for the extended header.  */
Packit 6c4009
  if (__glibc_unlikely (dsize == 0))
Packit 6c4009
    {
Packit 6c4009
      cmsg->cmsg_len += sizeof (struct ip6_ext);
Packit 6c4009
      dsize = sizeof (struct ip6_ext);
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  /* First add padding.  */
Packit 6c4009
  add_pad (cmsg, ((multx - (dsize & (multx - 1))) & (multx - 1)) + plusy);
Packit 6c4009
Packit 6c4009
  /* Return the pointer to the start of the option space.  */
Packit 6c4009
  uint8_t *result = CMSG_DATA (cmsg) + cmsg->cmsg_len - CMSG_LEN (0);
Packit 6c4009
  cmsg->cmsg_len += datalen;
Packit 6c4009
Packit 6c4009
  /* The extended option header length is measured in 8-byte groups.
Packit 6c4009
     To represent the current length we might have to add padding.  */
Packit 6c4009
  dsize = cmsg->cmsg_len - CMSG_LEN (0);
Packit 6c4009
  add_pad (cmsg, (8 - (dsize & (8 - 1))) & (8 - 1));
Packit 6c4009
Packit 6c4009
  /* Record the new length of the option.  */
Packit 6c4009
  assert (((cmsg->cmsg_len - CMSG_LEN (0)) % 8) == 0);
Packit 6c4009
  int len8b = (cmsg->cmsg_len - CMSG_LEN (0)) / 8 - 1;
Packit 6c4009
  if (len8b >= 256)
Packit 6c4009
    /* Too long.  */
Packit 6c4009
    return NULL;
Packit 6c4009
Packit 6c4009
  struct ip6_ext *ie = (void *) CMSG_DATA (cmsg);
Packit 6c4009
  ie->ip6e_len = len8b;
Packit 6c4009
Packit 6c4009
  return result;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
uint8_t *
Packit 6c4009
inet6_option_alloc (struct cmsghdr *cmsg, int datalen, int multx, int plusy)
Packit 6c4009
{
Packit 6c4009
  return option_alloc (cmsg, datalen, multx, plusy);
Packit 6c4009
}
Packit 6c4009
link_warning (inet6_option_alloc,
Packit 6c4009
	      "inet6_option_alloc is obsolete, use the RFC 3542 interfaces")
Packit 6c4009
Packit 6c4009
Packit 6c4009
/* RFC 2292, 6.3.5
Packit 6c4009
Packit 6c4009
   This function processes the next Hop-by-Hop option or Destination
Packit 6c4009
   option in an ancillary data object.  If another option remains to be
Packit 6c4009
   processed, the return value of the function is 0 and *tptrp points to
Packit 6c4009
   the 8-bit option type field (which is followed by the 8-bit option
Packit 6c4009
   data length, followed by the option data).  If no more options remain
Packit 6c4009
   to be processed, the return value is -1 and *tptrp is NULL.  If an
Packit 6c4009
   error occurs, the return value is -1 and *tptrp is not NULL.  */
Packit 6c4009
int
Packit 6c4009
inet6_option_next (const struct cmsghdr *cmsg, uint8_t **tptrp)
Packit 6c4009
{
Packit 6c4009
  /* Make sure it is an option of the right type.  */
Packit 6c4009
  if (cmsg->cmsg_level != IPPROTO_IPV6
Packit 6c4009
      || (cmsg->cmsg_type != IPV6_HOPOPTS && cmsg->cmsg_type != IPV6_DSTOPTS))
Packit 6c4009
    return -1;
Packit 6c4009
Packit 6c4009
  /* Pointer to the extension header.  We only compute the address, we
Packit 6c4009
     don't access anything yet.  */
Packit 6c4009
  const struct ip6_ext *ip6e = (const struct ip6_ext *) CMSG_DATA (cmsg);
Packit 6c4009
Packit 6c4009
  /* Make sure the message is long enough.  */
Packit 6c4009
  if (cmsg->cmsg_len < CMSG_LEN (sizeof (struct ip6_ext))
Packit 6c4009
      /* Now we can access the extension header.  */
Packit 6c4009
      || cmsg->cmsg_len < CMSG_LEN ((ip6e->ip6e_len + 1) * 8))
Packit 6c4009
    /* Too small.  */
Packit 6c4009
    return -1;
Packit 6c4009
Packit 6c4009
  /* Determine the address of the byte past the message.  */
Packit 6c4009
  const uint8_t *endp = CMSG_DATA (cmsg) + (ip6e->ip6e_len + 1) * 8;
Packit 6c4009
Packit 6c4009
  const uint8_t *result;
Packit 6c4009
  if (*tptrp == NULL)
Packit 6c4009
    /* This is the first call, return the first option if there is one.  */
Packit 6c4009
    result = (const uint8_t *) (ip6e + 1);
Packit 6c4009
  else
Packit 6c4009
    {
Packit 6c4009
      /* Make sure *TPTRP points to a beginning of a new option in
Packit 6c4009
	 the message.  The upper limit is checked in get_opt_end.  */
Packit 6c4009
      if (*tptrp < (const uint8_t *) (ip6e + 1))
Packit 6c4009
	return -1;
Packit 6c4009
Packit 6c4009
      /* Get the beginning of the next option.  */
Packit 6c4009
      if (get_opt_end (&result, *tptrp, endp) != 0)
Packit 6c4009
	return -1;
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  /* We know where the next option starts.  */
Packit 6c4009
  *tptrp = (uint8_t *) result;
Packit 6c4009
Packit 6c4009
  /* Check the option is fully represented in the message.  */
Packit 6c4009
  return get_opt_end (&result, result, endp);
Packit 6c4009
}
Packit 6c4009
link_warning (inet6_option_next,
Packit 6c4009
	      "inet6_option_next is obsolete, use the RFC 3542 interfaces")
Packit 6c4009
Packit 6c4009
Packit 6c4009
/* RFC 2292, 6.3.6
Packit 6c4009
Packit 6c4009
   This function is similar to the previously described
Packit 6c4009
   inet6_option_next() function, except this function lets the caller
Packit 6c4009
   specify the option type to be searched for, instead of always
Packit 6c4009
   returning the next option in the ancillary data object.  cmsg is a
Packit 6c4009
   pointer to cmsghdr structure of which cmsg_level equals IPPROTO_IPV6
Packit 6c4009
   and cmsg_type equals either IPV6_HOPOPTS or IPV6_DSTOPTS.  */
Packit 6c4009
int
Packit 6c4009
inet6_option_find (const struct cmsghdr *cmsg, uint8_t **tptrp, int type)
Packit 6c4009
{
Packit 6c4009
  /* Make sure it is an option of the right type.  */
Packit 6c4009
  if (cmsg->cmsg_level != IPPROTO_IPV6
Packit 6c4009
      || (cmsg->cmsg_type != IPV6_HOPOPTS && cmsg->cmsg_type != IPV6_DSTOPTS))
Packit 6c4009
    return -1;
Packit 6c4009
Packit 6c4009
  /* Pointer to the extension header.  We only compute the address, we
Packit 6c4009
     don't access anything yet.  */
Packit 6c4009
  const struct ip6_ext *ip6e = (const struct ip6_ext *) CMSG_DATA (cmsg);
Packit 6c4009
Packit 6c4009
  /* Make sure the message is long enough.  */
Packit 6c4009
  if (cmsg->cmsg_len < CMSG_LEN (sizeof (struct ip6_ext))
Packit 6c4009
      /* Now we can access the extension header.  */
Packit 6c4009
      || cmsg->cmsg_len < CMSG_LEN ((ip6e->ip6e_len + 1) * 8))
Packit 6c4009
    /* Too small.  */
Packit 6c4009
    return -1;
Packit 6c4009
Packit 6c4009
  /* Determine the address of the byte past the message.  */
Packit 6c4009
  const uint8_t *endp = CMSG_DATA (cmsg) + (ip6e->ip6e_len + 1) * 8;
Packit 6c4009
Packit 6c4009
  const uint8_t *next;
Packit 6c4009
  if (*tptrp == NULL)
Packit 6c4009
    /* This is the first call, return the first option if there is one.  */
Packit 6c4009
    next = (const uint8_t *) (ip6e + 1);
Packit 6c4009
  else
Packit 6c4009
    {
Packit 6c4009
      /* Make sure *TPTRP points to a beginning of a new option in
Packit 6c4009
	 the message.  The upper limit is checked in get_opt_end.  */
Packit 6c4009
      if (*tptrp < (const uint8_t *) (ip6e + 1))
Packit 6c4009
	return -1;
Packit 6c4009
Packit 6c4009
      /* Get the beginning of the next option.  */
Packit 6c4009
      if (get_opt_end (&next, *tptrp, endp) != 0)
Packit 6c4009
	return -1;
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  /* Now search for the appropriate typed entry.  */
Packit 6c4009
  const uint8_t *result;
Packit 6c4009
  do
Packit 6c4009
    {
Packit 6c4009
      result = next;
Packit 6c4009
Packit 6c4009
      /* Get the end of this entry.  */
Packit 6c4009
      if (get_opt_end (&next, result, endp) != 0)
Packit 6c4009
	return -1;
Packit 6c4009
    }
Packit 6c4009
  while (*result != type);
Packit 6c4009
Packit 6c4009
  /* We know where the next option starts.  */
Packit 6c4009
  *tptrp = (uint8_t *) result;
Packit 6c4009
Packit 6c4009
  /* Success.  */
Packit 6c4009
  return 0;
Packit 6c4009
}
Packit 6c4009
link_warning (inet6_option_find,
Packit 6c4009
	      "inet6_option_find is obsolete, use the RFC 3542 interfaces")