1 #include <arpa/inet.h>
2 #include <netinet/ether.h>
3 
4 #include <array>
5 #include <bitset>
6 #include <cinttypes>
7 #include <cstdint>
8 #include <cstring>
9 #include <functional>
10 #include <ipmid/api.hpp>
11 #include <ipmid/message.hpp>
12 #include <ipmid/message/types.hpp>
13 #include <ipmid/types.hpp>
14 #include <ipmid/utils.hpp>
15 #include <optional>
16 #include <phosphor-logging/elog-errors.hpp>
17 #include <phosphor-logging/elog.hpp>
18 #include <phosphor-logging/log.hpp>
19 #include <sdbusplus/bus.hpp>
20 #include <sdbusplus/exception.hpp>
21 #include <string>
22 #include <string_view>
23 #include <type_traits>
24 #include <unordered_map>
25 #include <unordered_set>
26 #include <user_channel/channel_layer.hpp>
27 #include <utility>
28 #include <vector>
29 #include <xyz/openbmc_project/Common/error.hpp>
30 #include <xyz/openbmc_project/Network/IP/server.hpp>
31 
32 namespace ipmi
33 {
34 namespace transport
35 {
36 
37 using phosphor::logging::commit;
38 using phosphor::logging::elog;
39 using phosphor::logging::entry;
40 using phosphor::logging::level;
41 using phosphor::logging::log;
42 using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
43 using sdbusplus::xyz::openbmc_project::Network::server::IP;
44 
45 // LAN Handler specific response codes
46 constexpr Cc ccParamNotSupported = 0x80;
47 constexpr Cc ccParamSetLocked = 0x81;
48 constexpr Cc ccParamReadOnly = 0x82;
49 
50 // VLANs are a 12-bit value
51 constexpr uint16_t VLAN_VALUE_MASK = 0x0fff;
52 constexpr uint16_t VLAN_ENABLE_FLAG = 0x8000;
53 
54 // D-Bus Network Daemon definitions
55 constexpr auto PATH_ROOT = "/xyz/openbmc_project/network";
56 constexpr auto PATH_SYSTEMCONFIG = "/xyz/openbmc_project/network/config";
57 
58 constexpr auto INTF_SYSTEMCONFIG =
59     "xyz.openbmc_project.Network.SystemConfiguration";
60 constexpr auto INTF_ETHERNET = "xyz.openbmc_project.Network.EthernetInterface";
61 constexpr auto INTF_IP = "xyz.openbmc_project.Network.IP";
62 constexpr auto INTF_IP_CREATE = "xyz.openbmc_project.Network.IP.Create";
63 constexpr auto INTF_MAC = "xyz.openbmc_project.Network.MACAddress";
64 constexpr auto INTF_VLAN = "xyz.openbmc_project.Network.VLAN";
65 constexpr auto INTF_VLAN_CREATE = "xyz.openbmc_project.Network.VLAN.Create";
66 
67 /** @brief Generic paramters for different address families */
68 template <int family>
69 struct AddrFamily
70 {
71 };
72 
73 /** @brief Parameter specialization for IPv4 */
74 template <>
75 struct AddrFamily<AF_INET>
76 {
77     using addr = in_addr;
78     static constexpr auto protocol = IP::Protocol::IPv4;
79     static constexpr size_t maxStrLen = INET6_ADDRSTRLEN;
80     static constexpr uint8_t defaultPrefix = 32;
81     static constexpr char propertyGateway[] = "DefaultGateway";
82 };
83 
84 /** @brief Valid address origins for IPv4 */
85 const std::unordered_set<IP::AddressOrigin> originsV4 = {
86     IP::AddressOrigin::Static,
87     IP::AddressOrigin::DHCP,
88 };
89 
90 /** @brief Interface IP Address configuration parameters */
91 template <int family>
92 struct IfAddr
93 {
94     std::string path;
95     typename AddrFamily<family>::addr address;
96     IP::AddressOrigin origin;
97     uint8_t prefix;
98 };
99 
100 /** @brief IPMI LAN Parameters */
101 enum class LanParam : uint8_t
102 {
103     SetStatus = 0,
104     AuthSupport = 1,
105     AuthEnables = 2,
106     IP = 3,
107     IPSrc = 4,
108     MAC = 5,
109     SubnetMask = 6,
110     Gateway1 = 12,
111     VLANId = 20,
112     CiphersuiteSupport = 22,
113     CiphersuiteEntries = 23,
114 };
115 
116 /** @brief IPMI IP Origin Types */
117 enum class IPSrc : uint8_t
118 {
119     Unspecified = 0,
120     Static = 1,
121     DHCP = 2,
122     BIOS = 3,
123     BMC = 4,
124 };
125 
126 /** @brief IPMI Set Status */
127 enum class SetStatus : uint8_t
128 {
129     Complete = 0,
130     InProgress = 1,
131     Commit = 2,
132 };
133 
134 /** @brief Copies bytes from an array into a trivially copyable container
135  *
136  *  @params[out] t     - The container receiving the data
137  *  @params[in]  bytes - The data to copy
138  */
139 template <size_t N, typename T>
140 void copyInto(T& t, const std::array<uint8_t, N>& bytes)
141 {
142     static_assert(std::is_trivially_copyable_v<T>);
143     static_assert(N == sizeof(T));
144     std::memcpy(&t, bytes.data(), bytes.size());
145 }
146 
147 /** @brief Gets a generic view of the bytes in the input container
148  *
149  *  @params[in] t - The data to reference
150  *  @return A string_view referencing the bytes in the container
151  */
152 template <typename T>
153 std::string_view dataRef(const T& t)
154 {
155     static_assert(std::is_trivially_copyable_v<T>);
156     return {reinterpret_cast<const char*>(&t), sizeof(T)};
157 }
158 
159 /** @brief The dbus parameters for the interface corresponding to a channel
160  *         This helps reduce the number of mapper lookups we need for each
161  *         query and simplifies finding the VLAN interface if needed.
162  */
163 struct ChannelParams
164 {
165     /** @brief The channel ID */
166     int id;
167     /** @brief channel name for the interface */
168     std::string ifname;
169     /** @brief Name of the service on the bus */
170     std::string service;
171     /** @brief Lower level adapter path that is guaranteed to not be a VLAN */
172     std::string ifPath;
173     /** @brief Logical adapter path used for address assignment */
174     std::string logicalPath;
175 };
176 
177 /** @brief Determines the ethernet interface name corresponding to a channel
178  *         Tries to map a VLAN object first so that the address information
179  *         is accurate. Otherwise it gets the standard ethernet interface.
180  *
181  *  @param[in] bus     - The bus object used for lookups
182  *  @param[in] channel - The channel id corresponding to an ethernet interface
183  *  @return Ethernet interface service and object path if it exists
184  */
185 std::optional<ChannelParams> maybeGetChannelParams(sdbusplus::bus::bus& bus,
186                                                    uint8_t channel)
187 {
188     auto ifname = getChannelName(channel);
189     if (ifname.empty())
190     {
191         return std::nullopt;
192     }
193 
194     // Enumerate all VLAN + ETHERNET interfaces
195     auto req = bus.new_method_call(MAPPER_BUS_NAME, MAPPER_OBJ, MAPPER_INTF,
196                                    "GetSubTree");
197     req.append(PATH_ROOT, 0,
198                std::vector<std::string>{INTF_VLAN, INTF_ETHERNET});
199     auto reply = bus.call(req);
200     ObjectTree objs;
201     reply.read(objs);
202 
203     ChannelParams params;
204     for (const auto& [path, impls] : objs)
205     {
206         if (path.find(ifname) == path.npos)
207         {
208             continue;
209         }
210         for (const auto& [service, intfs] : impls)
211         {
212             bool vlan = false;
213             bool ethernet = false;
214             for (const auto& intf : intfs)
215             {
216                 if (intf == INTF_VLAN)
217                 {
218                     vlan = true;
219                 }
220                 else if (intf == INTF_ETHERNET)
221                 {
222                     ethernet = true;
223                 }
224             }
225             if (params.service.empty() && (vlan || ethernet))
226             {
227                 params.service = service;
228             }
229             if (params.ifPath.empty() && !vlan && ethernet)
230             {
231                 params.ifPath = path;
232             }
233             if (params.logicalPath.empty() && vlan)
234             {
235                 params.logicalPath = path;
236             }
237         }
238     }
239 
240     // We must have a path for the underlying interface
241     if (params.ifPath.empty())
242     {
243         return std::nullopt;
244     }
245     // We don't have a VLAN so the logical path is the same
246     if (params.logicalPath.empty())
247     {
248         params.logicalPath = params.ifPath;
249     }
250 
251     params.id = channel;
252     params.ifname = std::move(ifname);
253     return std::move(params);
254 }
255 
256 /** @brief A trivial helper around maybeGetChannelParams() that throws an
257  *         exception when it is unable to acquire parameters for the channel.
258  *
259  *  @param[in] bus     - The bus object used for lookups
260  *  @param[in] channel - The channel id corresponding to an ethernet interface
261  *  @return Ethernet interface service and object path
262  */
263 ChannelParams getChannelParams(sdbusplus::bus::bus& bus, uint8_t channel)
264 {
265     auto params = maybeGetChannelParams(bus, channel);
266     if (!params)
267     {
268         log<level::ERR>("Failed to get channel params",
269                         entry("CHANNEL=%" PRIu8, channel));
270         elog<InternalFailure>();
271     }
272     return std::move(*params);
273 }
274 
275 /** @brief Wraps the phosphor logging method to insert some additional metadata
276  *
277  *  @param[in] params - The parameters for the channel
278  *  ...
279  */
280 template <auto level, typename... Args>
281 auto logWithChannel(const ChannelParams& params, Args&&... args)
282 {
283     return log<level>(std::forward<Args>(args)...,
284                       entry("CHANNEL=%d", params.id),
285                       entry("IFNAME=%s", params.ifname.c_str()));
286 }
287 template <auto level, typename... Args>
288 auto logWithChannel(const std::optional<ChannelParams>& params, Args&&... args)
289 {
290     if (params)
291     {
292         return logWithChannel<level>(*params, std::forward<Args>(args)...);
293     }
294     return log<level>(std::forward<Args>(args)...);
295 }
296 
297 /** @brief Trivializes using parameter getter functions by providing a bus
298  *         and channel parameters automatically.
299  *
300  *  @param[in] channel - The channel id corresponding to an ethernet interface
301  *  ...
302  */
303 template <auto func, typename... Args>
304 auto channelCall(uint8_t channel, Args&&... args)
305 {
306     sdbusplus::bus::bus bus(ipmid_get_sd_bus_connection());
307     auto params = getChannelParams(bus, channel);
308     return std::invoke(func, bus, params, std::forward<Args>(args)...);
309 }
310 
311 /** @brief Determines if the ethernet interface is using DHCP
312  *
313  *  @param[in] bus    - The bus object used for lookups
314  *  @param[in] params - The parameters for the channel
315  *  @return True if DHCP is enabled, false otherwise
316  */
317 bool getDHCPProperty(sdbusplus::bus::bus& bus, const ChannelParams& params)
318 {
319     return std::get<bool>(getDbusProperty(
320         bus, params.service, params.logicalPath, INTF_ETHERNET, "DHCPEnabled"));
321 }
322 
323 /** @brief Sets the system value for DHCP on the given interface
324  *
325  *  @param[in] bus    - The bus object used for lookups
326  *  @param[in] params - The parameters for the channel
327  *  @param[in] on     - Whether or not to enable DHCP
328  */
329 void setDHCPProperty(sdbusplus::bus::bus& bus, const ChannelParams& params,
330                      bool on)
331 {
332     setDbusProperty(bus, params.service, params.logicalPath, INTF_ETHERNET,
333                     "DHCPEnabled", on);
334 }
335 
336 /** @brief Converts a human readable MAC string into MAC bytes
337  *
338  *  @param[in] mac - The MAC string
339  *  @return MAC in bytes
340  */
341 ether_addr stringToMAC(const char* mac)
342 {
343     const ether_addr* ret = ether_aton(mac);
344     if (ret == nullptr)
345     {
346         log<level::ERR>("Invalid MAC Address", entry("MAC=%s", mac));
347         elog<InternalFailure>();
348     }
349     return *ret;
350 }
351 
352 /** @brief Determines the MAC of the ethernet interface
353  *
354  *  @param[in] bus    - The bus object used for lookups
355  *  @param[in] params - The parameters for the channel
356  *  @return The configured mac address
357  */
358 ether_addr getMACProperty(sdbusplus::bus::bus& bus, const ChannelParams& params)
359 {
360     auto macStr = std::get<std::string>(getDbusProperty(
361         bus, params.service, params.ifPath, INTF_MAC, "MACAddress"));
362     return stringToMAC(macStr.c_str());
363 }
364 
365 /** @brief Sets the system value for MAC address on the given interface
366  *
367  *  @param[in] bus    - The bus object used for lookups
368  *  @param[in] params - The parameters for the channel
369  *  @param[in] mac    - MAC address to apply
370  */
371 void setMACProperty(sdbusplus::bus::bus& bus, const ChannelParams& params,
372                     const ether_addr& mac)
373 {
374     std::string macStr = ether_ntoa(&mac);
375     setDbusProperty(bus, params.service, params.ifPath, INTF_MAC, "MACAddress",
376                     macStr);
377 }
378 
379 /** @brief Turns an IP address string into the network byte order form
380  *         NOTE: This version strictly validates family matches
381  *
382  *  @param[in] address - The string form of the address
383  *  @return A network byte order address or none if conversion failed
384  */
385 template <int family>
386 std::optional<typename AddrFamily<family>::addr>
387     maybeStringToAddr(const char* address)
388 {
389     typename AddrFamily<family>::addr ret;
390     if (inet_pton(family, address, &ret) == 1)
391     {
392         return ret;
393     }
394     return std::nullopt;
395 }
396 
397 /** @brief Turns an IP address string into the network byte order form
398  *         NOTE: This version strictly validates family matches
399  *
400  *  @param[in] address - The string form of the address
401  *  @return A network byte order address
402  */
403 template <int family>
404 typename AddrFamily<family>::addr stringToAddr(const char* address)
405 {
406     auto ret = maybeStringToAddr<family>(address);
407     if (!ret)
408     {
409         log<level::ERR>("Failed to convert IP Address",
410                         entry("FAMILY=%d", family),
411                         entry("ADDRESS=%s", address));
412         elog<InternalFailure>();
413     }
414     return *ret;
415 }
416 
417 /** @brief Turns an IP address in network byte order into a string
418  *
419  *  @param[in] address - The string form of the address
420  *  @return A network byte order address
421  */
422 template <int family>
423 std::string addrToString(const typename AddrFamily<family>::addr& address)
424 {
425     std::string ret(AddrFamily<family>::maxStrLen, '\0');
426     inet_ntop(family, &address, ret.data(), ret.size());
427     ret.resize(strlen(ret.c_str()));
428     return ret;
429 }
430 
431 /** @brief Retrieves the current gateway for the address family on the system
432  *         NOTE: The gateway is currently system wide and not per channel
433  *
434  *  @param[in] bus    - The bus object used for lookups
435  *  @param[in] params - The parameters for the channel
436  *  @return An address representing the gateway address if it exists
437  */
438 template <int family>
439 std::optional<typename AddrFamily<family>::addr>
440     getGatewayProperty(sdbusplus::bus::bus& bus, const ChannelParams& params)
441 {
442     auto gatewayStr = std::get<std::string>(getDbusProperty(
443         bus, params.service, PATH_SYSTEMCONFIG, INTF_SYSTEMCONFIG,
444         AddrFamily<family>::propertyGateway));
445     if (gatewayStr.empty())
446     {
447         return std::nullopt;
448     }
449     return stringToAddr<family>(gatewayStr.c_str());
450 }
451 
452 /** @brief Sets the system wide value for the default gateway
453  *
454  *  @param[in] bus     - The bus object used for lookups
455  *  @param[in] params  - The parameters for the channel
456  *  @param[in] gateway - Gateway address to apply
457  */
458 template <int family>
459 void setGatewayProperty(sdbusplus::bus::bus& bus, const ChannelParams& params,
460                         const typename AddrFamily<family>::addr& address)
461 {
462     setDbusProperty(bus, params.service, PATH_SYSTEMCONFIG, INTF_SYSTEMCONFIG,
463                     AddrFamily<family>::propertyGateway,
464                     addrToString<family>(address));
465 }
466 
467 /** @brief A lazy lookup mechanism for iterating over object properties stored
468  *         in DBus. This will only perform the object lookup when needed, and
469  *         retains a cache of previous lookups to speed up future iterations.
470  */
471 class ObjectLookupCache
472 {
473   public:
474     using PropertiesCache = std::unordered_map<std::string, PropertyMap>;
475 
476     /** @brief Creates a new ObjectLookupCache for the interface on the bus
477      *         NOTE: The inputs to this object must outlive the object since
478      *         they are only referenced by it.
479      *
480      *  @param[in] bus    - The bus object used for lookups
481      *  @param[in] params - The parameters for the channel
482      *  @param[in] intf   - The interface we are looking up
483      */
484     ObjectLookupCache(sdbusplus::bus::bus& bus, const ChannelParams& params,
485                       const char* intf) :
486         bus(bus),
487         params(params), intf(intf),
488         objs(getAllDbusObjects(bus, params.logicalPath, intf, ""))
489     {
490     }
491 
492     class iterator : public ObjectTree::const_iterator
493     {
494       public:
495         using value_type = PropertiesCache::value_type;
496 
497         iterator(ObjectTree::const_iterator it, ObjectLookupCache& container) :
498             ObjectTree::const_iterator(it), container(container),
499             ret(container.cache.end())
500         {
501         }
502         value_type& operator*()
503         {
504             ret = container.get(ObjectTree::const_iterator::operator*().first);
505             return *ret;
506         }
507         value_type* operator->()
508         {
509             return &operator*();
510         }
511 
512       private:
513         ObjectLookupCache& container;
514         PropertiesCache::iterator ret;
515     };
516 
517     iterator begin() noexcept
518     {
519         return iterator(objs.begin(), *this);
520     }
521 
522     iterator end() noexcept
523     {
524         return iterator(objs.end(), *this);
525     }
526 
527   private:
528     sdbusplus::bus::bus& bus;
529     const ChannelParams& params;
530     const char* const intf;
531     const ObjectTree objs;
532     PropertiesCache cache;
533 
534     /** @brief Gets a cached copy of the object properties if possible
535      *         Otherwise performs a query on DBus to look them up
536      *
537      *  @param[in] path - The object path to lookup
538      *  @return An iterator for the specified object path + properties
539      */
540     PropertiesCache::iterator get(const std::string& path)
541     {
542         auto it = cache.find(path);
543         if (it != cache.end())
544         {
545             return it;
546         }
547         auto properties = getAllDbusProperties(bus, params.service, path, intf);
548         return cache.insert({path, std::move(properties)}).first;
549     }
550 };
551 
552 /** @brief Searches the ip object lookup cache for an address matching
553  *         the input parameters. NOTE: The index lacks stability across address
554  *         changes since the network daemon has no notion of stable indicies.
555  *
556  *  @param[in] bus     - The bus object used for lookups
557  *  @param[in] params  - The parameters for the channel
558  *  @param[in] idx     - The index of the desired address on the interface
559  *  @param[in] origins - The allowed origins for the address objects
560  *  @param[in] ips     - The object lookup cache holding all of the address info
561  *  @return The address and prefix if it was found
562  */
563 template <int family>
564 std::optional<IfAddr<family>>
565     findIfAddr(sdbusplus::bus::bus& bus, const ChannelParams& params,
566                uint8_t idx,
567                const std::unordered_set<IP::AddressOrigin>& origins,
568                ObjectLookupCache& ips)
569 {
570     for (const auto& [path, properties] : ips)
571     {
572         const auto& addrStr = std::get<std::string>(properties.at("Address"));
573         auto addr = maybeStringToAddr<family>(addrStr.c_str());
574         if (!addr)
575         {
576             continue;
577         }
578 
579         IP::AddressOrigin origin = IP::convertAddressOriginFromString(
580             std::get<std::string>(properties.at("Origin")));
581         if (origins.find(origin) == origins.end())
582         {
583             continue;
584         }
585 
586         if (idx > 0)
587         {
588             idx--;
589             continue;
590         }
591 
592         IfAddr<family> ifaddr;
593         ifaddr.path = path;
594         ifaddr.address = *addr;
595         ifaddr.prefix = std::get<uint8_t>(properties.at("PrefixLength"));
596         ifaddr.origin = origin;
597         return std::move(ifaddr);
598     }
599 
600     return std::nullopt;
601 }
602 
603 /** @brief Trivial helper around findIfAddr that simplifies calls
604  *         for one off lookups. Don't use this if you intend to do multiple
605  *         lookups at a time.
606  *
607  *  @param[in] bus     - The bus object used for lookups
608  *  @param[in] params  - The parameters for the channel
609  *  @param[in] idx     - The index of the desired address on the interface
610  *  @param[in] origins - The allowed origins for the address objects
611  *  @return The address and prefix if it was found
612  */
613 template <int family>
614 auto getIfAddr(sdbusplus::bus::bus& bus, const ChannelParams& params,
615                uint8_t idx,
616                const std::unordered_set<IP::AddressOrigin>& origins)
617 {
618     ObjectLookupCache ips(bus, params, INTF_IP);
619     return findIfAddr<family>(bus, params, idx, origins, ips);
620 }
621 
622 /** @brief Deletes the dbus object. Ignores empty objects or objects that are
623  *         missing from the bus.
624  *
625  *  @param[in] bus     - The bus object used for lookups
626  *  @param[in] service - The name of the service
627  *  @param[in] path    - The path of the object to delete
628  */
629 void deleteObjectIfExists(sdbusplus::bus::bus& bus, const std::string& service,
630                           const std::string& path)
631 {
632     if (path.empty())
633     {
634         return;
635     }
636     try
637     {
638         auto req = bus.new_method_call(service.c_str(), path.c_str(),
639                                        ipmi::DELETE_INTERFACE, "Delete");
640         bus.call_noreply(req);
641     }
642     catch (const sdbusplus::exception::SdBusError& e)
643     {
644         if (strcmp(e.name(), "org.freedesktop.DBus.Error.UnknownObject") != 0)
645         {
646             // We want to rethrow real errors
647             throw;
648         }
649     }
650 }
651 
652 /** @brief Sets the address info configured for the interface
653  *         If a previous address path exists then it will be removed
654  *         before the new address is added.
655  *
656  *  @param[in] bus     - The bus object used for lookups
657  *  @param[in] params  - The parameters for the channel
658  *  @param[in] address - The address of the new IP
659  *  @param[in] prefix  - The prefix of the new IP
660  */
661 template <int family>
662 void createIfAddr(sdbusplus::bus::bus& bus, const ChannelParams& params,
663                   const typename AddrFamily<family>::addr& address,
664                   uint8_t prefix)
665 {
666     auto newreq =
667         bus.new_method_call(params.service.c_str(), params.logicalPath.c_str(),
668                             INTF_IP_CREATE, "IP");
669     std::string protocol =
670         sdbusplus::xyz::openbmc_project::Network::server::convertForMessage(
671             AddrFamily<family>::protocol);
672     newreq.append(protocol, addrToString<family>(address), prefix, "");
673     bus.call_noreply(newreq);
674 }
675 
676 /** @brief Trivial helper for getting the IPv4 address from getIfAddrs()
677  *
678  *  @param[in] bus    - The bus object used for lookups
679  *  @param[in] params - The parameters for the channel
680  *  @return The address and prefix if found
681  */
682 auto getIfAddr4(sdbusplus::bus::bus& bus, const ChannelParams& params)
683 {
684     return getIfAddr<AF_INET>(bus, params, 0, originsV4);
685 }
686 
687 /** @brief Reconfigures the IPv4 address info configured for the interface
688  *
689  *  @param[in] bus     - The bus object used for lookups
690  *  @param[in] params  - The parameters for the channel
691  *  @param[in] address - The new address if specified
692  *  @param[in] prefix  - The new address prefix if specified
693  */
694 void reconfigureIfAddr4(sdbusplus::bus::bus& bus, const ChannelParams& params,
695                         const std::optional<in_addr>& address,
696                         std::optional<uint8_t> prefix)
697 {
698     auto ifaddr = getIfAddr4(bus, params);
699     if (!ifaddr && !address)
700     {
701         log<level::ERR>("Missing address for IPv4 assignment");
702         elog<InternalFailure>();
703     }
704     uint8_t fallbackPrefix = AddrFamily<AF_INET>::defaultPrefix;
705     if (ifaddr)
706     {
707         fallbackPrefix = ifaddr->prefix;
708         deleteObjectIfExists(bus, params.service, ifaddr->path);
709     }
710     createIfAddr<AF_INET>(bus, params, address.value_or(ifaddr->address),
711                           prefix.value_or(fallbackPrefix));
712 }
713 
714 /** @brief Gets the vlan ID configured on the interface
715  *
716  *  @param[in] bus    - The bus object used for lookups
717  *  @param[in] params - The parameters for the channel
718  *  @return VLAN id or the standard 0 for no VLAN
719  */
720 uint16_t getVLANProperty(sdbusplus::bus::bus& bus, const ChannelParams& params)
721 {
722     // VLAN devices will always have a separate logical object
723     if (params.ifPath == params.logicalPath)
724     {
725         return 0;
726     }
727 
728     auto vlan = std::get<uint32_t>(getDbusProperty(
729         bus, params.service, params.logicalPath, INTF_VLAN, "Id"));
730     if ((vlan & VLAN_VALUE_MASK) != vlan)
731     {
732         logWithChannel<level::ERR>(params, "networkd returned an invalid vlan",
733                                    entry("VLAN=%" PRIu32, vlan));
734         elog<InternalFailure>();
735     }
736     return vlan;
737 }
738 
739 /** @brief Deletes all of the possible configuration parameters for a channel
740  *
741  *  @param[in] bus    - The bus object used for lookups
742  *  @param[in] params - The parameters for the channel
743  */
744 void deconfigureChannel(sdbusplus::bus::bus& bus, ChannelParams& params)
745 {
746     // Delete all objects associated with the interface
747     auto objreq = bus.new_method_call(MAPPER_BUS_NAME, MAPPER_OBJ, MAPPER_INTF,
748                                       "GetSubTree");
749     objreq.append(PATH_ROOT, 0, std::vector<std::string>{DELETE_INTERFACE});
750     auto objreply = bus.call(objreq);
751     ObjectTree objs;
752     objreply.read(objs);
753     for (const auto& [path, impls] : objs)
754     {
755         if (path.find(params.ifname) == path.npos)
756         {
757             continue;
758         }
759         for (const auto& [service, intfs] : impls)
760         {
761             deleteObjectIfExists(bus, service, path);
762         }
763         // Update params to reflect the deletion of vlan
764         if (path == params.logicalPath)
765         {
766             params.logicalPath = params.ifPath;
767         }
768     }
769 
770     // Clear out any settings on the lower physical interface
771     setDHCPProperty(bus, params, false);
772 }
773 
774 /** @brief Creates a new VLAN on the specified interface
775  *
776  *  @param[in] bus    - The bus object used for lookups
777  *  @param[in] params - The parameters for the channel
778  *  @param[in] vlan   - The id of the new vlan
779  */
780 void createVLAN(sdbusplus::bus::bus& bus, ChannelParams& params, uint16_t vlan)
781 {
782     if (vlan == 0)
783     {
784         return;
785     }
786 
787     auto req = bus.new_method_call(params.service.c_str(), PATH_ROOT,
788                                    INTF_VLAN_CREATE, "VLAN");
789     req.append(params.ifname, static_cast<uint32_t>(vlan));
790     auto reply = bus.call(req);
791     sdbusplus::message::object_path newPath;
792     reply.read(newPath);
793     params.logicalPath = std::move(newPath);
794 }
795 
796 /** @brief Performs the necessary reconfiguration to change the VLAN
797  *
798  *  @param[in] bus    - The bus object used for lookups
799  *  @param[in] params - The parameters for the channel
800  *  @param[in] vlan   - The new vlan id to use
801  */
802 void reconfigureVLAN(sdbusplus::bus::bus& bus, ChannelParams& params,
803                      uint16_t vlan)
804 {
805     // Unfortunatetly we don't have built-in functions to migrate our interface
806     // customizations to new VLAN interfaces, or have some kind of decoupling.
807     // We therefore must retain all of our old information, setup the new VLAN
808     // configuration, then restore the old info.
809 
810     // Save info from the old logical interface
811     ObjectLookupCache ips(bus, params, INTF_IP);
812     auto ifaddr4 = findIfAddr<AF_INET>(bus, params, 0, originsV4, ips);
813     auto dhcp = getDHCPProperty(bus, params);
814 
815     deconfigureChannel(bus, params);
816     createVLAN(bus, params, vlan);
817 
818     // Re-establish the saved settings
819     setDHCPProperty(bus, params, dhcp);
820     if (ifaddr4)
821     {
822         createIfAddr<AF_INET>(bus, params, ifaddr4->address, ifaddr4->prefix);
823     }
824 }
825 
826 /** @brief Turns a prefix into a netmask
827  *
828  *  @param[in] prefix - The prefix length
829  *  @return The netmask
830  */
831 in_addr prefixToNetmask(uint8_t prefix)
832 {
833     if (prefix > 32)
834     {
835         log<level::ERR>("Invalid prefix", entry("PREFIX=%" PRIu8, prefix));
836         elog<InternalFailure>();
837     }
838     if (prefix == 0)
839     {
840         // Avoids 32-bit lshift by 32 UB
841         return {};
842     }
843     return {htobe32(~UINT32_C(0) << (32 - prefix))};
844 }
845 
846 /** @brief Turns a a netmask into a prefix length
847  *
848  *  @param[in] netmask - The netmask in byte form
849  *  @return The prefix length
850  */
851 uint8_t netmaskToPrefix(in_addr netmask)
852 {
853     uint32_t x = be32toh(netmask.s_addr);
854     if ((~x & (~x + 1)) != 0)
855     {
856         char maskStr[INET_ADDRSTRLEN];
857         inet_ntop(AF_INET, &netmask, maskStr, sizeof(maskStr));
858         log<level::ERR>("Invalid netmask", entry("NETMASK=%s", maskStr));
859         elog<InternalFailure>();
860     }
861     return 32 - __builtin_ctz(x);
862 }
863 
864 // We need to store this value so it can be returned to the client
865 // It is volatile so safe to store in daemon memory.
866 static std::unordered_map<uint8_t, SetStatus> setStatus;
867 
868 // Until we have good support for fixed versions of IPMI tool
869 // we need to return the VLAN id for disabled VLANs. The value is only
870 // used for verification that a disable operation succeeded and will only
871 // be sent if our system indicates that vlans are disabled.
872 static std::unordered_map<uint8_t, uint16_t> lastDisabledVlan;
873 
874 /** @brief Gets the set status for the channel if it exists
875  *         Otherise populates and returns the default value.
876  *
877  *  @param[in] channel - The channel id corresponding to an ethernet interface
878  *  @return A reference to the SetStatus for the channel
879  */
880 SetStatus& getSetStatus(uint8_t channel)
881 {
882     auto it = setStatus.find(channel);
883     if (it != setStatus.end())
884     {
885         return it->second;
886     }
887     return setStatus[channel] = SetStatus::Complete;
888 }
889 
890 RspType<> setLan(uint4_t channelBits, uint4_t, uint8_t parameter,
891                  message::Payload& req)
892 {
893     auto channel = static_cast<uint8_t>(channelBits);
894     if (!doesDeviceExist(channel))
895     {
896         req.trailingOk = true;
897         return responseInvalidFieldRequest();
898     }
899 
900     switch (static_cast<LanParam>(parameter))
901     {
902         case LanParam::SetStatus:
903         {
904             uint2_t flag;
905             uint6_t rsvd;
906             if (req.unpack(flag, rsvd) != 0 || !req.fullyUnpacked())
907             {
908                 return responseReqDataLenInvalid();
909             }
910             auto status = static_cast<SetStatus>(static_cast<uint8_t>(flag));
911             switch (status)
912             {
913                 case SetStatus::Complete:
914                 {
915                     getSetStatus(channel) = status;
916                     return responseSuccess();
917                 }
918                 case SetStatus::InProgress:
919                 {
920                     auto& storedStatus = getSetStatus(channel);
921                     if (storedStatus == SetStatus::InProgress)
922                     {
923                         return response(ccParamSetLocked);
924                     }
925                     storedStatus = status;
926                     return responseSuccess();
927                 }
928                 case SetStatus::Commit:
929                     if (getSetStatus(channel) != SetStatus::InProgress)
930                     {
931                         return responseInvalidFieldRequest();
932                     }
933                     return responseSuccess();
934             }
935             return response(ccParamNotSupported);
936         }
937         case LanParam::AuthSupport:
938         {
939             req.trailingOk = true;
940             return response(ccParamReadOnly);
941         }
942         case LanParam::AuthEnables:
943         {
944             req.trailingOk = true;
945             return response(ccParamNotSupported);
946         }
947         case LanParam::IP:
948         {
949             in_addr ip;
950             std::array<uint8_t, sizeof(ip)> bytes;
951             if (req.unpack(bytes) != 0 || !req.fullyUnpacked())
952             {
953                 return responseReqDataLenInvalid();
954             }
955             copyInto(ip, bytes);
956             channelCall<reconfigureIfAddr4>(channel, ip, std::nullopt);
957             return responseSuccess();
958         }
959         case LanParam::IPSrc:
960         {
961             uint4_t flag;
962             uint4_t rsvd;
963             if (req.unpack(flag, rsvd) != 0 || !req.fullyUnpacked())
964             {
965                 return responseReqDataLenInvalid();
966             }
967             switch (static_cast<IPSrc>(static_cast<uint8_t>(flag)))
968             {
969                 case IPSrc::DHCP:
970                 {
971                     channelCall<setDHCPProperty>(channel, true);
972                     return responseSuccess();
973                 }
974                 case IPSrc::Unspecified:
975                 case IPSrc::Static:
976                 case IPSrc::BIOS:
977                 case IPSrc::BMC:
978                 {
979                     channelCall<setDHCPProperty>(channel, false);
980                     return responseSuccess();
981                 }
982             }
983             return response(ccParamNotSupported);
984         }
985         case LanParam::MAC:
986         {
987             ether_addr mac;
988             std::array<uint8_t, sizeof(mac)> bytes;
989             if (req.unpack(bytes) != 0 || !req.fullyUnpacked())
990             {
991                 return responseReqDataLenInvalid();
992             }
993             copyInto(mac, bytes);
994             channelCall<setMACProperty>(channel, mac);
995             return responseSuccess();
996         }
997         case LanParam::SubnetMask:
998         {
999             in_addr netmask;
1000             std::array<uint8_t, sizeof(netmask)> bytes;
1001             if (req.unpack(bytes) != 0 || !req.fullyUnpacked())
1002             {
1003                 return responseReqDataLenInvalid();
1004             }
1005             copyInto(netmask, bytes);
1006             channelCall<reconfigureIfAddr4>(channel, std::nullopt,
1007                                             netmaskToPrefix(netmask));
1008             return responseSuccess();
1009         }
1010         case LanParam::Gateway1:
1011         {
1012             in_addr gateway;
1013             std::array<uint8_t, sizeof(gateway)> bytes;
1014             if (req.unpack(bytes) != 0 || !req.fullyUnpacked())
1015             {
1016                 return responseReqDataLenInvalid();
1017             }
1018             copyInto(gateway, bytes);
1019             channelCall<setGatewayProperty<AF_INET>>(channel, gateway);
1020             return responseSuccess();
1021         }
1022         case LanParam::VLANId:
1023         {
1024             uint16_t vlanData;
1025             if (req.unpack(vlanData) != 0 || !req.fullyUnpacked())
1026             {
1027                 return responseReqDataLenInvalid();
1028             }
1029             if ((vlanData & VLAN_ENABLE_FLAG) == 0)
1030             {
1031                 lastDisabledVlan[channel] = vlanData & VLAN_VALUE_MASK;
1032                 vlanData = 0;
1033             }
1034             channelCall<reconfigureVLAN>(channel, vlanData & VLAN_VALUE_MASK);
1035             return responseSuccess();
1036         }
1037         case LanParam::CiphersuiteSupport:
1038         case LanParam::CiphersuiteEntries:
1039         {
1040             req.trailingOk = true;
1041             return response(ccParamReadOnly);
1042         }
1043     }
1044 
1045     req.trailingOk = true;
1046     return response(ccParamNotSupported);
1047 }
1048 
1049 RspType<message::Payload> getLan(uint4_t channelBits, uint3_t, bool revOnly,
1050                                  uint8_t parameter, uint8_t set, uint8_t block)
1051 {
1052     message::Payload ret;
1053     constexpr uint8_t current_revision = 0x11;
1054     ret.pack(current_revision);
1055 
1056     if (revOnly)
1057     {
1058         return responseSuccess(std::move(ret));
1059     }
1060 
1061     auto channel = static_cast<uint8_t>(channelBits);
1062     if (!doesDeviceExist(channel))
1063     {
1064         return responseInvalidFieldRequest();
1065     }
1066 
1067     switch (static_cast<LanParam>(parameter))
1068     {
1069         case LanParam::SetStatus:
1070         {
1071             SetStatus status;
1072             try
1073             {
1074                 status = setStatus.at(channel);
1075             }
1076             catch (const std::out_of_range&)
1077             {
1078                 status = SetStatus::Complete;
1079             }
1080             ret.pack(static_cast<uint2_t>(status), uint6_t{});
1081             return responseSuccess(std::move(ret));
1082         }
1083         case LanParam::AuthSupport:
1084         {
1085             std::bitset<6> support;
1086             ret.pack(support, uint2_t{});
1087             return responseSuccess(std::move(ret));
1088         }
1089         case LanParam::AuthEnables:
1090         {
1091             std::bitset<6> enables;
1092             ret.pack(enables, uint2_t{}); // Callback
1093             ret.pack(enables, uint2_t{}); // User
1094             ret.pack(enables, uint2_t{}); // Operator
1095             ret.pack(enables, uint2_t{}); // Admin
1096             ret.pack(enables, uint2_t{}); // OEM
1097             return responseSuccess(std::move(ret));
1098         }
1099         case LanParam::IP:
1100         {
1101             auto ifaddr = channelCall<getIfAddr4>(channel);
1102             in_addr addr{};
1103             if (ifaddr)
1104             {
1105                 addr = ifaddr->address;
1106             }
1107             ret.pack(dataRef(addr));
1108             return responseSuccess(std::move(ret));
1109         }
1110         case LanParam::IPSrc:
1111         {
1112             auto src = IPSrc::Static;
1113             if (channelCall<getDHCPProperty>(channel))
1114             {
1115                 src = IPSrc::DHCP;
1116             }
1117             ret.pack(static_cast<uint4_t>(src), uint4_t{});
1118             return responseSuccess(std::move(ret));
1119         }
1120         case LanParam::MAC:
1121         {
1122             ether_addr mac = channelCall<getMACProperty>(channel);
1123             ret.pack(dataRef(mac));
1124             return responseSuccess(std::move(ret));
1125         }
1126         case LanParam::SubnetMask:
1127         {
1128             auto ifaddr = channelCall<getIfAddr4>(channel);
1129             uint8_t prefix = AddrFamily<AF_INET>::defaultPrefix;
1130             if (ifaddr)
1131             {
1132                 prefix = ifaddr->prefix;
1133             }
1134             in_addr netmask = prefixToNetmask(prefix);
1135             ret.pack(dataRef(netmask));
1136             return responseSuccess(std::move(ret));
1137         }
1138         case LanParam::Gateway1:
1139         {
1140             auto gateway =
1141                 channelCall<getGatewayProperty<AF_INET>>(channel).value_or(
1142                     in_addr{});
1143             ret.pack(dataRef(gateway));
1144             return responseSuccess(std::move(ret));
1145         }
1146         case LanParam::VLANId:
1147         {
1148             uint16_t vlan = channelCall<getVLANProperty>(channel);
1149             if (vlan != 0)
1150             {
1151                 vlan |= VLAN_ENABLE_FLAG;
1152             }
1153             else
1154             {
1155                 vlan = lastDisabledVlan[channel];
1156             }
1157             ret.pack(vlan);
1158             return responseSuccess(std::move(ret));
1159         }
1160         case LanParam::CiphersuiteSupport:
1161         case LanParam::CiphersuiteEntries:
1162             return response(ccParamNotSupported);
1163     }
1164 
1165     return response(ccParamNotSupported);
1166 }
1167 
1168 } // namespace transport
1169 } // namespace ipmi
1170 
1171 void register_netfn_transport_functions() __attribute__((constructor));
1172 
1173 void register_netfn_transport_functions()
1174 {
1175     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnTransport,
1176                           ipmi::transport::cmdSetLanConfigParameters,
1177                           ipmi::Privilege::Admin, ipmi::transport::setLan);
1178     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnTransport,
1179                           ipmi::transport::cmdGetLanConfigParameters,
1180                           ipmi::Privilege::Admin, ipmi::transport::getLan);
1181 }
1182