mirror of
				https://sourceware.org/git/glibc.git
				synced 2025-11-03 20:53:13 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			282 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			282 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/* Copyright (C) 2006-2017 Free Software Foundation, Inc.
 | 
						|
   This file is part of the GNU C Library.
 | 
						|
   Contributed by Ulrich Drepper <drepper@redhat.com>, 2006.
 | 
						|
 | 
						|
   The GNU C Library is free software; you can redistribute it and/or
 | 
						|
   modify it under the terms of the GNU Lesser General Public
 | 
						|
   License as published by the Free Software Foundation; either
 | 
						|
   version 2.1 of the License, or (at your option) any later version.
 | 
						|
 | 
						|
   The GNU C Library is distributed in the hope that it will be useful,
 | 
						|
   but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | 
						|
   Lesser General Public License for more details.
 | 
						|
 | 
						|
   You should have received a copy of the GNU Lesser General Public
 | 
						|
   License along with the GNU C Library; if not, see
 | 
						|
   <http://www.gnu.org/licenses/>.  */
 | 
						|
 | 
						|
#include <string.h>
 | 
						|
#include <netinet/in.h>
 | 
						|
#include <netinet/ip6.h>
 | 
						|
 | 
						|
 | 
						|
/* RFC 3542, 10.1
 | 
						|
 | 
						|
   This function returns the number of bytes needed for the empty
 | 
						|
   extension header i.e., without any options.  If EXTBUF is not NULL it
 | 
						|
   also initializes the extension header to have the correct length
 | 
						|
   field.  In that case if the EXTLEN value is not a positive (i.e.,
 | 
						|
   non-zero) multiple of 8 the function fails and returns -1.  */
 | 
						|
int
 | 
						|
inet6_opt_init (void *extbuf, socklen_t extlen)
 | 
						|
{
 | 
						|
  if (extbuf != NULL)
 | 
						|
    {
 | 
						|
      if (extlen <= 0 || (extlen % 8) != 0 || extlen > 256 * 8)
 | 
						|
	return -1;
 | 
						|
 | 
						|
      /* Fill in the length in units of 8 octets.  */
 | 
						|
      struct ip6_hbh *extp = (struct ip6_hbh *) extbuf;
 | 
						|
 | 
						|
      /* RFC 2460 requires that the header extension length is the
 | 
						|
	 length of the option header in 8-byte units, not including
 | 
						|
	 the first 8 bytes.  Hence we have to subtract one.  */
 | 
						|
      extp->ip6h_len = extlen / 8 - 1;
 | 
						|
    }
 | 
						|
 | 
						|
  return sizeof (struct ip6_hbh);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void
 | 
						|
add_padding (uint8_t *extbuf, int offset, int npad)
 | 
						|
{
 | 
						|
  if (npad == 1)
 | 
						|
    extbuf[offset] = IP6OPT_PAD1;
 | 
						|
  else if (npad > 0)
 | 
						|
    {
 | 
						|
      struct ip6_opt *pad_opt = (struct ip6_opt *) (extbuf + offset);
 | 
						|
 | 
						|
      pad_opt->ip6o_type = IP6OPT_PADN;
 | 
						|
      pad_opt->ip6o_len = npad - sizeof (struct ip6_opt);
 | 
						|
      /* Clear the memory used by the padding.  */
 | 
						|
      memset (pad_opt + 1, '\0', pad_opt->ip6o_len);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/* RFC 3542, 10.2
 | 
						|
 | 
						|
   This function returns the updated total length taking into account
 | 
						|
   adding an option with length 'len' and alignment 'align'.  If
 | 
						|
   EXTBUF is not NULL then, in addition to returning the length, the
 | 
						|
   function inserts any needed pad option, initializes the option
 | 
						|
   (setting the type and length fields) and returns a pointer to the
 | 
						|
   location for the option content in databufp.  If the option does
 | 
						|
   not fit in the extension header buffer the function returns -1.  */
 | 
						|
int
 | 
						|
inet6_opt_append (void *extbuf, socklen_t extlen, int offset, uint8_t type,
 | 
						|
		  socklen_t len, uint8_t align, void **databufp)
 | 
						|
{
 | 
						|
  /* Check minimum offset.  */
 | 
						|
  if (offset < sizeof (struct ip6_hbh))
 | 
						|
    return -1;
 | 
						|
 | 
						|
  /* One cannot add padding options.  */
 | 
						|
  if (type == IP6OPT_PAD1 || type == IP6OPT_PADN)
 | 
						|
    return -1;
 | 
						|
 | 
						|
  /* The option length must fit in one octet.  */
 | 
						|
  if (len > 255)
 | 
						|
    return -1;
 | 
						|
 | 
						|
  /* The alignment can only by 1, 2, 4, or 8 and must not exceed the
 | 
						|
     option length.  */
 | 
						|
  if (align == 0 || align > 8 || (align & (align - 1)) != 0 || align > len)
 | 
						|
    return -1;
 | 
						|
 | 
						|
  /* Determine the needed padding for alignment.  Following the
 | 
						|
     current content of the buffer we have the is the IPv6 option type
 | 
						|
     and length, followed immediately by the data.  The data has the
 | 
						|
     alignment constraints.  Therefore padding must be inserted in the
 | 
						|
     form of padding options before the new option. */
 | 
						|
  int data_offset = offset + sizeof (struct ip6_opt);
 | 
						|
  int npad = (align - data_offset % align) & (align - 1);
 | 
						|
 | 
						|
  if (extbuf != NULL)
 | 
						|
    {
 | 
						|
      /* Now we can check whether the buffer is large enough.  */
 | 
						|
      if (data_offset + npad + len > extlen)
 | 
						|
	return -1;
 | 
						|
 | 
						|
      add_padding (extbuf, offset, npad);
 | 
						|
 | 
						|
      offset += npad;
 | 
						|
 | 
						|
      /* Now prepare the option itself.  */
 | 
						|
      struct ip6_opt *opt = (struct ip6_opt *) ((uint8_t *) extbuf + offset);
 | 
						|
 | 
						|
      opt->ip6o_type = type;
 | 
						|
      opt->ip6o_len = len;
 | 
						|
 | 
						|
      *databufp = opt + 1;
 | 
						|
    }
 | 
						|
  else
 | 
						|
    offset += npad;
 | 
						|
 | 
						|
  return offset + sizeof (struct ip6_opt) + len;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/* RFC 3542, 10.3
 | 
						|
 | 
						|
   This function returns the updated total length taking into account
 | 
						|
   the final padding of the extension header to make it a multiple of
 | 
						|
   8 bytes.  If EXTBUF is not NULL the function also initializes the
 | 
						|
   option by inserting a Pad1 or PadN option of the proper length.  */
 | 
						|
int
 | 
						|
inet6_opt_finish (void *extbuf, socklen_t extlen, int offset)
 | 
						|
{
 | 
						|
  /* Check minimum offset.  */
 | 
						|
  if (offset < sizeof (struct ip6_hbh))
 | 
						|
    return -1;
 | 
						|
 | 
						|
  /* Required padding at the end.  */
 | 
						|
  int npad = (8 - (offset & 7)) & 7;
 | 
						|
 | 
						|
  if (extbuf != NULL)
 | 
						|
    {
 | 
						|
      /* Make sure the buffer is large enough.  */
 | 
						|
      if (offset + npad > extlen)
 | 
						|
	return -1;
 | 
						|
 | 
						|
      add_padding (extbuf, offset, npad);
 | 
						|
    }
 | 
						|
 | 
						|
  return offset + npad;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/* RFC 3542, 10.4
 | 
						|
 | 
						|
   This function inserts data items of various sizes in the data
 | 
						|
   portion of the option.  VAL should point to the data to be
 | 
						|
   inserted.  OFFSET specifies where in the data portion of the option
 | 
						|
   the value should be inserted; the first byte after the option type
 | 
						|
   and length is accessed by specifying an offset of zero.  */
 | 
						|
int
 | 
						|
inet6_opt_set_val (void *databuf, int offset, void *val, socklen_t vallen)
 | 
						|
{
 | 
						|
  memcpy ((uint8_t *) databuf + offset, val, vallen);
 | 
						|
 | 
						|
  return offset + vallen;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/* RFC 3542, 10.5
 | 
						|
 | 
						|
   This function parses received option extension headers returning
 | 
						|
   the next option.  EXTBUF and EXTLEN specifies the extension header.
 | 
						|
   OFFSET should either be zero (for the first option) or the length
 | 
						|
   returned by a previous call to 'inet6_opt_next' or
 | 
						|
   'inet6_opt_find'.  It specifies the position where to continue
 | 
						|
   scanning the extension buffer.  */
 | 
						|
int
 | 
						|
inet6_opt_next (void *extbuf, socklen_t extlen, int offset, uint8_t *typep,
 | 
						|
		socklen_t *lenp, void **databufp)
 | 
						|
{
 | 
						|
  if (offset == 0)
 | 
						|
    offset = sizeof (struct ip6_hbh);
 | 
						|
  else if (offset < sizeof (struct ip6_hbh))
 | 
						|
    return -1;
 | 
						|
 | 
						|
  while (offset < extlen)
 | 
						|
    {
 | 
						|
      struct ip6_opt *opt = (struct ip6_opt *) ((uint8_t *) extbuf + offset);
 | 
						|
 | 
						|
      if (opt->ip6o_type == IP6OPT_PAD1)
 | 
						|
	/* Single byte padding.  */
 | 
						|
	++offset;
 | 
						|
      else if (opt->ip6o_type == IP6OPT_PADN)
 | 
						|
	offset += sizeof (struct ip6_opt) + opt->ip6o_len;
 | 
						|
      else
 | 
						|
	{
 | 
						|
	  /* Check whether the option is valid.  */
 | 
						|
	  offset += sizeof (struct ip6_opt) + opt->ip6o_len;
 | 
						|
	  if (offset > extlen)
 | 
						|
	    return -1;
 | 
						|
 | 
						|
	  *typep = opt->ip6o_type;
 | 
						|
	  *lenp = opt->ip6o_len;
 | 
						|
	  *databufp = opt + 1;
 | 
						|
	  return offset;
 | 
						|
	}
 | 
						|
    }
 | 
						|
 | 
						|
  return -1;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/* RFC 3542, 10.6
 | 
						|
 | 
						|
   This function is similar to the previously described
 | 
						|
   'inet6_opt_next' function, except this function lets the caller
 | 
						|
   specify the option type to be searched for, instead of always
 | 
						|
   returning the next option in the extension header.  */
 | 
						|
int
 | 
						|
inet6_opt_find (void *extbuf, socklen_t extlen, int offset, uint8_t type,
 | 
						|
		socklen_t *lenp, void **databufp)
 | 
						|
{
 | 
						|
  if (offset == 0)
 | 
						|
    offset = sizeof (struct ip6_hbh);
 | 
						|
  else if (offset < sizeof (struct ip6_hbh))
 | 
						|
    return -1;
 | 
						|
 | 
						|
  while (offset < extlen)
 | 
						|
    {
 | 
						|
      struct ip6_opt *opt = (struct ip6_opt *) ((uint8_t *) extbuf + offset);
 | 
						|
 | 
						|
      if (opt->ip6o_type == IP6OPT_PAD1)
 | 
						|
	{
 | 
						|
	  /* Single byte padding.  */
 | 
						|
	  ++offset;
 | 
						|
	  if (type == IP6OPT_PAD1)
 | 
						|
	    {
 | 
						|
	      *lenp = 0;
 | 
						|
	      *databufp = (uint8_t *) extbuf + offset;
 | 
						|
	      return offset;
 | 
						|
	    }
 | 
						|
	}
 | 
						|
      else if (opt->ip6o_type != type)
 | 
						|
	offset += sizeof (struct ip6_opt) + opt->ip6o_len;
 | 
						|
      else
 | 
						|
	{
 | 
						|
	  /* Check whether the option is valid.  */
 | 
						|
	  offset += sizeof (struct ip6_opt) + opt->ip6o_len;
 | 
						|
	  if (offset > extlen)
 | 
						|
	    return -1;
 | 
						|
 | 
						|
	  *lenp = opt->ip6o_len;
 | 
						|
	  *databufp = opt + 1;
 | 
						|
	  return offset;
 | 
						|
	}
 | 
						|
    }
 | 
						|
 | 
						|
  return -1;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/* RFC 3542, 10.7
 | 
						|
 | 
						|
   This function extracts data items of various sizes in the data
 | 
						|
   portion of the option.  */
 | 
						|
int
 | 
						|
inet6_opt_get_val (void *databuf, int offset, void *val, socklen_t vallen)
 | 
						|
{
 | 
						|
  memcpy (val, (uint8_t *) databuf + offset, vallen);
 | 
						|
 | 
						|
  return offset + vallen;
 | 
						|
}
 |