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 <linux/if.h>
18 #include <net/if_arp.h>
19 #include <sys/ioctl.h>
20 #include <sys/types.h>
21 #include <sys/wait.h>
22 #include <unistd.h>
23 
24 #include <sdbusplus/bus.hpp>
25 #include <stdplus/fd/create.hpp>
26 #include <stdplus/fd/ops.hpp>
27 #include <stdplus/print.hpp>
28 #include <stdplus/raw.hpp>
29 #include <stdplus/util/string.hpp>
30 
31 #include <cstdio>
32 #include <cstring>
33 #include <filesystem>
34 #include <format>
35 #include <thread>
36 #include <utility>
37 #include <variant>
38 
39 /* Most of the code for interacting with DBus is from
40  * phosphor-host-ipmid/utils.cpp
41  */
42 
43 namespace net
44 {
45 
46 namespace
47 {
48 
49 constexpr auto IFACE_ROOT = "/xyz/openbmc_project/network/";
50 constexpr auto MAC_FORMAT = "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx";
51 // 2 chars for every byte + 5 colons + Null byte
52 constexpr auto MAC_FORMAT_LENGTH = 6 * 2 + 5 + 1;
53 constexpr auto MAC_INTERFACE = "xyz.openbmc_project.Network.MACAddress";
54 constexpr auto NETWORK_SERVICE = "xyz.openbmc_project.Network";
55 constexpr auto PROP_INTERFACE = "org.freedesktop.DBus.Properties";
56 
format_mac(const mac_addr_t & mac)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 
PhosphorConfig(const std::string & iface_name)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 
get_mac_addr(mac_addr_t * mac)75 int PhosphorConfig::get_mac_addr(mac_addr_t* mac)
76 {
77     if (mac == nullptr)
78     {
79         stdplus::println(stderr, "mac is nullptr");
80         return -1;
81     }
82 
83     // Cache hit: we have stored host MAC.
84     if (shared_host_mac_)
85     {
86         *mac = shared_host_mac_.value();
87     }
88     else // Cache miss: read from interface, cache it for future requests.
89     {
90         struct ifreq ifr = {};
91         try
92         {
93             auto fd = stdplus::fd::socket(stdplus::fd::SocketDomain::INet6,
94                                           stdplus::fd::SocketType::Datagram,
95                                           stdplus::fd::SocketProto::IP);
96             call_nic(fd, ifr, SIOCGIFHWADDR);
97         }
98         catch (const std::exception& ex)
99         {
100             stdplus::println(
101                 stderr,
102                 "Failed to get MAC Addr for Interface {} writing file: {}",
103                 iface_name_, ex.what());
104             return -1;
105         }
106         std::copy_n(ifr.ifr_addr.sa_data, sizeof(*mac), mac->octet);
107         shared_host_mac_ = *mac;
108     }
109     return 0;
110 }
111 
call_nic(auto fd,struct ifreq & ifr,int op)112 void PhosphorConfig::call_nic(auto fd, struct ifreq& ifr, int op)
113 {
114     std::copy_n(iface_name_.c_str(), iface_name_.size() + 1, ifr.ifr_name);
115     fd.ioctl(op, &ifr);
116 }
117 
set_mac_addr(const mac_addr_t & mac)118 int PhosphorConfig::set_mac_addr(const mac_addr_t& mac)
119 {
120     std::string mac_value = format_mac(mac);
121     struct ifreq ifr = {};
122     short flags_copy;
123     mac_addr_t cur_mac;
124     int ret;
125     ret = get_mac_addr(&cur_mac);
126     if (ret == 0)
127     {
128         if (stdplus::raw::equal(cur_mac, mac))
129         {
130             // mac value is the same not doing anything, returning
131             return 0;
132         }
133     }
134 
135     try
136     {
137         auto netdir = std::format("/run/systemd/network/00-bmc-{}.network.d",
138                                   iface_name_);
139         std::filesystem::create_directories(netdir);
140         auto netfile = std::format("{}/60-ncsi-mac.conf", netdir);
141         auto fd = stdplus::fd::open(
142             netfile,
143             stdplus::fd::OpenFlags(stdplus::fd::OpenAccess::WriteOnly)
144                 .set(stdplus::fd::OpenFlag::Create),
145             0644);
146         auto contents = std::format("[Link]\nMACAddress={}\n", mac_value);
147         stdplus::fd::writeExact(fd, contents);
148     }
149     catch (const std::exception& ex)
150     {
151         stdplus::println(stderr, "Failed to set MAC Addr `{}` writing file: {}",
152                          mac_value, ex.what());
153         return -1;
154     }
155     try
156     {
157         auto fd = stdplus::fd::socket(stdplus::fd::SocketDomain::INet6,
158                                       stdplus::fd::SocketType::Datagram,
159                                       stdplus::fd::SocketProto::IP);
160         // Try setting MAC Address directly without bringing interface down
161         try
162         {
163             std::copy_n(mac.octet, 6, ifr.ifr_hwaddr.sa_data);
164             call_nic(fd, ifr, SIOCSIFHWADDR);
165         }
166         catch (const std::exception& e)
167         {
168             // Regardless of error attempt to set MAC Address again after
169             // bringing interface down
170             stdplus::println(
171                 stderr,
172                 "Could not set MAC Address directly, retrying after bringing interface down, error = {}",
173                 e.what());
174             try
175             {
176                 // Read interface flags configuration and store (once interface
177                 // is brought down, existing state is lost)
178                 call_nic(fd, ifr, SIOCGIFFLAGS);
179                 flags_copy = ifr.ifr_flags;
180                 // set interface down
181                 ifr.ifr_flags &= ~IFF_UP;
182                 call_nic(fd, ifr, SIOCSIFFLAGS);
183                 // Wait for 1 milliseconds - sometimes interface is still
184                 // going down
185                 std::this_thread::sleep_for(std::chrono::milliseconds(1));
186                 // set MAC Address
187                 ifr.ifr_hwaddr.sa_family = ARPHRD_ETHER;
188                 std::copy_n(mac.octet, 6, ifr.ifr_hwaddr.sa_data);
189                 call_nic(fd, ifr, SIOCSIFHWADDR);
190                 // set interface up with the flags state prior to bringing
191                 // it down
192                 ifr.ifr_flags = flags_copy | IFF_UP;
193                 call_nic(fd, ifr, SIOCSIFFLAGS);
194             }
195             catch (const std::exception& e)
196             {
197                 stdplus::println(
198                     stderr, "Failed to set MAC Address {} writing file: {}",
199                     mac_value, e.what());
200                 return -1;
201             }
202         }
203     }
204     catch (const std::exception& e)
205     {
206         stdplus::println(stderr, "Error creating socket: {}", e.what());
207         return -1;
208     }
209     stdplus::println(stderr, "Success setting Mac address for {}: {}",
210                      iface_name_, mac_value);
211     shared_host_mac_ = std::experimental::nullopt;
212     return 0;
213 }
214 
set_nic_hostless(bool is_nic_hostless)215 int PhosphorConfig::set_nic_hostless(bool is_nic_hostless)
216 {
217     // Ensure that we don't trigger the target multiple times. This is
218     // undesirable because it will cause any inactive services to re-trigger
219     // every time we run this code. Since the loop calling this executes this
220     // code every 1s, we don't want to keep re-executing services. A fresh
221     // start of the daemon will always trigger the service to ensure system
222     // consistency.
223     if (was_nic_hostless_ && is_nic_hostless == *was_nic_hostless_)
224     {
225         return 0;
226     }
227 
228     static constexpr auto systemdService = "org.freedesktop.systemd1";
229     static constexpr auto systemdRoot = "/org/freedesktop/systemd1";
230     static constexpr auto systemdInterface = "org.freedesktop.systemd1.Manager";
231 
232     auto method = bus.new_method_call(systemdService, systemdRoot,
233                                       systemdInterface, "StartUnit");
234     if (is_nic_hostless)
235     {
236         method.append(
237             stdplus::util::strCat("nic-hostless@", iface_name_, ".target"));
238     }
239     else
240     {
241         method.append(
242             stdplus::util::strCat("nic-hostful@", iface_name_, ".target"));
243     }
244 
245     // Specify --job-mode (see systemctl(1) for detail).
246     method.append("replace");
247 
248     try
249     {
250         bus.call_noreply(method);
251         was_nic_hostless_ = is_nic_hostless;
252         return 0;
253     }
254     catch (const sdbusplus::exception::SdBusError& ex)
255     {
256         stdplus::println(stderr, "Failed to set systemd nic status: {}",
257                          ex.what());
258         return 1;
259     }
260 }
261 
262 } // namespace net
263