| /* | 
 |  * Copyright 2019 Google Inc. | 
 |  * | 
 |  * Licensed under the Apache License, Version 2.0 (the "License"); | 
 |  * you may not use this file except in compliance with the License. | 
 |  * You may obtain a copy of the License at | 
 |  * | 
 |  *     http://www.apache.org/licenses/LICENSE-2.0 | 
 |  * | 
 |  * Unless required by applicable law or agreed to in writing, software | 
 |  * distributed under the License is distributed on an "AS IS" BASIS, | 
 |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 |  * See the License for the specific language governing permissions and | 
 |  * limitations under the License. | 
 |  */ | 
 |  | 
 | #include "ethstats.hpp" | 
 |  | 
 | #include "handler.hpp" | 
 |  | 
 | #include <ipmid/api.h> | 
 |  | 
 | #include <cstdint> | 
 | #include <cstdio> | 
 | #include <cstring> | 
 | #include <map> | 
 | #include <sstream> | 
 | #include <string> | 
 |  | 
 | namespace ethstats | 
 | { | 
 | // If this changes in the future, there should be some alternative | 
 | // source for the information if possible to provide continuined functionality. | 
 | static const std::map<std::uint8_t, std::string> statLookup = { | 
 |     {RX_BYTES, "rx_bytes"}, | 
 |     {RX_COMPRESSED, "rx_compressed"}, | 
 |     {RX_CRC_ERRORS, "rx_crc_errors"}, | 
 |     {RX_DROPPED, "rx_dropped"}, | 
 |     {RX_ERRORS, "rx_errors"}, | 
 |     {RX_FIFO_ERRORS, "rx_fifo_errors"}, | 
 |     {RX_FRAME_ERRORS, "rx_frame_errors"}, | 
 |     {RX_LENGTH_ERRORS, "rx_length_errors"}, | 
 |     {RX_MISSED_ERRORS, "rx_missed_errors"}, | 
 |     {RX_NOHANDLER, "rx_nohandler"}, | 
 |     {RX_OVER_ERRORS, "rx_over_errors"}, | 
 |     {RX_PACKETS, "rx_packets"}, | 
 |     {TX_ABORTED_ERRORS, "tx_aborted_errors"}, | 
 |     {TX_BYTES, "tx_bytes"}, | 
 |     {TX_CARRIER_ERRORS, "tx_carrier_errors"}, | 
 |     {TX_COMPRESSED, "tx_compressed"}, | 
 |     {TX_DROPPED, "tx_dropped"}, | 
 |     {TX_ERRORS, "tx_errors"}, | 
 |     {TX_FIFO_ERRORS, "tx_fifo_errors"}, | 
 |     {TX_HEARTBEAT_ERRORS, "tx_heartbeat_errors"}, | 
 |     {TX_PACKETS, "tx_packets"}, | 
 |     {TX_WINDOW_ERRORS, "tx_window_errors"}, | 
 | }; | 
 |  | 
 | std::string buildPath(const std::string& ifName, const std::string& field) | 
 | { | 
 |     std::ostringstream opath; | 
 |     opath << "/sys/class/net/" << ifName << "/statistics/" << field; | 
 |  | 
 |     return opath.str(); | 
 | } | 
 |  | 
 | ipmi_ret_t handleEthStatCommand(const std::uint8_t* reqBuf, | 
 |                                 std::uint8_t* replyCmdBuf, size_t* dataLen, | 
 |                                 const EthStatsInterface* handler) | 
 | { | 
 |     auto reqLength = (*dataLen); | 
 |  | 
 |     // Verify the reqBuf is the minimum length. | 
 |     // [0] == statistics id | 
 |     // [1] == if_name_length | 
 |     // [2..N] == if_name | 
 |     // In theory the smallest can be a one-letter name. (3 bytes). | 
 |     if (reqLength < sizeof(struct EthStatRequest) + sizeof(std::uint8_t)) | 
 |     { | 
 |         std::fprintf(stderr, "*dataLen too small: %u\n", | 
 |                      static_cast<std::uint32_t>(reqLength)); | 
 |         return IPMI_CC_REQ_DATA_LEN_INVALID; | 
 |     } | 
 |  | 
 |     // using struct prefix due to nature as c-style pod struct. | 
 |     struct EthStatRequest request; | 
 |     std::memcpy(&request, &reqBuf[0], sizeof(request)); | 
 |     auto nameLen = static_cast<std::uint32_t>(request.if_name_len); | 
 |  | 
 |     if (reqLength < (sizeof(request) + nameLen)) | 
 |     { | 
 |         std::fprintf(stderr, "*dataLen too small: %u\n", | 
 |                      static_cast<std::uint32_t>(reqLength)); | 
 |         return IPMI_CC_REQ_DATA_LEN_INVALID; | 
 |     } | 
 |  | 
 |     // Check the statistic to see if we recognize it. | 
 |     auto stat = statLookup.find(request.statId); | 
 |     if (stat == statLookup.end()) | 
 |     { | 
 |         std::fprintf(stderr, "stat not known: 0x%x\n", request.statId); | 
 |         return IPMI_CC_INVALID_FIELD_REQUEST; | 
 |     } | 
 |  | 
 |     // The if_name handling plus a few other things was taken from the | 
 |     // CableCheck command implementation. | 
 |     // | 
 |     // Ok, so we know what statistic they want.  Let's validate their | 
 |     // if_name.  The string is length delimited (like dns). | 
 |  | 
 |     // Copy the string out of the request buffer. | 
 |     // Maximum length is 256 bytes, excluding the nul-terminator. | 
 |     auto name = std::string( | 
 |         reinterpret_cast<const char*>(&reqBuf[0] + sizeof(request)), nameLen); | 
 |  | 
 |     // Minor sanity & security check (of course, I'm less certain if unicode | 
 |     // comes into play here. | 
 |     // | 
 |     // Basically you can't easily inject ../ or /../ into the path below. | 
 |     // Decided to make this more robust, although since it appends to the path | 
 |     // it would limit any exposure. | 
 |     if (name.find('/') != std::string::npos) | 
 |     { | 
 |         std::fprintf(stderr, "Invalid or illegal name: '%s'\n", name.c_str()); | 
 |         return IPMI_CC_INVALID_FIELD_REQUEST; | 
 |     } | 
 |  | 
 |     std::string fullPath = buildPath(name, stat->second); | 
 |  | 
 |     if (!handler->validIfNameAndField(fullPath)) | 
 |     { | 
 |         return IPMI_CC_INVALID_FIELD_REQUEST; | 
 |     } | 
 |  | 
 |     struct EthStatReply reply; | 
 |     reply.statId = request.statId; | 
 |     reply.value = handler->readStatistic(fullPath); | 
 |  | 
 |     // Store the result. | 
 |     std::memcpy(&replyCmdBuf[0], &reply, sizeof(reply)); | 
 |     (*dataLen) = sizeof(reply); | 
 |  | 
 |     return IPMI_CC_OK; | 
 | } | 
 |  | 
 | } // namespace ethstats |