(root)/
glibc-2.38/
inet/
inet6_opt.c
       1  /* Copyright (C) 2006-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 <string.h>
      19  #include <netinet/in.h>
      20  #include <netinet/ip6.h>
      21  
      22  
      23  /* RFC 3542, 10.1
      24  
      25     This function returns the number of bytes needed for the empty
      26     extension header i.e., without any options.  If EXTBUF is not NULL it
      27     also initializes the extension header to have the correct length
      28     field.  In that case if the EXTLEN value is not a positive (i.e.,
      29     non-zero) multiple of 8 the function fails and returns -1.  */
      30  int
      31  inet6_opt_init (void *extbuf, socklen_t extlen)
      32  {
      33    if (extbuf != NULL)
      34      {
      35        if (extlen <= 0 || (extlen % 8) != 0 || extlen > 256 * 8)
      36  	return -1;
      37  
      38        /* Fill in the length in units of 8 octets.  */
      39        struct ip6_hbh *extp = (struct ip6_hbh *) extbuf;
      40  
      41        /* RFC 2460 requires that the header extension length is the
      42  	 length of the option header in 8-byte units, not including
      43  	 the first 8 bytes.  Hence we have to subtract one.  */
      44        extp->ip6h_len = extlen / 8 - 1;
      45      }
      46  
      47    return sizeof (struct ip6_hbh);
      48  }
      49  
      50  
      51  static void
      52  add_padding (uint8_t *extbuf, int offset, int npad)
      53  {
      54    if (npad == 1)
      55      extbuf[offset] = IP6OPT_PAD1;
      56    else if (npad > 0)
      57      {
      58        struct ip6_opt *pad_opt = (struct ip6_opt *) (extbuf + offset);
      59  
      60        pad_opt->ip6o_type = IP6OPT_PADN;
      61        pad_opt->ip6o_len = npad - sizeof (struct ip6_opt);
      62        /* Clear the memory used by the padding.  */
      63        memset (pad_opt + 1, '\0', pad_opt->ip6o_len);
      64      }
      65  }
      66  
      67  
      68  
      69  /* RFC 3542, 10.2
      70  
      71     This function returns the updated total length taking into account
      72     adding an option with length 'len' and alignment 'align'.  If
      73     EXTBUF is not NULL then, in addition to returning the length, the
      74     function inserts any needed pad option, initializes the option
      75     (setting the type and length fields) and returns a pointer to the
      76     location for the option content in databufp.  If the option does
      77     not fit in the extension header buffer the function returns -1.  */
      78  int
      79  inet6_opt_append (void *extbuf, socklen_t extlen, int offset, uint8_t type,
      80  		  socklen_t len, uint8_t align, void **databufp)
      81  {
      82    /* Check minimum offset.  */
      83    if (offset < sizeof (struct ip6_hbh))
      84      return -1;
      85  
      86    /* One cannot add padding options.  */
      87    if (type == IP6OPT_PAD1 || type == IP6OPT_PADN)
      88      return -1;
      89  
      90    /* The option length must fit in one octet.  */
      91    if (len > 255)
      92      return -1;
      93  
      94    /* The alignment can only by 1, 2, 4, or 8 and must not exceed the
      95       option length.  */
      96    if (align == 0 || align > 8 || (align & (align - 1)) != 0 || align > len)
      97      return -1;
      98  
      99    /* Determine the needed padding for alignment.  Following the
     100       current content of the buffer we have the is the IPv6 option type
     101       and length, followed immediately by the data.  The data has the
     102       alignment constraints.  Therefore padding must be inserted in the
     103       form of padding options before the new option. */
     104    int data_offset = offset + sizeof (struct ip6_opt);
     105    int npad = (align - data_offset % align) & (align - 1);
     106  
     107    if (extbuf != NULL)
     108      {
     109        /* Now we can check whether the buffer is large enough.  */
     110        if (data_offset + npad + len > extlen)
     111  	return -1;
     112  
     113        add_padding (extbuf, offset, npad);
     114  
     115        offset += npad;
     116  
     117        /* Now prepare the option itself.  */
     118        struct ip6_opt *opt = (struct ip6_opt *) ((uint8_t *) extbuf + offset);
     119  
     120        opt->ip6o_type = type;
     121        opt->ip6o_len = len;
     122  
     123        *databufp = opt + 1;
     124      }
     125    else
     126      offset += npad;
     127  
     128    return offset + sizeof (struct ip6_opt) + len;
     129  }
     130  
     131  
     132  /* RFC 3542, 10.3
     133  
     134     This function returns the updated total length taking into account
     135     the final padding of the extension header to make it a multiple of
     136     8 bytes.  If EXTBUF is not NULL the function also initializes the
     137     option by inserting a Pad1 or PadN option of the proper length.  */
     138  int
     139  inet6_opt_finish (void *extbuf, socklen_t extlen, int offset)
     140  {
     141    /* Check minimum offset.  */
     142    if (offset < sizeof (struct ip6_hbh))
     143      return -1;
     144  
     145    /* Required padding at the end.  */
     146    int npad = (8 - (offset & 7)) & 7;
     147  
     148    if (extbuf != NULL)
     149      {
     150        /* Make sure the buffer is large enough.  */
     151        if (offset + npad > extlen)
     152  	return -1;
     153  
     154        add_padding (extbuf, offset, npad);
     155      }
     156  
     157    return offset + npad;
     158  }
     159  
     160  
     161  /* RFC 3542, 10.4
     162  
     163     This function inserts data items of various sizes in the data
     164     portion of the option.  VAL should point to the data to be
     165     inserted.  OFFSET specifies where in the data portion of the option
     166     the value should be inserted; the first byte after the option type
     167     and length is accessed by specifying an offset of zero.  */
     168  int
     169  inet6_opt_set_val (void *databuf, int offset, void *val, socklen_t vallen)
     170  {
     171    memcpy ((uint8_t *) databuf + offset, val, vallen);
     172  
     173    return offset + vallen;
     174  }
     175  
     176  
     177  /* RFC 3542, 10.5
     178  
     179     This function parses received option extension headers returning
     180     the next option.  EXTBUF and EXTLEN specifies the extension header.
     181     OFFSET should either be zero (for the first option) or the length
     182     returned by a previous call to 'inet6_opt_next' or
     183     'inet6_opt_find'.  It specifies the position where to continue
     184     scanning the extension buffer.  */
     185  int
     186  inet6_opt_next (void *extbuf, socklen_t extlen, int offset, uint8_t *typep,
     187  		socklen_t *lenp, void **databufp)
     188  {
     189    if (offset == 0)
     190      offset = sizeof (struct ip6_hbh);
     191    else if (offset < sizeof (struct ip6_hbh))
     192      return -1;
     193  
     194    while (offset < extlen)
     195      {
     196        struct ip6_opt *opt = (struct ip6_opt *) ((uint8_t *) extbuf + offset);
     197  
     198        if (opt->ip6o_type == IP6OPT_PAD1)
     199  	/* Single byte padding.  */
     200  	++offset;
     201        else if (opt->ip6o_type == IP6OPT_PADN)
     202  	offset += sizeof (struct ip6_opt) + opt->ip6o_len;
     203        else
     204  	{
     205  	  /* Check whether the option is valid.  */
     206  	  offset += sizeof (struct ip6_opt) + opt->ip6o_len;
     207  	  if (offset > extlen)
     208  	    return -1;
     209  
     210  	  *typep = opt->ip6o_type;
     211  	  *lenp = opt->ip6o_len;
     212  	  *databufp = opt + 1;
     213  	  return offset;
     214  	}
     215      }
     216  
     217    return -1;
     218  }
     219  
     220  
     221  /* RFC 3542, 10.6
     222  
     223     This function is similar to the previously described
     224     'inet6_opt_next' function, except this function lets the caller
     225     specify the option type to be searched for, instead of always
     226     returning the next option in the extension header.  */
     227  int
     228  inet6_opt_find (void *extbuf, socklen_t extlen, int offset, uint8_t type,
     229  		socklen_t *lenp, void **databufp)
     230  {
     231    if (offset == 0)
     232      offset = sizeof (struct ip6_hbh);
     233    else if (offset < sizeof (struct ip6_hbh))
     234      return -1;
     235  
     236    while (offset < extlen)
     237      {
     238        struct ip6_opt *opt = (struct ip6_opt *) ((uint8_t *) extbuf + offset);
     239  
     240        if (opt->ip6o_type == IP6OPT_PAD1)
     241  	{
     242  	  /* Single byte padding.  */
     243  	  ++offset;
     244  	  if (type == IP6OPT_PAD1)
     245  	    {
     246  	      *lenp = 0;
     247  	      *databufp = (uint8_t *) extbuf + offset;
     248  	      return offset;
     249  	    }
     250  	}
     251        else if (opt->ip6o_type != type)
     252  	offset += sizeof (struct ip6_opt) + opt->ip6o_len;
     253        else
     254  	{
     255  	  /* Check whether the option is valid.  */
     256  	  offset += sizeof (struct ip6_opt) + opt->ip6o_len;
     257  	  if (offset > extlen)
     258  	    return -1;
     259  
     260  	  *lenp = opt->ip6o_len;
     261  	  *databufp = opt + 1;
     262  	  return offset;
     263  	}
     264      }
     265  
     266    return -1;
     267  }
     268  
     269  
     270  /* RFC 3542, 10.7
     271  
     272     This function extracts data items of various sizes in the data
     273     portion of the option.  */
     274  int
     275  inet6_opt_get_val (void *databuf, int offset, void *val, socklen_t vallen)
     276  {
     277    memcpy (val, (uint8_t *) databuf + offset, vallen);
     278  
     279    return offset + vallen;
     280  }