Skip to content

Commit 51bbd34

Browse files
committed
confd: wifi: Apply station and mesh changes on commit
Adding station settings to a scan-only interface, or changing them, only rewrote the wpa_supplicant config; nothing told the daemon. Reload it on config changes and start it over when the netdev is recreated. Fix #1679 Signed-off-by: Mattias Walström <lazzer@gmail.com>
1 parent 6e6959f commit 51bbd34

10 files changed

Lines changed: 318 additions & 6 deletions

File tree

‎doc/ChangeLog.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ All notable changes to the project are documented in this file.
1616
- Acer Connect Vero W6m: WiFi hardware offloading is now active, which
1717
lowers the CPU load of WiFi traffic
1818

19+
### Fixes
20+
21+
- Fix #1679: a WiFi station set up on an interface that was in scan-only
22+
mode, as in the Raspberry Pi 4 factory configuration, did not connect
23+
until the device was rebooted. Changes to a station's or mesh point's
24+
settings now take effect on commit
1925

2026
[wds]: https://www.kernelkit.org/infix/latest/wifi/#wds-backhaul-and-repeaters
2127

‎src/confd/src/if-wifi.c‎

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,72 @@ int wifi_mode_changed(struct lyd_node *wifi)
166166
if (node && (op == LYDX_OP_CREATE || op == LYDX_OP_DELETE))
167167
return 1;
168168

169+
/* Scan-only <-> station, and the 4-address flag is set at creation */
170+
node = lydx_get_child(wifi, "station");
171+
if (node)
172+
op = lydx_get_op(node);
173+
if (node && (op == LYDX_OP_CREATE || op == LYDX_OP_DELETE))
174+
return 1;
175+
if (node && lydx_get_child(node, "wds"))
176+
return 1;
177+
169178
return 0;
170179
}
171180

181+
/*
182+
* A changed wpa_supplicant config only takes effect when the daemon
183+
* reloads it, so nudge the service whenever the wifi subtree changed.
184+
*/
185+
static int wifi_gen_reload(struct lyd_node *dif, struct lyd_node *cif, struct dagger *net)
186+
{
187+
const char *ifname = lydx_get_cattr(cif, "name");
188+
const char *svc;
189+
FILE *fp;
190+
191+
if (!lydx_get_child(dif, "wifi"))
192+
return SR_ERR_OK;
193+
194+
switch (wifi_get_mode(cif)) {
195+
case wifi_station:
196+
svc = "wifi";
197+
break;
198+
case wifi_mesh:
199+
svc = "mesh";
200+
break;
201+
default:
202+
return SR_ERR_OK;
203+
}
204+
205+
fp = dagger_fopen_net_init(net, ifname, NETDAG_INIT_DAEMON, "wifi-reload.sh");
206+
if (!fp)
207+
return SR_ERR_INTERNAL;
208+
209+
fprintf(fp, "initctl -bfq touch %s@%s\n", svc, ifname);
210+
fclose(fp);
211+
212+
return SR_ERR_OK;
213+
}
214+
215+
/* Settings of an existing station or mesh point changed */
216+
int wifi_gen_settings(sr_session_ctx_t *session, struct lyd_node *dif,
217+
struct lyd_node *cif, struct dagger *net)
218+
{
219+
int rc;
220+
221+
switch (wifi_get_mode(cif)) {
222+
case wifi_station:
223+
rc = wifi_validate_secret(session, cif) ? : wifi_gen_station(cif);
224+
break;
225+
case wifi_mesh:
226+
rc = wifi_gen_mesh(cif);
227+
break;
228+
default:
229+
return SR_ERR_OK;
230+
}
231+
232+
return rc ? : wifi_gen_reload(dif, cif, net);
233+
}
234+
172235
/*
173236
* Generate wpa_supplicant config for station mode
174237
*/
@@ -586,6 +649,10 @@ int wifi_add_iface(struct lyd_node *cif, struct dagger *net)
586649
wifi_gen_station(cif);
587650
fprintf(iw, "initctl -bfq enable wifi@%s\n", ifname);
588651
fprintf(iw, "initctl -bfq touch wifi@%s\n", ifname);
652+
/* A running instance from before the netdev was recreated
653+
* only gets a SIGHUP from the touch and keeps stale driver
654+
* state, so make sure it starts over on the new netdev. */
655+
fprintf(iw, "initctl -bnq restart wpa_supplicant:%s\n", ifname);
589656
break;
590657
}
591658
case wifi_wds: {
@@ -605,6 +672,7 @@ int wifi_add_iface(struct lyd_node *cif, struct dagger *net)
605672
wifi_gen_mesh(cif);
606673
fprintf(iw, "initctl -bfq enable mesh@%s\n", ifname);
607674
fprintf(iw, "initctl -bfq touch mesh@%s\n", ifname);
675+
fprintf(iw, "initctl -bnq restart wpa_supplicant:%s\n", ifname);
608676
break;
609677
default:
610678
ERROR("WiFi mode %d unknown", mode);

‎src/confd/src/interfaces.c‎

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -453,12 +453,7 @@ static int netdag_gen_afspec_set(sr_session_ctx_t *session, struct dagger *net,
453453
case IFT_ETH:
454454
return netdag_gen_ethtool(net, cif, dif);
455455
case IFT_WIFI:
456-
if (wifi_get_mode(cif) == wifi_station)
457-
return wifi_validate_secret(session, cif)
458-
? : wifi_gen_station(cif);
459-
if (wifi_get_mode(cif) == wifi_mesh)
460-
return wifi_gen_mesh(cif);
461-
return 0;
456+
return wifi_gen_settings(session, dif, cif, net);
462457
case IFT_DUMMY:
463458
case IFT_GRE:
464459
case IFT_GRETAP:

‎src/confd/src/interfaces.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ int wifi_add_deps(struct lyd_node *cif);
141141
int wifi_add_iface(struct lyd_node *cif, struct dagger *net);
142142
int wifi_del_iface(struct lyd_node *dif, struct dagger *net);
143143
int wifi_mode_changed(struct lyd_node *wifi);
144+
int wifi_gen_settings(sr_session_ctx_t *session, struct lyd_node *dif,
145+
struct lyd_node *cif, struct dagger *net);
144146
int wifi_gen_station(struct lyd_node *cif);
145147
int wifi_gen_mesh(struct lyd_node *cif);
146148
wifi_mode_t wifi_get_mode(struct lyd_node *wifi);

‎test/case/interfaces/wifi.yaml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,6 @@
1616

1717
- name: WiFi repeater over a 4-address (WDS) backhaul
1818
case: wifi_wds_repeater/test.py
19+
20+
- name: WiFi station set up from a scan-only interface
21+
case: wifi_station_from_scan/test.py
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
test.adoc
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
=== WiFi station set up from a scan-only interface
2+
3+
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/wifi_station_from_scan]
4+
5+
==== Description
6+
7+
A WiFi interface with only a radio, no station or access point, is in
8+
scan-only mode. That is how the factory configuration of boards with a
9+
built-in radio ships, so the usual way to get online is to add the station
10+
settings to that existing interface. The connection has to come up from
11+
that change alone, without a reboot or a service restart.
12+
13+
Topology:
14+
....
15+
host ==(mgmt)== ap ))) ~ cell ~ ((( station ==(mgmt)== host
16+
....
17+
18+
==== Topology
19+
20+
image::topology.svg[WiFi station set up from a scan-only interface topology, align=center, scaledwidth=75%]
21+
22+
==== Sequence
23+
24+
. Set up topology and attach to the ap and the station
25+
. Configure the ap with access point 'infix-scan' and a DHCP server on 192.168.21.1
26+
. Configure wifi0 on the station with only radio0, scan-only mode
27+
. Verify the station sees 'infix-scan' in its scan results
28+
. Add station settings for 'infix-scan' to wifi0 on the station
29+
. Verify the station associates to 'infix-scan' without a restart
30+
. Verify the station leases 192.168.21.100 over wifi
31+
32+
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#!/usr/bin/env python3
2+
r"""
3+
WiFi station set up from a scan-only interface
4+
5+
A WiFi interface with only a radio, no station or access point, is in
6+
scan-only mode. That is how the factory configuration of boards with a
7+
built-in radio ships, so the usual way to get online is to add the station
8+
settings to that existing interface. The connection has to come up from
9+
that change alone, without a reboot or a service restart.
10+
11+
Topology:
12+
....
13+
host ==(mgmt)== ap ))) ~ cell ~ ((( station ==(mgmt)== host
14+
....
15+
"""
16+
import infamy
17+
import infamy.iface as iface
18+
import infamy.wifi as wifi
19+
from infamy.util import until, parallel
20+
21+
SSID = "infix-scan"
22+
PSK = "infixinfix"
23+
24+
SUBNET = "192.168.21.0/24"
25+
AP_IP = "192.168.21.1"
26+
LEASE = "192.168.21.100"
27+
28+
29+
with infamy.Test() as test:
30+
with test.step("Set up topology and attach to the ap and the station"):
31+
env = infamy.Env()
32+
ap, station = parallel(
33+
lambda: env.attach("ap", "mgmt"),
34+
lambda: env.attach("station", "mgmt"),
35+
)
36+
wifi.skip_unless_supported(test, ap, station)
37+
38+
with test.step("Configure the ap with access point 'infix-scan' and a DHCP server on 192.168.21.1"):
39+
ap.put_config_dicts({
40+
"ietf-hardware": {"hardware": {"component": [
41+
wifi.radio("radio0", band="2.4GHz", channel=1)]}},
42+
"ietf-keystore": wifi.keystore({"wifi": PSK}),
43+
"ietf-interfaces": {"interfaces": {"interface": [
44+
wifi.iface("wifi0", "02:00:00:00:00:01", {
45+
"radio": "radio0",
46+
"access-point": {
47+
"ssid": SSID,
48+
"security": {"mode": "wpa2-wpa3-personal", "secret": "wifi"},
49+
},
50+
}, ipv4={"address": [{"ip": AP_IP, "prefix-length": 24}]}),
51+
]}},
52+
"infix-dhcp-server": {"dhcp-server": {"subnet": [{
53+
"subnet": SUBNET,
54+
"pool": {"start-address": LEASE, "end-address": LEASE},
55+
}]}},
56+
})
57+
58+
with test.step("Configure wifi0 on the station with only radio0, scan-only mode"):
59+
station.put_config_dicts({
60+
"ietf-hardware": {"hardware": {"component": [wifi.radio("radio0")]}},
61+
"ietf-interfaces": {"interfaces": {"interface": [
62+
wifi.iface("wifi0", "02:00:00:00:00:02", {"radio": "radio0"},
63+
ipv4={"infix-dhcp-client:dhcp": {}}),
64+
]}},
65+
})
66+
67+
with test.step("Verify the station sees 'infix-scan' in its scan results"):
68+
until(lambda: SSID in {n.get("ssid") for n in
69+
wifi.station(station).get("scan-results") or []},
70+
attempts=60, interval=2)
71+
72+
with test.step("Add station settings for 'infix-scan' to wifi0 on the station"):
73+
station.put_config_dicts({
74+
"ietf-keystore": wifi.keystore({"wifi": PSK}),
75+
"ietf-interfaces": {"interfaces": {"interface": [
76+
{"name": "wifi0", "infix-interfaces:wifi": {
77+
"radio": "radio0",
78+
"station": {
79+
"ssid": SSID,
80+
"security": {"mode": "auto", "secret": "wifi"},
81+
},
82+
}},
83+
]}},
84+
})
85+
86+
with test.step("Verify the station associates to 'infix-scan' without a restart"):
87+
until(lambda: wifi.associated(station, SSID), attempts=60, interval=2)
88+
89+
with test.step("Verify the station leases 192.168.21.100 over wifi"):
90+
until(lambda: iface.address_exist(station, "wifi0", LEASE),
91+
attempts=60, interval=2)
92+
93+
test.succeed()
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
graph "wifi-station-from-scan" {
2+
layout="neato";
3+
overlap="false";
4+
esep="+40";
5+
6+
node [shape=record, fontname="DejaVu Sans Mono, Book"];
7+
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
8+
9+
host [
10+
label="host | { <mgmt1> mgmt1 | <mgmt2> mgmt2 }",
11+
pos="0,0!",
12+
requires="controller",
13+
];
14+
15+
ap [
16+
label="{ <mgmt> mgmt | <wifi> wifi } | ap",
17+
pos="6,2!",
18+
requires="infix",
19+
];
20+
21+
station [
22+
label="{ <mgmt> mgmt | <wifi> wifi } | station",
23+
pos="6,-2!",
24+
requires="infix",
25+
];
26+
27+
// The wireless cell the ap and the station share, modelled as a medium
28+
// node both join (one radio each, same index -> same cell):
29+
// * physical: maps onto a real over-the-air RF cell;
30+
// * virtual (qeneth): maps onto one of the multicast cells over which
31+
// the wifimedium relay bridges mac80211_hwsim frames.
32+
// The mapper guarantees the two DUTs actually share this medium.
33+
cell [
34+
label="cell",
35+
pos="9,0!",
36+
requires="wifi-radio",
37+
];
38+
39+
host:mgmt1 -- ap:mgmt [requires="mgmt", color="lightgray"]
40+
host:mgmt2 -- station:mgmt [requires="mgmt", color="lightgray"]
41+
42+
ap:wifi -- cell [requires="wifi2.4GHz", style="dashed"]
43+
station:wifi -- cell [requires="wifi2.4GHz", style="dashed"]
44+
}
Lines changed: 68 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)