/* * 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 #include #include #include #include #include #include #include #include #include #include #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; }