From c439b52400978dd3799c66e1f632ee68d2c7c9eb Mon Sep 17 00:00:00 2001
From: Hans Dedecker <dedeckeh@gmail.com>
Date: Tue, 19 Nov 2013 12:17:08 +0100
Subject: [PATCH] netifd: Link layer state support on device level

Patch implements link layer state awareness (aka carrier detection) in
netifd on device level.

Signed-off-by: Hans Dedecker <dedeckeh@gmail.com>
---
 alias.c        |  16 +++++--
 device.c       |  29 +++++++++++-
 device.h       |   4 ++
 macvlan.c      |   6 +++
 system-linux.c | 117 ++++++++++++++++++++++++++++++++++++++++---------
 vlan.c         |  13 ++++--
 6 files changed, 155 insertions(+), 30 deletions(-)

diff --git a/alias.c b/alias.c
index 4e0a6be..cef125f 100644
--- a/alias.c
+++ b/alias.c
@@ -51,7 +51,7 @@ static void alias_set_device(struct alias_device *alias, struct device *dev)
 	device_remove_user(&alias->dep);
 	alias->dev.hidden = !dev;
 	if (dev) {
-		alias->dev.ifindex = dev->ifindex;
+		device_set_ifindex(&alias->dev, dev->ifindex);
 		strcpy(alias->dev.ifname, dev->ifname);
 		device_broadcast_event(&alias->dev, DEV_EVENT_UPDATE_IFNAME);
 		device_add_user(&alias->dep, dev);
@@ -83,14 +83,22 @@ alias_device_set_state(struct device *dev, bool state)
 static void alias_device_cb(struct device_user *dep, enum device_event ev)
 {
 	struct alias_device *alias;
-	bool present = false;
+	bool new_state = false;
 
 	alias = container_of(dep, struct alias_device, dep);
 	switch (ev) {
 	case DEV_EVENT_ADD:
-		present = true;
+		new_state = true;
 	case DEV_EVENT_REMOVE:
-		device_set_present(&alias->dev, present);
+		device_set_present(&alias->dev, new_state);
+		break;
+	case DEV_EVENT_LINK_UP:
+		new_state = true;
+	case DEV_EVENT_LINK_DOWN:
+		device_set_link(&alias->dev, new_state);
+		break;
+	case DEV_EVENT_UPDATE_IFINDEX:
+		device_set_ifindex(&alias->dev, dep->dev->ifindex);
 		break;
 	default:
 		device_broadcast_event(&alias->dev, ev);
diff --git a/device.c b/device.c
index 56fc3f7..f8834f0 100644
--- a/device.c
+++ b/device.c
@@ -207,7 +207,7 @@ int device_claim(struct device_user *dep)
 		return 0;
 
 	dep->claimed = true;
-	D(DEVICE, "Claim %s %s, new refcount: %d\n", dev->type->name, dev->ifname, dev->active + 1);
+	D(DEVICE, "Claim %s %s, new active count: %d\n", dev->type->name, dev->ifname, dev->active + 1);
 	if (++dev->active != 1)
 		return 0;
 
@@ -233,7 +233,7 @@ void device_release(struct device_user *dep)
 
 	dep->claimed = false;
 	dev->active--;
-	D(DEVICE, "Release %s %s, new refcount: %d\n", dev->type->name, dev->ifname, dev->active);
+	D(DEVICE, "Release %s %s, new active count: %d\n", dev->type->name, dev->ifname, dev->active);
 	assert(dev->active >= 0);
 
 	if (dev->active)
@@ -390,6 +390,26 @@ void device_set_present(struct device *dev, bool state)
 	device_refresh_present(dev);
 }
 
+void device_set_link(struct device *dev, bool state)
+{
+	if (dev->link_active == state)
+		return;
+
+	netifd_log_message(L_NOTICE, "%s '%s' link is %s\n", dev->type->name, dev->ifname, state ? "up" : "down" );
+
+	dev->link_active = state;
+	device_broadcast_event(dev, state ? DEV_EVENT_LINK_UP : DEV_EVENT_LINK_DOWN);
+}
+
+void device_set_ifindex(struct device *dev, int ifindex)
+{
+	if (dev->ifindex == ifindex)
+		return;
+
+	dev->ifindex = ifindex;
+	device_broadcast_event(dev, DEV_EVENT_UPDATE_IFINDEX);
+}
+
 static int device_refcount(struct device *dev)
 {
 	struct list_head *list;
@@ -431,6 +451,9 @@ void device_add_user(struct device_user *dep, struct device *dev)
 		dep->cb(dep, DEV_EVENT_ADD);
 		if (dev->active)
 			dep->cb(dep, DEV_EVENT_UP);
+
+		if (dev->link_active)
+			dep->cb(dep, DEV_EVENT_LINK_UP);
 	}
 }
 
@@ -663,6 +686,8 @@ device_dump_status(struct blob_buf *b, struct device *dev)
 		return;
 
 	blobmsg_add_u8(b, "up", !!dev->active);
+	blobmsg_add_u8(b, "carrier", !!dev->link_active);
+
 	if (dev->type->dump_info)
 		dev->type->dump_info(dev, b);
 	else
diff --git a/device.h b/device.h
index d63ffe3..dd57927 100644
--- a/device.h
+++ b/device.h
@@ -68,6 +68,7 @@ enum device_event {
 	DEV_EVENT_REMOVE,
 
 	DEV_EVENT_UPDATE_IFNAME,
+	DEV_EVENT_UPDATE_IFINDEX,
 
 	DEV_EVENT_SETUP,
 	DEV_EVENT_TEARDOWN,
@@ -122,6 +123,7 @@ struct device {
 	bool sys_present;
 	bool present;
 	int active;
+	bool link_active;
 
 	bool external;
 	bool disabled;
@@ -178,6 +180,8 @@ void device_remove_user(struct device_user *dep);
 void device_broadcast_event(struct device *dev, enum device_event ev);
 
 void device_set_present(struct device *dev, bool state);
+void device_set_link(struct device *dev, bool state);
+void device_set_ifindex(struct device *dev, int ifindex);
 void device_refresh_present(struct device *dev);
 int device_claim(struct device_user *dep);
 void device_release(struct device_user *dep);
diff --git a/macvlan.c b/macvlan.c
index 28567dc..9c03cd8 100644
--- a/macvlan.c
+++ b/macvlan.c
@@ -69,6 +69,12 @@ macvlan_base_cb(struct device_user *dev, enum device_event ev)
 	case DEV_EVENT_REMOVE:
 		device_set_present(&mvdev->dev, false);
 		break;
+	case DEV_EVENT_LINK_UP:
+		device_set_link(&mvdev->dev, true);
+		break;
+	case DEV_EVENT_LINK_DOWN:
+		device_set_link(&mvdev->dev, false);
+		break;
 	default:
 		return;
 	}
diff --git a/system-linux.c b/system-linux.c
index dae98b0..714729f 100644
--- a/system-linux.c
+++ b/system-linux.c
@@ -58,7 +58,6 @@
 struct event_socket {
 	struct uloop_fd uloop;
 	struct nl_sock *sock;
-	struct nl_cb *cb;
 };
 
 static int sock_ioctl = -1;
@@ -73,7 +72,7 @@ static void
 handler_nl_event(struct uloop_fd *u, unsigned int events)
 {
 	struct event_socket *ev = container_of(u, struct event_socket, uloop);
-	nl_recvmsgs(ev->sock, ev->cb);
+	nl_recvmsgs_default(ev->sock);
 }
 
 static struct nl_sock *
@@ -104,7 +103,9 @@ create_raw_event_socket(struct event_socket *ev, int protocol, int groups,
 
 	ev->uloop.fd = nl_socket_get_fd(ev->sock);
 	ev->uloop.cb = cb;
-	uloop_fd_add(&ev->uloop, ULOOP_READ | ULOOP_EDGE_TRIGGER);
+	if (uloop_fd_add(&ev->uloop, ULOOP_READ))
+		return false;
+
 	return true;
 }
 
@@ -112,14 +113,20 @@ static bool
 create_event_socket(struct event_socket *ev, int protocol,
 		    int (*cb)(struct nl_msg *msg, void *arg))
 {
-	// Prepare socket for link events
-	ev->cb = nl_cb_alloc(NL_CB_DEFAULT);
-	if (!ev->cb)
+	if (!create_raw_event_socket(ev, protocol, 0, handler_nl_event))
 		return false;
 
-	nl_cb_set(ev->cb, NL_CB_VALID, NL_CB_CUSTOM, cb, NULL);
+	// Install the valid custom callback handler
+	nl_socket_modify_cb(ev->sock, NL_CB_VALID, NL_CB_CUSTOM, cb, NULL);
 
-	return create_raw_event_socket(ev, protocol, 0, handler_nl_event);
+	// Disable sequence number checking on event sockets
+	nl_socket_disable_seq_check(ev->sock);
+
+	// Increase rx buffer size to 65K on event sockets
+	if (nl_socket_set_buffer_size(ev->sock, 65535, 0))
+		return false;
+
+	return true;
 }
 
 int system_init(void)
@@ -128,7 +135,7 @@ int system_init(void)
 	static struct event_socket hotplug_event;
 
 	sock_ioctl = socket(AF_LOCAL, SOCK_DGRAM, 0);
-	fcntl(sock_ioctl, F_SETFD, fcntl(sock_ioctl, F_GETFD) | FD_CLOEXEC);
+	system_fd_set_cloexec(sock_ioctl);
 
 	// Prepare socket for routing / address control
 	sock_rtnl = create_socket(NETLINK_ROUTE, 0);
@@ -171,6 +178,10 @@ static void system_set_disable_ipv6(struct device *dev, const char *val)
 	system_set_dev_sysctl("/proc/sys/net/ipv6/conf/%s/disable_ipv6", dev->ifname, val);
 }
 
+#ifndef IFF_LOWER_UP
+#define IFF_LOWER_UP	0x10000
+#endif
+
 // Evaluate netlink messages
 static int cb_rtnl_event(struct nl_msg *msg, void *arg)
 {
@@ -178,19 +189,19 @@ static int cb_rtnl_event(struct nl_msg *msg, void *arg)
 	struct ifinfomsg *ifi = NLMSG_DATA(nh);
 	struct nlattr *nla[__IFLA_MAX];
 
-	if (nh->nlmsg_type != RTM_DELLINK && nh->nlmsg_type != RTM_NEWLINK)
+	if (nh->nlmsg_type != RTM_NEWLINK)
 		goto out;
 
 	nlmsg_parse(nh, sizeof(*ifi), nla, __IFLA_MAX - 1, NULL);
 	if (!nla[IFLA_IFNAME])
 		goto out;
 
-	struct device *dev = device_get(RTA_DATA(nla[IFLA_IFNAME]), false);
+	struct device *dev = device_get(nla_data(nla[IFLA_IFNAME]), false);
 	if (!dev)
 		goto out;
 
-	dev->ifindex = ifi->ifi_index;
-	/* TODO: parse link status */
+	device_set_ifindex(dev, ifi->ifi_index);
+	device_set_link(dev, ifi->ifi_flags & IFF_LOWER_UP ? true : false);
 
 out:
 	return 0;
@@ -537,7 +548,7 @@ void system_if_clear_state(struct device *dev)
 	if (dev->external)
 		return;
 
-	dev->ifindex = system_if_resolve(dev);
+	device_set_ifindex(dev, system_if_resolve(dev));
 	if (!dev->ifindex)
 		return;
 
@@ -781,7 +792,7 @@ int system_if_up(struct device *dev)
 {
 	system_if_get_settings(dev, &dev->orig_settings);
 	system_if_apply_settings(dev, &dev->settings);
-	dev->ifindex = system_if_resolve(dev);
+	device_set_ifindex(dev, system_if_resolve(dev));
 	return system_if_flags(dev->ifname, IFF_UP, 0);
 }
 
@@ -793,10 +804,78 @@ int system_if_down(struct device *dev)
 	return ret;
 }
 
+struct if_check_data {
+	struct device *dev;
+	int pending;
+	int ret;
+};
+
+static int cb_if_check_valid(struct nl_msg *msg, void *arg)
+{
+	struct nlmsghdr *nh = nlmsg_hdr(msg);
+	struct ifinfomsg *ifi = NLMSG_DATA(nh);
+	struct if_check_data *chk = (struct if_check_data *)arg;
+
+	if (nh->nlmsg_type != RTM_NEWLINK)
+		return NL_SKIP;
+
+	device_set_present(chk->dev, ifi->ifi_index > 0 ? true : false);
+	device_set_link(chk->dev, ifi->ifi_flags & IFF_LOWER_UP ? true : false);
+
+	return NL_OK;
+}
+
+static int cb_if_check_ack(struct nl_msg *msg, void *arg)
+{
+	struct if_check_data *chk = (struct if_check_data *)arg;
+	chk->pending = 0;
+	return NL_STOP;
+}
+
+static int cb_if_check_error(struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg)
+{
+	struct if_check_data *chk = (struct if_check_data *)arg;
+
+	device_set_present(chk->dev, false);
+	device_set_link(chk->dev, false);
+	chk->pending = err->error;
+
+	return NL_STOP;
+}
+
 int system_if_check(struct device *dev)
 {
-	device_set_present(dev, (system_if_resolve(dev) > 0));
-	return 0;
+	struct nl_cb *cb = nl_cb_alloc(NL_CB_DEFAULT);
+	struct nl_msg *msg;
+	struct ifinfomsg ifi = {
+		.ifi_family = AF_UNSPEC,
+		.ifi_index = 0,
+	};
+	struct if_check_data chk = {
+		.dev = dev,
+		.pending = 1,
+	};
+	int ret = 1;
+
+	msg = nlmsg_alloc_simple(RTM_GETLINK, 0);
+	if (!msg || nlmsg_append(msg, &ifi, sizeof(ifi), 0) ||
+	    nla_put_string(msg, IFLA_IFNAME, dev->ifname))
+		goto out;
+
+	nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, cb_if_check_valid, &chk);
+	nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, cb_if_check_ack, &chk);
+	nl_cb_err(cb, NL_CB_CUSTOM, cb_if_check_error, &chk);
+
+	nl_send_auto_complete(sock_rtnl, msg);
+	while (chk.pending > 0)
+		nl_recvmsgs(sock_rtnl, cb);
+
+	nlmsg_free(msg);
+	ret = chk.pending;
+
+out:
+	nl_cb_put(cb);
+	return ret;
 }
 
 struct device *
@@ -914,14 +993,10 @@ system_if_dump_info(struct device *dev, struct blob_buf *b)
 	char buf[64], *s;
 	void *c;
 	int dir_fd;
-	uint64_t val = 0;
 
 	snprintf(buf, sizeof(buf), "/sys/class/net/%s", dev->ifname);
 	dir_fd = open(buf, O_DIRECTORY);
 
-	if (read_uint64_file(dir_fd, "carrier", &val))
-		blobmsg_add_u8(b, "link", !!val);
-
 	memset(&ecmd, 0, sizeof(ecmd));
 	memset(&ifr, 0, sizeof(ifr));
 	strcpy(ifr.ifr_name, dev->ifname);
diff --git a/vlan.c b/vlan.c
index f70420a..9201979 100644
--- a/vlan.c
+++ b/vlan.c
@@ -70,14 +70,19 @@ static void vlan_dev_set_name(struct vlan_device *vldev, struct device *dev)
 static void vlan_dev_cb(struct device_user *dep, enum device_event ev)
 {
 	struct vlan_device *vldev;
+	bool new_state = false;
 
 	vldev = container_of(dep, struct vlan_device, dep);
 	switch(ev) {
 	case DEV_EVENT_ADD:
-		device_set_present(&vldev->dev, true);
-		break;
+		new_state = true;
 	case DEV_EVENT_REMOVE:
-		device_set_present(&vldev->dev, false);
+		device_set_present(&vldev->dev, new_state);
+		break;
+	case DEV_EVENT_LINK_UP:
+		new_state = true;
+	case DEV_EVENT_LINK_DOWN:
+		device_set_link(&vldev->dev, new_state);
 		break;
 	case DEV_EVENT_UPDATE_IFNAME:
 		vlan_dev_set_name(vldev, dep->dev);
@@ -113,6 +118,8 @@ static struct device *get_vlan_device(struct device *dev, int id, bool create)
 	if (!create)
 		return NULL;
 
+	D(DEVICE, "Create vlan device '%s.%d'\n", dev->ifname, id);
+
 	vldev = calloc(1, sizeof(*vldev));
 
 	vldev->id = id;
-- 
2.30.2