Skip to content

Commit 8510a02

Browse files
committed
net: explicitly reap gateway veth on gateway stop to free orphan netns
Signed-off-by: ravindu644 <droidcasts@protonmail.com>
1 parent 9e738a8 commit 8510a02

2 files changed

Lines changed: 114 additions & 2 deletions

File tree

src/include/droidspace.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,11 @@ void ds_net_start_route_monitor(void);
695695
* cable to every running client that delegates to it, with no client restart.
696696
* Called from the gateway container's monitor on each boot cycle. */
697697
void ds_net_rewire_gateway_clients(const char *gateway_name, pid_t gateway_pid);
698+
/* Gateway teardown: when a container that ACTS AS A GATEWAY stops, explicitly
699+
* delete the gateway-side veth(s) it serves and reap any now-idle delegated
700+
* bridge. The kernel does not auto-reap these (the host-side veth pins its
701+
* orphan peer netns), so this prevents the leak. No-op for a non-gateway. */
702+
void ds_net_gateway_teardown(const char *gateway_name);
698703
int ds_net_disable_tx_checksum(const char *ifname);
699704
void parse_cidr(const char *cidr, uint32_t *ip_out, uint32_t *mask_out);
700705

src/net/network.c

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1291,6 +1291,105 @@ void ds_net_rewire_gateway_clients(const char *gateway_name,
12911291
closedir(d);
12921292
}
12931293

1294+
/* ---------------------------------------------------------------------------
1295+
* ds_net_gateway_teardown
1296+
*
1297+
* Called when a container that ACTS AS A GATEWAY stops. The gateway-side veth
1298+
* ds-g<hash> lives in the host netns; its peer is the gateway's eth1. When the
1299+
* gateway stops the kernel does NOT auto-reap ds-g: the host-side veth itself
1300+
* pins its now-process-less peer netns (a veth end holds a reference to its
1301+
* peer's namespace), and that netns can only be freed by a cleanup_net that
1302+
* cannot run while ds-g pins it - a self-sustaining orphan. So we delete ds-g
1303+
* explicitly, exactly as NAT mode deletes ds-v<pid>.
1304+
*
1305+
* We do not track our own segments (clients choose --gateway-net), so scan the
1306+
* client configs that delegate to us, derive each segment's bridge + gateway
1307+
* veth, delete the veth (reaping the peer and freeing the netns), and reap the
1308+
* bridge once no client veths remain on it. Per-segment work runs under the
1309+
* same advisory lock client setup/cleanup use, and bridges are de-duplicated
1310+
* since many clients can share one segment. A no-op for a container that is
1311+
* nobody's gateway.
1312+
* ---------------------------------------------------------------------------*/
1313+
void ds_net_gateway_teardown(const char *gateway_name) {
1314+
if (!gateway_name || !gateway_name[0])
1315+
return;
1316+
1317+
char containers_dir[PATH_MAX];
1318+
snprintf(containers_dir, sizeof(containers_dir), "%s/Containers",
1319+
get_workspace_dir());
1320+
DIR *d = opendir(containers_dir);
1321+
if (!d)
1322+
return;
1323+
1324+
/* De-dupe segments: many clients can share one --gateway-net (one bridge). */
1325+
char seen[32][IFNAMSIZ];
1326+
int seen_count = 0;
1327+
1328+
struct dirent *ent;
1329+
while ((ent = readdir(d)) != NULL) {
1330+
if (ent->d_name[0] == '.')
1331+
continue;
1332+
1333+
struct ds_config c = {0};
1334+
if (ds_config_load_by_name(ent->d_name, &c) != 0)
1335+
continue;
1336+
1337+
if (!(c.net_mode == DS_NET_GATEWAY && c.gateway_container[0] &&
1338+
strcmp(c.gateway_container, gateway_name) == 0)) {
1339+
ds_config_free(&c);
1340+
continue;
1341+
}
1342+
1343+
char bridge[IFNAMSIZ], gw_host[IFNAMSIZ], gw_peer[IFNAMSIZ];
1344+
gateway_bridge_name(&c, bridge, sizeof(bridge));
1345+
gateway_veth_names(&c, gw_host, sizeof(gw_host), gw_peer, sizeof(gw_peer));
1346+
1347+
int dup = 0;
1348+
for (int i = 0; i < seen_count; i++)
1349+
if (strcmp(seen[i], bridge) == 0) {
1350+
dup = 1;
1351+
break;
1352+
}
1353+
if (dup) {
1354+
ds_config_free(&c);
1355+
continue;
1356+
}
1357+
if (seen_count < (int)(sizeof(seen) / sizeof(seen[0])))
1358+
safe_strncpy(seen[seen_count++], bridge, IFNAMSIZ);
1359+
1360+
ds_nl_ctx_t *ctx = ds_nl_open();
1361+
if (!ctx) {
1362+
ds_config_free(&c);
1363+
continue;
1364+
}
1365+
1366+
/* Same lock client setup/cleanup take, so we cannot race a concurrent
1367+
* client start/wire or the gateway's own rewire on the segment. */
1368+
int lock = gateway_segment_lock(bridge);
1369+
1370+
ds_nl_del_link(ctx, gw_host);
1371+
ds_log("[NET] Gateway teardown: removed gateway veth %s (segment %s)",
1372+
gw_host, bridge);
1373+
1374+
int clients = ds_nl_count_bridge_members_with_prefix(
1375+
ctx, bridge, app_veth_host_prefix(&c));
1376+
if (clients > 0) {
1377+
ds_log("[NET] Gateway teardown: %d client(s) still on %s - keeping "
1378+
"bridge",
1379+
clients, bridge);
1380+
} else {
1381+
ds_nl_del_link(ctx, bridge);
1382+
ds_log("[NET] Gateway teardown: reaped idle delegated LAN bridge %s",
1383+
bridge);
1384+
}
1385+
1386+
gateway_segment_unlock(lock);
1387+
ds_nl_close(ctx);
1388+
ds_config_free(&c);
1389+
}
1390+
closedir(d);
1391+
}
1392+
12941393
/* ---------------------------------------------------------------------------
12951394
* setup_veth_child_side_named
12961395
*
@@ -1999,6 +2098,12 @@ void ds_net_start_route_monitor(void) {
19992098
* ---------------------------------------------------------------------------*/
20002099

20012100
void ds_net_cleanup(struct ds_config *cfg, pid_t container_pid) {
2101+
/* If this container is a gateway for others, explicitly tear down the
2102+
* gateway-side veth(s) it serves: the kernel will not auto-reap them (the
2103+
* host-side veth pins its orphan peer netns). No-op when nobody delegates
2104+
* to us, so it is safe to run for every stopping container. */
2105+
ds_net_gateway_teardown(cfg->container_name);
2106+
20022107
if (cfg->net_mode == DS_NET_GATEWAY) {
20032108
ds_nl_ctx_t *ctx = ds_nl_open();
20042109
if (!ctx)
@@ -2037,8 +2142,10 @@ void ds_net_cleanup(struct ds_config *cfg, pid_t container_pid) {
20372142
"- keeping bridge to avoid flapping its LAN iface",
20382143
bridge, cfg->gateway_container);
20392144
} else {
2040-
/* No clients and the gateway is gone (its netns death already took the
2041-
* gateway veth with its peer) - safe to reap the now-idle bridge. */
2145+
/* No clients and the gateway is gone. The gateway's own stop already
2146+
* ran ds_net_gateway_teardown(), which explicitly deleted the gateway
2147+
* veth (the kernel does not auto-reap it), so the bridge is now idle
2148+
* and safe to reap. */
20422149
ds_nl_del_link(ctx, bridge);
20432150
ds_log("[NET] Gateway cleanup: reaped idle delegated LAN bridge %s",
20442151
bridge);

0 commit comments

Comments
 (0)