Skip to content

Commit

Permalink
Rsu health monitor (#564)
Browse files Browse the repository at this point in the history
<!-- Thanks for the contribution, this is awesome. -->

# PR Details
## Description
RSU health monitor plugin is a V2xHub plugin that interface with its
connected RSU directly via SNMP protocol. The plugin is responsible for
monitoring the connected RSU status by constantly ping RSU device. For
detailed design of this plugin, refer to
https://usdot-carma.atlassian.net/wiki/spaces/WFD2/pages/2640740360/RSU+Health+Monitor+Plugin+Design
.
<!--- Describe your changes in detail -->

## Related Issue
NA
<!--- This project only accepts pull requests related to open issues -->
<!--- If suggesting a new feature or change, please discuss it in an
issue first -->
<!--- If fixing a bug, there should be an issue describing it with steps
to reproduce -->
<!--- Please link to the issue here: -->

## Motivation and Context
Data visualization
<!--- Why is this change required? What problem does it solve? -->

## How Has This Been Tested?
Integration test
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran
to -->
<!--- see how your change affects other areas of the code, etc. -->

## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->

- [ ] Defect fix (non-breaking change that fixes an issue)
- [x] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that cause existing functionality
to change)

## Checklist:

<!--- Go over all the following points, and put an `x` in all the boxes
that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're
here to help! -->

- [ ] I have added any new packages to the sonar-scanner.properties file
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [x] I have read the **CONTRIBUTING** document.
[V2XHUB Contributing
Guide](https://github.com/usdot-fhwa-OPS/V2X-Hub/blob/develop/Contributing.md)
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.
  • Loading branch information
dan-du-car authored Nov 14, 2023
1 parent 5328f89 commit a098635
Show file tree
Hide file tree
Showing 19 changed files with 1,246 additions and 4 deletions.
8 changes: 6 additions & 2 deletions .sonarqube/sonar-scanner.properties
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ sonar.modules= PedestrianPlugin, \
MessageReceiverPlugin, \
CARMAStreetsPlugin, \
ERVCloudForwardingPlugin, \
CDASimAdapter
CDASimAdapter, \
RSUHealthMonitorPlugin



Expand All @@ -82,6 +83,7 @@ TimPlugin.sonar.projectBaseDir =src/v2i-hub/TimPlugin
CARMAStreetsPlugin.sonar.projectBaseDir =src/v2i-hub/CARMAStreetsPlugin
ERVCloudForwardingPlugin.sonar.projectBaseDir =src/v2i-hub/ERVCloudForwardingPlugin
CDASimAdapter.sonar.projectBaseDir =src/v2i-hub/CDASimAdapter
RSUHealthMonitorPlugin.sonar.projectBaseDir =src/v2i-hub/RSUHealthMonitorPlugin



Expand Down Expand Up @@ -117,7 +119,8 @@ CARMAStreetsPlugin.sonar.exclusions =test/**
ERVCloudForwardingPlugin.sonar.sources =src
CDASimAdapter.sonar.sources =src
CDASimAdapter.sonar.exclusions =test/**

RSUHealthMonitorPlugin.sonar.sources =src
RSUHealthMonitorPlugin.sonar.exclusions =test/**

# Tests
# Note: For C++ setting this field does not cause test analysis to occur. It only allows the test source code to be evaluated.
Expand Down Expand Up @@ -145,3 +148,4 @@ SpatPlugin.sonar.tests=test
CARMAStreetsPlugin.sonar.tests=test
ERVCloudForwardingPlugin.sonar.tests=test
CDASimAdapter.sonar.tests=test
RSUHealthMonitorPlugin.sonar.tests=test
1 change: 1 addition & 0 deletions src/tmx/Messages/include/MessageTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ static CONSTEXPR const char *MSGSUBTYPE_OUTGOING_STRING = "Outgoing";
static CONSTEXPR const char *MSGSUBTYPE_SHUTDOWN_STRING = "Shutdown";
static CONSTEXPR const char *MSGSUBTYPE_TIMESYNC_STRING = "TimeSync";
static CONSTEXPR const char *MSGSUBTYPE_SENSOR_DETECTED_OBJECT_STRING = "SensorDetectedObject";
static CONSTEXPR const char *MSGSUBTYPE_RSU_STATUS_STRING = "RSUStatus";

} /* End namespace messages */

Expand Down
25 changes: 25 additions & 0 deletions src/tmx/Messages/include/RSUStatusMessage.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#pragma once


#include <tmx/messages/message.hpp>
#include "MessageTypes.h"


namespace tmx::messages {


class RSUStatusMessage : public tmx::message
{
public:
RSUStatusMessage() {}

/// Message type for routing this message through TMX core.
static constexpr const char* MessageType = MSGTYPE_APPLICATION_STRING;

/// Message sub type for routing this message through TMX core.
static constexpr const char* MessageSubType = MSGSUBTYPE_RSU_STATUS_STRING;
};

} /* namespace tmx::messages */


12 changes: 10 additions & 2 deletions src/tmx/TmxUtils/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
PROJECT ( tmxutils CXX )
FILE (GLOB_RECURSE HEADERS "src/" "*.h*")
FILE (GLOB_RECURSE SOURCES "src/" "*.c*")

find_library(NETSNMPAGENT "netsnmpagent")
find_library(NETSNMPMIBS "netsnmpmibs")
find_library(NETSNMP "netsnmp")

FIND_PACKAGE (carma-clock)
FIND_LIBRARY (UUID_LIBRARY uuid)
Expand All @@ -16,12 +18,18 @@ TARGET_INCLUDE_DIRECTORIES (${PROJECT_NAME} SYSTEM PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>
$<BUILD_INTERFACE:${TMXMESSAGES_INCLUDE_DIR}>
${MYSQL_INCLUDE_DIR}
${NETSNMP_INCLUDE_DIRS}
${MYSQLCPPCONN_INCLUDE_DIR})
TARGET_LINK_LIBRARIES (${PROJECT_NAME} PUBLIC
${TMXAPI_LIBRARIES}
${MYSQL_LIBRARIES}
${MYSQLCPPCONN_LIBRARIES}
${UUID_LIBRARY}
${UUID_LIBRARY}
${NETSNMPAGENT}
${NETSNMPMIBS}
${NETSNMP}
${NETSNMP_LIBRARIES}
rdkafka++
::carma-clock
gmock
Expand Down Expand Up @@ -54,4 +62,4 @@ add_executable(${BINARY} ${TEST_SOURCES})

add_test(NAME ${BINARY} COMMAND ${BINARY})

target_link_libraries(${BINARY} PUBLIC ${PROJECT_NAME} rdkafka++ gmock ${TMXAPI_LIBRARIES} ${ASN_J2735_LIBRARIES} ${UUID_LIBRARY} gtest)
target_link_libraries(${BINARY} PUBLIC ${PROJECT_NAME} rdkafka++ gmock ${TMXAPI_LIBRARIES} ${ASN_J2735_LIBRARIES} ${UUID_LIBRARY} ${NETSNMPAGENT} ${NETSNMPMIBS} ${NETSNMP} ${NETSNMP_LIBRARIES} gtest)
56 changes: 56 additions & 0 deletions src/tmx/TmxUtils/src/RSU_MIB_4_1.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#pragma once
namespace tmx::utils::rsu41::mib::oid
{
/**
* @brief This header file contains a subset of RSU MIB definition from https://github.com/certificationoperatingcouncil/COC_TestSpecs/blob/master/AppNotes/RSU/RSU-MIB.txt
*/
// Contains the ID given to this RSU.
static constexpr const char *RSU_ID_OID = "1.0.15628.4.1.17.4.0";

// Contains the version of this MIB supported by this RSU, e.g. rsuMIB 4.1 rev201812060000Z
static constexpr const char *RSU_MIB_VERSION = "1.0.15628.4.1.17.1.0";

// Contains the version of firmware running on this RSU.
static constexpr const char *RSU_FIRMWARE_VERSION = "1.0.15628.4.1.17.2.0";

// Contains the name of the manufacturer of this RSU.
static constexpr const char *RSU_MANUFACTURER = "1.0.15628.4.1.17.5.0";

// Contains GPS NMEA GPGGA output string.
static constexpr const char *RSU_GPS_OUTPUT_STRING = "1.0.15628.4.1.8.5.0";

// Immediate Forward Message Index
static constexpr const char *RSU_IFM_INDEX = "1.0.15628.4.1.5.1.1.0";

// Immediate Forward Message PSID.
static constexpr const char *RSU_IFM_PSID = "1.0.15628.4.1.5.1.2.0";

// Immediate Forward Message DSRC Message ID
static constexpr const char *RSU_IFM_DSRC_MSG_ID = "1.0.15628.4.1.5.1.3.0";

// Immediate Forward Message Transmit Mode
static constexpr const char *RSU_IFM_TX_MODE = "1.0.15628.4.1.5.1.4.0";

// DSRC channel set for Immediate Forward Message transmit
static constexpr const char *RSU_IFM_TX_CHANNEL = "1.0.15628.4.1.5.1.5.0";

// Set this bit to enable transmission of the message 0=off, 1=on
static constexpr const char *RSU_IFM_ENABLE = "1.0.15628.4.1.5.1.6.0";

// Create (4) or Destroy (6) row entry
static constexpr const char *RSU_IFM_STATUS = "1.0.15628.4.1.5.1.7.0";

// Specifies the current mode of operation of the RSU and provides capability to transition the device into a new mode, e.g. from the current mode to off, etc
static constexpr const char *RSU_MODE = "1.0.15628.4.1.99.0";

/*
SYNTAX INTEGER {
bothOp (0), --both Continuous and Alternating modes are operational
altOp (1), --Alternating mode is operational,
--Continuous mode is not operational
contOp (2), --Continuous mode is operational,
--Alternating mode is not operational
noneOp (3) --neither Continuous nor Alternating mode is operational
*/
static constexpr const char *RSU_CHAN_STATUS = "1.0.15628.4.1.19.1.0";
}
227 changes: 227 additions & 0 deletions src/tmx/TmxUtils/src/SNMPClient.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
#include "SNMPClient.h"

namespace tmx::utils
{

// Client defaults to SNMPv3
snmp_client::snmp_client(const std::string &ip, const int &port, const std::string &community,
const std::string &snmp_user, const std::string &securityLevel, const std::string &authPassPhrase, int snmp_version, int timeout)

: ip_(ip), port_(port), community_(community), snmp_version_(snmp_version), timeout_(timeout)
{

PLOG(logDEBUG1) << "Starting SNMP Client. Target device IP address: " << ip_ << ", Target device SNMP port: " << port_;

// Bring the IP address and port of the target SNMP device in the required form, which is "IPADDRESS:PORT":
std::string ip_port_string = ip_ + ":" + std::to_string(port_);
char *ip_port = &ip_port_string[0];

// Initialize SNMP session parameters
init_snmp("carma_snmp");
snmp_sess_init(&session);
session.peername = ip_port;
session.version = snmp_version_; // SNMP_VERSION_3
session.securityName = (char *)snmp_user.c_str();
session.securityNameLen = snmp_user.length();

// Fallback behavior to setup a community for SNMP V1/V2
if (snmp_version_ != SNMP_VERSION_3)
{
session.community = (unsigned char *)community.c_str();
session.community_len = community_.length();
}

// SNMP authorization/privach config
if (securityLevel == "authPriv")
{
session.securityLevel = SNMP_SEC_LEVEL_AUTHPRIV;
}

else if (securityLevel == "authNoPriv")
{
session.securityLevel = SNMP_SEC_LEVEL_AUTHNOPRIV;
}

else
session.securityLevel = SNMP_SEC_LEVEL_NOAUTH;

// Passphrase used for both authentication and privacy
auto phrase_len = authPassPhrase.length();
auto phrase = (u_char *)authPassPhrase.c_str();

// Defining and generating auth config with SHA1
session.securityAuthProto = snmp_duplicate_objid(usmHMACSHA1AuthProtocol, USM_AUTH_PROTO_SHA_LEN);
session.securityAuthProtoLen = USM_AUTH_PROTO_SHA_LEN;
session.securityAuthKeyLen = USM_AUTH_KU_LEN;
if (session.securityLevel != SNMP_SEC_LEVEL_NOAUTH && generate_Ku(session.securityAuthProto,
session.securityAuthProtoLen,
phrase, phrase_len,
session.securityAuthKey,
&session.securityAuthKeyLen) != SNMPERR_SUCCESS)
{
std::string errMsg = "Error generating Ku from authentication pass phrase. \n";
throw snmp_client_exception(errMsg);
}

// Defining and generating priv config with AES (since using SHA1)
session.securityPrivKeyLen = USM_PRIV_KU_LEN;
session.securityPrivProto =
snmp_duplicate_objid(usmAESPrivProtocol,
OID_LENGTH(usmAESPrivProtocol));
session.securityPrivProtoLen = OID_LENGTH(usmAESPrivProtocol);

if (session.securityLevel == SNMP_SEC_LEVEL_AUTHPRIV && generate_Ku(session.securityAuthProto,
session.securityAuthProtoLen,
phrase, phrase_len,
session.securityPrivKey,
&session.securityPrivKeyLen) != SNMPERR_SUCCESS)
{
std::string errMsg = "Error generating Ku from privacy pass phrase. \n";
throw snmp_client_exception(errMsg);
}

session.timeout = timeout_;

// Opens the snmp session if it exists
ss = snmp_open(&session);

if (ss == nullptr)
{
PLOG(logERROR) << "Failed to establish session with target device";
snmp_sess_perror("snmpget", &session);
throw snmp_client_exception("Failed to establish session with target device");
}
else
{
PLOG(logINFO) << "Established session with device at " << ip_;
}
}

snmp_client::~snmp_client()
{
PLOG(logINFO) << "Closing SNMP session";
snmp_close(ss);
}

// Original implementation used in Carma Streets https://github.com/usdot-fhwa-stol/snmp-client
bool snmp_client::process_snmp_request(const std::string &input_oid, const request_type &request_type, snmp_response_obj &val)
{

/*Structure to hold response from the remote host*/
snmp_pdu *response;

// Create pdu for the data
if (request_type == request_type::GET)
{
PLOG(logDEBUG1) << "Attempting to GET value for: " << input_oid;
pdu = snmp_pdu_create(SNMP_MSG_GET);
}
else if (request_type == request_type::SET)
{
PLOG(logDEBUG1) << "Attempting to SET value for " << input_oid << " to " << val.val_int;
pdu = snmp_pdu_create(SNMP_MSG_SET);
}
else
{
PLOG(logERROR) << "Invalid request type, method accpets only GET and SET";
return false;
}

// Read input OID into an OID variable:
// net-snmp has several methods for creating an OID object
// their documentation suggests using get_node. read_objid seems like a simpler approach
// TO DO: investigate update to get_node
if (!read_objid(input_oid.c_str(), OID, &OID_len))
{
// If oid cannot be created
PLOG(logERROR) << "OID could not be created from input: " << input_oid;
return false;
}
else
{

if (request_type == request_type::GET)
{
// Add OID to pdu for get request
snmp_add_null_var(pdu, OID, OID_len);
}
else if (request_type == request_type::SET)
{
if (val.type == snmp_response_obj::response_type::INTEGER)
{
snmp_add_var(pdu, OID, OID_len, 'i', (std::to_string(val.val_int)).c_str());
}
// Needs to be finalized to support octet string use
else if (val.type == snmp_response_obj::response_type::STRING)
{
PLOG(logERROR) << "Setting string value is currently not supported";
}
}

PLOG(logINFO) << "Created OID for input: " << input_oid;
}
// Send the request
int status = snmp_synch_response(ss, pdu, &response);
PLOG(logINFO) << "Response request status: " << status << " (=" << (status == STAT_SUCCESS ? "SUCCESS" : "FAILED") << ")";

// Check GET response
if (status == STAT_SUCCESS && response && response->errstat == SNMP_ERR_NOERROR && request_type == request_type::GET)
{
for (auto vars = response->variables; vars; vars = vars->next_variable)
{
// Get value of variable depending on ASN.1 type
// Variable could be a integer, string, bitstring, ojbid, counter : defined here https://github.com/net-snmp/net-snmp/blob/master/include/net-snmp/types.h
// get Integer value
if (vars->type == ASN_INTEGER && vars->val.integer)
{
val.type = snmp_response_obj::response_type::INTEGER;
val.val_int = *vars->val.integer;
}
else if (vars->type == ASN_OCTET_STR && vars->val.string)
{
size_t str_len = vars->val_len;
for (size_t i = 0; i < str_len; ++i)
{
val.val_string.push_back(vars->val.string[i]);
}
val.type = snmp_response_obj::response_type::STRING;
}
}
}
else
{
log_error(status, request_type, response);
return false;
}

if (response)
{
snmp_free_pdu(response);
OID_len = MAX_OID_LEN;
}

return true;
}

int snmp_client::get_port() const
{
return port_;
}

void snmp_client::log_error(const int &status, const request_type &request_type, const snmp_pdu *response) const
{

if (status == STAT_SUCCESS)
{
PLOG(logERROR) << "Variable type: " << response->variables->type << ". Error in packet " << static_cast<std::string>(snmp_errstring(static_cast<int>(response->errstat)));
}
else if (status == STAT_TIMEOUT)
{
PLOG(logERROR) << "Timeout, no response from server";
}
else
{
PLOG(logERROR) << "Unknown SNMP Error for " << (request_type == request_type::GET ? "GET" : "SET");
}
}
} // namespace
Loading

0 comments on commit a098635

Please sign in to comment.