XRootD
Loading...
Searching...
No Matches
XrdHttpTpcTPC.cc
Go to the documentation of this file.
4#include "XrdOuc/XrdOucEnv.hh"
8#include "XrdSys/XrdSysFD.hh"
9#include "XrdVersion.hh"
10
15
16#include <curl/curl.h>
17
18#include <dlfcn.h>
19#include <fcntl.h>
20
21#include <algorithm>
22#include <memory>
23#include <sstream>
24#include <stdexcept>
25#include <thread>
26
27#include "XrdHttpTpcState.hh"
28#include "XrdHttpTpcStream.hh"
29#include "XrdHttpTpcTPC.hh"
30#include <fstream>
31
32using namespace TPC;
33
34XrdXrootdTpcMon* TPCHandler::TPCLogRecord::tpcMonitor = 0;
35
36uint64_t TPCHandler::m_monid{0};
37int TPCHandler::m_marker_period = 5;
38size_t TPCHandler::m_block_size = 16*1024*1024;
39size_t TPCHandler::m_small_block_size = 1*1024*1024;
40XrdSysMutex TPCHandler::m_monid_mutex;
41bool TPCHandler::allowMissingCRL = false;
42
44
45/******************************************************************************/
46/* T P C H a n d l e r : : T P C L o g R e c o r d D e s t r u c t o r */
47/******************************************************************************/
48
49TPCHandler::TPCLogRecord::~TPCLogRecord()
50{
51// Record monitoring data is enabled
52//
53 if (tpcMonitor)
55
56 monInfo.clID = clID.c_str();
57 monInfo.begT = begT;
58 gettimeofday(&monInfo.endT, 0);
59
60 if (mTpcType == TpcType::Pull)
61 {monInfo.dstURL = local.c_str();
62 monInfo.srcURL = remote.c_str();
63 } else {
64 monInfo.dstURL = remote.c_str();
65 monInfo.srcURL = local.c_str();
67 }
68
69 if (!status) monInfo.endRC = 0;
70 else if (tpc_status > 0) monInfo.endRC = tpc_status;
71 else monInfo.endRC = 1;
72 monInfo.strm = static_cast<unsigned char>(streams);
73 monInfo.fSize = (bytes_transferred < 0 ? 0 : bytes_transferred);
74 if (!isIPv6) monInfo.opts |= XrdXrootdTpcMon::TpcInfo::isIPv4;
75
76 tpcMonitor->Report(monInfo);
77 }
78}
79
80/******************************************************************************/
81/* C u r l D e l e t e r : : o p e r a t o r ( ) */
82/******************************************************************************/
83
85{
86 if (curl) curl_easy_cleanup(curl);
87}
88
89/******************************************************************************/
90/* s o c k o p t _ s e t c l o e x e c _ c a l l b a c k */
91/******************************************************************************/
92
101int TPCHandler::sockopt_callback(void *clientp, curl_socket_t curlfd, curlsocktype purpose) {
102 TPCLogRecord * rec = (TPCLogRecord *)clientp;
103 if (purpose == CURLSOCKTYPE_IPCXN && rec && rec->pmarkManager.isEnabled()) {
104 // We will not reach this callback if the corresponding socket could not have been connected
105 // the socket is already connected only if the packet marking is enabled
106 return CURL_SOCKOPT_ALREADY_CONNECTED;
107 }
108 return CURL_SOCKOPT_OK;
109}
110
111/******************************************************************************/
112/* o p e n s o c k e t _ c a l l b a c k */
113/******************************************************************************/
114
115
120int TPCHandler::opensocket_callback(void *clientp,
121 curlsocktype purpose,
122 struct curl_sockaddr *aInfo)
123{
124 /* CURLSOCKTYPE_IPCXN (for IP based connections) is the only type currently known by curl,
125 * so let's make sure to reject other types if they appear in the furure */
126 if (purpose != CURLSOCKTYPE_IPCXN)
127 return CURL_SOCKET_BAD;
128
129 if (!aInfo)
130 return CURL_SOCKET_BAD;
131
132 // Create the socket (note that O_CLOEXEC flag will be set)
133 int fd = XrdSysFD_Socket(aInfo->family, aInfo->socktype, aInfo->protocol);
134
135 if (fd < 0) {
136 return CURL_SOCKET_BAD;
137 }
138
139 if (!clientp)
140 return fd;
141
142 XrdNetAddr thePeer(&(aInfo->addr));
143 TPCLogRecord *rec = static_cast<TPCLogRecord*>(clientp);
144
145 /* Reject attempts to connect to local/private addresses unless allowed by configuration */
146 if ((!rec->allow_private && thePeer.isPrivate()) || (!rec->allow_local && thePeer.isLocal())) {
147 rec->tpc_status = 403; // Forbidden
148 rec->m_log->Emsg(rec->log_prefix.c_str(),
149 "Connection to local/private address is forbidden");
150 close(fd);
151 return CURL_SOCKET_BAD;
152 }
153
154 rec->isIPv6 = (thePeer.isIPType(XrdNetAddrInfo::IPv6) && !thePeer.isMapped());
155
156 std::stringstream connectErrMsg;
157 if(!rec->pmarkManager.connect(fd, &(aInfo->addr), aInfo->addrlen, CONNECT_TIMEOUT, connectErrMsg)) {
158 rec->m_log->Emsg(rec->log_prefix.c_str(), "Unable to connect socket: ", connectErrMsg.str().c_str());
159 close(fd);
160 return CURL_SOCKET_BAD;
161 }
162
163 return fd;
164}
165
166int TPCHandler::closesocket_callback(void *clientp, curl_socket_t fd) {
167 TPCLogRecord * rec = (TPCLogRecord *)clientp;
168
169 // Destroy the PMark handle associated to the file descriptor before closing it.
170 // Otherwise, we would lose the socket usage information if the socket is closed before
171 // the PMark handle is closed.
172 rec->pmarkManager.endPmark(fd);
173
174 return close(fd);
175}
176
177/******************************************************************************/
178/* s s l _ c t x _ c a l l b a c k */
179/******************************************************************************/
180
187int TPCHandler::ssl_ctx_callback(CURL *curl, void *ssl_ctx, void *clientp) {
188 TPCLogRecord * rec = (TPCLogRecord *)clientp;
189 SSL_CTX* ctx = static_cast<SSL_CTX*>(ssl_ctx);
190
191 if (rec && rec->ca_store) {
192 // Bumps the store's reference count instead of re-parsing the CA and CRL
193 // bundles for this connection. libcurl runs this callback after it has
194 // applied its own TLS options, so this replaces whatever store it built.
195 SSL_CTX_set1_cert_store(ctx, rec->ca_store.get());
196 }
197 if (allowMissingCRL) {
198 // verify_callback only excuses X509_V_ERR_UNABLE_TO_GET_CRL, i.e. a CA in
199 // the chain for which no CRL could be found. Every other verification rule
200 // still applies, including revocation itself whenever a CRL is present.
201 SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_callback);
202 }
203 return CURLE_OK;
204}
205
206int TPCHandler::verify_callback(int preverify_ok, X509_STORE_CTX* ctx) {
207 if (preverify_ok == 1) return 1;
208
209 int err = X509_STORE_CTX_get_error(ctx);
210
211 if (err == X509_V_ERR_UNABLE_TO_GET_CRL) {
212 X509_STORE_CTX_set_error(ctx, X509_V_OK);
213 return 1;
214 }
215
216 return 0;
217}
218
219/******************************************************************************/
220/* p r e p a r e U R L */
221/******************************************************************************/
222
223// See XrdHttpTpcUtils::prepareOpenURL() documentation
224std::string TPCHandler::prepareURL(XrdHttpExtReq &req) {
225 return XrdHttpTpcUtils::prepareOpenURL(req.resource, req.headers,hdr2cgimap);
226}
227
228/******************************************************************************/
229/* e n c o d e _ x r o o t d _ o p a q u e _ t o _ u r i */
230/******************************************************************************/
231
232// When processing a redirection from the filesystem layer, it is permitted to return
233// some xrootd opaque data. The quoting rules for xrootd opaque data are significantly
234// more permissive than a URI (basically, only '&' and '=' are disallowed while some
235// URI parsers may dislike characters like '"'). This function takes an opaque string
236// (e.g., foo=1&bar=2&baz=") and makes it safe for all URI parsers.
237std::string encode_xrootd_opaque_to_uri(CURL *curl, const std::string &opaque)
238{
239 std::stringstream parser(opaque);
240 std::string sequence;
241 std::stringstream output;
242 bool first = true;
243 while (getline(parser, sequence, '&')) {
244 if (sequence.empty()) {continue;}
245 size_t equal_pos = sequence.find('=');
246 char *val = NULL;
247 if (equal_pos != std::string::npos)
248 val = curl_easy_escape(curl, sequence.c_str() + equal_pos + 1, sequence.size() - equal_pos - 1);
249 // Do not emit parameter if value exists and escaping failed.
250 if (!val && equal_pos != std::string::npos) {continue;}
251
252 if (!first) output << "&";
253 first = false;
254 output << sequence.substr(0, equal_pos);
255 if (val) {
256 output << "=" << val;
257 curl_free(val);
258 }
259 }
260 return output.str();
261}
262
263/******************************************************************************/
264/* T P C H a n d l e r : : C o n f i g u r e C u r l C A */
265/******************************************************************************/
266
267bool
268TPCHandler::ConfigureCurlCA(CURL *curl, TPCLogRecord &rec)
269{
270 // Preferred path: hand libcurl the CA/CRL store that XrdTlsTempCA already
271 // parsed, rather than the bundle filenames. Passing filenames makes libcurl
272 // build a private X509_STORE per connection, which costs tens of MB for a grid
273 // CA directory and is held for the whole transfer; sharing one store makes that
274 // a reference count. See https://github.com/xrootd/xrootd/issues/2873
275 //
276 // Skipped when m_cafile is set, so that the http.cafile precedence established
277 // at the bottom of this function is preserved.
278 if (m_ca_file && m_sslctx_supported && m_cafile.empty()) {
279 rec.ca_store = m_ca_file->CAStore();
280 if (!rec.ca_store) {
281 m_log.Log(Error, "TpcHandler", "No CA store is available; refusing to "
282 "fall back to libcurl's default CA bundle");
283 return false;
284 }
285 // Stop libcurl loading its build-time default bundle, which the callback
286 // below would only discard; the callback supplies the trust anchors.
287 curl_easy_setopt(curl, CURLOPT_CAINFO, static_cast<char *>(nullptr));
288 curl_easy_setopt(curl, CURLOPT_CAPATH, static_cast<char *>(nullptr));
289 curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, ssl_ctx_callback);
290 curl_easy_setopt(curl, CURLOPT_SSL_CTX_DATA, &rec);
291 return true;
292 }
293
294 auto ca_filename = m_ca_file ? m_ca_file->CAFilename() : "";
295 auto crl_filename = m_ca_file ? m_ca_file->CRLFilename() : "";
296 if (!ca_filename.empty() && !crl_filename.empty()) {
297 curl_easy_setopt(curl, CURLOPT_CAINFO, ca_filename.c_str());
298 //Check that the CRL file contains at least one entry before setting this option to curl
299 //Indeed, an empty CRL file will make curl unhappy and therefore will fail
300 //all HTTP TPC transfers (https://github.com/xrootd/xrootd/issues/1543)
301 std::ifstream in(crl_filename, std::ifstream::ate | std::ifstream::binary);
302 if(in.tellg() > 0 && m_ca_file->atLeastOneValidCRLFound()){
303 curl_easy_setopt(curl, CURLOPT_CRLFILE, crl_filename.c_str());
304 if (allowMissingCRL) {
305 // No need to set the callback if there is no need to do it
306 curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, ssl_ctx_callback);
307 }
308 } else {
309 std::ostringstream oss;
310 oss << "No valid CRL file has been found in the file " << crl_filename << ". Disabling CRL checking.";
311 m_log.Log(Warning,"TpcHandler",oss.str().c_str());
312 }
313 }
314 else if (!m_cadir.empty()) {
315 curl_easy_setopt(curl, CURLOPT_CAPATH, m_cadir.c_str());
316 }
317 if (!m_cafile.empty()) {
318 curl_easy_setopt(curl, CURLOPT_CAINFO, m_cafile.c_str());
319 }
320 return true;
321}
322
323void
324TPCHandler::ConfigureCurlLowSpeed(CURL *curl)
325{
326 // Older versions have poor transfer performance when low-speed limits are
327 // enabled; this was corrected in curl commit cacdc27f for version 7.38.0.
328 curl_version_info_data *curl_ver = curl_version_info(CURLVERSION_NOW);
329 if (m_low_speed_limit > 0 && curl_ver && curl_ver->age > 0 &&
330 curl_ver->version_num >= 0x072600) {
331 curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, m_low_speed_time);
332 curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, m_low_speed_limit);
333 }
334}
335
336
337bool TPCHandler::MatchesPath(const char *verb, const char *path) {
338 return !strcmp(verb, "COPY") || !strcmp(verb, "OPTIONS");
339}
340
341/******************************************************************************/
342/* P r e p a r e U R L */
343/******************************************************************************/
344
345static std::string PrepareURL(const std::string &url)
346{
347 const std::string replace_schemes[] = { "davs://", "s3://", "s3s://" };
348
349 for (const auto& s : replace_schemes)
350 if (url.compare(0, s.size(), s) == 0)
351 return "https://" + url.substr(s.size());
352
353 return url;
354}
355
356static bool IsAllowedScheme(const std::string& url)
357{
358 const std::string allowed_schemes[] = { "https://", "http://" };
359
360 for (const auto& s : allowed_schemes)
361 if (url.compare(0, s.size(), s) == 0)
362 return true;
363
364 return false;
365}
366
367/******************************************************************************/
368/* T P C H a n d l e r : : P r o c e s s R e q */
369/******************************************************************************/
370
372 if (req.verb == "OPTIONS") {
373 return ProcessOptionsReq(req);
374 }
375 auto header = XrdOucTUtils::caseInsensitiveFind(req.headers,"credential");
376 if (header != req.headers.end()) {
377 if (header->second != "none") {
378 m_log.Emsg("ProcessReq", "COPY requested an unsupported credential type: ", header->second.c_str());
379 return req.SendSimpleResp(400, NULL, NULL, "COPY requestd an unsupported Credential type", 0);
380 }
381 }
382 header = XrdOucTUtils::caseInsensitiveFind(req.headers,"source");
383 if (header != req.headers.end()) {
384 std::string src = PrepareURL(header->second);
385 if (!IsAllowedScheme(src)) {
386 const char *error_src = "COPY rejected: disallowed scheme in source URL";
387 m_log.Emsg("ProcessReq", error_src, src.c_str());
388 return req.SendSimpleResp(400, NULL, NULL, error_src, 0);
389 }
390 return ProcessPullReq(src, req);
391 }
392 header = XrdOucTUtils::caseInsensitiveFind(req.headers,"destination");
393 if (header != req.headers.end()) {
394 const std::string& dst = header->second;
395 if (!IsAllowedScheme(dst)) {
396 const char *error_dst = "COPY rejected: disallowed scheme in destination URL";
397 m_log.Emsg("ProcessReq", error_dst, dst.c_str());
398 return req.SendSimpleResp(400, NULL, NULL, error_dst, 0);
399 }
400 return ProcessPushReq(header->second, req);
401 }
402 m_log.Emsg("ProcessReq", "COPY verb requested but no source or destination specified.");
403 return req.SendSimpleResp(400, NULL, NULL, "No Source or Destination specified", 0);
404}
405
406/******************************************************************************/
407/* T P C H a n d l e r D e s t r u c t o r */
408/******************************************************************************/
409
411 m_sfs = NULL;
412}
413
414/******************************************************************************/
415/* T P C H a n d l e r C o n s t r u c t o r */
416/******************************************************************************/
417
418TPCHandler::TPCHandler(XrdSysError *log, const char *config, XrdOucEnv *myEnv) :
419 m_allow_local(false),
420 m_allow_private(true),
421 m_desthttps(false),
422 m_fixed_route(false),
423 m_low_speed_limit(10*1024),
424 m_low_speed_time(2*60),
425 m_timeout(60),
426 m_first_timeout(120),
427 m_log(log->logger(), "TPC_"),
428 m_sfs(NULL)
429{
430 if (!Configure(config, myEnv)) {
431 throw std::runtime_error("Failed to configure the HTTP third-party-copy handler.");
432 }
433
434// Extract out the TPC monitoring object (we share it with xrootd).
435//
436 XrdXrootdGStream *gs = (XrdXrootdGStream*)myEnv->GetPtr("Tpc.gStream*");
437 if (gs)
438 TPCLogRecord::tpcMonitor = new XrdXrootdTpcMon("http",log->logger(),*gs);
439}
440
441/******************************************************************************/
442/* T P C H a n d l e r : : P r o c e s s O p t i o n s R e q */
443/******************************************************************************/
444
448int TPCHandler::ProcessOptionsReq(XrdHttpExtReq &req) {
449 return req.SendSimpleResp(200, NULL, (char *) "DAV: 1\r\nDAV: <http://apache.org/dav/propset/fs/1>\r\nAllow: HEAD,GET,PUT,PROPFIND,DELETE,OPTIONS,COPY", NULL, 0);
450}
451
452/******************************************************************************/
453/* T P C H a n d l e r : : G e t A u t h z */
454/******************************************************************************/
455
456std::string TPCHandler::GetAuthz(XrdHttpExtReq &req) {
457 std::string authz;
458 auto authz_header = XrdOucTUtils::caseInsensitiveFind(req.headers,"authorization");
459 if (authz_header != req.headers.end()) {
460 std::stringstream ss;
461 ss << "authz=" << encode_str(authz_header->second);
462 authz += ss.str();
463 }
464 return authz;
465}
466
467/******************************************************************************/
468/* T P C H a n d l e r : : R e d i r e c t T r a n s f e r */
469/******************************************************************************/
470
471int TPCHandler::RedirectTransfer(CURL *curl, const std::string &redirect_resource,
472 XrdHttpExtReq &req, XrdOucErrInfo &error, TPCLogRecord &rec)
473{
474 int port;
475 const char *ptr = error.getErrText(port);
476 if ((ptr == NULL) || (*ptr == '\0') || (port == 0)) {
477 rec.status = 500;
478 std::stringstream ss;
479 ss << "Internal error: redirect without hostname";
480 logTransferEvent(LogMask::Error, rec, "REDIRECT_INTERNAL_ERROR", ss.str());
481 return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
482 }
483
484 // Construct redirection URL taking into consideration any opaque info
485 std::string rdr_info = ptr;
486 std::string host, opaque;
487 size_t pos = rdr_info.find('?');
488 host = rdr_info.substr(0, pos);
489
490 if (pos != std::string::npos) {
491 opaque = rdr_info.substr(pos + 1);
492 }
493
494 std::stringstream ss;
495 ss << "Location: http" << (m_desthttps ? "s" : "") << "://" << host << ":" << port << "/" << redirect_resource;
496
497 if (!opaque.empty()) {
498 ss << "?" << encode_xrootd_opaque_to_uri(curl, opaque);
499 }
500
501 rec.status = 307;
502 logTransferEvent(LogMask::Info, rec, "REDIRECT", ss.str());
503 return req.SendSimpleResp(rec.status, NULL, const_cast<char *>(ss.str().c_str()),
504 NULL, 0);
505}
506
507/******************************************************************************/
508/* T P C H a n d l e r : : O p e n W a i t S t a l l */
509/******************************************************************************/
510
511int TPCHandler::OpenWaitStall(XrdSfsFile &fh, const std::string &resource,
512 int mode, int openMode, const XrdSecEntity &sec,
513 const std::string &authz)
514{
515 int open_result;
516 while (1) {
517 int orig_ucap = fh.error.getUCap();
518 fh.error.setUCap(orig_ucap | XrdOucEI::uIPv64);
519 std::string opaque;
520 size_t pos = resource.find('?');
521 // Extract the path and opaque info from the resource
522 std::string path = resource.substr(0, pos);
523
524 if (pos != std::string::npos) {
525 opaque = resource.substr(pos + 1);
526 }
527
528 // Append the authz information if there are some
529 if(!authz.empty()) {
530 opaque += (opaque.empty() ? "" : "&");
531 opaque += authz;
532 }
533 open_result = fh.open(path.c_str(), mode, openMode, &sec, opaque.c_str());
534
535 if ((open_result == SFS_STALL) || (open_result == SFS_STARTED)) {
536 int secs_to_stall = fh.error.getErrInfo();
537 if (open_result == SFS_STARTED) {secs_to_stall = secs_to_stall/2 + 5;}
538 std::this_thread::sleep_for (std::chrono::seconds(secs_to_stall));
539 }
540 break;
541 }
542 return open_result;
543}
544
545/******************************************************************************/
546/* T P C H a n d l e r : : D e t e r m i n e X f e r S i z e */
547/******************************************************************************/
548
549
550
554int TPCHandler::DetermineXferSize(CURL *curl, XrdHttpExtReq &req, State &state,
555 bool &success, TPCLogRecord &rec, bool shouldReturnErrorToClient) {
556 success = false;
557 curl_easy_setopt(curl, CURLOPT_NOBODY, 1);
558 // Set a custom timeout of 60 seconds (= CONNECT_TIMEOUT for convenience) for the HEAD request
559 curl_easy_setopt(curl, CURLOPT_TIMEOUT, CONNECT_TIMEOUT);
560 CURLcode res;
561 res = curl_easy_perform(curl);
562 //Immediately set the CURLOPT_NOBODY flag to 0 as we anyway
563 //don't want the next curl call to do be a HEAD request
564 curl_easy_setopt(curl, CURLOPT_NOBODY, 0);
565 // Reset the CURLOPT_TIMEOUT to no timeout (default)
566 curl_easy_setopt(curl, CURLOPT_TIMEOUT, 0L);
567 curl_easy_setopt(curl, CURLOPT_FAILONERROR, true);
568
569 std::stringstream ss;
570
571 if (state.GetStatusCode() >= 400)
572 res = CURLE_HTTP_RETURNED_ERROR;
573
574 if (res != CURLE_OK) { /* curl failed */
575 ss << curl_easy_strerror(res);
576 switch (res) {
577 case CURLE_HTTP_RETURNED_ERROR: /* remote side may have returned an error */
578 rec.tpc_status = state.GetStatusCode(); /* relay status received from remote side to the client */
579 ss << ": remote host returned '" << rec.tpc_status << " "
580 << httpStatusToString(rec.tpc_status) << "' while fetching file size";
581 break;
582 case CURLE_COULDNT_CONNECT: /* socket callback may have failed */
583 switch (rec.tpc_status) {
584 case 403:
585 ss << ": connection to local/private addresses is forbidden";
586 break;
587 default:
588 ss << ": internal server failure";
589 rec.tpc_status = 500;
590 }
591 break;
592 default:
593 rec.tpc_status = 500;
594 state.SetErrorCode(500);
595 }
596 }
597
598 if (rec.tpc_status >= 400) {
599 logTransferEvent(LogMask::Error, rec, "SIZE_FAIL", ss.str());
600 return shouldReturnErrorToClient ? req.SendSimpleResp(rec.tpc_status, NULL, NULL, generateClientErr(ss, rec, res).c_str(), 0) : -1;
601 }
602
603 success = true;
604 ss << "Successfully determined remote size for pull request: " << state.GetContentLength();
605 logTransferEvent(LogMask::Debug, rec, "SIZE_SUCCESS", ss.str());
606 return 0;
607}
608
609int TPCHandler::GetContentLengthTPCPull(CURL *curl, XrdHttpExtReq &req, uint64_t &contentLength, bool & success, TPCLogRecord &rec) {
610 State state(curl,req.tpcForwardCreds);
611 //Don't forget to copy the headers of the client's request before doing the HEAD call. Otherwise, if there is a need for authentication,
612 //it will fail
613 state.SetupHeaders(req);
614 int result;
615 //In case we cannot get the content length, we return the error to the client
616 if ((result = DetermineXferSize(curl, req, state, success, rec)) || !success) {
617 return result;
618 }
619 contentLength = state.GetContentLength();
620 return result;
621}
622
623/******************************************************************************/
624/* T P C H a n d l e r : : S e n d P e r f M a r k e r */
625/******************************************************************************/
626
627int TPCHandler::SendPerfMarker(XrdHttpExtReq &req, TPCLogRecord &rec, TPC::State &state) {
628 std::stringstream ss;
629 const std::string crlf = "\n";
630 ss << "Perf Marker" << crlf;
631 ss << "Timestamp: " << time(NULL) << crlf;
632 ss << "Stripe Index: 0" << crlf;
633 ss << "Stripe Bytes Transferred: " << state.BytesTransferred() << crlf;
634 ss << "Total Stripe Count: 1" << crlf;
635 // Include the TCP connection associated with this transfer; used by
636 // the TPC client for monitoring purposes.
637 std::string desc = state.GetConnectionDescription();
638 if (!desc.empty())
639 ss << "RemoteConnections: " << desc << crlf;
640 ss << "End" << crlf;
641 rec.bytes_transferred = state.BytesTransferred();
642 logTransferEvent(LogMask::Debug, rec, "PERF_MARKER");
643
644 return req.ChunkResp(ss.str().c_str(), 0);
645}
646
647/******************************************************************************/
648/* T P C H a n d l e r : : S e n d P e r f M a r k e r */
649/******************************************************************************/
650
651int TPCHandler::SendPerfMarker(XrdHttpExtReq &req, TPCLogRecord &rec, std::vector<State*> &state,
652 off_t bytes_transferred)
653{
654 // The 'performance marker' format is largely derived from how GridFTP works
655 // (e.g., the concept of `Stripe` is not quite so relevant here). See:
656 // https://twiki.cern.ch/twiki/bin/view/LCG/HttpTpcTechnical
657 // Example marker:
658 // Perf Marker\n
659 // Timestamp: 1537788010\n
660 // Stripe Index: 0\n
661 // Stripe Bytes Transferred: 238745\n
662 // Total Stripe Count: 1\n
663 // RemoteConnections: tcp:129.93.3.4:1234,tcp:[2600:900:6:1301:268a:7ff:fef6:a590]:2345\n
664 // End\n
665 //
666 std::stringstream ss;
667 const std::string crlf = "\n";
668 ss << "Perf Marker" << crlf;
669 ss << "Timestamp: " << time(NULL) << crlf;
670 ss << "Stripe Index: 0" << crlf;
671 ss << "Stripe Bytes Transferred: " << bytes_transferred << crlf;
672 ss << "Total Stripe Count: 1" << crlf;
673 // Build a list of TCP connections associated with this transfer; used by
674 // the TPC client for monitoring purposes.
675 bool first = true;
676 std::stringstream ss2;
677 for (std::vector<State*>::const_iterator iter = state.begin();
678 iter != state.end(); iter++)
679 {
680 std::string desc = (*iter)->GetConnectionDescription();
681 if (!desc.empty()) {
682 ss2 << (first ? "" : ",") << desc;
683 first = false;
684 }
685 }
686 if (!first)
687 ss << "RemoteConnections: " << ss2.str() << crlf;
688 ss << "End" << crlf;
689 rec.bytes_transferred = bytes_transferred;
690 logTransferEvent(LogMask::Debug, rec, "PERF_MARKER");
691
692 return req.ChunkResp(ss.str().c_str(), 0);
693}
694
695/******************************************************************************/
696/* T P C H a n d l e r : : R u n C u r l W i t h U p d a t e s */
697/******************************************************************************/
698
699int TPCHandler::RunCurlWithUpdates(CURL *curl, XrdHttpExtReq &req, State &state,
700 TPCLogRecord &rec)
701{
702 // Create the multi-handle and add in the current transfer to it.
703 CURLM *multi_handle = curl_multi_init();
704 if (!multi_handle) {
705 rec.status = 500;
706 logTransferEvent(LogMask::Error, rec, "CURL_INIT_FAIL",
707 "Failed to initialize a libcurl multi-handle");
708 std::stringstream ss;
709 ss << "Failed to initialize internal server memory";
710 return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
711 }
712
713 //curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 128*1024);
714
715 CURLMcode mres;
716 mres = curl_multi_add_handle(multi_handle, curl);
717 if (mres) {
718 rec.status = 500;
719 std::stringstream ss;
720 ss << "Failed to add transfer to libcurl multi-handle: HTTP library failure=" << curl_multi_strerror(mres);
721 logTransferEvent(LogMask::Error, rec, "CURL_INIT_FAIL", ss.str());
722 curl_multi_cleanup(multi_handle);
723 return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
724 }
725
726 // Start response to client prior to the first call to curl_multi_perform
727 int retval = req.StartChunkedResp(201, "Created", "Content-Type: text/plain");
728 if (retval) {
729 curl_multi_cleanup(multi_handle);
730 logTransferEvent(LogMask::Error, rec, "RESPONSE_FAIL",
731 "Failed to send the initial response to the TPC client");
732 return retval;
733 } else {
734 logTransferEvent(LogMask::Debug, rec, "RESPONSE_START",
735 "Initial transfer response sent to the TPC client");
736 }
737
738 // Transfer loop: use curl to actually run the transfer, but periodically
739 // interrupt things to send back performance updates to the client.
740 int running_handles = 1;
741 time_t last_marker = 0;
742 // Track how long it's been since the last time we recorded more bytes being transferred.
743 off_t last_advance_bytes = 0;
744 time_t last_advance_time = time(NULL);
745 time_t transfer_start = last_advance_time;
746 CURLcode res = static_cast<CURLcode>(-1);
747 do {
748 time_t now = time(NULL);
749 time_t next_marker = last_marker + m_marker_period;
750 if (now >= next_marker) {
751 off_t bytes_xfer = state.BytesTransferred();
752 if (bytes_xfer > last_advance_bytes) {
753 last_advance_bytes = bytes_xfer;
754 last_advance_time = now;
755 }
756 if (SendPerfMarker(req, rec, state)) {
757 curl_multi_remove_handle(multi_handle, curl);
758 curl_multi_cleanup(multi_handle);
759 logTransferEvent(LogMask::Error, rec, "PERFMARKER_FAIL",
760 "Failed to send a perf marker to the TPC client");
761 return -1;
762 }
763 int timeout = (transfer_start == last_advance_time) ? m_first_timeout : m_timeout;
764 if (now > last_advance_time + timeout) {
765 const char *log_prefix = rec.log_prefix.c_str();
766 bool tpc_pull = strncmp("Pull", log_prefix, 4) == 0;
767
769 std::stringstream ss;
770 ss << "Transfer failed because no bytes have been "
771 << (tpc_pull ? "received from the source (pull mode) in "
772 : "transmitted to the destination (push mode) in ") << timeout << " seconds.";
773 state.SetErrorMessage(ss.str());
774 curl_multi_remove_handle(multi_handle, curl);
775 curl_multi_cleanup(multi_handle);
776 break;
777 }
778 last_marker = now;
779 }
780 // The transfer will start after this point, notify the packet marking manager
781 rec.pmarkManager.startTransfer();
782 mres = curl_multi_perform(multi_handle, &running_handles);
783 if (mres == CURLM_CALL_MULTI_PERFORM) {
784 // curl_multi_perform should be called again immediately. On newer
785 // versions of curl, this is no longer used.
786 continue;
787 } else if (mres != CURLM_OK) {
788 break;
789 } else if (running_handles == 0) {
790 break;
791 }
792
793 rec.pmarkManager.beginPMarks();
794 //printf("There are %d running handles\n", running_handles);
795
796 // Harvest any messages, looking for CURLMSG_DONE.
797 CURLMsg *msg;
798 do {
799 int msgq = 0;
800 msg = curl_multi_info_read(multi_handle, &msgq);
801 if (msg && (msg->msg == CURLMSG_DONE)) {
802 CURL *easy_handle = msg->easy_handle;
803 res = msg->data.result;
804 curl_multi_remove_handle(multi_handle, easy_handle);
805 }
806 } while (msg);
807
808 int64_t max_sleep_time = next_marker - time(NULL);
809 if (max_sleep_time <= 0) {
810 continue;
811 }
812 int fd_count;
813 mres = curl_multi_wait(multi_handle, NULL, 0, max_sleep_time*1000, &fd_count);
814 if (mres != CURLM_OK) {
815 break;
816 }
817 } while (running_handles);
818
819 if (mres != CURLM_OK) {
820 std::stringstream ss;
821 ss << "Internal libcurl multi-handle error: HTTP library failure=" << curl_multi_strerror(mres);
822 logTransferEvent(LogMask::Error, rec, "TRANSFER_CURL_ERROR", ss.str());
823
824 curl_multi_remove_handle(multi_handle, curl);
825 curl_multi_cleanup(multi_handle);
826
827 if ((retval = req.ChunkResp(generateClientErr(ss, rec).c_str(), 0))) {
828 logTransferEvent(LogMask::Error, rec, "RESPONSE_FAIL",
829 "Failed to send error message to the TPC client");
830 return retval;
831 }
832 return req.ChunkResp(NULL, 0);
833 }
834
835 // Harvest any messages, looking for CURLMSG_DONE.
836 CURLMsg *msg;
837 do {
838 int msgq = 0;
839 msg = curl_multi_info_read(multi_handle, &msgq);
840 if (msg && (msg->msg == CURLMSG_DONE)) {
841 CURL *easy_handle = msg->easy_handle;
842 res = msg->data.result;
843 curl_multi_remove_handle(multi_handle, easy_handle);
844 }
845 } while (msg);
846
847 if (!state.GetErrorCode() && res == static_cast<CURLcode>(-1)) { // No transfers returned?!?
848 curl_multi_remove_handle(multi_handle, curl);
849 curl_multi_cleanup(multi_handle);
850 std::stringstream ss;
851 ss << "Internal state error in libcurl";
852 logTransferEvent(LogMask::Error, rec, "TRANSFER_CURL_ERROR", ss.str());
853
854 if ((retval = req.ChunkResp(generateClientErr(ss, rec).c_str(), 0))) {
855 logTransferEvent(LogMask::Error, rec, "RESPONSE_FAIL",
856 "Failed to send error message to the TPC client");
857 return retval;
858 }
859 return req.ChunkResp(NULL, 0);
860 }
861 curl_multi_cleanup(multi_handle);
862
863 // The transfer is over at this point: any error recorded so far - a failed
864 // write to the local filesystem or the stall detector having fired - is the
865 // reason why the transfer failed. Flushing and closing the destination file
866 // below may fail as well but, as such a failure is usually a consequence of
867 // the transfer failure, it must not be reported instead of it.
868 const int transferErrorCode = state.GetErrorCode();
869 std::string transferErrorMsg = state.GetErrorMessage();
870
871 state.Flush();
872
873 rec.bytes_transferred = state.BytesTransferred();
874 rec.tpc_status = state.GetStatusCode();
875
876 // Explicitly finalize the stream (which will close the underlying file
877 // handle) before the response is sent. In some cases, subsequent HTTP
878 // requests can occur before the filesystem is done closing the handle -
879 // and those requests may occur against partial data.
880 state.Finalize();
881
882 // A failure to flush or to close the destination file is always logged and is
883 // appended to the error reported to the client, but it never replaces the
884 // transfer failure itself: it is usually a consequence of it.
885 std::string finalizeErrorMsg, finalizeErrorSuffix;
886 if (state.GetFinalizeErrorCode()) {
887 std::stringstream ss2;
888 ss2 << (state.GetFinalizeErrorCode() == State::errFlush
889 ? "Failed to flush the file to the local filesystem."
890 : "Failed to finalize and close file handle.");
891 std::string err = state.GetFinalizeErrorMessage();
892 if (!err.empty()) {
893 std::replace(err.begin(), err.end(), '\n', ' ');
894 ss2 << " " << err;
895 }
896 finalizeErrorMsg = ss2.str();
897 logTransferEvent(LogMask::Error, rec, "CLOSE_FAIL", finalizeErrorMsg);
898 finalizeErrorSuffix = "; " + finalizeErrorMsg;
899 }
900
901 // Generate the final response back to the client.
902 std::stringstream ss;
903 bool success = false;
904 if (state.GetStatusCode() >= 400) {
905 std::string err = state.GetErrorMessage();
906 std::stringstream ss2;
907 ss2 << "Remote side failed with status code " << state.GetStatusCode();
908 if (!err.empty()) {
909 std::replace(err.begin(), err.end(), '\n', ' ');
910 ss2 << "; error message: \"" << err << "\"";
911 }
912 logTransferEvent(LogMask::Error, rec, "TRANSFER_FAIL", ss2.str());
913 ss2 << finalizeErrorSuffix;
914 ss << generateClientErr(ss2, rec);
915 } else if (transferErrorCode == State::errTimeout) {
916 // The stall detector fired; its message already describes precisely
917 // what happened, report it as-is.
918 std::stringstream ss2;
919 ss2 << transferErrorMsg;
920 logTransferEvent(LogMask::Error, rec, "TRANSFER_FAIL", ss2.str());
921 ss2 << finalizeErrorSuffix;
922 ss << generateClientErr(ss2, rec);
923 } else if (transferErrorCode) {
924 if (transferErrorMsg.empty()) {transferErrorMsg = "(no error message provided)";}
925 else {std::replace(transferErrorMsg.begin(), transferErrorMsg.end(), '\n', ' ');}
926 std::stringstream ss2;
927 ss2 << "Error when interacting with local filesystem: " << transferErrorMsg;
928 logTransferEvent(LogMask::Error, rec, "TRANSFER_FAIL", ss2.str());
929 ss2 << finalizeErrorSuffix;
930 ss << generateClientErr(ss2, rec);
931 } else if (res != CURLE_OK) {
932 std::stringstream ss2;
933 ss2 << "Internal transfer failure";
934 std::stringstream ss3;
935 ss3 << ss2.str() << ": " << curl_easy_strerror(res);
936 logTransferEvent(LogMask::Error, rec, "TRANSFER_FAIL", ss3.str());
937 ss2 << finalizeErrorSuffix;
938 ss << generateClientErr(ss2, rec, res);
939 } else if (!finalizeErrorMsg.empty()) {
940 // Nothing else went wrong: the flush/close failure is the reason of the failure.
941 std::stringstream ss2;
942 ss2 << finalizeErrorMsg;
943 ss << generateClientErr(ss2, rec);
944 } else {
945 ss << "success: Created";
946 success = true;
947 }
948
949 if ((retval = req.ChunkResp(ss.str().c_str(), 0))) {
950 logTransferEvent(LogMask::Error, rec, "TRANSFER_ERROR",
951 "Failed to send last update to remote client");
952 return retval;
953 } else if (success) {
954 logTransferEvent(LogMask::Info, rec, "TRANSFER_SUCCESS");
955 rec.status = 0;
956 }
957 return req.ChunkResp(NULL, 0);
958}
959
960/******************************************************************************/
961/* T P C H a n d l e r : : P r o c e s s P u s h R e q */
962/******************************************************************************/
963
964int TPCHandler::ProcessPushReq(const std::string & resource, XrdHttpExtReq &req) {
965 TPCLogRecord rec(req, TpcType::Push);
966 rec.allow_local = m_allow_local;
967 rec.allow_private = m_allow_private;
968 rec.log_prefix = "PushRequest";
969 rec.local = req.resource;
970 rec.remote = resource;
971 rec.m_log = &m_log;
972 char *name = req.GetSecEntity().name;
973 req.GetClientID(rec.clID);
974 if (name) rec.name = name;
975 logTransferEvent(LogMask::Info, rec, "PUSH_START", "Starting a push request");
976
977 ManagedCurlHandle curlPtr(curl_easy_init());
978 auto curl = curlPtr.get();
979 if (!curl) {
980 std::stringstream ss;
981 ss << "Failed to initialize internal transfer resources";
982 rec.status = 500;
983 logTransferEvent(LogMask::Error, rec, "PUSH_FAIL", ss.str());
984 return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
985 }
986 ConfigureCurlLowSpeed(curl);
987 curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
988 curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
989 curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (long) CURL_HTTP_VERSION_1_1);
990#if CURL_AT_LEAST_VERSION(7, 85, 0)
991 curl_easy_setopt(curl, CURLOPT_PROTOCOLS_STR, "https,http");
992 curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS_STR, "https,http");
993#else
994 long protocols = CURLPROTO_HTTP | CURLPROTO_HTTPS;
995 curl_easy_setopt(curl, CURLOPT_PROTOCOLS, protocols);
996 curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, protocols);
997#endif
998 curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, opensocket_callback);
999 curl_easy_setopt(curl, CURLOPT_OPENSOCKETDATA, &rec);
1000 curl_easy_setopt(curl, CURLOPT_CLOSESOCKETFUNCTION, closesocket_callback);
1001 curl_easy_setopt(curl, CURLOPT_SOCKOPTFUNCTION, sockopt_callback);
1002 curl_easy_setopt(curl, CURLOPT_CLOSESOCKETDATA, &rec);
1003 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, CONNECT_TIMEOUT);
1004
1005 auto query_header = XrdOucTUtils::caseInsensitiveFind(req.headers,"xrd-http-fullresource");
1006 std::string redirect_resource = req.resource;
1007 if (query_header != req.headers.end()) {
1008 redirect_resource = query_header->second;
1009 }
1010
1011 AtomicBeg(m_monid_mutex);
1012 uint64_t file_monid = AtomicInc(m_monid);
1013 AtomicEnd(m_monid_mutex);
1014 std::unique_ptr<XrdSfsFile> fh(m_sfs->newFile(name, file_monid));
1015 if (!fh.get()) {
1016 rec.status = 500;
1017 std::stringstream ss;
1018 ss << "Failed to initialize internal transfer file handle";
1019 logTransferEvent(LogMask::Error, rec, "OPEN_FAIL",
1020 ss.str());
1021 return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
1022 }
1023 std::string full_url = prepareURL(req);
1024
1025 std::string authz = GetAuthz(req);
1026
1027 int open_results = OpenWaitStall(*fh, full_url, SFS_O_RDONLY, 0644,
1028 req.GetSecEntity(), authz);
1029 if (SFS_REDIRECT == open_results) {
1030 int result = RedirectTransfer(curl, redirect_resource, req, fh->error, rec);
1031 return result;
1032 } else if (SFS_OK != open_results) {
1033 int code;
1034 std::stringstream ss;
1035 const char *msg = fh->error.getErrText(code);
1036 if (msg == NULL) ss << "Failed to open local resource";
1037 else ss << msg;
1038 rec.status = mapErrNoToHttp(code);
1039 logTransferEvent(LogMask::Error, rec, "OPEN_FAIL", msg);
1040 int resp_result = req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
1041 fh->close();
1042 return resp_result;
1043 }
1044 if (!ConfigureCurlCA(curl, rec)) {
1045 std::stringstream ss;
1046 ss << "Failed to configure the certificate authorities for the transfer";
1047 rec.status = 500;
1048 logTransferEvent(LogMask::Error, rec, "PUSH_FAIL", ss.str());
1049 int resp_result = req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
1050 fh->close();
1051 return resp_result;
1052 }
1053 curl_easy_setopt(curl, CURLOPT_URL, resource.c_str());
1054
1055 Stream stream(std::move(fh), 0, 0, m_log);
1056 State state(0, stream, curl, true, req.tpcForwardCreds);
1057 state.SetupHeaders(req);
1058
1059 return RunCurlWithUpdates(curl, req, state, rec);
1060}
1061
1062/******************************************************************************/
1063/* T P C H a n d l e r : : P r o c e s s P u l l R e q */
1064/******************************************************************************/
1065
1066int TPCHandler::ProcessPullReq(const std::string &resource, XrdHttpExtReq &req) {
1067 TPCLogRecord rec(req,TpcType::Pull);
1068 rec.allow_local = m_allow_local;
1069 rec.allow_private = m_allow_private;
1070 rec.log_prefix = "PullRequest";
1071 rec.local = req.resource;
1072 rec.remote = resource;
1073 rec.m_log = &m_log;
1074 char *name = req.GetSecEntity().name;
1075 req.GetClientID(rec.clID);
1076 if (name) rec.name = name;
1077 logTransferEvent(LogMask::Info, rec, "PULL_START", "Starting a pull request");
1078
1079 ManagedCurlHandle curlPtr(curl_easy_init());
1080 auto curl = curlPtr.get();
1081 if (!curl) {
1082 std::stringstream ss;
1083 ss << "Failed to initialize internal transfer resources";
1084 rec.status = 500;
1085 logTransferEvent(LogMask::Error, rec, "PULL_FAIL", ss.str());
1086 return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
1087 }
1088 ConfigureCurlLowSpeed(curl);
1089
1090 // ddavila 2023-01-05:
1091 // The following change was required by the Rucio/SENSE project where
1092 // multiple IP addresses, each from a different subnet, are assigned to a
1093 // single server and routed differently by SENSE.
1094 // The above requires the server to utilize the same IP, that was used to
1095 // start the TPC, for the resolution of the given TPC instead of
1096 // using any of the IPs available.
1097 if (m_fixed_route) {
1098 // Get the hostname used to contact the server from the http header
1099 std::string host;
1100 auto host_header = XrdOucTUtils::caseInsensitiveFind(req.headers, "host");
1101
1102 if (host_header != req.headers.end()) {
1103 host = host_header->second;
1104 }
1105
1106 // Get the IP addresses associated with the hostname
1107 char ip[64]; // IPv6 addresses are up to 45 characters long
1108 std::vector<XrdNetAddr> addresses;
1109 const char *eText = XrdNetUtils::GetAddrs(host, addresses, nullptr, XrdNetUtils::prefAuto, 0);
1110
1111 if (eText || addresses.empty() ||
1112 addresses.front().Format(ip, sizeof(ip), XrdNetAddrInfo::fmtAddr,XrdNetAddrInfo::noPortRaw) <= 0) {
1113 std::stringstream ss;
1114 ss << "Failed to determine host address of incoming request";
1115 rec.status = 500;
1116 logTransferEvent(LogMask::Error, rec, "PULL_FAIL", ss.str());
1117 return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
1118 }
1119
1120 logTransferEvent(LogMask::Info, rec, "LOCAL IP", ip);
1121 curl_easy_setopt(curl, CURLOPT_INTERFACE, ip);
1122 }
1123 curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
1124 curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
1125 curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (long) CURL_HTTP_VERSION_1_1);
1126#if CURL_AT_LEAST_VERSION(7, 85, 0)
1127 curl_easy_setopt(curl, CURLOPT_PROTOCOLS_STR, "https,http");
1128 curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS_STR, "https,http");
1129#else
1130 long protocols = CURLPROTO_HTTP | CURLPROTO_HTTPS;
1131 curl_easy_setopt(curl, CURLOPT_PROTOCOLS, protocols);
1132 curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, protocols);
1133#endif
1134 curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, opensocket_callback);
1135 curl_easy_setopt(curl, CURLOPT_OPENSOCKETDATA, &rec);
1136 curl_easy_setopt(curl, CURLOPT_SOCKOPTFUNCTION, sockopt_callback);
1137 curl_easy_setopt(curl, CURLOPT_SOCKOPTDATA , &rec);
1138 curl_easy_setopt(curl, CURLOPT_CLOSESOCKETFUNCTION, closesocket_callback);
1139 curl_easy_setopt(curl, CURLOPT_CLOSESOCKETDATA, &rec);
1140 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, CONNECT_TIMEOUT);
1141 std::unique_ptr<XrdSfsFile> fh(m_sfs->newFile(name, m_monid++));
1142 if (!fh.get()) {
1143 std::stringstream ss;
1144 ss << "Failed to initialize internal transfer file handle";
1145 rec.status = 500;
1146 logTransferEvent(LogMask::Error, rec, "PULL_FAIL", ss.str());
1147 return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
1148 }
1149 auto query_header = XrdOucTUtils::caseInsensitiveFind(req.headers,"xrd-http-fullresource");
1150 std::string redirect_resource = req.resource;
1151 if (query_header != req.headers.end()) {
1152 redirect_resource = query_header->second;
1153 }
1155 auto overwrite_header = XrdOucTUtils::caseInsensitiveFind(req.headers,"overwrite");
1156 if ((overwrite_header == req.headers.end()) || (overwrite_header->second == "T")) {
1157 if (! usingEC) mode = SFS_O_TRUNC;
1158 }
1159 int streams = 1;
1160 {
1161 auto streams_header = XrdOucTUtils::caseInsensitiveFind(req.headers,"x-number-of-streams");
1162 if (streams_header != req.headers.end()) {
1163 int stream_req = -1;
1164 try {
1165 stream_req = std::stol(streams_header->second);
1166 } catch (...) { // Handled below
1167 }
1168 if (stream_req < 0 || stream_req > 100) {
1169 std::stringstream ss;
1170 ss << "Invalid request for number of streams";
1171 rec.status = 400;
1172 logTransferEvent(LogMask::Info, rec, "INVALID_REQUEST", ss.str());
1173 return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
1174 }
1175 streams = stream_req == 0 ? 1 : stream_req;
1176 }
1177 }
1178 rec.streams = streams;
1179 std::string full_url = prepareURL(req);
1180 std::string authz = GetAuthz(req);
1181 curl_easy_setopt(curl, CURLOPT_URL, resource.c_str());
1182 if (!ConfigureCurlCA(curl, rec)) {
1183 std::stringstream ss;
1184 ss << "Failed to configure the certificate authorities for the transfer";
1185 rec.status = 500;
1186 logTransferEvent(LogMask::Error, rec, "PULL_FAIL", ss.str());
1187 return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
1188 }
1189 uint64_t sourceFileContentLength = 0;
1190 {
1191 //Get the content-length of the source file and pass it to the OSS layer
1192 //during the open
1193 bool success;
1194 GetContentLengthTPCPull(curl, req, sourceFileContentLength, success, rec);
1195 if(success) {
1196 //In the case we cannot get the information from the source server (offline or other error)
1197 //we just don't add the size information to the opaque of the local file to open
1198 full_url += "&oss.asize=" + std::to_string(sourceFileContentLength);
1199 } else {
1200 // In the case the GetContentLength is not successful, an error will be returned to the client
1201 // just exit here so we don't open the file!
1202 return 0;
1203 }
1204 }
1205 int open_result = OpenWaitStall(*fh, full_url, mode|SFS_O_WRONLY,
1206 0644 | SFS_O_MKPTH,
1207 req.GetSecEntity(), authz);
1208 if (SFS_REDIRECT == open_result) {
1209 int result = RedirectTransfer(curl, redirect_resource, req, fh->error, rec);
1210 return result;
1211 } else if (SFS_OK != open_result) {
1212 int code;
1213 std::stringstream ss;
1214 const char *msg = fh->error.getErrText(code);
1215 if ((msg == NULL) || (*msg == '\0')) ss << "Failed to open local resource";
1216 else ss << msg;
1217 rec.status = mapErrNoToHttp(code);
1218 logTransferEvent(LogMask::Error, rec, "OPEN_FAIL", ss.str());
1219 int resp_result = req.SendSimpleResp(rec.status, NULL, NULL,
1220 generateClientErr(ss, rec).c_str(), 0);
1221 fh->close();
1222 return resp_result;
1223 }
1224 Stream stream(std::move(fh), streams * m_pipelining_multiplier, streams > 1 ? m_block_size : m_small_block_size, m_log);
1225 State state(0, stream, curl, false, req.tpcForwardCreds);
1226 state.SetupHeaders(req);
1227 state.SetContentLength(sourceFileContentLength);
1228
1229 if (streams > 1) {
1230 return RunCurlWithStreams(req, state, streams, rec);
1231 } else {
1232 return RunCurlWithUpdates(curl, req, state, rec);
1233 }
1234}
1235
1236/******************************************************************************/
1237/* T P C H a n d l e r : : l o g T r a n s f e r E v e n t */
1238/******************************************************************************/
1239
1240void TPCHandler::logTransferEvent(LogMask mask, const TPCLogRecord &rec,
1241 const std::string &event, const std::string &message)
1242{
1243 if (!(m_log.getMsgMask() & mask)) {return;}
1244
1245 std::stringstream ss;
1246 ss << "event=" << event << ", local=" << rec.local << ", remote=" << rec.remote;
1247 if (rec.name.empty())
1248 ss << ", user=(anonymous)";
1249 else
1250 ss << ", user=" << rec.name;
1251 if (rec.streams != 1)
1252 ss << ", streams=" << rec.streams;
1253 if (rec.bytes_transferred >= 0)
1254 ss << ", bytes_transferred=" << rec.bytes_transferred;
1255 if (rec.status >= 0)
1256 ss << ", status=" << rec.status;
1257 if (rec.tpc_status >= 0)
1258 ss << ", tpc_status=" << rec.tpc_status;
1259 if (!message.empty())
1260 ss << "; " << message;
1261 m_log.Log(mask, rec.log_prefix.c_str(), ss.str().c_str());
1262}
1263
1264std::string TPCHandler::generateClientErr(std::stringstream &err_ss, const TPCLogRecord &rec, CURLcode cCode) {
1265 std::stringstream ssret;
1266 ssret << "failure: " << err_ss.str() << ", local=" << rec.local <<", remote=" << rec.remote;
1267 if(cCode != CURLcode::CURLE_OK) {
1268 ssret << ", HTTP library failure=" << curl_easy_strerror(cCode);
1269 }
1270 return ssret.str();
1271}
1272/******************************************************************************/
1273/* X r d H t t p G e t E x t H a n d l e r */
1274/******************************************************************************/
1275
1276extern "C" {
1277
1278XrdHttpExtHandler *XrdHttpGetExtHandler(XrdSysError *log, const char * config, const char * /*parms*/, XrdOucEnv *myEnv) {
1279 if (curl_global_init(CURL_GLOBAL_DEFAULT)) {
1280 log->Emsg("TPCInitialize", "libcurl failed to initialize");
1281 return NULL;
1282 }
1283
1284 TPCHandler *retval{NULL};
1285 if (!config) {
1286 log->Emsg("TPCInitialize", "TPC handler requires a config filename in order to load");
1287 return NULL;
1288 }
1289 try {
1290 log->Emsg("TPCInitialize", "Will load configuration for the TPC handler from", config);
1291 retval = new TPCHandler(log, config, myEnv);
1292 } catch (std::runtime_error &re) {
1293 log->Emsg("TPCInitialize", "Encountered a runtime failure when loading ", re.what());
1294 //printf("Provided env vars: %p, XrdInet*: %p\n", myEnv, myEnv->GetPtr("XrdInet*"));
1295 }
1296 return retval;
1297}
1298
1299}
XrdHttpExtHandler * XrdHttpGetExtHandler(XrdHttpExtHandlerArgs)
void CURL
XrdVERSIONINFO(XrdHttpGetExtHandler, HttpTPC)
static std::string PrepareURL(const std::string &url)
std::string encode_xrootd_opaque_to_uri(CURL *curl, const std::string &opaque)
static bool IsAllowedScheme(const std::string &url)
int mapErrNoToHttp(int errNo)
std::string httpStatusToString(int status)
Utility functions for XrdHTTP.
std::string encode_str(const std::string &str)
#define close(a)
Definition XrdPosix.hh:48
void getline(uchar *buff, int blen)
#define SFS_REDIRECT
#define SFS_O_MKPTH
#define SFS_STALL
#define SFS_O_RDONLY
#define SFS_STARTED
#define SFS_O_WRONLY
#define SFS_O_CREAT
int XrdSfsFileOpenMode
#define SFS_OK
#define SFS_O_TRUNC
#define AtomicInc(x)
#define AtomicBeg(Mtx)
#define AtomicEnd(Mtx)
if(Avsz)
int GetFinalizeErrorCode() const
int GetStatusCode() const
off_t BytesTransferred() const
void SetErrorMessage(const std::string &error_msg)
int GetErrorCode() const
std::string GetFinalizeErrorMessage() const
std::string GetErrorMessage() const
std::string GetConnectionDescription()
void SetupHeaders(XrdHttpExtReq &req)
void SetContentLength(const off_t content_length)
off_t GetContentLength() const
void SetErrorCode(int error_code)
TPCHandler(XrdSysError *log, const char *config, XrdOucEnv *myEnv)
virtual int ProcessReq(XrdHttpExtReq &req)
virtual ~TPCHandler()
virtual bool MatchesPath(const char *verb, const char *path)
Tells if the incoming path is recognized as one of the paths that have to be processed.
int ChunkResp(const char *body, long long bodylen)
Send a (potentially partial) body in a chunked response; invoking with NULL body.
void GetClientID(std::string &clid)
std::map< std::string, std::string > & headers
std::string resource
int StartChunkedResp(int code, const char *desc, const char *header_to_add)
Starts a chunked response; body of request is sent over multiple parts using the SendChunkResp.
const XrdSecEntity & GetSecEntity() const
int SendSimpleResp(int code, const char *desc, const char *header_to_add, const char *body, long long bodylen)
Sends a basic response. If the length is < 0 then it is calculated internally.
static std::string prepareOpenURL(const std::string &reqResource, std::map< std::string, std::string > &reqHeaders, const std::map< std::string, std::string > &hdr2cgimap)
static const int noPortRaw
Use raw address format (no port)
@ fmtAddr
Address using suitable ipv4 or ipv6 format.
static const char * GetAddrs(const char *hSpec, XrdNetAddr *aListP[], int &aListN, AddrOpts opts=allIPMap, int pNum=PortInSpec)
void * GetPtr(const char *varname)
Definition XrdOucEnv.cc:281
const char * getErrText()
void setUCap(int ucval)
Set user capabilties.
static std::map< std::string, T >::const_iterator caseInsensitiveFind(const std::map< std::string, T > &m, const std::string &lowerCaseSearchKey)
char * name
Entity's name.
XrdOucErrInfo & error
virtual int open(const char *fileName, XrdSfsFileOpenMode openMode, mode_t createMode, const XrdSecEntity *client=0, const char *opaque=0)=0
virtual int close()=0
int Emsg(const char *esfx, int ecode, const char *text1, const char *text2=0)
XrdSysLogger * logger(XrdSysLogger *lp=0)
std::unique_ptr< CURL, CurlDeleter > ManagedCurlHandle
void operator()(CURL *curl)
static const int uIPv64
ucap: Supports only IPv4 info