summaryrefslogtreecommitdiff
path: root/accel-pppd/ctrl/ipoe/ipoe_netlink.c
diff options
context:
space:
mode:
authorDenys Fedoryshchenko <denys.f@collabora.com>2026-08-08 17:47:49 +0300
committerDenys Fedoryshchenko <denys.f@collabora.com>2026-08-08 17:47:49 +0300
commit6693d18ae0232da2c6febc0decf099e4fd064381 (patch)
treec8d9c3eb0e9084a053cd38d4d000a61c35722f04 /accel-pppd/ctrl/ipoe/ipoe_netlink.c
parent4fb21bfd967ee834d2b5d5ea11d0838002d6ac1a (diff)
downloadaccel-ppp-6693d18ae0232da2c6febc0decf099e4fd064381.tar.gz
accel-ppp-6693d18ae0232da2c6febc0decf099e4fd064381.zip
ipoe: flush sessions left by a previous instance with a single command
On startup accel-pppd is expected to drop everything a previous instance left behind in the kernel. For sessions it did so by dumping them with IPOE_CMD_GET and sending one IPOE_CMD_DELETE per ifindex. That dump was written for the session backup code removed in 1972a7e5c and was never adapted to its new, destructive role: - it was issued on the socket subscribed to the packet multicast group, so it competed with the notifications that the still attached stale rx handlers keep generating; on a loaded box the receive buffer overruns and rtnl_dump_filter() gives up with ENOBUFS, - its return value was discarded, so such a failure was silent, - it ran before the interfaces were detached, which is what produces those notifications in the first place, - it was skipped entirely when the multicast group could not be resolved, again with nothing but a warning about packet handling, - and every session cost a socket, a round trip, a grace period and a full unregister_netdev(). Whatever it missed stays in the kernel forever: the daemon has no record of those sessions, so nothing ever deletes them, and every subscriber later assigned one of their addresses is refused with EEXIST by IPOE_CMD_MODIFY. Add IPOE_CMD_FLUSH, which unlinks all sessions in one go, waits for a single grace period and unregisters the devices with unregister_netdevice_many(), and use it instead. The old path is kept as a fallback for a module predating the command, which is reported as EOPNOTSUPP, and now checks its return value and uses a private socket. Reorder init() so the interfaces are detached before the multicast group is joined, and so the flush also runs when only the group lookup failed.
Diffstat (limited to 'accel-pppd/ctrl/ipoe/ipoe_netlink.c')
-rw-r--r--accel-pppd/ctrl/ipoe/ipoe_netlink.c101
1 files changed, 89 insertions, 12 deletions
diff --git a/accel-pppd/ctrl/ipoe/ipoe_netlink.c b/accel-pppd/ctrl/ipoe/ipoe_netlink.c
index 94b7f42a..6eee2dca 100644
--- a/accel-pppd/ctrl/ipoe/ipoe_netlink.c
+++ b/accel-pppd/ctrl/ipoe/ipoe_netlink.c
@@ -410,17 +410,23 @@ static int dump_session(const struct sockaddr_nl *addr, struct nlmsghdr *n, void
return 0;
}
-void ipoe_nl_get_sessions(struct list_head *list)
+int ipoe_nl_get_sessions(struct list_head *list)
{
+ struct rtnl_handle rth;
struct nlmsghdr *nlh;
struct genlmsghdr *ghdr;
struct {
struct nlmsghdr n;
char buf[1024];
} req;
+ int ret;
- if (rth.fd == -1)
- return;
+ /* a private socket, so that the dump does not have to compete with
+ * the packet notifications delivered to the multicast one */
+ if (rtnl_open_byproto(&rth, 0, NETLINK_GENERIC)) {
+ log_error("ipoe: cannot open generic netlink socket\n");
+ return -1;
+ }
memset(&req, 0, sizeof(req));
@@ -434,11 +440,51 @@ void ipoe_nl_get_sessions(struct list_head *list)
ghdr->cmd = IPOE_CMD_GET;
if (rtnl_send(&rth, (char *)nlh, nlh->nlmsg_len) < 0) {
- log_emerg("ipoe: failed to send dump request: %s\n", strerror(errno));
- return;
+ log_error("ipoe: failed to send dump request: %s\n", strerror(errno));
+ rtnl_close(&rth);
+ return -1;
+ }
+
+ ret = rtnl_dump_filter(&rth, dump_session, list, NULL, NULL);
+
+ rtnl_close(&rth);
+
+ return ret;
+}
+
+int ipoe_nl_flush_sessions(void)
+{
+ struct rtnl_handle rth;
+ struct nlmsghdr *nlh;
+ struct genlmsghdr *ghdr;
+ struct {
+ struct nlmsghdr n;
+ char buf[128];
+ } req;
+ int ret = 0;
+
+ if (rtnl_open_byproto(&rth, 0, NETLINK_GENERIC)) {
+ log_error("ipoe: cannot open generic netlink socket\n");
+ errno = EIO;
+ return -1;
}
- rtnl_dump_filter(&rth, dump_session, list, NULL, NULL);
+ memset(&req, 0, sizeof(req));
+
+ nlh = &req.n;
+ nlh->nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
+ nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+ nlh->nlmsg_type = ipoe_genl_id;
+
+ ghdr = NLMSG_DATA(&req.n);
+ ghdr->cmd = IPOE_CMD_FLUSH;
+
+ if (rtnl_talk(&rth, nlh, 0, 0, nlh, NULL, NULL, 0) < 0)
+ ret = -1;
+
+ rtnl_close(&rth);
+
+ return ret;
}
void ipoe_nl_delete(int ifindex)
@@ -479,7 +525,10 @@ static void delete_sessions()
struct ipoe_session_info *info;
LIST_HEAD(ds_list);
- ipoe_nl_get_sessions(&ds_list);
+
+ if (ipoe_nl_get_sessions(&ds_list))
+ log_error("ipoe: failed to enumerate sessions left by a previous"
+ " instance, some of them are not removed\n");
while (!list_empty(&ds_list)) {
info = list_entry(ds_list.next, typeof(*info), entry);
@@ -489,6 +538,22 @@ static void delete_sessions()
}
}
+static void flush_sessions()
+{
+ if (!ipoe_nl_flush_sessions())
+ return;
+
+ if (errno == EOPNOTSUPP) {
+ log_warn("ipoe: loaded ipoe module does not support IPOE_CMD_FLUSH,"
+ " removing sessions one by one, reload the module to fix\n");
+ delete_sessions();
+ return;
+ }
+
+ log_error("ipoe: failed to remove sessions left by a previous instance:"
+ " %s\n", strerror(errno));
+}
+
static void ipoe_up_handler(const struct sockaddr_nl *addr, struct nlmsghdr *h)
{
struct rtattr *tb[PKT_ATTR_MAX + 1];
@@ -648,6 +713,23 @@ static void init(void)
log_warn("failed to load ipoe module\n");
mcg_id = genl_resolve_mcg(IPOE_GENL_NAME, IPOE_GENL_MCG_PKT, &ipoe_genl_id);
+
+ if (!ipoe_genl_id) {
+ log_error("ipoe: cannot resolve netlink family, state left by a"
+ " previous instance is not removed\n");
+ rth.fd = -1;
+ return;
+ }
+
+ /* Drop everything a previous instance may have left in the kernel.
+ * The interfaces go first: while their rx handlers are still attached
+ * the module keeps reporting unclassified packets, and once we join
+ * the multicast group that traffic competes with our own replies. */
+ ipoe_nl_delete_interfaces();
+ flush_sessions();
+ ipoe_nl_del_exclude(0);
+ ipoe_nl_del_net(0);
+
if (mcg_id == -1) {
log_warn("ipoe: unclassified packet handling is disabled\n");
rth.fd = -1;
@@ -660,11 +742,6 @@ static void init(void)
return;
}
- delete_sessions();
- ipoe_nl_del_exclude(0);
- ipoe_nl_del_net(0);
- ipoe_nl_delete_interfaces();
-
fcntl(rth.fd, F_SETFL, O_NONBLOCK);
fcntl(rth.fd, F_SETFD, fcntl(rth.fd, F_GETFD) | FD_CLOEXEC);