+#define _GNU_SOURCE
+
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
+#include <sys/syscall.h>
#include <linux/rtnetlink.h>
#include <linux/sockios.h>
#include <linux/if_vlan.h>
+#include <linux/if_bridge.h>
#include <unistd.h>
#include <string.h>
return ioctl(sock_ioctl, SIOCBRDELBR, bridge->ifname);
}
-static int system_bridge_if(const char *bridge, struct device *dev, int cmd)
+static int system_bridge_if(const char *bridge, struct device *dev, int cmd, void *data)
{
struct ifreq ifr;
- ifr.ifr_ifindex = dev->ifindex;
+ if (dev)
+ ifr.ifr_ifindex = dev->ifindex;
+ else
+ ifr.ifr_data = data;
strncpy(ifr.ifr_name, bridge, sizeof(ifr.ifr_name));
return ioctl(sock_ioctl, cmd, &ifr);
}
int system_bridge_addif(struct device *bridge, struct device *dev)
{
- return system_bridge_if(bridge->ifname, dev, SIOCBRADDIF);
+ return system_bridge_if(bridge->ifname, dev, SIOCBRADDIF, NULL);
}
int system_bridge_delif(struct device *bridge, struct device *dev)
{
- return system_bridge_if(bridge->ifname, dev, SIOCBRDELIF);
+ return system_bridge_if(bridge->ifname, dev, SIOCBRDELIF, NULL);
}
static bool system_is_bridge(const char *name, char *buf, int buflen)
return ioctl(sock_ioctl, SIOCSIFFLAGS, &ifr);
}
+struct clear_data {
+ struct nl_msg *msg;
+ struct device *dev;
+ int type;
+ int size;
+ int af;
+};
+
+
+static bool check_ifaddr(struct nlmsghdr *hdr, int ifindex)
+{
+ struct ifaddrmsg *ifa = NLMSG_DATA(hdr);
+
+ return ifa->ifa_index == ifindex;
+}
+
+static bool check_route(struct nlmsghdr *hdr, int ifindex)
+{
+ struct nlattr *tb[__RTA_MAX];
+
+ nlmsg_parse(hdr, sizeof(struct rtmsg), tb, __RTA_MAX - 1, NULL);
+ if (!tb[RTA_OIF])
+ return false;
+
+ return *(int *)RTA_DATA(tb[RTA_OIF]) == ifindex;
+}
+
+static int cb_clear_event(struct nl_msg *msg, void *arg)
+{
+ struct clear_data *clr = arg;
+ struct nlmsghdr *hdr = nlmsg_hdr(msg);
+ bool (*cb)(struct nlmsghdr *, int ifindex);
+ int type;
+
+ switch(clr->type) {
+ case RTM_GETADDR:
+ type = RTM_DELADDR;
+ if (hdr->nlmsg_type != RTM_NEWADDR)
+ return NL_SKIP;
+
+ cb = check_ifaddr;
+ break;
+ case RTM_GETROUTE:
+ type = RTM_DELROUTE;
+ if (hdr->nlmsg_type != RTM_NEWROUTE)
+ return NL_SKIP;
+
+ cb = check_route;
+ break;
+ default:
+ return NL_SKIP;
+ }
+
+ if (!cb(hdr, clr->dev->ifindex))
+ return NL_SKIP;
+
+ D(SYSTEM, "Remove %s from device %s\n",
+ type == RTM_DELADDR ? "an address" : "a route",
+ clr->dev->ifname);
+ memcpy(nlmsg_hdr(clr->msg), hdr, hdr->nlmsg_len);
+ hdr = nlmsg_hdr(clr->msg);
+ hdr->nlmsg_type = type;
+ hdr->nlmsg_flags = NLM_F_REQUEST;
+
+ if (!nl_send_auto_complete(sock_rtnl, clr->msg))
+ nl_wait_for_ack(sock_rtnl);
+
+ return NL_SKIP;
+}
+
+static int
+cb_finish_event(struct nl_msg *msg, void *arg)
+{
+ int *pending = arg;
+ *pending = 0;
+ return NL_STOP;
+}
+
+static int
+error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg)
+{
+ int *pending = arg;
+ *pending = err->error;
+ return NL_STOP;
+}
+
+static void
+system_if_clear_entries(struct device *dev, int type, int af)
+{
+ struct clear_data clr;
+ struct nl_cb *cb = nl_cb_alloc(NL_CB_DEFAULT);
+ struct rtmsg rtm = {
+ .rtm_family = af,
+ .rtm_flags = RTM_F_CLONED,
+ };
+ int flags = NLM_F_DUMP;
+ int pending = 1;
+
+ clr.af = af;
+ clr.dev = dev;
+ clr.type = type;
+ switch (type) {
+ case RTM_GETADDR:
+ clr.size = sizeof(struct rtgenmsg);
+ break;
+ case RTM_GETROUTE:
+ clr.size = sizeof(struct rtmsg);
+ break;
+ default:
+ return;
+ }
+
+ if (!cb)
+ return;
+
+ clr.msg = nlmsg_alloc_simple(type, flags);
+ if (!clr.msg)
+ goto out;
+
+ nlmsg_append(clr.msg, &rtm, clr.size, 0);
+ nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, cb_clear_event, &clr);
+ nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, cb_finish_event, &pending);
+ nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &pending);
+
+ nl_send_auto_complete(sock_rtnl, clr.msg);
+ while (pending > 0)
+ nl_recvmsgs(sock_rtnl, cb);
+
+ nlmsg_free(clr.msg);
+out:
+ nl_cb_put(cb);
+}
+
/*
* Clear bridge (membership) state and bring down device
*/
-static void system_if_clear_state(struct device *dev)
+void system_if_clear_state(struct device *dev)
{
char buf[256];
char *bridge;
bridge = system_get_bridge(dev->ifname, buf, sizeof(buf));
if (bridge) {
D(SYSTEM, "Remove device '%s' from bridge '%s'\n", dev->ifname, bridge);
- system_bridge_if(bridge, dev, SIOCBRDELIF);
+ system_bridge_if(bridge, dev, SIOCBRDELIF, NULL);
}
+
+ system_if_clear_entries(dev, RTM_GETROUTE, AF_INET);
+ system_if_clear_entries(dev, RTM_GETADDR, AF_INET);
+ system_if_clear_entries(dev, RTM_GETROUTE, AF_INET6);
+ system_if_clear_entries(dev, RTM_GETADDR, AF_INET6);
+}
+
+static inline unsigned long
+sec_to_jiffies(int val)
+{
+ return (unsigned long) val * 100;
}
-int system_bridge_addbr(struct device *bridge)
+int system_bridge_addbr(struct device *bridge, struct bridge_config *cfg)
{
- system_if_clear_state(bridge);
- return ioctl(sock_ioctl, SIOCBRADDBR, bridge->ifname);
+ unsigned long args[4] = {};
+
+ if (ioctl(sock_ioctl, SIOCBRADDBR, bridge->ifname) < 0)
+ return -1;
+
+ args[0] = BRCTL_SET_BRIDGE_STP_STATE;
+ args[1] = !!cfg->stp;
+ system_bridge_if(bridge->ifname, NULL, SIOCDEVPRIVATE, &args);
+
+ args[0] = BRCTL_SET_BRIDGE_FORWARD_DELAY;
+ args[1] = sec_to_jiffies(cfg->forward_delay);
+ system_bridge_if(bridge->ifname, NULL, SIOCDEVPRIVATE, &args);
+
+ if (cfg->flags & BRIDGE_OPT_AGEING_TIME) {
+ args[0] = BRCTL_SET_AGEING_TIME;
+ args[1] = sec_to_jiffies(cfg->ageing_time);
+ system_bridge_if(bridge->ifname, NULL, SIOCDEVPRIVATE, &args);
+ }
+
+ if (cfg->flags & BRIDGE_OPT_HELLO_TIME) {
+ args[0] = BRCTL_SET_BRIDGE_HELLO_TIME;
+ args[1] = sec_to_jiffies(cfg->hello_time);
+ system_bridge_if(bridge->ifname, NULL, SIOCDEVPRIVATE, &args);
+ }
+
+ if (cfg->flags & BRIDGE_OPT_MAX_AGE) {
+ args[0] = BRCTL_SET_BRIDGE_MAX_AGE;
+ args[1] = sec_to_jiffies(cfg->max_age);
+ system_bridge_if(bridge->ifname, NULL, SIOCDEVPRIVATE, &args);
+ }
+
+ return 0;
}
static int system_vlan(struct device *dev, int id)
int system_vlan_add(struct device *dev, int id)
{
- system_if_clear_state(dev);
return system_vlan(dev, id);
}
int system_if_check(struct device *dev)
{
- system_if_clear_state(dev);
device_set_present(dev, (system_if_resolve(dev) >= 0));
return 0;
}
+int system_if_dump_stats(struct device *dev, struct blob_buf *b)
+{
+ const char *const counters[] = {
+ "collisions", "rx_frame_errors", "tx_compressed",
+ "multicast", "rx_length_errors", "tx_dropped",
+ "rx_bytes", "rx_missed_errors", "tx_errors",
+ "rx_compressed", "rx_over_errors", "tx_fifo_errors",
+ "rx_crc_errors", "rx_packets", "tx_heartbeat_errors",
+ "rx_dropped", "tx_aborted_errors", "tx_packets",
+ "rx_errors", "tx_bytes", "tx_window_errors",
+ "rx_fifo_errors", "tx_carrier_errors",
+ };
+ char buf[64];
+ int stats_dir;
+ int i, fd, len;
+
+ 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);
+ }
+
+ 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;
{
int alen = ((route->flags & DEVADDR_FAMILY) == DEVADDR_INET4) ? 4 : 16;
bool have_gw;
+ unsigned int flags = 0;
if (alen == 4)
have_gw = !!route->nexthop.in.s_addr;
.rtm_type = (cmd == RTM_DELROUTE) ? 0: RTN_UNICAST,
};
- struct nl_msg *msg = nlmsg_alloc_simple(cmd, 0);
+ if (cmd == RTM_NEWROUTE)
+ flags |= NLM_F_CREATE | NLM_F_REPLACE;
+
+ struct nl_msg *msg = nlmsg_alloc_simple(cmd, flags);
if (!msg)
return -1;
{
return system_rt(dev, route, RTM_DELROUTE);
}
+
+int system_flush_routes(void)
+{
+ const char *names[] = {
+ "/proc/sys/net/ipv4/route/flush",
+ "/proc/sys/net/ipv6/route/flush"
+ };
+ int fd, i;
+
+ for (i = 0; i < ARRAY_SIZE(names); i++) {
+ fd = open(names[i], O_WRONLY);
+ if (fd < 0)
+ continue;
+
+ write(fd, "-1", 2);
+ close(fd);
+ }
+ return 0;
+}
+
+time_t system_get_rtime(void)
+{
+ struct timespec ts;
+ struct timeval tv;
+
+ if (syscall(__NR_clock_gettime, CLOCK_MONOTONIC, &ts) == 0)
+ return ts.tv_sec;
+
+ if (gettimeofday(&tv, NULL) == 0)
+ return tv.tv_sec;
+
+ return 0;
+}