#include <sys/stat.h>
#include <sys/syscall.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
#include <linux/rtnetlink.h>
#include <linux/sockios.h>
+#include <linux/ip.h>
#include <linux/if_vlan.h>
#include <linux/if_bridge.h>
+#include <linux/if_tunnel.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 int cb_rtnl_event(struct nl_msg *msg, void *arg);
static void handle_hotplug_event(struct uloop_fd *u, unsigned int events);
+static char dev_buf[256];
+
static void
handler_nl_event(struct uloop_fd *u, unsigned int events)
{
static void system_set_dev_sysctl(const char *path, const char *device, const char *val)
{
- static char buf[256];
-
- snprintf(buf, sizeof(buf), path, val);
- system_set_sysctl(buf, val);
+ snprintf(dev_buf, sizeof(dev_buf), path, val);
+ system_set_sysctl(dev_buf, val);
}
static void system_set_disable_ipv6(struct device *dev, const char *val)
return ioctl(sock_ioctl, cmd, &ifr);
}
-int system_bridge_addif(struct device *bridge, struct device *dev)
-{
- system_set_disable_ipv6(dev, "1");
- return system_bridge_if(bridge->ifname, dev, SIOCBRADDIF, NULL);
-}
-
-int system_bridge_delif(struct device *bridge, struct device *dev)
-{
- system_set_disable_ipv6(dev, "0");
- return system_bridge_if(bridge->ifname, dev, SIOCBRDELIF, NULL);
-}
-
static bool system_is_bridge(const char *name, char *buf, int buflen)
{
struct stat st;
return path + 1;
}
+int system_bridge_addif(struct device *bridge, struct device *dev)
+{
+ char *oldbr;
+
+ system_set_disable_ipv6(dev, "1");
+ oldbr = system_get_bridge(dev->ifname, dev_buf, sizeof(dev_buf));
+ if (oldbr && !strcmp(oldbr, bridge->ifname))
+ return 0;
+
+ return system_bridge_if(bridge->ifname, dev, SIOCBRADDIF, NULL);
+}
+
+int system_bridge_delif(struct device *bridge, struct device *dev)
+{
+ system_set_disable_ipv6(dev, "0");
+ return system_bridge_if(bridge->ifname, dev, SIOCBRDELIF, NULL);
+}
+
static int system_if_resolve(struct device *dev)
{
struct ifreq ifr;
}
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.sa_data, 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) {
+ ifr.ifr_hwaddr.sa_family = ARPHRD_ETHER;
+ memcpy(&ifr.ifr_hwaddr.sa_data, 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);
+ dev->orig_settings.flags &= dev->settings.flags;
+ system_if_apply_settings(dev, &dev->orig_settings);
+ return ret;
}
int system_if_check(struct device *dev)
return device_get(devname, true);
}
-int system_if_dump_stats(struct device *dev, struct blob_buf *b)
+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)
{
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;
static int system_addr(struct device *dev, struct device_addr *addr, int cmd)
{
- int alen = ((addr->flags & DEVADDR_FAMILY) == DEVADDR_INET4) ? 4 : 16;
+ bool v4 = ((addr->flags & DEVADDR_FAMILY) == DEVADDR_INET4);
+ int alen = v4 ? 4 : 16;
struct ifaddrmsg ifa = {
.ifa_family = (alen == 4) ? AF_INET : AF_INET6,
.ifa_prefixlen = addr->mask,
struct nl_msg *msg;
- dev = addr->device;
- if (dev) {
- if (!dev->ifindex)
- return -1;
-
- ifa.ifa_index = dev->ifindex;
- }
-
msg = nlmsg_alloc_simple(cmd, 0);
if (!msg)
return -1;
nlmsg_append(msg, &ifa, sizeof(ifa), 0);
nla_put(msg, IFA_LOCAL, alen, &addr->addr);
+ if (v4)
+ nla_put_u32(msg, IFA_BROADCAST, addr->broadcast);
+
return system_rtnl_call(msg);
}
if (cmd == RTM_NEWROUTE)
flags |= NLM_F_CREATE | NLM_F_REPLACE;
- dev = route->device;
- if (dev) {
- if (!dev->ifindex)
- return -1;
-
- ifindex = dev->ifindex;
- }
-
msg = nlmsg_alloc_simple(cmd, flags);
if (!msg)
return -1;
if (route->mask)
nla_put(msg, RTA_DST, alen, &route->addr);
+ if (route->metric > 0)
+ nla_put_u32(msg, RTA_PRIORITY, route->metric);
+
if (have_gw)
nla_put(msg, RTA_GATEWAY, alen, &route->nexthop);
- if (route->flags & DEVADDR_DEVICE)
- nla_put_u32(msg, RTA_OIF, ifindex);
+ nla_put_u32(msg, RTA_OIF, ifindex);
return system_rtnl_call(msg);
}
return 0;
}
+
+#ifndef IP_DF
+#define IP_DF 0x4000
+#endif
+
+static void tunnel_parm_init(struct ip_tunnel_parm *p)
+{
+ memset(p, 0, sizeof(*p));
+ p->iph.version = 4;
+ p->iph.ihl = 5;
+ p->iph.frag_off = htons(IP_DF);
+}
+
+static int tunnel_ioctl(const char *name, int cmd, void *p)
+{
+ struct ifreq ifr;
+
+ memset(&ifr, 0, sizeof(ifr));
+ strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+ ifr.ifr_ifru.ifru_data = p;
+ return ioctl(sock_ioctl, cmd, &ifr);
+}
+
+int system_del_ip_tunnel(const char *name)
+{
+ struct ip_tunnel_parm p;
+
+ tunnel_parm_init(&p);
+ return tunnel_ioctl(name, SIOCDELTUNNEL, &p);
+}
+
+static int parse_ipaddr(struct blob_attr *attr, __be32 *addr)
+{
+ if (!attr)
+ return 1;
+
+ return inet_pton(AF_INET, blobmsg_data(attr), (void *) addr);
+}
+
+
+int system_add_ip_tunnel(const char *name, struct blob_attr *attr)
+{
+ struct blob_attr *tb[__TUNNEL_ATTR_MAX];
+ struct blob_attr *cur;
+ struct ip_tunnel_parm p;
+ const char *base, *str;
+ int cmd = SIOCADDTUNNEL;
+
+ system_del_ip_tunnel(name);
+
+ tunnel_parm_init(&p);
+
+ blobmsg_parse(tunnel_attr_list.params, __TUNNEL_ATTR_MAX, tb,
+ blob_data(attr), blob_len(attr));
+
+ cur = tb[TUNNEL_ATTR_TYPE];
+ if (!cur)
+ return -EINVAL;
+
+ str = blobmsg_data(cur);
+ if (!strcmp(str, "sit")) {
+ p.iph.protocol = IPPROTO_IPV6;
+ base = "sit0";
+ } else
+ return -EINVAL;
+
+ if (!parse_ipaddr(tb[TUNNEL_ATTR_LOCAL], &p.iph.saddr))
+ return -EINVAL;
+
+ if (!parse_ipaddr(tb[TUNNEL_ATTR_REMOTE], &p.iph.daddr))
+ return -EINVAL;
+
+ if ((cur = tb[TUNNEL_ATTR_TTL])) {
+ unsigned int val = blobmsg_get_u32(cur);
+
+ if (val > 255)
+ return -EINVAL;
+
+ p.iph.ttl = val;
+ }
+
+ strncpy(p.name, name, sizeof(p.name));
+ return tunnel_ioctl(base, cmd, &p);
+}