curl-fuzzer C++ reference
Loading...
Searching...
No Matches
mock_server_base.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
11
13
14#include <curl/multi.h>
15#include <sys/select.h>
16
17#include <cstdio>
18
19#include "proto_fuzzer/allocation_fault_injection.h"
23
24namespace proto_fuzzer {
25
26namespace {
27
28constexpr long kSelectTimeoutUs = 1000; // 1 ms; explicit timing cases only.
29
35CURL** GetHandlesForCleanup(CURLM* multi) {
37 return curl_multi_get_handles(multi);
38}
39
40} // namespace
41
48curl_socket_t MockServerBaseOpenSocketTrampoline(void* clientp, curlsocktype purpose, struct curl_sockaddr* address) {
49 return static_cast<MockServerBase*>(clientp)->HandleOpenSocket(purpose, address);
50}
51
61int MockServerBaseSockOptTrampoline(void* clientp, curl_socket_t curlfd, curlsocktype purpose) {
62 const auto disposition = static_cast<MockServerBase*>(clientp)->GetSocketSetupDisposition(curlfd, purpose);
63 return disposition == SocketSetupDisposition::kAlreadyConnected ? CURL_SOCKOPT_ALREADY_CONNECTED : CURL_SOCKOPT_OK;
64}
65
69MockServerBase::MockServerBase(MultiDrivePolicy drive_policy)
70 : connection_(nullptr),
73 multi_socket_driver_(nullptr),
75 resume_response_(false),
76 drive_policy_(drive_policy) {}
77
82
87
90
97void MockServerBase::Install(CURL* easy) {
98 curl_easy_setopt(easy, CURLOPT_OPENSOCKETFUNCTION, &MockServerBaseOpenSocketTrampoline);
99 curl_easy_setopt(easy, CURLOPT_OPENSOCKETDATA, this);
100 curl_easy_setopt(easy, CURLOPT_SOCKOPTFUNCTION, &MockServerBaseSockOptTrampoline);
101 curl_easy_setopt(easy, CURLOPT_SOCKOPTDATA, this);
102}
103
110SocketSetupDisposition MockServerBase::GetSocketSetupDisposition(curl_socket_t /*curlfd*/, curlsocktype purpose) const {
111 // curl itself accepted CURLSOCKTYPE_ACCEPT descriptors, so they must follow
112 // its normal post-accept option path. Every IPCXN socket returned by the
113 // ordinary stream mocks is one end of an already-connected socketpair.
114 return purpose == CURLSOCKTYPE_ACCEPT ? SocketSetupDisposition::kNeedsSetup
115 : SocketSetupDisposition::kAlreadyConnected;
116}
117
126
138CURLcode MockServerBase::DriveScenario(CURL* easy, const curl::fuzzer::proto::Scenario& scenario) {
139 const curl::fuzzer::proto::ApiPlan* api_plan =
140 drive_policy_ == MultiDrivePolicy::kFromApiPlan && scenario.has_api_plan() ? &scenario.api_plan() : nullptr;
141 const bool use_multi_socket =
142 drive_policy_ == MultiDrivePolicy::kSocketAction ||
143 (api_plan != nullptr && api_plan->drive_mode() == curl::fuzzer::proto::API_DRIVE_MULTI_SOCKET);
144
145 // Cache backpressure knobs so HandleOpenSocket can apply them the moment
146 // connection_ exists. Both default to 0, which matches the legacy "drain
147 // greedily, kernel-default buffers" behaviour exactly.
148 const auto& bp = scenario.connection().backpressure();
149 pending_recv_buf_bytes_ = static_cast<int>(bp.recv_buf_bytes());
150 pending_drain_limit_ = static_cast<std::size_t>(bp.drain_limit());
152 resume_response_ = api_plan != nullptr && api_plan->pause_response_once();
153
154 CurlMultiPtr multi(curl_multi_init());
155 if (multi == nullptr) {
156 resume_response_ = false;
157 return CURLE_FAILED_INIT;
158 }
159
160 CURLcode transfer_result = CURLE_FAILED_INIT;
161
162 // Callback data must survive both remove_handle and multi_cleanup, since
163 // either may emit CURL_POLL_REMOVE. Keeping it in this outer scope provides
164 // that lifetime without allocating per-watch state.
165 MultiSocketDriver socket_driver;
166 if (use_multi_socket && socket_driver.Install(multi.get())) {
167 multi_socket_driver_ = &socket_driver;
168 }
169 if (curl_multi_add_handle(multi.get(), easy) == CURLM_OK) {
170 if (multi_observer_) {
171 multi_observer_(multi.get());
172 }
173 if (api_plan != nullptr && api_plan->wake_multi()) {
174 if (multi_socket_driver_ != nullptr) {
175 multi_socket_driver_->ProbeControlApis();
176 } else {
177 long timeout_ms = -1;
178 (void)curl_multi_timeout(multi.get(), &timeout_ms);
179 (void)curl_multi_wakeup(multi.get());
180 }
181 }
182 RunLoop(multi.get(), easy, scenario);
183
184 // Completion messages are the multi API's only durable record of the
185 // transfer result. Consume them while all easy handles are still attached:
186 // otherwise every scenario systematically skips curl_multi_info_read's
187 // result path and removal discards the opportunity. An accepted HTTP/2
188 // push can contribute one additional completion message; only the caller's
189 // parent determines DriveScenario's result.
190 int messages_remaining = 0;
191 CURLMsg* message = nullptr;
192 while ((message = curl_multi_info_read(multi.get(), &messages_remaining)) != nullptr) {
193 if (message->msg == CURLMSG_DONE && message->easy_handle == easy) {
194 transfer_result = message->data.result;
195 }
196 }
197
198 // CURL_PUSH_OK transfers ownership of each automatically-added easy to
199 // the application. Enumerate attached handles only after draining their
200 // completion messages, then honor the public remove-before-cleanup
201 // lifecycle. The original `easy` remains caller-owned.
202 CURL** handles = GetHandlesForCleanup(multi.get());
203 if (handles != nullptr) {
204 for (std::size_t index = 0; handles[index] != nullptr; ++index) {
205 CURL* handle = handles[index];
206 if (handle != easy && curl_multi_remove_handle(multi.get(), handle) == CURLM_OK) {
207 curl_easy_cleanup(handle);
209 }
210 }
211 curl_free(handles);
212 }
213
214 curl_multi_remove_handle(multi.get(), easy);
215 }
216 // Socket-action callbacks borrow the stack driver above, so that path must
217 // clean its multi before this function returns. Perform-based protocol mocks
218 // may retain the detached multi when their fixed lifecycle requires it.
219 if (use_multi_socket) {
220 multi.reset();
221 } else {
222 HandleDetachedMulti(std::move(multi));
223 }
224 multi_socket_driver_ = nullptr;
225 if (socket_driver.watch_exhausted()) {
226 std::fputs("curl-fuzzer: socket watch capacity exhausted\n", stderr);
227 }
228 resume_response_ = false;
229 return transfer_result;
230}
231
238
249CURLcode MockServerBase::DriveEasyScenario(CURL* easy, const curl::fuzzer::proto::Scenario& scenario,
250 bool /*use_events*/) {
251 return DriveScenario(easy, scenario);
252}
253
260 const curl::fuzzer::proto::Scenario& scenario) {
262 stats.connect_result = DriveScenario(easy, scenario);
263 return stats;
264}
265
273
279
286 if (resume_response_) {
287 (void)curl_easy_pause(easy, CURLPAUSE_CONT);
288 }
289}
290
301
307bool MockServerBase::UsesTimedDrive(const curl::fuzzer::proto::Scenario& scenario) {
308 const auto& bp = scenario.connection().backpressure();
309 return bp.recv_buf_bytes() != 0 || bp.drain_limit() != 0;
310}
311
318int MockServerBase::WaitOnMultiFdset(CURLM* multi, CURLMcode* rc) {
319 fd_set readfds;
320 fd_set writefds;
321 fd_set excfds;
322 FD_ZERO(&readfds);
323 FD_ZERO(&writefds);
324 FD_ZERO(&excfds);
325 int maxfd = -1;
326 *rc = curl_multi_fdset(multi, &readfds, &writefds, &excfds, &maxfd);
327 if (*rc != CURLM_OK) {
328 return -1;
329 }
330 if (maxfd < 0) {
331 return 0;
332 }
333 struct timeval timeout;
334 timeout.tv_sec = 0;
335 timeout.tv_usec = kSelectTimeoutUs;
336 return ::select(maxfd + 1, &readfds, &writefds, &excfds, &timeout);
337}
338
347 int numfds = 0;
348 (void)curl_multi_poll(multi, nullptr, 0, 0, &numfds);
349}
350
357bool MockServerBase::MultiPollsetWantsWrite(CURLM* multi, curl_socket_t socket) {
358 if (multi == nullptr || socket == CURL_SOCKET_BAD || socket < 0 || socket >= FD_SETSIZE) {
359 return false;
360 }
361 fd_set readfds;
362 fd_set writefds;
363 fd_set excfds;
364 FD_ZERO(&readfds);
365 FD_ZERO(&writefds);
366 FD_ZERO(&excfds);
367 int maxfd = -1;
368 if (curl_multi_fdset(multi, &readfds, &writefds, &excfds, &maxfd) != CURLM_OK) {
369 return false;
370 }
371 return socket <= maxfd && FD_ISSET(socket, &writefds);
372}
373
374} // namespace proto_fuzzer
Owns one half of a socketpair used to feed canned responses to libcurl.
Definition mock_server.h:29
static bool UsesTimedDrive(const curl::fuzzer::proto::Scenario &scenario)
Treat a non-default BackpressureConfig as an explicit request for the slower, timed drive policy.
virtual void HandleDetachedMulti(CurlMultiPtr multi)
Dispose of or retain the multi after its easy handle has been removed.
virtual void RunLoop(CURLM *multi, CURL *easy, const curl::fuzzer::proto::Scenario &scenario)=0
Subclass hook invoked from DriveScenario.
virtual ConnectOnlyRunStats DriveConnectOnlyScenario(CURL *easy, const curl::fuzzer::proto::Scenario &scenario)
Establish a CONNECT_ONLY transport, then perform bounded direct I/O.
virtual void Install(CURL *easy)
Install the common OPENSOCKETFUNCTION / OPENSOCKETDATA / SOCKOPTFUNCTION callbacks on 'easy'.
CURLcode DriveScenario(CURL *easy, const curl::fuzzer::proto::Scenario &scenario)
Run 'scenario' to completion on 'easy': allocate a multi, attach 'easy', delegate protocol-specific w...
std::size_t additional_handle_cleanup_count() const
int pending_recv_buf_bytes_
Cached SO_RCVBUF/SO_SNDBUF setting in bytes.
std::unique_ptr< MockConnection > connection_
The per-scenario MockConnection, lazily created by HandleOpenSocket().
std::size_t pending_drain_limit_
Cached per-call DrainIncoming byte budget.
static int WaitOnMultiFdset(CURLM *multi, CURLMcode *rc)
Wait on curl's fdset with a short timeout.
std::size_t additional_handle_cleanup_count_
Number of application-created handles, excluding the caller's parent, successfully removed from the c...
static void ProbeMultiPollset(CURLM *multi)
Ask curl to construct and inspect its connection-filter pollset without waiting.
friend curl_socket_t MockServerBaseOpenSocketTrampoline(void *, curlsocktype, struct curl_sockaddr *)
C trampoline for CURLOPT_OPENSOCKETFUNCTION.
virtual curl_socket_t HandleOpenSocket(curlsocktype purpose, struct curl_sockaddr *address)=0
Subclass hook invoked by the OPENSOCKET trampoline.
virtual void ObserveState()
Default no-op for peers without protocol-specific observations.
bool resume_response_
True only for an API-plan multi drive with its one-shot write callback.
void ResumeResponseIfRequested(CURL *easy)
Resume receive-side callback output at one bounded drive boundary when the dedicated API plan request...
virtual SocketSetupDisposition GetSocketSetupDisposition(curl_socket_t curlfd, curlsocktype purpose) const
Describe the socket returned by HandleOpenSocket without issuing native descriptor queries.
MultiSocketDriver * multi_socket_driver_
Non-owning pointer into DriveScenario's stack.
virtual CURLcode DriveEasyScenario(CURL *easy, const curl::fuzzer::proto::Scenario &scenario, bool use_events=false)
Run through the public easy entrypoint when a protocol mock can preload all peer work before curl tak...
friend int MockServerBaseSockOptTrampoline(void *, curl_socket_t, curlsocktype)
Translate explicit peer transport metadata into curl's sockopt callback contract.
virtual void ConfigureRequestData(ScenarioRequestData *request_data)
Bind protocol-specific work to the request-data callbacks after their per-scenario state has been con...
MultiSocketDriver * multi_socket_driver()
Expose the callback state installed for the current socket-action drive.
virtual ~MockServerBase()
Out-of-line destructor so MockConnection can stay forward-declared in the base header (its complete t...
static bool MultiPollsetWantsWrite(CURLM *multi, curl_socket_t socket)
Inspect one socket's current writable interest through curl's public multi fdset API.
void ApplyPendingBackpressure()
Apply the BackpressureConfig cached by DriveScenario to the new connection_.
MultiObserver multi_observer_
Observer receiving the live multi handle at the start of a drive.
Owns the socket and timer callback state required by libcurl's event-driven multi API.
bool Install(CURLM *multi)
Install socket/timer callbacks on a caller-owned multi before adding an easy handle.
Builds HTTP headers or TELNET options, MIME state, and upload callbacks whose storage or userdata lib...
Exclude a narrow harness-only operation from the allocation-failure plan.
Common ownership types for independently cleaned-up curl handles.
std::unique_ptr< CURLM, CurlMultiDeleter > CurlMultiPtr
Unique ownership of a CURL multi handle.
Definition curl_raii.h:58
MockConnection and MockServer — the in-process peer that feeds canned response bytes to libcurl over ...
MockServerBase — common plumbing shared by every protocol-specific mock server.
SocketSetupDisposition
Tell curl whether an application-provided socket still needs its normal connect/options setup or alre...
MultiDrivePolicy
Fixed policy for interpreting a scenario's multi-drive fields.
@ kFromApiPlan
Read the multi-loop choice and probes from Scenario::api_plan.
@ kSocketAction
Use the callback-driven curl_multi_socket_action loop.
Bounded event-loop state for driving curl_multi_socket_action.
Results from the bounded CONNECT_ONLY public-API probe.
CURLcode connect_result
Result of establishing the connect-only transport.