Webserver+3d print

Dependents:   Nucleo

Revision:
0:8918a71cdbe9
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cyclone_tcp/ipv4/ipv4.c	Sat Feb 04 18:15:49 2017 +0000
@@ -0,0 +1,1576 @@
+/**
+ * @file ipv4.c
+ * @brief IPv4 (Internet Protocol Version 4)
+ *
+ * @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
+ *
+ * The Internet Protocol (IP) provides the functions necessary to deliver a
+ * datagram from a source to a destination over an interconnected system of
+ * networks. Refer to RFC 791 for complete details
+ *
+ * @author Oryx Embedded SARL (www.oryx-embedded.com)
+ * @version 1.7.6
+ **/
+
+//Switch to the appropriate trace level
+#define TRACE_LEVEL IPV4_TRACE_LEVEL
+
+//Dependencies
+#include <string.h>
+#include <ctype.h>
+#include "core/net.h"
+#include "core/ethernet.h"
+#include "core/ip.h"
+#include "core/udp.h"
+#include "core/tcp_fsm.h"
+#include "core/raw_socket.h"
+#include "ipv4/arp.h"
+#include "ipv4/ipv4.h"
+#include "ipv4/ipv4_routing.h"
+#include "ipv4/icmp.h"
+#include "ipv4/igmp.h"
+#include "ipv4/auto_ip.h"
+#include "dhcp/dhcp_client.h"
+#include "mdns/mdns_responder.h"
+#include "mibs/mib2_module.h"
+#include "debug.h"
+
+//Check TCP/IP stack configuration
+#if (IPV4_SUPPORT == ENABLED)
+
+
+/**
+ * @brief IPv4 related initialization
+ * @param[in] interface Underlying network interface
+ * @return Error code
+ **/
+
+error_t ipv4Init(NetInterface *interface)
+{
+   Ipv4Context *context;
+
+   //Point to the IPv4 context
+   context = &interface->ipv4Context;
+
+   //Clear the IPv4 context
+   memset(context, 0, sizeof(Ipv4Context));
+
+   //Initialize interface specific variables
+   context->linkMtu = interface->nicDriver->mtu;
+   context->isRouter = FALSE;
+
+   //Identification field is primarily used to identify
+   //fragments of an original IP datagram
+   context->identification = 0;
+
+   //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 (IPV4_FRAG_SUPPORT == ENABLED)
+   //Initialize the reassembly queue
+   memset(context->fragQueue, 0, sizeof(context->fragQueue));
+#endif
+
+   //Successful initialization
+   return NO_ERROR;
+}
+
+
+/**
+ * @brief Assign host address
+ * @param[in] interface Pointer to the desired network interface
+ * @param[in] addr IPv4 host address
+ * @return Error code
+ **/
+
+error_t ipv4SetHostAddr(NetInterface *interface, Ipv4Addr addr)
+{
+   //Check parameters
+   if(interface == NULL)
+      return ERROR_INVALID_PARAMETER;
+
+   //The IPv4 address must be a valid unicast address
+   if(ipv4IsMulticastAddr(addr))
+      return ERROR_INVALID_ADDRESS;
+
+   //Get exclusive access
+   osAcquireMutex(&netMutex);
+
+   //Set up host address
+   interface->ipv4Context.addr = addr;
+   //Clear conflict flag
+   interface->ipv4Context.addrConflict = FALSE;
+
+   //Check whether the new host address is valid
+   if(addr != IPV4_UNSPECIFIED_ADDR)
+   {
+      //The use of the IPv4 address is now unrestricted
+      interface->ipv4Context.addrState = IPV4_ADDR_STATE_VALID;
+   }
+   else
+   {
+      //The IPv4 address is no longer valid
+      interface->ipv4Context.addrState = IPV4_ADDR_STATE_INVALID;
+   }
+
+#if (MDNS_RESPONDER_SUPPORT == ENABLED)
+   //Restart mDNS probing process
+   mdnsResponderStartProbing(interface->mdnsResponderContext);
+#endif
+
+   //Release exclusive access
+   osReleaseMutex(&netMutex);
+
+   //Successful processing
+   return NO_ERROR;
+}
+
+
+/**
+ * @brief Retrieve host address
+ * @param[in] interface Pointer to the desired network interface
+ * @param[out] addr IPv4 host address
+ * @return Error code
+ **/
+
+error_t ipv4GetHostAddr(NetInterface *interface, Ipv4Addr *addr)
+{
+   //Check parameters
+   if(interface == NULL || addr == NULL)
+      return ERROR_INVALID_PARAMETER;
+
+   //Get exclusive access
+   osAcquireMutex(&netMutex);
+
+   //Check whether the host address is valid
+   if(interface->ipv4Context.addrState == IPV4_ADDR_STATE_VALID)
+   {
+      //Get IPv4 address
+      *addr = interface->ipv4Context.addr;
+   }
+   else
+   {
+      //Return the unspecified address when no address has been assigned
+      *addr = IPV4_UNSPECIFIED_ADDR;
+   }
+
+   //Release exclusive access
+   osReleaseMutex(&netMutex);
+
+   //Successful processing
+   return NO_ERROR;
+}
+
+
+/**
+ * @brief Configure subnet mask
+ * @param[in] interface Pointer to the desired network interface
+ * @param[in] mask Subnet mask
+ * @return Error code
+ **/
+
+error_t ipv4SetSubnetMask(NetInterface *interface, Ipv4Addr mask)
+{
+   //Check parameters
+   if(interface == NULL)
+      return ERROR_INVALID_PARAMETER;
+
+   //Get exclusive access
+   osAcquireMutex(&netMutex);
+   //Set up subnet mask
+   interface->ipv4Context.subnetMask = mask;
+   //Release exclusive access
+   osReleaseMutex(&netMutex);
+
+   //Successful processing
+   return NO_ERROR;
+}
+
+
+/**
+ * @brief Retrieve subnet mask
+ * @param[in] interface Pointer to the desired network interface
+ * @param[out] mask Subnet mask
+ * @return Error code
+ **/
+
+error_t ipv4GetSubnetMask(NetInterface *interface, Ipv4Addr *mask)
+{
+   //Check parameters
+   if(interface == NULL || mask == NULL)
+      return ERROR_INVALID_PARAMETER;
+
+   //Get exclusive access
+   osAcquireMutex(&netMutex);
+   //Get subnet mask
+   *mask = interface->ipv4Context.subnetMask;
+   //Release exclusive access
+   osReleaseMutex(&netMutex);
+
+   //Successful processing
+   return NO_ERROR;
+}
+
+
+/**
+ * @brief Configure default gateway
+ * @param[in] interface Pointer to the desired network interface
+ * @param[in] addr Default gateway address
+ * @return Error code
+ **/
+
+error_t ipv4SetDefaultGateway(NetInterface *interface, Ipv4Addr addr)
+{
+   //Check parameters
+   if(interface == NULL)
+      return ERROR_INVALID_PARAMETER;
+
+   //The IPv4 address must be a valid unicast address
+   if(ipv4IsMulticastAddr(addr))
+      return ERROR_INVALID_ADDRESS;
+
+   //Get exclusive access
+   osAcquireMutex(&netMutex);
+   //Set up default gateway address
+   interface->ipv4Context.defaultGateway = addr;
+   //Release exclusive access
+   osReleaseMutex(&netMutex);
+
+   //Successful processing
+   return NO_ERROR;
+}
+
+
+/**
+ * @brief Retrieve default gateway
+ * @param[in] interface Pointer to the desired network interface
+ * @param[out] addr Default gateway address
+ * @return Error code
+ **/
+
+error_t ipv4GetDefaultGateway(NetInterface *interface, Ipv4Addr *addr)
+{
+   //Check parameters
+   if(interface == NULL || addr == NULL)
+      return ERROR_INVALID_PARAMETER;
+
+   //Get exclusive access
+   osAcquireMutex(&netMutex);
+   //Get default gateway address
+   *addr = interface->ipv4Context.defaultGateway;
+   //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 ipv4SetDnsServer(NetInterface *interface, uint_t index, Ipv4Addr addr)
+{
+   //Check parameters
+   if(interface == NULL)
+      return ERROR_INVALID_PARAMETER;
+
+   //Make sure that the index is valid
+   if(index >= IPV4_DNS_SERVER_LIST_SIZE)
+      return ERROR_OUT_OF_RANGE;
+
+   //The IPv4 address must be a valid unicast address
+   if(ipv4IsMulticastAddr(addr))
+      return ERROR_INVALID_ADDRESS;
+
+   //Get exclusive access
+   osAcquireMutex(&netMutex);
+   //Set up DNS server address
+   interface->ipv4Context.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 ipv4GetDnsServer(NetInterface *interface, uint_t index, Ipv4Addr *addr)
+{
+   //Check parameters
+   if(interface == NULL || addr == NULL)
+      return ERROR_INVALID_PARAMETER;
+
+   //Make sure that the index is valid
+   if(index >= IPV4_DNS_SERVER_LIST_SIZE)
+   {
+      //Return the unspecified address when the index is out of range
+      *addr = IPV4_UNSPECIFIED_ADDR;
+      //Report an error
+      return ERROR_OUT_OF_RANGE;
+   }
+
+   //Get exclusive access
+   osAcquireMutex(&netMutex);
+   //Get DNS server address
+   *addr = interface->ipv4Context.dnsServerList[index];
+   //Release exclusive access
+   osReleaseMutex(&netMutex);
+
+   //Successful processing
+   return NO_ERROR;
+}
+
+
+/**
+ * @brief Get IPv4 broadcast address
+ * @param[in] interface Pointer to the desired network interface
+ * @param[out] addr IPv4 broadcast address
+ **/
+
+error_t ipv4GetBroadcastAddr(NetInterface *interface, Ipv4Addr *addr)
+{
+   //Check parameters
+   if(interface == NULL || addr == NULL)
+      return ERROR_INVALID_PARAMETER;
+
+   //The broadcast address is obtained by performing a bitwise OR operation
+   //between the bit complement of the subnet mask and the host IP address
+   *addr = interface->ipv4Context.addr;
+   *addr |= ~interface->ipv4Context.subnetMask;
+
+   //Successful processing
+   return NO_ERROR;
+}
+
+
+/**
+ * @brief Callback function for link change event
+ * @param[in] interface Underlying network interface
+ **/
+
+void ipv4LinkChangeEvent(NetInterface *interface)
+{
+   Ipv4Context *context;
+
+   //Point to the IPv4 context
+   context = &interface->ipv4Context;
+
+   //Restore default MTU
+   context->linkMtu = interface->nicDriver->mtu;
+
+#if (ETH_SUPPORT == ENABLED)
+   //Flush ARP cache contents
+   arpFlushCache(interface);
+#endif
+
+#if (IPV4_FRAG_SUPPORT == ENABLED)
+   //Flush the reassembly queue
+   ipv4FlushFragQueue(interface);
+#endif
+
+#if (IGMP_SUPPORT == ENABLED)
+   //Notify IGMP of link state changes
+   igmpLinkChangeEvent(interface);
+#endif
+
+#if (AUTO_IP_SUPPORT == ENABLED)
+   //Notify Auto-IP of link state changes
+   autoIpLinkChangeEvent(interface->autoIpContext);
+#endif
+
+#if (DHCP_CLIENT_SUPPORT == ENABLED)
+   //Notify the DHCP client of link state changes
+   dhcpClientLinkChangeEvent(interface->dhcpClientContext);
+#endif
+}
+
+
+/**
+ * @brief Incoming IPv4 packet processing
+ * @param[in] interface Underlying network interface
+ * @param[in] packet Incoming IPv4 packet
+ * @param[in] length Packet length including header and payload
+ **/
+
+void ipv4ProcessPacket(NetInterface *interface, Ipv4Header *packet, size_t length)
+{
+   //Total number of input datagrams received from interfaces,
+   //including those received in error
+   MIB2_INC_COUNTER32(mib2Base.ipGroup.ipInReceives, 1);
+
+   //Ensure the packet length is greater than 20 bytes
+   if(length < sizeof(Ipv4Header))
+   {
+      //Number of input datagrams discarded due to errors in their IP headers
+      MIB2_INC_COUNTER32(mib2Base.ipGroup.ipInHdrErrors, 1);
+      //Discard the received packet
+      return;
+   }
+
+   //Debug message
+   TRACE_INFO("IPv4 packet received (%" PRIuSIZE " bytes)...\r\n", length);
+   //Dump IP header contents for debugging purpose
+   ipv4DumpHeader(packet);
+
+   //A packet whose version number is not 4 must be silently discarded
+   if(packet->version != IPV4_VERSION)
+   {
+      //Number of input datagrams discarded due to errors in their IP headers
+      MIB2_INC_COUNTER32(mib2Base.ipGroup.ipInHdrErrors, 1);
+      //Discard the received packet
+      return;
+   }
+
+   //Valid IPv4 header shall contains more than five 32-bit words
+   if(packet->headerLength < 5)
+   {
+      //Number of input datagrams discarded due to errors in their IP headers
+      MIB2_INC_COUNTER32(mib2Base.ipGroup.ipInHdrErrors, 1);
+      //Discard the received packet
+      return;
+   }
+
+   //Ensure the total length is correct before processing the packet
+   if(ntohs(packet->totalLength) < (packet->headerLength * 4))
+   {
+      //Number of input datagrams discarded due to errors in their IP headers
+      MIB2_INC_COUNTER32(mib2Base.ipGroup.ipInHdrErrors, 1);
+      //Discard the received packet
+      return;
+   }
+   if(ntohs(packet->totalLength) > length)
+   {
+      //Number of input datagrams discarded due to errors in their IP headers
+      MIB2_INC_COUNTER32(mib2Base.ipGroup.ipInHdrErrors, 1);
+      //Discard the received packet
+      return;
+   }
+
+   //Source address filtering
+   if(ipv4CheckSourceAddr(interface, packet->srcAddr))
+   {
+      //Number of input datagrams discarded due to errors in their IP headers
+      MIB2_INC_COUNTER32(mib2Base.ipGroup.ipInHdrErrors, 1);
+      //Discard the received packet
+      return;
+   }
+
+#if defined(IPV4_PACKET_FORWARD_HOOK)
+   IPV4_PACKET_FORWARD_HOOK(interface, packet, length);
+#else
+   //Destination address filtering
+   if(ipv4CheckDestAddr(interface, packet->destAddr))
+   {
+#if(IPV4_ROUTING_SUPPORT == ENABLED)
+      NetBuffer1 buffer;
+
+      //Unfragmented datagrams fit in a single chunk
+      buffer.chunkCount = 1;
+      buffer.maxChunkCount = 1;
+      buffer.chunk[0].address = packet;
+      buffer.chunk[0].length = length;
+
+      //Forward the packet according to the routing table
+      ipv4ForwardPacket(interface, (NetBuffer *) &buffer, 0);
+#else
+      //Number of input datagrams discarded because the destination IP
+      //address was not a valid address
+      MIB2_INC_COUNTER32(mib2Base.ipGroup.ipInAddrErrors, 1);
+#endif
+      //We are done
+      return;
+   }
+#endif
+
+   //Packets addressed to a tentative address should be silently discarded
+   if(ipv4IsTentativeAddr(interface, packet->destAddr))
+   {
+      //Number of input datagrams discarded because the destination IP
+      //address was not a valid address
+      MIB2_INC_COUNTER32(mib2Base.ipGroup.ipInAddrErrors, 1);
+      //Discard the received packet
+      return;
+   }
+
+   //The host must verify the IP header checksum on every received
+   //datagram and silently discard every datagram that has a bad
+   //checksum (see RFC 1122 3.2.1.2)
+   if(ipCalcChecksum(packet, packet->headerLength * 4) != 0x0000)
+   {
+      //Debug message
+      TRACE_WARNING("Wrong IP header checksum!\r\n");
+      //Number of input datagrams discarded due to errors in their IP headers
+      MIB2_INC_COUNTER32(mib2Base.ipGroup.ipInHdrErrors, 1);
+      //Discard incoming packet
+      return;
+   }
+
+   //Convert the total length from network byte order
+   length = ntohs(packet->totalLength);
+
+   //A fragmented packet was received?
+   if(ntohs(packet->fragmentOffset) & (IPV4_FLAG_MF | IPV4_OFFSET_MASK))
+   {
+#if (IPV4_FRAG_SUPPORT == ENABLED)
+      //Reassemble the original datagram
+      ipv4ReassembleDatagram(interface, packet, length);
+#endif
+   }
+   else
+   {
+      NetBuffer1 buffer;
+
+      //Unfragmented datagrams fit in a single chunk
+      buffer.chunkCount = 1;
+      buffer.maxChunkCount = 1;
+      buffer.chunk[0].address = packet;
+      buffer.chunk[0].length = length;
+
+      //Pass the IPv4 datagram to the higher protocol layer
+      ipv4ProcessDatagram(interface, (NetBuffer *) &buffer);
+   }
+}
+
+
+/**
+ * @brief Incoming IPv4 datagram processing
+ * @param[in] interface Underlying network interface
+ * @param[in] buffer Multi-part buffer that holds the incoming IPv4 datagram
+ **/
+
+void ipv4ProcessDatagram(NetInterface *interface, const NetBuffer *buffer)
+{
+   error_t error;
+   size_t offset;
+   size_t length;
+   Ipv4Header *header;
+   IpPseudoHeader pseudoHeader;
+
+   //Retrieve the length of the IPv4 datagram
+   length = netBufferGetLength(buffer);
+
+   //Point to the IPv4 header
+   header = netBufferAt(buffer, 0);
+   //Sanity check
+   if(header == NULL)
+      return;
+
+   //Debug message
+   TRACE_INFO("IPv4 datagram received (%" PRIuSIZE " bytes)...\r\n", length);
+   //Dump IP header contents for debugging purpose
+   ipv4DumpHeader(header);
+
+   //Get the offset to the payload
+   offset = header->headerLength * 4;
+   //Compute the length of the payload
+   length -= header->headerLength * 4;
+
+   //Form the IPv4 pseudo header
+   pseudoHeader.length = sizeof(Ipv4PseudoHeader);
+   pseudoHeader.ipv4Data.srcAddr = header->srcAddr;
+   pseudoHeader.ipv4Data.destAddr = header->destAddr;
+   pseudoHeader.ipv4Data.reserved = 0;
+   pseudoHeader.ipv4Data.protocol = header->protocol;
+   pseudoHeader.ipv4Data.length = htons(length);
+
+#if defined(IPV4_DATAGRAM_FORWARD_HOOK)
+   IPV4_DATAGRAM_FORWARD_HOOK(interface, &pseudoHeader, buffer, offset);
+#endif
+
+   //Check the protocol field
+   switch(header->protocol)
+   {
+   //ICMP protocol?
+   case IPV4_PROTOCOL_ICMP:
+      //Process incoming ICMP message
+      icmpProcessMessage(interface, header->srcAddr, buffer, offset);
+#if (RAW_SOCKET_SUPPORT == ENABLED)
+      //Allow raw sockets to process ICMP messages
+      rawSocketProcessIpPacket(interface, &pseudoHeader, buffer, offset);
+#endif
+      //No error to report
+      error = NO_ERROR;
+      //Continue processing
+      break;
+
+#if (IGMP_SUPPORT == ENABLED)
+   //IGMP protocol?
+   case IPV4_PROTOCOL_IGMP:
+      //Process incoming IGMP message
+      igmpProcessMessage(interface, buffer, offset);
+#if (RAW_SOCKET_SUPPORT == ENABLED)
+      //Allow raw sockets to process IGMP messages
+      rawSocketProcessIpPacket(interface, &pseudoHeader, buffer, offset);
+#endif
+      //No error to report
+      error = NO_ERROR;
+      //Continue processing
+      break;
+#endif
+
+#if (TCP_SUPPORT == ENABLED)
+   //TCP protocol?
+   case IPV4_PROTOCOL_TCP:
+      //Process incoming TCP segment
+      tcpProcessSegment(interface, &pseudoHeader, buffer, offset);
+      //No error to report
+      error = NO_ERROR;
+      //Continue processing
+      break;
+#endif
+
+#if (UDP_SUPPORT == ENABLED)
+   //UDP protocol?
+   case IPV4_PROTOCOL_UDP:
+      //Process incoming UDP datagram
+      error = udpProcessDatagram(interface, &pseudoHeader, buffer, offset);
+      //Continue processing
+      break;
+#endif
+
+   //Unknown protocol?
+   default:
+#if (RAW_SOCKET_SUPPORT == ENABLED)
+      //Allow raw sockets to process IPv4 packets
+      error = rawSocketProcessIpPacket(interface, &pseudoHeader, buffer, offset);
+#else
+      //Report an error
+      error = ERROR_PROTOCOL_UNREACHABLE;
+#endif
+      //Continue processing
+      break;
+   }
+
+   //Unreachable protocol?
+   if(error == ERROR_PROTOCOL_UNREACHABLE)
+   {
+      //Number of locally-addressed datagrams received successfully but
+      //discarded because of an unknown or unsupported protocol
+      MIB2_INC_COUNTER32(mib2Base.ipGroup.ipInUnknownProtos, 1);
+
+      //Send a Destination Unreachable message
+      icmpSendErrorMessage(interface, ICMP_TYPE_DEST_UNREACHABLE,
+         ICMP_CODE_PROTOCOL_UNREACHABLE, 0, buffer, 0);
+   }
+   else
+   {
+      //Total number of input datagrams successfully delivered to IP
+      //user-protocols
+      MIB2_INC_COUNTER32(mib2Base.ipGroup.ipInDelivers, 1);
+   }
+
+   //Unreachable port?
+   if(error == ERROR_PORT_UNREACHABLE)
+   {
+      //Send a Destination Unreachable message
+      icmpSendErrorMessage(interface, ICMP_TYPE_DEST_UNREACHABLE,
+         ICMP_CODE_PORT_UNREACHABLE, 0, buffer, 0);
+   }
+}
+
+
+/**
+ * @brief Send an IPv4 datagram
+ * @param[in] interface Underlying network interface
+ * @param[in] pseudoHeader IPv4 pseudo header
+ * @param[in] buffer Multi-part buffer containing the payload
+ * @param[in] offset Offset to the first byte of the payload
+ * @param[in] ttl TTL value. Default Time-To-Live is used when this parameter is zero
+ * @return Error code
+ **/
+
+error_t ipv4SendDatagram(NetInterface *interface, Ipv4PseudoHeader *pseudoHeader,
+   NetBuffer *buffer, size_t offset, uint8_t ttl)
+{
+   error_t error;
+   size_t length;
+   uint16_t id;
+
+   //Total number of IP datagrams which local IP user-protocols supplied
+   //to IP in requests for transmission
+   MIB2_INC_COUNTER32(mib2Base.ipGroup.ipOutRequests, 1);
+
+   //Retrieve the length of payload
+   length = netBufferGetLength(buffer) - offset;
+
+   //Check whether the TTL value is zero
+   if(ttl == 0)
+   {
+      //Use default Time-To-Live value
+      ttl = IPV4_DEFAULT_TTL;
+   }
+
+   //Identification field is primarily used to identify
+   //fragments of an original IP datagram
+   id = interface->ipv4Context.identification++;
+
+   //If the payload length is smaller than the network
+   //interface MTU then no fragmentation is needed
+   if((length + sizeof(Ipv4Header)) <= interface->ipv4Context.linkMtu)
+   {
+      //Send data as is
+      error = ipv4SendPacket(interface,
+         pseudoHeader, id, 0, buffer, offset, ttl);
+   }
+   //If the payload length exceeds the network interface MTU
+   //then the device must fragment the data
+   else
+   {
+#if (IPV4_FRAG_SUPPORT == ENABLED)
+      //Fragment IP datagram into smaller packets
+      error = ipv4FragmentDatagram(interface,
+         pseudoHeader, id, buffer, offset, ttl);
+#else
+      //Fragmentation is not supported
+      error = ERROR_MESSAGE_TOO_LONG;
+#endif
+   }
+
+   //Return status code
+   return error;
+}
+
+
+/**
+ * @brief Send an IPv4 packet
+ * @param[in] interface Underlying network interface
+ * @param[in] pseudoHeader IPv4 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] ttl Time-To-Live value
+ * @return Error code
+ **/
+
+error_t ipv4SendPacket(NetInterface *interface, Ipv4PseudoHeader *pseudoHeader,
+   uint16_t fragId, size_t fragOffset, NetBuffer *buffer, size_t offset, uint8_t ttl)
+{
+   error_t error;
+   size_t length;
+   Ipv4Header *packet;
+
+   //Is there enough space for the IPv4 header?
+   if(offset < sizeof(Ipv4Header))
+      return ERROR_INVALID_PARAMETER;
+
+   //Make room for the header
+   offset -= sizeof(Ipv4Header);
+   //Calculate the size of the entire packet, including header and data
+   length = netBufferGetLength(buffer) - offset;
+
+   //Point to the IPv4 header
+   packet = netBufferAt(buffer, offset);
+
+   //Format IPv4 header
+   packet->version = IPV4_VERSION;
+   packet->headerLength = 5;
+   packet->typeOfService = 0;
+   packet->totalLength = htons(length);
+   packet->identification = htons(fragId);
+   packet->fragmentOffset = htons(fragOffset);
+   packet->timeToLive = ttl;
+   packet->protocol = pseudoHeader->protocol;
+   packet->headerChecksum = 0;
+   packet->srcAddr = pseudoHeader->srcAddr;
+   packet->destAddr = pseudoHeader->destAddr;
+
+   //Calculate IP header checksum
+   packet->headerChecksum = ipCalcChecksumEx(buffer, offset, packet->headerLength * 4);
+
+   //Ensure the source address is valid
+   error = ipv4CheckSourceAddr(interface, pseudoHeader->srcAddr);
+   //Invalid source address?
+   if(error)
+      return error;
+
+   //Destination address is the unspecified address?
+   if(pseudoHeader->destAddr == IPV4_UNSPECIFIED_ADDR)
+   {
+      //Destination address is not acceptable
+      return ERROR_INVALID_ADDRESS;
+   }
+   //Destination address is the loopback address?
+   else if(pseudoHeader->destAddr == IPV4_LOOPBACK_ADDR)
+   {
+      //Not yet implemented...
+      return ERROR_NOT_IMPLEMENTED;
+   }
+
+#if (ETH_SUPPORT == ENABLED)
+   //Ethernet interface?
+   if(interface->nicDriver->type == NIC_TYPE_ETHERNET)
+   {
+      Ipv4Addr destIpAddr;
+      MacAddr destMacAddr;
+
+      //Get the destination IPv4 address
+      destIpAddr = pseudoHeader->destAddr;
+
+      //Destination address is a broadcast address?
+      if(ipv4IsBroadcastAddr(interface, destIpAddr))
+      {
+         //Use of the broadcast MAC address to send the packet
+         destMacAddr = MAC_BROADCAST_ADDR;
+         //No error to report
+         error = NO_ERROR;
+      }
+      //Destination address is a multicast address?
+      else if(ipv4IsMulticastAddr(destIpAddr))
+      {
+         //Map IPv4 multicast address to MAC-layer multicast address
+         error = ipv4MapMulticastAddrToMac(destIpAddr, &destMacAddr);
+      }
+      //Source or destination address is a link-local address?
+      else if(ipv4IsLinkLocalAddr(pseudoHeader->srcAddr) ||
+         ipv4IsLinkLocalAddr(destIpAddr))
+      {
+         //Packets with a link-local source or destination address are not
+         //routable off the link
+         error = arpResolve(interface, destIpAddr, &destMacAddr);
+      }
+      //Destination host is on the local subnet?
+      else if(ipv4IsOnLocalSubnet(interface, destIpAddr))
+      {
+         //Resolve destination address before sending the packet
+         error = arpResolve(interface, destIpAddr, &destMacAddr);
+      }
+      //Destination host is outside the local subnet?
+      else
+      {
+         //Make sure the default gateway is properly set
+         if(interface->ipv4Context.defaultGateway != IPV4_UNSPECIFIED_ADDR)
+         {
+            //Use the default gateway to forward the packet
+            destIpAddr = interface->ipv4Context.defaultGateway;
+            //Perform address resolution
+            error = arpResolve(interface, destIpAddr, &destMacAddr);
+         }
+         else
+         {
+            //Number of IP datagrams discarded because no route could be found
+            //to transmit them to their destination
+            MIB2_INC_COUNTER32(mib2Base.ipGroup.ipOutNoRoutes, 1);
+
+            //Report an error
+            error = ERROR_NO_ROUTE;
+         }
+      }
+
+      //Successful address resolution?
+      if(!error)
+      {
+         //Debug message
+         TRACE_INFO("Sending IPv4 packet (%" PRIuSIZE " bytes)...\r\n", length);
+         //Dump IP header contents for debugging purpose
+         ipv4DumpHeader(packet);
+
+         //Send Ethernet frame
+         error = ethSendFrame(interface, &destMacAddr, buffer, offset, ETH_TYPE_IPV4);
+      }
+      //Address resolution is in progress?
+      else if(error == ERROR_IN_PROGRESS)
+      {
+         //Debug message
+         TRACE_INFO("Enqueuing IPv4 packet (%" PRIuSIZE " bytes)...\r\n", length);
+         //Dump IP header contents for debugging purpose
+         ipv4DumpHeader(packet);
+
+         //Enqueue packets waiting for address resolution
+         error = arpEnqueuePacket(interface, destIpAddr, buffer, offset);
+      }
+      //Address resolution failed?
+      else
+      {
+         //Debug message
+         TRACE_WARNING("Cannot map IPv4 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 IPv4 packet (%" PRIuSIZE " bytes)...\r\n", length);
+      //Dump IP header contents for debugging purpose
+      ipv4DumpHeader(packet);
+
+      //Send PPP frame
+      error = pppSendFrame(interface, buffer, offset, PPP_PROTOCOL_IP);
+   }
+   else
+#endif
+   //Unknown interface type?
+   {
+      //Report an error
+      error = ERROR_INVALID_INTERFACE;
+   }
+
+   //Return status code
+   return error;
+}
+
+
+/**
+ * @brief Source IPv4 address filtering
+ * @param[in] interface Underlying network interface
+ * @param[in] ipAddr Source IPv4 address to be checked
+ * @return Error code
+ **/
+
+error_t ipv4CheckSourceAddr(NetInterface *interface, Ipv4Addr ipAddr)
+{
+   //Broadcast and multicast addresses must not be used as source
+   //address (see RFC 1122 3.2.1.3)
+   if(ipv4IsBroadcastAddr(interface, ipAddr) || ipv4IsMulticastAddr(ipAddr))
+   {
+      //Debug message
+      TRACE_WARNING("Wrong source IPv4 address!\r\n");
+      //The source address not is acceptable
+      return ERROR_INVALID_ADDRESS;
+   }
+
+   //The source address is acceptable
+   return NO_ERROR;
+}
+
+
+/**
+ * @brief Destination IPv4 address filtering
+ * @param[in] interface Underlying network interface
+ * @param[in] ipAddr Destination IPv4 address to be checked
+ * @return Error code
+ **/
+
+error_t ipv4CheckDestAddr(NetInterface *interface, Ipv4Addr ipAddr)
+{
+   error_t error;
+   uint_t i;
+   Ipv4FilterEntry *entry;
+
+   //Filter out any invalid addresses
+   error = ERROR_INVALID_ADDRESS;
+
+   //Broadcast address?
+   if(ipv4IsBroadcastAddr(interface, ipAddr))
+   {
+      //Always accept broadcast address
+      error = NO_ERROR;
+   }
+   //Multicast address?
+   else if(ipv4IsMulticastAddr(ipAddr))
+   {
+      //Go through the multicast filter table
+      for(i = 0; i < IPV4_MULTICAST_FILTER_SIZE; i++)
+      {
+         //Point to the current entry
+         entry = &interface->ipv4Context.multicastFilter[i];
+
+         //Valid entry?
+         if(entry->refCount > 0)
+         {
+            //Check whether the destination IPv4 address matches
+            //a relevant multicast address
+            if(entry->addr == ipAddr)
+            {
+               //The multicast address is acceptable
+               error = NO_ERROR;
+               //Stop immediately
+               break;
+            }
+         }
+      }
+   }
+   //Unicast address?
+   else
+   {
+      //Valid host address?
+      if(interface->ipv4Context.addrState != IPV4_ADDR_STATE_INVALID)
+      {
+         //Check whether the destination address matches the host address
+         if(interface->ipv4Context.addr == ipAddr)
+         {
+            //The destination address is acceptable
+            error = NO_ERROR;
+         }
+      }
+   }
+
+   //Return status code
+   return error;
+}
+
+
+/**
+ * @brief IPv4 source address selection
+ *
+ * This function selects the source address and the relevant network interface
+ * to be used in order to join the specified destination address
+ *
+ * @param[in,out] interface A pointer to a valid network interface may be provided as
+ *   a hint. The function returns a pointer identifying the interface to be used
+ * @param[in] destAddr Destination IPv4 address
+ * @param[out] srcAddr Local IPv4 address to be used
+ * @return Error code
+ **/
+
+error_t ipv4SelectSourceAddr(NetInterface **interface,
+   Ipv4Addr destAddr, Ipv4Addr *srcAddr)
+{
+   uint_t i;
+   NetInterface *currentInterface;
+   NetInterface *bestInterface;
+
+   //Initialize variables
+   bestInterface = NULL;
+
+   //Loop through network interfaces
+   for(i = 0; i < NET_INTERFACE_COUNT; i++)
+   {
+      //Point to the current interface
+      currentInterface = &netInterface[i];
+
+      //A network interface may be provided as a hint...
+      if(*interface != currentInterface && *interface != NULL)
+      {
+         //Select the next interface in the list
+         continue;
+      }
+
+      //Check the state of the address
+      if(currentInterface->ipv4Context.addrState != IPV4_ADDR_STATE_VALID)
+      {
+         //Select the next interface in the list
+         continue;
+      }
+
+      //Select the first interface as default
+      if(bestInterface == NULL)
+      {
+         //Give the current interface the higher precedence
+         bestInterface = currentInterface;
+         //Select the next interface in the list
+         continue;
+      }
+
+      //Prefer same address
+      if(bestInterface->ipv4Context.addr == destAddr)
+      {
+         //Select the next interface in the list
+         continue;
+      }
+      else if(currentInterface->ipv4Context.addr == destAddr)
+      {
+         //Give the current interface the higher precedence
+         bestInterface = currentInterface;
+         //Select the next interface in the list
+         continue;
+      }
+
+      //Prefer appropriate scope
+      if(ipv4GetAddrScope(currentInterface->ipv4Context.addr) <
+         ipv4GetAddrScope(bestInterface->ipv4Context.addr))
+      {
+         if(ipv4GetAddrScope(currentInterface->ipv4Context.addr) >=
+            ipv4GetAddrScope(destAddr))
+         {
+            //Give the current interface the higher precedence
+            bestInterface = currentInterface;
+         }
+
+         //Select the next interface in the list
+         continue;
+      }
+      else if(ipv4GetAddrScope(bestInterface->ipv4Context.addr) <
+         ipv4GetAddrScope(currentInterface->ipv4Context.addr))
+      {
+         if(ipv4GetAddrScope(bestInterface->ipv4Context.addr) <
+            ipv4GetAddrScope(destAddr))
+         {
+            //Give the current interface the higher precedence
+            bestInterface = currentInterface;
+         }
+
+         //Select the next interface in the list
+         continue;
+      }
+
+      //Prefer appropriate subnet mask
+      if(ipv4IsOnLocalSubnet(bestInterface, destAddr))
+      {
+         //Select the next interface in the list
+         continue;
+      }
+      else if(ipv4IsOnLocalSubnet(currentInterface, destAddr))
+      {
+         //Give the current interface the higher precedence
+         bestInterface = currentInterface;
+         //Select the next interface in the list
+         continue;
+      }
+   }
+
+   //Source address selection failed?
+   if(bestInterface == NULL)
+   {
+      //Report an error
+      return ERROR_NO_ADDRESS;
+   }
+
+   //Return the out-going interface and the source address to be used
+   *interface = bestInterface;
+   *srcAddr = bestInterface->ipv4Context.addr;
+
+   //Successful source address selection
+   return NO_ERROR;
+}
+
+
+/**
+ * @brief Check whether an IPv4 address is a broadcast address
+ * @param[in] interface Underlying network interface
+ * @param[in] ipAddr IPv4 address to be checked
+ * @return TRUE if the IPv4 address is a broadcast address, else FALSE
+ **/
+
+bool_t ipv4IsBroadcastAddr(NetInterface *interface, Ipv4Addr ipAddr)
+{
+   //Check whether the specified IPv4 address is the broadcast address
+   if(ipAddr == IPV4_BROADCAST_ADDR)
+      return TRUE;
+
+   //Check whether the specified IPv4 address belongs to the local network
+   if(ipv4IsOnLocalSubnet(interface, ipAddr))
+   {
+      //Make sure the subnet mask is not 255.255.255.255
+      if(interface->ipv4Context.subnetMask != IPV4_BROADCAST_ADDR)
+      {
+         //Directed broadcast address?
+         if((ipAddr | interface->ipv4Context.subnetMask) == IPV4_BROADCAST_ADDR)
+            return TRUE;
+      }
+   }
+
+   //The specified IPv4 address is not a broadcast address
+   return FALSE;
+}
+
+
+/**
+ * @brief Retrieve the scope of an IPv4 address
+ * @param[in] ipAddr IPv4 address
+ * @return IPv4 address scope
+ **/
+
+uint_t ipv4GetAddrScope(Ipv4Addr ipAddr)
+{
+   uint_t scope;
+
+   //Broadcast address?
+   if(ipAddr == IPV4_BROADCAST_ADDR)
+   {
+      //The broadcast address is never forwarded by the routers connecting
+      //the local network to other networks
+      scope = IPV4_ADDR_SCOPE_LINK_LOCAL;
+   }
+   //Multicast address?
+   else if(ipv4IsMulticastAddr(ipAddr))
+   {
+      //Local Network Control Block?
+      if((ipAddr & IPV4_MULTICAST_LNCB_MASK) == IPV4_MULTICAST_LNCB_PREFIX)
+      {
+         //Addresses in the Local Network Control Block are used for protocol
+         //control traffic that is not forwarded off link
+         scope = IPV4_ADDR_SCOPE_LINK_LOCAL;
+      }
+      //Any other multicast address?
+      else
+      {
+         //Other addresses are assigned global scope
+         scope = IPV4_ADDR_SCOPE_GLOBAL;
+      }
+   }
+   //Unicast address?
+   else
+   {
+      //Loopback address?
+      if((ipAddr & IPV4_LOOPBACK_ADDR_MASK) == IPV4_LOOPBACK_ADDR_PREFIX)
+      {
+         //IPv4 loopback addresses, which have the prefix 127.0.0.0/8,
+         //are assigned interface-local scope
+         scope = IPV4_ADDR_SCOPE_INTERFACE_LOCAL;
+      }
+      //Link-local address?
+      else if((ipAddr & IPV4_LINK_LOCAL_MASK) == IPV4_LINK_LOCAL_PREFIX)
+      {
+         //IPv4 auto-configuration addresses, which have the prefix
+         //169.254.0.0/16, are assigned link-local scope
+         scope = IPV4_ADDR_SCOPE_LINK_LOCAL;
+      }
+      //Any other unicast address?
+      else
+      {
+         //Other addresses are assigned global scope
+         scope = IPV4_ADDR_SCOPE_GLOBAL;
+      }
+   }
+
+   //Return the scope of the specified IPv4 address
+   return scope;
+}
+
+
+/**
+ * @brief Join the specified host group
+ * @param[in] interface Underlying network interface
+ * @param[in] groupAddr IPv4 address identifying the host group to join
+ * @return Error code
+ **/
+
+error_t ipv4JoinMulticastGroup(NetInterface *interface, Ipv4Addr groupAddr)
+{
+   error_t error;
+   uint_t i;
+   Ipv4FilterEntry *entry;
+   Ipv4FilterEntry *firstFreeEntry;
+#if (ETH_SUPPORT == ENABLED)
+   MacAddr macAddr;
+#endif
+
+   //The IPv4 address must be a valid multicast address
+   if(!ipv4IsMulticastAddr(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 < IPV4_MULTICAST_FILTER_SIZE; i++)
+   {
+      //Point to the current entry
+      entry = &interface->ipv4Context.multicastFilter[i];
+
+      //Valid entry?
+      if(entry->refCount > 0)
+      {
+         //Check whether the table already contains the specified IPv4 address
+         if(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 IPv4 multicast address to a MAC-layer address
+   ipv4MapMulticastAddrToMac(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 (IGMP_SUPPORT == ENABLED)
+      //Report multicast group membership to the router
+      igmpJoinGroup(interface, firstFreeEntry);
+#endif
+   }
+
+   //Return status code
+   return error;
+}
+
+
+/**
+ * @brief Leave the specified host group
+ * @param[in] interface Underlying network interface
+ * @param[in] groupAddr IPv4 address identifying the host group to leave
+ * @return Error code
+ **/
+
+error_t ipv4LeaveMulticastGroup(NetInterface *interface, Ipv4Addr groupAddr)
+{
+   uint_t i;
+   Ipv4FilterEntry *entry;
+#if (ETH_SUPPORT == ENABLED)
+   MacAddr macAddr;
+#endif
+
+   //The IPv4 address must be a valid multicast address
+   if(!ipv4IsMulticastAddr(groupAddr))
+      return ERROR_INVALID_ADDRESS;
+
+   //Go through the multicast filter table
+   for(i = 0; i < IPV4_MULTICAST_FILTER_SIZE; i++)
+   {
+      //Point to the current entry
+      entry = &interface->ipv4Context.multicastFilter[i];
+
+      //Valid entry?
+      if(entry->refCount > 0)
+      {
+         //Specified IPv4 address found?
+         if(entry->addr == groupAddr)
+         {
+            //Decrement the reference count
+            entry->refCount--;
+
+            //Remove the entry if the reference count drops to zero
+            if(entry->refCount == 0)
+            {
+#if (IGMP_SUPPORT == ENABLED)
+               //Report group membership termination
+               igmpLeaveGroup(interface, entry);
+#endif
+#if (ETH_SUPPORT == ENABLED)
+               //Map the IPv4 multicast address to a MAC-layer address
+               ipv4MapMulticastAddrToMac(groupAddr, &macAddr);
+               //Drop the corresponding address from the MAC filter table
+               ethDropMulticastAddr(interface, &macAddr);
+#endif
+               //Remove the multicast address from the list
+               entry->addr = IPV4_UNSPECIFIED_ADDR;
+            }
+
+            //Successful processing
+            return NO_ERROR;
+         }
+      }
+   }
+
+   //The specified IPv4 address does not exist
+   return ERROR_ADDRESS_NOT_FOUND;
+}
+
+
+/**
+ * @brief Map an host group address to a MAC-layer multicast address
+ * @param[in] ipAddr IPv4 host group address
+ * @param[out] macAddr Corresponding MAC-layer multicast address
+ * @return Error code
+ **/
+
+error_t ipv4MapMulticastAddrToMac(Ipv4Addr ipAddr, MacAddr *macAddr)
+{
+   uint8_t *p;
+
+   //Ensure the specified IPv4 address is a valid host group address
+   if(!ipv4IsMulticastAddr(ipAddr))
+      return ERROR_INVALID_ADDRESS;
+
+   //Cast the address to byte array
+   p = (uint8_t *) &ipAddr;
+
+   //An IP host group address is mapped to an Ethernet multicast address
+   //by placing the low-order 23-bits of the IP address into the low-order
+   //23 bits of the Ethernet multicast address 01-00-5E-00-00-00
+   macAddr->b[0] = 0x01;
+   macAddr->b[1] = 0x00;
+   macAddr->b[2] = 0x5E;
+   macAddr->b[3] = p[1] & 0x7F;
+   macAddr->b[4] = p[2];
+   macAddr->b[5] = p[3];
+
+   //The specified host group address was successfully
+   //mapped to a MAC-layer address
+   return NO_ERROR;
+}
+
+
+/**
+ * @brief Convert a dot-decimal string to a binary IPv4 address
+ * @param[in] str NULL-terminated string representing the IPv4 address
+ * @param[out] ipAddr Binary representation of the IPv4 address
+ * @return Error code
+ **/
+
+error_t ipv4StringToAddr(const char_t *str, Ipv4Addr *ipAddr)
+{
+   error_t error;
+   int_t i = 0;
+   int_t value = -1;
+
+   //Parse input string
+   while(1)
+   {
+      //Decimal digit found?
+      if(isdigit((uint8_t) *str))
+      {
+         //First digit to be decoded?
+         if(value < 0) value = 0;
+         //Update the value of the current byte
+         value = (value * 10) + (*str - '0');
+
+         //The resulting value shall be in range 0 to 255
+         if(value > 255)
+         {
+            //The conversion failed
+            error = ERROR_INVALID_SYNTAX;
+            break;
+         }
+      }
+      //Dot separator found?
+      else if(*str == '.' && i < 4)
+      {
+         //Each dot must be preceded by a valid number
+         if(value < 0)
+         {
+            //The conversion failed
+            error = ERROR_INVALID_SYNTAX;
+            break;
+         }
+
+         //Save the current byte
+         ((uint8_t *) ipAddr)[i++] = value;
+         //Prepare to decode the next byte
+         value = -1;
+      }
+      //End of string detected?
+      else if(*str == '\0' && i == 3)
+      {
+         //The NULL character must be preceded by a valid number
+         if(value < 0)
+         {
+            //The conversion failed
+            error = ERROR_INVALID_SYNTAX;
+         }
+         else
+         {
+            //Save the last byte of the IPv4 address
+            ((uint8_t *) ipAddr)[i] = value;
+            //The conversion succeeded
+            error = NO_ERROR;
+         }
+
+         //We are done
+         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 IPv4 address to dot-decimal notation
+ * @param[in] ipAddr Binary representation of the IPv4 address
+ * @param[out] str NULL-terminated string representing the IPv4 address
+ * @return Pointer to the formatted string
+ **/
+
+char_t *ipv4AddrToString(Ipv4Addr ipAddr, char_t *str)
+{
+   uint8_t *p;
+   static char_t buffer[16];
+
+   //If the NULL pointer is given as parameter, then the internal buffer is used
+   if(str == NULL)
+      str = buffer;
+
+   //Cast the address to byte array
+   p = (uint8_t *) &ipAddr;
+   //Format IPv4 address
+   sprintf(str, "%" PRIu8 ".%" PRIu8 ".%" PRIu8 ".%" PRIu8 "", p[0], p[1], p[2], p[3]);
+
+   //Return a pointer to the formatted string
+   return str;
+}
+
+
+/**
+ * @brief Dump IPv4 header for debugging purpose
+ * @param[in] ipHeader Pointer to the IPv4 header
+ **/
+
+void ipv4DumpHeader(const Ipv4Header *ipHeader)
+{
+   //Dump IP header contents
+   TRACE_DEBUG("  Version = %" PRIu8 "\r\n", ipHeader->version);
+   TRACE_DEBUG("  Header Length = %" PRIu8 "\r\n", ipHeader->headerLength);
+   TRACE_DEBUG("  Type Of Service = %" PRIu8 "\r\n", ipHeader->typeOfService);
+   TRACE_DEBUG("  Total Length = %" PRIu16 "\r\n", ntohs(ipHeader->totalLength));
+   TRACE_DEBUG("  Identification = %" PRIu16 "\r\n", ntohs(ipHeader->identification));
+   TRACE_DEBUG("  Flags = 0x%01X\r\n", ntohs(ipHeader->fragmentOffset) >> 13);
+   TRACE_DEBUG("  Fragment Offset = %" PRIu16 "\r\n", ntohs(ipHeader->fragmentOffset) & 0x1FFF);
+   TRACE_DEBUG("  Time To Live = %" PRIu8 "\r\n", ipHeader->timeToLive);
+   TRACE_DEBUG("  Protocol = %" PRIu8 "\r\n", ipHeader->protocol);
+   TRACE_DEBUG("  Header Checksum = 0x%04" PRIX16 "\r\n", ntohs(ipHeader->headerChecksum));
+   TRACE_DEBUG("  Src Addr = %s\r\n", ipv4AddrToString(ipHeader->srcAddr, NULL));
+   TRACE_DEBUG("  Dest Addr = %s\r\n", ipv4AddrToString(ipHeader->destAddr, NULL));
+}
+
+#endif
+