#include <linux/sockios.h>
#include <linux/if_vlan.h>
#include <linux/if_bridge.h>
+#include <linux/ethtool.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <glob.h>
+#include <time.h>
#include <netlink/msg.h>
#include <netlink/attr.h>
static struct nl_sock *sock_rtnl = NULL;
static int cb_rtnl_event(struct nl_msg *msg, void *arg);
+static void handle_hotplug_event(struct uloop_fd *u, unsigned int events);
static void
handler_nl_event(struct uloop_fd *u, unsigned int events)
nl_recvmsgs(ev->sock, ev->cb);
}
+static struct nl_sock *
+create_socket(int protocol, int groups)
+{
+ struct nl_sock *sock;
+
+ sock = nl_socket_alloc();
+ if (!sock)
+ return NULL;
+
+ if (groups)
+ nl_join_groups(sock, groups);
+
+ if (nl_connect(sock, protocol))
+ return NULL;
+
+ return sock;
+}
+
+static bool
+create_raw_event_socket(struct event_socket *ev, int protocol, int groups,
+ uloop_fd_handler cb)
+{
+ ev->sock = create_socket(protocol, groups);
+ if (!ev->sock)
+ return false;
+
+ ev->uloop.fd = nl_socket_get_fd(ev->sock);
+ ev->uloop.cb = cb;
+ uloop_fd_add(&ev->uloop, ULOOP_READ | ULOOP_EDGE_TRIGGER);
+ return true;
+}
+
static bool
create_event_socket(struct event_socket *ev, int protocol,
int (*cb)(struct nl_msg *msg, void *arg))
nl_cb_set(ev->cb, NL_CB_VALID, NL_CB_CUSTOM, cb, NULL);
- ev->sock = nl_socket_alloc();
- if (!ev->sock)
- return false;
-
- if (nl_connect(ev->sock, protocol))
- return false;
-
- ev->uloop.fd = nl_socket_get_fd(ev->sock);
- ev->uloop.cb = handler_nl_event;
- uloop_fd_add(&ev->uloop, ULOOP_READ | ULOOP_EDGE_TRIGGER);
- return true;
+ return create_raw_event_socket(ev, protocol, 0, handler_nl_event);
}
int system_init(void)
{
static struct event_socket rtnl_event;
+ 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);
// Prepare socket for routing / address control
- sock_rtnl = nl_socket_alloc();
+ sock_rtnl = create_socket(NETLINK_ROUTE, 0);
if (!sock_rtnl)
return -1;
- if (nl_connect(sock_rtnl, NETLINK_ROUTE))
+ if (!create_event_socket(&rtnl_event, NETLINK_ROUTE, cb_rtnl_event))
return -1;
- if (!create_event_socket(&rtnl_event, NETLINK_ROUTE, cb_rtnl_event))
+ if (!create_raw_event_socket(&hotplug_event, NETLINK_KOBJECT_UEVENT, 1,
+ handle_hotplug_event))
return -1;
// Receive network link events form kernel
nl_socket_add_membership(rtnl_event.sock, RTNLGRP_LINK);
-
return 0;
}
goto out;
dev->ifindex = ifi->ifi_index;
- device_set_present(dev, (nh->nlmsg_type == RTM_NEWLINK));
+ /* TODO: parse link status */
out:
return 0;
}
+static void
+handle_hotplug_msg(char *data, int size)
+{
+ const char *subsystem = NULL, *interface = NULL;
+ char *cur, *end, *sep;
+ struct device *dev;
+ int skip;
+ bool add;
+
+ if (!strncmp(data, "add@", 4))
+ add = true;
+ else if (!strncmp(data, "remove@", 7))
+ add = false;
+ else
+ return;
+
+ skip = strlen(data) + 1;
+ end = data + size;
+
+ for (cur = data + skip; cur < end; cur += skip) {
+ skip = strlen(cur) + 1;
+
+ sep = strchr(cur, '=');
+ if (!sep)
+ continue;
+
+ *sep = 0;
+ if (!strcmp(cur, "INTERFACE"))
+ interface = sep + 1;
+ else if (!strcmp(cur, "SUBSYSTEM")) {
+ subsystem = sep + 1;
+ if (strcmp(subsystem, "net") != 0)
+ return;
+ }
+ if (subsystem && interface)
+ goto found;
+ }
+ return;
+
+found:
+ dev = device_get(interface, false);
+ if (!dev)
+ return;
+
+ if (dev->type != &simple_device_type)
+ return;
+
+ device_set_present(dev, add);
+}
+
+static void
+handle_hotplug_event(struct uloop_fd *u, unsigned int events)
+{
+ struct event_socket *ev = container_of(u, struct event_socket, uloop);
+ struct sockaddr_nl nla;
+ unsigned char *buf = NULL;
+ int size;
+
+ while ((size = nl_recv(ev->sock, &nla, &buf, NULL)) > 0) {
+ if (nla.nl_pid == 0)
+ handle_hotplug_msg((char *) buf, size);
+
+ free(buf);
+ }
+}
+
static int system_rtnl_call(struct nl_msg *msg)
{
int s = -(nl_send_auto_complete(sock_rtnl, msg)
}
static void
-system_if_apply_settings(struct device *dev)
+system_if_get_settings(struct device *dev, struct device_settings *s)
{
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, dev->ifname, sizeof(ifr.ifr_name));
- if (dev->flags & DEV_OPT_MTU) {
- ifr.ifr_mtu = dev->mtu;
- ioctl(sock_ioctl, SIOCSIFMTU, &ifr);
+
+ if (ioctl(sock_ioctl, SIOCGIFMTU, &ifr) == 0) {
+ s->mtu = ifr.ifr_mtu;
+ s->flags |= DEV_OPT_MTU;
}
- if (dev->flags & DEV_OPT_TXQUEUELEN) {
- ifr.ifr_qlen = dev->txqueuelen;
- ioctl(sock_ioctl, SIOCSIFTXQLEN, &ifr);
+
+ if (ioctl(sock_ioctl, SIOCGIFTXQLEN, &ifr) == 0) {
+ s->txqueuelen = ifr.ifr_qlen;
+ s->flags |= DEV_OPT_TXQUEUELEN;
}
- if (dev->flags & DEV_OPT_MACADDR) {
- memcpy(&ifr.ifr_hwaddr, dev->macaddr, sizeof(dev->macaddr));
- ioctl(sock_ioctl, SIOCSIFHWADDR, &ifr);
+
+ if (ioctl(sock_ioctl, SIOCGIFHWADDR, &ifr) == 0) {
+ memcpy(s->macaddr, &ifr.ifr_hwaddr, sizeof(s->macaddr));
+ s->flags |= DEV_OPT_MACADDR;
}
+}
- dev->ifindex = system_if_resolve(dev);
+static void
+system_if_apply_settings(struct device *dev, struct device_settings *s)
+{
+ struct ifreq ifr;
+
+ memset(&ifr, 0, sizeof(ifr));
+ strncpy(ifr.ifr_name, dev->ifname, sizeof(ifr.ifr_name));
+ if (s->flags & DEV_OPT_MTU) {
+ ifr.ifr_mtu = s->mtu;
+ if (ioctl(sock_ioctl, SIOCSIFMTU, &ifr) < 0)
+ s->flags &= ~DEV_OPT_MTU;
+ }
+ if (s->flags & DEV_OPT_TXQUEUELEN) {
+ ifr.ifr_qlen = s->txqueuelen;
+ if (ioctl(sock_ioctl, SIOCSIFTXQLEN, &ifr) < 0)
+ s->flags &= ~DEV_OPT_TXQUEUELEN;
+ }
+ if (s->flags & DEV_OPT_MACADDR) {
+ memcpy(&ifr.ifr_hwaddr, s->macaddr, sizeof(s->macaddr));
+ if (ioctl(sock_ioctl, SIOCSIFHWADDR, &ifr) < 0)
+ s->flags &= ~DEV_OPT_MACADDR;
+ }
}
int system_if_up(struct device *dev)
{
- system_if_apply_settings(dev);
+ system_if_get_settings(dev, &dev->orig_settings);
+ system_if_apply_settings(dev, &dev->settings);
+ dev->ifindex = system_if_resolve(dev);
return system_if_flags(dev->ifname, IFF_UP, 0);
}
int system_if_down(struct device *dev)
{
- return system_if_flags(dev->ifname, 0, IFF_UP);
+ int ret = system_if_flags(dev->ifname, 0, IFF_UP);
+ system_if_apply_settings(dev, &dev->orig_settings);
+ return ret;
}
int system_if_check(struct device *dev)
{
- device_set_present(dev, (system_if_resolve(dev) >= 0));
+ device_set_present(dev, (system_if_resolve(dev) > 0));
+ return 0;
+}
+
+struct device *
+system_if_get_parent(struct device *dev)
+{
+ char buf[64], *devname;
+ int ifindex, iflink, len;
+ FILE *f;
+
+ snprintf(buf, sizeof(buf), "/sys/class/net/%s/iflink", dev->ifname);
+ f = fopen(buf, "r");
+ if (!f)
+ return NULL;
+
+ len = fread(buf, 1, sizeof(buf) - 1, f);
+ fclose(f);
+
+ if (len <= 0)
+ return NULL;
+
+ buf[len] = 0;
+ iflink = strtoul(buf, NULL, 0);
+ ifindex = system_if_resolve(dev);
+ if (!iflink || iflink == ifindex)
+ return NULL;
+
+ devname = if_indextoname(iflink, buf);
+ if (!devname)
+ return NULL;
+
+ return device_get(devname, true);
+}
+
+static bool
+read_string_file(int dir_fd, const char *file, char *buf, int len)
+{
+ bool ret = false;
+ char *c;
+ int fd;
+
+ fd = openat(dir_fd, file, O_RDONLY);
+ if (fd < 0)
+ return false;
+
+retry:
+ len = read(fd, buf, len - 1);
+ if (len < 0) {
+ if (errno == EINTR)
+ goto retry;
+ } else if (len > 0) {
+ buf[len] = 0;
+
+ c = strchr(buf, '\n');
+ if (c)
+ *c = 0;
+
+ ret = true;
+ }
+
+ close(fd);
+
+ return ret;
+}
+
+static bool
+read_int_file(int dir_fd, const char *file, int *val)
+{
+ char buf[64];
+ bool ret = false;
+
+ ret = read_string_file(dir_fd, file, buf, sizeof(buf));
+ if (ret)
+ *val = strtoul(buf, NULL, 0);
+
+ return ret;
+}
+
+/* Assume advertised flags == supported flags */
+static const struct {
+ uint32_t mask;
+ const char *name;
+} ethtool_link_modes[] = {
+ { ADVERTISED_10baseT_Half, "10H" },
+ { ADVERTISED_10baseT_Full, "10F" },
+ { ADVERTISED_100baseT_Half, "100H" },
+ { ADVERTISED_100baseT_Full, "100F" },
+ { ADVERTISED_1000baseT_Half, "1000H" },
+ { ADVERTISED_1000baseT_Full, "1000F" },
+};
+
+static void system_add_link_modes(struct blob_buf *b, __u32 mask)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(ethtool_link_modes); i++) {
+ if (mask & ethtool_link_modes[i].mask)
+ blobmsg_add_string(b, NULL, ethtool_link_modes[i].name);
+ }
+}
+
+int
+system_if_dump_info(struct device *dev, struct blob_buf *b)
+{
+ struct ethtool_cmd ecmd;
+ struct ifreq ifr;
+ char buf[64], *s;
+ void *c;
+ int dir_fd, val = 0;
+
+ snprintf(buf, sizeof(buf), "/sys/class/net/%s", dev->ifname);
+ dir_fd = open(buf, O_DIRECTORY);
+
+ if (read_int_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);
+ ifr.ifr_data = (caddr_t) &ecmd;
+ ecmd.cmd = ETHTOOL_GSET;
+
+ if (ioctl(sock_ioctl, SIOCETHTOOL, &ifr) == 0) {
+ c = blobmsg_open_array(b, "link-advertising");
+ system_add_link_modes(b, ecmd.advertising);
+ blobmsg_close_array(b, c);
+
+ c = blobmsg_open_array(b, "link-supported");
+ system_add_link_modes(b, ecmd.supported);
+ blobmsg_close_array(b, c);
+
+ s = blobmsg_alloc_string_buffer(b, "speed", 8);
+ snprintf(s, 8, "%d%c", ethtool_cmd_speed(&ecmd),
+ ecmd.duplex == DUPLEX_HALF ? 'H' : 'F');
+ blobmsg_add_string_buffer(b);
+ }
+
+ close(dir_fd);
return 0;
}
-int system_if_dump_stats(struct device *dev, struct blob_buf *b)
+int
+system_if_dump_stats(struct device *dev, struct blob_buf *b)
{
const char *const counters[] = {
"collisions", "rx_frame_errors", "tx_compressed",
};
char buf[64];
int stats_dir;
- int i, fd, len;
+ int i, val = 0;
snprintf(buf, sizeof(buf), "/sys/class/net/%s/statistics", dev->ifname);
stats_dir = open(buf, O_DIRECTORY);
if (stats_dir < 0)
return -1;
- for (i = 0; i < ARRAY_SIZE(counters); i++) {
- fd = openat(stats_dir, counters[i], O_RDONLY);
- if (fd < 0)
- continue;
-
-retry:
- len = read(fd, buf, sizeof(buf));
- if (len < 0) {
- if (errno == EINTR)
- goto retry;
- continue;
- }
-
- buf[len] = 0;
- blobmsg_add_u32(b, counters[i], strtoul(buf, NULL, 0));
- close(fd);
- }
+ for (i = 0; i < ARRAY_SIZE(counters); i++)
+ if (read_int_file(stats_dir, counters[i], &val))
+ blobmsg_add_u32(b, counters[i], val);
close(stats_dir);
return 0;