feat: initial build

This commit is contained in:
Hammerfall 2025-08-31 00:13:29 +02:00
commit ac436fae7e
No known key found for this signature in database
5 changed files with 210 additions and 0 deletions

9
.dockerignore Normal file
View file

@ -0,0 +1,9 @@
.git
.gitignore
build
cmake-build-*
.vscode
.idea
*.o
*.obj
*.log

15
CMakeLists.txt Normal file
View file

@ -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})

41
Dockerfile Normal file
View file

@ -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"]

13
docker-compose.yaml Normal file
View file

@ -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

132
src/main.cpp Normal file
View file

@ -0,0 +1,132 @@
#include <iostream>
#include <string>
#include <cstring>
#include <ifaddrs.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <curl/curl.h>
#include <thread>
#include <chrono>
#include <fstream>
#include <vector>
#include <filesystem>
#include <algorithm>
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;
}