Files
OpenUniFi/src/main.c
T
2026-07-14 21:17:59 +01:00

295 lines
9.8 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* openuf - main.c
*
* Main daemon. Loop with three tasks:
* 1. Announce UDP broadcast+multicast each 10s (discovery L2)
* 2. Inform HTTP POST cifrado each 10s (adoption + telemetrics)
* 3. LLDP Raw frame L2 each 30s (visual topology in UniFi)
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/route.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "config.h"
#include "state.h"
#include "ufmodel.h"
#include "announce.h"
#include "inform.h"
#include "lldp.h"
#if ENABLE_LOGGING
FILE *log_fp = NULL;
#endif
#define LLDP_INTERVAL 30 /* seconds between LLDP frames */
#define LLDP_TTL 120 /* LLDP record lifetime in seconds */
static int get_mac(const char *iface, char *out, size_t sz)
{
char path[128];
snprintf(path, sizeof(path), "/sys/class/net/%s/address", iface);
FILE *f = fopen(path, "r");
if (!f) return -1;
char buf[32] = {0};
fgets(buf, sizeof(buf), f);
fclose(f);
buf[strcspn(buf, "\r\n")] = '\0';
if (strlen(buf) < 11) return -1;
strncpy(out, buf, sz-1);
return 0;
}
static int get_ip(const char *iface, char *out, size_t sz)
{
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0) return -1;
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, iface, IFNAMSIZ-1);
int ret = -1;
if (ioctl(fd, SIOCGIFADDR, &ifr) == 0) {
struct sockaddr_in *sa = (struct sockaddr_in *)&ifr.ifr_addr;
strncpy(out, inet_ntoa(sa->sin_addr), sz-1);
ret = 0;
}
close(fd);
return ret;
}
/* Find the IPv4 next hop used by the kernel's default route. Prefer the
* configured LAN interface when more than one default route exists. */
static int get_default_gateway(const char *preferred_iface,
char *out, size_t sz)
{
FILE *f = fopen("/proc/net/route", "r");
if (!f) return -1;
char line[256];
char iface[IFNAMSIZ];
unsigned long destination, gateway;
unsigned int flags, metric;
unsigned int best_metric = ~0U;
unsigned long best_gateway = 0;
int best_preferred = 0;
/* Skip the column headings. */
if (!fgets(line, sizeof(line), f)) {
fclose(f);
return -1;
}
while (fgets(line, sizeof(line), f)) {
if (sscanf(line, "%15s %lx %lx %x %*u %*u %u %*lx",
iface, &destination, &gateway, &flags,
&metric) != 5)
continue;
if (destination != 0 || gateway == 0 ||
!(flags & RTF_UP) || !(flags & RTF_GATEWAY))
continue;
int preferred = preferred_iface && preferred_iface[0] &&
!strcmp(iface, preferred_iface);
if (!best_gateway || preferred > best_preferred ||
(preferred == best_preferred && metric < best_metric)) {
best_gateway = gateway;
best_metric = metric;
best_preferred = preferred;
}
}
fclose(f);
if (!best_gateway) return -1;
struct in_addr addr = { .s_addr = (in_addr_t)best_gateway };
return inet_ntop(AF_INET, &addr, out, sz) ? 0 : -1;
}
int main(int argc, char *argv[])
{
for (int i = 1; i < argc-1; i++)
if (!strcmp(argv[i], "-c"))
setenv("OPENUF_CONF", argv[i+1], 1);
openuf_config_t cfg;
config_load(&cfg);
#if ENABLE_LOGGING
if (cfg.enable_logging) {
log_fp = fopen("/var/log/openuf.log", "a");
if (log_fp) {
LOG("Logging enabled");
}
}
inform_set_debug_level(cfg.protocol_debug_level);
LOG("Protocol debug level=%d", cfg.protocol_debug_level);
#endif
const uf_model_t *model = ufmodel_find(cfg.ufmodel);
char mac_str[32] = "00:00:00:00:00:00";
char ip_str[64] = "192.168.1.1";
if (get_mac(cfg.lan_if, mac_str, sizeof(mac_str)) != 0)
get_mac("eth0", mac_str, sizeof(mac_str));
if (get_ip(cfg.lan_if, ip_str, sizeof(ip_str)) != 0)
get_ip("eth0", ip_str, sizeof(ip_str));
openuf_state_t state;
state_load(&state);
strncpy(state.mac, mac_str, sizeof(state.mac)-1);
strncpy(state.ip, ip_str, sizeof(state.ip)-1);
if (!state.hostname[0])
strncpy(state.hostname, model->display_name, sizeof(state.hostname)-1);
/* Log initial state */
LOG("Initial device state: adopted=%d, authkey=%.8s...", state.adopted,
state.authkey[0] ? state.authkey : "DEFAULT");
/*
* A configured controller is an explicit override, including after
* adoption. With an empty setting, discover the initial controller from
* the default-route gateway and then preserve controller-issued state.
*/
if (cfg.controller_ip[0]) {
snprintf(state.inform_url, sizeof(state.inform_url),
"http://%s:%d%s", cfg.controller_ip, INFORM_PORT, INFORM_PATH);
LOG("Using configured controller override: %s", cfg.controller_ip);
} else if (!state.adopted || !state.inform_url[0]) {
char controller_ip[INET_ADDRSTRLEN] = {0};
if (get_default_gateway(cfg.lan_if, controller_ip,
sizeof(controller_ip)) == 0) {
snprintf(state.inform_url, sizeof(state.inform_url),
"http://%s:%d%s", controller_ip,
INFORM_PORT, INFORM_PATH);
LOG("Autodetected controller from default gateway: %s",
controller_ip);
} else {
state.inform_url[0] = '\0';
fprintf(stderr,
"[openuf] No controller configured and no IPv4 default "
"gateway found\n");
}
}
state_save(&state);
printf("[openuf] Starting version=%s model=%-8s MAC=%s IP=%s\n",
OPENUF_VERSION, model->model, mac_str, ip_str);
printf("[openuf] Controller: %s\n", state.inform_url);
printf("[openuf] Adopted: %s\n", state.adopted ? "yes" : "no");
printf("[openuf] LLDP available: %s\n",
lldp_available() ? "yes (lldpd)" : "no (transmit only)");
fflush(stdout);
LOG("Daemon started");
/* ── Announce socket ────────────────────────────────────────── */
announce_ctx_t ann;
if (cfg.enable_announce) {
if (announce_init(&ann, model, mac_str, ip_str) != 0) {
LOG("Failed to initialize announce service");
cfg.enable_announce = 0;
}
}
/* ── LLDP device description ───────────────────────── */
char lldp_desc[128];
snprintf(lldp_desc, sizeof(lldp_desc),
"%s %s%s (openuf)",
model->model_display, model->fw_pre, model->fw_ver);
/* ── Main loop ─────────────────────────────────────────── */
time_t start_time = time(NULL);
time_t last_announce = 0;
time_t last_inform = 0;
time_t last_lldp = 0;
printf("[openuf] Main loop started\n");
fflush(stdout);
while (1) {
time_t now = time(NULL);
/* Announce L2/UDP */
if (cfg.enable_announce &&
(now - last_announce) >= ANNOUNCE_INTERVAL) {
LOG("Sending announce");
int announce_rc = announce_send(&ann);
LOG("Announce completed with result=%d", announce_rc);
last_announce = now;
}
/* LLDP frames for each Ethernet interface */
if ((now - last_lldp) >= LLDP_INTERVAL) {
LOG("Sending LLDP frames");
for (int i = 0; i < model->port_table_len; i++) {
const char *iface = model->port_table[i].ifname;
/* Read the actual MAC of the interface if available */
char iface_mac[32];
if (get_mac(iface, iface_mac, sizeof(iface_mac)) != 0)
strncpy(iface_mac, mac_str, sizeof(iface_mac)-1);
int lldp_rc = lldp_send_frame(iface, iface_mac,
state.hostname, lldp_desc,
LLDP_TTL);
LOG("LLDP frame interface=%s mac=%s result=%d",
iface, iface_mac, lldp_rc);
}
last_lldp = now;
}
/* Inform HTTP POST */
if (cfg.enable_inform &&
(now - last_inform) >= cfg.inform_interval) {
last_inform = now;
LOG("Sending inform");
/* The default route may appear after the service starts. */
if (!cfg.controller_ip[0] && !state.inform_url[0]) {
char controller_ip[INET_ADDRSTRLEN] = {0};
if (get_default_gateway(cfg.lan_if, controller_ip,
sizeof(controller_ip)) == 0) {
snprintf(state.inform_url, sizeof(state.inform_url),
"http://%s:%d%s", controller_ip,
INFORM_PORT, INFORM_PATH);
LOG("Autodetected controller after startup: %s",
controller_ip);
state_save(&state);
}
}
/* Update IP each cycle */
char new_ip[64] = {0};
if (get_ip(cfg.lan_if, new_ip, sizeof(new_ip)) == 0 ||
get_ip("eth0", new_ip, sizeof(new_ip)) == 0)
strncpy(state.ip, new_ip, sizeof(state.ip)-1);
long uptime = (long)(now - start_time);
char err[128] = {0};
if (inform_send(&state, model, uptime, err) != 0) {
LOG("inform error: %s", err);
}
}
sleep(1);
}
announce_close(&ann);
#if ENABLE_LOGGING
if (log_fp) {
LOG("Shutting down");
fclose(log_fp);
}
#endif
return 0;
}