1 #include "net_config.h"
2 
3 #include <fmt/format.h>
4 #include <sys/types.h>
5 #include <sys/wait.h>
6 #include <unistd.h>
7 
8 #include <sdbusplus/bus.hpp>
9 #include <stdplus/util/string.hpp>
10 
11 #include <cstdio>
12 #include <cstring>
13 #include <utility>
14 #include <variant>
15 
16 /* Most of the code for interacting with DBus is from
17  * phosphor-host-ipmid/utils.cpp
18  */
19 
20 namespace net
21 {
22 
23 namespace
24 {
25 
26 constexpr auto IFACE_ROOT = "/xyz/openbmc_project/network/";
27 constexpr auto MAC_FORMAT = "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx";
28 // 2 chars for every byte + 5 colons + Null byte
29 constexpr auto MAC_FORMAT_LENGTH = 6 * 2 + 5 + 1;
30 constexpr auto MAC_INTERFACE = "xyz.openbmc_project.Network.MACAddress";
31 constexpr auto NETWORK_SERVICE = "xyz.openbmc_project.Network";
32 constexpr auto PROP_INTERFACE = "org.freedesktop.DBus.Properties";
33 
34 int parse_mac(const std::string& mac_addr, mac_addr_t* mac)
35 {
36     int ret =
37         sscanf(mac_addr.c_str(), MAC_FORMAT, mac->octet, mac->octet + 1,
38                mac->octet + 2, mac->octet + 3, mac->octet + 4, mac->octet + 5);
39 
40     return ret < 6 ? -1 : 0;
41 }
42 
43 std::string format_mac(const mac_addr_t& mac)
44 {
45     // 2 chars for every byte + 5 colons + Null byte
46     char mac_str[MAC_FORMAT_LENGTH];
47     snprintf(mac_str, sizeof(mac_str), MAC_FORMAT, mac.octet[0], mac.octet[1],
48              mac.octet[2], mac.octet[3], mac.octet[4], mac.octet[5]);
49 
50     return std::string{mac_str};
51 }
52 
53 } // namespace
54 
55 PhosphorConfig::PhosphorConfig(const std::string& iface_name) :
56     iface_name_{iface_name}, iface_path_{std::string(IFACE_ROOT) + iface_name},
57     shared_host_mac_(std::experimental::nullopt),
58     bus(sdbusplus::bus::new_default())
59 {}
60 
61 sdbusplus::message::message
62     PhosphorConfig::new_networkd_call(sdbusplus::bus::bus* dbus, bool get) const
63 {
64     auto networkd_call =
65         dbus->new_method_call(NETWORK_SERVICE, iface_path_.c_str(),
66                               PROP_INTERFACE, get ? "Get" : "Set");
67 
68     networkd_call.append(MAC_INTERFACE, "MACAddress");
69 
70     return networkd_call;
71 }
72 
73 int PhosphorConfig::get_mac_addr(mac_addr_t* mac)
74 {
75     if (mac == nullptr)
76     {
77         fmt::print(stderr, "mac is nullptr\n");
78         return -1;
79     }
80 
81     // Cache hit: we have stored host MAC.
82     if (shared_host_mac_)
83     {
84         *mac = shared_host_mac_.value();
85     }
86     else // Cache miss: read MAC over DBus, and store in cache.
87     {
88         std::string mac_string;
89         try
90         {
91             auto networkd_call = new_networkd_call(&bus, true);
92             auto reply = bus.call(networkd_call);
93             std::variant<std::string> result;
94             reply.read(result);
95             mac_string = std::get<std::string>(result);
96         }
97         catch (const sdbusplus::exception::SdBusError& ex)
98         {
99             fmt::print(stderr, "Failed to get MACAddress: {}\n", ex.what());
100             return -1;
101         }
102 
103         if (parse_mac(mac_string, mac) < 0)
104         {
105             fmt::print(stderr, "Failed to parse MAC Address `{}`\n",
106                        mac_string);
107             return -1;
108         }
109 
110         shared_host_mac_ = *mac;
111     }
112 
113     return 0;
114 }
115 
116 int PhosphorConfig::set_mac_addr(const mac_addr_t& mac)
117 {
118     auto networkd_call = new_networkd_call(&bus, false);
119     std::variant<std::string> mac_value(format_mac(mac));
120     networkd_call.append(mac_value);
121 
122     try
123     {
124         auto reply = bus.call(networkd_call);
125     }
126     catch (const sdbusplus::exception::SdBusError& ex)
127     {
128         fmt::print(stderr, "Failed to set MAC Addr `{}`: {}\n",
129                    std::get<std::string>(mac_value), ex.what());
130         return -1;
131     }
132 
133     shared_host_mac_ = std::experimental::nullopt;
134     return 0;
135 }
136 
137 int PhosphorConfig::set_nic_hostless(bool is_nic_hostless)
138 {
139     // Ensure that we don't trigger the target multiple times. This is
140     // undesirable because it will cause any inactive services to re-trigger
141     // every time we run this code. Since the loop calling this executes this
142     // code every 1s, we don't want to keep re-executing services. A fresh
143     // start of the daemon will always trigger the service to ensure system
144     // consistency.
145     if (was_nic_hostless_ && is_nic_hostless == *was_nic_hostless_)
146     {
147         return 0;
148     }
149 
150     static constexpr auto systemdService = "org.freedesktop.systemd1";
151     static constexpr auto systemdRoot = "/org/freedesktop/systemd1";
152     static constexpr auto systemdInterface = "org.freedesktop.systemd1.Manager";
153 
154     auto method = bus.new_method_call(systemdService, systemdRoot,
155                                       systemdInterface, "StartUnit");
156     if (is_nic_hostless)
157     {
158         method.append(
159             stdplus::util::strCat("nic-hostless@", iface_name_, ".target"));
160     }
161     else
162     {
163         method.append(
164             stdplus::util::strCat("nic-hostful@", iface_name_, ".target"));
165     }
166 
167     // Specify --job-mode (see systemctl(1) for detail).
168     method.append("replace");
169 
170     try
171     {
172         bus.call_noreply(method);
173         was_nic_hostless_ = is_nic_hostless;
174         return 0;
175     }
176     catch (const sdbusplus::exception::SdBusError& ex)
177     {
178         fmt::print(stderr, "Failed to set systemd nic status: {}\n", ex.what());
179         return 1;
180     }
181 }
182 
183 } // namespace net
184