mirror of
https://github.com/stargieg/bacnet-stack
synced 2025-10-26 23:35:52 +08:00
415 lines
13 KiB
C
415 lines
13 KiB
C
/**
|
|
* @file
|
|
* @author Andriy Sukhynyuk, Vasyl Tkhir, Andriy Ivasiv
|
|
* @date 2012
|
|
* @brief Datalink IP module
|
|
*
|
|
* @section LICENSE
|
|
*
|
|
* 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.
|
|
*
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "ipmodule.h"
|
|
#include "bacint.h"
|
|
|
|
#ifdef TEST_PACKET
|
|
uint8_t test_packet[] = { 0x81, 0x0a, 0x00, 0x16, /* BVLC header */
|
|
0x01, 0x24, 0x00, 0x01, 0x01, 0x0b, 0xff, /* NPDU */
|
|
0x00, 0x03, 0x01, 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x02, 0x19, 0x55
|
|
}; /* APDU */
|
|
#endif
|
|
|
|
extern int get_local_address_ioctl(
|
|
char *ifname,
|
|
struct in_addr *addr,
|
|
int request);
|
|
|
|
void *dl_ip_thread(
|
|
void *pArgs)
|
|
{
|
|
MSGBOX_ID msgboxid;
|
|
BACMSG msg_storage, *bacmsg = NULL;
|
|
MSG_DATA *msg_data;
|
|
ROUTER_PORT *port = (ROUTER_PORT *) pArgs;
|
|
IP_DATA ip_data; /* port specific parameters */
|
|
BACNET_ADDRESS address = { 0 };
|
|
int status;
|
|
uint8_t shutdown = 0;
|
|
|
|
/* initialize router port */
|
|
if (!dl_ip_init(port, &ip_data)) {
|
|
port->state = INIT_FAILED;
|
|
return NULL;
|
|
}
|
|
|
|
/* allocate buffer */
|
|
ip_data.max_buff = MAX_BIP_MPDU;
|
|
ip_data.buff = (uint8_t *) malloc(ip_data.max_buff);
|
|
|
|
if (ip_data.buff == NULL) {
|
|
port->state = INIT_FAILED;
|
|
return NULL;
|
|
}
|
|
|
|
msgboxid = create_msgbox();
|
|
if (msgboxid == INVALID_MSGBOX_ID) {
|
|
PRINT(ERROR, "Error: Failed to create message box");
|
|
port->state = INIT_FAILED;
|
|
return NULL;
|
|
}
|
|
|
|
port->port_id = msgboxid;
|
|
port->state = RUNNING;
|
|
|
|
while (!shutdown) {
|
|
|
|
/* check for incoming messages */
|
|
bacmsg = recv_from_msgbox(port->port_id, &msg_storage);
|
|
|
|
if (bacmsg) {
|
|
switch (bacmsg->type) {
|
|
case DATA:{
|
|
msg_data = (MSG_DATA *) bacmsg->data;
|
|
memmove(&address.net, &msg_data->dest.net, 2);
|
|
memmove(&address.mac_len, &msg_data->dest.len, 1);
|
|
memmove(&address.mac[0], &msg_data->dest.adr[0],
|
|
MAX_MAC_LEN);
|
|
|
|
dl_ip_send(&ip_data, &address, msg_data->pdu,
|
|
msg_data->pdu_len);
|
|
|
|
check_data(msg_data);
|
|
|
|
break;
|
|
}
|
|
|
|
case SERVICE:{
|
|
switch (bacmsg->subtype) {
|
|
case SHUTDOWN:
|
|
del_msgbox(port->port_id);
|
|
shutdown = 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
} else {
|
|
status = dl_ip_recv(&ip_data, &msg_data, &address, 5);
|
|
if (status > 0) {
|
|
memmove(&msg_data->src.len, &address.mac_len, 1);
|
|
memmove(&msg_data->src.adr[0], &address.mac[0], MAX_MAC_LEN);
|
|
msg_storage.origin = port->port_id;
|
|
msg_storage.type = DATA;
|
|
msg_storage.data = msg_data;
|
|
|
|
if (!send_to_msgbox(port->main_id, &msg_storage)) {
|
|
free_data(msg_data);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* cleanup procedure */
|
|
dl_ip_cleanup(&ip_data);
|
|
port->state = FINISHED;
|
|
return NULL;
|
|
}
|
|
|
|
bool dl_ip_init(
|
|
ROUTER_PORT * port,
|
|
IP_DATA * ip_data)
|
|
{
|
|
struct sockaddr_in sin;
|
|
int socket_opt = 0;
|
|
int status = 0; /* for error checking */
|
|
|
|
/* setup port for later use */
|
|
ip_data->port = htons(port->params.bip_params.port);
|
|
|
|
/* get local address */
|
|
status =
|
|
get_local_address_ioctl(port->iface, &ip_data->local_addr,
|
|
SIOCGIFADDR);
|
|
if (status < 0) {
|
|
return false;
|
|
}
|
|
/* get broadcast address */
|
|
status =
|
|
get_local_address_ioctl(port->iface, &ip_data->broadcast_addr,
|
|
SIOCGIFBRDADDR);
|
|
if (status < 0) {
|
|
return false;
|
|
}
|
|
|
|
ip_data->socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (ip_data->socket < 0)
|
|
return false;
|
|
|
|
/* setup socket options */
|
|
|
|
socket_opt = 1;
|
|
status =
|
|
setsockopt(ip_data->socket, SOL_SOCKET, SO_REUSEADDR, &socket_opt,
|
|
sizeof(socket_opt));
|
|
if (status < 0) {
|
|
close(ip_data->socket);
|
|
return false;
|
|
}
|
|
|
|
status =
|
|
setsockopt(ip_data->socket, SOL_SOCKET, SO_BROADCAST, &socket_opt,
|
|
sizeof(socket_opt));
|
|
if (status < 0) {
|
|
close(ip_data->socket);
|
|
return false;
|
|
}
|
|
|
|
/* bind the socket to the local port number */
|
|
sin.sin_family = AF_INET;
|
|
sin.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
sin.sin_port = ip_data->port;
|
|
|
|
memset(&sin.sin_zero, '\0', sizeof(sin.sin_zero));
|
|
|
|
status =
|
|
bind(ip_data->socket, (const struct sockaddr *) &sin,
|
|
sizeof(struct sockaddr));
|
|
if (status < 0) {
|
|
close(ip_data->socket);
|
|
return false;
|
|
}
|
|
|
|
/* add BIP address to router port structure */
|
|
memcpy(&port->route_info.mac[0], &ip_data->local_addr.s_addr, 4);
|
|
memcpy(&port->route_info.mac[4], &port->params.bip_params.port, 2);
|
|
port->route_info.mac_len = 6;
|
|
|
|
PRINT(INFO, "Interface: %s\n", port->iface);
|
|
PRINT(INFO, "IP Address: %s\n", inet_ntoa(ip_data->local_addr));
|
|
PRINT(INFO, "IP Broadcast Address: %s\n",
|
|
inet_ntoa(ip_data->broadcast_addr));
|
|
PRINT(INFO, "UDP Port: 0x%04X [%hu]\n", (port->params.bip_params.port),
|
|
(port->params.bip_params.port));
|
|
|
|
return true;
|
|
}
|
|
|
|
int dl_ip_send(
|
|
IP_DATA * data,
|
|
BACNET_ADDRESS * dest,
|
|
uint8_t * pdu,
|
|
unsigned pdu_len)
|
|
{
|
|
struct sockaddr_in bip_dest = { 0 };
|
|
int buff_len = 0;
|
|
int bytes_sent = 0;
|
|
|
|
if (data->socket < 0)
|
|
return -1;
|
|
|
|
data->buff[0] = BVLL_TYPE_BACNET_IP;
|
|
bip_dest.sin_family = AF_INET;
|
|
if (dest->net == BACNET_BROADCAST_NETWORK) {
|
|
/* broadcast */
|
|
bip_dest.sin_addr.s_addr = data->broadcast_addr.s_addr;
|
|
bip_dest.sin_port = data->port;
|
|
data->buff[1] = BVLC_ORIGINAL_BROADCAST_NPDU;
|
|
} else if (dest->mac_len == 6) {
|
|
memcpy(&bip_dest.sin_addr.s_addr, &dest->mac[0], 4);
|
|
memcpy(&bip_dest.sin_port, &dest->mac[4], 2);
|
|
data->buff[1] = BVLC_ORIGINAL_UNICAST_NPDU;
|
|
} else {
|
|
/* invalid address */
|
|
return -1;
|
|
}
|
|
|
|
buff_len = 2;
|
|
buff_len +=
|
|
encode_unsigned16(&data->buff[buff_len],
|
|
(uint16_t) (pdu_len + 4 /*inclusive */ ));
|
|
memcpy(&data->buff[buff_len], pdu, pdu_len);
|
|
buff_len += pdu_len;
|
|
|
|
/* send the packet */
|
|
bytes_sent =
|
|
sendto(data->socket, (char *) data->buff, buff_len, 0,
|
|
(struct sockaddr *) &bip_dest, sizeof(struct sockaddr));
|
|
|
|
PRINT(DEBUG, "send to %s\n", inet_ntoa(bip_dest.sin_addr));
|
|
|
|
return bytes_sent;
|
|
}
|
|
|
|
int dl_ip_recv(
|
|
IP_DATA * data,
|
|
MSG_DATA ** msg_data,
|
|
BACNET_ADDRESS * src,
|
|
unsigned timeout)
|
|
{
|
|
int received_bytes = 0;
|
|
uint16_t buff_len = 0; /* return value */
|
|
fd_set read_fds;
|
|
struct timeval select_timeout;
|
|
struct sockaddr_in sin = { 0 };
|
|
socklen_t sin_len = sizeof(sin);
|
|
|
|
/* make sure the socket is open */
|
|
if (data->socket < 0)
|
|
return 0;
|
|
|
|
if (timeout >= 1000) {
|
|
select_timeout.tv_sec = timeout / 1000;
|
|
select_timeout.tv_usec =
|
|
1000 * (timeout - select_timeout.tv_sec * 1000);
|
|
} else {
|
|
select_timeout.tv_sec = 0;
|
|
select_timeout.tv_usec = 1000 * timeout;
|
|
}
|
|
|
|
FD_ZERO(&read_fds);
|
|
FD_SET(data->socket, &read_fds);
|
|
|
|
#ifdef TEST_PACKET
|
|
received_bytes = sizeof(test_packet);
|
|
memmove(data->buff, &test_packet, received_bytes);
|
|
sin.sin_addr.s_addr = 0x7E1D40A;
|
|
sin.sin_port = 0xC0BA;
|
|
#else
|
|
int ret = select(data->socket + 1, &read_fds, NULL, NULL, &select_timeout);
|
|
/* see if there is a packet for us */
|
|
if (ret > 0)
|
|
received_bytes =
|
|
recvfrom(data->socket, (char *) &data->buff[0], data->max_buff, 0,
|
|
(struct sockaddr *) &sin, &sin_len);
|
|
else
|
|
return 0;
|
|
#endif
|
|
PRINT(DEBUG, "received from %s\n", inet_ntoa(sin.sin_addr));
|
|
|
|
/* check for errors */
|
|
if (received_bytes <= 0) {
|
|
return 0;
|
|
}
|
|
|
|
/* the signature of a BACnet/IP packet */
|
|
if (data->buff[0] != BVLL_TYPE_BACNET_IP)
|
|
return 0;
|
|
|
|
switch (data->buff[1]) {
|
|
case BVLC_ORIGINAL_UNICAST_NPDU:
|
|
case BVLC_ORIGINAL_BROADCAST_NPDU:{
|
|
if ((sin.sin_addr.s_addr == data->local_addr.s_addr) &&
|
|
(sin.sin_port == data->port)) {
|
|
buff_len = 0;
|
|
|
|
PRINT(DEBUG, "BIP: src is me. Discarded!\n");
|
|
|
|
} else {
|
|
src->mac_len = 6;
|
|
memcpy(&src->mac[0], &sin.sin_addr.s_addr, 4);
|
|
memcpy(&src->mac[4], &sin.sin_port, 2);
|
|
|
|
(void) decode_unsigned16(&data->buff[2], &buff_len);
|
|
/* subtract off the BVLC header */
|
|
buff_len -= 4;
|
|
if (buff_len < data->max_buff) {
|
|
/* allocate data message stucture */
|
|
(*msg_data) = (MSG_DATA *) malloc(sizeof(MSG_DATA));
|
|
(*msg_data)->pdu_len = buff_len;
|
|
(*msg_data)->pdu =
|
|
(uint8_t *) malloc((*msg_data)->pdu_len);
|
|
/* fill up data message structure */
|
|
memmove(&(*msg_data)->pdu[0], &data->buff[4],
|
|
(*msg_data)->pdu_len);
|
|
memmove(&(*msg_data)->src, src,
|
|
sizeof(BACNET_ADDRESS));
|
|
}
|
|
/* ignore packets that are too large */
|
|
else {
|
|
buff_len = 0;
|
|
|
|
PRINT(ERROR, "BIP: PDU too large. Discarded!.\n");
|
|
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case BVLC_FORWARDED_NPDU:{
|
|
memcpy(&sin.sin_addr.s_addr, &data->buff[4], 4);
|
|
memcpy(&sin.sin_port, &data->buff[8], 2);
|
|
if ((sin.sin_addr.s_addr == data->local_addr.s_addr) &&
|
|
(sin.sin_port == data->port)) {
|
|
buff_len = 0;
|
|
} else {
|
|
src->mac_len = 6;
|
|
memcpy(&src->mac[0], &sin.sin_addr.s_addr, 4);
|
|
memcpy(&src->mac[4], &sin.sin_port, 2);
|
|
|
|
(void) decode_unsigned16(&data->buff[2], &buff_len);
|
|
/* subtract off the BVLC header */
|
|
buff_len -= 10;
|
|
if (buff_len < data->max_buff) {
|
|
/* allocate data message stucture */
|
|
(*msg_data) = (MSG_DATA *) malloc(sizeof(MSG_DATA));
|
|
(*msg_data)->pdu_len = buff_len;
|
|
(*msg_data)->pdu =
|
|
(uint8_t *) malloc((*msg_data)->pdu_len);
|
|
/* fill up data message structure */
|
|
memmove(&(*msg_data)->pdu[0], &data->buff[4 + 6],
|
|
(*msg_data)->pdu_len);
|
|
memmove(&(*msg_data)->src, src,
|
|
sizeof(BACNET_ADDRESS));
|
|
} else {
|
|
/* ignore packets that are too large */
|
|
buff_len = 0;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
|
|
PRINT(ERROR, "BIP: BVLC discarded!\n");
|
|
|
|
break;
|
|
}
|
|
return buff_len;
|
|
}
|
|
|
|
void dl_ip_cleanup(
|
|
IP_DATA * ip_data)
|
|
{
|
|
/* free buffer */
|
|
if (ip_data->buff)
|
|
free(ip_data->buff);
|
|
/* close socket */
|
|
if (ip_data->socket > 0)
|
|
close(ip_data->socket);
|
|
return;
|
|
}
|