Feat: fetch public ip against from multiple services
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <curl/curl.h>
|
||||
#include "ipcheck.h"
|
||||
#include "log.h"
|
||||
|
||||
static const char *SERVICES[] = {
|
||||
"https://ipv4.icanhazip.com",
|
||||
"https://api4.ipify.org",
|
||||
"https://ipv4.wtfismyip.com/text",
|
||||
"https://v4.ident.me",
|
||||
"https://ipv4.am.i.mullvad.net/ip",
|
||||
"https://4.ident.me",
|
||||
"https://4.tnedi.me",
|
||||
};
|
||||
|
||||
#define SERVICE_COUNT (int)(sizeof(SERVICES) / sizeof(SERVICES[0]))
|
||||
|
||||
typedef struct {
|
||||
char buf[IPCHECK_MAX_IP_LEN];
|
||||
size_t len;
|
||||
} IPBuf;
|
||||
|
||||
static size_t write_cb(void *data, size_t size, size_t nmemb, void *userp) {
|
||||
IPBuf *out = (IPBuf *)userp;
|
||||
size_t total = size * nmemb;
|
||||
if (out->len + total >= IPCHECK_MAX_IP_LEN) return 0;
|
||||
memcpy(out->buf + out->len, data, total);
|
||||
out->len += total;
|
||||
return total;
|
||||
}
|
||||
|
||||
int ipcheck_get_public_ip(char *out_ip) {
|
||||
char results[SERVICE_COUNT][IPCHECK_MAX_IP_LEN];
|
||||
int valid = 0;
|
||||
|
||||
CURL *curl = curl_easy_init();
|
||||
if (!curl) return -1;
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
|
||||
curl_easy_setopt(curl, CURLOPT_CAINFO, "/etc/ssl/certs/ca-certificates.crt");
|
||||
curl_easy_setopt(curl, CURLOPT_CAPATH, "/etc/ssl/certs");
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, IPCHECK_TIMEOUT_SECS);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
|
||||
for (int i = 0; i < SERVICE_COUNT; i++) {
|
||||
IPBuf buf = { .buf = {0}, .len = 0 };
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buf);
|
||||
curl_easy_setopt(curl, CURLOPT_URL, SERVICES[i]);
|
||||
|
||||
CURLcode res = curl_easy_perform(curl);
|
||||
|
||||
if (res != CURLE_OK) continue;
|
||||
|
||||
long http_code = 0;
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
|
||||
if (http_code != 200) continue;
|
||||
|
||||
buf.buf[buf.len] = '\0';
|
||||
if (sscanf(buf.buf, IPCHECK_IP_SCAN_FMT, results[valid]) != 1) continue;
|
||||
valid++;
|
||||
}
|
||||
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
if (valid < IPCHECK_MIN_RESPONDERS) {
|
||||
log_warn("ipcheck: only %d/%d services responded, need at least %d",
|
||||
valid, SERVICE_COUNT, IPCHECK_MIN_RESPONDERS);
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < valid; i++) {
|
||||
int count = 0;
|
||||
for (int j = 0; j < valid; j++) {
|
||||
if (strcmp(results[i], results[j]) == 0) count++;
|
||||
}
|
||||
if (count * 2 > valid) {
|
||||
strncpy(out_ip, results[i], IPCHECK_MAX_IP_LEN - 1);
|
||||
out_ip[IPCHECK_MAX_IP_LEN - 1] = '\0';
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
log_warn("ipcheck: no strict majority among %d/%d responders",
|
||||
valid, SERVICE_COUNT);
|
||||
return -2;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#ifndef IPCHECK_H
|
||||
#define IPCHECK_H
|
||||
|
||||
#define IPCHECK_MIN_RESPONDERS 3
|
||||
#define IPCHECK_TIMEOUT_SECS 10L
|
||||
#define IPCHECK_MAX_IP_LEN 46
|
||||
#define IPCHECK_IP_SCAN_FMT "%45s"
|
||||
|
||||
/*
|
||||
* Tries to get the public IP by seeing if a strict majority
|
||||
* of services agree. At least a minimum amount of services
|
||||
* need to respond.
|
||||
*
|
||||
* Returns 0 on success.
|
||||
* Returns -1 if fewer than IPCHECK_MIN_RESPONDERS answered.
|
||||
* Returns -2 if no strict majority was reached (tie or split).
|
||||
*/
|
||||
int ipcheck_get_public_ip(char *out_ip);
|
||||
|
||||
#endif /* IPCHECK_H */
|
||||
Reference in New Issue
Block a user