13#include <sys/socket.h>
22namespace proto_fuzzer {
27 : response_datagrams_(),
28 response_datagram_count_(0),
29 next_response_datagram_(0),
33 client_address_length_(0),
34 has_client_address_(false),
35 socket_opened_(false),
38 received_datagrams_() {}
67 curlsocktype )
const {
68 return SocketSetupDisposition::kNeedsSetup;
77void TftpMockServer::PrepareScript(
const curl::fuzzer::proto::Connection& connection) {
78 response_datagrams_.fill(
nullptr);
79 response_datagram_count_ = 0;
80 next_response_datagram_ = 0;
83 response_datagrams_[response_datagram_count_++] = &
connection.initial_response();
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));
96void TftpMockServer::ResetPeer() {
97 if (request_fd_ >= 0) {
98 (void)::close(request_fd_);
101 if (transfer_fd_ >= 0) {
102 (void)::close(transfer_fd_);
105 std::memset(&client_address_, 0,
sizeof(client_address_));
106 client_address_length_ = 0;
107 has_client_address_ =
false;
108 socket_opened_ =
false;
118bool TftpMockServer::ConfigureSocket(
int fd) {
122 const int descriptor_flags = ::fcntl(fd, F_GETFD, 0);
123 if (descriptor_flags < 0 || ::fcntl(fd, F_SETFD, descriptor_flags | FD_CLOEXEC) < 0) {
126 const int status_flags = ::fcntl(fd, F_GETFL, 0);
127 return status_flags >= 0 && ::fcntl(fd, F_SETFL, status_flags | O_NONBLOCK) == 0;
136int TftpMockServer::OpenLoopbackSocket(
struct sockaddr_in* bound_address) {
137 if (bound_address ==
nullptr) {
140 const int fd = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
141 if (fd < 0 || !ConfigureSocket(fd)) {
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) {
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) {
175bool TftpMockServer::RewriteDestination(
struct curl_sockaddr* address,
const struct sockaddr_in& destination) {
176 if (address ==
nullptr ||
sizeof(destination) >
sizeof(address->addr)) {
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));
197 if (purpose != CURLSOCKTYPE_IPCXN || address ==
nullptr || socket_opened_) {
198 return CURL_SOCKET_BAD;
200 socket_opened_ =
true;
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)) {
208 return CURL_SOCKET_BAD;
211 request_port_ = ntohs(request_address.sin_port);
212 transfer_port_ = ntohs(transfer_address.sin_port);
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);
220 return CURL_SOCKET_BAD;
222 return static_cast<curl_socket_t
>(client_fd);
230void TftpMockServer::RememberClientAddress(
const struct sockaddr_in& address, socklen_t length) {
231 if (has_client_address_ || length !=
sizeof(address) || address.sin_family != AF_INET) {
234 client_address_ = address;
235 client_address_length_ = length;
236 has_client_address_ =
true;
245std::size_t TftpMockServer::DrainSocket(
int fd,
TftpSocketRole role) {
250 std::array<unsigned char, 65536> packet;
251 std::size_t count = 0;
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);
258 if (errno == EINTR) {
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));
283std::size_t TftpMockServer::DrainClientDatagrams() {
284 return DrainSocket(request_fd_, TftpSocketRole::kRequest) + DrainSocket(transfer_fd_, TftpSocketRole::kTransfer);
292bool TftpMockServer::SendNextDatagram() {
293 if (!has_client_address_ || transfer_fd_ < 0 || next_response_datagram_ >= response_datagram_count_) {
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_);
314 PrepareScript(scenario.connection());
315 received_datagrams_.clear();
316 received_datagrams_.reserve(kMaxCapturedDatagrams);
318 int still_running = 1;
319 int idle_iterations = 0;
321 const CURLMcode result = curl_multi_perform(multi, &still_running);
322 if (result != CURLM_OK) {
326 const bool received = DrainClientDatagrams() != 0;
327 const bool sent = still_running != 0 && received && SendNextDatagram();
328 if (received || sent) {
342#if defined(PROTO_FUZZER_OBSERVE_STATE)
345 (void)tftp_observation;
MockConnection * connection()
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.