Skip to content

Instantly share code, notes, and snippets.

@kizernis
Created September 2, 2018 12:28
Show Gist options
  • Save kizernis/4b85d352ea5166ee15c3c36e6ec6c294 to your computer and use it in GitHub Desktop.
Save kizernis/4b85d352ea5166ee15c3c36e6ec6c294 to your computer and use it in GitHub Desktop.
#include <stdio.h>
#if defined(__unix__) || defined(__unix)
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <malloc.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <signal.h>
#include <errno.h>
typedef int SOCKET;
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
#define SD_RECEIVE SHUT_RD
#define closesocket(s) close(s)
typedef int BOOL;
#define TRUE 1
#define FALSE 0
#else
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
#endif
long unsigned int time_stamp()
{
#if defined(__unix__) || defined(__unix)
static struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec * 1000 + tv.tv_usec / 1000;
#else
return GetTickCount();
#endif
}
void number_format(long unsigned int n, char * buf)
{
int n1 = 0, scale = 1;
while (n >= 1000)
{ n1 = n1 + scale * (n % 1000); n /= 1000; scale *= 1000; }
buf += sprintf(buf, "%lu", n);
while (scale != 1)
{ scale /= 1000; n = n1 / scale; n1 = n1 % scale; buf += sprintf(buf, ",%03lu", n); }
}
int g_exit_code = 0;
void print_error_message(const char * prefix, BOOL is_dns_error)
{
char * buf;
int e;
#if !defined(__unix__) && !defined(__unix)
int len;
#endif
if (prefix)
{ fputs(prefix, stderr); fputs(" ", stderr); }
#if defined(__unix__) || defined(__unix)
if (is_dns_error)
{ e = h_errno; buf = (char *)hstrerror(e); }
else
{
e = errno; if (e == EINPROGRESS) e = ETIMEDOUT; // connect() returns EINPROGRESS instead of ETIMEDOUT if timeout value is set by setsockopt()
buf = malloc(1024); strerror_r(e, buf, 1024);
}
fprintf(stderr, "failed (%d: %s).", e, buf);
if (!is_dns_error) free(buf);
#else
e = WSAGetLastError(); if (e == WSAECONNABORTED) e = WSAECONNRESET; // ?
len = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK,
NULL, e, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), (LPTSTR)&buf, 256, NULL);
if (buf[len - 1] == ' ')
{ if (buf[len - 2] == '.') buf[len - 2] = '\0'; else buf[len - 1] = '\0'; }
else if (buf[len - 1] == '.')
buf[len - 1] = '\0';
fprintf(stderr, "failed (%d: %s).", e, buf);
LocalFree(buf);
#endif
g_exit_code = 1;
}
int main(int argc, char ** argv)
{
unsigned int upload_size = 1024 * 1024, download_limit = 0;
char *host_name = "cachefly.cachefly.net", *host_file_path = "1mb.test";
BOOL is_url_set = FALSE;
SOCKET sock = INVALID_SOCKET;
struct sockaddr sa;
struct sockaddr_in * p_sa_in;
struct hostent * host;
unsigned char buf[1024];
unsigned char * p_buf = buf;
unsigned int buf_len, full_request_len;
char msg[256], fmt1[32], fmt2[32];
int msg_len1, msg_len2;
int transferred;
unsigned int transferred_total, kB_per_sec;
BOOL is_first_packet, is_header_sent;
long unsigned int t0, t1, t2, td, td_total;
#if defined(__unix__) || defined(__unix)
struct timeval timeout;
#else
DWORD timeout;
WSADATA wsa;
#endif
if (argc > 1)
{
const char *err_duplicate_key = "Error: duplicate key\n\n", *err_number_missing = "Error: NUMBER missing\n\n",
*err_unknown_key = "Error: unknown key '%s'\n\n";
const char *usage =
"Usage: speedt [KEY]... [URL] [KEY]...\n"
"Test (roughly) internet upload and download speeds.\n\n"
"Keys:\n"
" -u NUMBER, --upload-size=NUMBER Upload NUMBER bytes (default 1M)\n"
" -d NUMBER, --download-limit=NUMBER Limit downloading to NUMBER bytes\n"
" -h, --help Show this help message and exit\n"
" NUMBERs may have M or K postfixes for megabytes and kilobytes (e. g. 5M, 300K)\n\n"
"URL must point to a webserver which can take the POST data of the specified upload size\n"
" (default http://cachefly.cachefly.net/1mb.test)\n\n"
"Examples:\n"
" speedt -u 7.5m http://speed.kubtel.ru/speedtest/random2000x2000.jpg\n"
" speedt -u 61.1m http://ftp.dlink.ru/pub/vpn/robot.mpg\n"
" speedt -u 20m -d 20m http://ipv4.download.thinkbroadband.com/1GB.zip\n";
int i; char * a;
BOOL is_upload_size_set = FALSE, is_download_limit_set = FALSE; // is_url_set = FALSE
char *upload_size_string, *download_limit_string;
BOOL is_pending_upload_value = FALSE, is_pending_download_value = FALSE;
for (i = 1; i < argc; i++)
{
a = argv[i];
if (a[0] == '-')
{
if (is_pending_upload_value || is_pending_download_value)
{ fputs(err_number_missing, stderr); fputs(usage, stderr); return 1; }
if (a[1] == '-')
{
if (strncmp(&a[2], "upload-size=", 12) == 0)
{
if (is_upload_size_set) { fputs(err_duplicate_key, stderr); fputs(usage, stderr); return 1; } else is_upload_size_set = TRUE;
upload_size_string = &a[14];
}
else if (strncmp(&a[2], "download-limit=", 15) == 0)
{
if (is_download_limit_set) { fputs(err_duplicate_key, stderr); fputs(usage, stderr); return 1; } else is_download_limit_set = TRUE;
download_limit_string = &a[17];
}
else if (strcmp(&a[2], "help") == 0)
{ fputs(usage, stdout); return 0; }
else
{ char * p = strchr(&a[2], '='); if (p) *p = '\0'; fprintf(stderr, err_unknown_key, &a[2]); fputs(usage, stderr); return 1; }
}
else if (a[2] != '\0')
{ char * p = strchr(&a[1], '='); if (p) *p = '\0'; fprintf(stderr, "Error: long key '%s' without '--' prefix\n\n", &a[1]); fputs(usage, stderr); return 1; }
else
{
switch (a[1])
{
case 'u':
if (is_upload_size_set) { fputs(err_duplicate_key, stderr); fputs(usage, stderr); return 1; } else is_upload_size_set = TRUE;
is_pending_upload_value = TRUE;
break;
case 'd':
if (is_download_limit_set) { fputs(err_duplicate_key, stderr); fputs(usage, stderr); return 1; } else is_download_limit_set = TRUE;
is_pending_download_value = TRUE;
break;
case 'h':
fputs(usage, stdout); return 0;
default:
fprintf(stderr, err_unknown_key, &a[1]); fputs(usage, stderr); return 1;
}
}
}
else
{
if (is_pending_upload_value)
{ is_pending_upload_value = FALSE; upload_size_string = a; }
else if (is_pending_download_value)
{ is_pending_download_value = FALSE; download_limit_string = a; }
else if (is_url_set)
{ fprintf(stderr, "Error: unexpected argument: '%s'\n\n", a); fputs(usage, stderr); return 1; }
else
{
BOOL url_has_prefix = (strncmp(a, "http://", 7) == 0);
if (!url_has_prefix && strstr(a, "://")) // TODO: check if "://" is in URI reference
{ fputs("Error: only HTTP protocol is supported\n", stderr); return 1; }
host_name = url_has_prefix ? &a[7] : a; host_file_path = strchr(host_name, '/');
if (host_file_path == host_name)
{ fprintf(stderr, "Error: incorrect URL '%s'\n", a); return 1; }
if (host_file_path) { *host_file_path = '\0'; host_file_path++; } else host_file_path = "";
is_url_set = TRUE;
}
}
}
if (is_pending_upload_value || is_pending_download_value)
{ fputs(err_number_missing, stderr); fputs(usage, stderr); return 1; }
for (i = 1; i <= 2; i++)
{
char * s; unsigned int * p_n;
unsigned int len;
double multiplier;
double number; char * end_ptr;
if (i == 1)
{ if (!is_upload_size_set) continue; s = upload_size_string; p_n = &upload_size; }
else
{ if (!is_download_limit_set) break; s = download_limit_string; p_n = &download_limit; } // GCC shows a warning here
len = strlen(s); multiplier = 1;
if (s[len - 1] == 'm' || s[len - 1] == 'M')
{ s[len - 1] = '\0'; len--; multiplier = 1024 * 1024; }
else if (s[len - 1] == 'k' || s[len - 1] == 'K')
{ s[len - 1] = '\0'; len--; multiplier = 1024; }
while (len && (s[len - 1] == ' ' || s[len - 1] == '\t'))
{ s[len - 1] = '\0'; len--; }
number = strtod(s, &end_ptr);
if (*end_ptr != '\0' || number < 0 || number * multiplier > 1024 * 1024 * 1024)
{ fputs("Error: NUMBER must be a numeric value from 0 to 1,073,741,824\n\n", stderr); fputs(usage, stderr); return 1; }
*p_n = (unsigned int)(number * multiplier);
}
}
#if defined(__unix__) || defined(__unix)
timeout.tv_sec = 5; timeout.tv_usec = 0;
signal(SIGPIPE, SIG_IGN);
#else
timeout = 5000;
WSAStartup(0x0202, &wsa);
#endif
memset(&sa, 0, sizeof(struct sockaddr));
p_sa_in = (struct sockaddr_in *)&sa;
p_sa_in->sin_family = AF_INET; p_sa_in->sin_port = htons(80);
p_sa_in->sin_addr.s_addr = inet_addr(host_name);
setvbuf(stdout, NULL, _IONBF, BUFSIZ);
if (!is_url_set)
printf("URL: http://%s/%s\n", host_name, host_file_path);
if (p_sa_in->sin_addr.s_addr == INADDR_NONE)
{
fputs("Domain name resolving... ", stdout);
t1 = time_stamp();
if (!(host = gethostbyname(host_name))) // TODO: check host->h_addrtype
{ print_error_message(NULL, TRUE); goto exit; }
memcpy(&(p_sa_in->sin_addr.s_addr), host->h_addr, host->h_length); // host_name = host->h_name
t2 = time_stamp(); number_format(t2 - t1, fmt1);
printf("%s ms\n", fmt1);
}
if (INVALID_SOCKET == (sock = socket(AF_INET, SOCK_STREAM, 0)))
{ print_error_message("Socket creating", FALSE); goto exit; }
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (unsigned char *)&timeout, sizeof(timeout));
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (unsigned char *)&timeout, sizeof(timeout));
printf("Connecting to %s... ", inet_ntoa(p_sa_in->sin_addr));
t1 = time_stamp();
if (SOCKET_ERROR == connect(sock, &sa, sizeof(struct sockaddr)))
{ print_error_message(NULL, FALSE); goto exit; }
t2 = time_stamp(); number_format(t2 - t1, fmt1);
printf("%s ms\n", fmt1);
buf_len = sprintf((char *)buf, "POST /%s HTTP/1.0\r\nHost: %s\r\nUser-Agent: speedt\r\nContent-Length: %u\r\n\r\n", host_file_path, host_name, upload_size);
full_request_len = buf_len + upload_size;
transferred_total = 0;
is_header_sent = FALSE;
t0 = t1 = t2; msg_len1 = 0; is_first_packet = TRUE;
for (;;)
{
if (SOCKET_ERROR == (transferred = send(sock, p_buf, buf_len, 0)))
{ if (transferred_total) fputs("\n", stdout); print_error_message("Uploading", FALSE); goto exit; }
transferred_total += transferred;
t2 = time_stamp(); td = t2 - t1;
if (td >= 200 || is_first_packet || transferred_total == full_request_len)
{
if (is_first_packet) is_first_packet = FALSE;
td_total = t2 - t0;
kB_per_sec = transferred_total / (td_total ? td_total : 1) * 1000 / 1024;
number_format(transferred_total, fmt1); number_format(kB_per_sec, fmt2);
msg_len2 = sprintf(msg, "Uploading... %s bytes in %.3f sec (%s kB/s or %.2f Mb/s)", fmt1, (float)td_total / 1000, fmt2, (float)kB_per_sec / 125);
printf("%-*s\r", msg_len1, msg); msg_len1 = msg_len2;
t1 = t2;
}
if ((unsigned int)transferred < buf_len)
{ buf_len -= transferred; if (!is_header_sent) p_buf = buf + transferred; }
else
{
if (!is_header_sent)
{ is_header_sent = TRUE; if (p_buf != buf) p_buf = buf; memset(buf, '\0', 1024); buf_len = (upload_size > 1024 ? 1024 : upload_size); }
else if (transferred_total < full_request_len)
buf_len = (transferred_total + 1024 > full_request_len ? full_request_len - transferred_total : 1024);
else
break;
}
}
fputs("\n", stdout);
transferred_total = 0;
t0 = t1; msg_len1 = 0; is_first_packet = TRUE;
for (;;)
{
buf_len = (download_limit && sizeof(buf) > download_limit - transferred_total ? download_limit - transferred_total : sizeof(buf));
if (SOCKET_ERROR == (transferred = recv(sock, buf, buf_len, 0)))
{ if (transferred_total) fputs("\n", stdout); print_error_message("Downloading", FALSE); goto exit; }
if (buf_len != sizeof(buf))
shutdown(sock, SD_RECEIVE);
if (transferred)
transferred_total += transferred;
t2 = time_stamp(); td = t2 - t1;
if (td >= 200 || is_first_packet || !transferred || buf_len != sizeof(buf))
{
if (is_first_packet) is_first_packet = FALSE;
td_total = t2 - t0;
kB_per_sec = transferred_total / (td_total ? td_total : 1) * 1000 / 1024;
number_format(transferred_total, fmt1); number_format(kB_per_sec, fmt2);
msg_len2 = sprintf(msg, "Downloading... %s bytes in %.3f sec (%s kB/s or %.2f Mb/s)", fmt1, (float)td_total / 1000, fmt2, (float)kB_per_sec / 125);
printf("%-*s\r", msg_len1, msg); msg_len1 = msg_len2;
if (!transferred || buf_len != sizeof(buf))
break;
t1 = t2;
}
}
exit:
fputs("\n", stdout);
if (sock != INVALID_SOCKET)
closesocket(sock);
#if !defined(__unix__) && !defined(__unix)
WSACleanup();
#endif
return g_exit_code;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment