前言
本文档介绍了lwIP(A Lightweight TCP/IP stack)的相关内容,包括lwIP简介、应用开发、网络安全和常见问题。本文档主要用于适配lwIP和Huawei LiteOS,同时也用于实现如DNS客户端、DHCP服务器等附加特性。
与本文档相对应的产品版本如下。
本文档主要适用于以下对象:
测试工程师
软件开发工程师
在本文中可能出现下列标志,它们所代表的含义如下。
概述
背景介绍
为了实现将智能家居芯片迁移到轻量级操作系统和TCP/IP协议栈上,开发了lwIP,其特点如下:
Lite Operating System是RTOS,意为“轻量级的实时操作系统”;专门针对Cortex-M series、Cortex-R series、Cortex-A series芯片架构而设计的RTOS,该系统主要针对智能终端/穿戴式设备定制,目前已有成熟商业应用。
在开源lwIP基础上,实现了DHCP服务器等附加特性。
第三方参考
本文档提供客户第三方应用参考如下:
SNTP客户端:此lwIP没有重新实现SNTP(Simple Network Time Protocol)客户端,但lwIP contrib开源中已经有实现了的SNTP客户端,可供用户使用。
操作系统适配层代码:lwIP contrib中有适配到Linux平台的代码,LiteOS支持大部分的POSIX(Portable Operating System Interface of UNIX)接口,在集成时使用到此代码。
RFC(Request For Comments)遵从
说明: 按照业界协议的标准实现,和其他芯片交互可以正常使用,无私有化的协议,这些协议仅少部分未完全遵从。
lwIP遵从如下RFC协议标准:
RFC 791 (IPv4 Standard)
RFC 2460 (IPv6 Standard)
RFC 768 (UDP) User Datagram Protocol
RFC 793 (TCP) Transmission Control Protocol
RFC 792 (ICMP) Internet Control Message Protocol
RFC 826 (ARP) Address Resolution Protocol
RFC 1035(DNS)DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION
RFC 2030(SNTP)Simple Network Timer Protocal(SNTP) Version 4 for IPv4,IPv6 and OSI
RFC 2131 (DHCP) Dynamic Host Configuration Protocol
RFC 2018 (SACK) TCP Selective Acknowledgment Options
RFC 7323 (Window Scaling)
RFC 6675 (SACK for TCP) RFC 3927 (Autoip) Dynamic Configuration of IPv4 Link-Local Addresses
RFC 2236 (IGMP) Internet Group Management Protocol, Version 2
RFC4861 (ND for IPv6) Neighbor Discovery for IP version 6 (IPv6)
RFC4443 Internet Control Message Protocol (ICMPv6) for the Internet Protocol Version 6 (IPv6) Specification
RFC4862 IPv6 Stateless Address Autoconfiguration
RFC2710 Multicast Listener Discovery (MLD) for IPv6
功能特性
支持的特性
lwIP支持的特性如下:
网际协议版本4(IPv4:Internet Protocol version 4)
IPv4是一种无连接的协议,在分组交换网络中使用。此协议会尽最大努力交付数据包,意即它不保证无丢包,也不保证所有数据包均按照正确的顺序无重复地到达。
互联网控制消息协议(ICMP:Internet Control Message Protocol)
互联网协议族的核心协议之一。该协议用于发送“主机不可达”等通知消息,也可以发送“回显请求”和“回显回复”等Ping消息。
用户数据报协议(UDP:User Datagram Protocol)
互联网协议族的核心协议之一。该协议使用简单的无连接的传输模型和最小协议机制,这种协议提供无连接的数据包服务,低延迟,可靠性稍弱。因此,该协议会将底层网络协议的不可靠性暴露给用户程序,不能保证无丢包、有序及无重复。UDP提供校验和用于数据完整性,以及端口号用于寻址数据包的源和目的地等不同功能。
传输控制协议(TCP:Transmission Control Protocol)
互联网协议族的核心协议之一。该协议起源于最初的网络实现,它补充了IP。因此,整个协议族通常称为TCP/IP。TCP为IP网络上主机的应用程序提供可靠、有序和纠错的传输。不需要可靠数据流服务的应用程序可以使用UDP。
域名解析器(DNS:Domain Name System)客户端
DNS是一个分层的分布式命名系统,用于计算机、服务或连接到互联网或私有网络的资源。lwIP支持基于RFC 1035协议的,特性少的DNS客户端(支持域名解析)。
在lwip中,DNS在A记录和AAAA记录之间采用了可配置的回退机制。
回退机制意味着,如果主机名解析到“A”记录失败,那么DNS会自动尝试将主机名解析为“AAAA”记录,或反之亦然,而不通知应用程序主机名解析到“A”记录失败。
DNS响应依赖于配置参数LWIP_DNS_ADDRTYPE_DEFAULT。该参数取值如下:
LWIP_DNS_ADDRTYPE_IPV4
DNS只响应客户端请求解析的主机名的A记录。
LWIP_DNS_ADDRTYPE_IPV6
DNS只响应客户端请求解析的主机名的AAAA记录。
LWIP_DNS_ADDRTYPE_IPV4_IPV6
优先解析A记录,如果失败,则回落到AAAA记录。
LWIP_DNS_ADDRTYPE_IPV6_IPV4
优先解析AAAA记录,如果失败,则回落到A记录。
lwip支持为单个主机名获取多个已解析的IP地址。
“count”参数中应提供客户端所需解析的IP地址数量。
“count”的最小值应为1,如果“count”小于1,则DNS解析失败,返回ERR _ ARG。
“count”的最大值等于可配置的DNS_MAX_IPADDR参数。
如果给定的“count”值大于DNS_MAX_IPADDR,那么主机名DNS_MAX_IPADDR最大解析IP地址只会返回给应用程序。
DNS接口可以以阻塞或非阻塞的方式调用。
说明: lwIP 2.1.3上的DNS不会验证或更改DNS服务器提供的已解析的IP地址。DNS只会检查解析后的IP地址是否正确。因此,被解析的IP地址将直接返回给应用程序。
动态主机配置协议(DHCP)客户端和服务器
一种在IP网络上使用的标准化网络协议,用于动态分配网络配置参数,如接口IP地址、业务IP地址等。通过DHCP,计算机自动从DHCP服务器请求IP地址和网络参数,减少了网络管理员或用户手动配置的工作量。lwIP支持基于RFC 2131协议的DHCP协议的客户端和服务器。
如果DHCPv4范围(地址池)使用缺省配置,则start_ip和ip_num必须为NULL。
如果需要手动配置DHCPv4范围(地址池),则start_ip和ip_num不能为NULL。
无论使用缺省地址范围还是手动配置地址范围,可分配IP是否少一个并不决定于是否使用缺省地址范围,而是取决于服务器的IP地址是否在地址池的范围内: 如果在地址池的范围内,则少一个;如果不在地址池的范围内,则不会少一个。
注意:现在缺省地址池的范围是从2开始。
配置DHCPv4范围(地址池)时,如果DHCPv4服务器的IP地址在(ip_start,ip_start + ip_num)范围内,则客户端池中可用的地址总数将小于ip_num,因为池中的一个地址将被DHCPv4服务器占用。
DHCPv4范围内的地址总数将是ip_num和LWIP_DHCPS_MAX_LEASE的最小值。
手动配置DHCPv4范围时,如果ip_start + ip_num超过x.y.z.254,则池中的地址总数只能从(ip_start,x.y.z.254)开始, 如果子网掩码是255.255.0.0或255.255.255.128,则上述条件不适用。
DNS SERVER OPTION 是由DHCP服务器返回给客户端的,DISCOVER和REQUEST报文不会携带该OPTION,在lwIP 2.1.2中,该DNS服务器地址有以下两种情况:
如果配置了IPv4的DNS服务器,将会返回配置的DNS地址(可能有2个)。
如果没有配置DNS地址,将会返回DHCP服务器的接口IP地址。
DHCPv4服务器只响应主DNS服务器地址,不提供备DNS服务器地址;如果系统配置了两个DNS地址,则会将2个DNS服务器地址都返回。
RFC 2131偏差标准
根据RFC 2131协议正文的4.3.2节,DHCPv4服务器在DHCP REQUEST报文的Init-Reboot、Renew或Rebinding状态中检查客户端是否填充了server_id,如果客户端在这些状态下填充了server_id,则服务器不会接受这些报文。但在lwIP 2.1.2中,DHCPv4服务器没有考虑server_id报文的Init-Reboot、Renew或Rebinding状态。
以太网地址解析协议(ARP)
ARP是一种用于将网络层地址解析为链路层地址的电信协议,是多址网络中的一个重要功能。基于RFC 826协议的Huawei LiteOS lwIP2.1.2支持ARP协议和可配置的ARP表。
因特网组管理协议(IGMP)
IGMP协议用于在主机和本地路由器之间传递组播组成员的信息。lwIP目前只支持基于RFC 2236协议的IGMP v2协议。
socket接口类型
lwIP提供了以下几种类型的socket接口:
非线程安全的低级接口
线程安全的Netconn接口
内部调用Netconn接口的BSD(Berkeley Software Distribution)式接口。提供Berkeley Software Distribution式接口,帮助应用从Linux TCP/IP堆栈平滑迁移到lwIP TCP/IP堆栈。
TCP选择性确认选项
此选项用于确认堆栈接收到的失序列段,以便在丢失恢复期间可以跳过这些选择性确认的列段进行重传。
须知: LWIP相对开源代码优化如下:
支持丰富的AT调测命令。
多项RFC遵从性整改,有助于提高对商用路由的兼容性 。
提供标准的tcp sack流控算法(RFC2018),提高丢包场景下的传输性能 。
完善ipv6协议支持,如扩展头域处理、Socket Interface Extensions。
新增RDNS协议和RPL协议。
不支持的特性
lwIP不支持的特性或协议如下:
PPPoS/PPPOE
SNMP代理(目前,lwIP仅支持私有MIB)
通过多个网络接口实现IP转发
路由功能(仅支持终端设备功能)
lwIP小型化的特性如下:
简单网络时间协议(Simple Network Time Protocol)
SNTP(Simple Network Time Protocol)是一种基于包交换、可变延迟数据网络的计算机系统之间的时钟同步网络协议。lwIP支持基于RFC 2030协议的SNTP版本4。
lwip_get_conn_info()接口有如下限制:
调用该函数获取TCP或UDP连接信息。
空指针是tcpip_conn类型。
在LISTEN状态下,此接口不支持获取TCP套接字连接信息。
TCP窗口扩大选项
lwIP支持基于RFC 7323协议的TCP窗口扩大选项。
TCP中的窗口扩大选项允许在TCP连接中使用30位的窗口大小,而不是16位。
窗口扩大功能将TCP窗口的像素扩展到30位,然后使用隐式缩放因子在TCP报头的16位窗口字段中携带这个30位的值。
缩放因子的指数携带在一个名为窗口扩大的TCP选项中。
lwIP支持AutoIP模块。
lwIP提供设置Wi-Fi驱动状态的接口。
lwIP提供设置Wi-Fi驱动状态为lwIP栈的接口。
当Wi-Fi驱动忙时,lwIP栈停止发送消息;
当Wi-Fi驱动ready时,lwIP栈继续发送消息。
如果在DRIVER_WAKEUP_INTERVAL超时之前,驱动程序没有从繁忙状态唤醒,那么所有使用这个Netif驱动的TCP连接都会被删除。因此,阻塞连接调用将等待Netif驱动程序唤醒,SYN(chronize Sequence NumbersSyn)重传,或等待TCP连接超时被清除。
lwIP在SOCK_RAW上提供PF_PACKET选项。
lwIP支持PF_PACKET族的SOCK_RAW。应用程序可以使用这个特性创建链路层套接字。因此,lwIP堆栈发送报文时期望应用程序中包含链路层标头。SOCK_RAW报文在设备驱动之间传递,报文数据无变化。
默认情况下,所有指定协议类型的报文都被传递到一个报文套接字。若要只从特定接口获取报文,请将该套接字绑定到指定接口。sll _ protocol和sll_ifindex地址字段用于绑定。当应用程序发送报文时,只需指定sll_family、sll_addr、sll_halen、sll_ifindex。其他字段应该为0,收到的报文设置为sll_hatype和sll_pkttype。
开发指引
lwIP支持以太网或Wi-Fi,应用须根据实际需求配置lwIP和适配驱动。
前提条件
lwIP需满足下列前提条件:
lwIP需要优化后方能支持Huawei LiteOS;目前lwIP暂不支持其他操作系统,具体请参见“优化lwIP”。
使用lwIP前,基于lwIP相关配置更新驱动代码。详情请参见“移植方法”中的伪代码。
依赖关系
lwIP的依赖关系如下:
依赖于Huawei LiteOS调用。
需要通过以太网或Wi-Fi驱动模块完成物理层数据的发送和接收。
lwIP的使用
lwIP提供了BSD TCP/IP套接字接口,应用可通过该接口进行连接。lwIP的优势在于:
运行在BSD TCP/IP协议栈上的旧应用代码可直接被移植到lwIP中。
lwIP支持DHCP客户端特性,用于配置动态IP地址。
lwIP支持DNS客户端特性,应用可通过该特性解析域名。
lwIP占用资源少,可满足协议栈功能。
须知: lwIP 2.1.3使用socket对外API,close()使用closesocket()接口替换,ioctl()接口使用lwip_ioctl()接口替换,避免和LiteOS的原生接口产生冲突。
移植方法
lwIP初始化
lwIP的初始化由tcpip_init()接口完成。该接口接收可选的回调函数及其参数(参数值也可以设为NULL)。一旦初始化完成,回调函数会被调用,并传入对应的参数。
示例代码如下:
void tcpip_init_func(void *arg) {
printf("lwIP initialization successfully done");
}
void Init_lwIP() {
tcpip_init(tcpip_init_func, NULL);
}
添加netif接口及驱动功能
初始化完成后,应用必须至少添加一个Netif接口用于通信。
应用需要根据驱动类型实现相关回调函数。相关链路层类型、MAC地址和发送回调函数使用前必须完成注册。如果需要支持原始套接字接口的混杂模式,则还需要在驱动中注册实现该功能的回调函数。
以太网的示例代码如下:
struct netif g_netif;
/* user_driver_send mentioned below is the pseudocode for the */
/* driver send function. It explains the prototype for the driver send function. */
/* User should implement this function based on their driver */
void user_driver_send(struct netif *netif, struct pbuf *p) {
/* This will be the send function of the driver */
/* It should send the data in pbuf p->payload of size p->tot_len */
}
/* user_driver_recv mentioned below is the pseudocode for the */
/* driver receive function. It explains how it should create pbuf */
/* and copy the incoming packets. User should implement this function */
/* based on their driver */
void user_driver_recv(char *data, int len) {
/* This should be the receive function of the user driver */
/* Once it receives the data it should do the following*/
struct pbuf *p = NULL;
struct pbuf *q = NULL;
p = pbuf_alloc(PBUF_RAW, (len + ETH_PAD_SIZE), PBUF_RAM);
if (p == NULL) {
printf("user_driver_recv : pbuf_alloc failed\n");
return;
}
#if ETH_PAD_SIZE
pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */
#endif
memcpy(p->payload, data, len);
#if ETH_PAD_SIZE
pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */
#endif
driverif_input(&gnetif, p);
}
void eth_drv_config(struct netif *netif, u32_t config_flags,u8_t setBit) {
/* Enable/Disable promiscuous mode in driver code. */
}
/* user_driver_init_func mentioned below is the pseudocode for the */
/* driver initialization function. It explains the lwIP configuration which needs to be */
/* done along with driver initialization. User should implement this function based on their driver */
void user_driver_init_func() {
ip4_addr_t ipaddr, netmask, gw;
/* After performing user driver initialization operation here */
/* lwIP driver configuration needs to be done*/
#ifndef lwIP_WITH_DHCP_CLIENT
IP4_ADDR(gw, 192, 168, 2, 1);
IP4_ADDR(ipaddr, 192, 168, 2, 5);
IP4_ADDR(netmask, 255, 255, 255, 0);
#endif
g_netif.link_layer_type = ETHERNET_DRIVER_IF;
g_netif.hwaddr_len = ETHARP_HWADDR_LEN;
g_netif.drv_send = user_driver_send;
memcpy(g_netif.hwaddr, driver_mac_address, ETHER_ADDR_LEN);
#if LWIP_NETIF_PROMISC
g_netif.drv_config = eth_drv_config;
#endif
#ifndef lwIP_WITH_DHCP_CLIENT
netifapi_netif_add(&g_netif, ipaddr, netmask, gw);
#else
netifapi_netif_add(&g_netif, 0, 0, 0);
#endif
/* lwIP configuration ends */
}
void Init_Configure_lwIP() {
Init_lwIP();
user_driver_init_func();
netifapi_netif_set_up(&g_netif);
#ifndef lwIP_WITH_DHCP_CLIENT
netifapi_dhcp_start(&g_netif);
do {
msleep(20);
} while (netifapi_dhcp_is_bound(&g_netif) != ERR_OK);
#endif
printf("Network is up !!!\n");
}
优化lwIP
优化吞吐量
不同的应用场景,为提升lwIP吞吐量,建议采用下列优化方案:
设置宏MEMP_NUM_UDP_PCB的值,即所需UDP连接数。
DHCP也创建了一个UDP连接,所以在设置该宏的值时也要予以考虑。
设置宏MEMP_NUM_TCP_PCB的值,即所需TCP连接数。
设置MEMP_NUM_RAW_PCB的值,即所需RAW连接数。
LWIP_ENABLE_LOS_SHELL_CMD模块使用一个RAW连接来执行ping命令。
设置MEMP_NUM_NETCONN的值,即所需UDP、TCP和RAW连接的总数。
如果LWIP_ENABLE_LOS_SHELL_CMD和RAW连接均未被使用,则禁用LWIP_RAW。
如果UDP未被使用,那么禁用LWIP_UDP。如果TCP未被使用,则禁用LWIP_TCP。
如果调用驱动接收函数中的pbuf_alloc()申请PBUF_RAM类型内存失败,则增加MEM_SIZE。
如果TCPIP mbox内收入报文空间不可用而导致driverif_input()调用失败,则增加TCPIP_MBOX_SIZE和MEMP_NUM_TCPIP_MSG_INPKT的值。如果驱动模块接收报文太快,则也会发生报文空间不可用导致driverif_input调用失败。
禁用所有调试选项,并且不定义LWIP_DEBUG。
如果架构字长为4,则设置ETH_PAD_SIZE为2。
优化内存
为节省lwIP内存空间,建议采用下列优化方案:
调用驱动接收函数中pbuf_alloc()申请PBUF_POOL类型内存,设置PBUF_POOL_SIZE的值(即所需pbuf数量),并根据MTU设置PBUF_POOL_BUFSIZE的值。
根据所需TCP、UDP和RAW以及总的连接数,设置MEMP_NUM_TCP_PCB、MEMP_NUM_UDP_PCB、MEMP_NUM_RAW_PCB和MEMP_NUM_NETCONN的值。
根据对端的实际发送数据量,可降低TCPIP_MBOX_SIZE和MEMP_NUM_TCPIP_MSG_INPKT的值。
禁用所有调试选项,并且不定义LWIP_DEBUG。
在lwipopts.h里面将改为#define LWIP_DBG_TYPES_ON LWIP_DBG_OFF。
禁用ETHARP_TRUST_IP_MAC。
如果启用此宏,则所有接收到的报文都会被用于更新ARP表。
如果禁用此宏,则只有进行ARP查询才会更新ARP表中项目内容。
定制化
在实际应用中,可能会需要定制一些功能,lwIP中新定义的宏的使用说明如下:
LWIP_DHCP
当用户启用DHCP服务器时,需要启用LWIP_DHCPS宏。如果LWIP_DHCPS宏被启用,则也必须启用LWIP_DHCP。
LWIP_DHCPS_DISCOVER_BROADCAST
通常DHCP服务器会根据Discover消息中由客户端设置的标志,广播或单播Offer报文。但如果用户希望一直广播Offer消息,则可以启用LWIP_DHCPS_DISCOVER_BROADCAST宏。
lwIP宏
说明: lwIP 2.1.3的配置宏由于开源很多,未全部描述,请参考开源链接说明:https://www.nongnu.org/lwip/2_1_x/index.html,如需修改,请联系相应接口人。
本节列出了lwIP宏,这些宏的描述如下(如需更改,请联系相应接口人):
lwIP宏的默认值在opt.h和lwipopts_default.h头文件中有定义。
lwipopts_default.h头文件中的宏定义会覆盖opt.h头文件中的宏定义。
表 1 宏列表
示例代码
本节列出了部分lwIP示例代码,对lwIP的用途进行了阐述。lwIP示例代码包含一个伪代码,说明了在驱动(以太网或Wi-Fi)模块上需要完成的更改。该示例代码与lwIP和Huawei LiteOS同时被海思平台交叉编译器编译。
须知: 该示例代码禁止直接商用。
应用示例代码
UDP示例代码
#include <stdio.h>
#include <netinet/in.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#define STACK_IP "192.168.2.5"
#define STACK_PORT 2277
#define PEER_PORT 3377
#define PEER_IP "192.168.2.2"
#define MSG "Hi, I am lwIP"
#define BUF_SIZE (1024 * 8)
typedef unsigned char u8_t;
typedef signed int s32_t;
u8_t g_buf[BUF_SIZE+1] = {0};
int sample_udp() {
s32_t sfd;
struct sockaddr_in srv_addr = {0};
struct sockaddr_in cln_addr = {0};
socklen_t cln_addr_len = sizeof(cln_addr);
s32_t ret = 0, i = 0;
/* socket creation */
printf("going to call socket\n");
sfd = socket(AF_INET,SOCK_DGRAM,0);
if (sfd == -1) {
printf("socket failed, return is %d\n", sfd);
goto FAILURE;
}
printf("socket succeeded\n");
srv_addr.sin_family = AF_INET;
srv_addr.sin_addr.s_addr=inet_addr(STACK_IP);
srv_addr.sin_port=htons(STACK_PORT);
printf("going to call bind\n");
ret = bind(sfd,(struct sockaddr*)&srv_addr, sizeof(srv_addr));
if (ret != 0) {
printf("bind failed, return is %d\n", ret);
goto FAILURE;
}
printf("bind succeeded\n");
/* socket creation */
/* send */
cln_addr.sin_family = AF_INET;
cln_addr.sin_addr.s_addr=inet_addr(PEER_IP);
cln_addr.sin_port=htons(PEER_PORT);
printf("calling sendto...\n");
memset(g_buf, 0, BUF_SIZE);
strcpy(g_buf, MSG);
ret = sendto(sfd, g_buf, strlen(MSG),
0, (struct sockaddr *)&cln_addr,
(socklen_t)sizeof(cln_addr));
if (ret <= 0) {
printf("sendto failed,return is %d\n", ret);
goto FAILURE;
}
printf("sendto succeeded,return is %d\n", ret);
/* send */
/* recv */
printf("going to call recvfrom\n");
memset(g_buf, 0, BUF_SIZE);
ret = recvfrom(sfd, g_buf, sizeof(g_buf), 0,
(struct sockaddr *)&cln_addr, &cln_addr_len);
if (ret <= 0) {
printf("recvfrom failed,
return is %d\n", ret);
goto FAILURE;
}
printf("recvfrom succeeded, return is %d\n", ret);
printf("received msg is : %s\n", g_buf);
printf("client ip %x, port %d\n",
cln_addr.sin_addr.s_addr,
cln_addr.sin_port);
/* recv */
close(sfd);
return 0;
FAILURE:
printf("failed, errno is %d\n", errno);
close(sfd);
return -1;
}
int main() {
int ret;
ret = sample_udp();
if (ret != 0) {
printf("Sample Test case failed\n");
exit(0);
}
return 0;
}
TCP客户端示例代码
#include <stdio.h>
#include <netinet/in.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#define STACK_IP "192.168.2.5"
#define STACK_PORT 2277
#define PEER_PORT 3377
#define PEER_IP "192.168.2.2"
#define MSG "Hi, I am lwIP"
#define BUF_SIZE (1024 * 8)
typedef unsigned char u8_t;
typedef signed int s32_t;
u8_t g_buf[BUF_SIZE+1] = {0};
/* Global variable for lwIP Network interface */
int sample_tcp_client() {
s32_t sfd = -1;
struct sockaddr_in srv_addr = {0};
struct sockaddr_in cln_addr = {0};
socklen_t cln_addr_len = sizeof(cln_addr);
s32_t ret = 0, i = 0;
/* tcp client connection */
printf("going to call socket\n");
sfd = socket(AF_INET,SOCK_STREAM,0);
if (sfd == -1) {
printf("socket failed, return is %d\n", sfd);
goto FAILURE;
}
printf("socket succeeded, sfd %d\n", sfd);
srv_addr.sin_family = AF_INET;
srv_addr.sin_addr.s_addr = inet_addr(PEER_IP);
srv_addr.sin_port = htons(PEER_PORT);
printf("going to call connect\n");
ret = connect(sfd, (struct sockaddr *)&srv_addr, sizeof(srv_addr));
if (ret != 0) {
printf("connect failed, return is %d\n", ret);
goto FAILURE;
}
printf("connec succeeded, return is %d\n", ret);
/* tcp client connection */
/* send */
memset(g_buf, 0, BUF_SIZE);
strcpy(g_buf, MSG);
printf("calling send...\n");
ret = send(sfd, g_buf, sizeof(MSG), 0);
if (ret <= 0) {
printf("send failed, return is %d,i is %d\n", ret, i);
goto FAILURE;
}
printf("send finished ret is %d\n", ret);
/* send */
/* recv */
memset(g_buf, 0, BUF_SIZE);
printf("going to call recv\n");
ret = recv(sfd, g_buf, sizeof(g_buf), 0);
if (ret <= 0) {
printf("recv failed, return is %d\n", ret);
goto FAILURE;
}
printf("recv succeeded, return is %d\n", ret);
printf("received msg is : %s\n", g_buf);
/* recv */
close(sfd);
return 0;
FAILURE:
close(sfd);
printf("errno is %d\n", errno);
return -1;
}
int main() {
int ret;
ret = sample_tcp_client();
if (ret != 0) {
printf("Sample Test case failed\n");
exit(0);
}
return 0;
}
TCP服务端示例代码
#include <stdio.h>
#include <netinet/in.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#define STACK_IP "192.168.2.5"
#define STACK_PORT 2277
#define PEER_PORT 3377
#define PEER_IP "192.168.2.2"
#define MSG "Hi, I am lwIP"
#define BUF_SIZE (1024 * 8)
typedef unsigned char u8_t;
typedef signed int s32_t;
u8_t g_buf[BUF_SIZE+1] = {0};
/* Global variable for lwIP Network interface */
int sample_tcp_server() {
s32_t sfd = -1;
s32_t lsfd = -1;
struct sockaddr_in srv_addr = {0};
struct sockaddr_in cln_addr = {0};
socklen_t cln_addr_len = sizeof(cln_addr);
s32_t ret = 0, i = 0;
/* tcp server */
printf("going to call socket\n");
lsfd = socket(AF_INET,SOCK_STREAM,0);
if (lsfd == -1) {
printf("socket failed, return is %d\n", lsfd);
goto FAILURE;
}
printf("socket succeeded\n");
srv_addr.sin_family = AF_INET;
srv_addr.sin_addr.s_addr = inet_addr(STACK_IP);
srv_addr.sin_port = htons(STACK_PORT);
ret = bind(lsfd, (struct sockaddr *)&srv_addr, sizeof(srv_addr));
if (ret != 0) {
printf("bind failed, return is %d\n", ret);
goto FAILURE;
}
ret = listen(lsfd, 0);
if (ret != 0) {
printf("listen failed, return is %d\n", ret);
goto FAILURE;
}
printf("listen succeeded, return is %d\n", ret);
printf("going to call accept\n");
sfd = accept(lsfd, (struct sockaddr *)&cln_addr, &cln_addr_len);
if (sfd < 0) {
printf("accept failed, return is %d\n", sfd);
}
printf("accept succeeded, return is %d\n", sfd);
/* tcp server */
/* send */
memset(g_buf, 0, BUF_SIZE);
strcpy(g_buf, MSG);
printf("calling send...\n");
ret = send(sfd, g_buf, sizeof(MSG), 0);
if (ret <= 0) {
printf("send failed, return is %d,
i is %d\n", ret, i);
goto FAILURE;
}
printf("send finished ret is %d\n", ret);
/* send */
/* recv */
memset(g_buf, 0, BUF_SIZE);
printf("going to call recv\n");
ret = recv(sfd, g_buf, sizeof(g_buf), 0);
if (ret <= 0) {
printf("recv failed, return is %d\n", ret);
goto FAILURE;
}
printf("recv succeeded, return is %d\n", ret);
printf("received msg is : %s\n", g_buf);
/* recv */
close(sfd);
close(lsfd);
return 0;
FAILURE:
close(sfd);
close(lsfd);
printf("errno is %d\n", errno);
return -1;
}
int main() {
int ret;
ret = sample_tcp_server();
if (ret != 0) {
printf("Sample Test case failed\n");
exit(0);
}
return 0;
}
DNS示例代码
#include <netdb.h>
#include "lwip/opt.h"
//#include "lwip/sockets.h"
//#include "lwip/netdb.h"
#include "lwip/err.h"
#include "lwip/inet.h"
#include "lwip/dns.h"
int gmutexFail;
int gmutexFailCount;
struct gethostbyname_r_helper {
ip_addr_t *addr_list[DNS_MAX_IPADDR+1];
ip_addr_t addr[DNS_MAX_IPADDR];
char *aliases;
};
void dns_call_with_unsafe_api() {
struct hostent *result = NULL;
int i = 0;
ip_addr_t *addr = NULL;
char addrString[20] = {0};
char *hostname;
char *dns_server_ip;
ip_addr_t dns_server_ipaddr;
hostname = "www.huawei.com";
dns_server_ip = "192.168.0.2";
inet_aton(dns_server_ip, &dns_server_ipaddr);
dns_setserver(0, &dns_server_ipaddr);
result = gethostbyname(hostname);
if (result)
{
while (1)
{
addr = *(((ip_addr_t **)result->h_addr_list) + i);
if (addr == NULL)
{
break;
}
inet_ntoa_r(*addr, addrString, 20);
printf("dns call for %s, returns %s\n",
hostname, addrString);
i++;
}
}
else
{
printf("dns call failed\n");
}
}
void dns_call_with_safe_api()
{
int i = 0;
ip_addr_t *addr = NULL;
char addrString[20] = {0};
char *buf = NULL;
int buflen;
char *hostname = NULL;
char *dns_server_ip = NULL;
ip_addr_t dns_server_ipaddr;
struct hostent ret;
struct hostent *result = NULL;
int h_errnop;
hostname = "www.huawei.com";
dns_server_ip = "192.168.0.2";
inet_aton(dns_server_ip, &dns_server_ipaddr);
lwip_dns_setserver(0, &dns_server_ipaddr);
buflen = sizeof(struct gethostbyname_r_helper) +
strlen(hostname) + MEM_ALIGNMENT;
buf = malloc(buflen);
gethostbyname_r(hostname, &ret, buf, buflen, &result, &h_errnop);
if (result)
{
while (1)
{
addr = *(((ip_addr_t **)result->h_addr_list) + i);
if (addr == NULL)
{
break;
}
inet_ntoa_r(*addr, addrString, 20);
printf("dns call for %s, returns %s\n",
hostname, addrString);
i++;
}
}
else
{
printf("dns call failed\n");
}
free(buf);
}
/* To get the dns server address configured in lwIP */
/* Application can call dns_getserver() API and then decide whether to
change it */
/* If application needs to change it then, it needs */
void display_dns_server_address()
{
int i;
ip_addr_t addr;
int ret;
char addrString[20] = {0};
for (i = 0; i < DNS_MAX_SERVERS; i++)
{
ret = lwip_dns_getserver(i, &addr);
if (ret != ERR_OK)
{
printf("dns_getserver failed\n");
return;
}
memset(addrString, 0, sizeof(addrString));
inet_ntoa_r(addr, addrString, 20);
printf("dns server address configured \
at index %d, is %s\n", i,
addrString);
}
return;
}
int main() {
/* after doing lwIP init, driver init and netifapi_netif_add */
display_dns_server_address();
dns_call_with_unsafe_api();
dns_call_with_safe_api();
return 0;
}
说明: lwIP内部通过dns_servers全局变量存储和管理DNS地址,通过DNS_MAX_SERVERS宏控制DNS存储的个数;在IPv4组网下,设备关联AP之后,通过DHCP的方式获取到DNS地址,通常是AP的网关地址;在IPv4和IPv6组网场景下,DNS地址除了来源于DHCP报文,还会来源于IPv6组网内的RA报文,这种组网场景下,设备可能同时存在IPv4和IPv6的DNS地址,协议栈默认使用dns_servers数组索引0位置的地址(IPv4或者IPv6),应用需要明确使用v4还是v6的DNS地址。
驱动相关示例代码
#include <string.h>
#include "lwip/ip.h"
unsigned char SUT_MAC[6] = { 0x46, 0x44, 0x2, 0x2, 0x3, 0x3 };
#define ETHER_ADDR_LEN 6
/* Global variable for lwIP Network interface */
struct netif g_netif;
/* user_driver_send mentioned below is the pseudocode for the */
/* driver send function. It explains the prototype for the driver send function.
*/
/* User should implement this function based on their driver */
void user_driver_send(struct netif *netif, struct pbuf *p)
{
/* This will be the send function of the
driver */
/* It should send the data in pbuf
p->payload of size p->tot_len */
}
void user_driver_init_func()
{
ip4_addr_t ipaddr, netmask, gw;
/* After performing user driver init
operation */
/* lwIP driver configuration needs to be
done*/
/* lwIP configuration starts */
IP4_ADDR(&gw, 192, 168, 2, 1);
IP4_ADDR(&ipaddr, 192, 168, 2, 5);
IP4_ADDR(&netmask, 255, 255, 255, 0);
g_netif.link_layer_type = ETHERNET_DRIVER_IF;
g_netif.hwaddr_len = ETHARP_HWADDR_LEN;
g_netif.drv_send = user_driver_send;
memcpy(g_netif.hwaddr, SUT_MAC, ETHER_ADDR_LEN);
netifapi_netif_add(&g_netif, &ipaddr, &netmask, &gw);
netifapi_netif_set_default(&g_netif);
/* lwIP configuratin ends */
}
/* user_driver_recv mentioned below is the pseudocode for the */
/* driver receive function. It explains how it should create pbuf */
/* and copy the incoming packets. User should implement this function */
/* based on their driver */
void user_driver_recv(char * data, int len)
{
/* This should be the receive function of
the user driver */
/* Once it receives the data it should do
the below */
struct pbuf *p;
p = pbuf_alloc(PBUF_RAW, (len +
ETH_PAD_SIZE), PBUF_RAM);
if (p == NULL)
{
printf("user_driver_recv : pbuf_alloc \
failed\n");
return;
}
#if ETH_PAD_SIZE
pbuf_header(p, -ETH_PAD_SIZE); /* drop the
padding word */
#endif
memcpy(p->payload, data, len);
#if ETH_PAD_SIZE
pbuf_header(p, ETH_PAD_SIZE); /* reclaim the
padding word */
#endif
driverif_input(&g_netif,p);
}
int main()
{
/* Call lwIP tcpip_init before driver init*/
tcpip_init(NULL, NULL);
user_driver_init_func();
return 0;
}
服务示例代码
SNTP示例代码
#include "lwip/opt.h"
//#include "lwip/sntp.h"
/* Compile time configuration for SNTP
* 1) Configure SNTP server address
*/
int gmutexFail;
int gmutexFailCount;
int start_sntp()
{
int ret;
int server_num = 1; /*Number of SNTP servers available*/
char *sntp_server = "192.168.0.2"; /*sntp_server : List of the available servers*/
struct timeval time_local; /*Output Local time of server, which will be received in NTP response from server*/
memset(&time_local, 0, sizeof(time_local));
ret = lwip_sntp_start(server_num, &sntp_server, &time_local);
printf("Recevied time from server = [%li]sec [%li]u sec\n", time_local.tv_sec, time_local.tv_usec);
/* After the SNTP time synchronization is complete, the time calibration is not performed periodically.
*/
return ret;
}
int main()
{
/* after doing lwIP init, driver init and
netifapi_netif_add */
start_sntp();
return 0;
}
DHCP客户端示例代码
#include <unistd.h>
#include "lwip/opt.h"
#include "lwip/netifapi.h"
#include "lwip/inet.h"
//#include "lwip/netif.h"
struct netif g_netif;
int gmutexFail;
int gmutexFailCount;
int dhcp_client_start(struct netif *pnetif)
{
int ret;
char addrString[20] = {0};
/* Calling netifapi_dhcp_start() will start
initiating DHCP configuration
* process by sending DHCP messages */
ret = netifapi_dhcp_start(pnetif);
if (ret == ERR_OK)
{
printf("dhcp client started \
successfully\n");
}
else
{
printf("dhcp client start failed\n");
}
/* After doing this it will get the IP and
update to netif, once it finishes
the process with DHCP server. Application
need to call netifapi_dhcp_is_bound()
API to check whether DHCP process is
finished or not */
do
{
sleep(1); /* sleep for sometime,
like 1 sec */
ret = netifapi_dhcp_is_bound(pnetif);
} while(ret != ERR_OK);
memset(addrString, 0, sizeof(addrString));
inet_ntoa_r(pnetif->ip_addr, addrString, 20);
printf("ipaddr %s\n", addrString);
memset(addrString, 0, sizeof(addrString));
inet_ntoa_r(pnetif->netmask, addrString, 20);
printf("netmask %s\n", addrString);
memset(addrString, 0, sizeof(addrString));
inet_ntoa_r(pnetif->gw, addrString, 20);
printf("gw %s\n", addrString);
return 0;
}
int main()
{
/* after doing lwIP init, driver init and
netifapi_netif_add */
dhcp_client_start(&g_netif);
/* Later if application wants to stop the
DHCP client then it should
call netifapi_dhcp_stop() and
netifapi_dhcp_cleanup() */
/* netifapi_dhcp_stop(&g_netif); */
/* netifapi_dhcp_cleanup(&g_netif); */
return 0;
}
DHCP服务端示例代码
#include "lwip/opt.h"
#include "lwip/netifapi.h"
#include "lwip/inet.h"
#include "lwip/netif.h"
struct netif g_netif;
int gmutexFail;
int gmutexFailCount;
int dhcp_server_start(struct netif *pnetif, char *startIP, int ipNum)
{
int ret;
/* Calling netifapi_dhcps_start() will start DHCP server */
if ( startIP == NULL )
{
/* For Automatic Configuration */
ret = netifapi_dhcps_start(pnetif, NULL, NULL);
}
else
{
/* For Manual Configuration */
ret = netifapi_dhcps_start(pnetif, startIP, ipNum);
}
if (ret == ERR_OK)
{
printf("dhcp server started successfully\n");
}
else
{
printf("dhcp server start failed\n");
}
}
int main()
{
/* after doing lwIP init, driver init and netifapi_netif_add */
/* DHCP Server Address Pool Configuration */
char *startIP; // IP from where the DHCP Server address pool has to start
int ipNum; // Number of IPs that is to be offered by DHCP Server starting from startIP
/*****************************************/
char manualDHCPServerConfiguration = 'Y';
if ( manualDHCPServerConfiguration == 'Y' )
{
/* For Manual Configuration */
startIP = "192.168.0.5";
ipNum = 15;
}
else
{
/* For Automatic Configuration */
startIP = NULL;
ipNum = 0;
}
dhcp_server_start(&g_netif, startIP, ipNum);
/* Later if application wants to stop the DHCP client then it should call netifapi_dhcps_stop()*/
/*netifapi_dhcps_stop(&g_netif);*/
return 0;
}
限制条件
在使用lwIP之前,需要考虑以下限制条件:
lwIP内的操作系统适配层与Huawei LiteOS接口紧耦合,所以lwIP只支持在Huawei LiteOS上运行。
lwIP目前的RAM和ROM进一步裁剪,通过配置lwipopts.h中的宏控制,更多限制条件请参见lwipopts.h代码宏LWIP_SMALL_SIZE。
DHCP客户端允许UDP套接字接口绑定在DHCP客户端端口上,而lwIP不允许多个网口(包括以太网和Wi-Fi的netif结构体)绑定到同一个端口。因此,如果SO_BINDTODEVICE被禁用,则lwIP可以与两个网口(以太网和Wi-Fi)同时运行,但DHCP客户端不可以。
DNS客户端仅支持响应消息中的A或AAAA类资源记录。解析DNS响应消息中的多个应答记录时,如果遇到任何异常的应答记录,则解析终止并返回已成功解析的记录(如果存在成功解析的记录);否则返回解析失败的记录。
lwIP提供了以下类型的接口:
BSD接口
均能够确保线程安全。
netconn接口
线程安全接口。
低阶接口
低阶接口不是线程安全接口。因此,不建议使用该接口。如果用户使用了这种接口,应用线程和驱动线程需要注意锁定核心TCPIP线程功能。
lwIP的多线程使用有如下限制:
lwIP核心不是线程安全。如果多线程环境中的应用需要使用lwIP,则应使用接口层(例如:netconn或socket接口层)。但如果使用了低阶接口,则应保护lwIP核心。
netif_xxx和dhcp_xxx不是线程安全接口。因此应用应该使用netifapi模块中可用的线程安全接口netifapi_netif_xxx和netifapi_dhcp_xxx。
对于lwip接口的线程安全性,说明如下:
所有的socket BSD API都是线程安全的。
所有形如netifapi_xxx的API都是线程安全的。
所有形如netif_xxx的API都是非线程安全的。
由于网口维护所需接口索引的变化范围为1~254,因此应用创建的网口数不能超过254。
lwip_select和closesocket接口的多线程考虑:
如果某个套接字受到lwip_select()接口监测,并在另一线程被closesocket()接口关闭,则lwIP中的lwip_select()接口会从锁闭状态返回,不标记具体套接字。而Linux中,在另一线程关闭套接字对lwip_select()接口没有影响。
lwip_shutdown()接口有如下限制:
如果通过SHUT_RDWR或SHUT_RD标志调用了lwip_shutdown()接口,则任何待接收的数据应由lwIP清除,RST被发送给对端,并且应用在调用SHUT_RDWR或SHUT_RD之前必须读取数据。
当发送被阻塞且lwip_shutdown(SHUT_RDWR)被调用时,将返回EINPROGRESS (115)。
lwip_listen()接口有如下限制:
Backlog的最大值为16,最小值为0。
如果backlog数值≤0,则backlog值使用1。
监听操作中,lwIP不支持自动绑定,所以lwip_bind()必须在lwip_listen()之前调用。
lwip_listen()接口支持多路调用。如果socket已开启监听,则socket会更新监听backlog。
新的backlog数值只适用于新的输入连接请求。
lwip_recv()接口有如下限制:
当lwIP接收下一个期望的数据段时,lwIP会更新接收缓存列表。
如果与接收数据段相邻的是一个乱序数据段,lwIP会合并这两个数据段为一个,并放入接收缓存列表。
UDP或RAW套接字不支持MSG_PEEK。
UDP或RAW不会报告接收包内存分配失败。
不支持MSG_WAITALL,支持MSG_DONTWAIT。
设置SO_RECVTIMEO套接字选项,socket不会被标记为O_NONBLOCK(无阻塞),由于socket超时时间,会接收非数据,返回错误并显示ETIMEDOUT集。
recvfrom()接口有如下限制:
UDP或RAW套接字不支持MSG_PEEK。
UDP或RAW不会报告接收包内存分配失败。
不支持MSG_WAITALL,支持MSG_DONTWAIT。
在TCP套接字中,如果可以实现,recvfrom()会尝试接收所有来自接收缓存器的数据。
TCP接收缓存器是一个列表,保留从对端接收的数据段。如果应用调用recv函数来获取数据,该函数会从列表中获取首个条目并将它传回给应用。该函数不重复接收来自列表的条目来填充完整用户缓冲区。
当lwIP接收下一个期望的数据段时,lwIP会更新接收缓存列表。如果与接收数据段相邻的是个乱序数据段,lwIP会合并这两个数据段为一个,并放入接收缓存列表。
如果“length”参数设为零,则协议栈将返回的值为-1,且errno将设为EINVAL。但是,POSIX规范未对该设置进行明确说明。如果lwip_recv()的Linux实现出现偏差,则当缓存大小设为0时,会返回值0。
不支持MSG_TRUNC标志。
lwip_send()接口和lwip_sendmsg()接口有如下限制:
UDP和RAW连接能发送的数据长度最大为65332。如果发送更长的数据,返回的值为-1,且errno将设为ENOMEM。
只支持MSG_MORE和MSG_DONTWAIT标志。不支持如MSG_OOB/MSG_NOSIGNAL/MSG_EOR的其他标志。
lwip_sendto()接口有如下限制:
AF_INET/AF_INET6 UDP和RAW连接能发送的数据长度最大为65332。如果发送更长的数据,返回失败,值为-1,errno将设为ENOMEM。
只支持MSG_MORE和MSG_DONTWAIT标志。不支持如MSG_OOB/MSG_NOSIGNAL/MSG_EOR的其他标志。
socket()接口有如下限制:
只有SOCK_RAW支持PF_PACKET。
AF_INET套接字支持SOCK_RAW | SOCK_DGRAM | SOCK_STREAM类型。
AF_PACKET只支持SOCK_RAW类型。
lwip_write()接口有如下限制:
对于没有标记O_NONBLOCK、设置了SP_SENDTIMEO选项且历时时间比超时时间长的socket,lwIP失败并且提示errno EAGAIN。
lwip_select()接口有如下限制:
select()接口不会更新超时参数来显示剩余时长。
FD_SETSIZE是lwIP中可配置的编译时间,应用必须确保不违反这个边界值,lwIP不在运行过程中对此进行验证。
lwip_fcntl()接口有如下限制:
只支持F_GETFL and F_SETFL命令。对于F_SETFL,val只支持O_NONBLOCK。
PF_PACKET套接字支持F_SETFL和F_GETFL选项。
任何方式的IP地址变更会导致如下行为:
TCP
所有现有的TCP连接掉线,此种连接上任何操作均显示ECONNABORTED。
所有界限地址被更改为新的IP地址。
如果有一个监听套接字,它会在新IP地址上接受连接。
UDP
所有套接字的IP地址更改为新IP地址,通信会在新IP地址上继续。
多种绑定调用行为:bind()接口调用会更改UDP套接字的端口绑定。
UDP套接字会更改本地绑定端口。
TCP套接字上不允许,如果执行多种绑定调用,则会返回失败,并显示errno EINVAL。
在AF_INET/AF_INET6 SOCK_RAW套接字上,多种绑定调用行为会更新本地IP地址。
在PF_PACKET套接字上,多种绑定调用行为会更改绑定为新的接口编号。
AF_INET/AF_INET6 SOCK_RAW绑定不会检查套接字地址是否可用。
以下所列接口不支持PF_PACKET选项:
lwip_accept()
lwip_shutdown()
lwip_getpeername()
lwip_getsockname()
lwip_listen()
对于ping6,如果应用创建原始ICMPV6消息,协议栈不会储存这些消息的统计数据。应用必须保留计数并处理这些统计数据。
当数据包发往一个链路本地地址时,必须设置当前netif的链路本地地址。否则,路由会返回,显示网络不可达。
支持IPv6邻居发现功能解析选项,这些解析选项定义在RFC 4861中。为了确保后续扩建与现有实现的合理共存,协议栈忽略接收的ND数据包中任何不可识别的选项,并继续处理数据包。执行验证,对可能影响后续扩建的具体类型进行程序检查。
当网络掩码配置为0.0.0.0时,以下限制可适用:
不支持将接口的网络掩码设为0.0.0.0。此种条件下的lwIP协议栈行为与Linux行为有偏差。
例如:有一个eth0接口和loopback接口。如果运行如下命令:
ifconfig eth0 netmask 0.0.0.0
ifconfig成功将eth0的网络掩码设为具体数值,但在路由中会产生干扰结果,甚至会导致环回ping失败,显示目的地不可达。
setsockopt()中的SO_ATTACH_FILTER选项有如下限制:
因为不支持LSF_MSH,所以不支持加载IP地址首部长度的LSF_LDX hack。因此不支持下列过滤模式:
LSF_LDX+LSF_B+LSF_MSH X <- 4_(P[k:1]&0xf)_
当套接字绑定到一个IP地址,而这个IP地址配置了使用bind()的接口,则该IP地址被更改。如果一个应用调用listen(),会返回并显示error ECONNABORTED。绑定在旧IP地址的套接字不会更新配置在接口的新IP地址,但状态更改为ERR_ABRT。所以调用listen()会返回并显示error ECONNABORTED。
在IPv6原始套接字中,不支持IPv4的映射IPv6[双栈支持],即不支持IPv6原始套接字接收IPv4数据包或包含IPv4映射的IPv6地址的数据包。
ping IPv4地址有如下限制:
没有ICMP标识符或ICMP序列号的匹配来维持ping会话,也没有来自对端的各自ping响应排序。
网络安全
网络安全声明
为避免敏感信息泄露,用户须关注以下安全注意事项:
调试日志:通过配置opt.h头文件中的宏LWIP_DBG_TYPES_ON,可启用或禁用DebugFlag。该宏默认被禁用。
表 1 调试关闭选项
注意阅读本声明,防止安全相关风险的发生。用户须关注以下安全注意事项:
用户在lwipopts.h头文件中定义LWIP_RAND函数宏。DHCP和DNS会使用对应的函数随机生成业务的ID。用户应该将LWIP_RAND宏定义为合适的随机数发生器函数。
基于UDP或TCP向lwIP发送的应用数据将先暂时保存在lwIP缓冲区中,在释放内存之前,缓冲区内的数据并不会被明确地清理掉。上述内容是基于这样一个假设,即应用不会将用户敏感信息以明文形式提供给lwIP。这是因为应用通常会通过传输安全协议(如TLS或DTLS)加密用户敏感信息,并将加密消息发送给lwIP。
常见问题
答:lwIP不支持route命令。
答:lwIP不支持路由选择,但如果启用了IP_FORWARD标记,则lwIP支持转发。网口报文是通过路由发送。
答:不可能。必须添加Netif,并执行回调。
答:是的。通过设置LWIP_IGMP为1,并使用setsockopt()设置IP_ADD_MEMBERSHIP和IP_DROP_MEMBERSHIP等,可以启用该功能。
答:现有的TCP连接都将被丢弃,并且任何TCP连接操作都将返回ECONNABORTED。
所有绑定的地址都修改为新的IP地址。
如果有一个监听套接字,它将接受新IP地址上的连接。
答:所有套接字的IP地址被修改为新的IP地址。在新的IP地址上继续通信。
7. 按照RFC 2710第5节的要求,协议栈是否支持发送包括被请求节点组播地址在内的MLD消息?
答:按照RFC 2710第5节的要求,在主机部分工作时,MLD消息不应发送给链路范围内全部节点组播地址,例如FF02::1。
按照RFC 2710第4节的要求:
如果某个节点在通过接口接收到另一个节点的组播地址REPORT消息时,其计时器正在运行,那么该节点会停止计时器且不再发送组播地址REPORT消息,从而抑制在链路上复制报告。
当发送DONE消息时,如果该节点的最近REPORT消息因监听其它REPORT消息而被抑制,那么它可能停止发送消息,因为很有可能在同一链路上存在一个监听该地址的设备。这种机制可以被禁用,但默认是被开启的。
8. 如果邻居缓存数据存取状态不是INCOMPLETE时接收到目标地址为组播地址的未被请求的NA消息,会发生什么?
答:状态变为STALE。
答:当协议栈接收到路由器发送的RA消息时,会向默认路由地址发送NS来及时解析默认的路由地址。
答:最多3个,包括链路本地地址。如果添加的IPv6地址超过两个,那么第一个地址将被新地址替代,小型化会进一步优化。
答:会注册回调函数来执行重复地址检测。重复地址检测失败时,可调用该回调函数来删除原有的IPv6地址,并添加新的地址,小型化已经不支持此特性。
12. lwIP在配有多个接口的设备上运行时能否通过这些接口接收对端的消息?
答:当lwIP在配有多个接口的设备上运行时,如果通过这些接口从对端接收的消息中的目标地址为广播IP地址,那么lwIP可以接收该消息。一旦调用recv()函数,配置多少次接口,应用就可接收多少消息。
举例如下:
设备有两个接口,假设IP地址按如下方式配置:
Peer_Node 设备(lwIP)
192.168.23.119 (p5p2) <--> 192.168.23.117 (eth15)
192.168.2.119 (eth2) <--> 192.168.2.117 (eth1)
lwIP套接字设置有SO_BROADCAST套接字选项。
将lwIP套接字与INADDR_ANY绑定。
将从Peer_Node接收的DATA消息发送至目标地址为192.168.2.255的设备。
结果:
设备通过192.168.23.117和192.168.2.117两个接口接收Peer_Node的消息。应用两次调用recv()函数后,会接收两次同样的消息。
说明: 当192.168.23.117接口接收Peer_Node消息时,发现该消息并不是发送给该接口。这时,该接口会将该消息转发给192.168.2.117接口。
答:修改lwipopts.h中的宏定义,会覆盖opt.h中宏的配置,如表1所示。
表 1 lwipopts.h的宏定义
须知:
LWIP_NUM_SOCKETS_MAX和DEFAULT_LWIP_NUM_SOCKETS、MEMP_NUM_NETCONN的值须保持一致。
调整TCP或UDP的最大连接个数需要考虑LWIP_NUM_SOCKETS_MAX、DEFAULT_LWIP_NUM_SOCKETS、MEMP_NUM_NETCONN的总数量。
对于TCP Server端,accept建立连接需要1个socket。
如果增加TCP或UDP的个数,会增加相应的RAM和ROM资源,请评估测试后使用。
默认场景下,支持的socket资源最大为9个,超过9个之后会创建socket失败。
答:通过减少lwIP接收窗口,降低内存占用,但是一定程度上会降低跑流峰值性能,建议合入修改后测试,确保满足实际应用场景。
说明: 跑流场景下最多可减少内存占用约8KB。
修改1
Components/lwip_sack/include/lwip/lwipopts.h
#define TCP_SND_BUF (65535 / 3)
修改为:
#define TCP_SND_BUF (12*1024)
修改2
#define TCP_WND ((TCP_SND_BUF * 2) / 3)
修改为:
#define TCP_WND (8*1024)
修改3
#define TCP_OOSEQ_MAX_PBUFS 8
修改为:
#define TCP_OOSEQ_MAX_PBUFS 5
答:LwIP路由机制是基于子网匹配,从netif列表查找合适的网络接口来发送报文。这里未采用最长前缀匹配算法,而是优先选择第一个与出报文目标IP匹配,即在同个子网的网络接口。如果未找到匹配,则使用默认的网络接口发送报文。如果通过setsockopt()启用SO_BINDTODEVICE,则只能通过绑定的网络接口发送报文;如果通过setsockopt()启用SO_DONTROUTE,则会使用同样匹配原则查找网络接口,但是在未找到匹配时,不会使用默认的网络接口。
答:当前针对路由选择需要指定相应的接口,应用层可以使用下面的方式,比如需要往ap发包指定AP0,需要往wlan的时候, 需要指定wlan0。
ret = lwip_setsockopt(sfd, SOL_SOCKET, SO_BINDTODEVICE, "wlan0", IFNAMSIZ);
if (ret == -1) {
hi_at_printf("setsockopt: unknown iface %s\n", src_iface);
return -1;
}
16. 当系统内存不足,修改tcp减少丢包情况下接收端乱序队列个数?
答:当前tcp的实现为了高吞吐量,将tcp的接收端乱序队列设置为8,当环境干扰大的时候,接收端最多缓存8个tcp包,由于liteos实现每个包最多占用1748字节,因此对占用内存,如果某些业务场景下不需要高带宽,需要设置lwipopts.h里面这个宏配置:TCP_QUEUE_OOSEQ。
答:lwip的设计只允许gw和ip在相同子网内,lwip ifconfig命令允许同时配置ip、netmask、gw。当新配的ip是与旧ip不在同一子网的合法值,但gw非法时,新ip配置成功但原有的gw被清零。
14. 已关闭的TCP连接,用netstat命令查看一直处于状态10,为什么?
答:在打开低功耗模式的情况下,对TIME_WAIT pcb采取延迟释放策略,即在下一次分配tcp pcb时如果没有空闲pcb才释放。因此,netstat显示的状态10(TIME_WAIT)的连接并不影响建立新连接的建立。如果希望关闭低功耗模式,可将编译选项opt.h中的LWIP_LOWPOWER宏改为0。
如果关闭了低功耗模式,TIME_WAIT连接也会等待一段时间再释放,等待时间可通过tcp_priv.h中的TCP_MSL宏设置。
13. 如何调整TCP建链的SYN报文的重传次数和重传间隔?
答:可以修改TCP_SYNMAXRTX(SYN报文最大重传次数),以及全局变量tcp_persist_backoff(决定间隔周期数),注意tcp_persist_backoff的元素个数必须至少是TCP_SYNMAXRTX+1。例如:进行以下修改,可以大幅缩短connect接口在对端不回复SYN+ACK的场景下的超时等待时间。
#define TCP_SYNMAXRTX 3
static const u8_t tcp_backoff[4] = { 1, 2, 4, 8 };