Skip to content

Instantly share code, notes, and snippets.

@jay
Last active December 14, 2017 07:39
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save jay/704ef499230c961070fd71424aa91e97 to your computer and use it in GitHub Desktop.
Use libcurl to show the local port and IP address in the progress function.
/* Use libcurl to show the local port and IP address in the progress function.
Usage: ShowIPPort <URL> [filename]
If filename is specified then the data received is saved to filename.tmp which
is then renamed to filename if the download was successful (2xx) or deleted if
not.
If filename is not specified then the data received is sent to stdout. If
stdout is attached to a tty and the data received appears to be binary data
that would mess up a terminal then the transfer is aborted.
Reporter:
"I tried to use curl_easy_getinfo CURLINFO_LOCAL_PORT, but it is only updated
after curl_easy_perform finishes - In my case it newer does."
curl-library mailing list thread:
'getting local port number'
https://curl.haxx.se/mail/lib-2017-12/0050.html
Copyright (C) 2017 Jay Satiro <raysatiro@yahoo.com>
http://curl.haxx.se/docs/copyright.html
https://gist.github.com/jay/704ef499230c961070fd71424aa91e97
*/
#define _CRT_NONSTDC_NO_DEPRECATE
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#if defined(_WIN32) || defined(__CYGWIN__)
#include <io.h>
#endif
#if !defined(_WIN32)
#include <unistd.h>
#endif
/* http://curl.haxx.se/download.html */
#include <curl/curl.h>
#undef FALSE
#define FALSE 0
#undef TRUE
#define TRUE 1
#ifndef O_BINARY
#ifdef _O_BINARY
#define O_BINARY _O_BINARY
#else
#define O_BINARY 0
#endif
#endif
#if defined(_WIN32) && !defined(strncasecmp)
#define strncasecmp strnicmp
#endif
struct ipport {
char *ip;
long port;
};
/* to initialize zero out the struct then set session */
struct progress_data {
CURL *session; /* curl easy_handle to the calling session */
struct ipport local, primary;
};
int progress_callback(void *clientp, curl_off_t dltotal, curl_off_t dlnow,
curl_off_t ultotal, curl_off_t ulnow)
{
struct progress_data *data = (struct progress_data *)clientp;
struct ipport local = {"", 0}, primary = {"", 0}; /* do not free */
int ipport_changed = FALSE;
(void)dltotal; (void)dlnow; (void)ultotal; (void)ulnow;
/* Save the IP and port values for the connection used by this transfer.
Note these values MAY change if a transfer changes connections. It's also
possible the same values were reused to reconnect to the same destination.
Note these values are updated by libcurl *after* a connection has been
established and it's using it for the transfer. It's possible the number
of new connections (CURLINFO_NUM_CONNECTS) may show an increase before
these values have been updated.
*/
if(!curl_easy_getinfo(data->session, CURLINFO_LOCAL_IP, &local.ip) &&
!curl_easy_getinfo(data->session, CURLINFO_LOCAL_PORT, &local.port) &&
!curl_easy_getinfo(data->session, CURLINFO_PRIMARY_IP, &primary.ip) &&
!curl_easy_getinfo(data->session, CURLINFO_PRIMARY_PORT, &primary.port) &&
local.ip && *local.ip && primary.ip && *primary.ip &&
(!data->local.ip || strcmp(local.ip, data->local.ip) ||
local.port != data->local.port ||
!data->primary.ip || strcmp(primary.ip, data->primary.ip) ||
primary.port != data->primary.port)) {
free(data->local.ip);
free(data->primary.ip);
data->local.port = local.port;
data->local.ip = strdup(local.ip);
data->primary.port = primary.port;
data->primary.ip = strdup(primary.ip);
if(!data->local.ip || !data->primary.ip)
return CURLE_ABORTED_BY_CALLBACK;
ipport_changed = TRUE;
}
if(ipport_changed) {
fprintf(stderr, "\n\n%s:%ld -> %s:%ld\n\n", local.ip, local.port,
primary.ip, primary.port);
}
return 0;
}
/* to initialize zero out the struct then set fp and fp_istty */
struct write_data {
FILE *fp;
int fp_istty; /* TRUE/FALSE depending on isatty(fileno(data->fp)) */
unsigned long long total_written;
};
size_t write_callback(char *ptr, size_t size, size_t nmemb, void *clientp)
{
size_t written;
size_t bytes = size * nmemb;
struct write_data *data = (struct write_data *)clientp;
if(!bytes)
return 0;
if(data->fp_istty && data->total_written < 2000 && memchr(ptr, 0, bytes)) {
fprintf(stderr, "\nError: The received data is binary and should not be "
"output to the terminal, aborting.\n");
/* to indicate failure return a byte count other than what was passed in */
return 0;
}
written = fwrite(ptr, 1, bytes, data->fp);
if(written != bytes)
fprintf(stderr, "\nError: fwrite failed. errno: %d\n", errno);
data->total_written += written;
return written;
}
/* Download URL to filename.
If filename is NULL then data is sent to stdout.
*/
int download(const char *url, const char *filename /* optional */)
{
int success = FALSE;
CURL *curl = NULL;
CURLcode res = CURLE_FAILED_INIT;
char errbuf[CURL_ERROR_SIZE] = { 0, };
FILE *fp = NULL;
char *filename_tmp = NULL;
struct progress_data progress_data = { 0, };
struct write_data write_data = { 0, };
double average_speed = 0;
double bytes_downloaded = 0;
double total_download_time = 0;
long response_code = 0;
if(!url || !*url) {
fprintf(stderr, "Error: url parameter is missing.\n");
goto cleanup;
}
if(filename && *filename) {
const char *ext_tmp = ".tmp";
filename_tmp = malloc(strlen(filename) + strlen(ext_tmp) + 1);
if(!filename_tmp) {
fprintf(stderr, "Error: malloc failed.\n");
goto cleanup;
}
strcpy(filename_tmp, filename);
strcpy(filename_tmp + strlen(filename_tmp), ext_tmp);
fp = fopen(filename_tmp, "wb");
if(!fp) {
fprintf(stderr, "Error: fopen failed to open \"%s\"\n", filename_tmp);
goto cleanup;
}
}
curl = curl_easy_init();
if(!curl) {
fprintf(stderr, "Error: curl_easy_init failed.\n");
goto cleanup;
}
/* CURLOPT_CAINFO
To verify SSL sites you may need to load a bundle of certificates.
You can download the default bundle here:
https://curl.haxx.se/ca/cacert.pem
However your SSL backend might use a database in addition to or instead of
the bundle.
http://curl.haxx.se/docs/ssl-compared.html
*/
curl_easy_setopt(curl, CURLOPT_CAINFO, "curl-ca-bundle.crt");
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &write_data);
if(fp)
write_data.fp = fp;
else {
write_data.fp = stdout;
#if O_BINARY
setmode(fileno(stdout), O_BINARY);
#endif
}
write_data.fp_istty = (isatty(fileno(write_data.fp)) ? TRUE : FALSE);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_callback);
curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &progress_data);
progress_data.session = curl;
curl_easy_setopt(curl, CURLOPT_AUTOREFERER, 1L);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 20L);
/* For security reasons we only allow redirects to safe redirect protocols */
curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS,
CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP | CURLPROTO_FTPS);
/* Leave TCP Fast Open (TFO) disabled since the IP and port information may
not be available when using that option. TFO is disabled by default so it
is set disabled here just for example purposes:
curl_easy_setopt(curl, CURLOPT_TCP_FASTOPEN, 0L);
https://github.com/curl/curl/issues/1332
*/
curl_easy_setopt(curl, CURLOPT_URL, url);
/* curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); */
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
res = curl_easy_perform(curl);
if(res != CURLE_OK) {
size_t len = strlen(errbuf);
fprintf(stderr, "\nError: libcurl: (%d) ", res);
if(len)
fprintf(stderr, "%s%s", errbuf, ((errbuf[len - 1] != '\n') ? "\n" : ""));
fprintf(stderr, "%s\n\n", curl_easy_strerror(res));
goto cleanup;
}
fprintf(stderr, "\n");
curl_easy_getinfo(curl, CURLINFO_SPEED_DOWNLOAD, &average_speed);
curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD, &bytes_downloaded);
curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME, &total_download_time);
fprintf(stderr, "\nTransfer rate: %.0f KB/sec"
" (%.0f bytes in %.0f seconds)\n",
average_speed / 1024, bytes_downloaded, total_download_time);
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
if((response_code / 100) != 2) {
fprintf(stderr, "Error: Server response code is not 2xx: %ld\n",
response_code);
goto cleanup;
}
success = TRUE;
cleanup:
curl_easy_cleanup(curl);
if(fp) {
fclose(fp);
if(success) {
unlink(filename);
if(rename(filename_tmp, filename)) {
fprintf(stderr, "Error: Transfer was successful but unable to rename "
"temp file to destination file. Temp file will not be "
"deleted.\n"
"temp: %s\n"
"dest: %s\n",
filename_tmp, filename);
success = FALSE;
}
}
else {
if(filename_tmp && unlink(filename_tmp) == -1)
fprintf(stderr, "Error: Unable to remove temp file: %s\n",
filename_tmp);
}
}
if(success)
fprintf(stderr, "Transfer successful.\n");
return success;
}
int main(int argc, char *argv[])
{
if(argc < 2) {
fprintf(stderr,
"Usage: ShowIPPort <URL> [filename]\n"
"\n"
"If filename is specified then the data received is saved to "
"filename.tmp which is then renamed to filename if the download was "
"successful (2xx) or deleted if not.\n"
"\n"
"If filename is not specified then the data received is sent to "
"stdout. If stdout is attached to a tty and the data received appears "
"to be binary data that would mess up a terminal then the transfer is "
"aborted.\n"
"\n");
return EXIT_FAILURE;
}
if(curl_global_init(CURL_GLOBAL_ALL)) {
fprintf(stderr, "Fatal: The initialization of libcurl has failed.\n");
return EXIT_FAILURE;
}
if(atexit(curl_global_cleanup)) {
fprintf(stderr, "Fatal: atexit failed to register curl_global_cleanup.\n");
curl_global_cleanup();
return EXIT_FAILURE;
}
if(!download(argv[1], argv[2])) {
fprintf(stderr, "Fatal: download failed.\n");
return EXIT_FAILURE;
}
fprintf(stderr, "\n");
return EXIT_SUCCESS;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment