blob: 96a462d5f46793ae8195535ef464ddebab7dbd95 [file] [log] [blame]
Ratan Gupta7a7f0122018-03-07 12:31:05 +05301#include <chrono>
Adriana Kobylak5d6481f2015-10-29 21:44:55 -05002#include <stdio.h>
3#include <string.h>
4#include <stdint.h>
Hariharasubramanian R83951912016-01-20 07:06:36 -06005#include <arpa/inet.h>
tomjose26e17732016-03-03 08:52:51 -06006#include <string>
Ratan Guptacc6cdbf2017-09-01 23:06:25 +05307#include <experimental/filesystem>
Adriana Kobylak5d6481f2015-10-29 21:44:55 -05008
Patrick Williams37af7332016-09-02 21:21:42 -05009#include "host-ipmid/ipmid-api.h"
Patrick Williams53a360e2016-08-12 22:01:02 -050010#include "ipmid.hpp"
Ratan Gupta7a7f0122018-03-07 12:31:05 +053011#include "timer.hpp"
Ratan Guptab8e99552017-07-27 07:07:48 +053012#include "transporthandler.hpp"
13#include "utils.hpp"
Patrick Venturec7c1c3c2017-11-15 14:29:18 -080014#include "net.hpp"
Ratan Guptab8e99552017-07-27 07:07:48 +053015
16#include <phosphor-logging/log.hpp>
17#include <phosphor-logging/elog-errors.hpp>
18#include "xyz/openbmc_project/Common/error.hpp"
Adriana Kobylak5d6481f2015-10-29 21:44:55 -050019
Hariharasubramanian R83951912016-01-20 07:06:36 -060020#define SYSTEMD_NETWORKD_DBUS 1
21
22#ifdef SYSTEMD_NETWORKD_DBUS
23#include <systemd/sd-bus.h>
Sergey Solomineb9b8142016-08-23 09:07:28 -050024#include <mapper.h>
Hariharasubramanian R83951912016-01-20 07:06:36 -060025#endif
26
Ratan Gupta7a7f0122018-03-07 12:31:05 +053027extern std::unique_ptr<phosphor::ipmi::Timer> networkTimer;
Ratan Gupta1247e0b2018-03-07 10:47:25 +053028
Adriana Kobylake08fbc62016-02-09 16:17:23 -060029const int SIZE_MAC = 18; //xx:xx:xx:xx:xx:xx
Ratan Gupta1247e0b2018-03-07 10:47:25 +053030constexpr auto ipv4Protocol = "xyz.openbmc_project.Network.IP.Protocol.IPv4";
Adriana Kobylake08fbc62016-02-09 16:17:23 -060031
Patrick Venturec7c1c3c2017-11-15 14:29:18 -080032std::map<int, std::unique_ptr<struct ChannelConfig_t>> channelConfig;
Hariharasubramanian R83951912016-01-20 07:06:36 -060033
Ratan Guptab8e99552017-07-27 07:07:48 +053034using namespace phosphor::logging;
35using namespace sdbusplus::xyz::openbmc_project::Common::Error;
Ratan Gupta7a7f0122018-03-07 12:31:05 +053036
Ratan Guptacc6cdbf2017-09-01 23:06:25 +053037namespace fs = std::experimental::filesystem;
Hariharasubramanian R83951912016-01-20 07:06:36 -060038
Adriana Kobylak5d6481f2015-10-29 21:44:55 -050039void register_netfn_transport_functions() __attribute__((constructor));
40
Patrick Venturec7c1c3c2017-11-15 14:29:18 -080041struct ChannelConfig_t* getChannelConfig(int channel)
42{
43 auto item = channelConfig.find(channel);
44 if (item == channelConfig.end())
45 {
46 channelConfig[channel] = std::make_unique<struct ChannelConfig_t>();
47 }
48
49 return channelConfig[channel].get();
50}
51
Ratan Guptab8e99552017-07-27 07:07:48 +053052// Helper Function to get IP Address/NetMask/Gateway/MAC Address from Network Manager or
Nan Li3d0df912016-10-18 19:51:41 +080053// Cache based on Set-In-Progress State
Patrick Venturec7c1c3c2017-11-15 14:29:18 -080054ipmi_ret_t getNetworkData(uint8_t lan_param, uint8_t* data, int channel)
tomjose26e17732016-03-03 08:52:51 -060055{
tomjose26e17732016-03-03 08:52:51 -060056 ipmi_ret_t rc = IPMI_CC_OK;
Ratan Guptab8e99552017-07-27 07:07:48 +053057 sdbusplus::bus::bus bus(ipmid_get_sd_bus_connection());
Ratan Gupta533d03b2017-07-30 10:39:22 +053058
Patrick Venturec7c1c3c2017-11-15 14:29:18 -080059 auto ethdevice = ipmi::network::ChanneltoEthernet(channel);
60 // if ethdevice is an empty string they weren't expecting this channel.
61 if (ethdevice.empty())
62 {
63 // TODO: return error from getNetworkData()
64 return IPMI_CC_INVALID_FIELD_REQUEST;
65 }
66 auto ethIP = ethdevice + "/" + ipmi::network::IP_TYPE;
67 auto channelConf = getChannelConfig(channel);
68
Ratan Guptab8e99552017-07-27 07:07:48 +053069 try
tomjose26e17732016-03-03 08:52:51 -060070 {
Ratan Guptab8e99552017-07-27 07:07:48 +053071 switch (lan_param)
tomjose26e17732016-03-03 08:52:51 -060072 {
Ratan Guptab8e99552017-07-27 07:07:48 +053073 case LAN_PARM_IP:
74 {
75 std::string ipaddress;
Patrick Venturec7c1c3c2017-11-15 14:29:18 -080076 if (channelConf->lan_set_in_progress == SET_COMPLETE)
Ratan Guptab8e99552017-07-27 07:07:48 +053077 {
Ratan Guptacc6cdbf2017-09-01 23:06:25 +053078 try
79 {
Ratan Guptadd646202017-11-21 17:46:59 +053080 auto ipObjectInfo = ipmi::getIPObject(
81 bus,
82 ipmi::network::IP_INTERFACE,
83 ipmi::network::ROOT,
Patrick Venturec7c1c3c2017-11-15 14:29:18 -080084 ethIP);
Ratan Guptadd646202017-11-21 17:46:59 +053085
86 auto properties = ipmi::getAllDbusProperties(
87 bus,
88 ipObjectInfo.second,
89 ipObjectInfo.first,
90 ipmi::network::IP_INTERFACE);
91
92 ipaddress = properties["Address"].get<std::string>();
Ratan Guptacc6cdbf2017-09-01 23:06:25 +053093 }
94 // ignore the exception, as it is a valid condtion that
95 // system is not confiured with any ip.
96 catch (InternalFailure& e)
97 {
98 // nothing to do.
99 }
Ratan Guptab8e99552017-07-27 07:07:48 +0530100 }
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800101 else if (channelConf->lan_set_in_progress == SET_IN_PROGRESS)
Ratan Guptab8e99552017-07-27 07:07:48 +0530102 {
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800103 ipaddress = channelConf->ipaddr;
Ratan Guptab8e99552017-07-27 07:07:48 +0530104 }
105
106 inet_pton(AF_INET, ipaddress.c_str(),
107 reinterpret_cast<void*>(data));
108 }
109 break;
110
Ratan Guptacc6cdbf2017-09-01 23:06:25 +0530111 case LAN_PARM_IPSRC:
Ratan Guptab8e99552017-07-27 07:07:48 +0530112 {
Ratan Guptacc6cdbf2017-09-01 23:06:25 +0530113 std::string networkInterfacePath;
114
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800115 if (channelConf->lan_set_in_progress == SET_COMPLETE)
Ratan Guptab8e99552017-07-27 07:07:48 +0530116 {
Ratan Guptacc6cdbf2017-09-01 23:06:25 +0530117 try
118 {
119 ipmi::ObjectTree ancestorMap;
120 // if the system is having ip object,then
121 // get the IP object.
122 auto ipObject = ipmi::getDbusObject(
123 bus,
124 ipmi::network::IP_INTERFACE,
125 ipmi::network::ROOT,
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800126 ethIP);
Ratan Guptab8e99552017-07-27 07:07:48 +0530127
Ratan Guptacc6cdbf2017-09-01 23:06:25 +0530128 // Get the parent interface of the IP object.
129 try
130 {
131 ipmi::InterfaceList interfaces;
132 interfaces.emplace_back(
133 ipmi::network::ETHERNET_INTERFACE);
Ratan Guptab8e99552017-07-27 07:07:48 +0530134
Ratan Guptacc6cdbf2017-09-01 23:06:25 +0530135 ancestorMap = ipmi::getAllAncestors(
136 bus,
137 ipObject.first,
138 std::move(interfaces));
139 }
140 catch (InternalFailure& e)
141 {
142 // if unable to get the parent interface
143 // then commit the error and return.
144 log<level::ERR>("Unable to get the parent interface",
145 entry("PATH=%s", ipObject.first.c_str()),
146 entry("INTERFACE=%s",
147 ipmi::network::ETHERNET_INTERFACE));
148 break;
149
150 }
151 // for an ip object there would be single parent
152 // interface.
153 networkInterfacePath = ancestorMap.begin()->first;
154 }
155 catch (InternalFailure& e)
156 {
157 // if there is no ip configured on the system,then
158 // get the network interface object.
159 auto networkInterfaceObject = ipmi::getDbusObject(
160 bus,
161 ipmi::network::ETHERNET_INTERFACE,
162 ipmi::network::ROOT,
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800163 ethdevice);
Ratan Guptacc6cdbf2017-09-01 23:06:25 +0530164
165 networkInterfacePath = networkInterfaceObject.first;
166 }
167
168 auto variant = ipmi::getDbusProperty(
169 bus,
170 ipmi::network::SERVICE,
171 networkInterfacePath,
172 ipmi::network::ETHERNET_INTERFACE,
173 "DHCPEnabled");
174
175 auto dhcpEnabled = variant.get<bool>();
176 // As per IPMI spec 2=>DHCP, 1=STATIC
177 auto ipsrc = dhcpEnabled ? ipmi::network::IPOrigin::DHCP :
178 ipmi::network::IPOrigin::STATIC;
179
180 memcpy(data, &ipsrc, ipmi::network::IPSRC_SIZE_BYTE);
181 }
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800182 else if (channelConf->lan_set_in_progress == SET_IN_PROGRESS)
Ratan Guptacc6cdbf2017-09-01 23:06:25 +0530183 {
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800184 memcpy(data, &(channelConf->ipsrc),
Ratan Guptacc6cdbf2017-09-01 23:06:25 +0530185 ipmi::network::IPSRC_SIZE_BYTE);
186 }
187 }
188 break;
189
190 case LAN_PARM_SUBNET:
191 {
192 unsigned long mask {};
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800193 if (channelConf->lan_set_in_progress == SET_COMPLETE)
Ratan Guptacc6cdbf2017-09-01 23:06:25 +0530194 {
195 try
196 {
Ratan Guptadd646202017-11-21 17:46:59 +0530197 auto ipObjectInfo = ipmi::getIPObject(
Ratan Guptacc6cdbf2017-09-01 23:06:25 +0530198 bus,
199 ipmi::network::IP_INTERFACE,
200 ipmi::network::ROOT,
201 ipmi::network::IP_TYPE);
202
203 auto properties = ipmi::getAllDbusProperties(
204 bus,
205 ipObjectInfo.second,
206 ipObjectInfo.first,
207 ipmi::network::IP_INTERFACE);
208
209 auto prefix = properties["PrefixLength"].get<uint8_t>();
210 mask = ipmi::network::MASK_32_BIT;
211 mask = htonl(mask << (ipmi::network::BITS_32 - prefix));
212 }
213 // ignore the exception, as it is a valid condtion that
214 // system is not confiured with any ip.
215 catch (InternalFailure& e)
216 {
217 // nothing to do
218 }
Ratan Guptab8e99552017-07-27 07:07:48 +0530219 memcpy(data, &mask, ipmi::network::IPV4_ADDRESS_SIZE_BYTE);
220 }
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800221 else if (channelConf->lan_set_in_progress == SET_IN_PROGRESS)
Ratan Guptab8e99552017-07-27 07:07:48 +0530222 {
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800223 inet_pton(AF_INET, channelConf->netmask.c_str(),
Ratan Guptab8e99552017-07-27 07:07:48 +0530224 reinterpret_cast<void*>(data));
Ratan Guptab8e99552017-07-27 07:07:48 +0530225 }
226
227 }
228 break;
229
230 case LAN_PARM_GATEWAY:
231 {
232 std::string gateway;
233
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800234 if (channelConf->lan_set_in_progress == SET_COMPLETE)
Ratan Guptab8e99552017-07-27 07:07:48 +0530235 {
Ratan Guptacc6cdbf2017-09-01 23:06:25 +0530236 try
237 {
238 auto systemObject = ipmi::getDbusObject(
239 bus,
240 ipmi::network::SYSTEMCONFIG_INTERFACE,
241 ipmi::network::ROOT);
Ratan Guptab8e99552017-07-27 07:07:48 +0530242
Ratan Guptacc6cdbf2017-09-01 23:06:25 +0530243 auto systemProperties = ipmi::getAllDbusProperties(
244 bus,
245 systemObject.second,
246 systemObject.first,
247 ipmi::network::SYSTEMCONFIG_INTERFACE);
Ratan Guptab8e99552017-07-27 07:07:48 +0530248
Ratan Guptacc6cdbf2017-09-01 23:06:25 +0530249 gateway = systemProperties["DefaultGateway"].get<
250 std::string>();
251 }
252 // ignore the exception, as it is a valid condtion that
253 // system is not confiured with any ip.
254 catch (InternalFailure& e)
255 {
256 // nothing to do
257 }
Ratan Guptab8e99552017-07-27 07:07:48 +0530258
259 }
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800260 else if (channelConf->lan_set_in_progress == SET_IN_PROGRESS)
Ratan Guptab8e99552017-07-27 07:07:48 +0530261 {
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800262 gateway = channelConf->gateway;
Ratan Guptab8e99552017-07-27 07:07:48 +0530263 }
264
265 inet_pton(AF_INET, gateway.c_str(),
266 reinterpret_cast<void*>(data));
Ratan Guptab8e99552017-07-27 07:07:48 +0530267 }
268 break;
269
270 case LAN_PARM_MAC:
271 {
272 std::string macAddress;
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800273 if (channelConf->lan_set_in_progress == SET_COMPLETE)
Ratan Guptab8e99552017-07-27 07:07:48 +0530274 {
275 auto macObjectInfo = ipmi::getDbusObject(
276 bus,
277 ipmi::network::MAC_INTERFACE,
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800278 ipmi::network::ROOT,
279 ethdevice);
Ratan Guptab8e99552017-07-27 07:07:48 +0530280
281 auto variant = ipmi::getDbusProperty(
282 bus,
283 macObjectInfo.second,
284 macObjectInfo.first,
285 ipmi::network::MAC_INTERFACE,
286 "MACAddress");
287
288 macAddress = variant.get<std::string>();
289
290 }
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800291 else if (channelConf->lan_set_in_progress == SET_IN_PROGRESS)
Ratan Guptab8e99552017-07-27 07:07:48 +0530292 {
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800293 macAddress = channelConf->macAddress;
Ratan Guptab8e99552017-07-27 07:07:48 +0530294 }
295
296 sscanf(macAddress.c_str(), ipmi::network::MAC_ADDRESS_FORMAT,
297 (data),
298 (data + 1),
299 (data + 2),
300 (data + 3),
301 (data + 4),
302 (data + 5));
303 }
304 break;
305
Ratan Gupta533d03b2017-07-30 10:39:22 +0530306 case LAN_PARM_VLAN:
307 {
Ratan Guptacc6cdbf2017-09-01 23:06:25 +0530308 uint16_t vlanID {};
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800309 if (channelConf->lan_set_in_progress == SET_COMPLETE)
Ratan Gupta533d03b2017-07-30 10:39:22 +0530310 {
Ratan Guptacc6cdbf2017-09-01 23:06:25 +0530311 try
Ratan Gupta533d03b2017-07-30 10:39:22 +0530312 {
Ratan Guptadd646202017-11-21 17:46:59 +0530313 auto ipObjectInfo = ipmi::getIPObject(
Ratan Guptacc6cdbf2017-09-01 23:06:25 +0530314 bus,
315 ipmi::network::IP_INTERFACE,
316 ipmi::network::ROOT,
317 ipmi::network::IP_TYPE);
318
319 vlanID = static_cast<uint16_t>(
320 ipmi::network::getVLAN(ipObjectInfo.first));
321
322 vlanID = htole16(vlanID);
323
324 if (vlanID)
325 {
326 //Enable the 16th bit
327 vlanID |= htole16(ipmi::network::VLAN_ENABLE_MASK);
328 }
329 }
330 // ignore the exception, as it is a valid condtion that
331 // system is not confiured with any ip.
332 catch (InternalFailure& e)
333 {
334 // nothing to do
Ratan Gupta533d03b2017-07-30 10:39:22 +0530335 }
336
337 memcpy(data, &vlanID, ipmi::network::VLAN_SIZE_BYTE);
338 }
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800339 else if (channelConf->lan_set_in_progress == SET_IN_PROGRESS)
Ratan Gupta533d03b2017-07-30 10:39:22 +0530340 {
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800341 memcpy(data, &(channelConf->vlanID),
Ratan Gupta533d03b2017-07-30 10:39:22 +0530342 ipmi::network::VLAN_SIZE_BYTE);
343 }
344 }
345 break;
346
Ratan Guptab8e99552017-07-27 07:07:48 +0530347 default:
348 rc = IPMI_CC_PARM_OUT_OF_RANGE;
tomjose26e17732016-03-03 08:52:51 -0600349 }
350 }
Ratan Guptab8e99552017-07-27 07:07:48 +0530351 catch (InternalFailure& e)
tomjose26e17732016-03-03 08:52:51 -0600352 {
Ratan Guptab8e99552017-07-27 07:07:48 +0530353 commit<InternalFailure>();
354 rc = IPMI_CC_UNSPECIFIED_ERROR;
355 return rc;
tomjose26e17732016-03-03 08:52:51 -0600356 }
tomjose26e17732016-03-03 08:52:51 -0600357 return rc;
358}
359
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500360ipmi_ret_t ipmi_transport_wildcard(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
361 ipmi_request_t request, ipmi_response_t response,
362 ipmi_data_len_t data_len, ipmi_context_t context)
363{
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500364 // Status code.
Nan Li70aa8d92016-08-29 00:11:10 +0800365 ipmi_ret_t rc = IPMI_CC_INVALID;
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500366 *data_len = 0;
367 return rc;
368}
369
Ratan Guptab8e99552017-07-27 07:07:48 +0530370struct set_lan_t
371{
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500372 uint8_t channel;
373 uint8_t parameter;
374 uint8_t data[8]; // Per IPMI spec, not expecting more than this size
Ratan Guptab8e99552017-07-27 07:07:48 +0530375} __attribute__((packed));
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500376
Ratan Guptab8e99552017-07-27 07:07:48 +0530377ipmi_ret_t ipmi_transport_set_lan(ipmi_netfn_t netfn,
378 ipmi_cmd_t cmd,
379 ipmi_request_t request,
380 ipmi_response_t response,
381 ipmi_data_len_t data_len,
382 ipmi_context_t context)
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500383{
384 ipmi_ret_t rc = IPMI_CC_OK;
385 *data_len = 0;
Nan Li3d0df912016-10-18 19:51:41 +0800386
Ratan Gupta7a7f0122018-03-07 12:31:05 +0530387 using namespace std::chrono_literals;
388
389 // time to wait before applying the network changes.
390 constexpr auto networkTimeout = 10000000us; // 10 sec
391
Ratan Guptab8e99552017-07-27 07:07:48 +0530392 char ipaddr[INET_ADDRSTRLEN];
393 char netmask[INET_ADDRSTRLEN];
394 char gateway[INET_ADDRSTRLEN];
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500395
Ratan Guptab8e99552017-07-27 07:07:48 +0530396 auto reqptr = reinterpret_cast<const set_lan_t*>(request);
397 sdbusplus::bus::bus bus(ipmid_get_sd_bus_connection());
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500398
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800399 // channel number is the lower nibble
400 int channel = reqptr->channel & CHANNEL_MASK;
401 auto ethdevice = ipmi::network::ChanneltoEthernet(channel);
402 if (ethdevice.empty())
403 {
404 return IPMI_CC_INVALID_FIELD_REQUEST;
405 }
406 auto channelConf = getChannelConfig(channel);
407
Ratan Guptab8e99552017-07-27 07:07:48 +0530408 switch (reqptr->parameter)
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500409 {
Ratan Guptab8e99552017-07-27 07:07:48 +0530410 case LAN_PARM_IP:
Hariharasubramanian R83951912016-01-20 07:06:36 -0600411 {
Ratan Guptab8e99552017-07-27 07:07:48 +0530412 snprintf(ipaddr, INET_ADDRSTRLEN, ipmi::network::IP_ADDRESS_FORMAT,
413 reqptr->data[0], reqptr->data[1],
414 reqptr->data[2], reqptr->data[3]);
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500415
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800416 channelConf->ipaddr.assign(ipaddr);
Ratan Guptab8e99552017-07-27 07:07:48 +0530417 }
418 break;
419
Ratan Guptacc6cdbf2017-09-01 23:06:25 +0530420 case LAN_PARM_IPSRC:
421 {
422 uint8_t ipsrc{};
423 memcpy(&ipsrc, reqptr->data, ipmi::network::IPSRC_SIZE_BYTE);
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800424 channelConf->ipsrc = static_cast<ipmi::network::IPOrigin>(ipsrc);
Ratan Guptacc6cdbf2017-09-01 23:06:25 +0530425 }
426 break;
427
Ratan Guptab8e99552017-07-27 07:07:48 +0530428 case LAN_PARM_MAC:
429 {
430 char mac[SIZE_MAC];
431
432 snprintf(mac, SIZE_MAC, ipmi::network::MAC_ADDRESS_FORMAT,
433 reqptr->data[0],
434 reqptr->data[1],
435 reqptr->data[2],
436 reqptr->data[3],
437 reqptr->data[4],
438 reqptr->data[5]);
439
440 auto macObjectInfo = ipmi::getDbusObject(
441 bus,
442 ipmi::network::MAC_INTERFACE,
443 ipmi::network::ROOT,
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800444 ethdevice);
Ratan Guptab8e99552017-07-27 07:07:48 +0530445
446 ipmi::setDbusProperty(bus,
447 macObjectInfo.second,
448 macObjectInfo.first,
449 ipmi::network::MAC_INTERFACE,
450 "MACAddress",
451 std::string(mac));
452
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800453 channelConf->macAddress = mac;
Ratan Guptab8e99552017-07-27 07:07:48 +0530454 }
455 break;
456
457 case LAN_PARM_SUBNET:
458 {
459 snprintf(netmask, INET_ADDRSTRLEN, ipmi::network::IP_ADDRESS_FORMAT,
460 reqptr->data[0], reqptr->data[1],
461 reqptr->data[2], reqptr->data[3]);
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800462 channelConf->netmask.assign(netmask);
Ratan Guptab8e99552017-07-27 07:07:48 +0530463 }
464 break;
465
466 case LAN_PARM_GATEWAY:
467 {
468 snprintf(gateway, INET_ADDRSTRLEN, ipmi::network::IP_ADDRESS_FORMAT,
469 reqptr->data[0], reqptr->data[1],
470 reqptr->data[2], reqptr->data[3]);
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800471 channelConf->gateway.assign(gateway);
Ratan Guptab8e99552017-07-27 07:07:48 +0530472 }
473 break;
474
Ratan Gupta533d03b2017-07-30 10:39:22 +0530475 case LAN_PARM_VLAN:
476 {
477 uint16_t vlan {};
478 memcpy(&vlan, reqptr->data, ipmi::network::VLAN_SIZE_BYTE);
479 // We are not storing the enable bit
480 // We assume that ipmitool always send enable
481 // bit as 1.
482 vlan = le16toh(vlan);
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800483 channelConf->vlanID = vlan;
Ratan Gupta533d03b2017-07-30 10:39:22 +0530484 }
485 break;
486
Ratan Guptab8e99552017-07-27 07:07:48 +0530487 case LAN_PARM_INPROGRESS:
488 {
489 if (reqptr->data[0] == SET_COMPLETE)
490 {
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800491 channelConf->lan_set_in_progress = SET_COMPLETE;
Ratan Guptab8e99552017-07-27 07:07:48 +0530492
493 log<level::INFO>("Network data from Cache",
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800494 entry("PREFIX=%s", channelConf->netmask.c_str()),
495 entry("ADDRESS=%s", channelConf->ipaddr.c_str()),
496 entry("GATEWAY=%s", channelConf->gateway.c_str()),
497 entry("VLAN=%d", channelConf->vlanID));
Ratan Guptab8e99552017-07-27 07:07:48 +0530498
Ratan Gupta7a7f0122018-03-07 12:31:05 +0530499 if (!networkTimer)
500 {
501 log<level::ERR>("Network timer is not instantiated");
502 return IPMI_CC_UNSPECIFIED_ERROR;
503 }
504
505 // start/restart the timer
506 networkTimer->startTimer(networkTimeout);
Ratan Guptab8e99552017-07-27 07:07:48 +0530507 }
508 else if (reqptr->data[0] == SET_IN_PROGRESS) // Set In Progress
509 {
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800510 channelConf->lan_set_in_progress = SET_IN_PROGRESS;
Ratan Gupta7a7f0122018-03-07 12:31:05 +0530511 channelConf->flush = true;
Ratan Guptab8e99552017-07-27 07:07:48 +0530512 }
Ratan Guptab8e99552017-07-27 07:07:48 +0530513 }
514 break;
515
516 default:
517 {
Ratan Guptab8e99552017-07-27 07:07:48 +0530518 rc = IPMI_CC_PARM_NOT_SUPPORTED;
519 }
Ratan Guptab8e99552017-07-27 07:07:48 +0530520 }
vishwa1eaea4f2016-02-26 11:57:40 -0600521
tomjose26e17732016-03-03 08:52:51 -0600522 return rc;
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500523}
524
Ratan Guptab8e99552017-07-27 07:07:48 +0530525struct get_lan_t
526{
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500527 uint8_t rev_channel;
528 uint8_t parameter;
529 uint8_t parameter_set;
530 uint8_t parameter_block;
Ratan Guptab8e99552017-07-27 07:07:48 +0530531} __attribute__((packed));
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500532
Ratan Guptab8e99552017-07-27 07:07:48 +0530533ipmi_ret_t ipmi_transport_get_lan(ipmi_netfn_t netfn,
534 ipmi_cmd_t cmd,
535 ipmi_request_t request,
536 ipmi_response_t response,
537 ipmi_data_len_t data_len,
538 ipmi_context_t context)
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500539{
540 ipmi_ret_t rc = IPMI_CC_OK;
541 *data_len = 0;
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500542 const uint8_t current_revision = 0x11; // Current rev per IPMI Spec 2.0
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500543
544 get_lan_t *reqptr = (get_lan_t*) request;
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800545 // channel number is the lower nibble
546 int channel = reqptr->rev_channel & CHANNEL_MASK;
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500547
548 if (reqptr->rev_channel & 0x80) // Revision is bit 7
549 {
550 // Only current revision was requested
551 *data_len = sizeof(current_revision);
552 memcpy(response, &current_revision, *data_len);
553 return IPMI_CC_OK;
554 }
555
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800556 auto ethdevice = ipmi::network::ChanneltoEthernet(channel);
557 if (ethdevice.empty())
558 {
559 return IPMI_CC_INVALID_FIELD_REQUEST;
560 }
561 auto channelConf = getChannelConfig(channel);
562
Adriana Kobylake08fbc62016-02-09 16:17:23 -0600563 if (reqptr->parameter == LAN_PARM_INPROGRESS)
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500564 {
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800565 uint8_t buf[] = {current_revision, channelConf->lan_set_in_progress};
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500566 *data_len = sizeof(buf);
567 memcpy(response, &buf, *data_len);
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500568 }
Adriana Kobylake08fbc62016-02-09 16:17:23 -0600569 else if (reqptr->parameter == LAN_PARM_AUTHSUPPORT)
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500570 {
571 uint8_t buf[] = {current_revision,0x04};
572 *data_len = sizeof(buf);
573 memcpy(response, &buf, *data_len);
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500574 }
Adriana Kobylake08fbc62016-02-09 16:17:23 -0600575 else if (reqptr->parameter == LAN_PARM_AUTHENABLES)
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500576 {
577 uint8_t buf[] = {current_revision,0x04,0x04,0x04,0x04,0x04};
578 *data_len = sizeof(buf);
579 memcpy(response, &buf, *data_len);
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500580 }
Ratan Guptab8e99552017-07-27 07:07:48 +0530581 else if ((reqptr->parameter == LAN_PARM_IP) ||
582 (reqptr->parameter == LAN_PARM_SUBNET) ||
583 (reqptr->parameter == LAN_PARM_GATEWAY) ||
584 (reqptr->parameter == LAN_PARM_MAC))
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500585 {
Ratan Guptacc6cdbf2017-09-01 23:06:25 +0530586 uint8_t buf[ipmi::network::MAC_ADDRESS_SIZE_BYTE + 1] = {};
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500587
tomjose26e17732016-03-03 08:52:51 -0600588 *data_len = sizeof(current_revision);
589 memcpy(buf, &current_revision, *data_len);
590
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800591 if (getNetworkData(reqptr->parameter, &buf[1], channel) == IPMI_CC_OK)
vishwa1eaea4f2016-02-26 11:57:40 -0600592 {
Ratan Guptab8e99552017-07-27 07:07:48 +0530593 if (reqptr->parameter == LAN_PARM_MAC)
594 {
595 *data_len = sizeof(buf);
596 }
597 else
598 {
599 *data_len = ipmi::network::IPV4_ADDRESS_SIZE_BYTE + 1;
600 }
tomjose26e17732016-03-03 08:52:51 -0600601 memcpy(response, &buf, *data_len);
Adriana Kobylak342df102016-02-10 13:48:16 -0600602 }
tomjose26e17732016-03-03 08:52:51 -0600603 else
Hariharasubramanian R83951912016-01-20 07:06:36 -0600604 {
tomjose26e17732016-03-03 08:52:51 -0600605 rc = IPMI_CC_UNSPECIFIED_ERROR;
Hariharasubramanian R83951912016-01-20 07:06:36 -0600606 }
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500607 }
Ratan Gupta533d03b2017-07-30 10:39:22 +0530608 else if (reqptr->parameter == LAN_PARM_VLAN)
609 {
Ratan Guptacc6cdbf2017-09-01 23:06:25 +0530610 uint8_t buf[ipmi::network::VLAN_SIZE_BYTE + 1] = {};
Ratan Gupta533d03b2017-07-30 10:39:22 +0530611
612 *data_len = sizeof(current_revision);
613 memcpy(buf, &current_revision, *data_len);
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800614 if (getNetworkData(reqptr->parameter, &buf[1], channel) == IPMI_CC_OK)
Ratan Gupta533d03b2017-07-30 10:39:22 +0530615 {
616 *data_len = sizeof(buf);
617 memcpy(response, &buf, *data_len);
618 }
619 }
Ratan Guptacc6cdbf2017-09-01 23:06:25 +0530620 else if (reqptr->parameter == LAN_PARM_IPSRC)
621 {
622 uint8_t buff[ipmi::network::IPSRC_SIZE_BYTE + 1] = {};
623 *data_len = sizeof(current_revision);
624 memcpy(buff, &current_revision, *data_len);
Patrick Venturec7c1c3c2017-11-15 14:29:18 -0800625 if (getNetworkData(reqptr->parameter, &buff[1], channel) == IPMI_CC_OK)
Ratan Guptacc6cdbf2017-09-01 23:06:25 +0530626 {
627 *data_len = sizeof(buff);
628 memcpy(response, &buff, *data_len);
629 }
630 }
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500631 else
632 {
Ratan Guptab8e99552017-07-27 07:07:48 +0530633 log<level::ERR>("Unsupported parameter",
634 entry("PARAMETER=0x%x", reqptr->parameter));
vishwa1eaea4f2016-02-26 11:57:40 -0600635 rc = IPMI_CC_PARM_NOT_SUPPORTED;
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500636 }
637
638 return rc;
639}
640
Ratan Gupta7a7f0122018-03-07 12:31:05 +0530641void applyChanges(int channel)
Ratan Gupta1247e0b2018-03-07 10:47:25 +0530642{
Ratan Gupta1247e0b2018-03-07 10:47:25 +0530643 std::string ipaddress;
644 std::string gateway;
645 uint8_t prefix {};
646 uint32_t vlanID {};
647 std::string networkInterfacePath;
648 ipmi::DbusObjectInfo ipObject;
649 ipmi::DbusObjectInfo systemObject;
650
Ratan Gupta1247e0b2018-03-07 10:47:25 +0530651 auto ethdevice = ipmi::network::ChanneltoEthernet(channel);
652 if (ethdevice.empty())
653 {
Ratan Gupta7a7f0122018-03-07 12:31:05 +0530654 log<level::ERR>("Unable to get the interface name",
655 entry("CHANNEL=%d", channel));
656 return;
Ratan Gupta1247e0b2018-03-07 10:47:25 +0530657 }
658 auto ethIp = ethdevice + "/" + ipmi::network::IP_TYPE;
659 auto channelConf = getChannelConfig(channel);
660
Ratan Gupta1247e0b2018-03-07 10:47:25 +0530661 try
662 {
663 sdbusplus::bus::bus bus(ipmid_get_sd_bus_connection());
664
665 log<level::INFO>("Network data from Cache",
666 entry("PREFIX=%s", channelConf->netmask.c_str()),
667 entry("ADDRESS=%s", channelConf->ipaddr.c_str()),
668 entry("GATEWAY=%s", channelConf->gateway.c_str()),
669 entry("VLAN=%d", channelConf->vlanID),
670 entry("IPSRC=%d", channelConf->ipsrc));
671 if (channelConf->vlanID != ipmi::network::VLAN_ID_MASK)
672 {
673 //get the first twelve bits which is vlan id
674 //not interested in rest of the bits.
675 channelConf->vlanID = le32toh(channelConf->vlanID);
676 vlanID = channelConf->vlanID & ipmi::network::VLAN_ID_MASK;
677 }
678
679 // if the asked ip src is DHCP then not interested in
680 // any given data except vlan.
681 if (channelConf->ipsrc != ipmi::network::IPOrigin::DHCP)
682 {
683 // always get the system object
684 systemObject = ipmi::getDbusObject(
685 bus,
686 ipmi::network::SYSTEMCONFIG_INTERFACE,
687 ipmi::network::ROOT);
688
689 // the below code is to determine the mode of the interface
690 // as the handling is same, if the system is configured with
691 // DHCP or user has given all the data.
692 try
693 {
694 ipmi::ObjectTree ancestorMap;
695
696 ipmi::InterfaceList interfaces {
697 ipmi::network::ETHERNET_INTERFACE };
698
699 // if the system is having ip object,then
700 // get the IP object.
Ratan Guptadf53d222018-03-18 23:13:39 +0530701 ipObject = ipmi::getIPObject(bus,
Ratan Gupta1247e0b2018-03-07 10:47:25 +0530702 ipmi::network::IP_INTERFACE,
703 ipmi::network::ROOT,
704 ethIp);
705
706 // Get the parent interface of the IP object.
707 try
708 {
709 ancestorMap = ipmi::getAllAncestors(bus,
710 ipObject.first,
711 std::move(interfaces));
712 }
713 catch (InternalFailure& e)
714 {
715 // if unable to get the parent interface
716 // then commit the error and return.
717 log<level::ERR>("Unable to get the parent interface",
718 entry("PATH=%s", ipObject.first.c_str()),
719 entry("INTERFACE=%s",
720 ipmi::network::ETHERNET_INTERFACE));
721 commit<InternalFailure>();
Ratan Gupta1247e0b2018-03-07 10:47:25 +0530722 channelConf->clear();
Ratan Gupta7a7f0122018-03-07 12:31:05 +0530723 return;
Ratan Gupta1247e0b2018-03-07 10:47:25 +0530724 }
725
726 networkInterfacePath = ancestorMap.begin()->first;
727 }
728 catch (InternalFailure& e)
729 {
730 // TODO Currently IPMI supports single interface,need to handle
731 // Multiple interface through
732 // https://github.com/openbmc/openbmc/issues/2138
733
734 // if there is no ip configured on the system,then
735 // get the network interface object.
736 auto networkInterfaceObject = ipmi::getDbusObject(
737 bus,
738 ipmi::network::ETHERNET_INTERFACE,
739 ipmi::network::ROOT,
740 ethdevice);
741
742 networkInterfacePath = std::move(networkInterfaceObject.first);
743 }
744
745 // get the configured mode on the system.
746 auto enableDHCP = ipmi::getDbusProperty(
747 bus,
748 ipmi::network::SERVICE,
749 networkInterfacePath,
750 ipmi::network::ETHERNET_INTERFACE,
751 "DHCPEnabled").get<bool>();
752
753 // if ip address source is not given then get the ip source mode
754 // from the system so that it can be applied later.
755 if (channelConf->ipsrc == ipmi::network::IPOrigin::UNSPECIFIED)
756 {
757 channelConf->ipsrc = (enableDHCP) ?
758 ipmi::network::IPOrigin::DHCP :
759 ipmi::network::IPOrigin::STATIC;
760 }
761
762 // check whether user has given all the data
763 // or the configured system interface is dhcp enabled,
764 // in both of the cases get the values from the cache.
765 if ((!channelConf->ipaddr.empty() &&
766 !channelConf->netmask.empty() &&
767 !channelConf->gateway.empty()) ||
768 (enableDHCP)) // configured system interface mode = DHCP
769 {
770 //convert mask into prefix
771 ipaddress = channelConf->ipaddr;
772 prefix = ipmi::network::toPrefix(AF_INET, channelConf->netmask);
773 gateway = channelConf->gateway;
774 }
775 else // asked ip src = static and configured system src = static
776 // or partially given data.
777 {
778 // We have partial filled cache so get the remaining
779 // info from the system.
780
781 // Get the network data from the system as user has
782 // not given all the data then use the data fetched from the
783 // system but it is implementation dependent,IPMI spec doesn't
784 // force it.
785
786 // if system is not having any ip object don't throw error,
787 try
788 {
789 auto properties = ipmi::getAllDbusProperties(
790 bus,
791 ipObject.second,
792 ipObject.first,
793 ipmi::network::IP_INTERFACE);
794
795 ipaddress = channelConf->ipaddr.empty() ?
796 properties["Address"].get<std::string>() :
797 channelConf->ipaddr;
798
799 prefix = channelConf->netmask.empty() ?
800 properties["PrefixLength"].get<uint8_t>() :
801 ipmi::network::toPrefix(AF_INET,
802 channelConf->netmask);
803 }
804 catch (InternalFailure& e)
805 {
806 log<level::INFO>("Failed to get IP object which matches",
807 entry("INTERFACE=%s", ipmi::network::IP_INTERFACE),
808 entry("MATCH=%s", ethIp));
809 }
810
811 auto systemProperties = ipmi::getAllDbusProperties(
812 bus,
813 systemObject.second,
814 systemObject.first,
815 ipmi::network::SYSTEMCONFIG_INTERFACE);
816
817 gateway = channelConf->gateway.empty() ?
818 systemProperties["DefaultGateway"].get<std::string>() :
819 channelConf->gateway;
820 }
821 }
822
823 // Currently network manager doesn't support purging of all the
824 // ip addresses and the vlan interfaces from the parent interface,
825 // TODO once the support is there, will make the change here.
826 // https://github.com/openbmc/openbmc/issues/2141.
827
828 // TODO Currently IPMI supports single interface,need to handle
829 // Multiple interface through
830 // https://github.com/openbmc/openbmc/issues/2138
831
832 // instead of deleting all the vlan interfaces and
833 // all the ipv4 address,we will call reset method.
834 //delete all the vlan interfaces
835
836 ipmi::deleteAllDbusObjects(bus,
837 ipmi::network::ROOT,
838 ipmi::network::VLAN_INTERFACE);
839
840 // set the interface mode to static
841 auto networkInterfaceObject = ipmi::getDbusObject(
842 bus,
843 ipmi::network::ETHERNET_INTERFACE,
844 ipmi::network::ROOT,
845 ethdevice);
846
847 // setting the physical interface mode to static.
848 ipmi::setDbusProperty(bus,
849 ipmi::network::SERVICE,
850 networkInterfaceObject.first,
851 ipmi::network::ETHERNET_INTERFACE,
852 "DHCPEnabled",
853 false);
854
855 networkInterfacePath = networkInterfaceObject.first;
856
857 //delete all the ipv4 addresses
858 ipmi::deleteAllDbusObjects(bus,
859 ipmi::network::ROOT,
860 ipmi::network::IP_INTERFACE,
861 ethIp);
862
863 if (vlanID)
864 {
865 ipmi::network::createVLAN(bus,
866 ipmi::network::SERVICE,
867 ipmi::network::ROOT,
868 ethdevice,
869 vlanID);
870
871 auto networkInterfaceObject = ipmi::getDbusObject(
872 bus,
873 ipmi::network::VLAN_INTERFACE,
874 ipmi::network::ROOT);
875
876 networkInterfacePath = networkInterfaceObject.first;
877 }
878
879 if (channelConf->ipsrc == ipmi::network::IPOrigin::DHCP)
880 {
881 ipmi::setDbusProperty(bus,
882 ipmi::network::SERVICE,
883 networkInterfacePath,
884 ipmi::network::ETHERNET_INTERFACE,
885 "DHCPEnabled",
886 true);
887 }
888 else
889 {
890 //change the mode to static
891 ipmi::setDbusProperty(bus,
892 ipmi::network::SERVICE,
893 networkInterfacePath,
894 ipmi::network::ETHERNET_INTERFACE,
895 "DHCPEnabled",
896 false);
897
898 if (!ipaddress.empty())
899 {
900 ipmi::network::createIP(bus,
901 ipmi::network::SERVICE,
902 networkInterfacePath,
903 ipv4Protocol,
904 ipaddress,
905 prefix);
906 }
907
908 if (!gateway.empty())
909 {
910 ipmi::setDbusProperty(bus,
911 systemObject.second,
912 systemObject.first,
913 ipmi::network::SYSTEMCONFIG_INTERFACE,
914 "DefaultGateway",
915 std::string(gateway));
916 }
917 }
918
919 }
920 catch (InternalFailure& e)
921 {
922 log<level::ERR>("Failed to set network data",
923 entry("PREFIX=%d", prefix),
924 entry("ADDRESS=%s", ipaddress.c_str()),
925 entry("GATEWAY=%s", gateway.c_str()),
926 entry("VLANID=%d", vlanID),
927 entry("IPSRC=%d", channelConf->ipsrc));
928
929 commit<InternalFailure>();
Ratan Gupta1247e0b2018-03-07 10:47:25 +0530930 }
931
932 channelConf->clear();
Ratan Gupta7a7f0122018-03-07 12:31:05 +0530933}
934
935void commitNetworkChanges()
936{
937 for (const auto &channel : channelConfig)
938 {
939 if (channel.second->flush)
940 {
941 applyChanges(channel.first);
942 }
943 }
944}
945
946void createNetworkTimer()
947{
948 if (!networkTimer)
949 {
950 std::function<void()> networkTimerCallback(
951 std::bind(&commitNetworkChanges));
952
953 networkTimer =
954 std::make_unique<phosphor::ipmi::Timer>(
955 ipmid_get_sd_event_connection(),
956 networkTimerCallback);
957 }
958
Ratan Gupta1247e0b2018-03-07 10:47:25 +0530959}
960
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500961void register_netfn_transport_functions()
962{
Ratan Gupta7a7f0122018-03-07 12:31:05 +0530963 // As this timer is only for transport handler
964 // so creating it here.
965 createNetworkTimer();
Tom05732372016-09-06 17:21:23 +0530966 // <Wildcard Command>
Tom05732372016-09-06 17:21:23 +0530967 ipmi_register_callback(NETFUN_TRANSPORT, IPMI_CMD_WILDCARD, NULL, ipmi_transport_wildcard,
968 PRIVILEGE_USER);
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500969
Tom05732372016-09-06 17:21:23 +0530970 // <Set LAN Configuration Parameters>
Tom05732372016-09-06 17:21:23 +0530971 ipmi_register_callback(NETFUN_TRANSPORT, IPMI_CMD_SET_LAN, NULL, ipmi_transport_set_lan,
972 PRIVILEGE_ADMIN);
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500973
Tom05732372016-09-06 17:21:23 +0530974 // <Get LAN Configuration Parameters>
Tom05732372016-09-06 17:21:23 +0530975 ipmi_register_callback(NETFUN_TRANSPORT, IPMI_CMD_GET_LAN, NULL, ipmi_transport_get_lan,
976 PRIVILEGE_OPERATOR);
Adriana Kobylak5d6481f2015-10-29 21:44:55 -0500977
978 return;
979}