Skip to content

Commit

Permalink
Initial EtherNet/IP support
Browse files Browse the repository at this point in the history
  • Loading branch information
thiagoralves committed Jun 17, 2019
1 parent 0d0e2f0 commit a95ce61
Show file tree
Hide file tree
Showing 8 changed files with 390 additions and 31 deletions.
189 changes: 189 additions & 0 deletions webserver/core/enip.cpp
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
//------
//
// 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <time.h>
#include <string.h>

#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;
}
}
58 changes: 52 additions & 6 deletions webserver/core/interactive_server.cpp
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
}

//-----------------------------------------------------------------------------
Expand All @@ -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
//-----------------------------------------------------------------------------
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -411,4 +457,4 @@ void startInteractiveServer(int port)
closeSocket(client_fd);
sprintf(log_msg, "Terminating interactive server thread\r\n");
log(log_msg);
}
}
10 changes: 9 additions & 1 deletion webserver/core/ladder.h
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
#include <pthread.h>
#include <stdint.h>

#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
Expand Down Expand Up @@ -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);
Expand All @@ -126,13 +130,17 @@ 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;

//modbus.cpp
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);
Expand Down
Loading

0 comments on commit a95ce61

Please sign in to comment.