diff --git a/webserver/core/enip.cpp b/webserver/core/enip.cpp
new file mode 100644
index 0000000..a182656
--- /dev/null
+++ b/webserver/core/enip.cpp
@@ -0,0 +1,189 @@
+//-----------------------------------------------------------------------------
+// Copyright 2019 Thiago Alves
+// This file is part of the OpenPLC Software Stack.
+//
+// OpenPLC is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// OpenPLC is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with OpenPLC. If not, see .
+//------
+//
+// This file has all the EtherNet/IP functions supported by the OpenPLC. If any
+// other function is to be added to the project, it must be added here
+// Thiago Alves, Apr 2019
+//-----------------------------------------------------------------------------
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "ladder.h"
+
+#define ENIP_MIN_LENGTH 28
+
+struct enip_header
+{
+ unsigned char *command;//[2];
+ unsigned char *length;//[2];
+ unsigned char *session_handle;//[4];
+ unsigned char *status;//[4];
+ unsigned char *sender_context;//[8];
+ unsigned char *options;//[4];
+ unsigned char *data;
+};
+
+struct enip_data
+{
+ unsigned char *interface_handle;
+ unsigned char *timeout;
+ unsigned char *item_count;
+
+ unsigned char *item1_id;
+ unsigned char *item1_length;
+ unsigned char *item1_data;
+
+ unsigned char *item2_id;
+ unsigned char *item2_length;
+ unsigned char *item2_data;
+};
+
+
+thread_local unsigned char enip_session[4];
+
+int respondToError(unsigned char *buffer, int buffer_size, int error_code)
+{
+ return -1;
+}
+
+int parseEnipHeader(unsigned char *buffer, int buffer_size, struct enip_header *header)
+{
+ //verify if message is big enough
+ if (buffer_size < ENIP_MIN_LENGTH)
+ return -1;
+
+ header->command = &buffer[0];
+ header->length = &buffer[2];
+ header->session_handle = &buffer[4];
+ header->status = &buffer[8];
+ header->sender_context = &buffer[12];
+ header->options = &buffer[20];
+ /*
+ memcpy(header->command, &buffer[0], 2);
+ memcpy(header->length, &buffer[2], 2);
+ memcpy(header->session_handle, &buffer[4], 4);
+ memcpy(header->status, &buffer[8], 4);
+ memcpy(header->sender_context, &buffer[12], 8);
+ memcpy(header->options, &buffer[20], 4);
+ */
+ header->data = &buffer[24];
+
+ uint16_t enip_data_size = ((uint16_t)header->length[1] << 8) | (uint16_t)header->length[0];
+
+ //verify if buffer_size matches enip_data_size
+ if (buffer_size - 24 < enip_data_size)
+ return -1;
+
+ return enip_data_size;
+}
+
+int parseEnipData(struct enip_header *header, struct enip_data *data)
+{
+ data->interface_handle = &header->data[0];
+ data->timeout = &header->data[4];
+ data->item_count = &header->data[6];
+ data->item1_id = &header->data[8];
+ data->item1_length = &header->data[10];
+ data->item1_data = &header->data[12];
+
+ uint16_t item_length = ((uint16_t)data->item1_length[1] << 8) | (uint16_t)data->item1_length[0];
+
+ data->item2_id = &header->data[12+item_length];
+ data->item2_length = &header->data[14+item_length];
+ data->item2_data = &header->data[16+item_length];
+
+ return 1;
+}
+
+int registerEnipSession(struct enip_header *header)
+{
+ unsigned char r[4];
+ srand((unsigned)time(NULL));
+
+ for (int i = 0; i < 4; ++i)
+ r[i] = rand();
+
+ memcpy(header->session_handle, &r[0], 4);
+ memcpy(&enip_session[0], &r[0], 4);
+
+ return ENIP_MIN_LENGTH;
+}
+
+
+//-----------------------------------------------------------------------------
+// This function must parse and process the client request and write back the
+// response for it. The return value is the size of the response message in
+// bytes.
+//-----------------------------------------------------------------------------
+int processEnipMessage(unsigned char *buffer, int buffer_size)
+{
+ int error_code;
+ struct enip_header header;
+
+ error_code = parseEnipHeader(buffer, buffer_size, &header);
+ if (error_code < 0)
+ return respondToError(buffer, buffer_size, error_code);
+
+ if (header.command[0] == 0x65)
+ return registerEnipSession(&header);
+
+ else if (header.command[0] == 0x6f)
+ {
+ struct enip_data data;
+ error_code = parseEnipData(&header, &data);
+ if (error_code < 0)
+ return respondToError(buffer, buffer_size, error_code);
+
+ if (data.item2_id[0] == 0x91)
+ {
+ //PCCC type 1 - Unknown
+ }
+ else if (data.item2_id[0] == 0xb2)
+ {
+ //PCCC type 2 - Unconnected Data Item
+ }
+ else if (data.item1_id[0] == 0xa1 && data.item2_id[0] == 0xb1)
+ {
+ //PCCC type 3 - Connected Data Item
+ }
+ else
+ {
+ //Unknown type ID. Respond with error_code
+ }
+ }
+
+ else
+ {
+ unsigned char log_msg[1000];
+ unsigned char *p = log_msg;
+ p += sprintf(p, "Unknown EtherNet/IP request: ");
+ for (int i = 0; i < buffer_size; i++)
+ {
+ p += sprintf(p, "%02x ", (unsigned char)buffer[i]);
+ }
+ p += sprintf(p, "\n");
+ printf(log_msg);
+
+ return -1;
+ }
+}
diff --git a/webserver/core/interactive_server.cpp b/webserver/core/interactive_server.cpp
old mode 100755
new mode 100644
index dc6feee..3c8e33f
--- a/webserver/core/interactive_server.cpp
+++ b/webserver/core/interactive_server.cpp
@@ -42,6 +42,8 @@ bool run_modbus = 0;
int modbus_port = 502;
bool run_dnp3 = 0;
int dnp3_port = 20000;
+bool run_enip = 0;
+int enip_port = 44818;
unsigned char server_command[1024];
int command_index = 0;
bool processing_command = 0;
@@ -51,13 +53,14 @@ time_t end_time;
//Global Threads
pthread_t modbus_thread;
pthread_t dnp3_thread;
+pthread_t enip_thread;
//-----------------------------------------------------------------------------
// Start the Modbus Thread
//-----------------------------------------------------------------------------
void *modbusThread(void *arg)
{
- startServer(modbus_port);
+ startServer(modbus_port, MODBUS_PROTOCOL);
}
//-----------------------------------------------------------------------------
@@ -68,6 +71,14 @@ void *dnp3Thread(void *arg)
dnp3StartServer(dnp3_port);
}
+//-----------------------------------------------------------------------------
+// Start the Enip Thread
+//-----------------------------------------------------------------------------
+void *enipThread(void *arg)
+{
+ startServer(enip_port, ENIP_PROTOCOL);
+}
+
//-----------------------------------------------------------------------------
// Read the argument from a command function
//-----------------------------------------------------------------------------
@@ -216,9 +227,9 @@ void processCommand(unsigned char *buffer, int client_fd)
else if (strncmp(buffer, "start_modbus(", 13) == 0)
{
processing_command = true;
- sprintf(log_msg, "Issued start_modbus() command to start on port: %d\n", readCommandArgument(buffer));
- log(log_msg);
modbus_port = readCommandArgument(buffer);
+ sprintf(log_msg, "Issued start_modbus() command to start on port: %d\n", modbus_port);
+ log(log_msg);
if (run_modbus)
{
sprintf(log_msg, "Modbus server already active. Restarting on port: %d\n", modbus_port);
@@ -251,9 +262,9 @@ void processCommand(unsigned char *buffer, int client_fd)
else if (strncmp(buffer, "start_dnp3(", 11) == 0)
{
processing_command = true;
- sprintf(log_msg, "Issued start_dnp3() command to start on port: %d\n", readCommandArgument(buffer));
- log(log_msg);
dnp3_port = readCommandArgument(buffer);
+ sprintf(log_msg, "Issued start_dnp3() command to start on port: %d\n", dnp3_port);
+ log(log_msg);
if (run_dnp3)
{
sprintf(log_msg, "DNP3 server already active. Restarting on port: %d\n", dnp3_port);
@@ -283,6 +294,41 @@ void processCommand(unsigned char *buffer, int client_fd)
}
processing_command = false;
}
+ else if (strncmp(buffer, "start_enip(", 11) == 0)
+ {
+ processing_command = true;
+ enip_port = readCommandArgument(buffer);
+ sprintf(log_msg, "Issued start_enip() command to start on port: %d\n", enip_port);
+ log(log_msg);
+ if (run_enip)
+ {
+ sprintf(log_msg, "EtherNet/IP server already active. Restarting on port: %d\n", enip_port);
+ log(log_msg);
+ //Stop Enip server
+ run_enip = 0;
+ pthread_join(enip_thread, NULL);
+ sprintf(log_msg, "EtherNet/IP server was stopped\n");
+ log(log_msg);
+ }
+ //Start Enip server
+ run_enip = 1;
+ pthread_create(&enip_thread, NULL, enipThread, NULL);
+ processing_command = false;
+ }
+ else if (strncmp(buffer, "stop_enip()", 11) == 0)
+ {
+ processing_command = true;
+ sprintf(log_msg, "Issued stop_enip() command\n");
+ log(log_msg);
+ if (run_enip)
+ {
+ run_enip = 0;
+ pthread_join(enip_thread, NULL);
+ sprintf(log_msg, "EtherNet/IP server was stopped\n");
+ log(log_msg);
+ }
+ processing_command = false;
+ }
else if (strncmp(buffer, "runtime_logs()", 14) == 0)
{
processing_command = true;
@@ -411,4 +457,4 @@ void startInteractiveServer(int port)
closeSocket(client_fd);
sprintf(log_msg, "Terminating interactive server thread\r\n");
log(log_msg);
-}
+}
\ No newline at end of file
diff --git a/webserver/core/ladder.h b/webserver/core/ladder.h
old mode 100755
new mode 100644
index 44b846b..0ae62dd
--- a/webserver/core/ladder.h
+++ b/webserver/core/ladder.h
@@ -26,6 +26,10 @@
#include
#include
+#define MODBUS_PROTOCOL 0
+#define DNP3_PROTOCOL 1
+#define ENIP_PROTOCOL 2
+
//Internal buffers for I/O and memory. These buffers are defined in the
//auto-generated glueVars.cpp file
#define BUFFER_SIZE 1024
@@ -117,7 +121,7 @@ extern int log_index;
void handleSpecialFunctions();
//server.cpp
-void startServer(int port);
+void startServer(int port, int protocol_type);
int getSO_ERROR(int fd);
void closeSocket(int fd);
bool SetSocketBlockingEnabled(int fd, bool blocking);
@@ -126,6 +130,7 @@ bool SetSocketBlockingEnabled(int fd, bool blocking);
void startInteractiveServer(int port);
extern bool run_modbus;
extern bool run_dnp3;
+extern bool run_enip;
extern time_t start_time;
extern time_t end_time;
@@ -133,6 +138,9 @@ extern time_t end_time;
int processModbusMessage(unsigned char *buffer, int bufferSize);
void mapUnusedIO();
+//enip.cpp
+int processEnipMessage(unsigned char *buffer, int buffer_size);
+
//modbus_master.cpp
void initializeMB();
void *querySlaveDevices(void *arg);
diff --git a/webserver/core/server.cpp b/webserver/core/server.cpp
old mode 100755
new mode 100644
index 137e443..876934a
--- a/webserver/core/server.cpp
+++ b/webserver/core/server.cpp
@@ -35,6 +35,7 @@
#define MAX_INPUT 16
#define MAX_OUTPUT 16
#define MAX_MODBUS 100
+#define NET_BUFFER_SIZE 10000
//-----------------------------------------------------------------------------
@@ -93,7 +94,7 @@ int createSocket(int port)
socket_fd = socket(AF_INET,SOCK_STREAM,0);
if (socket_fd<0)
{
- sprintf(log_msg, "Modbus Server: error creating stream socket => %s\n", strerror(errno));
+ sprintf(log_msg, "Server: error creating stream socket => %s\n", strerror(errno));
log(log_msg);
return -1;
}
@@ -114,14 +115,14 @@ int createSocket(int port)
//Bind socket
if (bind(socket_fd,(struct sockaddr *)&server_addr,sizeof(server_addr)) < 0)
{
- sprintf(log_msg, "Modbus Server: error binding socket => %s\n", strerror(errno));
+ sprintf(log_msg, "Server: error binding socket => %s\n", strerror(errno));
log(log_msg);
return -1;
}
// we accept max 5 pending connections
listen(socket_fd,5);
- sprintf(log_msg, "Modbus Server: Listening on port %d\n", port);
+ sprintf(log_msg, "Server: Listening on port %d\n", port);
log(log_msg);
return socket_fd;
@@ -131,18 +132,24 @@ int createSocket(int port)
// Blocking call. Wait here for the client to connect. Returns the file
// descriptor to communicate with the client.
//-----------------------------------------------------------------------------
-int waitForClient(int socket_fd)
+int waitForClient(int socket_fd, int protocol_type)
{
unsigned char log_msg[1000];
int client_fd;
struct sockaddr_in client_addr;
+ bool *run_server;
socklen_t client_len;
- sprintf(log_msg, "Modbus Server: waiting for new client...\n");
+ if (protocol_type == MODBUS_PROTOCOL)
+ run_server = &run_modbus;
+ else if (protocol_type == ENIP_PROTOCOL)
+ run_server = &run_enip;
+
+ sprintf(log_msg, "Server: waiting for new client...\n");
log(log_msg);
client_len = sizeof(client_addr);
- while (run_modbus)
+ while (*run_server)
{
client_fd = accept(socket_fd, (struct sockaddr *)&client_addr, &client_len); //non-blocking call
if (client_fd > 0)
@@ -163,8 +170,8 @@ int waitForClient(int socket_fd)
//-----------------------------------------------------------------------------
int listenToClient(int client_fd, unsigned char *buffer)
{
- bzero(buffer, 1024);
- int n = read(client_fd, buffer, 1024);
+ bzero(buffer, NET_BUFFER_SIZE);
+ int n = read(client_fd, buffer, NET_BUFFER_SIZE);
return n;
}
@@ -173,8 +180,16 @@ int listenToClient(int client_fd, unsigned char *buffer)
//-----------------------------------------------------------------------------
void processMessage(unsigned char *buffer, int bufferSize, int client_fd)
{
- int messageSize = processModbusMessage(buffer, bufferSize);
- write(client_fd, buffer, messageSize);
+ if (protocol_type == MODBUS_PROTOCOL)
+ {
+ int messageSize = processModbusMessage(buffer, bufferSize);
+ write(client_fd, buffer, messageSize);
+ }
+ else if (protocol_type == ENIP_PROTOCOL)
+ {
+ int messageSize = processEnipMessage(buffer, bufferSize);
+ write(client_fd, buffer, messageSize);
+ }
}
//-----------------------------------------------------------------------------
@@ -183,20 +198,28 @@ void processMessage(unsigned char *buffer, int bufferSize, int client_fd)
void *handleConnections(void *arguments)
{
unsigned char log_msg[1000];
- int client_fd = *(int *)arguments;
- unsigned char buffer[1024];
+ int *args = (int *)arguments;
+ int client_fd = args[0];
+ int protocol_type = args[1];
+ unsigned char buffer[NET_BUFFER_SIZE];
int messageSize;
+ bool *run_server;
+
+ if (protocol_type == MODBUS_PROTOCOL)
+ run_server = &run_modbus;
+ else if (protocol_type == ENIP_PROTOCOL)
+ run_server = &run_enip;
- sprintf(log_msg, "Modbus Server: Thread created for client ID: %d\n", client_fd);
+ sprintf(log_msg, "Server: Thread created for client ID: %d\n", client_fd);
log(log_msg);
- while(run_modbus)
+ while(*run_server)
{
- //unsigned char buffer[1024];
+ //unsigned char buffer[NET_BUFFER_SIZE];
//int messageSize;
messageSize = listenToClient(client_fd, buffer);
- if (messageSize <= 0 || messageSize > 1024)
+ if (messageSize <= 0 || messageSize > NET_BUFFER_SIZE)
{
// something has gone wrong or the client has closed connection
if (messageSize == 0)
@@ -212,7 +235,7 @@ void *handleConnections(void *arguments)
break;
}
- processMessage(buffer, messageSize, client_fd);
+ processMessage(buffer, messageSize, client_fd, protocol_type);
}
//printf("Debug: Closing client socket and calling pthread_exit in server.cpp\n");
close(client_fd);
@@ -230,13 +253,21 @@ void startServer(int port)
{
unsigned char log_msg[1000];
int socket_fd, client_fd;
-
- socket_fd = createSocket(port);
- mapUnusedIO();
+ bool *run_server;
- while(run_modbus)
+ socket_fd = createSocket(port);
+
+ if (protocol_type == MODBUS_PROTOCOL)
{
- client_fd = waitForClient(socket_fd); //block until a client connects
+ mapUnusedIO();
+ run_server = &run_modbus;
+ }
+ else if (protocol_type == ENIP_PROTOCOL)
+ run_server = &run_enip;
+
+ while(*run_server)
+ {
+ client_fd = waitForClient(socket_fd, protocol_type); //block until a client connects
if (client_fd < 0)
{
sprintf(log_msg, "Modbus Server: Error accepting client!\n");
@@ -245,13 +276,14 @@ void startServer(int port)
else
{
- int arguments[1];
+ int arguments[2];
pthread_t thread;
int ret = -1;
sprintf(log_msg, "Modbus Server: Client accepted! Creating thread for the new client ID: %d...\n", client_fd);
log(log_msg);
arguments[0] = client_fd;
- ret = pthread_create(&thread, NULL, handleConnections, arguments);
+ arguments[1] = protocol_type;
+ ret = pthread_create(&thread, NULL, handleConnections, (void*)arguments);
if (ret==0)
{
pthread_detach(thread);
@@ -262,4 +294,4 @@ void startServer(int port)
close(client_fd);
sprintf(log_msg, "Terminating Modbus thread\r\n");
log(log_msg);
-}
+}
\ No newline at end of file
diff --git a/webserver/openplc.db b/webserver/openplc.db
index 18c74df..3e4d7a6 100644
Binary files a/webserver/openplc.db and b/webserver/openplc.db differ
diff --git a/webserver/openplc.py b/webserver/openplc.py
old mode 100755
new mode 100644
index 9ef58de..f2f76e9
--- a/webserver/openplc.py
+++ b/webserver/openplc.py
@@ -178,6 +178,28 @@ class runtime:
s.close()
except:
print("Error connecting to OpenPLC runtime")
+
+ def start_enip(self, port_num):
+ if (self.status() == "Running"):
+ try:
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.connect(('localhost', 43628))
+ s.send('start_enip(' + str(port_num) + ')\n')
+ data = s.recv(1000)
+ s.close()
+ except:
+ print("Error connecting to OpenPLC runtime")
+
+ def stop_enip(self):
+ if (self.status() == "Running"):
+ try:
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.connect(('localhost', 43628))
+ s.send('stop_enip()\n')
+ data = s.recv(1000)
+ s.close()
+ except:
+ print("Error connecting to OpenPLC runtime")
def logs(self):
if (self.status() == "Running"):
diff --git a/webserver/pages.py b/webserver/pages.py
old mode 100755
new mode 100644
index b6cf37f..131ada9
--- a/webserver/pages.py
+++ b/webserver/pages.py
@@ -884,6 +884,8 @@ settings_tail = """
var modbus_text = document.getElementById('modbus_server_port');
var dnp3_checkbox = document.getElementById('dnp3_server');
var dnp3_text = document.getElementById('dnp3_server_port');
+ var enip_checkbox = document.getElementById('enip_server');
+ var enip_text = document.getElementById('enip_server_port');
var auto_run_checkbox = document.getElementById('auto_run');
var auto_run_text = document.getElementById('auto_run_text');
@@ -905,6 +907,15 @@ settings_tail = """
dnp3_text.disabled = true;
}
+ if (enip_checkbox.checked == true)
+ {
+ enip_text.disabled = false;
+ }
+ else
+ {
+ enip_text.disabled = true;
+ }
+
if (auto_run_checkbox.checked == true)
{
auto_run_text.value = 'true';
@@ -925,6 +936,11 @@ settings_tail = """
setupCheckboxes();
}
+ document.getElementById('enip_server').onchange = function()
+ {
+ setupCheckboxes();
+ }
+
document.getElementById('auto_run').onchange = function()
{
setupCheckboxes();
@@ -936,6 +952,8 @@ settings_tail = """
var modbus_port = document.forms["uploadForm"]["modbus_server_port"].value;
var dnp3_checkbox = document.forms["uploadForm"]["dnp3_server"].checked;
var dnp3_port = document.forms["uploadForm"]["dnp3_server_port"].value;
+ var enip_checkbox = document.forms["uploadForm"]["enip_server"].checked;
+ var enip_port = document.forms["uploadForm"]["enip_server_port"].value;
if (modbus_checkbox && (Number(modbus_port) < 0 || Number(modbus_port) > 65535))
{
@@ -947,6 +965,11 @@ settings_tail = """
alert("Please select a port number between 0 and 65535");
return false;
}
+ if (enip_checkbox && (Number(enip_port) < 0 || Number(enip_port) > 65535))
+ {
+ alert("Please select a port number between 0 and 65535");
+ return false;
+ }
return true;
}
diff --git a/webserver/webserver.py b/webserver/webserver.py
old mode 100755
new mode 100644
index 524ca26..8cb8aeb
--- a/webserver/webserver.py
+++ b/webserver/webserver.py
@@ -53,6 +53,13 @@ def configure_runtime():
else:
print("Disabling DNP3")
openplc_runtime.stop_dnp3()
+ elif (row[0] == "Enip_port"):
+ if (row[1] != "disabled"):
+ print("Enabling EtherNet/IP on port " + str(int(row[1])))
+ openplc_runtime.start_enip(int(row[1]))
+ else:
+ print("Disabling EtherNet/IP")
+ openplc_runtime.stop_enip()
except Error as e:
print("error connecting to the database" + str(e))
else:
@@ -1684,6 +1691,8 @@ def settings():
modbus_port = str(row[1])
elif (row[0] == "Dnp3_port"):
dnp3_port = str(row[1])
+ elif (row[0] == "Enip_port"):
+ enip_port = str(row[1])
elif (row[0] == "Start_run_mode"):
start_run = str(row[1])
elif (row[0] == "Slave_polling"):
@@ -1728,6 +1737,28 @@ def settings():
"
+ return_str += """
+
+
+
+
+
+ """
+ else:
+ return_str += """
+
+
+
+
+ "
+
return_str += """
@@ -1776,6 +1807,7 @@ def settings():
elif (flask.request.method == 'POST'):
modbus_port = flask.request.form.get('modbus_server_port')
dnp3_port = flask.request.form.get('dnp3_server_port')
+ enip_port = flask.request.form.get('enip_server_port')
start_run = flask.request.form.get('auto_run_text')
slave_polling = flask.request.form.get('slave_polling_period')
slave_timeout = flask.request.form.get('slave_timeout')
@@ -1799,6 +1831,13 @@ def settings():
cur.execute("UPDATE Settings SET Value = ? WHERE Key = 'Dnp3_port'", (str(dnp3_port),))
conn.commit()
+ if (enip_port == None):
+ cur.execute("UPDATE Settings SET Value = 'disabled' WHERE Key = 'Enip_port'")
+ conn.commit()
+ else:
+ cur.execute("UPDATE Settings SET Value = ? WHERE Key = 'Enip_port'", (str(enip_port),))
+ conn.commit()
+
if (start_run == 'true'):
cur.execute("UPDATE Settings SET Value = 'true' WHERE Key = 'Start_run_mode'")
conn.commit()