1 /*
2 * Copyright (c) 2022, The OpenThread Authors.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * 3. Neither the name of the copyright holder nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 /**
30 * @file
31 * The file implements the Infrastructure Link Selector.
32 */
33
34 #if __linux__
35
36 #define OTBR_LOG_TAG "ILS"
37
38 #include "utils/infra_link_selector.hpp"
39
40 #include <linux/rtnetlink.h>
41 #include <net/if.h>
42 #include <sys/ioctl.h>
43 #include <sys/socket.h>
44 #include <unistd.h>
45
46 #include <openthread/backbone_router_ftd.h>
47
48 #include "utils/socket_utils.hpp"
49
50 namespace otbr {
51 namespace Utils {
52
53 constexpr Milliseconds InfraLinkSelector::kInfraLinkSelectionDelay;
54
Update(LinkState aState)55 bool InfraLinkSelector::LinkInfo::Update(LinkState aState)
56 {
57 bool changed = mState != aState;
58
59 VerifyOrExit(changed);
60
61 if (mState == kUpAndRunning && aState != kUpAndRunning)
62 {
63 mWasUpAndRunning = true;
64 mLastRunningTime = Clock::now();
65 }
66
67 mState = aState;
68 exit:
69 return changed;
70 }
71
InfraLinkSelector(std::vector<const char * > aInfraLinkNames)72 InfraLinkSelector::InfraLinkSelector(std::vector<const char *> aInfraLinkNames)
73 : mInfraLinkNames(std::move(aInfraLinkNames))
74 {
75 if (mInfraLinkNames.size() >= 2)
76 {
77 mNetlinkSocket = CreateNetLinkRouteSocket(RTMGRP_LINK);
78 VerifyOrDie(mNetlinkSocket != -1, "Failed to create netlink socket");
79 }
80
81 for (const char *name : mInfraLinkNames)
82 {
83 mInfraLinkInfos[name].Update(QueryInfraLinkState(name));
84 }
85 }
86
~InfraLinkSelector(void)87 InfraLinkSelector::~InfraLinkSelector(void)
88 {
89 if (mNetlinkSocket != -1)
90 {
91 close(mNetlinkSocket);
92 mNetlinkSocket = -1;
93 }
94 }
95
Select(void)96 const char *InfraLinkSelector::Select(void)
97 {
98 const char *sel;
99
100 #if OTBR_ENABLE_VENDOR_INFRA_LINK_SELECT
101 sel = otbrVendorInfraLinkSelect();
102
103 if (sel == nullptr)
104 {
105 sel = SelectGeneric();
106 }
107 #else
108 sel = SelectGeneric();
109 #endif
110
111 return sel;
112 }
113
SelectGeneric(void)114 const char *InfraLinkSelector::SelectGeneric(void)
115 {
116 const char *prevInfraLink = mCurrentInfraLink;
117 InfraLinkSelector::LinkState currentInfraLinkState = kInvalid;
118 auto now = Clock::now();
119 LinkInfo *currentInfraLinkInfo = nullptr;
120
121 VerifyOrExit(!mInfraLinkNames.empty(), mCurrentInfraLink = kDefaultInfraLinkName);
122 VerifyOrExit(mInfraLinkNames.size() > 1, mCurrentInfraLink = mInfraLinkNames.front());
123 VerifyOrExit(mRequireReselect, assert(mCurrentInfraLink != nullptr));
124
125 otbrLogInfo("Evaluating infra link among %zu netifs:", mInfraLinkNames.size());
126
127 // Prefer `mCurrentInfraLink` if it's up and running.
128 if (mCurrentInfraLink != nullptr)
129 {
130 currentInfraLinkInfo = &mInfraLinkInfos[mCurrentInfraLink];
131
132 otbrLogInfo("\tInfra link %s is in state %s", mCurrentInfraLink,
133 LinkStateToString(currentInfraLinkInfo->mState));
134
135 VerifyOrExit(currentInfraLinkInfo->mState != kUpAndRunning);
136 }
137
138 // Select an infra link with best state.
139 {
140 const char *bestInfraLink = mCurrentInfraLink;
141 InfraLinkSelector::LinkState bestState = currentInfraLinkState;
142
143 for (const char *name : mInfraLinkNames)
144 {
145 if (name != mCurrentInfraLink)
146 {
147 LinkInfo &linkInfo = mInfraLinkInfos[name];
148
149 otbrLogInfo("\tInfra link %s is in state %s", name, LinkStateToString(linkInfo.mState));
150 if (bestInfraLink == nullptr || linkInfo.mState > bestState)
151 {
152 bestInfraLink = name;
153 bestState = linkInfo.mState;
154 }
155 }
156 }
157
158 VerifyOrExit(bestInfraLink != mCurrentInfraLink);
159
160 // Prefer `mCurrentInfraLink` if no other infra link is up and running
161 VerifyOrExit(mCurrentInfraLink == nullptr || bestState == kUpAndRunning);
162
163 // Prefer `mCurrentInfraLink` if it's down for less than `kInfraLinkSelectionDelay`
164 if (mCurrentInfraLink != nullptr && currentInfraLinkInfo->mWasUpAndRunning)
165 {
166 Milliseconds timeSinceLastRunning =
167 std::chrono::duration_cast<Milliseconds>(now - currentInfraLinkInfo->mLastRunningTime);
168
169 if (timeSinceLastRunning < kInfraLinkSelectionDelay)
170 {
171 Milliseconds delay = kInfraLinkSelectionDelay - timeSinceLastRunning;
172
173 otbrLogInfo("Infra link %s was running %lldms ago, wait for %lldms to recheck.", mCurrentInfraLink,
174 timeSinceLastRunning.count(), delay.count());
175 mTaskRunner.Post(delay, [this]() { mRequireReselect = true; });
176 ExitNow();
177 }
178 }
179
180 // Current infra link changed.
181 mCurrentInfraLink = bestInfraLink;
182 }
183
184 exit:
185 if (mRequireReselect)
186 {
187 mRequireReselect = false;
188
189 if (prevInfraLink != mCurrentInfraLink)
190 {
191 if (prevInfraLink == nullptr)
192 {
193 otbrLogNotice("Infra link selected: %s", mCurrentInfraLink);
194 }
195 else
196 {
197 otbrLogWarning("Infra link switched from %s to %s", prevInfraLink, mCurrentInfraLink);
198 }
199 }
200 else
201 {
202 otbrLogInfo("Infra link unchanged: %s", mCurrentInfraLink);
203 }
204 }
205
206 return mCurrentInfraLink;
207 }
208
QueryInfraLinkState(const char * aInfraLinkName)209 InfraLinkSelector::LinkState InfraLinkSelector::QueryInfraLinkState(const char *aInfraLinkName)
210 {
211 int sock = 0;
212 struct ifreq ifReq;
213 InfraLinkSelector::LinkState state = kInvalid;
214
215 sock = SocketWithCloseExec(AF_INET6, SOCK_DGRAM, IPPROTO_IP, kSocketBlock);
216 VerifyOrDie(sock != -1, "Failed to create AF_INET6 socket.");
217
218 memset(&ifReq, 0, sizeof(ifReq));
219 strncpy(ifReq.ifr_name, aInfraLinkName, sizeof(ifReq.ifr_name) - 1);
220
221 VerifyOrExit(ioctl(sock, SIOCGIFFLAGS, &ifReq) != -1);
222
223 state = (ifReq.ifr_flags & IFF_UP) ? ((ifReq.ifr_flags & IFF_RUNNING) ? kUpAndRunning : kUp) : kDown;
224
225 exit:
226 if (sock != 0)
227 {
228 close(sock);
229 }
230
231 return state;
232 }
233
Update(MainloopContext & aMainloop)234 void InfraLinkSelector::Update(MainloopContext &aMainloop)
235 {
236 if (mNetlinkSocket != -1)
237 {
238 aMainloop.AddFdToReadSet(mNetlinkSocket);
239 }
240 }
241
Process(const MainloopContext & aMainloop)242 void InfraLinkSelector::Process(const MainloopContext &aMainloop)
243 {
244 OTBR_UNUSED_VARIABLE(aMainloop);
245
246 if (mNetlinkSocket != -1 && FD_ISSET(mNetlinkSocket, &aMainloop.mReadFdSet))
247 {
248 ReceiveNetLinkMessage();
249 }
250 }
251
ReceiveNetLinkMessage(void)252 void InfraLinkSelector::ReceiveNetLinkMessage(void)
253 {
254 const size_t kMaxNetLinkBufSize = 8192;
255 ssize_t len;
256 union
257 {
258 nlmsghdr mHeader;
259 uint8_t mBuffer[kMaxNetLinkBufSize];
260 } msgBuffer;
261
262 len = recv(mNetlinkSocket, msgBuffer.mBuffer, sizeof(msgBuffer.mBuffer), 0);
263 if (len < 0)
264 {
265 otbrLogWarning("Failed to receive netlink message: %s", strerror(errno));
266 ExitNow();
267 }
268
269 for (struct nlmsghdr *header = &msgBuffer.mHeader; NLMSG_OK(header, static_cast<size_t>(len));
270 header = NLMSG_NEXT(header, len))
271 {
272 struct ifinfomsg *ifinfo;
273
274 switch (header->nlmsg_type)
275 {
276 case RTM_NEWLINK:
277 case RTM_DELLINK:
278 ifinfo = reinterpret_cast<struct ifinfomsg *>(NLMSG_DATA(header));
279 HandleInfraLinkStateChange(ifinfo->ifi_index);
280 break;
281 case NLMSG_ERROR:
282 {
283 struct nlmsgerr *errMsg = reinterpret_cast<struct nlmsgerr *>(NLMSG_DATA(header));
284
285 otbrLogWarning("netlink NLMSG_ERROR response: seq=%u, error=%d", header->nlmsg_seq, errMsg->error);
286 break;
287 }
288 default:
289 break;
290 }
291 }
292
293 exit:
294 return;
295 }
296
HandleInfraLinkStateChange(uint32_t aInfraLinkIndex)297 void InfraLinkSelector::HandleInfraLinkStateChange(uint32_t aInfraLinkIndex)
298 {
299 const char *infraLinkName = nullptr;
300
301 for (const char *name : mInfraLinkNames)
302 {
303 if (aInfraLinkIndex == if_nametoindex(name))
304 {
305 infraLinkName = name;
306 break;
307 }
308 }
309
310 VerifyOrExit(infraLinkName != nullptr);
311
312 {
313 LinkInfo &linkInfo = mInfraLinkInfos[infraLinkName];
314 LinkState prevState = linkInfo.mState;
315
316 if (linkInfo.Update(QueryInfraLinkState(infraLinkName)))
317 {
318 otbrLogInfo("Infra link name %s index %lu state changed: %s -> %s", infraLinkName, aInfraLinkIndex,
319 LinkStateToString(prevState), LinkStateToString(linkInfo.mState));
320 mRequireReselect = true;
321 }
322 }
323 exit:
324 return;
325 }
326
LinkStateToString(LinkState aState)327 const char *InfraLinkSelector::LinkStateToString(LinkState aState)
328 {
329 const char *str = "";
330
331 switch (aState)
332 {
333 case kInvalid:
334 str = "INVALID";
335 break;
336 case kDown:
337 str = "DOWN";
338 break;
339 case kUp:
340 str = "UP";
341 break;
342 case kUpAndRunning:
343 str = "UP+RUNNING";
344 break;
345 }
346 return str;
347 }
348
349 } // namespace Utils
350 } // namespace otbr
351
352 #endif // __linux__
353