Feat: fetch public ip against from multiple services

This commit is contained in:
2026-06-01 01:31:35 +02:00
parent c419bb38e9
commit 3570eb02ea
2 changed files with 109 additions and 0 deletions
+89
View File
@@ -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;
}
+20
View File
@@ -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 */