milan-avb: stabilization extras - MRP registrar-before-notify, scoped-fd cleanup, VLAN sub-iface RX, SET_NAME validation, entity/firmware/8ch, MEDIA_UNLOCK 100ms + seq-settle, MRP Leave on teardown, reset STREAM_INPUT counters on bind

This commit is contained in:
hackerman-kl 2026-05-31 15:05:27 +02:00 committed by Wim Taymans
parent 93e4957959
commit afc7724070
8 changed files with 482 additions and 134 deletions

View file

@ -5,6 +5,9 @@
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <linux/filter.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/if_link.h>
#include <linux/net_tstamp.h>
#include <limits.h>
#include <net/if.h>
@ -253,11 +256,187 @@ static int raw_transport_setup(struct server *server)
return 0;
}
/* milan-avb: hand-rolled netlink VLAN sub-iface creator.
*
* On I210-class NICs with rx-vlan-filter[fixed]=on the silicon drops
* VLAN-tagged frames whose VID is not registered. Registering happens
* implicitly when a VLAN sub-iface is added via RTM_NEWLINK. We create
* <parent>.<vid> on first use, never delete it (bounded leak: one
* sub-iface per SR class), and bind the listener stream socket to it.
*
* Fall back to PACKET_MR_PROMISC if any step fails (no CAP_NET_ADMIN,
* no 8021q module loaded, etc.). */
#define MILAN_NLALIGN(n) (((n) + 3U) & ~3U)
static int milan_nl_send(int fd, uint16_t mt, uint16_t flags,
uint32_t seq, const void *payload, size_t plen)
{
struct {
struct nlmsghdr nlh;
char body[2048];
} msg = { 0 };
size_t need = NLMSG_HDRLEN + plen;
if (need > sizeof(msg))
return -EMSGSIZE;
msg.nlh.nlmsg_len = need;
msg.nlh.nlmsg_type = mt;
msg.nlh.nlmsg_flags = flags;
msg.nlh.nlmsg_seq = seq;
msg.nlh.nlmsg_pid = 0;
if (plen)
memcpy(msg.body, payload, plen);
if (send(fd, &msg, need, 0) < 0)
return -errno;
return 0;
}
static int milan_nl_recv_ack(int fd, uint32_t seq)
{
char buf[4096];
for (;;) {
ssize_t n = recv(fd, buf, sizeof(buf), 0);
size_t off = 0;
if (n < 0)
return -errno;
while (off + NLMSG_HDRLEN <= (size_t)n) {
struct nlmsghdr *h = (struct nlmsghdr *)(buf + off);
if (h->nlmsg_len < NLMSG_HDRLEN ||
off + h->nlmsg_len > (size_t)n)
break;
if (h->nlmsg_type == NLMSG_ERROR && h->nlmsg_seq == seq) {
struct nlmsgerr *e = NLMSG_DATA(h);
return e->error;
}
off += NLMSG_ALIGN(h->nlmsg_len);
}
}
}
static size_t milan_nl_attr(char *p, uint16_t type, const void *val, uint16_t vlen)
{
struct rtattr *r = (struct rtattr *)p;
size_t total;
r->rta_len = RTA_LENGTH(vlen);
r->rta_type = type;
memcpy(RTA_DATA(r), val, vlen);
total = RTA_ALIGN(r->rta_len);
if (total > (size_t)r->rta_len)
memset(p + r->rta_len, 0, total - r->rta_len);
return total;
}
/* Returns ifindex (>0) or -errno. */
static int milan_get_ifindex(const char *name)
{
int fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
struct ifreq req;
int res, saved;
if (fd < 0)
return -errno;
spa_zero(req);
snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", name);
res = ioctl(fd, SIOCGIFINDEX, &req);
saved = -errno;
close(fd);
return res < 0 ? saved : req.ifr_ifindex;
}
/* Ensure <parent>.<vid> exists, is UP, and is a VLAN sub-iface of <parent>.
* Writes the sub-iface name to out and returns 0 on success. */
static int milan_ensure_vlan_iface(const char *parent_ifname, uint16_t vid,
char *out, size_t out_size)
{
int rc, existing, parent_idx, nlfd, err, new_idx;
struct sockaddr_nl sa = { .nl_family = AF_NETLINK };
char payload[256];
char *p = payload;
struct ifinfomsg ifi = { 0 };
uint32_t pidx;
char nested[64];
char *np = nested;
const char kind[] = "vlan";
char data[16];
char *dp = data;
uint16_t vidv = vid;
uint32_t seq = 1;
struct ifinfomsg up_ifi = { 0 };
if (vid == 0 || vid >= 4095)
return -EINVAL;
rc = snprintf(out, out_size, "%s.%u", parent_ifname, (unsigned)vid);
if (rc < 0 || (size_t)rc >= out_size)
return -ENAMETOOLONG;
/* If the sub-iface already exists, assume it is what we want and reuse. */
existing = milan_get_ifindex(out);
if (existing > 0)
return 0;
parent_idx = milan_get_ifindex(parent_ifname);
if (parent_idx <= 0)
return parent_idx ? parent_idx : -ENODEV;
nlfd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
if (nlfd < 0)
return -errno;
if (bind(nlfd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
int e = -errno; close(nlfd); return e;
}
/* RTM_NEWLINK: ifinfomsg + IFLA_IFNAME + IFLA_LINK + IFLA_LINKINFO{KIND=vlan, DATA{VLAN_ID}} */
ifi.ifi_family = AF_UNSPEC;
ifi.ifi_change = 0xFFFFFFFFu;
memcpy(p, &ifi, sizeof(ifi)); p += sizeof(ifi);
p += milan_nl_attr(p, IFLA_IFNAME, out, (uint16_t)(strlen(out) + 1));
pidx = (uint32_t)parent_idx;
p += milan_nl_attr(p, IFLA_LINK, &pidx, sizeof(pidx));
/* LINKINFO is nested: KIND=vlan, DATA={VLAN_ID=vid} */
np += milan_nl_attr(np, IFLA_INFO_KIND, kind, sizeof(kind));
dp += milan_nl_attr(dp, IFLA_VLAN_ID, &vidv, sizeof(vidv));
np += milan_nl_attr(np, IFLA_INFO_DATA, data, (uint16_t)(dp - data));
p += milan_nl_attr(p, IFLA_LINKINFO, nested, (uint16_t)(np - nested));
err = milan_nl_send(nlfd, RTM_NEWLINK,
NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK,
seq, payload, (size_t)(p - payload));
if (err == 0)
err = milan_nl_recv_ack(nlfd, seq);
if (err != 0 && err != -EEXIST) {
close(nlfd);
return err;
}
/* Bring it UP. */
new_idx = milan_get_ifindex(out);
if (new_idx <= 0) {
close(nlfd);
return new_idx ? new_idx : -ENODEV;
}
up_ifi.ifi_family = AF_UNSPEC;
up_ifi.ifi_index = new_idx;
up_ifi.ifi_flags = IFF_UP;
up_ifi.ifi_change = IFF_UP;
err = milan_nl_send(nlfd, RTM_NEWLINK, NLM_F_REQUEST | NLM_F_ACK,
++seq, &up_ifi, sizeof(up_ifi));
if (err == 0)
err = milan_nl_recv_ack(nlfd, seq);
close(nlfd);
if (err != 0)
return err;
return 0;
}
static int raw_stream_setup_socket(struct server *server, struct stream *stream)
{
int res;
char buf[128];
struct ifreq req;
const char *bind_ifname = server->ifname;
char vlan_ifname[IFNAMSIZ];
bool used_vlan_subiface = false;
spa_autoclose int fd = socket(AF_PACKET, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, htons(ETH_P_ALL));
if (fd < 0) {
@ -265,8 +444,30 @@ static int raw_stream_setup_socket(struct server *server, struct stream *stream)
return -errno;
}
/* For listener RX: route stream via a VLAN sub-iface so the NIC's
* hardware filter accepts VID-tagged AAF without promisc on parent. */
/* Listener-only: route stream RX via a VLAN sub-iface so the NIC accepts
* VID-tagged AAF without promisc on parent. OUTPUT direction stays on
* the parent because setup_pdu_milan_v12() already inserts a manual
* 802.1Q tag in the PDU header the kernel would add a second tag
* (QinQ) if we bound the talker socket to enp6s0.<vid>. */
if (stream->direction == SPA_DIRECTION_INPUT && stream->vlan_id > 0) {
int e = milan_ensure_vlan_iface(server->ifname,
(uint16_t)stream->vlan_id,
vlan_ifname, sizeof(vlan_ifname));
if (e == 0) {
bind_ifname = vlan_ifname;
used_vlan_subiface = true;
pw_log_info("milan-avb: listener RX via VLAN sub-iface %s (vid %d)",
vlan_ifname, stream->vlan_id);
} else {
pw_log_warn("milan-avb: VLAN sub-iface setup failed (%d), "
"falling back to PACKET_MR_PROMISC on parent", -e);
}
}
spa_zero(req);
snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", server->ifname);
snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", bind_ifname);
res = ioctl(fd, SIOCGIFINDEX, &req);
if (res < 0) {
pw_log_error("SIOCGIFINDEX %s failed: %m", server->ifname);
@ -319,6 +520,18 @@ static int raw_stream_setup_socket(struct server *server, struct stream *stream)
pw_log_error("setsockopt(ADD_MEMBERSHIP) failed: %m");
return -errno;
}
/* Fallback: lift promisc only when the VLAN sub-iface path didn't
* take. With the sub-iface, the NIC accepts VID 2 natively. */
if (!used_vlan_subiface) {
spa_zero(mreq);
mreq.mr_ifindex = req.ifr_ifindex;
mreq.mr_type = PACKET_MR_PROMISC;
res = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP,
&mreq, sizeof(struct packet_mreq));
if (res < 0)
pw_log_warn("setsockopt(PACKET_MR_PROMISC) fallback failed: %m");
}
}
return spa_steal_fd(fd);
}