summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorHans Dedecker <dedeckeh@gmail.com>2014-02-11 08:30:20 +0000
committerSteven Barth <steven@midlink.org>2014-02-27 12:15:24 +0100
commit3d26347d0042313be3c9347f8d82c4ccea67fe68 (patch)
treea0afcc0ec93e1cac9b4a905d76dfac884e9e6e25
parent4d09244bc73a3b8bc8168366526e4ddded41057e (diff)
netifd: Link layer state awareness support on device level
Device layer is informed by netlink events regarding the link layer status. Link layer status change results in a DEV_EVENT_LINK_UP/DEV_EVENT_LINK_DOWN broadcast event for a given device. Depends on uloop error callback patch. Solves issue reported in https://dev.openwrt.org/ticket/14590 Signed-off-by: Hans Dedecker <dedeckeh@gmail.com> Acked-by: Karl Vogel <karl.vogel@gmail.com>
-rw-r--r--device.c29
-rw-r--r--device.h4
-rw-r--r--system-linux.c154
3 files changed, 162 insertions, 25 deletions
diff --git a/device.c b/device.c
index feeb290..6a770ac 100644
--- a/device.c
+++ b/device.c
@@ -211,7 +211,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;
@@ -237,7 +237,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)
@@ -394,6 +394,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;
@@ -435,6 +455,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);
}
}
@@ -667,6 +690,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/system-linux.c b/system-linux.c
index cbcc5bd..12682dc 100644
--- a/system-linux.c
+++ b/system-linux.c
@@ -58,7 +58,7 @@
struct event_socket {
struct uloop_fd uloop;
struct nl_sock *sock;
- struct nl_cb *cb;
+ int bufsize;
};
static int sock_ioctl = -1;
@@ -73,7 +73,38 @@ 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);
+ int err;
+ socklen_t errlen = sizeof(err);
+
+ if (!u->error) {
+ nl_recvmsgs_default(ev->sock);
+ return;
+ }
+
+ if (getsockopt(u->fd, SOL_SOCKET, SO_ERROR, (void *)&err, &errlen))
+ goto abort;
+
+ switch(err) {
+ case ENOBUFS:
+ // Increase rx buffer size on netlink socket
+ ev->bufsize *= 2;
+ if (nl_socket_set_buffer_size(ev->sock, ev->bufsize, 0))
+ goto abort;
+
+ // Request full dump since some info got dropped
+ struct rtgenmsg msg = { .rtgen_family = AF_UNSPEC };
+ nl_send_simple(ev->sock, RTM_GETLINK, NLM_F_DUMP, &msg, sizeof(msg));
+ break;
+
+ default:
+ goto abort;
+ }
+ u->error = false;
+ return;
+
+abort:
+ uloop_fd_delete(&ev->uloop);
+ return;
}
static struct nl_sock *
@@ -96,7 +127,7 @@ create_socket(int protocol, int groups)
static bool
create_raw_event_socket(struct event_socket *ev, int protocol, int groups,
- uloop_fd_handler cb)
+ uloop_fd_handler cb, int flags)
{
ev->sock = create_socket(protocol, groups);
if (!ev->sock)
@@ -104,7 +135,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|flags))
+ return false;
+
return true;
}
@@ -112,14 +145,21 @@ 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, ULOOP_ERROR_CB))
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);
+
+ // Disable sequence number checking on event sockets
+ nl_socket_disable_seq_check(ev->sock);
- return create_raw_event_socket(ev, protocol, 0, handler_nl_event);
+ // Increase rx buffer size to 65K on event sockets
+ ev->bufsize = 65535;
+ if (nl_socket_set_buffer_size(ev->sock, ev->bufsize, 0))
+ return false;
+
+ return true;
}
int system_init(void)
@@ -128,7 +168,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);
@@ -139,7 +179,7 @@ int system_init(void)
return -1;
if (!create_raw_event_socket(&hotplug_event, NETLINK_KOBJECT_UEVENT, 1,
- handle_hotplug_event))
+ handle_hotplug_event, 0))
return -1;
// Receive network link events form kernel
@@ -171,6 +211,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 +222,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;
@@ -538,7 +582,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;
@@ -782,7 +826,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);
}
@@ -794,10 +838,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 *
@@ -915,14 +1027,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);