From 902949a192c13c8c8a591b9583448d0a07c04ba8 Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Tue, 29 Mar 2022 17:47:44 -0500 Subject: [PATCH] update LwIP BACnet/IP abstraction (#246) * update LwIP BACnet/IP abstraction. Integrate LwIP build with continuous integration. Co-authored-by: Steve Karg --- .github/workflows/gcc.yml | 13 ++ Makefile | 5 + ports/lwip/Makefile | 35 ++++ ports/lwip/bacport.h | 39 ++++ ports/lwip/bip.c | 366 ++++++++++++++++---------------------- 5 files changed, 246 insertions(+), 212 deletions(-) create mode 100644 ports/lwip/Makefile create mode 100644 ports/lwip/bacport.h diff --git a/.github/workflows/gcc.yml b/.github/workflows/gcc.yml index 4aa00f82..d99368ce 100644 --- a/.github/workflows/gcc.yml +++ b/.github/workflows/gcc.yml @@ -168,6 +168,19 @@ jobs: make atmega168 make bdk-atxx4-mstp + ports-lwip: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Create Build Environment + run: | + sudo apt-get update -qq + sudo apt-get install -qq build-essential + sudo apt-get install -qq liblwip-dev + - name: ports-lwip + run: | + make lwip + mingw: runs-on: ubuntu-latest steps: diff --git a/Makefile b/Makefile index 3844094f..831eaacc 100644 --- a/Makefile +++ b/Makefile @@ -191,6 +191,10 @@ stm32f4xx-clean: ports/stm32f4xx/Makefile mstpsnap: ports/linux/mstpsnap.mak $(MAKE) -s -C ports/linux -f mstpsnap.mak clean all +.PHONY: lwip +lwip: ports/lwip/Makefile + $(MAKE) -s -C ports/lwip clean all + .PHONY: pretty pretty: find ./src -iname *.h -o -iname *.c -exec \ @@ -252,6 +256,7 @@ clean: ports-clean $(MAKE) -s -C apps/router-ipv6 clean $(MAKE) -s -C apps/router-mstp clean $(MAKE) -s -C apps/gateway clean + $(MAKE) -s -C ports/lwip clean $(MAKE) -s -C test clean rm -rf ./build diff --git a/ports/lwip/Makefile b/ports/lwip/Makefile new file mode 100644 index 00000000..8948b09e --- /dev/null +++ b/ports/lwip/Makefile @@ -0,0 +1,35 @@ +# Makefile to build BACnet/IP LwIP library + +# Executable file name +TARGET = bip-lwip + +SRC = bip.c + +BACNET_DIR = ../../src +LWIP_DIR = /usr/include/lwip + +CFLAGS += -I$(LWIP_DIR) -I$(BACNET_DIR) + +OBJS += ${SRC:.c=.o} + +all: Makefile $(TARGET) + +lib: $(TARGET) + +$(TARGET): $(OBJS) Makefile + ${AR} rcs $@ $(OBJS) + +.c.o: + ${CC} -c ${CFLAGS} $*.c -o $@ + +.PHONY: depend +depend: + rm -f .depend + ${CC} -MM ${CFLAGS} *.c >> .depend + +.PHONY: clean +clean: + -rm -f core ${TARGET} ${OBJS} $(TARGET).map + +.PHONY: include +include: .depend diff --git a/ports/lwip/bacport.h b/ports/lwip/bacport.h new file mode 100644 index 00000000..5582fed5 --- /dev/null +++ b/ports/lwip/bacport.h @@ -0,0 +1,39 @@ +/************************************************************************** +* +* Copyright (C) 2020 Steve Karg +* +* Permission is hereby granted, free of charge, to any person obtaining +* a copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to +* permit persons to whom the Software is furnished to do so, subject to +* the following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*********************************************************************/ + +#ifndef BACPORT_H +#define BACPORT_H + +#include +#include +#include +#include "lwip/def.h" +#include "lwip/udp.h" +#include "lwip/memp.h" +#include "netif/etharp.h" +#include "lwip/dhcp.h" +#include "lwip/inet.h" + +#endif diff --git a/ports/lwip/bip.c b/ports/lwip/bip.c index be46c0b1..07456248 100644 --- a/ports/lwip/bip.c +++ b/ports/lwip/bip.c @@ -39,17 +39,17 @@ #include "bacnet/datalink/bip.h" #include "bacnet/datalink/bvlc.h" #include "bacnet/basic/services.h" +#include "bacnet/basic/bbmd/h_bbmd.h" #include "bacport.h" /* custom per port */ /** @file bip.c Configuration and Operations for BACnet/IP */ /* port to use - stored in network byte order */ -static uint16_t BIP_Port = 0xBAC0U; static bool BIP_Port_Changed; -/* IP Address - stored in network byte order */ -static struct in_addr BIP_Address; -/* Broadcast Address - stored in network byte order */ -static struct in_addr BIP_Broadcast_Address; +/* IP Address */ +static BACNET_IP_ADDRESS BIP_Address; +/* Broadcast Address */ +static BACNET_IP_ADDRESS BIP_Broadcast_Address; /* lwIP socket, of sorts */ static struct udp_pcb *Server_upcb; /* track packets for diagnostics */ @@ -61,41 +61,71 @@ struct bacnet_stats { struct bacnet_stats BIP_Stats; #define BIP_STATS_INC(x) ++BIP_Stats.x +/** + * @brief Get the BACnet/IP transmit stats + * @return number of BACnet/IP transmitted packets + */ uint32_t bip_stats_xmit(void) { return BIP_Stats.xmit; } +/** + * @brief Get the BACnet/IP received stats + * @return number of BACnet/IP received packets + */ uint32_t bip_stats_recv(void) { return BIP_Stats.recv; } +/** + * @brief Get the BACnet/IP drop stats + * @return number of BACnet/IP drops + */ uint32_t bip_stats_drop(void) { return BIP_Stats.drop; } -void bip_set_addr(uint32_t net_address) -{ /* in network byte order */ - BIP_Address.s_addr = net_address; -} - -/* returns network byte order */ -uint32_t bip_get_addr(void) +/** + * @brief Set the BACnet/IP address + * @param addr - network IPv4 address + * @return true if the address is set + */ +bool bip_set_addr(BACNET_IP_ADDRESS *addr) { - return BIP_Address.s_addr; + return bvlc_address_copy(&BIP_Address, addr); } -void bip_set_broadcast_addr(uint32_t net_address) -{ /* in network byte order */ - BIP_Broadcast_Address.s_addr = net_address; -} - -/* returns network byte order */ -uint32_t bip_get_broadcast_addr(void) +/** + * @brief Get the BACnet/IP address + * @param network IPv4 address + * @return true if the address is set + */ +bool bip_get_addr(BACNET_IP_ADDRESS *addr) { - return BIP_Broadcast_Address.s_addr; + return bvlc_address_copy(addr, &BIP_Address); +} + +/** + * @brief Set the BACnet/IP broadcast address + * @param network IPv4 broadcast address + * @return true if the broadcast address is retrieved + */ +bool bip_set_broadcast_addr(BACNET_IP_ADDRESS *addr) +{ + return bvlc_address_copy(&BIP_Broadcast_Address, addr); +} + +/** + * @brief Get the BACnet/IP broadcast address + * @param network IPv4 broadcast address + * @return true if the broadcast address is set + */ +bool bip_get_broadcast_addr(BACNET_IP_ADDRESS *addr) +{ + return bvlc_address_copy(addr, &BIP_Broadcast_Address); } /** @@ -104,24 +134,36 @@ uint32_t bip_get_broadcast_addr(void) */ void bip_set_port(uint16_t port) { - if (BIP_Port != htons(port)) { + if (BIP_Address.port != htons(port)) { BIP_Port_Changed = true; - BIP_Port = htons(port); + BIP_Address.port = htons(port); } } +/** + * @brief Determine if the BACnet IPv4 UDP port number changed + * @return true of the BACnet IPv4 UDP port number changed + */ bool bip_port_changed(void) { return BIP_Port_Changed; } -/* returns host byte order */ +/** + * @brief Get the BACnet IPv4 UDP port number + * @return IPv4 UDP port number - in host byte order + */ uint16_t bip_get_port(void) { - return ntohs(BIP_Port); + return ntohs(BIP_Address.port); } -static void bip_mac_to_addr(struct ip_addr *address, uint8_t *mac) +/** + * @brief Convert the BACnet IPv4 address + * @param address - IPv4 address from LwIP + * @param mac - IP address from BACnet/IP + */ +static void bip_mac_to_addr(ip4_addr_t *address, uint8_t *mac) { if (mac && address) { address->addr = ((u32_t)((((uint32_t)mac[0]) << 24) & 0xff000000)); @@ -131,7 +173,34 @@ static void bip_mac_to_addr(struct ip_addr *address, uint8_t *mac) } } -static void bip_addr_to_mac(uint8_t *mac, struct ip_addr *address) +/** + * @brief Convert from a BACnet/IP address + * @param baddr - BACnet/IP address + * @param address - IPv4 address from LwIP + * @param port - IPv4 UDP port number + */ +static int bip_decode_bip_address(BACNET_IP_ADDRESS *baddr, + ip_addr_t *address, + uint16_t *port) +{ + int len = 0; + + if (baddr && address && port) { + address->type = IPADDR_TYPE_V4; + bip_mac_to_addr(&address->u_addr.ip4, &baddr->address[0]); + *port = baddr->port; + len = 6; + } + + return len; +} + +/** + * @brief Convert the BACnet IPv4 address + * @param mac - IP address from BACnet/IP + * @param address - IPv4 address from LwIP + */ +static void bip_addr_to_mac(uint8_t *mac, const ip4_addr_t *address) { if (mac && address) { mac[0] = (uint8_t)(address->addr >> 24); @@ -141,16 +210,24 @@ static void bip_addr_to_mac(uint8_t *mac, struct ip_addr *address) } } -static int bip_decode_bip_address(BACNET_ADDRESS *bac_addr, - struct ip_addr *address, /* in network format */ - uint16_t *port) -{ /* in network format */ +/** + * @brief Convert to a BACnet/IP address + * @param baddr - BACnet/IP address + * @param address - IPv4 address from LwIP + * @param port - IPv4 UDP port number + */ +static int bip_encode_bip_address(BACNET_IP_ADDRESS *baddr, + const ip_addr_t *address, + uint16_t port) +{ int len = 0; - if (bac_addr) { - bip_mac_to_addr(address, &bac_addr->mac[0]); - memcpy(port, &bac_addr->mac[4], 2); - len = 6; + if (baddr && address) { + if (address->type == IPADDR_TYPE_V4) { + bip_addr_to_mac(&baddr->address[0], &address->u_addr.ip4); + baddr->port = port; + len = 6; + } } return len; @@ -159,26 +236,35 @@ static int bip_decode_bip_address(BACNET_ADDRESS *bac_addr, /** Function to send a packet out the BACnet/IP socket (Annex J). * @ingroup DLBIP * - * @param dst_ip [in] Destination address + * @param dest [in] Destination address * @param port [in] UDP port number - * @param pkt [in] PBUF packet - * - * @return true if the packet was sent + * @param mtu_len [in] PBUF packet + * @return number of bytes sent, or 0 on failure. */ -static bool bip_send_mpdu( - struct ip_addr *dst_ip, uint16_t port, struct pbuf *pkt) +int bip_send_mpdu( + BACNET_IP_ADDRESS *dest, uint8_t *mtu, uint16_t mtu_len) { + struct pbuf *pkt = NULL; + /* addr and port in host format */ + ip_addr_t dst_ip; + uint16_t port = 0; err_t status = ERR_OK; - /* Send the packet */ - status = udp_sendto(Server_upcb, pkt, dst_ip, port); - /* free the buffer pbuf */ - pbuf_free(pkt); + pkt = pbuf_alloc(PBUF_TRANSPORT, mtu_len, PBUF_POOL); + if (pkt == NULL) { + return 0; + } + bip_decode_bip_address(dest, &dst_ip, &port); + pbuf_take(pkt, mtu, mtu_len); + status = udp_sendto(Server_upcb, pkt, &dst_ip, port); if (status == ERR_OK) { BIP_STATS_INC(xmit); + } else { + mtu_len = 0; } + pbuf_free(pkt); - return (status == ERR_OK); + return mtu_len; } /** Send the Original Broadcast or Unicast messages @@ -195,77 +281,7 @@ int bip_send_pdu(BACNET_ADDRESS *dest, /* destination address */ uint8_t *pdu, /* any data to be sent - may be null */ unsigned pdu_len) { - struct pbuf *pkt = NULL, *pkt0 = NULL; - uint8_t mtu[4] = { 0 }; - int mtu_len = 0; - /* addr and port in host format */ - struct ip_addr dst_ip; - uint16_t port = 0; - err_t status = ERR_OK; - - pkt0 = pbuf_alloc(PBUF_TRANSPORT, 4, PBUF_POOL); - if (pkt0 == NULL) { - return 0; - } - pkt = pbuf_alloc(PBUF_TRANSPORT, pdu_len, PBUF_POOL); - if (pkt == NULL) { - pbuf_free(pkt0); - return 0; - } - mtu[0] = BVLL_TYPE_BACNET_IP; - if (dest->net == BACNET_BROADCAST_NETWORK) { - /* broadcast */ - dst_ip.addr = BIP_Broadcast_Address.s_addr; - port = BIP_Port; - mtu[1] = BVLC_ORIGINAL_BROADCAST_NPDU; - } else if (dest->mac_len == 6) { - /* unicast */ - bip_decode_bip_address(dest, &dst_ip, &port); - mtu[1] = BVLC_ORIGINAL_UNICAST_NPDU; - } else { - /* invalid address */ - return -1; - } - mtu_len = pdu_len + 4 /*inclusive */; - encode_unsigned16(&mtu[2], mtu_len); - pbuf_take(pkt0, mtu, 4); - pbuf_take(pkt, pdu, pdu_len); - pbuf_cat(pkt0, pkt); - status = bip_send_mpdu(&dst_ip, port, pkt0); - if (!status) { - mtu_len = 0; - } - - return mtu_len; -} - -/** Send the BVLC Result message - * - * @param addr [in] Destination address - * @param result_code - BVLC result code - * - * @return number of bytes encoded - */ -static bool bvlc_send_result(struct ip_addr *addr, uint16_t result_code) -{ - struct pbuf *pkt = NULL; - uint8_t mtu[6] = { 0 }; - uint16_t mtu_len = 6; - - pkt = pbuf_alloc(PBUF_TRANSPORT, mtu_len, PBUF_POOL); - if (pkt == NULL) { - return 0; - } - mtu[0] = BVLL_TYPE_BACNET_IP; - mtu[1] = BVLC_RESULT; - /* The 2-octet BVLC Length field is the length, in octets, - of the entire BVLL message, including the two octets of the - length field itself, most significant octet first. */ - encode_unsigned16(&mtu[2], 6); - encode_unsigned16(&mtu[4], result_code); - pbuf_take(pkt, mtu, mtu_len); - - return bip_send_mpdu(addr, BIP_Port, pkt); + return bvlc_send_pdu(dest, npdu_data, pdu, pdu_len); } /** LwIP BACnet service callback @@ -279,99 +295,24 @@ static bool bvlc_send_result(struct ip_addr *addr, uint16_t result_code) void bip_server_callback(void *arg, struct udp_pcb *upcb, struct pbuf *pkt, - struct ip_addr *addr, + const ip_addr_t *addr, u16_t port) { uint8_t function = 0; - uint16_t pdu_len = 0; - uint16_t pdu_offset = 0; + uint16_t npdu_offset = 0; BACNET_ADDRESS src = { 0 }; /* address where message came from */ - struct ip_addr sin_addr; - uint16_t sin_port = 0; - uint8_t *pdu = (uint8_t *)pkt->payload; + BACNET_IP_ADDRESS saddr; + uint8_t *npdu = (uint8_t *) pkt->payload; + uint16_t npdu_len = pkt->tot_len; - /* the signature of a BACnet/IP packet */ - if ((pkt->tot_len >= 2) && - (pdu[0] == BVLL_TYPE_BACNET_IP)) { - function = pdu[1]; - if ((function == BVLC_ORIGINAL_UNICAST_NPDU) || - (function == BVLC_ORIGINAL_BROADCAST_NPDU)) { - /* ignore messages from me */ - if ((addr->addr == BIP_Address.s_addr) && (port == BIP_Port)) { - pdu_len = 0; - } else if (pkt->tot_len >= 4) { - /* data in src->mac[] is in network format */ - src.mac_len = 6; - bip_addr_to_mac(&src.mac[0], addr); - memcpy(&src.mac[4], &port, 2); - /* decode the length of the PDU - length is inclusive of BVLC */ - (void)decode_unsigned16(&pdu[2], &pdu_len); - if (pdu_len > pkt->tot_len) { - /* BVLC length is too long - someone is lying */ - pdu_len = 0; - } else { - /* subtract off the BVLC header */ - pdu_len -= 4; - pdu_offset = 4; - } - } - } else if (function == BVLC_FORWARDED_NPDU) { - if (pkt->tot_len >= 10) { - IP4_ADDR(&sin_addr, pdu[4], pdu[5], pdu[6], pdu[7]); - decode_unsigned16(&pdu[8], &sin_port); - if ((sin_addr.addr == BIP_Address.s_addr) && - (sin_port == BIP_Port)) { - /* ignore forwarded messages from me */ - pdu_len = 0; - } else { - /* data in src->mac[] is in network format */ - src.mac_len = 6; - bip_addr_to_mac(&src.mac[0], &sin_addr); - memcpy(&src.mac[4], &sin_port, 2); - /* decode the length of the PDU - length is inclusive of BVLC */ - (void)decode_unsigned16(&pdu[2], &pdu_len); - if (pdu_len > pkt->tot_len) { - /* BVLC length is too long - someone is lying */ - pdu_len = 0; - } else { - /* subtract off the BVLC header */ - pdu_len -= 10; - pdu_offset = 10; - } - } - } - } else if (function == BVLC_WRITE_BROADCAST_DISTRIBUTION_TABLE) { - bvlc_send_result( - addr, BVLC_RESULT_WRITE_BROADCAST_DISTRIBUTION_TABLE_NAK); - } else if (function == BVLC_READ_BROADCAST_DIST_TABLE) { - bvlc_send_result( - addr, BVLC_RESULT_READ_BROADCAST_DISTRIBUTION_TABLE_NAK); - } else if (function == BVLC_REGISTER_FOREIGN_DEVICE) { - bvlc_send_result(addr, BVLC_RESULT_REGISTER_FOREIGN_DEVICE_NAK); - } else if (function == BVLC_READ_FOREIGN_DEVICE_TABLE) { - bvlc_send_result(addr, BVLC_RESULT_READ_FOREIGN_DEVICE_TABLE_NAK); - } else if (function == BVLC_DELETE_FOREIGN_DEVICE_TABLE_ENTRY) { - bvlc_send_result( - addr, BVLC_RESULT_DELETE_FOREIGN_DEVICE_TABLE_ENTRY_NAK); - } else if (function == BVLC_DISTRIBUTE_BROADCAST_TO_NETWORK) { - bvlc_send_result(addr, BVLC_RESULT_DISTRIBUTE_BROADCAST_TO_NETWORK_NAK); - } - if (pdu_len) { - BIP_STATS_INC(recv); - if ((function == BVLC_ORIGINAL_BROADCAST_NPDU) && - (npdu_confirmed_service(&pdu[pdu_offset], pdu_len))) { - /* BTL test: verifies that the IUT will quietly discard any - Confirmed-Request-PDU, whose destination address is a - multicast or broadcast address, received from the - network layer. */ - } else { - npdu_handler(&src, &pdu[pdu_offset], pdu_len); - } - } else { - BIP_STATS_INC(rxdrop); - } + bip_encode_bip_address(&saddr, addr, port); + npdu_offset = bvlc_handler(&saddr, &src, npdu, npdu_len); + if (npdu_offset > 0) { + BIP_STATS_INC(recv); + npdu_len -= npdu_offset; + npdu_handler(&src, &npdu[npdu_offset], npdu_len); + } else { + BIP_STATS_INC(drop); } /* free our packet */ pbuf_free(pkt); @@ -383,8 +324,8 @@ void bip_get_my_address(BACNET_ADDRESS *my_address) if (my_address) { my_address->mac_len = 6; - memcpy(&my_address->mac[0], &BIP_Address.s_addr, 4); - memcpy(&my_address->mac[4], &BIP_Port, 2); + memcpy(&my_address->mac[0], &BIP_Address.address, 4); + memcpy(&my_address->mac[4], &BIP_Address.port, 2); my_address->net = 0; /* local only, no routing */ my_address->len = 0; /* no SLEN */ for (i = 0; i < MAX_MAC_LEN; i++) { @@ -402,8 +343,8 @@ void bip_get_broadcast_address(BACNET_ADDRESS *dest) if (dest) { dest->mac_len = 6; - memcpy(&dest->mac[0], &BIP_Broadcast_Address.s_addr, 4); - memcpy(&dest->mac[4], &BIP_Port, 2); + memcpy(&dest->mac[0], &BIP_Broadcast_Address.address, 4); + memcpy(&dest->mac[4], &BIP_Address.port, 2); dest->net = BACNET_BROADCAST_NETWORK; dest->len = 0; /* no SLEN */ for (i = 0; i < MAX_MAC_LEN; i++) { @@ -438,12 +379,13 @@ bool bip_init(char *ifname) /* Create a new UDP control block */ Server_upcb = udp_new(); if (Server_upcb == NULL) { - /* increase MEMP_NUM_UDP_PCB in lwipopts.h */ - while (1) { }; + while (1) { + /* fixme: increase MEMP_NUM_UDP_PCB in lwipopts.h */ + }; } /* Bind the upcb to the UDP_PORT port */ /* Using IP_ADDR_ANY allow the upcb to be used by any local interface */ - udp_bind(Server_upcb, IP_ADDR_ANY, BIP_Port); + udp_bind(Server_upcb, IP_ADDR_ANY, BIP_Address.port); /* Set a receive callback for the upcb */ udp_recv(Server_upcb, bip_server_callback, NULL);