From ac436fae7ef82d9b051098b033dc7fcf6c6fca55 Mon Sep 17 00:00:00 2001 From: Hammerfall Date: Sun, 31 Aug 2025 00:13:29 +0200 Subject: [PATCH] feat: initial build --- .dockerignore | 9 +++ CMakeLists.txt | 15 +++++ Dockerfile | 41 ++++++++++++++ docker-compose.yaml | 13 +++++ src/main.cpp | 132 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 210 insertions(+) create mode 100644 .dockerignore create mode 100644 CMakeLists.txt create mode 100644 Dockerfile create mode 100644 docker-compose.yaml create mode 100644 src/main.cpp diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..00a565d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +.git +.gitignore +build +cmake-build-* +.vscode +.idea +*.o +*.obj +*.log diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..04271f7 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.10) +project(ipv6) + +# Fügen Sie die folgenden Zeilen hinzu, um cURL zu Ihrem Projekt hinzuzufügen +find_package(CURL REQUIRED) +include_directories(${CURL_INCLUDE_DIRS}) + +# Setzen Sie das Ausgabeverzeichnis für ausführbare Dateien +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + +# Fügen Sie Ihre C++-Quelldateien hinzu +add_executable(ipv6 src/main.cpp) + +# Linken Sie Ihr ausführbares Programm mit der cURL-Bibliothek +target_link_libraries(ipv6 ${CURL_LIBRARIES}) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..854f007 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,41 @@ +# ---- Build stage ------------------------------------------------------------ +FROM debian:bookworm-slim AS build + +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential cmake pkg-config \ + libcurl4-openssl-dev ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app +COPY CMakeLists.txt . +COPY src ./src + +# Build +RUN mkdir -p build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && cmake --build . --config Release + +# ---- Runtime stage ---------------------------------------------------------- +FROM debian:bookworm-slim + +# nur Runtime-Libs +RUN apt-get update && apt-get install -y --no-install-recommends \ + libcurl4 ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# non-root User +RUN useradd -r -u 10001 appuser + +WORKDIR /app +COPY --from=build /app/build/ipv6_logger /app/ipv6_logger + +# Standard-Env (kannst du per Compose/CLI überschreiben) +ENV URL="http://j-massing.de:19121/logs" \ + INTERVAL="60" + +# Healthcheck: prüft IPv6-Erreichbarkeit von 1.1.1.1 (via curl connect-only) +HEALTHCHECK --interval=60s --timeout=5s --start-period=10s --retries=3 \ + CMD curl --connect-timeout 3 --silent --head http://1.1.1.1 || exit 1 + +USER appuser + +# Loggt Hostname beim Start und läuft als Daemon-Loop +ENTRYPOINT ["/app/ipv6_logger"] diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..a8c51d2 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,13 @@ +version: "3.8" +services: + ipv6-client: + build: . + image: ipv6-client:latest + container_name: ipv6-client + network_mode: host # <<< sieht Host-Interfaces u. echte IPv6 + restart: unless-stopped + environment: + HOSTNAME: "${HOSTNAME:jenspi}" + URL: "http://j-massing.de:19121/logs" + INTERVAL: "60" # Seconds + # Keine Ports nötig, da client-only diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..ece12f8 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,132 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +bool isPhysicalInterface(const char *interfaceName) +{ + return (std::strncmp(interfaceName, "eth", 3) == 0 || std::strncmp(interfaceName, "wlan", 4) == 0); +} + +std::string getGlobalIpv6() +{ + std::string ipv6; + struct ifaddrs *handle, *addressInfo; + if (getifaddrs(&handle) == -1) + { + std::cerr << "Error getting interface addresses" << std::endl; + return ""; + } + + for (addressInfo = handle; addressInfo != nullptr; addressInfo = addressInfo->ifa_next) + { + if (addressInfo->ifa_addr == nullptr || addressInfo->ifa_addr->sa_family != AF_INET6 || !isPhysicalInterface(addressInfo->ifa_name)) + { + continue; + } + + char addr[INET6_ADDRSTRLEN]; + if (inet_ntop(AF_INET6, &((struct sockaddr_in6 *)addressInfo->ifa_addr)->sin6_addr, addr, sizeof(addr)) == nullptr) + { + perror("inet_ntop"); + continue; + } + + /* INFO: Check if is GUA (Global unique address) currently only reserved are 2xxx and 3xxx */ + if (addr[0] != '2' && addr[0] != '3') + { + continue; + } + + if ((addressInfo->ifa_flags & IFF_UP) && !(addressInfo->ifa_flags & (IFF_LOOPBACK | IFF_POINTOPOINT)) && !IN6_IS_ADDR_LINKLOCAL(&((struct sockaddr_in6 *)addressInfo->ifa_addr)->sin6_addr)) + { + ipv6 = addr; + break; + } + } + freeifaddrs(handle); + return ipv6; +} + +void send(const std::string &url, const std::string &data) +{ + CURL *curl = curl_easy_init(); + if (!curl) + { + std::cerr << "Failed to initialize CURL" << std::endl; + return; + } + + struct curl_slist *headers = nullptr; + headers = curl_slist_append(headers, "Content-Type: text/plain"); + + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_POST, 1L); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, data.size()); + + CURLcode res = curl_easy_perform(curl); + if (res != CURLE_OK) + { + std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl; + } + + curl_slist_free_all(headers); + curl_easy_cleanup(curl); +} + +// Simple function to check internet connectivity by pinging a public DNS server +bool internetAvailable() +{ + CURL *curl = curl_easy_init(); + if (!curl) + return false; + curl_easy_setopt(curl, CURLOPT_URL, "http://1.1.1.1"); + curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 1L); + CURLcode res = curl_easy_perform(curl); + curl_easy_cleanup(curl); + return (res == CURLE_OK); +} + +std::string getEnv(const std::string &var, const std::string &defaultValue = "") +{ + const char *val = std::getenv(var.c_str()); + return val == nullptr ? defaultValue : std::string(val); +} + +std::string hostName = getEnv("HOSTNAME"); +std::string url = getEnv("URL", "http://j-massing.de:19121/logs"); +long interval = std::stol(getEnv("INTERVAL", "60")); + +int main(int argc, char **argv) +{ + printf("using hostname '%s'\n", hostName.c_str()); + + std::string lastIpv6; + lastIpv6.reserve(INET6_ADDRSTRLEN); + + while (true) + { + if (internetAvailable()) + { + std::string ipv6 = getGlobalIpv6(); + if (!ipv6.empty()) + { + send(url, hostName + "#" + ipv6); + } + } + std::this_thread::sleep_for(std::chrono::seconds(interval)); + } + return 0; +}