mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-31 13:03:02 +02:00
@@ -13,6 +13,13 @@ import sys
|
|||||||
import json
|
import json
|
||||||
import subprocess
|
import subprocess
|
||||||
import re
|
import re
|
||||||
|
def decode_iw_ssid(ssid):
|
||||||
|
"""Decode iw escaped SSID (\\xHH) to UTF-8, stripping non-printable chars."""
|
||||||
|
try:
|
||||||
|
ssid = ssid.encode().decode('unicode_escape').encode('latin-1').decode('utf-8')
|
||||||
|
except (UnicodeDecodeError, UnicodeEncodeError):
|
||||||
|
return ssid
|
||||||
|
return ''.join(c for c in ssid if c.isprintable())
|
||||||
|
|
||||||
|
|
||||||
def run_iw(*args):
|
def run_iw(*args):
|
||||||
@@ -260,7 +267,7 @@ def parse_interface_info(ifname):
|
|||||||
|
|
||||||
# SSID
|
# SSID
|
||||||
elif stripped.startswith('ssid '):
|
elif stripped.startswith('ssid '):
|
||||||
result['ssid'] = ' '.join(stripped.split()[1:])
|
result['ssid'] = decode_iw_ssid(' '.join(stripped.split()[1:]))
|
||||||
|
|
||||||
# Channel/frequency
|
# Channel/frequency
|
||||||
elif stripped.startswith('channel '):
|
elif stripped.startswith('channel '):
|
||||||
@@ -322,7 +329,7 @@ def parse_stations(ifname):
|
|||||||
try:
|
try:
|
||||||
if key == 'signal':
|
if key == 'signal':
|
||||||
# Format: "-42 dBm" or "-42 [-44, -45] dBm"
|
# Format: "-42 dBm" or "-42 [-44, -45] dBm"
|
||||||
current['rssi'] = int(value.split()[0])
|
current['signal-strength'] = int(value.split()[0])
|
||||||
elif key == 'connected time':
|
elif key == 'connected time':
|
||||||
# Format: "123 seconds"
|
# Format: "123 seconds"
|
||||||
current['connected-time'] = int(value.split()[0])
|
current['connected-time'] = int(value.split()[0])
|
||||||
@@ -488,7 +495,7 @@ def parse_link(ifname):
|
|||||||
|
|
||||||
# SSID: NetworkName
|
# SSID: NetworkName
|
||||||
elif stripped.startswith('SSID: '):
|
elif stripped.startswith('SSID: '):
|
||||||
result['ssid'] = stripped[6:]
|
result['ssid'] = decode_iw_ssid(stripped[6:])
|
||||||
|
|
||||||
# freq: 5180
|
# freq: 5180
|
||||||
elif stripped.startswith('freq: '):
|
elif stripped.startswith('freq: '):
|
||||||
@@ -500,7 +507,7 @@ def parse_link(ifname):
|
|||||||
# signal: -42 dBm
|
# signal: -42 dBm
|
||||||
elif stripped.startswith('signal: '):
|
elif stripped.startswith('signal: '):
|
||||||
try:
|
try:
|
||||||
result['rssi'] = int(stripped.split()[1])
|
result['signal-strength'] = int(stripped.split()[1])
|
||||||
except (ValueError, IndexError):
|
except (ValueError, IndexError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -582,7 +589,7 @@ def main():
|
|||||||
else:
|
else:
|
||||||
data = {'error': f'Unknown command: {command}'}
|
data = {'error': f'Unknown command: {command}'}
|
||||||
|
|
||||||
print(json.dumps(data, indent=2))
|
print(json.dumps(data, indent=2, ensure_ascii=False))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(json.dumps({'error': str(e)}))
|
print(json.dumps({'error': str(e)}))
|
||||||
|
|||||||
+2
-2
@@ -238,8 +238,8 @@ CoffeeShop 00:1a:2b:3c:4d:5e Open bad 1
|
|||||||
</code></pre>
|
</code></pre>
|
||||||
|
|
||||||
In the CLI, signal strength is reported as: excellent, good, fair or bad.
|
In the CLI, signal strength is reported as: excellent, good, fair or bad.
|
||||||
For precise RSSI values in dBm, use NETCONF or RESTCONF to access the
|
For precise signal strength values in dBm, use NETCONF or RESTCONF to access
|
||||||
operational datastore directly.
|
the `signal-strength` leaf in the operational datastore.
|
||||||
|
|
||||||
## Station Mode (Client)
|
## Station Mode (Client)
|
||||||
|
|
||||||
|
|||||||
+9
-12
@@ -14,18 +14,15 @@
|
|||||||
|
|
||||||
#define WPA_SUPPLICANT_CONF "/etc/wpa_supplicant-%s.conf"
|
#define WPA_SUPPLICANT_CONF "/etc/wpa_supplicant-%s.conf"
|
||||||
|
|
||||||
/*
|
|
||||||
* Determine WiFi mode from YANG configuration
|
|
||||||
*/
|
|
||||||
typedef enum wifi_mode_t {
|
|
||||||
wifi_station,
|
|
||||||
wifi_ap,
|
|
||||||
wifi_unknown
|
|
||||||
} wifi_mode_t;
|
|
||||||
|
|
||||||
static wifi_mode_t wifi_get_mode(struct lyd_node *wifi)
|
wifi_mode_t wifi_get_mode(struct lyd_node *iface)
|
||||||
{
|
{
|
||||||
struct lyd_node *ap;
|
struct lyd_node *ap, *wifi;
|
||||||
|
|
||||||
|
wifi = lydx_get_child(iface, "wifi");
|
||||||
|
if (!wifi)
|
||||||
|
return wifi_unknown;
|
||||||
|
|
||||||
|
|
||||||
ap = lydx_get_child(wifi, "access-point");
|
ap = lydx_get_child(wifi, "access-point");
|
||||||
if (ap) {
|
if (ap) {
|
||||||
@@ -60,7 +57,7 @@ int wifi_mode_changed(struct lyd_node *wifi)
|
|||||||
/*
|
/*
|
||||||
* Generate wpa_supplicant config for station mode
|
* Generate wpa_supplicant config for station mode
|
||||||
*/
|
*/
|
||||||
static int wifi_gen_station(struct lyd_node *cif)
|
int wifi_gen_station(struct lyd_node *cif)
|
||||||
{
|
{
|
||||||
const char *ifname, *ssid, *secret_name, *secret, *security_mode, *radio;
|
const char *ifname, *ssid, *secret_name, *secret, *security_mode, *radio;
|
||||||
struct lyd_node *security, *secret_node, *radio_node, *station, *wifi;
|
struct lyd_node *security, *secret_node, *radio_node, *station, *wifi;
|
||||||
@@ -213,7 +210,7 @@ int wifi_add_iface(struct lyd_node *cif, struct dagger *net)
|
|||||||
return SR_ERR_INTERNAL;
|
return SR_ERR_INTERNAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
mode = wifi_get_mode(wifi);
|
mode = wifi_get_mode(cif);
|
||||||
probe_timeout = wifi_get_probe_timeout(net->session, radio);
|
probe_timeout = wifi_get_probe_timeout(net->session, radio);
|
||||||
|
|
||||||
fprintf(iw, "# Generated by Infix confd - WiFi Interface Creation\n");
|
fprintf(iw, "# Generated by Infix confd - WiFi Interface Creation\n");
|
||||||
|
|||||||
@@ -452,9 +452,8 @@ static int netdag_gen_afspec_set(sr_session_ctx_t *session, struct dagger *net,
|
|||||||
case IFT_ETH:
|
case IFT_ETH:
|
||||||
return netdag_gen_ethtool(net, cif, dif);
|
return netdag_gen_ethtool(net, cif, dif);
|
||||||
case IFT_WIFI:
|
case IFT_WIFI:
|
||||||
/* WiFi daemon config (hostapd/wpa_supplicant) is handled by
|
if (wifi_get_mode(cif) == wifi_station)
|
||||||
* hardware.c when the radio (phy) is configured. Interface
|
return wifi_gen_station(cif);
|
||||||
* creation/deletion is handled in netdag_gen_afspec_add(). */
|
|
||||||
return 0;
|
return 0;
|
||||||
case IFT_DUMMY:
|
case IFT_DUMMY:
|
||||||
case IFT_GRE:
|
case IFT_GRE:
|
||||||
|
|||||||
@@ -124,9 +124,17 @@ int bridge_mcd_gen(struct lyd_node *cifs);
|
|||||||
int bridge_port_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip);
|
int bridge_port_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip);
|
||||||
|
|
||||||
/* if-wifi.c */
|
/* if-wifi.c */
|
||||||
|
typedef enum wifi_mode_t {
|
||||||
|
wifi_station,
|
||||||
|
wifi_ap,
|
||||||
|
wifi_unknown
|
||||||
|
} wifi_mode_t;
|
||||||
|
|
||||||
int wifi_add_iface(struct lyd_node *cif, struct dagger *net);
|
int wifi_add_iface(struct lyd_node *cif, struct dagger *net);
|
||||||
int wifi_del_iface(struct lyd_node *dif, struct dagger *net);
|
int wifi_del_iface(struct lyd_node *dif, struct dagger *net);
|
||||||
int wifi_mode_changed(struct lyd_node *wifi);
|
int wifi_mode_changed(struct lyd_node *wifi);
|
||||||
|
int wifi_gen_station(struct lyd_node *cif);
|
||||||
|
wifi_mode_t wifi_get_mode(struct lyd_node *wifi);
|
||||||
|
|
||||||
/* if-gre.c */
|
/* if-gre.c */
|
||||||
int gre_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip);
|
int gre_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip);
|
||||||
|
|||||||
@@ -100,6 +100,9 @@ submodule infix-if-wifi {
|
|||||||
must "count(/if:interfaces/if:interface[wifi/radio = current()][not(wifi/access-point)]) <= 1" {
|
must "count(/if:interfaces/if:interface[wifi/radio = current()][not(wifi/access-point)]) <= 1" {
|
||||||
error-message "Only one station or scan interface is allowed per radio";
|
error-message "Only one station or scan interface is allowed per radio";
|
||||||
}
|
}
|
||||||
|
must "count(/if:interfaces/if:interface[wifi/radio = current()][not(infix-if:custom-phys-address/*)]) <= 1" {
|
||||||
|
error-message "Only one interface per radio can use the default MAC address. Configure custom-phys-address on additional interfaces.";
|
||||||
|
}
|
||||||
|
|
||||||
description
|
description
|
||||||
"Reference to parent WiFi radio (PHY).
|
"Reference to parent WiFi radio (PHY).
|
||||||
@@ -148,6 +151,9 @@ submodule infix-if-wifi {
|
|||||||
leaf ssid {
|
leaf ssid {
|
||||||
type string {
|
type string {
|
||||||
length "1..32";
|
length "1..32";
|
||||||
|
pattern '[^\x00-\x1f\x22\x5c\x7f]*' {
|
||||||
|
error-message "SSID must not contain control characters, double quotes, or backslashes.";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
mandatory true;
|
mandatory true;
|
||||||
description
|
description
|
||||||
@@ -201,12 +207,12 @@ submodule infix-if-wifi {
|
|||||||
|
|
||||||
/* Operational state */
|
/* Operational state */
|
||||||
|
|
||||||
leaf rssi {
|
leaf signal-strength {
|
||||||
config false;
|
config false;
|
||||||
type int16;
|
type int16;
|
||||||
units "dBm";
|
units "dBm";
|
||||||
description
|
description
|
||||||
"Current received signal strength indication (RSSI) in dBm.
|
"Current signal strength in dBm.
|
||||||
|
|
||||||
More negative values indicate weaker signal.
|
More negative values indicate weaker signal.
|
||||||
|
|
||||||
@@ -266,7 +272,7 @@ submodule infix-if-wifi {
|
|||||||
description "BSSID (MAC address) of the AP.";
|
description "BSSID (MAC address) of the AP.";
|
||||||
}
|
}
|
||||||
|
|
||||||
leaf rssi {
|
leaf signal-strength {
|
||||||
type int16;
|
type int16;
|
||||||
units "dBm";
|
units "dBm";
|
||||||
description "Signal strength of the network.";
|
description "Signal strength of the network.";
|
||||||
@@ -319,6 +325,9 @@ submodule infix-if-wifi {
|
|||||||
leaf ssid {
|
leaf ssid {
|
||||||
type string {
|
type string {
|
||||||
length "1..32";
|
length "1..32";
|
||||||
|
pattern '[^\x00-\x1f\x22\x5c\x7f]*' {
|
||||||
|
error-message "SSID must not contain control characters, double quotes, or backslashes.";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
mandatory true;
|
mandatory true;
|
||||||
description
|
description
|
||||||
@@ -415,7 +424,7 @@ submodule infix-if-wifi {
|
|||||||
description "Client MAC address.";
|
description "Client MAC address.";
|
||||||
}
|
}
|
||||||
|
|
||||||
leaf rssi {
|
leaf signal-strength {
|
||||||
type int16;
|
type int16;
|
||||||
units "dBm";
|
units "dBm";
|
||||||
description "Client signal strength in dBm.";
|
description "Client signal strength in dBm.";
|
||||||
|
|||||||
@@ -565,12 +565,12 @@ class Decore():
|
|||||||
print(txt)
|
print(txt)
|
||||||
|
|
||||||
|
|
||||||
def rssi_to_status(rssi):
|
def signal_to_status(signal):
|
||||||
if rssi <= -75:
|
if signal >= -50:
|
||||||
status = Decore.bright_green("excellent")
|
status = Decore.bright_green("excellent")
|
||||||
elif rssi <= -65:
|
elif signal >= -60:
|
||||||
status = Decore.green("good")
|
status = Decore.green("good")
|
||||||
elif rssi <= -50:
|
elif signal >= -70:
|
||||||
status = Decore.yellow("poor")
|
status = Decore.yellow("poor")
|
||||||
else:
|
else:
|
||||||
status = Decore.red("bad")
|
status = Decore.red("bad")
|
||||||
@@ -1249,7 +1249,7 @@ class Iface:
|
|||||||
ssid = result.get('ssid', 'Hidden')
|
ssid = result.get('ssid', 'Hidden')
|
||||||
bssid = result.get("bssid", "unknown")
|
bssid = result.get("bssid", "unknown")
|
||||||
encstr = ", ".join(result.get("encryption", ["Unknown"]))
|
encstr = ", ".join(result.get("encryption", ["Unknown"]))
|
||||||
status = rssi_to_status(result.get("rssi", -100))
|
status = signal_to_status(result.get("signal-strength", -100))
|
||||||
channel = result.get("channel", "?")
|
channel = result.get("channel", "?")
|
||||||
|
|
||||||
ssid_table.row(ssid, bssid, encstr, status, channel)
|
ssid_table.row(ssid, bssid, encstr, status, channel)
|
||||||
@@ -1283,8 +1283,8 @@ class Iface:
|
|||||||
|
|
||||||
for station in stations:
|
for station in stations:
|
||||||
mac = station.get("mac-address", "unknown")
|
mac = station.get("mac-address", "unknown")
|
||||||
rssi = station.get("rssi")
|
signal = station.get("signal-strength")
|
||||||
signal_str = rssi_to_status(rssi) if rssi is not None else "------"
|
signal_str = signal_to_status(signal) if signal is not None else "------"
|
||||||
|
|
||||||
conn_time = station.get("connected-time", 0)
|
conn_time = station.get("connected-time", 0)
|
||||||
time_str = f"{conn_time}s"
|
time_str = f"{conn_time}s"
|
||||||
@@ -1310,11 +1310,11 @@ class Iface:
|
|||||||
row = self._pr_proto_common("ethernet", True, pipe);
|
row = self._pr_proto_common("ethernet", True, pipe);
|
||||||
print(row)
|
print(row)
|
||||||
ssid = None
|
ssid = None
|
||||||
rssi = None
|
signal = None
|
||||||
mode = None
|
mode = None
|
||||||
|
|
||||||
if self.wifi:
|
if self.wifi:
|
||||||
# Detect mode: AP has "stations", Station has "rssi" or "scan-results"
|
# Detect mode: AP has "stations", Station has "signal-strength" or "scan-results"
|
||||||
ap=self.wifi.get("access-point", {})
|
ap=self.wifi.get("access-point", {})
|
||||||
if ap:
|
if ap:
|
||||||
ssid = ap.get("ssid", "------")
|
ssid = ap.get("ssid", "------")
|
||||||
@@ -1326,11 +1326,11 @@ class Iface:
|
|||||||
else:
|
else:
|
||||||
station=self.wifi.get("station", {})
|
station=self.wifi.get("station", {})
|
||||||
ssid = station.get("ssid", "------")
|
ssid = station.get("ssid", "------")
|
||||||
rssi = station.get("rssi")
|
signal = station.get("signal-strength")
|
||||||
mode = "Station"
|
mode = "Station"
|
||||||
if rssi is not None:
|
if signal is not None:
|
||||||
signal = rssi_to_status(rssi)
|
signal_str = signal_to_status(signal)
|
||||||
data_str = f"{mode}, ssid: {ssid}, signal: {signal}"
|
data_str = f"{mode}, ssid: {ssid}, signal: {signal_str}"
|
||||||
else:
|
else:
|
||||||
data_str = f"{mode}, ssid: {ssid}"
|
data_str = f"{mode}, ssid: {ssid}"
|
||||||
else:
|
else:
|
||||||
@@ -1618,7 +1618,7 @@ class Iface:
|
|||||||
print(f"{'out-octets':<{20}}: {self.out_octets}")
|
print(f"{'out-octets':<{20}}: {self.out_octets}")
|
||||||
|
|
||||||
if self.wifi:
|
if self.wifi:
|
||||||
# Detect mode: AP has "stations", Station has "rssi" or "scan-results"
|
# Detect mode: AP has "stations", Station has "signal-strength" or "scan-results"
|
||||||
ap = self.wifi.get('access-point')
|
ap = self.wifi.get('access-point')
|
||||||
if ap:
|
if ap:
|
||||||
mode = "access-point"
|
mode = "access-point"
|
||||||
@@ -1632,13 +1632,13 @@ class Iface:
|
|||||||
else:
|
else:
|
||||||
mode = "station"
|
mode = "station"
|
||||||
station = self.wifi.get('station', {})
|
station = self.wifi.get('station', {})
|
||||||
rssi = station.get('rssi')
|
signal = station.get('signal-strength')
|
||||||
ssid = station.get('ssid', "----")
|
ssid = station.get('ssid', "----")
|
||||||
print(f"{'mode':<{20}}: {mode}")
|
print(f"{'mode':<{20}}: {mode}")
|
||||||
print(f"{'ssid':<{20}}: {ssid}")
|
print(f"{'ssid':<{20}}: {ssid}")
|
||||||
if rssi is not None:
|
if signal is not None:
|
||||||
signal_status = rssi_to_status(rssi)
|
signal_status = signal_to_status(signal)
|
||||||
print(f"{'signal':<{20}}: {rssi} dBm ({signal_status})")
|
print(f"{'signal':<{20}}: {signal} dBm ({signal_status})")
|
||||||
rx_speed = station.get('rx-speed')
|
rx_speed = station.get('rx-speed')
|
||||||
tx_speed = station.get('tx-speed')
|
tx_speed = station.get('tx-speed')
|
||||||
if rx_speed is not None:
|
if rx_speed is not None:
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ def main():
|
|||||||
common.LOG.warning("Unsupported model %s", args.model)
|
common.LOG.warning("Unsupported model %s", args.model)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
print(json.dumps(yang_data, indent=2))
|
print(json.dumps(yang_data, indent=2, ensure_ascii=False))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -62,14 +62,14 @@ def wifi_station(ifname):
|
|||||||
"""Get operational data for Station mode using iw + wpa_cli for scanning"""
|
"""Get operational data for Station mode using iw + wpa_cli for scanning"""
|
||||||
station_data = {}
|
station_data = {}
|
||||||
|
|
||||||
# Get link info (includes SSID and RSSI when connected)
|
# Get link info (includes SSID and signal strength when connected)
|
||||||
link = get_iw_link(ifname)
|
link = get_iw_link(ifname)
|
||||||
|
|
||||||
if link.get('connected'):
|
if link.get('connected'):
|
||||||
if link.get('ssid'):
|
if link.get('ssid'):
|
||||||
station_data['ssid'] = link['ssid']
|
station_data['ssid'] = link['ssid']
|
||||||
if link.get('rssi') is not None:
|
if link.get('signal-strength') is not None:
|
||||||
station_data['rssi'] = link['rssi']
|
station_data['signal-strength'] = link['signal-strength']
|
||||||
if link.get('rx-speed') is not None:
|
if link.get('rx-speed') is not None:
|
||||||
station_data['rx-speed'] = link['rx-speed']
|
station_data['rx-speed'] = link['rx-speed']
|
||||||
if link.get('tx-speed') is not None:
|
if link.get('tx-speed') is not None:
|
||||||
@@ -122,29 +122,35 @@ def parse_wpa_scan_result(scan_output):
|
|||||||
|
|
||||||
flags = parts[3].strip()
|
flags = parts[3].strip()
|
||||||
ssid = parts[4].strip() if len(parts) > 4 else ""
|
ssid = parts[4].strip() if len(parts) > 4 else ""
|
||||||
|
try:
|
||||||
|
ssid = ssid.encode().decode('unicode_escape').encode('latin-1').decode('utf-8')
|
||||||
|
except (UnicodeDecodeError, UnicodeEncodeError):
|
||||||
|
pass
|
||||||
|
# Strip control chars (terminal injection risk from rogue APs)
|
||||||
|
ssid = ''.join(c for c in ssid if c.isprintable())
|
||||||
|
|
||||||
# Skip hidden SSIDs (empty or null-filled)
|
# Skip hidden SSIDs (empty or null-filled)
|
||||||
if not ssid or ssid.isspace() or '\\x00' in ssid:
|
if not ssid or ssid.isspace():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
encryption = extract_encryption(flags)
|
encryption = extract_encryption(flags)
|
||||||
channel = frequency_to_channel(frequency)
|
channel = frequency_to_channel(frequency)
|
||||||
|
|
||||||
# Keep best RSSI per SSID
|
# Keep best signal per SSID
|
||||||
if ssid not in networks or rssi > networks[ssid]['rssi']:
|
if ssid not in networks or rssi > networks[ssid]['signal-strength']:
|
||||||
networks[ssid] = {
|
networks[ssid] = {
|
||||||
'bssid': bssid,
|
'bssid': bssid,
|
||||||
'ssid': ssid,
|
'ssid': ssid,
|
||||||
'rssi': rssi,
|
'signal-strength': rssi,
|
||||||
'encryption': encryption,
|
'encryption': encryption,
|
||||||
'channel': channel
|
'channel': channel
|
||||||
}
|
}
|
||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Sort by RSSI (best first)
|
# Sort by signal strength (best first)
|
||||||
result = list(networks.values())
|
result = list(networks.values())
|
||||||
result.sort(key=lambda x: x['rssi'], reverse=True)
|
result.sort(key=lambda x: x['signal-strength'], reverse=True)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user