curl-fuzzer C++ reference
Loading...
Searching...
No Matches
tftp_mock_server.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) Max Dymond, <cmeister2@gmail.com>, et al.
3 *
4 * SPDX-License-Identifier: curl
5 */
6
9
11
12#include <fcntl.h>
13#include <sys/socket.h>
14#include <unistd.h>
15
16#include <algorithm>
17#include <array>
18#include <cerrno>
19#include <cstring>
20#include <utility>
21
22namespace proto_fuzzer {
23
27 : response_datagrams_(),
28 response_datagram_count_(0),
29 next_response_datagram_(0),
30 request_fd_(-1),
31 transfer_fd_(-1),
32 client_address_(),
33 client_address_length_(0),
34 has_client_address_(false),
35 socket_opened_(false),
36 request_port_(0),
37 transfer_port_(0),
38 received_datagrams_() {}
39
43
49const std::vector<TftpReceivedDatagram>& TftpMockServer::received_datagrams() const { return received_datagrams_; }
50
54std::uint16_t TftpMockServer::request_port() const { return request_port_; }
55
59std::uint16_t TftpMockServer::transfer_port() const { return transfer_port_; }
60
67 curlsocktype /*purpose*/) const {
68 return SocketSetupDisposition::kNeedsSetup;
69}
70
77void TftpMockServer::PrepareScript(const curl::fuzzer::proto::Connection& connection) {
78 response_datagrams_.fill(nullptr);
79 response_datagram_count_ = 0;
80 next_response_datagram_ = 0;
81
82 if (!connection.initial_response().empty()) {
83 response_datagrams_[response_datagram_count_++] = &connection.initial_response();
84 }
85 const std::size_t chunk_count =
86 std::min<std::size_t>(scenario_limits::kMaxResponseChunks, connection.on_readable_size());
87 for (std::size_t index = 0; index < chunk_count; ++index) {
88 response_datagrams_[response_datagram_count_++] = &connection.on_readable(static_cast<int>(index));
89 }
90}
91
96void TftpMockServer::ResetPeer() {
97 if (request_fd_ >= 0) {
98 (void)::close(request_fd_);
99 request_fd_ = -1;
100 }
101 if (transfer_fd_ >= 0) {
102 (void)::close(transfer_fd_);
103 transfer_fd_ = -1;
104 }
105 std::memset(&client_address_, 0, sizeof(client_address_));
106 client_address_length_ = 0;
107 has_client_address_ = false;
108 socket_opened_ = false;
109 request_port_ = 0;
110 transfer_port_ = 0;
111}
112
118bool TftpMockServer::ConfigureSocket(int fd) {
119 if (fd < 0) {
120 return false;
121 }
122 const int descriptor_flags = ::fcntl(fd, F_GETFD, 0);
123 if (descriptor_flags < 0 || ::fcntl(fd, F_SETFD, descriptor_flags | FD_CLOEXEC) < 0) {
124 return false;
125 }
126 const int status_flags = ::fcntl(fd, F_GETFL, 0);
127 return status_flags >= 0 && ::fcntl(fd, F_SETFL, status_flags | O_NONBLOCK) == 0;
128}
129
136int TftpMockServer::OpenLoopbackSocket(struct sockaddr_in* bound_address) {
137 if (bound_address == nullptr) {
138 return -1;
139 }
140 const int fd = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
141 if (fd < 0 || !ConfigureSocket(fd)) {
142 if (fd >= 0) {
143 (void)::close(fd);
144 }
145 return -1;
146 }
147
148 struct sockaddr_in requested = {};
149 requested.sin_family = AF_INET;
150 requested.sin_port = htons(0);
151 requested.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
152 if (::bind(fd, reinterpret_cast<const struct sockaddr*>(&requested), sizeof(requested)) != 0) {
153 (void)::close(fd);
154 return -1;
155 }
156
157 socklen_t length = sizeof(*bound_address);
158 std::memset(bound_address, 0, sizeof(*bound_address));
159 if (::getsockname(fd, reinterpret_cast<struct sockaddr*>(bound_address), &length) != 0 ||
160 length != sizeof(*bound_address) || bound_address->sin_family != AF_INET) {
161 (void)::close(fd);
162 return -1;
163 }
164 return fd;
165}
166
175bool TftpMockServer::RewriteDestination(struct curl_sockaddr* address, const struct sockaddr_in& destination) {
176 if (address == nullptr || sizeof(destination) > sizeof(address->addr)) {
177 return false;
178 }
179 address->family = AF_INET;
180 address->socktype = SOCK_DGRAM;
181 address->protocol = IPPROTO_UDP;
182 address->addrlen = sizeof(destination);
183 std::memset(&address->addr, 0, sizeof(address->addr));
184 std::memcpy(&address->addr, &destination, sizeof(destination));
185 return true;
186}
187
196curl_socket_t TftpMockServer::HandleOpenSocket(curlsocktype purpose, struct curl_sockaddr* address) {
197 if (purpose != CURLSOCKTYPE_IPCXN || address == nullptr || socket_opened_) {
198 return CURL_SOCKET_BAD;
199 }
200 socket_opened_ = true;
201
202 struct sockaddr_in request_address = {};
203 struct sockaddr_in transfer_address = {};
204 request_fd_ = OpenLoopbackSocket(&request_address);
205 transfer_fd_ = OpenLoopbackSocket(&transfer_address);
206 if (request_fd_ < 0 || transfer_fd_ < 0 || !RewriteDestination(address, request_address)) {
207 ResetPeer();
208 return CURL_SOCKET_BAD;
209 }
210
211 request_port_ = ntohs(request_address.sin_port);
212 transfer_port_ = ntohs(transfer_address.sin_port);
213
214 const int client_fd = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
215 if (client_fd < 0 || !ConfigureSocket(client_fd)) {
216 if (client_fd >= 0) {
217 (void)::close(client_fd);
218 }
219 ResetPeer();
220 return CURL_SOCKET_BAD;
221 }
222 return static_cast<curl_socket_t>(client_fd);
223}
224
230void TftpMockServer::RememberClientAddress(const struct sockaddr_in& address, socklen_t length) {
231 if (has_client_address_ || length != sizeof(address) || address.sin_family != AF_INET) {
232 return;
233 }
234 client_address_ = address;
235 client_address_length_ = length;
236 has_client_address_ = true;
237}
238
245std::size_t TftpMockServer::DrainSocket(int fd, TftpSocketRole role) {
246 if (fd < 0) {
247 return 0;
248 }
249
250 std::array<unsigned char, 65536> packet;
251 std::size_t count = 0;
252 while (true) {
253 struct sockaddr_in source = {};
254 socklen_t source_length = sizeof(source);
255 const ssize_t received =
256 ::recvfrom(fd, packet.data(), packet.size(), 0, reinterpret_cast<struct sockaddr*>(&source), &source_length);
257 if (received < 0) {
258 if (errno == EINTR) {
259 continue;
260 }
261 break;
262 }
263
264 ++count;
265 RememberClientAddress(source, source_length);
266 if (received_datagrams_.size() < kMaxCapturedDatagrams) {
267 TftpReceivedDatagram observation;
268 observation.received_on = role;
269 observation.source_port =
270 source_length == sizeof(source) && source.sin_family == AF_INET ? ntohs(source.sin_port) : 0;
271 observation.bytes.assign(reinterpret_cast<const char*>(packet.data()), static_cast<std::size_t>(received));
272 received_datagrams_.push_back(std::move(observation));
273 }
274 }
275 return count;
276}
277
283std::size_t TftpMockServer::DrainClientDatagrams() {
284 return DrainSocket(request_fd_, TftpSocketRole::kRequest) + DrainSocket(transfer_fd_, TftpSocketRole::kTransfer);
285}
286
292bool TftpMockServer::SendNextDatagram() {
293 if (!has_client_address_ || transfer_fd_ < 0 || next_response_datagram_ >= response_datagram_count_) {
294 return false;
295 }
296
297 const std::string& datagram = *response_datagrams_[next_response_datagram_++];
298 (void)::sendto(transfer_fd_, datagram.data(), datagram.size(), 0,
299 reinterpret_cast<const struct sockaddr*>(&client_address_), client_address_length_);
300 return true;
301}
302
311void TftpMockServer::RunLoop(CURLM* multi, CURL* easy, const curl::fuzzer::proto::Scenario& scenario) {
312 (void)easy;
313 ResetPeer();
314 PrepareScript(scenario.connection());
315 received_datagrams_.clear();
316 received_datagrams_.reserve(kMaxCapturedDatagrams);
317
318 int still_running = 1;
319 int idle_iterations = 0;
320 for (int iteration = 0; iteration < kMaxDriveIterations; ++iteration) {
321 const CURLMcode result = curl_multi_perform(multi, &still_running);
322 if (result != CURLM_OK) {
323 break;
324 }
325
326 const bool received = DrainClientDatagrams() != 0;
327 const bool sent = still_running != 0 && received && SendNextDatagram();
328 if (received || sent) {
329 idle_iterations = 0;
330 } else {
331 ++idle_iterations;
332 }
333
334 if (still_running == 0 || idle_iterations >= kMaxIdleIterations) {
335 break;
336 }
337 }
338}
339
342#if defined(PROTO_FUZZER_OBSERVE_STATE)
344 const volatile std::size_t tftp_observation[] = {received_datagrams().size(), request_port(), transfer_port()};
345 (void)tftp_observation;
346#endif
347}
348
349} // namespace proto_fuzzer
virtual void ObserveState()
Default no-op for peers without protocol-specific observations.
static constexpr int kMaxDriveIterations
Hard operation budget for one scenario.
static constexpr int kMaxIdleIterations
Consecutive no-progress budget for ordinary scenarios.
~TftpMockServer() override
Close the two server endpoints after the base drive has removed curl's separately-owned client descri...
void RunLoop(CURLM *multi, CURL *easy, const curl::fuzzer::proto::Scenario &scenario) override
Give curl one nonblocking state-machine turn, retain all client packets that turn produced,...
SocketSetupDisposition GetSocketSetupDisposition(curl_socket_t curlfd, curlsocktype purpose) const override
TFTP returns an unconnected IPv4 datagram descriptor, so curl must still bind/configure it before the...
std::uint16_t request_port() const
Expose the kernel-selected request endpoint for transfer-ID assertions, not for routing.
void ObserveState() override
Retain protocol outcomes in coverage and introspector builds.
curl_socket_t HandleOpenSocket(curlsocktype purpose, struct curl_sockaddr *address) override
Create both server transfer IDs before returning curl's client socket.
std::uint16_t transfer_port() const
Expose the kernel-selected response endpoint for transfer-ID assertions, not for routing.
TftpMockServer()
Start without live descriptors or borrowed scenario storage.
const std::vector< TftpReceivedDatagram > & received_datagrams() const
Return bounded observations from the most recent run rather than parsing them in the peer.
SocketSetupDisposition
Tell curl whether an application-provided socket still needs its normal connect/options setup or alre...
Bounded loopback UDP peer for structured TFTP scenarios.
TftpSocketRole
Identifies which stable server transfer ID received a client datagram.