lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <c042ed8271d86fbea62807c3dbeaddb8e9b80810.1704449760.git.ante.knezic@helmholz.de>
Date: Fri, 5 Jan 2024 11:46:16 +0100
From: Ante Knezic <ante.knezic@...mholz.de>
To: <netdev@...r.kernel.org>
CC: <andrew@...n.ch>, <f.fainelli@...il.com>, <olteanv@...il.com>,
	<davem@...emloft.net>, <edumazet@...gle.com>, <kuba@...nel.org>,
	<pabeni@...hat.com>, <ante.knezic@...mholz.de>
Subject: [RFC PATCH net-next 3/6] net: dsa: implement cross chip port mirroring

Cross-chip port mirroring requires creating mirroring
segments on each switch that is acting as a part of the
mirroring route, from source to destination mirroring port.
For example, following configuration:

  SW0            SW1           SW2           SW3
+-------+     +-------+     +-------+     +--------+
|     P9|<--->|P10  P9|<--->|P10  P9|<--->|P10     |
|       |     |       |     |       |     |        |
+-------+     +-------+     +----+--+     +--------+
    ^                            |
    |                            v
   P2                           P8

needs dsa tree devices to be configured as follows:
  ----------------------------------------
  |    |     source         destination  |
  | SW |  mirror port       mirror port  |
  ----------------------------------------
  |  0 |   P2          ->      P9        |
  |  1 |   P10         ->      P9        |
  |  2 |   P10         ->      P8        |
  |  3 |               ->                |
  ----------------------------------------

This means that request for adding port mirroring needs to
be propagated to the entire dsa switch tree as we can have
mirroring path in which all switches need to be setup.
This is achieved through use of switch event notifiers.
When adding port mirroring, first step is to create a mirroring
route which will contain source and destination ports for each
switch in the route. Then, this route is passed on to each switch
where it is evaluated for mirror settings for this particular
switch. If a switch is contained inside the route, its source
and destination ports are passed on to ds->ops->port_mirror_add()
normally.
Similar principle applies when removing port mirroring, where
complete mirroring route needs to be extracted from dsa_tree
mirrors list.

Signed-off-by: Ante Knezic <ante.knezic@...mholz.de>
---
 net/dsa/switch.c |  52 +++++++++++++++++
 net/dsa/switch.h |   8 +++
 net/dsa/user.c   | 174 ++++++++++++++++++++++++++++++++++++++++++++++++++-----
 3 files changed, 219 insertions(+), 15 deletions(-)

diff --git a/net/dsa/switch.c b/net/dsa/switch.c
index 3d2feeea897b..5a81742cb139 100644
--- a/net/dsa/switch.c
+++ b/net/dsa/switch.c
@@ -125,6 +125,52 @@ static int dsa_switch_bridge_leave(struct dsa_switch *ds,
 	return 0;
 }
 
+static int dsa_switch_mirror_add(struct dsa_switch *ds,
+				 struct dsa_notifier_mirror_info *info)
+{
+	struct dsa_route *dr;
+	struct dsa_port *dp;
+	bool ingress;
+	int to_port;
+
+	list_for_each_entry(dr, &info->mirror->route, list) {
+		if (ds->index == dr->sw_index) {
+			ingress = info->mirror->ingress;
+			dp = dsa_to_port(ds, dr->to_local_p);
+			to_port = dp->index;
+
+			return ds->ops->port_mirror_add(ds, dr->from_local_p,
+							to_port, ingress,
+							info->extack);
+		}
+	}
+
+	return 0;
+}
+
+static int dsa_switch_mirror_del(struct dsa_switch *ds,
+				 struct dsa_notifier_mirror_info *info)
+{
+	struct dsa_route *dr;
+	struct dsa_port *dp;
+	bool ingress;
+	int to_port;
+
+	/* check if switch is a part of the route we are trying to delete */
+	list_for_each_entry(dr, &info->mirror->route, list) {
+		if (ds->index == dr->sw_index) {
+			ingress = info->mirror->ingress;
+			dp = dsa_to_port(ds, dr->to_local_p);
+			to_port = dp->index;
+
+			ds->ops->port_mirror_del(ds, dr->from_local_p,
+						 to_port, ingress);
+		}
+	}
+
+	return 0;
+}
+
 /* Matches for all upstream-facing ports (the CPU port and all upstream-facing
  * DSA links) that sit between the targeted port on which the notifier was
  * emitted and its dedicated CPU port.
@@ -1059,6 +1105,12 @@ static int dsa_switch_event(struct notifier_block *nb,
 	case DSA_NOTIFIER_CONDUIT_STATE_CHANGE:
 		err = dsa_switch_conduit_state_change(ds, info);
 		break;
+	case DSA_NOTIFIER_MIRROR_ADD:
+		err = dsa_switch_mirror_add(ds, info);
+		break;
+	case DSA_NOTIFIER_MIRROR_DEL:
+		err = dsa_switch_mirror_del(ds, info);
+		break;
 	default:
 		err = -EOPNOTSUPP;
 		break;
diff --git a/net/dsa/switch.h b/net/dsa/switch.h
index be0a2749cd97..9fa52f5bc11d 100644
--- a/net/dsa/switch.h
+++ b/net/dsa/switch.h
@@ -35,6 +35,8 @@ enum {
 	DSA_NOTIFIER_TAG_8021Q_VLAN_ADD,
 	DSA_NOTIFIER_TAG_8021Q_VLAN_DEL,
 	DSA_NOTIFIER_CONDUIT_STATE_CHANGE,
+	DSA_NOTIFIER_MIRROR_ADD,
+	DSA_NOTIFIER_MIRROR_DEL,
 };
 
 /* DSA_NOTIFIER_AGEING_TIME */
@@ -111,6 +113,12 @@ struct dsa_notifier_conduit_state_info {
 	bool operational;
 };
 
+/* DSA_NOTIFIER_MIRROR_ADD */
+struct dsa_notifier_mirror_info {
+	const struct dsa_mirror *mirror;
+	struct netlink_ext_ack *extack;
+};
+
 struct dsa_vlan *dsa_vlan_find(struct list_head *vlan_list,
 			       const struct switchdev_obj_port_vlan *vlan);
 
diff --git a/net/dsa/user.c b/net/dsa/user.c
index ce73d0a5140d..7f705ae8633b 100644
--- a/net/dsa/user.c
+++ b/net/dsa/user.c
@@ -1359,6 +1359,89 @@ dsa_user_mall_tc_entry_find(struct net_device *dev, unsigned long cookie)
 	return NULL;
 }
 
+static int dsa_route_add_segment(int sw_index, int from_port,
+				 int to_port, struct list_head *route)
+{
+	struct dsa_route *nr;
+
+	nr = kzalloc(sizeof(*nr), GFP_KERNEL);
+	if (!nr)
+		return -ENOMEM;
+
+	nr->sw_index = sw_index;
+	nr->from_local_p = from_port;
+	nr->to_local_p = to_port;
+	list_add(&nr->list, route);
+	return 0;
+}
+
+static int dsa_route_create(struct dsa_port *from_dp, struct dsa_port *to_dp,
+			    struct dsa_port *iter, struct list_head *route)
+{
+	struct dsa_switch_tree *dst = from_dp->ds->dst;
+	struct dsa_route *dr;
+	struct dsa_link *dl;
+	int err;
+
+	if (from_dp->ds == to_dp->ds)
+		return dsa_route_add_segment(from_dp->ds->index, from_dp->index,
+					     to_dp->index, route);
+
+	/* Assign iter to final "to port" on first entry */
+	if (!iter)
+		iter = to_dp;
+
+	list_for_each_entry(dl, &dst->rtable, list) {
+		if (dl->dp->ds != iter->ds)
+			continue;
+
+		dr = list_first_entry_or_null(route, struct dsa_route, list);
+
+		if (!dr || dr->sw_index != dl->link_dp->ds->index) {
+			err = dsa_route_add_segment(dl->dp->ds->index, dl->dp->index,
+						    dr ? iter->index : to_dp->index,
+						    route);
+			if (err)
+				return err;
+
+			/* have we reached the final "from" device */
+			if (dl->link_dp->ds == from_dp->ds)
+				return dsa_route_add_segment(dl->link_dp->ds->index,
+							     from_dp->index,
+							     dl->link_dp->index, route);
+
+			err = dsa_route_create(from_dp, to_dp, dl->link_dp, route);
+			if (err <= 0)
+				return err;
+		}
+	}
+
+	dr = list_first_entry_or_null(route, struct dsa_route, list);
+	if (dr) {
+		list_del(&dr->list);
+		kfree(dr);
+	}
+
+	return 1;
+}
+
+static struct dsa_mirror *dsa_tree_find_mirror(struct dsa_switch_tree *dst,
+					       struct dsa_port *from_dp,
+					       struct dsa_mall_mirror_tc_entry *mirror)
+{
+	struct dsa_mirror *dm;
+
+	list_for_each_entry(dm, &dst->mirrors, list) {
+		if (dm->ingress != mirror->ingress)
+			continue;
+
+		if (dm->from_dp == from_dp && dm->to_dp == mirror->to_port)
+			return dm;
+	}
+
+	return NULL;
+}
+
 static int
 dsa_user_add_cls_matchall_mirred(struct net_device *dev,
 				 struct tc_cls_matchall_offload *cls,
@@ -1369,9 +1452,12 @@ dsa_user_add_cls_matchall_mirred(struct net_device *dev,
 	struct dsa_user_priv *p = netdev_priv(dev);
 	struct dsa_mall_mirror_tc_entry *mirror;
 	struct dsa_mall_tc_entry *mall_tc_entry;
+	struct dsa_notifier_mirror_info info;
 	struct dsa_switch *ds = dp->ds;
 	struct flow_action_entry *act;
+	struct dsa_route *dr, *n;
 	struct dsa_port *to_dp;
+	struct dsa_mirror *dm;
 	int err;
 
 	if (!ds->ops->port_mirror_add)
@@ -1389,6 +1475,10 @@ dsa_user_add_cls_matchall_mirred(struct net_device *dev,
 	if (!dsa_user_dev_check(act->dev))
 		return -EOPNOTSUPP;
 
+	to_dp = dsa_user_to_port(act->dev);
+	if (ds->dst != to_dp->ds->dst)
+		return -EINVAL;
+
 	mall_tc_entry = kzalloc(sizeof(*mall_tc_entry), GFP_KERNEL);
 	if (!mall_tc_entry)
 		return -ENOMEM;
@@ -1396,20 +1486,53 @@ dsa_user_add_cls_matchall_mirred(struct net_device *dev,
 	mall_tc_entry->cookie = cls->cookie;
 	mall_tc_entry->type = DSA_PORT_MALL_MIRROR;
 	mirror = &mall_tc_entry->mirror;
-
-	to_dp = dsa_user_to_port(act->dev);
-
 	mirror->to_port = to_dp;
 	mirror->ingress = ingress;
 
-	err = ds->ops->port_mirror_add(ds, dp->index, to_dp->index, ingress, extack);
-	if (err) {
-		kfree(mall_tc_entry);
-		return err;
+	if (dsa_tree_find_mirror(ds->dst, dp, mirror)) {
+		err = -EINVAL;
+		goto err_mirror;
+	}
+
+	dm = kzalloc(sizeof(*dm), GFP_KERNEL);
+	if (!dm) {
+		err = -ENOMEM;
+		goto err_mirror;
 	}
 
+	INIT_LIST_HEAD(&dm->route);
+	dm->from_dp = dp;
+	dm->to_dp = to_dp;
+	dm->ingress = ingress;
+
+	if (dsa_route_create(dp, to_dp, NULL, &dm->route)) {
+		err = -EINVAL;
+		goto err_route;
+	}
+
+	info.mirror = dm;
+	info.extack = extack;
+
+	err = dsa_tree_notify(ds->dst, DSA_NOTIFIER_MIRROR_ADD, &info);
+	if (err)
+		goto err_route;
+
+	list_add(&dm->list, &ds->dst->mirrors);
 	list_add_tail(&mall_tc_entry->list, &p->mall_tc_list);
 
+	return 0;
+
+err_route:
+	dsa_tree_notify(ds->dst, DSA_NOTIFIER_MIRROR_DEL, &info);
+
+	list_for_each_entry_safe(dr, n, &dm->route, list) {
+		list_del(&dr->list);
+		kfree(dr);
+	}
+	kfree(dm);
+err_mirror:
+	kfree(mall_tc_entry);
+
 	return err;
 }
 
@@ -1491,6 +1614,33 @@ static int dsa_user_add_cls_matchall(struct net_device *dev,
 	return err;
 }
 
+static void dsa_user_mirror_del(struct dsa_switch *ds, struct dsa_port *dp,
+				struct dsa_mall_mirror_tc_entry *mirror)
+{
+	struct dsa_notifier_mirror_info info;
+	struct dsa_route *dr, *n;
+	struct dsa_mirror *dm;
+
+	dm = dsa_tree_find_mirror(ds->dst, dp, mirror);
+
+	if (!dm) {
+		netdev_err(dp->user, "failed to delete mirror\n");
+		return;
+	}
+
+	info.mirror = dm;
+
+	dsa_tree_notify(ds->dst, DSA_NOTIFIER_MIRROR_DEL, &info);
+
+	list_for_each_entry_safe(dr, n, &dm->route, list) {
+		list_del(&dr->list);
+		kfree(dr);
+	}
+
+	list_del(&dm->list);
+	kfree(dm);
+}
+
 static void dsa_user_del_cls_matchall(struct net_device *dev,
 				      struct tc_cls_matchall_offload *cls)
 {
@@ -1506,14 +1656,8 @@ static void dsa_user_del_cls_matchall(struct net_device *dev,
 
 	switch (mall_tc_entry->type) {
 	case DSA_PORT_MALL_MIRROR:
-		if (ds->ops->port_mirror_del) {
-			struct dsa_mall_mirror_tc_entry *mirror;
-
-			mirror = &mall_tc_entry->mirror;
-			ds->ops->port_mirror_del(ds, dp->index,
-						 mirror->to_port->index,
-						 mirror->ingress);
-		}
+		if (ds->ops->port_mirror_del)
+			dsa_user_mirror_del(ds, dp, &mall_tc_entry->mirror);
 		break;
 	case DSA_PORT_MALL_POLICER:
 		if (ds->ops->port_policer_del)
-- 
2.11.0


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ