Procházet zdrojové kódy

Enhancement: Add NV data support for TCP/IP object attributes 3, 5, 6, 13

The mentioned attributes now can be saved to NV storage when they are set
from the EIP network.

This patch adds also the define OPENER_TCPIP_IFACE_CFG_SETTABLE to
opener_user_conf.h to decide whether the network interface configuration
in the attributes 3, 5 and 6 is settable from the EIP network.

This patch changes the way the network is "initialized" in the main routine
of the sample_application. Changes are marked with NEW below. The live
cycle is approximately:
- NEW: Check if interface is present and get the MAC address to the static
  EthernetLink object using ConfigureMacAddressByInterface().
- Initialize the stack objects and structures using CipStackInit(). The
  attributes are initialized with their default values.
- NEW: Set up application specific attribute values (here setting the
  current hostname with ConfigureHostName()).
- Load the NV data using NvdataLoad(). This will now also load the TCP/IP
  objects network configuration (DHCP or static IP configuration).
- NEW: Based on the NV data the network is started with BringupNetwork().
  This is only a dummy macro atm. Needs to be implemented.
- NEW: Need to wait for the network having an IP address (forever). This
  can be canceled via a signal.
- Set the CIP network layer up using NetworkHandlerInitialize().
- Handle the EIP stack in the event loop using NetworkHandlerProcessOnce().
  The event loop will also update the NV data using the callbacks from the
  object's SetAttribute routines. The event loop can be canceled via a
  signal.
- If the EIP stack event loop is aborted the ShutdownCipStack() should
  shut down the network sockets too.
- NEW: Shut down the network using ShutdownNetwork(). This is a dummy macro
  atm. and needs to be implemented.

Signed-off-by: Stefan Mätje <stefan.maetje@esd.eu>
Stefan Mätje před 6 roky
rodič
revize
10cf08fe31

+ 334 - 13
source/src/cip/ciptcpipinterface.c

@@ -7,22 +7,45 @@
 
 #include <string.h>
 
+#include <netinet/in.h> /* IN_CLASSx() macros / INADDR_LOOPBACK / etc. */
+
 #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_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) && OPENER_TCPIP_IFACE_CFG_SETTABLE != 0
+  #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 or BOOTP */
-  .config_capability = 0x04, /* attribute #2 config_capability: This is a default value meaning that it is a DHCP client see 5-3.2.2.2 EIP specification. */
+  .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. */
   .physical_link_object = {     /* attribute #4 physical link object */
     2,  /* PathSize in 16 Bit chunks */
@@ -54,6 +77,204 @@ CipTcpIpObject g_tcpip =
   .encapsulation_inactivity_timeout = 120 /* attribute #13 encapsulation_inactivity_timeout, use a default value of 120 */
 };
 
+/************** Static Functions *********************************/
+
+#if defined (OPENER_TCPIP_IFACE_CFG_SETTABLE) && OPENER_TCPIP_IFACE_CFG_SETTABLE != 0
+/** Check for pb being an alphanumerical character
+ *
+ * Is slow but avoids issues with the locale if we're NOT int 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  p_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 *p_label)
+{
+  if (!isalnum_c(*p_label)) {
+    return false;
+  }
+  ++p_label;
+  while ('\0' != *p_label && (isalnum_c(*p_label) || '-' == *p_label)) {
+    ++p_label;
+  }
+  return ('\0' == *p_label && '-' != p_label[-1]);
+}
+
+/** Check if p_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 *p_domain)
+{
+  bool status = true;
+
+  OPENER_TRACE_INFO("Enter '%s'->", p_domain);
+  if ('.' == *p_domain) { /* Forbid leading dot */
+    return false;
+  }
+  EipByte *p_dot = (EipByte *)strchr((char *)p_domain, '.');
+  if (p_dot) {
+    bool rc;
+
+    *p_dot = '\0';
+    status &= rc = IsValidNameLabel(p_domain);
+    OPENER_TRACE_INFO("Checked %d '%s'\n", rc, p_domain);
+    if ('\0' != p_dot[1]) {
+      status &= IsValidDomain(p_dot +1);
+    }
+    else {  /* Forbid trailing dot */
+      status = false;
+    }
+    *p_dot = '.';
+  }
+  else {
+    status = IsValidNameLabel(p_domain);
+    OPENER_TRACE_INFO("Check end %d '%s'\n", status, p_domain);
+  }
+  return status;
+}
+
+
+/** Check if a 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)
+{
+    bool valid;
+    in_addr_t v = ntohl(netmask);
+
+    v = ~v; /* Create the host mask */
+    ++v;    /* This must be a power of 2 then */
+    valid = v && !(v & (v - 1)); /* Check if it is a power of 2 */
+
+    return valid && (INADDR_BROADCAST != netmask);
+}
+
+/** Check if a 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 a 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 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
+ *
+ * This is the table which checks are applied to what IP:
+ *  N0 | MASK | ABC | NLCL | 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 *p_if_cfg)
+{
+  if (INADDR_ANY == ntohl(p_if_cfg->ip_address)) {  /* N0 */
+    return false;
+  }
+  if (INADDR_ANY != ntohl(p_if_cfg->network_mask) &&  /* MASK */
+      !IsValidNetmask(p_if_cfg->network_mask)) {
+    return false;
+  }
+  if (!IsInClassAbc(p_if_cfg->ip_address) ||        /* ABC */
+      !IsInClassAbc(p_if_cfg->gateway) ||
+      !IsInClassAbc(p_if_cfg->name_server) ||
+      !IsInClassAbc(p_if_cfg->name_server_2)) {
+    return false;
+  }
+  if (IsOnLoopbackNetwork(p_if_cfg->ip_address) ||  /* NLCL */
+      IsOnLoopbackNetwork(p_if_cfg->gateway)) {
+    return false;
+  }
+  if (INADDR_ANY != ntohl(p_if_cfg->gateway) &&
+      INADDR_ANY != ntohl(p_if_cfg->network_mask)) {
+    /* gateway is configured. Check if it is reachable. */
+    if ((p_if_cfg->network_mask & p_if_cfg->ip_address) !=
+        (p_if_cfg->network_mask & p_if_cfg->gateway)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+static bool IsIOConnectionActive(void)
+{
+  DoublyLinkedListNode *node = connection_list.first;
+
+  while (NULL != node) {
+    CipConnectionObject *p_connection = node->data;
+    if (ConnectionObjectIsTypeIOConnection(p_connection)) {
+      /* An IO connection is found */
+      return true;
+    }
+    node = node->next;
+  }
+
+  return false;
+}
+#endif /* defined (OPENER_TCPIP_IFACE_CFG_SETTABLE) && OPENER_TCPIP_IFACE_CFG_SETTABLE != 0*/
 
 
 /************** Functions ****************************************/
@@ -79,7 +300,6 @@ EipStatus SetAttributeSingleTcpIpInterface(
   const int encapsulation_session) {
   CipAttributeStruct *attribute = GetCipAttribute(
     instance, message_router_request->request_path.attribute_number);
-  (void) instance; /*Suppress compiler warning */
   EipUint16 attribute_number = message_router_request->request_path
                                .attribute_number;
 
@@ -88,12 +308,20 @@ EipStatus SetAttributeSingleTcpIpInterface(
                                                                 attribute_number)
                             ]);
     if ( set_bit_mask & ( 1 << ( (attribute_number) % 8 ) ) ) {
+
+      if (attribute->attribute_flags & kPreSetFunc
+          && instance->cip_class->PreSetCallback) {
+          instance->cip_class->PreSetCallback(instance,
+                                              attribute,
+                                              message_router_request->service);
+      }
+
       switch (attribute_number) {
         case 3: {
-          CipUint configuration_control_recieved = GetDintFromMessage(
+          CipDword configuration_control_received = GetUdintFromMessage(
             &(message_router_request->data) );
-          if ( (configuration_control_recieved >= 0x03)
-               && (configuration_control_recieved <= 0x0F) ) {
+          if ((configuration_control_received & kTcpipCfgCtrlMethodMask) >= 0x03 ||
+              (configuration_control_received & ~kTcpipCfgCtrlMethodMask)) {
             message_router_response->general_status =
               kCipErrorInvalidAttributeValue;
 
@@ -103,7 +331,9 @@ EipStatus SetAttributeSingleTcpIpInterface(
 
             if (attribute->data != NULL) {
               CipDword *data = (CipDword *) attribute->data;
-              *(data) = configuration_control_recieved;
+              /* Set reserved bits to zero on reception. */
+              configuration_control_received &= (kTcpipCfgCtrlMethodMask | kTcpipCfgCtrlDnsEnable);
+              *(data) = configuration_control_received;
               message_router_response->general_status = kCipErrorSuccess;
             } else {
               message_router_response->general_status = kCipErrorNotEnoughData;
@@ -112,9 +342,89 @@ EipStatus SetAttributeSingleTcpIpInterface(
         }
         break;
 
+#if defined (OPENER_TCPIP_IFACE_CFG_SETTABLE) && OPENER_TCPIP_IFACE_CFG_SETTABLE != 0
+        case 5: { /* Interface configuration */
+          CipTcpIpInterfaceConfiguration if_cfg;
+          CipUdint  tmp_ip;
+
+          if (IsIOConnectionActive()) {
+            message_router_response->general_status = kCipErrorDeviceStateConflict;
+            break;
+          }
+          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 s_len = GetUintFromMessage(&(message_router_request->data));
+          if (s_len > 48) {  /* see Vol. 2, Table 5-4.3 Instance Attributes */
+            message_router_response->general_status = kCipErrorTooMuchData;
+            break;
+          }
+          SetCipStringByData(&if_cfg.domain_name, s_len, message_router_request->data);
+          s_len = (s_len +1) & (~0x0001u);  /* Align for possible pad byte */
+          OPENER_TRACE_INFO("Domain: ds %hu '%s'\n", s_len, if_cfg.domain_name.string);
+          message_router_request->data += s_len;
+
+          if (!IsValidNetworkConfig(&if_cfg) ||
+              (s_len > 0 && !IsValidDomain(if_cfg.domain_name.string)) ) {
+            message_router_response->general_status = kCipErrorInvalidAttributeValue;
+            break;
+          }
+          OPENER_TRACE_INFO(" setAttribute %d\n", attribute_number);
+          CipTcpIpInterfaceConfiguration * const p_interface_configuration =
+            (CipTcpIpInterfaceConfiguration *)attribute->data;
+          /* Free first and then making a shallow copy of if_cfg.domain_name is ok,
+           * because if_cfg goes out of scope now. */
+          FreeCipString(&p_interface_configuration->domain_name);
+          *p_interface_configuration = if_cfg;
+          /* Tell that this configuration change becomes active after a reset */
+          g_tcpip.status |= kTcpipStatusIfaceCfgPend;
+          message_router_response->general_status = kCipErrorSuccess;
+        }
+        break;
+
+        case 6: { /* host name */
+          CipString tmp_host_name = {
+            .length = 0u,
+            .string = NULL
+          };
+          CipUint s_len = GetUintFromMessage(&(message_router_request->data));
+          if (s_len > 64) {  /* see RFC 1123 on more details */
+            message_router_response->general_status = kCipErrorTooMuchData;
+            break;
+          }
+          SetCipStringByData(&tmp_host_name, s_len, message_router_request->data);
+          s_len = (s_len +1) & (~0x0001u);  /* Align for possible pad byte */
+          OPENER_TRACE_INFO("Host Name: ds %hu '%s'\n", s_len, tmp_host_name.string);
+          message_router_request->data += s_len;
+          if (!IsValidNameLabel(tmp_host_name.string)) {
+            message_router_response->general_status = kCipErrorInvalidAttributeValue;
+            break;
+          }
+          OPENER_TRACE_INFO(" setAttribute %d\n", attribute_number);
+          CipString * const p_host_name = (CipString *)attribute->data;
+          /* Free first and then making a shallow copy of tmp_host_name is ok,
+           * because tmp_host_name goes out of scope now. */
+          FreeCipString(p_host_name);
+          *p_host_name = tmp_host_name;
+          /* Tell that this configuration change becomes active after a reset */
+          g_tcpip.status |= kTcpipStatusIfaceCfgPend;
+          message_router_response->general_status = kCipErrorSuccess;
+        }
+        break;
+#endif /* defined (OPENER_TCPIP_IFACE_CFG_SETTABLE) && OPENER_TCPIP_IFACE_CFG_SETTABLE != 0*/
+
         case 13: {
 
-          CipUint inactivity_timeout_received = GetIntFromMessage(
+          CipUint inactivity_timeout_received = GetUintFromMessage(
             &(message_router_request->data) );
 
           if (inactivity_timeout_received > 3600) {
@@ -125,8 +435,8 @@ EipStatus SetAttributeSingleTcpIpInterface(
             OPENER_TRACE_INFO("setAttribute %d\n", attribute_number);
 
             if (attribute->data != NULL) {
-
               CipUint *data = (CipUint *) attribute->data;
+
               *(data) = inactivity_timeout_received;
               message_router_response->general_status = kCipErrorSuccess;
             } else {
@@ -141,6 +451,16 @@ EipStatus SetAttributeSingleTcpIpInterface(
             kCipErrorAttributeNotSetable;
           break;
       }
+
+      /* Call the PostSetCallback if enabled. */
+      if (attribute->attribute_flags & (kPostSetFunc | kNvDataFunc)
+          && NULL != instance->cip_class->PostSetCallback) {
+        CipUsint service = message_router_request->service;
+        if (kCipErrorSuccess != message_router_response->general_status) {
+            service |= 0x80;  /* Flag no update, TODO: remove this workaround*/
+        }
+        instance->cip_class->PostSetCallback(instance, attribute, service);
+      }
     } else {
       message_router_response->general_status = kCipErrorAttributeNotSetable;
     }
@@ -180,13 +500,14 @@ EipStatus CipTcpIpInterfaceInit() {
   InsertAttribute(instance, 2, kCipDword, &g_tcpip.config_capability,
                   kGetableSingleAndAll);
   InsertAttribute(instance, 3, kCipDword, &g_tcpip.config_control,
-                  kSetAndGetAble);
+                  kSetAndGetAble | kNvDataFunc | IFACE_CFG_SET_MODE );
   InsertAttribute(instance, 4, kCipEpath, &g_tcpip.physical_link_object,
                   kGetableSingleAndAll);
   InsertAttribute(instance, 5, kCipUdintUdintUdintUdintUdintString,
-                  &g_tcpip.interface_configuration, kGetableSingleAndAll);
+                  &g_tcpip.interface_configuration,
+                  kGetableSingleAndAll | kNvDataFunc | IFACE_CFG_SET_MODE);
   InsertAttribute(instance, 6, kCipString, &g_tcpip.hostname,
-                  kGetableSingleAndAll);
+                  kGetableSingleAndAll | kNvDataFunc | IFACE_CFG_SET_MODE);
 
   InsertAttribute(instance, 8, kCipUsint, &g_tcpip.mcast_ttl_value,
                   kGetableSingleAndAll);
@@ -196,7 +517,7 @@ EipStatus CipTcpIpInterfaceInit() {
                   13,
                   kCipUint,
                   &g_tcpip.encapsulation_inactivity_timeout,
-                  kSetAndGetAble);
+                  kSetAndGetAble | kNvDataFunc);
 
   InsertService(tcp_ip_class, kGetAttributeSingle,
                 &GetAttributeSingleTcpIpInterface,

+ 16 - 0
source/src/cip/ciptcpipinterface.h

@@ -17,6 +17,22 @@
 /** @brief TCP/IP Interface class code */
 static const CipUint kCipTcpIpInterfaceClassCode = 0xF5u;
 
+/* Declare constants for status attribute (#1) */
+/** Indicates a pending configuration change in the TTL Value and/or Mcast Config attributes.*/
+static const CipDword kTcpipStatusMcastPend = 0x10u;
+/** Indicates a pending configuration change in the Interface Configuration attribute. */
+static const CipDword kTcpipStatusIfaceCfgPend = 0x20u;
+/** Indicates when an IP address conflict has been detected by ACD. */
+static const CipDword kTcpipStatusAcdStatus = 0x40u;
+/** Indicates when an IP address conflict has been detected by ACD or the defense failed. */
+static const CipDword kTcpipStatusAcdFault = 0x80u;
+
+/* Declare constants for config_control attribute (#3) */
+static const CipDword kTcpipCfgCtrlStaticIp   = 0x00u;  /**< IP configuration method is manual IP assignment */
+static const CipDword kTcpipCfgCtrlDhcp       = 0x02u;  /**< IP configuration method is DHCP */
+static const CipDword kTcpipCfgCtrlMethodMask = 0x0fu;  /**< bit mask for the method field */
+static const CipDword kTcpipCfgCtrlDnsEnable  = 0x10u;  /**< enables DNS resolution on originator devices */
+
 /** @brief Multicast Configuration struct, called Mcast config
  *
  */

+ 1 - 1
source/src/opener_api.h

@@ -37,7 +37,7 @@ ConfigureNetworkInterface(const char *const network_interface);
  *
  *  @param mac_address  the hardware MAC address of the network interface
  */
-void ConfigureMacAddress(const char *network_interface);
+EipStatus ConfigureMacAddressByInterface(const char *network_interface);
 
 /** @ingroup CIP_API
  * @brief Configure the domain name of the device

+ 1 - 1
source/src/ports/POSIX/CMakeLists.txt

@@ -41,7 +41,7 @@ if( OPENER_BUILD_SHARED_LIBS )
 endif()
 
 add_executable(OpENer main.c)
-target_link_libraries( OpENer CIP Utils SAMPLE_APP ENET_ENCAP NVDATA PLATFORM_GENERIC ${OpENer_PLATFORM}PLATFORM ${PLATFORM_SPEC_LIBS} rt cap pthread)
+target_link_libraries( OpENer CIP Utils SAMPLE_APP ENET_ENCAP NVDATA PLATFORM_GENERIC ${OpENer_PLATFORM}PLATFORM ${PLATFORM_SPEC_LIBS} CIP rt cap pthread)
 
 # Add additional CIP Objects
 string(COMPARE NOTEQUAL "${OpENer_ADD_CIP_OBJECTS}" "" OpENer_HAS_ADDITIONAL_OBJECT )

+ 77 - 33
source/src/ports/POSIX/main.c

@@ -18,12 +18,16 @@
 #include "generic_networkhandler.h"
 #include "opener_api.h"
 #include "cipcommon.h"
+#include "ciptcpipinterface.h"
 #include "trace.h"
 #include "networkconfig.h"
 #include "doublylinkedlist.h"
 #include "cipconnectionobject.h"
 #include "nvdata.h"
 
+#define BringupNetwork(if_name, method, if_cfg, hostname)  (0)
+#define ShutdownNetwork(if_name)  (0)
+
 /******************************************************************************/
 /** @brief Signal handler function for ending stack execution
  *
@@ -45,11 +49,12 @@ static void *executeEventLoop(void *pthread_arg);
 /*****************************************************************************/
 /** @brief Flag indicating if the stack should end its execution
  */
-int g_end_stack = 0;
+volatile int g_end_stack = 0;
 
 /******************************************************************************/
 int main(int argc,
          char *arg[]) {
+  int ret;
 
   cap_t capabilities;
   cap_value_t capabilies_list[1];
@@ -57,7 +62,7 @@ int main(int argc,
   capabilities = cap_get_proc();
   if (NULL == capabilities) {
     printf("Could not get capabilities\n");
-    exit(0);
+    exit(EXIT_FAILURE);
   }
 
   capabilies_list[0] = CAP_NET_RAW;
@@ -66,18 +71,18 @@ int main(int argc,
                       CAP_SET) ) {
     cap_free(capabilities);
     printf("Could not set CAP_NET_RAW capability\n");
-    exit(0);
+    exit(EXIT_FAILURE);
   }
 
   if (-1 == cap_set_proc(capabilities) ) {
     cap_free(capabilities);
     printf("Could not push CAP_NET_RAW capability to process\n");
-    exit(0);
+    exit(EXIT_FAILURE);
   }
 
   if (-1 == cap_free(capabilities) ) {
     printf("Could not free capabilites value\n");
-    exit(0);
+    exit(EXIT_FAILURE);
   }
 
   if (argc != 2) {
@@ -85,20 +90,17 @@ int main(int argc,
     printf("The correct command line parameters are:\n");
     printf("./OpENer interfacename\n");
     printf("    e.g. ./OpENer eth1\n");
-    exit(0);
-  } else {
-    DoublyLinkedListInitialize(&connection_list,
-                               CipConnectionObjectListArrayAllocator,
-                               CipConnectionObjectListArrayFree);
-    /* fetch Internet address info from the platform */
-    if (kEipStatusError == ConfigureNetworkInterface(arg[1]) ) {
-      printf("Network interface %s not found.\n", arg[1]);
-      exit(0);
-    }
-    ConfigureDomainName();
-    ConfigureHostName();
+    exit(EXIT_FAILURE);
+  }
 
-    ConfigureMacAddress(arg[1]);
+  DoublyLinkedListInitialize(&connection_list,
+                             CipConnectionObjectListArrayAllocator,
+                             CipConnectionObjectListArrayFree);
+  /* Fetch MAC address from the platform. This tests also if the interface
+   *  is present. */
+  if (kEipStatusError == ConfigureMacAddressByInterface(arg[1]) ) {
+    printf("Network interface %s not found.\n", arg[1]);
+    exit(EXIT_FAILURE);
   }
 
   /* for a real device the serial number should be unique per device */
@@ -109,25 +111,64 @@ int main(int argc,
    */
   EipUint16 unique_connection_id = rand();
 
-  /* Setup the CIP Layer */
+  /* Setup the CIP Layer. All objects are initialized with the default
+   * values for the attribute contents. */
   CipStackInit(unique_connection_id);
 
-  /* The CIP objects are now created and initialized with their default values.
-   *  Now any NV data values are loaded to change the data to the stored
-   *  configuration.
+  /* The current host name is used as a default. This value is kept in the
+   *  case NvdataLoad() needs to recreate the TCP/IP object's settings from
+   *  the defaults on the first start without a valid TCP/IP configuration
+   *  file.
+   */
+  ConfigureHostName();
+
+  /* Now any NV data values are loaded to change the attribute contents to
+   *  the stored configuration.
    */
   if (kEipStatusError == NvdataLoad()) {
     OPENER_TRACE_WARN("Loading of some NV data failed. Maybe the first start?\n");
   }
 
-  /* Setup Network Handles */
-  if (kEipStatusOk == NetworkHandlerInitialize() ) {
-    g_end_stack = 0;
-#ifndef WIN32
-    /* register for closing signals so that we can trigger the stack to end */
-    signal(SIGHUP, LeaveStack);
-    signal(SIGINT, LeaveStack); /* needed to be able to abort with ^C */
-#endif
+  /* Bring up network interface or start DHCP client ... */
+  ret = BringupNetwork(arg[1],
+                       g_tcpip.config_control,
+                       &g_tcpip.interface_configuration,
+                       &g_tcpip.hostname);
+  if (ret < 0) {
+    OPENER_TRACE_ERR("BringUpNetwork() failed\n");
+  }
+
+  /* register for closing signals so that we can trigger the stack to end */
+  g_end_stack = 0;
+  signal(SIGHUP, LeaveStack);
+  signal(SIGINT, LeaveStack); /* needed to be able to abort with ^C */
+
+  /* Next actions depend on the set network configuration method. */
+  CipDword network_config_method = g_tcpip.config_control & kTcpipCfgCtrlMethodMask;
+  if (kTcpipCfgCtrlStaticIp == network_config_method) {
+    OPENER_TRACE_INFO("Static network configuration done\n");
+  }
+  if (kTcpipCfgCtrlDhcp == network_config_method) {
+    OPENER_TRACE_INFO("DHCP network configuration started\n");
+    /* Wait for DHCP done here ...*/
+    /* TODO: implement this function to wait for IP present
+        or abort through g_end_stack.
+        rc = WaitInterfaceIsUp(arg[1], g_end_stack);
+      */
+    OPENER_TRACE_INFO("DHCP wait for interface: status %d\n", ret);
+
+    /* Read IP configuration received via DHCP from interface and store in
+     *  the TCP/IP object.*/
+    ret = ConfigureNetworkInterface(arg[1]);
+    if (ret < 0) {
+      OPENER_TRACE_WARN("Problems getting interface configuration\n");
+    }
+    ConfigureDomainName();
+  }
+
+
+  /* The network initialization of the EIP stack for the NetworkHandler. */
+  if (!g_end_stack && kEipStatusOk == NetworkHandlerInitialize()) {
 #ifdef OPENER_RT
     /* Memory lock all*/
     if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1) {
@@ -138,7 +179,7 @@ int main(int argc,
     struct sched_param param;
     pthread_attr_t attr;
     pthread_t thread;
-    CipUint ret = pthread_attr_init(&attr);
+    ret = pthread_attr_init(&attr);
     if (ret) {
       OPENER_TRACE_ERR("init pthread attributes failed\n");
       exit(-2);
@@ -194,7 +235,10 @@ int main(int argc,
   /* close remaining sessions and connections, clean up used data */
   ShutdownCipStack();
 
-  return -1;
+  /* Shut down the network interface now. */
+  (void) ShutdownNetwork(arg[1]);
+
+  return EXIT_SUCCESS;
 }
 
 void LeaveStack(int signal) {
@@ -208,7 +252,7 @@ void *executeEventLoop(void *pthread_arg) {
   (void) pthread_arg;
 
   /* The event loop. Put other processing you need done continually in here */
-  while (1 != g_end_stack) {
+  while (!g_end_stack) {
     if (kEipStatusOk != NetworkHandlerProcessOnce() ) {
       OPENER_TRACE_ERR("Error in NetworkHandler loop! Exiting OpENer!\n");
       break;

+ 2 - 0
source/src/ports/POSIX/networkconfig.c

@@ -139,9 +139,11 @@ EipStatus ConfigureNetworkInterface(const char *const network_interface) {
   else {
     g_tcpip.interface_configuration.gateway = 0;
   }
+#if defined(OPENER_TRACE_ENABLED)
   {
     char ip_str[INET_ADDRSTRLEN];
     OPENER_TRACE_INFO("Decoded gateway: %s\n", inet_ntop(AF_INET, &gateway, ip_str, sizeof ip_str));
  }
+#endif
 
   return kEipStatusOk;
 }

+ 30 - 0
source/src/ports/POSIX/sample_application/opener_user_conf.h

@@ -29,6 +29,36 @@
 
 #include "typedefs.h"
 
+
+/** Set this define if you have a DLR capable device
+ *
+ * This define changes the OpENer device configuration in a way that
+ *  the DLR object is initialized and the other configuration stuff
+ *  that is mandatory for a DLR device is also enabled.
+ */
+#ifndef OPENER_IS_DLR_DEVICE
+  #define OPENER_IS_DLR_DEVICE  0 /* not yet implemented */
+#endif
+
+#if defined(OPENER_IS_DLR_DEVICE) && OPENER_IS_DLR_DEVICE != 0
+  /* Enable all the stuff the DLR device depends on */
+  #define OPENER_TCPIP_IFACE_CFG_SETTABLE 1
+#endif
+
+
+/** Set this define if you want the Interface Configuration to be settable
+ *
+ * This define makes the TCP/IP object's Interface Configuration (attribute #5)
+ *  and the Host Name (attribute #6) settable. This is required as per ODVA
+ *  publication 70 "Recommended Functionality for EIP Devices" Version
+ *  10. This also enables the storage of these attributes in NV data
+ *  storage area.
+ */
+#ifndef OPENER_TCPIP_IFACE_CFG_SETTABLE
+  #define OPENER_TCPIP_IFACE_CFG_SETTABLE 0
+#endif
+
+
 /** @brief Define the number of objects that may be used in connections
  *
  *  This number needs only to consider additional objects. Connections to

+ 3 - 0
source/src/ports/nvdata/nvdata.c

@@ -103,11 +103,14 @@ EipStatus NvTcpipSetCallback
     EipStatus status = kEipStatusOk;
 
     if (0 != (kNvDataFunc & attribute->attribute_flags)) {
+      /* Workaround: Update only if service is not flagged. */
+      if (0 == (0x80 & service)) {
         OPENER_TRACE_INFO("NV data update: %s, i %" PRIu32 ", a %" PRIu16 "\n",
                           instance->cip_class->class_name,
                           instance->instance_number,
                           attribute->attribute_number);
         status = NvTcpipStore(&g_tcpip);
+      }
     }
     return status;
 }