xref: /aosp_15_r20/external/ot-br-posix/src/utils/infra_link_selector.cpp (revision 4a64e381480ef79f0532b2421e44e6ee336b8e0d)
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