LwIP pbuf of TCP/IP protocol stack

1, Overview
lwIP - A Lightweight TCP/IP stack
The focus of the lwIP TCP/IP implementation is to reduce resource usage while still having a full scale TCP. This makes lwIP suitable for use in embedded systems with tens of kilobytes of free RAM and room for around 40 kilobytes of code ROM.
Main features include:

  • Protocols: IP, IPv6, ICMP, ND, MLD, UDP, TCP, IGMP, ARP, PPPoS, PPPoE
  • DHCP client, DNS client (incl. mDNS hostname resolver), AutoIP/APIPA (Zeroconf), SNMP agent (v1, v2c, v3, private MIB support & MIB compiler)
  • APIs: specialized APIs for enhanced performance, optional Berkeley-alike socket API
  • Extended features: IP forwarding over multiple network interfaces, TCP congestion control, RTT estimation and fast recovery/fast retransmit
  • Addon applications: HTTP(S) server, SNTP client, SMTP(S) client, ping, NetBIOS nameserver, mDNS responder, MQTT client, TFTP server

The above is a description of LwIP's official website. LwIP's full name: Lightweight IP, which means lightweight TCP/IP protocol, is a small open source TCP/IP protocol stack developed by Adam Dunkels of the Swedish Academy of Computer Sciences (SICS). The original design intention of LwIP is to realize a relatively complete TCP/IP protocol stack with a small amount of resource consumption, in which "integrity" mainly refers to the integrity of TCP protocol, and the focus of implementation is to reduce the occupation of RAM on the basis of maintaining the main functions of TCP protocol. In addition, LwIP can be ported to the operating system or run independently without the operating system.

LwIP has the following main features:

  1. Support ARP protocol (Ethernet Address Resolution Protocol).
  2. Support ICMP Protocol (control message protocol) for network debugging and maintenance.
  3. It supports IGMP protocol (Internet Group Management Protocol) and can receive multicast data.
  4. Support UDP protocol (User Datagram Protocol).
  5. Support TCP protocol (transmission control protocol), including blocking control, RTT estimation, fast recovery and fast forwarding.
  6. Support PPP protocol (point-to-point communication protocol), PPPoS and PPPoE.
  7. Support DNS (domain name resolution).
  8. Support DHCP protocol and dynamically allocate IP addresses.
  9. Support IP protocols, including IPv4 and IPv6 protocols, support IP fragmentation and reload functions, and packet forwarding under multiple network interfaces.
  10. Support SNMP protocol (Simple Network Management Protocol).
  11. Support AUTOIP and automatic IP address configuration.
  12. Provide a special internal callback interface (Raw API) to improve application performance.
  13. Provide optional Socket API and NETCONN API (used in multithreading).

LwIP has the following advantages when used in embedded system:

  1. Low resource overhead, i.e. lightweight. LwIP kernel has its own memory management strategy and packet management strategy, which makes the kernel highly efficient in processing packets. In addition, LwIP is highly tailorable, and all unnecessary functions can be removed through macro compilation options. LwIP needs 40KB code ROM and tens of KB RAM for smooth operation, which makes it very suitable for embedded devices with limited memory resources.
  2. The supported protocols are relatively complete. Almost all common protocols in TCP/IP are supported, which has long been enough in embedded devices.
  3. Some common applications are implemented: DHCP client, DNS client, HTTP server, MQTT client, TFTP server, SNTP client and so on.
  4. It also provides three programming interfaces: RAW API, NETCONN API and Socket API. The execution efficiency, ease of use, portability and time and space overhead of these three APIs are different. Users can balance the advantages and disadvantages and choose the appropriate API for network application development according to their actual needs.
  5. Highly portable. Its source code is all implemented in C, and users can easily transplant across processors and compilers. In addition, it abstracts the functions of the operating system that will be used in the kernel, and uses a set of custom APIs. Users can implement these APIs by themselves, so as to realize cross operating system migration.
  6. Open source and free, users can use it without any commercial risk.
  7. Compared with other TCP/IP protocol stacks in the embedded field, such as UC TCP/IP and FreeRTOS TCP, LwIP has a longer development history and has been verified and tested more. LwIP is widely used in embedded network equipment. The TCP/IP core of the Internet of things operating system launched by some domestic Internet of things companies is LwIP; The well-known WiFi module ESP8266 of the Internet of things uses LwIP for its TCP/IP firmware.

Shortcomings of LwIP:
Although LwIP has so many advantages, it is born for embedded after all, so it does not fully implement TCP/IP protocol stack. Compared with the TCP/IP protocol stack of Linux and Windows systems, the function of LwIP is not complete and powerful. However, LwIP is sufficient for most network applications in the field of Internet of things.

2, Network packet processing
1. TCP/IP is a data communication mechanism. Therefore, the implementation of protocol stack is essentially to process data packets. In order to achieve efficient efficiency, LwIP packet management should provide an efficient processing mechanism. Each layer of the protocol stack can process data packets flexibly and reduce the time and space overhead of data transmission between layers, which is the key to improve the work efficiency of the protocol stack. In the implementation of BSD, a structure describing data packets is called mbuf. Similarly, in LwIP, there is a similar structure called pbuf.

  • Differences between LwIP and standard TCP/IP protocol stack
LwIP protocol stackStandard protocol stack
Fuzzy stratificationStrict stratification
Data transmission layer by layer copyDirect operation of data transmission
Packet privatePacket sharing
Low efficiencyefficient
Low data processing requirementsHigh data processing requirements
Complete TCP/IP protocolRelatively complete TCP/IP protocol

2. The pbuf structure is a data structure that describes the packets in the protocol stack:

/** Main packet buffer struct */
struct pbuf
{
    /** next pbuf in singly linked pbuf chain */
    struct pbuf *next;

    /** pointer to the actual data in the buffer */
    void *payload;

    /**
     * total length of this buffer and all next buffers in chain
     * belonging to the same packet.
     *
     * For non-queue packet chains this is the invariant:
     * p->tot_len == p->len + (p->next? p->next->tot_len: 0)
     */
    u16_t tot_len;

    /** length of this buffer */
    u16_t len;

    /** a bit field indicating pbuf type and allocation sources
        (see PBUF_TYPE_FLAG_*, PBUF_ALLOC_FLAG_* and PBUF_TYPE_ALLOC_SRC_MASK)
      */
    u8_t type_internal;

    /** misc flags */
    u8_t flags;

    /**
     * the reference count always equals the number of pointers
     * that refer to this pbuf. This can be pointers from an application,
     * the stack itself, or pbuf->next pointers from a chain.
     */
    LWIP_PBUF_REF_T ref;

    /** For incoming packets, this contains the input netif's index */
    u8_t if_idx;
};
  • Next is a pointer of pbuf type, pointing to the next pbuf. Because the data packets in the network may be large and the data packet size that pbuf can manage is limited, all pbuf packets will be connected in the form of a linked list, so as to fully describe a data packet. These connected pbuf packets will form a linked list, which is called pbuf linked list.
  • payload is a pointer to the data area and points to the starting address of the data area managed by the pbuf. The data area here can be the RAM space immediately following the pbuf structure address or an address in the ROM, depending on the type of pbuf.
  • tot_len records the length of all data of the current pbuf and its subsequent pbuf. For example, if the current pbuf is the first data structure on the pbuf linked list, then tot_len records the length of all pbuf data in the whole pbuf linked list; If the current pbuf is the last data structure on the linked list, the length of the current pbuf is recorded.
  • len indicates the valid data length in the current pbuf.
  • type_internal represents the type of pbuf. There are four types of pbuf in LwIP, and they are defined by an enumerated data structure.
  • The flags field is miscellaneous.
  • Ref indicates the number of times the pbuf has been referenced. Reference indicates that other pointers point to the current pbuf. The pointer here can be the next pointer of the pbuf or any other form of pointer. When initializing a pbuf, ref will be set to 1, because the address of the pbuf must return a pointer variable. When other pointers point to the pbuf, You must call the related function to add 1 to the ref field.
  • if_idx is used to record the index of netif input in the incoming packet, that is, the num field in netif.

3. Type of pbuf:

/**
 * @ingroup pbuf
 * Enumeration of pbuf types
 */
typedef enum
{
    /** pbuf data is stored in RAM, used for TX mostly, struct pbuf and its payload
        are allocated in one piece of contiguous memory (so the first payload byte
        can be calculated from struct pbuf).
        pbuf_alloc() allocates PBUF_RAM pbufs as unchained pbufs (although that might
        change in future versions).
        This should be used for all OUTGOING packets (TX).*/
    PBUF_RAM = (PBUF_ALLOC_FLAG_DATA_CONTIGUOUS | PBUF_TYPE_FLAG_STRUCT_DATA_CONTIGUOUS | PBUF_TYPE_ALLOC_SRC_MASK_STD_HEAP),
    /** pbuf data is stored in ROM, i.e. struct pbuf and its payload are located in
        totally different memory areas. Since it points to ROM, payload does not
        have to be copied when queued for transmission. */
    PBUF_ROM = PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF,
    /** pbuf comes from the pbuf pool. Much like PBUF_ROM but payload might change
        so it has to be duplicated when queued before transmitting, depending on
        who has a 'ref' to it. */
    PBUF_REF = (PBUF_TYPE_FLAG_DATA_VOLATILE | PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF),
    /** pbuf payload refers to RAM. This one comes from a pool and should be used
        for RX. Payload can be chained (scatter-gather RX) but like PBUF_RAM, struct
        pbuf and its payload are allocated in one piece of contiguous memory (so
        the first payload byte can be calculated from struct pbuf).
        Don't use this for TX, if the pool becomes empty e.g. because of TCP queuing,
        you are unable to receive TCP acks! */
    PBUF_POOL = (PBUF_ALLOC_FLAG_RX | PBUF_TYPE_FLAG_STRUCT_DATA_CONTIGUOUS | PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF_POOL)
} pbuf_type;
  • PBUF_RAM type pbuf space is allocated through the memory heap. This type of pbuf is most used in the protocol stack. Generally, the data to be sent in the protocol stack adopts this form. When applying for this pbuf memory block, the protocol stack will allocate the corresponding memory space according to the required size in the managed memory heap, This pbuf memory block contains data space and pbuf data structure area in continuous RAM memory space. When the kernel applies for this type of pbuf, it also counts the space of the protocol header. Of course, it applies according to the headers required by different layers of the protocol stack. LwIP also uses an enumeration type to define the header size required by different layers of the protocol stack.
/**
 * @ingroup pbuf
 * Enumeration of pbuf layers
 */
typedef enum
{
    /** Includes spare room for transport layer header, e.g. UDP header.
     * Use this if you intend to pass the pbuf to functions like udp_send().
     */
    PBUF_TRANSPORT = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN,
    /** Includes spare room for IP header.
     * Use this if you intend to pass the pbuf to functions like raw_send().
     */
    PBUF_IP = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN,
    /** Includes spare room for link layer header (ethernet header).
     * Use this if you intend to pass the pbuf to functions like ethernet_output().
     * @see PBUF_LINK_HLEN
     */
    PBUF_LINK = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN,
    /** Includes spare room for additional encapsulation header before ethernet
     * headers (e.g. 802.11).
     * Use this if you intend to pass the pbuf to functions like netif->linkoutput().
     * @see PBUF_LINK_ENCAPSULATION_HLEN
     */
    PBUF_RAW_TX = PBUF_LINK_ENCAPSULATION_HLEN,
    /** Use this for input packets in a netif driver when calling netif->input()
     * in the most common case - ethernet-layer netif driver. */
    PBUF_RAW = 0
} pbuf_layer;

PBUF_RAM type pbuf:

Layer (offset) is the header of each layer protocol, such as TCP message header, IP header, Ethernet frame header, etc. these spaces are reserved to flexibly process these data in each protocol layer. Of course, the size of layer can also be 0. The specific amount is related to the application method of data packet.

  • PBUF_ Pbuf and pbuf of pool type_ Ram type pbufs are similar, and their pbuf structures and data buffers also exist in continuous memory blocks, but their space is allocated through the memory pool. This type of pbuf can be allocated in a very short time, because this is the advantage of the memory pool allocation strategy. When the network card receives data, LwIP generally uses this type of pbuf to store the received data and apply for pbuf_ For pool type, the protocol stack will allocate an appropriate number of memory pools in the memory pool to meet the required data area size.
    PBUF_POOL type pbuf:
  • PBUF_ROM and PBUF_REF type pbuf:
    PBUF_ROM and pbuf_ The pbufs of ref type are basically the same. The pbufs applied in the memory POOL do not contain data areas, but only pbuf structures, that is, memps_ POOL of pbuf type, which is also PBUF_ROM and PBUF_REF has the biggest difference from the previous two types of pbuf.
    PBUF_ The data area of pbuf of ROM type is stored in ROM and is a piece of static data, while pbuf_ The data area of pbuf of type ref is stored in RAM space. When applying for these two types of pbuf, you only need to call memp_malloc() function can apply from the memory pool. The size of the applied memory is MEMP_PBUF, which is just the size of a pbuf structure.
    PBUF_ROM/PBUF_REF type pbuf:
  • For a data packet, it may use any type of pbuf to describe it, or it may use a variety of different pbufs to describe a data packet. However, no matter how described, the processing of the data packet is unchanged. The payload always points to the data area. The tot of the data packet connected in the form of linked list_ The len field always records the total size of the current and subsequent pbuf.

Different types of pbufs form a pbuf linked list:

  • pbuf_alloc()
    In the protocols of different layers, the size of the layer field is different, because the header size of different protocols is different, and the header size of each layer in the protocol stack will be reserved. LwIP uses enumeration type variables to record the header size of each layer, and allocates the required space of the layer according to the protocol when applying.
struct pbuf *pbuf_alloc(pbuf_layer layer, u16_t length, pbuf_type type);

For example, if the TCP protocol needs to apply for a pbuf packet, the following code will be called to apply:

p = pbuf_alloc(PBUF_TRANSPORT, 1472, PBUF_RAM);

The kernel will allocate a pbuf according to this code_ The data area size of ram type pbuf is 1472 bytes, and the protocol header space will be reserved according to the protocol level. As it is the transport layer, the kernel needs to reserve 54 bytes of space, that is, the Ethernet frame header length PBUF_LINK_HLEN (14 bytes), IP datagram header length PBUF_IP_HLEN (20 bytes), TCP header length PBUF_TRANSPORT_HLEN (20 bytes). When the datagram is submitted to the next layer, other layers
You can directly fill in the corresponding protocol header without copying the data, which is also the advantage of LwIP's fast processing.

/**
 * @ingroup pbuf
 * Allocates a pbuf of the given type (possibly a chain for PBUF_POOL type).
 *
 * The actual memory allocated for the pbuf is determined by the
 * layer at which the pbuf is allocated and the requested size
 * (from the size parameter).
 *
 * @param layer header size
 * @param length size of the pbuf's payload
 * @param type this parameter decides how and where the pbuf
 * should be allocated as follows:
 *
 * - PBUF_RAM: buffer memory for pbuf is allocated as one large
 *             chunk. This includes protocol headers as well.
 * - PBUF_ROM: no buffer memory is allocated for the pbuf, even for
 *             protocol headers. Additional headers must be prepended
 *             by allocating another pbuf and chain in to the front of
 *             the ROM pbuf. It is assumed that the memory used is really
 *             similar to ROM in that it is immutable and will not be
 *             changed. Memory which is dynamic should generally not
 *             be attached to PBUF_ROM pbufs. Use PBUF_REF instead.
 * - PBUF_REF: no buffer memory is allocated for the pbuf, even for
 *             protocol headers. It is assumed that the pbuf is only
 *             being used in a single thread. If the pbuf gets queued,
 *             then pbuf_take should be called to copy the buffer.
 * - PBUF_POOL: the pbuf is allocated as a pbuf chain, with pbufs from
 *              the pbuf pool that is allocated during pbuf_init().
 *
 * @return the allocated pbuf. If multiple pbufs where allocated, this
 * is the first pbuf of a pbuf chain.
 */
struct pbuf *pbuf_alloc(pbuf_layer layer, u16_t length, pbuf_type type)
{
    struct pbuf *p;
    u16_t offset = (u16_t)layer;
    LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_alloc(length=%"U16_F")\n", length));

    switch (type)
    {
        case PBUF_REF: /* fall through */
        case PBUF_ROM:
            p = pbuf_alloc_reference(NULL, length, type);
            break;

        case PBUF_POOL:
        {
            struct pbuf *q, *last;
            u16_t rem_len; /* remaining length */
            p = NULL;
            last = NULL;
            rem_len = length;

            do
            {
                u16_t qlen;
                q = (struct pbuf *)memp_malloc(MEMP_PBUF_POOL);

                if (q == NULL)
                {
                    PBUF_POOL_IS_EMPTY();

                    /* free chain so far allocated */
                    if (p)
                    {
                        pbuf_free(p);
                    }

                    /* bail out unsuccessfully */
                    return NULL;
                }

                qlen = LWIP_MIN(rem_len, (u16_t)(PBUF_POOL_BUFSIZE_ALIGNED - LWIP_MEM_ALIGN_SIZE(offset)));
                pbuf_init_alloced_pbuf(q, LWIP_MEM_ALIGN((void *)((u8_t *)q + SIZEOF_STRUCT_PBUF + offset)), rem_len, qlen, type, 0);
                LWIP_ASSERT("pbuf_alloc: pbuf q->payload properly aligned", ((mem_ptr_t)q->payload % MEM_ALIGNMENT) == 0);
                LWIP_ASSERT("PBUF_POOL_BUFSIZE must be bigger than MEM_ALIGNMENT", (PBUF_POOL_BUFSIZE_ALIGNED - LWIP_MEM_ALIGN_SIZE(offset)) > 0);

                if (p == NULL)
                {
                    /* allocated head of pbuf chain (into p) */
                    p = q;
                }
                else
                {
                    /* make previous pbuf point to this pbuf */
                    last->next = q;
                }

                last = q;
                rem_len = (u16_t)(rem_len - qlen);
                offset = 0;
            } while (rem_len > 0);

            break;
        }

        case PBUF_RAM:
        {
            u16_t payload_len = (u16_t)(LWIP_MEM_ALIGN_SIZE(offset) + LWIP_MEM_ALIGN_SIZE(length));
            mem_size_t alloc_len = (mem_size_t)(LWIP_MEM_ALIGN_SIZE(SIZEOF_STRUCT_PBUF) + payload_len);

            /* bug #50040: Check for integer overflow when calculating alloc_len */
            if ((payload_len < LWIP_MEM_ALIGN_SIZE(length)) || (alloc_len < LWIP_MEM_ALIGN_SIZE(length)))
            {
                return NULL;
            }

            /* If pbuf is to be allocated in RAM, allocate memory for it. */
            p = (struct pbuf *)mem_malloc(alloc_len);

            if (p == NULL)
            {
                return NULL;
            }

            pbuf_init_alloced_pbuf(p, LWIP_MEM_ALIGN((void *)((u8_t *)p + SIZEOF_STRUCT_PBUF + offset)), length, length, type, 0);
            LWIP_ASSERT("pbuf_alloc: pbuf->payload properly aligned", ((mem_ptr_t)p->payload % MEM_ALIGNMENT) == 0);
            break;
        }

        default:
            LWIP_ASSERT("pbuf_alloc: erroneous type", 0);
            return NULL;
    }

    LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_alloc(length=%"U16_F") == %p\n", length, (void *)p));
    return p;
}
  • pbuf_free()
    The ref field in pbuf records the number of times pbuf packets are referenced. When applying for pbuf, the ref field is initialized to 1. When releasing pbuf, first reduce ref by 1. If ref is reduced to 0, it means that pbuf packets can be released. In addition, pbuf packets that can be released by the kernel can only be the first node or nodes not referenced elsewhere.
    The packet release function of LwIP will automatically delete all pbufs belonging to a packet, including the first node. For example, if a packet needs three pbufs connected, when deleting the first pbuf, the kernel will check whether the next pbuf releases the data of the same packet as the first node. If so, delete the second node, Similarly, the third one will be deleted. However, if the ref field in the pbuf of the second node in the list is not 0 when deleting the first node of a pbuf linked list, it means that the node is still referenced elsewhere. If the second node does not store the same packet as the first node, the second node will not be deleted.


    Be careful when releasing pbuf. If the pbuf is in a linked list, the pbuf will reduce the ref value of the pbuf by 1 when it is released, and then the function will judge whether the ref becomes 0 after it is reduced. If it is 0, the memory pool or memory heap recycling function will be called according to the type of pbuf for collection. Then there is a very dangerous thing here, for this pbuf_ For the free () function, the parameter passed by the user must be the chain header pointer. If it is not the chain header but a pointer to a pbuf in the middle of the chain list, it is easy to cause problems because of this pbuf_ The free () function will not help us check whether the chain header is correct. In this way, some pbuf will not be recycled, which means that some memory pools have been leaked and can not be used in the future. At the same time, some unprocessed data may be recycled, so that the whole subsystem will be out of order.
/**
 * @ingroup pbuf
 * Dereference a pbuf chain or queue and deallocate any no-longer-used
 * pbufs at the head of this chain or queue.
 *
 * Decrements the pbuf reference count. If it reaches zero, the pbuf is
 * deallocated.
 *
 * For a pbuf chain, this is repeated for each pbuf in the chain,
 * up to the first pbuf which has a non-zero reference count after
 * decrementing. So, when all reference counts are one, the whole
 * chain is free'd.
 *
 * @param p The pbuf (chain) to be dereferenced.
 *
 * @return the number of pbufs that were de-allocated
 * from the head of the chain.
 *
 * @note MUST NOT be called on a packet queue (Not verified to work yet).
 * @note the reference counter of a pbuf equals the number of pointers
 * that refer to the pbuf (or into the pbuf).
 *
 * @internal examples:
 *
 * Assuming existing chains a->b->c with the following reference
 * counts, calling pbuf_free(a) results in:
 *
 * 1->2->3 becomes ...1->3
 * 3->3->3 becomes 2->3->3
 * 1->1->2 becomes ......1
 * 2->1->1 becomes 1->1->1
 * 1->1->1 becomes .......
 *
 */
u8_t pbuf_free(struct pbuf *p)
{
    u8_t alloc_src;
    struct pbuf *q;
    u8_t count;

    if (p == NULL)
    {
        LWIP_ASSERT("p != NULL", p != NULL);
        /* if assertions are disabled, proceed with debug output */
        LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("pbuf_free(p == NULL) was called.\n"));
        return 0;
    }

    LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_free(%p)\n", (void *)p));
    PERF_START;
    count = 0;

    /* de-allocate all consecutive pbufs from the head of the chain that
     * obtain a zero reference count after decrementing*/
    while (p != NULL)
    {
        LWIP_PBUF_REF_T ref;
        SYS_ARCH_DECL_PROTECT(old_level);
        /* Since decrementing ref cannot be guaranteed to be a single machine operation
         * we must protect it. We put the new ref into a local variable to prevent
         * further protection. */
        SYS_ARCH_PROTECT(old_level);
        /* all pbufs in a chain are referenced at least once */
        LWIP_ASSERT("pbuf_free: p->ref > 0", p->ref > 0);
        /* decrease reference count (number of pointers to pbuf) */
        ref = --(p->ref);
        SYS_ARCH_UNPROTECT(old_level);

        /* this pbuf is no longer referenced to? */
        if (ref == 0)
        {
            /* remember next pbuf in chain for next iteration */
            q = p->next;
            LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_free: deallocating %p\n", (void *)p));
            alloc_src = pbuf_get_allocsrc(p);
            #if LWIP_SUPPORT_CUSTOM_PBUF

            /* is this a custom pbuf? */
            if ((p->flags & PBUF_FLAG_IS_CUSTOM) != 0)
            {
                struct pbuf_custom *pc = (struct pbuf_custom *)p;
                LWIP_ASSERT("pc->custom_free_function != NULL", pc->custom_free_function != NULL);
                pc->custom_free_function(p);
            }
            else
            #endif /* LWIP_SUPPORT_CUSTOM_PBUF */
            {
                /* is this a pbuf from the pool? */
                if (alloc_src == PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF_POOL)
                {
                    memp_free(MEMP_PBUF_POOL, p);
                    /* is this a ROM or RAM referencing pbuf? */
                }
                else if (alloc_src == PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF)
                {
                    memp_free(MEMP_PBUF, p);
                    /* type == PBUF_RAM */
                }
                else if (alloc_src == PBUF_TYPE_ALLOC_SRC_MASK_STD_HEAP)
                {
                    mem_free(p);
                }
                else
                {
                    /* @todo: support freeing other types */
                    LWIP_ASSERT("invalid pbuf type", 0);
                }
            }

            count++;
            /* proceed to next pbuf */
            p = q;
            /* p->ref > 0, this pbuf is still referenced to */
            /* (and so the remaining pbufs in chain as well) */
        }
        else
        {
            LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_free: %p has ref %"U16_F", ending here.\n", (void *)p, (u16_t)ref));
            /* stop walking through the chain */
            p = NULL;
        }
    }

    PERF_STOP("pbuf_free");
    /* return number of de-allocated pbufs */
    return count;
}
  • pbuf_header()
    pbuf_ The header () function is used to adjust the pbuf's payload pointer (move a certain number of bytes forward or backward). As mentioned earlier, some protocol header space may be reserved in front of the pbuf's data area. When the pbuf is created, the payload pointer points to the data area. In order to realize the operation of these reserved spaces, you can call pbuf_ The header () function makes the payload pointer point to the header field in front of the data area, which provides convenience for each layer to operate the packet header. Of course, when doing this, len and tot_ The len field value is also updated.
/**
 * Adjusts the payload pointer to hide headers in the payload.
 *
 * Adjusts the ->payload pointer so that space for a header
 * disappears in the pbuf payload.
 *
 * The ->payload, ->tot_len and ->len fields are adjusted.
 *
 * @param p pbuf to change the header size.
 * @param header_size_decrement Number of bytes to decrement header size which
 *          decreases the size of the pbuf.
 *          If header_size_decrement is 0, this function does nothing and returns successful.
 * @return non-zero on failure, zero on success.
 *
 */
u8_t pbuf_remove_header(struct pbuf *p, size_t header_size_decrement)
{
    void *payload;
    u16_t increment_magnitude;
    LWIP_ASSERT("p != NULL", p != NULL);

    if ((p == NULL) || (header_size_decrement > 0xFFFF))
    {
        return 1;
    }

    if (header_size_decrement == 0)
    {
        return 0;
    }

    increment_magnitude = (u16_t)header_size_decrement;
    /* Check that we aren't going to move off the end of the pbuf */
    LWIP_ERROR("increment_magnitude <= p->len", (increment_magnitude <= p->len), return 1;);
    /* remember current payload pointer */
    payload = p->payload;
    LWIP_UNUSED_ARG(payload); /* only used in LWIP_DEBUGF below */
    /* increase payload pointer (guarded by length check above) */
    p->payload = (u8_t *)p->payload + header_size_decrement;
    /* modify pbuf length fields */
    p->len = (u16_t)(p->len - increment_magnitude);
    p->tot_len = (u16_t)(p->tot_len - increment_magnitude);
    LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_remove_header: old %p new %p (%"U16_F")\n", (void *)payload, (void *)p->payload, increment_magnitude));
    return 0;
}

/**
 * Adjusts the payload pointer to reveal headers in the payload.
 * @see pbuf_add_header.
 *
 * @param p pbuf to change the header size.
 * @param header_size_increment Number of bytes to increment header size.
 * @param force Allow 'header_size_increment > 0' for PBUF_REF/PBUF_ROM types
 *
 * @return non-zero on failure, zero on success.
 *
 */
static u8_t pbuf_add_header_impl(struct pbuf *p, size_t header_size_increment, u8_t force)
{
    u16_t type_internal;
    void *payload;
    u16_t increment_magnitude;
    LWIP_ASSERT("p != NULL", p != NULL);

    if ((p == NULL) || (header_size_increment > 0xFFFF))
    {
        return 1;
    }

    if (header_size_increment == 0)
    {
        return 0;
    }

    increment_magnitude = (u16_t)header_size_increment;

    /* Do not allow tot_len to wrap as a result. */
    if ((u16_t)(increment_magnitude + p->tot_len) < increment_magnitude)
    {
        return 1;
    }

    type_internal = p->type_internal;

    /* pbuf types containing payloads? */
    if (type_internal & PBUF_TYPE_FLAG_STRUCT_DATA_CONTIGUOUS)
    {
        /* set new payload pointer */
        payload = (u8_t *)p->payload - header_size_increment;

        /* boundary check fails? */
        if ((u8_t *)payload < (u8_t *)p + SIZEOF_STRUCT_PBUF)
        {
            LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_add_header: failed as %p < %p (not enough space for new header size)\n", (void *)payload, (void *)((u8_t *)p + SIZEOF_STRUCT_PBUF)));
            /* bail out unsuccessfully */
            return 1;
        }

        /* pbuf types referring to external payloads? */
    }
    else
    {
        /* hide a header in the payload? */
        if (force)
        {
            payload = (u8_t *)p->payload - header_size_increment;
        }
        else
        {
            /* cannot expand payload to front (yet!)
             * bail out unsuccessfully */
            return 1;
        }
    }

    LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_add_header: old %p new %p (%"U16_F")\n", (void *)p->payload, (void *)payload, increment_magnitude));
    /* modify pbuf fields */
    p->payload = payload;
    p->len = (u16_t)(p->len + increment_magnitude);
    p->tot_len = (u16_t)(p->tot_len + increment_magnitude);
    return 0;
}

static u8_t pbuf_header_impl(struct pbuf *p, s16_t header_size_increment, u8_t force)
{
    if (header_size_increment < 0)
    {
        return pbuf_remove_header(p, (size_t) - header_size_increment);
    }
    else
    {
        return pbuf_add_header_impl(p, (size_t)header_size_increment, force);
    }
}

/**
 * Adjusts the payload pointer to hide or reveal headers in the payload.
 *  * Adjusts the ->payload pointer so that space for a header
 * (dis)appears in the pbuf payload.
 *  * The ->payload, ->tot_len and ->len fields are adjusted.
 *  * @param p pbuf to change the header size.
 * @param header_size_increment Number of bytes to increment header size which
 * increases the size of the pbuf. New space is on the front.
 * (Using a negative value decreases the header size.)
 * If header_size_increment is 0, this function does nothing and returns successful.
 *  * PBUF_ROM and PBUF_REF type buffers cannot have their sizes increased, so
 * the call will fail. A check is made that the increase in header size does
 * not move the payload pointer in front of the start of the buffer.
 * @return non-zero on failure, zero on success.
 *  */
u8_t pbuf_header(struct pbuf *p, s16_t header_size_increment)
{
    return pbuf_header_impl(p, header_size_increment, 0);
}
  • pbuf_chain()
/**
 * @ingroup pbuf
 * Concatenate two pbufs (each may be a pbuf chain) and take over
 * the caller's reference of the tail pbuf.
 *
 * @note The caller MAY NOT reference the tail pbuf afterwards.
 * Use pbuf_chain() for that purpose.
 *
 * This function explicitly does not check for tot_len overflow to prevent
 * failing to queue too long pbufs. This can produce invalid pbufs, so
 * handle with care!
 *
 * @see pbuf_chain()
 */
void pbuf_cat(struct pbuf *h, struct pbuf *t)
{
    struct pbuf *p;
    LWIP_ERROR("(h != NULL) && (t != NULL) (programmer violates API)", ((h != NULL) && (t != NULL)), return;);

    /* proceed to last pbuf of chain */
    for (p = h; p->next != NULL; p = p->next)
    {
        /* add total length of second chain to all totals of first chain */
        p->tot_len = (u16_t)(p->tot_len + t->tot_len);
    }

    /* { p is last pbuf of first h chain, p->next == NULL } */
    LWIP_ASSERT("p->tot_len == p->len (of last pbuf in chain)", p->tot_len == p->len);
    LWIP_ASSERT("p->next == NULL", p->next == NULL);
    /* add total length of second chain to last pbuf total of first chain */
    p->tot_len = (u16_t)(p->tot_len + t->tot_len);
    /* chain last pbuf of head (p) with first of tail (t) */
    p->next = t;
    /* p->next now references t, but the caller will drop its reference to t,
     * so netto there is no change to the reference count of t.
     */
}

/**
 * @ingroup pbuf
 * Increment the reference count of the pbuf.
 *
 * @param p pbuf to increase reference counter of
 *
 */
void pbuf_ref(struct pbuf *p)
{
    /* pbuf given? */
    if (p != NULL)
    {
        SYS_ARCH_SET(p->ref, (LWIP_PBUF_REF_T)(p->ref + 1));
        LWIP_ASSERT("pbuf ref overflow", p->ref > 0);
    }
}

/**
 * @ingroup pbuf
 * Chain two pbufs (or pbuf chains) together.
 *  * The caller MUST call pbuf_free(t) once it has stopped
 * using it. Use pbuf_cat() instead if you no longer use t.
 *  * @param h head pbuf (chain)
 * @param t tail pbuf (chain)
 * @note The pbufs MUST belong to the same packet.
 * @note MAY NOT be called on a packet queue.
 *  * The ->tot_len fields of all pbufs of the head chain are adjusted.
 * The ->next field of the last pbuf of the head chain is adjusted.
 * The ->ref field of the first pbuf of the tail chain is adjusted.
 *  */
void pbuf_chain(struct pbuf *h, struct pbuf *t)
{
    pbuf_cat(h, t);
    /* t is now referenced by h */
    pbuf_ref(t);
    LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_chain: %p references %p\n", (void *)h, (void *)t));
}
  • pbuf_dechain()
/**
 * Dechains the first pbuf from its succeeding pbufs in the chain.
 *
 * Makes p->tot_len field equal to p->len.
 * @param p pbuf to dechain
 * @return remainder of the pbuf chain, or NULL if it was de-allocated.
 * @note May not be called on a packet queue.
 */
struct pbuf *pbuf_dechain(struct pbuf *p)
{
    struct pbuf *q;
    u8_t tail_gone = 1;
    /* tail */
    q = p->next;

    /* pbuf has successor in chain? */
    if (q != NULL)
    {
        /* assert tot_len invariant: (p->tot_len == p->len + (p->next? p->next->tot_len: 0) */
        LWIP_ASSERT("p->tot_len == p->len + q->tot_len", q->tot_len == p->tot_len - p->len);
        /* enforce invariant if assertion is disabled */
        q->tot_len = (u16_t)(p->tot_len - p->len);
        /* decouple pbuf from remainder */
        p->next = NULL;
        /* total length of pbuf p is its own length only */
        p->tot_len = p->len;
        /* q is no longer referenced by p, free it */
        LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_dechain: unreferencing %p\n", (void *)q));
        tail_gone = pbuf_free(q);

        if (tail_gone > 0)
        {
            LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_dechain: deallocated %p (as it is no longer referenced)\n", (void *)q));
        }

        /* return remaining tail or NULL if deallocated */
    }

    /* assert tot_len invariant: (p->tot_len == p->len + (p->next? p->next->tot_len: 0) */
    LWIP_ASSERT("p->tot_len == p->len", p->tot_len == p->len);
    return ((tail_gone > 0) ? NULL : q);
}

The above contents are applied from the practical guide for [wildfire] LwIP application development.

Keywords: network server net TCP/IP

Added by kontesto on Wed, 05 Jan 2022 07:23:04 +0200