/src/connectedhomeip/src/platform/Linux/PlatformManagerImpl.cpp
Line | Count | Source |
1 | | /* |
2 | | * |
3 | | * Copyright (c) 2020 Project CHIP Authors |
4 | | * Copyright (c) 2018 Nest Labs, Inc. |
5 | | * |
6 | | * Licensed under the Apache License, Version 2.0 (the "License"); |
7 | | * you may not use this file except in compliance with the License. |
8 | | * You may obtain a copy of the License at |
9 | | * |
10 | | * http://www.apache.org/licenses/LICENSE-2.0 |
11 | | * |
12 | | * Unless required by applicable law or agreed to in writing, software |
13 | | * distributed under the License is distributed on an "AS IS" BASIS, |
14 | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
15 | | * See the License for the specific language governing permissions and |
16 | | * limitations under the License. |
17 | | */ |
18 | | |
19 | | /** |
20 | | * @file |
21 | | * Provides an implementation of the PlatformManager object |
22 | | * for Linux platforms. |
23 | | */ |
24 | | |
25 | | #include <platform/internal/CHIPDeviceLayerInternal.h> |
26 | | |
27 | | #include <arpa/inet.h> |
28 | | #include <dirent.h> |
29 | | #include <errno.h> |
30 | | #include <linux/netlink.h> |
31 | | #include <linux/rtnetlink.h> |
32 | | #include <net/if.h> |
33 | | #include <netinet/in.h> |
34 | | #include <unistd.h> |
35 | | |
36 | | #include <mutex> |
37 | | |
38 | | #include <app-common/zap-generated/ids/Events.h> |
39 | | #include <lib/support/CHIPMem.h> |
40 | | #include <lib/support/logging/CHIPLogging.h> |
41 | | #include <platform/DeviceControlServer.h> |
42 | | #include <platform/DeviceInstanceInfoProvider.h> |
43 | | #include <platform/Linux/DeviceInstanceInfoProviderImpl.h> |
44 | | #include <platform/Linux/DiagnosticDataProviderImpl.h> |
45 | | #include <platform/PlatformManager.h> |
46 | | #include <platform/internal/GenericPlatformManagerImpl_POSIX.ipp> |
47 | | |
48 | | using namespace ::chip::app::Clusters; |
49 | | |
50 | | namespace chip { |
51 | | namespace DeviceLayer { |
52 | | |
53 | | PlatformManagerImpl PlatformManagerImpl::sInstance; |
54 | | |
55 | | namespace { |
56 | | |
57 | | #if CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP |
58 | | void * GLibMainLoopThread(void * userData) |
59 | 0 | { |
60 | 0 | GMainLoop * loop = static_cast<GMainLoop *>(userData); |
61 | 0 | GMainContext * context = g_main_loop_get_context(loop); |
62 | |
|
63 | 0 | g_main_context_push_thread_default(context); |
64 | 0 | g_main_loop_run(loop); |
65 | |
|
66 | 0 | return nullptr; |
67 | 0 | } |
68 | | #endif |
69 | | |
70 | | #if CHIP_DEVICE_CONFIG_ENABLE_WIFI |
71 | | |
72 | | gboolean WiFiIPChangeListener(GIOChannel * ch, GIOCondition /* condition */, void * /* userData */) |
73 | 0 | { |
74 | |
|
75 | 0 | char buffer[4096]; |
76 | 0 | auto * header = reinterpret_cast<struct nlmsghdr *>(buffer); |
77 | 0 | ssize_t len; |
78 | |
|
79 | 0 | if ((len = recv(g_io_channel_unix_get_fd(ch), buffer, sizeof(buffer), 0)) == -1) |
80 | 0 | { |
81 | 0 | if (errno == EINTR || errno == EAGAIN) |
82 | 0 | return G_SOURCE_CONTINUE; |
83 | 0 | ChipLogError(DeviceLayer, "Error reading from netlink socket: %d", errno); |
84 | 0 | return G_SOURCE_CONTINUE; |
85 | 0 | } |
86 | | |
87 | 0 | if (len > 0) |
88 | 0 | { |
89 | 0 | for (struct nlmsghdr * messageHeader = header; |
90 | 0 | (NLMSG_OK(messageHeader, static_cast<uint32_t>(len))) && (messageHeader->nlmsg_type != NLMSG_DONE); |
91 | 0 | messageHeader = NLMSG_NEXT(messageHeader, len)) |
92 | 0 | { |
93 | 0 | if (header->nlmsg_type == RTM_NEWADDR) |
94 | 0 | { |
95 | 0 | struct ifaddrmsg * addressMessage = (struct ifaddrmsg *) NLMSG_DATA(header); |
96 | 0 | struct rtattr * routeInfo = IFA_RTA(addressMessage); |
97 | 0 | size_t rtl = IFA_PAYLOAD(header); |
98 | |
|
99 | 0 | for (; rtl && RTA_OK(routeInfo, rtl); routeInfo = RTA_NEXT(routeInfo, rtl)) |
100 | 0 | { |
101 | 0 | if (routeInfo->rta_type == IFA_LOCAL) |
102 | 0 | { |
103 | 0 | char name[Inet::InterfaceId::kMaxIfNameLength]; |
104 | 0 | if (if_indextoname(addressMessage->ifa_index, name) == nullptr) |
105 | 0 | { |
106 | 0 | ChipLogError(DeviceLayer, "Error %d when getting the interface name at index: %d", errno, |
107 | 0 | addressMessage->ifa_index); |
108 | 0 | continue; |
109 | 0 | } |
110 | | |
111 | 0 | ChipLogDetail(DeviceLayer, "Got IP address on interface: %s", name); |
112 | |
|
113 | 0 | const char * wifiIfName = ConnectivityMgrImpl().GetWiFiIfName(); |
114 | 0 | if (wifiIfName == nullptr) |
115 | 0 | { |
116 | 0 | ChipLogDetail(DeviceLayer, "Ignoring IP update event: No WiFi interface name configured"); |
117 | 0 | continue; |
118 | 0 | } |
119 | | |
120 | 0 | if (strcmp(name, wifiIfName) != 0) |
121 | 0 | { |
122 | 0 | ChipLogDetail(DeviceLayer, "Ignoring IP update event: Interface name mismatch: %s != %s", name, |
123 | 0 | wifiIfName); |
124 | 0 | continue; |
125 | 0 | } |
126 | | |
127 | 0 | char ipStrBuf[chip::Inet::IPAddress::kMaxStringLength] = { 0 }; |
128 | 0 | inet_ntop(AF_INET, RTA_DATA(routeInfo), ipStrBuf, sizeof(ipStrBuf)); |
129 | 0 | ChipLogDetail(DeviceLayer, "Got IP address on interface: %s IP: %s", name, ipStrBuf); |
130 | |
|
131 | 0 | ChipDeviceEvent event{ .Type = DeviceEventType::kInternetConnectivityChange, |
132 | 0 | .InternetConnectivityChange = { .IPv4 = kConnectivity_Established, |
133 | 0 | .IPv6 = kConnectivity_NoChange } }; |
134 | |
|
135 | 0 | if (!chip::Inet::IPAddress::FromString(ipStrBuf, event.InternetConnectivityChange.ipAddress)) |
136 | 0 | { |
137 | 0 | ChipLogDetail(DeviceLayer, "Failed to report IP address - ip address parsing failed"); |
138 | 0 | continue; |
139 | 0 | } |
140 | | |
141 | 0 | CHIP_ERROR status = PlatformMgr().PostEvent(&event); |
142 | 0 | if (status != CHIP_NO_ERROR) |
143 | 0 | { |
144 | 0 | ChipLogDetail(DeviceLayer, "Failed to report IP address: %" CHIP_ERROR_FORMAT, status.Format()); |
145 | 0 | } |
146 | 0 | } |
147 | 0 | } |
148 | 0 | } |
149 | 0 | } |
150 | 0 | } |
151 | 0 | else |
152 | 0 | { |
153 | 0 | ChipLogError(DeviceLayer, "EOF on netlink socket"); |
154 | 0 | return G_SOURCE_REMOVE; |
155 | 0 | } |
156 | | |
157 | 0 | return G_SOURCE_CONTINUE; |
158 | 0 | } |
159 | | |
160 | | // The temporary hack for getting IP address change on linux for network provisioning in the rendezvous session. |
161 | | // This should be removed or find a better place once we deprecate the rendezvous session. |
162 | | CHIP_ERROR RunWiFiIPChangeListener() |
163 | 0 | { |
164 | 0 | int sock; |
165 | 0 | if ((sock = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) == -1) |
166 | 0 | { |
167 | 0 | ChipLogError(DeviceLayer, "Failed to init netlink socket for IP addresses: %d", errno); |
168 | 0 | return CHIP_ERROR_INTERNAL; |
169 | 0 | } |
170 | | |
171 | 0 | struct sockaddr_nl addr; |
172 | 0 | memset(&addr, 0, sizeof(addr)); |
173 | 0 | addr.nl_family = AF_NETLINK; |
174 | 0 | addr.nl_groups = RTMGRP_IPV4_IFADDR; |
175 | |
|
176 | 0 | if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) == -1) |
177 | 0 | { |
178 | 0 | ChipLogError(DeviceLayer, "Failed to bind netlink socket for IP addresses: %d", errno); |
179 | 0 | close(sock); |
180 | 0 | return CHIP_ERROR_INTERNAL; |
181 | 0 | } |
182 | | |
183 | 0 | GIOChannel * ch = g_io_channel_unix_new(sock); |
184 | 0 | GSource * watchSource = g_io_create_watch(ch, G_IO_IN); |
185 | 0 | g_source_set_callback(watchSource, G_SOURCE_FUNC(WiFiIPChangeListener), nullptr, nullptr); |
186 | 0 | g_io_channel_set_close_on_unref(ch, TRUE); |
187 | 0 | g_io_channel_set_encoding(ch, nullptr, nullptr); |
188 | |
|
189 | 0 | PlatformMgrImpl().GLibMatterContextAttachSource(watchSource); |
190 | |
|
191 | 0 | g_source_unref(watchSource); |
192 | 0 | g_io_channel_unref(ch); |
193 | |
|
194 | 0 | return CHIP_NO_ERROR; |
195 | 0 | } |
196 | | |
197 | | #endif // #if CHIP_DEVICE_CONFIG_ENABLE_WIFI |
198 | | |
199 | | } // namespace |
200 | | |
201 | | CHIP_ERROR PlatformManagerImpl::_InitChipStack() |
202 | 0 | { |
203 | 0 | #if CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP |
204 | |
|
205 | 0 | auto * context = g_main_context_new(); |
206 | 0 | mGLibMainLoop = g_main_loop_new(context, FALSE); |
207 | 0 | mGLibMainLoopThread = g_thread_new("gmatter", GLibMainLoopThread, mGLibMainLoop); |
208 | 0 | g_main_context_unref(context); |
209 | |
|
210 | 0 | { |
211 | | // Wait for the GLib main loop to start. It is required that the context used |
212 | | // by the main loop is acquired before any other GLib functions are called. Otherwise, |
213 | | // the GLibMatterContextInvokeSync() might run functions on the wrong thread. |
214 | |
|
215 | 0 | std::unique_lock<std::mutex> lock(mGLibMainLoopCallbackIndirectionMutex); |
216 | 0 | GLibMatterContextInvokeData invokeData{}; |
217 | |
|
218 | 0 | auto * idleSource = g_idle_source_new(); |
219 | 0 | g_source_set_callback( |
220 | 0 | idleSource, |
221 | 0 | [](void * userData_) { |
222 | 0 | auto * data = reinterpret_cast<GLibMatterContextInvokeData *>(userData_); |
223 | 0 | std::unique_lock<std::mutex> lock_(PlatformMgrImpl().mGLibMainLoopCallbackIndirectionMutex); |
224 | 0 | data->mDone = true; |
225 | 0 | data->mDoneCond.notify_one(); |
226 | 0 | return G_SOURCE_REMOVE; |
227 | 0 | }, |
228 | 0 | &invokeData, nullptr); |
229 | 0 | GLibMatterContextAttachSource(idleSource); |
230 | 0 | g_source_unref(idleSource); |
231 | |
|
232 | 0 | invokeData.mDoneCond.wait(lock, [&invokeData]() { return invokeData.mDone; }); |
233 | 0 | } |
234 | |
|
235 | 0 | #endif |
236 | |
|
237 | 0 | #if CHIP_DEVICE_CONFIG_ENABLE_WIFI |
238 | 0 | ReturnErrorOnFailure(RunWiFiIPChangeListener()); |
239 | 0 | #endif |
240 | | |
241 | | // Initialize the configuration system. |
242 | 0 | ReturnErrorOnFailure(Internal::PosixConfig::Init()); |
243 | | |
244 | | // Initialize the reference time point as soon as possible, so we could |
245 | | // use it during the CHIP stack initialization, e.g. time counters for |
246 | | // diagnostics. |
247 | 0 | mStartTime = System::SystemClock().GetMonotonicTimestamp(); |
248 | | |
249 | | // Call _InitChipStack() on the generic implementation base class |
250 | | // to finish the initialization process. |
251 | 0 | ReturnErrorOnFailure(Internal::GenericPlatformManagerImpl_POSIX<PlatformManagerImpl>::_InitChipStack()); |
252 | | |
253 | | // Now set up our device instance info provider. We couldn't do that |
254 | | // earlier, because the generic implementation sets a generic one. |
255 | 0 | SetDeviceInstanceInfoProvider(&DeviceInstanceInfoProviderMgrImpl()); |
256 | |
|
257 | 0 | return CHIP_NO_ERROR; |
258 | 0 | } |
259 | | |
260 | | void PlatformManagerImpl::_Shutdown() |
261 | 0 | { |
262 | 0 | uint64_t upTime = 0; |
263 | |
|
264 | 0 | if (GetDiagnosticDataProvider().GetUpTime(upTime) == CHIP_NO_ERROR) |
265 | 0 | { |
266 | 0 | uint32_t totalOperationalHours = 0; |
267 | |
|
268 | 0 | if (ConfigurationMgr().GetTotalOperationalHours(totalOperationalHours) == CHIP_NO_ERROR) |
269 | 0 | { |
270 | 0 | TEMPORARY_RETURN_IGNORED ConfigurationMgr().StoreTotalOperationalHours(totalOperationalHours + |
271 | 0 | static_cast<uint32_t>(upTime / 3600)); |
272 | 0 | } |
273 | 0 | else |
274 | 0 | { |
275 | 0 | ChipLogError(DeviceLayer, "Failed to get total operational hours of the Node"); |
276 | 0 | } |
277 | 0 | } |
278 | 0 | else |
279 | 0 | { |
280 | 0 | ChipLogError(DeviceLayer, "Failed to get current uptime since the Node’s last reboot"); |
281 | 0 | } |
282 | |
|
283 | 0 | Internal::GenericPlatformManagerImpl_POSIX<PlatformManagerImpl>::_Shutdown(); |
284 | |
|
285 | 0 | #if CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP |
286 | 0 | if (mGLibMainLoop != nullptr) |
287 | 0 | { |
288 | 0 | #if CHIP_DEVICE_CONFIG_ENABLE_WPA |
289 | | // The wpa_supplicant GLib objects must be released while the GLib main loop is still running. Release them here, before |
290 | | // quitting the loop, otherwise they leak when ConnectivityManager is destructed. |
291 | 0 | ConnectivityMgrImpl().StopWiFiManagement(); |
292 | 0 | #endif |
293 | 0 | g_main_loop_quit(mGLibMainLoop); |
294 | 0 | g_thread_join(mGLibMainLoopThread); |
295 | 0 | g_main_loop_unref(mGLibMainLoop); |
296 | 0 | mGLibMainLoop = nullptr; |
297 | 0 | } |
298 | 0 | #endif |
299 | 0 | } |
300 | | |
301 | | #if CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP |
302 | | void PlatformManagerImpl::_GLibMatterContextInvokeSync(LambdaBridge && bridge) |
303 | 0 | { |
304 | | // Because of TSAN false positives, we need to use a mutex to synchronize access to all members of |
305 | | // the GLibMatterContextInvokeData object (including constructor and destructor). This is a temporary |
306 | | // workaround until TSAN-enabled GLib will be used in our CI. |
307 | 0 | std::unique_lock<std::mutex> lock(mGLibMainLoopCallbackIndirectionMutex); |
308 | |
|
309 | 0 | GLibMatterContextInvokeData invokeData{ std::move(bridge) }; |
310 | |
|
311 | 0 | lock.unlock(); |
312 | |
|
313 | 0 | g_main_context_invoke_full( |
314 | 0 | g_main_loop_get_context(mGLibMainLoop), G_PRIORITY_HIGH_IDLE, |
315 | 0 | [](void * userData_) { |
316 | 0 | auto * data = reinterpret_cast<GLibMatterContextInvokeData *>(userData_); |
317 | | |
318 | | // XXX: Temporary workaround for TSAN false positives. |
319 | 0 | std::unique_lock<std::mutex> lock_(PlatformMgrImpl().mGLibMainLoopCallbackIndirectionMutex); |
320 | |
|
321 | 0 | lock_.unlock(); |
322 | 0 | data->bridge(); |
323 | 0 | lock_.lock(); |
324 | |
|
325 | 0 | data->mDone = true; |
326 | 0 | data->mDoneCond.notify_one(); |
327 | |
|
328 | 0 | return G_SOURCE_REMOVE; |
329 | 0 | }, |
330 | 0 | &invokeData, nullptr); |
331 | |
|
332 | 0 | lock.lock(); |
333 | 0 | invokeData.mDoneCond.wait(lock, [&invokeData]() { return invokeData.mDone; }); |
334 | 0 | } |
335 | | #endif // CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP |
336 | | |
337 | | } // namespace DeviceLayer |
338 | | } // namespace chip |