Beispiel #1
0
/*
 *===========================================================================
 *                    ipnet_usr_sock_tcp_recv
 *===========================================================================
 * Description: Receives data from a TCP stream.
 * Parameters:  sock - The socket to receive data from.
 *              msg - Receive parameters.
 *              flags - IP_MSG_xxx flags.
 * Returns:     0 = a packet was successfully received, -1 = error.
 *
 */
IP_STATIC int
ipnet_usr_sock_tcp_recv(Ipnet_socket *sock, struct Ip_msghdr *msg, int flags)
{
    Ip_size_t i;
    int       r;
    int       bytes = 0;

    /* No support for ancillary data on TCP sockets */
    msg->msg_controllen = 0;

    if (msg->msg_name != IP_NULL)
    {
        Ip_socklen_t msg_namelen = msg->msg_namelen;

        r = ipnet_sys_getname(sock->ipcom.fd,
                              msg->msg_name,
                              &msg_namelen,
                              IP_FALSE);
        if (r < 0)
            return r;
        msg->msg_namelen = msg_namelen;
    }

    for (i = 0; i < msg->msg_iovlen; i++)
    {
        r = iptcp_usr_recv(sock,
                           msg->msg_iov[i].iov_base,
                           msg->msg_iov[i].iov_len,
                           flags);

        if (r == 0)
            /* EOF received */
            break;

        if (r < 0)
        {
            if (bytes > 0)
                break;
            IPCOM_WV_EVENT_2 (IPCOM_WV_NETD_IP4_DATAPATH_EVENT, IPCOM_WV_NETD_WARNING,
                              1, 1, IPCOM_WV_NETDEVENT_WARNING, IPCOM_WV_NETD_RECV,
                              ipnet_usr_sock_tcp_recv, IPCOM_WV_NETD_BADSOCK,
                              IPCOM_WV_IPNET_SOCK_TCP_MODULE, IPCOM_WV_NETD_TCP4);
            IPNET_STATS(sock_recvmsg_tcp_err++);
            return r;
        }
        bytes += r;

        if ((Ip_size_t) r != msg->msg_iov[i].iov_len)
            /* Continue only if the current buffer was filled to its
               limit */
            break;
        if (IP_BIT_ISFALSE(flags, IP_MSG_WAITALL))
            /* We have some data, return the data we have so far if there
               isn't anything more to receive */
            IP_BIT_SET(flags, IP_MSG_DONTWAIT);
    }
    return bytes;
}
Beispiel #2
0
/*
 *===========================================================================
 *                    ipnet_udp_input
 *===========================================================================
 * Description: Input handler for packet to UDP sockets. This function will
 *              NOT free the packet if the operation fails.
 * Parameters:  pkt - The received packet, must be freed by the called if
 *              this function fails.
 *              is_multicast - Set to IP_TRUE if the packet was sent to a
 *              multicast address.
 *              proto - The protocol number.
 *              src_addr - The address (Ipv4 or IPv6) of the sender.
 *              dst_addr - The address (Ipv4 or IPv6) of the receiver (this system).
 *              socklookup - A pointer to a function that can return the socket
 *              that is associated with this packet.
 *              nat_t   - NAT traversal; if used.
 * Returns:     0 >= number of matching sockets, <0 = error code
 *
 */
IP_GLOBAL int
ipnet_udp_input(Ipcom_pkt *pkt, Ip_bool is_multicast,
                IP_CONST void* src_addr, IP_CONST void* dst_addr,
                Ipnet_sock_lookup_f socklookup,
                int   *nat_t)
{
    Ipnet_pkt_udp *udp_hdr;
    int            retval;
    int            ulen_h;
    Ipnet_socket  *sock;
    Ip_u16         src_port;
    Ip_u16         dst_port;
    Ip_bool        pkt_no_rx_cache;


    IPCOM_WV_MARKER_1 (IPCOM_WV_NETD_IP4_DATAPATH_EVENT, IPCOM_WV_NETD_VERBOSE, 1, 3, IPCOM_WV_NETDEVENT_START,
                       ipnet_udp_input, IPCOM_WV_IPNET_UDP_MODULE, IPCOM_WV_NETD_UDP4);
    IPNET_STATS(udp_input++);
    IPCOM_PKT_TRACE(pkt, IPCOM_PKT_ID_UDP_INPUT);

    if (IP_UNLIKELY(pkt->end - pkt->start < IPNET_UDP_HDR_SIZE))
    {
        IPCOM_WV_EVENT_2 (IPCOM_WV_NETD_IP4_DATAPATH_EVENT, IPCOM_WV_NETD_WARNING,
                          1, 4, IPCOM_WV_NETDEVENT_WARNING, IPCOM_WV_NETD_RECV,
                          ipnet_udp_input, IPCOM_WV_NETD_BADHLEN,
                          IPCOM_WV_IPNET_UDP_MODULE, IPCOM_WV_NETD_UDP4);
        IPNET_STATS(udp_input_hdr_trunc++);
        IPCOM_MIB2(udpInErrors++);
        return -IP_ERRNO_EINVAL;
    }

    udp_hdr = (Ipnet_pkt_udp*) &pkt->data[pkt->start];
    ulen_h = ip_ntohs(udp_hdr->ulen);
    src_port = ip_ntohs(udp_hdr->sport);
    dst_port = ip_ntohs(udp_hdr->dport);

    if (IP_UNLIKELY(pkt->end - pkt->start < ulen_h))
    {
        IPCOM_WV_EVENT_2 (IPCOM_WV_NETD_IP4_DATAPATH_EVENT, IPCOM_WV_NETD_WARNING,
                          1, 5, IPCOM_WV_NETDEVENT_WARNING, IPCOM_WV_NETD_RECV,
                          ipnet_udp_input, IPCOM_WV_NETD_BADLEN,
                          IPCOM_WV_IPNET_UDP_MODULE, IPCOM_WV_NETD_UDP4);
        IPNET_STATS(udp_input_hdr_trunc++);
        IPCOM_MIB2(udpInErrors++);
        return -IP_ERRNO_EINVAL;
    }
    pkt->end = pkt->start + ulen_h;

    if (udp_hdr->sum != 0 && IP_BIT_ISFALSE(pkt->flags, IPCOM_PKT_FLAG_LOOPED_BACK))
    {
        Ip_u16 chksum;
        /* Only check packets that was not created locally */
#ifdef IPCOM_USE_HW_CHECKSUM_RX
        if (IP_BIT_ISSET(pkt->flags, IPCOM_PKT_FLAG_HW_CHECKSUM))
            chksum = 0;
        else if (IP_BIT_ISSET(pkt->flags, IPCOM_PKT_FLAG_TL_CHECKSUM))
            chksum = ipcom_in_checksum_finish(pkt->chk);
        else
#endif /* IPCOM_USE_HW_CHECKSUM_RX */
        {
            pkt->chk += ipcom_in_checksum_update(&pkt->data[pkt->start], (Ip_size_t) ulen_h);
            chksum = ipcom_in_checksum_finish(pkt->chk);
        }

        if (IP_UNLIKELY(chksum != 0))
        {
            /* Wrong checksum */
            IPCOM_WV_EVENT_2 (IPCOM_WV_NETD_IP4_DATAPATH_EVENT, IPCOM_WV_NETD_WARNING,
                              1, 6, IPCOM_WV_NETDEVENT_WARNING, IPCOM_WV_NETD_RECV,
                              ipnet_udp_input, IPCOM_WV_NETD_BADSUM,
                              IPCOM_WV_IPNET_UDP_MODULE, IPCOM_WV_NETD_UDP4);
            IPNET_STATS(udp_input_badchksum++);
            IPCOM_MIB2(udpInErrors++);
            return -IP_ERRNO_EINVAL;
        }
    }

    /* Move the start to beginning of application data */
    pkt->start += IPNET_UDP_HDR_SIZE;

    if (is_multicast)
    {
        IPCOM_WV_EVENT_2 (IPCOM_WV_NETD_IP4_DATAPATH_EVENT, IPCOM_WV_NETD_INFO,
                          1, 7, IPCOM_WV_NETDEVENT_INFO, IPCOM_WV_NETD_RECV,
                          ipnet_udp_input, IPCOM_WV_NETD_INFO_RECEIVE,
                          IPCOM_WV_IPNET_UDP_MODULE, IPCOM_WV_NETD_UDP4);
        IPNET_STATS(udp_input_multicast++);
        retval = ipnet_raw_input(pkt, IP_FALSE, IP_IPPROTO_UDP,
                                 src_addr, src_port,
                                 dst_addr, dst_port,
                                 socklookup);
        if (retval >= 0)
        {
            /* The packet must be freed unlesss an error occured */
            ipcom_pkt_free(pkt);
            IPCOM_MIB2(udpInDatagrams++);
            IPCOM_MIB2_U64_INC(udpHCInDatagrams);
        }
        else
        {
            IPCOM_MIB2(udpInErrors++);
        }
        return retval;
    }

    sock = socklookup(pkt->vr_index,
                      IP_IPPROTO_UDP,
                      dst_addr,
                      dst_port,
                      src_addr,
                      src_port);
    if (sock == IP_NULL)
        /* No matching socket, try the wildcard group */
        sock = socklookup(IPCOM_VR_ANY,
                          IP_IPPROTO_UDP,
                          dst_addr,
                          dst_port,
                          src_addr,
                          src_port);

    if (sock != IP_NULL)
    {
        Ipcom_socket_eventcb event_cb = sock->ipcom.event_cb;

        /* Verify NAT traversal */
        if (IP_UNLIKELY(ipnet_udp_encapsulation(sock, pkt, nat_t)))
        {
            ipcom_pkt_set_info(pkt, IPNET_PKT_INFO_ENCAP_UDP, sizeof(*udp_hdr), udp_hdr);
            pkt->start -= IPNET_UDP_HDR_SIZE;
            return 0;
        }

        IPCOM_WV_EVENT_2 (IPCOM_WV_NETD_IP4_DATAPATH_EVENT, IPCOM_WV_NETD_INFO,
                          1, 8, IPCOM_WV_NETDEVENT_INFO, IPCOM_WV_NETD_RECV,
                          ipnet_udp_input, IPCOM_WV_NETD_INFO_RECEIVE,
                          IPCOM_WV_IPNET_UDP_MODULE, IPCOM_WV_NETD_UDP4);
        IPNET_STATS(udp_input_sock_match++);
        pkt->fd = sock->ipcom.fd;
        pkt_no_rx_cache = IP_BIT_ISSET(pkt->flags, IPCOM_PKT_FLAG_NO_RX_CACHE);

        retval = ipnet_queue_received_packet(pkt, sock);
        if (retval < 0)
        {
            IPCOM_WV_EVENT_2 (IPCOM_WV_NETD_IP4_DATAPATH_EVENT, IPCOM_WV_NETD_WARNING,
                              1, 9, IPCOM_WV_NETDEVENT_WARNING, IPCOM_WV_NETD_RECV,
                              ipnet_udp_input, IPCOM_WV_NETD_INFO_QUEUE_FULL,
                              IPCOM_WV_IPNET_UDP_MODULE, IPCOM_WV_NETD_UDP4);
            IPNET_STATS(udp_input_queue_pkt_err++);
            IPCOM_MIB2(udpInErrors++);
            return retval;
        }

        IPCOM_WV_EVENT_2 (IPCOM_WV_NETD_IP4_DATAPATH_EVENT, IPCOM_WV_NETD_INFO,
                          1, 10, IPCOM_WV_NETDEVENT_INFO, IPCOM_WV_NETD_RECV,
                          ipnet_udp_input, IPCOM_WV_NETD_INFO_RECEIVE,
                          IPCOM_WV_IPNET_UDP_MODULE, IPCOM_WV_NETD_UDP4);
        IPNET_STATS(udp_input_queue_pkt_ok++);

        if (event_cb != IP_NULL)
            event_cb(&sock->ipcom, pkt, IP_SOEVENT_CB_INPKT);

        IPCOM_MIB2(udpInDatagrams++);
        IPCOM_MIB2_U64_INC(udpHCInDatagrams);

        if (IP_BIT_ISFALSE(sock->flags, IPNET_SOCKET_FLAG_RX_CACHED)
            && !pkt_no_rx_cache)
            ipnet_sock_add_to_rx_cache(sock,
                                       src_addr,
                                       src_port,
                                       dst_addr,
                                       dst_port,
                                       ipnet_udp_fast_deliver_data);

        return 1;
    }
    else
    {
        IPCOM_WV_EVENT_2 (IPCOM_WV_NETD_IP4_DATAPATH_EVENT, IPCOM_WV_NETD_WARNING,
                          1, 13, IPCOM_WV_NETDEVENT_WARNING, IPCOM_WV_NETD_RECV,
                          ipnet_udp_input, IPCOM_WV_NETD_NOPORT,
                          IPCOM_WV_IPNET_UDP_MODULE, IPCOM_WV_NETD_UDP4);
        IPNET_STATS(udp_input_sock_nomatch++);
        IPCOM_MIB2(udpNoPorts++);
    }

    IPCOM_WV_EVENT_2 (IPCOM_WV_NETD_IP4_DATAPATH_EVENT, IPCOM_WV_NETD_WARNING,
                      1, 14, IPCOM_WV_NETDEVENT_WARNING, IPCOM_WV_NETD_RECV,
                      ipnet_udp_input, IPCOM_WV_NETD_NOPORT,
                      IPCOM_WV_IPNET_UDP_MODULE, IPCOM_WV_NETD_UDP4);
    IPNET_STATS(udp_input_econnrefused++);
    return -IP_ERRNO_ECONNREFUSED;
}
/*
 *===========================================================================
 *                    ipnet_create_reassembled_packet
 *===========================================================================
 * Description: Reassembles a list of fragments to the orginal packet and
 *              creates a new packet of it.
 * Parameters:  fragment_list - The first fragment in the list.
 *              ip_hdr_size - The size of the IP header.
 *              frag_hdr_size - The size of the header containing the fragmentation id.
 *              update_ip_header - A function that can finish the IP header
 *              of the reassembled packet.
 *              reassemble_err_func - Handler for reassembly error.
 * Returns:     The reassembled packet or IP_NULL if a packet big enough
 *              to hold the whole packet.
 */
IP_GLOBAL Ipcom_pkt *
ipnet_create_reassembled_packet(Ipcom_pkt *fragment_list,
                                int ip_hdr_size,
                                int frag_hdr_size,
                                Ipnet_update_ip_header_func update_ip_header,
                                Ipnet_reassemble_err_func report_reassemble_err)
{
    Ipcom_pkt *pkt = IP_NULL;
    Ipcom_pkt *pkt_it;
    int        total_size = 0;
    int        mtu;

    for (pkt_it = fragment_list; pkt_it != IP_NULL; pkt_it = pkt_it->next_fragment)
    {
        pkt_it->start += frag_hdr_size;
        total_size += pkt_it->end - pkt_it->start;
        ip_assert(pkt_it->ipstart <= pkt_it->start);
        ip_assert(pkt_it->start <= pkt_it->end);
    }

    if (total_size > 65535)
    {
        IPCOM_WV_EVENT_2 (IPCOM_WV_NETD_IP4_DATAPATH_EVENT, IPCOM_WV_NETD_WARNING,
                          1, 4, IPCOM_WV_NETDEVENT_WARNING, IPCOM_WV_NETD_RECV,
                          ipnet_create_reassembled_packet, IPCOM_WV_NETD_BADLEN,
                          IPCOM_WV_IPNET_FRAG_MODULE, IPCOM_WV_NETD_IP_REASSEMBLE);
        IPNET_STATS(pkt_reasm_too_big++);
        if (report_reassemble_err)
            report_reassemble_err(fragment_list);
        goto done;
    }

    total_size += ip_hdr_size;
    mtu = total_size;

    pkt = ipcom_pkt_malloc(mtu,
                           IP_FLAG_FC_STACKCONTEXT);
    if (pkt == IP_NULL || pkt->maxlen < total_size)
    {
        IPCOM_WV_EVENT_2 (IPCOM_WV_NETD_IP4_DATAPATH_EVENT, IPCOM_WV_NETD_CRITICAL,
                          1, 5, IPCOM_WV_NETDEVENT_CRITICAL, IPCOM_WV_NETD_RECV,
                          ipnet_create_reassembled_packet, IPCOM_WV_NETD_NOBUFS,
                          IPCOM_WV_IPNET_FRAG_MODULE, IPCOM_WV_NETD_IP_REASSEMBLE);
        IPNET_STATS(pkt_reasm_nomem++);
        if (pkt != IP_NULL)
        {
            ipcom_pkt_free(pkt);
            pkt = IP_NULL;
        }
        goto done;
    }

    pkt->start = fragment_list->ipstart;
    if (pkt->start + total_size > pkt->maxlen)
        pkt->start  = (pkt->maxlen - total_size) & ~0x3;
    pkt->ipstart    = pkt->start;
    pkt->end        = pkt->start + ip_hdr_size;
    pkt->ifindex    = fragment_list->ifindex;
    pkt->vr_index   = fragment_list->vr_index;
    pkt->flags      = fragment_list->flags;
    pkt->flags &= ~(IPCOM_PKT_FLAG_FRAGMENT
                    | IPCOM_PKT_FLAG_MORE_FRAG
                    | IPCOM_PKT_FLAG_HW_CHECKSUM
                    | IPCOM_PKT_FLAG_TL_CHECKSUM);

    /* Copy the IP header */
    ipcom_memcpy(&pkt->data[pkt->start],
                 &fragment_list->data[fragment_list->ipstart],
                 (Ip_size_t)ip_hdr_size);

    /* Copy the rest of the packet */
    for (pkt_it = fragment_list; pkt_it != IP_NULL; pkt_it = pkt_it->next_fragment)
    {
        ipcom_memcpy(&pkt->data[pkt->end],
                     &pkt_it->data[pkt_it->start],
                     (Ip_size_t)(pkt_it->end - pkt_it->start));
        pkt->end += pkt_it->end - pkt_it->start;
        ip_assert(pkt->end <= pkt->maxlen);
    }

    update_ip_header(pkt, fragment_list);

done:
    ipcom_pkt_free(fragment_list);
    return pkt;
}
Beispiel #4
0
/*
 *===========================================================================
 *                    ipnet_sock_udp_send
 *===========================================================================
 * Description: Adds a UDP header to the application data.
 * Parameters:  sock - The socket to use when sending.
 *              msg - The send parameters.
 *              pkt - Packet that contains the UDP payload.
 * Returns:     ==0 = success, <0 = error code.
 *
 */
IP_STATIC int
ipnet_sock_udp_send(Ipnet_socket *sock, IP_CONST struct Ip_msghdr *msg, Ipcom_pkt *pkt)
{
    Ipnet_sock_udp_ops *ops = (Ipnet_sock_udp_ops *) sock->ops;
    Ip_u16              to_port_n;
    Ipnet_pkt_udp      *udp;
    int                 ret;

    IPCOM_WV_MARKER_1 (IPCOM_WV_NETD_IP4_DATAPATH_EVENT, IPCOM_WV_NETD_VERBOSE, 1, 1, IPCOM_WV_NETDEVENT_START,
                       ipnet_sock_udp_send, IPCOM_WV_IPNET_UDP_MODULE, IPCOM_WV_NETD_UDP4);
    IPNET_STATS(udp_output++);
    ip_assert(sock->ipcom.type == IP_SOCK_DGRAM);
    ip_assert(sock->proto == IP_IPPROTO_UDP);

    if (msg->msg_name == IP_NULL)
        to_port_n = ip_htons(sock->dport);
    else
        /* Ok for both IPv4 and IPv6 */
        to_port_n = ((struct Ip_sockaddr_in*) msg->msg_name)->sin_port;

    pkt->start -= IPNET_UDP_HDR_SIZE;

    /* Fill in UDP header. */
    udp = (Ipnet_pkt_udp *)&pkt->data[pkt->start];

    if (sock->sport)
        udp->sport = ip_htons(sock->sport);
    else
    {
        /* UDP socket unbound, get a temporary port. */
        udp->sport = ipnet_next_ephemeral_port(sock);
        if (udp->sport == 0)
        {
            IPCOM_WV_EVENT_2 (IPCOM_WV_NETD_IP4_DATAPATH_EVENT, IPCOM_WV_NETD_WARNING,
                              1, 2, IPCOM_WV_NETDEVENT_WARNING, IPCOM_WV_NETD_SEND,
                              ipnet_sock_udp_send, IPCOM_WV_NETD_ADDRINUSE,
                              IPCOM_WV_IPNET_UDP_MODULE, IPCOM_WV_NETD_UDP4);
            IPNET_STATS(udp_output_eaddrinuse++);
            ret = -IP_ERRNO_EADDRINUSE;
            goto errout;
        }
        udp->sport = ip_htons(udp->sport);
    }

    udp->dport = to_port_n;
    udp->sum   = 0;
#ifdef IPCOM_ZEROCOPY
    udp->ulen  = (Ip_u16) ip_htons(ipcom_pkt_get_length(pkt));
#else
    udp->ulen  = (Ip_u16) ip_htons(pkt->end - pkt->start);
#endif

    /* Update the checksum with the UDP header */
#ifdef IPCOM_USE_HW_CHECKSUM_TX
    if (IP_BIT_ISFALSE(pkt->flags, IPCOM_PKT_FLAG_HW_CHECKSUM))
#endif
#ifdef IPCOM_ZEROCOPY
    	if (IP_BIT_ISFALSE(msg->msg_flags, IP_MSG_ZBUF))
#endif
    		pkt->chk += ipcom_in_checksum_update(&pkt->data[pkt->start], IPNET_UDP_HDR_SIZE);
    IPCOM_MIB2(udpOutDatagrams++);
    IPCOM_MIB2_U64_INC(udpHCOutDatagrams);

    return ops->inet.network_send(sock, msg, pkt);

errout:
    ipcom_pkt_free(pkt);
    return ret;
}
/*
 *===========================================================================
 *                    ipnet_reassembly
 *===========================================================================
 * Description: Reassembles fragmented IP packets.
 *
 *                          ...                 ...
 *                           |                   |
 *                         pkt  #A             pkt  #B
 *                         frag #b             frag #y
 *                           ^                   ^
 *                           |                   |
 *                           |                   |
 *                      next_fragment       next_fragment
 *                           |                   |
 *                           |                   |
 *                    ---> pkt  #A -- next --> pkt  #B -- next --> ...
 *                    |    frag #a             frag #x
 *                    |
 *              reassembly_list
 *
 *              pkt #A chain will time out before pkt #B if not all fragments
 *              are received.
 *              frag #a and frag #x has lower offset than frag #b and frag #y.
 *              NOTE: this function must be called with the reassembly_list_lock
 *                    taken.
 * Parameters:  reassembly_list - Pointer to the head of the list of
 *                  reassembled packets.
 *              pkt - A newly received fragment.
 *              is_part_of_same_pkt - function that returns IP_TRUE if two
 *                  fragments is part of the same packet.
 *              get_offset - function that returns the offset of a within
 *                  the reassembled packet.
 *              more_fragments - function that returns if a fragment is the
 *                  last fragment of a packet.
 *              tmo_cb - handler for reassembly timeout of a packet.
 *              frag_hdr_len - size of the fragmentation header.
 * Returns:     Pointer to the reassembled packet or IP_NULL if more fragments
 *              is needed.
 */
IP_GLOBAL Ipcom_pkt *
ipnet_reassembly(Ipcom_pkt **reassembly_list,
                 Ipcom_pkt *pkt,
                 Ipnet_is_part_of_same_pkt_func is_part_of_same_pkt,
                 Ipnet_get_offset_func get_offset,
                 Ipnet_more_fragments_func more_fragments,
                 Ipnet_timeout_handler tmo_cb,
                 int frag_hdr_len)
{
    Ipcom_pkt **pkt_it;     /* The packet this fragment belongs to */
    Ipcom_pkt **frag_it;    /* The fragment within the selected packet this fragment
                               should inserted in-font of */
    Ipcom_pkt  *frag_head = IP_NULL;
    Ipcom_pkt  *tmo_pkt;
    Ipcom_pkt  *new_tmo_pkt;
    Ipcom_pkt  *drop_pkt_list;
    int         offset;
    int         previous_end;

    tmo_pkt = *reassembly_list;

    IPNET_PKT_SET_TMO(pkt, IP_NULL);
    offset = get_offset(pkt);
    for (pkt_it = reassembly_list; *pkt_it != IP_NULL; pkt_it = &(*pkt_it)->next)
    {
        if (is_part_of_same_pkt(pkt, *pkt_it))
            break;
    }

    if (*pkt_it == IP_NULL)
    {
        /* First fragment received of this packet, add it last to the list */
        *pkt_it = pkt;
        pkt->next = IP_NULL;
#if defined(IPNET_DEBUG) || defined(IPNET_STATISTICS)
#ifdef IPCOM_USE_INET
        if (is_part_of_same_pkt == ipnet_ip4_is_part_of_same_pkt)
        {
            IPCOM_WV_EVENT_2 (IPCOM_WV_NETD_IP4_DATAPATH_EVENT, IPCOM_WV_NETD_INFO,
                              1, 2, IPCOM_WV_NETDEVENT_INFO, IPCOM_WV_NETD_RECV,
                              ipnet_reassembly, IPCOM_WV_NETD_INFO_RECEIVE,
                              IPCOM_WV_IPNET_FRAG_MODULE, IPCOM_WV_NETD_IP_REASSEMBLE);
            IPNET_STATS(pkt_num_ip4_reassembly_list++);
        }
#endif
#ifdef IPCOM_USE_INET6
        if (is_part_of_same_pkt == ipnet_ip6_is_part_of_same_pkt)
        {
            IPCOM_WV_EVENT_2 (IPCOM_WV_NETD_IP6_DATAPATH_EVENT, IPCOM_WV_NETD_INFO,
                              1, 3, IPCOM_WV_NETDEVENT_INFO, IPCOM_WV_NETD_RECV,
                              ipnet_reassembly, IPCOM_WV_NETD_INFO_RECEIVE,
                              IPCOM_WV_IPNET_FRAG_MODULE, IPCOM_WV_NETD_IP_REASSEMBLE);
            IPNET_STATS(pkt_num_ip6_reassembly_list++);
        }
#endif
#endif
        ip_assert(pkt->next_fragment == IP_NULL);
        IPNET_PKT_SET_TIMEOUT_ABS(pkt, IPNET_SECONDS_ABS + ipnet_conf_reassembly_timeout);
    }
    else
    {
        previous_end = 0;
        for (frag_it = pkt_it; (*frag_it) != IP_NULL; frag_it = &(*frag_it)->next_fragment)
        {
            if (offset <= get_offset(*frag_it))
            {
                /* The new fragment should be in front of this fragment */
                break;
            }
            previous_end = (*frag_it)->end - (*frag_it)->start
                + get_offset(*frag_it) - frag_hdr_len;
        }

        if (previous_end > offset
            || (*frag_it
                && (offset == get_offset(*frag_it)
                    || offset + pkt->end - pkt->start - frag_hdr_len > get_offset(*frag_it))))
        {
            /* Overlapping segment or this fragment has already been received */
            ipcom_pkt_free(pkt);
            return IP_NULL;
        }

        /* The timeout is calculated from the point when the first fragment
           in this packet was received */
        IPNET_PKT_SET_TIMEOUT_ABS(pkt, IPNET_PKT_GET_TIMEOUT_ABS(*pkt_it));

        /* Insert the fragment into the fragment list */
        pkt->next_fragment = *frag_it;
        *frag_it = pkt;
        if (pkt->next_fragment != IP_NULL && pkt->next_fragment->next != IP_NULL)
        {
            /*
             * 'pkt->next_fragment' was the first fragment received
             * for this datagram, but the new fragment should be
             * in-front of that one and will become the new head of
             * that IP datagram.
             */
            pkt->next = pkt->next_fragment->next;
            pkt->next_fragment->next = IP_NULL;
        }
    }

    /*
     * Check if all fragments has been received
     */
    for (frag_it = pkt_it, offset = 0; (*frag_it) != IP_NULL; frag_it = &(*frag_it)->next_fragment)
    {
        ip_assert((offset & 0x7) == 0);
        if (offset != get_offset(*frag_it))
            break;

        if (!more_fragments(*frag_it))
        {
            /* All fragments was received */
            frag_head = *pkt_it;

            /* Remove the fragment list from reassembly list */
            *pkt_it = frag_head->next;
            frag_head->next = IP_NULL;
            break;
        }

        offset += (*frag_it)->end - (*frag_it)->start - frag_hdr_len;
    }

    if (frag_head != IP_NULL
        || (ipnet_frag_list_len(*reassembly_list) <= ipnet_conf_max_reassembly_list_len
            && ipnet_frag_dgram_list_len(*pkt_it) <= ipnet_conf_max_dgram_frag_list_len))
        /*
         * Number of individual IP-datagrams and fragments of a
         * specific IP datagram are within limits.
         */
        drop_pkt_list = IP_NULL;
    else
    {
        /*
         * Too many partially reassembled fragments or one of the IP
         * datagrams consists of too many fragments (DoS attack?).
         * Drop the oldest partly reassembled IP
         * datagram. drop_pkt_list can never be equal to frag_head
         * since the frag_head list is either IP_NULL (more fragments
         * needed) or already removed from the list.
         */
        drop_pkt_list = *reassembly_list;
        *reassembly_list = drop_pkt_list->next;
        drop_pkt_list->next = IP_NULL;
    }

    /*
     * The TMO packet will change if
     *  - the oldest fragment list is returned
     *  - if the new fragment is a new head in the oldest fragment list
     */
    new_tmo_pkt = *reassembly_list;
    if (new_tmo_pkt != tmo_pkt)
    {
        if (tmo_pkt)
            ipnet_timeout_cancel(IPNET_PKT_GET_TMO(tmo_pkt));

        if (new_tmo_pkt)
            (void) ipnet_timeout_schedule(IPNET_PKT_GET_TIMEOUT(new_tmo_pkt) * 1000,
                                          tmo_cb,
                                          new_tmo_pkt,
                                          IPNET_PKT_GET_TMO_PTR(new_tmo_pkt));
    }

    if (drop_pkt_list)
        ipcom_pkt_free(drop_pkt_list);

    /*
     * All fragments has not been received if frag_head == IP_NULL
     */
    return frag_head;
}