14#include <netinet/in.h>
15#include <sys/socket.h>
30namespace proto_fuzzer {
38constexpr std::size_t kMaxConnectAttempts = 16;
42constexpr std::size_t kMaxAcceptedPeers = 12;
43constexpr int kMaxAcceptsPerCall = 8;
48constexpr int kFirstServeHostByte = 2;
49constexpr int kLastServeHostByte = 9;
55constexpr int kRaceBacklog = 1;
56constexpr int kRaceFillConnections = 2;
58constexpr std::size_t kReadBufferSize = 4096;
61void CloseFd(
int* fd) {
71bool MakeNonBlocking(
int fd,
bool require_selectable) {
72 if (require_selectable && fd >= FD_SETSIZE) {
75 const int flags = fcntl(fd, F_GETFL, 0);
76 return flags >= 0 && fcntl(fd, F_SETFL, flags | O_NONBLOCK) >= 0;
81std::size_t RawChunkCount(
const curl::fuzzer::proto::Connection& script) {
89struct sockaddr_in LoopbackAddress(int host_byte, std::uint16_t port) {
90 struct sockaddr_in addr;
91 std::memset(&addr, 0,
sizeof(addr));
92 addr.sin_family = AF_INET;
93 addr.sin_addr.s_addr = htonl(0x7f000000U |
static_cast<std::uint32_t
>(host_byte));
106 opened_socket_count_(0),
107 accepted_peer_count_(0),
108 scenario_(nullptr) {}
115 for (Peer& peer : peers_) {
129 setup_ok_ = OpenListeners();
130 if (easy_ !=
nullptr && setup_ok_) {
131 setup_ok_ = curl_easy_setopt(easy_, CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS, 0L) == CURLE_OK;
145 if (easy_ ==
nullptr || listener_port_ <= 0 || serve_address_.empty()) {
149 const std::string mapping =
"resolve.test:" + std::to_string(listener_port_) +
":127.0.0.1," + serve_address_;
152 curl_slist* entry = curl_slist_append(
nullptr, mapping.c_str());
153 if (entry ==
nullptr) {
156 resolve_list.reset(entry);
157 if (curl_easy_setopt(easy_, CURLOPT_RESOLVE, resolve_list.get()) != CURLE_OK) {
160 resolve_list_ = std::move(resolve_list);
161 if (curl_easy_setopt(easy_, CURLOPT_PORT,
static_cast<long>(listener_port_)) != CURLE_OK) {
175 if (purpose != CURLSOCKTYPE_IPCXN || address ==
nullptr || opened_socket_count_ >= kMaxConnectAttempts) {
176 return CURL_SOCKET_BAD;
178 if (!IsOwnedDestination(address)) {
179 return CURL_SOCKET_BAD;
182 curl_socket_t fd = socket(address->family, address->socktype, address->protocol);
183 if (fd == CURL_SOCKET_BAD) {
184 return CURL_SOCKET_BAD;
186 if (!MakeNonBlocking(fd,
true)) {
188 return CURL_SOCKET_BAD;
190 ++opened_socket_count_;
200 curlsocktype )
const {
201 return SocketSetupDisposition::kNeedsSetup;
209bool HappyEyeballsMockServer::IsOwnedDestination(
const struct curl_sockaddr* address)
const {
210 if (address ==
nullptr || address->family != AF_INET || listener_port_ <= 0) {
213 const struct sockaddr_in* sin =
reinterpret_cast<const struct sockaddr_in*
>(&address->addr);
214 const std::uint32_t host = ntohl(sin->sin_addr.s_addr);
215 return (host & 0xff000000U) == 0x7f000000U && sin->sin_port == htons(
static_cast<std::uint16_t
>(listener_port_));
230 scenario_ = &scenario;
232 int still_running = 1;
233 int idle_iterations = 0;
234 int drive_iterations = 0;
235 CURLMcode rc = CURLM_OK;
238 bool made_progress = AcceptPending();
239 made_progress = ServicePeers() || made_progress;
241 const int running_before = still_running;
242 rc = curl_multi_perform(multi, &still_running);
243 if (rc != CURLM_OK) {
246 made_progress = still_running != running_before || made_progress;
247 made_progress = AcceptPending() || made_progress;
248 made_progress = ServicePeers() || made_progress;
249 if (!still_running) {
258 CURLMcode wait_rc = CURLM_OK;
268 for (Peer& peer : peers_) {
279bool HappyEyeballsMockServer::OpenListeners() {
282 const int race_fd = socket(AF_INET, SOCK_STREAM, 0);
283 if (race_fd < 0 || race_fd >= FD_SETSIZE) {
291 (void)setsockopt(race_fd, SOL_SOCKET, SO_REUSEADDR, &reuse,
sizeof(reuse));
293 struct sockaddr_in race_addr = LoopbackAddress(1, 0);
294 if (bind(race_fd,
reinterpret_cast<const struct sockaddr*
>(&race_addr),
sizeof(race_addr)) != 0 ||
295 listen(race_fd, kRaceBacklog) != 0) {
300 socklen_t addrlen =
sizeof(race_addr);
301 if (getsockname(race_fd,
reinterpret_cast<struct sockaddr*
>(&race_addr), &addrlen) != 0 ||
302 !MakeNonBlocking(race_fd,
true)) {
307 listener_port_ =
static_cast<int>(ntohs(race_addr.sin_port));
308 const std::uint16_t network_port = race_addr.sin_port;
310 for (
int host_byte = kFirstServeHostByte; host_byte <= kLastServeHostByte; ++host_byte) {
311 const int serve_fd = socket(AF_INET, SOCK_STREAM, 0);
312 if (serve_fd < 0 || serve_fd >= FD_SETSIZE) {
318 (void)setsockopt(serve_fd, SOL_SOCKET, SO_REUSEADDR, &reuse,
sizeof(reuse));
320 struct sockaddr_in serve_addr = LoopbackAddress(host_byte, network_port);
321 if (bind(serve_fd,
reinterpret_cast<const struct sockaddr*
>(&serve_addr),
sizeof(serve_addr)) == 0 &&
322 listen(serve_fd, 8) == 0 && MakeNonBlocking(serve_fd,
true)) {
323 serve_fd_ = serve_fd;
324 serve_address_ =
"127.0.0." + std::to_string(host_byte);
338 struct timeval connect_timeout;
339 connect_timeout.tv_sec = 0;
340 connect_timeout.tv_usec = 10000;
341 const struct sockaddr_in race_target = LoopbackAddress(1, network_port);
342 for (
int index = 0; index < kRaceFillConnections; ++index) {
343 const int fd = socket(AF_INET, SOCK_STREAM, 0);
344 if (fd < 0 || fd >= FD_SETSIZE) {
350 (void)setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &connect_timeout,
sizeof(connect_timeout));
353 connect_result = connect(fd,
reinterpret_cast<const struct sockaddr*
>(&race_target),
sizeof(race_target));
354 }
while (connect_result != 0 && errno == EINTR);
355 if (connect_result != 0) {
359 dummy_fds_.push_back(fd);
366void HappyEyeballsMockServer::CloseListeners() {
367 for (
int& fd : dummy_fds_) {
374 serve_address_.clear();
381bool HappyEyeballsMockServer::AcceptPending() {
382 bool accepted_any =
false;
384 int accepted_this_call = 0;
385 while (accepted_this_call < kMaxAcceptsPerCall) {
386 const int fd = accept(serve_fd_,
nullptr,
nullptr);
388 if (errno == EINTR) {
393 ++accepted_this_call;
395 ++accepted_peer_count_;
397 if (!MakeNonBlocking(fd,
true) || peers_.size() >= kMaxAcceptedPeers) {
402 const curl::fuzzer::proto::Connection* script = ScriptForAccept(accepted_peer_count_ - 1);
403 if (script ==
nullptr) {
410 peer.script = script;
411 peer.pending = script->initial_response();
412 peers_.push_back(std::move(peer));
420bool HappyEyeballsMockServer::ServicePeers() {
421 bool made_progress =
false;
423 for (std::size_t index = 0; index < peers_.size();) {
424 Peer& peer = peers_[index];
425 bool keep_peer = peer.fd >= 0;
427 unsigned char scratch[kReadBufferSize];
429 const ssize_t count = recv(peer.fd, scratch,
sizeof(scratch), 0);
431 made_progress =
true;
436 made_progress =
true;
437 }
else if (errno == EINTR) {
439 }
else if (errno != EAGAIN && errno != EWOULDBLOCK) {
441 made_progress =
true;
446 while (keep_peer && peer.pending_offset >= peer.pending.size()) {
447 peer.pending.clear();
448 peer.pending_offset = 0;
449 if (peer.script !=
nullptr && peer.next_chunk < RawChunkCount(*peer.script)) {
450 peer.pending = peer.script->on_readable(
static_cast<int>(peer.next_chunk++));
451 if (!peer.pending.empty()) {
459 while (keep_peer && peer.pending_offset < peer.pending.size()) {
460 const ssize_t count = send(peer.fd, peer.pending.data() + peer.pending_offset,
461 peer.pending.size() - peer.pending_offset, MSG_NOSIGNAL);
463 peer.pending_offset +=
static_cast<std::size_t
>(count);
464 made_progress =
true;
465 }
else if (count < 0 && errno == EINTR) {
467 }
else if (count < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
471 made_progress =
true;
475 if (keep_peer && peer.pending_offset >= peer.pending.size() &&
476 (peer.script ==
nullptr || peer.next_chunk >= RawChunkCount(*peer.script)) && !peer.sent_final) {
477 (void)shutdown(peer.fd, SHUT_WR);
478 peer.sent_final =
true;
479 made_progress =
true;
484 peers_.erase(peers_.begin() +
static_cast<std::ptrdiff_t
>(index));
489 return made_progress;
493void HappyEyeballsMockServer::ClosePeer(Peer* peer) {
494 if (peer !=
nullptr) {
501const curl::fuzzer::proto::Connection* HappyEyeballsMockServer::ScriptForAccept(std::size_t index)
const {
502 if (scenario_ ==
nullptr) {
511 return &scenario_->connection();
513 const std::size_t subsequent_index = index - 1;
514 if (subsequent_index <
static_cast<std::size_t
>(scenario_->subsequent_connections_size()) &&
515 subsequent_index < scenario_limits::kMaxConnections - 1) {
516 return &scenario_->subsequent_connections(
static_cast<int>(subsequent_index));
void RunLoop(CURLM *multi, CURL *easy, const curl::fuzzer::proto::Scenario &scenario) override
Run a bounded perform loop that also accepts real loopback connections and serves their response scri...
curl_socket_t HandleOpenSocket(curlsocktype purpose, struct curl_sockaddr *address) override
Hand curl a fresh unconnected socket for every address its happy-eyeballs filter decides to race.
~HappyEyeballsMockServer() override
Release listeners and accepted loopback peers.
void Install(CURL *easy) override
Install the shared socket callbacks and remember the easy handle for the later resolve/port/timeout s...
HappyEyeballsMockServer()
Construct an idle peer with no listener or accepted connections.
SocketSetupDisposition GetSocketSetupDisposition(curl_socket_t curlfd, curlsocktype purpose) const override
Stream peers must follow curl's ordinary connect path; unlike the socketpair mocks,...
void ConfigureRequestData(ScenarioRequestData *request_data) override
Replace the resolver lane's single-address mapping with a two-address entry for the fixed canonical h...
virtual void Install(CURL *easy)
Install the common OPENSOCKETFUNCTION / OPENSOCKETDATA / SOCKOPTFUNCTION callbacks on 'easy'.
static int WaitOnMultiFdset(CURLM *multi, CURLMcode *rc)
Wait on curl's fdset with a short timeout.
static constexpr int kMaxDriveIterations
Hard operation budget for one scenario.
static constexpr int kMaxIdleIterations
Consecutive no-progress budget for ordinary scenarios.
Builds HTTP headers or TELNET options, MIME state, and upload callbacks whose storage or userdata lib...
std::unique_ptr< curl_slist, CurlSlistDeleter > CurlSlistPtr
Unique ownership of a caller-owned curl_slist.
HappyEyeballsMockServer — a loopback peer that lets curl's happy-eyeballs connection filter race seve...
SocketSetupDisposition
Tell curl whether an application-provided socket still needs its normal connect/options setup or alre...
Shared complexity limits for structured scenarios.
constexpr std::size_t kMaxResponseChunks
Enough response boundaries to exercise incremental parsers while keeping per-perform delivery work fi...