725 lines
26 KiB
C
725 lines
26 KiB
C
/*******************************************************************************
|
|
* Copyright (c) 2009, Rockwell Automation, Inc.
|
|
* All rights reserved.
|
|
*
|
|
******************************************************************************/
|
|
#include "ciptcpipinterface.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include "opener_user_conf.h"
|
|
#include "cipcommon.h"
|
|
#include "cipconnectionobject.h"
|
|
#include "cipmessagerouter.h"
|
|
#include "ciperror.h"
|
|
#include "cipstring.h"
|
|
#include "endianconv.h"
|
|
#include "cipethernetlink.h"
|
|
#include "opener_api.h"
|
|
#include "trace.h"
|
|
#include "cipassembly.h"
|
|
|
|
/* Define constants to initialize the config_capability attribute (#2). These
|
|
* are needed as defines because we use them for static initialization. */
|
|
#define CFG_CAPS_BOOTP_CLIENT 0x01U /**< Device has BOOTP client */
|
|
#define CFG_CAPS_DNS_CLIENT 0x02U /**< Device has DNS client */
|
|
#define CFG_CAPS_DHCP_CLIENT 0x04U /**< Device has DHCP client */
|
|
#define CFG_CAPS_CFG_SETTABLE 0x10U /**< Interface configuration can be set */
|
|
#define CFG_CAPS_CFG_CHG_NEEDS_RESET 0x40U /**< Interface configuration change needs RESET */
|
|
#define CFG_CAPS_ACD_CAPABLE 0x80U /**< Device supports ACD */
|
|
|
|
/* OPENER_TCPIP_IFACE_CFG_SETTABLE controls if the interface configuration is fully settable.
|
|
* Prepare additional defines needed here:
|
|
* - IFACE_CFG_SET_MODE is used to initialize the set mode of the affected attributes (3, 5, 6).
|
|
* - CFG_CAPS is the matching initial value for .config_capability
|
|
*/
|
|
#if defined (OPENER_TCPIP_IFACE_CFG_SETTABLE) && \
|
|
0 != OPENER_TCPIP_IFACE_CFG_SETTABLE
|
|
#define IFACE_CFG_SET_MODE kSetable
|
|
#define CFG_CAPS (CFG_CAPS_DHCP_CLIENT | CFG_CAPS_CFG_SETTABLE | \
|
|
CFG_CAPS_CFG_CHG_NEEDS_RESET)
|
|
#else
|
|
#define IFACE_CFG_SET_MODE kNotSetOrGetable
|
|
#define CFG_CAPS (CFG_CAPS_DHCP_CLIENT)
|
|
#endif
|
|
|
|
/** definition of TCP/IP object instance 1 data */
|
|
CipTcpIpObject g_tcpip =
|
|
{
|
|
.status = 0x01, /* attribute #1 TCP status with 1 we indicate that we got a valid configuration from DHCP, BOOTP or NV data */
|
|
.config_capability = CFG_CAPS, /* attribute #2 config_capability */
|
|
.config_control = 0x02, /* attribute #3 config_control: 0x02 means that the device shall obtain its interface configuration values via DHCP. */
|
|
#if 2 != OPENER_ETHLINK_INSTANCE_CNT
|
|
/* For the details where the physical_link_object path should point to, depending on the #
|
|
* of Ethernet Link objects refer to Vol. 2, Section 5-4.3.2.4 "Physical Link Object". */
|
|
.physical_link_object = { /* attribute #4 physical link object */
|
|
2, /* PathSize in 16 Bit chunks */
|
|
CIP_ETHERNETLINK_CLASS_CODE, /* Class Code */
|
|
OPENER_ETHLINK_INSTANCE_CNT, /* Instance # */
|
|
0 /* Attribute # (not used as this is the EPATH to the EthernetLink object)*/
|
|
},
|
|
#else
|
|
.physical_link_object = { /* attribute #4 physical link object */
|
|
0, /* PathSize in 16 Bit chunks */
|
|
0, /* Class Code */
|
|
0, /* Instance # */
|
|
0 /* Attribute # */
|
|
},
|
|
#endif /* #if OPENER_ETHLINK_INSTANCE_CNT != 2 */
|
|
.interface_configuration = { /* attribute #5 interface_configuration */
|
|
0, /* IP address */
|
|
0, /* NetworkMask */
|
|
0, /* Gateway */
|
|
0, /* NameServer */
|
|
0, /* NameServer2 */
|
|
{ /* DomainName */
|
|
0, NULL,
|
|
}
|
|
},
|
|
.hostname = { /* attribute #6 hostname */
|
|
0,
|
|
NULL
|
|
},
|
|
.mcast_ttl_value = 1, /* attribute #8 mcast TTL value */
|
|
.mcast_config = { /* attribute #9 multicast configuration */
|
|
0, /* use the default allocation algorithm */
|
|
0, /* reserved */
|
|
1, /* we currently use only one multicast address */
|
|
0 /* the multicast address will be allocated on IP address configuration */
|
|
},
|
|
.select_acd = false,
|
|
.encapsulation_inactivity_timeout = 120 /* attribute #13 encapsulation_inactivity_timeout, use a default value of 120 */
|
|
};
|
|
|
|
/************** Static Functions *********************************/
|
|
|
|
#if defined (OPENER_TCPIP_IFACE_CFG_SETTABLE) && \
|
|
0 != OPENER_TCPIP_IFACE_CFG_SETTABLE
|
|
/** Check for pb being an alphanumerical character
|
|
*
|
|
* Is slow but avoids issues with the locale if we're NOT in the 'C' locale.
|
|
*/
|
|
static bool isalnum_c(const EipByte byte) {
|
|
return
|
|
('a' <= byte && byte <= 'z') ||
|
|
('A' <= byte && byte <= 'Z') ||
|
|
('0' <= byte && byte <= '9');
|
|
}
|
|
|
|
/** Check passed string to conform to the rules for host name labels
|
|
*
|
|
* @param label pointer to the label string to check
|
|
* @return true if label is valid
|
|
*
|
|
* A host name label is a string of length 1 to 63 characters with
|
|
* the characters of the string conforming to this rules:
|
|
* - 1st character: [A-Za-z0-9]
|
|
* - next character: [-A-Za-z0-9]
|
|
* - last character: [A-Za-z0-9]
|
|
* The minimum length of 1 is checked but not the maximum length
|
|
* that has already been enforced on data reception.
|
|
*/
|
|
static bool IsValidNameLabel(const EipByte *label) {
|
|
if (!isalnum_c(*label) ) {
|
|
return false;
|
|
}
|
|
++label;
|
|
while ('\0' != *label && (isalnum_c(*label) || '-' == *label) ) {
|
|
++label;
|
|
}
|
|
return ('\0' == *label && '-' != label[-1]);
|
|
}
|
|
|
|
/** Check if domain is a valid domain
|
|
*
|
|
* @param p_domain pointer to domain string to check
|
|
* @return true if domain is valid
|
|
*
|
|
* We check here for domain names that are part of a valid host name.
|
|
* - Do not allow leading or trailing dots.
|
|
* - Also a single '.' (the root domain) is not allowed.
|
|
* - A complete numeric domain is accepted even if it should not.
|
|
* - IDN domain names are not supported. Any IDN domain names must
|
|
* be converted to punycode (see https://www.punycoder.com/) by
|
|
* the user in advance.
|
|
*/
|
|
static bool IsValidDomain(EipByte *domain) {
|
|
bool status = true;
|
|
|
|
OPENER_TRACE_INFO("Enter '%s'->", domain);
|
|
if ('.' == *domain) { /* Forbid leading dot */
|
|
return false;
|
|
}
|
|
EipByte *dot = (EipByte *)strchr( (char *)domain, '.' );
|
|
if (dot) {
|
|
bool rc;
|
|
|
|
*dot = '\0';
|
|
status &= rc = IsValidNameLabel(domain);
|
|
OPENER_TRACE_INFO("Checked %d '%s'\n", rc, domain);
|
|
if ('\0' != dot[1]) {
|
|
status &= IsValidDomain(dot + 1);
|
|
}
|
|
else { /* Forbid trailing dot */
|
|
status = false;
|
|
}
|
|
*dot = '.';
|
|
}
|
|
else {
|
|
status = IsValidNameLabel(domain);
|
|
OPENER_TRACE_INFO("Check end %d '%s'\n", status, domain);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
|
|
/** Check if an IP address is a valid network mask
|
|
*
|
|
* @param netmask network mask in network byte order
|
|
* @return valid status
|
|
*
|
|
* Check if it is a valid network mask pattern. The pattern 0xffffffff and
|
|
* 0x00000000 are considered as invalid.
|
|
*/
|
|
static bool IsValidNetmask(in_addr_t netmask) {
|
|
in_addr_t v = ntohl(netmask);
|
|
|
|
v = ~v; /* Create the host mask */
|
|
++v; /* This must be a power of 2 then */
|
|
bool valid = v && !(v & (v - 1) ); /* Check if it is a power of 2 */
|
|
|
|
return valid && (INADDR_BROADCAST != netmask);
|
|
}
|
|
|
|
/** Check if an IP address is in one of the network classes A, B or C
|
|
*
|
|
* @param ip_addr IP address in network byte order
|
|
* @return status
|
|
*
|
|
* Check if the IP address belongs to the network classes A, B or C.
|
|
*/
|
|
static bool IsInClassAbc(in_addr_t ip_addr) {
|
|
in_addr_t ip = ntohl(ip_addr);
|
|
return IN_CLASSA(ip) || IN_CLASSB(ip) || IN_CLASSC(ip);
|
|
}
|
|
|
|
/** Check if an IP address is on the loopback network
|
|
*
|
|
* @param ip_addr IP address in network byte order
|
|
* @return status
|
|
*
|
|
* Check if the IP address belongs to the loopback network
|
|
* 127.0.0.0 - 127.255.255.255.
|
|
*/
|
|
static bool IsOnLoopbackNetwork(in_addr_t ip_addr) {
|
|
in_addr_t ip = ntohl(ip_addr);
|
|
return (ip & IN_CLASSA_NET) == (INADDR_LOOPBACK & IN_CLASSA_NET);
|
|
}
|
|
|
|
/** Check if an IP address is either the network or the broadcast address
|
|
*
|
|
* @param ip_addr IP address in network byte order
|
|
* @param net_mask network mask in network byte order
|
|
* @return status
|
|
*
|
|
* Check if an IP address is either the network or the broadcast address.
|
|
* In this case it is not a valid IP address for a host.
|
|
* This check is endian agnostic.
|
|
*/
|
|
static bool IsNetworkOrBroadcastIp(in_addr_t ip_addr,
|
|
in_addr_t net_mask) {
|
|
return ( (ip_addr & net_mask) == ip_addr ) || /* is network address */
|
|
( (ip_addr | ~net_mask) == ip_addr ); /* is broadcast address */
|
|
}
|
|
|
|
/** Check the Interface configuration being valid according to EIP specification
|
|
*
|
|
* In Vol. 2 the "Table 5-4.3 Instance Attributes" provides some information
|
|
* which checks should be carried out on the Interface configuration's IP
|
|
* addresses. Also there are some hints in the
|
|
* Figure 5-4.1 "Diagram Showing the Behavior of the TCP/IP Object".
|
|
*
|
|
* The following checks may carried out on the IP addresses:
|
|
* - N0: IP is not 0 aka. INADDR_ANY
|
|
* - MASK: IP is a valid network mask
|
|
* - ABC: IP is in class A, B or C
|
|
* - NLCL: IP is not localhost aka. INADDR_LOOPBACK
|
|
* - NB: IP is neither network or broadcast address (using network_mask)
|
|
*
|
|
* This is the table which checks are applied to what IP:
|
|
* N0 | MASK | ABC | NLCL | NB | IP address
|
|
* + | - | + | + | + | ip_address
|
|
* - | + | - | - | - | network_mask
|
|
* - | - | + | + | + | gateway
|
|
* - | - | + | - | - | name_server / name_server_2
|
|
* A configured gateway must be reachable according to the network mask.
|
|
*/
|
|
static bool IsValidNetworkConfig(const CipTcpIpInterfaceConfiguration *if_cfg) {
|
|
if (INADDR_ANY == ntohl(if_cfg->ip_address) ) { /* N0 */
|
|
return false;
|
|
}
|
|
if (INADDR_ANY != ntohl(if_cfg->network_mask) && /* MASK */
|
|
!IsValidNetmask(if_cfg->network_mask) ) {
|
|
return false;
|
|
}
|
|
if (!IsInClassAbc(if_cfg->ip_address) || /* ABC */
|
|
!IsInClassAbc(if_cfg->gateway) ||
|
|
!IsInClassAbc(if_cfg->name_server) ||
|
|
!IsInClassAbc(if_cfg->name_server_2) ) {
|
|
return false;
|
|
}
|
|
if (IsOnLoopbackNetwork(if_cfg->ip_address) || /* NLCL */
|
|
IsOnLoopbackNetwork(if_cfg->gateway) ) {
|
|
return false;
|
|
}
|
|
/* Check NB */
|
|
if (IsNetworkOrBroadcastIp(if_cfg->ip_address, if_cfg->network_mask) ||
|
|
(INADDR_ANY != ntohl(if_cfg->gateway) &&
|
|
IsNetworkOrBroadcastIp(if_cfg->gateway, if_cfg->network_mask) ) ) {
|
|
return false;
|
|
}
|
|
if (INADDR_ANY != ntohl(if_cfg->gateway) &&
|
|
INADDR_ANY != ntohl(if_cfg->network_mask) ) {
|
|
/* gateway is configured. Check if it is reachable. */
|
|
if ( (if_cfg->network_mask & if_cfg->ip_address) !=
|
|
(if_cfg->network_mask & if_cfg->gateway) ) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool IsIOConnectionActive(void) {
|
|
DoublyLinkedListNode *node = connection_list.first;
|
|
|
|
while (NULL != node) {
|
|
CipConnectionObject *connection = node->data;
|
|
if (ConnectionObjectIsTypeIOConnection(connection) &&
|
|
kConnectionObjectStateTimedOut !=
|
|
ConnectionObjectGetState(connection) ) {
|
|
/* An IO connection is present but is only considered active
|
|
* if it is NOT in timeout state. */
|
|
return true;
|
|
}
|
|
node = node->next;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
#endif /* defined (OPENER_TCPIP_IFACE_CFG_SETTABLE) && 0 != OPENER_TCPIP_IFACE_CFG_SETTABLE*/
|
|
|
|
|
|
static CipUsint dummy_data_field = 0; /**< dummy data fiel to provide non-null data pointers for attributes without data fields */
|
|
|
|
/************** Functions ****************************************/
|
|
|
|
void EncodeCipTcpIpInterfaceConfiguration(const void *const data,
|
|
ENIPMessage *const outgoing_message)
|
|
{
|
|
CipTcpIpInterfaceConfiguration *
|
|
tcp_ip_network_interface_configuration =
|
|
(CipTcpIpInterfaceConfiguration *) data;
|
|
AddDintToMessage(ntohl(tcp_ip_network_interface_configuration->ip_address),
|
|
outgoing_message);
|
|
AddDintToMessage(ntohl(tcp_ip_network_interface_configuration->network_mask),
|
|
outgoing_message);
|
|
AddDintToMessage(ntohl(tcp_ip_network_interface_configuration->gateway),
|
|
outgoing_message);
|
|
AddDintToMessage(ntohl(tcp_ip_network_interface_configuration->name_server),
|
|
outgoing_message);
|
|
AddDintToMessage(ntohl(tcp_ip_network_interface_configuration->name_server_2),
|
|
outgoing_message);
|
|
EncodeCipString(&(tcp_ip_network_interface_configuration->domain_name),
|
|
outgoing_message);
|
|
}
|
|
|
|
void EncodeCipTcpIpMulticastConfiguration(const void *const data,
|
|
ENIPMessage *const outgoing_message) {
|
|
/* Suppress unused parameter compiler warning. */
|
|
(void)data;
|
|
|
|
EncodeCipUsint(&(g_tcpip.mcast_config.alloc_control), outgoing_message);
|
|
EncodeCipUsint(&(g_tcpip.mcast_config.reserved_shall_be_zero),
|
|
outgoing_message);
|
|
EncodeCipUint(&(g_tcpip.mcast_config.number_of_allocated_multicast_addresses),
|
|
outgoing_message);
|
|
|
|
CipUdint multicast_address = ntohl(
|
|
g_tcpip.mcast_config.starting_multicast_address);
|
|
|
|
EncodeCipUdint(&multicast_address, outgoing_message);
|
|
}
|
|
|
|
void EncodeSafetyNetworkNumber(const void *const data,
|
|
ENIPMessage *const outgoing_message) {
|
|
/* Suppress unused parameter compiler warning. */
|
|
(void)data;
|
|
|
|
FillNextNMessageOctetsWithValueAndMoveToNextPosition(0, 6, outgoing_message);
|
|
}
|
|
|
|
void EncodeCipLastConflictDetected(const void *const data,
|
|
ENIPMessage *const outgoing_message) {
|
|
/* Suppress unused parameter compiler warning. */
|
|
(void)data;
|
|
|
|
const size_t kAttribute11Size = sizeof(CipUsint) + 6 * sizeof(CipUsint) + 28 *
|
|
sizeof(CipUsint);
|
|
OPENER_ASSERT(kAttribute11Size == 35);
|
|
FillNextNMessageOctetsWithValueAndMoveToNextPosition(0,
|
|
kAttribute11Size,
|
|
outgoing_message);
|
|
}
|
|
|
|
|
|
int DecodeTcpIpInterfaceConfigurationControl( /* Attribute 3 */
|
|
void *const data,
|
|
CipMessageRouterRequest *const message_router_request,
|
|
CipMessageRouterResponse *const message_router_response) {
|
|
|
|
int number_of_decoded_bytes = -1;
|
|
|
|
CipDword configuration_control_received = GetDintFromMessage(
|
|
&(message_router_request->data));
|
|
if ((configuration_control_received & kTcpipCfgCtrlMethodMask) >= 0x03
|
|
|| (configuration_control_received & ~kTcpipCfgCtrlMethodMask)) {
|
|
message_router_response->general_status =
|
|
kCipErrorInvalidAttributeValue;
|
|
|
|
} else {
|
|
|
|
/* Set reserved bits to zero on reception. */
|
|
configuration_control_received &= (kTcpipCfgCtrlMethodMask
|
|
| kTcpipCfgCtrlDnsEnable);
|
|
|
|
*(CipDword *)data = configuration_control_received;
|
|
number_of_decoded_bytes = 4;
|
|
message_router_response->general_status = kCipErrorSuccess;
|
|
}
|
|
|
|
return number_of_decoded_bytes;
|
|
}
|
|
|
|
#if defined (OPENER_TCPIP_IFACE_CFG_SETTABLE) && \
|
|
0 != OPENER_TCPIP_IFACE_CFG_SETTABLE
|
|
|
|
int DecodeCipTcpIpInterfaceConfiguration( /* Attribute 5 */
|
|
CipTcpIpInterfaceConfiguration *const data, //kCipUdintUdintUdintUdintUdintString
|
|
CipMessageRouterRequest *const message_router_request,
|
|
CipMessageRouterResponse *const message_router_response) {
|
|
|
|
int number_of_decoded_bytes = -1;
|
|
|
|
CipTcpIpInterfaceConfiguration if_cfg;
|
|
CipUdint tmp_ip;
|
|
|
|
if (IsIOConnectionActive()) {
|
|
message_router_response->general_status = kCipErrorDeviceStateConflict;
|
|
return number_of_decoded_bytes;
|
|
}
|
|
if (kTcpipCfgCtrlStaticIp
|
|
!= (g_tcpip.config_control & kTcpipCfgCtrlMethodMask)) {
|
|
message_router_response->general_status = kCipErrorObjectStateConflict;
|
|
return number_of_decoded_bytes;
|
|
}
|
|
memset(&if_cfg, 0, sizeof if_cfg);
|
|
tmp_ip = GetUdintFromMessage(&(message_router_request->data));
|
|
if_cfg.ip_address = htonl(tmp_ip);
|
|
tmp_ip = GetUdintFromMessage(&(message_router_request->data));
|
|
if_cfg.network_mask = htonl(tmp_ip);
|
|
tmp_ip = GetUdintFromMessage(&(message_router_request->data));
|
|
if_cfg.gateway = htonl(tmp_ip);
|
|
tmp_ip = GetUdintFromMessage(&(message_router_request->data));
|
|
if_cfg.name_server = htonl(tmp_ip);
|
|
tmp_ip = GetUdintFromMessage(&(message_router_request->data));
|
|
if_cfg.name_server_2 = htonl(tmp_ip);
|
|
|
|
CipUint domain_name_length = GetUintFromMessage(
|
|
&(message_router_request->data));
|
|
if (domain_name_length > 48) { /* see Vol. 2, Table 5-4.3 Instance Attributes */
|
|
message_router_response->general_status = kCipErrorTooMuchData;
|
|
return number_of_decoded_bytes;
|
|
}
|
|
SetCipStringByData(&if_cfg.domain_name, domain_name_length,
|
|
message_router_request->data);
|
|
domain_name_length = (domain_name_length + 1) & (~0x0001u); /* Align for possible pad byte */
|
|
OPENER_TRACE_INFO("Domain: ds %hu '%s'\n",
|
|
domain_name_length,
|
|
if_cfg.domain_name.string);
|
|
|
|
if (!IsValidNetworkConfig(&if_cfg)
|
|
|| (domain_name_length > 0
|
|
&& !IsValidDomain(if_cfg.domain_name.string))) {
|
|
message_router_response->general_status =
|
|
kCipErrorInvalidAttributeValue;
|
|
return number_of_decoded_bytes;
|
|
}
|
|
|
|
*data = if_cfg; //write data to attribute
|
|
number_of_decoded_bytes = 20 + domain_name_length;
|
|
|
|
/* Tell that this configuration change becomes active after a reset */
|
|
g_tcpip.status |= kTcpipStatusIfaceCfgPend;
|
|
message_router_response->general_status = kCipErrorSuccess;
|
|
|
|
return number_of_decoded_bytes;
|
|
|
|
}
|
|
|
|
int DecodeCipTcpIpInterfaceHostName( /* Attribute 6 */
|
|
CipString *const data,
|
|
CipMessageRouterRequest *const message_router_request,
|
|
CipMessageRouterResponse *const message_router_response) {
|
|
|
|
int number_of_decoded_bytes = -1;
|
|
|
|
CipString tmp_host_name = {
|
|
.length = 0u,
|
|
.string = NULL
|
|
};
|
|
CipUint host_name_length =
|
|
GetUintFromMessage(&(message_router_request->data) );
|
|
if (host_name_length > 64) { /* see RFC 1123 on more details */
|
|
message_router_response->general_status = kCipErrorTooMuchData;
|
|
return number_of_decoded_bytes;
|
|
}
|
|
SetCipStringByData(&tmp_host_name,
|
|
host_name_length,
|
|
message_router_request->data);
|
|
host_name_length = (host_name_length + 1) & (~0x0001u); /* Align for possible pad byte */
|
|
OPENER_TRACE_INFO("Host Name: ds %hu '%s'\n",
|
|
host_name_length,
|
|
tmp_host_name.string);
|
|
|
|
if (!IsValidNameLabel(tmp_host_name.string) ) {
|
|
message_router_response->general_status =
|
|
kCipErrorInvalidAttributeValue;
|
|
return number_of_decoded_bytes;
|
|
}
|
|
|
|
*data = tmp_host_name; //write data to attribute
|
|
|
|
/* Tell that this configuration change becomes active after a reset */
|
|
g_tcpip.status |= kTcpipStatusIfaceCfgPend;
|
|
message_router_response->general_status = kCipErrorSuccess;
|
|
|
|
return number_of_decoded_bytes;
|
|
|
|
}
|
|
|
|
#endif /* defined (OPENER_TCPIP_IFACE_CFG_SETTABLE) && 0 != OPENER_TCPIP_IFACE_CFG_SETTABLE*/
|
|
|
|
int DecodeCipTcpIpInterfaceEncapsulationInactivityTimeout( /* Attribute 13 */
|
|
void *const data,
|
|
CipMessageRouterRequest *const message_router_request,
|
|
CipMessageRouterResponse *const message_router_response) {
|
|
|
|
int number_of_decoded_bytes = -1;
|
|
|
|
CipUint inactivity_timeout_received = GetUintFromMessage(
|
|
&(message_router_request->data));
|
|
|
|
if (inactivity_timeout_received > 3600) {
|
|
message_router_response->general_status =
|
|
kCipErrorInvalidAttributeValue;
|
|
} else {
|
|
|
|
*(CipUint *)data = inactivity_timeout_received;
|
|
message_router_response->general_status = kCipErrorSuccess;
|
|
number_of_decoded_bytes = 2;
|
|
|
|
}
|
|
|
|
return number_of_decoded_bytes;
|
|
|
|
}
|
|
|
|
|
|
EipStatus CipTcpIpInterfaceInit() {
|
|
CipClass *tcp_ip_class = NULL;
|
|
|
|
if ( ( tcp_ip_class = CreateCipClass(kCipTcpIpInterfaceClassCode, /* class code */
|
|
0, /* # class attributes */
|
|
7, /* # highest class attribute number */
|
|
2, /* # class services */
|
|
13, /* # instance attributes */
|
|
13, /* # highest instance attribute number */
|
|
3, /* # instance services */
|
|
1, /* # instances */
|
|
"TCP/IP interface", 4, /* # class revision */
|
|
NULL /* # function pointer for initialization */
|
|
) ) == 0 ) {
|
|
return kEipStatusError;
|
|
}
|
|
|
|
CipInstance *instance = GetCipInstance(tcp_ip_class, 1); /* bind attributes to the instance #1 that was created above */
|
|
|
|
InsertAttribute(instance,
|
|
1,
|
|
kCipDword,
|
|
EncodeCipDword,
|
|
NULL,
|
|
&g_tcpip.status,
|
|
kGetableSingleAndAll);
|
|
InsertAttribute(instance,
|
|
2,
|
|
kCipDword,
|
|
EncodeCipDword,
|
|
NULL,
|
|
&g_tcpip.config_capability,
|
|
kGetableSingleAndAll);
|
|
InsertAttribute(instance,
|
|
3,
|
|
kCipDword,
|
|
EncodeCipDword,
|
|
DecodeTcpIpInterfaceConfigurationControl,
|
|
&g_tcpip.config_control,
|
|
kSetAndGetAble | kNvDataFunc | IFACE_CFG_SET_MODE );
|
|
InsertAttribute(instance,
|
|
4,
|
|
kCipEpath,
|
|
EncodeCipEPath,
|
|
NULL,
|
|
&g_tcpip.physical_link_object,
|
|
kGetableSingleAndAll);
|
|
|
|
#if defined (OPENER_TCPIP_IFACE_CFG_SETTABLE) && \
|
|
0 != OPENER_TCPIP_IFACE_CFG_SETTABLE
|
|
InsertAttribute(instance,
|
|
5,
|
|
kCipUdintUdintUdintUdintUdintString,
|
|
EncodeCipTcpIpInterfaceConfiguration,
|
|
DecodeCipTcpIpInterfaceConfiguration,
|
|
&g_tcpip.interface_configuration,
|
|
kGetableSingleAndAll | kNvDataFunc | IFACE_CFG_SET_MODE);
|
|
InsertAttribute(instance,
|
|
6,
|
|
kCipString,
|
|
EncodeCipString,
|
|
DecodeCipTcpIpInterfaceHostName,
|
|
&g_tcpip.hostname,
|
|
kGetableSingleAndAll | kNvDataFunc | IFACE_CFG_SET_MODE);
|
|
|
|
#else
|
|
InsertAttribute(instance,
|
|
5,
|
|
kCipUdintUdintUdintUdintUdintString,
|
|
EncodeCipTcpIpInterfaceConfiguration,
|
|
NULL, //not settable
|
|
&g_tcpip.interface_configuration,
|
|
kGetableSingleAndAll | kNvDataFunc | IFACE_CFG_SET_MODE);
|
|
InsertAttribute(instance,
|
|
6,
|
|
kCipString,
|
|
EncodeCipString,
|
|
NULL, //not settable
|
|
&g_tcpip.hostname,
|
|
kGetableSingleAndAll | kNvDataFunc | IFACE_CFG_SET_MODE);
|
|
|
|
#endif /* defined (OPENER_TCPIP_IFACE_CFG_SETTABLE) && 0 != OPENER_TCPIP_IFACE_CFG_SETTABLE*/
|
|
|
|
InsertAttribute(instance,
|
|
7,
|
|
kCipAny,
|
|
EncodeSafetyNetworkNumber,
|
|
NULL,
|
|
&dummy_data_field,
|
|
kGetableAllDummy);
|
|
InsertAttribute(instance,
|
|
8,
|
|
kCipUsint,
|
|
EncodeCipUsint,
|
|
NULL,
|
|
&g_tcpip.mcast_ttl_value,
|
|
kGetableSingleAndAll);
|
|
InsertAttribute(instance,
|
|
9,
|
|
kCipAny,
|
|
EncodeCipTcpIpMulticastConfiguration,
|
|
NULL,
|
|
&g_tcpip.mcast_config,
|
|
kGetableSingleAndAll);
|
|
InsertAttribute(instance,
|
|
10,
|
|
kCipBool,
|
|
EncodeCipBool,
|
|
NULL,
|
|
&g_tcpip.select_acd,
|
|
kGetableAllDummy);
|
|
InsertAttribute(instance,
|
|
11,
|
|
kCipBool,
|
|
EncodeCipLastConflictDetected,
|
|
NULL,
|
|
&dummy_data_field,
|
|
kGetableAllDummy);
|
|
InsertAttribute(instance,
|
|
12,
|
|
kCipBool,
|
|
EncodeCipBool,
|
|
NULL,
|
|
&dummy_data_field, kGetableAllDummy);
|
|
InsertAttribute(instance,
|
|
13,
|
|
kCipUint,
|
|
EncodeCipUint,
|
|
DecodeCipTcpIpInterfaceEncapsulationInactivityTimeout,
|
|
&g_tcpip.encapsulation_inactivity_timeout,
|
|
kSetAndGetAble | kNvDataFunc);
|
|
|
|
InsertService(tcp_ip_class, kGetAttributeSingle,
|
|
&GetAttributeSingle,
|
|
"GetAttributeSingle");
|
|
|
|
InsertService(tcp_ip_class, kGetAttributeAll, &GetAttributeAll,
|
|
"GetAttributeAll");
|
|
|
|
InsertService(tcp_ip_class, kSetAttributeSingle,
|
|
&SetAttributeSingle,
|
|
"SetAttributeSingle");
|
|
|
|
return kEipStatusOk;
|
|
}
|
|
|
|
void ShutdownTcpIpInterface(void) {
|
|
/*Only free the resources if they are initialized */
|
|
if (NULL != g_tcpip.hostname.string) {
|
|
CipFree(g_tcpip.hostname.string);
|
|
g_tcpip.hostname.string = NULL;
|
|
}
|
|
|
|
if (NULL != g_tcpip.interface_configuration.domain_name.string) {
|
|
CipFree(g_tcpip.interface_configuration.domain_name.string);
|
|
g_tcpip.interface_configuration.domain_name.string = NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function calculates the multicast base address to be used for CIP
|
|
* connections from the current IP setting. The algorithm is implemented
|
|
* according to CIP spec Volume 2,
|
|
* section 3-5.3 "Multicast Address Allocation for EtherNet/IP"
|
|
*/
|
|
void CipTcpIpCalculateMulticastIp(CipTcpIpObject *const tcpip) {
|
|
/* Multicast base address according to spec: 239.192.1.0 */
|
|
static const CipUdint cip_mcast_base_addr = 0xEFC00100;
|
|
|
|
/* Calculate the CIP multicast address. The multicast address is calculated, not input */
|
|
CipUdint host_id = ntohl(tcpip->interface_configuration.ip_address) &
|
|
~ntohl(tcpip->interface_configuration.network_mask);
|
|
host_id -= 1;
|
|
host_id &= 0x3ff;
|
|
|
|
tcpip->mcast_config.starting_multicast_address =
|
|
htonl(cip_mcast_base_addr + (host_id << 5) );
|
|
}
|
|
|
|
|
|
EipUint16 GetEncapsulationInactivityTimeout(CipInstance *instance) {
|
|
CipAttributeStruct *attribute = GetCipAttribute(instance, 13);
|
|
OPENER_ASSERT(NULL != attribute);
|
|
CipUint *data = (CipUint *) attribute->data;
|
|
EipUint16 encapsulation_inactivity_timeout = *data;
|
|
return encapsulation_inactivity_timeout;
|
|
}
|
|
|