blob: 36df0e80ae5cd625acad9a409a7df93951f5a4b1 [file] [log] [blame]
Patrick Venture08f879c2019-03-18 10:51:42 -07001/*
2 * Copyright 2019 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "ethstats.hpp"
18
Patrick Ventureaca98132019-03-18 11:14:16 -070019#include "handler.hpp"
20
Patrick Venture08f879c2019-03-18 10:51:42 -070021#include <ipmid/api.h>
22
23#include <cstdint>
24#include <cstdio>
25#include <cstring>
Patrick Venture08f879c2019-03-18 10:51:42 -070026#include <map>
27#include <sstream>
28#include <string>
29
30namespace ethstats
31{
Patrick Venture08f879c2019-03-18 10:51:42 -070032// If this changes in the future, there should be some alternative
33// source for the information if possible to provide continuined functionality.
34static const std::map<std::uint8_t, std::string> statLookup = {
35 {RX_BYTES, "rx_bytes"},
36 {RX_COMPRESSED, "rx_compressed"},
37 {RX_CRC_ERRORS, "rx_crc_errors"},
38 {RX_DROPPED, "rx_dropped"},
39 {RX_ERRORS, "rx_errors"},
40 {RX_FIFO_ERRORS, "rx_fifo_errors"},
41 {RX_FRAME_ERRORS, "rx_frame_errors"},
42 {RX_LENGTH_ERRORS, "rx_length_errors"},
43 {RX_MISSED_ERRORS, "rx_missed_errors"},
44 {RX_NOHANDLER, "rx_nohandler"},
45 {RX_OVER_ERRORS, "rx_over_errors"},
46 {RX_PACKETS, "rx_packets"},
47 {TX_ABORTED_ERRORS, "tx_aborted_errors"},
48 {TX_BYTES, "tx_bytes"},
49 {TX_CARRIER_ERRORS, "tx_carrier_errors"},
50 {TX_COMPRESSED, "tx_compressed"},
51 {TX_DROPPED, "tx_dropped"},
52 {TX_ERRORS, "tx_errors"},
53 {TX_FIFO_ERRORS, "tx_fifo_errors"},
54 {TX_HEARTBEAT_ERRORS, "tx_heartbeat_errors"},
55 {TX_PACKETS, "tx_packets"},
56 {TX_WINDOW_ERRORS, "tx_window_errors"},
57};
58
Patrick Ventureaca98132019-03-18 11:14:16 -070059std::string buildPath(const std::string& ifName, const std::string& field)
60{
61 std::ostringstream opath;
62 opath << "/sys/class/net/" << ifName << "/statistics/" << field;
63
64 return opath.str();
65}
66
67ipmi_ret_t handleEthStatCommand(const std::uint8_t* reqBuf,
68 std::uint8_t* replyCmdBuf, size_t* dataLen,
69 const EthStatsInterface* handler)
Patrick Venture08f879c2019-03-18 10:51:42 -070070{
71 auto reqLength = (*dataLen);
72
73 // Verify the reqBuf is the minimum length.
74 // [0] == statistics id
75 // [1] == if_name_length
76 // [2..N] == if_name
77 // In theory the smallest can be a one-letter name. (3 bytes).
78 if (reqLength < sizeof(struct EthStatRequest) + sizeof(std::uint8_t))
79 {
80 std::fprintf(stderr, "*dataLen too small: %u\n",
81 static_cast<std::uint32_t>(reqLength));
82 return IPMI_CC_REQ_DATA_LEN_INVALID;
83 }
84
85 // using struct prefix due to nature as c-style pod struct.
86 struct EthStatRequest request;
87 std::memcpy(&request, &reqBuf[0], sizeof(request));
88 auto nameLen = static_cast<std::uint32_t>(request.if_name_len);
89
90 if (reqLength < (sizeof(request) + nameLen))
91 {
92 std::fprintf(stderr, "*dataLen too small: %u\n",
93 static_cast<std::uint32_t>(reqLength));
94 return IPMI_CC_REQ_DATA_LEN_INVALID;
95 }
96
97 // Check the statistic to see if we recognize it.
98 auto stat = statLookup.find(request.statId);
99 if (stat == statLookup.end())
100 {
101 std::fprintf(stderr, "stat not known: 0x%x\n", request.statId);
102 return IPMI_CC_INVALID_FIELD_REQUEST;
103 }
104
105 // The if_name handling plus a few other things was taken from the
106 // CableCheck command implementation.
107 //
108 // Ok, so we know what statistic they want. Let's validate their
109 // if_name. The string is length delimited (like dns).
110
111 // Copy the string out of the request buffer.
112 // Maximum length is 256 bytes, excluding the nul-terminator.
113 auto name = std::string(
114 reinterpret_cast<const char*>(&reqBuf[0] + sizeof(request)), nameLen);
115
116 // Minor sanity & security check (of course, I'm less certain if unicode
117 // comes into play here.
118 //
119 // Basically you can't easily inject ../ or /../ into the path below.
120 // Decided to make this more robust, although since it appends to the path
121 // it would limit any exposure.
Patrick Venture6b48b922020-12-16 12:50:31 -0800122 if (name.find('/') != std::string::npos)
Patrick Venture08f879c2019-03-18 10:51:42 -0700123 {
124 std::fprintf(stderr, "Invalid or illegal name: '%s'\n", name.c_str());
125 return IPMI_CC_INVALID_FIELD_REQUEST;
126 }
127
Patrick Ventureaca98132019-03-18 11:14:16 -0700128 std::string fullPath = buildPath(name, stat->second);
Patrick Venture08f879c2019-03-18 10:51:42 -0700129
Patrick Ventureaca98132019-03-18 11:14:16 -0700130 if (!handler->validIfNameAndField(fullPath))
Patrick Venture08f879c2019-03-18 10:51:42 -0700131 {
Patrick Venture08f879c2019-03-18 10:51:42 -0700132 return IPMI_CC_INVALID_FIELD_REQUEST;
133 }
Patrick Venture08f879c2019-03-18 10:51:42 -0700134
135 struct EthStatReply reply;
136 reply.statId = request.statId;
Patrick Ventureaca98132019-03-18 11:14:16 -0700137 reply.value = handler->readStatistic(fullPath);
Patrick Venture08f879c2019-03-18 10:51:42 -0700138
139 // Store the result.
140 std::memcpy(&replyCmdBuf[0], &reply, sizeof(reply));
141 (*dataLen) = sizeof(reply);
142
143 return IPMI_CC_OK;
144}
145
146} // namespace ethstats