From: Dirk Brenken Date: Thu, 6 Aug 2026 13:56:40 +0000 (+0200) Subject: travelmate: release 2.4.7-1 X-Git-Url: http://git.99rst.org/?a=commitdiff_plain;h=28da48b9cb2e0cbd6f904e08dc3aa42297c16e20;p=openwrt-packages.git travelmate: release 2.4.7-1 * harden the connectivity check in f_net() * drop the ineffective 'trm_netcheck' term from the minimum signal quality branch in f_check() * reset 'trm_connection' explicitly in the netcheck exit path * log the affected uplink in the netcheck messages * update and restructure the readme Signed-off-by: Dirk Brenken --- diff --git a/net/travelmate/Makefile b/net/travelmate/Makefile index 94769f3b4..a7db32390 100644 --- a/net/travelmate/Makefile +++ b/net/travelmate/Makefile @@ -6,8 +6,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=travelmate -PKG_VERSION:=2.4.6 -PKG_RELEASE:=3 +PKG_VERSION:=2.4.7 +PKG_RELEASE:=1 PKG_LICENSE:=GPL-3.0-or-later PKG_MAINTAINER:=Dirk Brenken diff --git a/net/travelmate/files/README.md b/net/travelmate/files/README.md index b8f735184..08505bb40 100644 --- a/net/travelmate/files/README.md +++ b/net/travelmate/files/README.md @@ -2,88 +2,139 @@ # Travelmate, a wlan connection manager for travel routers +## Table of Contents +* [Description](#description) +* [Quick Start](#quick-start) +* [Main Features](#main-features) +* [Prerequisites](#prerequisites) +* [Installation and Usage](#installation-and-usage) +* [Travelmate CLI interface](#travelmate-cli-interface) +* [Travelmate config options](#travelmate-config-options) +* [Examples](#examples) +* [Best practice and tweaks](#best-practice-and-tweaks) +* [Troubleshooting & debug options](#troubleshooting-and-debug-options) +* [Support](#support) +* [Removal](#removal) +* [Donations](#donations) + + ## Description -If you’re taking your laptop, tablet, or phone on -an upcoming vacation or business trip, you'll want -to connect with friends or complete work on the go. -But many hotels don’t have a secure wireless network setup or -limit you to using a single device at a time. +If you're taking your laptop, tablet, or phone on an upcoming vacation or business trip, you'll want to connect with friends or complete work on the go. But many hotels don't have a secure wireless network setup or limit you to using a single device at a time. -Travelmate lets you use a small "travel router" to connect -all of your devices at once while having total control over your own -personal wireless network. +Travelmate lets you use a small "travel router" to connect all of your devices at once while having total control over your own personal wireless network. -Travelmate runs on OpenWrt, and provides an "uplink" to the hotel's wireless access point/hotspot. -Travelmate then becomes the Access Point (AP) for you and your companions, -providing secure access to the internet. -See the [Installation and Usage](#installation-and-usage) section below. +Travelmate runs on OpenWrt and provides an "uplink" to the hotel's wireless access point/hotspot. Travelmate then becomes the Access Point (AP) for you and your companions, providing secure access to the internet. It manages all the network settings, firewall settings, connections to a hotel network, etc. and automatically (re)connects to configured APs/hotspots as they become available. -Travelmate manages all the network settings, firewall settings, -connections to a hotel network, etc. and -automatically (re)connnects to configured APs/hotspots as they become available. + +## Quick Start +For a typical setup these few steps are enough to get travelmate up and running — see the sections below for details: +1. Install the LuCI companion package: `apk update && apk add luci-app-travelmate` (this pulls in the `travelmate` backend as a dependency). +2. Open LuCI under `Services → Travelmate` and run the **Interface Wizard** once. It creates the uplink interface, the firewall assignment and the required network settings. +3. Switch to the **Wireless Stations** tab, scan the radio you want to use as uplink, click `Add Uplink...` for the desired SSID and enter its credentials. +4. Start and verify the service: -## Main Benefits and Features +```sh +/etc/init.d/travelmate start +/etc/init.d/travelmate status +``` -* Easy setup from LuCI web interface with **Interface Wizard** and **Wireless Station manager** +**Please note:** configure your AP on a different radio than the uplink whenever your router has more than one. Sharing a single radio between AP and uplink works, but costs a noticeable amount of throughput. + + +## Main Features +* Easy setup from the LuCI web interface with **Interface Wizard** and **Wireless Station manager** * Display a QR code to transfer the wireless credentials to your mobile devices * Fast uplink connections * Supports routers with multiple radios in any order -* Supports all kinds of uplinks, including hidden and enterprise uplinks (WEP-based uplinks are no longer supported). +* Supports all kinds of uplinks, including hidden and enterprise uplinks (WEP-based uplinks are no longer supported) * Continuously checks the existing uplink quality, e.g. for conditional uplink (dis)connections * Automatically add open uplinks to your wireless config, e.g. hotel captive portals * Captive portal detection with a 'heartbeat' function to keep the uplink connection up and running -* Captive portal hook for auto-login configured via uci/LuCI. Use an external script for captive portal auto-logins (see example below) +* Captive portal hook for auto-login configured via uci/LuCI, using an external script (see the examples below) * VPN hook supports 'wireguard' or 'openvpn' client setups to handle VPN (re)connections automatically -* Email hook via 'msmtp' sends notification e-mails after every successful uplink connect +* E-mail hook via 'msmtp' sends notification e-mails after every successful uplink connect * Proactively scan and switch to a higher priority uplink, replacing an existing connection -* Check router subnet vs. uplink subnet, to show conflicts with router LAN network +* Check router subnet vs. uplink subnet, to show conflicts with the router LAN network * (Optional) Generate a random unicast MAC address for each uplink connection * (Optional) Evil twin protection by skipping access points with locally-administered (LAA) BSSIDs * Configurable retry limit per uplink, with optional unlimited retry mode -* NTP time sync before sending emails +* NTP time sync before sending e-mails * procd init and ntp-hotplug support -* Runtime information available via LuCI & via 'status' init command +* Runtime information available via LuCI and via the 'status' init command * Log status and debug information to syslog -* STA interfaces operate in an "always off" mode, - to make sure that the AP is always accessible +* STA interfaces operate in an "always off" mode, to make sure that the AP is always accessible + ## Prerequisites * [OpenWrt](https://openwrt.org), tested/compatible with current stable and latest OpenWrt snapshot -* The `luci-app-travelmate` ensures these packages are present: +* The `luci-app-travelmate` package ensures these dependencies are present: * 'dnsmasq' as dns backend * 'ubus iwinfo' for wlan scanning - * 'curl' for connection checking and all kinds of captive portal magic, - e.g. cp detection and auto-logins - * a 'wpad' variant to support various WPA encrypted networks - (WEP-based uplinks are no longer supported!) + * 'curl' for connection checking and all kinds of captive portal magic, e.g. cp detection and auto-logins + * a 'wpad' variant to support various WPA encrypted networks (WEP-based uplinks are no longer supported!) * optional: 'wireguard' or 'openvpn' for vpn client connections -* optional: 'msmtp' to send out Travelmate related status messages via email +* optional: 'msmtp' to send out travelmate related status messages via e-mail + ## Installation and Usage -* Install OpenWrt on your router, and set it up to allow wireless connections. - Be sure to set a strong password on the wireless channel(s) so that only - you and your companions can use it. -* Decide which radio you'll use for the Travelmate uplink (radio0, radio1, etc): - * 2.4GHz allows a longer (more distant) link; 5GHz provides a faster link - * Travelmate works on all radios. - But for better performance, configure the AP on a separate radio from - the one you're planning to use as the uplink. -* Use LuCI web interface to install both **travelmate** and **luci-app-travelmate** -* Open the Travelmate LuCI application - **Services -> Travelmate** -* You must use the Travelmate **Interface Wizard** one time to - configure the uplink, firewall and other network settings -* Use the **Wireless Stations** tab to add an uplink station +* Install OpenWrt on your router and set it up to allow wireless connections. Be sure to set a strong password on the wireless channel(s) so that only you and your companions can use it +* Decide which radio you'll use for the travelmate uplink (radio0, radio1, etc.). 2.4GHz allows a longer (more distant) link, 5GHz provides a faster link. Travelmate works on all radios, but for better performance configure the AP on a separate radio from the one you're planning to use as the uplink +* Install both **travelmate** and **luci-app-travelmate**, the application is located in LuCI under the `Services` menu +* You must use the travelmate **Interface Wizard** one time to configure the uplink, firewall and other network settings +* Use the **Wireless Stations** tab to add an uplink station: * **Scan** the radio you chose for the uplink - * Click **Add Uplink...** for the desired SSID. - If there are multiples, choose the one with the largest _Strength_ - * You'll need to enter the credentials (password, etc) + * Click `Add Uplink...` for the desired SSID. If there are multiples, choose the one with the largest _Strength_ + * Enter the credentials (password, etc.) * You should be "on the air" - test by browsing the internet -* You may add additional uplinks (for different locations) - by repeating the previous step +* You may add additional uplinks for different locations by repeating the previous step * Happy traveling ... + +## Travelmate CLI interface +* All important travelmate functions are accessible via CLI, too. If you're going to configure travelmate via CLI, edit the config file `/etc/config/travelmate` and enable the service, see the options reference tables below. + +```sh +~# /etc/init.d/travelmate +Syntax: /etc/init.d/travelmate [command] + +Available commands: + start Start the service + stop Stop the service + restart Restart the service + reload Reload configuration files (or restart if service does not implement reload) + enable Enable service autostart + disable Disable service autostart + enabled Check if service is started on boot + scan [|] Scan for available nearby uplinks + setup [] [] [] Setup the travelmate uplink interface, by default 'trm_wwan' with firewall zone 'wan' and metric '100' + running Check if service is running + status Service status + trace Start with syscall trace + info Dump procd service info +``` + +The `setup` sub-command is the CLI equivalent of the LuCI **Interface Wizard** and only needs to be run once. The `scan` sub-command writes its result to `/var/run/travelmate/travelmate.scan`, sorted by signal quality, and is used by the **Wireless Stations** tab in LuCI as well. + +The `status` sub-command prints the current runtime information: + +```sh +~# /etc/init.d/travelmate status +::: travelmate runtime information + + travelmate_status : connected, net ok/100 + + frontend_ver : 2.4.7-1 + + backend_ver : 2.4.7-1 + + station_id : radio0/GlutenfreiVerbunden/- + + station_mac : 42:40:45:EC:B3:D1 + + station_interfaces : wwan, - + + station_subnet : 10.168.20.0 (lan: 10.200.1.0) + + run_flags : autoadd: ✘, captive: ✔, eviltwin: ✘, mail: ✔, netcheck: ✘, ntp: ✔, proactive: ✔, randomize: ✔, vpn: ✘ + + last_run : mode: start, date / time: 2026-08-06 09:08:24, memory: 412.35 MB available + + system_info : cores: 2, fetch: curl, Cudy TR3000 v1, mediatek/filogic, OpenWrt SNAPSHOT (r32287-1c7ec8ab19) +``` + + ## Travelmate config options -* usually the pre-configured Travelmate setup works quite well and no manual config overrides are needed, all listed options apply to the 'global' section: +* Usually the pre-configured travelmate setup works quite well and no manual config overrides are needed. All options listed below apply to the 'global' section: | Option | Default | Description/Valid Values | | :----------------- | :--------------------------------- | :---------------------------------------------------------------------------------------------------- | @@ -94,7 +145,7 @@ automatically (re)connnects to configured APs/hotspots as they become available. | trm_radio | -, not set | restrict travelmate to certain radio(s) | | trm_revradio | 0, disabled | change the radio processing order, e.g. 'radio1 radio0' | | trm_captive | 1, enabled | check the internet availability and handle captive portal redirections | -| trm_netcheck | 0, disabled | treat missing internet availability as an error | +| trm_netcheck | 0, disabled | treat missing internet availability as an error, see the note below | | trm_proactive | 0, disabled | proactively scan and switch to a higher prioritized uplink, despite of an already existing connection | | trm_autoadd | 0, disabled | automatically add open uplinks like hotel captive portals to your wireless config | | trm_ssidfilter | -, not set | list of SSID patterns for filtering/skipping specific open uplinks, e.g. 'Chromecast*' | @@ -117,9 +168,10 @@ automatically (re)connnects to configured APs/hotspots as they become available. | trm_vpn | 0, disabled | VPN connections will be managed by travelmate | | trm_stdvpnservice | -, not set | standard vpn service which will be automatically added to new STA profiles | | trm_stdvpniface | -, not set | standard vpn interface which will be automatically added to new STA profiles | - -* per uplink exist an additional 'uplink' section in the travelmate config, with the following options: +**Please note:** 'trm_netcheck' is evaluated independently of 'trm_captive'. A detected captive portal does not count as an error, such an uplink stays connected so that a manual or script based portal login is still possible. Only a failed connectivity check counts, and it is confirmed by a second probe before it is acted on. Such a failure is then handled like a failed connection attempt: after 'trm_maxretry' tries the affected uplink gets disabled in the travelmate config. A longer lasting outage of your upstream provider may therefore disable all configured uplinks, which have to be re-enabled manually. + +* Per uplink there is an additional 'uplink' section in the travelmate config, with the following options: | Option | Default | Description/Valid Values | | :----------------- | :--------------------------------- | :---------------------------------------------------------------------------------------------------- | @@ -134,26 +186,27 @@ automatically (re)connnects to configured APs/hotspots as they become available. | vpnservice | -, not set | reference the already configured 'wireguard' or 'openvpn' client instance as vpn provider | | vpniface | -, not set | the logical vpn interface, e.g. 'wg0' or 'tun0' | - -## VPN client setup + +## Examples +**VPN client setup** Please read one of the following guides to get a working vpn client setup on your travel router: * [Wireguard client setup guide](https://openwrt.org/docs/guide-user/services/vpn/wireguard/client) * [OpenVPN client setup guide](https://openwrt.org/docs/guide-user/services/vpn/openvpn/client-luci) -**Please note:** Make sure to uncheck the "Bring up on boot" option during vpn interface setup, so that netifd doesn't interfere with travelmate. -Also please prevent potential vpn protocol autostarts, e.g. add in newer openvpn uci configs an additional 'globals' section: -

+Make sure to uncheck the "Bring up on boot" option during vpn interface setup, so that netifd doesn't interfere with travelmate. Also prevent potential vpn protocol autostarts, e.g. add an additional 'globals' section in newer openvpn uci configs:
+
+```
 config globals 'globals'
         option autostart '0'
-
-Once your vpn client connection setup is correct, you can reference to that config in travelmate to handle VPN (re-) connections automatically. +``` + +Once your vpn client connection setup is correct, you can reference that config in travelmate to handle VPN (re-) connections automatically. -## E-Mail setup -To use E-Mail notifications you have to setup the package 'msmtp'. +**E-Mail setup** +To use e-mail notifications you have to set up the package 'msmtp'. Modify the file `/etc/msmtprc`, e.g. for gmail: -Modify the file '/etc/msmtprc', e.g. for gmail: -

+```
 [...]
 defaults
 auth            on
@@ -168,66 +221,84 @@ port            587
 from            xxx@gmail.com
 user            yyy
 password        zzz
-
+``` + +Finally enable e-mail support in travelmate and add a valid e-mail receiver address. -Finally enable E-Mail support in Travelmate and add a valid E-Mail receiver address. +**Captive portal auto-logins** +For automated captive portal logins you can reference an external shell script per uplink. All login scripts have to be executable and located in `/etc/travelmate` with the extension `.login`. The package ships multiple ready to run auto-login scripts: -## Captive Portal auto-logins -For automated captive portal logins you can reference an external shell script per uplink. All login scripts should be executable and located in '/etc/travelmate' with the extension '.login'. The package ships multiple ready to run auto-login scripts: - * 'wifibahn.login' for german DB railway hotspots - * 'telekom.login' for telekom hotspots (DE) - * 'vodafone.login' for vodafone hotspots (DE) - * 'generic-user-pass.login' a template to demonstrate the optional parameter handling in login scripts +* 'wifibahn.login' for german DB railway hotspots +* 'telekom.login' for telekom hotspots (DE) +* 'vodafone.login' for vodafone hotspots (DE) +* 'generic-user-pass.login' a template to demonstrate the optional parameter handling in login scripts A typical and successful captive portal login looks like this: -

+
+```
 [...]
-Thu Sep 10 13:30:16 2020 user.info trm-2.0.0[26222]: captive portal domain 'www.wifionice.de' added to to dhcp rebind whitelist
-Thu Sep 10 13:30:19 2020 user.info trm-2.0.0[26222]: captive portal login '/etc/travelmate/wifionice.login ' for 'www.wifionice.de' has been executed with rc '0'
-Thu Sep 10 13:30:19 2020 user.info trm-2.0.0[26222]: connected to uplink 'radio1/WIFIonICE/-' with mac 'B2:9D:F5:96:86:A4' (1/3)
+user.info trm-2.4.7-1[26222]: captive portal domain 'www.wifibahn.de' added to dhcp rebind allowlist
+user.info trm-2.4.7-1[26222]: captive portal login script for 'www.wifibahn.de' has been finished with rc '0'
+user.info trm-2.4.7-1[26222]: connected to uplink 'radio1/WIFI@DB/-' with mac 'B2:9D:F5:96:86:A4' (1/3)
 [...]
-
+``` Hopefully more scripts for different captive portals will be provided by the community! -## Runtime information + +## Best practice and tweaks +**Radio assignment** +If your router has more than one radio, keep the AP and the uplink on separate radios - sharing one radio between both costs a noticeable amount of throughput. Use `trm_radio` to restrict travelmate to the uplink radio(s) and `trm_revradio` to change the processing order, e.g. to prefer 5GHz over 2.4GHz. -Travelmate stores all runtime files (pid, scan results, status JSON, etc.) under `/var/run/travelmate/`. The runtime status is exposed both via the LuCI status panel and the init command: +**Signal quality thresholds** +`trm_minquality` (default 35%) decides twice: a scan result below the threshold is not considered as a connection candidate at all, and an existing connection that drops below it is torn down so that travelmate can look for something better. Raising the value makes travelmate switch away earlier, lowering it keeps weak uplinks alive longer. Values well above 50% tend to make travelmate restless in hotels. -

-root@2go:~# /etc/init.d/travelmate status
-::: travelmate runtime information
-  + travelmate_status  : connected, net ok/100
-  + frontend_ver       : 2.3.0-r1
-  + backend_ver        : 2.3.0-r1
-  + station_id         : radio0/GlutenfreiVerbunden/-
-  + station_mac        : 42:40:45:EC:B3:D1
-  + station_interfaces : wwan, -
-  + station_subnet     : 10.168.20.0 (lan: 10.200.1.0)
-  + run_flags          : captive: ✔, proactive: ✔, netcheck: ✘, autoadd: ✘, randomize: ✔, eviltwin: ✘
-  + ext_hooks          : ntp: ✔, vpn: ✘, mail: ✔
-  + last_run           : 2025.12.11-09:08:24
-  + system             : Cudy TR3000 v1, mediatek/filogic, OpenWrt SNAPSHOT (r32287-1c7ec8ab19)
-
+**Retry behaviour** +`trm_maxretry` (default 3) limits the connection attempts per uplink. When the limit is reached, the affected uplink is disabled in the travelmate config and has to be re-enabled manually - which is intentional for permanently broken credentials, but worth keeping in mind in combination with `trm_netcheck`. Set `trm_maxretry` to '0' for unlimited retries if you never want an uplink to be disabled automatically. + +**Open uplinks** +`trm_autoadd` adds open networks to your wireless config on the fly, which is handy in hotels and on trains. Keep `trm_maxautoadd` at a sane value so that a busy location doesn't flood your config, and use `trm_ssidfilter` to skip the usual noise, e.g. `Chromecast*` or printer and camera SSIDs. + +**Privacy and evil twin protection** +`trm_randomize` generates a new random unicast MAC address for each uplink connection, so a hotspot operator can't trivially recognize your router across visits. `trm_eviltwin` skips access points with a locally administered (LAA) BSSID, which is a cheap indicator for a spoofed access point - note that some legit setups (mesh, repeaters) use LAA BSSIDs as well. + +**Timing** +`trm_maxwait` (default 30s) is the budget for a single connection attempt and also scales the internal curl timeouts. On slow uplinks or with captive portals that take their time, raising it to 45-60s helps, lowering it makes travelmate give up on weak candidates faster. `trm_triggerdelay` adds a delay before processing starts after an ifup event, which is useful if your uplink needs a moment to settle. + +**Subnet conflicts** +Travelmate compares the uplink subnet against your router LAN network and logs a warning if both overlap. If you run into this frequently, change your LAN network to something uncommon, e.g. `10.200.1.0/24`, because most hotel networks use `192.168.0.0/24` or `192.168.1.0/24`. + + +## Troubleshooting & debug options +Travelmate provides an optional debug mode that writes detailed diagnostic information about every processing step to the system log. Under normal conditions only the relevant status messages are logged, to keep regular runs clean and silent. To enable debug mode, set the option `trm_debug` to `1`. + +Whenever you encounter travelmate related processing problems, please enable `trm_debug`, restart travelmate and check the `Log View` tab in LuCI (or the syslog via `logread -e "trm-"`). -To debug travelmate runtime problems, please always enable the 'trm\_debug' flag, restart Travelmate and check the system log afterwards (_logread -e "trm-"_) +Typical symptoms: +* No uplink is found although it is in range: compare the reported signal quality in the debug log against `trm_minquality`, and make sure the uplink is still enabled in the travelmate config - the retry limit may have disabled it +* An uplink connects but has no internet: the debug output of `f_net` shows the probe host, the effective url and the resulting state (`net ok`, `net cp ''` or `net nok`). A `net cp` state means a captive portal was detected and is waiting for a login +* A captive portal login script doesn't run: it has to be executable and located in `/etc/travelmate` with the extension `.login`, everything else is rejected for security reasons +* The connection drops right after connecting: check for a subnet conflict warning between the uplink network and your router LAN network + ## Support -Please join the Travelmate discussion in this [forum thread](https://forum.openwrt.org/t/travelmate-support-thread/5155) or contact me by [mail](mailto:dev@brenken.org) +Please join the travelmate discussion in this [forum thread](https://forum.openwrt.org/t/travelmate-support-thread/5155) or contact me by mail +If you want to report an error, please describe it in as much detail as possible - with (debug) logs, the current travelmate status and your travelmate configuration. + ## Removal -* stop the Travelmate daemon with _/etc/init.d/travelmate stop_ -* remove the Travelmate package (_opkg remove luci-app-travelmate_, _opkg remove travelmate_) +Stop the travelmate daemon with _/etc/init.d/travelmate stop_ and remove the travelmate packages if necessary. + ## Donations -You like this project - is there a way to donate? Generally speaking "No" - I have a well-paying full-time job and my OpenWrt projects are just a hobby of mine in my spare time. +You like this project - is there a way to donate? Generally speaking "No" - I have a well-paying full-time job and my OpenWrt projects are just a hobby of mine in my spare time. -If you still insist to donate some bucks ... +If you still insist to donate some bucks ... * I would be happy if you put your money in kind into other, social projects in your area, e.g. a children's hospice * Let's meet and invite me for a coffee if you are in my area, the “Markgräfler Land” in southern Germany or in Switzerland (Basel) * Send your money to my [PayPal account](https://www.paypal.me/DirkBrenken) and I will collect your donations over the year to support various social projects in my area -No matter what you decide - thank you very much for your support! +No matter what you decide - thank you very much for your support! Have fun! -Dirk +Dirk diff --git a/net/travelmate/files/travelmate-functions.sh b/net/travelmate/files/travelmate-functions.sh index ce7d3d03f..d5d78f909 100644 --- a/net/travelmate/files/travelmate-functions.sh +++ b/net/travelmate/files/travelmate-functions.sh @@ -51,6 +51,8 @@ trm_tmpfile="${trm_rundir}/travelmate.tmp" trm_rtfile="${trm_rundir}/travelmate.runtime.json" trm_captiveurl="http://detectportal.firefox.com" trm_useragent="Mozilla/5.0 (X11; Linux x86_64; rv:144.0) Gecko/20100101 Firefox/144.0" +trm_action="${1}" +trm_runmode="" # ensure runtime directory exists # @@ -59,6 +61,8 @@ trm_useragent="Mozilla/5.0 (X11; Linux x86_64; rv:144.0) Gecko/20100101 Firefox/ # gather system information # f_system() { + # query package list for travelmate frontend/backend versions, and system model/release info + # trm_packages="$("${trm_ubuscmd}" -S call rpc-sys packagelist '{ "all": true }' 2>/dev/null)" trm_fver="$(printf "%s" "${trm_packages}" | "${trm_jsoncmd}" -ql1 -e '@.packages["luci-app-travelmate"]')" trm_bver="$(printf "%s" "${trm_packages}" | "${trm_jsoncmd}" -ql1 -e '@.packages.travelmate')" @@ -66,6 +70,13 @@ f_system() { "${trm_jsoncmd}" -ql1 -e '@.model' -e '@.release.target' -e '@.release.distribution' -e '@.release.version' -e '@.release.revision' | "${trm_awkcmd}" 'BEGIN{RS="";FS="\n"}{printf "%s, %s, %s %s (%s)",$1,$2,$3,$4,$5}')" + # detect cpu cores + # + [ -z "${trm_cores}" ] && trm_cores="$("${trm_grepcmd}" -cm16 '^processor' /proc/cpuinfo 2>/dev/null)" + case "${trm_cores}" in "" | 0 | *[!0-9]*) trm_cores="1" ;; esac + + # trigger ntp sync if no lock file exists (to avoid multiple concurrent calls) + # if [ ! -d "${trm_ntplock}" ]; then "${trm_ubuscmd}" -S call hotplug.ntp call '{ "env": [ "ACTION=stratum" ] }' >/dev/null 2>&1 fi @@ -94,6 +105,20 @@ f_cmd() { fi } +# determine available system memory (MemAvailable) in MB +# mode "float" returns MiB with two decimals, default is integer MiB +# +f_mem() { + local mem mode="${1}" + + if [ "${mode}" = "float" ]; then + mem="$("${trm_awkcmd}" '/^MemAvailable/{printf "%.2f", $2/1024}' "/proc/meminfo" 2>/dev/null)" + else + mem="$("${trm_awkcmd}" '/^MemAvailable/{printf "%s", int($2/1024)}' "/proc/meminfo" 2>/dev/null)" + fi + printf '%s' "${mem:-"0"}" +} + # load travelmate config # f_conf() { @@ -132,6 +157,10 @@ f_conf() { } config_load travelmate + # remember the initial run mode, the service script clears 'trm_action' after its first cycle + # + [ -n "${trm_action}" ] && trm_runmode="${trm_action}" + # early exit on stop action, otherwise run runtime sanity checks # if [ "${trm_action}" = "stop" ]; then @@ -173,7 +202,7 @@ f_conf() { # build curl fetch parameters, bind to uplink device if known # - trm_fetchparm="--silent --show-error --location --fail --referer http://www.example.com --retry $((trm_maxwait / 6)) --retry-delay $((trm_maxwait / 6)) --max-time $((trm_maxwait / 6))" + trm_fetchparm="--silent --show-error --location --fail --referer http://www.example.com --retry 2 --retry-delay $((trm_maxwait / 6)) --max-time $((trm_maxwait / 6))" device="$("${trm_ifstatuscmd}" "${trm_iface}" | "${trm_jsoncmd}" -ql1 -e '@.device')" [ -n "${device}" ] && trm_fetchparm="${trm_fetchparm} --interface ${device}" @@ -779,18 +808,28 @@ f_addsta() { # check net status # f_net() { - local parse err_msg raw json_raw html_raw html_cp js_cp json_ec json_rc json_cp json_cp_url json_ed result="net nok" + local parse err_msg raw marker probe_host json_raw html_raw html_cp js_cp json_ec json_rc json_cp json_cp_url json_ed result="net nok" - # fetch captive-detection url, curl appends '%{json}' metadata after the response body + # host of the configured probe url, used to spot foreign redirect targets # - raw="$("${trm_fetchcmd}" ${trm_fetchparm} --user-agent "${trm_useragent}" --header "Cache-Control: no-cache, no-store, must-revalidate, max-age=0" --write-out "%{json}" "${trm_captiveurl}")" - json_raw="${raw#*\{}" - html_raw="${raw%%\{*}" + probe_host="${trm_captiveurl#*://}" + probe_host="${probe_host%%/*}" + probe_host="$(printf "%s" "${probe_host}" | "${trm_awkcmd}" '{printf "%s",tolower($0)}')" - # parse curl metadata: exit code, http response code, final redirect target + # fetch captive-detection url, curl appends '%{json}' metadata behind a unique + # marker - splitting on the first curly brace would break on any response body + # that contains one, e.g. inline css/js of a captive portal login page + # + marker="#trm-meta#" + raw="$("${trm_fetchcmd}" ${trm_fetchparm} --user-agent "${trm_useragent}" --header "Cache-Control: no-cache, no-store, must-revalidate, max-age=0" --write-out "\n${marker}%{json}" "${trm_captiveurl}")" + json_raw="${raw##*${marker}}" + html_raw="${raw%${marker}*}" + + # parse curl metadata: exit code, http response code, effective url. Note that + # 'redirect_url' stays empty as long as curl follows redirects on its own # if [ -n "${json_raw}" ]; then - parse="$(printf "%s" "{${json_raw}" | "${trm_jsoncmd}" -e '@.exitcode' -e '@.response_code' -e '@.redirect_url')" + parse="$(printf "%s" "${json_raw}" | "${trm_jsoncmd}" -e '@.exitcode' -e '@.response_code' -e '@.url_effective')" { IFS= read -r json_ec IFS= read -r json_rc @@ -799,17 +838,17 @@ f_net() { ${parse} EOF - # extract lowercased host portion of the redirect url + # extract lowercased host portion of the effective url # json_cp="$(printf "%s" "${json_cp_url}" | "${trm_awkcmd}" 'BEGIN{FS="/"}{printf "%s",tolower($3)}')" if [ "${json_ec}" = "0" ]; then - # http redirect present: captive portal at redirect host + # request ended up on a foreign host: captive portal at that host # - if [ -n "${json_cp}" ]; then + if [ -n "${json_cp}" ] && [ "${json_cp}" != "${probe_host}" ]; then result="net cp '${json_cp}'" - # no http redirect: scan body for meta-refresh / js location.href redirects + # probe host answered: scan body for meta-refresh / js location.href redirects # else if [ "${json_rc}" = "200" ] || [ "${json_rc}" = "204" ]; then @@ -828,10 +867,10 @@ f_net() { # curl error path: extract errormsg and any trailing domain token # else - err_msg="$(printf "%s" "{${json_raw}" | "${trm_jsoncmd}" -ql1 -e '@.errormsg')" - json_ed="$(printf "%s" "{${err_msg}" | "${trm_awkcmd}" '/([[:alnum:]_-]{1,63}\.)+[[:alpha:]]+$/{printf "%s",tolower($NF)}')" + err_msg="$(printf "%s" "${json_raw}" | "${trm_jsoncmd}" -ql1 -e '@.errormsg')" + json_ed="$(printf "%s" "${err_msg}" | "${trm_awkcmd}" '/([[:alnum:]_-]{1,63}\.)+[[:alpha:]]+$/{printf "%s",tolower($NF)}')" if [ "${json_ec}" = "6" ]; then - if [ -n "${json_ed}" ] && [ "${json_ed}" != "${trm_captiveurl#http*://*}" ]; then + if [ -n "${json_ed}" ] && [ "${json_ed}" != "${probe_host}" ]; then result="net cp '${json_ed}'" fi fi @@ -839,13 +878,13 @@ f_net() { fi printf "%s" "${result}" - f_log "debug" "f_net ::: timeout: $((trm_maxwait / 6)), cp (json/html/js): ${json_cp:-"-"}/${html_cp:-"-"}/${js_cp:-"-"}, result: ${result}, error (rc/msg): ${json_ec}/${err_msg:-"-"}, url: ${trm_captiveurl}" + f_log "debug" "f_net ::: timeout: $((trm_maxwait / 6)), cp (url/html/js): ${json_cp:-"-"}/${html_cp:-"-"}/${js_cp:-"-"}, result: ${result}, error (rc/msg): ${json_ec}/${err_msg:-"-"}, probe_host: ${probe_host:-"-"}, eff_url: ${json_cp_url:-"-"}" } # check interface status # f_check() { - local rc raw ifname dev_status result login_script login_script_args cp_domain station_id ifquality + local rc raw ifname dev_status result login_script login_script_args cp_domain station_id ifquality sta_id local wait_time="0" enabled="1" mode="${1}" status="${2}" sta_radio="${3}" sta_essid="${4}" sta_bssid="${5}" # parse station id from runtime json (initial/dev mode only) @@ -859,6 +898,7 @@ f_check() { sta_bssid="${sta_bssid//-/}" fi f_getcfg "${sta_radio}" "${sta_essid}" "${sta_bssid}" + sta_id="${sta_radio:-"-"}/${sta_essid:-"-"}/${sta_bssid:-"-"}" # resolve uplink 'enabled' flag (skip for rev mode and unset stations) # @@ -929,9 +969,9 @@ f_check() { trm_ifstatus="$("${trm_ifstatuscmd}" "${trm_iface}" | "${trm_jsoncmd}" -ql1 -e '@.up')" if { [ -n "${trm_connection}" ] && [ "${trm_ifstatus}" = "false" ]; } || [ "${wait_time}" -eq "${trm_maxwait}" ]; then if [ -n "${trm_connection}" ] && [ "${trm_ifstatus}" = "false" ]; then - f_log "info" "no signal from uplink" + f_log "info" "no signal from uplink '${sta_id}'" else - f_log "info" "uplink connection could not be established after ${trm_maxwait} seconds" + f_log "info" "uplink connection could not be established after ${trm_maxwait} seconds '${sta_id}'" fi f_vpn "disable" trm_connection="" @@ -986,7 +1026,7 @@ f_check() { exec "${login_script}" ${login_script_args} >/dev/null 2>&1 ) rc="${?}" - f_log "info" "captive portal login script for '${cp_domain}' has been finished with rc '${rc}'" + f_log "info" "captive portal login script for '${cp_domain}' has been finished with rc '${rc}'" if [ "${rc}" = "0" ]; then result="$(f_net)" fi @@ -994,12 +1034,20 @@ f_check() { fi fi - # no internet: tear down vpn, exit early if netcheck enabled + # no internet: re-check once before netcheck acts on it, a single + # failed probe must not disable an otherwise working uplink + # + if [ "${result}" = "net nok" ] && [ "${trm_netcheck}" = "1" ]; then + result="$(f_net)" + fi + + # still no internet: tear down vpn, exit early if netcheck enabled # if [ "${result}" = "net nok" ]; then f_vpn "disable" if [ "${trm_netcheck}" = "1" ]; then - f_log "info" "uplink has no internet" + f_log "info" "uplink has no internet '${sta_id}'" + trm_connection="" trm_ifstatus="${status}" f_genstatus break @@ -1015,8 +1063,8 @@ f_check() { # signal below minquality on existing link: drop and exit # - elif [ -n "${trm_connection}" ] && { [ "${trm_netcheck}" = "1" ] || [ "${mode}" = "initial" ]; }; then - f_log "info" "uplink is out of range (${ifquality}/${trm_minquality})" + elif [ -n "${trm_connection}" ] && [ "${mode}" = "initial" ]; then + f_log "info" "uplink is out of range '${sta_id}' (${ifquality}/${trm_minquality})" f_vpn "disable" trm_connection="" trm_ifstatus="${status}" @@ -1035,7 +1083,7 @@ f_check() { # sta interface vanished while connected # elif [ -n "${trm_connection}" ]; then - f_log "info" "uplink connection lost (interface gone)" + f_log "info" "uplink connection lost '${sta_id}' (interface gone)" f_vpn "disable" trm_connection="" trm_ifstatus="${status}" @@ -1056,7 +1104,7 @@ f_check() { # if [ "${mode}" = "initial" ]; then if [ -n "${trm_connection}" ]; then - f_log "info" "uplink connection lost (interface down)" + f_log "info" "uplink connection lost '${sta_id}' (interface down)" f_vpn "disable" trm_connection="" fi @@ -1066,7 +1114,7 @@ f_check() { fi done - f_log "debug" "f_check ::: mode: ${mode}, name: ${ifname:-"-"}, status: ${trm_ifstatus}, enabled: ${enabled}, connection: ${trm_connection:-"-"}, wait: ${wait_time}, max_wait: ${trm_maxwait}, min_quality/quality: ${trm_minquality}/${ifquality:-"-"}, captive: ${trm_captive}, netcheck: ${trm_netcheck}" + f_log "debug" "f_check ::: mode: ${mode}, sta_id: ${sta_id}, name: ${ifname:-"-"}, status: ${trm_ifstatus}, enabled: ${enabled}, connection: ${trm_connection:-"-"}, wait: ${wait_time}, max_wait: ${trm_maxwait}, min_quality/quality: ${trm_minquality}/${ifquality:-"-"}, captive: ${trm_captive}, netcheck: ${trm_netcheck}" } # get status information @@ -1091,12 +1139,12 @@ f_getstatus() { # generate status information # f_genstatus() { - local sta_json temp_ns s_captive s_proactive s_netcheck s_autoadd s_randomize s_eviltwin s_ntp s_vpn s_mail vpn vpn_iface - local section last_date sta_iface sta_radio sta_essid sta_bssid sta_mac dev_status status="${trm_ifstatus}" ntp_done="0" vpn_done="0" mail_done="0" + local sta_json temp_ns s_captive s_proactive s_netcheck s_autoadd s_randomize s_eviltwin s_ntp s_vpn s_mail vpn vpn_iface free_mem runtime + local section ts sta_iface sta_radio sta_essid sta_bssid sta_mac dev_status status ntp_done="0" vpn_done="0" mail_done="0" # get current connection information # - if [ "${status}" = "true" ]; then + if [ "${trm_ifstatus}" = "true" ]; then status="connected, ${trm_connection:-"-"}" dev_status="$("${trm_ubuscmd}" -S call network.wireless status 2>/dev/null)" sta_json="$(printf "%s" "${dev_status}" | "${trm_jsoncmd}" -ql1 -e '@.*.interfaces[@.config.mode="sta"]')" @@ -1120,25 +1168,19 @@ f_genstatus() { sta_radio="$(uci_get "wireless" "${section}" "device")" f_getcfg "${sta_radio}" "${sta_essid}" "${sta_bssid}" fi - json_get_var last_date "last_run" vpn="$(f_getval "vpn")" if [ "${trm_vpn}" = "1" ] && [ -n "${trm_vpninfolist}" ] && [ "${vpn}" = "1" ] && [ -f "${trm_vpnfile}" ]; then vpn_iface="$(f_getval "vpniface")" vpn_done="1" fi - elif [ "${status}" = "error" ]; then + elif [ "${trm_ifstatus}" = "error" ]; then trm_connection="" status="program error" else trm_connection="" status="processing" - fi - - # fallback for missing last_run value - # - if [ -z "${last_date}" ]; then - last_date="$(date "+%Y.%m.%d-%H:%M:%S")" + runtime="-" fi # check for presence of ntp lock file and mail notification conditions @@ -1162,6 +1204,14 @@ f_genstatus() { case "${vpn_done}" in "1") s_vpn="✔" ;; *) s_vpn="✘" ;; esac case "${mail_done}" in "1") s_mail="✔" ;; *) s_mail="✘" ;; esac + # compose runtime string for status file + # + if [ "${trm_ifstatus}" = "true" ] || [ "${trm_ifstatus}" = "error" ]; then + free_mem="$(f_mem float)" + ts="$(date "+%Y-%m-%d %H:%M:%S")" + runtime="mode: ${trm_runmode:-"n/a"}, date / time: ${ts}, memory: ${free_mem:-0} MB available" + fi + # generate runtime status file # f_subnet @@ -1172,10 +1222,9 @@ f_genstatus() { json_add_string "station_mac" "${sta_mac:-"-"}" json_add_string "station_interfaces" "${sta_iface:-"-"}, ${vpn_iface:-"-"}" json_add_string "station_subnet" "${trm_subnet:-"-"}" - json_add_string "run_flags" "captive: ${s_captive}, proactive: ${s_proactive}, netcheck: ${s_netcheck}, autoadd: ${s_autoadd}, randomize: ${s_randomize}, eviltwin: ${s_eviltwin}" - json_add_string "ext_hooks" "ntp: ${s_ntp}, vpn: ${s_vpn}, mail: ${s_mail}" - json_add_string "last_run" "${last_date}" - json_add_string "system" "${trm_sysver}" + json_add_string "run_flags" "autoadd: ${s_autoadd}, captive: ${s_captive}, eviltwin: ${s_eviltwin}, mail: ${s_mail}, netcheck: ${s_netcheck}, ntp: ${s_ntp}, proactive: ${s_proactive}, randomize: ${s_randomize}, vpn: ${s_vpn}" + json_add_string "last_run" "${runtime:-"-"}" + json_add_string "system_info" "cores: ${trm_cores}, fetch: ${trm_fetchcmd##*/}, ${trm_sysver}" json_dump >"${trm_rtfile}" # send mail notification if enabled and conditions are met @@ -1189,7 +1238,7 @@ f_genstatus() { fi fi - f_log "debug" "f_genstatus ::: section: ${section:-"-"}, status: ${status:-"-"}, sta_iface: ${sta_iface:-"-"}, sta_radio: ${sta_radio:-"-"}, sta_essid: ${sta_essid:-"-"}, sta_bssid: ${sta_bssid:-"-"}, ntp: ${ntp_done}, vpn: ${vpn:-"0"}/${vpn_done}, mail: ${trm_mail}/${mail_done}" + f_log "debug" "f_genstatus ::: section: ${section:-"-"}, status: ${trm_ifstatus:-"-"}, sta_iface: ${sta_iface:-"-"}, sta_radio: ${sta_radio:-"-"}, sta_essid: ${sta_essid:-"-"}, sta_bssid: ${sta_bssid:-"-"}, ntp: ${ntp_done}, vpn: ${vpn:-"0"}/${vpn_done}, mail: ${trm_mail}/${mail_done}" } # send status mail @@ -1228,7 +1277,7 @@ f_log() { fi if [ "${class}" = "err" ] || [ "${class}" = "emerg" ]; then trm_ifstatus="error" - [ -s "${trm_rtfile}" ] && f_genstatus + [ -s "${trm_rtfile}" ] && [ -n "${trm_bver}" ] && f_genstatus : >"${trm_pidfile}" exit 1 fi @@ -1529,6 +1578,7 @@ f_main() { trm_catcmd="$(f_cmd cat)" trm_awkcmd="$(f_cmd gawk awk)" trm_sortcmd="$(f_cmd sort)" +trm_grepcmd="$(f_cmd grep)" trm_pgrepcmd="$(f_cmd pgrep)" trm_killcmd="$(f_cmd kill)" trm_jsoncmd="$(f_cmd jsonfilter)"