From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: passt.top; dmarc=fail (p=none dis=none) header.from=mur.at Received: from efeu.mur.at (efeu.mur.at [IPv6:2a02:3e0::42]) by passt.top (Postfix) with ESMTPS id D73915A0265 for ; Sat, 07 Nov 2026 12:47:09 +0100 (CET) Received: from acer-laptop (lan1.raspi.ma39.ffgraz.net [10.12.1.243]) by efeu.mur.at (Postfix) with ESMTPSA id B5D3C46EAA; Sat, 7 Nov 2026 12:47:07 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=mur.at; s=dkim2; t=1794052027; bh=xybQ6+9SY2pkXwfCiBEQ3OPk2Ir79BItr8fETtOTbnA=; h=From:To:Cc:Subject:Date:From; b=GE8McXMJMYbmMd5FlI8o98CU2aXvVt8pnb7KSRSEALxKbc0Ohf/V+I2ohZ7OGPBOG 9QaDg32kmBH1dYSzp8M4YoxuK22gJpZu58VCWJmGM9MyspSsVgkzC2t21N6DAb1a3q 58phMPQl0L0BLRfsZsFA5KzekhPGm55XLV4jWSwC+RPuVH8XAURA/Dj5sslaKTd7MY PnaTG7/LOUGsomIUHdiu5rC5cJIIhMRqb+zruf6ZH5LvQGPz+V8xH3yFqJdzMoL23j jlXqkFWKQPpRTTC4tRTiqtSXrde1W8cpo/dIP+2FBQUu69E5gutojptC28IowXLggJ 87kIJL5LsWT+WoxM1nk2ZzyAW1MnjQ/jAZipYvZkE7qqpxXheX6db5Qz3yjalrGkNX 9pv/ZXHcAmxcoWiEnRyfUM5LVOIStfPhPqFogzbwUZvOlfx8uRhtWHAWac46+wYlfg Y0MW7UDnaAlLpF0zuSbmz9hGCCvnnm3DQ025j+OMBxrgxIsKaZZ From: Martin Schitter To: passt-dev@passt.top Subject: [PATCH] netlink: Fix nl_route_dup by 3-step processing Date: Sat, 7 Nov 2026 12:46:21 +0100 Message-ID: <20261107114621.804266-1-ms+git@mur.at> X-Mailer: git-send-email 2.53.0 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-MailFrom: ms+git@mur.at X-Mailman-Rule-Hits: nonmember-moderation X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation Message-ID-Hash: AOOPUE2LKNOMWPOEARYCPAGWHWPKK2ZO X-Message-ID-Hash: AOOPUE2LKNOMWPOEARYCPAGWHWPKK2ZO X-Mailman-Approved-At: Sat, 07 Nov 2026 13:03:28 +0100 CC: Martin Schitter X-Mailman-Version: 3.3.8 Precedence: list List-Id: Development discussion and patches for passt Archived-At: Archived-At: List-Archive: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Eliminate the route entries capacity limitations of nl_route_dup by copying them to the destination namespace in three successive steps to bypass dependency issues. This approach is inspired by the code design used in the restore subcommand of iproute2. Fixes: #221 Link: https://bugs.passt.top/show_bug.cgi?id=221 Signed-off-by: Martin Schitter --- netlink.c | 216 +++++++++++++++++++++++++++--------------------------- 1 file changed, 107 insertions(+), 109 deletions(-) diff --git a/netlink.c b/netlink.c index 225b666..d5c704b 100644 --- a/netlink.c +++ b/netlink.c @@ -525,6 +525,21 @@ int nl_route_set_def(int s, unsigned int ifi, sa_family_t af, const void *gw) return nl_do(s, &req, RTM_NEWROUTE, NLM_F_CREATE | NLM_F_EXCL, len); } +/** + * nl_rtattr_cmp() - Compare two routing attributes + * @rta1: Routing attribute + * @rta2: Routing attribute + * + * Return: 0 if equal + */ +static int nl_rtattr_cmp(const struct rtattr *rta1, const struct rtattr *rta2) +{ + if (!rta1 || !rta2 || rta1->rta_len != rta2->rta_len) + return 1; + + return memcmp(RTA_DATA(rta1), RTA_DATA(rta2), RTA_PAYLOAD(rta1)); +} + /** * nl_route_dup() - Copy routes for given interface and address family * @s_src: Netlink socket in source namespace @@ -535,56 +550,81 @@ int nl_route_set_def(int s, unsigned int ifi, sa_family_t af, const void *gw) * * Return: 0 on success, negative error code on failure */ -int nl_route_dup(int s_src, unsigned int ifi_src, - int s_dst, unsigned int ifi_dst, sa_family_t af) +int nl_route_dup(int s_src, unsigned int ifi_src, int s_dst, + unsigned int ifi_dst, sa_family_t af) { - struct req_t { - struct nlmsghdr nlh; - struct rtmsg rtm; - struct rtattr rta; - unsigned int ifi; - } req = { - .rtm.rtm_family = af, - .rtm.rtm_table = RT_TABLE_MAIN, - .rtm.rtm_scope = RT_SCOPE_UNIVERSE, - .rtm.rtm_type = RTN_UNICAST, - - .rta.rta_type = RTA_OIF, - .rta.rta_len = RTA_LENGTH(sizeof(unsigned int)), - .ifi = ifi_src, + /* Duplicate routes in correct order using 3 successive stages: */ + enum tasks { + DUP_LOCAL_ADDR, /* Duplicate local route address entries */ + DUP_LOCAL_NET, /* Duplicate local network entries */ + DUP_OTHER, /* Duplicate all other entries */ + NR_OF_TASKS, }; - ssize_t nlmsgs_size, left, status; - unsigned dup_routes = 0; - struct nlmsghdr *nh; - char buf[NLBUFSIZ]; - uint32_t seq; - unsigned i; - seq = nl_send(s_src, &req, RTM_GETROUTE, NLM_F_DUMP, sizeof(req)); + for (enum tasks task = DUP_LOCAL_ADDR; task < NR_OF_TASKS; task++) { + struct req_t { + struct nlmsghdr nlh; + struct rtmsg rtm; + struct rtattr rta; + unsigned int ifi; + } req = { + .rtm.rtm_family = af, + .rtm.rtm_table = RT_TABLE_MAIN, + .rtm.rtm_scope = RT_SCOPE_UNIVERSE, + .rtm.rtm_type = RTN_UNICAST, + + .rta.rta_type = RTA_OIF, + .rta.rta_len = RTA_LENGTH(sizeof(unsigned int)), + .ifi = ifi_src, + }; + struct nlmsghdr *nh; + char buf[NLBUFSIZ]; + ssize_t status; + uint32_t seq; + + seq = nl_send(s_src, &req, RTM_GETROUTE, NLM_F_DUMP, + sizeof(req)); + + nl_foreach(nh, status, s_src, buf, seq) { + struct rtmsg *rtm = (struct rtmsg *)NLMSG_DATA(nh); + uint16_t flags = nh->nlmsg_flags; + struct rtattr *rta; + size_t na; + int rc; - /* nl_foreach() will step through multiple response datagrams, - * which we don't want here because we need to have all the - * routes in the buffer at once. - */ - nh = nl_next(s_src, buf, NULL, &nlmsgs_size); - for (left = nlmsgs_size; - NLMSG_OK(nh, left) && (status = nl_status(nh, left, seq)) > 0; - nh = NLMSG_NEXT(nh, left)) { - struct rtmsg *rtm = (struct rtmsg *)NLMSG_DATA(nh); - bool discard = false; - struct rtattr *rta; - size_t na; + if (nh->nlmsg_type != RTM_NEWROUTE) + continue; - if (nh->nlmsg_type != RTM_NEWROUTE) - continue; + struct rtattr *tb[RTA_MAX + 1] = { 0 }; - /* nexthop state flags don't apply to freshly created routes, - * and the kernel will refuse our route if they are set. - */ - rtm->rtm_flags &= ~RTNH_COMPARE_MASK; + for (rta = RTM_RTA(rtm), na = RTM_PAYLOAD(nh); + RTA_OK(rta, na); rta = RTA_NEXT(rta, na)) { + tb[rta->rta_type] = rta; + } + + /* Duplicate only those routes that belong + * to the current processing stage. + * An approach inspired by iproute2's + * restore_handler() function. + * https://github.com/iproute2/iproute2/blob/e6471d7/ip/iproute.c#L2314 + */ + if (task == DUP_OTHER && !tb[RTA_GATEWAY]) + continue; + if (task == DUP_LOCAL_NET && + (tb[RTA_GATEWAY] || !tb[RTA_PREFSRC] || + !nl_rtattr_cmp(tb[RTA_PREFSRC], tb[RTA_DST]))) + continue; + if (task == DUP_LOCAL_ADDR && (tb[RTA_GATEWAY] || + (tb[RTA_PREFSRC] && + nl_rtattr_cmp(tb[RTA_PREFSRC], tb[RTA_DST])))) + continue; + + /* nexthop state flags don't apply to freshly created + * routes, and the kernel will refuse our route if they + * are set. + */ + rtm->rtm_flags &= ~RTNH_COMPARE_MASK; - for (rta = RTM_RTA(rtm), na = RTM_PAYLOAD(nh); RTA_OK(rta, na); - rta = RTA_NEXT(rta, na)) { /* RTA_OIF and RTA_MULTIPATH attributes carry the * identifier of a host interface. If they match the * host interface we're copying from, change them to @@ -595,103 +635,61 @@ int nl_route_dup(int s_src, unsigned int ifi_src, * available), or if any interface index in nexthop * objects differ from the host interface, discard the * route altogether. - */ - if (rta->rta_type == RTA_OIF) { - if (*(unsigned int *)RTA_DATA(rta) != ifi_src) { - discard = true; - break; - } - - *(unsigned int *)RTA_DATA(rta) = ifi_dst; - } else if (rta->rta_type == RTA_MULTIPATH) { - int nh_len = RTA_PAYLOAD(rta); + */ + if (tb[RTA_OIF]) { + if (*(unsigned int *)RTA_DATA(tb[RTA_OIF]) != + ifi_src) + continue; + *(unsigned int *)RTA_DATA(tb[RTA_OIF]) = + ifi_dst; + } else if (tb[RTA_MULTIPATH]) { + int nh_len = RTA_PAYLOAD(tb[RTA_MULTIPATH]); struct rtnexthop *rtnh; - - for (rtnh = (struct rtnexthop *)RTA_DATA(rta); + bool discard = false; + for (rtnh = (struct rtnexthop *) + RTA_DATA(tb[RTA_MULTIPATH]); RTNH_OK(rtnh, nh_len); rtnh = RTNH_NEXT_AND_DEC(rtnh, nh_len)) { int src = (int)ifi_src; - if (rtnh->rtnh_ifindex != src) { discard = true; break; } - rtnh->rtnh_ifindex = ifi_dst; } - if (discard) - break; - } else if (rta->rta_type == RTA_PREFSRC || - rta->rta_type == RTA_NH_ID) { + continue; + } else if (tb[RTA_PREFSRC]) { /* Strip RTA_PREFSRC attributes: host routes * might include a preferred source address, * which must be one of the host's addresses. * However, with -a, pasta will use a different * namespace address, making such a route * invalid in the namespace. - * - * Strip RTA_NH_ID attributes: host routes set + */ + tb[RTA_PREFSRC]->rta_type = RTA_UNSPEC; + } else if (tb[RTA_NH_ID]) { + /* Strip RTA_NH_ID attributes: host routes set * up via routing protocols (e.g. OSPF) might * contain a nexthop ID (and not nexthop * objects, which are taken care of in the * RTA_MULTIPATH case above) that's not valid * in the target namespace. */ - rta->rta_type = RTA_UNSPEC; + tb[RTA_NH_ID]->rta_type = RTA_UNSPEC; } - } - - if (discard) - nh->nlmsg_type = NLMSG_NOOP; - else - dup_routes++; - } - - if (!NLMSG_OK(nh, left)) { - /* Process any remaining datagrams in a different - * buffer so we don't overwrite the first one. - */ - char tail[NLBUFSIZ]; - unsigned extra = 0; - - nl_foreach_oftype(nh, status, s_src, tail, seq, RTM_NEWROUTE) - extra++; - - if (extra) { - err("netlink: Too many routes to duplicate"); - return -E2BIG; - } - } - if (status < 0) - return status; - - /* Routes might have dependencies between each other, and the kernel - * processes RTM_NEWROUTE messages sequentially. For n routes, we might - * need to send the requests up to n times to get all of them inserted. - * Routes that have been already inserted will return -EEXIST, but we - * can safely ignore that and repeat the requests. This avoids the need - * to calculate dependencies: let the kernel do that. - */ - for (i = 0; i < dup_routes; i++) { - for (nh = (struct nlmsghdr *)buf, left = nlmsgs_size; - NLMSG_OK(nh, left); - nh = NLMSG_NEXT(nh, left)) { - uint16_t flags = nh->nlmsg_flags; - int rc; - - if (nh->nlmsg_type != RTM_NEWROUTE) - continue; rc = nl_do(s_dst, nh, RTM_NEWROUTE, - (flags & ~NLM_F_DUMP_FILTERED) | NLM_F_CREATE, - nh->nlmsg_len); + (flags & ~NLM_F_DUMP_FILTERED) | + NLM_F_CREATE, + nh->nlmsg_len); if (rc < 0 && rc != -EEXIST && rc != -ENETUNREACH && rc != -EHOSTUNREACH) return rc; } + if (status < 0) + return status; } - return 0; } -- 2.53.0