/**************************************************************************
*
* Copyright 2008-2018 by Andrey Butok. FNET Community
*
***************************************************************************
*
*  Licensed under the Apache License, Version 2.0 (the "License"); you may
*  not use this file except in compliance with the License.
*  You may obtain a copy of the License at
*
*  http://www.apache.org/licenses/LICENSE-2.0
*
*  Unless required by applicable law or agreed to in writing, software
*  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
*  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*  See the License for the specific language governing permissions and
*  limitations under the License.
*
***************************************************************************
*
*  IP protocol implementation.
*
***************************************************************************/

#include "fnet.h"
#include "fnet_ip4_prv.h"
#include "fnet_icmp4.h"
#include "fnet_checksum_prv.h"
#include "fnet_timer_prv.h"
#include "fnet_loop.h"
#include "fnet_raw.h"
#include "fnet_stack_prv.h"
#include "fnet_ip_prv.h"
#include "fnet_igmp.h"

/* Check max/min. values.*/
#if (FNET_IP4_MAX_PACKET > 65535U)
    #error  "FNET_IP4_MAX_PACKET may not be more than 65535."
#endif

#if (FNET_IP4_MAX_PACKET < 200U)
    #error  "FNET_IP4_MAX_PACKET must be more than 200."
#endif

#if FNET_CFG_IP4

#if FNET_CFG_IP4_FRAGMENTATION
    static fnet_ip4_frag_list_t *ip_frag_list_head;
    static fnet_timer_desc_t ip_timer_ptr;
#endif

static fnet_ip_queue_t   ip_queue;
static fnet_event_desc_t ip_event;

#if FNET_CFG_MULTICAST
    fnet_ip4_multicast_list_entry_t fnet_ip4_multicast_list[FNET_CFG_MULTICAST_MAX];
#endif /* FNET_CFG_MULTICAST */

/************************************************************************
*     Function Prototypes
*************************************************************************/
static void _fnet_ip4_netif_output(struct fnet_netif *netif, fnet_ip4_addr_t dest_ip_addr, fnet_netbuf_t *nb, fnet_bool_t do_not_route);
static void _fnet_ip4_input_low(void *cookie);
static fnet_bool_t _fnet_ip4_addr_is_onlink(fnet_netif_t *netif, fnet_ip4_addr_t addr);

#if FNET_CFG_IP4_FRAGMENTATION
    static void _fnet_ip4_reassembly( fnet_netbuf_t **nb_ptr );
    static void _fnet_ip4_frag_list_add( fnet_ip4_frag_list_t **head, fnet_ip4_frag_list_t *fl );
    static void _fnet_ip4_frag_list_del( fnet_ip4_frag_list_t **head, fnet_ip4_frag_list_t *fl );
    static void _fnet_ip4_frag_add( fnet_ip4_frag_header_t *FNET_COMP_PACKED_VAR *head, fnet_ip4_frag_header_t *frag, fnet_ip4_frag_header_t *frag_prev );
    static void _fnet_ip4_frag_del( fnet_ip4_frag_header_t *FNET_COMP_PACKED_VAR *head, fnet_ip4_frag_header_t *frag );
    static void _fnet_ip4_frag_list_free( fnet_ip4_frag_list_t *list );
    static void _fnet_ip4_timer(fnet_uint32_t cookie );
#endif

#if FNET_CFG_DEBUG_TRACE_IP4 && FNET_CFG_DEBUG_TRACE
    static void _fnet_ip4_trace(fnet_uint8_t *str, fnet_ip4_header_t *ip_hdr);
#else
    #define _fnet_ip4_trace(str, ip_hdr)  do{}while(0)
#endif

/************************************************************************
* DESCRIPTION: This function makes initialization of the IP layer.
*************************************************************************/
fnet_return_t _fnet_ip4_init( void )
{
    fnet_return_t result = FNET_ERR;

#if FNET_CFG_IP4_FRAGMENTATION

    ip_frag_list_head = 0;
    ip_timer_ptr = _fnet_timer_new(FNET_IP4_TIMER_PERIOD, _fnet_ip4_timer, 0u);

    if(ip_timer_ptr)
    {
#endif

#if FNET_CFG_MULTICAST
        /* Clear the multicast list.*/
        fnet_memset_zero(fnet_ip4_multicast_list, sizeof(fnet_ip4_multicast_list));
#endif /* FNET_CFG_MULTICAST */

        /* Install SW Interrupt handler. */
        ip_event = fnet_event_init(_fnet_ip4_input_low, 0u);
        if(ip_event)
        {
            result = FNET_OK;
        }

#if FNET_CFG_IP4_FRAGMENTATION
    }
#endif

    return result;
}

/************************************************************************
* DESCRIPTION: This function makes release of the all resources
*              allocated for IP layer module.
*************************************************************************/
void _fnet_ip4_release( void )
{
    _fnet_ip4_drain();
#if FNET_CFG_IP4_FRAGMENTATION

    _fnet_timer_free(ip_timer_ptr);
    ip_timer_ptr = 0;

#endif
}

/************************************************************************
* DESCRIPTION: This function performs IP routing
*              on an outgoing IP packet.
*************************************************************************/
fnet_netif_t *_fnet_ip4_route( fnet_ip4_addr_t dest_ip )
{
    fnet_netif_t    *netif;
    fnet_netif_t    *res_netif = FNET_NULL;
    fnet_netif_t    *netif_default = _fnet_netif_get_default();

    /* Local network */
    for (netif = fnet_netif_list; netif != 0; netif = netif->next)
    {
        if(netif->ip4_addr.subnetmask && ((dest_ip & netif->ip4_addr.subnetmask) == (netif->ip4_addr.address & netif->ip4_addr.subnetmask)))
        {
            res_netif = netif;
            break;
        }
    }

    /* Public network. Get interface wit a gateway. */
    if(res_netif == FNET_NULL)
    {
        /* Default interface has priority. */
        if(netif_default && netif_default->ip4_addr.gateway)
        {
            res_netif = netif_default;
        }
        else
        {
            for (netif = fnet_netif_list; netif != 0; netif = netif->next)
            {
                /* If there is a gateway */
                if(netif->ip4_addr.gateway)
                {
                    res_netif = netif;
                    break;
                }
            }
        }
    }

    /* If there is no proper interface - choose the default one */
    if(res_netif == FNET_NULL)
    {
        res_netif = netif_default;
    }

#if FNET_CFG_LOOPBACK
    /* Anything sent to one of the host's own IP address is sent to the loopback interface.*/
    if(res_netif && (dest_ip == res_netif->ip4_addr.address))
    {
        res_netif = FNET_LOOP_IF;
    }
#endif /* FNET_CFG_LOOPBACK */

    return res_netif;
}

/************************************************************************
* DESCRIPTION: This function returns FNET_TRUE if the protocol message
*              will be fragmented by IPv4, and FNET_FALSE otherwise.
*************************************************************************/
fnet_bool_t _fnet_ip4_will_fragment( fnet_netif_t *netif, fnet_size_t protocol_message_size)
{
    fnet_bool_t res;

    if((protocol_message_size + sizeof(fnet_ip4_header_t)) > netif->netif_mtu)
    {
        res = FNET_TRUE;
    }
    else
    {
        res = FNET_FALSE;
    }

    return res;
}

/************************************************************************
* DESCRIPTION: IP output function.
*
* RETURNS: FNET_OK=OK
*          FNET_ERR_NETUNREACH=No route
*          FNET_ERR_MSGSIZE=Size error
*          FNET_ERR_NOMEM=No memory
*************************************************************************/
fnet_error_t _fnet_ip4_output( fnet_netif_t *netif,    fnet_ip4_addr_t src_ip, fnet_ip4_addr_t dest_ip,
                               fnet_uint8_t protocol, fnet_uint8_t tos,     fnet_uint8_t ttl,
                               fnet_netbuf_t *nb, fnet_bool_t DF, fnet_bool_t do_not_route,
                               FNET_COMP_PACKED_VAR fnet_uint16_t *checksum )
{
    static fnet_uint16_t    ip_id = 0u;
    fnet_netbuf_t           *nb_header;
    fnet_ip4_header_t       *ipheader;
    fnet_size_t             total_length;
    fnet_error_t            error_code;

    if(netif == 0)
    {
        if((netif = _fnet_ip4_route(dest_ip)) == 0) /* No route */
        {
            error_code = FNET_ERR_NETUNREACH;
            goto DROP;
        }
    }

    /* If source address not specified, use address of outgoing interface */
    if(src_ip == INADDR_ANY)
    {
        src_ip = netif->ip4_addr.address;
    }

    if((nb->total_length + sizeof(fnet_ip4_header_t)) > FNET_IP4_MAX_PACKET)
    {
        error_code = FNET_ERR_MSGSIZE;
        goto DROP;
    }

    /* Construct IP header */
    if((nb_header = _fnet_netbuf_new(sizeof(fnet_ip4_header_t), FNET_TRUE)) == 0)
    {
        error_code = FNET_ERR_NOMEM;
        goto DROP;
    }

    /* Pseudo checksum. */
    if(checksum)
    {
        *checksum = _fnet_checksum_pseudo_netbuf_end( *checksum, (fnet_uint8_t *)&src_ip, (fnet_uint8_t *)&dest_ip, sizeof(fnet_ip4_addr_t) );
    }

    ipheader = (fnet_ip4_header_t *)nb_header->data_ptr;

    FNET_IP_HEADER_SET_VERSION(ipheader, (fnet_uint8_t)FNET_IP4_VERSION); /* version =4 */
    ipheader->id = fnet_htons(ip_id++);              /* Id */

    ipheader->tos = tos;                 /* Type of service */
    total_length = (fnet_uint16_t)(nb->total_length + sizeof(fnet_ip4_header_t)); /* total length*/
    FNET_IP_HEADER_SET_HEADER_LENGTH(ipheader, sizeof(fnet_ip4_header_t) >> 2);
    ipheader->flags_fragment_offset = 0x0000u; /* flags & fragment offset field */

    if(DF)
    {
        ipheader->flags_fragment_offset |= FNET_HTONS(FNET_IP4_DF);
    }

    ipheader->ttl = ttl;                 /* time to live */
    ipheader->protocol = protocol;       /* protocol */
    ipheader->source_addr = src_ip;      /* source address */
    ipheader->desination_addr = dest_ip; /* destination address */

    ipheader->total_length = fnet_htons((fnet_uint16_t)total_length);

    nb = _fnet_netbuf_concat(nb_header, nb);

    if(total_length > netif->netif_mtu) /* IP Fragmentation. */
    {
#if FNET_CFG_IP4_FRAGMENTATION

        fnet_size_t         first_frag_length;
        fnet_size_t         frag_length;            /* The number of data in each fragment. */
        fnet_size_t         offset;
        fnet_index_t        error = 0u;
        fnet_netbuf_t       *nb_prev;
        fnet_netbuf_t       **nb_next_ptr = &nb->next_chain;
        fnet_size_t         new_header_length;
        fnet_size_t         header_length = (fnet_size_t)(FNET_IP_HEADER_GET_HEADER_LENGTH(ipheader) << 2);
        fnet_ip4_header_t   *new_ipheader;

        frag_length = (netif->netif_mtu - header_length) & ~7u; /* rounded down to an 8-byte boundary.*/
        first_frag_length = frag_length;

        if(((ipheader->flags_fragment_offset & FNET_HTONS(FNET_IP4_DF)) != 0u) ||   /* The fragmentation is prohibited. */
           (frag_length < 8u))                                    /* The MTU is too small.*/
        {
            error_code = FNET_ERR_MSGSIZE;
            goto DROP;
        }

        /* The header (and options) must reside in contiguous area of memory.*/
        if(_fnet_netbuf_pullup(&nb, header_length) == FNET_ERR)
        {
            error_code = FNET_ERR_NOMEM;
            goto DROP;
        }

        ipheader = (fnet_ip4_header_t *)nb->data_ptr;

        nb_prev = nb;

        /* Go through the whole data segment after first fragment.*/
        for (offset = (header_length + frag_length); offset < total_length; offset += frag_length)
        {
            fnet_netbuf_t *nb_tmp;

            nb = _fnet_netbuf_new(header_length, FNET_FALSE); /* Allocate a new header.*/

            if(nb == 0)
            {
                error++;
                goto FRAG_END;
            }

            fnet_memcpy(nb->data_ptr, ipheader, header_length); /* Copy IP header.*/
            new_ipheader = (fnet_ip4_header_t *)nb->data_ptr;
            new_header_length = sizeof(fnet_ip4_header_t);

            FNET_IP_HEADER_SET_HEADER_LENGTH(new_ipheader, (fnet_uint8_t)(new_header_length >> 2));
            new_ipheader->flags_fragment_offset = fnet_htons((fnet_uint16_t)((offset - header_length) >> 3));

            if(offset + frag_length >= total_length)
            {
                frag_length = (total_length - offset);
            }
            else
            {
                new_ipheader->flags_fragment_offset |= FNET_HTONS(FNET_IP4_MF);
            }

            /* Copy the data from the original packet into the fragment.*/
            if((nb_tmp = _fnet_netbuf_copy(nb_prev, offset, frag_length, FNET_FALSE)) == 0)
            {
                error++;
                _fnet_netbuf_free_chain(nb);
                goto FRAG_END;
            }

            nb = _fnet_netbuf_concat(nb, nb_tmp);

            new_ipheader->total_length = fnet_htons((fnet_uint16_t)nb->total_length);

            *nb_next_ptr = nb;
            nb_next_ptr = &nb->next_chain;
        }

        /* Update the first fragment.*/
        nb = nb_prev;
        _fnet_netbuf_trim(&nb, (fnet_int32_t)(header_length + first_frag_length - fnet_ntohs(ipheader->total_length)));
        ipheader->total_length = fnet_htons((fnet_uint16_t)nb->total_length);
        ipheader->flags_fragment_offset |= FNET_HTONS(FNET_IP4_MF);

    FRAG_END:
        for (nb = nb_prev; nb; nb = nb_prev)    /* Send each fragment.*/
        {
            nb_prev = nb->next_chain;
            nb->next_chain = 0;

            if(error == 0u)
            {
                _fnet_ip4_trace("TX", nb->data_ptr); /* Print IP header. */
                _fnet_ip4_netif_output(netif, dest_ip, nb, do_not_route);
            }
            else
            {
                _fnet_netbuf_free_chain(nb);
            }
        }

#else

        error_code = FNET_ERR_MSGSIZE;   /* Discard datagram.*/
        goto DROP;

#endif  /* FNET_CFG_IP4_FRAGMENTATION */

    }
    else
    {
        _fnet_ip4_netif_output(netif, dest_ip, nb, do_not_route);
    }

    return (FNET_ERR_OK);

DROP:
    _fnet_netbuf_free_chain(nb);           /* Discard datagram */

    return (error_code);
}

/************************************************************************
* DESCRIPTION:
*************************************************************************/
static void _fnet_ip4_netif_output(struct fnet_netif *netif, fnet_ip4_addr_t dest_ip_addr, fnet_netbuf_t *nb, fnet_bool_t do_not_route)
{
    fnet_ip4_header_t   *ipheader = (fnet_ip4_header_t *)nb->data_ptr;

    /* IPv4 Header Checksum*/
    ipheader->checksum = 0u;

#if FNET_CFG_CPU_ETH_HW_TX_IP_CHECKSUM
    if(netif->features & FNET_NETIF_FEATURE_HW_TX_IP_CHECKSUM)
        nb->flags |= FNET_NETBUF_FLAG_HW_IP_CHECKSUM;
    else
#endif
        ipheader->checksum = _fnet_checksum_netbuf(nb, (fnet_size_t)FNET_IP_HEADER_GET_HEADER_LENGTH(ipheader) << 2); /* IP checksum*/

    if( /* Datagrams sent to a broadcast address */
        (_fnet_ip4_addr_is_broadcast(dest_ip_addr, netif))
        /* Datagrams sent to a multicast address. */
        || FNET_IP4_ADDR_IS_MULTICAST(dest_ip_addr) )
    {
#if  FNET_CFG_LOOPBACK && (FNET_CFG_LOOPBACK_MULTICAST || FNET_CFG_LOOPBACK_BROADCAST)
        fnet_netbuf_t *nb_loop;

        if(netif != FNET_LOOP_IF) /* Avoid double send to the loopback interface.*/
        {
            /* Datagrams sent to a broadcast/multicast address are copied to the loopback interface.*/
            if((nb_loop = _fnet_netbuf_copy(nb, 0, FNET_NETBUF_COPYALL, FNET_TRUE)) != 0)
            {
                _fnet_loop_output_ip4(netif, dest_ip_addr, nb_loop);
            }
        }
#endif /* FNET_CFG_LOOPBACK && (FNET_CFG_LOOPBACK_MULTICAST || FNET_CFG_LOOPBACK_BROADCAST) */
    }
    else
    {
        if(!((do_not_route) || (_fnet_ip4_addr_is_onlink(netif, dest_ip_addr) == FNET_TRUE)))
        {
            /* Use the default router as the address to send.*/
            dest_ip_addr = netif->ip4_addr.gateway;
        }
    }

    /* Send to Interface.*/
    netif->netif_api->netif_output_ip4(netif, dest_ip_addr, nb);
}

/************************************************************************
* DESCRIPTION: Checks if the address is on-link.
*              Returns FNET_TRUE if it is on-link, FNET_FALSE otherwise.
*************************************************************************/
static fnet_bool_t _fnet_ip4_addr_is_onlink(fnet_netif_t *netif, fnet_ip4_addr_t addr)
{
    fnet_bool_t on_link;
    if(((addr & netif->ip4_addr.subnetmask) == (netif->ip4_addr.address & netif->ip4_addr.subnetmask))
       /* RFC3927: If the destination address is in the 169.254/16 prefix, then the sender
           MUST send its packet directly to the destination on the same physical link.  This MUST be
           done whether the interface is configured with a Link-Local or a routable IPv4 address.    */
       || ((addr & FNET_IP4_ADDR_LINK_LOCAL_PREFIX) == (FNET_IP4_ADDR_LINK_LOCAL_PREFIX)))

    {
        on_link = FNET_TRUE;
    }
    else
    {
        on_link = FNET_FALSE;
    }
    return on_link;
}

/************************************************************************
* DESCRIPTION: Prepare sockets addreses for upper protocol.
*************************************************************************/
void _fnet_ip4_set_socket_addr(fnet_netif_t *netif, fnet_ip4_header_t *ip_hdr, struct fnet_sockaddr *src_addr,  struct fnet_sockaddr *dest_addr )
{
    fnet_memset_zero(src_addr, sizeof(struct fnet_sockaddr));
    src_addr->sa_family = AF_INET;
    src_addr->sa_scope_id = netif->scope_id;
    ((struct fnet_sockaddr_in *)(src_addr))->sin_addr.s_addr = ip_hdr->source_addr;

    fnet_memset_zero(dest_addr, sizeof(struct fnet_sockaddr));
    dest_addr->sa_family = AF_INET;
    dest_addr->sa_scope_id = netif->scope_id;
    ((struct fnet_sockaddr_in *)(dest_addr))->sin_addr.s_addr = ip_hdr->desination_addr;
}

/************************************************************************
* DESCRIPTION: IP input function.
*************************************************************************/
void _fnet_ip4_input( fnet_netif_t *netif, fnet_netbuf_t *nb )
{
    if(netif && nb)
    {

        if(_fnet_ip_queue_append(&ip_queue, netif, nb) != FNET_OK)
        {
            _fnet_netbuf_free_chain(nb);
            return;
        }

        /* Initiate S/W Interrupt*/
        fnet_event_raise(ip_event);
    }
}

/************************************************************************
* DESCRIPTION: This function performs handling of incoming datagrams.
*************************************************************************/
static void _fnet_ip4_input_low(void *cookie)
{
    fnet_ip4_header_t       *hdr;
    fnet_netbuf_t           *ip4_nb;
    fnet_prot_if_t          *protocol;
    fnet_netif_t            *netif;
    fnet_netbuf_t           *nb;
    fnet_ip4_addr_t         destination_addr;
    fnet_size_t             total_length;
    fnet_size_t             header_length;
    struct fnet_sockaddr    src_addr;
    struct fnet_sockaddr    dest_addr;

    FNET_COMP_UNUSED_ARG(cookie);

    fnet_isr_lock();

    while((nb = _fnet_ip_queue_read(&ip_queue, &netif)) != 0)
    {
        nb->next_chain = 0;

        /* The header must reside in contiguous area of memory. */
        if(_fnet_netbuf_pullup(&nb, sizeof(fnet_ip4_header_t)) == FNET_ERR)
        {
            _fnet_netbuf_free_chain(nb);
            continue;
        }

        hdr = (fnet_ip4_header_t *)nb->data_ptr;
        destination_addr = hdr->desination_addr;
        total_length = fnet_ntohs(hdr->total_length);
        header_length = (fnet_size_t)FNET_IP_HEADER_GET_HEADER_LENGTH(hdr) << 2;

        _fnet_ip4_trace("RX", hdr); /* Print IP header. */

        if((nb->total_length >= total_length)                            /* Check the amount of data*/
           && (nb->total_length >= sizeof(fnet_ip4_header_t))
           && (FNET_IP_HEADER_GET_VERSION(hdr) == 4u)                   /* Check the IP Version*/
           && (header_length >= sizeof(fnet_ip4_header_t))               /* Check the IP header length*/
           && (total_length >= header_length)
#if FNET_CFG_CPU_ETH_HW_TX_IP_CHECKSUM || FNET_CFG_CPU_ETH_HW_RX_IP_CHECKSUM
           && ((netif->features & FNET_NETIF_FEATURE_HW_RX_IP_CHECKSUM)
               || (nb->flags | FNET_NETBUF_FLAG_HW_IP_CHECKSUM)
               || (_fnet_checksum_netbuf(nb, header_length) == 0u) )       /* Checksum*/
#else
           && (_fnet_checksum_netbuf(nb, header_length) == 0u)             /* Checksum*/
#endif
           && ((destination_addr == netif->ip4_addr.address)           /* It is final destination*/
               || (_fnet_ip4_addr_is_broadcast(destination_addr, netif))
               || ((netif->ip4_addr.address == INADDR_ANY) && ((nb->flags & FNET_NETBUF_FLAG_BROADCAST) == FNET_NETBUF_FLAG_BROADCAST)) /* Check frame broadcast flag, in case no address signed yet (e.g. DHCP).*/
#if FNET_CFG_MULTICAST
               || (FNET_IP4_ADDR_IS_MULTICAST(destination_addr))
#endif
               || (netif->netif_api->netif_type == FNET_NETIF_TYPE_LOOPBACK)
              )
          )
        {
            if(nb->total_length > total_length)
            {
                /* Logical size and the physical size of the packet should be the same.*/
                _fnet_netbuf_trim(&nb, (fnet_int32_t)(total_length - nb->total_length));
            }

            /* Reassembly.*/
            if((hdr->flags_fragment_offset & ~FNET_HTONS(FNET_IP4_DF)) != 0u) /* the MF bit or fragment offset is nonzero.*/
            {
#if FNET_CFG_IP4_FRAGMENTATION
                _fnet_ip4_reassembly(&nb);
                if(nb == FNET_NULL)
                {
                    continue;
                }

                hdr = (fnet_ip4_header_t *)nb->data_ptr;
                header_length = (fnet_size_t)FNET_IP_HEADER_GET_HEADER_LENGTH(hdr) << 2;
#else
                _fnet_netbuf_free_chain(nb);
                continue;
#endif
            }
#if FNET_CFG_CPU_ETH_HW_RX_PROTOCOL_CHECKSUM
            else
            {
                if((netif->features & FNET_NETIF_FEATURE_HW_RX_PROTOCOL_CHECKSUM) &&
                   ((hdr->protocol == FNET_PROT_ICMP4) ||
                    (hdr->protocol == FNET_PROT_UDP) ||
                    (hdr->protocol == FNET_PROT_TCP)) )
                {
                    nb->flags |= FNET_NETBUF_FLAG_HW_PROTOCOL_CHECKSUM;
                }
            }
#endif
            if(nb->total_length > FNET_IP4_MAX_PACKET)
            {
                _fnet_netbuf_free_chain(nb); /* Discard datagram */
                continue;
            }

            ip4_nb = _fnet_netbuf_copy(nb, 0u, (header_length + 8u), FNET_FALSE);

            _fnet_netbuf_trim(&nb, (fnet_int32_t)header_length);

            /**************************************
             *  Send to upper layers.
             **************************************/

            /* Prepare addreses for upper protocol.*/
            _fnet_ip4_set_socket_addr(netif, hdr, &src_addr,  &dest_addr );

#if FNET_CFG_RAW
            /* RAW Sockets inpput.*/
            _fnet_raw_input(netif, &src_addr, &dest_addr, nb, ip4_nb);
#endif

            /* Find transport protocol.*/
            if((protocol = _fnet_prot_find(AF_INET, SOCK_UNSPEC, (fnet_uint32_t)hdr->protocol)) != FNET_NULL)
            {
                protocol->prot_input(netif, &src_addr,  &dest_addr, nb, ip4_nb);
                /* After that nb may point to wrong place. Do not use it.*/
            }
            else
                /* No protocol found.*/
            {
                _fnet_netbuf_free_chain(nb);
                _fnet_icmp4_error(netif, FNET_ICMP4_UNREACHABLE, FNET_ICMP4_UNREACHABLE_PROTOCOL, ip4_nb);
            }
        }
        else
        {
            _fnet_netbuf_free_chain(nb);
        }
    } /* while end */

    fnet_isr_unlock();
}

/************************************************************************
* DESCRIPTION: This function attempts to assemble a complete datagram.
*************************************************************************/
#if FNET_CFG_IP4_FRAGMENTATION

static void _fnet_ip4_reassembly( fnet_netbuf_t **nb_ptr )
{
    fnet_ip4_frag_list_t     *frag_list_ptr;
    fnet_ip4_frag_header_t   *frag_ptr;
    fnet_ip4_frag_header_t   *cur_frag_ptr;
    fnet_netbuf_t           *nb = *nb_ptr;
    fnet_ip4_header_t       *iphdr;
    fnet_size_t             i;
    fnet_uint16_t           offset;
    fnet_size_t             hdr_length;

    /* For this algorithm the all datagram must reside in contiguous area of memory.*/
    if(_fnet_netbuf_pullup(&nb, nb->total_length) == FNET_ERR)
    {
        goto DROP_FRAG;
    }

    iphdr = (fnet_ip4_header_t *)nb->data_ptr;

    /* Liner search of the list to locate the appropriate datagram for the current fragment.*/
    for (frag_list_ptr = ip_frag_list_head; frag_list_ptr != 0; frag_list_ptr = frag_list_ptr->next)
    {
        if((frag_list_ptr->id == iphdr->id) && (frag_list_ptr->protocol == iphdr->protocol)
           && (frag_list_ptr->source_addr == iphdr->source_addr)
           && (frag_list_ptr->desination_addr == iphdr->desination_addr))
        {
            break;
        }
    }

    cur_frag_ptr = (fnet_ip4_frag_header_t *)iphdr;

    /* Exclude the standard IP header and options.*/
    hdr_length = (fnet_size_t)FNET_IP_HEADER_GET_HEADER_LENGTH(iphdr) << 2;
    _fnet_netbuf_trim(&nb, (fnet_int32_t)hdr_length);
    cur_frag_ptr->total_length = (fnet_uint16_t)(fnet_ntohs(cur_frag_ptr->total_length) - hdr_length); /* Host endian.*/


    if(frag_list_ptr == 0)                                                  /* The first fragment of the new datagram.*/
    {
        if((frag_list_ptr = (fnet_ip4_frag_list_t *)_fnet_malloc_zero(sizeof(fnet_ip4_frag_list_t))) == 0) /* Create list.*/
        {
            goto DROP_FRAG;
        }

        _fnet_ip4_frag_list_add(&ip_frag_list_head, frag_list_ptr);

        frag_list_ptr->ttl = (fnet_uint8_t)FNET_IP4_FRAG_TTL;
        frag_list_ptr->id = iphdr->id;
        frag_list_ptr->protocol = iphdr->protocol;
        frag_list_ptr->source_addr = iphdr->source_addr;
        frag_list_ptr->desination_addr = iphdr->desination_addr;
        frag_ptr = 0;

        if((iphdr->flags_fragment_offset & FNET_HTONS(FNET_IP4_MF)) != 0u)
        {
            cur_frag_ptr->mf |= 1u;
        }

        cur_frag_ptr->offset = (fnet_uint16_t)(fnet_ntohs(cur_frag_ptr->offset) << 3); /* Convert offset to bytes (Host endian).*/
        cur_frag_ptr->nb = nb;

        _fnet_ip4_frag_add(&frag_list_ptr->frag_ptr, cur_frag_ptr, FNET_NULL);
    }
    else
    {
        if((iphdr->flags_fragment_offset & FNET_HTONS(FNET_IP4_MF)) != 0u)
        {
            cur_frag_ptr->mf |= 1u;
        }

        cur_frag_ptr->offset = (fnet_uint16_t)(fnet_ntohs(cur_frag_ptr->offset) << 3); /* Convert offset to bytes.*/
        cur_frag_ptr->nb = nb;

        /* Find position in reassembly list.*/
        frag_ptr = frag_list_ptr->frag_ptr;
        do
        {
            if(frag_ptr->offset > cur_frag_ptr->offset)
            {
                break;
            }

            frag_ptr = frag_ptr->next;
        }
        while (frag_ptr != frag_list_ptr->frag_ptr);

        /* Trims or discards icoming fragments.*/
        if(frag_ptr != frag_list_ptr->frag_ptr)
        {
            if((i = (fnet_size_t)(frag_ptr->prev->offset + frag_ptr->prev->total_length - cur_frag_ptr->offset)) != 0u)
            {
                if(i >= cur_frag_ptr->total_length)
                {
                    goto DROP_FRAG;
                }

                _fnet_netbuf_trim(&nb, (fnet_int32_t)i);
                cur_frag_ptr->total_length -= (fnet_uint16_t)i;
                cur_frag_ptr->offset += (fnet_uint16_t)i;
            }
        }

        /* Trims or discards existing fragments.*/
        while((frag_ptr != frag_list_ptr->frag_ptr)
              && ((cur_frag_ptr->offset + cur_frag_ptr->total_length) > frag_ptr->offset))
        {
            i = (fnet_size_t)((cur_frag_ptr->offset + cur_frag_ptr->total_length) - frag_ptr->offset);

            if(i < frag_ptr->total_length)
            {
                frag_ptr->total_length -= (fnet_uint16_t)i;
                frag_ptr->offset += (fnet_uint16_t)i;
                _fnet_netbuf_trim((fnet_netbuf_t **)&frag_ptr->nb, (fnet_int32_t)i);
                break;
            }

            frag_ptr = frag_ptr->next;
            _fnet_netbuf_free_chain(frag_ptr->prev->nb);
            _fnet_ip4_frag_del(&frag_list_ptr->frag_ptr, frag_ptr->prev);
        }

        /* Insert fragment to the list.*/
        _fnet_ip4_frag_add(&frag_list_ptr->frag_ptr, cur_frag_ptr, frag_ptr->prev);
    }

    offset = 0u;
    frag_ptr = frag_list_ptr->frag_ptr;

    do
    {
        if(frag_ptr->offset != offset)
        {
            goto NEXT_FRAG;
        }

        offset += frag_ptr->total_length;
        frag_ptr = frag_ptr->next;
    }
    while (frag_ptr != frag_list_ptr->frag_ptr);

    if((frag_ptr->prev->mf & 1u) != 0u)
    {
        goto NEXT_FRAG;
    }

    /* Reconstruct datagram.*/
    frag_ptr = frag_list_ptr->frag_ptr;
    nb = frag_ptr->nb;
    frag_ptr = frag_ptr->next;

    while(frag_ptr != frag_list_ptr->frag_ptr)
    {
        nb = _fnet_netbuf_concat(nb, frag_ptr->nb);

        frag_ptr = frag_ptr->next;
    }

    /* Reconstruct datagram header.*/
    iphdr = (fnet_ip4_header_t *)frag_list_ptr->frag_ptr;
    nb->total_length += (fnet_size_t)FNET_IP_HEADER_GET_HEADER_LENGTH(iphdr) << 2;
    nb->length += (fnet_size_t)FNET_IP_HEADER_GET_HEADER_LENGTH(iphdr) << 2;
    nb->data_ptr = (fnet_uint8_t *)nb->data_ptr - (FNET_IP_HEADER_GET_HEADER_LENGTH(iphdr) << 2);

    iphdr->total_length = fnet_htons((fnet_uint16_t)nb->total_length);

    iphdr->source_addr = frag_list_ptr->source_addr;
    iphdr->desination_addr = frag_list_ptr->desination_addr;
    iphdr->protocol = frag_list_ptr->protocol;
    iphdr->tos &= ~1u;

    _fnet_ip4_frag_list_del(&ip_frag_list_head, frag_list_ptr);
    _fnet_free(frag_list_ptr);

    goto EXIT;

DROP_FRAG:
    _fnet_netbuf_free_chain(nb);
NEXT_FRAG:
    nb = FNET_NULL;
EXIT:
    *nb_ptr =  nb;
}
#endif /* FNET_CFG_IP4_FRAGMENTATION */

/************************************************************************
* DESCRIPTION: IP timer function.
*************************************************************************/
#if FNET_CFG_IP4_FRAGMENTATION

static void _fnet_ip4_timer(fnet_uint32_t cookie)
{
    fnet_ip4_frag_list_t *frag_list_ptr;
    fnet_ip4_frag_list_t *tmp_frag_list_ptr;

    FNET_COMP_UNUSED_ARG(cookie);

    fnet_isr_lock();
    frag_list_ptr = ip_frag_list_head;

    while(frag_list_ptr != 0)
    {
        frag_list_ptr->ttl--;

        if(frag_list_ptr->ttl == 0u)
        {
            tmp_frag_list_ptr = frag_list_ptr->next;
            _fnet_ip4_frag_list_free(frag_list_ptr);
            frag_list_ptr = tmp_frag_list_ptr;
        }
        else
        {
            frag_list_ptr = frag_list_ptr->next;
        }
    }

    fnet_isr_unlock();
}

#endif

/************************************************************************
* DESCRIPTION: This function tries to free not critical parts
*              of memory occupied by the IP module.
*************************************************************************/
void _fnet_ip4_drain( void )
{
    fnet_isr_lock();

#if FNET_CFG_IP4_FRAGMENTATION

    while(((volatile fnet_ip4_frag_list_t *)ip_frag_list_head) != 0)
    {
        _fnet_ip4_frag_list_free(ip_frag_list_head);
    }

#endif

    while(((volatile fnet_netbuf_t *)ip_queue.head) != 0)
    {
        _fnet_netbuf_queue_del(&ip_queue.head, ip_queue.head);
    }

    ip_queue.count = 0u;

    fnet_isr_unlock();
}

/************************************************************************
* DESCRIPTION: This function frees list of datagram fragments.
*************************************************************************/
#if FNET_CFG_IP4_FRAGMENTATION
static void _fnet_ip4_frag_list_free( fnet_ip4_frag_list_t *list )
{
    fnet_netbuf_t *nb;

    if(list)
    {
        fnet_isr_lock();

        while((volatile fnet_ip4_frag_header_t *)(list->frag_ptr) != 0)
        {
            nb = list->frag_ptr->nb;
            _fnet_ip4_frag_del((fnet_ip4_frag_header_t **)(&list->frag_ptr), list->frag_ptr);
            _fnet_netbuf_free_chain(nb);
        }

        _fnet_ip4_frag_list_del(&ip_frag_list_head, list);
        _fnet_free(list);

        fnet_isr_unlock();
    }
}
#endif /* FNET_CFG_IP4_FRAGMENTATION */

/************************************************************************
* DESCRIPTION: Adds frag list to the general frag list.
*************************************************************************/
#if FNET_CFG_IP4_FRAGMENTATION
static void _fnet_ip4_frag_list_add( fnet_ip4_frag_list_t **head, fnet_ip4_frag_list_t *fl )
{
    fl->next = *head;

    if(fl->next != 0)
    {
        fl->next->prev = fl;
    }

    fl->prev = 0;
    *head = fl;
}

#endif /* FNET_CFG_IP4_FRAGMENTATION */

/************************************************************************
* DESCRIPTION: Deletes frag list from the general frag list.
*************************************************************************/
#if FNET_CFG_IP4_FRAGMENTATION
static void _fnet_ip4_frag_list_del( fnet_ip4_frag_list_t **head, fnet_ip4_frag_list_t *fl )
{
    if(fl->prev == 0)
    {
        *head = fl->next;
    }
    else
    {
        fl->prev->next = fl->next;
    }

    if(fl->next != 0)
    {
        fl->next->prev = fl->prev;
    }
}

#endif /* FNET_CFG_IP4_FRAGMENTATION */

/************************************************************************
* DESCRIPTION: Adds frag to the frag list.
*************************************************************************/
#if FNET_CFG_IP4_FRAGMENTATION
static void _fnet_ip4_frag_add( fnet_ip4_frag_header_t *FNET_COMP_PACKED_VAR *head, fnet_ip4_frag_header_t *frag,
                                fnet_ip4_frag_header_t *frag_prev )
{
    if(frag_prev && ( *head))
    {
        frag->next = frag_prev->next;
        frag->prev = frag_prev;
        frag_prev->next->prev = frag;
        frag_prev->next = frag;

        if((*head)->offset > frag->offset)
        {
            *head = frag;
        }
    }
    else
    {
        frag->next = frag;
        frag->prev = frag;
        *head = frag;
    }
}
#endif /* FNET_CFG_IP4_FRAGMENTATION */

/************************************************************************
* DESCRIPTION: Deletes frag from the frag list.
*************************************************************************/
#if FNET_CFG_IP4_FRAGMENTATION
static void _fnet_ip4_frag_del( fnet_ip4_frag_header_t *FNET_COMP_PACKED_VAR *head, fnet_ip4_frag_header_t *frag )
{
    if(frag->prev == frag)
    {
        *head = 0;
    }
    else
    {
        frag->prev->next = frag->next;
        frag->next->prev = frag->prev;

        if(*head == frag)
        {
            *head = frag->next;
        }
    }
}
#endif /* FNET_CFG_IP4_FRAGMENTATION */

#if FNET_CFG_MULTICAST

/************************************************************************
* DESCRIPTION: Join a multicast group. Returns pointer to the entry in
*              the multicast list, or FNET_NULL if any error;
*************************************************************************/
fnet_ip4_multicast_list_entry_t *_fnet_ip4_multicast_join( fnet_netif_t *netif, fnet_ip4_addr_t group_addr )
{
    fnet_index_t                    i;
    fnet_ip4_multicast_list_entry_t *result = FNET_NULL;

    /* Find existing entry or free one.*/
    for(i = 0u; i < FNET_CFG_MULTICAST_MAX; i++)
    {
        if(fnet_ip4_multicast_list[i].user_counter > 0u)
        {
            if((fnet_ip4_multicast_list[i].netif == netif) && (fnet_ip4_multicast_list[i].group_addr == group_addr))
            {
                result = &fnet_ip4_multicast_list[i];
                break; /* Found.*/
            }
        }
        else /* user_counter == 0.*/
        {
            result = &fnet_ip4_multicast_list[i]; /* Save the last free.*/
        }
    }

    if(result)
    {
        result->user_counter++; /* Increment user counter.*/

        if(result->user_counter == 1u) /* New entry.*/
        {
            result->group_addr = group_addr;
            result->netif = netif;

            /* Join HW interface. */
            _fnet_netif_join_ip4_multicast ( (fnet_netif_desc_t) netif, group_addr );

#if FNET_CFG_IGMP  /* Send IGMP report.*/
            /*
             * When a host joins a new group, it should immediately transmit a
             * Report for that group.
             * TBD To cover the possibility of the initial Report being lost or damaged, it is
             * recommended that it be repeated once or twice after short delays.
             */
            _fnet_igmp_join(netif, group_addr );
#endif /* FNET_CFG_IGMP */
        }
    }

    return result;
}

/************************************************************************
* DESCRIPTION: Leave a multicast group.
*************************************************************************/
void _fnet_ip4_multicast_leave_entry( fnet_ip4_multicast_list_entry_t *multicastentry )
{
    if(multicastentry)
    {
        multicastentry->user_counter--; /* Decrement user counter.*/

        if(multicastentry->user_counter == 0u)
        {

#if FNET_CFG_IGMP  /* Send IGMP leave.*/
            /* Leave via IGMP */
            _fnet_igmp_leave( multicastentry->netif, multicastentry->group_addr );
#endif /* FNET_CFG_IGMP */

            /* Leave HW interface. */
            _fnet_netif_leave_ip4_multicast ( (fnet_netif_desc_t) multicastentry->netif, multicastentry->group_addr );
        }
    }
}

#endif /* FNET_CFG_MULTICAST */

/************************************************************************
* DESCRIPTION: Is the address is broadcast?
*************************************************************************/
fnet_bool_t _fnet_ip4_addr_is_broadcast( fnet_ip4_addr_t addr, fnet_netif_t *netif )
{
    fnet_netif_ip4_addr_t   *netif_addr;
    fnet_bool_t             result = FNET_FALSE;

    if((addr == INADDR_BROADCAST)                   /* Limited broadcast */
       || (addr == INADDR_ANY)
       || (addr == FNET_IP4_ADDR_LINK_LOCAL_BROADCAST) )/* Link-local broadcast (RFC3927)*/
    {
        result = FNET_TRUE;
    }
    else if(netif == FNET_NULL)
    {
        _fnet_stack_mutex_lock();
        for (netif = fnet_netif_list; netif != 0; netif = netif->next)
        {
            netif_addr = &(netif->ip4_addr);

            if( (addr == netif_addr->netbroadcast) ||          /* Net-directed broadcast */
                (addr == netif_addr->subnetbroadcast) ||      /* Subnet-directed broadcast */
                (addr == netif_addr->subnet))
            {
                result = FNET_TRUE;
                break;
            }
        }
        _fnet_stack_mutex_unlock();
    }
    else
    {
        netif_addr = &(netif->ip4_addr);

        if( (addr == netif_addr->netbroadcast) ||              /* Net-directed broadcast */
            (addr == netif_addr->subnetbroadcast) ||          /* Subnet-directed broadcast */
            (addr == netif_addr->subnet))
        {
            result = FNET_TRUE;
        }
    }
    return result;
}

/*Todo path MTU discovery feature
 *Todo get MTU from a routing table, depending on destination MTU*/
fnet_size_t _fnet_ip4_maximum_packet( fnet_ip4_addr_t dest_ip )
{
    fnet_size_t result;

#if FNET_CFG_IP4_FRAGMENTATION == 0

    {
        fnet_netif_t *netif;

        if((netif = _fnet_ip4_route(dest_ip)) == 0) /* No route*/
        {
            result = FNET_IP4_MAX_PACKET;
        }
        else
        {
            result = netif->netif_mtu;
        }
    }

#else

    FNET_COMP_UNUSED_ARG(dest_ip);

    result = FNET_IP4_MAX_PACKET;

#endif

    result = (result - (FNET_IP4_MAX_OPTIONS + sizeof(fnet_ip4_header_t))) & (~0x3LU);

    return result;
}

/************************************************************************
* DESCRIPTION: This function retrieves the current value
*              of IPv4 socket option.
*************************************************************************/
fnet_error_t _fnet_ip4_getsockopt(struct _fnet_socket_if_t *sock, fnet_socket_options_t optname, void *optval, fnet_size_t *optlen )
{
    fnet_error_t result = FNET_ERR_OK;

    switch(optname)      /* Socket options processing. */
    {
        case IP_TOS: /* Get IP TOS for outgoing datagrams.*/
            if(*optlen < sizeof(fnet_uint32_t))
            {
                result = FNET_ERR_INVAL;
                break;
            }

            *((fnet_uint32_t *)optval) = (fnet_uint32_t)sock->options.ip4_opt.tos;
            *optlen = sizeof(fnet_uint32_t);
            break;
        case IP_TTL: /* Get IP TTL for outgoing datagrams.*/
            if(*optlen < sizeof(fnet_uint32_t))
            {
                result = FNET_ERR_INVAL;
                break;
            }

            *((fnet_uint32_t *)optval) = (fnet_uint32_t)sock->options.ip4_opt.ttl;
            *optlen = sizeof(fnet_uint32_t);
            break;
#if FNET_CFG_MULTICAST
        case IP_MULTICAST_TTL: /* Get IP TTL for outgoing datagrams.*/
            if(*optlen < sizeof(fnet_uint32_t))
            {
                result = FNET_ERR_INVAL;
                break;
            }

            *((fnet_uint32_t *)optval) = (fnet_uint32_t)sock->options.ip4_opt.ttl_multicast;
            *optlen = sizeof(fnet_uint32_t);
            break;
#endif /* FNET_CFG_MULTICAST */
        default:
            result = FNET_ERR_NOPROTOOPT; /* The option is unknown or unsupported.*/
            break;
    }

    return result;
}

/************************************************************************
* DESCRIPTION: This function sets the value of IPv4 socket option.
*************************************************************************/
fnet_error_t _fnet_ip4_setsockopt(struct _fnet_socket_if_t  *sock, fnet_socket_options_t optname, const void *optval, fnet_size_t optlen )
{
    fnet_error_t result = FNET_ERR_OK;

    switch(optname)      /* Socket options processing. */
    {
        /******************************/
        case IP_TOS: /* Set IP TOS for outgoing datagrams. */
            if(optlen < sizeof(fnet_uint32_t))
            {
                result = FNET_ERR_INVAL;
                break;
            }

            sock->options.ip4_opt.tos = (fnet_uint8_t) (*((const fnet_uint32_t *)(optval)));
            break;
        /******************************/
        case IP_TTL: /* Set IP TTL for outgoing datagrams. */
            if(optlen < sizeof(fnet_uint32_t))
            {
                result = FNET_ERR_INVAL;
                break;
            }

            sock->options.ip4_opt.ttl = (fnet_uint8_t) (*((const fnet_uint32_t *)(optval)));
            break;
#if FNET_CFG_MULTICAST
        /******************************/
        case IP_MULTICAST_TTL: /* Set IP TTL for outgoing Multicast datagrams. */
            /* Validation.*/
            if( (optlen < sizeof(fnet_uint32_t)) || (!(sock->protocol_interface)) || (sock->protocol_interface->type != SOCK_DGRAM )  )
            {
                result = FNET_ERR_INVAL;
                break;
            }
            sock->options.ip4_opt.ttl_multicast = (fnet_uint8_t) (*((const fnet_uint32_t *)(optval)));
            break;
        /******************************/
        case IP_ADD_MEMBERSHIP:     /* Join the socket to the supplied multicast group on
                                     * the specified interface. */
        case IP_DROP_MEMBERSHIP:    /* Drops membership to the given multicast group and interface.*/
        {
            fnet_index_t                    i;
            fnet_ip4_multicast_list_entry_t **multicast_entry = FNET_NULL;
            const struct fnet_ip_mreq       *mreq = (const struct fnet_ip_mreq *)optval;
            fnet_netif_t                    *netif;


            if(mreq->imr_interface == 0u)
            {
                netif = _fnet_netif_get_default();
            }
            else
            {
                netif = _fnet_netif_get_by_scope_id(mreq->imr_interface);
            }

            if((optlen != sizeof(struct fnet_ip_mreq)) /* Check size.*/
               || (netif == FNET_NULL) /* Found IF.*/
               || (!FNET_IP4_ADDR_IS_MULTICAST(mreq->imr_multiaddr.s_addr)) /* Check if the address is multicast.*/
               || (!(sock->protocol_interface)) || (sock->protocol_interface->type != SOCK_DGRAM )
              )
            {
                result = FNET_ERR_INVAL;
                break;
            }

            /* Find the existing entry with same parameters.*/
            for(i = 0u; i < FNET_CFG_MULTICAST_SOCKET_MAX; i++)
            {
                if( (sock->ip4_multicast_entry[i] != FNET_NULL)
                    && (sock->ip4_multicast_entry[i]->netif == netif)
                    && (sock->ip4_multicast_entry[i]->group_addr == mreq->imr_multiaddr.s_addr) )
                {
                    multicast_entry = &sock->ip4_multicast_entry[i];
                    break; /* Found.*/
                }
            }

            /******************************/
            if(optname == IP_ADD_MEMBERSHIP)
            {
                if(multicast_entry != FNET_NULL)
                {
                    /* Already joined.*/
                    result = FNET_ERR_ADDRINUSE;
                    break;
                }

                /* Find free entry.*/
                for(i = 0u; i < FNET_CFG_MULTICAST_SOCKET_MAX; i++)
                {
                    if(sock->ip4_multicast_entry[i] == FNET_NULL)
                    {
                        multicast_entry = &sock->ip4_multicast_entry[i];
                        break; /* Found.*/
                    }
                }

                if(multicast_entry != FNET_NULL)
                {
                    *multicast_entry = _fnet_ip4_multicast_join( netif, mreq->imr_multiaddr.s_addr );

                    if(*multicast_entry == FNET_NULL)
                    {
                        result = FNET_ERR_ADDRINUSE;
                        break;
                    }
                }
                else
                {
                    result = FNET_ERR_ADDRINUSE;
                    break;
                }

            }
            /******************************/
            else /* IP_DROP_MEMBERSHIP */
            {
                if(multicast_entry != FNET_NULL)
                {
                    /* Leave the group.*/
                    _fnet_ip4_multicast_leave_entry(*multicast_entry);
                    *multicast_entry = FNET_NULL;
                }
                else
                {
                    /* Join entry is not foud.*/
                    result = FNET_ERR_INVAL;
                    break;
                }
            }
        }
        break;
#endif /* FNET_CFG_MULTICAST */
        /******************************/
        default:
            result = FNET_ERR_NOPROTOOPT; /* The option is unknown or unsupported. */
            break;
    }

    return result;
}

/************************************************************************
* DESCRIPTION: Prints an IP header. For debug needs only.
*************************************************************************/
#if FNET_CFG_DEBUG_TRACE_IP4 && FNET_CFG_DEBUG_TRACE
static void _fnet_ip4_trace(fnet_uint8_t *str, fnet_ip4_header_t *ip_hdr)
{
    fnet_uint8_t ip_str[FNET_IP4_ADDR_STR_SIZE];

    fnet_printf(FNET_SERIAL_ESC_FG_GREEN"%s", str); /* Print app-specific header.*/
    fnet_println("[IPv4 header]"FNET_SERIAL_ESC_ATTR_RESET);
    fnet_println("+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+");
    fnet_println("|(V) %2d |(HL)%2d |(TOS)     0x%02x |(L)                      %5u |",
                 FNET_IP_HEADER_GET_VERSION(ip_hdr),
                 FNET_IP_HEADER_GET_HEADER_LENGTH(ip_hdr),
                 ip_hdr->tos,
                 fnet_ntohs(ip_hdr->total_length));
    fnet_println("+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+");
    fnet_println("|(Id)                     %5u |(F)%u |(Offset)            %4u |",
                 fnet_ntohs(ip_hdr->id),
                 fnet_ntohs(FNET_IP_HEADER_GET_FLAG(ip_hdr)) >> 15,
                 fnet_ntohs(FNET_IP_HEADER_GET_OFFSET(ip_hdr)));
    fnet_println("+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+");
    fnet_println("|(TTL)      %3u |(Proto)    "FNET_SERIAL_ESC_FG_BLUE"%3u"FNET_SERIAL_ESC_ATTR_RESET" |(Cheksum)               0x%04x |",
                 ip_hdr->ttl,
                 ip_hdr->protocol,
                 fnet_ntohs(ip_hdr->checksum));
    fnet_println("+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+");
    fnet_println("|(Src)                                          "FNET_SERIAL_ESC_FG_BLUE"%15s"FNET_SERIAL_ESC_ATTR_RESET" |",
                 fnet_inet_ntoa(*(struct fnet_in_addr *)(&ip_hdr->source_addr), ip_str));
    fnet_println("+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+");
    fnet_println("|(Dest)                                         "FNET_SERIAL_ESC_FG_BLUE"%15s"FNET_SERIAL_ESC_ATTR_RESET" |",
                 fnet_inet_ntoa(*(struct fnet_in_addr *)(&ip_hdr->desination_addr), ip_str));
    fnet_println("+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+");

}
#endif /* FNET_CFG_DEBUG_TRACE_IP4 */

#endif /* FNET_CFG_IP4 */
