1 #pragma once
2 
3 #include "app/channel.hpp"
4 #include "transportconstants.hpp"
5 #include "user_channel/cipher_mgmt.hpp"
6 
7 #include <ipmid/api-types.hpp>
8 #include <ipmid/api.hpp>
9 #include <ipmid/message.hpp>
10 #include <ipmid/message/types.hpp>
11 #include <ipmid/types.hpp>
12 #include <ipmid/utils.hpp>
13 #include <phosphor-logging/elog-errors.hpp>
14 #include <phosphor-logging/elog.hpp>
15 #include <phosphor-logging/log.hpp>
16 #include <sdbusplus/bus.hpp>
17 #include <sdbusplus/exception.hpp>
18 #include <stdplus/net/addr/ether.hpp>
19 #include <stdplus/net/addr/ip.hpp>
20 #include <stdplus/str/conv.hpp>
21 #include <stdplus/zstring_view.hpp>
22 #include <user_channel/channel_layer.hpp>
23 #include <xyz/openbmc_project/Common/error.hpp>
24 #include <xyz/openbmc_project/Network/EthernetInterface/server.hpp>
25 #include <xyz/openbmc_project/Network/IP/server.hpp>
26 #include <xyz/openbmc_project/Network/Neighbor/server.hpp>
27 
28 #include <cinttypes>
29 #include <functional>
30 #include <optional>
31 #include <string>
32 #include <string_view>
33 #include <unordered_map>
34 #include <unordered_set>
35 #include <utility>
36 
37 namespace ipmi
38 {
39 namespace transport
40 {
41 
42 /** @brief The dbus parameters for the interface corresponding to a channel
43  *         This helps reduce the number of mapper lookups we need for each
44  *         query and simplifies finding the VLAN interface if needed.
45  */
46 struct ChannelParams
47 {
48     /** @brief The channel ID */
49     int id;
50     /** @brief channel name for the interface */
51     std::string ifname;
52     /** @brief Name of the service on the bus */
53     std::string service;
54     /** @brief Lower level adapter path that is guaranteed to not be a VLAN */
55     std::string ifPath;
56     /** @brief Logical adapter path used for address assignment */
57     std::string logicalPath;
58 };
59 
60 /** @brief Determines the ethernet interface name corresponding to a channel
61  *         Tries to map a VLAN object first so that the address information
62  *         is accurate. Otherwise it gets the standard ethernet interface.
63  *
64  *  @param[in] bus     - The bus object used for lookups
65  *  @param[in] channel - The channel id corresponding to an ethernet interface
66  *  @return Ethernet interface service and object path if it exists
67  */
68 std::optional<ChannelParams> maybeGetChannelParams(sdbusplus::bus_t& bus,
69                                                    uint8_t channel);
70 
71 /** @brief A trivial helper around maybeGetChannelParams() that throws an
72  *         exception when it is unable to acquire parameters for the channel.
73  *
74  *  @param[in] bus     - The bus object used for lookups
75  *  @param[in] channel - The channel id corresponding to an ethernet interface
76  *  @return Ethernet interface service and object path
77  */
78 ChannelParams getChannelParams(sdbusplus::bus_t& bus, uint8_t channel);
79 
80 /** @brief Trivializes using parameter getter functions by providing a bus
81  *         and channel parameters automatically.
82  *
83  *  @param[in] channel - The channel id corresponding to an ethernet interface
84  *  ...
85  */
86 template <auto func, typename... Args>
87 auto channelCall(uint8_t channel, Args&&... args)
88 {
89     sdbusplus::bus_t bus(ipmid_get_sd_bus_connection());
90     auto params = getChannelParams(bus, channel);
91     return std::invoke(func, bus, params, std::forward<Args>(args)...);
92 }
93 
94 /** @brief Generic paramters for different address families */
95 template <int family>
96 struct AddrFamily
97 {};
98 
99 /** @brief Parameter specialization for IPv4 */
100 template <>
101 struct AddrFamily<AF_INET>
102 {
103     using addr = stdplus::In4Addr;
104     static constexpr auto protocol =
105         sdbusplus::server::xyz::openbmc_project::network::IP::Protocol::IPv4;
106     static constexpr size_t maxStrLen = INET6_ADDRSTRLEN;
107     static constexpr uint8_t defaultPrefix = 32;
108     static constexpr char propertyGateway[] = "DefaultGateway";
109 };
110 
111 /** @brief Parameter specialization for IPv6 */
112 template <>
113 struct AddrFamily<AF_INET6>
114 {
115     using addr = stdplus::In6Addr;
116     static constexpr auto protocol =
117         sdbusplus::server::xyz::openbmc_project::network::IP::Protocol::IPv6;
118     static constexpr size_t maxStrLen = INET6_ADDRSTRLEN;
119     static constexpr uint8_t defaultPrefix = 128;
120     static constexpr char propertyGateway[] = "DefaultGateway6";
121 };
122 
123 /** @brief Interface Neighbor configuration parameters */
124 template <int family>
125 struct IfNeigh
126 {
127     std::string path;
128     typename AddrFamily<family>::addr ip;
129     stdplus::EtherAddr mac;
130 };
131 
132 /** @brief Interface IP Address configuration parameters */
133 template <int family>
134 struct IfAddr
135 {
136     std::string path;
137     typename AddrFamily<family>::addr address;
138     sdbusplus::server::xyz::openbmc_project::network::IP::AddressOrigin origin;
139     uint8_t prefix;
140 };
141 
142 /** @brief Valid address origins for IPv6 */
143 static inline const std::unordered_set<
144     sdbusplus::server::xyz::openbmc_project::network::IP::AddressOrigin>
145     originsV6Static = {sdbusplus::server::xyz::openbmc_project::network::IP::
146                            AddressOrigin::Static};
147 static inline const std::unordered_set<
148     sdbusplus::server::xyz::openbmc_project::network::IP::AddressOrigin>
149     originsV6Dynamic = {
150         sdbusplus::server::xyz::openbmc_project::network::IP::AddressOrigin::
151             DHCP,
152         sdbusplus::server::xyz::openbmc_project::network::IP::AddressOrigin::
153             SLAAC,
154 };
155 
156 /** @brief A lazy lookup mechanism for iterating over object properties stored
157  *         in DBus. This will only perform the object lookup when needed, and
158  *         retains a cache of previous lookups to speed up future iterations.
159  */
160 class ObjectLookupCache
161 {
162   public:
163     using PropertiesCache = std::unordered_map<std::string, PropertyMap>;
164 
165     /** @brief Creates a new ObjectLookupCache for the interface on the bus
166      *         NOTE: The inputs to this object must outlive the object since
167      *         they are only referenced by it.
168      *
169      *  @param[in] bus    - The bus object used for lookups
170      *  @param[in] params - The parameters for the channel
171      *  @param[in] intf   - The interface we are looking up
172      */
173     ObjectLookupCache(sdbusplus::bus_t& bus, const ChannelParams& params,
174                       const char* intf) :
175         bus(bus),
176         params(params), intf(intf),
177         objs(getAllDbusObjects(bus, params.logicalPath, intf, ""))
178     {}
179 
180     class iterator : public ObjectTree::const_iterator
181     {
182       public:
183         using value_type = PropertiesCache::value_type;
184 
185         iterator(ObjectTree::const_iterator it, ObjectLookupCache& container) :
186             ObjectTree::const_iterator(it), container(container),
187             ret(container.cache.end())
188         {}
189         value_type& operator*()
190         {
191             ret = container.get(ObjectTree::const_iterator::operator*().first);
192             return *ret;
193         }
194         value_type* operator->()
195         {
196             return &operator*();
197         }
198 
199       private:
200         ObjectLookupCache& container;
201         PropertiesCache::iterator ret;
202     };
203 
204     iterator begin() noexcept
205     {
206         return iterator(objs.begin(), *this);
207     }
208 
209     iterator end() noexcept
210     {
211         return iterator(objs.end(), *this);
212     }
213 
214   private:
215     sdbusplus::bus_t& bus;
216     const ChannelParams& params;
217     const char* const intf;
218     const ObjectTree objs;
219     PropertiesCache cache;
220 
221     /** @brief Gets a cached copy of the object properties if possible
222      *         Otherwise performs a query on DBus to look them up
223      *
224      *  @param[in] path - The object path to lookup
225      *  @return An iterator for the specified object path + properties
226      */
227     PropertiesCache::iterator get(const std::string& path)
228     {
229         auto it = cache.find(path);
230         if (it != cache.end())
231         {
232             return it;
233         }
234         auto properties = getAllDbusProperties(bus, params.service, path, intf);
235         return cache.insert({path, std::move(properties)}).first;
236     }
237 };
238 
239 /** @brief Searches the ip object lookup cache for an address matching
240  *         the input parameters. NOTE: The index lacks stability across address
241  *         changes since the network daemon has no notion of stable indicies.
242  *
243  *  @param[in] bus     - The bus object used for lookups
244  *  @param[in] params  - The parameters for the channel
245  *  @param[in] idx     - The index of the desired address on the interface
246  *  @param[in] origins - The allowed origins for the address objects
247  *  @param[in] ips     - The object lookup cache holding all of the address info
248  *  @return The address and prefix if it was found
249  */
250 template <int family>
251 std::optional<IfAddr<family>> findIfAddr(
252     [[maybe_unused]] sdbusplus::bus_t& bus,
253     [[maybe_unused]] const ChannelParams& params, uint8_t idx,
254     const std::unordered_set<
255         sdbusplus::server::xyz::openbmc_project::network::IP::AddressOrigin>&
256         origins,
257     ObjectLookupCache& ips)
258 {
259     for (const auto& [path, properties] : ips)
260     {
261         std::optional<typename AddrFamily<family>::addr> addr;
262         try
263         {
264             addr.emplace(stdplus::fromStr<typename AddrFamily<family>::addr>(
265                 std::get<std::string>(properties.at("Address"))));
266         }
267         catch (...)
268         {
269             continue;
270         }
271 
272         sdbusplus::server::xyz::openbmc_project::network::IP::AddressOrigin
273             origin = sdbusplus::server::xyz::openbmc_project::network::IP::
274                 convertAddressOriginFromString(
275                     std::get<std::string>(properties.at("Origin")));
276         if (origins.find(origin) == origins.end())
277         {
278             continue;
279         }
280 
281         if (idx > 0)
282         {
283             idx--;
284             continue;
285         }
286 
287         IfAddr<family> ifaddr;
288         ifaddr.path = path;
289         ifaddr.address = *addr;
290         ifaddr.prefix = std::get<uint8_t>(properties.at("PrefixLength"));
291         ifaddr.origin = origin;
292         return ifaddr;
293     }
294 
295     return std::nullopt;
296 }
297 /** @brief Trivial helper around findIfAddr that simplifies calls
298  *         for one off lookups. Don't use this if you intend to do multiple
299  *         lookups at a time.
300  *
301  *  @param[in] bus     - The bus object used for lookups
302  *  @param[in] params  - The parameters for the channel
303  *  @param[in] idx     - The index of the desired address on the interface
304  *  @param[in] origins - The allowed origins for the address objects
305  *  @return The address and prefix if it was found
306  */
307 template <int family>
308 auto getIfAddr(
309     sdbusplus::bus_t& bus, const ChannelParams& params, uint8_t idx,
310     const std::unordered_set<
311         sdbusplus::server::xyz::openbmc_project::network::IP::AddressOrigin>&
312         origins)
313 {
314     ObjectLookupCache ips(bus, params, INTF_IP);
315     return findIfAddr<family>(bus, params, idx, origins, ips);
316 }
317 
318 /** @brief Reconfigures the IPv6 address info configured for the interface
319  *
320  *  @param[in] bus     - The bus object used for lookups
321  *  @param[in] params  - The parameters for the channel
322  *  @param[in] idx     - The address index to operate on
323  *  @param[in] address - The new address
324  *  @param[in] prefix  - The new address prefix
325  */
326 void reconfigureIfAddr6(sdbusplus::bus_t& bus, const ChannelParams& params,
327                         uint8_t idx, stdplus::In6Addr address, uint8_t prefix);
328 
329 /** @brief Retrieves the current gateway for the address family on the system
330  *         NOTE: The gateway is per channel instead of the system wide one.
331  *
332  *  @param[in] bus    - The bus object used for lookups
333  *  @param[in] params - The parameters for the channel
334  *  @return An address representing the gateway address if it exists
335  */
336 template <int family>
337 std::optional<typename AddrFamily<family>::addr>
338     getGatewayProperty(sdbusplus::bus_t& bus, const ChannelParams& params)
339 {
340     auto objPath = "/xyz/openbmc_project/network/" + params.ifname;
341     auto gatewayStr = std::get<std::string>(
342         getDbusProperty(bus, params.service, objPath, INTF_ETHERNET,
343                         AddrFamily<family>::propertyGateway));
344     if (gatewayStr.empty())
345     {
346         return std::nullopt;
347     }
348     return stdplus::fromStr<typename AddrFamily<family>::addr>(gatewayStr);
349 }
350 
351 template <int family>
352 std::optional<IfNeigh<family>>
353     findStaticNeighbor(sdbusplus::bus_t&, const ChannelParams&,
354                        typename AddrFamily<family>::addr ip,
355                        ObjectLookupCache& neighbors)
356 {
357     using sdbusplus::server::xyz::openbmc_project::network::Neighbor;
358     const auto state =
359         sdbusplus::common::xyz::openbmc_project::network::convertForMessage(
360             Neighbor::State::Permanent);
361     for (const auto& [path, neighbor] : neighbors)
362     {
363         std::optional<typename AddrFamily<family>::addr> neighIP;
364         try
365         {
366             neighIP.emplace(stdplus::fromStr<typename AddrFamily<family>::addr>(
367                 std::get<std::string>(neighbor.at("IPAddress"))));
368         }
369         catch (...)
370         {
371             continue;
372         }
373         if (*neighIP != ip)
374         {
375             continue;
376         }
377         if (state != std::get<std::string>(neighbor.at("State")))
378         {
379             continue;
380         }
381 
382         IfNeigh<family> ret;
383         ret.path = path;
384         ret.ip = ip;
385         ret.mac = stdplus::fromStr<stdplus::EtherAddr>(
386             std::get<std::string>(neighbor.at("MACAddress")));
387         return ret;
388     }
389 
390     return std::nullopt;
391 }
392 
393 template <int family>
394 void createNeighbor(sdbusplus::bus_t& bus, const ChannelParams& params,
395                     typename AddrFamily<family>::addr address,
396                     stdplus::EtherAddr mac)
397 {
398     auto newreq = bus.new_method_call(params.service.c_str(),
399                                       params.logicalPath.c_str(),
400                                       INTF_NEIGHBOR_CREATE_STATIC, "Neighbor");
401     stdplus::ToStrHandle<stdplus::ToStr<stdplus::EtherAddr>> macToStr;
402     stdplus::ToStrHandle<stdplus::ToStr<typename AddrFamily<family>::addr>>
403         addrToStr;
404     newreq.append(addrToStr(address), macToStr(mac));
405     bus.call_noreply(newreq);
406 }
407 
408 /** @brief Deletes the dbus object. Ignores empty objects or objects that are
409  *         missing from the bus.
410  *
411  *  @param[in] bus     - The bus object used for lookups
412  *  @param[in] service - The name of the service
413  *  @param[in] path    - The path of the object to delete
414  */
415 void deleteObjectIfExists(sdbusplus::bus_t& bus, const std::string& service,
416                           const std::string& path);
417 
418 /** @brief Sets the value for the default gateway of the channel
419  *
420  *  @param[in] bus     - The bus object used for lookups
421  *  @param[in] params  - The parameters for the channel
422  *  @param[in] gateway - Gateway address to apply
423  */
424 template <int family>
425 void setGatewayProperty(sdbusplus::bus_t& bus, const ChannelParams& params,
426                         typename AddrFamily<family>::addr address)
427 {
428     // Save the old gateway MAC address if it exists so we can recreate it
429     auto gateway = getGatewayProperty<family>(bus, params);
430     std::optional<IfNeigh<family>> neighbor;
431     if (gateway)
432     {
433         ObjectLookupCache neighbors(bus, params, INTF_NEIGHBOR);
434         neighbor = findStaticNeighbor<family>(bus, params, *gateway, neighbors);
435     }
436 
437     auto objPath = "/xyz/openbmc_project/network/" + params.ifname;
438     setDbusProperty(bus, params.service, objPath, INTF_ETHERNET,
439                     AddrFamily<family>::propertyGateway,
440                     stdplus::toStr(address));
441 
442     // Restore the gateway MAC if we had one
443     if (neighbor)
444     {
445         deleteObjectIfExists(bus, params.service, neighbor->path);
446         createNeighbor<family>(bus, params, address, neighbor->mac);
447     }
448 }
449 
450 } // namespace transport
451 } // namespace ipmi
452