feat(gateway): log periodic CAM refreshes
The gateway already sent periodic CAM refresh frames so the physical switch keeps remote client MACs learned on the gateway port. Those periodic refreshes were silent, which made the manual MVP switch-learning check harder to diagnose after the initial peer-joined refresh had passed. Keep the peer id and MAC attached to periodic refresh work and emit the same structured CAM refresh log line with reason=periodic. The join-triggered refresh still logs reason=peer_joined, so the test guide can distinguish the immediate proof from the recurring keepalive signal. Test Plan: - cargo test -p lanparty-gateway cam_refresh - cargo test -p lanparty-gateway - cargo fmt --check - cargo test --workspace - cargo clippy --workspace --all-targets -- -D warnings - git diff --check - git diff --cached --check Refs: MVP switch MAC-learning diagnostics
This commit is contained in:
@@ -198,12 +198,13 @@ The gateway rejects Linux interfaces that sysfs identifies as Wi-Fi, and rejects
|
|||||||
wired interfaces whose sysfs carrier state reports no link; managed wireless
|
wired interfaces whose sysfs carrier state reports no link; managed wireless
|
||||||
NICs are not supported for the physical LAN bridge.
|
NICs are not supported for the physical LAN bridge.
|
||||||
It tracks remote-client MACs from relay lifecycle events and periodically emits
|
It tracks remote-client MACs from relay lifecycle events and periodically emits
|
||||||
small CAM refresh frames so the physical switch keeps those MACs associated
|
small CAM refresh frames, logged with `reason=periodic`, so the physical
|
||||||
with the gateway port. A newly observed client also triggers an immediate CAM
|
switch keeps those MACs associated with the gateway port. A newly observed
|
||||||
refresh frame instead of waiting for the first periodic refresh tick. When
|
client also triggers an immediate CAM refresh frame logged with
|
||||||
control events and frame work are both ready, the bridge handles the lifecycle
|
`reason=peer_joined` instead of waiting for the first periodic refresh tick.
|
||||||
event first so first packets after a client joins use the freshest remote-MAC
|
When control events and frame work are both ready, the bridge handles the
|
||||||
state available locally. Gateway
|
lifecycle event first so first packets after a client joins use the freshest
|
||||||
|
remote-MAC state available locally. Gateway
|
||||||
frame logs include direction, peer id when present, MACs, ethertype/length,
|
frame logs include direction, peer id when present, MACs, ethertype/length,
|
||||||
frame length, action, and drop reason. The gateway also tracks frame/datagram
|
frame length, action, and drop reason. The gateway also tracks frame/datagram
|
||||||
counters and periodically sends stats snapshots to the relay. Malformed or runt
|
counters and periodically sends stats snapshots to the relay. Malformed or runt
|
||||||
|
|||||||
+5
-3
@@ -221,6 +221,7 @@ gateway control event: client peer ... joined with MAC ...
|
|||||||
gateway frame interface=eth0 direction=LanToRemote ... action=Forwarded
|
gateway frame interface=eth0 direction=LanToRemote ... action=Forwarded
|
||||||
gateway frame interface=eth0 direction=RemoteToLan ... action=Forwarded
|
gateway frame interface=eth0 direction=RemoteToLan ... action=Forwarded
|
||||||
gateway CAM refresh interface=eth0 peer_id=... mac=... reason=peer_joined
|
gateway CAM refresh interface=eth0 peer_id=... mac=... reason=peer_joined
|
||||||
|
gateway CAM refresh interface=eth0 peer_id=... mac=... reason=periodic
|
||||||
```
|
```
|
||||||
|
|
||||||
Client health:
|
Client health:
|
||||||
@@ -293,9 +294,10 @@ firewall, and whether the LAN subnet conflicts with the client's home LAN.
|
|||||||
Uncommon LAN subnets such as `10.73.42.0/24` are safer than `192.168.0.0/24`.
|
Uncommon LAN subnets such as `10.73.42.0/24` are safer than `192.168.0.0/24`.
|
||||||
|
|
||||||
If switch MAC learning does not show the Windows client MAC on the gateway
|
If switch MAC learning does not show the Windows client MAC on the gateway
|
||||||
port, look for `gateway CAM refresh ... reason=peer_joined`. If that line is
|
port, look for `gateway CAM refresh ... reason=peer_joined` immediately after
|
||||||
present but the switch still does not learn it, check the selected gateway
|
join and `gateway CAM refresh ... reason=periodic` about once per minute after
|
||||||
interface and switch port first.
|
that. If those lines are present but the switch still does not learn it, check
|
||||||
|
the selected gateway interface and switch port first.
|
||||||
|
|
||||||
## Cleanup
|
## Cleanup
|
||||||
|
|
||||||
|
|||||||
@@ -458,8 +458,17 @@ impl GatewayConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = cam_refresh_tick.tick() => {
|
_ = cam_refresh_tick.tick() => {
|
||||||
for frame in remote_clients.refresh_frames() {
|
for refresh in remote_clients.refreshes() {
|
||||||
write_lan_ethernet(&packet_socket, &frame).await?;
|
write_lan_ethernet(&packet_socket, refresh.frame()).await?;
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
gateway_cam_refresh_log_line(
|
||||||
|
packet_socket.get_ref().interface(),
|
||||||
|
refresh.peer_id(),
|
||||||
|
refresh.mac(),
|
||||||
|
"periodic",
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = stats_tick.tick() => {
|
_ = stats_tick.tick() => {
|
||||||
@@ -932,10 +941,10 @@ impl RemoteClientTable {
|
|||||||
self.remote_clients.remove(&peer_id);
|
self.remote_clients.remove(&peer_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn refresh_frames(&self) -> Vec<Vec<u8>> {
|
fn refreshes(&self) -> Vec<CamRefresh> {
|
||||||
self.remote_clients
|
self.remote_clients
|
||||||
.values()
|
.iter()
|
||||||
.map(|source| cam_refresh_frame(*source, self.gateway_mac))
|
.map(|(peer_id, mac)| CamRefresh::new(*peer_id, *mac, self.gateway_mac))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1412,8 +1421,12 @@ mod tests {
|
|||||||
assert_eq!(immediate_frame.source(), remote_mac);
|
assert_eq!(immediate_frame.source(), remote_mac);
|
||||||
assert_eq!(immediate_frame.destination(), gateway_mac);
|
assert_eq!(immediate_frame.destination(), gateway_mac);
|
||||||
assert_eq!(refresh.remote_mac_count(), 1);
|
assert_eq!(refresh.remote_mac_count(), 1);
|
||||||
|
let periodic_refreshes = refresh.refreshes();
|
||||||
|
assert_eq!(periodic_refreshes.len(), 1);
|
||||||
|
assert_eq!(periodic_refreshes[0].peer_id(), 7);
|
||||||
|
assert_eq!(periodic_refreshes[0].mac(), remote_mac);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
EthernetFrame::parse(&refresh.refresh_frames()[0])
|
EthernetFrame::parse(periodic_refreshes[0].frame())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.source(),
|
.source(),
|
||||||
remote_mac
|
remote_mac
|
||||||
@@ -1427,7 +1440,7 @@ mod tests {
|
|||||||
None
|
None
|
||||||
);
|
);
|
||||||
assert_eq!(refresh.remote_mac_count(), 0);
|
assert_eq!(refresh.remote_mac_count(), 0);
|
||||||
assert!(refresh.refresh_frames().is_empty());
|
assert!(refresh.refreshes().is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
|
|||||||
Reference in New Issue
Block a user