Webserver+3d print

Dependents:   Nucleo

Revision:
0:8918a71cdbe9
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cyclone_tcp/ipv6/ipv6.c	Sat Feb 04 18:15:49 2017 +0000
@@ -0,0 +1,2138 @@
+/**
+ * @file ipv6.c
+ * @brief IPv6 (Internet Protocol Version 6)
+ *
+ * @section License
+ *
+ * Copyright (C) 2010-2017 Oryx Embedded SARL. All rights reserved.
+ *
+ * This file is part of CycloneTCP Open.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ * @section Description
+ *
+ * IP version 6 (IPv6) is a new version of the Internet Protocol, designed
+ * as the successor to IP version 4 (IPv4). Refer to RFC 2460
+ *
+ * @author Oryx Embedded SARL (www.oryx-embedded.com)
+ * @version 1.7.6
+ **/
+
+//Switch to the appropriate trace level
+#define TRACE_LEVEL IPV6_TRACE_LEVEL
+
+//Dependencies
+#include <string.h>
+#include <ctype.h>
+#include "core/net.h"
+#include "ipv6/ipv6.h"
+#include "ipv6/ipv6_frag.h"
+#include "ipv6/ipv6_misc.h"
+#include "ipv6/ipv6_pmtu.h"
+#include "ipv6/ipv6_routing.h"
+#include "ipv6/icmpv6.h"
+#include "ipv6/mld.h"
+#include "ipv6/ndp.h"
+#include "ipv6/ndp_cache.h"
+#include "ipv6/ndp_misc.h"
+#include "ipv6/ndp_router_adv.h"
+#include "ipv6/slaac.h"
+#include "dhcpv6/dhcpv6_client.h"
+#include "core/udp.h"
+#include "core/tcp_fsm.h"
+#include "core/raw_socket.h"
+#include "debug.h"
+
+//Check TCP/IP stack configuration
+#if (IPV6_SUPPORT == ENABLED)
+
+//Unspecified IPv6 address
+const Ipv6Addr IPV6_UNSPECIFIED_ADDR =
+   IPV6_ADDR(0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000);
+
+//Loopback IPv6 address
+const Ipv6Addr IPV6_LOOPBACK_ADDR =
+   IPV6_ADDR(0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001);
+
+//Link-local All-Nodes IPv6 address
+const Ipv6Addr IPV6_LINK_LOCAL_ALL_NODES_ADDR =
+   IPV6_ADDR(0xFF02, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001);
+
+//Link-local All-Routers IPv6 address
+const Ipv6Addr IPV6_LINK_LOCAL_ALL_ROUTERS_ADDR =
+   IPV6_ADDR(0xFF02, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0002);
+
+//Link-local IPv6 address prefix
+const Ipv6Addr IPV6_LINK_LOCAL_ADDR_PREFIX =
+   IPV6_ADDR(0xFE80, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000);
+
+//Solicited-node IPv6 address prefix
+const Ipv6Addr IPV6_SOLICITED_NODE_ADDR_PREFIX =
+   IPV6_ADDR(0xFF02, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0xFF00, 0x0000);
+
+
+/**
+ * @brief IPv6 related initialization
+ * @param[in] interface Underlying network interface
+ * @return Error code
+ **/
+
+error_t ipv6Init(NetInterface *interface)
+{
+   Ipv6Context *context;
+
+   //Point to the IPv6 context
+   context = &interface->ipv6Context;
+
+   //Clear the IPv6 context
+   memset(context, 0, sizeof(Ipv6Context));
+
+   //Initialize interface specific variables
+   context->linkMtu = interface->nicDriver->mtu;
+   context->isRouter = FALSE;
+   context->curHopLimit = IPV6_DEFAULT_HOP_LIMIT;
+
+   //Multicast ICMPv6 Echo Request messages are allowed by default
+   context->enableMulticastEchoReq = TRUE;
+
+   //Initialize the list of IPv6 addresses assigned to the interface
+   memset(context->addrList, 0, sizeof(context->addrList));
+   //Initialize the Prefix List
+   memset(context->prefixList, 0, sizeof(context->prefixList));
+   //Initialize the Default Router List
+   memset(context->routerList, 0, sizeof(context->routerList));
+   //Initialize the list of DNS servers
+   memset(context->dnsServerList, 0, sizeof(context->dnsServerList));
+   //Initialize the multicast filter table
+   memset(context->multicastFilter, 0, sizeof(context->multicastFilter));
+
+#if (IPV6_FRAG_SUPPORT == ENABLED)
+   //Identification field is used to identify fragments of an original IP datagram
+   context->identification = 0;
+   //Initialize the reassembly queue
+   memset(context->fragQueue, 0, sizeof(context->fragQueue));
+#endif
+
+   //Successful initialization
+   return NO_ERROR;
+}
+
+
+/**
+ * @brief Change the MTU of a network interface
+ * @param[in] interface Pointer to the desired network interface
+ * @param[in] mtu Maximum transmit unit
+ * @return Error code
+ **/
+
+error_t ipv6SetMtu(NetInterface *interface, size_t mtu)
+{
+   error_t error;
+
+   //Check parameters
+   if(interface == NULL)
+      return ERROR_INVALID_PARAMETER;
+
+   //Get exclusive access
+   osAcquireMutex(&netMutex);
+
+   //Make sure the specified MTU is greater than or equal to the minimum
+   //IPv6 MTU and does not exceed the maximum MTU of the interface
+   if(mtu >= IPV6_DEFAULT_MTU && mtu <= interface->nicDriver->mtu)
+   {
+      //Set the MTU to be used
+      interface->ipv6Context.linkMtu = mtu;
+      //Successful processing
+      error = NO_ERROR;
+   }
+   else
+   {
+      //The specified MTU is not valid
+      error = ERROR_OUT_OF_RANGE;
+   }
+
+   //Release exclusive access
+   osReleaseMutex(&netMutex);
+
+   //Return status code
+   return error;
+}
+
+
+/**
+ * @brief Retrieve the MTU for the specified interface
+ * @param[in] interface Pointer to the desired network interface
+ * @param[out] mtu Maximum transmit unit
+ * @return Error code
+ **/
+
+error_t ipv6GetMtu(NetInterface *interface, size_t *mtu)
+{
+   //Check parameters
+   if(interface == NULL || mtu == NULL)
+      return ERROR_INVALID_PARAMETER;
+
+   //Get exclusive access
+   osAcquireMutex(&netMutex);
+   //Return the current MTU value
+   *mtu = interface->ipv6Context.linkMtu;
+   //Release exclusive access
+   osReleaseMutex(&netMutex);
+
+   //Successful processing
+   return NO_ERROR;
+}
+
+
+/**
+ * @brief Assign link-local address
+ * @param[in] interface Pointer to the desired network interface
+ * @param[in] addr Link-local address
+ * @return Error code
+ **/
+
+error_t ipv6SetLinkLocalAddr(NetInterface *interface, const Ipv6Addr *addr)
+{
+   error_t error;
+
+   //Get exclusive access
+   osAcquireMutex(&netMutex);
+
+#if (NDP_SUPPORT == ENABLED)
+   //Check whether Duplicate Address Detection should be performed
+   if(interface->ndpContext.dupAddrDetectTransmits > 0)
+   {
+      //Use the link-local address as a tentative address
+      error = ipv6SetAddr(interface, 0, addr, IPV6_ADDR_STATE_TENTATIVE,
+         NDP_INFINITE_LIFETIME, NDP_INFINITE_LIFETIME, TRUE);
+   }
+   else
+#endif
+   {
+      //The use of the link-local address is now unrestricted
+      error = ipv6SetAddr(interface, 0, addr, IPV6_ADDR_STATE_PREFERRED,
+         NDP_INFINITE_LIFETIME, NDP_INFINITE_LIFETIME, TRUE);
+   }
+
+   //Release exclusive access
+   osReleaseMutex(&netMutex);
+
+   //Return status code
+   return error;
+}
+
+
+/**
+ * @brief Retrieve link-local address
+ * @param[in] interface Pointer to the desired network interface
+ * @param[out] addr link-local address
+ * @return Error code
+ **/
+
+error_t ipv6GetLinkLocalAddr(NetInterface *interface, Ipv6Addr *addr)
+{
+   Ipv6AddrEntry *entry;
+
+   //Check parameters
+   if(interface == NULL || addr == NULL)
+      return ERROR_INVALID_PARAMETER;
+
+   //Get exclusive access
+   osAcquireMutex(&netMutex);
+
+   //Point to the corresponding address entry
+   entry = &interface->ipv6Context.addrList[0];
+
+   //Check whether the IPv6 address is valid
+   if(entry->state == IPV6_ADDR_STATE_PREFERRED ||
+      entry->state == IPV6_ADDR_STATE_DEPRECATED)
+   {
+      //Get IPv6 address
+      *addr = entry->addr;
+   }
+   else
+   {
+      //Return the unspecified address when no address has been assigned
+      *addr = IPV6_UNSPECIFIED_ADDR;
+   }
+
+   //Release exclusive access
+   osReleaseMutex(&netMutex);
+
+   //Successful processing
+   return NO_ERROR;
+}
+
+
+/**
+ * @brief Assign global address
+ * @param[in] interface Pointer to the desired network interface
+ * @param[in] index Zero-based index
+ * @param[in] addr Global address
+ * @return Error code
+ **/
+
+error_t ipv6SetGlobalAddr(NetInterface *interface, uint_t index, const Ipv6Addr *addr)
+{
+   error_t error;
+
+   //Get exclusive access
+   osAcquireMutex(&netMutex);
+
+#if (NDP_SUPPORT == ENABLED)
+   //Check whether Duplicate Address Detection should be performed
+   if(interface->ndpContext.dupAddrDetectTransmits > 0)
+   {
+      //Use the global address as a tentative address
+      error = ipv6SetAddr(interface, index + 1, addr, IPV6_ADDR_STATE_TENTATIVE,
+         NDP_INFINITE_LIFETIME, NDP_INFINITE_LIFETIME, TRUE);
+   }
+   else
+#endif
+   {
+      //The use of the global address is now unrestricted
+      error = ipv6SetAddr(interface, index + 1, addr, IPV6_ADDR_STATE_PREFERRED,
+         NDP_INFINITE_LIFETIME, NDP_INFINITE_LIFETIME, TRUE);
+   }
+
+   //Release exclusive access
+   osReleaseMutex(&netMutex);
+
+   //Return status code
+   return error;
+}
+
+
+/**
+ * @brief Retrieve global address
+ * @param[in] interface Pointer to the desired network interface
+ * @param[in] index Zero-based index
+ * @param[out] addr Global address
+ * @return Error code
+ **/
+
+error_t ipv6GetGlobalAddr(NetInterface *interface, uint_t index, Ipv6Addr *addr)
+{
+   Ipv6AddrEntry *entry;
+
+   //Check parameters
+   if(interface == NULL || addr == NULL)
+      return ERROR_INVALID_PARAMETER;
+
+   //Make sure that the index is valid
+   if((index + 1) >= IPV6_ADDR_LIST_SIZE)
+   {
+      //Return the unspecified address when the index is out of range
+      *addr = IPV6_UNSPECIFIED_ADDR;
+      //Report an error
+      return ERROR_OUT_OF_RANGE;
+   }
+
+   //Get exclusive access
+   osAcquireMutex(&netMutex);
+
+   //Point to the corresponding address entry
+   entry = &interface->ipv6Context.addrList[index + 1];
+
+   //Check whether the IPv6 address is valid
+   if(entry->state == IPV6_ADDR_STATE_PREFERRED ||
+      entry->state == IPV6_ADDR_STATE_DEPRECATED)
+   {
+      //Get IPv6 address
+      *addr = entry->addr;
+   }
+   else
+   {
+      //Return the unspecified address when no address has been assigned
+      *addr = IPV6_UNSPECIFIED_ADDR;
+   }
+
+   //Release exclusive access
+   osReleaseMutex(&netMutex);
+
+   //Successful processing
+   return NO_ERROR;
+}
+
+
+/**
+ * @brief Assign anycast address
+ * @param[in] interface Pointer to the desired network interface
+ * @param[in] index Zero-based index
+ * @param[in] addr Anycast address
+ * @return Error code
+ **/
+
+error_t ipv6SetAnycastAddr(NetInterface *interface, uint_t index, const Ipv6Addr *addr)
+{
+   error_t error;
+   Ipv6Addr *anycastAddrList;
+   Ipv6Addr solicitedNodeAddr;
+
+   //Check parameters
+   if(interface == NULL || addr == NULL)
+      return ERROR_INVALID_PARAMETER;
+
+   //Make sure that the index is valid
+   if(index >= IPV6_ANYCAST_ADDR_LIST_SIZE)
+      return ERROR_OUT_OF_RANGE;
+
+   //The IPv6 address must be a valid unicast address
+   if(ipv6IsMulticastAddr(addr))
+      return ERROR_INVALID_ADDRESS;
+
+   //Initialize status code
+   error = NO_ERROR;
+
+   //Get exclusive access
+   osAcquireMutex(&netMutex);
+
+   //Point to the list of anycast addresses assigned to the interface
+   anycastAddrList = interface->ipv6Context.anycastAddrList;
+
+   //Check whether an anycast address is already assigned
+   if(!ipv6CompAddr(&anycastAddrList[index], &IPV6_UNSPECIFIED_ADDR))
+   {
+      //Ethernet interface?
+      if(interface->nicDriver->type == NIC_TYPE_ETHERNET)
+      {
+         //Form the Solicited-Node address
+         ipv6ComputeSolicitedNodeAddr(&anycastAddrList[index], &solicitedNodeAddr);
+         //Leave the Solicited-Node multicast group
+         ipv6LeaveMulticastGroup(interface, &solicitedNodeAddr);
+      }
+   }
+
+   //Assign the specified anycast address to the interface
+   anycastAddrList[index] = *addr;
+
+   //Check whether the anycast address is valid
+   if(!ipv6CompAddr(addr, &IPV6_UNSPECIFIED_ADDR))
+   {
+      //Ethernet interface?
+      if(interface->nicDriver->type == NIC_TYPE_ETHERNET)
+      {
+         //Form the Solicited-Node address for the link-local address
+         ipv6ComputeSolicitedNodeAddr(addr, &solicitedNodeAddr);
+         //Join the Solicited-Node multicast group for each assigned address
+         error = ipv6JoinMulticastGroup(interface, &solicitedNodeAddr);
+      }
+   }
+
+   //Release exclusive access
+   osReleaseMutex(&netMutex);
+
+   //Return status code
+   return error;
+}
+
+
+/**
+ * @brief Retrieve anycast address
+ * @param[in] interface Pointer to the desired network interface
+ * @param[in] index Zero-based index
+ * @param[out] addr Anycast address
+ * @return Error code
+ **/
+
+error_t ipv6GetAnycastAddr(NetInterface *interface, uint_t index, Ipv6Addr *addr)
+{
+   //Check parameters
+   if(interface == NULL || addr == NULL)
+      return ERROR_INVALID_PARAMETER;
+
+   //Make sure that the index is valid
+   if(index >= IPV6_ANYCAST_ADDR_LIST_SIZE)
+   {
+      //Return the unspecified address when the index is out of range
+      *addr = IPV6_UNSPECIFIED_ADDR;
+      //Report an error
+      return ERROR_OUT_OF_RANGE;
+   }
+
+   //Get exclusive access
+   osAcquireMutex(&netMutex);
+   //Return the corresponding address entry
+   *addr = interface->ipv6Context.anycastAddrList[index];
+   //Release exclusive access
+   osReleaseMutex(&netMutex);
+
+   //Successful processing
+   return NO_ERROR;
+}
+
+
+/**
+ * @brief Configure IPv6 prefix
+ * @param[in] interface Pointer to the desired network interface
+ * @param[in] index Zero-based index
+ * @param[in] prefix IPv6 prefix
+ * @param[in] length The number of leading bits in the prefix that are valid
+ **/
+
+error_t ipv6SetPrefix(NetInterface *interface,
+   uint_t index, const Ipv6Addr *prefix, uint_t length)
+{
+   Ipv6PrefixEntry *entry;
+
+   //Check parameters
+   if(interface == NULL || prefix == NULL)
+      return ERROR_INVALID_PARAMETER;
+
+   //Make sure that the index is valid
+   if(index >= IPV6_PREFIX_LIST_SIZE)
+      return ERROR_OUT_OF_RANGE;
+
+   //Make sure the prefix length is valid
+   if(length >= 128)
+      return ERROR_INVALID_PARAMETER;
+
+   //Get exclusive access
+   osAcquireMutex(&netMutex);
+
+   //Point to the corresponding entry
+   entry = &interface->ipv6Context.prefixList[index];
+
+   //Set up IPv6 prefix
+   entry->prefix = *prefix;
+   entry->prefixLength = length;
+
+   //Check prefix length
+   if(length > 0)
+   {
+      //Manually assigned prefixes have infinite lifetime
+      entry->validLifetime = INFINITE_DELAY;
+      entry->preferredLifetime = INFINITE_DELAY;
+      entry->permanent = TRUE;
+   }
+   else
+   {
+      //Immediately time-out the entry
+      entry->validLifetime = 0;
+      entry->preferredLifetime = 0;
+      entry->permanent = FALSE;
+   }
+
+   //Release exclusive access
+   osReleaseMutex(&netMutex);
+
+   //Successful processing
+   return NO_ERROR;
+}
+
+
+/**
+ * @brief Retrieve IPv6 prefix
+ * @param[in] interface Pointer to the desired network interface
+ * @param[in] index Zero-based index
+ * @param[out] prefix IPv6 prefix
+ * @param[out] length The number of leading bits in the prefix that are valid
+ * @return Error code
+ **/
+
+error_t ipv6GetPrefix(NetInterface *interface,
+   uint_t index, Ipv6Addr *prefix, uint_t *length)
+{
+   Ipv6PrefixEntry *entry;
+
+   //Check parameters
+   if(interface == NULL || prefix == NULL)
+      return ERROR_INVALID_PARAMETER;
+
+   //Make sure that the index is valid
+   if(index >= IPV6_PREFIX_LIST_SIZE)
+   {
+      //Return the ::/0 prefix when the index is out of range
+      *prefix = IPV6_UNSPECIFIED_ADDR;
+      *length = 0;
+      //Report an error
+      return ERROR_OUT_OF_RANGE;
+   }
+
+   //Get exclusive access
+   osAcquireMutex(&netMutex);
+
+   //Point to the corresponding entry
+   entry = &interface->ipv6Context.prefixList[index];
+
+   //Check whether the prefix is valid
+   if(entry->validLifetime > 0)
+   {
+      //Get IPv6 prefix
+      *prefix = entry->prefix;
+      *length = entry->prefixLength;
+   }
+   else
+   {
+      //Return the ::/0 prefix when the valid lifetime has expired
+      *prefix = IPV6_UNSPECIFIED_ADDR;
+      *length = 0;
+   }
+
+   //Release exclusive access
+   osReleaseMutex(&netMutex);
+
+   //Successful processing
+   return NO_ERROR;
+}
+
+
+/**
+ * @brief Configure default router
+ * @param[in] interface Pointer to the desired network interface
+ * @param[in] index Zero-based index
+ * @param[in] addr Default router address
+ * @return Error code
+ **/
+
+error_t ipv6SetDefaultRouter(NetInterface *interface, uint_t index, const Ipv6Addr *addr)
+{
+   Ipv6RouterEntry *entry;
+
+   //Check parameters
+   if(interface == NULL || addr == NULL)
+      return ERROR_INVALID_PARAMETER;
+
+   //Make sure that the index is valid
+   if(index >= IPV6_ROUTER_LIST_SIZE)
+      return ERROR_OUT_OF_RANGE;
+
+   //The IPv6 address must be a valid unicast address
+   if(ipv6IsMulticastAddr(addr))
+      return ERROR_INVALID_ADDRESS;
+
+   //Get exclusive access
+   osAcquireMutex(&netMutex);
+
+   //Point to the corresponding entry
+   entry = &interface->ipv6Context.routerList[index];
+
+   //Set up router address
+   entry->addr = *addr;
+
+   //Valid IPv6 address?
+   if(!ipv6CompAddr(addr, &IPV6_UNSPECIFIED_ADDR))
+   {
+      //Manually assigned routers have infinite lifetime
+      entry->lifetime = INFINITE_DELAY;
+      entry->permanent = TRUE;
+   }
+   else
+   {
+      //Immediately time-out the entry
+      entry->lifetime = 0;
+      entry->permanent = FALSE;
+   }
+
+   //Release exclusive access
+   osReleaseMutex(&netMutex);
+
+   //Successful processing
+   return NO_ERROR;
+}
+
+
+/**
+ * @brief Retrieve default router
+ * @param[in] interface Pointer to the desired network interface
+ * @param[in] index Zero-based index
+ * @param[out] addr Default router address
+ * @return Error code
+ **/
+
+error_t ipv6GetDefaultRouter(NetInterface *interface, uint_t index, Ipv6Addr *addr)
+{
+   Ipv6RouterEntry *entry;
+
+   //Check parameters
+   if(interface == NULL || addr == NULL)
+      return ERROR_INVALID_PARAMETER;
+
+   //Make sure that the index is valid
+   if(index >= IPV6_ROUTER_LIST_SIZE)
+   {
+      //Return the unspecified address when the index is out of range
+      *addr = IPV6_UNSPECIFIED_ADDR;
+      //Report an error
+      return ERROR_OUT_OF_RANGE;
+   }
+
+   //Get exclusive access
+   osAcquireMutex(&netMutex);
+
+   //Point to the corresponding entry
+   entry = &interface->ipv6Context.routerList[index];
+
+   //Check the lifetime of the entry
+   if(entry->lifetime > 0)
+   {
+      //Get router address
+      *addr = entry->addr;
+   }
+   else
+   {
+      //Return the unspecified address when the lifetime has expired
+      *addr = IPV6_UNSPECIFIED_ADDR;
+   }
+
+   //Release exclusive access
+   osReleaseMutex(&netMutex);
+
+   //Successful processing
+   return NO_ERROR;
+}
+
+
+/**
+ * @brief Configure DNS server
+ * @param[in] interface Pointer to the desired network interface
+ * @param[in] index This parameter selects between the primary and secondary DNS server
+ * @param[in] addr DNS server address
+ * @return Error code
+ **/
+
+error_t ipv6SetDnsServer(NetInterface *interface, uint_t index, const Ipv6Addr *addr)
+{
+   //Check parameters
+   if(interface == NULL || addr == NULL)
+      return ERROR_INVALID_PARAMETER;
+
+   //Make sure that the index is valid
+   if(index >= IPV6_DNS_SERVER_LIST_SIZE)
+      return ERROR_OUT_OF_RANGE;
+
+   //The IPv6 address must be a valid unicast address
+   if(ipv6IsMulticastAddr(addr))
+      return ERROR_INVALID_ADDRESS;
+
+   //Get exclusive access
+   osAcquireMutex(&netMutex);
+   //Set up DNS server address
+   interface->ipv6Context.dnsServerList[index] = *addr;
+   //Release exclusive access
+   osReleaseMutex(&netMutex);
+
+   //Successful processing
+   return NO_ERROR;
+}
+
+
+/**
+ * @brief Retrieve DNS server
+ * @param[in] interface Pointer to the desired network interface
+ * @param[in] index This parameter selects between the primary and secondary DNS server
+ * @param[out] addr DNS server address
+ * @return Error code
+ **/
+
+error_t ipv6GetDnsServer(NetInterface *interface, uint_t index, Ipv6Addr *addr)
+{
+   //Check parameters
+   if(interface == NULL || addr == NULL)
+      return ERROR_INVALID_PARAMETER;
+
+   //Make sure that the index is valid
+   if(index >= IPV6_DNS_SERVER_LIST_SIZE)
+   {
+      //Return the unspecified address when the index is out of range
+      *addr = IPV6_UNSPECIFIED_ADDR;
+      //Report an error
+      return ERROR_OUT_OF_RANGE;
+   }
+
+   //Get exclusive access
+   osAcquireMutex(&netMutex);
+   //Get DNS server address
+   *addr = interface->ipv6Context.dnsServerList[index];
+   //Release exclusive access
+   osReleaseMutex(&netMutex);
+
+   //Successful processing
+   return NO_ERROR;
+}
+
+
+/**
+ * @brief Callback function for link change event
+ * @param[in] interface Underlying network interface
+ **/
+
+void ipv6LinkChangeEvent(NetInterface *interface)
+{
+   uint_t i;
+   Ipv6Context *context;
+   Ipv6AddrEntry *entry;
+
+   //Point to the IPv6 context
+   context = &interface->ipv6Context;
+
+   //Restore default parameters
+   context->linkMtu = interface->nicDriver->mtu;
+   context->curHopLimit = IPV6_DEFAULT_HOP_LIMIT;
+
+   //Clear the list of IPv6 addresses
+   ipv6FlushAddrList(interface);
+   //Clear the Prefix List
+   ipv6FlushPrefixList(interface);
+   //Clear the Default Router List
+   ipv6FlushDefaultRouterList(interface);
+
+#if (IPV6_FRAG_SUPPORT == ENABLED)
+   //Flush the reassembly queue
+   ipv6FlushFragQueue(interface);
+#endif
+
+#if (MLD_SUPPORT == ENABLED)
+   //Notify MLD of link state changes
+   mldLinkChangeEvent(interface);
+#endif
+
+#if (NDP_SUPPORT == ENABLED)
+   //Notify NDP of link state changes
+   ndpLinkChangeEvent(interface);
+#endif
+
+#if (NDP_ROUTER_ADV_SUPPORT == ENABLED)
+   //Notify the RA service of link state changes
+   ndpRouterAdvLinkChangeEvent(interface->ndpRouterAdvContext);
+#endif
+
+#if (SLAAC_SUPPORT == ENABLED)
+   //Notify the SLAAC service of link state changes
+   slaacLinkChangeEvent(interface->slaacContext);
+#endif
+
+#if (DHCPV6_CLIENT_SUPPORT == ENABLED)
+   //Notify the DHCPv6 client of link state changes
+   dhcpv6ClientLinkChangeEvent(interface->dhcpv6ClientContext);
+#endif
+
+   //Go through the list of IPv6 addresses
+   for(i = 0; i < IPV6_ADDR_LIST_SIZE; i++)
+   {
+      //Point to the current entry
+      entry = &context->addrList[i];
+
+      //Check whether the IPv6 address has been manually assigned
+      if(entry->permanent)
+      {
+#if (NDP_SUPPORT == ENABLED)
+         //Check whether Duplicate Address Detection should be performed
+         if(interface->ndpContext.dupAddrDetectTransmits > 0)
+         {
+            //Use the IPv6 address as a tentative address
+            ipv6SetAddr(interface, i, &entry->addr, IPV6_ADDR_STATE_TENTATIVE,
+               NDP_INFINITE_LIFETIME, NDP_INFINITE_LIFETIME, TRUE);
+         }
+         else
+#endif
+         {
+            //The use of the IPv6 address is now unrestricted
+            ipv6SetAddr(interface, i, &entry->addr, IPV6_ADDR_STATE_PREFERRED,
+               NDP_INFINITE_LIFETIME, NDP_INFINITE_LIFETIME, TRUE);
+         }
+      }
+   }
+}
+
+
+/**
+ * @brief Incoming IPv6 packet processing
+ * @param[in] interface Underlying network interface
+ * @param[in] ipPacket Multi-part buffer that holds the incoming IPv6 packet
+ * @param[in] ipPacketOffset Offset to the first byte of the IPv6 packet
+ **/
+
+void ipv6ProcessPacket(NetInterface *interface,
+   NetBuffer *ipPacket, size_t ipPacketOffset)
+{
+   error_t error;
+   size_t i;
+   size_t length;
+   size_t nextHeaderOffset;
+   uint8_t *type;
+   Ipv6Header *ipHeader;
+   IpPseudoHeader pseudoHeader;
+
+   //Retrieve the length of the IPv6 packet
+   length = netBufferGetLength(ipPacket);
+
+   //Ensure the packet length is greater than 40 bytes
+   if(length < sizeof(Ipv6Header))
+      return;
+
+   //Point to the IPv6 header
+   ipHeader = netBufferAt(ipPacket, ipPacketOffset);
+   //Sanity check
+   if(ipHeader == NULL)
+      return;
+
+   //Debug message
+   TRACE_INFO("IPv6 packet received (%" PRIuSIZE " bytes)...\r\n", length);
+   //Dump IPv6 header contents for debugging purpose
+   ipv6DumpHeader(ipHeader);
+
+   //Check IP version number
+   if(ipHeader->version != IPV6_VERSION)
+      return;
+   //Ensure the payload length is correct before processing the packet
+   if(ntohs(ipHeader->payloadLength) > (length - sizeof(Ipv6Header)))
+      return;
+   //Source address filtering
+   if(ipv6CheckSourceAddr(interface, &ipHeader->srcAddr))
+      return;
+
+#if defined(IPV6_PACKET_FORWARD_HOOK)
+   IPV6_PACKET_FORWARD_HOOK(interface, ipPacket, ipPacketOffset);
+#else
+   //Destination address filtering
+   if(ipv6CheckDestAddr(interface, &ipHeader->destAddr))
+   {
+#if(IPV6_ROUTING_SUPPORT == ENABLED)
+      //Forward the packet according to the routing table
+      ipv6ForwardPacket(interface, ipPacket, ipPacketOffset);
+#endif
+      //We are done
+      return;
+   }
+#endif
+
+   //Calculate the effective length of the multi-part buffer
+   length = ipPacketOffset + sizeof(Ipv6Header) +
+      ntohs(ipHeader->payloadLength);
+
+   //Adjust the length of the multi-part buffer if necessary
+   netBufferSetLength(ipPacket, length);
+
+   //Form the IPv6 pseudo header
+   pseudoHeader.length = sizeof(Ipv6PseudoHeader);
+   pseudoHeader.ipv6Data.srcAddr = ipHeader->srcAddr;
+   pseudoHeader.ipv6Data.destAddr = ipHeader->destAddr;
+   pseudoHeader.ipv6Data.reserved = 0;
+
+   //Keep track of Next Header field
+   nextHeaderOffset = ipPacketOffset + &ipHeader->nextHeader -
+      (uint8_t *) ipHeader;
+
+   //Point to the first extension header
+   i = ipPacketOffset + sizeof(Ipv6Header);
+
+   //Parse extension headers
+   while(i < length)
+   {
+      //Retrieve the Next Header field of preceding header
+      type = netBufferAt(ipPacket, nextHeaderOffset);
+      //Sanity check
+      if(type == NULL)
+         return;
+
+      //Update IPv6 pseudo header
+      pseudoHeader.ipv6Data.length = htonl(length - i);
+      pseudoHeader.ipv6Data.nextHeader = *type;
+
+      //Each extension header is identified by the
+      //Next Header field of the preceding header
+      switch(*type)
+      {
+      //Hop-by-Hop Options header?
+      case IPV6_HOP_BY_HOP_OPT_HEADER:
+         //Parse current extension header
+         error = ipv6ParseHopByHopOptHeader(interface,
+            ipPacket, ipPacketOffset, &i, &nextHeaderOffset);
+         //Continue processing
+         break;
+
+      //Destination Options header?
+      case IPV6_DEST_OPT_HEADER:
+         //Parse current extension header
+         error = ipv6ParseDestOptHeader(interface,
+            ipPacket, ipPacketOffset, &i, &nextHeaderOffset);
+         //Continue processing
+         break;
+
+      //Routing header?
+      case IPV6_ROUTING_HEADER:
+         //Parse current extension header
+         error = ipv6ParseRoutingHeader(interface,
+            ipPacket, ipPacketOffset, &i, &nextHeaderOffset);
+         //Continue processing
+         break;
+
+      //Fragment header?
+      case IPV6_FRAGMENT_HEADER:
+#if (IPV6_FRAG_SUPPORT == ENABLED)
+         //Parse current extension header
+         ipv6ParseFragmentHeader(interface,
+            ipPacket, ipPacketOffset, i, nextHeaderOffset);
+#endif
+         //Exit immediately
+         return;
+
+      //Authentication header?
+      case IPV6_AUTH_HEADER:
+         //Parse current extension header
+         error = ipv6ParseAuthHeader(interface,
+            ipPacket, ipPacketOffset, &i, &nextHeaderOffset);
+         //Continue processing
+         break;
+
+      //Encapsulating Security Payload header?
+      case IPV6_ESP_HEADER:
+         //Parse current extension header
+         error = ipv6ParseEspHeader(interface,
+            ipPacket, ipPacketOffset, &i, &nextHeaderOffset);
+         //Continue processing
+         break;
+
+      //ICMPv6 header?
+      case IPV6_ICMPV6_HEADER:
+         //Process incoming ICMPv6 message
+         icmpv6ProcessMessage(interface, &pseudoHeader.ipv6Data,
+            ipPacket, i, ipHeader->hopLimit);
+
+#if (RAW_SOCKET_SUPPORT == ENABLED)
+         //Packets addressed to the tentative address should be silently discarded
+         if(!ipv6IsTentativeAddr(interface, &ipHeader->destAddr))
+         {
+            //Allow raw sockets to process ICMPv6 messages
+            rawSocketProcessIpPacket(interface, &pseudoHeader, ipPacket, i);
+         }
+#endif
+         //Exit immediately
+         return;
+
+#if (TCP_SUPPORT == ENABLED)
+      //TCP header?
+      case IPV6_TCP_HEADER:
+         //Packets addressed to the tentative address should be silently discarded
+         if(!ipv6IsTentativeAddr(interface, &ipHeader->destAddr))
+         {
+            //Process incoming TCP segment
+            tcpProcessSegment(interface, &pseudoHeader, ipPacket, i);
+         }
+         //Exit immediately
+         return;
+#endif
+
+#if (UDP_SUPPORT == ENABLED)
+      //UDP header?
+      case IPV6_UDP_HEADER:
+         //Packets addressed to the tentative address should be silently discarded
+         if(!ipv6IsTentativeAddr(interface, &ipHeader->destAddr))
+         {
+            //Process incoming UDP datagram
+            error = udpProcessDatagram(interface, &pseudoHeader, ipPacket, i);
+
+            //Unreachable port?
+            if(error == ERROR_PORT_UNREACHABLE)
+            {
+               //A destination node should originate a Destination Unreachable
+               //message with Code 4 in response to a packet for which the
+               //transport protocol has no listener
+               icmpv6SendErrorMessage(interface, ICMPV6_TYPE_DEST_UNREACHABLE,
+                  ICMPV6_CODE_PORT_UNREACHABLE, 0, ipPacket, ipPacketOffset);
+            }
+         }
+         //Exit immediately
+         return;
+#endif
+
+      //No next header?
+      case IPV6_NO_NEXT_HEADER:
+         //If the payload length field of the IPv6 header indicates the presence of
+         //octets past the end of the previous header, these octets must be ignored
+         return;
+
+      //Unrecognized header type?
+      default:
+         //Debug message
+         TRACE_WARNING("Unrecognized Next Header type\r\n");
+
+         //Packets addressed to the tentative address should be silently discarded
+         if(!ipv6IsTentativeAddr(interface, &ipHeader->destAddr))
+         {
+            //Compute the offset of the unrecognized Next Header field within the packet
+            size_t n = nextHeaderOffset - ipPacketOffset;
+
+            //Send an ICMP Parameter Problem message
+            icmpv6SendErrorMessage(interface, ICMPV6_TYPE_PARAM_PROBLEM,
+               ICMPV6_CODE_UNKNOWN_NEXT_HEADER, n, ipPacket, ipPacketOffset);
+         }
+
+         //Discard incoming packet
+         return;
+      }
+
+      //Any error while processing the current extension header?
+      if(error)
+         return;
+   }
+}
+
+
+/**
+ * @brief Parse Hop-by-Hop Options header
+ * @param[in] interface Underlying network interface
+ * @param[in] ipPacket Multi-part buffer containing the IPv6 packet
+ * @param[in] ipPacketOffset Offset to the first byte of the IPv6 packet
+ * @param[in,out] headerOffset Offset to the Hop-by-Hop Options header
+ * @param[in,out] nextHeaderOffset Offset to the Next Header field
+ * @brief Error code
+ **/
+
+error_t ipv6ParseHopByHopOptHeader(NetInterface *interface, const NetBuffer *ipPacket,
+   size_t ipPacketOffset, size_t *headerOffset, size_t *nextHeaderOffset)
+{
+   error_t error;
+   size_t n;
+   size_t length;
+   size_t headerLength;
+   Ipv6HopByHopOptHeader *header;
+
+   //Remaining bytes to process in the IPv6 packet
+   length = netBufferGetLength(ipPacket) - *headerOffset;
+
+   //Make sure the extension header is valid
+   if(length < sizeof(Ipv6HopByHopOptHeader))
+      return ERROR_INVALID_HEADER;
+
+   //Point to the Hop-by-Hop Options header
+   header = netBufferAt(ipPacket, *headerOffset);
+   //Sanity check
+   if(header == NULL)
+      return ERROR_FAILURE;
+
+   //Calculate the length of the entire header
+   headerLength = (header->hdrExtLen * 8) + 8;
+
+   //Check header length
+   if(headerLength > length)
+      return ERROR_INVALID_HEADER;
+
+   //Debug message
+   TRACE_DEBUG("  Hop-by-Hop Options header\r\n");
+
+   //The Hop-by-Hop Options header, when present, must immediately follow
+   //the IPv6 header
+   if(*headerOffset != (ipPacketOffset + sizeof(Ipv6Header)))
+   {
+      //Compute the offset of the unrecognized Next Header field within the packet
+      n = *nextHeaderOffset - ipPacketOffset;
+
+      //Send an ICMP Parameter Problem message to the source of the packet
+      icmpv6SendErrorMessage(interface, ICMPV6_TYPE_PARAM_PROBLEM,
+         ICMPV6_CODE_UNKNOWN_NEXT_HEADER, n, ipPacket, ipPacketOffset);
+
+      //Discard incoming packet
+      return ERROR_INVALID_HEADER;
+   }
+
+   //Compute the length of the Options field
+   n = headerLength - sizeof(Ipv6HopByHopOptHeader);
+
+   //Parse options
+   error = ipv6ParseOptions(interface, ipPacket, ipPacketOffset,
+      *headerOffset + sizeof(Ipv6HopByHopOptHeader), n);
+
+   //Any error to report?
+   if(error)
+      return error;
+
+   //Keep track of Next Header field
+   *nextHeaderOffset = *headerOffset + &header->nextHeader - (uint8_t *) header;
+   //Point to the next extension header
+   *headerOffset += headerLength;
+
+   //Successful processing
+   return NO_ERROR;
+}
+
+
+/**
+ * @brief Parse Destination Options header
+ * @param[in] interface Underlying network interface
+ * @param[in] ipPacket Multi-part buffer containing the IPv6 packet
+ * @param[in] ipPacketOffset Offset to the first byte of the IPv6 packet
+ * @param[in,out] headerOffset Offset to the Destination Options header
+ * @param[in,out] nextHeaderOffset Offset to the Next Header field
+ * @brief Error code
+ **/
+
+error_t ipv6ParseDestOptHeader(NetInterface *interface, const NetBuffer *ipPacket,
+   size_t ipPacketOffset, size_t *headerOffset, size_t *nextHeaderOffset)
+{
+   error_t error;
+   size_t n;
+   size_t length;
+   size_t headerLength;
+   Ipv6DestOptHeader *header;
+
+   //Remaining bytes to process in the IPv6 packet
+   length = netBufferGetLength(ipPacket) - *headerOffset;
+
+   //Make sure the extension header is valid
+   if(length < sizeof(Ipv6DestOptHeader))
+      return ERROR_INVALID_HEADER;
+
+   //Point to the Destination Options header
+   header = netBufferAt(ipPacket, *headerOffset);
+   //Sanity check
+   if(header == NULL)
+      return ERROR_FAILURE;
+
+   //Calculate the length of the entire header
+   headerLength = (header->hdrExtLen * 8) + 8;
+
+   //Check header length
+   if(headerLength > length)
+      return ERROR_INVALID_HEADER;
+
+   //Debug message
+   TRACE_DEBUG("  Destination Options header\r\n");
+
+   //Compute the length of the Options field
+   n = headerLength - sizeof(Ipv6DestOptHeader);
+
+   //Parse options
+   error = ipv6ParseOptions(interface, ipPacket, ipPacketOffset,
+      *headerOffset + sizeof(Ipv6DestOptHeader), n);
+
+   //Any error to report?
+   if(error)
+      return error;
+
+   //Keep track of Next Header field
+   *nextHeaderOffset = *headerOffset + &header->nextHeader - (uint8_t *) header;
+   //Point to the next extension header
+   *headerOffset += headerLength;
+
+   //Successful processing
+   return NO_ERROR;
+}
+
+
+/**
+ * @brief Parse Routing header
+ * @param[in] interface Underlying network interface
+ * @param[in] ipPacket Multi-part buffer containing the IPv6 packet
+ * @param[in] ipPacketOffset Offset to the first byte of the IPv6 packet
+ * @param[in,out] headerOffset Offset to the Routing header
+ * @param[in,out] nextHeaderOffset Offset to the Next Header field
+ * @brief Error code
+ **/
+
+error_t ipv6ParseRoutingHeader(NetInterface *interface, const NetBuffer *ipPacket,
+   size_t ipPacketOffset, size_t *headerOffset, size_t *nextHeaderOffset)
+{
+   size_t n;
+   size_t length;
+   size_t headerLength;
+   Ipv6RoutingHeader *header;
+
+   //Remaining bytes to process in the IPv6 packet
+   length = netBufferGetLength(ipPacket) - *headerOffset;
+
+   //Make sure the extension header is valid
+   if(length < sizeof(Ipv6RoutingHeader))
+      return ERROR_INVALID_HEADER;
+
+   //Point to the Routing header
+   header = netBufferAt(ipPacket, *headerOffset);
+   //Sanity check
+   if(header == NULL)
+      return ERROR_FAILURE;
+
+   //Calculate the length of the entire header
+   headerLength = (header->hdrExtLen * 8) + 8;
+
+   //Check header length
+   if(headerLength > length)
+      return ERROR_INVALID_HEADER;
+
+   //Debug message
+   TRACE_DEBUG("  Routing header\r\n");
+
+   //If, while processing a received packet, a node encounters a Routing
+   //header with an unrecognized Routing Type value, the required behavior
+   //of the node depends on the value of the Segments Left field
+   if(header->segmentsLeft != 0)
+   {
+      //Retrieve the offset of the Routing header within the packet
+      n = *headerOffset - ipPacketOffset;
+      //Compute the exact offset of the Routing Type field
+      n += (uint8_t *) &header->routingType - (uint8_t *) header;
+
+      //If Segments Left is non-zero, send an ICMP Parameter Problem,
+      //Code 0, message to the packet's Source Address, pointing to
+      //the unrecognized Routing Type
+      icmpv6SendErrorMessage(interface, ICMPV6_TYPE_PARAM_PROBLEM,
+         ICMPV6_CODE_INVALID_HEADER_FIELD, n, ipPacket, ipPacketOffset);
+
+      //The node must discard the packet
+      return ERROR_INVALID_TYPE;
+   }
+
+   //Keep track of Next Header field
+   *nextHeaderOffset = *headerOffset + &header->nextHeader - (uint8_t *) header;
+   //Point to the next extension header
+   *headerOffset += headerLength;
+
+   //Successful processing
+   return NO_ERROR;
+}
+
+
+/**
+ * @brief Parse Authentication header
+ * @param[in] interface Underlying network interface
+ * @param[in] ipPacket Multi-part buffer containing the IPv6 packet
+ * @param[in] ipPacketOffset Offset to the first byte of the IPv6 packet
+ * @param[in,out] headerOffset Offset to the Authentication header
+ * @param[in,out] nextHeaderOffset Offset to the Next Header field
+ * @brief Error code
+ **/
+
+error_t ipv6ParseAuthHeader(NetInterface *interface, const NetBuffer *ipPacket,
+   size_t ipPacketOffset, size_t *headerOffset, size_t *nextHeaderOffset)
+{
+   //Debug message
+   TRACE_DEBUG("  Authentication header\r\n");
+   //Authentication not supported
+   return ERROR_FAILURE;
+}
+
+
+/**
+ * @brief Parse Encapsulating Security Payload header
+ * @param[in] interface Underlying network interface
+ * @param[in] ipPacket Multi-part buffer containing the IPv6 packet
+ * @param[in] ipPacketOffset Offset to the first byte of the IPv6 packet
+ * @param[in,out] headerOffset Offset to the Encapsulating Security Payload header
+ * @param[in,out] nextHeaderOffset Offset to the Next Header field
+ * @brief Error code
+ **/
+
+error_t ipv6ParseEspHeader(NetInterface *interface, const NetBuffer *ipPacket,
+   size_t ipPacketOffset, size_t *headerOffset, size_t *nextHeaderOffset)
+{
+   //Debug message
+   TRACE_DEBUG("  Encapsulating Security Payload header\r\n");
+   //Authentication not supported
+   return ERROR_FAILURE;
+}
+
+
+/**
+ * @brief Parse IPv6 options
+ * @param[in] interface Underlying network interface
+ * @param[in] ipPacket Multi-part buffer containing the IPv6 packet
+ * @param[in] ipPacketOffset Offset to the first byte of the IPv6 packet
+ * @param[in] optOffset Offset to the first byte of the Options field
+ * @param[in] optLength Length of the Options field
+ * @brief Error code
+ **/
+
+error_t ipv6ParseOptions(NetInterface *interface, const NetBuffer *ipPacket,
+   size_t ipPacketOffset, size_t optOffset, size_t optLength)
+{
+   size_t i;
+   size_t n;
+   uint8_t type;
+   uint8_t action;
+   uint8_t *options;
+   Ipv6Option *option;
+   Ipv6Header *ipHeader;
+
+   //Point to the first byte of the Options field
+   options = netBufferAt(ipPacket, optOffset);
+
+   //Sanity check
+   if(options == NULL)
+      return ERROR_FAILURE;
+
+   //Parse options
+   for(i = 0; i < optLength; )
+   {
+      //Point to the current option
+      option = (Ipv6Option *) (options + i);
+      //Get option type
+      type = option->type & IPV6_OPTION_TYPE_MASK;
+
+      //Pad1 option?
+      if(type == IPV6_OPTION_TYPE_PAD1)
+      {
+         //Advance data pointer
+         i++;
+      }
+      //PadN option?
+      else if(type == IPV6_OPTION_TYPE_PADN)
+      {
+         //Malformed IPv6 packet?
+         if((i + sizeof(Ipv6Option)) > optLength)
+            return ERROR_INVALID_LENGTH;
+
+         //Advance data pointer
+         i += sizeof(Ipv6Option) + option->length;
+      }
+      //Unrecognized option?
+      else
+      {
+         //Point to the IPv6 header
+         ipHeader = netBufferAt(ipPacket, ipPacketOffset);
+
+         //Sanity check
+         if(ipHeader == NULL)
+            return ERROR_FAILURE;
+
+         //Get the value of the highest-order two bits
+         action = option->type & IPV6_ACTION_MASK;
+
+         //The highest-order two bits specify the action that must be taken
+         //if the processing IPv6 node does not recognize the option type
+         if(action == IPV6_ACTION_SKIP_OPTION)
+         {
+            //Skip over this option and continue processing the header
+         }
+         else if(action == IPV6_ACTION_DISCARD_PACKET)
+         {
+            //Discard the packet
+            return ERROR_INVALID_OPTION;
+         }
+         else if(action == IPV6_ACTION_SEND_ICMP_ERROR_ALL)
+         {
+            //Calculate the octet offset within the invoking packet
+            //where the error was detected
+            n = optOffset + i - ipPacketOffset;
+
+            //Send an ICMP Parameter Problem message to the source of the
+            //packet, regardless of whether or not the destination address
+            //was a multicast address
+            icmpv6SendErrorMessage(interface, ICMPV6_TYPE_PARAM_PROBLEM,
+               ICMPV6_CODE_UNKNOWN_IPV6_OPTION, n, ipPacket, ipPacketOffset);
+
+            //Discard the packet
+            return ERROR_INVALID_OPTION;
+         }
+         else if(action == IPV6_ACTION_SEND_ICMP_ERROR_UNI)
+         {
+            //Send an ICMP Parameter Problem message to the source of the
+            //packet, only if the destination address was not a multicast
+            //address
+            if(!ipv6IsMulticastAddr(&ipHeader->destAddr))
+            {
+               //Calculate the octet offset within the invoking packet
+               //where the error was detected
+               n = optOffset + i - ipPacketOffset;
+
+               //Send the ICMP Parameter Problem message
+               icmpv6SendErrorMessage(interface, ICMPV6_TYPE_PARAM_PROBLEM,
+                  ICMPV6_CODE_UNKNOWN_IPV6_OPTION, n, ipPacket, ipPacketOffset);
+            }
+
+            //Discard the packet
+            return ERROR_INVALID_OPTION;
+         }
+
+         //Malformed IPv6 packet?
+         if((i + sizeof(Ipv6Option)) > optLength)
+            return ERROR_INVALID_LENGTH;
+
+         //Advance data pointer
+         i += sizeof(Ipv6Option) + option->length;
+      }
+   }
+
+   //Successful processing
+   return NO_ERROR;
+}
+
+
+/**
+ * @brief Send an IPv6 datagram
+ * @param[in] interface Underlying network interface
+ * @param[in] pseudoHeader IPv6 pseudo header
+ * @param[in] buffer Multi-part buffer containing the payload
+ * @param[in] offset Offset to the first byte of the payload
+ * @param[in] hopLimit Hop Limit value. Default value is used when this parameter is zero
+ * @return Error code
+ **/
+
+error_t ipv6SendDatagram(NetInterface *interface, Ipv6PseudoHeader *pseudoHeader,
+   NetBuffer *buffer, size_t offset, uint8_t hopLimit)
+{
+   error_t error;
+   size_t length;
+   size_t pathMtu;
+
+   //Retrieve the length of payload
+   length = netBufferGetLength(buffer) - offset;
+
+   //Check whether the Hop Limit value is zero
+   if(hopLimit == 0)
+   {
+      //Use default Hop Limit value
+      hopLimit = interface->ipv6Context.curHopLimit;
+   }
+
+#if (IPV6_PMTU_SUPPORT == ENABLED)
+   //Retrieve the PMTU for the specified destination address
+   pathMtu = ipv6GetPathMtu(interface, &pseudoHeader->destAddr);
+
+   //The PMTU should not exceed the MTU of the first-hop link
+   if(pathMtu > interface->ipv6Context.linkMtu)
+      pathMtu = interface->ipv6Context.linkMtu;
+#else
+   //The PMTU value for the path is assumed to be the MTU of the first-hop link
+   pathMtu = interface->ipv6Context.linkMtu;
+#endif
+
+   //If the payload is smaller than the PMTU then no fragmentation is needed
+   if((length + sizeof(Ipv6Header)) <= pathMtu)
+   {
+      //Send data as is
+      error = ipv6SendPacket(interface, pseudoHeader,
+         0, 0, buffer, offset, hopLimit);
+   }
+   //If the payload length exceeds the PMTU then the device must fragment the data
+   else
+   {
+#if (IPV6_FRAG_SUPPORT == ENABLED)
+      //Fragment IP datagram into smaller packets
+      error = ipv6FragmentDatagram(interface, pseudoHeader,
+         buffer, offset, pathMtu, hopLimit);
+#else
+      //Fragmentation is not supported
+      error = ERROR_MESSAGE_TOO_LONG;
+#endif
+   }
+
+   //Return status code
+   return error;
+}
+
+
+/**
+ * @brief Send an IPv6 packet
+ * @param[in] interface Underlying network interface
+ * @param[in] pseudoHeader IPv6 pseudo header
+ * @param[in] fragId Fragment identification field
+ * @param[in] fragOffset Fragment offset field
+ * @param[in] buffer Multi-part buffer containing the payload
+ * @param[in] offset Offset to the first byte of the payload
+ * @param[in] hopLimit Hop Limit value
+ * @return Error code
+ **/
+
+error_t ipv6SendPacket(NetInterface *interface, Ipv6PseudoHeader *pseudoHeader,
+   uint32_t fragId, size_t fragOffset, NetBuffer *buffer, size_t offset, uint8_t hopLimit)
+{
+   error_t error;
+   size_t length;
+   Ipv6Header *packet;
+
+   //Calculate the length of the payload
+   length = netBufferGetLength(buffer) - offset;
+
+   //Add Fragment header?
+   if(fragOffset != 0)
+   {
+      Ipv6FragmentHeader *header;
+
+      //Is there enough space for the IPv6 header and the Fragment header?
+      if(offset < (sizeof(Ipv6Header) + sizeof(Ipv6FragmentHeader)))
+         return ERROR_INVALID_PARAMETER;
+
+      //Make room for the Fragment header
+      offset -= sizeof(Ipv6FragmentHeader);
+      length += sizeof(Ipv6FragmentHeader);
+
+      //Point to the Fragment header
+      header = netBufferAt(buffer, offset);
+      //Format the Fragment header
+      header->nextHeader = pseudoHeader->nextHeader;
+      header->reserved = 0;
+      header->fragmentOffset = htons(fragOffset);
+      header->identification = htonl(fragId);
+
+      //Make room for the IPv6 header
+      offset -= sizeof(Ipv6Header);
+      length += sizeof(Ipv6Header);
+
+      //Point to the IPv6 header
+      packet = netBufferAt(buffer, offset);
+      //Properly set the Next Header field
+      packet->nextHeader = IPV6_FRAGMENT_HEADER;
+   }
+   else
+   {
+      //Is there enough space for the IPv6 header?
+      if(offset < sizeof(Ipv6Header))
+         return ERROR_INVALID_PARAMETER;
+
+      //Make room for the IPv6 header
+      offset -= sizeof(Ipv6Header);
+      length += sizeof(Ipv6Header);
+
+      //Point to the IPv6 header
+      packet = netBufferAt(buffer, offset);
+      //Properly set the Next Header field
+      packet->nextHeader = pseudoHeader->nextHeader;
+   }
+
+   //Format IPv6 header
+   packet->version = IPV6_VERSION;
+   packet->trafficClassH = 0;
+   packet->trafficClassL = 0;
+   packet->flowLabelH = 0;
+   packet->flowLabelL = 0;
+   packet->payloadLength = htons(length - sizeof(Ipv6Header));
+   packet->hopLimit = hopLimit;
+   packet->srcAddr = pseudoHeader->srcAddr;
+   packet->destAddr = pseudoHeader->destAddr;
+
+   //Check whether the source address is acceptable
+   error = ipv6CheckSourceAddr(interface, &pseudoHeader->srcAddr);
+   //Invalid source address?
+   if(error)
+      return error;
+
+   //Destination IPv6 address is the unspecified address?
+   if(ipv6CompAddr(&pseudoHeader->destAddr, &IPV6_UNSPECIFIED_ADDR))
+   {
+      //Destination address is not acceptable
+      return ERROR_INVALID_ADDRESS;
+   }
+   //Destination address is the loopback address?
+   else if(ipv6CompAddr(&pseudoHeader->destAddr, &IPV6_LOOPBACK_ADDR))
+   {
+      //Not yet implemented...
+      return ERROR_NOT_IMPLEMENTED;
+   }
+
+#if (ETH_SUPPORT == ENABLED)
+   //Ethernet interface?
+   if(interface->nicDriver->type == NIC_TYPE_ETHERNET)
+   {
+      Ipv6Addr destIpAddr;
+      MacAddr destMacAddr;
+      NdpDestCacheEntry *entry;
+
+      //When the sending node has a packet to send, it first examines
+      //the Destination Cache
+      entry = ndpFindDestCacheEntry(interface, &pseudoHeader->destAddr);
+
+      //Check whether a matching entry exists
+      if(entry != NULL)
+      {
+         //Retrieve the address of the next-hop
+         destIpAddr = entry->nextHop;
+         //Update timestamp
+         entry->timestamp = osGetSystemTime();
+         //No error to report
+         error = NO_ERROR;
+      }
+      else
+      {
+         //Perform next-hop determination
+         error = ndpSelectNextHop(interface,
+            &pseudoHeader->destAddr, NULL, &destIpAddr);
+
+         //Check status code
+         if(error == NO_ERROR)
+         {
+            //Create a new Destination Cache entry
+            entry = ndpCreateDestCacheEntry(interface);
+
+            //Destination cache entry successfully created?
+            if(entry != NULL)
+            {
+               //Destination address
+               entry->destAddr = pseudoHeader->destAddr;
+               //Address of the next hop
+               entry->nextHop = destIpAddr;
+
+               //Initially, the PMTU value for a path is assumed to be
+               //the MTU of the first-hop link
+               entry->pathMtu = interface->ipv6Context.linkMtu;
+
+               //Set timestamp
+               entry->timestamp = osGetSystemTime();
+            }
+         }
+      }
+
+      //Successful next-hop determination?
+      if(error == NO_ERROR)
+      {
+         //Destination IPv6 address is a multicast address?
+         if(ipv6IsMulticastAddr(&destIpAddr))
+         {
+            //Map IPv6 multicast address to MAC-layer multicast address
+            error = ipv6MapMulticastAddrToMac(&destIpAddr, &destMacAddr);
+         }
+         else
+         {
+            //Resolve host address using Neighbor Discovery protocol
+            error = ndpResolve(interface, &destIpAddr, &destMacAddr);
+         }
+
+         //Successful address resolution?
+         if(error == NO_ERROR)
+         {
+            //Debug message
+            TRACE_INFO("Sending IPv6 packet (%" PRIuSIZE " bytes)...\r\n", length);
+            //Dump IP header contents for debugging purpose
+            ipv6DumpHeader(packet);
+
+            //Send Ethernet frame
+            error = ethSendFrame(interface, &destMacAddr, buffer, offset, ETH_TYPE_IPV6);
+         }
+         //Address resolution is in progress?
+         else if(error == ERROR_IN_PROGRESS)
+         {
+            //Debug message
+            TRACE_INFO("Enqueuing IPv6 packet (%" PRIuSIZE " bytes)...\r\n", length);
+            //Dump IP header contents for debugging purpose
+            ipv6DumpHeader(packet);
+
+            //Enqueue packets waiting for address resolution
+            error = ndpEnqueuePacket(NULL, interface, &destIpAddr, buffer, offset);
+         }
+         //Address resolution failed?
+         else
+         {
+            //Debug message
+            TRACE_WARNING("Cannot map IPv6 address to Ethernet address!\r\n");
+         }
+      }
+   }
+   else
+#endif
+#if (PPP_SUPPORT == ENABLED)
+   //PPP interface?
+   if(interface->nicDriver->type == NIC_TYPE_PPP)
+   {
+      //Debug message
+      TRACE_INFO("Sending IPv6 packet (%" PRIuSIZE " bytes)...\r\n", length);
+      //Dump IP header contents for debugging purpose
+      ipv6DumpHeader(packet);
+
+      //Send PPP frame
+      error = pppSendFrame(interface, buffer, offset, PPP_PROTOCOL_IPV6);
+   }
+   else
+#endif
+   //6LoWPAN interface?
+   if(interface->nicDriver->type == NIC_TYPE_6LOWPAN)
+   {
+      //Debug message
+      TRACE_INFO("Sending IPv6 packet (%" PRIuSIZE " bytes)...\r\n", length);
+      //Dump IP header contents for debugging purpose
+      ipv6DumpHeader(packet);
+
+      //Send the packet over the specified link
+      error = nicSendPacket(interface, buffer, offset);
+   }
+   else
+   //Unknown interface type?
+   {
+      //Report an error
+      error = ERROR_INVALID_INTERFACE;
+   }
+
+   //Return status code
+   return error;
+}
+
+
+/**
+ * @brief Join an IPv6 multicast group
+ * @param[in] interface Underlying network interface
+ * @param[in] groupAddr IPv6 Multicast address to join
+ * @return Error code
+ **/
+
+error_t ipv6JoinMulticastGroup(NetInterface *interface, const Ipv6Addr *groupAddr)
+{
+   error_t error;
+   uint_t i;
+   Ipv6FilterEntry *entry;
+   Ipv6FilterEntry *firstFreeEntry;
+#if (ETH_SUPPORT == ENABLED)
+   MacAddr macAddr;
+#endif
+
+   //The IPv6 address must be a valid multicast address
+   if(!ipv6IsMulticastAddr(groupAddr))
+      return ERROR_INVALID_ADDRESS;
+
+   //Initialize error code
+   error = NO_ERROR;
+   //Keep track of the first free entry
+   firstFreeEntry = NULL;
+
+   //Go through the multicast filter table
+   for(i = 0; i < IPV6_MULTICAST_FILTER_SIZE; i++)
+   {
+      //Point to the current entry
+      entry = &interface->ipv6Context.multicastFilter[i];
+
+      //Valid entry?
+      if(entry->refCount > 0)
+      {
+         //Check whether the table already contains the specified IPv6 address
+         if(ipv6CompAddr(&entry->addr, groupAddr))
+         {
+            //Increment the reference count
+            entry->refCount++;
+            //Successful processing
+            return NO_ERROR;
+         }
+      }
+      else
+      {
+         //Keep track of the first free entry
+         if(firstFreeEntry == NULL)
+            firstFreeEntry = entry;
+      }
+   }
+
+   //Check whether the multicast filter table is full
+   if(firstFreeEntry == NULL)
+   {
+      //A new entry cannot be added
+      return ERROR_FAILURE;
+   }
+
+#if (ETH_SUPPORT == ENABLED)
+   //Map the IPv6 multicast address to a MAC-layer address
+   ipv6MapMulticastAddrToMac(groupAddr, &macAddr);
+   //Add the corresponding address to the MAC filter table
+   error = ethAcceptMulticastAddr(interface, &macAddr);
+#endif
+
+   //MAC filter table successfully updated?
+   if(!error)
+   {
+      //Now we can safely add a new entry to the table
+      firstFreeEntry->addr = *groupAddr;
+      //Initialize the reference count
+      firstFreeEntry->refCount = 1;
+
+#if (MLD_SUPPORT == ENABLED)
+      //Start listening to the multicast address
+      mldStartListening(interface, firstFreeEntry);
+#endif
+   }
+
+   //Return status code
+   return error;
+}
+
+
+/**
+ * @brief Leave an IPv6 multicast group
+ * @param[in] interface Underlying network interface
+ * @param[in] groupAddr IPv6 multicast address to drop
+ * @return Error code
+ **/
+
+error_t ipv6LeaveMulticastGroup(NetInterface *interface, const Ipv6Addr *groupAddr)
+{
+   uint_t i;
+   Ipv6FilterEntry *entry;
+#if (ETH_SUPPORT == ENABLED)
+   MacAddr macAddr;
+#endif
+
+   //The IPv6 address must be a valid multicast address
+   if(!ipv6IsMulticastAddr(groupAddr))
+      return ERROR_INVALID_ADDRESS;
+
+   //Go through the multicast filter table
+   for(i = 0; i < IPV6_MULTICAST_FILTER_SIZE; i++)
+   {
+      //Point to the current entry
+      entry = &interface->ipv6Context.multicastFilter[i];
+
+      //Valid entry?
+      if(entry->refCount > 0)
+      {
+         //Specified IPv6 address found?
+         if(ipv6CompAddr(&entry->addr, groupAddr))
+         {
+            //Decrement the reference count
+            entry->refCount--;
+
+            //Remove the entry if the reference count drops to zero
+            if(entry->refCount == 0)
+            {
+#if (MLD_SUPPORT == ENABLED)
+               //Stop listening to the multicast address
+               mldStopListening(interface, entry);
+#endif
+#if (ETH_SUPPORT == ENABLED)
+               //Map the IPv6 multicast address to a MAC-layer address
+               ipv6MapMulticastAddrToMac(groupAddr, &macAddr);
+               //Drop the corresponding address from the MAC filter table
+               ethDropMulticastAddr(interface, &macAddr);
+#endif
+               //Remove the multicast address from the list
+               entry->addr = IPV6_UNSPECIFIED_ADDR;
+            }
+
+            //Successful processing
+            return NO_ERROR;
+         }
+      }
+   }
+
+   //The specified IPv6 address does not exist
+   return ERROR_ADDRESS_NOT_FOUND;
+}
+
+
+/**
+ * @brief Convert a string representation of an IPv6 address to a binary IPv6 address
+ * @param[in] str NULL-terminated string representing the IPv6 address
+ * @param[out] ipAddr Binary representation of the IPv6 address
+ * @return Error code
+ **/
+
+error_t ipv6StringToAddr(const char_t *str, Ipv6Addr *ipAddr)
+{
+   error_t error;
+   int_t i = 0;
+   int_t j = -1;
+   int_t k = 0;
+   int32_t value = -1;
+
+   //Parse input string
+   while(1)
+   {
+      //Hexadecimal digit found?
+      if(isxdigit((uint8_t) *str))
+      {
+         //First digit to be decoded?
+         if(value < 0)
+            value = 0;
+
+         //Update the value of the current 16-bit word
+         if(isdigit((uint8_t) *str))
+            value = (value * 16) + (*str - '0');
+         else if(isupper((uint8_t) *str))
+            value = (value * 16) + (*str - 'A' + 10);
+         else
+            value = (value * 16) + (*str - 'a' + 10);
+
+         //Check resulting value
+         if(value > 0xFFFF)
+         {
+            //The conversion failed
+            error = ERROR_INVALID_SYNTAX;
+            break;
+         }
+      }
+      //"::" symbol found?
+      else if(!strncmp(str, "::", 2))
+      {
+         //The "::" can only appear once in an IPv6 address
+         if(j >= 0)
+         {
+            //The conversion failed
+            error = ERROR_INVALID_SYNTAX;
+            break;
+         }
+
+         //The "::" symbol is preceded by a number?
+         if(value >= 0)
+         {
+            //Save the current 16-bit word
+            ipAddr->w[i++] = htons(value);
+            //Prepare to decode the next 16-bit word
+            value = -1;
+         }
+
+         //Save the position of the "::" symbol
+         j = i;
+         //Point to the next character
+         str++;
+      }
+      //":" symbol found?
+      else if(*str == ':' && i < 8)
+      {
+         //Each ":" must be preceded by a valid number
+         if(value < 0)
+         {
+            //The conversion failed
+            error = ERROR_INVALID_SYNTAX;
+            break;
+         }
+
+         //Save the current 16-bit word
+         ipAddr->w[i++] = htons(value);
+         //Prepare to decode the next 16-bit word
+         value = -1;
+      }
+      //End of string detected?
+      else if(*str == '\0' && i == 7 && j < 0)
+      {
+         //The NULL character must be preceded by a valid number
+         if(value < 0)
+         {
+            //The conversion failed
+            error = ERROR_INVALID_SYNTAX;
+         }
+         else
+         {
+            //Save the last 16-bit word of the IPv6 address
+            ipAddr->w[i] = htons(value);
+            //The conversion succeeded
+            error = NO_ERROR;
+         }
+
+         //We are done
+         break;
+      }
+      else if(*str == '\0' && i < 7 && j >= 0)
+      {
+         //Save the last 16-bit word of the IPv6 address
+         if(value >= 0)
+            ipAddr->w[i++] = htons(value);
+
+         //Move the part of the address that follows the "::" symbol
+         for(k = 0; k < (i - j); k++)
+            ipAddr->w[7 - k] = ipAddr->w[i - 1 - k];
+         //A sequence of zeroes can now be written in place of "::"
+         for(k = 0; k < (8 - i); k++)
+            ipAddr->w[j + k] = 0;
+
+         //The conversion succeeded
+         error = NO_ERROR;
+         break;
+      }
+      //Invalid character...
+      else
+      {
+         //The conversion failed
+         error = ERROR_INVALID_SYNTAX;
+         break;
+      }
+
+      //Point to the next character
+      str++;
+   }
+
+   //Return status code
+   return error;
+}
+
+
+/**
+ * @brief Convert a binary IPv6 address to a string representation
+ *
+ * Call ipv6AddrToString() to convert an IPv6 address to a text representation. The
+ * implementation of ipv6AddrToString() function follows RFC 5952 recommendations
+ *
+ * @param[in] ipAddr Binary representation of the IPv6 address
+ * @param[out] str NULL-terminated string representing the IPv6 address
+ * @return Pointer to the formatted string
+ **/
+
+char_t *ipv6AddrToString(const Ipv6Addr *ipAddr, char_t *str)
+{
+   static char_t buffer[40];
+   uint_t i;
+   uint_t j;
+   char_t *p;
+
+   //Best run of zeroes
+   uint_t zeroRunStart = 0;
+   uint_t zeroRunEnd = 0;
+
+   //If the NULL pointer is given as parameter, then the internal buffer is used
+   if(str == NULL)
+      str = buffer;
+
+   //Find the longest run of zeros for "::" short-handing
+   for(i = 0; i < 8; i++)
+   {
+      //Compute the length of the current sequence of zeroes
+      for(j = i; j < 8 && !ipAddr->w[j]; j++);
+
+      //Keep track of the longest one
+      if((j - i) > 1 && (j - i) > (zeroRunEnd - zeroRunStart))
+      {
+         //The symbol "::" should not be used to shorten just one zero field
+         zeroRunStart = i;
+         zeroRunEnd = j;
+      }
+   }
+
+   //Format IPv6 address
+   for(p = str, i = 0; i < 8; i++)
+   {
+      //Are we inside the best run of zeroes?
+      if(i >= zeroRunStart && i < zeroRunEnd)
+      {
+         //Append a separator
+         *(p++) = ':';
+         //Skip the sequence of zeroes
+         i = zeroRunEnd - 1;
+      }
+      else
+      {
+         //Add a separator between each 16-bit word
+         if(i > 0)
+            *(p++) = ':';
+
+         //Convert the current 16-bit word to string
+         p += sprintf(p, "%" PRIx16, ntohs(ipAddr->w[i]));
+      }
+   }
+
+   //A trailing run of zeroes has been found?
+   if(zeroRunEnd == 8)
+      *(p++) = ':';
+
+   //Properly terminate the string
+   *p = '\0';
+
+   //Return a pointer to the formatted string
+   return str;
+}
+
+
+/**
+ * @brief Dump IPv6 header for debugging purpose
+ * @param[in] ipHeader IPv6 header
+ **/
+
+void ipv6DumpHeader(const Ipv6Header *ipHeader)
+{
+   //Dump IPv6 header contents
+   TRACE_DEBUG("  Version = %" PRIu8 "\r\n", ipHeader->version);
+   TRACE_DEBUG("  Traffic Class = %u\r\n", (ipHeader->trafficClassH << 4) | ipHeader->trafficClassL);
+   TRACE_DEBUG("  Flow Label = 0x%05X\r\n", (ipHeader->flowLabelH << 16) | ntohs(ipHeader->flowLabelL));
+   TRACE_DEBUG("  Payload Length = %" PRIu16 "\r\n", ntohs(ipHeader->payloadLength));
+   TRACE_DEBUG("  Next Header = %" PRIu8 "\r\n", ipHeader->nextHeader);
+   TRACE_DEBUG("  Hop Limit = %" PRIu8 "\r\n", ipHeader->hopLimit);
+   TRACE_DEBUG("  Src Addr = %s\r\n", ipv6AddrToString(&ipHeader->srcAddr, NULL));
+   TRACE_DEBUG("  Dest Addr = %s\r\n", ipv6AddrToString(&ipHeader->destAddr, NULL));
+}
+
+#endif
+