STM32 LwIP单网卡绑定多个IP地址

本文介绍如何在STM32F107VC上使用LwIP配置单网卡多IP地址,并解决由此产生的数据包丢失、重复及接收问题。文中详细解释了通过修改ARP层来区分不同IP地址的接收逻辑。

STM32 LwIP单网卡绑定多个IP地址

芯片:STM32F107VC

编译器:KEIL4

作者:SY

日期:2018-6-28 11:02:53

概述

在只有一个物理网卡的情况下,可以通过软件虚拟多个 IP地址 ,分时通讯。

  • 协议栈: LwIP
  • 主芯片:STM32F107VC

实现

首先移植 LwIP ,保证单个 IP地址 时可用。

然后添加虚拟网卡:

void LwIP_Init(void)
{
    /* 添加网卡1 */
    IP4_ADDR(&ipaddr1, 192, 168, 0, 234);
    IP4_ADDR(&netmask1, 255, 255, 255, 0);
    IP4_ADDR(&gw1, 192, 168, 0, 1);
    netif_add(&netif1, &ipaddr1, &netmask1, &gw1, NULL, &ethernetif_init, &ethernet_input);
    netif_set_up(&netif1);
    netif_set_default(&netif1);

    /* 添加网卡2 */
    IP4_ADDR(&ipaddr2, 192, 168, 1, 234);
    IP4_ADDR(&netmask2, 255, 255, 255, 0);
    IP4_ADDR(&gw2, 192, 168, 1, 1);
    netif_add(&netif2, &ipaddr2, &netmask2, &gw2, NULL, &ethernetif_init, &ethernet_input);
    netif_set_up(&netif2);

    /* 新建UDP通信 */
    struct udp_pcb *UdpPcb;
    /* Create a new UDP control block  */
    UdpPcb = udp_new();  

    /* Bind the upcb to any IP address and the UDP_PORT port*/
    udp_bind(UdpPcb, IP_ADDR_ANY, UDP_CLIENT_PORT);

    /* Set a receive callback for the upcb */
    udp_recv(UdpPcb, udp_client_callback, NULL); 
}

这样处理之后,出现下面的问题:

  • 有些数据包会在网卡中逗留,在不确定的时间后,发出去
  • 数据包每次都发了两次
  • 第一张网卡的 IP地址 收不到数据,甚至于使用以太网调试助手向该 IP 发送数据都抓不到数据包。

根据以太网通信协议,局域网内数据收发,需要使用 MAC 地址通讯,因此首先需要发送 ARP 数据包,需要向目的 IP地址 发送协议包,请求对方的 MAC地址 。通过抓包工具查看,果然是这样,电脑端发送的 ARP 数据包,设备没有回复。

因此修改 ARP层

/*
*********************************************************************************************************
* Function Name : GetActiveNetif
* Description   : 获取当前活动的网卡
* Input         : None
* Output        : None
* Return        : None
*********************************************************************************************************
*/
struct netif *GetActiveNetif(struct ip_addr *src_ip)
{
    struct netif *curNetif = NULL;
    if (ip_addr_cmp(src_ip, &netif1.ip_addr)) {
        curNetif = &netif1;
    } else if (ip_addr_cmp(src_ip, &netif2.ip_addr)) {
        curNetif = &netif2;
    }
    return curNetif;
}

通过解析数据包中的 源IP 字段,和当前 2 张网卡中的 源IP 字段进行比较,从而找到数据包是发送给哪一张网卡的。

void
etharp_arp_input(struct netif *netif, struct eth_addr *ethaddr, struct pbuf *p)
{
    /* this interface is not configured? */
    if (netif->ip_addr.addr == 0) {
        for_us = 0;
    } else {
        /* ARP packet directed to us? */
        struct netif *GetActiveNetif(struct ip_addr *src_ip);
        struct netif *activeNetif = GetActiveNetif(&dipaddr);
        if (activeNetif != NULL) {
            for_us = 1;
            netif = activeNetif;
        }
    }

    /* ARP message directed to us? */
    if (for_us) {
        /* add IP address in ARP cache; assume requester wants to talk to us.
         * can result in directly sending the queued packets for this host. */
        update_arp_entry(netif, &sipaddr, &(hdr->shwaddr), ETHARP_TRY_HARD);
        /* ARP message not directed to us? */
    } else {
        /* update the source IP address in the cache, if present */
        update_arp_entry(netif, &sipaddr, &(hdr->shwaddr), 0);
    }
}

通过上述修改,上位机的数据包就可以收到了。

/**
 * In this function, the hardware should be initialized.
 * Called from ethernetif_init().
 *
 * @param netif the already initialized lwip network interface structure
 *        for this ethernetif
 */
static void
low_level_init(struct netif *netif)
{
    /* device capabilities */
      /* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */
      netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_LINK_UP;
}

根据上面提示,如果有多个网络,需要去掉 NETIF_FLAG_ETHARP 字段。

OK ,经过上述修改,两个 IP地址 都可以收到数据包。

总结

IP地址 需要配置为不同网段

参考 IP 路由规则:

/**
 * Finds the appropriate network interface for a given IP address. It
 * searches the list of network interfaces linearly. A match is found
 * if the masked IP address of the network interface equals the masked
 * IP address given to the function.
 *
 * @param dest the destination IP address for which to find the route
 * @return the netif on which to send to reach dest
 */
struct netif *
ip_route(struct ip_addr *dest)
{
  struct netif *netif;

  /* iterate through netifs */
  for(netif = netif_list; netif != NULL; netif = netif->next) {
    /* network mask matches? */
    if (netif_is_up(netif)) {
      if (ip_addr_netcmp(dest, &(netif->ip_addr), &(netif->netmask))) {
        /* return netif on which to forward IP packet */
        return netif;
      }
    }
  }
  if ((netif_default == NULL) || (!netif_is_up(netif_default))) {
    LWIP_DEBUGF(IP_DEBUG | 2, ("ip_route: No route to 0x%"X32_F"\n", dest->addr));
    IP_STATS_INC(ip.rterr);
    snmp_inc_ipoutnoroutes();
    return NULL;
  }
  /* no matching netif found, use default netif */
  return netif_default;
}

如果两张网卡的 IP地址 在同一个网段,那么在遍历网卡时,会优先找到最后添加的网卡,也就是说,不管你往哪个网卡发送数据包,在设备回复数据时,都会从第二张网卡回复数据。

比如说:向 IP (192, 168, 0, 234) 发送数据包,收到数据回复时,源 IP地址 会变成 IP(192, 168, 1, 234)

如果想要两张网卡独立工作,都可以正常收发数据,需要将两张网卡的 IP地址 设置为不同的网段,同时电脑端需要和设备端设置在同一个网段!

比如:

主机1:

IP:     192.168.0.88
MASK:   255.255.255.0
GATEWAY:192.168.0.1 

设备1:

IP:     192.168.0.249
MASK:   255.255.255.0
GATEWAY:192.168.0.1 

这样,主机1 和 设备1 可以正常通讯。

主机2:

IP:     192.168.1.88
MASK:   255.255.255.0
GATEWAY:192.168.1.1 

设备2:

IP:     192.168.1.249
MASK:   255.255.255.0
GATEWAY:192.168.1.1 

这样,主机2 和 设备2 可以正常通讯。

如果交叉会怎么样呢?你会发现抓包工具会显示目标不可达,因为他们不再同一个子网内!

如果设备1和设备2在同一个子网内会怎么样呢?

主机:

IP:     192.168.0.88
MASK:   255.255.255.0
GATEWAY:192.168.0.1 

设备1:

IP:     192.168.0.249
MASK:   255.255.255.0
GATEWAY:192.168.0.1 

设备2:

IP:     192.168.0.248
MASK:   255.255.255.0
GATEWAY:192.168.0.1 

现象:

主机 –> 设备1:设备2 –> 主机

主机 –> 设备2:设备2 –>主机

因此,我们得出结论:

  • 只有在同一个子网内的主机和设备才可以正常收发数据
  • 如果两台设备在同一个子网内,主机只能和最后添加的网卡绑定的 IP地址 正常通讯。和最开始添加的网卡通讯时,由于 ip_rout 的关系,将使用最后绑定网卡的 IP地址 回复!
### STM32 LwIP 单网卡绑定多个 IP 地址实现方法 在 STM32 上使用 LwIP 实现单网卡绑定多个 IP 地址的功能,可以通过创建虚拟网络接口来完成。以下是具体的实现方式: #### 1. 初始化默认网络接口 首先需要初始化一个默认的网络接口,并将其设置为活动状态。这是整个网络通信的基础。 ```c struct netif netif1; ip4_addr_t ipaddr1, netmask1, gw1; /* 设置默认网卡 */ IP4_ADDR(&ipaddr1, 192, 168, 0, 234); IP4_ADDR(&netmask1, 255, 255, 255, 0); IP4_ADDR(&gw1, 192, 168, 0, 1); netif_add(&netif1, &ipaddr1, &netmask1, &gw1, NULL, &ethernetif_init, &tcpip_input); netif_set_up(&netif1); netif_set_default(&netif1); // 将此网卡设为默认网卡 ``` 这段代码定义并初始化了第一个物理网卡 `netif1`,设置了其 IP 地址、子网掩码和网关[^2]。 --- #### 2. 创建虚拟网络接口 为了支持多个 IP 地址,可以利用 LwIP 的 `netif_add()` 函数添加额外的虚拟网络接口。这些虚拟接口共享同一个物理硬件资源。 以下是一个示例代码片段,用于添加两个虚拟网络接口: ```c struct netif g_netif_v[2]; ip4_addr_t ipaddr_v[2], netmask_v[2], gw_v[2]; // 虚拟多 IP -- 1 IP4_ADDR(&ipaddr_v[0], 192, 168, 20, 48); IP4_ADDR(&netmask_v[0], 255, 255, 255, 0); IP4_ADDR(&gw_v[0], 192, 168, 20, 1); netif_add(&g_netif_v[0], &ipaddr_v[0], &netmask_v[0], &gw_v[0], NULL, &ethernetif_init, &tcpip_input); netif_set_up(&g_netif_v[0]); // 虚拟多 IP -- 2 IP4_ADDR(&ipaddr_v[1], 192, 168, 30, 48); IP4_ADDR(&netmask_v[1], 255, 255, 255, 0); IP4_ADDR(&gw_v[1], 192, 168, 30, 1); netif_add(&g_netif_v[1], &ipaddr_v[1], &netmask_v[1], &gw_v[1], NULL, &ethernetif_init, &tcpip_input); netif_set_up(&g_netif_v[1]); ``` 通过调用两次 `netif_add()` 和 `netif_set_up()` 方法,成功创建了两个虚拟网络接口 `g_netif_v[0]` 和 `g_netif_v[1]`,分别绑定了不同的 IP 地址[^1]。 --- #### 3. 处理内存泄漏问题 如果未正确释放动态分配的内存,则可能导致系统运行一段时间后崩溃。因此,在实际应用中需要注意及时清理不再使用的资源。 例如,当某个虚拟网络接口被移除时,应确保释放与其关联的所有内存空间。这通常涉及销毁对应的 PCB 结构体以及解除绑定操作。 --- #### 4. 测试功能 最后一步是对新配置进行测试,确认所有指定的 IP 地址都能正常工作。可以在程序中加入简单的 UDP 或 TCP 客户端/服务器逻辑来进行验证。 ```c struct udp_pcb *udp_server_pcb; /* 创建一个新的 UDP 控制块 */ udp_server_pcb = udp_new(); /* 绑定到任意 IP 地址上的特定端口 */ udp_bind(udp_server_pcb, IP_ADDR_ANY, SERVER_PORT); /* 注册接收回调函数 */ udp_recv(udp_server_pcb, udp_receive_callback, NULL); ``` 以上代码展示了如何建立一个监听所有本地 IP 地址的服务端实例[^2]。 --- ### 注意事项 - **性能优化**:由于增加了多个虚拟网络接口,可能会稍微影响系统的整体吞吐量。建议根据具体需求调整参数。 - **冲突检测**:务必保证所设定的不同 IP 不会与其他设备发生地址重叠现象。 - **错误处理机制**:完善异常情况下的恢复策略,比如断线重连等场景。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值