mirror of
https://github.com/Telecominfraproject/wlan-ap.git
synced 2025-10-29 09:32:34 +00:00
431 lines
9.7 KiB
Plaintext
Executable File
431 lines
9.7 KiB
Plaintext
Executable File
#!/usr/bin/ucode
|
|
|
|
'use strict';
|
|
|
|
import { ulog_open, ulog, ULOG_SYSLOG, ULOG_STDIO, LOG_DAEMON, LOG_INFO } from 'log';
|
|
import * as libubus from 'ubus';
|
|
import * as uloop from 'uloop';
|
|
import * as libuci from 'uci';
|
|
import * as math from 'math';
|
|
import * as fs from 'fs';
|
|
|
|
const DISCOVER = 0;
|
|
const VALIDATING = 1;
|
|
const ONLINE = 2;
|
|
const OFFLINE = 3;
|
|
const ORPHAN = 4;
|
|
|
|
const DISCOVER_DHCP = "DHCP";
|
|
const DISCOVER_FLASH = "FLASH";
|
|
const DISCOVER_LOOKUP = "OpenLAN";
|
|
|
|
let ubus = libubus.connect();
|
|
let uci = libuci.cursor();
|
|
let state = DISCOVER;
|
|
let discovery_method = "";
|
|
let validate_time;
|
|
let offline_time;
|
|
let orphan_time;
|
|
let interval;
|
|
let timeouts = {
|
|
'offline': 4 * 60 * 60,
|
|
'validate': 120,
|
|
'orphan': 2 * 60 * 60,
|
|
interval: 10000,
|
|
expiry_interval: 60 * 60 * 1000,
|
|
expiry_threshold: 1 * 365 * 24 * 60 * 60,
|
|
};
|
|
|
|
ulog_open(ULOG_SYSLOG | ULOG_STDIO, LOG_DAEMON, "cloud_discover");
|
|
|
|
ulog(LOG_INFO, 'Start\n');
|
|
|
|
uloop.init();
|
|
|
|
let cds_server = 'discovery.open-lan.org';
|
|
|
|
function detect_certificate_type() {
|
|
let pipe = fs.popen(`openssl x509 -in /etc/ucentral/cert.pem -noout -issuer`);
|
|
let issuer = pipe.read("all");
|
|
pipe.close();
|
|
|
|
if (match(issuer, /OpenLAN Demo Birth CA/)) {
|
|
ulog(LOG_INFO, 'Certificate type is "Demo" \n');
|
|
cds_server = 'discovery-qa.open-lan.org';
|
|
timeouts.expiry_threshold = 3 * 24 * 60 * 60;
|
|
} else if (match(issuer, /OpenLAN Birth Issuing CA/)) {
|
|
ulog(LOG_INFO, 'Certificate type is "Production"\n');
|
|
} else {
|
|
ulog(LOG_INFO, 'Certificate type is "TIP"\n');
|
|
}
|
|
}
|
|
|
|
function readjsonfile(path) {
|
|
let file = fs.readfile(path);
|
|
if (file)
|
|
file = json(file);
|
|
return file;
|
|
}
|
|
|
|
function timeouts_load(){
|
|
let data = uci.get_all('ucentral', 'timeouts');
|
|
|
|
for (let key in [ 'offline', 'validate', 'orphan' ])
|
|
if (data && data[key])
|
|
timeouts[key] = +data[key];
|
|
let time_skew = timeouts.offline / 50 * (math.rand() % 50);
|
|
timeouts.offline_skew = timeouts.offline + time_skew;
|
|
ulog(LOG_INFO, 'Randomizing offline time from %d->%d \n', timeouts.offline, timeouts.offline_skew);
|
|
|
|
time_skew = timeouts.orphan / 50 * (math.rand() % 50);
|
|
timeouts.orphan_skew = timeouts.orphan + time_skew;
|
|
ulog(LOG_INFO, 'Randomizing orphan time from %d->%d \n', timeouts.orphan, timeouts.orphan_skew);
|
|
}
|
|
|
|
function client_start() {
|
|
ulog(LOG_INFO, '(re)starting client\n');
|
|
system('/etc/init.d/ucentral restart');
|
|
}
|
|
|
|
function dhcp_restart() {
|
|
ulog(LOG_INFO, 'restarting dhcp\n');
|
|
system('killall -USR1 udhcpc');
|
|
}
|
|
|
|
function ntp_restart() {
|
|
ulog(LOG_INFO, 'restarting ntp\n');
|
|
system('/etc/init.d/sysntpd restart');
|
|
}
|
|
|
|
function gateway_load() {
|
|
return readjsonfile('/etc/ucentral/gateway.json');
|
|
}
|
|
|
|
function discovery_state_write() {
|
|
let discovery_state = {
|
|
"type": discovery_method,
|
|
"updated": time()
|
|
};
|
|
fs.writefile('/etc/ucentral/discovery.state.json', discovery_state);
|
|
}
|
|
|
|
function gateway_write(data) {
|
|
let gateway = gateway_load();
|
|
gateway ??= {};
|
|
let new = {};
|
|
let changed = false;
|
|
for (let key in [ 'server', 'port', 'valid', 'hostname_validate' ]) {
|
|
if (exists(data, key))
|
|
new[key] = data[key];
|
|
else if (exists(gateway, key))
|
|
new[key] = gateway[key];
|
|
if (new[key] != gateway[key])
|
|
changed = true;
|
|
}
|
|
if (changed) {
|
|
fs.writefile('/etc/ucentral/gateway.json', new);
|
|
system('sync');
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
function gateway_available() {
|
|
let gateway = gateway_load();
|
|
if (!gateway || !gateway.server || !gateway.port)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
function set_state(set) {
|
|
if (state == set)
|
|
return;
|
|
let prev = state;
|
|
state = set;
|
|
|
|
switch(state) {
|
|
case DISCOVER:
|
|
ulog(LOG_INFO, 'Setting cloud to undiscovered\n');
|
|
fs.unlink('/tmp/cloud.json');
|
|
fs.unlink('/etc/ucentral/gateway.json');
|
|
gateway_write({ valid: false });
|
|
dhcp_restart();
|
|
break;
|
|
|
|
case VALIDATING:
|
|
ulog(LOG_INFO, 'Wait for validation\n');
|
|
validate_time = time();
|
|
state = VALIDATING;
|
|
break;
|
|
|
|
case ONLINE:
|
|
ulog(LOG_INFO, 'Connected to cloud\n');
|
|
if (prev == VALIDATING) {
|
|
ulog(LOG_INFO, 'Setting cloud controller to validated\n');
|
|
gateway_write({ valid: true });
|
|
discovery_state_write();
|
|
}
|
|
break;
|
|
|
|
case OFFLINE:
|
|
ulog(LOG_INFO, 'Lost connection to cloud\n');
|
|
offline_time = time();
|
|
break;
|
|
|
|
case ORPHAN:
|
|
ulog(LOG_INFO, 'Device is an orphan\n');
|
|
orphan_time = time();
|
|
break;
|
|
}
|
|
}
|
|
|
|
function discover_dhcp() {
|
|
let dhcp = readjsonfile('/tmp/cloud.json');
|
|
if (dhcp?.dhcp_server && dhcp?.dhcp_port) {
|
|
if (gateway_write({ server: dhcp.dhcp_server, port:dhcp.dhcp_port, valid: false, hostname_validate: dhcp.no_validation ? 0 : 1 })) {
|
|
ulog(LOG_INFO, `Discovered cloud via DHCP ${dhcp.dhcp_server}:${dhcp.dhcp_port}\n`);
|
|
client_start();
|
|
set_state(VALIDATING);
|
|
}
|
|
return true;
|
|
}
|
|
return !dhcp?.lease;
|
|
}
|
|
|
|
function redirector_lookup() {
|
|
const path = '/tmp/ucentral.redirector';
|
|
ulog(LOG_INFO, 'Contact redirector service\n');
|
|
let serial = uci.get('system', '@system[-1]', 'mac');
|
|
|
|
fs.unlink(path);
|
|
system(`curl -k --cert /etc/ucentral/operational.pem --key /etc/ucentral/key.pem --cacert /etc/ucentral/operational.ca https://${cds_server}/v1/devices/${serial} --output /tmp/ucentral.redirector`);
|
|
if (!fs.stat(path))
|
|
return;
|
|
let redir = readjsonfile(path);
|
|
if (redir?.controller_endpoint) {
|
|
let controller_endpoint = split(redir.controller_endpoint, ':');
|
|
if (gateway_write({ server: controller_endpoint[0], port: controller_endpoint[1] || 15002, valid: false, hostname_validate: 1 })) {
|
|
ulog(LOG_INFO, `Discovered cloud via lookup service ${controller_endpoint[0]}:${controller_endpoint[1] || 15002}\n`);
|
|
client_start();
|
|
set_state(VALIDATING);
|
|
}
|
|
} else {
|
|
ulog(LOG_INFO, 'Failed to discover cloud endpoint\n');
|
|
}
|
|
}
|
|
|
|
function discover_flash() {
|
|
if (!fs.stat('/etc/ucentral/gateway.flash'))
|
|
return 1;
|
|
ulog(LOG_INFO, 'Using pre-populated cloud information\n');
|
|
fs.writefile('/etc/ucentral/gateway.json', fs.readfile('/etc/ucentral/gateway.flash'));
|
|
client_start();
|
|
set_state(VALIDATING);
|
|
return 0;
|
|
}
|
|
|
|
function time_is_valid() {
|
|
let valid = !!fs.stat('/tmp/ntp.set');
|
|
if (!valid)
|
|
ntp_restart();
|
|
ulog(LOG_INFO, `Time is ${valid ? '': 'not '}valid\n`);
|
|
return valid;
|
|
}
|
|
|
|
function interval_handler() {
|
|
printf(`State ${state}\n`);
|
|
switch(state) {
|
|
case DISCOVER:
|
|
if (timeouts.interval < 60000)
|
|
timeouts.interval += 10000;
|
|
break;
|
|
default:
|
|
timeouts.interval = 10000;
|
|
break;
|
|
}
|
|
|
|
printf('setting interval to %d\n', timeouts.interval);
|
|
interval.set(timeouts.interval);
|
|
|
|
switch(state) {
|
|
case ORPHAN:
|
|
if (time() - orphan_time <= timeouts.orphan_skew)
|
|
break;
|
|
orphan_time = time();
|
|
|
|
/* fall through */
|
|
|
|
case DISCOVER:
|
|
ulog(LOG_INFO, 'Starting discover\n');
|
|
|
|
if (!time_is_valid())
|
|
return;
|
|
|
|
discovery_method = DISCOVER_DHCP;
|
|
if (discover_dhcp())
|
|
return;
|
|
|
|
if (system('/usr/bin/est_client enroll'))
|
|
return;
|
|
|
|
discovery_method = DISCOVER_FLASH;
|
|
if (!discover_flash())
|
|
return;
|
|
|
|
discovery_method = DISCOVER_LOOKUP;
|
|
redirector_lookup();
|
|
break;
|
|
|
|
case VALIDATING:
|
|
if (time() - validate_time <= timeouts.validate)
|
|
break;
|
|
ulog(LOG_INFO, 'validation failed, restarting discovery process\n');
|
|
set_state(DISCOVER);
|
|
break;
|
|
|
|
case OFFLINE:
|
|
if (time() - offline_time <= timeouts.offline_skew)
|
|
break;
|
|
ulog(LOG_INFO, 'offline for too long, setting device as orphaned\n');
|
|
set_state(ORPHAN);
|
|
break;
|
|
}
|
|
}
|
|
|
|
function trigger_reenroll() {
|
|
ulog(LOG_INFO, 'triggering reenroll\n');
|
|
|
|
if (system('/usr/bin/est_client reenroll')) {
|
|
ulog(LOG_INFO, 'reenroll failed\n');
|
|
return;
|
|
}
|
|
|
|
ulog(LOG_INFO, 'reenroll succeeded\n');
|
|
ulog(LOG_INFO, 'stopping client\n');
|
|
|
|
system('/etc/init.d/ucentral stop');
|
|
set_state(DISCOVER);
|
|
}
|
|
|
|
function expiry_handler() {
|
|
let stat = fs.stat('/etc/ucentral/operational.ca');
|
|
if (!stat)
|
|
return;
|
|
|
|
let ret = system(`openssl x509 -checkend ${timeouts.expiry_threshold} -noout -in /certificates/operational.pem`);
|
|
if (!ret) {
|
|
ulog(LOG_INFO, 'checked certificate expiry - all ok\n');
|
|
return;
|
|
}
|
|
|
|
ulog(LOG_INFO, 'certificate will expire soon\n');
|
|
trigger_reenroll();
|
|
}
|
|
|
|
let ubus_methods = {
|
|
discover: {
|
|
call: function(req) {
|
|
set_state(DISCOVER);
|
|
return 0;
|
|
},
|
|
args: {
|
|
}
|
|
},
|
|
renew: {
|
|
call: function(req) {
|
|
if (state != ONLINE)
|
|
return;
|
|
|
|
ulog(LOG_INFO, 'Validate cloud due to DHCP renew event\n');
|
|
|
|
let gateway = gateway_load();
|
|
let cloud = readjsonfile('/tmp/cloud.json');
|
|
if (!cloud?.dhcp_server || !cloud?.dhcp_port)
|
|
return 0;
|
|
|
|
if (cloud.dhcp_server != gateway?.server || cloud.dhcp_port != gateway?.port)
|
|
set_state(DISCOVER);
|
|
else
|
|
ulog(LOG_INFO, 'Cloud has not changed\n');
|
|
},
|
|
args: {
|
|
}
|
|
},
|
|
online: {
|
|
call: function(req) {
|
|
set_state(ONLINE);
|
|
return 0;
|
|
},
|
|
args: {
|
|
}
|
|
},
|
|
offline: {
|
|
call: function(req) {
|
|
if (state == ONLINE)
|
|
set_state(OFFLINE);
|
|
return 0;
|
|
},
|
|
args: {
|
|
}
|
|
},
|
|
reload: {
|
|
call: function(req) {
|
|
timeouts_load();
|
|
return 0;
|
|
},
|
|
args: {
|
|
|
|
}
|
|
},
|
|
status: {
|
|
call: function(req) {
|
|
const names = [ 'discover', 'validate', 'online', 'offline', 'orphan' ];
|
|
let ret = { state: names[state] };
|
|
switch(state){
|
|
case OFFLINE:
|
|
ret.since = time() - offline_time;
|
|
break;
|
|
case ORPHAN:
|
|
ret.since = time() - orphan_time;
|
|
break;
|
|
case VALIDATING:
|
|
ret.since = time() - validate_time;;
|
|
break;
|
|
}
|
|
return ret;
|
|
},
|
|
args: {},
|
|
},
|
|
reenroll: {
|
|
call: function(req) {
|
|
trigger_reenroll();
|
|
return 0;
|
|
},
|
|
args: {},
|
|
},
|
|
};
|
|
|
|
detect_certificate_type();
|
|
|
|
if (gateway_available()) {
|
|
let status = ubus.call('ucentral', 'status');
|
|
ulog(LOG_INFO, 'cloud is known\n');
|
|
if (status?.connected) {
|
|
state = ONLINE;
|
|
} else {
|
|
client_start();
|
|
set_state(VALIDATING);
|
|
}
|
|
} else {
|
|
dhcp_restart();
|
|
}
|
|
|
|
timeouts_load();
|
|
|
|
interval = uloop.interval(timeouts.interval, interval_handler);
|
|
uloop.interval(timeouts.expiry_interval, expiry_handler);
|
|
|
|
ubus.publish('cloud', ubus_methods);
|
|
|
|
uloop.run();
|
|
uloop.done();
|