From 1c0caf15d6ccf807ebd74360c34e0404483e4f76 Mon Sep 17 00:00:00 2001
From: Anjan Chanda <anjan.chanda@iopsys.eu>
Date: Thu, 15 Jun 2023 10:58:27 +0200
Subject: [PATCH] decide on neigh's reachability from received arp response

---
 src/hostmngr.c | 131 ++++++++++++++++++++++++++++++++++++++++++++++++-
 src/neigh.c    |  65 +++++++++++++++++++++++-
 src/neigh.h    |   3 ++
 src/netlink.c  |  21 +++++---
 src/topology.h |  18 ++++++-
 src/ubus.c     |  16 +++---
 6 files changed, 234 insertions(+), 20 deletions(-)

diff --git a/src/hostmngr.c b/src/hostmngr.c
index fed6a85..0fdf5ea 100644
--- a/src/hostmngr.c
+++ b/src/hostmngr.c
@@ -12,12 +12,16 @@
 #include <stdlib.h>
 #include <stdint.h>
 #include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <sys/ioctl.h>
 #include <net/if_arp.h>
 #include <net/if.h>
 #include <arpa/inet.h>
+#include <linux/if_packet.h>
+#include <net/ethernet.h>
 
 #include <netlink/route/link.h>
 
@@ -76,6 +80,7 @@ void enum_interfaces_cb(struct nl_object *obj, void *arg)
 	strncpy(iface->ifname, rtnl_link_get_name(link), 16);
 	iface->ifindex = rtnl_link_get_ifindex(link);
 	iface->exclude = 1;
+	iface->priv = NULL;
 
 	a = rtnl_link_get_addr(link);
 	if (nl_addr_get_len(a) == 6)
@@ -112,6 +117,8 @@ void enum_interfaces_cb(struct nl_object *obj, void *arg)
 		iface->mediatype = IF_MEDIA_ETH;
 	}
 
+	topology_register_arp_sock(iface->ifname, &iface->arpsk, iface);
+
 	list_add_tail(&iface->list, argp->iflist);
 	*argp->n += 1;
 
@@ -158,6 +165,18 @@ int topology_enumerate_interfaces(struct topologyd_private *priv)
 	return enum_interfaces(&priv->iflist, &priv->num_local_interfaces);
 }
 
+static void topology_init_interface_private(struct topologyd_private *priv)
+{
+	struct local_interface *e;
+
+	if (priv->num_local_interfaces == 0 || list_empty(&priv->iflist))
+		return;
+
+	list_for_each_entry(e, &priv->iflist, list) {
+		e->priv = priv;
+	}
+}
+
 void topology_print_interfaces(struct topologyd_private *priv)
 {
 	struct local_interface *e;
@@ -452,6 +471,102 @@ int topology_register_notifier_file(struct topologyd_private *priv, const char *
 	return 0;
 }
 
+static void recv_arp_cb(struct uloop_fd *u, unsigned int events)
+{
+	struct arp_sock *sk = container_of(u, struct arp_sock, uloop);
+	struct local_interface *iface = (struct local_interface *)sk->priv;
+
+	if (u->error) {
+               //int ret = -1;
+               //socklen_t ret_len = sizeof(ret);
+
+		dbg("%s: - error -\n", __func__);
+               u->error = false;
+               /*
+               if (e->error_cb &&
+                   getsockopt(u->fd, SOL_SOCKET, SO_ERROR, &ret, &ret_len) == 0) {
+                       e->error_cb(e, ret);
+               }
+	       */
+	}
+
+	for (;;) {
+		char buf[512] = {0};
+		ssize_t len;
+		struct ethhdr *eh;
+
+		len = read(u->fd, buf, sizeof(buf));
+		if (len == -1) {
+			//dbg("%s: len = %d (%s)\n", __func__, len, strerror(errno));
+			return;
+		}
+
+		if (len <= sizeof(struct ethhdr)) {
+			return;
+		}
+
+		eh = (struct ethhdr *)buf;
+		if (ntohs(eh->h_proto) == ETH_P_ARP) {
+			struct topologyd_private *priv = (struct topologyd_private *)iface->priv;
+
+			dbg("ARP received from " MACFMT"\n", MAC2STR(eh->h_source));
+			neigh_mark_reachable(&priv->neigh_q, eh->h_source, iface->ifname);
+		}
+	}
+}
+
+int topology_register_arp_sock(const char *ifname, struct arp_sock *arpsk, void *priv)
+{
+	struct sockaddr_ll sa;
+	int ifindex;
+	int flags;
+	int s;
+
+	if (!ifname || !arpsk)
+		return -1;
+
+	s = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ARP));
+	if (s < 0)
+		return -1;
+
+	flags = fcntl(s, F_GETFL, 0);
+	if (flags != -1)
+		fcntl(s, F_SETFL, flags | O_NONBLOCK);
+
+	ifindex = if_nametoindex(ifname);
+	if (!ifindex) {
+		close(s);
+		return -1;
+	}
+
+	memset(&sa, 0, sizeof(struct sockaddr_ll));
+	sa.sll_family = AF_PACKET;
+	sa.sll_protocol = htons(ETH_P_ARP);
+	sa.sll_ifindex = ifindex;
+
+	if (bind(s, (struct sockaddr *)&sa, sizeof(struct sockaddr_ll)) < 0) {
+		err("Failed to bind arp sock\n");
+		close(s);
+		return -1;
+	}
+
+	arpsk->uloop.fd = s;
+	arpsk->uloop.cb = recv_arp_cb;
+	uloop_fd_add(&arpsk->uloop, ULOOP_READ);
+	arpsk->priv = priv;
+
+	return 0;
+}
+
+void topology_unregister_arp_sock(struct arp_sock *arpsk)
+{
+	if (arpsk) {
+		uloop_fd_delete(&arpsk->uloop);
+		close(arpsk->uloop.fd);
+		arpsk->priv = NULL;
+	}
+}
+
 char *topology_brport_to_ifname(struct topologyd_private *priv, uint16_t brport)
 {
 	struct local_interface *iface;
@@ -488,12 +603,22 @@ struct local_interface *topologyd_ifname_to_interface(struct topologyd_private *
 }
 
 int topology_handle_ethport_carrier_off(struct topologyd_private *priv,
-					const char *ifname, int off)
+					const char *ifname)
+{
+	if (!priv || !ifname)
+		return -1;
+
+	neigh_set_unreachable(&priv->neigh_q, ifname, 1);
+	return 0;
+}
+
+int topology_handle_ethport_carrier_on(struct topologyd_private *priv,
+				       const char *ifname)
 {
 	if (!priv || !ifname)
 		return -1;
 
-	neigh_set_unreachable(&priv->neigh_q, ifname, off);
+	neigh_probe_unreachable(&priv->neigh_q, ifname);
 	return 0;
 }
 
@@ -554,6 +679,7 @@ int topologyd_init_private(struct topologyd_private *priv, void *useropts)
 	if (!ret)
 		topology_print_interfaces(priv);
 
+	topology_init_interface_private(priv);
 	topology_include_interface(priv, DEFAULT_LAN_IFNAME);
 	topology_exclude_interfaces(priv, useropts);
 
@@ -565,6 +691,7 @@ int topologyd_init_private(struct topologyd_private *priv, void *useropts)
 	topology_get_interface_neighbors(priv);
 	topology_get_neighbors_hostname(priv);
 
+	//topology_register_arp_sock("br-lan", &priv->arpsk, priv);
 	topology_register_notifier_file(priv, "/var/dhcp.leases");
 
 	nfct_get_entries_nolo(priv);
diff --git a/src/neigh.c b/src/neigh.c
index abe661e..db09db5 100644
--- a/src/neigh.c
+++ b/src/neigh.c
@@ -649,7 +649,7 @@ void neigh_set_unreachable(void *nq, const char *ifname, int val)
 				//struct neigh_history_entry *he = NULL;
 
 				e->unreachable = val;
-				dbg(MACFMT " marking it %sreachable\n",
+				err(MACFMT " marking it %sreachable\n",
 				    MAC2STR(e->macaddr), val ? "un" : "");
 
 				/*
@@ -665,6 +665,69 @@ void neigh_set_unreachable(void *nq, const char *ifname, int val)
 	}
 }
 
+void neigh_mark_reachable(void *nq, uint8_t *macaddr, const char *ifname)
+{
+	struct neigh_queue *q = (struct neigh_queue *)nq;
+	struct neigh_entry *e = NULL;
+	int idx = 0;
+
+	if (!macaddr || hwaddr_is_zero(macaddr) || !ifname)
+		return;
+
+	for (idx = 0; idx < NEIGH_ENTRIES_MAX; idx++) {
+		if (hlist_empty(&q->table[idx]))
+			continue;
+
+		hlist_for_each_entry(e, &q->table[idx], hlist) {
+			if (hwaddr_equal(e->macaddr, macaddr)) {
+				//struct neigh_history_entry *he = NULL;
+
+				e->unreachable = 0;
+				err("Marking " MACFMT " reachable through %s\n",
+				    MAC2STR(e->macaddr), ifname);
+
+				memset(e->ifname, 0, sizeof(e->ifname));
+				strncpy(e->ifname, ifname, strlen(ifname));
+				/*
+				he = neigh_history_lookup(nq, e->macaddr);
+				if (he) {
+					dbg("Update history last-seen time\n");
+					time(&he->lastseen);
+					he->alive = false;
+				}
+				*/
+			}
+		}
+	}
+}
+
+void neigh_probe_unreachable(void *nq, const char *ifname)
+{
+	struct neigh_queue *q = (struct neigh_queue *)nq;
+	struct neigh_entry *e = NULL;
+	int idx = 0;
+
+	for (idx = 0; idx < NEIGH_ENTRIES_MAX; idx++) {
+		if (hlist_empty(&q->table[idx]))
+			continue;
+
+		hlist_for_each_entry(e, &q->table[idx], hlist) {
+			if (e->unreachable) {
+				char cmd[256] = {0};
+				char ipbuf[46] = {0};
+
+				if (e->ipv4.addr.ip4.s_addr == INADDR_ANY)
+					continue;
+
+				inet_ntop(e->ipv4.family, &e->ipv4.addr, ipbuf, sizeof(ipbuf));
+				dbg("arping %s ...\n", ipbuf);
+				snprintf(cmd, 255, "arping -I %s -c 1 -w 1 -f %s &", "br-lan" /* ifname */, ipbuf);	//FIXME
+				runCmd(cmd); /* Flawfinder: ignore */
+			}
+		}
+	}
+}
+
 int neigh_set_type(void *nq, uint8_t *macaddr, enum neigh_type type)
 {
 	struct neigh_entry *e = NULL;
diff --git a/src/neigh.h b/src/neigh.h
index e5f6803..e3d34c4 100644
--- a/src/neigh.h
+++ b/src/neigh.h
@@ -151,6 +151,9 @@ extern void neigh_queue_flush(void *q);
 struct neigh_entry *neigh_queue_print(void *q);
 
 void neigh_set_unreachable(void *nq, const char *ifname, int val);
+void neigh_probe_unreachable(void *nq, const char *ifname);
+void neigh_mark_reachable(void *nq, uint8_t *macaddr, const char *ifname);
+
 int neigh_set_type(void *q, uint8_t *macaddr, enum neigh_type type);
 uint16_t neigh_get_brport(void *q, uint8_t *macaddr);
 int neigh_set_1905(void *q, uint8_t *macaddr);
diff --git a/src/netlink.c b/src/netlink.c
index 67551c1..9c2947c 100644
--- a/src/netlink.c
+++ b/src/netlink.c
@@ -215,8 +215,10 @@ static int topologyd_handle_neigh_tbl_change(struct topologyd_private *priv, boo
 	}
 
 	/* update bridge port_nos on which the hosts are last seen */
-	if (if_isbridge(ifname))
+	if (if_isbridge(ifname)) {
+		dbg("%s: update brport number\n", __func__);
 		topologyd_update_neigh_brport(priv, ifname);
+	}
 
 #if 1	//def NEIGH_DEBUG
 	if (priv->neigh_q.pending_cnt > 0) {
@@ -266,8 +268,8 @@ static int topologyd_handle_nlevents_neigh(struct topologyd_private *priv,
 	if (!iface || iface->exclude)
 		return NL_SKIP;
 
-	dbg("%s: Neigh " MACFMT " ip = %s on %s, state = %s\n",
-		__func__, MAC2STR(macaddr), ipbuf, ifname,
+	dbg("%s: [ %s ] Neigh " MACFMT " ip = %s on %s, state = %s\n",
+	     __func__, add ? "NEW" : "DEL", MAC2STR(macaddr), ipbuf, ifname,
 		rtnl_neigh_state2str(ndm->ndm_state, state, sizeof(state)));
 
 
@@ -307,10 +309,13 @@ static int topologyd_handle_nlevents_link(struct topologyd_private *priv,
 	if (iface)
 		iface->ifflags = ifi->ifi_flags;
 
-	if (if_isbridge(ifname)) {
-		dbg("%s: %s: %s (ifindex = %d) bridge\n", __func__,
-		    add ? "NEWLINK" : "DELLINK", ifname, ifi->ifi_index);
+	char flagstr[256] = {0};
+	rtnl_link_flags2str(ifi->ifi_flags, flagstr, sizeof(flagstr));
 
+	err("%s: %s: %s (ifindex = %d) family = %d, flags = 0x%x %s --------->\n", __func__,
+	    add ? "NEWLINK" : "DELLINK", ifname, ifi->ifi_index, ifi->ifi_family, ifi->ifi_flags, flagstr);
+
+	if (if_isbridge(ifname)) {
 #if 0	//TODO
 		if (topologyd_lookup_interface_in_config(priv, ifname)) {
 			struct topologyd_interface *ifs;
@@ -347,11 +352,11 @@ static int topologyd_handle_nlevents_link(struct topologyd_private *priv,
 		return NL_SKIP;
 	}
 
-	dbg("%s: %s : %s (" MACFMT ", %d), master = %d, fam = %d, flags = 0x%x\n",
+	dbg("%s: %s : %s (" MACFMT ", %d), master = %d, fam = %d, flags = 0x%x %s\n",
 	    __func__, add ? "NEWLINK" : "DELLINK",
 	    ifname, MAC2STR(macaddr), ifi->ifi_index,
 	    br_ifindex, ifi->ifi_family,
-	    ifi->ifi_flags);
+	    ifi->ifi_flags, flagstr);
 
 
 #if 0	//TODO
diff --git a/src/topology.h b/src/topology.h
index cd51c81..ca453e7 100644
--- a/src/topology.h
+++ b/src/topology.h
@@ -10,6 +10,16 @@
 #ifndef TOPOLOGY_H
 #define TOPOLOGY_H
 
+
+struct arp_sock {
+	struct uloop_fd uloop;
+	void *priv;
+};
+
+int topology_register_arp_sock(const char *ifname, struct arp_sock *arpsk, void *priv);
+void topology_unregister_arp_sock(struct arp_sock *arpsk);
+
+
 struct i1905_metric {
 	bool br_present;
 	uint32_t tx_errors;
@@ -193,6 +203,8 @@ struct local_interface {
 	uint8_t sizeof_mediainfo;
 	uint8_t *mediainfo;            /**< media specific data */
 
+	void *priv;
+	struct arp_sock arpsk;
 	struct list_head list;
 };
 
@@ -264,6 +276,8 @@ struct topologyd_private {
 	struct ubus_context *bus;
 	struct ubus_object obj;
 	struct ubus_event_handler evh;
+
+	struct arp_sock arpsk;
 };
 
 
@@ -299,8 +313,8 @@ static inline struct node *topology_get_selfdevice(struct topology_datamodel *dm
 char *topology_brport_to_ifname(struct topologyd_private *priv, uint16_t brport);
 struct local_interface *topologyd_ifname_to_interface(struct topologyd_private *priv, const char *ifname);
 
-int topology_handle_ethport_carrier_off(struct topologyd_private *priv,
-					const char *ifname, int off);
+int topology_handle_ethport_carrier_off(struct topologyd_private *priv, const char *ifname);
+int topology_handle_ethport_carrier_on(struct topologyd_private *priv, const char *ifname);
 
 struct node *alloc_node(uint8_t *macaddr);
 void free_node(struct node *n);
diff --git a/src/ubus.c b/src/ubus.c
index 4d21ce8..b6036a5 100644
--- a/src/ubus.c
+++ b/src/ubus.c
@@ -366,12 +366,13 @@ int topologyd_ubus_show_hosts(struct ubus_context *ctx, struct ubus_object *obj,
 			uint16_t brport;
 			void *wt;
 
-			if (e->unreachable)
-				continue;
+			//if (e->unreachable)
+			//	continue;
 
 			neigh_update_ip_entry_stats(p, &e->ipv4, e);
 
 			aa = blobmsg_open_table(&bb, "");
+			blobmsg_add_u8(&bb, "reachable", e->unreachable ? false : true);
 			hwaddr_ntoa(e->macaddr, macstr);
 			blobmsg_add_string(&bb, "macaddress", macstr);
 			blobmsg_add_string(&bb, "hostname", e->hostname);
@@ -797,7 +798,7 @@ static void topologyd_ethport_event_handler(struct topologyd_private *p,
 	};
 	char ifname[16] = {0}, link[8] = {0};
 	struct blob_attr *tb[4];
-	bool up, down;
+	int down;
 
 
 	blobmsg_parse(ev_attr, 4, tb, blob_data(msg), blob_len(msg));
@@ -806,12 +807,13 @@ static void topologyd_ethport_event_handler(struct topologyd_private *p,
 
 	strncpy(ifname,	blobmsg_data(tb[0]), sizeof(ifname) - 1);
 	strncpy(link, blobmsg_data(tb[1]), sizeof(link) - 1);
-
-	up = !strcmp(link, "up");
 	down = !strcmp(link, "down");
 
-	topology_handle_ethport_carrier_off(p, ifname, down);
-	UNUSED(up);
+	dbg("UBUS: %s %s\n", ifname, link);
+	if (down)
+		topology_handle_ethport_carrier_off(p, ifname);
+	else
+		topology_handle_ethport_carrier_on(p, ifname);
 
 	return;
 }
-- 
GitLab