implement auto gateway Ip auto detection (#25)
Reviewed-on: #25 implemented #19
This commit was merged in pull request #25.
This commit is contained in:
@@ -84,7 +84,7 @@ vi /etc/openuf/openuf.conf
|
|||||||
Example Configuration
|
Example Configuration
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
controller_ip = 192.168.1.1 # UniFi controller IP
|
controller_ip = # Empty: use IPv4 default gateway
|
||||||
lan_if = br-lan # Default Lan interface Name
|
lan_if = br-lan # Default Lan interface Name
|
||||||
ufmodel = u6-inwall # emulated model
|
ufmodel = u6-inwall # emulated model
|
||||||
inform_interval = 10 # inform interval
|
inform_interval = 10 # inform interval
|
||||||
@@ -96,6 +96,10 @@ protocol_debug_level = 0 # 0=off, 1=safe response details,
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Set `controller_ip` to a controller IPv4 address or hostname to override
|
||||||
|
autodetection. The automatic mode uses the IPv4 default-route gateway for
|
||||||
|
initial adoption; after adoption, the controller-provided inform URL is kept.
|
||||||
|
|
||||||
Daemon settings live in `/etc/openuf/openuf.conf`. Adoption and controller state
|
Daemon settings live in `/etc/openuf/openuf.conf`. Adoption and controller state
|
||||||
are persisted separately in `/etc/openuf/state.json`; do not copy that file
|
are persisted separately in `/etc/openuf/state.json`; do not copy that file
|
||||||
between devices because it contains the device authentication key.
|
between devices because it contains the device authentication key.
|
||||||
|
|||||||
+3
-2
@@ -1,6 +1,7 @@
|
|||||||
# openuf — configuration
|
# openuf — configuration
|
||||||
#
|
#
|
||||||
# controller_ip: UniFi controller IP address or hostname
|
# controller_ip: UniFi controller IP address or hostname; when empty,
|
||||||
|
# autodetect from the IPv4 default-route gateway
|
||||||
# lan_if: primary LAN interface (used for the AP MAC and IP)
|
# lan_if: primary LAN interface (used for the AP MAC and IP)
|
||||||
# ufmodel: emulated model (u6-inwall recommended)
|
# ufmodel: emulated model (u6-inwall recommended)
|
||||||
# inform_interval: seconds between inform requests (minimum 5)
|
# inform_interval: seconds between inform requests (minimum 5)
|
||||||
@@ -10,7 +11,7 @@
|
|||||||
# protocol_debug_level: 0=off, 1=safe response structure,
|
# protocol_debug_level: 0=off, 1=safe response structure,
|
||||||
# 2=full decrypted response; may expose credentials
|
# 2=full decrypted response; may expose credentials
|
||||||
|
|
||||||
controller_ip = 10.10.10.1
|
controller_ip =
|
||||||
lan_if = br-lan
|
lan_if = br-lan
|
||||||
ufmodel = uapg2-ac-lr
|
ufmodel = uapg2-ac-lr
|
||||||
inform_interval = 10
|
inform_interval = 10
|
||||||
|
|||||||
+22
-6
@@ -1,11 +1,13 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <ctype.h>
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
void config_load(openuf_config_t *cfg)
|
void config_load(openuf_config_t *cfg)
|
||||||
{
|
{
|
||||||
/* Defaults */
|
/* Defaults */
|
||||||
|
memset(cfg, 0, sizeof(*cfg));
|
||||||
strncpy(cfg->controller_ip, DEFAULT_CONTROLLER_IP, sizeof(cfg->controller_ip) - 1);
|
strncpy(cfg->controller_ip, DEFAULT_CONTROLLER_IP, sizeof(cfg->controller_ip) - 1);
|
||||||
strncpy(cfg->lan_if, DEFAULT_LAN_IF, sizeof(cfg->lan_if) - 1);
|
strncpy(cfg->lan_if, DEFAULT_LAN_IF, sizeof(cfg->lan_if) - 1);
|
||||||
strncpy(cfg->ufmodel, DEFAULT_UFMODEL, sizeof(cfg->ufmodel) - 1);
|
strncpy(cfg->ufmodel, DEFAULT_UFMODEL, sizeof(cfg->ufmodel) - 1);
|
||||||
@@ -21,15 +23,29 @@ void config_load(openuf_config_t *cfg)
|
|||||||
|
|
||||||
char line[256];
|
char line[256];
|
||||||
while (fgets(line, sizeof(line), f)) {
|
while (fgets(line, sizeof(line), f)) {
|
||||||
/* Strip newline */
|
char *key, *val, *eq, *end;
|
||||||
|
|
||||||
line[strcspn(line, "\r\n")] = '\0';
|
line[strcspn(line, "\r\n")] = '\0';
|
||||||
/* Skip comments / empty */
|
key = line;
|
||||||
if (line[0] == '#' || line[0] == '\0') continue;
|
while (isspace((unsigned char)*key)) key++;
|
||||||
|
if (*key == '#' || *key == '\0') continue;
|
||||||
|
|
||||||
char key[64] = {0}, val[192] = {0};
|
eq = strchr(key, '=');
|
||||||
if (sscanf(line, " %63[^= ] = %191s", key, val) != 2) continue;
|
if (!eq) continue;
|
||||||
|
*eq = '\0';
|
||||||
|
end = eq;
|
||||||
|
while (end > key && isspace((unsigned char)end[-1])) *--end = '\0';
|
||||||
|
|
||||||
if (!strcmp(key, "controller_ip")) strncpy(cfg->controller_ip, val, sizeof(cfg->controller_ip) - 1);
|
val = eq + 1;
|
||||||
|
while (isspace((unsigned char)*val)) val++;
|
||||||
|
end = val;
|
||||||
|
while (*end && !isspace((unsigned char)*end) && *end != '#') end++;
|
||||||
|
*end = '\0';
|
||||||
|
|
||||||
|
if (!strcmp(key, "controller_ip")) {
|
||||||
|
strncpy(cfg->controller_ip, val, sizeof(cfg->controller_ip) - 1);
|
||||||
|
cfg->controller_ip[sizeof(cfg->controller_ip) - 1] = '\0';
|
||||||
|
}
|
||||||
else if (!strcmp(key, "lan_if")) strncpy(cfg->lan_if, val, sizeof(cfg->lan_if) - 1);
|
else if (!strcmp(key, "lan_if")) strncpy(cfg->lan_if, val, sizeof(cfg->lan_if) - 1);
|
||||||
else if (!strcmp(key, "ufmodel")) strncpy(cfg->ufmodel, val, sizeof(cfg->ufmodel) - 1);
|
else if (!strcmp(key, "ufmodel")) strncpy(cfg->ufmodel, val, sizeof(cfg->ufmodel) - 1);
|
||||||
else if (!strcmp(key, "inform_interval")) cfg->inform_interval = atoi(val);
|
else if (!strcmp(key, "inform_interval")) cfg->inform_interval = atoi(val);
|
||||||
|
|||||||
+3
-2
@@ -9,7 +9,7 @@
|
|||||||
#ifndef ENABLE_LOGGING
|
#ifndef ENABLE_LOGGING
|
||||||
#define ENABLE_LOGGING 1
|
#define ENABLE_LOGGING 1
|
||||||
#endif
|
#endif
|
||||||
#define DEFAULT_CONTROLLER_IP "10.10.10.1"
|
#define DEFAULT_CONTROLLER_IP ""
|
||||||
#define DEFAULT_LAN_IF "br-lan"
|
#define DEFAULT_LAN_IF "br-lan"
|
||||||
#define DEFAULT_UFMODEL "uapg2-ac-lr"
|
#define DEFAULT_UFMODEL "uapg2-ac-lr"
|
||||||
#define DEFAULT_INFORM_INTERVAL 10
|
#define DEFAULT_INFORM_INTERVAL 10
|
||||||
@@ -39,7 +39,8 @@ typedef struct {
|
|||||||
} openuf_config_t;
|
} openuf_config_t;
|
||||||
|
|
||||||
/* Parse /etc/openuf/openuf.conf (simple key=value).
|
/* Parse /etc/openuf/openuf.conf (simple key=value).
|
||||||
* Fills *cfg with defaults first, then overrides from file. */
|
* Fills *cfg with defaults first, then overrides from file.
|
||||||
|
* An empty controller_ip selects default-gateway autodetection. */
|
||||||
void config_load(openuf_config_t *cfg);
|
void config_load(openuf_config_t *cfg);
|
||||||
|
|
||||||
#endif /* OPENUF_CONFIG_H */
|
#endif /* OPENUF_CONFIG_H */
|
||||||
|
|||||||
+86
-1
@@ -15,6 +15,7 @@
|
|||||||
#include <net/if.h>
|
#include <net/if.h>
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
|
#include <net/route.h>
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
|
|
||||||
@@ -64,6 +65,54 @@ static int get_ip(const char *iface, char *out, size_t sz)
|
|||||||
return ret;
|
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[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
for (int i = 1; i < argc-1; i++)
|
for (int i = 1; i < argc-1; i++)
|
||||||
@@ -105,9 +154,31 @@ int main(int argc, char *argv[])
|
|||||||
LOG("Initial device state: adopted=%d, authkey=%.8s...", state.adopted,
|
LOG("Initial device state: adopted=%d, authkey=%.8s...", state.adopted,
|
||||||
state.authkey[0] ? state.authkey : "DEFAULT");
|
state.authkey[0] ? state.authkey : "DEFAULT");
|
||||||
|
|
||||||
if (!state.adopted || !state.inform_url[0])
|
/*
|
||||||
|
* 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),
|
snprintf(state.inform_url, sizeof(state.inform_url),
|
||||||
"http://%s:%d%s", cfg.controller_ip, INFORM_PORT, INFORM_PATH);
|
"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);
|
state_save(&state);
|
||||||
|
|
||||||
printf("[openuf] Starting model=%-8s MAC=%s IP=%s\n",
|
printf("[openuf] Starting model=%-8s MAC=%s IP=%s\n",
|
||||||
@@ -180,6 +251,20 @@ int main(int argc, char *argv[])
|
|||||||
last_inform = now;
|
last_inform = now;
|
||||||
LOG("Sending inform");
|
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 */
|
/* Update IP each cycle */
|
||||||
char new_ip[64] = {0};
|
char new_ip[64] = {0};
|
||||||
if (get_ip(cfg.lan_if, new_ip, sizeof(new_ip)) == 0 ||
|
if (get_ip(cfg.lan_if, new_ip, sizeof(new_ip)) == 0 ||
|
||||||
|
|||||||
Reference in New Issue
Block a user