Line data Source code
1 : /*
2 : * Copyright (c) 2026 Project CHIP Authors
3 : * All rights reserved.
4 : *
5 : * Licensed under the Apache License, Version 2.0 (the "License");
6 : * you may not use this file except in compliance with the License.
7 : * You may obtain a copy of the License at
8 : *
9 : * http://www.apache.org/licenses/LICENSE-2.0
10 : *
11 : * Unless required by applicable law or agreed to in writing, software
12 : * distributed under the License is distributed on an "AS IS" BASIS,
13 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 : * See the License for the specific language governing permissions and
15 : * limitations under the License.
16 : *
17 : */
18 :
19 : #include "ThreadMeshcopCommissionProxy.h"
20 :
21 : #include <lib/core/CHIPEncoding.h>
22 : #include <lib/dnssd/TxtFields.h>
23 : #include <lib/dnssd/minimal_mdns/core/QNameString.h> // nogncheck
24 : #include <lib/support/CHIPMemString.h>
25 : #include <lib/support/CodeUtils.h>
26 : #include <lib/support/logging/CHIPLogging.h>
27 : #include <transport/raw/MessageHeader.h>
28 :
29 : #include <errno.h>
30 : #include <inttypes.h>
31 : #include <netinet/in.h>
32 : #include <sys/socket.h>
33 : #include <unistd.h>
34 :
35 : #include <chrono>
36 : #include <thread>
37 :
38 : using namespace chip;
39 :
40 : namespace {
41 : /**
42 : * Internal OT Commissioner Logger implementation.
43 : */
44 : class CommissionerLogger : public ot::commissioner::Logger
45 : {
46 : public:
47 0 : void Log(ot::commissioner::LogLevel level, const std::string & region, const std::string & message) override
48 : {
49 0 : ChipLogProgress(Controller, "[ot-commissioner][%u][%s] %s", static_cast<unsigned>(level), region.c_str(), message.c_str());
50 0 : }
51 : };
52 :
53 : constexpr char kMatterCServiceSuffix[] = "_matterc._udp.local";
54 :
55 0 : uint64_t JoinerIdFromBytes(const std::vector<uint8_t> & bytes)
56 : {
57 0 : const uint8_t * buffer = bytes.data();
58 0 : return Encoding::BigEndian::Read64(buffer);
59 : }
60 :
61 0 : std::vector<uint8_t> DiscoveryCodeToVector(Thread::DiscoveryCode code)
62 : {
63 : uint8_t bytes[sizeof(uint64_t)];
64 0 : Encoding::BigEndian::Put64(bytes, code.AsUInt64());
65 0 : return std::vector<uint8_t>(bytes, bytes + sizeof(bytes));
66 : }
67 : } // namespace
68 :
69 : namespace chip {
70 : namespace Controller {
71 :
72 13 : ThreadMeshcopCommissionProxy::ThreadMeshcopCommissionProxy() : mState(State::kConnecting), mPromiseFulfilled(false)
73 : {
74 13 : mCommissioner = ot::commissioner::Commissioner::Create(*this);
75 13 : }
76 :
77 13 : ThreadMeshcopCommissionProxy::~ThreadMeshcopCommissionProxy()
78 : {
79 13 : std::lock_guard<std::recursive_mutex> lock(mMutex);
80 13 : if (mProxyFd != -1)
81 : {
82 0 : if (shutdown(mProxyFd, SHUT_RDWR) == 0 || errno != EBADF)
83 : {
84 0 : close(mProxyFd);
85 : }
86 :
87 0 : mProxyFd = -1;
88 : }
89 :
90 13 : if (mProxyThread.joinable())
91 : {
92 0 : mProxyThread.join();
93 : }
94 13 : }
95 :
96 0 : void ThreadMeshcopCommissionProxy::SetState(State state)
97 : {
98 0 : mState = state;
99 0 : }
100 :
101 0 : void ThreadMeshcopCommissionProxy::OnHeader(mdns::Minimal::ConstHeaderRef & header)
102 : {
103 0 : ChipLogDetail(Controller, "mDNS Response: ID=%u, Answers=%u, Additional=%u", header.GetMessageId(), header.GetAnswerCount(),
104 : header.GetAdditionalCount());
105 0 : }
106 :
107 0 : void ThreadMeshcopCommissionProxy::OnQuery(const mdns::Minimal::QueryData & data)
108 : {
109 0 : if (mState != State::kDiscovering)
110 : {
111 0 : ChipLogProgress(Controller, "Received mDNS query but proxy is not in discovery state");
112 : }
113 :
114 0 : ChipLogDetail(Controller, "mDNS query: %s", mdns::Minimal::QNameString(data.GetName()).c_str());
115 0 : mNodeData.Set<Dnssd::CommissionNodeData>();
116 0 : }
117 :
118 0 : void ThreadMeshcopCommissionProxy::OnResource(mdns::Minimal::ResourceType section, const mdns::Minimal::ResourceData & data)
119 : {
120 0 : if (mState != State::kDiscovering)
121 : {
122 0 : return;
123 : }
124 :
125 0 : auto name = mdns::Minimal::QNameString(data.GetName());
126 0 : auto & commissionData = mNodeData.Get<Dnssd::CommissionNodeData>();
127 :
128 0 : commissionData.threadMeshcop = true;
129 :
130 0 : switch (data.GetType())
131 : {
132 0 : case mdns::Minimal::QType::A:
133 : case mdns::Minimal::QType::AAAA:
134 0 : Platform::CopyString(commissionData.hostName, name.c_str());
135 0 : break;
136 :
137 0 : case mdns::Minimal::QType::SRV: {
138 0 : mdns::Minimal::SrvRecord srv;
139 0 : if (!srv.Parse(data.GetData(), mDnsPacket))
140 : {
141 0 : ChipLogError(Controller, "Failed to parse mDNS SRV record");
142 0 : return;
143 : }
144 :
145 0 : if (!name.EndsWith(kMatterCServiceSuffix))
146 : {
147 0 : ChipLogDetail(Controller, "Ignoring non-Matter service: %s", name.c_str());
148 0 : return;
149 : }
150 :
151 : // Extract the instance label (portion before "._matterc._udp.local") for CommissionNodeData::instanceName.
152 0 : std::string fullName(name.c_str());
153 0 : constexpr size_t kMatterCServiceSuffixLen = sizeof(kMatterCServiceSuffix) - 1; // exclude null terminator
154 0 : if (fullName.length() >= kMatterCServiceSuffixLen)
155 : {
156 0 : fullName.erase(fullName.length() - kMatterCServiceSuffixLen);
157 : }
158 0 : Platform::CopyString(commissionData.instanceName, fullName.c_str());
159 :
160 0 : mServicePort = srv.GetPort();
161 :
162 0 : if (mProxyFd == -1)
163 : {
164 0 : CHIP_ERROR err = CreateProxySocket(commissionData);
165 0 : if (err != CHIP_NO_ERROR)
166 : {
167 0 : ChipLogError(Controller, "Failed to setup proxy socket: %" CHIP_ERROR_FORMAT, err.Format());
168 0 : SetState(State::kAborted);
169 : }
170 : }
171 0 : break;
172 0 : }
173 :
174 0 : case mdns::Minimal::QType::TXT:
175 0 : mdns::Minimal::ParseTxtRecord(data.GetData(), this);
176 0 : break;
177 :
178 0 : default:
179 0 : break;
180 : }
181 : }
182 :
183 0 : CHIP_ERROR ThreadMeshcopCommissionProxy::CreateProxySocket(chip::Dnssd::CommissionNodeData & commissionData)
184 : {
185 0 : mProxyFd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
186 0 : VerifyOrReturnError(mProxyFd >= 0, CHIP_ERROR_POSIX(errno));
187 :
188 0 : sockaddr_in6 addr = {};
189 0 : addr.sin6_family = AF_INET6;
190 0 : addr.sin6_port = 0;
191 0 : addr.sin6_addr = in6addr_loopback;
192 :
193 0 : if (bind(mProxyFd, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) != 0)
194 : {
195 0 : close(mProxyFd);
196 0 : mProxyFd = -1;
197 0 : return CHIP_ERROR_POSIX(errno);
198 : }
199 :
200 0 : socklen_t addr_len = sizeof(addr);
201 0 : if (getsockname(mProxyFd, reinterpret_cast<struct sockaddr *>(&addr), &addr_len) == -1)
202 : {
203 0 : close(mProxyFd);
204 0 : mProxyFd = -1;
205 0 : return CHIP_ERROR_POSIX(errno);
206 : }
207 :
208 0 : commissionData.numIPs = 1;
209 0 : commissionData.port = ntohs(addr.sin6_port);
210 0 : commissionData.ipAddress[0] = Inet::IPAddress::FromSockAddr(addr);
211 0 : commissionData.interfaceId = Inet::InterfaceId::FromIPAddress(commissionData.ipAddress[0]);
212 :
213 0 : ChipLogProgress(Controller, "Proxy socket created on port %u", commissionData.port);
214 0 : return CHIP_NO_ERROR;
215 : }
216 :
217 0 : void ThreadMeshcopCommissionProxy::OnRecord(const mdns::Minimal::BytesRange & name, const mdns::Minimal::BytesRange & value)
218 : {
219 0 : ByteSpan key(name.Start(), name.Size());
220 0 : ByteSpan val(value.Start(), value.Size());
221 :
222 0 : Dnssd::FillNodeDataFromTxt(key, val, mNodeData.Get<Dnssd::CommissionNodeData>());
223 0 : }
224 :
225 0 : void ThreadMeshcopCommissionProxy::ProcessAnnouncement(const std::vector<uint8_t> & joinerIdBytes, uint16_t joinerPort,
226 : const std::vector<uint8_t> & payload)
227 : {
228 0 : std::lock_guard<std::recursive_mutex> lock(mMutex);
229 :
230 0 : if (mPromiseFulfilled)
231 : {
232 0 : return;
233 : }
234 :
235 0 : mNodeData.Set<Dnssd::CommissionNodeData>();
236 0 : mDnsPacket = mdns::Minimal::BytesRange(payload.data(), payload.data() + payload.size());
237 :
238 0 : if (!mdns::Minimal::ParsePacket(mDnsPacket, this))
239 : {
240 0 : ChipLogError(Controller, "Failed to parse joiner mDNS announcement");
241 0 : return;
242 : }
243 :
244 0 : uint32_t discoveredDiscriminator = mNodeData.Get<Dnssd::CommissionNodeData>().longDiscriminator;
245 0 : ChipLogProgress(Controller, "Discovered joiner with discriminator: %u", discoveredDiscriminator);
246 :
247 0 : if (!mExpectedDiscriminator.MatchesLongDiscriminator(static_cast<uint16_t>(discoveredDiscriminator)))
248 : {
249 0 : ChipLogProgress(Controller, "Discriminator mismatch (Expected %u, Got %u). Ignoring announcement.",
250 : mExpectedDiscriminator.GetLongValue(), discoveredDiscriminator);
251 0 : return;
252 : }
253 :
254 0 : mDiscoveredNodePromise.set_value(mNodeData);
255 0 : mPromiseFulfilled = true;
256 :
257 0 : SetState(State::kDiscovered);
258 :
259 0 : if (mProxyThread.joinable())
260 : {
261 0 : mProxyThread.join();
262 : }
263 :
264 0 : mProxyThread = std::thread([id = joinerIdBytes, this]() {
265 : struct sockaddr_storage addr;
266 0 : socklen_t len = sizeof(addr);
267 : uint8_t buf[chip::detail::kMaxIPPacketSizeBytes];
268 : ssize_t received;
269 :
270 0 : while ((received = recvfrom(mProxyFd, buf, sizeof(buf), 0, reinterpret_cast<struct sockaddr *>(&addr), &len)) > 0)
271 : {
272 0 : switch (mState)
273 : {
274 0 : case State::kDiscovered: {
275 0 : int rval = connect(mProxyFd, reinterpret_cast<struct sockaddr *>(&addr), len);
276 0 : if (rval < 0)
277 : {
278 0 : ChipLogError(Controller, "Failed to connect to Matter Commissioner: %s", strerror(errno));
279 0 : continue;
280 : }
281 0 : SetState(State::kCommissioning);
282 : FALLTHROUGH;
283 : }
284 :
285 0 : case State::kCommissioning: {
286 0 : std::vector<uint8_t> pkt(buf, buf + received);
287 :
288 0 : auto error = mCommissioner->SendToJoiner(id, mServicePort, pkt);
289 0 : if (error != ot::commissioner::ErrorCode::kNone)
290 : {
291 0 : ChipLogError(Controller, "Failed to send packet to joiner: %s", error.GetMessage().c_str());
292 0 : return;
293 : }
294 0 : break;
295 0 : }
296 0 : default:
297 0 : ChipLogError(Controller, "Invalid CommissionProxy state: %d", static_cast<int>(mState.load()));
298 0 : return;
299 : }
300 : }
301 0 : });
302 0 : }
303 :
304 0 : void ThreadMeshcopCommissionProxy::OnJoinerMessage(const std::vector<uint8_t> & joinerIdBytes, uint16_t joinerPort,
305 : const std::vector<uint8_t> & payload)
306 : {
307 0 : std::lock_guard<std::recursive_mutex> lock(mMutex);
308 :
309 0 : if (joinerIdBytes.size() != sizeof(uint64_t) || mState == State::kAborted)
310 : {
311 0 : return;
312 : }
313 :
314 0 : uint64_t joinerId = JoinerIdFromBytes(joinerIdBytes);
315 0 : ChipLogDetail(Controller, "Message from joiner 0x%" PRIx64 " on port %u", joinerId, joinerPort);
316 :
317 0 : if (mJoinerId == 0)
318 : {
319 0 : mJoinerId = joinerId;
320 : }
321 0 : else if (mJoinerId != joinerId)
322 : {
323 0 : ChipLogProgress(Controller, "Ignoring message from unexpected joiner 0x%" PRIx64, joinerId);
324 0 : return;
325 : }
326 :
327 0 : switch (mState)
328 : {
329 0 : case State::kCommissioning:
330 0 : if (mProxyFd != -1)
331 : {
332 0 : if (send(mProxyFd, payload.data(), payload.size(), 0) < 0)
333 : {
334 0 : ChipLogError(Controller, "Failed to forward packet to local proxy: %s", strerror(errno));
335 0 : SetState(State::kAborted);
336 : }
337 : }
338 0 : break;
339 0 : case State::kAborted:
340 0 : break;
341 0 : case State::kConnecting:
342 : // First message from joiner is usually the mDNS announcement
343 0 : SetState(State::kDiscovering);
344 : FALLTHROUGH;
345 0 : case State::kDiscovering:
346 0 : ProcessAnnouncement(joinerIdBytes, joinerPort, payload);
347 0 : break;
348 0 : case State::kDiscovered:
349 0 : ChipLogProgress(Controller, "WARNING ignore unsolicited messages after joiner is already discovered");
350 0 : break;
351 : }
352 0 : }
353 :
354 0 : ot::commissioner::CommissionerDataset ThreadMeshcopCommissionProxy::MakeCommissionerDataset(Thread::DiscoveryCode code)
355 : {
356 0 : ot::commissioner::CommissionerDataset dataset;
357 :
358 0 : dataset.mJoinerUdpPort = ot::commissioner::kDefaultJoinerUdpPort;
359 0 : dataset.mPresentFlags |= ot::commissioner::CommissionerDataset::kJoinerUdpPortBit;
360 0 : dataset.mPresentFlags &=
361 : ~(ot::commissioner::CommissionerDataset::kSessionIdBit | ot::commissioner::CommissionerDataset::kBorderAgentLocatorBit);
362 :
363 0 : if (code.IsAny())
364 : {
365 0 : dataset.mSteeringData = std::vector<uint8_t>{ 0xff };
366 : }
367 : else
368 : {
369 0 : std::vector<uint8_t> steeringData(ot::commissioner::kMaxSteeringDataLength);
370 0 : ot::commissioner::Commissioner::AddJoiner(steeringData, DiscoveryCodeToVector(code));
371 0 : dataset.mSteeringData = steeringData;
372 0 : }
373 :
374 0 : dataset.mPresentFlags |= ot::commissioner::CommissionerDataset::kSteeringDataBit;
375 0 : return dataset;
376 : }
377 0 : CHIP_ERROR ThreadMeshcopCommissionProxy::InitializeCommissioner(ByteSpan & pskc)
378 : {
379 0 : VerifyOrReturnError(pskc.size() == Thread::kSizePSKc, CHIP_ERROR_INVALID_ARGUMENT);
380 0 : ot::commissioner::Config config;
381 0 : config.mLogger = std::make_shared<CommissionerLogger>();
382 0 : config.mEnableCcm = false;
383 0 : config.mProxyMode = true;
384 0 : config.mPSKc = std::vector<uint8_t>(pskc.begin(), pskc.end());
385 :
386 0 : auto error = mCommissioner->Init(config);
387 0 : if (error != ot::commissioner::ErrorCode::kNone)
388 : {
389 0 : ChipLogError(Controller, "OT Commissioner Init failed: %s", error.GetMessage().c_str());
390 0 : return CHIP_ERROR_INTERNAL;
391 : }
392 0 : return CHIP_NO_ERROR;
393 0 : }
394 :
395 0 : CHIP_ERROR ThreadMeshcopCommissionProxy::Discover(ByteSpan & pskc, const Transport::PeerAddress & peerAddr,
396 : const Thread::DiscoveryCode code, SetupDiscriminator expectedDiscriminator,
397 : Dnssd::DiscoveredNodeData & nodeData, uint16_t timeout)
398 : {
399 : using ot::commissioner::Error;
400 :
401 0 : Error error;
402 :
403 : // Reset the promise and state for a new discovery session
404 0 : std::future<Dnssd::DiscoveredNodeData> future;
405 : {
406 0 : std::lock_guard<std::recursive_mutex> lock(mMutex);
407 0 : mExpectedDiscriminator = expectedDiscriminator;
408 0 : SetState(State::kConnecting);
409 0 : mDiscoveredNodePromise = std::promise<Dnssd::DiscoveredNodeData>();
410 0 : future = mDiscoveredNodePromise.get_future();
411 0 : mPromiseFulfilled = false;
412 0 : mJoinerId = 0;
413 0 : }
414 :
415 0 : ReturnErrorOnFailure(InitializeCommissioner(pskc));
416 :
417 : {
418 0 : std::string id;
419 : char host[Inet::IPAddress::kMaxStringLength];
420 0 : peerAddr.GetIPAddress().ToString(host);
421 :
422 0 : ChipLogProgress(Controller, "Petitioning Thread Border Agent at %s:%u", host, peerAddr.GetPort());
423 0 : error = mCommissioner->Petition(id, std::string(host), peerAddr.GetPort());
424 0 : if (error != ot::commissioner::ErrorCode::kNone)
425 : {
426 0 : ChipLogError(Controller, "Petition failed: %s", error.GetMessage().c_str());
427 0 : SetState(State::kAborted);
428 0 : return CHIP_ERROR_INTERNAL;
429 : }
430 :
431 0 : ChipLogProgress(Controller, "Thread Commissioner active with ID: %s", id.c_str());
432 0 : }
433 :
434 0 : error = mCommissioner->SetCommissionerDataset(MakeCommissionerDataset(code));
435 0 : if (error != ot::commissioner::ErrorCode::kNone)
436 : {
437 0 : ChipLogError(Controller, "Failed to set Steering Data: %s", error.GetMessage().c_str());
438 0 : SetState(State::kAborted);
439 0 : return CHIP_ERROR_INTERNAL;
440 : }
441 :
442 0 : ChipLogProgress(Controller, "Waiting for mDNS announcement from joiner...");
443 0 : auto waitDuration = std::chrono::seconds(timeout);
444 0 : if (future.wait_for(waitDuration) == std::future_status::timeout)
445 : {
446 0 : ChipLogError(Controller, "Timed out waiting for joiner mDNS announcement after %u seconds", timeout);
447 0 : SetState(State::kAborted);
448 0 : return CHIP_ERROR_TIMEOUT;
449 : }
450 0 : nodeData = future.get();
451 0 : return CHIP_NO_ERROR;
452 0 : }
453 : } // namespace Controller
454 : } // namespace chip
|