6 Commits

Author SHA1 Message Date
Mike Hansen
60be392661 Update ucentral-schema to latest ra_proposal branch commit
- Updated feeds/ucentral/ucentral-schema/Makefile to reference commit a96f40c302526229878fe1007135fd189b2b9e67 from ra_proposal branch
- Updated PKG_SOURCE_DATE to 2026-03-10
- Updated PKG_MIRROR_HASH to b2c419e65e906bbdca14a0fd44260d6c20b6ee5e9dc7a29a3ae021e0d2671bcf
- Removed problematic patch 0094-include-download.mk-switch-to-using-git-instead-of-h.patch that was forcing git:// protocol and causing build failures

Following the same fix applied in wlan-ap: 333a8fb2a0
2026-03-10 15:28:45 -04:00
Mike Hansen
f1d25f4913 Merge pull request #8 from Telecominfraproject/minimal-vyos-api-support
Add VyOS operational show command support to HTTPS API
2026-03-09 09:54:54 -04:00
Mike Hansen
29e3068c0e Add VyOS operational show command support to HTTPS API
This adds support for VyOS operational mode commands to the HTTPS API,
enabling state collection capabilities in olg-ucentral-schema.

Changes:
  - Add /show endpoint for operational mode commands (show system uptime,
    show interfaces, show dhcp server leases, etc.)
  - Fix form data encoding: change --form-string to --form for proper
    multipart/form-data submission to VyOS API

API now supports:
  - load/merge (config-file endpoint) - for applying configurations
  - showConfig (retrieve endpoint) - for reading configurations
  - show (show endpoint) - NEW for operational commands

The /show endpoint will be used by state.uc in olg-ucentral-schema to
query VyOS operational state for cloud reporting.

Tested on VyOS 2025.12.13-0020-rolling with OLG hardware.

Signed-off-by: Mike Hansen <mike.hansen@netexperience.com>
2026-03-05 14:38:18 -05:00
NavneetBarwal-RA
e74f615c36 Updates for schema enhancement for NAT object 2026-01-16 18:31:26 +05:30
NavneetBarwal-RA
a9173bf602 Added support to build olg ucentral-client image for x86.
Added VyOS API's to configure the VYOS Subsystem from olg ucentral client.
Added Reference for OLG ucentral schema and client.
2026-01-15 21:28:57 +05:30
NavneetBarwal-RA
e43625c24e Base SDK Commit 2026-01-15 18:51:47 +05:30
516 changed files with 60167 additions and 0 deletions

5
README.md Normal file
View File

@@ -0,0 +1,5 @@
# OLG Ucentral Client SDK
This is a sdk to build ucentral client image to support olg schema and configure VYOS. It can be used to validate OLG's end-to-end working, from accepting configuration from the Cloud controller to configuring VyOS.
- vyos/config_prepare.uc: Conversion from templates to VyOS text style configuration.
- vyos/https_server_api.uc: This will call VyOS retrieve and load APIs.

View File

@@ -0,0 +1,8 @@
.PHONY: all purge
all:
./dock-run.sh ./build.sh $(TARGET)
purge:
cd openwrt && rm -rf * && rm -rf .*
@echo Done

View File

@@ -0,0 +1,83 @@
# OpenWiFi AP NOS
OpenWrt-based access point network operating system (AP NOS) for TIP OpenWiFi.
Read more at [openwifi.tip.build](https://openwifi.tip.build/).
## Building
### Setting up your build machine
Building requires a recent Linux installation. Older systems without Python 3.7
will have trouble. See this guide for details:
https://openwrt.org/docs/guide-developer/toolchain/beginners-build-guide
Install build packages on Debian/Ubuntu (or see above guide for other systems):
```
sudo apt install build-essential libncurses5-dev gawk git libssl-dev gettext zlib1g-dev swig unzip time rsync python3 python3-setuptools python3-yaml
```
### Doing a native build on Linux
Use `./build.sh <target>`, or follow the manual steps below:
1. Clone and set up the tree. This will create an `openwrt/` directory.
```shell
./setup.py --setup # for subsequent builds, use --rebase instead
```
2. Select the profile and base package selection. This setup will install the
feeds and packages and generate the `.config` file.
```shell
cd openwrt
./scripts/gen_config.py linksys_ea8300
```
3. Build the tree (replace `-j 8` with the number of cores to use).
```shell
make -j 8 V=s
```
### Build output
The build results are located in the `openwrt/bin/` directory:
| Type | Path |
| ---------------- | ---------------------------------------------------- |
| Firmware images | `openwrt/bin/targets/<target>/<subtarget>/` |
| Kernel modules | `openwrt/bin/targets/<target>/<subtarget>/packages/` |
| Package binaries | `openwrt/bin/packages/<platform>/<feed>/` |
## Developer Notes
### Branching model
- `main` - Stable dev branch
- `next` - Integration branch
- `staging-*` - Feature/bug branches
- `release/v#.#.#` - Release branches (*major.minor.patch*)
### Repository structure
Build files:
- `Makefile` - Calls Docker environment per target
- `dock-run.sh` - Dockerized build environment
- `docker/Dockerfile` - Dockerfile for build image
- `build.sh` - Build script
- `setup.py` - Clone and set up the tree
- `config.yml` - Specifies OpenWrt version and patches to apply
Directories:
- `feeds/` - OpenWiFi feeds
- `patches/` - OpenWiFi patches applied during builds
- `profiles/` - Per-target kernel configs, packages, and feeds
- [wifi-ax](profiles/wifi-ax.yml): Wi-Fi AX packages
- [ucentral-ap](profiles/ucentral-ap.yml): uCentral packages
- [x64_vm](profiles/x64_vm.yml): x86-64 VM image
### uCentral packages
AP-NOS packages implementing the uCentral protocol include the following
repositories (refer to the [ucentral](feeds/ucentral/) feed for a full list):
- ucentral-client: https://github.com/Telecominfraproject/wlan-ucentral-client
- ucentral-schema: https://github.com/Telecominfraproject/wlan-ucentral-schema
- ucentral-wifi: https://github.com/blogic/ucentral-wifi

View File

@@ -0,0 +1,27 @@
#!/bin/bash
set -ex
ROOT_PATH=${PWD}
BUILD_DIR=${ROOT_PATH}/openwrt
TARGET=${1}
if [ -z "$1" ]; then
echo "Error: please specify TARGET"
exit 1
fi
if [ ! "$(ls -A $BUILD_DIR)" ]; then
python3 setup.py --setup || exit 1
else
python3 setup.py --rebase
echo "### OpenWrt repo already setup"
fi
cd ${BUILD_DIR}
./scripts/gen_config.py ${TARGET} || exit 1
cd -
echo "### Building image ..."
cd $BUILD_DIR
make -j$(nproc) V=s

View File

@@ -0,0 +1,7 @@
repo: https://github.com/openwrt/openwrt.git
branch: openwrt-23.05
revision: e92cf0c46ffe3ac7fca936c18577bfb19eb4ce9e
output_dir: ./output
patch_folders:
- patches

View File

@@ -0,0 +1,9 @@
#!/bin/bash -ex
tag=$(echo ${PWD} | tr / - | cut -b2- | tr A-Z a-z)
groups=$(id -G | xargs -n1 echo -n " --group-add ")
params="-v ${PWD}:${PWD} --rm -w ${PWD} -u"$(id -u):$(id -g)" $groups -v/etc/passwd:/etc/passwd:ro -v/etc/group:/etc/group:ro -v$HOME/.gitconfig:$HOME/.gitconfig:ro ${tag}"
docker build --tag=${tag} docker
docker run $params $@

View File

@@ -0,0 +1,12 @@
FROM ubuntu:20.04
RUN apt-get update \
&& DEBIAN_FRONTEND="noninteractive" apt-get -y install tzdata \
&& apt-get install -y \
time git-core build-essential gcc-multilib clang \
libncurses5-dev zlib1g-dev gawk flex gettext wget unzip python \
python3 python3-pip python3-yaml libssl-dev rsync \
&& apt-get clean
RUN git config --global user.email "you@example.com"
RUN git config --global user.name "Your Name"
RUN pip3 install kconfiglib

View File

@@ -0,0 +1,30 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=certificates
PKG_RELEASE:=1
PKG_LICENSE:=BSD-3-Clause
PKG_MAINTAINER:=John Crispin <john@phrozen.org>
include $(INCLUDE_DIR)/package.mk
define Package/certificates
SECTION:=ucentral
CATEGORY:=uCentral
TITLE:=TIP DigiCer certificate store
endef
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
endef
define Build/Compile/Default
endef
Build/Compile = $(Build/Compile/Default)
define Package/certificates/install
$(CP) ./files/* $(1)
endef
$(eval $(call BuildPackage,certificates))

View File

@@ -0,0 +1,36 @@
#!/bin/sh /etc/rc.common
START=09
copy_certificates() {
[ -f /certificates/key.pem ] || return
cp /certificates/*.pem /certificates/*.ca /etc/ucentral/ 2>/dev/null || true
chown root.network /etc/ucentral/*.pem /etc/ucentral/*.ca
chmod 0440 root.network /etc/ucentral/*.pem /etc/ucentral/*.ca
[ -f /certificates/gateway.json ] && cp /certificates/gateway.json /etc/ucentral/gateway.flash
[ -f /certificates/restrictions.json ] && cp /certificates/restrictions.json /etc/ucentral/
[ -f /certificates/sign_pubkey.pem ] && cp /certificates/sign_pubkey.pem /etc/ucentral/
country=`cat /certificates/ucentral.defaults | jsonfilter -e '@.country'`
[ -z "$country" -a -f /etc/uci-defaults/30_uboot-envtools ] && {
(. /etc/uci-defaults/30_uboot-envtools)
fw_printenv
country=$(fw_printenv -n country)
}
[ -z "$country" ] && country=US
echo "options cfg80211 ieee80211_regdom="$country > /etc/modules.conf
echo -n $country > /etc/ucentral/country
sync
exit 0
}
boot() {
case "$(board_name)" in
sonicfi,rap6*)
touch /tmp/squashfs
;;
esac
[ -f /etc/ucentral/key.pem ] && return
/usr/bin/mount_certs
copy_certificates
}

View File

@@ -0,0 +1,7 @@
#!/bin/sh
uci add system certificates
uci set system.@certificates[-1].key=/etc/ucentral/key.pem
uci set system.@certificates[-1].cert=/etc/ucentral/operational.pem
uci set system.@certificates[-1].ca=/etc/ucentral/operational.ca
uci commit

View File

@@ -0,0 +1,18 @@
generate_certificate_volume() {
grep certificates /proc/mtd > /dev/null
[ $? -eq 0 ] && return
ls /dev/ubi0 > /dev/null
[ $? -eq 0 ] || return
ubinfo /dev/ubi0 -N certificates > /dev/null
[ $? -eq 0 ] || {
ubinfo /dev/ubi0 -N wifi_fw > /dev/null
[ $? -eq 0 ] && ubirmvol /dev/ubi0 -N wifi_fw
ubirsvol /dev/ubi0 -N rootfs_data -s 10MiB
ubimkvol /dev/ubi0 -N certificates -S 20
}
}
boot_hook_add preinit_main generate_certificate_volume

View File

@@ -0,0 +1,158 @@
#!/bin/sh
check_certificates() {
[ -f /certificates/cert.pem -a -f /certificates/key.pem ] && exit 0
}
check_certificates
tar_part_lookup() {
part="$(fw_printenv -n cert_part)"
if [ "$part" -eq 0 ]; then
echo "$2"
part=1
else
echo "$1"
part=0
fi
fw_setenv cert_part $part
}
. /lib/functions.sh
mkdir -p /certificates /etc/ucentral/
case "$(board_name)" in
cig,wf660a)
mmc_dev=$(echo $(find_mmc_part "0:ETHPHYFW") | sed 's/^.\{5\}//')
[ -n "$mmc_dev" ] && mount -t ext4 /dev/$mmc_dev /certificates
;;
cig,wf672)
mmc_dev=$(echo $(find_mmc_part "cert") | sed 's/^.\{5\}//')
[ -n "$mmc_dev" ] && mount -t ext4 /dev/$mmc_dev /certificates
;;
sonicfi,rap7*)
if [ "$(board_name)" = "sonicfi,rap7110c-341x" ]; then
mmc_dev=$(echo $(find_mmc_part "certificates") | sed 's/^.\{5\}//')
[ -n "$mmc_dev" ] && mount -t ext4 /dev/$mmc_dev /certificates
else
mtd=$(find_mtd_index certificates)
[ -n "$mtd" ] && mount -t ext4 /dev/mtdblock$mtd /certificates
fi
;;
sonicfi,rap6*)
mtd=$(find_mtd_index certificates)
if [ "$(head -c 4 /dev/mtd$mtd)" == "hsqs" ]; then
mount -t squashfs /dev/mtdblock$mtd /mnt
cp /mnt/* /certificates
umount /mnt
fi
mtd=$(find_mtd_index devinfo)
[ -n "$mtd" ] && tar xf /dev/mtdblock$mtd -C /certificates
;;
udaya,a5-id2|\
yuncore,ax820)
mtd=$(find_mtd_index certificates)
if [ "$(head -c 4 /dev/mtd$mtd)" == "hsqs" ]; then
mount -t squashfs /dev/mtdblock$mtd /mnt
cp /mnt/* /certificates
umount /mnt
fi
part=$(tar_part_lookup "insta1" "insta2")
if [ -n "insta" ]; then
mtd=$(find_mtd_index $part)
[ -n "$mtd" ] && tar xf /dev/mtdblock$mtd -C /certificates
fi
;;
*)
mtd=$(find_mtd_index certificates)
if [ "$(head -c 4 /dev/mtd$mtd)" == "hsqs" ]; then
mount -t squashfs /dev/mtdblock$mtd /certificates
else
[ -n "$mtd" -a -f /sys/class/mtd/mtd$mtd/oobsize ] && ubiattach -p /dev/mtd$mtd
if [ -n "$(ubinfo -a | grep certificates)" ]; then
[ -e /dev/ubi0 ] && mount -t ubifs ubi0:certificates /certificates
[ -e /dev/ubi1 ] && mount -t ubifs ubi1:certificates /certificates
fi
fi
esac
check_certificates
# if we get here no valid certificates were found
PART_NAME=
case "$(board_name)" in
actiontec,web7200)
if grep -q bootselect=0 /proc/cmdline; then
PART_NAME=firmware2
else
PART_NAME=firmware1
fi
;;
edgecore,ecw5211|\
edgecore,eap101|\
edgecore,eap102|\
edgecore,eap104|\
edgecore,eap105|\
edgecore,eap111|\
edgecore,eap112|\
edgecore,oap101|\
edgecore,oap101e|\
edgecore,oap101-6e|\
edgecore,oap101e-6e|\
edgecore,oap103)
if grep -q rootfs1 /proc/cmdline; then
PART_NAME=rootfs2
else
PART_NAME=rootfs1
fi
;;
hfcl,ion4xi|\
hfcl,ion4xi_w|\
hfcl,ion4x_w|\
hfcl,ion4xi_HMR|\
hfcl,ion4x|\
hfcl,ion4x_2|\
hfcl,ion4xi_wp|\
hfcl,ion4xe)
if grep -q rootfs_1 /proc/cmdline; then
PART_NAME=rootfs
else
PART_NAME=rootfs_1
fi
;;
cig,wf186w|\
cig,wf189|\
cig,wf189w|\
cig,wf189h|\
cig,wf186h|\
cig,wf196|\
cig,wf188n|\
emplus,wap380c|\
emplus,wap385c|\
emplus,wap386v2|\
emplus,wap581|\
yuncore,ax840|\
yuncore,fap655)
PART_NAME=rootfs_1
;;
senao,iap2300m|\
senao,iap4300m|\
emplus,wap588m|\
senao,jeap6500)
PART_NAME=ubi
;;
*)
return 1
;;
esac
MTD=$(find_mtd_index $PART_NAME)
[ -z "$MTD" ] && return 1
ubiattach -m $MTD -d 3
[ -e /dev/ubi3 ] && mount -t ubifs ubi3:certificates /certificates
check_certificates

View File

@@ -0,0 +1,36 @@
#!/bin/sh
tar_part_lookup() {
part="$(fw_printenv -n cert_part)"
if [ "$part" -eq 0 ]; then
echo "$2"
part=1
else
echo "$1"
part=0
fi
fw_setenv cert_part $part
}
. /lib/functions.sh
case "$(board_name)" in
udaya,a5-id2|\
yuncore,ax820)
cd /certificates
tar cf /tmp/certs.tar .
part=$(tar_part_lookup "insta1" "insta2")
mtd=$(find_mtd_index $part)
dd if=/tmp/certs.tar of=/dev/mtdblock$mtd
;;
sonicfi,rap6*)
if [ "$(fw_printenv -n store_certs_disabled)" != "1" ]; then
cd /certificates
tar cf /tmp/certs.tar .
mtd=$(find_mtd_index devinfo)
block_size=$(cat /sys/class/mtd/mtd$mtd/size)
dd if=/tmp/certs.tar of=/tmp/certs_pad.tar bs=$block_size conv=sync
mtd write /tmp/certs_pad.tar /dev/mtd$mtd
rm -f /tmp/certs.tar /tmp/certs_pad.tar
fi
;;
esac

View File

@@ -0,0 +1,24 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=cloud_discovery
PKG_RELEASE:=1
PKG_LICENSE:=BSD-3-Clause
PKG_MAINTAINER:=John Crispin <john@phrozen.org>
include $(INCLUDE_DIR)/package.mk
define Package/cloud_discovery
SECTION:=ucentral
CATEGORY:=uCentral
TITLE:=TIP cloud_discovery
DEPENDS:=+certificates +bind-dig
endef
Build/Compile=
define Package/cloud_discovery/install
$(CP) ./files/* $(1)
endef
$(eval $(call BuildPackage,cloud_discovery))

View File

@@ -0,0 +1,42 @@
#!/bin/sh /etc/rc.common
START=98
USE_PROCD=1
PROG=/usr/bin/cloud_discovery
service_triggers() {
procd_add_reload_trigger ucentral
}
reload_service() {
ubus call cloud reload
}
start_service() {
[ -f /etc/ucentral/capabilities.json ] || {
mkdir -p /etc/ucentral/
/usr/share/ucentral/capabilities.uc
}
local valid=$(cat /etc/ucentral/gateway.json | jsonfilter -e '@["valid"]')
[ "$valid" == "true" ] ||
/usr/share/ucentral/ucentral.uc /etc/ucentral/ucentral.cfg.0000000001 > /dev/null
est_client check
[ $? -eq 1 ] && {
logger ERROR
logger ERROR
logger ERROR
logger The certificate used has a CN that does not match the serial of the device
echo The certificate used has a CN that does not match the serial of the device
logger ERROR
logger ERROR
logger ERROR
return
}
procd_open_instance
procd_set_param command "$PROG"
procd_set_param respawn
procd_close_instance
}

View File

@@ -0,0 +1,3 @@
#!/bin/sh
/usr/share/ucentral/cloud_discovery.uc $1

View File

@@ -0,0 +1,519 @@
#!/usr/bin/ucode
'use strict';
import { ulog_open, ulog, ULOG_SYSLOG, ULOG_STDIO, LOG_DAEMON, LOG_INFO } from 'log';
import { query } from 'resolv';
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_FQDN = "STANDARD_FQDN";
const DISCOVER_LOOKUP = "OpenLAN";
const STANDARD_FQDN = "openwifi.wlan.local";
const STANDARD_FQDN_PORT = 15002;
let ubus = libubus.connect();
let uci = libuci.cursor();
let state = DISCOVER;
let discovery_method = "";
let discovery_block_list = [];
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() {
if (length(discovery_method) == 0)
return;
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', 'cert', 'ca' ]) {
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 dnsmasq_rebind_allow(fqdn) {
let config_dir = '/tmp/dnsmasq.d';
let config_file = `${config_dir}/cloud-discovery.conf`;
if (!fs.stat(config_dir))
fs.mkdir(config_dir);
fs.writefile(config_file, `rebind-domain-ok=/${fqdn}/\n`);
system('/etc/init.d/dnsmasq reload');
}
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;
push(discovery_block_list, discovery_method);
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();
discovery_block_list = [];
}
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) {
let fqdn = split(dhcp.dhcp_server, ':')[0];
dnsmasq_rebind_allow(fqdn);
if (gateway_write({
server: dhcp.dhcp_server,
port: dhcp.dhcp_port,
valid: false,
hostname_validate: dhcp.no_validation ? 0 : 1,
cert: `/etc/ucentral/${fqdn}.pem`,
ca: `/etc/ucentral/${fqdn}.ca`
})) {
ulog(LOG_INFO, `Discovered cloud via DHCP ${dhcp.dhcp_server}:${dhcp.dhcp_port} - trying EST\n`);
fs.writefile('/tmp/discovery.method', DISCOVER_DHCP);
if (system('/usr/bin/est_client enroll'))
return false;
ulog(LOG_INFO, `Discovered cloud via DHCP ${dhcp.dhcp_server}:${dhcp.dhcp_port} - starting client\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,
cert: '/etc/ucentral/operational.pem',
ca: '/etc/ucentral/operational.ca'
})) {
ulog(LOG_INFO, `Discovered cloud via lookup service ${controller_endpoint[0]}:${controller_endpoint[1] || 15002}\n`);
fs.writefile('/tmp/discovery.method', DISCOVER_LOOKUP);
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'));
fs.writefile('/tmp/discovery.method', DISCOVER_FLASH);
client_start();
set_state(VALIDATING);
return 0;
}
function discover_standard_fqdn() {
ulog(LOG_INFO, `Trying standard FQDN: ${STANDARD_FQDN}\n`);
let result = query([STANDARD_FQDN], { type: ['A'] });
if (!result || !result[STANDARD_FQDN] || !result[STANDARD_FQDN].A) {
ulog(LOG_INFO, `Failed to resolve ${STANDARD_FQDN}\n`);
return false;
}
let address = result[STANDARD_FQDN].A[0];
ulog(LOG_INFO, `Resolved ${STANDARD_FQDN} to ${address}\n`);
dnsmasq_rebind_allow(STANDARD_FQDN);
if (gateway_write({
server: STANDARD_FQDN,
port: STANDARD_FQDN_PORT,
valid: false,
hostname_validate: 1,
cert: `/etc/ucentral/${STANDARD_FQDN}.pem`,
ca: `/etc/ucentral/${STANDARD_FQDN}.ca`
})) {
ulog(LOG_INFO, `Discovered cloud via standard FQDN ${STANDARD_FQDN}\n`);
fs.writefile('/tmp/discovery.method', DISCOVER_FQDN);
client_start();
set_state(VALIDATING);
return true;
}
return false;
}
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 is_discover_method_blacked() {
if (discovery_method in discovery_block_list)
return true;
return false;
}
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 (!is_discover_method_blacked() && discover_dhcp())
return;
if (system('/usr/bin/est_client enroll'))
return;
discovery_method = DISCOVER_FLASH;
if (!is_discover_method_blacked() && !discover_flash())
return;
discovery_method = DISCOVER_FQDN;
if (!is_discover_method_blacked() && discover_standard_fqdn())
return;
discovery_method = DISCOVER_LOOKUP;
redirector_lookup();
discovery_block_list = [];
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();

View File

@@ -0,0 +1,303 @@
#!/usr/bin/ucode
'use strict';
import { ulog_open, ulog, ULOG_SYSLOG, ULOG_STDIO, LOG_DAEMON, LOG_INFO } from 'log';
import * as fs from 'fs';
import * as libuci from 'uci';
let store_operational_pem = false;
let store_operational_ca = false;
let est_server = 'est.certificates.open-lan.org';
let cert_prefix = 'operational';
function cert_prefix_determine() {
let cloud_config = fs.readfile('/tmp/cloud.json');
if (cloud_config) {
let cloud = json(cloud_config);
if (cloud?.dhcp_server) {
let fqdn = split(cloud.dhcp_server, ':')[0];
ulog(LOG_INFO, `Using controller-specific cert prefix from cloud.json: ${fqdn}\n`);
return fqdn;
}
}
let discovery_method = trim(fs.readfile('/tmp/discovery.method') || 'OpenLAN');
ulog(LOG_INFO, `Discovery method from file: ${discovery_method}\n`);
if (discovery_method == 'OpenLAN') {
ulog(LOG_INFO, 'Using operational cert prefix\n');
return 'operational';
}
ulog(LOG_INFO, 'Using operational cert prefix as fallback\n');
return 'operational';
}
function discover_est_server_via_caa() {
let cloud_config = fs.readfile('/tmp/cloud.json');
if (!cloud_config)
return null;
let cloud = json(cloud_config);
if (!cloud || !cloud.dhcp_server)
return null;
let controller_fqdn = cloud.dhcp_server;
let fqdn_parts = split(controller_fqdn, ':');
if (length(fqdn_parts) > 0)
controller_fqdn = fqdn_parts[0];
ulog(LOG_INFO, `Attempting CAA lookup for controller FQDN: ${controller_fqdn}\n`);
let pipe = fs.popen(`dig @localhost ${controller_fqdn} CAA +short | cut -d'"' -f2`);
let est_server = pipe.read('all');
pipe.close();
if (!est_server)
return null;
est_server = trim(est_server);
if (est_server) {
ulog(LOG_INFO, `Found EST server via CAA: ${est_server}\n`);
return est_server;
}
return null;
}
function set_est_server() {
let discovered_server = discover_est_server_via_caa();
if (discovered_server) {
est_server = discovered_server;
return;
}
ulog(LOG_INFO, 'No EST server found via CAA, using certificate issuer-based selection\n');
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');
est_server = 'qaest.certificates.open-lan.org:8001';
} 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');
}
}
if (getenv('EST_SERVER'))
est_server = getenv('EST_SERVER');
if (getenv('CERT_PREFIX'))
cert_prefix = getenv('CERT_PREFIX');
ulog_open(ULOG_SYSLOG | ULOG_STDIO, LOG_DAEMON, "est_client");
function generate_csr(cert) {
if (!fs.stat('/tmp/csr.nohdr.p10')) {
let pipe = fs.popen(`openssl x509 -in ${cert} -noout -subject`);
let subject = pipe.read("all");
pipe.close();
subject = rtrim(subject);
subject = replace(subject, 'subject=', '/');
subject = replace(subject, ' = ', '=');
subject = replace(subject, ', ', '/');
let ret = system(`openssl req -subj "${subject}" -new -key /etc/ucentral/key.pem -out /tmp/csr.p10`);
if (ret) {
ulog(LOG_INFO, 'Failed to generate CSR\n');
return 1;
}
let input = fs.open('/tmp/csr.p10', 'r');
let output = fs.open('/tmp/csr.nohdr.p10', 'w');
let line;
while (line = input.read('line')) {
if (substr(line, 0, 4) == '----')
continue;
output.write(line);
}
input.close();
output.close();
ulog(LOG_INFO, 'Generated CSR\n');
}
return 0;
}
function store_operational_cert(path, target) {
system('mount_certs');
system(`cp ${path} /certificates/${target}`);
ulog(LOG_INFO, `Persistently stored ${target}\n`);
}
function p7_too_pem(src, dst) {
let input = fs.readfile(src);
let output = fs.open('/tmp/convert.p7', 'w');
output.write('-----BEGIN PKCS #7 SIGNED DATA-----\n');
output.write(`${input}\n-----END PKCS #7 SIGNED DATA-----`);
output.close();
let ret = system(`openssl pkcs7 -outform PEM -print_certs -in /tmp/convert.p7 -out ${dst}`);
if (ret) {
ulog(LOG_INFO, 'Failed to convert P7 to PEM\n');
return 1;
}
ulog(LOG_INFO, 'Converted P7 to PEM\n');
return 0;
}
function call_est_server(path, cert, target) {
if (generate_csr(cert))
return 1;
set_est_server();
let curl_cmd = 'curl -m 10 -X POST https://' + est_server + '/.well-known/est/' + path + ' -d @/tmp/csr.nohdr.p10 -H "Content-Type: application/pkcs10" --cert ' + cert + ' --key /etc/ucentral/key.pem --cacert /etc/ucentral/insta.pem -o /tmp/operational.nohdr.p7';
ulog(LOG_INFO, `Executing: ${curl_cmd}\n`);
let ret = system(curl_cmd);
if (ret) {
ulog(LOG_INFO, `Failed to request operational certificate (exit code: ${ret})\n`);
return 1;
}
ulog(LOG_INFO, 'EST succeeded\n');
return p7_too_pem('/tmp/operational.nohdr.p7', target);
}
function simpleenroll() {
cert_prefix = cert_prefix_determine();
ulog(LOG_INFO, `Checking for certificate: /etc/ucentral/${cert_prefix}.pem\n`);
if (fs.stat('/etc/ucentral/' + cert_prefix + '.pem')) {
ulog(LOG_INFO, 'Operational certificate is present\n');
return 0;
}
ulog(LOG_INFO, 'Operational certificate not found, enrolling...\n');
if (call_est_server('simpleenroll', '/etc/ucentral/cert.pem', '/etc/ucentral/' + cert_prefix + '.pem'))
return 1;
ulog(LOG_INFO, 'Operational cert acquired\n');
store_operational_pem = true;
return 0;
}
function simplereenroll() {
cert_prefix = cert_prefix_determine();
if (!fs.stat('/etc/ucentral/' + cert_prefix + '.pem')) {
ulog(LOG_INFO, 'Operational certificate was not found\n');
return 0;
}
if (call_est_server('simplereenroll', '/etc/ucentral/' + cert_prefix + '.pem', '/tmp/' + cert_prefix + '.pem'))
return 1;
ulog(LOG_INFO, 'Operational cert updated\n');
store_operational_cert('/tmp/' + cert_prefix + '.pem', cert_prefix + '.pem');
system('cp /tmp/' + cert_prefix + '.pem /etc/ucentral/');
system('store_certs');
return 0;
}
function load_operational_ca() {
cert_prefix = cert_prefix_determine();
if (fs.stat('/etc/ucentral/' + cert_prefix + '.ca')) {
ulog(LOG_INFO, 'Operational CA is present\n');
return 0;
}
set_est_server();
let curl_cmd = 'curl -m 10 -X GET https://' + est_server + '/.well-known/est/cacerts --cert /etc/ucentral/' + cert_prefix + '.pem --key /etc/ucentral/key.pem --cacert /etc/ucentral/insta.pem -o /tmp/' + cert_prefix + '.ca.nohdr.p7';
ulog(LOG_INFO, `Executing: ${curl_cmd}\n`);
let ret = system(curl_cmd);
if (!ret)
ret = p7_too_pem('/tmp/' + cert_prefix + '.ca.nohdr.p7', '/etc/ucentral/' + cert_prefix + '.ca');
if (ret) {
ulog(LOG_INFO, `Failed to load CA (exit code: ${ret})\n`);
return 1;
}
system('cat /etc/ucentral/openlan.pem >> /etc/ucentral/' + cert_prefix + '.ca');
ulog(LOG_INFO, 'Acquired CA\n');
store_operational_ca = true;
return 0;
}
function fwtool() {
if (!fs.stat('/etc/ucentral/cert.pem'))
return 0;
let pipe = fs.popen(`openssl x509 -in /etc/ucentral/cert.pem -noout -issuer`);
let issuer = pipe.read("all");
pipe.close();
if (!(match(issuer, /OpenLAN/) && match(issuer, /Birth/)))
return 0;
ulog(LOG_INFO, 'The issuer is insta\n');
let metadata = fs.readfile('/tmp/sysupgrade.meta');
if (metadata)
metadata = json(metadata);
if (!metadata)
return 0;
if (!metadata.est_supported) {
ulog(LOG_INFO, 'The image does not support EST\n');
return 1;
}
ulog(LOG_INFO, 'The image supports EST\n');
return 0;
}
function check_cert() {
if (!fs.stat('/etc/ucentral/cert.pem'))
return 0;
let pipe = fs.popen("openssl x509 -in /etc/ucentral/cert.pem -noout -subject -nameopt multiline | grep commonName | awk '{ print $3 }'");
let cn = pipe.read("all");
pipe.close();
if (!cn)
return 0;
cn = lc(trim(cn));
let uci = libuci.cursor();
let serial = uci.get('ucentral', 'config', 'serial');
return cn != serial;
}
switch(ARGV[0]) {
case 'enroll':
let ret = simpleenroll();
if (!ret)
ret = load_operational_ca();
if (store_operational_pem)
store_operational_cert('/etc/ucentral/' + cert_prefix + '.pem', cert_prefix + '.pem');
if (store_operational_ca)
store_operational_cert('/etc/ucentral/' + cert_prefix + '.ca', cert_prefix + '.ca');
if (store_operational_pem || store_operational_ca)
system('store_certs');
exit(ret);
case 'reenroll':
if (simplereenroll())
exit(1);
exit(0);
case 'fwtool':
exit(fwtool());
case 'check':
exit(check_cert());
}

View File

@@ -0,0 +1,45 @@
#!/usr/bin/ucode
import * as libubus from 'ubus';
import * as fs from 'fs';
let cmd = ARGV[0];
let ifname = getenv("interface");
let opt138 = fs.readfile('/tmp/dhcp-option-138');
let opt224 = fs.readfile('/tmp/dhcp-option-224');
if (cmd != 'bound' && cmd != 'renew')
exit(0);
/*let file = fs.readfile('/etc/ucentral/gateway.json');
if (file)
file = json(file);
file ??= {};
if (file.server && file.port && file.valid)
exit(0);
*/
let cloud = {
lease: true,
};
if (opt138) {
let dhcp = opt138;
dhcp = split(dhcp, ':');
cloud.dhcp_server = dhcp[0];
cloud.dhcp_port = dhcp[1] ?? 15002;
cloud.no_validation = true;
}
if (opt224) {
let dhcp = opt224;
dhcp = split(dhcp, ':');
cloud.dhcp_server = dhcp[0];
cloud.dhcp_port = dhcp[1] ?? 15002;
}
fs.writefile('/tmp/cloud.json', cloud);
if ((opt138 || opt224) && cmd == 'renew') {
let ubus = libubus.connect();
ubus.call('cloud', 'renew');
}
exit(0);

View File

@@ -0,0 +1,17 @@
#
# Copyright (C) 2021 Jo-Philipp Wich <jo@mein.io>
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI uCentral Configuration
LUCI_DEPENDS:=+luci-base
PKG_LICENSE:=Apache-2.0
include ../luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@@ -0,0 +1,98 @@
'use strict';
'require fs';
'require ui';
'require dom';
'require rpc';
'require session';
'require baseclass';
var callReboot = rpc.declare({
object: 'system',
method: 'reboot',
expect: { result: 0 }
});
function handleReboot(ev) {
return callReboot().then(function(res) {
if (res != 0) {
showError(_('The reboot command failed with code %d').format(res));
L.raise('Error', 'Reboot failed');
}
showProgress(_('The system is rebooting in order to attempt applying the remote configuration profile now. If not successful, the device will revert back into the initial provisioning state.'));
ui.awaitReconnect();
}).catch(function(e) { showError(e.message) });
}
function setDirty(isDirty) {
if (isDirty) {
session.setLocalData('ucentral-dirty', true);
ui.showIndicator('ucentral-dirty', _('Reboot required'), showApplySettings);
}
else {
session.setLocalData('ucentral-dirty', null);
ui.hideIndicator('ucentral-dirty');
}
}
function handleContinue(ev) {
setDirty(true);
ui.hideModal();
}
function showApplySettings() {
ui.showModal(_('Apply Settings'), [
E('p', _('The device must be rebooted in order to apply the changed settings. Once the uCentral agent successfully connects to the controller, the remote configuration profile will be applied and the initial provisioning web interface is disabled.')),
E('div', { 'class': 'right' }, [
E('button', { 'click': handleReboot, 'class': 'btn primary' }, [ _('Apply settings and reboot device now') ]),
'\xa0',
E('button', { 'click': handleContinue, 'class': 'btn' }, [ _('Continue configuration') ])
])
]);
}
function showProgress(text, timeout) {
var dlg = ui.showModal(null, [
E('p', { 'class': (timeout > 0) ? null : 'spinning' }, text)
]);
dlg.removeChild(dlg.firstElementChild);
if (timeout > 0)
window.setTimeout(ui.hideModal, timeout);
return dlg;
}
function showError(text) {
ui.showModal(_('Error'), [
E('p', [ text ]),
E('div', { 'class': 'right' }, [
E('button', { 'class': 'btn', 'click': ui.hideModal }, _('OK'))
])
]);
}
if (session.getLocalData('ucentral-dirty'))
setDirty(true);
return baseclass.extend({
save: function(serializeFn, ev) {
var m = dom.findClassInstance(document.querySelector('.cbi-map'));
return m.save().then(function() {
return fs.write('/etc/ucentral/profile.json', serializeFn(m.data.data));
}).then(function() {
return fs.exec('/sbin/profileupdate');
}).then(function() {
showApplySettings();
}).catch(function(err) {
showError(_('Unable to save settings: %s').format(err));
});
},
setDirty: setDirty,
showError: showError,
showProgress: showProgress,
});

View File

@@ -0,0 +1,70 @@
'use strict';
'require view';
'require form';
'require fs';
'require ui';
'require tools.ucentral as uctool';
var profile = null;
function serialize(data) {
if (!L.isObject(profile.unit))
profile.unit = {};
profile.redirector = data.local.redirector;
profile.unit.location = data.local.location;
return JSON.stringify(profile, null, '\t');
}
return view.extend({
load: function() {
return L.resolveDefault(fs.read('/etc/ucentral/profile.json'), '').then(function(data) {
try { profile = JSON.parse(data); }
catch(e) { profile = {}; };
});
},
render: function() {
var m, s, o, data = { local: {
redirector: profile.redirector,
location: L.isObject(profile.unit) ? profile.unit.location : ''
} };
m = new form.JSONMap(data);
m.readonly = !L.hasViewPermission();
s = m.section(form.NamedSection, 'local', 'local', _('Local settings'),
_('The settings on this page specify how the local uCentral client connects to the controller server.'));
s.option(form.Value, 'redirector', _('Redirector URL'));
s.option(form.Value, 'location', _('Unit location'));
o = s.option(form.Button, '_certs', _('Certificates'));
o.inputtitle = _('Upload certificate bundle…');
o.onclick = function(ev) {
return ui.uploadFile('/tmp/certs.tar').then(function(res) {
uctool.showProgress(_('Verifying certificates…'));
return fs.exec('/sbin/certupdate').then(function(res) {
if (res.code) {
uctool.showError(_('Certificate validation failed: %s').format(res.stderr || res.stdout));
}
else {
uctool.showProgress(_('Certificates updated.'), 1500);
uctool.setDirty(true);
}
}, function(err) {
uctool.showError(_('Unable to verify certificates: %s').format(err));
});
});
};
return m.render();
},
handleSave: uctool.save.bind(uctool, serialize),
handleSaveApply: null,
handleReset: null
});

View File

@@ -0,0 +1,121 @@
'use strict';
'require rpc';
'require view';
'require tools.ucentral as uctool';
var callSystemBoard = rpc.declare({
object: 'system',
method: 'board'
});
var callSystemInfo = rpc.declare({
object: 'system',
method: 'info'
});
function progressbar(value, max, byte) {
var vn = parseInt(value) || 0,
mn = parseInt(max) || 100,
fv = byte ? String.format('%1024.2mB', value) : value,
fm = byte ? String.format('%1024.2mB', max) : max,
pc = Math.floor((100 / mn) * vn);
return E('div', {
'class': 'cbi-progressbar',
'title': '%s / %s (%d%%)'.format(fv, fm, pc)
}, E('div', { 'style': 'width:%.2f%%'.format(pc) }));
}
return view.extend({
load: function() {
return Promise.all([
L.resolveDefault(callSystemBoard(), {}),
L.resolveDefault(callSystemInfo(), {})
]);
},
render: function(data) {
var boardinfo = data[0],
systeminfo = data[1],
mem = L.isObject(systeminfo.memory) ? systeminfo.memory : {},
swap = L.isObject(systeminfo.swap) ? systeminfo.swap : {},
datestr = null;
if (systeminfo.localtime) {
var date = new Date(systeminfo.localtime * 1000);
datestr = '%04d-%02d-%02d %02d:%02d:%02d'.format(
date.getUTCFullYear(),
date.getUTCMonth() + 1,
date.getUTCDate(),
date.getUTCHours(),
date.getUTCMinutes(),
date.getUTCSeconds()
);
}
var sysfields = [
_('Hostname'), boardinfo.hostname,
_('Model'), boardinfo.model,
_('Architecture'), boardinfo.system,
_('Firmware Version'), (L.isObject(boardinfo.release) ? boardinfo.release.description : '?'),
_('Kernel Version'), boardinfo.kernel,
_('Local Time'), datestr,
_('Uptime'), systeminfo.uptime ? '%t'.format(systeminfo.uptime) : null,
_('Load Average'), Array.isArray(systeminfo.load) ? '%.2f, %.2f, %.2f'.format(
systeminfo.load[0] / 65535.0,
systeminfo.load[1] / 65535.0,
systeminfo.load[2] / 65535.0
) : null
];
var systable = E('table', { 'class': 'table' });
for (var i = 0; i < sysfields.length; i += 2) {
systable.appendChild(E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left', 'width': '33%' }, [ sysfields[i] ]),
E('td', { 'class': 'td left' }, [ (sysfields[i + 1] != null) ? sysfields[i + 1] : '?' ])
]));
}
var memfields = [
_('Total Available'), (mem.available) ? mem.available : (mem.total && mem.free && mem.buffered) ? mem.free + mem.buffered : null, mem.total,
_('Used'), (mem.total && mem.free) ? (mem.total - mem.free) : null, mem.total,
_('Buffered'), (mem.total && mem.buffered) ? mem.buffered : null, mem.total
];
if (mem.cached)
memfields.push(_('Cached'), mem.cached, mem.total);
if (swap.total > 0)
memfields.push(_('Swap free'), swap.free, swap.total);
var memtable = E('table', { 'class': 'table' });
for (var i = 0; i < memfields.length; i += 3) {
memtable.appendChild(E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left', 'width': '33%' }, [ memfields[i] ]),
E('td', { 'class': 'td left' }, [
(memfields[i + 1] != null) ? progressbar(memfields[i + 1], memfields[i + 2], true) : '?'
])
]));
}
return E([], [
E('div', { 'class': 'cbi-section' }, [
E('h3', _('System')),
systable
]),
E('div', { 'class': 'cbi-section' }, [
E('h3', _('Memory')),
memtable
]),
]);
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});

View File

@@ -0,0 +1,210 @@
'use strict';
'require view';
'require dom';
'require form';
'require rpc';
'require fs';
'require ui';
'require tools.ucentral as uctool';
var callSystemValidateFirmwareImage = rpc.declare({
object: 'system',
method: 'validate_firmware_image',
params: [ 'path' ],
expect: { '': { valid: false, forcable: true } }
});
var callReboot = rpc.declare({
object: 'system',
method: 'reboot',
expect: { result: 0 }
});
var mapdata = { actions: {}, config: {} };
return view.extend({
load: function() {
var tasks = [
fs.trimmed('/proc/mtd'),
fs.trimmed('/proc/mounts')
];
return Promise.all(tasks);
},
handleFirstboot: function(ev) {
if (!confirm(_('Do you really want to erase all settings?')))
return;
uctool.showProgress(_('The system is erasing the configuration partition now and will reboot itself when finished.'));
/* Currently the sysupgrade rpc call will not return, hence no promise handling */
fs.exec('/sbin/firstboot', [ '-r', '-y' ]);
ui.awaitReconnect('192.168.1.1', 'openwrt.lan');
},
handleDiagnostics: function(ev) {
return fs.exec('/sbin/diagnostic-bundle').then(function(result) {
var form = E('form', {
method: 'post',
action: L.env.cgi_base + '/cgi-download',
enctype: 'application/x-www-form-urlencoded'
}, [
E('input', { 'type': 'hidden', 'name': 'sessionid', 'value': L.env.sessionid }),
E('input', { 'type': 'hidden', 'name': 'path', 'value': '/tmp/bundle.maverick.tar.gz' }),
E('input', { 'type': 'hidden', 'name': 'filename', 'value': 'bundle.maverick.tar.gz' }),
E('input', { 'type': 'hidden', 'name': 'mimetype', 'value': 'application/gzip' })
]);
document.body.appendChild(form);
form.submit();
form.parentNode.removeChild(form);
});
},
handleSysupgrade: function(ev) {
return ui.uploadFile('/tmp/firmware.bin', ev.target.firstChild)
.then(L.bind(function(btn, reply) {
btn.firstChild.data = _('Checking image…');
uctool.showProgress(_('Verifying the uploaded image file.'));
return callSystemValidateFirmwareImage('/tmp/firmware.bin')
.then(function(res) { return [ reply, res ]; });
}, this, ev.target))
.then(L.bind(function(btn, reply) {
return fs.exec('/sbin/sysupgrade', [ '--test', '/tmp/firmware.bin' ])
.then(function(res) { reply.push(res); return reply; });
}, this, ev.target))
.then(L.bind(function(btn, res) {
var is_valid = res[1].valid,
body = [];
if (is_valid) {
body.push(E('p', _("The firmware image was uploaded. Compare the checksum and file size listed below with the original file to ensure data integrity. <br /> Click 'Continue' below to start the flash procedure.")));
body.push(E('ul', {}, [
res[0].size ? E('li', {}, '%s: %1024.2mB'.format(_('Size'), res[0].size)) : '',
res[0].checksum ? E('li', {}, '%s: %s'.format(_('MD5'), res[0].checksum)) : '',
res[0].sha256sum ? E('li', {}, '%s: %s'.format(_('SHA256'), res[0].sha256sum)) : ''
]));
}
else {
body.push(E('p', _("The firmware image is invalid and cannot be flashed. Check the diagnostics below for further details.")));
if (res[2].stderr)
body.push(E('pre', { 'class': 'alert-message' }, [ res[2].stderr ]));
}
var cntbtn = E('button', {
'class': 'btn cbi-button-action important',
'click': ui.createHandlerFn(this, 'handleSysupgradeConfirm', btn)
}, [ _('Continue') ]);
if (!is_valid)
cntbtn.disabled = true;
body.push(E('div', { 'class': 'right' }, [
E('button', {
'class': 'btn',
'click': ui.createHandlerFn(this, function(ev) {
return fs.remove('/tmp/firmware.bin').finally(ui.hideModal);
})
}, [ _('Cancel') ]), ' ', cntbtn
]));
ui.showModal(is_valid ? _('Flash image?') : _('Invalid image'), body);
}, this, ev.target))
.catch(function(e) { uctool.showError(e.message) })
.finally(L.bind(function(btn) {
btn.firstChild.data = _('Flash image…');
}, this, ev.target));
},
handleSysupgradeConfirm: function(btn, ev) {
btn.firstChild.data = _('Flashing…');
uctool.showProgress(_('The system is flashing now.<br /> DO NOT POWER OFF THE DEVICE!<br /> Wait a few minutes before you try to reconnect. It might be necessary to renew the address of your computer to reach the device again, depending on your settings.'));
/* Currently the sysupgrade rpc call will not return, hence no promise handling */
fs.exec('/sbin/sysupgrade', [ '-n', '/tmp/firmware.bin' ]);
ui.awaitReconnect('192.168.1.1', 'openwrt.lan');
},
handleReboot: function(ev) {
return callReboot().then(function(res) {
if (res != 0) {
uctool.showError(_('The reboot command failed with code %d').format(res));
L.raise('Error', 'Reboot failed');
}
uctool.showProgress(_('The device is rebooting now. This page will try to reconnect automatically once the device is fully booted.'));
ui.awaitReconnect();
})
.catch(function(e) { uctool.showError(e.message) });
},
render: function(rpc_replies) {
var procmtd = rpc_replies[0],
procmounts = rpc_replies[1],
has_rootfs_data = (procmtd.match(/"rootfs_data"/) != null) || (procmounts.match("overlayfs:\/overlay \/ ") != null),
m, s, o, ss;
m = new form.JSONMap(mapdata);
m.readonly = !L.hasViewPermission();
s = m.section(form.NamedSection, 'actions');
o = s.option(form.SectionValue, 'actions', form.NamedSection, 'actions', 'actions', _('Reboot device'),
_('Issue a reboot and restart the operating system on this device.'));
ss = o.subsection;
o = ss.option(form.Button, 'reboot');
o.inputstyle = 'action important';
o.inputtitle = _('Reboot');
o.onclick = L.bind(this.handleReboot, this);
o = s.option(form.SectionValue, 'actions', form.NamedSection, 'actions', 'actions', _('Reset to defaults'),
_('Reset the system to its initial state and discard any configuration changes.'));
ss = o.subsection;
if (has_rootfs_data) {
o = ss.option(form.Button, 'reset');
o.inputstyle = 'negative important';
o.inputtitle = _('Perform reset');
o.onclick = this.handleFirstboot;
}
o = s.option(form.SectionValue, 'actions', form.NamedSection, 'actions', 'actions', _('Firmware upgrade'),
_('Upload a compatible firmware image here to upgrade the running system.'));
ss = o.subsection;
o = ss.option(form.Button, 'sysupgrade');
o.inputstyle = 'action important';
o.inputtitle = _('Flash image…');
o.onclick = L.bind(this.handleSysupgrade, this);
o = s.option(form.SectionValue, 'actions', form.NamedSection, 'actions', 'actions', _('Diagnostic bundle'),
_('Download the default diagnostic bundle from the AP.'));
ss = o.subsection;
o = ss.option(form.Button, 'Diagnostic');
o.inputstyle = 'action important';
o.inputtitle = _('Download Diagnostics');
o.onclick = L.bind(this.handleDiagnostics, this);
return m.render();
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});

View File

@@ -0,0 +1,138 @@
'use strict';
'require view';
'require form';
'require fs';
'require tools.ucentral as uctool';
var profile = null;
function serialize(data) {
if (data.broadband.protocol != 'default')
profile.broadband = Object.assign({}, data.broadband);
else
delete profile.broadband;
return JSON.stringify(profile, function(key, val) {
return (key.charAt(0) != '.') ? val : undefined;
}, '\t');
}
return view.extend({
load: function() {
return L.resolveDefault(fs.read('/etc/ucentral/profile.json'), '').then(function(data) {
try { profile = JSON.parse(data); }
catch(e) { profile = {}; };
if (!L.isObject(profile.broadband))
profile.broadband = { protocol: 'default' };
});
},
render: function() {
var m, s, o, data = { broadband: {} };
m = new form.JSONMap(data);
m.readonly = !L.hasViewPermission();
s = m.section(form.NamedSection, 'broadband', 'broadband', _('Uplink configuration'),
_('The uplink settings allow overriding the WAN connection properties of the local device.'));
o = s.option(form.ListValue, 'protocol', _('Connection'));
o.value('default', _('Use default cloud settings'));
o.value('static', _('Static address configuration'));
o.value('dhcp', _('Address configuration via DHCP'));
o.value('pppoe', _('Address configuration via PPPoE'));
o.value('wwan', _('Cellular network connection'));
o.value('wds', _('WiFi WDS uplink'));
o = s.option(form.ListValue, 'modem-type', _('Modem type'));
o.depends('protocol', 'wwan');
o.value('wwan', _('Automatic', 'Automatic modem type selection'));
o.value('mbim', 'MBIM');
o.value('qmi', 'QMI');
o = s.option(form.Value, 'access-point-name', _('APN', 'Cellular access point name'));
o.depends('protocol', 'wwan');
o.validate = function(section_id, value) {
if (!/^[a-zA-Z0-9\-.]*[a-zA-Z0-9]$/.test(value))
return _('Invalid APN provided');
return true;
};
o = s.option(form.Value, 'pin-code', _('PIN'));
o.depends('protocol', 'wwan');
o.datatype = 'and(uinteger,minlength(4),maxlength(8))';
o = s.option(form.ListValue, 'authentication-type', _('Authentication'));
o.depends('protocol', 'wwan');
o.value('', _('No authentication'));
o.value('pap-chap', 'PAP/CHAP');
o.value('chap', 'CHAP');
o.value('pap', 'PAP');
o = s.option(form.Value, 'user-name', _('Username'));
o.depends('authentication-type', 'pap-chap');
o.depends('authentication-type', 'chap');
o.depends('authentication-type', 'pap');
o.depends('protocol', 'pppoe');
o = s.option(form.Value, 'password', _('Password'));
o.depends('authentication-type', 'pap-chap');
o.depends('authentication-type', 'chap');
o.depends('authentication-type', 'pap');
o.depends('protocol', 'pppoe');
o.password = true;
o = s.option(form.Value, 'ipv4-address', _('IPv4 Address'), _('Address and mask in CIDR notation.'));
o.depends('protocol', 'static');
o.datatype = 'or(cidr4,ipnet4)';
o.rmempty = false;
o = s.option(form.Value, 'ipv4-gateway', _('IPv4 Gateway'));
o.depends('protocol', 'static');
o.datatype = 'ip4addr("nomask")';
o.rmempty = false;
o = s.option(form.Value, 'ipv6-address', _('IPv6 Address'), _('Address and mask in CIDR notation.'));
o.depends('protocol', 'static');
o.datatype = 'or(cidr6,ipnet6)';
o = s.option(form.Value, 'ipv6-gateway', _('IPv6 Gateway'));
o.depends('protocol', 'static');
o.datatype = 'ip6addr("nomask")';
o = s.option(form.DynamicList, 'use-dns', _('DNS Servers'));
o.depends('protocol', 'static');
o.datatype = 'ipaddr("nomask")';
o = s.option(form.Value, 'ssid', _('SSID'));
o.depends('protocol', 'wds');
o.rmempty = false;
o = s.option(form.Value, 'passphrase', _('Passphrase'));
o.depends('protocol', 'wds');
o.password = true;
o.rmempty = false;
o.datatype = "rangelength(8, 31)";
o = s.option(form.ListValue, 'encryption', _('Encryption'));
o.depends('protocol', 'wds');
o.value('psk', 'PSK');
o.value('psk-mixed', 'PSK-Mixed');
o.value('psk2', 'PSK2');
o.value('sae', 'SAE');
o.value('sae-mixed', 'SAE-Mixed');
o.password = true;
for (var i = 0; i < s.children.length; i++)
data.broadband[s.children[i].option] = profile.broadband[s.children[i].option];
return m.render();
},
handleSave: uctool.save.bind(uctool, serialize),
handleSaveApply: null,
handleReset: null
});

View File

@@ -0,0 +1,426 @@
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Project-Id-Version: \n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"X-Generator: Poedit 2.4.2\n"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:53
msgctxt "Cellular access point name"
msgid "APN"
msgstr "APN"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:86
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:96
msgid "Address and mask in CIDR notation."
msgstr "Adresse und Netzmaske in CIDR-Notation."
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:43
msgid "Address configuration via DHCP"
msgstr "Adresskonfiguration mittels DHCP"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:44
msgid "Address configuration via PPPoE"
msgstr "Adresskonfiguration mittels PPPoE"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/tools/ucentral.js:49
msgid "Apply Settings"
msgstr "Einstellungen anwenden"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/tools/ucentral.js:52
msgid "Apply settings and reboot device now"
msgstr "Anwenden und Gerät jetzt neu starten"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/status.js:60
msgid "Architecture"
msgstr "Architektur"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:66
msgid "Authentication"
msgstr "Authentifizierung"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:49
msgctxt "Automatic modem type selection"
msgid "Automatic"
msgstr "automatisch"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/status.js:85
msgid "Buffered"
msgstr "Gepuffert"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/status.js:89
msgid "Cached"
msgstr "Gecached"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:92
msgid "Cancel"
msgstr "Abbrechen"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:45
msgid "Cellular network connection"
msgstr "Mobilfunkverbindung"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/settings.js:51
msgid "Certificate validation failed: %s"
msgstr "Validierung der Zertifikate fehlgeschlagen: %s"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/settings.js:43
msgid "Certificates"
msgstr "Zertifikate"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/settings.js:54
msgid "Certificates updated."
msgstr "Zertifikate aktualisiert."
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:51
msgid "Checking image…"
msgstr "Prüfe Imagedatei…"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:40
msgid "Connection"
msgstr "Verbindung"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:84
msgid "Continue"
msgstr "Fortfahren"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/tools/ucentral.js:54
msgid "Continue configuration"
msgstr "Konfiguration fortsetzen"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:104
msgid "DNS Servers"
msgstr "DNS-Server"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:37
msgid "Do you really want to erase all settings?"
msgstr "Sollen wirklich alle Systemeinstellungen zurückgesetzt werden?"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/tools/ucentral.js:73
msgid "Error"
msgstr "Fehler"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/status.js:61
msgid "Firmware Version"
msgstr "Firmware-Version"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:163
msgid "Firmware upgrade"
msgstr "Firmware Update"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:95
msgid "Flash image?"
msgstr "Image flashen?"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:99
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:170
msgid "Flash image…"
msgstr "Image flashen…"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:104
msgid "Flashing…"
msgstr "Schreibt…"
#: modules/luci-mod-ucentral/root/usr/share/rpcd/acl.d/luci-mod-ucentral.json:3
msgid "Grant access to ucentral configuration"
msgstr "Zugriff auf uCentral-Konfigurationseinstellungen ermöglichen"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/status.js:58
msgid "Hostname"
msgstr "Hostname"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:86
msgid "IPv4 Address"
msgstr "IPv4-Adresse"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:91
msgid "IPv4 Gateway"
msgstr "IPv4-Gateway"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:96
msgid "IPv6 Address"
msgstr "IPv6-Adresse"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:100
msgid "IPv6 Gateway"
msgstr "IPv6-Gateway"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:57
msgid "Invalid APN provided"
msgstr "Ungültige APN angegeben"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:95
msgid "Invalid image"
msgstr "Ungültige Image-Datei"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:141
msgid "Issue a reboot and restart the operating system on this device."
msgstr "Einen Reboot auslösen und das Betriebssystem des Gerätes neu starten."
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/status.js:62
msgid "Kernel Version"
msgstr "Kernel-Version"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/status.js:65
msgid "Load Average"
msgstr "Systemlast"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/status.js:63
msgid "Local Time"
msgstr "Lokalzeit"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/settings.js:37
msgid "Local settings"
msgstr "Lokale Einstellungen"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:69
msgid "MD5"
msgstr "MD5"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/status.js:112
msgid "Memory"
msgstr "Arbeitsspeicher"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/status.js:59
msgid "Model"
msgstr "Modell"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:47
msgid "Modem type"
msgstr "Modemtyp"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:68
msgid "No authentication"
msgstr "keine Authentifizierung"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/tools/ucentral.js:76
msgid "OK"
msgstr "OK"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:62
msgid "PIN"
msgstr "OIN"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:79
msgid "Password"
msgstr "Passwort"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:158
msgid "Perform reset"
msgstr "System zurücksetzen"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:147
msgid "Reboot"
msgstr "Reboot"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:140
msgid "Reboot device"
msgstr "Gerät neu starten"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/tools/ucentral.js:35
msgid "Reboot required"
msgstr "Neustart erforderlich"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/settings.js:40
msgid "Redirector URL"
msgstr "Redirector-URL"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:152
msgid ""
"Reset the system to its initial state and discard any configuration changes."
msgstr ""
"Das System auf Grundeinstellungen zurücksetzen und sämtliche "
"Konfigurationsänderungen verwerfen."
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:151
msgid "Reset to defaults"
msgstr "Grundeinstellungen wiederherstellen"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:70
msgid "SHA256"
msgstr "SHA256"
#: modules/luci-mod-ucentral/root/usr/share/luci/menu.d/luci-mod-ucentral.json:40
msgid "Settings"
msgstr "Einstellungen"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:68
msgid "Size"
msgstr "Größe"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:42
msgid "Static address configuration"
msgstr "Statische Adresskonfiguration"
#: modules/luci-mod-ucentral/root/usr/share/luci/menu.d/luci-mod-ucentral.json:16
msgid "Status"
msgstr "Status"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/status.js:92
msgid "Swap free"
msgstr "Freier Auslagerungsspeicher"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/status.js:108
#: modules/luci-mod-ucentral/root/usr/share/luci/menu.d/luci-mod-ucentral.json:52
msgid "System"
msgstr "System"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:121
msgid ""
"The device is rebooting now. This page will try to reconnect automatically "
"once the device is fully booted."
msgstr ""
"Das Gerät startet jetzt neu. Diese Seite wird versuchen sich automatisch neu "
"zu verbinden sobald das Gerät wieder voll hochgefahren ist."
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/tools/ucentral.js:50
msgid ""
"The device must be rebooted in order to apply the changed settings. Once the "
"uCentral agent successfully connects to the controller, the remote "
"configuration profile will be applied and the initial provisioning web "
"interface is disabled."
msgstr ""
"Das Gerät muss neu gestartet werden um die geänderten Einstellungen "
"anzuwenden. Sobald sich der uCentral-Client nach dem Neustart erfolgreich "
"mit dem Controller verbindet, wird das entfernte Konfigurationsprofil für "
"dieses Gerät angewendet und das initiale Provisionierungs-Webinterface "
"deaktiviert."
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:74
msgid ""
"The firmware image is invalid and cannot be flashed. Check the diagnostics "
"below for further details."
msgstr ""
"Die Firmware-Datei ist ungültig und kann nicht geflasht werden. Die "
"nachfolgenden Diagnosemeldungen enthalten weitere Details."
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:66
msgid ""
"The firmware image was uploaded. Compare the checksum and file size listed "
"below with the original file to ensure data integrity. <br /> Click "
"'Continue' below to start the flash procedure."
msgstr ""
"Die Firmware-Datei wurde hochgeladen. Die Prüfsummen und Dateigröße mit der "
"Originaldatei vergleichen um die Integrität des Images sicherzustellen.<br /"
"> \"Fortfahren\" anklicken um die Upgrade-Prozedur zu starten."
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/tools/ucentral.js:22
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:117
msgid "The reboot command failed with code %d"
msgstr "Das Reboot-Kommando wurde mit Fehlercode %d abgebrochen"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/settings.js:38
msgid ""
"The settings on this page specify how the local uCentral client connects to "
"the controller server."
msgstr ""
"Die Einstellungen auf dieser Seite beeinflussen die Verbindung des uCentral "
"Clients zum Controller-Server."
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:40
msgid ""
"The system is erasing the configuration partition now and will reboot itself "
"when finished."
msgstr ""
"Das System löscht nun die Konfigurationspartition und startet das Gerät neu "
"sobald die Prozedur abgeschlossen ist."
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:106
msgid ""
"The system is flashing now.<br /> DO NOT POWER OFF THE DEVICE!<br /> Wait a "
"few minutes before you try to reconnect. It might be necessary to renew the "
"address of your computer to reach the device again, depending on your "
"settings."
msgstr ""
"Das System-Upgrade läuft jetzt.<br />DAS GERÄT NICHT AUSSCHALTEN!<br /"
">Einige Minuten warten, bevor ein Verbindungsversuch unternommen wird. Ja "
"nach Netzwerktopologie kann es nötig sein, die lokalen Adresseinstellungen "
"des Computers zu verändern bevor wieder eine Verbindung zum Gerät möglich "
"ist."
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/tools/ucentral.js:26
msgid ""
"The system is rebooting in order to attempt applying the remote "
"configuration profile now. If not successful, the device will revert back "
"into the initial provisioning state."
msgstr ""
"Das System startet jetzt neu um zu versuchen das entfernte "
"Konfigurationsprofil anzuwenden. Im Fehlerfall wird das Gerät in den "
"initialen Provisionierungs-Zustand zurückversetzt."
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:38
msgid ""
"The uplink settings allow overriding the WAN connection properties of the "
"local device."
msgstr ""
"Die Uplink-Einstellungen ermöglichen es die WAN-Verbindungseigenschaften des "
"lokalen Gerätes zu überschreiben."
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/status.js:83
msgid "Total Available"
msgstr "Gesamt verfügbar"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/tools/ucentral.js:95
msgid "Unable to save settings: %s"
msgstr "Einstellungen konnten nicht gespeichert werden: %s"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/settings.js:58
msgid "Unable to verify certificates: %s"
msgstr "Zertifikate konnten nicht verifiziert werden: %s"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/settings.js:41
msgid "Unit location"
msgstr "Gerätestandort"
#: modules/luci-mod-ucentral/root/usr/share/luci/menu.d/luci-mod-ucentral.json:28
msgid "Uplink"
msgstr "Uplink"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:37
msgid "Uplink configuration"
msgstr "Uplink-Einstellungen"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:164
msgid "Upload a compatible firmware image here to upgrade the running system."
msgstr ""
"Kompatible Firmware-Datei hier hochladen um das laufende System zu "
"aktualisieren."
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/settings.js:44
msgid "Upload certificate bundle…"
msgstr "Zertifikatsarchiv hochladen…"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/status.js:64
msgid "Uptime"
msgstr "Laufzeit"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:41
msgid "Use default cloud settings"
msgstr "Cloud-Einstellungen nutzen"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/status.js:84
msgid "Used"
msgstr "In Benutzung"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:73
msgid "Username"
msgstr "Benutzername"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/settings.js:47
msgid "Verifying certificates…"
msgstr "Überprüfe Zertifikate…"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:52
msgid "Verifying the uploaded image file."
msgstr "Überprüfe die hochgeladene Image-Datei."
#: modules/luci-mod-ucentral/root/usr/share/luci/menu.d/luci-mod-ucentral.json:3
msgid "uCentral"
msgstr "uCentral"

View File

@@ -0,0 +1,385 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:53
msgctxt "Cellular access point name"
msgid "APN"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:86
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:96
msgid "Address and mask in CIDR notation."
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:43
msgid "Address configuration via DHCP"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:44
msgid "Address configuration via PPPoE"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/tools/ucentral.js:49
msgid "Apply Settings"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/tools/ucentral.js:52
msgid "Apply settings and reboot device now"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/status.js:60
msgid "Architecture"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:66
msgid "Authentication"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:49
msgctxt "Automatic modem type selection"
msgid "Automatic"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/status.js:85
msgid "Buffered"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/status.js:89
msgid "Cached"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:92
msgid "Cancel"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:45
msgid "Cellular network connection"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/settings.js:51
msgid "Certificate validation failed: %s"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/settings.js:43
msgid "Certificates"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/settings.js:54
msgid "Certificates updated."
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:51
msgid "Checking image…"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:40
msgid "Connection"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:84
msgid "Continue"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/tools/ucentral.js:54
msgid "Continue configuration"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:104
msgid "DNS Servers"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:37
msgid "Do you really want to erase all settings?"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/tools/ucentral.js:73
msgid "Error"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/status.js:61
msgid "Firmware Version"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:163
msgid "Firmware upgrade"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:95
msgid "Flash image?"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:99
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:170
msgid "Flash image…"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:104
msgid "Flashing…"
msgstr ""
#: modules/luci-mod-ucentral/root/usr/share/rpcd/acl.d/luci-mod-ucentral.json:3
msgid "Grant access to ucentral configuration"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/status.js:58
msgid "Hostname"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:86
msgid "IPv4 Address"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:91
msgid "IPv4 Gateway"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:96
msgid "IPv6 Address"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:100
msgid "IPv6 Gateway"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:57
msgid "Invalid APN provided"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:95
msgid "Invalid image"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:141
msgid "Issue a reboot and restart the operating system on this device."
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/status.js:62
msgid "Kernel Version"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/status.js:65
msgid "Load Average"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/status.js:63
msgid "Local Time"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/settings.js:37
msgid "Local settings"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:69
msgid "MD5"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/status.js:112
msgid "Memory"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/status.js:59
msgid "Model"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:47
msgid "Modem type"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:68
msgid "No authentication"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/tools/ucentral.js:76
msgid "OK"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:62
msgid "PIN"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:79
msgid "Password"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:158
msgid "Perform reset"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:147
msgid "Reboot"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:140
msgid "Reboot device"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/tools/ucentral.js:35
msgid "Reboot required"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/settings.js:40
msgid "Redirector URL"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:152
msgid ""
"Reset the system to its initial state and discard any configuration changes."
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:151
msgid "Reset to defaults"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:70
msgid "SHA256"
msgstr ""
#: modules/luci-mod-ucentral/root/usr/share/luci/menu.d/luci-mod-ucentral.json:40
msgid "Settings"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:68
msgid "Size"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:42
msgid "Static address configuration"
msgstr ""
#: modules/luci-mod-ucentral/root/usr/share/luci/menu.d/luci-mod-ucentral.json:16
msgid "Status"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/status.js:92
msgid "Swap free"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/status.js:108
#: modules/luci-mod-ucentral/root/usr/share/luci/menu.d/luci-mod-ucentral.json:52
msgid "System"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:121
msgid ""
"The device is rebooting now. This page will try to reconnect automatically "
"once the device is fully booted."
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/tools/ucentral.js:50
msgid ""
"The device must be rebooted in order to apply the changed settings. Once the "
"uCentral agent successfully connects to the controller, the remote "
"configuration profile will be applied and the initial provisioning web "
"interface is disabled."
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:74
msgid ""
"The firmware image is invalid and cannot be flashed. Check the diagnostics "
"below for further details."
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:66
msgid ""
"The firmware image was uploaded. Compare the checksum and file size listed "
"below with the original file to ensure data integrity. <br /> Click "
"'Continue' below to start the flash procedure."
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/tools/ucentral.js:22
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:117
msgid "The reboot command failed with code %d"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/settings.js:38
msgid ""
"The settings on this page specify how the local uCentral client connects to "
"the controller server."
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:40
msgid ""
"The system is erasing the configuration partition now and will reboot itself "
"when finished."
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:106
msgid ""
"The system is flashing now.<br /> DO NOT POWER OFF THE DEVICE!<br /> Wait a "
"few minutes before you try to reconnect. It might be necessary to renew the "
"address of your computer to reach the device again, depending on your "
"settings."
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/tools/ucentral.js:26
msgid ""
"The system is rebooting in order to attempt applying the remote "
"configuration profile now. If not successful, the device will revert back "
"into the initial provisioning state."
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:38
msgid ""
"The uplink settings allow overriding the WAN connection properties of the "
"local device."
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/status.js:83
msgid "Total Available"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/tools/ucentral.js:95
msgid "Unable to save settings: %s"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/settings.js:58
msgid "Unable to verify certificates: %s"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/settings.js:41
msgid "Unit location"
msgstr ""
#: modules/luci-mod-ucentral/root/usr/share/luci/menu.d/luci-mod-ucentral.json:28
msgid "Uplink"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:37
msgid "Uplink configuration"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:164
msgid "Upload a compatible firmware image here to upgrade the running system."
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/settings.js:44
msgid "Upload certificate bundle…"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/status.js:64
msgid "Uptime"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:41
msgid "Use default cloud settings"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/status.js:84
msgid "Used"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/uplink.js:73
msgid "Username"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/settings.js:47
msgid "Verifying certificates…"
msgstr ""
#: modules/luci-mod-ucentral/htdocs/luci-static/resources/view/ucentral/system.js:52
msgid "Verifying the uploaded image file."
msgstr ""
#: modules/luci-mod-ucentral/root/usr/share/luci/menu.d/luci-mod-ucentral.json:3
msgid "uCentral"
msgstr ""

View File

@@ -0,0 +1,37 @@
#!/bin/sh
# make sure we have a tar file
[ -f /tmp/certs.tar ] || exit 1
. /lib/functions.sh
# amke sure the cert partition is mounted
mount_certs
# make sure that this is a UBI volume
ubi=$(grep certificates /proc/mounts | tail -n 1 | grep ubi)
[ -z "$ubi" ] && exit 1
# extract the certificates
mkdir -p /tmp/certs
tar x -C /tmp/certs -f /tmp/certs.tar
# make sure the required files exist
[ -f /tmp/certs/key.pem -a -f /tmp/certs/cert.pem ] || exit 1
# copy the certificates to /etc
cp /tmp/certs/*.pem /certificates
# remove old operational certs
rm /certificates/operational.*
# copy dev-id or gateway.json
for a in gateway.json; do
if [ -f /tmp/certs/$a ]; then
cp /tmp/certs/$a /certificates
else
rm -f /certificates/$a
fi
done
exit 0

View File

@@ -0,0 +1,8 @@
#!/usr/bin/ucode
push(REQUIRE_SEARCH_PATH, '/usr/share/ucentral/*.uc');
let bundle = require('bundle');
bundle.init('maverick');
include('/usr/share/ucentral/diagnostic.uc', { bundle });
bundle.complete();
system('chmod +r /tmp/bundle.maverick.tar.gz');

View File

@@ -0,0 +1,15 @@
#!/bin/sh
REDIRECTOR=$(cat /etc/ucentral/profile.json | jsonfilter -e '@.redirector')
if [ -n "$REDIRECTOR" ]; then
uci -c /etc/config-shadow/ set ucentral.config.server="$REDIRECTOR"
uci -c /etc/config-shadow/ commit ucentral
/etc/init.d/firstcontact disable
/etc/init.d/ucentral enable
else
rm /etc/ucentral/redirector.json
/etc/init.d/firstcontact enable
/etc/init.d/ucentral disable
fi
exit 0

View File

@@ -0,0 +1,62 @@
{
"ucentral": {
"title": "uCentral",
"order": 20,
"action": {
"type": "firstchild",
"recurse": true
},
"auth": {
"methods": [ "cookie:sysauth", "cookie:sysauth_http", "cookie:sysauth_https" ],
"login": true
}
},
"ucentral/status": {
"title": "Status",
"order": 1,
"action": {
"type": "view",
"path": "ucentral/status"
},
"depends": {
"acl": [ "luci-mod-ucentral" ]
}
},
"ucentral/uplink": {
"title": "Uplink",
"order": 2,
"action": {
"type": "view",
"path": "ucentral/uplink"
},
"depends": {
"acl": [ "luci-mod-ucentral" ]
}
},
"ucentral/settings": {
"title": "Settings",
"order": 3,
"action": {
"type": "view",
"path": "ucentral/settings"
},
"depends": {
"acl": [ "luci-mod-ucentral" ]
}
},
"ucentral/system": {
"title": "System",
"order": 4,
"action": {
"type": "view",
"path": "ucentral/system"
},
"depends": {
"acl": [ "luci-mod-ucentral" ]
}
}
}

View File

@@ -0,0 +1,36 @@
{
"luci-mod-ucentral": {
"description": "Grant access to ucentral configuration",
"read": {
"cgi-io": [ "download" ],
"file": {
"/etc/ucentral/profile.json": [ "read" ],
"/proc/mounts": [ "read" ],
"/proc/mtd": [ "read" ],
"/tmp/bundle.maverick.tar.gz": [ "read" ]
},
"ubus": {
"file": [ "read" ],
"system": [ "board", "info" ]
}
},
"write": {
"cgi-io": [ "upload" ],
"file": {
"/etc/ucentral/profile.json": [ "write" ],
"/sbin/certupdate": [ "exec" ],
"/sbin/diagnostic-bundle": [ "exec" ],
"/sbin/firstboot -r -y": [ "exec" ],
"/sbin/profileupdate": [ "exec" ],
"/sbin/sysupgrade -n /tmp/firmware.bin": [ "exec" ],
"/sbin/sysupgrade --test /tmp/firmware.bin": [ "exec" ],
"/tmp/certs.tar": [ "write" ],
"/tmp/firmware.bin": [ "write" ]
},
"ubus": {
"file": [ "exec", "remove", "write" ],
"system": [ "reboot", "validate_firmware_image" ]
}
}
}
}

View File

@@ -0,0 +1,14 @@
#
# Copyright (C) 2021 Jo-Philipp Wich <jo@mein.io>
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI theme for uCentral
LUCI_DEPENDS:=+luci-lua-runtime
include ../luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@@ -0,0 +1,152 @@
'use strict';
'require baseclass';
'require ui';
return baseclass.extend({
__init__: function() {
ui.menu.load().then(L.bind(this.render, this));
},
render: function(tree) {
var menu = document.querySelector('#mainmenu'),
nav = document.querySelector('#menubar > .navigation'),
node = tree,
url = '';
this.renderModeMenu(node);
if (L.env.dispatchpath.length >= 3) {
for (var i = 0; i < 3 && node; i++) {
node = node.children[L.env.dispatchpath[i]];
url = url + (url ? '/' : '') + L.env.dispatchpath[i];
}
if (node)
this.renderTabMenu(node, url);
}
if (menu.firstElementChild) {
nav.addEventListener('click', ui.createHandlerFn(this, 'handleSidebarToggle'));
nav.style.visibility = 'visible';
}
},
handleMenuExpand: function(ev) {
var a = ev.target, ul1 = a.parentNode.parentNode, ul2 = a.nextElementSibling;
document.querySelectorAll('ul.mainmenu.l1 > li.active').forEach(function(li) {
if (li !== a.parentNode)
li.classList.remove('active');
});
if (!ul2)
return;
if (ul2.parentNode.offsetLeft + ul2.offsetWidth <= ul1.offsetLeft + ul1.offsetWidth)
ul2.classList.add('align-left');
ul1.classList.add('active');
a.parentNode.classList.add('active');
a.blur();
ev.preventDefault();
ev.stopPropagation();
},
renderMainMenu: function(tree, url, level) {
var l = (level || 0) + 1,
ul = E('ul', { 'class': 'mainmenu l%d'.format(l) }),
children = ui.menu.getChildren(tree);
if (children.length == 0 || l > 2)
return E([]);
for (var i = 0; i < children.length; i++) {
var isActive = (L.env.dispatchpath[l] == children[i].name),
isReadonly = children[i].readonly,
activeClass = 'mainmenu-item-%s%s'.format(children[i].name, isActive ? ' selected' : '');
ul.appendChild(E('li', { 'class': activeClass }, [
E('a', {
'href': L.url(url, children[i].name),
'click': (l == 1) ? ui.createHandlerFn(this, 'handleMenuExpand') : null
}, [ _(children[i].title) ]),
this.renderMainMenu(children[i], url + '/' + children[i].name, l)
]));
}
if (l == 1)
document.querySelector('#mainmenu').appendChild(E('div', [ ul ]));
return ul;
},
renderModeMenu: function(tree, root) {
var menu = document.querySelector('#modemenu'),
children = ui.menu.getChildren(tree);
for (var i = 0; i < children.length; i++) {
var isActive = (L.env.requestpath.length ? children[i].name == L.env.requestpath[+!!root] : i == 0),
isUcentral = (!root && children[i].name == 'ucentral');
if (root || children.length > 1)
menu.appendChild(E('div', { 'class': isActive ? 'active' : null }, [
E('a', { 'href': root ? L.url(root, children[i].name) : L.url(children[i].name) }, [ _(children[i].title) ])
]));
if (isUcentral && isActive)
this.renderModeMenu(children[i], children[i].name);
else if (isActive)
this.renderMainMenu(children[i], children[i].name);
}
if (menu.children.length > 1)
menu.style.display = '';
},
renderTabMenu: function(tree, url, level) {
var container = document.querySelector('#tabmenu'),
l = (level || 0) + 1,
ul = E('ul', { 'class': 'cbi-tabmenu' }),
children = ui.menu.getChildren(tree),
activeNode = null;
if (children.length == 0)
return E([]);
for (var i = 0; i < children.length; i++) {
var isActive = (L.env.dispatchpath[l + 2] == children[i].name),
activeClass = isActive ? ' cbi-tab' : '',
className = 'tabmenu-item-%s %s'.format(children[i].name, activeClass);
ul.appendChild(E('li', { 'class': className }, [
E('a', { 'href': L.url(url, children[i].name) }, [ _(children[i].title) ] )
]));
if (isActive)
activeNode = children[i];
}
container.appendChild(ul);
container.style.display = '';
if (activeNode)
container.appendChild(this.renderTabMenu(activeNode, url + '/' + activeNode.name, l));
return ul;
},
handleSidebarToggle: function(ev) {
var btn = ev.currentTarget,
bar = document.querySelector('#mainmenu');
if (btn.classList.contains('active')) {
btn.classList.remove('active');
bar.classList.remove('active');
}
else {
btn.classList.add('active');
bar.classList.add('active');
}
}
});

View File

@@ -0,0 +1,140 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 251.2 114.2" style="enable-background:new 0 0 251.2 114.2;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FED206;}
.st1{fill:#EB6F53;}
.st2{fill:#3BA9B6;}
.st3{fill:#414141;}
</style>
<g>
<path class="st0" d="M219.6,43.3C219.5,43.3,219.5,43.3,219.6,43.3c-1.3,0-2.2-1-2.2-2.2c0-0.2,0-0.4,0-0.6
c0-11.9-9.7-21.6-21.6-21.6c-0.2,0-0.4,0-0.6,0c-1.2,0-2.2-0.9-2.2-2.1c0-1.2,0.9-2.2,2.1-2.2c0.2,0,0.5,0,0.7,0
c14.3,0,25.9,11.6,25.9,25.9c0,0.2,0,0.5,0,0.7C221.7,42.4,220.7,43.3,219.6,43.3z"/>
<path class="st1" d="M212.1,43.3C212,43.3,212,43.3,212.1,43.3c-1.3-0.1-2.2-1.1-2.2-2.3c0-0.2,0-0.4,0-0.6
c0-7.7-6.3-14.1-14.1-14.1c-0.2,0-0.4,0-0.6,0c-1.2,0.1-2.2-0.9-2.3-2.1c0-1.2,0.9-2.2,2.1-2.3c0.3,0,0.5,0,0.8,0
c10.2,0,18.4,8.3,18.4,18.4c0,0.2,0,0.5,0,0.8C214.2,42.4,213.2,43.3,212.1,43.3z"/>
<path class="st2" d="M204.3,43.3c-0.1,0-0.1,0-0.2,0c-1.2-0.1-2.1-1.1-2-2.3c0-0.2,0-0.4,0-0.5c0-3.5-2.8-6.3-6.3-6.3
c-0.1,0-0.3,0-0.5,0c-1.2,0.1-2.3-0.8-2.3-2c-0.1-1.2,0.8-2.3,2-2.3c0.3,0,0.6,0,0.9,0c5.9,0,10.7,4.8,10.7,10.7c0,0.3,0,0.5,0,0.9
C206.4,42.4,205.4,43.3,204.3,43.3z"/>
<g>
<g>
<g>
<path class="st3" d="M61.9,89.9v-4.7h-1.7v-0.9h4.4v0.9h-1.7v4.7H61.9z"/>
</g>
<g>
<path class="st3" d="M65.6,89.9v-5.6h3.8v0.9h-2.9v1.4h2.8v0.9h-2.8V89h2.9v0.9H65.6z"/>
</g>
<g>
<path class="st3" d="M70.7,89.9v-5.6h1V89h2.5v0.9H70.7z"/>
</g>
<g>
<path class="st3" d="M74.9,89.9v-5.6h3.8v0.9h-2.9v1.4h2.8v0.9h-2.8V89h2.9v0.9H74.9z"/>
</g>
<g>
<path class="st3" d="M79.8,87.1c0-1.7,1.3-2.9,2.9-2.9c1.1,0,1.8,0.6,2.2,1.3l-0.8,0.4c-0.3-0.5-0.8-0.8-1.4-0.8
c-1.1,0-1.9,0.8-1.9,2c0,1.2,0.8,2,1.9,2c0.6,0,1.1-0.4,1.4-0.8l0.8,0.4c-0.4,0.7-1.1,1.3-2.2,1.3C81.1,90,79.8,88.8,79.8,87.1z
"/>
</g>
<g>
<path class="st3" d="M85.5,87.1c0-1.7,1.2-2.9,2.9-2.9c1.7,0,2.9,1.2,2.9,2.9S90,90,88.3,90C86.7,90,85.5,88.8,85.5,87.1z
M90.2,87.1c0-1.2-0.7-2-1.9-2c-1.1,0-1.9,0.9-1.9,2c0,1.1,0.7,2,1.9,2C89.5,89.1,90.2,88.3,90.2,87.1z"/>
</g>
<g>
<path class="st3" d="M96.9,89.9v-4.3l-1.7,4.3h-0.4l-1.7-4.3v4.3h-1v-5.6h1.4l1.5,3.8l1.5-3.8h1.4v5.6H96.9z"/>
</g>
<g>
<path class="st3" d="M103,89.9v-5.6h1v5.6H103z"/>
</g>
<g>
<path class="st3" d="M109.7,89.9l-2.9-4v4h-1v-5.6h1l2.9,3.9v-3.9h1v5.6H109.7z"/>
</g>
<g>
<path class="st3" d="M112.4,89.9v-5.6h3.8v0.9h-2.9v1.4h2.8v0.9h-2.8v2.4H112.4z"/>
</g>
<g>
<path class="st3" d="M120.3,89.9l-1.2-2.1h-1v2.1h-1v-5.6h2.5c1.1,0,1.8,0.7,1.8,1.8c0,1-0.7,1.5-1.3,1.6l1.4,2.2H120.3z
M120.4,86.1c0-0.5-0.4-0.9-1-0.9h-1.4V87h1.4C120,87,120.4,86.6,120.4,86.1z"/>
</g>
<g>
<path class="st3" d="M126.6,89.9l-0.4-1.1h-2.6l-0.4,1.1h-1.1l2.2-5.6h1.2l2.2,5.6H126.6z M124.9,85.3l-1,2.7h2L124.9,85.3z"/>
</g>
<g>
<path class="st3" d="M131.4,89.9v-5.6h2.1c1.1,0,1.7,0.8,1.7,1.6c0,0.9-0.6,1.6-1.7,1.6h-1.6v2.3H131.4z M134.7,86
c0-0.7-0.5-1.2-1.2-1.2h-1.6v2.4h1.6C134.2,87.2,134.7,86.6,134.7,86z"/>
</g>
<g>
<path class="st3" d="M139.4,89.9l-1.6-2.3h-1.2v2.3h-0.5v-5.6h2.1c1,0,1.7,0.6,1.7,1.6c0,1-0.7,1.6-1.6,1.6l1.6,2.3H139.4z
M139.4,86c0-0.7-0.5-1.2-1.2-1.2h-1.6v2.4h1.6C138.9,87.2,139.4,86.7,139.4,86z"/>
</g>
<g>
<path class="st3" d="M141.2,87.1c0-1.6,1.1-2.9,2.7-2.9c1.6,0,2.7,1.3,2.7,2.9c0,1.6-1.1,2.9-2.7,2.9
C142.3,90,141.2,88.8,141.2,87.1z M146.1,87.1c0-1.4-0.9-2.5-2.2-2.5c-1.4,0-2.2,1-2.2,2.5c0,1.4,0.9,2.5,2.2,2.5
C145.2,89.6,146.1,88.5,146.1,87.1z"/>
</g>
<g>
<path class="st3" d="M147,89.3l0.3-0.4c0.3,0.3,0.6,0.6,1.1,0.6c0.8,0,1.2-0.5,1.2-1.3v-4h0.5v4c0,1.2-0.8,1.7-1.7,1.7
C147.9,90,147.4,89.8,147,89.3z"/>
</g>
<g>
<path class="st3" d="M151.8,89.9v-5.6h3.5v0.4h-3.1v2.1h3v0.4h-3v2.2h3.1v0.4H151.8z"/>
</g>
<g>
<path class="st3" d="M156.3,87.1c0-1.7,1.3-2.9,2.8-2.9c0.9,0,1.6,0.4,2,1l-0.4,0.3c-0.4-0.5-1-0.8-1.6-0.8
c-1.3,0-2.3,1-2.3,2.5c0,1.4,1,2.5,2.3,2.5c0.7,0,1.3-0.3,1.6-0.8l0.4,0.3c-0.5,0.6-1.2,1-2,1C157.5,90,156.3,88.8,156.3,87.1z"
/>
</g>
<g>
<path class="st3" d="M163.5,89.9v-5.2h-1.8v-0.4h4.1v0.4H164v5.2H163.5z"/>
</g>
</g>
<g>
<polygon class="st3" points="33.7,86.5 41.2,79 48.6,86.5 49.8,86.5 41.2,77.9 32.6,86.5 "/>
<polygon class="st3" points="48.6,87.8 41.2,95.2 33.7,87.8 32.6,87.8 41.2,96.4 49.8,87.8 "/>
<polygon class="st3" points="40.3,86.5 47.8,79 55.3,86.5 56.4,86.5 47.8,77.9 39.2,86.5 "/>
<polygon class="st3" points="55.3,87.8 47.8,95.2 40.3,87.8 39.2,87.8 47.8,96.4 56.4,87.8 "/>
</g>
</g>
</g>
<g>
<path class="st3" d="M51.2,41.3c2,1.1,3.6,2.6,4.7,4.5c1.1,1.9,1.7,4,1.7,6.4c0,2.3-0.6,4.5-1.7,6.4c-1.1,1.9-2.7,3.4-4.7,4.6
c-2,1.1-4.2,1.7-6.6,1.7c-2.4,0-4.6-0.6-6.6-1.7c-2-1.1-3.6-2.6-4.7-4.6c-1.1-1.9-1.7-4.1-1.7-6.4c0-2.3,0.6-4.5,1.7-6.4
c1.1-1.9,2.7-3.4,4.7-4.5c2-1.1,4.2-1.6,6.6-1.6C47,39.6,49.2,40.2,51.2,41.3z M40.5,44.9c-1.3,0.7-2.3,1.7-3,3
c-0.7,1.3-1.1,2.7-1.1,4.2s0.4,3,1.1,4.2c0.8,1.3,1.8,2.3,3,3c1.3,0.7,2.7,1.1,4.1,1.1c1.5,0,2.8-0.4,4.1-1.1c1.3-0.7,2.3-1.8,3-3
c0.7-1.3,1.1-2.7,1.1-4.2s-0.4-2.9-1.1-4.2c-0.7-1.3-1.7-2.3-3-3c-1.3-0.7-2.6-1.1-4.1-1.1C43.2,43.8,41.8,44.2,40.5,44.9z"/>
<path class="st3" d="M76.9,46.8c1.3,0.8,2.4,1.9,3.1,3.4c0.7,1.4,1.1,3.1,1.1,5c0,1.9-0.4,3.5-1.1,4.9c-0.7,1.4-1.8,2.5-3.1,3.3
c-1.3,0.8-2.9,1.2-4.6,1.2c-1.4,0-2.6-0.3-3.7-0.8c-1.1-0.5-2-1.3-2.7-2.4v9.8h-4.6V45.7H66v3.1c0.7-1.1,1.5-1.8,2.6-2.4
c1.1-0.5,2.3-0.8,3.7-0.8C74,45.6,75.6,46,76.9,46.8z M75.1,59.1c1-1.1,1.5-2.4,1.5-4.1c0-1.7-0.5-3-1.5-4.1
c-1-1.1-2.2-1.6-3.8-1.6c-1.6,0-2.8,0.5-3.8,1.6c-1,1-1.5,2.4-1.5,4.1c0,1.7,0.5,3,1.5,4.1c1,1.1,2.3,1.6,3.8,1.6
C72.8,60.7,74.1,60.2,75.1,59.1z"/>
<path class="st3" d="M99.3,48.1c1.5,1.7,2.3,4.1,2.3,7.2c0,0.6,0,1.1,0,1.4H87.7c0.3,1.3,0.9,2.4,1.9,3.1c0.9,0.8,2.1,1.1,3.5,1.1
c1,0,1.9-0.2,2.7-0.5c0.9-0.4,1.6-0.9,2.3-1.6l2.5,2.6c-0.9,1-2.1,1.8-3.4,2.4c-1.3,0.6-2.8,0.8-4.5,0.8c-1.9,0-3.6-0.4-5.1-1.2
c-1.5-0.8-2.6-1.9-3.4-3.3c-0.8-1.4-1.2-3.1-1.2-5c0-1.9,0.4-3.5,1.2-5c0.8-1.4,1.9-2.6,3.4-3.4c1.4-0.8,3.1-1.2,4.9-1.2
C95.5,45.6,97.8,46.4,99.3,48.1z M97.4,53.6c0-1.4-0.5-2.5-1.4-3.3c-0.9-0.8-2-1.2-3.4-1.2c-1.3,0-2.4,0.4-3.3,1.2
c-0.9,0.8-1.5,1.9-1.7,3.3H97.4z"/>
<path class="st3" d="M121.5,47.5c1.2,1.3,1.9,3.1,1.9,5.3v11.7h-4.6V54.1c0-1.3-0.4-2.3-1.1-3.1c-0.7-0.8-1.8-1.1-3-1.1
c-1.5,0-2.7,0.5-3.6,1.5s-1.3,2.3-1.3,3.8v9.2h-4.5V45.7h4.5v3.5c1.3-2.4,3.5-3.6,6.7-3.7C118.5,45.5,120.2,46.2,121.5,47.5z"/>
<path class="st3" d="M156.5,39.9h4.9l-8.3,24.5h-4.9l-5.6-18.6l-5.7,18.6h-4.8l-8.3-24.5h5l5.8,19.4l5.7-19.4h4.6l5.8,19.5
L156.5,39.9z"/>
<path class="st3" d="M168,38.4c0.5,0.5,0.7,1.2,0.7,2c0,0.8-0.2,1.4-0.7,1.9c-0.5,0.5-1.1,0.8-1.9,0.8c-0.7,0-1.4-0.3-1.9-0.8
c-0.5-0.5-0.7-1.2-0.7-1.9c0-0.8,0.2-1.4,0.7-2c0.5-0.5,1.1-0.8,1.9-0.8C166.9,37.7,167.6,37.9,168,38.4z M164,45.7h4.5v18.7H164
V45.7z"/>
<path class="st3" d="M174,39.9h16.9l0,4.1h-12.2v6.6h11.1v4.1h-11.1v9.7H174V39.9z"/>
<path class="st3" d="M197.9,38.4c0.5,0.5,0.7,1.2,0.7,2c0,0.8-0.2,1.4-0.7,1.9c-0.5,0.5-1.1,0.8-1.9,0.8c-0.7,0-1.4-0.3-1.9-0.8
c-0.5-0.5-0.7-1.2-0.7-1.9c0-0.8,0.2-1.4,0.7-2c0.5-0.5,1.1-0.8,1.9-0.8C196.8,37.7,197.4,37.9,197.9,38.4z M193.8,45.7h4.5v18.7
h-4.5V45.7z"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="132 132 264 264">
<defs>
<radialGradient id="g" cx="0%" cy="0%" r="60%">
<stop offset=".8" style="stop-opacity:1" />
<stop offset="1" style="stop-opacity:.5" />
</radialGradient>
</defs>
<g>
<path style="fill:url(#g)" d="M 264 132 A 132 132 0 0 0 132 264 A 132 132 0 0 0 264 396 A 132 132 0 0 0 396 264 A 132 132 0 0 0 264 132 z M 264 170 A 94 94 0 0 1 359 264 A 94 94 0 0 1 264 359 A 94 94 0 0 1 170 264 A 94 94 0 0 1 264 170 z " />
</g>
</svg>

After

Width:  |  Height:  |  Size: 582 B

View File

@@ -0,0 +1,13 @@
<%#
Copyright 2021 Jo-Philipp Wich <jo@mein.io>
Licensed to the public under the Apache License 2.0.
-%>
</div>
</div>
</div>
<script type="text/javascript">L.require('menu-ucentral')</script>
</body>
</html>

View File

@@ -0,0 +1,67 @@
<%#
Copyright 2021 Jo-Philipp Wich <jo@mein.io>
Licensed to the public under the Apache License 2.0.
-%>
<%
local sys = require "luci.sys"
local util = require "luci.util"
local http = require "luci.http"
local disp = require "luci.dispatcher"
local ver = require "luci.version"
local boardinfo = util.ubus("system", "board") or { }
local node = disp.context.dispatched
local path = table.concat(disp.context.path, "-")
http.prepare_content("text/html; charset=UTF-8")
-%>
<!DOCTYPE html>
<html lang="<%=luci.i18n.context.lang%>">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Script-Type" content="text/javascript" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" type="text/css" media="screen" href="<%=media%>/cascade.css" />
<link rel="icon" href="<%=media%>/logo.svg" type="image/svg+xml" />
<script type="text/javascript" src="<%=url('admin/translations', luci.i18n.context.lang)%><%# ?v=PKG_VERSION %>"></script>
<script type="text/javascript" src="<%=resource%>/cbi.js"></script>
<title><%=striptags( (boardinfo.hostname or "?") .. ( (node and node.title) and ' - ' .. translate(node.title) or '')) %> - LuCI</title>
<% if css then %><style title="text/css">
<%= css %>
</style>
<% end -%>
</head>
<body class="lang_<%=luci.i18n.context.lang%>" data-page="<%= pcdata(path) %>">
<p class="skiplink">
<span id="skiplink1"><a href="#navigation"><%:Skip to navigation%></a></span>
<span id="skiplink2"><a href="#content"><%:Skip to content%></a></span>
</p>
<div id="page">
<div id="menubar">
<h2 class="navigation" style="visibility:hidden"><a id="navigation" name="navigation"><%:Navigation%></a></h2>
<img src="<%=media%>/logo.svg" />
<span id="indicators"></span>
</div>
<div id="modemenu" style="display:none"></div>
<div id="maincontainer">
<div id="mainmenu"></div>
<div id="maincontent">
<%- if luci.sys.process.info("uid") == 0 and luci.sys.user.getuser("root") and not luci.sys.user.getpasswd("root") and path ~= "admin-system-admin-password" then -%>
<div class="alert-message warning">
<h4><%:No password set!%></h4>
<p><%:There is no password set on this router. Please configure a root password to protect the web interface.%></p>
<% if disp.lookup("admin/system/admin") then %>
<div class="right"><a class="btn" href="<%=url("admin/system/admin")%>"><%:Go to password configuration...%></a></div>
<% end %>
</div>
<%- end -%>
<div id="tabmenu" style="display:none"></div>

View File

@@ -0,0 +1,12 @@
#!/bin/sh
if [ "$PKG_UPGRADE" != 1 ]; then
uci get luci.themes.uCentral >/dev/null 2>&1 || \
uci batch <<-EOF
set luci.themes.uCentral=/luci-static/ucentral
set luci.main.mediaurlbase=/luci-static/ucentral
commit luci
EOF
fi
exit 0

View File

@@ -0,0 +1,294 @@
#
# Copyright (C) 2008-2015 The LuCI Team <luci@lists.subsignal.org>
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
LUCI_NAME?=$(notdir ${CURDIR})
LUCI_TYPE?=$(word 2,$(subst -, ,$(LUCI_NAME)))
LUCI_BASENAME?=$(patsubst luci-$(LUCI_TYPE)-%,%,$(LUCI_NAME))
LUCI_LANGUAGES:=$(sort $(filter-out templates,$(notdir $(wildcard ${CURDIR}/po/*))))
LUCI_DEFAULTS:=$(notdir $(wildcard ${CURDIR}/root/etc/uci-defaults/*))
LUCI_PKGARCH?=$(if $(realpath src/Makefile),,all)
# Language code titles
LUCI_LANG.bg=български (Bulgarian)
LUCI_LANG.bn_BD=বাংলা (Bengali)
LUCI_LANG.ca=Català (Catalan)
LUCI_LANG.cs=Čeština (Czech)
LUCI_LANG.de=Deutsch (German)
LUCI_LANG.el=Ελληνικά (Greek)
LUCI_LANG.en=English
LUCI_LANG.es=Español (Spanish)
LUCI_LANG.fr=Français (French)
LUCI_LANG.he=עִבְרִית (Hebrew)
LUCI_LANG.hi=हिंदी (Hindi)
LUCI_LANG.hu=Magyar (Hungarian)
LUCI_LANG.it=Italiano (Italian)
LUCI_LANG.ja=日本語 (Japanese)
LUCI_LANG.ko=한국어 (Korean)
LUCI_LANG.mr=Marāṭhī (Marathi)
LUCI_LANG.ms=Bahasa Melayu (Malay)
LUCI_LANG.nb_NO=Norsk (Norwegian)
LUCI_LANG.pl=Polski (Polish)
LUCI_LANG.pt_BR=Português do Brasil (Brazilian Portuguese)
LUCI_LANG.pt=Português (Portuguese)
LUCI_LANG.ro=Română (Romanian)
LUCI_LANG.ru=Русский (Russian)
LUCI_LANG.sk=Slovenčina (Slovak)
LUCI_LANG.sv=Svenska (Swedish)
LUCI_LANG.tr=Türkçe (Turkish)
LUCI_LANG.uk=Українська (Ukrainian)
LUCI_LANG.vi=Tiếng Việt (Vietnamese)
LUCI_LANG.zh_Hans=简体中文 (Chinese Simplified)
LUCI_LANG.zh_Hant=繁體中文 (Chinese Traditional)
# Submenu titles
LUCI_MENU.col=1. Collections
LUCI_MENU.mod=2. Modules
LUCI_MENU.app=3. Applications
LUCI_MENU.theme=4. Themes
LUCI_MENU.proto=5. Protocols
LUCI_MENU.lib=6. Libraries
# Language aliases
LUCI_LC_ALIAS.bn_BD=bn
LUCI_LC_ALIAS.nb_NO=no
LUCI_LC_ALIAS.pt_BR=pt-br
LUCI_LC_ALIAS.zh_Hans=zh-cn
LUCI_LC_ALIAS.zh_Hant=zh-tw
PKG_NAME?=$(LUCI_NAME)
# 1: everything expect po subdir or only po subdir
define findrev
$(shell \
if git log -1 >/dev/null 2>/dev/null; then \
set -- $$(git log -1 --format="%ct %h" --abbrev=7 -- $(if $(1),. ':(exclude)po',po)); \
if [ -n "$$1" ]; then
secs="$$(($$1 % 86400))"; \
yday="$$(date --utc --date="@$$1" "+%y.%j")"; \
printf 'git-%s.%05d-%s' "$$yday" "$$secs" "$$2"; \
else \
echo "unknown"; \
fi; \
else \
ts=$$(find . -type f $(if $(1),-not) -path './po/*' -printf '%T@\n' 2>/dev/null | sort -rn | head -n1 | cut -d. -f1); \
if [ -n "$$ts" ]; then \
secs="$$(($$ts % 86400))"; \
date="$$(date --utc --date="@$$ts" "+%y%m%d")"; \
printf '%s.%05d' "$$date" "$$secs"; \
else \
echo "unknown"; \
fi; \
fi \
)
endef
PKG_PO_VERSION?=$(if $(DUMP),x,$(strip $(call findrev)))
PKG_SRC_VERSION?=$(if $(DUMP),x,$(strip $(call findrev,1)))
PKG_GITBRANCH?=$(if $(DUMP),x,$(strip $(shell \
variant="LuCI"; \
if git log -1 >/dev/null 2>/dev/null; then \
branch="$$(git branch --remote --verbose --no-abbrev --contains 2>/dev/null | \
sed -rne 's|^[^/]+/([^ ]+) [a-f0-9]{40} .+$$|\1|p' | head -n1)"; \
if [ "$$branch" != "master" ]; then \
variant="LuCI $$branch branch"; \
else \
variant="LuCI Master"; \
fi; \
fi; \
echo "$$variant" \
)))
PKG_RELEASE?=1
PKG_INSTALL:=$(if $(realpath src/Makefile),1)
PKG_BUILD_DEPENDS += lua/host luci-base/host LUCI_CSSTIDY:csstidy/host LUCI_SRCDIET:luasrcdiet/host $(LUCI_BUILD_DEPENDS)
PKG_CONFIG_DEPENDS += CONFIG_LUCI_SRCDIET CONFIG_LUCI_JSMIN CONFIG_LUCI_CSSTIDY
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
include $(INCLUDE_DIR)/package.mk
define Package/$(PKG_NAME)
SECTION:=luci
CATEGORY:=LuCI
SUBMENU:=$(if $(LUCI_MENU.$(LUCI_TYPE)),$(LUCI_MENU.$(LUCI_TYPE)),$(LUCI_MENU.app))
TITLE:=$(if $(LUCI_TITLE),$(LUCI_TITLE),LuCI $(LUCI_NAME) $(LUCI_TYPE))
DEPENDS:=$(LUCI_DEPENDS)
VERSION:=$(if $(PKG_VERSION),$(PKG_VERSION),$(PKG_SRC_VERSION))
$(if $(LUCI_EXTRA_DEPENDS),EXTRA_DEPENDS:=$(LUCI_EXTRA_DEPENDS))
$(if $(LUCI_PKGARCH),PKGARCH:=$(LUCI_PKGARCH))
endef
ifneq ($(LUCI_DESCRIPTION),)
define Package/$(PKG_NAME)/description
$(strip $(LUCI_DESCRIPTION))
endef
endif
# Language selection for luci-base
ifeq ($(PKG_NAME),luci-base)
define Package/luci-base/config
config LUCI_SRCDIET
bool "Minify Lua sources"
default n
config LUCI_JSMIN
bool "Minify JavaScript sources"
default y
config LUCI_CSSTIDY
bool "Minify CSS files"
default y
menu "Translations"$(foreach lang,$(LUCI_LANGUAGES),
config LUCI_LANG_$(lang)
tristate "$(shell echo '$(LUCI_LANG.$(lang))' | sed -e 's/^.* (\(.*\))$$/\1/') ($(lang))")
endmenu
endef
endif
define Build/Prepare
for d in luasrc htdocs root src; do \
if [ -d ./$$$$d ]; then \
mkdir -p $(PKG_BUILD_DIR)/$$$$d; \
$(CP) ./$$$$d/* $(PKG_BUILD_DIR)/$$$$d/; \
fi; \
done
$(call Build/Prepare/Default)
endef
define Build/Configure
endef
ifneq ($(wildcard ${CURDIR}/src/Makefile),)
MAKE_PATH := src/
MAKE_VARS += FPIC="$(FPIC)" LUCI_VERSION="$(PKG_SRC_VERSION)" LUCI_GITBRANCH="$(PKG_GITBRANCH)"
define Build/Compile
$(call Build/Compile/Default,clean compile)
endef
else
define Build/Compile
endef
endif
HTDOCS = /www
LUA_LIBRARYDIR = /usr/lib/lua
LUCI_LIBRARYDIR = $(LUA_LIBRARYDIR)/luci
define SrcDiet
$(FIND) $(1) -type f -name '*.lua' | while read src; do \
if LUA_PATH="$(STAGING_DIR_HOSTPKG)/lib/lua/5.1/?.lua" luasrcdiet --noopt-binequiv -o "$$$$src.o" "$$$$src"; \
then mv "$$$$src.o" "$$$$src"; fi; \
done
endef
define JsMin
$(FIND) $(1) -type f -name '*.js' | while read src; do \
if jsmin < "$$$$src" > "$$$$src.o"; \
then mv "$$$$src.o" "$$$$src"; fi; \
done
endef
define CssTidy
$(FIND) $(1) -type f -name '*.css' | while read src; do \
if csstidy "$$$$src" --template=highest --remove_last_semicolon=true "$$$$src.o"; \
then mv "$$$$src.o" "$$$$src"; fi; \
done
endef
define SubstituteVersion
$(FIND) $(1) -type f -name '*.htm' | while read src; do \
$(SED) 's/<%# *\([^ ]*\)PKG_VERSION *%>/\1$(if $(PKG_VERSION),$(PKG_VERSION),$(PKG_SRC_VERSION))/g' \
-e 's/"\(<%= *\(media\|resource\) *%>[^"]*\.\(js\|css\)\)"/"\1?v=$(if $(PKG_VERSION),$(PKG_VERSION),$(PKG_SRC_VERSION))"/g' \
"$$$$src"; \
done
endef
define Package/$(PKG_NAME)/install
if [ -d $(PKG_BUILD_DIR)/luasrc ]; then \
$(INSTALL_DIR) $(1)$(LUCI_LIBRARYDIR); \
cp -pR $(PKG_BUILD_DIR)/luasrc/* $(1)$(LUCI_LIBRARYDIR)/; \
$(FIND) $(1)$(LUCI_LIBRARYDIR)/ -type f -name '*.luadoc' | $(XARGS) rm; \
$(if $(CONFIG_LUCI_SRCDIET),$(call SrcDiet,$(1)$(LUCI_LIBRARYDIR)/),true); \
$(call SubstituteVersion,$(1)$(LUCI_LIBRARYDIR)/); \
else true; fi
if [ -d $(PKG_BUILD_DIR)/htdocs ]; then \
$(INSTALL_DIR) $(1)$(HTDOCS); \
cp -pR $(PKG_BUILD_DIR)/htdocs/* $(1)$(HTDOCS)/; \
$(if $(CONFIG_LUCI_JSMIN),$(call JsMin,$(1)$(HTDOCS)/),true); \
$(if $(CONFIG_LUCI_CSSTIDY),$(call CssTidy,$(1)$(HTDOCS)/),true); \
else true; fi
if [ -d $(PKG_BUILD_DIR)/root ]; then \
$(INSTALL_DIR) $(1)/; \
cp -pR $(PKG_BUILD_DIR)/root/* $(1)/; \
else true; fi
if [ -d $(PKG_BUILD_DIR)/src ]; then \
$(call Build/Install/Default) \
$(CP) $(PKG_INSTALL_DIR)/* $(1)/; \
else true; fi
endef
ifndef Package/$(PKG_NAME)/postinst
define Package/$(PKG_NAME)/postinst
[ -n "$${IPKG_INSTROOT}" ] || {$(foreach script,$(LUCI_DEFAULTS),
(. /etc/uci-defaults/$(script)) && rm -f /etc/uci-defaults/$(script))
rm -f /tmp/luci-indexcache
rm -rf /tmp/luci-modulecache/
killall -HUP rpcd 2>/dev/null
exit 0
}
endef
endif
LUCI_BUILD_PACKAGES := $(PKG_NAME)
# 1: LuCI language code
# 2: BCP 47 language tag
define LuciTranslation
define Package/luci-i18n-$(LUCI_BASENAME)-$(1)
SECTION:=luci
CATEGORY:=LuCI
TITLE:=$(PKG_NAME) - $(1) translation
HIDDEN:=1
DEFAULT:=LUCI_LANG_$(2)||(ALL&&m)
DEPENDS:=$(PKG_NAME)
VERSION:=$(PKG_PO_VERSION)
PKGARCH:=all
endef
define Package/luci-i18n-$(LUCI_BASENAME)-$(1)/description
Translation for $(PKG_NAME) - $(LUCI_LANG.$(2))
endef
define Package/luci-i18n-$(LUCI_BASENAME)-$(1)/install
$$(INSTALL_DIR) $$(1)/etc/uci-defaults
echo "uci set luci.languages.$(subst -,_,$(1))='$(LUCI_LANG.$(2))'; uci commit luci" \
> $$(1)/etc/uci-defaults/luci-i18n-$(LUCI_BASENAME)-$(1)
$$(INSTALL_DIR) $$(1)$(LUCI_LIBRARYDIR)/i18n
$(foreach po,$(wildcard ${CURDIR}/po/$(2)/*.po), \
po2lmo $(po) \
$$(1)$(LUCI_LIBRARYDIR)/i18n/$(basename $(notdir $(po))).$(1).lmo;)
endef
define Package/luci-i18n-$(LUCI_BASENAME)-$(1)/postinst
[ -n "$$$${IPKG_INSTROOT}" ] || {
(. /etc/uci-defaults/luci-i18n-$(LUCI_BASENAME)-$(1)) && rm -f /etc/uci-defaults/luci-i18n-$(LUCI_BASENAME)-$(1)
exit 0
}
endef
LUCI_BUILD_PACKAGES += luci-i18n-$(LUCI_BASENAME)-$(1)
endef
$(foreach lang,$(LUCI_LANGUAGES),$(eval $(call LuciTranslation,$(firstword $(LUCI_LC_ALIAS.$(lang)) $(lang)),$(lang))))
$(foreach pkg,$(LUCI_BUILD_PACKAGES),$(eval $(call BuildPackage,$(pkg))))

View File

@@ -0,0 +1,34 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=tip-defaults
PKG_RELEASE:=1
PKG_LICENSE:=BSD-3-Clause
PKG_MAINTAINER:=John Crispin <john@phrozen.org>
include $(INCLUDE_DIR)/package.mk
define Package/tip-defaults
SECTION:=ucentral
CATEGORY:=uCentral
TITLE:=tip-defaults
endef
define Package/tip-defaults/description
The default configuration of the AP.
endef
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
endef
define Build/Compile/Default
endef
Build/Compile = $(Build/Compile/Default)
define Package/tip-defaults/install
$(CP) ./files/* $(1)
endef
$(eval $(call BuildPackage,tip-defaults))

View File

@@ -0,0 +1,7 @@
#!/bin/sh /etc/rc.common
START=80
boot() {
echo $(cat /etc/openwrt_release | grep DISTRIB_TIP= | cut -d\' -f2) > /tmp/ucentral.version
}

View File

@@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIFajCCA1KgAwIBAgICDnowDQYJKoZIhvcNAQELBQAwHzEdMBsGA1UEAwwUT3BlbkxBTiBEZW1vIFJvb3QgQ0EwHhcNMjUwMjIxMTUwMDAwWhcNMjYwMjIxMTUwMDAwWjAgMR4wHAYDVQQDExVPcGVuTEFOIERlbW8gQmlydGggQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDVWIyySul6Fv4wl1O+DQpaLRa0p+Az5L/jcqTpdVf6w+8tlmeIY9C28uDQoDjewrIkvf3lcfK86nshs02s9ehqZUnEP8+GvKM19x3JbWxeTvWwFirjHir4x897iQ606bAMbrHHtntI9ZyBZyXDGeElGJxJQNX+0d50SFq609cB3yxpBPJ67ag+4Oq0uHgROHjEQMrfwLwlAune0c1fjQDrN14PDNjMZHvvhc/pkAHxR1PP6LOFNV5NuQ58tC5N7R2EqqFbIJ8VZgcagrGRYuAuFFTaV+D7RIt9xGTuWlCyxHI7VkRBJ1mRoEr4GOrP9QFjBD8NzNK+/wnR/fZwhpEnRsgHiI33wKHBDg+l3r8tvRzuB5X6Gl/SfuAeaoCuDHMncTjQg1zGhyEwjQhUe4RY3w+yHAjeeOE6c5spOMDDdaBibkzLmSjXztuLeAdzsUcD3fvGeOvh9vG14TKEmF8puNkqEcc0W8NyUWKFdr9umdJEMbaRSSsMGtp8bDj3Ddh4PhEJrIFeo89+HwXhU6sk+wzE9BULTohahsfwOV/08t1cZ3Q04Oj1KI+4YWu8BJns5gX35rQ8GIbkXQwfvFMwqmbg+ij2o9HWdkSL4bcqW/83Ho+31ce210rVGPK9cav0CjA2Eexgxi45cbgnfoade74Qa5zXboJEBmp7rbo4swIDAQABo4GuMIGrMB8GA1UdIwQYMBaAFDzIg8eyTI3xc4A2R60f8HanhBZDMB0GA1UdDgQWBBS5xC3inqLQl+vxzn9PsjNzlZ5hYDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADBFBgNVHR8EPjA8MDqgOKA2hjRodHRwOi8vZGVtby5jZXJ0aWZpY2F0ZS5maS9jcmwvT3BlbkxBTkRlbW9Sb290Q0EuY3JsMA0GCSqGSIb3DQEBCwUAA4ICAQA9DJEjsDLqtSFkF0XTWfzbebXA+X8++Qmiukrw0s2LRx798ce0mVITRAFDLf78BeUYF0B+PQ8hgq4fWyFsXRgZVrITd1BszT3LJ2r/6xWQJVpVHzLqKgIlW/PY/uTUz+xqR0Ev6hYrmrjfya0K0XEZZqxkmrTrcECaA3RCFkWQl9ZUlb9BClmdhayO8x1XpJplIYAMKVuoPL9IUQH6HUPFnzlPNQHIK9gcFACtgPVWCJg3IAvSLa41KpRxTDwGFvlrNKtkBlGRYhFGCHWXXZn8fdQHW9vykkkfPOaPR/AVyuRzfAT6wbtVWSy38BurSdqSCuNQPQBfF2vMeUGwNbD/7B4tYrWVtnIbgxRPKvX0o3mZkKry6BJf2m/AKWA16W+i4ojnPRORLTTq9cEZ0WL6NRHCgMrbWaCs/+ADTErqK6cv7GhoOVEiqugvnz93dit2IXg4zdDJ4hF9ZSlicwgZKVvMqnNQ1iiXezIQBehgYcWwXRIfdrRPe88HgkySuDZ2lkKYdc+oTc6e7upRh4Kh2ZSipcRb6ehPan533jnQJyU8A9vFAJiQfZZ4lD3tcsqlsDnlu5YEDYSjcfnkyOH/Mlx5VVTWYGvqNNVKRDw2slSxKwVCobkcF/2dAxP9DqOaGaCnMeOaR7kMaBm5d1fwb+bCl9usQAELjZBv2vAH8g==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFIDCCAwigAwIBAgICDnkwDQYJKoZIhvcNAQELBQAwHzEdMBsGA1UEAwwUT3BlbkxBTiBEZW1vIFJvb3QgQ0EwHhcNMjUwMjIxMTUwMDAwWhcNMjYwMjIxMTUwMDAwWjAfMR0wGwYDVQQDDBRPcGVuTEFOIERlbW8gUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMjExylKdJWoJu9mOHPJ6yZFXKe1lE467G65acpS2FKIWnPVFjNCmATMpkMOIFzEFwyFdbQjzOidtiL+73zlE52lOJpXCfOcxDFqDYDJJ8//J1/gQWsBaKpSvgLiHU/0awkQg+yJYZpj8YZa4NkFe+zTjQScSfOsqPPb3rZ7DOQ2BKAhjVShKmVbtNil0iO0zm8vE8DNkktTNMREp2pzb8MbCAgfOkwlrby6T+rV3TvmjThGdFUb5lWDFxWtlF8W0SUII9qj7p5TdGpryeLsO0nZTBtS4HxZNdvmKOHfgcRHmSZIJigB2NzKLNrXF9JBW0WnUSwZJZAG2C1RTx6lADILPueuusyfR/hZ3koKi4PHnSiTwQghzia9K9QjNHq5z9R9ZoCnhBg1VyU4LKmp862L0sIp2vgnOYunEIi9aCYBaDwo+0FuVjZuXyDIatwVuA7TN5IWPHA6XLdOt1mmkeYy1Ldr4XHjdondhtOyeei1UFXmyyLm2+kmRYfTm91TqYmNzRgbRV2NHO50AmsnBknX4Rv3gishGe0+dV5yFcUwZud0z2rSCkuoai5tKrPT+6Y6NqkT9u9HFifIBXnLwEzVUqHRtW6SuWj2DClVQIXIUZtFnhY4GuTuf6DlzgnXO58oDVCZmCW4ULIpbqGeRsvBHR8Sw5JXP/1+TMUYhE8TAgMBAAGjZjBkMB8GA1UdIwQYMBaAFDzIg8eyTI3xc4A2R60f8HanhBZDMB0GA1UdDgQWBBQ8yIPHskyN8XOANketH/B2p4QWQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATANBgkqhkiG9w0BAQsFAAOCAgEAkHZ5KR8IOrdfMFy+iOvauvZxfQ84LL6TpB2FQKDjneJUdd7c29UJJFNW/0mp4Gc6jKZab6J8Dx/pNnbH0RqFjGjeRGtJ4Sk0G7gf9zw1S7qut5WJDcisM9l/wXC+zy/KSKKPQmbt0grWOtU7+NNPh1YU76hIrInq/u2sVZyKH8SXQ957fbJk6BX6JTKyNEn05AB6rNSrbOWo8sy2MlcJ7bBsrWYI1t6GcWFh4b36bLu7/dKJWpyFNXXIkKJsgMEDpEQae56+fSSDo0KRNtYB82fNZDIQlGK81rGJWNzAahM+3GD1tgk/3ZVugfaJhcBpoHHKNOGqZAvtirLAIDocno7AzqoeIz974Rh2Olsl2/arApYPyyfi8PMYuFe/d4h+Wie8n+jh5n48lZ2Ve4PK+j+QHD6tTZS4f0bGnPL1puMxzQloltuQWgLDeVfEgrc3snLvjOg8aDzWm/es85lP8XcyW54U4t3JmrNUC2C7v+Uafx7cL7eDeunhs+BRhtGV+IUmjub2IrpqZp3zZqn+LVRdYJIy/qHhjS5+ImckXkFojOmeWhfmEmYSuNP8Oa6cGuXp829qnbxLh9Qzi3TfXV883KLse4kL5Zl7gBA/4hz2hVMyGJ8fY+VvzbaTuOXyvKJ+rGZCTcRSeotBLnIevVMiL7SqOEwN0j4Mfbznfq8=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFFTCCAv2gAwIBAgICAxIwDQYJKoZIhvcNAQELBQAwGjEYMBYGA1UEAwwPT3BlbkxBTiBSb290IENBMCAXDTI1MDUxNDA4NDcxMFoYDzIwNTUwNTE0MDg0NzEwWjAaMRgwFgYDVQQDDA9PcGVuTEFOIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDGibJ04A55kSURTBSKgcBmLnND2I5wws1taKqqU9aaRhB7NtvMHwh2voH9b1brUiulZaZwTN/9kzd4AnXeKQ+0u5tV7Ofk0fzF2MK47n17TS30Yenqc4NuQEKdpKK/pM3VvOEppR/bqtgyLtDmbDnmFOx+zTj/+smTgouwA+Iier0P4s5OohYxn/bjOqwQbHbU79VpGBIWv6/kt55AhH7zvsqqKHkrzTxnsRBv3SBIufrjJr9PIhZBLDrqr56P6KgAi0eoutNt2ToiJbE0WfjU7GI1RSiSN5bGj1zXhjNVzQWs1H9QzRf3c9pl3+haHQZ7FZ1UqiTRewmbNrQ6I9k81au3SttUlb87MyAuDSzatkiq7CjQ8VE1J6te6ZBt2zWpUhHsR/Lg7g3eOw5dL4oZJdK5GgGu/MUajLUXifIqM13Mvg0VTzDhN69VLXLSL0gPcicsQCwJuAza1IC/VqmBGx19fAkyJhOurCXWOgisi0g1+xzPKRphUNwMPUf8vBVOM/Vc6xDIvwVGE3+eWXyhixneFlSpAI03nWWjpwWXihTBoxbfRXO3Y/ilJqrgFN+U4PJcCPA+Wo7ThH0mgX6bOTPcgXMUzT3v3FF6Bx5/PNV3kYrw2yLzribUiS6AGvVGnW4hX2Z6OQvA/aHME8KF+6y6m4pC7FkUjVaRlzWu/wIDAQABo2MwYTAfBgNVHSMEGDAWgBSUaFuoOPk4QLByZP47kj4p1IbCJjAdBgNVHQ4EFgQUlGhbqDj5OECwcmT+O5I+KdSGwiYwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBAB+/RUC2X6eVoPsFNMkaXO5Iib/ub0JoWhODQm8j2Mr5dpGXESSpXjfDcqDOLuJbWWoflXBLdr8BsVCBqOA9YgCX0H8Br7dUWmCScixxLW0he592/424EvdwifxcKHZLjv9CKV5Txhqnm2djc5RY/nTH5MYVrIh/If2TNO5ydDP6+vgy9GQ4en04VK7rz+PW17O8l7k9/lOmYptZmHgSDAPj/cT3PlG+McqaI5rMSHeEHlzH+PvgWjtSeEhF4FwFBXroDl4/yb4l2JB8bqAZ3vsOXSkigFcZh5MXPe+zuSSW+G8iLr4xoi0CFsP2DaHEyxgqP4B1FtE9nFPo6cvWbwqTVT7QSzqfH+jPJuQvpFXeRF5UFegNZTFT5/uFFPamihakFslEYxeJey1y+OJdLcP6ef87ruSt8amsq56OAETYpnW4JFowlEh0C+QwLGHGGY6WrOgHY/90hJmPgXBdBVg/IoOhzbvk5A+LqZDvxV2/rLNfClw8Kr3g5e8obcB6dWgMCy2z+us0H79ucnmhzQKsjpxM9T1ncHovAQfiD3jVqfHULY53avh0wIAjosoTGbe8dyx80quHe+16qWan7C9idXeAYYJXbZt5hs6hLw4I8M1LsjTg6vwsqiaHZpsmDyyQLdFjNJldG7aosfS9F+BIpuwijF+1dashL0CPsbIJ
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIGBzCCA++gAwIBAgICCQYwDQYJKoZIhvcNAQELBQAwGjEYMBYGA1UEAwwPT3BlbkxBTiBSb290IENBMB4XDTI1MDUxNDA4NTY0MVoXDTQ1MDUxNDA5MjY0MVowJDEiMCAGA1UEAwwZT3BlbkxBTiBTZXJ2ZXIgSXNzdWluZyBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALSdJpzwPfQM9oHBGt6w8UDLDJNznxI7cpfl0u0xVCHN1YY7onpwxFVkFRzUx/JrQ/tbEGZH19XtngaCZ91KbGbqVao9S32H0tyn2t3eTJ5h+klJ7+7YAbZr8UfOi3nG4bZzNSa5dDBPaNPvI51byKDN7siXXnALV3f0l6lZgDpLQco/E7ANU3lslUVjVNALfFUEonDyP7XV+lFAyidpjIn6dRn7oYs3SUwkzZUntYJAhAykmxXMWox+85gDkdb+2O3G8ci0uHVbb0A9LP+MeIhzxHgnnAMfWLfEZexdmEd2PwVHaz/D2Xp/gYrpPDTsbqWjQ9NmgdASwqN5j8BuJ8vHDVBVCztVDltm6JPw3Y6GQPN1LmiSLUzst7VYpydUJRDHYIAKJhT9DYxQ126VfiyMo6Xl4IQO8YZ/J6r8yR7gyvyUiBW+wvvC1bCY5+VuI4P/cY+6iA1qwC1SOWjYlccy+tbfGj9zr32Qf27e9RXSAkcATHen1rc/9AGEeAuSpKrzhmZIIvM4+EtYgbBvf91NkP51zbGpvsAbfWN/ecNmqH9SeyrrVgv68Z34hMijCcvJNyIvloo3nkb/gHYV4tAiwTTrX13Rio/8qNF4nwHLsjw0t7jEyRiXdOciePyhGbtdicuiUxrShzbGY7ID0yNwyTKcJYhorL/8r+YFpsXrAgMBAAGjggFLMIIBRzAfBgNVHSMEGDAWgBSUaFuoOPk4QLByZP47kj4p1IbCJjAdBgNVHQ4EFgQUBwUkiaCh5hdY+ZH6O8NmEE/nH5EwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAwRwYDVR0fBEAwPjA8oDqgOIY2aHR0cDovL2NybC5jZXJ0aWZpY2F0ZXMub3Blbi1sYW4ub3JnL29wZW5sYW5yb290Y2EuY3JsMIGXBggrBgEFBQcBAQSBijCBhzBEBggrBgEFBQcwAoY4aHR0cDovL2NlcnRzLmNlcnRpZmljYXRlcy5vcGVuLWxhbi5vcmcvb3BlbmxhbnJvb3RjYS5jZXIwPwYIKwYBBQUHMAGGM2h0dHA6Ly9vY3NwLmNlcnRpZmljYXRlcy5vcGVuLWxhbi5vcmcvb3BlbmxhbnJvb3RjYTANBgkqhkiG9w0BAQsFAAOCAgEAqEk5ZJdpMVr2U0YhmqEU6gqxEeih9MWKcQfmsT/lhf5m5V7VuLMc3r+EBCsPssw60umdQcAU2IPlJXLAeWwdRyY7ZNNwQVgl9GBI/CM2b7x18+12/llCdXW9FOagdChTuuhwRnGTt71jcrJkleQyEYhqwwIEN82hxq4HSZO6XJDev4IsMRF00+qt8biJcf7OVGOSLoyiU6Dm/EzxoB+DZf3HdUc0vzfVjD4Im+yYzqXuwWV6c9oIBQH6obzaqlpg926CtEBFR8E1LQe93ahMvF7pExpIOkE5PTuqONvy7Xn3Ui8NRxHhmm8j/unql6bUTGENz9s68n8Im7weq6awC9Hfu8aGWjcnXI7tsDY5uJEguP5fSwCUrdTE85XgPgPHeKaIwBZsyRZTqVSvbky+c15Yv6ITXLWoA0AUxz9ste3WpqiWCNJVI90MCruSYKdpXGV0KU3QQXJDMKhHJBF5DLpuKiboFfh9O8pB7B4/tJ76JpAc6Z0rfaQUo2vxSpb3Sbd/IHNcL08zB8Ay+YUBULspxe+1StKthmCzCHI9DOhIgeASyNBpcL7uZPjCXiYGhUuzsFGv4sQ+d267Jyvql/Piw/vYg1k2aVBfdIoIU4TpIEVyQqPz4aAW+0SgL7OM+/zD9jxn3gVdusCpmHcoTzOfZRriH0FGIeDSQydpOJU=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,75 @@
-----BEGIN CERTIFICATE-----
MIIEcTCCA1mgAwIBAgIUJFhIMlIJHJ7hW4gEzZuLBUaWjNcwDQYJKoZIhvcNAQEL
BQAwbDELMAkGA1UEBhMCVVMxJDAiBgNVBAoTG1RlbGVjb20gSW5mcmEgUHJvamVj
dCwgSW5jLjEMMAoGA1UECxMDVElQMSkwJwYDVQQDEyBUZWxlY29tIEluZnJhIFBy
b2plY3QgSXNzdWluZyBDQTAeFw0yMTA0MjUyMDMzNTRaFw0yNjA0MTMyMjM4NDZa
MCMxITAfBgNVBAMTGGNhY2VydHMub25lLmRpZ2ljZXJ0LmNvbTCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAJwKRHdkdEQkp32bNi9TdgN4FNRG0nRppguQ
mdCysJHA6/SuyAXNwKSbENysjFrcBkfYTlALjvIMqSu4d26ix6Mv4HnVxLjDzapV
TZhOhfxIbRQa3HNieNup2vMi8jJvgwLcK/4CwhBJsbEMkB5lbyL8UnCBxzW9GGbM
IvurvDFkUDUpUmiFg47nTpjub79KME6NqK38DxKzlUHvJge1TKFM73kZ3YkfWExQ
yRQPRiU5KxMi/Wkr30FOf/rMTx4XNacOgyTJvzcStGwrlr0iGr8eLC1/XVXoOQz3
0lyOeUzTB+HPU1Z2JrbPW5PnGxcQ0f7v/3qkWV1B2wuvFcQk+D0CAwEAAaOCAVIw
ggFOMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFIj2Mhdk10e46DeI+aEZKSSK8Hj+
MB8GA1UdIwQYMBaAFLMbVLjgR6s98ziA5Dzl/QBhbdHoMA4GA1UdDwEB/wQEAwIE
8DAWBgNVHSUBAf8EDDAKBggrBgEFBQcDAjCBhgYIKwYBBQUHAQEEejB4MCgGCCsG
AQUFBzABhhxodHRwOi8vb2NzcC5vbmUuZGlnaWNlcnQuY29tMEwGCCsGAQUFBzAC
hkBodHRwOi8vY2FjZXJ0cy5vbmUuZGlnaWNlcnQuY29tL1RlbGVjb21JbmZyYVBy
b2plY3RJc3N1aW5nQ0EuY3J0ME0GA1UdHwRGMEQwQqBAoD6GPGh0dHA6Ly9jcmwu
b25lLmRpZ2ljZXJ0LmNvbS9UZWxlY29tSW5mcmFQcm9qZWN0SXNzdWluZ0NBLmNy
bDANBgkqhkiG9w0BAQsFAAOCAQEADlFwshNPkeI2Gl6ooIauZL9d+6k+RWa5RTle
JWziYL23XVEBT11+dvp4IB9HwVw5dByl3XAfTd1r4qyncwgXQpc6j2X8e45E8izI
z2S1zhLMe1bA2lOiZz/sdpbonvxIHdiISyQI7q3mWQsvNkpkbjivjxLAJTcGPmOS
gc/95YL+2xqPV45XAnPcl5qkLThtmb57Xst1sLWiSS2fUId6HMVuCgZa5su+aAl9
iMXv9YfHcvyfwXBaOtoBlItyMGl60uy0E/Fr5uEhEWi53EIqhty6KQckQBB7wdjQ
eiXNI5Ox5cf+TFdesuKPaoEn3WNpFL9PCA3S5nGegJlZQ4N9Eg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEnDCCA4SgAwIBAgIUVpyCUx1MUeUwxg+7I1BvGFTz7HkwDQYJKoZIhvcNAQEL
BQAwaTELMAkGA1UEBhMCVVMxJDAiBgNVBAoTG1RlbGVjb20gSW5mcmEgUHJvamVj
dCwgSW5jLjEMMAoGA1UECxMDVElQMSYwJAYDVQQDEx1UZWxlY29tIEluZnJhIFBy
b2plY3QgUm9vdCBDQTAeFw0yMTA0MTMyMjUxMjZaFw0yNjA0MTMyMjM4NDZaMGwx
CzAJBgNVBAYTAlVTMSQwIgYDVQQKExtUZWxlY29tIEluZnJhIFByb2plY3QsIElu
Yy4xDDAKBgNVBAsTA1RJUDEpMCcGA1UEAxMgVGVsZWNvbSBJbmZyYSBQcm9qZWN0
IElzc3VpbmcgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtKBrq
qd2aKVSk25KfL5xHu8X7/8rJrz3IvyPuVKWhk/N1zabot3suBcGaYNKjnRHxg78R
yKwKzajKYWtiQFqztu24g16LQeAnoUxZnF6a0z3JkkRPsz14A2y8TUhdEe1tx+UU
4VGsk3n+FMmOQHL+79FO57zQC1LwylgfLSltrI6mF3jowVUQvnwzKhUzT87AJ6EO
ndK/q0T/Bgi+aI39zfVOjJjsTJwghvrmYW3iarP1THSKxeib2s02bZKrvvHa5HL4
UI8+LvREpVZl4mzt1z6Nl344Y6f+UeJlYa/Ci0jJqaXJmyVnUbAz+c0i5JfwAVn3
YQzfC4eLnZCmdF8zAgMBAAGjggE3MIIBMzAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
DgQWBBSzG1S44EerPfM4gOQ85f0AYW3R6DAfBgNVHSMEGDAWgBQCRpZgebFT9qny
98WfIUDk6ZEB+jAOBgNVHQ8BAf8EBAMCAYYwgYMGCCsGAQUFBwEBBHcwdTAoBggr
BgEFBQcwAYYcaHR0cDovL29jc3Aub25lLmRpZ2ljZXJ0LmNvbTBJBggrBgEFBQcw
AoY9aHR0cDovL2NhY2VydHMub25lLmRpZ2ljZXJ0LmNvbS9UZWxlY29tSW5mcmFQ
cm9qZWN0Um9vdENBLmNydDBKBgNVHR8EQzBBMD+gPaA7hjlodHRwOi8vY3JsLm9u
ZS5kaWdpY2VydC5jb20vVGVsZWNvbUluZnJhUHJvamVjdFJvb3RDQS5jcmwwDQYJ
KoZIhvcNAQELBQADggEBAFbz+K94bHIkBMJqps0dApniUmOn0pO6Q6cGh47UP/kX
IiPIsnYgG+hqYD/qtsiqJhaWi0hixRWn38UmvZxMRk27aSTGE/TWx0JTC3qDGsSe
XkUagumbSfmS0ZyiTwMPeGAjXwyzGorqZWeA95eKfImntMiOf3E7//GK0K7HpCx8
IPCnLZsZD2q/mLyBsduImFIRQJbLAhwIxpcd1qYJk+BlGFL+HtBpEbq6JxW2Xy+v
DpNWc2WIsUTle0rTc9JNJrLX4ChUJmKqf8obKHap3Xh3//qw/jDB9pOAinA33FLJ
EmCnwBvQr9mfNmPBGMYZVU8cPruDQJ57GjmmvdisbJY=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDojCCAoqgAwIBAgIUPVYBpqNbcLYygF6Mx+qxSWwQyFowDQYJKoZIhvcNAQEL
BQAwaTELMAkGA1UEBhMCVVMxJDAiBgNVBAoTG1RlbGVjb20gSW5mcmEgUHJvamVj
dCwgSW5jLjEMMAoGA1UECxMDVElQMSYwJAYDVQQDEx1UZWxlY29tIEluZnJhIFBy
b2plY3QgUm9vdCBDQTAeFw0yMTA0MTMyMjQyNDRaFw0zMTA0MTMyMjM4NDZaMGkx
CzAJBgNVBAYTAlVTMSQwIgYDVQQKExtUZWxlY29tIEluZnJhIFByb2plY3QsIElu
Yy4xDDAKBgNVBAsTA1RJUDEmMCQGA1UEAxMdVGVsZWNvbSBJbmZyYSBQcm9qZWN0
IFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIGCibwf5u
AAwZ+1H8U0e3u2V+0d2gSctucoK86XwUmfe1V2a/qlCYZd29r80IuN1IIeB0naIm
KnK/MzXW87clF6tFd1+HzEvmlY/W4KyIXalVCTEzirFSvBEG2oZpM0yC3AefytAO
aOpA00LaM3xTfTqMKIRhJBuLy0I4ANUVG6ixVebbGuc78IodleqiLoWy2Q9QHyEO
t/7hZndJhiVogh0PveRhho45EbsACu7ymDY+JhlIleevqwlE3iQoq0YcmYADHno6
Eq8vcwLpZFxihupUafkd1T3WJYQAJf9coCjBu2qIhNgrcrGD8R9fGswwNRzMRMpX
720+GjcDW3bJAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFAJG
lmB5sVP2qfL3xZ8hQOTpkQH6MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsF
AAOCAQEAVjl9dm4epG9NUYnagT9sg7scVQEPfz3Lt6w1NXJXgD8mAUlK0jXmEyvM
dCPD4514n+8+lM7US8fh+nxc7jO//LwK17Wm9FblgjNFR7+anv0Q99T9fP19DLlF
PSNHL2emogy1bl1lLTAoj8nxg2wVKPDSHBGviQ5LR9fsWUIJDv9Bs5k0qWugWYSj
19S6qnHeskRDB8MqRLhKMG82oDVLerSnhD0P6HjySBHgTTU7/tYS/OZr1jI6MPbG
L+/DtiR5fDVMNdBSGU89UNTi0wHY9+RFuNlIuvZC+x/swF0V9R5mN+ywquTPtDLA
5IOM7ItsRmen6u3qu+JXros54e4juQ==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,36 @@
#
# Copyright (C) 2021 OpenWrt.org
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#
include $(TOPDIR)/rules.mk
include $(INCLUDE_DIR)/kernel.mk
PKG_NAME:=atfpolicy
PKG_VERSION:=1
PKG_LICENSE:=GPL-2.0
PKG_MAINTAINER:=Felix Fietkau <nbd@nbd.name>
include $(INCLUDE_DIR)/package.mk
include $(INCLUDE_DIR)/cmake.mk
define Package/atfpolicy
SECTION:=net
CATEGORY:=Network
TITLE:=A simple daemon for handling airtime fairness prioritization
DEPENDS:=+libubox +libubus +libnl-tiny
endef
TARGET_CFLAGS += -I$(STAGING_DIR)/usr/include/libnl-tiny
define Package/atfpolicy/install
$(INSTALL_DIR) $(1)/usr/sbin $(1)/etc/init.d $(1)/etc/config
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/atfpolicy $(1)/usr/sbin/
$(INSTALL_BIN) ./files/atfpolicy.init $(1)/etc/init.d/atfpolicy
$(INSTALL_DATA) ./files/atfpolicy.conf $(1)/etc/config/atfpolicy
endef
$(eval $(call BuildPackage,atfpolicy))

View File

@@ -0,0 +1,8 @@
config defaults
option vo_queue_weight 4
option update_pkt_threshold 100
option bulk_percent_thresh 50
option prio_percent_thresh 30
option weight_normal 256
option weight_prio 512
option weight_bulk 128

View File

@@ -0,0 +1,57 @@
#!/bin/sh /etc/rc.common
# Copyright (c) 2021 OpenWrt.org
START=50
USE_PROCD=1
PROG=/usr/sbin/atfpolicy
add_option() {
local type="$1"
local name="$2"
config_get val "$cfg" "$name"
[ -n "$val" ] && json_add_$type "$name" "$val"
}
add_defaults() {
cfg="$1"
json_add_boolean reset 1
add_option int vo_queue_weight
add_option int update_pkt_threshold
add_option int bulk_percent_thresh
add_option int prio_percent_thresh
add_option int weight_normal
add_option int weight_prio
add_option int weight_bulk
}
reload_service() {
json_init
config_load atfpolicy
config_foreach add_defaults defaults
ubus call atfpolicy config "$(json_dump)"
}
service_triggers() {
procd_add_reload_trigger atfpolicy
}
start_service() {
procd_open_instance
procd_set_param command "$PROG"
procd_set_param respawn
procd_close_instance
}
service_started() {
ubus -t 2 wait_for atfpolicy
[ $? = 0 ] && reload_service
}

View File

@@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 3.10)
PROJECT(atfpolicy C)
ADD_DEFINITIONS(-Os -Wall -Wno-unknown-warning-option -Wno-array-bounds -Wno-format-truncation -Werror --std=gnu99)
SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
find_library(nl NAMES nl-tiny)
ADD_EXECUTABLE(atfpolicy main.c ubus.c interface.c nl80211.c)
TARGET_LINK_LIBRARIES(atfpolicy ${nl} ubox ubus)
INSTALL(TARGETS atfpolicy
RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}
)

View File

@@ -0,0 +1,90 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
*/
#ifndef __ATF_H
#define __ATF_H
#include <net/if.h>
#include <stdint.h>
#include <libubox/avl.h>
#define ATF_AVG_SCALE 12
#define ATF_AVG_WEIGHT_FACTOR 3
#define ATF_AVG_WEIGHT_DIV 4
#define MAC_ADDR_FMT "%02x:%02x:%02x:%02x:%02x:%02x"
#define MAC_ADDR_DATA(_a) \
((const uint8_t *)(_a))[0], \
((const uint8_t *)(_a))[1], \
((const uint8_t *)(_a))[2], \
((const uint8_t *)(_a))[3], \
((const uint8_t *)(_a))[4], \
((const uint8_t *)(_a))[5]
#define D(format, ...) do { \
if (debug_flag) \
fprintf(stderr, "DEBUG: %s(%d) " format "\n", __func__, __LINE__, ## __VA_ARGS__); \
} while (0)
struct atf_config {
int voice_queue_weight;
int min_pkt_thresh;
int bulk_percent_thresh;
int prio_percent_thresh;
int weight_normal;
int weight_prio;
int weight_bulk;
};
struct atf_interface {
struct avl_node avl;
char ifname[IFNAMSIZ + 1];
uint32_t ubus_obj;
struct avl_tree stations;
};
struct atf_stats {
uint64_t bulk, normal, prio;
};
struct atf_station {
struct avl_node avl;
uint8_t macaddr[6];
bool present;
uint8_t stats_idx;
struct atf_stats stats[2];
uint16_t avg_bulk;
uint16_t avg_prio;
int weight;
};
extern struct atf_config config;
extern int debug_flag;
void reset_config(void);
struct atf_interface *atf_interface_get(const char *ifname);
void atf_interface_sta_update(struct atf_interface *iface);
struct atf_station *atf_interface_sta_get(struct atf_interface *iface, uint8_t *macaddr);
void atf_interface_sta_changed(struct atf_interface *iface, struct atf_station *sta);
void atf_interface_sta_flush(struct atf_interface *iface);
void atf_interface_update_all(void);
int atf_ubus_init(void);
void atf_ubus_stop(void);
void atf_ubus_set_sta_weight(struct atf_interface *iface, struct atf_station *sta);
int atf_nl80211_init(void);
int atf_nl80211_interface_update(struct atf_interface *iface);
#endif

View File

@@ -0,0 +1,108 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
*/
#include <string.h>
#include <stdlib.h>
#include <libubox/avl-cmp.h>
#include "atf.h"
static AVL_TREE(interfaces, avl_strcmp, false, NULL);
#ifndef container_of_safe
#define container_of_safe(ptr, type, member) \
(ptr ? container_of(ptr, type, member) : NULL)
#endif
static int avl_macaddr_cmp(const void *k1, const void *k2, void *ptr)
{
return memcmp(k1, k2, 6);
}
void atf_interface_sta_update(struct atf_interface *iface)
{
struct atf_station *sta;
avl_for_each_element(&iface->stations, sta, avl)
sta->present = false;
}
struct atf_station *atf_interface_sta_get(struct atf_interface *iface, uint8_t *macaddr)
{
struct atf_station *sta;
sta = avl_find_element(&iface->stations, macaddr, sta, avl);
if (sta)
goto out;
sta = calloc(1, sizeof(*sta));
memcpy(sta->macaddr, macaddr, sizeof(sta->macaddr));
sta->avl.key = sta->macaddr;
sta->weight = -1;
avl_insert(&iface->stations, &sta->avl);
out:
sta->present = true;
return sta;
}
void atf_interface_sta_flush(struct atf_interface *iface)
{
struct atf_station *sta, *tmp;
avl_for_each_element_safe(&iface->stations, sta, avl, tmp) {
if (sta->present)
continue;
avl_delete(&iface->stations, &sta->avl);
free(sta);
}
}
void atf_interface_sta_changed(struct atf_interface *iface, struct atf_station *sta)
{
int weight;
if (sta->avg_prio > config.prio_percent_thresh)
weight = config.weight_prio;
else if (sta->avg_prio > config.bulk_percent_thresh)
weight = config.weight_bulk;
else
weight = config.weight_normal;
if (sta->weight == weight)
return;
sta->weight = weight;
atf_ubus_set_sta_weight(iface, sta);
}
struct atf_interface *atf_interface_get(const char *ifname)
{
struct atf_interface *iface;
iface = avl_find_element(&interfaces, ifname, iface, avl);
if (iface)
return iface;
if (strlen(ifname) + 1 > sizeof(iface->ifname))
return NULL;
iface = calloc(1, sizeof(*iface));
strcpy(iface->ifname, ifname);
iface->avl.key = iface->ifname;
avl_init(&iface->stations, avl_macaddr_cmp, false, NULL);
avl_insert(&interfaces, &iface->avl);
return iface;
}
void atf_interface_update_all(void)
{
struct atf_interface *iface, *tmp;
avl_for_each_element_safe(&interfaces, iface, avl, tmp)
atf_nl80211_interface_update(iface);
}

View File

@@ -0,0 +1,62 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
*/
#include <stdio.h>
#include <string.h>
#include <getopt.h>
#include <libubox/uloop.h>
#include "atf.h"
struct atf_config config;
int debug_flag;
void reset_config(void)
{
memset(&config, 0, sizeof(config));
config.voice_queue_weight = 4;
config.min_pkt_thresh = 100;
config.bulk_percent_thresh = (50 << ATF_AVG_SCALE) / 100;
config.prio_percent_thresh = (30 << ATF_AVG_SCALE) / 100;
config.weight_normal = 256;
config.weight_bulk = 128;
config.weight_prio = 512;
}
static void atf_update_cb(struct uloop_timeout *t)
{
atf_interface_update_all();
uloop_timeout_set(t, 1000);
}
int main(int argc, char **argv)
{
static struct uloop_timeout update_timer = {
.cb = atf_update_cb,
};
int ch;
while ((ch = getopt(argc, argv, "d")) != -1) {
switch (ch) {
case 'd':
debug_flag = 1;
break;
}
}
reset_config();
uloop_init();
atf_ubus_init();
atf_nl80211_init();
atf_update_cb(&update_timer);
uloop_run();
atf_ubus_stop();
uloop_done();
return 0;
}

View File

@@ -0,0 +1,174 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
*/
#define _GNU_SOURCE
#include <linux/nl80211.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <net/if.h>
#include <unl.h>
#include "atf.h"
static struct unl unl;
static void
atf_parse_tid_stats(struct atf_interface *iface, struct atf_stats *stats,
int tid, struct nlattr *attr)
{
struct nlattr *tb[NL80211_TID_STATS_MAX + 1];
uint64_t msdu;
if (nla_parse_nested(tb, NL80211_TID_STATS_MAX, attr, NULL))
return;
if (!tb[NL80211_TID_STATS_TX_MSDU])
return;
msdu = nla_get_u64(tb[NL80211_TID_STATS_TX_MSDU]);
switch (tid) {
case 0:
case 3:
/* BE */
stats->normal += msdu;
break;
case 1:
case 2:
/* BK */
stats->bulk += msdu;
break;
case 4:
case 5:
/* VI */
stats->prio += msdu;
break;
case 6:
case 7:
stats->prio += msdu * config.voice_queue_weight;
/* VO */
break;
default:
break;
}
}
static uint64_t atf_stats_total(struct atf_stats *stats)
{
return stats->normal + stats->prio + stats->bulk;
}
static void atf_stats_diff(struct atf_stats *dest, struct atf_stats *cur, struct atf_stats *prev)
{
dest->normal = cur->normal - prev->normal;
dest->prio = cur->prio - prev->prio;
dest->bulk = cur->bulk - prev->bulk;
}
static uint16_t atf_stats_avg(uint16_t avg, uint64_t cur, uint32_t total)
{
cur <<= ATF_AVG_SCALE;
cur /= total;
if (!avg)
return (uint16_t)cur;
avg *= ATF_AVG_WEIGHT_FACTOR;
avg += cur * (ATF_AVG_WEIGHT_DIV - ATF_AVG_WEIGHT_FACTOR);
avg /= ATF_AVG_WEIGHT_DIV;
if (!avg)
avg = 1;
return avg;
}
static void atf_sta_update_avg(struct atf_station *sta, struct atf_stats *cur)
{
uint64_t total = atf_stats_total(cur);
D("sta "MAC_ADDR_FMT" total pkts: total=%d bulk=%d normal=%d prio=%d",
MAC_ADDR_DATA(sta->macaddr), (uint32_t)total,
(uint32_t)cur->bulk, (uint32_t)cur->normal, (uint32_t)cur->prio);
if (total < config.min_pkt_thresh)
return;
sta->avg_bulk = atf_stats_avg(sta->avg_bulk, cur->bulk, total);
sta->avg_prio = atf_stats_avg(sta->avg_prio, cur->prio, total);
D("avg bulk=%d prio=%d",
(sta->avg_bulk * 100) >> ATF_AVG_SCALE,
(sta->avg_prio * 100) >> ATF_AVG_SCALE);
sta->stats_idx = !sta->stats_idx;
}
static int
atf_sta_cb(struct nl_msg *msg, void *arg)
{
struct atf_interface *iface = arg;
struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
struct nlattr *tb[NL80211_ATTR_MAX + 1];
struct nlattr *sinfo[NL80211_STA_INFO_MAX + 1];
struct atf_station *sta;
struct atf_stats *stats, diff = {};
struct nlattr *cur;
int idx = 0;
int rem;
nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
genlmsg_attrlen(gnlh, 0), NULL);
if (!tb[NL80211_ATTR_STA_INFO] || !tb[NL80211_ATTR_MAC])
return NL_SKIP;
if (nla_parse_nested(sinfo, NL80211_STA_INFO_MAX,
tb[NL80211_ATTR_STA_INFO], NULL))
return NL_SKIP;
if (!sinfo[NL80211_STA_INFO_TID_STATS])
return NL_SKIP;
sta = atf_interface_sta_get(iface, nla_data(tb[NL80211_ATTR_MAC]));
if (!sta)
return NL_SKIP;
stats = &sta->stats[sta->stats_idx];
memset(stats, 0, sizeof(*stats));
nla_for_each_nested(cur, sinfo[NL80211_STA_INFO_TID_STATS], rem)
atf_parse_tid_stats(iface, stats, idx++, cur);
atf_stats_diff(&diff, stats, &sta->stats[!sta->stats_idx]);
atf_sta_update_avg(sta, &diff);
atf_interface_sta_changed(iface, sta);
return NL_SKIP;
}
int atf_nl80211_interface_update(struct atf_interface *iface)
{
struct nl_msg *msg;
int ifindex;
ifindex = if_nametoindex(iface->ifname);
if (!ifindex)
return -1;
atf_interface_sta_update(iface);
msg = unl_genl_msg(&unl, NL80211_CMD_GET_STATION, true);
NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, ifindex);
unl_genl_request(&unl, msg, atf_sta_cb, iface);
atf_interface_sta_flush(iface);
return 0;
nla_put_failure:
nlmsg_free(msg);
return -1;
}
int atf_nl80211_init(void)
{
return unl_genl_init(&unl, "nl80211");
}

View File

@@ -0,0 +1,164 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
*/
#include <libubus.h>
#include "atf.h"
#define HOSTAPD_PREFIX "hostapd."
static struct ubus_auto_conn conn;
static struct blob_buf b;
enum {
ATF_CONFIG_RESET,
ATF_CONFIG_VO_Q_WEIGHT,
ATF_CONFIG_MIN_PKT_THRESH,
ATF_CONFIG_BULK_PERCENT_THR,
ATF_CONFIG_PRIO_PERCENT_THR,
ATF_CONFIG_WEIGHT_NORMAL,
ATF_CONFIG_WEIGHT_PRIO,
ATF_CONFIG_WEIGHT_BULK,
__ATF_CONFIG_MAX
};
static const struct blobmsg_policy atf_config_policy[__ATF_CONFIG_MAX] = {
[ATF_CONFIG_VO_Q_WEIGHT] = { "vo_queue_weight", BLOBMSG_TYPE_INT32 },
[ATF_CONFIG_MIN_PKT_THRESH] = { "update_pkt_threshold", BLOBMSG_TYPE_INT32 },
[ATF_CONFIG_BULK_PERCENT_THR] = { "bulk_percent_thresh", BLOBMSG_TYPE_INT32 },
[ATF_CONFIG_PRIO_PERCENT_THR] = { "prio_percent_thresh", BLOBMSG_TYPE_INT32 },
[ATF_CONFIG_WEIGHT_NORMAL] = { "weight_normal", BLOBMSG_TYPE_INT32 },
[ATF_CONFIG_WEIGHT_PRIO] = { "weight_prio", BLOBMSG_TYPE_INT32 },
[ATF_CONFIG_WEIGHT_BULK] = { "weight_bulk", BLOBMSG_TYPE_INT32 },
};
static int
atf_ubus_config(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
struct blob_attr *tb[__ATF_CONFIG_MAX];
struct blob_attr *cur;
static const struct {
int id;
int *field;
} field_map[] = {
{ ATF_CONFIG_VO_Q_WEIGHT, &config.voice_queue_weight },
{ ATF_CONFIG_MIN_PKT_THRESH, &config.min_pkt_thresh },
{ ATF_CONFIG_BULK_PERCENT_THR, &config.bulk_percent_thresh },
{ ATF_CONFIG_PRIO_PERCENT_THR, &config.prio_percent_thresh },
{ ATF_CONFIG_WEIGHT_NORMAL, &config.weight_normal },
{ ATF_CONFIG_WEIGHT_PRIO, &config.weight_prio },
{ ATF_CONFIG_WEIGHT_BULK, &config.weight_bulk },
};
bool reset = false;
int i;
blobmsg_parse(atf_config_policy, __ATF_CONFIG_MAX, tb,
blobmsg_data(msg), blobmsg_len(msg));
if ((cur = tb[ATF_CONFIG_RESET]) != NULL)
reset = blobmsg_get_bool(cur);
if (reset)
reset_config();
for (i = 0; i < ARRAY_SIZE(field_map); i++) {
if ((cur = tb[field_map[i].id]) != NULL)
*(field_map[i].field) = blobmsg_get_u32(cur);
}
return 0;
}
static const struct ubus_method atf_methods[] = {
UBUS_METHOD("config", atf_ubus_config, atf_config_policy),
};
static struct ubus_object_type atf_object_type =
UBUS_OBJECT_TYPE("atfpolicy", atf_methods);
static struct ubus_object atf_object = {
.name = "atfpolicy",
.type = &atf_object_type,
.methods = atf_methods,
.n_methods = ARRAY_SIZE(atf_methods),
};
static void
atf_ubus_add_interface(struct ubus_context *ctx, const char *name)
{
struct atf_interface *iface;
iface = atf_interface_get(name + strlen(HOSTAPD_PREFIX));
if (!iface)
return;
iface->ubus_obj = 0;
ubus_lookup_id(ctx, name, &iface->ubus_obj);
D("add interface %s", name + strlen(HOSTAPD_PREFIX));
}
static void
atf_ubus_lookup_cb(struct ubus_context *ctx, struct ubus_object_data *obj,
void *priv)
{
if (!strncmp(obj->path, HOSTAPD_PREFIX, strlen(HOSTAPD_PREFIX)))
atf_ubus_add_interface(ctx, obj->path);
}
void atf_ubus_set_sta_weight(struct atf_interface *iface, struct atf_station *sta)
{
D("set sta "MAC_ADDR_FMT" weight=%d", MAC_ADDR_DATA(sta->macaddr), sta->weight);
blob_buf_init(&b, 0);
blobmsg_printf(&b, "sta", MAC_ADDR_FMT, MAC_ADDR_DATA(sta->macaddr));
blobmsg_add_u32(&b, "weight", sta->weight);
if (ubus_invoke(&conn.ctx, iface->ubus_obj, "update_airtime", b.head, NULL, NULL, 100))
D("set airtime weight failed");
}
static void
atf_ubus_event_cb(struct ubus_context *ctx, struct ubus_event_handler *ev,
const char *type, struct blob_attr *msg)
{
static const struct blobmsg_policy policy =
{ "path", BLOBMSG_TYPE_STRING };
struct ubus_object_data obj;
struct blob_attr *attr;
blobmsg_parse(&policy, 1, &attr, blobmsg_data(msg), blobmsg_len(msg));
if (!attr)
return;
obj.path = blobmsg_get_string(attr);
atf_ubus_lookup_cb(ctx, &obj, NULL);
}
static void
ubus_connect_handler(struct ubus_context *ctx)
{
static struct ubus_event_handler ev = {
.cb = atf_ubus_event_cb
};
ubus_add_object(ctx, &atf_object);
ubus_register_event_handler(ctx, &ev, "ubus.object.add");
ubus_lookup(ctx, "hostapd.*", atf_ubus_lookup_cb, NULL);
}
int atf_ubus_init(void)
{
conn.cb = ubus_connect_handler;
ubus_auto_connect(&conn);
return 0;
}
void atf_ubus_stop(void)
{
ubus_auto_shutdown(&conn);
}

View File

@@ -0,0 +1,62 @@
#
# Copyright (C) 2022 OpenWrt.org
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#
include $(TOPDIR)/rules.mk
PKG_NAME:=bridger
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL=https://github.com/nbd168/bridger
PKG_SOURCE_DATE:=2024-04-22
PKG_SOURCE_VERSION:=40b1c5b6be4e73a6749cf2997c664520eb20055d
PKG_MIRROR_HASH:=83e514f93fa3b8f1d7d3b13df6196a3d176521185dbbcdeccc8ef4d999f74cda
PKG_LICENSE:=GPL-2.0
PKG_MAINTAINER:=Felix Fietkau <nbd@nbd.name>
PKG_BUILD_DEPENDS:=bpf-headers
PKG_BUILD_PARALLEL:=1
include $(INCLUDE_DIR)/package.mk
include $(INCLUDE_DIR)/cmake.mk
include $(INCLUDE_DIR)/bpf.mk
include $(INCLUDE_DIR)/nls.mk
define Package/bridger
SECTION:=utils
CATEGORY:=Base system
TITLE:=Bridge forwarding accelerator
DEPENDS:=+libbpf +libubox +libubus +libnl-tiny +kmod-sched-core +kmod-sched-flower +kmod-sched-bpf +kmod-sched-act-vlan $(BPF_DEPENDS)
endef
TARGET_CFLAGS += \
-I$(STAGING_DIR)/usr/include/libnl-tiny \
-I$(STAGING_DIR)/usr/include
CMAKE_OPTIONS += \
-DLIBNL_LIBS=-lnl-tiny
define Build/Compile
$(call CompileBPF,$(PKG_BUILD_DIR)/bridger-bpf.c)
$(Build/Compile/Default)
endef
define Package/bridger/install
$(INSTALL_DIR) \
$(1)/etc/config \
$(1)/etc/init.d \
$(1)/lib/bpf \
$(1)/usr/sbin
$(INSTALL_DATA) $(PKG_BUILD_DIR)/bridger-bpf.o $(1)/lib/bpf
$(INSTALL_BIN) \
$(PKG_INSTALL_DIR)/usr/bin/bridger \
$(1)/usr/sbin/
$(INSTALL_DATA) ./files/bridger.conf $(1)/etc/config/bridger
$(INSTALL_BIN) ./files/bridger.init $(1)/etc/init.d/bridger
endef
$(eval $(call BuildPackage,bridger))

View File

@@ -0,0 +1,3 @@
config defaults
# example for blacklisting individual devices or bridges
# list blacklist eth0

View File

@@ -0,0 +1,44 @@
#!/bin/sh /etc/rc.common
# Copyright (c) 2021 OpenWrt.org
START=19
USE_PROCD=1
PROG=/usr/sbin/bridger
add_blacklist() {
cfg="$1"
config_get blacklist "$cfg" blacklist
for i in $blacklist; do
json_add_string "" "$i"
done
}
reload_service() {
config_load bridger
json_init
json_add_string name "config"
json_add_array devices
config_foreach add_blacklist defaults
json_close_array
ubus call bridger set_blacklist "$(json_dump)"
}
service_triggers() {
procd_add_reload_trigger bridger
}
start_service() {
procd_open_instance
procd_set_param command "$PROG"
procd_set_param respawn
procd_close_instance
}
service_started() {
ubus -t 10 wait_for bridger
[ $? = 0 ] && reload_service
}

View File

@@ -0,0 +1,15 @@
Index: bridger-2023-05-12-3159bbe0/bpf.c
===================================================================
--- bridger-2023-05-12-3159bbe0.orig/bpf.c
+++ bridger-2023-05-12-3159bbe0/bpf.c
@@ -42,6 +42,10 @@ void bridger_bpf_flow_upload(struct brid
if (bpf_map_lookup_elem(map_offload, &flow->key, &val) == 0)
flow->offload.packets = val.packets;
+ if ((flow->key.ifindex == flow->offload.target_port) &&
+ (flow->key.vlan == flow->offload.vlan)) {
+ return;
+ }
bpf_map_update_elem(map_offload, &flow->key, &flow->offload, BPF_ANY);
}

View File

@@ -0,0 +1,27 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=ieee8021x
PKG_RELEASE:=1
PKG_LICENSE:=GPL-2.0
PKG_MAINTAINER:=John Crispin <john@phrozen.org>
PKG_SOURCE_DATE:=2023-10-05
include $(INCLUDE_DIR)/package.mk
define Package/ieee8021x
SECTION:=net
CATEGORY:=Network
TITLE:=Wired 802.1x
DEPENDS:=+libubox +libubus +libuci
endef
define Build/Compile
endef
define Package/ieee8021x/install
$(CP) ./files/* $(1)
endef
$(eval $(call BuildPackage,ieee8021x))

View File

@@ -0,0 +1,5 @@
#config network
# list ports 'lan1'
# list ports 'lan2'
# list ports 'lan3'
# list ports 'lan4'

View File

@@ -0,0 +1,21 @@
#!/bin/sh /etc/rc.common
START=80
USE_PROCD=1
PROG=/usr/bin/ieee8021x.uc
reload_service() {
ubus call ieee8021x reload
restart
}
service_triggers() {
procd_add_reload_trigger ieee8021x
}
start_service() {
procd_open_instance
procd_set_param command "$PROG"
procd_set_param respawn
procd_close_instance
}

View File

@@ -0,0 +1,307 @@
#!/usr/bin/ucode
let ubus = require('ubus').connect();
let uci = require('uci').cursor();
let uloop = require('uloop');
let rtnl = require('rtnl');
let fs = require('fs');
let log = require("log");
let hapd_subscriber;
let device_subscriber;
let ifaces = {};
/* load UCI */
let config;
function config_load() {
config = uci.get_all('ieee8021x', '@config[0]');
config.ports = {};
for (let k, port in uci.get_all('ieee8021x')) {
if (port['.type'] != 'port')
continue;
config.ports[k] = port;
}
}
/* set a wired ports auth state */
function netifd_handle_iface(name, auth_status, vlan) {
let msg = { name, auth_status, auth_vlans: [ ifaces[name].default_vlan + ':u' ]};
if (ifaces[name].upstream && vlan)
msg.auth_vlans = [ vlan + ':u' ];
ubus.call('network.device', 'set_state', msg);
if (auth_status && ifaces[name].upstream && vlan) {
for (let wan in ifaces[name].wan_ports) {
let msg = {
name: wan,
vlan: [ `${vlan}:t` ]
};
ubus.call('network.interface.up_none', 'add_device', msg);
ubus.call('udevstats', 'add_device', { device: wan, vlan });
}
}
ifaces[name].authenticated = auth_status;
}
/* handle events from hostapd */
function hapd_subscriber_notify_cb(notify) {
switch(notify.type) {
case 'sta-authorized':
log.syslog(LOG_USER, "authenticated station");
push(ifaces[notify.data.ifname].lladdr, notify.data.address);
netifd_handle_iface(notify.data.ifname, true, notify.data.vlan);
break;
};
return 0;
}
/* remove arp and rate limit for a client */
function flush_iface(name) {
/* flush all arp entries */
let neighs = rtnl.request(rtnl.const.RTM_GETNEIGH, rtnl.const.NLM_F_DUMP, { dev: name });
for (let neigh in neighs)
if (neigh.lladdr in ifaces[name].lladdr) {
rtnl.request(rtnl.const.RTM_DELNEIGH, 0, { dst: neigh.dst, dev: neigh.dev, family: neigh.family });
ubus.call('ratelimit', 'client_delete', { device: neigh.dev, address: neigh.lladdr });
}
ifaces[name].lladdr = [];
}
/* generate a hostapd configuration */
function hostapd_start(iface) {
if (!fs.stat("/sys/class/net/" + iface)) {
log.syslog(LOG_ERR, "Interface ${iface} does not exist yet");
return;
}
if (!config.auth_server_addr) {
log.syslog(LOG_ERR, "Auth server address is empty");
return;
}
let path = '/var/run/hostapd-' + iface + '.conf';
let file = fs.open(path, 'w+');
file.write('driver=wired\n');
file.write('ieee8021x=1\n');
file.write('eap_reauth_period=0\n');
file.write('ctrl_interface=/var/run/hostapd\n');
file.write('interface=' + iface + '\n');
file.write('ca_cert=' + config.ca + '\n');
file.write('server_cert=' + config.cert + '\n');
file.write('private_key=' + config.key + '\n');
file.write('dynamic_vlan=1\n');
file.write('vlan_no_bridge=1\n');
file.write('vlan_naming=1\n');
if (config.auth_server_addr) {
file.write('dynamic_own_ip_addr=1\n');
file.write('auth_server_addr=' + config.auth_server_addr + '\n');
file.write('auth_server_port=' + config.auth_server_port + '\n');
file.write('auth_server_shared_secret=' + config.auth_server_secret + '\n');
if (config.acct_server_addr && config.acct_server_port && config.acct_server_secret + '\n') {
file.write('acct_server_addr=' + config.acct_server_addr + '\n');
file.write('acct_server_port=' + config.acct_server_port + '\n');
file.write('acct_server_shared_secret=' + config.acct_server_secret + '\n');
}
if (config.nas_identifier)
file.write('nas_identifier=${config.nas_identifier}\n');
if (config.coa_server_addr && config.coa_server_port && config.coa_server_secret) {
file.write('radius_das_client=' + config.coa_server_addr + ' ' + config.coa_server_secret + '\n');
file.write('radius_das_port=' + config.coa_server_port + '\n');
}
if (+config.mac_address_bypass)
file.write('macaddr_acl=2\n');
} else {
file.write('eap_server=1\n');
file.write('eap_user_file=/var/run/hostapd-ieee8021x.eap_user\n');
}
file.close();
/* is hostapd already running ? */
/*
* For certains scenarios, we need to remove and add
* instead of reload (reload did not work).
* Consider the following scenario -
* Say on CIG 186w as an example
* eth0.4086 interface exists with some non-ieee8021x config.
* Push ieee8021x config. In general the flow is that
* reload_config is called followed by invocation of services (from ucentral-schema)
* Services inovation does n't wait until the config reloaded ie in this context
* ieee8021x service is invoked much before the network interfaces are recreated.
* That is not correct. To handle this, we capture link-up events
* and remove the existing interface (in hostapd as shown below) and add again
*/
if (ifaces[iface].hostapd) {
log.syslog(LOG_USER, "Remove the config ${iface}");
ubus.call('hostapd', 'config_remove', { iface: iface });
}
log.syslog(LOG_USER, "Add config (clear the old one) " + iface);
ubus.call('hostapd', 'config_add', { iface: iface, config: path });
system('ifconfig ' + iface + ' up');
// Subscribe to corresponding hostapd if it is (re)added
hapd_subscriber.subscribe("hostapd." + iface);
}
/* build a list of all running and new interfaces */
/* handle events from netifd */
function device_subscriber_notify_cb(notify) {
switch(notify.type) {
case 'link_down':
if (!ifaces[notify.data.name])
break;
/* de-auth all clients */
ubus.call(ifaces[notify.data.name].path, 'del_clients');
ifaces[notify.data.name].authenticated = false;
flush_iface(notify.data.name);
ubus.call('ratelimit', 'device_delete', { device: notify.data.name });
break;
case 'link_up':
if (!ifaces[notify.data.name])
break;
log.syslog(LOG_USER, "starting iface ${notify.data.name}");
hostapd_start(notify.data.name);
break;
};
return 0;
}
function subscriber_remove_cb(remove) {
}
/* track and subscribe to a hostapd/netifd object */
function ubus_unsub_object(add, id, path) {
let object = split(path, '.');
if (path == 'network.device' && add) {
device_subscriber.subscribe(path);
}
let object_hostapd = object[0];
let object_ifaces = object[1];
if (length(object) > 2) {
// For swconfig platforms
// The interface name is of the form eth0.4086 etc
object_ifaces = object[1] + "." + object[2];
}
if (object_hostapd != 'hostapd' || !ifaces[object_ifaces])
return;
if (add) {
log.syslog(LOG_USER, "adding " + path);
hapd_subscriber.subscribe(path);
ifaces[object_ifaces].hostapd = true;
ifaces[object_ifaces].path = path;
} else {
// Mark the port as unauthorized. but dont delete it
// ifaces contains the configured (from uci config) ports
netifd_handle_iface(object_ifaces, false);
}
}
/* try to add all enumerated objects */
function ubus_listener_cb(event, payload) {
ubus_unsub_object(event == 'ubus.object.add', payload.id, payload.path);
}
/* setup the ubus object listener, allowing us to track hostapd objects that get added and removed */
function ubus_listener_init() {
/* setup the notification listener */
hapd_subscriber = ubus.subscriber(hapd_subscriber_notify_cb, subscriber_remove_cb);
device_subscriber = ubus.subscriber(device_subscriber_notify_cb, subscriber_remove_cb);
/* enumerate all existing ojects */
let list = ubus.list();
for (let k, path in list)
ubus_unsub_object(true, 0, path);
/* register add/remove handlers */
ubus.listener('ubus.object.add', ubus_listener_cb);
ubus.listener('ubus.object.remove', ubus_listener_cb);
}
function prepare_ifaces() {
for (let k, v in ifaces)
v.active = false;
for (let k, port in config.ports) {
ifaces[port.iface] ??= {};
ifaces[port.iface].active = true;
ifaces[port.iface].authenticated = false;
ifaces[port.iface].default_vlan = +port.vlan;
ifaces[port.iface].wan_ports = port.wan_ports;
ifaces[port.iface].lladdr = [];
}
for (let iface, v in ifaces) {
if (v.active)
continue;
ubus.call('hostapd', 'config_remove', { iface });
delete ifaces[iface];
system('ifconfig ' + iface + ' down');
}
}
/* start all active interfaces */
function start_ifaces() {
for (let iface, v in ifaces)
hostapd_start(iface);
}
/* shutdown all interfaces */
function shutdown() {
for (let iface, v in ifaces) {
log.syslog(LOG_USER, "shutdown");
ubus.call('hostapd', 'config_remove', { iface });
netifd_handle_iface(iface, false);
flush_iface(iface);
ubus.call('ratelimit', 'device_delete', { device: iface });
}
}
/* the object published on ubus */
let ubus_methods = {
dump: {
call: function(req) {
return ifaces;
},
args: {
}
},
reload: {
call: function(req) {
config_load();
prepare_ifaces();
start_ifaces();
return 0;
},
args: {
}
},
};
/* handle rtnl events for carrier-down events */
function rtnl_cb(msg) {
if (msg.cmd != rtnl.const.RTM_NEWNEIGH)
return;
if (!ifaces[msg.msg.dev] || ifaces[msg.msg.dev].authenticated)
return;
ubus.call(ifaces[msg.msg.dev].path, 'mac_auth', { addr: msg.msg.lladdr });
}
log.openlog("ieee8021x", log.LOG_PID, log.LOG_USER);
uloop.init();
ubus.publish('ieee8021x', ubus_methods);
config_load();
rtnl.listener(rtnl_cb, null, [ rtnl.const.RTNLGRP_NEIGH ]);
prepare_ifaces();
ubus_listener_init();
start_ifaces();
uloop.run();
uloop.done();
shutdown();
log.closelog();

View File

@@ -0,0 +1,103 @@
#
# Copyright (C) 2014-2015 OpenWrt.org
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#
include $(TOPDIR)/rules.mk
PKG_NAME:=libwebsockets
PKG_VERSION:=4.1.4
PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
PKG_SOURCE_URL:=https://codeload.github.com/warmcat/libwebsockets/tar.gz/v$(PKG_VERSION)?
PKG_HASH:=00da77b4b89db3e0b1a2778f756f08d55d7f6b1632e18d981fc399c412866147
PKG_SOURCE_VERSION:=v$(PKG_VERSION)
PKG_LICENSE:=LGPL-2.1+exception
PKG_LICENSE_FILES:=LICENSE
CMAKE_INSTALL:=1
include $(INCLUDE_DIR)/package.mk
include $(INCLUDE_DIR)/cmake.mk
CMAKE_OPTIONS += -DLWS_IPV6=$(if $(CONFIG_IPV6),ON,OFF)
CMAKE_OPTIONS += -DLWS_WITHOUT_TESTAPPS=ON
# other options worth noting
# CMAKE_OPTIONS += -DLWS_WITHOUT_EXTENSIONS=ON
# CMAKE_OPTIONS += -DLWS_WITHOUT_DAEMONIZE=ON
# CMAKE_OPTIONS += -DLWS_WITHOUT_SERVER=ON
# CMAKE_OPTIONS += -DLWS_WITHOUT_DEBUG=ON
define Package/libwebsockets/Default
SECTION:=libs
CATEGORY:=Libraries
TITLE:=libwebsockets
DEPENDS:=+zlib +libcap
URL:=https://libwebsockets.org
MAINTAINER:=Karl Palsson <karlp@etactica.com>
PROVIDES:= libwebsockets
endef
define Package/libwebsockets-openssl
$(call Package/libwebsockets/Default)
TITLE += (OpenSSL)
DEPENDS += +libopenssl
VARIANT:=openssl
endef
define Package/libwebsockets-mbedtls
$(call Package/$(PKG_NAME)/Default)
TITLE += (mbedTLS)
DEPENDS += +libmbedtls
VARIANT:=mbedtls
endef
define Package/libwebsockets-full
$(call Package/libwebsockets/Default)
TITLE += (Full - OpenSSL, libuv, plugins, CGI)
DEPENDS += +libopenssl +libuv
VARIANT:=full
endef
ifeq ($(BUILD_VARIANT),openssl)
CMAKE_OPTIONS += -DLWS_OPENSSL_CLIENT_CERTS=/etc/ssl/certs
CMAKE_OPTIONS += -DLWS_OPENSSL_SUPPORT=ON
CMAKE_OPTIONS += -DLWS_WITH_SSL=ON
endif
ifeq ($(BUILD_VARIANT),mbedtls)
CMAKE_OPTIONS += -DLWS_WITH_MBEDTLS=1
endif
ifeq ($(BUILD_VARIANT),full)
CMAKE_OPTIONS += -DLWS_OPENSSL_CLIENT_CERTS=/etc/ssl/certs
CMAKE_OPTIONS += -DLWS_OPENSSL_SUPPORT=ON
CMAKE_OPTIONS += -DLWS_WITH_SSL=ON
CMAKE_OPTIONS += -DLWS_WITH_LIBUV=ON
CMAKE_OPTIONS += -DLWS_WITH_PLUGINS=ON
CMAKE_OPTIONS += -DLWS_WITH_SERVER_STATUS=ON
CMAKE_OPTIONS += -DLWS_WITH_ACCESS_LOG=ON
CMAKE_OPTIONS += -DLWS_WITH_CGI=ON
CMAKE_OPTIONS += -DLWS_UNIX_SOCK=ON
endif
define Package/libwebsockets/install
$(INSTALL_DIR) $(1)/usr/lib
$(CP) $(PKG_INSTALL_DIR)/usr/lib/libwebsockets.so* $(1)/usr/lib/
endef
Package/libwebsockets-mbedtls/install = $(Package/libwebsockets/install)
Package/libwebsockets-openssl/install = $(Package/libwebsockets/install)
Package/libwebsockets-full/install = $(Package/libwebsockets/install)
$(eval $(call BuildPackage,libwebsockets-openssl))
$(eval $(call BuildPackage,libwebsockets-mbedtls))
$(eval $(call BuildPackage,libwebsockets-full))

View File

@@ -0,0 +1,16 @@
Index: libwebsockets-4.1.4/CMakeLists.txt
===================================================================
--- libwebsockets-4.1.4.orig/CMakeLists.txt
+++ libwebsockets-4.1.4/CMakeLists.txt
@@ -709,10 +709,10 @@ if ((CMAKE_COMPILER_IS_GNUCC OR CMAKE_CO
endif()
endif()
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-deprecated-declarations" )
if (COMPILER_IS_CLANG)
# otherwise osx blows a bunch of openssl deprecated api errors
- set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-deprecated-declarations" )
if (UNIX AND LWS_HAVE_PTHREAD_H)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread -Wno-error=unused-command-line-argument" )
endif()

View File

@@ -0,0 +1,30 @@
menu "Configuration"
depends on PACKAGE_modemmanager
config MODEMMANAGER_WITH_MBIM
bool "Include MBIM support"
default y
help
Compile ModemManager with MBIM support
config MODEMMANAGER_WITH_QMI
bool "Include QMI support"
default y
help
Compile ModemManager with QMI support
config MODEMMANAGER_WITH_QRTR
bool "Include QRTR support"
default y
depends on MODEMMANAGER_WITH_QMI
select LIBQMI_WITH_QRTR_GLIB
help
Compile ModemManager with QRTR support
config MODEMMANAGER_WITH_AT_COMMAND_VIA_DBUS
bool "Allow AT commands via DBus"
default n
help
Compile ModemManager allowing AT commands without debug flag
endmenu

View File

@@ -0,0 +1,140 @@
#
# Copyright (C) 2016 Velocloud Inc.
# Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es>
#
# This is free software, licensed under the GNU General Public License v2.
#
include $(TOPDIR)/rules.mk
PKG_NAME:=modemmanager
PKG_SOURCE_VERSION:=1.20.6
PKG_RELEASE:=12
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://gitlab.freedesktop.org/mobile-broadband/ModemManager.git
PKG_MIRROR_HASH:=e90103e2e42bb826bbbac83937a9a69f50348cd6ce0d8da655a12b65494ce7c9
PKG_MAINTAINER:=Nicholas Smith <nicholas@nbembedded.com>
PKG_LICENSE:=GPL-2.0-or-later
PKG_LICENSE_FILES:=COPYING
PKG_BUILD_DEPENDS:=glib2/host libxslt/host
PKG_BUILD_FLAGS:=gc-sections
include $(INCLUDE_DIR)/package.mk
include $(INCLUDE_DIR)/nls.mk
include $(INCLUDE_DIR)/meson.mk
TARGET_CFLAGS += -fno-merge-all-constants -fmerge-constants
define Package/modemmanager/config
source "$(SOURCE)/Config.in"
endef
define Package/modemmanager
SECTION:=net
CATEGORY:=Network
TITLE:=Control utility for any kind of mobile broadband modem
URL:=https://www.freedesktop.org/wiki/Software/ModemManager
DEPENDS:= \
$(INTL_DEPENDS) \
+glib2 \
+dbus \
+ppp \
+MODEMMANAGER_WITH_MBIM:libmbim \
+MODEMMANAGER_WITH_QMI:libqmi \
+MODEMMANAGER_WITH_QRTR:libqrtr-glib
endef
define Package/modemmanager/description
ModemManager is a D-Bus-activated service which allows controlling mobile
broadband modems. Add kernel modules for your modems as needed.
Select Utilities/usb-modeswitch if needed.
endef
MESON_ARGS += \
-Dudev=false \
-Dudevdir=/lib/udev \
-Dtests=false \
-Dsystemdsystemunitdir=no \
-Dsystemd_suspend_resume=false \
-Dsystemd_journal=false \
-Dpolkit=no \
-Dintrospection=false \
-Dman=false \
-Dbash_completion=false \
-Db_lto=true \
-Dmbim=$(if $(CONFIG_MODEMMANAGER_WITH_MBIM),true,false) \
-Dqmi=$(if $(CONFIG_MODEMMANAGER_WITH_QMI),true,false) \
-Dqrtr=$(if $(CONFIG_MODEMMANAGER_WITH_QRTR),true,false) \
-Dat_command_via_dbus=$(if $(CONFIG_MODEMMANAGER_WITH_AT_COMMAND_VIA_DBUS),true,false)
define Build/InstallDev
$(INSTALL_DIR) $(1)/usr/include/ModemManager
$(CP) $(PKG_INSTALL_DIR)/usr/include/ModemManager/*.h $(1)/usr/include/ModemManager
$(INSTALL_DIR) $(1)/usr/include/libmm-glib
$(CP) $(PKG_INSTALL_DIR)/usr/include/libmm-glib/*.h $(1)/usr/include/libmm-glib
$(INSTALL_DIR) $(1)/usr/lib
$(CP) $(PKG_INSTALL_DIR)/usr/lib/libmm-glib.so* $(1)/usr/lib
$(INSTALL_DIR) $(1)/usr/lib/pkgconfig
$(CP) $(PKG_INSTALL_DIR)/usr/lib/pkgconfig/ModemManager.pc $(1)/usr/lib/pkgconfig
$(CP) $(PKG_INSTALL_DIR)/usr/lib/pkgconfig/mm-glib.pc $(1)/usr/lib/pkgconfig
$(INSTALL_DIR) $(1)/usr/share/dbus-1/interfaces
$(CP) $(PKG_BUILD_DIR)/introspection/org.freedesktop.ModemManager1.* $(1)/usr/share/dbus-1/interfaces
endef
define Package/modemmanager/install
$(INSTALL_DIR) $(1)/lib/udev/rules.d
$(INSTALL_DATA) $(PKG_INSTALL_DIR)/lib/udev/rules.d/*.rules $(1)/lib/udev/rules.d
$(INSTALL_DIR) $(1)/usr/sbin
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/sbin/ModemManager $(1)/usr/sbin
$(INSTALL_BIN) ./files/usr/sbin/ModemManager-wrapper $(1)/usr/sbin
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/mmcli $(1)/usr/bin
$(INSTALL_DIR) $(1)/usr/lib
$(CP) $(PKG_INSTALL_DIR)/usr/lib/libmm-glib.so.* $(1)/usr/lib
$(INSTALL_DIR) $(1)/usr/lib/ModemManager
$(CP) $(PKG_INSTALL_DIR)/usr/lib/ModemManager/libmm-shared-*.so* $(1)/usr/lib/ModemManager
$(CP) $(PKG_INSTALL_DIR)/usr/lib/ModemManager/libmm-plugin-*.so* $(1)/usr/lib/ModemManager
$(INSTALL_DIR) $(1)/usr/lib/ModemManager/connection.d
$(INSTALL_BIN) ./files/10-report-down $(1)/usr/lib/ModemManager/connection.d
$(INSTALL_DIR) $(1)/etc/dbus-1/system.d
$(INSTALL_CONF) $(PKG_INSTALL_DIR)/etc/dbus-1/system.d/org.freedesktop.ModemManager1.conf $(1)/etc/dbus-1/system.d
$(INSTALL_DIR) $(1)/usr/share/dbus-1/system-services
$(INSTALL_DATA) $(PKG_INSTALL_DIR)/usr/share/dbus-1/system-services/org.freedesktop.ModemManager1.service $(1)/usr/share/dbus-1/system-services
$(INSTALL_DIR) $(1)/usr/share/ModemManager
$(INSTALL_DATA) $(PKG_INSTALL_DIR)/usr/share/ModemManager/*.conf $(1)/usr/share/ModemManager
$(INSTALL_DATA) ./files/modemmanager.common $(1)/usr/share/ModemManager
$(INSTALL_DIR) $(1)/usr/share/ModemManager/fcc-unlock.available.d
$(INSTALL_DATA) $(PKG_INSTALL_DIR)/usr/share/ModemManager/fcc-unlock.available.d/* $(1)/usr/share/ModemManager/fcc-unlock.available.d
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) ./files/modemmanager.init $(1)/etc/init.d/modemmanager
$(INSTALL_DIR) $(1)/etc/hotplug.d/usb
$(INSTALL_DATA) ./files/25-modemmanager-usb $(1)/etc/hotplug.d/usb
$(INSTALL_DIR) $(1)/etc/hotplug.d/net
$(INSTALL_DATA) ./files/25-modemmanager-net $(1)/etc/hotplug.d/net
$(INSTALL_DIR) $(1)/etc/hotplug.d/tty
$(INSTALL_DATA) ./files/25-modemmanager-tty $(1)/etc/hotplug.d/tty
$(INSTALL_DIR) $(1)/etc/hotplug.d/wwan
$(INSTALL_DATA) ./files/25-modemmanager-wwan $(1)/etc/hotplug.d/wwan
$(INSTALL_DIR) $(1)/lib/netifd/proto
$(INSTALL_BIN) ./files/modemmanager.proto $(1)/lib/netifd/proto/modemmanager.sh
endef
$(eval $(call BuildPackage,modemmanager))

View File

@@ -0,0 +1,44 @@
# OpenWrt ModemManager
## Description
Cellular modem control and connectivity
Optional libraries libmbim and libqmi are available.
Your modem may require additional kernel modules and/or the usb-modeswitch
package.
## Usage
Once installed, you can configure the 2G/3G/4G modem connections directly in
/etc/config/network as in the following example:
config interface 'broadband'
option device '/sys/devices/platform/soc/20980000.usb/usb1/1-1/1-1.2/1-1.2.1'
option proto 'modemmanager'
option apn 'ac.vodafone.es'
option allowedauth 'pap chap'
option username 'vodafone'
option password 'vodafone'
option pincode '7423'
option iptype 'ipv4'
option plmn '214001'
option lowpower '1'
option signalrate '30'
option allow_roaming '1'
Only 'device' and 'proto' are mandatory options, the remaining ones are all
optional.
The 'allowedauth' option allows limiting the list of authentication protocols.
It is given as a space-separated list of values, including any of the
following: 'pap', 'chap', 'mschap', 'mschapv2' or 'eap'. It will default to
allowing all protocols.
The 'iptype' option supports any of these values: 'ipv4', 'ipv6' or 'ipv4v6'.
It will default to 'ipv4' if not given.
The 'plmn' option allows to set the network operator MCCMNC.
The 'signalrate' option set's the signal refresh rate (in seconds) for the device.
You can call signal info with command: mmcli -m 0 --signal-get

View File

@@ -0,0 +1,35 @@
#!/bin/sh
# SPDX-License-Identifier: CC0-1.0
# 2022 Aleksander Morgado <aleksander@aleksander.es>
#
# Automatically report to netifd that the underlying modem
# is really disconnected
#
# require program name and at least 4 arguments
[ $# -lt 4 ] && exit 1
MODEM_PATH="$1"
BEARER_PATH="$2"
INTERFACE="$3"
STATE="$4"
[ "${STATE}" = "disconnected" ] || exit 0
. /usr/share/ModemManager/modemmanager.common
. /lib/netifd/netifd-proto.sh
INCLUDE_ONLY=1 . /lib/netifd/proto/modemmanager.sh
MODEM_STATUS=$(mmcli --modem="${MODEM_PATH}" --output-keyvalue)
[ -n "${MODEM_STATUS}" ] || exit 1
MODEM_DEVICE=$(modemmanager_get_field "${MODEM_STATUS}" "modem.generic.device")
[ -n "${MODEM_DEVICE}" ] || exit 2
CFG=$(mm_get_modem_config "${MODEM_DEVICE}")
[ -n "${CFG}" ] || exit 3
logger -t "modemmanager" "interface ${CFG} (network device ${INTERFACE}) ${STATE}"
proto_init_update $INTERFACE 0
proto_send_update $CFG
exit 0

View File

@@ -0,0 +1,31 @@
#!/bin/sh
# Copyright (C) 2016 Velocloud Inc
# Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es>
# Load common utilities
. /usr/share/ModemManager/modemmanager.common
# We require a interface name
[ -n "${INTERFACE}" ] || exit
# Always make sure the rundir exists
mkdir -m 0755 -p "${MODEMMANAGER_RUNDIR}"
# Report network interface
mm_log "info" "${ACTION} network interface ${INTERFACE}: event processed"
mm_report_event "${ACTION}" "${INTERFACE}" "net" "/sys${DEVPATH}"
# Look for an associated cdc-wdm interface
cdcwdm=""
case "${ACTION}" in
"add") cdcwdm=$(mm_track_cdcwdm "${INTERFACE}") ;;
"remove") cdcwdm=$(mm_untrack_cdcwdm "${INTERFACE}") ;;
esac
# Report cdc-wdm device, if any
[ -n "${cdcwdm}" ] && {
mm_log "info" "${ACTION} cdc interface ${cdcwdm}: custom event processed"
mm_report_event "${ACTION}" "${cdcwdm}" "usbmisc" "/sys${DEVPATH}"
}

View File

@@ -0,0 +1,16 @@
#!/bin/sh
# Copyright (C) 2016 Velocloud Inc
# Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es>
# Load hotplug common utilities
. /usr/share/ModemManager/modemmanager.common
# We require a device name
[ -n "$DEVNAME" ] || exit
# Always make sure the rundir exists
mkdir -m 0755 -p "${MODEMMANAGER_RUNDIR}"
# Report TTY
mm_log "info" "${ACTION} serial interface ${DEVNAME}: event processed"
mm_report_event "${ACTION}" "${DEVNAME}" "tty" "/sys${DEVPATH}"

View File

@@ -0,0 +1,13 @@
#!/bin/sh
# Copyright (C) 2019 Aleksander Morgado <aleksander@aleksander.es>
# We need to process only full USB device removal events, we don't
# want to process specific interface removal events.
[ "$ACTION" = remove ] || exit
[ -z "${INTERFACE}" ] || exit
# Load common utilities
. /usr/share/ModemManager/modemmanager.common
mm_clear_modem_wait_status "/sys${DEVPATH}"
mm_cleanup_interface_by_sysfspath "/sys${DEVPATH}"

View File

@@ -0,0 +1,15 @@
#!/bin/sh
# Copyright (C) 2021 Aleksander Morgado <aleksander@aleksander.es>
# Load hotplug common utilities
. /usr/share/ModemManager/modemmanager.common
# We require a device name
[ -n "$DEVNAME" ] || exit
# Always make sure the rundir exists
mkdir -m 0755 -p "${MODEMMANAGER_RUNDIR}"
# Report wwan
mm_log "info" "${ACTION} wwan control port ${DEVNAME}: event processed"
mm_report_event "${ACTION}" "${DEVNAME}" "wwan" "/sys${DEVPATH}"

View File

@@ -0,0 +1,376 @@
#!/bin/sh
# Copyright (C) 2016 Velocloud Inc
# Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es>
################################################################################
. /lib/functions.sh
. /lib/netifd/netifd-proto.sh
################################################################################
# Runtime state
MODEMMANAGER_RUNDIR="/var/run/modemmanager"
MODEMMANAGER_PID_FILE="${MODEMMANAGER_RUNDIR}/modemmanager.pid"
MODEMMANAGER_CDCWDM_CACHE="${MODEMMANAGER_RUNDIR}/cdcwdm.cache"
MODEMMANAGER_SYSFS_CACHE="${MODEMMANAGER_RUNDIR}/sysfs.cache"
MODEMMANAGER_EVENTS_CACHE="${MODEMMANAGER_RUNDIR}/events.cache"
################################################################################
# Common logging
mm_log() {
local level="$1"; shift
logger -p "daemon.${level}" -t "ModemManager[$$]" "hotplug: $*"
}
################################################################################
# Receives as input argument the full sysfs path of the device
# Returns the physical device sysfs path
#
# NOTE: this method only works when the device exists, i.e. it cannot be used
# on removal hotplug events
mm_find_physdev_sysfs_path() {
local tmp_path="$1"
while true; do
tmp_path=$(dirname "${tmp_path}")
# avoid infinite loops iterating
[ -z "${tmp_path}" ] || [ "${tmp_path}" = "/" ] && return
# For USB devices, the physical device will be that with a idVendor
# and idProduct pair of files
[ -f "${tmp_path}"/idVendor ] && [ -f "${tmp_path}"/idProduct ] && {
tmp_path=$(readlink -f "$tmp_path")
echo "${tmp_path}"
return
}
# For PCI devices, the physical device will be that with a vendor
# and device pair of files
[ -f "${tmp_path}"/vendor ] && [ -f "${tmp_path}"/device ] && {
tmp_path=$(readlink -f "$tmp_path")
echo "${tmp_path}"
return
}
done
}
################################################################################
# Returns the cdc-wdm name retrieved from sysfs
mm_track_cdcwdm() {
local wwan="$1"
local cdcwdm
cdcwdm=$(ls "/sys/class/net/${wwan}/device/usbmisc/")
[ -n "${cdcwdm}" ] || return
# We have to cache it for later, as we won't be able to get the
# associated cdc-wdm device on a remove event
echo "${wwan} ${cdcwdm}" >> "${MODEMMANAGER_CDCWDM_CACHE}"
echo "${cdcwdm}"
}
# Returns the cdc-wdm name retrieved from the cache
mm_untrack_cdcwdm() {
local wwan="$1"
local cdcwdm
# Look for the cached associated cdc-wdm device
[ -f "${MODEMMANAGER_CDCWDM_CACHE}" ] || return
cdcwdm=$(awk -v wwan="${wwan}" '!/^#/ && $0 ~ wwan { print $2 }' "${MODEMMANAGER_CDCWDM_CACHE}")
[ -n "${cdcwdm}" ] || return
# Remove from cache
sed -i "/${wwan} ${cdcwdm}/d" "${MODEMMANAGER_CDCWDM_CACHE}"
echo "${cdcwdm}"
}
################################################################################
# ModemManager needs some time from the ports being added until a modem object
# is exposed in DBus. With the logic here we do an explicit wait of N seconds
# for ModemManager to expose the new modem object, making sure that the wait is
# unique per device (i.e. per physical device sysfs path).
# Gets the modem wait status as retrieved from the cache
mm_get_modem_wait_status() {
local sysfspath="$1"
# If no sysfs cache file, we're done
[ -f "${MODEMMANAGER_SYSFS_CACHE}" ] || return
# Get status of the sysfs path
awk -v sysfspath="${sysfspath}" '!/^#/ && $0 ~ sysfspath { print $2 }' "${MODEMMANAGER_SYSFS_CACHE}"
}
# Clear the modem wait status from the cache, if any
mm_clear_modem_wait_status() {
local sysfspath="$1"
local escaped_sysfspath
[ -f "${MODEMMANAGER_SYSFS_CACHE}" ] && {
# escape '/', '\' and '&' for sed...
escaped_sysfspath=$(echo "$sysfspath" | sed -e 's/[\/&]/\\&/g')
sed -i "/${escaped_sysfspath}/d" "${MODEMMANAGER_SYSFS_CACHE}"
}
}
# Sets the modem wait status in the cache
mm_set_modem_wait_status() {
local sysfspath="$1"
local status="$2"
# Remove sysfs line before adding the new one with the new state
mm_clear_modem_wait_status "${sysfspath}"
# Add the new status
echo "${sysfspath} ${status}" >> "${MODEMMANAGER_SYSFS_CACHE}"
}
# Callback for config_foreach()
mm_get_modem_config_foreach_cb() {
local cfg="$1"
local sysfspath="$2"
local proto
config_get proto "${cfg}" proto
[ "${proto}" = modemmanager ] || return 0
local dev
dev=$(uci_get network "${cfg}" device)
[ "${dev}" = "${sysfspath}" ] || return 0
echo "${cfg}"
}
# Returns the name of the interface configured for this device
mm_get_modem_config() {
local sysfspath="$1"
# Look for configuration for the given sysfs path
config_load network
config_foreach mm_get_modem_config_foreach_cb interface "${sysfspath}"
}
# Callback for config_foreach()
mm_get_wwan_config_foreach_cb() {
local cfg="$1"
local sysfspath="$2"
local proto
config_get proto "${cfg}" proto
[ "${proto}" = "wwan" ] || return 0
local dev devname devpath
dev=$(uci_get network "${cfg}" device)
devname=$(basename "${dev}")
devpath=$(find /sys/devices -name "${devname}")
[[ "${devpath}" = "${sysfspath}*" ]] || return 0
echo "${cfg}"
}
# Returns the name of the interface configured for this device
mm_get_wwan_config() {
local sysfspath="$1"
# Look for configuration for the given sysfs path
config_load network
config_foreach mm_get_wwan_config_foreach_cb interface "${sysfspath}"
}
# Wait for a modem in the specified sysfspath
mm_wait_for_modem() {
local cfg="$1"
local sysfspath="$2"
# TODO: config max wait
local n=45
local step=5
while [ $n -ge 0 ]; do
[ -d "${sysfspath}" ] || {
mm_log "error" "ignoring modem detection request: no device at ${sysfspath}"
proto_set_available "${cfg}" 0
return 1
}
# Check if the modem exists at the given sysfs path
if ! mmcli -m "${sysfspath}" > /dev/null 2>&1
then
mm_log "error" "modem not detected at sysfs path"
else
mm_log "info" "modem exported successfully at ${sysfspath}"
mm_log "info" "setting interface '${cfg}' as available"
proto_set_available "${cfg}" 1
ifup "${cfg}"
return 0
fi
sleep $step
n=$((n-step))
done
mm_log "error" "timed out waiting for the modem to get exported at ${sysfspath}"
proto_set_available "${cfg}" 0
return 2
}
mm_report_modem_wait() {
local sysfspath=$1
local parent_sysfspath status
parent_sysfspath=$(mm_find_physdev_sysfs_path "$sysfspath")
[ -n "${parent_sysfspath}" ] || {
mm_log "error" "parent device sysfspath not found"
return
}
status=$(mm_get_modem_wait_status "${parent_sysfspath}")
case "${status}" in
"")
local cfg
cfg=$(mm_get_modem_config "${parent_sysfspath}")
[ -z "${cfg}" ] && cfg=$(mm_get_wwan_config "${parent_sysfspath}")
if [ -n "${cfg}" ]; then
mm_log "info" "interface '${cfg}' is set to configure device '${parent_sysfspath}'"
mm_log "info" "now waiting for modem at sysfs path ${parent_sysfspath}"
mm_set_modem_wait_status "${parent_sysfspath}" "processed"
# Launch subshell for the explicit wait
( mm_wait_for_modem "${cfg}" "${parent_sysfspath}" ) > /dev/null 2>&1 &
fi
;;
"processed")
mm_log "info" "already waiting for modem at sysfs path ${parent_sysfspath}"
;;
"ignored")
;;
*)
mm_log "error" "unknown status read for device at sysfs path ${parent_sysfspath}"
;;
esac
}
################################################################################
# Cleanup interfaces
mm_cleanup_interface_cb() {
local cfg="$1"
local proto
config_get proto "${cfg}" proto
[ "${proto}" = modemmanager ] || return 0
proto_set_available "${cfg}" 0
}
mm_cleanup_interfaces() {
config_load network
config_foreach mm_cleanup_interface_cb interface
}
mm_cleanup_interface_by_sysfspath() {
local dev="$1"
local cfg
cfg=$(mm_get_modem_config "$dev")
[ -n "${cfg}" ] || return
mm_log "info" "setting interface '$cfg' as unavailable"
proto_set_available "${cfg}" 0
}
################################################################################
# Event reporting
# Receives as input the action, the device name and the subsystem
mm_report_event() {
local action="$1"
local name="$2"
local subsystem="$3"
local sysfspath="$4"
# Do not save virtual devices
local virtual
virtual="$(echo "$sysfspath" | cut -d'/' -f4)"
[ "$virtual" = "virtual" ] && {
mm_log "debug" "sysfspath is a virtual device ($sysfspath)"
return
}
# Track/untrack events in cache
case "${action}" in
"add")
# On add events, store event details in cache (if not exists yet)
grep -qs "${name},${subsystem}" "${MODEMMANAGER_EVENTS_CACHE}" || \
echo "${action},${name},${subsystem},${sysfspath}" >> "${MODEMMANAGER_EVENTS_CACHE}"
;;
"remove")
# On remove events, remove old events from cache (match by subsystem+name)
sed -i "/${name},${subsystem}/d" "${MODEMMANAGER_EVENTS_CACHE}"
;;
esac
# Report the event
mm_log "debug" "event reported: action=${action}, name=${name}, subsystem=${subsystem}"
mmcli --report-kernel-event="action=${action},name=${name},subsystem=${subsystem}" 1>/dev/null 2>&1 &
# Wait for added modem if a sysfspath is given
[ -n "${sysfspath}" ] && [ "$action" = "add" ] && mm_report_modem_wait "${sysfspath}"
}
mm_report_event_from_cache_line() {
local event_line="$1"
local action name subsystem sysfspath
action=$(echo "${event_line}" | awk -F ',' '{ print $1 }')
name=$(echo "${event_line}" | awk -F ',' '{ print $2 }')
subsystem=$(echo "${event_line}" | awk -F ',' '{ print $3 }')
sysfspath=$(echo "${event_line}" | awk -F ',' '{ print $4 }')
mm_log "debug" "cached event found: action=${action}, name=${name}, subsystem=${subsystem}, sysfspath=${sysfspath}"
mm_report_event "${action}" "${name}" "${subsystem}" "${sysfspath}"
}
mm_report_events_from_cache() {
# Remove the sysfs cache
rm -f "${MODEMMANAGER_SYSFS_CACHE}"
local n=60
local step=1
local mmrunning=0
# Wait for ModemManager to be available in the bus
while [ $n -ge 0 ]; do
sleep $step
mm_log "info" "checking if ModemManager is available..."
if ! mmcli -L >/dev/null 2>&1
then
mm_log "info" "ModemManager not yet available"
else
mmrunning=1
break
fi
n=$((n-step))
done
[ ${mmrunning} -eq 1 ] || {
mm_log "error" "couldn't report initial kernel events: ModemManager not running"
return
}
# Report cached kernel events
while IFS= read -r event_line; do
mm_report_event_from_cache_line "${event_line}"
done < ${MODEMMANAGER_EVENTS_CACHE}
}

View File

@@ -0,0 +1,38 @@
#!/bin/sh /etc/rc.common
# Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es>
USE_PROCD=1
START=70
LOG_LEVEL="INFO"
stop_service() {
# Load common utils
. /usr/share/ModemManager/modemmanager.common
# Set all configured interfaces as unavailable
mm_cleanup_interfaces
}
start_service() {
# Setup ModemManager service
#
# We will make sure that the rundir always exists, and we initially cleanup
# all interfaces flagging them as unavailable.
#
# The cached events processing will wait for MM to be available in DBus
# and will make sure all ports are re-notified to ModemManager every time
# it starts.
#
# All these commands need to be executed on every MM start, even after
# procd-triggered respawns, which is why this is wrapped in a startup
# wrapper script called '/usr/sbin/ModemManager-wrapper'.
#
. /usr/share/ModemManager/modemmanager.common
procd_open_instance
procd_set_param command /usr/sbin/ModemManager-wrapper
procd_append_param command --log-level="$LOG_LEVEL"
[ "$LOG_LEVEL" = "DEBUG" ] && procd_append_param command --debug
procd_set_param respawn "${respawn_threshold:-3600}" "${respawn_timeout:-5}" "${respawn_retry:-5}"
procd_set_param pidfile "${MODEMMANAGER_PID_FILE}"
procd_close_instance
}

View File

@@ -0,0 +1,745 @@
#!/bin/sh
# Copyright (C) 2016-2019 Aleksander Morgado <aleksander@aleksander.es>
[ -x /usr/bin/mmcli ] || exit 0
[ -x /usr/sbin/pppd ] || exit 0
[ -n "$INCLUDE_ONLY" ] || {
. /lib/functions.sh
. ../netifd-proto.sh
. ./ppp.sh
init_proto "$@"
}
cdr2mask ()
{
# Number of args to shift, 255..255, first non-255 byte, zeroes
set -- $(( 5 - ($1 / 8) )) 255 255 255 255 $(( (255 << (8 - ($1 % 8))) & 255 )) 0 0 0
if [ "$1" -gt 1 ]
then
shift "$1"
else
shift
fi
echo "${1-0}"."${2-0}"."${3-0}"."${4-0}"
}
# This method expects as first argument a list of key-value pairs, as returned by mmcli --output-keyvalue
# The second argument must be exactly the name of the field to read
#
# Sample output:
# $ mmcli -m 0 -K
# modem.dbus-path : /org/freedesktop/ModemManager1/Modem/0
# modem.generic.device-identifier : ed6eff2e3e0f90463da1c2a755b2acacd1335752
# modem.generic.manufacturer : Dell Inc.
# modem.generic.model : DW5821e Snapdragon X20 LTE
# modem.generic.revision : T77W968.F1.0.0.4.0.GC.009\n026
# modem.generic.carrier-configuration : GCF
# modem.generic.carrier-configuration-revision : 08E00009
# modem.generic.hardware-revision : DW5821e Snapdragon X20 LTE
# ....
modemmanager_get_field() {
local list=$1
local field=$2
local value=""
[ -z "${list}" ] || [ -z "${field}" ] && return
# there is always at least a whitespace after each key, and we use that as part of the
# key matching we do (e.g. to avoid getting 'modem.generic.state-failed-reason' as a result
# when grepping for 'modem.generic.state'.
line=$(echo "${list}" | grep "${field} ")
value=$(echo ${line#*:})
# not found?
[ -n "${value}" ] || return 2
# only print value if set
[ "${value}" != "--" ] && echo "${value}"
return 0
}
# build a comma-separated list of values from the list
modemmanager_get_multivalue_field() {
local list=$1
local field=$2
local value=""
local length idx item
[ -z "${list}" ] || [ -z "${field}" ] && return
length=$(modemmanager_get_field "${list}" "${field}.length")
[ -n "${length}" ] || return 0
[ "$length" -ge 1 ] || return 0
idx=1
while [ $idx -le "$length" ]; do
item=$(modemmanager_get_field "${list}" "${field}.value\[$idx\]")
[ -n "${item}" ] && [ "${item}" != "--" ] && {
[ -n "${value}" ] && value="${value}, "
value="${value}${item}"
}
idx=$((idx + 1))
done
# nothing built?
[ -n "${value}" ] || return 2
# only print value if set
echo "${value}"
return 0
}
modemmanager_cleanup_connection() {
local modemstatus="$1"
local bearercount idx bearerpath
bearercount=$(modemmanager_get_field "${modemstatus}" "modem.generic.bearers.length")
# do nothing if no bearers reported
[ -n "${bearercount}" ] && [ "$bearercount" -ge 1 ] && {
# explicitly disconnect just in case
mmcli --modem="${device}" --simple-disconnect >/dev/null 2>&1
# and remove all bearer objects, if any found
idx=1
while [ $idx -le "$bearercount" ]; do
bearerpath=$(modemmanager_get_field "${modemstatus}" "modem.generic.bearers.value\[$idx\]")
mmcli --modem "${device}" --delete-bearer="${bearerpath}" >/dev/null 2>&1
idx=$((idx + 1))
done
}
}
modemmanager_connected_method_ppp_ipv4() {
local interface="$1"
local ttyname="$2"
local username="$3"
local password="$4"
local allowedauth="$5"
# all auth types are allowed unless a user given list is given
local authopts
local pap=1
local chap=1
local mschap=1
local mschapv2=1
local eap=1
[ -n "$allowedauth" ] && {
pap=0 chap=0 mschap=0 mschapv2=0 eap=0
for auth in $allowedauth; do
case $auth in
"pap") pap=1 ;;
"chap") chap=1 ;;
"mschap") mschap=1 ;;
"mschapv2") mschapv2=1 ;;
"eap") eap=1 ;;
*) ;;
esac
done
}
[ $pap -eq 1 ] || append authopts "refuse-pap"
[ $chap -eq 1 ] || append authopts "refuse-chap"
[ $mschap -eq 1 ] || append authopts "refuse-mschap"
[ $mschapv2 -eq 1 ] || append authopts "refuse-mschap-v2"
[ $eap -eq 1 ] || append authopts "refuse-eap"
proto_run_command "${interface}" /usr/sbin/pppd \
"${ttyname}" \
115200 \
nodetach \
noaccomp \
nobsdcomp \
nopcomp \
novj \
noauth \
$authopts \
${username:+ user "$username"} \
${password:+ password "$password"} \
lcp-echo-failure 5 \
lcp-echo-interval 15 \
lock \
crtscts \
nodefaultroute \
usepeerdns \
ipparam "${interface}" \
ip-up-script /lib/netifd/ppp-up \
ip-down-script /lib/netifd/ppp-down
}
modemmanager_disconnected_method_ppp_ipv4() {
local interface="$1"
echo "running disconnection (ppp method)"
[ -n "${ERROR}" ] && {
local errorstring
errorstring=$(ppp_exitcode_tostring "${ERROR}")
case "$ERROR" in
0)
;;
2)
proto_notify_error "$interface" "$errorstring"
proto_block_restart "$interface"
;;
*)
proto_notify_error "$interface" "$errorstring"
;;
esac
} || echo "pppd result code not given"
proto_kill_command "$interface"
}
modemmanager_connected_method_dhcp_ipv4() {
local interface="$1"
local wwan="$2"
local metric="$3"
proto_init_update "${wwan}" 1
proto_set_keep 1
proto_send_update "${interface}"
json_init
json_add_string name "${interface}_4"
json_add_string ifname "@${interface}"
json_add_string proto "dhcp"
proto_add_dynamic_defaults
[ -n "$metric" ] && json_add_int metric "${metric}"
json_close_object
ubus call network add_dynamic "$(json_dump)"
}
modemmanager_connected_method_static_ipv4() {
local interface="$1"
local wwan="$2"
local address="$3"
local prefix="$4"
local gateway="$5"
local mtu="$6"
local dns1="$7"
local dns2="$8"
local metric="$9"
local mask=""
[ -n "${address}" ] || {
proto_notify_error "${interface}" ADDRESS_MISSING
return
}
[ -n "${prefix}" ] || {
proto_notify_error "${interface}" PREFIX_MISSING
return
}
mask=$(cdr2mask "${prefix}")
[ -n "${mtu}" ] && /sbin/ip link set dev "${wwan}" mtu "${mtu}"
proto_init_update "${wwan}" 1
proto_set_keep 1
echo "adding IPv4 address ${address}, netmask ${mask}"
proto_add_ipv4_address "${address}" "${mask}"
[ -n "${gateway}" ] && {
echo "adding default IPv4 route via ${gateway}"
proto_add_ipv4_route "0.0.0.0" "0" "${gateway}" "${address}"
}
[ -n "${dns1}" ] && {
echo "adding primary DNS at ${dns1}"
proto_add_dns_server "${dns1}"
}
[ -n "${dns2}" ] && {
echo "adding secondary DNS at ${dns2}"
proto_add_dns_server "${dns2}"
}
[ -n "$metric" ] && json_add_int metric "${metric}"
proto_send_update "${interface}"
}
modemmanager_connected_method_dhcp_ipv6() {
local interface="$1"
local wwan="$2"
local metric="$3"
proto_init_update "${wwan}" 1
proto_set_keep 1
proto_send_update "${interface}"
json_init
json_add_string name "${interface}_6"
json_add_string ifname "@${interface}"
json_add_string proto "dhcpv6"
proto_add_dynamic_defaults
json_add_string extendprefix 1 # RFC 7278: Extend an IPv6 /64 Prefix to LAN
[ -n "$metric" ] && json_add_int metric "${metric}"
json_close_object
ubus call network add_dynamic "$(json_dump)"
}
modemmanager_connected_method_static_ipv6() {
local interface="$1"
local wwan="$2"
local address="$3"
local prefix="$4"
local gateway="$5"
local mtu="$6"
local dns1="$7"
local dns2="$8"
local metric="$9"
[ -n "${address}" ] || {
proto_notify_error "${interface}" ADDRESS_MISSING
return
}
[ -n "${prefix}" ] || {
proto_notify_error "${interface}" PREFIX_MISSING
return
}
[ -n "${mtu}" ] && /sbin/ip link set dev "${wwan}" mtu "${mtu}"
proto_init_update "${wwan}" 1
proto_set_keep 1
echo "adding IPv6 address ${address}, prefix ${prefix}"
proto_add_ipv6_address "${address}" "128"
proto_add_ipv6_prefix "${address}/${prefix}"
[ -n "${gateway}" ] && {
echo "adding default IPv6 route via ${gateway}"
proto_add_ipv6_route "${gateway}" "128"
proto_add_ipv6_route "::0" "0" "${gateway}" "" "" "${address}/${prefix}"
}
[ -n "${dns1}" ] && {
echo "adding primary DNS at ${dns1}"
proto_add_dns_server "${dns1}"
}
[ -n "${dns2}" ] && {
echo "adding secondary DNS at ${dns2}"
proto_add_dns_server "${dns2}"
}
[ -n "$metric" ] && json_add_int metric "${metric}"
proto_send_update "${interface}"
}
modemmanager_disconnected_method_common() {
local interface="$1"
echo "running disconnection (common)"
proto_init_update "*" 0
proto_send_update "${interface}"
}
proto_modemmanager_init_config() {
available=1
no_device=1
proto_config_add_string device
proto_config_add_string apn
proto_config_add_string 'allowedauth:list(string)'
proto_config_add_string username
proto_config_add_string password
proto_config_add_string allowedmode
proto_config_add_string preferredmode
proto_config_add_string pincode
proto_config_add_string iptype
proto_config_add_string plmn
proto_config_add_int signalrate
proto_config_add_boolean lowpower
proto_config_add_boolean allow_roaming
proto_config_add_defaults
}
# Append param to the global 'connectargs' variable.
append_param() {
local param="$1"
[ -z "$param" ] && return
[ -z "$connectargs" ] || connectargs="${connectargs},"
connectargs="${connectargs}${param}"
}
modemmanager_set_allowed_mode() {
local device="$1"
local interface="$2"
local allowedmode="$3"
echo "setting allowed mode to '${allowedmode}'"
mmcli --modem="${device}" --set-allowed-modes="${allowedmode}" || {
proto_notify_error "${interface}" MM_INVALID_ALLOWED_MODES_LIST
proto_block_restart "${interface}"
return 1
}
}
modemmanager_set_preferred_mode() {
local device="$1"
local interface="$2"
local allowedmode="$3"
local preferredmode="$4"
[ -z "${preferredmode}" ] && {
echo "no preferred mode configured"
proto_notify_error "${interface}" MM_NO_PREFERRED_MODE_CONFIGURED
proto_block_restart "${interface}"
return 1
}
[ -z "${allowedmode}" ] && {
echo "no allowed mode configured"
proto_notify_error "${interface}" MM_NO_ALLOWED_MODE_CONFIGURED
proto_block_restart "${interface}"
return 1
}
echo "setting preferred mode to '${preferredmode}' (${allowedmode})"
mmcli --modem="${device}" \
--set-preferred-mode="${preferredmode}" \
--set-allowed-modes="${allowedmode}" || {
proto_notify_error "${interface}" MM_FAILED_SETTING_PREFERRED_MODE
proto_block_restart "${interface}"
return 1
}
}
exec_at_command() {
local at_command=$1
local serial_dev=$2
local resp_file="/tmp/at_response.txt"
local cmd_timeout=1
local retry_limit=3
local i=0
local socat_pid reader_pid
while [ $i -lt ${retry_limit} ]; do
# Flush old data
> ${resp_file}
echo "${at_command}" | socat - /dev/${serial_dev},crnl &
socat_pid=$!
{
cat /dev/${serial_dev} > ${resp_file} &
reader_pid=$!
sleep ${cmd_timeout}
kill -9 ${reader_pid} 2>/dev/null
}
kill -9 ${socat_pid} 2>/dev/null
case $(cat ${resp_file}) in
*OK*)
echo "AT command executed successfully"
break
;;
*)
echo -n "AT command failed or no response"
[ -n "$i" ] && echo ", retry $i" || echo
;;
esac
let "i++"
done
rm -f ${resp_file}
}
_mm_get_processed_device() {
local mm_rundir="/var/run/modemmanager"
local mm_sysfs_cache="${mm_rundir}/sysfs.cache"
awk -v status="processed" '!/^#/ && $0 ~ status {print $1}' "${mm_sysfs_cache}"
}
proto_modemmanager_quectel_setup() {
local apn username password pdptype
local manufacturer ser_port context_id context_type at_command
json_get_vars apn username password pdptype
local device=$(_mm_get_processed_device)
[ -n "${device}" ] || {
echo "No processed device"
return 1
}
# validate that ModemManager is handling the modem at the sysfs path
modemstatus=$(mmcli --modem="${device}" --output-keyvalue)
modempath=$(modemmanager_get_field "${modemstatus}" "modem.dbus-path")
[ -n "${modempath}" ] || {
echo "Device not managed by ModemManager"
return 1
}
echo "modem available at ${modempath}"
# workaround for Quectel embedded TCP/IP Stack
manufacturer=$(modemmanager_get_field "${modemstatus}" "modem.generic.manufacturer")
ser_port=$(mmcli --modem="${device}" -K | grep modem.generic.ports | grep tty | awk '{print $3}' | head -n 1)
if [ "${manufacturer}" = "Quectel" ]; then
echo "setup TCP/IP Context"
case "${pdptype}" in
ip)
context_type=1
;;
ipv6)
context_type=2
;;
ipv4v6|*)
context_type=3
;;
esac
if [ -n "${username}" ] && [ -n "${password}" ]; then
at_command="at+qicsgp=1,${context_type},\"${apn}\",\"${username}\",\"${password}\",3"
else
at_command="at+qicsgp=1,${context_type},\"${apn}\",\"\",\"\",0"
fi
exec_at_command "${at_command}" "${ser_port}"
fi
}
proto_modemmanager_setup() {
local interface="$1"
local modempath modemstatus bearercount bearerpath connectargs bearerstatus beareriface
local bearermethod_ipv4 bearermethod_ipv6 auth cliauth
local operatorname operatorid registration accesstech signalquality
local allowedmode preferredmode
local device apn allowedauth username password pincode
local iptype plmn metric signalrate allow_roaming
local address prefix gateway mtu dns1 dns2
json_get_vars device apn allowedauth username password
json_get_vars pincode iptype plmn metric signalrate allow_roaming
json_get_vars allowedmode preferredmode
# validate sysfs path given in config
[ -n "${device}" ] || {
echo "No device specified"
proto_notify_error "${interface}" NO_DEVICE
proto_set_available "${interface}" 0
return 1
}
# validate that ModemManager is handling the modem at the sysfs path
modemstatus=$(mmcli --modem="${device}" --output-keyvalue)
modempath=$(modemmanager_get_field "${modemstatus}" "modem.dbus-path")
[ -n "${modempath}" ] || {
echo "Device not managed by ModemManager"
proto_notify_error "${interface}" DEVICE_NOT_MANAGED
proto_set_available "${interface}" 0
return 1
}
echo "modem available at ${modempath}"
[ -z "${allowedmode}" ] || {
case "$allowedmode" in
"2g")
modemmanager_set_allowed_mode "$device" \
"$interface" "2g"
;;
"3g")
modemmanager_set_allowed_mode "$device" \
"$interface" "3g"
;;
"4g")
modemmanager_set_allowed_mode "$device" \
"$interface" "4g"
;;
"5g")
modemmanager_set_allowed_mode "$device" \
"$interface" "5g"
;;
*)
modemmanager_set_preferred_mode "$device" \
"$interface" "${allowedmode}" "${preferredmode}"
;;
esac
# check error for allowed_mode and preferred_mode function call
[ "$?" -ne "0" ] && return 1
}
# always cleanup before attempting a new connection, just in case
modemmanager_cleanup_connection "${modemstatus}"
# if allowedauth list given, build option string
for auth in $allowedauth; do
cliauth="${cliauth}${cliauth:+|}$auth"
done
# setup connect args; APN mandatory (even if it may be empty)
echo "starting connection with apn '${apn}'..."
proto_notify_error "${interface}" MM_CONNECT_IN_PROGRESS
# setup allow-roaming parameter
if [ -n "${allow_roaming}" ] && [ "${allow_roaming}" -eq 0 ];then
allow_roaming="no"
else
# allowed unless a user set the opposite
allow_roaming="yes"
fi
# Append options to 'connectargs' variable
append_param "apn=${apn}"
append_param "allow-roaming=${allow_roaming}"
append_param "${iptype:+ip-type=${iptype}}"
append_param "${plmn:+operator-id=${plmn}}"
append_param "${cliauth:+allowed-auth=${cliauth}}"
append_param "${username:+user=${username}}"
append_param "${password:+password=${password}}"
append_param "${pincode:+pin=${pincode}}"
mmcli --modem="${device}" --timeout 120 --simple-connect="${connectargs}" || {
proto_notify_error "${interface}" MM_CONNECT_FAILED
proto_block_restart "${interface}"
return 1
}
# check if Signal refresh rate is set
if [ -n "${signalrate}" ] && [ "${signalrate}" -eq "${signalrate}" ] 2>/dev/null; then
echo "setting signal refresh rate to ${signalrate} seconds"
mmcli --modem="${device}" --signal-setup="${signalrate}"
else
echo "signal refresh rate is not set"
fi
# log additional useful information
modemstatus=$(mmcli --modem="${device}" --output-keyvalue)
operatorname=$(modemmanager_get_field "${modemstatus}" "modem.3gpp.operator-name")
[ -n "${operatorname}" ] && echo "network operator name: ${operatorname}"
operatorid=$(modemmanager_get_field "${modemstatus}" "modem.3gpp.operator-code")
[ -n "${operatorid}" ] && echo "network operator MCCMNC: ${operatorid}"
registration=$(modemmanager_get_field "${modemstatus}" "modem.3gpp.registration-state")
[ -n "${registration}" ] && echo "registration type: ${registration}"
accesstech=$(modemmanager_get_multivalue_field "${modemstatus}" "modem.generic.access-technologies")
[ -n "${accesstech}" ] && echo "access technology: ${accesstech}"
signalquality=$(modemmanager_get_field "${modemstatus}" "modem.generic.signal-quality.value")
[ -n "${signalquality}" ] && echo "signal quality: ${signalquality}%"
# we won't like it if there are more than one bearers, as that would mean the
# user manually created them, and that's unsupported by this proto
bearercount=$(modemmanager_get_field "${modemstatus}" "modem.generic.bearers.length")
[ -n "${bearercount}" ] && [ "$bearercount" -eq 1 ] || {
proto_notify_error "${interface}" INVALID_BEARER_LIST
return 1
}
# load connected bearer information
bearerpath=$(modemmanager_get_field "${modemstatus}" "modem.generic.bearers.value\[1\]")
bearerstatus=$(mmcli --bearer "${bearerpath}" --output-keyvalue)
# load network interface and method information
beareriface=$(modemmanager_get_field "${bearerstatus}" "bearer.status.interface")
bearermethod_ipv4=$(modemmanager_get_field "${bearerstatus}" "bearer.ipv4-config.method")
bearermethod_ipv6=$(modemmanager_get_field "${bearerstatus}" "bearer.ipv6-config.method")
# setup IPv4
[ -n "${bearermethod_ipv4}" ] && {
echo "IPv4 connection setup required in interface ${interface}: ${bearermethod_ipv4}"
case "${bearermethod_ipv4}" in
"dhcp")
modemmanager_connected_method_dhcp_ipv4 "${interface}" "${beareriface}" "${metric}"
;;
"static")
address=$(modemmanager_get_field "${bearerstatus}" "bearer.ipv4-config.address")
prefix=$(modemmanager_get_field "${bearerstatus}" "bearer.ipv4-config.prefix")
gateway=$(modemmanager_get_field "${bearerstatus}" "bearer.ipv4-config.gateway")
mtu=$(modemmanager_get_field "${bearerstatus}" "bearer.ipv4-config.mtu")
dns1=$(modemmanager_get_field "${bearerstatus}" "bearer.ipv4-config.dns.value\[1\]")
dns2=$(modemmanager_get_field "${bearerstatus}" "bearer.ipv4-config.dns.value\[2\]")
modemmanager_connected_method_static_ipv4 "${interface}" "${beareriface}" "${address}" "${prefix}" "${gateway}" "${mtu}" "${dns1}" "${dns2}" "${metric}"
;;
"ppp")
modemmanager_connected_method_ppp_ipv4 "${interface}" "${beareriface}" "${username}" "${password}" "${allowedauth}"
;;
*)
proto_notify_error "${interface}" UNKNOWN_METHOD
return 1
;;
esac
}
# setup IPv6
# note: if using ipv4v6, both IPv4 and IPv6 settings will have the same MTU and metric values reported
[ -n "${bearermethod_ipv6}" ] && {
echo "IPv6 connection setup required in interface ${interface}: ${bearermethod_ipv6}"
case "${bearermethod_ipv6}" in
"dhcp")
modemmanager_connected_method_dhcp_ipv6 "${interface}" "${beareriface}" "${metric}"
;;
"static")
address=$(modemmanager_get_field "${bearerstatus}" "bearer.ipv6-config.address")
prefix=$(modemmanager_get_field "${bearerstatus}" "bearer.ipv6-config.prefix")
gateway=$(modemmanager_get_field "${bearerstatus}" "bearer.ipv6-config.gateway")
mtu=$(modemmanager_get_field "${bearerstatus}" "bearer.ipv6-config.mtu")
dns1=$(modemmanager_get_field "${bearerstatus}" "bearer.ipv6-config.dns.value\[1\]")
dns2=$(modemmanager_get_field "${bearerstatus}" "bearer.ipv6-config.dns.value\[2\]")
modemmanager_connected_method_static_ipv6 "${interface}" "${beareriface}" "${address}" "${prefix}" "${gateway}" "${mtu}" "${dns1}" "${dns2}" "${metric}"
;;
"ppp")
proto_notify_error "${interface}" "unsupported method"
return 1
;;
*)
proto_notify_error "${interface}" UNKNOWN_METHOD
return 1
;;
esac
}
return 0
}
proto_modemmanager_teardown() {
local interface="$1"
local modemstatus bearerpath errorstring
local bearermethod_ipv4 bearermethod_ipv6
local device lowpower iptype
json_get_vars device lowpower iptype
echo "stopping network"
# load connected bearer information, just the first one should be ok
modemstatus=$(mmcli --modem="${device}" --output-keyvalue)
bearerpath=$(modemmanager_get_field "${modemstatus}" "modem.generic.bearers.value\[1\]")
[ -n "${bearerpath}" ] || {
echo "couldn't load bearer path: disconnecting anyway"
mmcli --modem="${device}" --simple-disconnect >/dev/null 2>&1
return
}
# load bearer connection methods
bearerstatus=$(mmcli --bearer "${bearerpath}" --output-keyvalue)
bearermethod_ipv4=$(modemmanager_get_field "${bearerstatus}" "bearer.ipv4-config.method")
[ -n "${bearermethod_ipv4}" ] &&
echo "IPv4 connection teardown required in interface ${interface}: ${bearermethod_ipv4}"
bearermethod_ipv6=$(modemmanager_get_field "${bearerstatus}" "bearer.ipv6-config.method")
[ -n "${bearermethod_ipv6}" ] &&
echo "IPv6 connection teardown required in interface ${interface}: ${bearermethod_ipv6}"
# disconnection handling only requires special treatment in IPv4/PPP
[ "${bearermethod_ipv4}" = "ppp" ] && modemmanager_disconnected_method_ppp_ipv4 "${interface}"
modemmanager_disconnected_method_common "${interface}"
# disconnect
mmcli --modem="${device}" --simple-disconnect ||
proto_notify_error "${interface}" DISCONNECT_FAILED
# disable
mmcli --modem="${device}" --disable
# low power, only if requested
[ "${lowpower:-0}" -lt 1 ] ||
mmcli --modem="${device}" --set-power-state-low
proto_init_update "*" 0
proto_send_update "$interface"
}
[ -n "$INCLUDE_ONLY" ] || {
add_protocol modemmanager
}

View File

@@ -0,0 +1,33 @@
#!/bin/sh
trap_with_arg() {
func="$1" ; shift
for sig ; do
# shellcheck disable=SC2064
trap "$func $sig" "$sig"
done
}
func_trap() {
logger "ModemManager-wrapper[$$]" "Sending signal ${1}..."
kill "-${1}" "$CHILD" 2>/dev/null
}
main() {
. /usr/share/ModemManager/modemmanager.common
trap_with_arg func_trap INT TERM KILL
mkdir -p "${MODEMMANAGER_RUNDIR}"
chmod 0755 "${MODEMMANAGER_RUNDIR}"
mm_cleanup_interfaces
/usr/sbin/ModemManager "$@" 1>/dev/null 2>/dev/null &
CHILD="$!"
mm_report_events_from_cache
wait "$CHILD"
}
main "$@"

View File

@@ -0,0 +1,42 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=poe
PKG_VERSION:=1.0
PKG_RELEASE:=1
PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
include $(INCLUDE_DIR)/package.mk
ifeq ($(CONFIG_TARGET_ipq50xx_generic_DEVICE_sonicfi_rap630w_311g),y)
TARGET_CFLAGS += -DPLATFORM_EWW631_B1=1
endif
define Package/poe
SECTION:=utils
CATEGORY:=Utilities
TITLE:=Turn on/off PoE ports with TSP23861 chipset
DEPENDS:= +libubox +libubus +libuci +libi2c
endef
define Package/poe/description
Turn on/off PoE ports
endef
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
$(CP) ./src/* $(PKG_BUILD_DIR)/
endef
define Package/poe/install
$(INSTALL_DIR) $(1)
$(INSTALL_DIR) $(1)/etc/config $(1)/etc/init.d
$(INSTALL_BIN) ./files/poe.init $(1)/etc/init.d/poe
$(INSTALL_BIN) ./files/poe.config $(1)/etc/config/poe
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/tps23861-poe-ctrl $(1)/usr/bin
endef
$(eval $(call BuildPackage,poe))

View File

@@ -0,0 +1,33 @@
#!/bin/sh /etc/rc.common
START=10
tps23861_poe_ctrl () {
local section="$1"
local num mode
config_get num "$section" port_num
config_get mode "$section" admin_mode
if [ "$mode" == "1" ]; then
output=$(tps23861-poe-ctrl -p "${num}" -P on)
echo "<6>${output}" > "/dev/kmsg"
else
output=$(tps23861-poe-ctrl -p "${num}" -P off)
echo "<6>${output}" > "/dev/kmsg"
fi
}
start(){
. /lib/functions.sh
board=$(board_name)
case $board in
sonicfi,rap630w-311g|\
cybertan,eww631-b1)
config_load poe
config_foreach tps23861_poe_ctrl port
;;
*)
;;
esac
}

View File

@@ -0,0 +1,26 @@
CFLAGS += -Wall -g
INCLUDES =
LDFLAGS = -lubus -lubox -li2c
LIBS =
SRCS = tps23861-poe-ctrl.c \
OBJS = $(SRCS:.c=.o)
MAIN = tps23861-poe-ctrl
all: $(MAIN)
$(MAIN): $(OBJS)
$(CC) $(CFLAGS) $(INCLUDES) -o $(MAIN) $(OBJS) $(LDFLAGS) $(LIBS)
.c.o:
$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
clean:
$(RM) *.o *~ $(MAIN) $(TEST)

View File

@@ -0,0 +1,215 @@
/*
* User-space daemon formonitoring and managing PoE ports with
* TI TPS23861 chips. based on the Linux Kernel TPS23861
* HWMON driver.
*/
#include <stdio.h> /* Standard input/output definitions */
#include <string.h> /* String function definitions */
#include <unistd.h> /* UNIX standard function definitions */
#include <fcntl.h> /* File control definitions */
#include <errno.h> /* Error number definitions */
#include <sys/ioctl.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h> /* uapi/linux/i2c-dev.h */
#include <libubox/ulog.h>
#define TPS23861_I2C_ADDR 0x20
#define DETECT_CLASS_RESTART 0x18
#define POWER_ENABLE 0x19
#define POWER_ON_SHIFT 0
#define POWER_OFF_SHIFT 4
typedef unsigned char u8;
#if defined(PLATFORM_EWW631_B1)
#define TPS23861_NUM_PORTS 1
#endif
#define CONVERT_PORT_NUM(x) (1 << ((u8)x-1))
unsigned int PORT_POWER_STATUS[TPS23861_NUM_PORTS];
int i2c_handler = -1;
#define ULOG_DBG(fmt, ...) ulog(LOG_DEBUG, fmt, ## __VA_ARGS__)
int open_device(void)
{
int fd, fset;
fd = open("/dev/i2c-0", O_RDWR);
fset = fcntl(fd, F_SETFL, 0);
if (fset < 0)
printf("fcntl failed!\n");
//if (isatty(STDIN_FILENO) == 0)
// printf("standard input is not a terminal device\n");
return fd;
}
int access_salve(int fd)
{
int ret;
if((ret = ioctl(fd, I2C_SLAVE, TPS23861_I2C_ADDR)) < 0)
{
printf("%s: Failed to access slave bus[%s]\n",__func__, strerror(errno));
return -1;
}
return(ret);
}
// Write to an I2C slave device's register:
int i2c_write(u8 slave_addr, u8 reg, u8 data)
{
u8 outbuf[2];
struct i2c_msg msgs[1];
struct i2c_rdwr_ioctl_data msgset[1];
outbuf[0] = reg;
outbuf[1] = data;
msgs[0].addr = slave_addr;
msgs[0].flags = 0;
msgs[0].len = 2;
msgs[0].buf = outbuf;
msgset[0].msgs = msgs;
msgset[0].nmsgs = 1;
if (ioctl(i2c_handler, I2C_RDWR, &msgset) < 0) {
perror("ioctl(I2C_RDWR) in i2c_write");
return -1;
}
return 0;
}
void poe_set_PowerOnOff(u8 port, u8 on_off) {
u8 value;
u8 portBit;
portBit = CONVERT_PORT_NUM(port+1);
if(on_off == 0) {
value = (portBit << POWER_OFF_SHIFT);
PORT_POWER_STATUS[port] = 0;
} else {
value = (portBit << POWER_ON_SHIFT);
PORT_POWER_STATUS[port] = 1;
}
ULOG_DBG("set Port%d Power Status [%d] portBit 0x[%x] value 0x[%x]\n", port+1, PORT_POWER_STATUS[port], portBit, value);
if(i2c_write(TPS23861_I2C_ADDR, POWER_ENABLE, value) < 0)
{
ULOG_ERR("Set port%d power on-off error (0x19)\n", port);
}
}
void RestartPortDetectClass(u8 port)
{
u8 value;
value = (1 << port) | (1 << (port + 4));
ULOG_DBG("RestartPortDetectClass value 0x%x\n", value);
if(i2c_write(TPS23861_I2C_ADDR, DETECT_CLASS_RESTART, value) < 0) {
ULOG_ERR("Set port%d detection and class on error\n",port);
}
}
int usage(const char *progname)
{
fprintf(stderr, "Usage: %s -p <1-3> -P <on|off> [options]\n"
"Required options:\n"
" -p <1-3>: Select port number (Only port 1 is supported)\n"
" -P <on|off>: Set PSE function state <on|off>\n"
"Optional options:\n"
" -d Enable debug mode\n"
"\n", progname);
return 1;
}
static int setPSE(int port ,char *optarg)
{
int ret = 0;
i2c_handler = open_device();
if (i2c_handler < 0) {
ULOG_ERR("open i2c-0 device error!\n");
goto EXIT;
}
ret = access_salve(i2c_handler);
if (ret < 0)
{
ULOG_ERR("The i2c-0 access error\n");
goto EXIT;
}
if(!strncmp("on", optarg, 2)) {
printf("Enable port%d PSE function\n", port);
RestartPortDetectClass(port-1);
}
else if (!strncmp("off", optarg, 3)) {
printf("Disable port%d PSE function\n", port);
poe_set_PowerOnOff(port-1, 0);
}
else {
ULOG_ERR("[Set] Do not accept this optarg!!!\n");
ret = 1;
}
EXIT:
close(i2c_handler);
return ret;
}
int main(int argc, char *argv[])
{
int ch, ret = 0, port = 0;
char *PSE = NULL;
if (argc == 1) {
return usage(argv[0]);
}
ulog_open(ULOG_STDIO | ULOG_SYSLOG, LOG_DAEMON, "tps23861");
ulog_threshold(LOG_INFO);
while ((ch = getopt(argc, argv, "dp:P:")) != -1) {
switch (ch) {
case 'd':
printf("tps23861-i2c-control ulog_threshold set to debug level\n");
ulog_threshold(LOG_DEBUG);
break;
case 'p':
port = atoi(optarg);
break;
case 'P':
PSE = optarg;
break;
default:
ret = usage(argv[0]);
break;
}
}
if (port < 1 || port > 3) {
ret = usage(argv[0]);
}
else {
if (PSE) {
setPSE(port, PSE);
}
else {
ret = usage(argv[0]);
}
}
return ret;
}

View File

@@ -0,0 +1,76 @@
#
# Copyright (C) 2021 OpenWrt.org
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#
include $(TOPDIR)/rules.mk
include $(INCLUDE_DIR)/kernel.mk
PKG_NAME:=qosify
PKG_SOURCE_URL=$(PROJECT_GIT)/project/qosify.git
PKG_MIRROR_HASH:=648e648655097d7063b3a2119b054cf9ee4a120a194cdf5fb5df8e03c9207c83
PKG_SOURCE_PROTO:=git
PKG_SOURCE_DATE:=2023-07-20
PKG_SOURCE_VERSION:=850cc271083d0ad4c3b2eefddb61f376390ddf62
PKG_RELEASE:=$(AUTORELEASE)
PKG_LICENSE:=GPL-2.0
PKG_MAINTAINER:=Felix Fietkau <nbd@nbd.name>
PKG_BUILD_DEPENDS:=bpf-headers
PKG_FLAGS:=nonshared
include $(INCLUDE_DIR)/package.mk
include $(INCLUDE_DIR)/cmake.mk
include $(INCLUDE_DIR)/bpf.mk
include $(INCLUDE_DIR)/nls.mk
define Package/qosify
SECTION:=utils
CATEGORY:=Base system
TITLE:=A simple QoS solution based eBPF + CAKE
DEPENDS:=+libbpf +libubox +libubus +libnl-tiny +kmod-sched-cake +kmod-sched-bpf +kmod-ifb +tc $(BPF_DEPENDS)
endef
TARGET_CFLAGS += \
-Wno-error=deprecated-declarations \
-I$(STAGING_DIR)/usr/include/libnl-tiny \
-I$(STAGING_DIR)/usr/include
CMAKE_OPTIONS += \
-DLIBNL_LIBS=-lnl-tiny
define Build/Compile
$(call CompileBPF,$(PKG_BUILD_DIR)/qosify-bpf.c)
$(Build/Compile/Default)
endef
define Package/qosify/conffiles
/etc/config/qosify
/etc/qosify/00-defaults.conf
endef
define Package/qosify/install
$(INSTALL_DIR) \
$(1)/lib/bpf \
$(1)/usr/sbin \
$(1)/etc/init.d \
$(1)/etc/config \
$(1)/etc/qosify \
$(1)/etc/hotplug.d/net \
$(1)/etc/hotplug.d/iface
$(INSTALL_DATA) $(PKG_BUILD_DIR)/qosify-bpf.o $(1)/lib/bpf
$(INSTALL_BIN) \
$(PKG_INSTALL_DIR)/usr/bin/qosify \
./files/qosify-status \
$(1)/usr/sbin/
$(INSTALL_BIN) ./files/qosify.init $(1)/etc/init.d/qosify
$(INSTALL_DATA) ./files/qosify-defaults.conf $(1)/etc/qosify/00-defaults.conf
$(INSTALL_DATA) ./files/qosify.conf $(1)/etc/config/qosify
$(INSTALL_DATA) ./files/qosify.hotplug $(1)/etc/hotplug.d/net/10-qosify
$(INSTALL_DATA) ./files/qosify.hotplug $(1)/etc/hotplug.d/iface/10-qosify
endef
$(eval $(call BuildPackage,qosify))

View File

@@ -0,0 +1,17 @@
# DNS
tcp:53 voice
tcp:5353 voice
udp:53 voice
udp:5353 voice
# NTP
udp:123 voice
# SSH
tcp:22 +video
# HTTP/QUIC
tcp:80 +besteffort
tcp:443 +besteffort
udp:80 +besteffort
udp:443 +besteffort

View File

@@ -0,0 +1,70 @@
#!/bin/sh
. /usr/share/libubox/jshn.sh
dev_status() {
tc -s qdisc sh dev "$1" root
echo
}
common_status() {
json_get_vars ifname ingress egress
[ -n "$ifname" ] || return
[ "$egress" -gt 0 ] && {
echo "egress status:"
dev_status "$ifname"
}
[ "$ingress" -gt 0 ] && {
echo "ingress status:"
dev_status "$(printf %.16s "ifb-$ifname")"
}
}
is_active() {
json_get_vars active
[ "${active:-0}" -gt 0 ]
}
device_status() {
local name="$2"
json_select "$name"
if is_active; then
status="active"
else
status="not found"
fi
echo "===== device $name: $status ====="
is_active && common_status
json_select ..
}
interface_status() {
local name="$2"
json_select "$name"
if is_active; then
status="active"
elif ubus -S -t 0 wait_for "network.interface.$name"; then
status="down"
else
status="not found"
fi
echo "===== interface $name: $status ====="
is_active && common_status
json_select ..
}
json_load "$(ubus call qosify status)"
json_for_each_item device_status devices
json_for_each_item interface_status interfaces

View File

@@ -0,0 +1,6 @@
config defaults
list defaults /etc/qosify/*.conf
option dscp_prio AF41
option dscp_icmp +CS0
option dscp_default_udp CS0
option prio_max_avg_pkt_len 500

View File

@@ -0,0 +1,2 @@
#!/bin/sh
ubus call qosify check_devices

View File

@@ -0,0 +1,171 @@
#!/bin/sh /etc/rc.common
# Copyright (c) 2021 OpenWrt.org
START=19
USE_PROCD=1
PROG=/usr/sbin/qosify
add_option() {
local type="$1"
local name="$2"
config_get val "$cfg" "$name"
[ -n "$val" ] && json_add_$type "$name" "$val"
}
add_flow_config() {
local cfg="$1"
add_option string dscp_prio
add_option string dscp_bulk
add_option int bulk_trigger_timeout
add_option int bulk_trigger_pps
add_option int prio_max_avg_pkt_len
}
add_defaults() {
cfg="$1"
json_add_boolean reset 1
config_get files "$cfg" defaults
json_add_array files
for i in $files; do
json_add_string "" "$i"
done
json_close_array
add_flow_config "$cfg"
add_option int timeout
add_option string dscp_icmp
add_option string dscp_default_udp
add_option string dscp_default_tcp
}
add_interface() {
local cfg="$1"
config_get_bool disabled "$cfg" disabled 0
[ "$disabled" -gt 0 ] && return
config_get name "$cfg" name
json_add_object "$name"
config_get bw "$cfg" bandwidth
config_get bw_up "$cfg" bandwidth_up
bw_up="${bw_up:-$bw}"
[ -n "$bw_up" ] && json_add_string bandwidth_up "$bw_up"
config_get bw_down "$cfg" bandwidth_down
bw_down="${bw_down:-$bw}"
[ -n "$bw_down" ] && json_add_string bandwidth_down "$bw_down"
add_option string bandwidth
add_option boolean ingress
add_option boolean egress
add_option string mode
add_option boolean nat
add_option boolean host_isolate
add_option boolean autorate_ingress
add_option string ingress_options
add_option string egress_options
config_get user_options "$cfg" options
config_get otype "$cfg" overhead_type
options=
case "$otype" in
none);;
manual)
config_get overhead "$cfg" overhead
[ -n "$overhead" ] && append options "overhead $overhead"
config_get encap "$cfg" overhead_encap
[ -n "$encap" ] && append options "$encap"
;;
conservative|\
pppoa-vcmux|\
pppoa-llc|\
pppoe-vcmux|\
pppoe-llcsnap|\
bridged-vcmux|\
bridged-llcsnap|\
ipoa-vcmux|\
ipoa-llcsnap|\
pppoe-ptm|\
bridged-ptm|\
docsis|\
ethernet)
append options "$otype"
;;
esac
config_get mpu "$cfg" overhead_mpu
[ -n "$mpu" ] && append options "mpu $mpu"
config_get ovlan "$cfg" overhead_vlan
[ "${ovlan:-0}" -ge 2 ] && append options "ether-vlan"
[ "${ovlan:-0}" -ge 1 ] && append options "ether-vlan"
[ -n "$user_options" ] && append options "$user_options"
[ -n "$options" ] && json_add_string options "$options"
json_close_object
}
add_class() {
local cfg="$1"
config_get value "$cfg" value
config_get ingress "$cfg" ingress
config_get egress "$cfg" egress
json_add_object "$cfg"
json_add_string ingress "${ingress:-$value}"
json_add_string egress "${egress:-$value}"
add_flow_config "$cfg"
json_close_object
}
reload_service() {
json_init
config_load qosify
config_foreach add_defaults defaults
json_add_object interfaces
config_foreach add_interface interface
json_close_object
json_add_object classes
config_foreach add_class class
config_foreach add_class alias
json_close_object
json_add_object devices
config_foreach add_interface device
json_close_object
ubus call qosify config "$(json_dump)"
}
service_triggers() {
procd_add_reload_trigger qosify
}
start_service() {
procd_open_instance
procd_set_param command "$PROG"
procd_set_param respawn
procd_close_instance
}
service_started() {
ubus -t 10 wait_for qosify
[ $? = 0 ] && reload_service
}

View File

@@ -0,0 +1,25 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=radius-gw-proxy
PKG_RELEASE:=1
PKG_LICENSE:=BSD-3-Clause
PKG_MAINTAINER:=John Crispin <john@phrozen.org>
include $(INCLUDE_DIR)/package.mk
include $(INCLUDE_DIR)/cmake.mk
define Package/radius-gw-proxy
SECTION:=ucentral
CATEGORY:=uCentral
TITLE:=uCentral Gateway radius-gw-proxy
DEPENDS:=+libubox +libubus
endef
define Package/radius-gw-proxy/install
$(INSTALL_DIR) $(1)/usr/sbin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/radius-gw-proxy $(1)/usr/sbin/
$(CP) ./files/* $(1)
endef
$(eval $(call BuildPackage,radius-gw-proxy))

View File

@@ -0,0 +1,11 @@
#!/bin/sh /etc/rc.common
START=99
USE_PROCD=1
start_service() {
procd_open_instance
procd_set_param command "/usr/sbin/radius-gw-proxy"
procd_close_instance
}

View File

@@ -0,0 +1,30 @@
cmake_minimum_required(VERSION 2.6)
PROJECT(radius-gw-proxy C)
ADD_DEFINITIONS(-Wall -Werror)
IF(CMAKE_C_COMPILER_VERSION VERSION_GREATER 6)
ADD_DEFINITIONS(-Wextra -Werror=implicit-function-declaration)
ADD_DEFINITIONS(-Wformat -Werror=format-security -Werror=format-nonliteral)
ENDIF()
ADD_DEFINITIONS(-Os -std=gnu99 -g3 -Wmissing-declarations -Wno-unused-parameter -Wno-strict-aliasing)
SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
SET(SOURCES main.c ubus.c)
FIND_LIBRARY(ubus NAMES ubus)
FIND_LIBRARY(ubox NAMES ubox)
FIND_PATH(ubox_include_dir libubox/uloop.h)
FIND_PATH(ubus_include_dir NAMES libubus.h)
INCLUDE_DIRECTORIES(${ubox_include_dir} ${ubus_include_dir})
ADD_EXECUTABLE(radius-gw-proxy ${SOURCES})
TARGET_LINK_LIBRARIES(radius-gw-proxy ${ubox} ${ubus})
INSTALL(TARGETS radius-gw-proxy
RUNTIME DESTINATION sbin
)

View File

@@ -0,0 +1,369 @@
/* SPDX-License-Identifier: BSD-3-Clause */
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libubox/uloop.h>
#include <libubox/usock.h>
#include <libubox/ulog.h>
#include <libubus.h>
#include "ubus.h"
#define RAD_PROX_BUFLEN (4 * 1024)
#define TLV_NAS_IP 4
#define TLV_PROXY_STATE 33
struct radius_socket {
struct uloop_fd fd;
enum socket_type type;
};
struct radius_header {
uint8_t code;
uint8_t id;
uint16_t len;
char auth[16];
char avp[];
};
struct radius_tlv {
uint8_t id;
uint8_t len;
char data[];
};
struct radius_proxy_state_key {
char id[256];
enum socket_type type;
};
struct radius_proxy_state {
struct avl_node avl;
struct radius_proxy_state_key key;
int port;
};
static struct radius_socket *sock_auth;
static struct radius_socket *sock_acct;
static struct radius_socket *sock_dae;
static int
avl_memcmp(const void *k1, const void *k2, void *ptr)
{
return memcmp(k1, k2, sizeof(struct radius_proxy_state_key));
}
static AVL_TREE(radius_proxy_states, avl_memcmp, false, NULL);
static struct blob_buf b;
static void
radius_proxy_state_add(char *id, int port, enum socket_type type)
{
struct radius_proxy_state *station;
struct radius_proxy_state_key key = { .type = type };
strcpy(key.id, id);
station = avl_find_element(&radius_proxy_states, &key, station, avl);
if (!station) {
ULOG_INFO("new station/port, adding to avl tree\n");
station = malloc(sizeof(*station));
memset(station, 0, sizeof(*station));
strcpy(station->key.id, id);
station->key.type = type;
station->avl.key = &station->key;
avl_insert(&radius_proxy_states, &station->avl);
}
station->port = port;
}
static char *
b64enc(char *src, int len)
{
char *dst;
int ret;
if (!src)
return NULL;
dst = malloc(len * 4);
ret = b64_encode(src, len, dst, len * 4);
if (ret < 1) {
free(dst);
return NULL;
}
return dst;
}
static char *
b64dec(char *src, int *ret)
{
int len = strlen(src);
char *dst = malloc(len);
*ret = b64_decode(src, dst, len);
if (*ret < 0)
return NULL;
return dst;
}
static void
radius_forward_gw(char *buf, enum socket_type type)
{
struct radius_header *hdr = (struct radius_header *) buf;
struct ubus_request async = { };
char *data = b64enc(buf, ntohs(hdr->len));
if (!data || !ucentral)
return;
blob_buf_init(&b, 0);
switch (type) {
case RADIUS_AUTH:
blobmsg_add_string(&b, "radius", "auth");
break;
case RADIUS_ACCT:
blobmsg_add_string(&b, "radius", "acct");
break;
case RADIUS_DAS:
blobmsg_add_string(&b, "radius", "coa");
break;
default:
return;
}
blobmsg_add_string(&b, "data", data);
ubus_invoke_async(&conn.ctx, ucentral, "radius", b.head, &async);
ubus_abort_request(&conn.ctx, &async);
free(data);
}
static int
radius_parse(char *buf, unsigned int len, int port, enum socket_type type, int tx)
{
struct radius_header *hdr = (struct radius_header *) buf;
struct radius_tlv *proxy_state = NULL;
char proxy_state_str[256] = {};
void *avp = hdr->avp;
unsigned int len_orig;
uint8_t localhost[] = { 0x7f, 0, 0, 1 };
if (len < sizeof(*hdr)) {
ULOG_ERR("invalid packet length, %d\n", len);
return -1;
}
len_orig = ntohs(hdr->len);
if (len_orig != len) {
ULOG_ERR("invalid header length, %d %d\n", len_orig, len);
return -1;
}
printf("\tcode:%d, id:%d, len:%d\n", hdr->code, hdr->id, len_orig);
len -= sizeof(*hdr);
while (len >= sizeof(struct radius_tlv)) {
struct radius_tlv *tlv = (struct radius_tlv *)avp;
if (len < tlv->len || tlv->len < sizeof(*tlv)) {
ULOG_ERR("invalid TLV length\n");
return -1;
}
if (tlv->id == TLV_PROXY_STATE)
proxy_state = tlv;
if (type == RADIUS_DAS && tlv->id == TLV_NAS_IP && tlv->len == 6)
memcpy(tlv->data, &localhost, 4);
printf("\tID:%d, len:%d\n", tlv->id, tlv->len);
avp += tlv->len;
len -= tlv->len;
}
if (type == RADIUS_DAS) {
if (tx) {
radius_forward_gw(buf, type);
} else {
struct sockaddr_in dest;
memset(&dest, 0, sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_port = htons(3799);
inet_pton(AF_INET, "127.0.0.1", &(dest.sin_addr.s_addr));
if (sendto(sock_dae->fd.fd, buf, len_orig,
MSG_DONTWAIT, (struct sockaddr*)&dest, sizeof(dest)) < 0)
ULOG_ERR("failed to deliver DAS frame to localhost\n");
}
return 0;
}
if (!proxy_state) {
ULOG_ERR("no proxy_state found\n");
return -1;
}
memcpy(proxy_state_str, proxy_state->data, proxy_state->len - 2);
printf("\tfowarding to %s, prox_state:%s\n", tx ? "gateway" : "hostapd", proxy_state_str);
if (tx) {
radius_proxy_state_add(proxy_state_str, port, type);
radius_forward_gw(buf, type);
} else {
struct radius_proxy_state *proxy = avl_find_element(&radius_proxy_states, proxy_state, proxy, avl);
struct radius_proxy_state_key key = {};
struct sockaddr_in dest;
struct radius_socket *sock;
switch(type) {
case RADIUS_AUTH:
sock = sock_auth;
break;
case RADIUS_ACCT:
sock = sock_acct;
break;
default:
ULOG_ERR("bad socket type\n");
return -1;
}
strcpy(key.id, proxy_state_str);
key.type = type;
proxy = avl_find_element(&radius_proxy_states, &key, proxy, avl);
if (!proxy) {
ULOG_ERR("unknown proxy_state, dropping frame\n");
return -1;
}
memset(&dest, 0, sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_port = proxy->port;
inet_pton(AF_INET, "127.0.0.1", &(dest.sin_addr.s_addr));
if (sendto(sock->fd.fd, buf, len_orig,
MSG_DONTWAIT, (struct sockaddr*)&dest, sizeof(dest)) < 0)
ULOG_ERR("failed to deliver frame to localhost\n");
}
return 0;
}
void
gateway_recv(char *data, enum socket_type type)
{
int len = 0;
char *frame;
frame = b64dec(data, &len);
if (!frame) {
ULOG_ERR("failed to b64_decode frame\n");
return;
}
radius_parse(frame, len, 0, type, 0);
free(frame);
}
static void
sock_recv(struct uloop_fd *u, unsigned int events)
{
static char buf[RAD_PROX_BUFLEN];
static char cmsg_buf[( CMSG_SPACE(sizeof(struct in_pktinfo)) + sizeof(int)) + 1];
static struct sockaddr_in sin;
char addr_str[INET_ADDRSTRLEN];
static struct iovec iov = {
.iov_base = buf,
.iov_len = sizeof(buf)
};
static struct msghdr msg = {
.msg_name = &sin,
.msg_namelen = sizeof(sin),
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = cmsg_buf,
.msg_controllen = sizeof(cmsg_buf),
};
struct radius_socket *sock = container_of(u, struct radius_socket, fd);
int len;
do {
len = recvmsg(u->fd, &msg, 0);
if (len < 0) {
switch (errno) {
case EAGAIN:
return;
case EINTR:
continue;
default:
perror("recvmsg");
uloop_fd_delete(u);
return;
}
}
inet_ntop(AF_INET, &sin.sin_addr, addr_str, sizeof(addr_str));
printf("RX: src:%s:%d, len=%d\n", addr_str, sin.sin_port, len);
radius_parse(buf, (unsigned int)len, sin.sin_port, sock->type, 1);
} while (1);
}
static struct radius_socket *
sock_open(char *port, enum socket_type type)
{
struct radius_socket *sock = malloc(sizeof(*sock));
if (!sock)
return NULL;
memset(sock, 0, sizeof(*sock));
sock->fd.fd = usock(USOCK_UDP | USOCK_SERVER | USOCK_NONBLOCK |
USOCK_NUMERIC | USOCK_IPV4ONLY,
"127.0.0.1", port);
if (sock->fd.fd < 0) {
perror("usock");
free(sock);
return NULL;
}
sock->type = type;
sock->fd.cb = sock_recv;
uloop_fd_add(&sock->fd, ULOOP_READ);
return sock;
}
int main(int argc, char **argv)
{
ulog_open(ULOG_STDIO | ULOG_SYSLOG, LOG_DAEMON, "radius-gw-proxy");
uloop_init();
ubus_init();
sock_auth = sock_open("1812", RADIUS_AUTH);
sock_acct = sock_open("1813", RADIUS_ACCT);
sock_dae = sock_open("3379", RADIUS_DAS);
uloop_run();
uloop_end();
ubus_deinit();
return 0;
}

View File

@@ -0,0 +1,124 @@
/* SPDX-License-Identifier: BSD-3-Clause */
#include <string.h>
#include <libubox/ulog.h>
#include <libubus.h>
#include "ubus.h"
struct ubus_auto_conn conn;
uint32_t ucentral;
enum {
RADIUS_TYPE,
RADIUS_DATA,
__RADIUS_MAX,
};
static const struct blobmsg_policy frame_policy[__RADIUS_MAX] = {
[RADIUS_TYPE] = { .name = "radius", .type = BLOBMSG_TYPE_STRING },
[RADIUS_DATA] = { .name = "data", .type = BLOBMSG_TYPE_STRING },
};
static int ubus_frame_cb(struct ubus_context *ctx,
struct ubus_object *obj,
struct ubus_request_data *req,
const char *method, struct blob_attr *msg)
{
struct blob_attr *tb[__RADIUS_MAX] = {};
enum socket_type type;
char *radius, *data;
blobmsg_parse(frame_policy, __RADIUS_MAX, tb, blobmsg_data(msg), blobmsg_data_len(msg));
if (!tb[RADIUS_TYPE] || !tb[RADIUS_DATA])
return UBUS_STATUS_INVALID_ARGUMENT;
radius = blobmsg_get_string(tb[RADIUS_TYPE]);
data = blobmsg_get_string(tb[RADIUS_DATA]);
if (!strcmp(radius, "auth"))
type = RADIUS_AUTH;
else if (!strcmp(radius, "acct"))
type = RADIUS_ACCT;
else if (!strcmp(radius, "coa"))
type = RADIUS_DAS;
else
return UBUS_STATUS_INVALID_ARGUMENT;
gateway_recv(data, type);
return UBUS_STATUS_OK;
}
static const struct ubus_method ucentral_methods[] = {
UBUS_METHOD("frame", ubus_frame_cb, frame_policy),
};
static struct ubus_object_type ubus_object_type =
UBUS_OBJECT_TYPE("radius.proxy", ucentral_methods);
struct ubus_object ubus_object = {
.name = "radius.proxy",
.type = &ubus_object_type,
.methods = ucentral_methods,
.n_methods = ARRAY_SIZE(ucentral_methods),
};
static void
ubus_event_handler_cb(struct ubus_context *ctx, struct ubus_event_handler *ev,
const char *type, struct blob_attr *msg)
{
enum {
EVENT_ID,
EVENT_PATH,
__EVENT_MAX
};
static const struct blobmsg_policy status_policy[__EVENT_MAX] = {
[EVENT_ID] = { .name = "id", .type = BLOBMSG_TYPE_INT32 },
[EVENT_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING },
};
struct blob_attr *tb[__EVENT_MAX];
char *path;
uint32_t id;
blobmsg_parse(status_policy, __EVENT_MAX, tb, blob_data(msg), blob_len(msg));
if (!tb[EVENT_ID] || !tb[EVENT_PATH])
return;
path = blobmsg_get_string(tb[EVENT_PATH]);
id = blobmsg_get_u32(tb[EVENT_ID]);
if (strcmp(path, "ucentral"))
return;
if (!strcmp("ubus.object.remove", type))
ucentral = 0;
else
ucentral = id;
}
static struct ubus_event_handler ubus_event_handler = { .cb = ubus_event_handler_cb };
static void
ubus_connect_handler(struct ubus_context *ctx)
{
ubus_add_object(ctx, &ubus_object);
ubus_register_event_handler(ctx, &ubus_event_handler, "ubus.object.add");
ubus_register_event_handler(ctx, &ubus_event_handler, "ubus.object.remove");
ubus_lookup_id(ctx, "ucentral", &ucentral);
}
void ubus_init(void)
{
memset(&conn, 0, sizeof(conn));
ucentral = 0;
conn.cb = ubus_connect_handler;
ubus_auto_connect(&conn);
}
void ubus_deinit(void)
{
ubus_auto_shutdown(&conn);
}

View File

@@ -0,0 +1,13 @@
enum socket_type {
RADIUS_AUTH = 0,
RADIUS_ACCT,
RADIUS_DAS
};
extern struct ubus_auto_conn conn;
extern uint32_t ucentral;
void ubus_init(void);
void ubus_deinit(void);
void gateway_recv(char *data, enum socket_type type);

View File

@@ -0,0 +1,34 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=ratelimit
PKG_RELEASE:=1
PKG_MAINTAINER:=John Crispin <john@phrozen.org>
include $(INCLUDE_DIR)/package.mk
define Package/ratelimit
SECTION:=net
CATEGORY:=Network
TITLE:=Wireless ratelimiting
DEPENDS:=+tc +kmod-ifb
endef
define Package/ratelimit/description
Allow Wireless client rate limiting
endef
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
endef
define Build/Compile/Default
endef
Build/Compile = $(Build/Compile/Default)
define Package/ratelimit/install
$(CP) ./files/* $(1)
endef
$(eval $(call BuildPackage,ratelimit))

View File

@@ -0,0 +1,3 @@
#config rate uCentral
# option egress 10
# option ingress 20

View File

@@ -0,0 +1,39 @@
#!/bin/sh /etc/rc.common
START=80
USE_PROCD=1
PROG=/usr/bin/ratelimit
add_rate() {
local cfg="$1"
config_get ssid "$cfg" ssid
config_get ingress "$cfg" ingress
config_get egress "$cfg" egress
ubus call ratelimit defaults_set "{\"name\": \"$ssid\", \"rate_ingress\": \""$ingress"mbit\", \"rate_egress\": \""$egress"mbit\" }"
}
reload_service() {
logger ratelimit reload
ubus call ratelimit flush
config_load ratelimit
config_foreach add_rate rate
ubus call ratelimit reload
}
service_triggers() {
procd_add_reload_trigger ratelimit
}
start_service() {
procd_open_instance
procd_set_param command "$PROG"
procd_set_param respawn
procd_close_instance
}
service_started() {
ubus -t 10 wait_for ratelimit
[ $? = 0 ] && reload_service
}

View File

@@ -0,0 +1,409 @@
#!/usr/bin/env ucode
'use strict';
import { basename, popen } from 'fs';
import * as ubus from 'ubus';
import * as uloop from 'uloop';
let defaults = {};
let devices = {};
function cmd(command, ignore_error) {
// if (ignore_error)
// command += "> /dev/null 2>&1";
warn(`> ${command}\n`);
let rc = system(command);
return ignore_error || rc == 0;
}
function qdisc_add_leaf(iface, id, opts) {
opts ??= "";
return cmd(`tc class replace dev ${iface} parent 1:1 classid 1:${id} htb rate 1mbit ${opts} burst 2k prio 1`) &&
cmd(`tc qdisc replace dev ${iface} parent 1:${id} handle ${id}: fq_codel flows 128 limit 800 quantum 300 noecn`);
}
function qdisc_del_leaf(iface, id) {
cmd(`tc class del dev ${iface} parent 1:1 classid 1:${id}`, true);
}
function qdisc_add(iface) {
return cmd(`tc qdisc add dev ${iface} root handle 1: htb default 2`) &&
cmd(`tc class add dev ${iface} parent 1: classid 1:1 htb rate 1000mbit burst 6k`) &&
qdisc_add_leaf(iface, 2, "ceil 1000mbit");
}
function qdisc_del(iface) {
cmd(`tc qdisc del dev ${iface} root`, true);
}
function ifb_dev(iface) {
let ifbname;
if ((index(iface, 'phy') != -1) && (index(iface, 'ap') != -1)) {
// For interfaces like phy6g-ap0, phy5g-ap0, phy2g-ap0
// we replace 'phy' with "p" to confine the ifb name length
// and replace 'ap' with 'a' to further shorten it.
ifbname = replace(iface, 'phy', 'p');
ifbname = replace(ifbname, 'ap', 'a');
} else {
ifbname = iface;
}
ifbname = "i-" + ifbname;
return ifbname;
}
function ifb_add(iface, ifbdev) {
return cmd(`ip link add ${ifbdev} type ifb`) &&
cmd(`ip link set ${ifbdev} up`) &&
cmd(`tc qdisc add dev ${iface} clsact`, true) &&
cmd(`tc filter add dev ${iface} ingress protocol all prio 512 matchall action mirred egress redirect dev ${ifbdev}`);
}
function ifb_del(iface, ifbdev) {
cmd(`tc filter del dev ${iface} ingress protocol all prio 512`);
cmd(`ip link set ${ifbdev} down`, true);
cmd(`ip link del ${ifbdev}`, true);
}
function macfilter_add(iface, id, type, mac) {
return cmd(`tc filter add dev ${iface} protocol all parent 1: prio 1 handle 800::${id} u32 match ether ${type} ${mac} flowid 1:${id}`);
}
function macfilter_del(iface, id) {
cmd(`tc filter del dev ${iface} protocol all parent 1: prio 1 handle 800::${id} u32`, true);
}
function linux_client_del(device, client) {
printf('-> linux_client_del\n');
let ifbdev = ifb_dev(device.name);
let id = client.id + 3;
macfilter_del(device.name, id);
qdisc_del_leaf(device.name, id);
macfilter_del(ifbdev, id);
qdisc_del_leaf(ifbdev, id);
}
function linux_client_set(device, client) {
printf('-> linux_client_set\n');
let ifbdev = ifb_dev(device.name);
let id = client.id + 3;
linux_client_del(device, client);
let ret = qdisc_add_leaf(device.name, id, `ceil ${client.data.rate_egress}`) &&
macfilter_add(device.name, id, "dst", client.address) &&
qdisc_add_leaf(ifbdev, id, `ceil ${client.data.rate_ingress}`) &&
macfilter_add(ifbdev, id, "src", client.address);
if (!ret)
linux_client_del(device, client);
return ret;
}
let ops = {
device: {
add: function(name) {
printf('-> device.add\n');
let ifbdev = ifb_dev(name);
qdisc_del(name);
ifb_del(name, ifbdev);
let ret = qdisc_add(name) &&
ifb_add(name, ifbdev) &&
qdisc_add(ifbdev);
if (!ret) {
qdisc_del(name);
ifb_del(name, ifbdev);
}
return ret;
},
remove: function(name) {
printf('-> device.remove\n');
let ifbdev = ifb_dev(name);
qdisc_del(name);
ifb_del(name, ifbdev);
}
},
client: {
set: function(device, client) {
printf('-> client.set\n');
return linux_client_set(device, client);
},
remove: function(device, client) {
printf('-> client.remove\n');
linux_client_del(device, client);
}
}
};
function get_device(devices, name) {
printf('-> get_device\n');
let device = devices[name];
if (device)
return device;
if (!ops.device.add(name))
return null;
device = {
name: name,
clients: {},
client_order: [],
};
devices[name] = device;
return device;
}
function del_device(name) {
printf('-> del_device\n');
if (!devices[name])
return;
ops.device.remove(name);
delete devices[name];
}
function get_free_idx(list) {
printf('-> get_free_idx\n');
for (let i = 0; i < length(list); i++)
if (list[i] == null)
return i;
return length(list);
}
function del_client(device, address) {
printf('-> del_client\n');
let client = device.clients[address];
if (!client)
return false;
delete device.clients[address];
device.client_order[client.id] = null;
ops.client.remove(device, client);
return true;
}
function get_client(device, address) {
printf('-> get_client\n');
let client = device.clients[address];
if (client)
return client;
let i = get_free_idx(device.client_order);
client = {};
client.address = address;
client.id = i;
client.data = {};
device.clients[address] = client;
device.client_order[i] = client;
return client;
}
function set_client(device, client, data) {
printf('-> set_client\n');
let update = false;
for (let key in data) {
if (client.data[key] != data[key])
update = true;
client.data[key] = data[key];
}
if (update && !ops.client.set(device, client)) {
del_client(device, client.address);
return false;
}
return true;
}
function run_service() {
let uctx = ubus.connect();
uctx.publish("ratelimit", {
flush: {
call: function(req) {
printf('-> flush\n');
defaults = {};
},
args: {
}
},
defaults_set: {
call: function(req) {
printf('-> defaults_set\n');
let r_i = req.args.rate_ingress ?? req.args.rate;
let r_e = req.args.rate_egress ?? req.args.rate;
let name = req.args.name;
if (!name || !r_i || !r_e)
return ubus.STATUS_INVALID_ARGUMENT;
defaults[name] = [ r_e, r_i ];
return 0;
},
args: {
name:"",
rate:"",
rate_ingress:"",
rate_egress:"",
}
},
client_set: {
call: function(req) {
printf('-> client_set\n');
let r_i = req.args.rate_ingress ?? req.args.rate;
let r_e = req.args.rate_egress ?? req.args.rate;
if (req.args.defaults && defaults[req.args.defaults]) {
let def = defaults[req.args.defaults];
r_e ??= def[0];
r_i ??= def[1];
}
if (!req.args.device || !req.args.address || !r_i || !r_e)
return ubus.STATUS_INVALID_ARGUMENT;
let device = get_device(devices, req.args.device);
if (!device)
return ubus.STATUS_INVALID_ARGUMENT;
let client = get_client(device, req.args.address);
if (!client)
return ubus.STATUS_INVALID_ARGUMENT;
let data = {
rate_ingress: r_i,
rate_egress: r_e
};
if (!set_client(device, client, data))
return ubus.STATUS_UNKNOWN_ERROR;
return 0;
},
args: {
device:"",
defaults:"",
address:"",
rate:"",
rate_ingress:"",
rate_egress:"",
}
},
client_delete: {
call: function(req) {
printf('-> client_delete\n');
if (!req.args.address)
return ubus.STATUS_INVALID_ARGUMENT;
if (req.args.device) {
let device = devices[req.args.device];
if (!device)
return ubus.STATUS_NOT_FOUND;
if (!del_client(device, req.args.address))
return ubus.STATUS_NOT_FOUND;
} else {
for (let dev in devices) {
let device = devices[dev];
del_client(device, req.args.address);
}
}
return 0;
},
args: {
device:"",
address:"",
}
},
device_delete: {
call: function(req) {
printf('-> device_delete\n');
let name = req.args.device;
if (!name)
return ubus.STATUS_INVALID_ARGUMENT;
if (!devices[name])
return ubus.STATUS_NOT_FOUND;
del_device(name);
return 0;
},
args: {
device:"",
}
},
reload: {
call: function(req) {
printf('-> reload\n');
let list = uctx.list();
for (let obj in list) {
if (!wildcard(obj, 'hostapd.wlan*') && !wildcard(obj, 'hostapd.phy*'))
continue;
let iface = split(obj, '.')[1];
let device = get_device(devices, req.args.device);
if (!device)
continue;
let status = uctx.call(obj, 'get_status');
if (!status?.ssid)
continue;
if (!defaults[status?.ssid])
continue;
let data = {
rate_ingress: defaults[status?.ssid][0],
rate_egress: defaults[status?.ssid][1]
};
for (let k, client in device.clients)
set_client(device, client, data);
}
return 0;
},
args: {
}
},
dump: {
call: function(req) {
return { devices, defaults };
},
args: {}
}
});
try {
uloop.run();
} catch (e) {
warn(`Error: ${e}\n${e.stacktrace[0].context}`);
}
for (let dev in devices) {
del_device(dev);
}
}
uloop.init();
run_service();
uloop.done();

View File

@@ -0,0 +1,27 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=rrmd
PKG_RELEASE:=1
PKG_LICENSE:=ISC
PKG_MAINTAINER:=John Crispin <john@phrozen.org>
include $(INCLUDE_DIR)/package.mk
define Package/rrmd
SECTION:=utils
CATEGORY:=Utilities
DEPENDS:=+ucrun
TITLE:=radio resource management
endef
define Build/Compile/Default
endef
Build/Compile = $(Build/Compile/Default)
define Package/rrmd/install
$(CP) ./files/* $(1)/
endef
$(eval $(call BuildPackage,rrmd))

Some files were not shown because too many files have changed in this diff Show More