3 * Copyright (c) 2020-2021 Project CHIP Authors
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 #include "ResponseSender.h"
20 #include "QueryReplyFilter.h"
22 #include <system/SystemClock.h>
24 #define RETURN_IF_ERROR(err) \
27 if (err != CHIP_NO_ERROR) \
38 constexpr uint16_t kMdnsStandardPort = 5353;
40 // Restriction for UDP packets: https://tools.ietf.org/html/rfc1035#section-4.2.1
42 // Messages carried by UDP are restricted to 512 bytes (not counting the IP
43 // or UDP headers). Longer messages are truncated and the TC bit is set in
45 constexpr uint16_t kPacketSizeBytes = 512;
50 bool ResponseSendingState::SendUnicast() const
52 return mQuery->RequestedUnicastAnswer() || (mSource->SrcPort != kMdnsStandardPort);
55 bool ResponseSendingState::IncludeQuery() const
57 return (mSource->SrcPort != kMdnsStandardPort);
60 } // namespace Internal
62 CHIP_ERROR ResponseSender::Respond(uint32_t messageId, const QueryData & query, const chip::Inet::IPPacketInfo * querySource)
64 mSendState.Reset(messageId, query, querySource);
66 // Responder has a stateful 'additional replies required' that is used within the response
67 // loop. 'no additionals required' is set at the start and additionals are marked as the query
69 mResponder->ResetAdditionals();
71 // send all 'Answer' replies
73 const uint64_t kTimeNowMs = chip::System::Platform::Layer::GetClock_MonotonicMS();
75 QueryReplyFilter queryReplyFilter(query);
76 QueryResponderRecordFilter responseFilter;
78 responseFilter.SetReplyFilter(&queryReplyFilter);
80 if (!mSendState.SendUnicast())
82 // According to https://tools.ietf.org/html/rfc6762#section-6 we should multicast at most 1/sec
84 // TODO: the 'last sent' value does NOT track the interface we used to send, so this may cause
85 // broadcasts on one interface to throttle broadcasts on another interface.
86 constexpr uint64_t kOneSecondMs = 1000;
87 responseFilter.SetIncludeOnlyMulticastBeforeMS(kTimeNowMs - kOneSecondMs);
90 for (auto it = mResponder->begin(&responseFilter); it != mResponder->end(); it++)
92 it->responder->AddAllResponses(querySource, this);
93 ReturnErrorOnFailure(mSendState.GetError());
95 mResponder->MarkAdditionalRepliesFor(it);
97 if (!mSendState.SendUnicast())
99 it->lastMulticastTime = kTimeNowMs;
104 // send all 'Additional' replies
106 mSendState.SetResourceType(ResourceType::kAdditional);
108 QueryReplyFilter queryReplyFilter(query);
110 queryReplyFilter.SetIgnoreNameMatch(true).SetSendingAdditionalItems(true);
112 QueryResponderRecordFilter responseFilter;
114 .SetReplyFilter(&queryReplyFilter) //
115 .SetIncludeAdditionalRepliesOnly(true);
117 for (auto it = mResponder->begin(&responseFilter); it != mResponder->end(); it++)
119 it->responder->AddAllResponses(querySource, this);
120 ReturnErrorOnFailure(mSendState.GetError());
127 CHIP_ERROR ResponseSender::FlushReply()
129 ReturnErrorCodeIf(!mResponseBuilder.HasPacketBuffer(), CHIP_NO_ERROR); // nothing to flush
131 if (mResponseBuilder.HasResponseRecords())
134 if (mSendState.SendUnicast())
136 ChipLogProgress(Discovery, "Directly sending mDns reply to peer on port %d", mSendState.GetSourcePort());
137 ReturnErrorOnFailure(mServer->DirectSend(mResponseBuilder.ReleasePacket(), mSendState.GetSourceAddress(),
138 mSendState.GetSourcePort(), mSendState.GetSourceInterfaceId()));
142 ChipLogProgress(Discovery, "Broadcasting mDns reply");
143 ReturnErrorOnFailure(
144 mServer->BroadcastSend(mResponseBuilder.ReleasePacket(), kMdnsStandardPort, mSendState.GetSourceInterfaceId()));
148 return CHIP_NO_ERROR;
151 CHIP_ERROR ResponseSender::PrepareNewReplyPacket()
153 chip::System::PacketBufferHandle buffer = chip::System::PacketBufferHandle::New(kPacketSizeBytes);
154 ReturnErrorCodeIf(buffer.IsNull(), CHIP_ERROR_NO_MEMORY);
156 mResponseBuilder.Reset(std::move(buffer));
157 mResponseBuilder.Header().SetMessageId(mSendState.GetMessageId());
159 if (mSendState.IncludeQuery())
161 mResponseBuilder.AddQuery(*mSendState.GetQuery());
164 return CHIP_NO_ERROR;
167 void ResponseSender::AddResponse(const ResourceRecord & record)
169 RETURN_IF_ERROR(mSendState.GetError());
171 if (!mResponseBuilder.HasPacketBuffer())
173 mSendState.SetError(PrepareNewReplyPacket());
174 RETURN_IF_ERROR(mSendState.GetError());
177 if (!mResponseBuilder.Ok())
179 mSendState.SetError(CHIP_ERROR_INCORRECT_STATE);
183 mResponseBuilder.AddRecord(mSendState.GetResourceType(), record);
185 // ResponseBuilder AddRecord will only fail if insufficient space is available (or at least this is
186 // the assumption here). It also guarantees that existing data and header are unchanged on
187 // failure, hence we can flush and try again. This allows for split replies.
188 if (!mResponseBuilder.Ok())
190 mResponseBuilder.Header().SetFlags(mResponseBuilder.Header().GetFlags().SetTruncated(true));
192 RETURN_IF_ERROR(mSendState.SetError(FlushReply()));
193 RETURN_IF_ERROR(mSendState.SetError(PrepareNewReplyPacket()));
195 mResponseBuilder.AddRecord(mSendState.GetResourceType(), record);
196 if (!mResponseBuilder.Ok())
198 // Very much unexpected: single record addtion should fit (our records should not be that big).
199 ChipLogError(Discovery, "Failed to add single record to mDNS response.");
200 mSendState.SetError(CHIP_ERROR_INTERNAL);
205 } // namespace Minimal