mirror of https://github.com/BOINC/boinc.git
439 lines
13 KiB
C
439 lines
13 KiB
C
// Berkeley Open Infrastructure for Network Computing
|
|
// http://boinc.berkeley.edu
|
|
// Copyright (C) 2005 University of California
|
|
//
|
|
// This is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU Lesser General Public
|
|
// License as published by the Free Software Foundation;
|
|
// either version 2.1 of the License, or (at your option) any later version.
|
|
//
|
|
// This software is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
// See the GNU Lesser General Public License for more details.
|
|
//
|
|
// To view the GNU Lesser General Public License visit
|
|
// http://www.gnu.org/copyleft/lesser.html
|
|
// or write to the Free Software Foundation, Inc.,
|
|
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
|
|
#include "cpp.h"
|
|
|
|
#ifdef _WIN32
|
|
#ifndef _CONSOLE
|
|
#include "stdafx.h"
|
|
#include "wingui_mainwindow.h"
|
|
#else
|
|
#include "boinc_win.h"
|
|
#endif
|
|
|
|
#include "win_util.h"
|
|
|
|
#endif
|
|
|
|
#ifndef _WIN32
|
|
#include "config.h"
|
|
#include <cstdio>
|
|
#include <unistd.h>
|
|
#include <arpa/inet.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <cerrno>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#endif
|
|
|
|
#include "error_numbers.h"
|
|
#include "net_xfer_curl.h"
|
|
#include "util.h"
|
|
#include "network.h"
|
|
#include "filesys.h"
|
|
|
|
#include "client_types.h"
|
|
#include "client_state.h"
|
|
#include "client_msgs.h"
|
|
|
|
using std::vector;
|
|
|
|
// if an active transfer doesn't get any activity
|
|
// in this many seconds, error out
|
|
//
|
|
#define NET_XFER_TIMEOUT 600
|
|
|
|
CURLM* g_curlMulti = NULL; // global curl for this module, can handle http & https
|
|
|
|
// the file descriptor sets need to be global so libcurl has access always
|
|
fd_set read_fds, write_fds, error_fds;
|
|
|
|
// call these once at the start of the program and once at the end (init & cleanup of course)
|
|
int curl_init() {
|
|
curl_global_init(CURL_GLOBAL_ALL);
|
|
g_curlMulti = curl_multi_init();
|
|
return (int)(g_curlMulti == NULL);
|
|
}
|
|
|
|
int curl_cleanup() {
|
|
if (g_curlMulti) {
|
|
curl_multi_cleanup(g_curlMulti);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void NET_XFER::reset() {
|
|
req1 = NULL;
|
|
strcpy(infile, "");
|
|
strcpy(outfile, "");
|
|
CurlResult = CURLE_OK;
|
|
strcpy(strCurlResult, "");
|
|
bTempOutfile = true;
|
|
is_connected = false;
|
|
want_download = false;
|
|
want_upload = false;
|
|
do_file_io = true; // CMC Note: should I default to true, i.e. this does all i/o?
|
|
io_done = false;
|
|
fileIn = NULL;
|
|
fileOut = NULL;
|
|
io_ready = true; // don't allow higher levels to do i/o?
|
|
error = 0;
|
|
file_read_buf_offset = 0;
|
|
file_read_buf_len = 0;
|
|
bytes_xferred = 0;
|
|
xfer_speed = 0;
|
|
bSentHeader = false;
|
|
close_socket();
|
|
}
|
|
|
|
NET_XFER::NET_XFER() {
|
|
pcurlList = NULL; // these have to be NULL, just in constructor
|
|
curlEasy = NULL;
|
|
pcurlFormStart = NULL;
|
|
pcurlFormEnd = NULL;
|
|
pByte = NULL;
|
|
lSeek = 0;
|
|
reset();
|
|
}
|
|
|
|
NET_XFER::~NET_XFER() {
|
|
close_socket();
|
|
close_file();
|
|
}
|
|
|
|
void NET_XFER::close_socket() {
|
|
// CMC: this just cleans up the curlEasy, and "spoofs" the old close_socket
|
|
if (pcurlList) {
|
|
curl_slist_free_all(pcurlList);
|
|
pcurlList = NULL;
|
|
}
|
|
if (curlEasy && pcurlFormStart) {
|
|
curl_formfree(pcurlFormStart);
|
|
curl_formfree(pcurlFormEnd);
|
|
pcurlFormStart = pcurlFormEnd = NULL;
|
|
}
|
|
if (curlEasy && g_curlMulti) { // release this handle
|
|
curl_multi_remove_handle(g_curlMulti, curlEasy);
|
|
curl_easy_cleanup(curlEasy);
|
|
curlEasy = NULL;
|
|
}
|
|
}
|
|
|
|
void NET_XFER::close_file() {
|
|
if (fileIn) {
|
|
fclose(fileIn);
|
|
fileIn = NULL;
|
|
}
|
|
if (fileOut) {
|
|
fclose(fileOut);
|
|
fileOut = NULL;
|
|
}
|
|
if (pByte) { //free any read memory used
|
|
delete [] pByte;
|
|
pByte = NULL;
|
|
}
|
|
}
|
|
|
|
void NET_XFER::init(char* host, int p, int b) {
|
|
reset();
|
|
safe_strcpy(hostname, host);
|
|
port = p;
|
|
blocksize = (b > MAX_BLOCKSIZE ? MAX_BLOCKSIZE : b);
|
|
start_time = gstate.now;
|
|
reset_timeout();
|
|
}
|
|
|
|
bool NET_XFER::check_timeout(bool time_passed) {
|
|
if (seconds_until_timeout == 0) {
|
|
io_done = true;
|
|
error = ERR_TIMEOUT;
|
|
return true;
|
|
}
|
|
if (time_passed) {
|
|
seconds_until_timeout--;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void NET_XFER::reset_timeout() {
|
|
seconds_until_timeout = NET_XFER_TIMEOUT;
|
|
}
|
|
|
|
char* NET_XFER::get_hostname() {
|
|
return hostname;
|
|
}
|
|
|
|
NET_XFER_SET::NET_XFER_SET() {
|
|
max_bytes_sec_up = 0;
|
|
max_bytes_sec_down = 0;
|
|
bytes_left_up = 0;
|
|
bytes_left_down = 0;
|
|
bytes_up = 0;
|
|
bytes_down = 0;
|
|
up_active = false;
|
|
down_active = false;
|
|
}
|
|
|
|
// Connect to a server,
|
|
// and if successful insert the NET_XFER object into the set
|
|
//
|
|
int NET_XFER_SET::insert(NET_XFER* nxp) {
|
|
//int retval = nxp->open_server();
|
|
//if (retval) return retval;
|
|
net_xfers.push_back(nxp);
|
|
return 0;
|
|
}
|
|
|
|
// Remove a NET_XFER object from the set
|
|
//
|
|
int NET_XFER_SET::remove(NET_XFER* nxp) {
|
|
vector<NET_XFER*>::iterator iter;
|
|
|
|
iter = net_xfers.begin();
|
|
while (iter != net_xfers.end()) {
|
|
if (*iter == nxp) {
|
|
net_xfers.erase(iter);
|
|
return 0;
|
|
}
|
|
iter++;
|
|
}
|
|
msg_printf(NULL, MSG_ERROR, "NET_XFER_SET::remove(): not found\n");
|
|
return ERR_NOT_FOUND;
|
|
}
|
|
|
|
|
|
void NET_XFER_SET::get_fdset(FDSET_GROUP& fg) {
|
|
CURLMcode curlMErr;
|
|
curlMErr = curl_multi_fdset(g_curlMulti, &fg.read_fds, &fg.write_fds, &fg.exc_fds, &fg.max_fd);
|
|
//printf("curl msfd %d %d\n", curlMErr, fg.max_fd);
|
|
}
|
|
|
|
void NET_XFER_SET::got_select(FDSET_GROUP&, double timeout) {
|
|
int iNumMsg;
|
|
NET_XFER* nxf = NULL;
|
|
//struct timeval tv;
|
|
bool time_passed = false;
|
|
CURLMsg *pcurlMsg = NULL;
|
|
|
|
SCOPE_MSG_LOG scope_messages(log_messages, CLIENT_MSG_LOG::DEBUG_NET_XFER);
|
|
|
|
// if a second has gone by, do rate-limit accounting
|
|
//
|
|
time_t t = time(0);
|
|
if (t != last_time) {
|
|
time_passed = true;
|
|
last_time = (int)t;
|
|
if (bytes_left_up < max_bytes_sec_up) {
|
|
bytes_left_up += max_bytes_sec_up;
|
|
}
|
|
if (bytes_left_down < max_bytes_sec_down) {
|
|
bytes_left_down += max_bytes_sec_down;
|
|
}
|
|
}
|
|
|
|
int iRunning = 0; // curl flags for max # of fds & # running queries
|
|
CURLMcode curlMErr;
|
|
CURLcode curlErr;
|
|
|
|
// get the data waiting for transfer in or out
|
|
// note that I use timeout value so that we don't "hog" in this loop
|
|
bool got_data = false;
|
|
while (1) {
|
|
curlMErr = curl_multi_perform(g_curlMulti, &iRunning);
|
|
if (curlMErr != CURLM_CALL_MULTI_PERFORM) break;
|
|
if (dtime() - gstate.now > timeout) break;
|
|
got_data = true;
|
|
}
|
|
|
|
// read messages from curl that may have come in from the above loop
|
|
while ((pcurlMsg = curl_multi_info_read(g_curlMulti, &iNumMsg))) {
|
|
// if we have a msg, then somebody finished
|
|
// can check also with pcurlMsg->msg == CURLMSG_DONE
|
|
if ((nxf = lookup_curl(pcurlMsg->easy_handle)) ) {
|
|
// CURLINFO ci=CURLINFO_OS_ERRNO;
|
|
// we have a message from one of our http_ops
|
|
// get the response code for this request
|
|
curlErr = curl_easy_getinfo(nxf->curlEasy,
|
|
CURLINFO_RESPONSE_CODE, &nxf->response);
|
|
// CURLINFO_LONG+25 is a workaround for a bug in the gcc version included
|
|
// with Mac OS X 10.3.9
|
|
curlErr = curl_easy_getinfo(nxf->curlEasy,
|
|
(CURLINFO)(CURLINFO_LONG+25) /*CURLINFO_OS_ERRNO*/, &nxf->error);
|
|
|
|
nxf->io_done = true;
|
|
nxf->io_ready = false;
|
|
|
|
if (nxf->want_download) {
|
|
bytes_down += nxf->bytes_xferred;
|
|
// get xfer speed (don't mess with bytes_xferred, that's in write function
|
|
curlErr = curl_easy_getinfo(nxf->curlEasy,
|
|
CURLINFO_SPEED_DOWNLOAD, &nxf->xfer_speed);
|
|
}
|
|
if (nxf->want_upload) {
|
|
bytes_up += nxf->bytes_xferred;
|
|
// get xfer speed (don't mess with bytes_xferred, that's in write function
|
|
curlErr = curl_easy_getinfo(nxf->curlEasy,
|
|
CURLINFO_SPEED_UPLOAD, &nxf->xfer_speed);
|
|
}
|
|
|
|
// the op is done if curl_multi_msg_read gave us a msg for this http_op
|
|
nxf->http_op_state = HTTP_STATE_DONE;
|
|
|
|
// added a more useful error string (just pass the curl string up for now)
|
|
nxf->CurlResult = pcurlMsg->data.result;
|
|
safe_strcpy(nxf->strCurlResult, curl_easy_strerror(nxf->CurlResult));
|
|
|
|
// optional, example use, non-zero CurlResult has a useful error string:
|
|
//if (nxf->CurlResult) fprintf(stdout, "Error: %s\n", nxf->strCurlResult);
|
|
|
|
// 200 is a good HTTP response code
|
|
// It may not mean the data received is "good"
|
|
// (the calling program will have to check/parse that)
|
|
// but it at least means that the server operation
|
|
// went through fine
|
|
//
|
|
// NOTE: http_op_retval is multipurposed, it can also contain any error
|
|
// code that BOINC would return for IO errors and DNS errors. We
|
|
// need to translate between the curl error codes and the equiv.
|
|
// BOINC error codes here.
|
|
nxf->http_op_retval = HTTP_STATUS_INTERNAL_SERVER_ERROR;
|
|
if (nxf->CurlResult == CURLE_OK) {
|
|
if ((nxf->response/100)*100 == HTTP_STATUS_OK) {
|
|
nxf->http_op_retval = 0;
|
|
} else {
|
|
nxf->http_op_retval = nxf->response;
|
|
}
|
|
} else if (nxf->CurlResult == CURLE_OUT_OF_MEMORY) {
|
|
msg_printf(0, MSG_ERROR, "An out of memory condition has been detected");
|
|
nxf->http_op_retval = ERR_MALLOC;
|
|
} else if (nxf->CurlResult == CURLE_COULDNT_RESOLVE_HOST) {
|
|
msg_printf(0, MSG_ERROR, "Couldn't resolve hostname [%s]", nxf->hostname);
|
|
nxf->http_op_retval = ERR_GETHOSTBYNAME;
|
|
} else if (nxf->CurlResult == CURLE_COULDNT_RESOLVE_PROXY) {
|
|
msg_printf(0, MSG_ERROR, "Couldn't resolve proxy [%s]", nxf->hostname);
|
|
nxf->http_op_retval = ERR_GETHOSTBYNAME;
|
|
} else if (nxf->CurlResult == CURLE_COULDNT_CONNECT) {
|
|
msg_printf(0, MSG_ERROR, "Couldn't connect to hostname [%s]", nxf->hostname);
|
|
nxf->http_op_retval = ERR_IO;
|
|
} else if (nxf->CurlResult == CURLE_LOGIN_DENIED) {
|
|
msg_printf(0, MSG_ERROR, "Proxy authentication failed against proxy [%s]", nxf->hostname);
|
|
nxf->http_op_retval = ERR_AUTHENTICATOR;
|
|
} else if (nxf->CurlResult == CURLE_OPERATION_TIMEOUTED) {
|
|
msg_printf(0, MSG_ERROR, "Attempting to communicate with [%s] timed out", nxf->hostname);
|
|
nxf->http_op_retval = ERR_TIMEOUT;
|
|
} else if (nxf->CurlResult == CURLE_READ_ERROR) {
|
|
msg_printf(0, MSG_ERROR, "Attempting to read a file failed [%s]", nxf->strCurlResult);
|
|
nxf->http_op_retval = ERR_FREAD;
|
|
} else if (nxf->CurlResult == CURLE_WRITE_ERROR) {
|
|
msg_printf(0, MSG_ERROR, "Attempting to write to a file failed [%s]", nxf->strCurlResult);
|
|
nxf->http_op_retval = ERR_FWRITE;
|
|
} else if (nxf->CurlResult == CURLE_RECV_ERROR) {
|
|
msg_printf(0, MSG_ERROR, "Attempting to receive data from [%s] failed [%s]", nxf->hostname, nxf->strCurlResult);
|
|
nxf->http_op_retval = ERR_READ;
|
|
} else if (nxf->CurlResult == CURLE_SEND_ERROR) {
|
|
msg_printf(0, MSG_ERROR, "Attempting to send data to [%s] failed [%s]", nxf->hostname, nxf->strCurlResult);
|
|
nxf->http_op_retval = ERR_WRITE;
|
|
}
|
|
|
|
if (!nxf->http_op_retval && nxf->http_op_type == HTTP_OP_POST2) {
|
|
// for a successfully completed request on a "post2" --
|
|
// read in the temp file into req1 memory
|
|
fclose(nxf->fileOut);
|
|
double dSize = 0.0f;
|
|
file_size(nxf->outfile, dSize);
|
|
nxf->fileOut = boinc_fopen(nxf->outfile, "rb");
|
|
if (!nxf->fileOut) { // ack, can't open back up!
|
|
nxf->response = 1; // flag as a bad response for a possible retry later
|
|
}
|
|
fseek(nxf->fileOut, 0, SEEK_SET);
|
|
// CMC Note: req1 is a pointer to "header" which is 4096
|
|
memset(nxf->req1, 0x00, 4096);
|
|
fread(nxf->req1, 1, (size_t) dSize, nxf->fileOut);
|
|
}
|
|
|
|
// close files and "sockets" (i.e. libcurl handles)
|
|
nxf->close_file();
|
|
nxf->close_socket();
|
|
|
|
// finally remove the tmpfile if not explicitly set
|
|
if (nxf->bTempOutfile) {
|
|
boinc_delete_file(nxf->outfile);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return the NET_XFER object whose socket matches fd
|
|
NET_XFER* NET_XFER_SET::lookup_curl(CURL* pcurl) {
|
|
for (unsigned int i=0; i<net_xfers.size(); i++) {
|
|
if (net_xfers[i]->curlEasy == pcurl) {
|
|
return net_xfers[i];
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Update the transfer speed for this NET_XFER
|
|
// called on every I/O
|
|
//
|
|
void NET_XFER::update_speed() {
|
|
double delta_t = dtime() - start_time;
|
|
if (delta_t > 0) {
|
|
xfer_speed = bytes_xferred / delta_t;
|
|
#if 0
|
|
// TODO: figure out what to do here
|
|
} else if (xfer_speed == 0) {
|
|
xfer_speed = 999999999;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void NET_XFER::got_error() {
|
|
// TODO: which socket??
|
|
error = ERR_IO;
|
|
io_done = true;
|
|
log_messages.printf(
|
|
CLIENT_MSG_LOG::DEBUG_NET_XFER, "IO error on socket\n"
|
|
);
|
|
}
|
|
|
|
// return true if an upload is currently in progress
|
|
// or has been since the last call to this.
|
|
// Similar for download.
|
|
void NET_XFER_SET::check_active(bool& up, bool& down) {
|
|
unsigned int i;
|
|
NET_XFER* nxp;
|
|
|
|
up = up_active;
|
|
down = down_active;
|
|
for (i=0; i<net_xfers.size(); i++) {
|
|
nxp = net_xfers[i];
|
|
if (nxp->is_connected && nxp->do_file_io) {
|
|
nxp->want_download?down=true:up=true;
|
|
}
|
|
}
|
|
up_active = false;
|
|
down_active = false;
|
|
}
|
|
|
|
const char *BOINC_RCSID_e0a7088e04 = "$Id$";
|