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