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()