From b3d1a72ffc9518268cf19f73b02d1214ad18b6ad Mon Sep 17 00:00:00 2001 From: pantonshire Date: Sun, 6 Sep 2026 11:43:19 +0100 Subject: [PATCH] get public wan ip --- include/config.h | 2 + meson.build | 31 +++++++++ src/config.c | 6 ++ src/main.c | 178 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 217 insertions(+) create mode 100644 include/config.h create mode 100644 meson.build create mode 100644 src/config.c create mode 100644 src/main.c diff --git a/include/config.h b/include/config.h new file mode 100644 index 0000000..3f59c93 --- /dev/null +++ b/include/config.h @@ -0,0 +1,2 @@ +#pragma once + diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..009195d --- /dev/null +++ b/meson.build @@ -0,0 +1,31 @@ +project( + 'hetzner_ddns', + 'c', + default_options : ['c_std=gnu11,c11'] +) + +sources = [ + 'src/main.c', + 'src/config.c', +] + +includes = include_directories('include') + +args = [ + '-Wall', +] + +link_args = [ + '-lubox', + '-lubus', + '-luci', + '-ltlsl', +] + +executable( + meson.project_name(), + sources, + include_directories: includes, + c_args: args, + link_args: link_args, +) diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..0b0d578 --- /dev/null +++ b/src/config.c @@ -0,0 +1,6 @@ +#include +#include + +#include "config.h" + + diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..e3a3e27 --- /dev/null +++ b/src/main.c @@ -0,0 +1,178 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define UBUS_TIMEOUT_MS 10000 + +static void do_ubus_free(struct ubus_context **ctx) { + if (*ctx) + ubus_free(*ctx); +} + +static void do_uci_free(struct uci_context **ctx) { + if (*ctx) + uci_free_context(*ctx); +} + +DEF_SCOPED_TYPE(blob_buf, struct blob_buf, (struct blob_buf) { 0 }, blob_buf_free) +DEF_SCOPED_TYPE(ubus_context, struct ubus_context *, NULL, do_ubus_free) +DEF_SCOPED_TYPE(uci_context, struct uci_context *, NULL, do_uci_free) + +struct cidr { + struct tlsl_ipv4_addr addr; + uint32_t mask; +}; + +#define CIDR(BYTE0, BYTE1, BYTE2, BYTE3, LEN) { \ + .addr = TLSL_IPV4_ADDR(BYTE0, BYTE1, BYTE2, BYTE3), \ + .mask = TLSL_IPV4_NETMASK_U32(LEN), \ + } + +static bool addr_is_private(const struct tlsl_ipv4_addr *addr) { + static const struct cidr s_private[] = { + CIDR(10,0,0,0, 8), + CIDR(172,16,0,0, 12), + CIDR(192,168,0,0, 16), + }; + + int i; + + for (i = 0; i < (sizeof(s_private) / sizeof(s_private[0])); i++) { + if ((addr->addr & s_private[i].mask) == s_private[i].addr.addr) + return true; + } + return false; +} + +// Valid for the lifetime of the uci_context +static str_view_t hddns_uci_get( + struct uci_context *ctx, + const char *package, + const char *section, + const char *option) +{ + struct uci_ptr ptr; + + memset(&ptr, 0, sizeof(ptr)); + ptr.package = package; + ptr.section = section; + ptr.option = option; + + if ((uci_lookup_ptr(ctx, &ptr, NULL, true) != UCI_OK) + || !(ptr.flags & UCI_LOOKUP_COMPLETE) + || !ptr.o + || (ptr.o->type != UCI_TYPE_STRING)) + return SVIEW_NULL; + + return sview_from_cstr_unbounded(ptr.o->v.string); +} + +static void get_public_wan_ubus_cb(struct ubus_request *req, int type, struct blob_attr *msg) { + enum { + ATTR_IFSTATUS_IPV4, + __ATTR_IFSTATUS_COUNT, + }; + static const struct blobmsg_policy s_ifstatus_policy[__ATTR_IFSTATUS_COUNT] = { + [ATTR_IFSTATUS_IPV4] = { .name = "ipv4-address", .type = BLOBMSG_TYPE_ARRAY }, + }; + + enum { + ATTR_IPV4_ADDR, + __ATTR_IPV4_COUNT, + }; + static const struct blobmsg_policy s_ipv4_policy[__ATTR_IPV4_COUNT] = { + [ATTR_IPV4_ADDR] = { .name = "address", .type = BLOBMSG_TYPE_STRING }, + }; + + struct tlsl_ipv4_addr *out = (struct tlsl_ipv4_addr *)req->priv; + struct tlsl_ip_addr parsed; + int parse_res; + struct blob_attr *root_attrs[__ATTR_IFSTATUS_COUNT]; + struct blob_attr *ipv4_attrs[__ATTR_IPV4_COUNT]; + struct blob_attr *ipv4_tab_attr; + int rem; + + (void)type; + + if (blobmsg_parse(s_ifstatus_policy, __ATTR_IFSTATUS_COUNT, root_attrs, blobmsg_data(msg), + blobmsg_len(msg))) + return; + + if (!root_attrs[ATTR_IFSTATUS_IPV4]) + return; + + blobmsg_for_each_attr(ipv4_tab_attr, root_attrs[ATTR_IFSTATUS_IPV4], rem) { + if (blobmsg_type(ipv4_tab_attr) != BLOBMSG_TYPE_TABLE) + continue; + + if (blobmsg_parse(s_ipv4_policy, __ATTR_IPV4_COUNT, ipv4_attrs, blobmsg_data(ipv4_tab_attr), + blobmsg_len(ipv4_tab_attr))) + continue; + + if (!ipv4_attrs[ATTR_IPV4_ADDR]) + continue; + + if (tlsl_ip_addr_parse(blobmsg_get_string(ipv4_attrs[ATTR_IPV4_ADDR]), &parsed) + || (parsed.l3proto != TLSL_L3PROTO_IPV4) + || addr_is_private(&parsed.v4)) + continue; + + *out = parsed.v4; + break; + } +} + +static int get_public_wan_addr(struct ubus_context *ctx, struct tlsl_ipv4_addr *out) { + uint32_t id; + int res; + + out->addr = 0; + if ((res = ubus_lookup_id(ctx, "network.interface.wan", &id))) + return res; + if ((res = ubus_invoke(ctx, id, "status", NULL, get_public_wan_ubus_cb, out, UBUS_TIMEOUT_MS))) + return res; + return 0; +} + +int main(int argc, char **argv) { + SCOPED(uci_context, uci_ctx); + SCOPED(ubus_context, ubus_ctx); + str_view_t wan_dev; + struct tlsl_ip_addr wan_addr; + + if (!(uci_ctx = uci_alloc_context())) { + fprintf(stderr, "uci_alloc_context failed\n"); + return 1; + } + if (!(ubus_ctx = ubus_connect(NULL))) { + fprintf(stderr, "ubus_connect failed\n"); + return 1; + } + + // if (sview_is_null(wan_dev = hddns_uci_get(uci_ctx, "network", "wan", "device"))) { + // printf("uci_get failed\n"); + // return 1; + // } + // printf("out: " SVIEW_PRI "\n", SVIEW_PRI_ARGS(wan_dev)); + + // I'll do ipv6 when virgin media starts actually supporting ipv6 + wan_addr.l3proto = TLSL_L3PROTO_IPV4; + if (get_public_wan_addr(ubus_ctx, &wan_addr.v4) || !wan_addr.v4.addr) { + fprintf(stderr, "failed to get public wan ip\n"); + return 1; + } + + str_buf_t wan_buf; + wan_buf = tlsl_ip_addr_fmt(&wan_addr); + printf("wan=%s\n", wan_buf.ptr); + + return 0; +} +