X-Git-Url: http://git.openwrt.org/?a=blobdiff_plain;f=system-linux.c;h=4463a2a8282a1151cb99734d65dd57689a7fad52;hb=33ec3daaaa52cea628df91eb0eb1701e16172c1e;hp=78455aeab38d4a97161b11ec910275b050568dea;hpb=4a41135750d97e06d1f6d808a9d24bb4b472aca4;p=project%2Fnetifd.git diff --git a/system-linux.c b/system-linux.c index 78455ae..4463a2a 100644 --- a/system-linux.c +++ b/system-linux.c @@ -28,8 +28,8 @@ #include #include -#include #include +#include #include #include @@ -48,6 +48,8 @@ #include +#include "ethtool-modes.h" + #ifndef RTN_FAILED_POLICY #define RTN_FAILED_POLICY 12 #endif @@ -89,27 +91,92 @@ static int cb_rtnl_event(struct nl_msg *msg, void *arg); static void handle_hotplug_event(struct uloop_fd *u, unsigned int events); static int system_add_proto_tunnel(const char *name, const uint8_t proto, const unsigned int link, struct blob_attr **tb); -static int __system_del_ip_tunnel(const char *name, struct blob_attr **tb); static char dev_buf[256]; +static const char *proc_path = "/proc"; +static const char *sysfs_path = "/sys"; + +struct netdev_type { + unsigned short id; + const char *name; +}; + +static const struct netdev_type netdev_types[] = { + {ARPHRD_NETROM, "netrom"}, + {ARPHRD_ETHER, "ethernet"}, + {ARPHRD_EETHER, "eethernet"}, + {ARPHRD_AX25, "ax25"}, + {ARPHRD_PRONET, "pronet"}, + {ARPHRD_CHAOS, "chaos"}, + {ARPHRD_IEEE802, "ieee802"}, + {ARPHRD_ARCNET, "arcnet"}, + {ARPHRD_APPLETLK, "appletlk"}, + {ARPHRD_DLCI, "dlci"}, + {ARPHRD_ATM, "atm"}, + {ARPHRD_METRICOM, "metricom"}, + {ARPHRD_IEEE1394, "ieee1394"}, + {ARPHRD_EUI64, "eui64"}, + {ARPHRD_INFINIBAND, "infiniband"}, + {ARPHRD_SLIP, "slip"}, + {ARPHRD_CSLIP, "cslip"}, + {ARPHRD_SLIP6, "slip6"}, + {ARPHRD_CSLIP6, "cslip6"}, + {ARPHRD_RSRVD, "rsrvd"}, + {ARPHRD_ADAPT, "adapt"}, + {ARPHRD_ROSE, "rose"}, + {ARPHRD_X25, "x25"}, + {ARPHRD_HWX25, "hwx25"}, + {ARPHRD_PPP, "ppp"}, + {ARPHRD_CISCO, "cisco"}, + {ARPHRD_LAPB, "lapb"}, + {ARPHRD_DDCMP, "ddcmp"}, + {ARPHRD_RAWHDLC, "rawhdlc"}, + {ARPHRD_TUNNEL, "tunnel"}, + {ARPHRD_TUNNEL6, "tunnel6"}, + {ARPHRD_FRAD, "frad"}, + {ARPHRD_SKIP, "skip"}, + {ARPHRD_LOOPBACK, "loopback"}, + {ARPHRD_LOCALTLK, "localtlk"}, + {ARPHRD_FDDI, "fddi"}, + {ARPHRD_BIF, "bif"}, + {ARPHRD_SIT, "sit"}, + {ARPHRD_IPDDP, "ipddp"}, + {ARPHRD_IPGRE, "ipgre"}, + {ARPHRD_PIMREG,"pimreg"}, + {ARPHRD_HIPPI, "hippi"}, + {ARPHRD_ASH, "ash"}, + {ARPHRD_ECONET, "econet"}, + {ARPHRD_IRDA, "irda"}, + {ARPHRD_FCPP, "fcpp"}, + {ARPHRD_FCAL, "fcal"}, + {ARPHRD_FCPL, "fcpl"}, + {ARPHRD_FCFABRIC, "fcfabric"}, + {ARPHRD_IEEE80211, "ieee80211"}, + {ARPHRD_IEEE80211_PRISM, "ie80211-prism"}, + {ARPHRD_IEEE80211_RADIOTAP, "ieee80211-radiotap"}, +#ifdef ARPHRD_PHONET + {ARPHRD_PHONET, "phonet"}, +#endif +#ifdef ARPHRD_PHONET_PIPE + {ARPHRD_PHONET_PIPE, "phonet-pipe"}, +#endif + {ARPHRD_IEEE802154, "ieee802154"}, + {ARPHRD_VOID, "void"}, + {ARPHRD_NONE, "none"} +}; static void handler_nl_event(struct uloop_fd *u, unsigned int events) { struct event_socket *ev = container_of(u, struct event_socket, uloop); - int err; - socklen_t errlen = sizeof(err); + int ret; - if (!u->error) { - nl_recvmsgs_default(ev->sock); + ret = nl_recvmsgs_default(ev->sock); + if (ret >= 0) return; - } - if (getsockopt(u->fd, SOL_SOCKET, SO_ERROR, (void *)&err, &errlen)) - goto abort; - - switch(err) { - case ENOBUFS: + switch (-ret) { + case NLE_NOMEM: /* Increase rx buffer size on netlink socket */ ev->bufsize *= 2; if (nl_socket_set_buffer_size(ev->sock, ev->bufsize, 0)) @@ -123,7 +190,6 @@ handler_nl_event(struct uloop_fd *u, unsigned int events) default: goto abort; } - u->error = false; return; abort: @@ -131,6 +197,14 @@ abort: return; } +static void +nl_udebug_cb(void *priv, struct nl_msg *msg) +{ + struct nlmsghdr *nlh = nlmsg_hdr(msg); + + udebug_netlink_msg(priv, nlmsg_get_proto(msg), nlh, nlh->nlmsg_len); +} + static struct nl_sock * create_socket(int protocol, int groups) { @@ -148,6 +222,9 @@ create_socket(int protocol, int groups) return NULL; } + nl_socket_set_tx_debug_cb(sock, nl_udebug_cb, &udb_nl); + nl_socket_set_rx_debug_cb(sock, nl_udebug_cb, &udb_nl); + return sock; } @@ -281,7 +358,7 @@ int system_init(void) return 0; } -static void system_set_sysctl(const char *path, const char *val) +static void write_file(const char *path, const char *val) { int fd; @@ -293,316 +370,369 @@ static void system_set_sysctl(const char *path, const char *val) close(fd); } -static void system_set_dev_sysctl(const char *path, const char *device, const char *val) +static int read_file(const char *path, char *buf, const size_t buf_sz) +{ + int fd = -1, ret = -1; + + fd = open(path, O_RDONLY); + if (fd < 0) + goto out; + + ssize_t len = read(fd, buf, buf_sz - 1); + if (len < 0) + goto out; + + ret = buf[len] = 0; + +out: + if (fd >= 0) + close(fd); + + return ret; +} + + +static const char * +dev_sysctl_path(const char *prefix, const char *ifname, const char *file) +{ + snprintf(dev_buf, sizeof(dev_buf), "%s/sys/net/%s/%s/%s", proc_path, prefix, ifname, file); + + return dev_buf; +} + +static const char * +dev_sysfs_path(const char *ifname, const char *file) +{ + snprintf(dev_buf, sizeof(dev_buf), "%s/class/net/%s/%s", sysfs_path, ifname, file); + + return dev_buf; +} + +static void +system_set_dev_sysctl(const char *prefix, const char *file, const char *ifname, + const char *val) +{ + write_file(dev_sysctl_path(prefix, ifname, file), val); +} + +static int +system_get_dev_sysctl(const char *prefix, const char *file, const char *ifname, + char *buf, size_t buf_sz) +{ + return read_file(dev_sysctl_path(prefix, ifname, file), buf, buf_sz); +} + +static void +system_set_dev_sysfs(const char *file, const char *ifname, const char *val) +{ + if (!val) + return; + + write_file(dev_sysfs_path(ifname, file), val); +} + +static void +system_set_dev_sysfs_int(const char *file, const char *ifname, int val) +{ + char buf[16]; + + snprintf(buf, sizeof(buf), "%d", val); + system_set_dev_sysfs(file, ifname, buf); +} + +static int +system_get_dev_sysfs(const char *file, const char *ifname, char *buf, size_t buf_sz) { - snprintf(dev_buf, sizeof(dev_buf), path, device); - system_set_sysctl(dev_buf, val); + return read_file(dev_sysfs_path(ifname, file), buf, buf_sz); } 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); + system_set_dev_sysctl("ipv6/conf", "disable_ipv6", dev->ifname, val); +} + +static void system_set_ip6segmentrouting(struct device *dev, const char *val) +{ + system_set_dev_sysctl("ipv6/conf", "seg6_enabled", dev->ifname, val); } static void system_set_rpfilter(struct device *dev, const char *val) { - system_set_dev_sysctl("/proc/sys/net/ipv4/conf/%s/rp_filter", dev->ifname, val); + system_set_dev_sysctl("ipv4/conf", "rp_filter", dev->ifname, val); } static void system_set_acceptlocal(struct device *dev, const char *val) { - system_set_dev_sysctl("/proc/sys/net/ipv4/conf/%s/accept_local", dev->ifname, val); + system_set_dev_sysctl("ipv4/conf", "accept_local", dev->ifname, val); } static void system_set_igmpversion(struct device *dev, const char *val) { - system_set_dev_sysctl("/proc/sys/net/ipv4/conf/%s/force_igmp_version", dev->ifname, val); + system_set_dev_sysctl("ipv4/conf", "force_igmp_version", dev->ifname, val); } static void system_set_mldversion(struct device *dev, const char *val) { - system_set_dev_sysctl("/proc/sys/net/ipv6/conf/%s/force_mld_version", dev->ifname, val); + system_set_dev_sysctl("ipv6/conf", "force_mld_version", dev->ifname, val); } static void system_set_neigh4reachabletime(struct device *dev, const char *val) { - system_set_dev_sysctl("/proc/sys/net/ipv4/neigh/%s/base_reachable_time_ms", dev->ifname, val); + system_set_dev_sysctl("ipv4/neigh", "base_reachable_time_ms", dev->ifname, val); } static void system_set_neigh6reachabletime(struct device *dev, const char *val) { - system_set_dev_sysctl("/proc/sys/net/ipv6/neigh/%s/base_reachable_time_ms", dev->ifname, val); + system_set_dev_sysctl("ipv6/neigh", "base_reachable_time_ms", dev->ifname, val); } static void system_set_neigh4gcstaletime(struct device *dev, const char *val) { - system_set_dev_sysctl("/proc/sys/net/ipv4/neigh/%s/gc_stale_time", dev->ifname, val); + system_set_dev_sysctl("ipv4/neigh", "gc_stale_time", dev->ifname, val); } static void system_set_neigh6gcstaletime(struct device *dev, const char *val) { - system_set_dev_sysctl("/proc/sys/net/ipv6/neigh/%s/gc_stale_time", dev->ifname, val); + system_set_dev_sysctl("ipv6/neigh", "gc_stale_time", dev->ifname, val); } static void system_set_neigh4locktime(struct device *dev, const char *val) { - system_set_dev_sysctl("/proc/sys/net/ipv4/neigh/%s/locktime", dev->ifname, val); + system_set_dev_sysctl("ipv4/neigh", "locktime", dev->ifname, val); } static void system_set_dadtransmits(struct device *dev, const char *val) { - system_set_dev_sysctl("/proc/sys/net/ipv6/conf/%s/dad_transmits", dev->ifname, val); + system_set_dev_sysctl("ipv6/conf", "dad_transmits", dev->ifname, val); } -static void system_bridge_set_multicast_to_unicast(struct device *dev, const char *val) +static void system_set_sendredirects(struct device *dev, const char *val) { - system_set_dev_sysctl("/sys/class/net/%s/brport/multicast_to_unicast", dev->ifname, val); + system_set_dev_sysctl("ipv4/conf", "send_redirects", dev->ifname, val); } -static void system_bridge_set_multicast_fast_leave(struct device *dev, const char *val) +static void system_set_drop_v4_unicast_in_l2_multicast(struct device *dev, const char *val) { - system_set_dev_sysctl("/sys/class/net/%s/brport/multicast_fast_leave", dev->ifname, val); + system_set_dev_sysctl("ipv4/conf", "drop_unicast_in_l2_multicast", dev->ifname, val); } -static void system_bridge_set_hairpin_mode(struct device *dev, const char *val) +static void system_set_drop_v6_unicast_in_l2_multicast(struct device *dev, const char *val) { - system_set_dev_sysctl("/sys/class/net/%s/brport/hairpin_mode", dev->ifname, val); + system_set_dev_sysctl("ipv6/conf", "drop_unicast_in_l2_multicast", dev->ifname, val); } -static void system_bridge_set_isolated(struct device *dev, const char *val) +static void system_set_drop_gratuitous_arp(struct device *dev, const char *val) { - system_set_dev_sysctl("/sys/class/net/%s/brport/isolated", dev->ifname, val); + system_set_dev_sysctl("ipv4/conf", "drop_gratuitous_arp", dev->ifname, val); } -static void system_bridge_set_multicast_router(struct device *dev, const char *val, bool bridge) +static void system_set_drop_unsolicited_na(struct device *dev, const char *val) { - system_set_dev_sysctl(bridge ? "/sys/class/net/%s/bridge/multicast_router" : - "/sys/class/net/%s/brport/multicast_router", - dev->ifname, val); + system_set_dev_sysctl("ipv6/conf", "drop_unsolicited_na", dev->ifname, val); } -static void system_bridge_set_robustness(struct device *dev, const char *val) +static void system_set_arp_accept(struct device *dev, const char *val) { - system_set_dev_sysctl("/sys/devices/virtual/net/%s/bridge/multicast_startup_query_count", - dev->ifname, val); - system_set_dev_sysctl("/sys/devices/virtual/net/%s/bridge/multicast_last_member_count", - dev->ifname, val); + system_set_dev_sysctl("ipv4/conf", "arp_accept", dev->ifname, val); } -static void system_bridge_set_query_interval(struct device *dev, const char *val) +static void system_bridge_set_multicast_to_unicast(struct device *dev, const char *val) { - system_set_dev_sysctl("/sys/devices/virtual/net/%s/bridge/multicast_query_interval", - dev->ifname, val); + system_set_dev_sysfs("brport/multicast_to_unicast", dev->ifname, val); } -static void system_bridge_set_query_response_interval(struct device *dev, const char *val) +static void system_bridge_set_multicast_fast_leave(struct device *dev, const char *val) { - system_set_dev_sysctl("/sys/devices/virtual/net/%s/bridge/multicast_query_response_interval", - dev->ifname, val); + system_set_dev_sysfs("brport/multicast_fast_leave", dev->ifname, val); } -static void system_bridge_set_last_member_interval(struct device *dev, const char *val) +static void system_bridge_set_hairpin_mode(struct device *dev, const char *val) { - system_set_dev_sysctl("/sys/devices/virtual/net/%s/bridge/multicast_last_member_interval", - dev->ifname, val); + system_set_dev_sysfs("brport/hairpin_mode", dev->ifname, val); } -static void system_bridge_set_membership_interval(struct device *dev, const char *val) +static void system_bridge_set_proxyarp_wifi(struct device *dev, const char *val) { - system_set_dev_sysctl("/sys/devices/virtual/net/%s/bridge/multicast_membership_interval", - dev->ifname, val); + system_set_dev_sysfs("brport/proxyarp_wifi", dev->ifname, val); } -static void system_bridge_set_other_querier_timeout(struct device *dev, const char *val) +static void system_bridge_set_bpdu_filter(struct device *dev, const char *val) { - system_set_dev_sysctl("/sys/devices/virtual/net/%s/bridge/multicast_querier_interval", - dev->ifname, val); + system_set_dev_sysfs("brport/bpdu_filter", dev->ifname, val); } -static void system_bridge_set_startup_query_interval(struct device *dev, const char *val) +static void system_bridge_set_isolated(struct device *dev, const char *val) { - system_set_dev_sysctl("/sys/devices/virtual/net/%s/bridge/multicast_startup_query_interval", - dev->ifname, val); + system_set_dev_sysfs("brport/isolated", dev->ifname, val); } -static void system_bridge_set_stp_state(struct device *dev, const char *val) +static void system_bridge_set_multicast_router(struct device *dev, const char *val) { - system_set_dev_sysctl("/sys/devices/virtual/net/%s/bridge/stp_state", dev->ifname, val); + system_set_dev_sysfs("brport/multicast_router", dev->ifname, val); } -static void system_bridge_set_forward_delay(struct device *dev, const char *val) +void system_bridge_set_stp_state(struct device *dev, bool val) { - system_set_dev_sysctl("/sys/devices/virtual/net/%s/bridge/forward_delay", dev->ifname, val); -} + const char *valstr = val ? "1" : "0"; -static void system_bridge_set_priority(struct device *dev, const char *val) -{ - system_set_dev_sysctl("/sys/devices/virtual/net/%s/bridge/priority", dev->ifname, val); + system_set_dev_sysfs("bridge/stp_state", dev->ifname, valstr); } -static void system_bridge_set_ageing_time(struct device *dev, const char *val) +static void system_bridge_set_learning(struct device *dev, const char *val) { - system_set_dev_sysctl("/sys/devices/virtual/net/%s/bridge/ageing_time", dev->ifname, val); + system_set_dev_sysfs("brport/learning", dev->ifname, val); } -static void system_bridge_set_hello_time(struct device *dev, const char *val) +static void system_bridge_set_unicast_flood(struct device *dev, const char *val) { - system_set_dev_sysctl("/sys/devices/virtual/net/%s/bridge/hello_time", dev->ifname, val); + system_set_dev_sysfs("brport/unicast_flood", dev->ifname, val); } -static void system_bridge_set_max_age(struct device *dev, const char *val) +static int system_get_disable_ipv6(struct device *dev, char *buf, const size_t buf_sz) { - system_set_dev_sysctl("/sys/devices/virtual/net/%s/bridge/max_age", dev->ifname, val); + return system_get_dev_sysctl("ipv6/conf", "disable_ipv6", + dev->ifname, buf, buf_sz); } -static void system_bridge_set_learning(struct device *dev, const char *val) +static int system_get_ip6segmentrouting(struct device *dev, char *buf, const size_t buf_sz) { - system_set_dev_sysctl("/sys/class/net/%s/brport/learning", dev->ifname, val); + return system_get_dev_sysctl("ipv6/conf", "seg6_enabled", + dev->ifname, buf, buf_sz); } -static void system_bridge_set_unicast_flood(struct device *dev, const char *val) +static int system_get_rpfilter(struct device *dev, char *buf, const size_t buf_sz) { - system_set_dev_sysctl("/sys/class/net/%s/brport/unicast_flood", dev->ifname, val); + return system_get_dev_sysctl("ipv4/conf", "rp_filter", + dev->ifname, buf, buf_sz); } -static void system_set_sendredirects(struct device *dev, const char *val) +static int system_get_acceptlocal(struct device *dev, char *buf, const size_t buf_sz) { - system_set_dev_sysctl("/proc/sys/net/ipv4/conf/%s/send_redirects", dev->ifname, val); + return system_get_dev_sysctl("ipv4/conf", "accept_local", + dev->ifname, buf, buf_sz); } -static void system_bridge_set_vlan_filtering(struct device *dev, const char *val) +static int system_get_igmpversion(struct device *dev, char *buf, const size_t buf_sz) { - system_set_dev_sysctl("/sys/devices/virtual/net/%s/bridge/vlan_filtering", dev->ifname, val); + return system_get_dev_sysctl("ipv4/conf", "force_igmp_version", + dev->ifname, buf, buf_sz); } -static int system_get_sysctl(const char *path, char *buf, const size_t buf_sz) +static int system_get_mldversion(struct device *dev, char *buf, const size_t buf_sz) { - int fd = -1, ret = -1; - - fd = open(path, O_RDONLY); - if (fd < 0) - goto out; - - ssize_t len = read(fd, buf, buf_sz - 1); - if (len < 0) - goto out; - - ret = buf[len] = 0; - -out: - if (fd >= 0) - close(fd); - - return ret; + return system_get_dev_sysctl("ipv6/conf", "force_mld_version", + dev->ifname, buf, buf_sz); } -static int -system_get_dev_sysctl(const char *path, const char *device, char *buf, const size_t buf_sz) +static int system_get_neigh4reachabletime(struct device *dev, char *buf, const size_t buf_sz) { - snprintf(dev_buf, sizeof(dev_buf), path, device); - return system_get_sysctl(dev_buf, buf, buf_sz); + return system_get_dev_sysctl("ipv4/neigh", "base_reachable_time_ms", + dev->ifname, buf, buf_sz); } -static int system_get_disable_ipv6(struct device *dev, char *buf, const size_t buf_sz) +static int system_get_neigh6reachabletime(struct device *dev, char *buf, const size_t buf_sz) { - return system_get_dev_sysctl("/proc/sys/net/ipv6/conf/%s/disable_ipv6", - dev->ifname, buf, buf_sz); + return system_get_dev_sysctl("ipv6/neigh", "base_reachable_time_ms", + dev->ifname, buf, buf_sz); } -static int system_get_rpfilter(struct device *dev, char *buf, const size_t buf_sz) +static int system_get_neigh4gcstaletime(struct device *dev, char *buf, const size_t buf_sz) { - return system_get_dev_sysctl("/proc/sys/net/ipv4/conf/%s/rp_filter", - dev->ifname, buf, buf_sz); + return system_get_dev_sysctl("ipv4/neigh", "gc_stale_time", + dev->ifname, buf, buf_sz); } -static int system_get_acceptlocal(struct device *dev, char *buf, const size_t buf_sz) +static int system_get_neigh6gcstaletime(struct device *dev, char *buf, const size_t buf_sz) { - return system_get_dev_sysctl("/proc/sys/net/ipv4/conf/%s/accept_local", + return system_get_dev_sysctl("ipv6/neigh", "gc_stale_time", dev->ifname, buf, buf_sz); } -static int system_get_igmpversion(struct device *dev, char *buf, const size_t buf_sz) +static int system_get_neigh4locktime(struct device *dev, char *buf, const size_t buf_sz) { - return system_get_dev_sysctl("/proc/sys/net/ipv4/conf/%s/force_igmp_version", + return system_get_dev_sysctl("ipv4/neigh", "locktime", dev->ifname, buf, buf_sz); } -static int system_get_mldversion(struct device *dev, char *buf, const size_t buf_sz) +static int system_get_dadtransmits(struct device *dev, char *buf, const size_t buf_sz) { - return system_get_dev_sysctl("/proc/sys/net/ipv6/conf/%s/force_mld_version", + return system_get_dev_sysctl("ipv6/conf", "dad_transmits", dev->ifname, buf, buf_sz); } -static int system_get_neigh4reachabletime(struct device *dev, char *buf, const size_t buf_sz) +static int system_get_sendredirects(struct device *dev, char *buf, const size_t buf_sz) { - return system_get_dev_sysctl("/proc/sys/net/ipv4/neigh/%s/base_reachable_time_ms", + return system_get_dev_sysctl("ipv4/conf", "send_redirects", dev->ifname, buf, buf_sz); } -static int system_get_neigh6reachabletime(struct device *dev, char *buf, const size_t buf_sz) + +static int system_get_drop_v4_unicast_in_l2_multicast(struct device *dev, char *buf, const size_t buf_sz) { - return system_get_dev_sysctl("/proc/sys/net/ipv6/neigh/%s/base_reachable_time_ms", + return system_get_dev_sysctl("ipv4/conf", "drop_unicast_in_l2_multicast", dev->ifname, buf, buf_sz); } -static int system_get_neigh4gcstaletime(struct device *dev, char *buf, const size_t buf_sz) +static int system_get_drop_v6_unicast_in_l2_multicast(struct device *dev, char *buf, const size_t buf_sz) { - return system_get_dev_sysctl("/proc/sys/net/ipv4/neigh/%s/gc_stale_time", + return system_get_dev_sysctl("ipv6/conf", "drop_unicast_in_l2_multicast", dev->ifname, buf, buf_sz); } -static int system_get_neigh6gcstaletime(struct device *dev, char *buf, const size_t buf_sz) +static int system_get_drop_gratuitous_arp(struct device *dev, char *buf, const size_t buf_sz) { - return system_get_dev_sysctl("/proc/sys/net/ipv6/neigh/%s/gc_stale_time", + return system_get_dev_sysctl("ipv4/conf", "drop_gratuitous_arp", dev->ifname, buf, buf_sz); } -static int system_get_neigh4locktime(struct device *dev, char *buf, const size_t buf_sz) +static int system_get_drop_unsolicited_na(struct device *dev, char *buf, const size_t buf_sz) { - return system_get_dev_sysctl("/proc/sys/net/ipv4/neigh/%s/locktime", + return system_get_dev_sysctl("ipv6/conf", "drop_unsolicited_na", dev->ifname, buf, buf_sz); } -static int system_get_dadtransmits(struct device *dev, char *buf, const size_t buf_sz) +static int system_get_arp_accept(struct device *dev, char *buf, const size_t buf_sz) { - return system_get_dev_sysctl("/proc/sys/net/ipv6/conf/%s/dad_transmits", + return system_get_dev_sysctl("ipv4/conf", "arp_accept", dev->ifname, buf, buf_sz); } -static int system_get_sendredirects(struct device *dev, char *buf, const size_t buf_sz) +#ifndef IFF_LOWER_UP +#define IFF_LOWER_UP 0x10000 +#endif + +static void +system_device_update_state(struct device *dev, unsigned int flags, unsigned int ifindex) { - return system_get_dev_sysctl("/proc/sys/net/ipv4/conf/%s/send_redirects", - dev->ifname, buf, buf_sz); + if (dev->type == &simple_device_type) { + if (dev->external) + device_set_disabled(dev, !(flags & IFF_UP)); + + device_set_present(dev, ifindex > 0); + } + device_set_link(dev, flags & IFF_LOWER_UP ? true : false); } /* Evaluate netlink messages */ static int cb_rtnl_event(struct nl_msg *msg, void *arg) { struct nlmsghdr *nh = nlmsg_hdr(msg); + struct ifinfomsg *ifi = NLMSG_DATA(nh); struct nlattr *nla[__IFLA_MAX]; - int link_state = 0; - char buf[10]; + struct device *dev; if (nh->nlmsg_type != RTM_NEWLINK) - goto out; + return 0; nlmsg_parse(nh, sizeof(struct ifinfomsg), nla, __IFLA_MAX - 1, NULL); if (!nla[IFLA_IFNAME]) - goto out; + return 0; - struct device *dev = device_find(nla_data(nla[IFLA_IFNAME])); + dev = device_find(nla_data(nla[IFLA_IFNAME])); if (!dev) - goto out; - - if (!system_get_dev_sysctl("/sys/class/net/%s/carrier", dev->ifname, buf, sizeof(buf))) - link_state = strtoul(buf, NULL, 0); - - if (dev->type == &simple_device_type && !system_if_force_external(dev->ifname)) - device_set_present(dev, true); - - device_set_link(dev, link_state ? true : false); + return 0; -out: + system_device_update_state(dev, ifi->ifi_flags, ifi->ifi_index); return 0; } @@ -611,18 +741,13 @@ handle_hotplug_msg(char *data, int size) { const char *subsystem = NULL, *interface = NULL, *interface_old = NULL; char *cur, *end, *sep; - struct device *dev; int skip; - bool add, move = false; + bool add; - if (!strncmp(data, "add@", 4)) + if (!strncmp(data, "add@", 4) || !strncmp(data, "move@", 5)) add = true; else if (!strncmp(data, "remove@", 7)) add = false; - else if (!strncmp(data, "move@", 5)) { - add = true; - move = true; - } else return; @@ -650,39 +775,13 @@ handle_hotplug_msg(char *data, int size) } } - if (subsystem && interface) { - if (move && interface_old) - goto move; - else - goto found; - } - - return; - -move: - dev = device_find(interface_old); - if (!dev) - return; - - if (dev->type != &simple_device_type) - goto found; - - device_set_present(dev, false); - - return; - -found: - dev = device_find(interface); - if (!dev) - return; - - if (dev->type != &simple_device_type) + if (!subsystem || !interface) return; - if (add && system_if_force_external(dev->ifname)) - return; + if (interface_old) + device_hotplug_event(interface_old, false); - device_set_present(dev, add); + device_hotplug_event(interface, add); } static void @@ -692,24 +791,19 @@ handle_hotplug_event(struct uloop_fd *u, unsigned int events) struct sockaddr_nl nla; unsigned char *buf = NULL; int size; - int err; - socklen_t errlen = sizeof(err); - if (!u->error) { - while ((size = nl_recv(ev->sock, &nla, &buf, NULL)) > 0) { - if (nla.nl_pid == 0) - handle_hotplug_msg((char *) buf, size); + while ((size = nl_recv(ev->sock, &nla, &buf, NULL)) > 0) { + if (nla.nl_pid == 0) + handle_hotplug_msg((char *) buf, size); - free(buf); - } - return; + free(buf); } - if (getsockopt(u->fd, SOL_SOCKET, SO_ERROR, (void *)&err, &errlen)) - goto abort; + switch (-size) { + case 0: + return; - switch(err) { - case ENOBUFS: + case NLE_NOMEM: /* Increase rx buffer size on netlink socket */ ev->bufsize *= 2; if (nl_socket_set_buffer_size(ev->sock, ev->bufsize, 0)) @@ -719,7 +813,6 @@ handle_hotplug_event(struct uloop_fd *u, unsigned int events) default: goto abort; } - u->error = false; return; abort: @@ -740,9 +833,44 @@ static int system_rtnl_call(struct nl_msg *msg) return nl_wait_for_ack(sock_rtnl); } +static struct nl_msg *__system_ifinfo_msg(int af, int index, const char *ifname, uint16_t type, uint16_t flags) +{ + struct nl_msg *msg; + struct ifinfomsg iim = { + .ifi_family = af, + .ifi_index = index, + }; + + msg = nlmsg_alloc_simple(type, flags | NLM_F_REQUEST); + if (!msg) + return NULL; + + nlmsg_append(msg, &iim, sizeof(iim), 0); + if (ifname) + nla_put_string(msg, IFLA_IFNAME, ifname); + + return msg; +} + +static struct nl_msg *system_ifinfo_msg(const char *ifname, uint16_t type, uint16_t flags) +{ + return __system_ifinfo_msg(AF_UNSPEC, 0, ifname, type, flags); +} + +static int system_link_del(const char *ifname) +{ + struct nl_msg *msg; + + msg = system_ifinfo_msg(ifname, RTM_DELLINK, 0); + if (!msg) + return -1; + + return system_rtnl_call(msg); +} + int system_bridge_delbr(struct device *bridge) { - return ioctl(sock_ioctl, SIOCBRDELBR, bridge->ifname); + return system_link_del(bridge->ifname); } static int system_bridge_if(const char *bridge, struct device *dev, int cmd, void *data) @@ -758,15 +886,11 @@ static int system_bridge_if(const char *bridge, struct device *dev, int cmd, voi return ioctl(sock_ioctl, cmd, &ifr); } -static bool system_is_bridge(const char *name, char *buf, int buflen) +static bool system_is_bridge(const char *name) { struct stat st; - snprintf(buf, buflen, "/sys/devices/virtual/net/%s/bridge", name); - if (stat(buf, &st) < 0) - return false; - - return true; + return stat(dev_sysfs_path(name, "bridge"), &st) >= 0; } static char *system_get_bridge(const char *name, char *buf, int buflen) @@ -775,7 +899,7 @@ static char *system_get_bridge(const char *name, char *buf, int buflen) ssize_t len = -1; glob_t gl; - snprintf(buf, buflen, "/sys/devices/virtual/net/*/brif/%s/bridge", name); + snprintf(buf, buflen, "%s/devices/virtual/net/*/brif/%s/bridge", sysfs_path, name); if (glob(buf, GLOB_NOSORT, NULL, &gl) < 0) return NULL; @@ -799,35 +923,51 @@ static void system_bridge_set_wireless(struct device *bridge, struct device *dev) { bool mcast_to_ucast = dev->wireless_ap; - bool hairpin = true; + bool hairpin; - if (bridge->settings.flags & DEV_OPT_MULTICAST_TO_UNICAST && - !bridge->settings.multicast_to_unicast) + if (dev->settings.flags & DEV_OPT_MULTICAST_TO_UNICAST) + mcast_to_ucast = dev->settings.multicast_to_unicast; + else if (bridge->settings.flags & DEV_OPT_MULTICAST_TO_UNICAST && + !bridge->settings.multicast_to_unicast) mcast_to_ucast = false; - if (!mcast_to_ucast || dev->wireless_isolate) + hairpin = mcast_to_ucast || dev->wireless_proxyarp; + if (dev->wireless_isolate) hairpin = false; system_bridge_set_multicast_to_unicast(dev, mcast_to_ucast ? "1" : "0"); system_bridge_set_hairpin_mode(dev, hairpin ? "1" : "0"); + system_bridge_set_proxyarp_wifi(dev, dev->wireless_proxyarp ? "1" : "0"); } int system_bridge_addif(struct device *bridge, struct device *dev) { char buf[64]; char *oldbr; - int ret = 0; + int tries = 0; + int ret; + + + for (tries = 0; tries < 3; tries++) { + ret = 0; + oldbr = system_get_bridge(dev->ifname, dev_buf, sizeof(dev_buf)); + if (oldbr && !strcmp(oldbr, bridge->ifname)) + break; - oldbr = system_get_bridge(dev->ifname, dev_buf, sizeof(dev_buf)); - if (!oldbr || strcmp(oldbr, bridge->ifname) != 0) ret = system_bridge_if(bridge->ifname, dev, SIOCBRADDIF, NULL); + if (!ret) + break; + + D(SYSTEM, "Failed to add device '%s' to bridge '%s' (tries=%d): %s", + dev->ifname, bridge->ifname, tries, strerror(errno)); + } if (dev->wireless) system_bridge_set_wireless(bridge, dev); if (dev->settings.flags & DEV_OPT_MULTICAST_ROUTER) { snprintf(buf, sizeof(buf), "%u", dev->settings.multicast_router); - system_bridge_set_multicast_router(dev, buf, false); + system_bridge_set_multicast_router(dev, buf); } if (dev->settings.flags & DEV_OPT_MULTICAST_FAST_LEAVE && @@ -846,6 +986,9 @@ int system_bridge_addif(struct device *bridge, struct device *dev) dev->settings.isolate) system_bridge_set_isolated(dev, "1"); + if (dev->bpdu_filter) + system_bridge_set_bpdu_filter(dev, dev->bpdu_filter ? "1" : "0"); + return ret; } @@ -854,25 +997,23 @@ int system_bridge_delif(struct device *bridge, struct device *dev) return system_bridge_if(bridge->ifname, dev, SIOCBRDELIF, NULL); } -int system_bridge_vlan(const char *iface, uint16_t vid, bool add, unsigned int vflags) +int system_bridge_vlan(const char *iface, uint16_t vid, int16_t vid_end, bool add, unsigned int vflags) { - struct ifinfomsg ifi = { .ifi_family = PF_BRIDGE, }; struct bridge_vlan_info vinfo = { .vid = vid, }; unsigned short flags = 0; struct nlattr *afspec; struct nl_msg *nlm; + int index; int ret = 0; - ifi.ifi_index = if_nametoindex(iface); - if (!ifi.ifi_index) + index = if_nametoindex(iface); + if (!index) return -1; - nlm = nlmsg_alloc_simple(add ? RTM_SETLINK : RTM_DELLINK, NLM_F_REQUEST); + nlm = __system_ifinfo_msg(PF_BRIDGE, index, NULL, add ? RTM_SETLINK : RTM_DELLINK, 0); if (!nlm) return -1; - nlmsg_append(nlm, &ifi, sizeof(ifi), 0); - if (vflags & BRVLAN_F_SELF) flags |= BRIDGE_FLAGS_SELF; @@ -891,7 +1032,18 @@ int system_bridge_vlan(const char *iface, uint16_t vid, bool add, unsigned int v if (flags) nla_put_u16(nlm, IFLA_BRIDGE_FLAGS, flags); + if (vid_end > vid) + vinfo.flags |= BRIDGE_VLAN_INFO_RANGE_BEGIN; + nla_put(nlm, IFLA_BRIDGE_VLAN_INFO, sizeof(vinfo), &vinfo); + + if (vid_end > vid) { + vinfo.flags &= ~BRIDGE_VLAN_INFO_RANGE_BEGIN; + vinfo.flags |= BRIDGE_VLAN_INFO_RANGE_END; + vinfo.vid = vid_end; + nla_put(nlm, IFLA_BRIDGE_VLAN_INFO, sizeof(vinfo), &vinfo); + } + nla_nest_end(nlm, afspec); return system_rtnl_call(nlm); @@ -901,6 +1053,91 @@ failure: return ret; } +int system_bonding_set_device(struct device *dev, struct bonding_config *cfg) +{ + const char *ifname = dev->ifname; + struct blob_attr *cur; + char op = cfg ? '+' : '-'; + char buf[64]; + size_t rem; + + snprintf(dev_buf, sizeof(dev_buf), "%s/class/net/bonding_masters", sysfs_path); + snprintf(buf, sizeof(buf), "%c%s", op, ifname); + write_file(dev_buf, buf); + + if (!cfg) + return 0; + + system_set_dev_sysfs("bonding/mode", ifname, bonding_policy_str[cfg->policy]); + + system_set_dev_sysfs_int("bonding/all_ports_active", ifname, cfg->all_ports_active); + + if (cfg->policy == BONDING_MODE_BALANCE_XOR || + cfg->policy == BONDING_MODE_BALANCE_TLB || + cfg->policy == BONDING_MODE_8023AD) + system_set_dev_sysfs("bonding/xmit_hash_policy", ifname, cfg->xmit_hash_policy); + + if (cfg->policy == BONDING_MODE_8023AD) { + system_set_dev_sysfs("bonding/ad_actor_system", ifname, cfg->ad_actor_system); + system_set_dev_sysfs_int("bonding/ad_actor_sys_prio", ifname, cfg->ad_actor_sys_prio); + system_set_dev_sysfs("bonding/ad_select", ifname, cfg->ad_select); + system_set_dev_sysfs("bonding/lacp_rate", ifname, cfg->lacp_rate); + system_set_dev_sysfs_int("bonding/min_links", ifname, cfg->min_links); + } + + if (cfg->policy == BONDING_MODE_BALANCE_RR) + system_set_dev_sysfs_int("bonding/packets_per_slave", ifname, cfg->packets_per_port); + + if (cfg->policy == BONDING_MODE_BALANCE_TLB || + cfg->policy == BONDING_MODE_BALANCE_ALB) + system_set_dev_sysfs_int("bonding/lp_interval", ifname, cfg->lp_interval); + + if (cfg->policy == BONDING_MODE_BALANCE_TLB) + system_set_dev_sysfs_int("bonding/tlb_dynamic_lb", ifname, cfg->dynamic_lb); + system_set_dev_sysfs_int("bonding/resend_igmp", ifname, cfg->resend_igmp); + system_set_dev_sysfs_int("bonding/num_grat_arp", ifname, cfg->num_peer_notif); + system_set_dev_sysfs("bonding/primary_reselect", ifname, cfg->primary_reselect); + system_set_dev_sysfs("bonding/fail_over_mac", ifname, cfg->failover_mac); + + system_set_dev_sysfs_int((cfg->monitor_arp ? + "bonding/arp_interval" : + "bonding/miimon"), ifname, cfg->monitor_interval); + + blobmsg_for_each_attr(cur, cfg->arp_target, rem) { + snprintf(buf, sizeof(buf), "+%s", blobmsg_get_string(cur)); + system_set_dev_sysfs("bonding/arp_ip_target", ifname, buf); + } + + system_set_dev_sysfs_int("bonding/arp_all_targets", ifname, cfg->arp_all_targets); + if (cfg->policy < BONDING_MODE_8023AD) + system_set_dev_sysfs("bonding/arp_validate", ifname, cfg->arp_validate); + system_set_dev_sysfs_int("bonding/use_carrier", ifname, cfg->use_carrier); + if (!cfg->monitor_arp && cfg->monitor_interval) { + system_set_dev_sysfs_int("bonding/updelay", ifname, cfg->updelay); + system_set_dev_sysfs_int("bonding/downdelay", ifname, cfg->downdelay); + } + + return 0; +} + +int system_bonding_set_port(struct device *dev, struct device *port, bool add, bool primary) +{ + const char *port_name = port->ifname; + const char op_ch = add ? '+' : '-'; + char buf[IFNAMSIZ + 1]; + + snprintf(buf, sizeof(buf), "%c%s", op_ch, port_name); + system_if_down(port); + system_set_dev_sysfs("bonding/slaves", dev->ifname, buf); + system_if_up(port); + + if (primary) + system_set_dev_sysfs("bonding/primary", dev->ifname, + add ? port_name : ""); + + return 0; +} + int system_if_resolve(struct device *dev) { struct ifreq ifr; @@ -940,7 +1177,7 @@ static bool check_ifaddr(struct nlmsghdr *hdr, int ifindex) { struct ifaddrmsg *ifa = NLMSG_DATA(hdr); - return ifa->ifa_index == ifindex; + return (long)ifa->ifa_index == ifindex; } static bool check_route(struct nlmsghdr *hdr, int ifindex) @@ -1001,9 +1238,9 @@ static int cb_clear_event(struct nl_msg *msg, void *arg) return NL_SKIP; if (type == RTM_DELRULE) - D(SYSTEM, "Remove a rule\n"); + D(SYSTEM, "Remove a rule"); else - D(SYSTEM, "Remove %s from device %s\n", + D(SYSTEM, "Remove %s from device %s", type == RTM_DELADDR ? "an address" : "a route", clr->dev->ifname); @@ -1016,9 +1253,9 @@ static int cb_clear_event(struct nl_msg *msg, void *arg) ret = nl_send_auto_complete(sock_rtnl, clr->msg); if (ret < 0) { if (type == RTM_DELRULE) - D(SYSTEM, "Error deleting a rule: %d\n", ret); + D(SYSTEM, "Error deleting a rule: %d", ret); else - D(SYSTEM, "Error deleting %s from device '%s': %d\n", + D(SYSTEM, "Error deleting %s from device '%s': %d", type == RTM_DELADDR ? "an address" : "a route", clr->dev->ifname, ret); } @@ -1110,15 +1347,15 @@ void system_if_clear_state(struct device *dev) system_if_flags(dev->ifname, 0, IFF_UP); - if (system_is_bridge(dev->ifname, buf, sizeof(buf))) { - D(SYSTEM, "Delete existing bridge named '%s'\n", dev->ifname); + if (system_is_bridge(dev->ifname)) { + D(SYSTEM, "Delete existing bridge named '%s'", dev->ifname); system_bridge_delbr(dev); return; } bridge = system_get_bridge(dev->ifname, buf, sizeof(buf)); if (bridge) { - D(SYSTEM, "Remove device '%s' from bridge '%s'\n", dev->ifname, bridge); + D(SYSTEM, "Remove device '%s' from bridge '%s'", dev->ifname, bridge); system_bridge_if(bridge, dev, SIOCBRDELIF, NULL); } @@ -1137,122 +1374,95 @@ sec_to_jiffies(int val) return (unsigned long) val * 100; } -static void system_bridge_conf_multicast_deps(struct device *bridge, - struct bridge_config *cfg, - char *buf, - int buf_len) +int system_bridge_addbr(struct device *bridge, struct bridge_config *cfg) { - int val; + struct nlattr *linkinfo, *data; + struct nl_msg *msg; + uint64_t val; + int rv; - if (cfg->flags & BRIDGE_OPT_ROBUSTNESS || - cfg->flags & BRIDGE_OPT_QUERY_INTERVAL || - cfg->flags & BRIDGE_OPT_QUERY_RESPONSE_INTERVAL) { - val = cfg->robustness * cfg->query_interval + - cfg->query_response_interval; - - snprintf(buf, buf_len, "%i", val); - system_bridge_set_membership_interval(bridge, buf); - - val = cfg->robustness * cfg->query_interval + - cfg->query_response_interval / 2; - - snprintf(buf, buf_len, "%i", val); - system_bridge_set_other_querier_timeout(bridge, buf); - } - - if (cfg->flags & BRIDGE_OPT_QUERY_INTERVAL) { - val = cfg->query_interval / 4; + msg = system_ifinfo_msg(bridge->ifname, RTM_NEWLINK, NLM_F_CREATE | NLM_F_EXCL); + if (!msg) + return -1; - snprintf(buf, buf_len, "%i", val); - system_bridge_set_startup_query_interval(bridge, buf); - } -} + if (!(linkinfo = nla_nest_start(msg, IFLA_LINKINFO))) + goto nla_put_failure; -static void system_bridge_conf_multicast(struct device *bridge, - struct bridge_config *cfg, - char *buf, - int buf_len) -{ - system_set_dev_sysctl("/sys/devices/virtual/net/%s/bridge/multicast_snooping", - bridge->ifname, cfg->igmp_snoop ? "1" : "0"); + nla_put_string(msg, IFLA_INFO_KIND, "bridge"); - system_set_dev_sysctl("/sys/devices/virtual/net/%s/bridge/multicast_querier", - bridge->ifname, cfg->multicast_querier ? "1" : "0"); + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; - snprintf(buf, buf_len, "%i", cfg->hash_max); - system_set_dev_sysctl("/sys/devices/virtual/net/%s/bridge/hash_max", - bridge->ifname, buf); + nla_put_u32(msg, IFLA_BR_STP_STATE, cfg->stp); + nla_put_u32(msg, IFLA_BR_FORWARD_DELAY, sec_to_jiffies(cfg->forward_delay)); + nla_put_u8(msg, IFLA_BR_MCAST_SNOOPING, !!cfg->igmp_snoop); + nla_put_u8(msg, IFLA_BR_MCAST_QUERIER, !!cfg->multicast_querier); + nla_put_u32(msg, IFLA_BR_MCAST_HASH_MAX, cfg->hash_max); - if (bridge->settings.flags & DEV_OPT_MULTICAST_ROUTER) { - snprintf(buf, buf_len, "%u", bridge->settings.multicast_router); - system_bridge_set_multicast_router(bridge, buf, true); - } + if (bridge->settings.flags & DEV_OPT_MULTICAST_ROUTER) + nla_put_u8(msg, IFLA_BR_MCAST_ROUTER, !!bridge->settings.multicast_router); if (cfg->flags & BRIDGE_OPT_ROBUSTNESS) { - snprintf(buf, buf_len, "%i", cfg->robustness); - system_bridge_set_robustness(bridge, buf); + nla_put_u32(msg, IFLA_BR_MCAST_STARTUP_QUERY_CNT, cfg->robustness); + nla_put_u32(msg, IFLA_BR_MCAST_LAST_MEMBER_CNT, cfg->robustness); } - if (cfg->flags & BRIDGE_OPT_QUERY_INTERVAL) { - snprintf(buf, buf_len, "%i", cfg->query_interval); - system_bridge_set_query_interval(bridge, buf); - } + if (cfg->flags & BRIDGE_OPT_QUERY_INTERVAL) + nla_put_u64(msg, IFLA_BR_MCAST_QUERY_INTVL, cfg->query_interval); - if (cfg->flags & BRIDGE_OPT_QUERY_RESPONSE_INTERVAL) { - snprintf(buf, buf_len, "%i", cfg->query_response_interval); - system_bridge_set_query_response_interval(bridge, buf); - } + if (cfg->flags & BRIDGE_OPT_QUERY_RESPONSE_INTERVAL) + nla_put_u64(msg, IFLA_BR_MCAST_QUERY_RESPONSE_INTVL, cfg->query_response_interval); - if (cfg->flags & BRIDGE_OPT_LAST_MEMBER_INTERVAL) { - snprintf(buf, buf_len, "%i", cfg->last_member_interval); - system_bridge_set_last_member_interval(bridge, buf); - } + if (cfg->flags & BRIDGE_OPT_LAST_MEMBER_INTERVAL) + nla_put_u64(msg, IFLA_BR_MCAST_LAST_MEMBER_INTVL, cfg->last_member_interval); - system_bridge_conf_multicast_deps(bridge, cfg, buf, buf_len); -} + if (cfg->flags & BRIDGE_OPT_ROBUSTNESS || + cfg->flags & BRIDGE_OPT_QUERY_INTERVAL || + cfg->flags & BRIDGE_OPT_QUERY_RESPONSE_INTERVAL) { + val = cfg->robustness * cfg->query_interval + + cfg->query_response_interval; -int system_bridge_addbr(struct device *bridge, struct bridge_config *cfg) -{ - char buf[64]; + nla_put_u64(msg, IFLA_BR_MCAST_MEMBERSHIP_INTVL, val); - if (ioctl(sock_ioctl, SIOCBRADDBR, bridge->ifname) < 0) - return -1; + val -= cfg->query_response_interval / 2; - system_bridge_set_stp_state(bridge, cfg->stp ? "1" : "0"); + nla_put_u64(msg, IFLA_BR_MCAST_QUERIER_INTVL, val); + } - snprintf(buf, sizeof(buf), "%lu", sec_to_jiffies(cfg->forward_delay)); - system_bridge_set_forward_delay(bridge, buf); + if (cfg->flags & BRIDGE_OPT_QUERY_INTERVAL) { + val = cfg->query_interval / 4; - system_bridge_conf_multicast(bridge, cfg, buf, sizeof(buf)); - system_bridge_set_vlan_filtering(bridge, cfg->vlan_filtering ? "1" : "0"); + nla_put_u64(msg, IFLA_BR_MCAST_STARTUP_QUERY_INTVL, val); + } - snprintf(buf, sizeof(buf), "%d", cfg->priority); - system_bridge_set_priority(bridge, buf); + nla_put_u8(msg, IFLA_BR_VLAN_FILTERING, !!cfg->vlan_filtering); + nla_put_u16(msg, IFLA_BR_PRIORITY, cfg->priority); + nla_put_u32(msg, IFLA_BR_HELLO_TIME, sec_to_jiffies(cfg->hello_time)); + nla_put_u32(msg, IFLA_BR_MAX_AGE, sec_to_jiffies(cfg->max_age)); - if (cfg->flags & BRIDGE_OPT_AGEING_TIME) { - snprintf(buf, sizeof(buf), "%lu", sec_to_jiffies(cfg->ageing_time)); - system_bridge_set_ageing_time(bridge, buf); - } + if (cfg->flags & BRIDGE_OPT_AGEING_TIME) + nla_put_u32(msg, IFLA_BR_AGEING_TIME, sec_to_jiffies(cfg->ageing_time)); - if (cfg->flags & BRIDGE_OPT_HELLO_TIME) { - snprintf(buf, sizeof(buf), "%lu", sec_to_jiffies(cfg->hello_time)); - system_bridge_set_hello_time(bridge, buf); - } + nla_nest_end(msg, data); + nla_nest_end(msg, linkinfo); - if (cfg->flags & BRIDGE_OPT_MAX_AGE) { - snprintf(buf, sizeof(buf), "%lu", sec_to_jiffies(cfg->max_age)); - system_bridge_set_max_age(bridge, buf); - } + rv = system_rtnl_call(msg); + if (rv) + D(SYSTEM, "Error adding bridge '%s': %d", bridge->ifname, rv); - return 0; + return rv; + +nla_put_failure: + nlmsg_free(msg); + return -ENOMEM; } int system_macvlan_add(struct device *macvlan, struct device *dev, struct macvlan_config *cfg) { struct nl_msg *msg; struct nlattr *linkinfo, *data; - struct ifinfomsg iim = { .ifi_family = AF_UNSPEC, }; - int i, rv; + size_t i; + int rv; static const struct { const char *name; enum macvlan_mode val; @@ -1263,16 +1473,12 @@ int system_macvlan_add(struct device *macvlan, struct device *dev, struct macvla { "passthru", MACVLAN_MODE_PASSTHRU }, }; - msg = nlmsg_alloc_simple(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL); - + msg = system_ifinfo_msg(macvlan->ifname, RTM_NEWLINK, NLM_F_CREATE | NLM_F_EXCL); if (!msg) return -1; - nlmsg_append(msg, &iim, sizeof(iim), 0); - if (cfg->flags & MACVLAN_OPT_MACADDR) nla_put(msg, IFLA_ADDRESS, sizeof(cfg->macaddr), cfg->macaddr); - nla_put_string(msg, IFLA_IFNAME, macvlan->ifname); nla_put_u32(msg, IFLA_LINK, dev->ifindex); if (!(linkinfo = nla_nest_start(msg, IFLA_LINKINFO))) @@ -1298,7 +1504,7 @@ int system_macvlan_add(struct device *macvlan, struct device *dev, struct macvla rv = system_rtnl_call(msg); if (rv) - D(SYSTEM, "Error adding macvlan '%s' over '%s': %d\n", macvlan->ifname, dev->ifname, rv); + D(SYSTEM, "Error adding macvlan '%s' over '%s': %d", macvlan->ifname, dev->ifname, rv); return rv; @@ -1310,45 +1516,20 @@ nla_put_failure: int system_link_netns_move(struct device *dev, int netns_fd, const char *target_ifname) { struct nl_msg *msg; - struct ifinfomsg iim = { - .ifi_family = AF_UNSPEC, - }; + int index; if (!dev) return -1; - iim.ifi_index = system_if_resolve(dev); - msg = nlmsg_alloc_simple(RTM_NEWLINK, NLM_F_REQUEST); - + index = system_if_resolve(dev); + msg = __system_ifinfo_msg(AF_UNSPEC, index, target_ifname, RTM_NEWLINK, 0); if (!msg) return -1; - nlmsg_append(msg, &iim, sizeof(iim), 0); - if (target_ifname) - nla_put_string(msg, IFLA_IFNAME, target_ifname); - nla_put_u32(msg, IFLA_NET_NS_FD, netns_fd); return system_rtnl_call(msg); } -static int system_link_del(const char *ifname) -{ - struct nl_msg *msg; - struct ifinfomsg iim = { - .ifi_family = AF_UNSPEC, - .ifi_index = 0, - }; - - msg = nlmsg_alloc_simple(RTM_DELLINK, NLM_F_REQUEST); - - if (!msg) - return -1; - - nlmsg_append(msg, &iim, sizeof(iim), 0); - nla_put_string(msg, IFLA_IFNAME, ifname); - return system_rtnl_call(msg); -} - int system_macvlan_del(struct device *macvlan) { return system_link_del(macvlan->ifname); @@ -1371,7 +1552,7 @@ int system_netns_set(int netns_fd) int system_veth_add(struct device *veth, struct veth_config *cfg) { struct nl_msg *msg; - struct ifinfomsg empty_iim = {}; + struct ifinfomsg empty_iim = {0,}; struct nlattr *linkinfo, *data, *veth_info; int rv; @@ -1411,9 +1592,9 @@ int system_veth_add(struct device *veth, struct veth_config *cfg) rv = system_rtnl_call(msg); if (rv) { if (cfg->flags & VETH_OPT_PEER_NAME) - D(SYSTEM, "Error adding veth '%s' with peer '%s': %d\n", veth->ifname, cfg->peer_name, rv); + D(SYSTEM, "Error adding veth '%s' with peer '%s': %d", veth->ifname, cfg->peer_name, rv); else - D(SYSTEM, "Error adding veth '%s': %d\n", veth->ifname, rv); + D(SYSTEM, "Error adding veth '%s': %d", veth->ifname, rv); } return rv; @@ -1519,7 +1700,7 @@ int system_vlandev_add(struct device *vlandev, struct device *dev, struct vlande rv = system_rtnl_call(msg); if (rv) - D(SYSTEM, "Error adding vlandev '%s' over '%s': %d\n", vlandev->ifname, dev->ifname, rv); + D(SYSTEM, "Error adding vlandev '%s' over '%s': %d", vlandev->ifname, dev->ifname, rv); return rv; @@ -1533,11 +1714,395 @@ int system_vlandev_del(struct device *vlandev) return system_link_del(vlandev->ifname); } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6,1,0) +struct if_get_master_data { + int ifindex; + int master_ifindex; + int pending; +}; + +static void if_get_master_dsa_linkinfo_attr(struct if_get_master_data *data, + struct rtattr *attr) +{ + struct rtattr *cur; + int rem = RTA_PAYLOAD(attr); + + for (cur = RTA_DATA(attr); RTA_OK(cur, rem); cur = RTA_NEXT(cur, rem)) { + if (cur->rta_type != IFLA_DSA_MASTER) + continue; + + data->master_ifindex = *(__u32 *)RTA_DATA(cur); + } +} + +static void if_get_master_linkinfo_attr(struct if_get_master_data *data, + struct rtattr *attr) +{ + struct rtattr *cur; + int rem = RTA_PAYLOAD(attr); + + for (cur = RTA_DATA(attr); RTA_OK(cur, rem); cur = RTA_NEXT(cur, rem)) { + if (cur->rta_type != IFLA_INFO_KIND && cur->rta_type != IFLA_INFO_DATA) + continue; + + if (cur->rta_type == IFLA_INFO_KIND && strcmp("dsa", (char *)RTA_DATA(cur))) + break; + + if (cur->rta_type == IFLA_INFO_DATA) + if_get_master_dsa_linkinfo_attr(data, cur); + } +} + +static int cb_if_get_master_valid(struct nl_msg *msg, void *arg) +{ + struct nlmsghdr *nh = nlmsg_hdr(msg); + struct ifinfomsg *ifi = NLMSG_DATA(nh); + struct if_get_master_data *data = (struct if_get_master_data *)arg; + struct rtattr *attr; + int rem; + + if (nh->nlmsg_type != RTM_NEWLINK) + return NL_SKIP; + + if (ifi->ifi_family != AF_UNSPEC) + return NL_SKIP; + + if (ifi->ifi_index != data->ifindex) + return NL_SKIP; + + attr = IFLA_RTA(ifi); + rem = nh->nlmsg_len - NLMSG_LENGTH(sizeof(*ifi)); + + while (RTA_OK(attr, rem)) { + if (attr->rta_type == IFLA_LINKINFO) + if_get_master_linkinfo_attr(data, attr); + + attr = RTA_NEXT(attr, rem); + } + + return NL_OK; +} + +static int cb_if_get_master_ack(struct nl_msg *msg, void *arg) +{ + struct if_get_master_data *data = (struct if_get_master_data *)arg; + data->pending = 0; + return NL_STOP; +} + +static int cb_if_get_master_error(struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg) +{ + struct if_get_master_data *data = (struct if_get_master_data *)arg; + data->pending = 0; + return NL_STOP; +} + +static int system_if_get_master_ifindex(struct device *dev) +{ + 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_get_master_data data = { + .ifindex = if_nametoindex(dev->ifname), + .master_ifindex = -1, + .pending = 1, + }; + int ret = -1; + + if (!cb) + return ret; + + msg = nlmsg_alloc_simple(RTM_GETLINK, NLM_F_REQUEST); + if (!msg) + goto out; + + if (nlmsg_append(msg, &ifi, sizeof(ifi), 0) || + nla_put_string(msg, IFLA_IFNAME, dev->ifname)) + goto free; + + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, cb_if_get_master_valid, &data); + nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, cb_if_get_master_ack, &data); + nl_cb_err(cb, NL_CB_CUSTOM, cb_if_get_master_error, &data); + + ret = nl_send_auto_complete(sock_rtnl, msg); + if (ret < 0) + goto free; + + while (data.pending > 0) + nl_recvmsgs(sock_rtnl, cb); + + if (data.master_ifindex >= 0) + ret = data.master_ifindex; + +free: + nlmsg_free(msg); +out: + nl_cb_put(cb); + return ret; +} + +static void system_refresh_orig_macaddr(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) - 1); + + if (ioctl(sock_ioctl, SIOCGIFHWADDR, &ifr) == 0) + memcpy(s->macaddr, &ifr.ifr_hwaddr.sa_data, sizeof(s->macaddr)); +} + +static void system_set_master(struct device *dev, int master_ifindex) +{ + struct ifinfomsg ifi = { .ifi_family = AF_UNSPEC, }; + struct nl_msg *nlm; + + nlm = nlmsg_alloc_simple(RTM_NEWLINK, NLM_F_REQUEST); + if (!nlm) + return; + + nlmsg_append(nlm, &ifi, sizeof(ifi), 0); + nla_put_string(nlm, IFLA_IFNAME, dev->ifname); + + struct nlattr *linkinfo = nla_nest_start(nlm, IFLA_LINKINFO); + if (!linkinfo) + goto failure; + + nla_put_string(nlm, IFLA_INFO_KIND, "dsa"); + struct nlattr *infodata = nla_nest_start(nlm, IFLA_INFO_DATA); + if (!infodata) + goto failure; + + nla_put_u32(nlm, IFLA_DSA_MASTER, master_ifindex); + + nla_nest_end(nlm, infodata); + nla_nest_end(nlm, linkinfo); + + system_rtnl_call(nlm); + + return; + +failure: + nlmsg_free(nlm); +} +#endif + +static void ethtool_link_mode_clear_bit(__s8 nwords, int nr, __u32 *mask) +{ + if (nr < 0) + return; + + if (nr >= (nwords * 32)) + return; + + mask[nr / 32] &= ~(1U << (nr % 32)); +} + +static bool ethtool_link_mode_test_bit(__s8 nwords, int nr, const __u32 *mask) +{ + if (nr < 0) + return false; + + if (nr >= (nwords * 32)) + return false; + + return !!(mask[nr / 32] & (1U << (nr % 32))); +} + +static int +system_get_ethtool_gro(struct device *dev) +{ + struct ethtool_value ecmd; + struct ifreq ifr = { + .ifr_data = (caddr_t)&ecmd, + }; + + memset(&ecmd, 0, sizeof(ecmd)); + ecmd.cmd = ETHTOOL_GGRO; + strncpy(ifr.ifr_name, dev->ifname, sizeof(ifr.ifr_name) - 1); + + if (ioctl(sock_ioctl, SIOCETHTOOL, &ifr)) + return -1; + + return ecmd.data; +} + +static void +system_set_ethtool_gro(struct device *dev, struct device_settings *s) +{ + struct ethtool_value ecmd; + struct ifreq ifr = { + .ifr_data = (caddr_t)&ecmd, + }; + + memset(&ecmd, 0, sizeof(ecmd)); + ecmd.cmd = ETHTOOL_SGRO; + ecmd.data = s->gro; + strncpy(ifr.ifr_name, dev->ifname, sizeof(ifr.ifr_name) - 1); + + ioctl(sock_ioctl, SIOCETHTOOL, &ifr); +} + +static void +system_set_ethtool_pause(struct device *dev, struct device_settings *s) +{ + struct ethtool_pauseparam pp; + struct ifreq ifr = { + .ifr_data = (caddr_t)&pp, + }; + + strncpy(ifr.ifr_name, dev->ifname, sizeof(ifr.ifr_name) - 1); + memset(&pp, 0, sizeof(pp)); + pp.cmd = ETHTOOL_GPAUSEPARAM; + if (ioctl(sock_ioctl, SIOCETHTOOL, &ifr)) + return; + + if (s->flags & DEV_OPT_RXPAUSE || s->flags & DEV_OPT_TXPAUSE) { + pp.autoneg = AUTONEG_DISABLE; + + if (s->flags & DEV_OPT_PAUSE) { + if (s->flags & DEV_OPT_RXPAUSE) + pp.rx_pause = s->rxpause && s->pause; + else + pp.rx_pause = s->pause; + + if (s->flags & DEV_OPT_TXPAUSE) + pp.tx_pause = s->txpause && s->pause; + else + pp.tx_pause = s->pause; + } else { + if (s->flags & DEV_OPT_RXPAUSE) + pp.rx_pause = s->rxpause; + + if (s->flags & DEV_OPT_TXPAUSE) + pp.tx_pause = s->txpause; + } + + if (s->flags & DEV_OPT_ASYM_PAUSE && + !s->asym_pause && (pp.rx_pause != pp.tx_pause)) + pp.rx_pause = pp.tx_pause = false; + } else { + pp.autoneg = AUTONEG_ENABLE; + /* Pause and Asym_Pause advertising bits will be set via + * ETHTOOL_SLINKSETTINGS in system_set_ethtool_settings() + */ + } + + pp.cmd = ETHTOOL_SPAUSEPARAM; + ioctl(sock_ioctl, SIOCETHTOOL, &ifr); +} + +static void +system_set_ethtool_eee_settings(struct device *dev, struct device_settings *s) +{ + struct ethtool_eee eeecmd; + struct ifreq ifr = { + .ifr_data = (caddr_t)&eeecmd, + }; + + memset(&eeecmd, 0, sizeof(eeecmd)); + eeecmd.cmd = ETHTOOL_SEEE; + eeecmd.eee_enabled = s->eee; + strncpy(ifr.ifr_name, dev->ifname, sizeof(ifr.ifr_name) - 1); + + if (ioctl(sock_ioctl, SIOCETHTOOL, &ifr) != 0) + netifd_log_message(L_WARNING, "cannot set eee %d for device %s", s->eee, dev->ifname); +} + +static void +system_set_ethtool_settings(struct device *dev, struct device_settings *s) +{ + struct { + struct ethtool_link_settings req; + __u32 link_mode_data[3 * 127]; + } ecmd; + struct ifreq ifr = { + .ifr_data = (caddr_t)&ecmd, + }; + size_t i; + __s8 nwords; + __u32 *supported, *advertising; + + system_set_ethtool_pause(dev, s); + + if (s->flags & DEV_OPT_EEE) + system_set_ethtool_eee_settings(dev, s); + + memset(&ecmd, 0, sizeof(ecmd)); + ecmd.req.cmd = ETHTOOL_GLINKSETTINGS; + strncpy(ifr.ifr_name, dev->ifname, sizeof(ifr.ifr_name) - 1); + + if (ioctl(sock_ioctl, SIOCETHTOOL, &ifr) < 0 || + ecmd.req.link_mode_masks_nwords >= 0 || + ecmd.req.cmd != ETHTOOL_GLINKSETTINGS) + return; + + ecmd.req.link_mode_masks_nwords = -ecmd.req.link_mode_masks_nwords; + + if (ioctl(sock_ioctl, SIOCETHTOOL, &ifr) < 0 || + ecmd.req.link_mode_masks_nwords <= 0 || + ecmd.req.cmd != ETHTOOL_GLINKSETTINGS) + return; + + nwords = ecmd.req.link_mode_masks_nwords; + supported = &ecmd.link_mode_data[0]; + advertising = &ecmd.link_mode_data[nwords]; + memcpy(advertising, supported, sizeof(__u32) * nwords); + + for (i = 0; i < ARRAY_SIZE(ethtool_modes); i++) { + if (s->flags & DEV_OPT_DUPLEX) { + if (s->duplex) + ethtool_link_mode_clear_bit(nwords, ethtool_modes[i].bit_half, advertising); + else + ethtool_link_mode_clear_bit(nwords, ethtool_modes[i].bit_full, advertising); + } + if (!(s->flags & DEV_OPT_SPEED) || + s->speed == ethtool_modes[i].speed) + continue; + + ethtool_link_mode_clear_bit(nwords, ethtool_modes[i].bit_full, advertising); + ethtool_link_mode_clear_bit(nwords, ethtool_modes[i].bit_half, advertising); + } + + if (s->flags & DEV_OPT_PAUSE) + if (!s->pause) + ethtool_link_mode_clear_bit(nwords, ETHTOOL_LINK_MODE_Pause_BIT, advertising); + + if (s->flags & DEV_OPT_ASYM_PAUSE) + if (!s->asym_pause) + ethtool_link_mode_clear_bit(nwords, ETHTOOL_LINK_MODE_Asym_Pause_BIT, advertising); + + if (s->flags & DEV_OPT_AUTONEG) { + ecmd.req.autoneg = s->autoneg ? AUTONEG_ENABLE : AUTONEG_DISABLE; + if (!s->autoneg) { + if (s->flags & DEV_OPT_SPEED) + ecmd.req.speed = s->speed; + + if (s->flags & DEV_OPT_DUPLEX) + ecmd.req.duplex = s->duplex ? DUPLEX_FULL : DUPLEX_HALF; + } + } + + ecmd.req.cmd = ETHTOOL_SLINKSETTINGS; + ioctl(sock_ioctl, SIOCETHTOOL, &ifr); +} + +static void +system_set_ethtool_settings_after_up(struct device *dev, struct device_settings *s) +{ + if (s->flags & DEV_OPT_GRO) + system_set_ethtool_gro(dev, s); +} + void system_if_get_settings(struct device *dev, struct device_settings *s) { struct ifreq ifr; char buf[10]; + int ret; memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, dev->ifname, sizeof(ifr.ifr_name) - 1); @@ -1566,6 +2131,11 @@ system_if_get_settings(struct device *dev, struct device_settings *s) s->flags |= DEV_OPT_IPV6; } + if (!system_get_ip6segmentrouting(dev, buf, sizeof(buf))) { + s->ip6segmentrouting = strtoul(buf, NULL, 0); + s->flags |= DEV_OPT_IP6SEGMENTROUTING; + } + if (ioctl(sock_ioctl, SIOCGIFFLAGS, &ifr) == 0) { s->promisc = ifr.ifr_flags & IFF_PROMISC; s->flags |= DEV_OPT_PROMISC; @@ -1628,145 +2198,395 @@ system_if_get_settings(struct device *dev, struct device_settings *s) s->sendredirects = strtoul(buf, NULL, 0); s->flags |= DEV_OPT_SENDREDIRECTS; } + + if (!system_get_drop_v4_unicast_in_l2_multicast(dev, buf, sizeof(buf))) { + s->drop_v4_unicast_in_l2_multicast = strtoul(buf, NULL, 0); + s->flags |= DEV_OPT_DROP_V4_UNICAST_IN_L2_MULTICAST; + } + + if (!system_get_drop_v6_unicast_in_l2_multicast(dev, buf, sizeof(buf))) { + s->drop_v6_unicast_in_l2_multicast = strtoul(buf, NULL, 0); + s->flags |= DEV_OPT_DROP_V6_UNICAST_IN_L2_MULTICAST; + } + + if (!system_get_drop_gratuitous_arp(dev, buf, sizeof(buf))) { + s->drop_gratuitous_arp = strtoul(buf, NULL, 0); + s->flags |= DEV_OPT_DROP_GRATUITOUS_ARP; + } + + if (!system_get_drop_unsolicited_na(dev, buf, sizeof(buf))) { + s->drop_unsolicited_na = strtoul(buf, NULL, 0); + s->flags |= DEV_OPT_DROP_UNSOLICITED_NA; + } + + if (!system_get_arp_accept(dev, buf, sizeof(buf))) { + s->arp_accept = strtoul(buf, NULL, 0); + s->flags |= DEV_OPT_ARP_ACCEPT; + } + + ret = system_get_ethtool_gro(dev); + if (ret >= 0) { + s->gro = ret; + s->flags |= DEV_OPT_GRO; + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6,1,0) + ret = system_if_get_master_ifindex(dev); + if (ret >= 0) { + s->master_ifindex = ret; + s->flags |= DEV_OPT_MASTER; + } +#endif } void -system_if_apply_settings(struct device *dev, struct device_settings *s, unsigned int apply_mask) +system_if_apply_settings(struct device *dev, struct device_settings *s, uint64_t apply_mask) { struct ifreq ifr; char buf[12]; + apply_mask &= s->flags; + + if (apply_mask & DEV_OPT_MASTER) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6,1,0) + system_set_master(dev, s->master_ifindex); + if (!(apply_mask & (DEV_OPT_MACADDR | DEV_OPT_DEFAULT_MACADDR)) || dev->external) + system_refresh_orig_macaddr(dev, &dev->orig_settings); +#else + netifd_log_message(L_WARNING, "%s Your kernel is older than linux 6.1.0, changing DSA port conduit is not supported!", dev->ifname); +#endif + } + memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, dev->ifname, sizeof(ifr.ifr_name) - 1); - if (s->flags & DEV_OPT_MTU & apply_mask) { + if (apply_mask & 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_MTU6 & apply_mask) { + if (apply_mask & DEV_OPT_MTU6) { system_update_ipv6_mtu(dev, s->mtu6); } - if (s->flags & DEV_OPT_TXQUEUELEN & apply_mask) { + if (apply_mask & 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 & apply_mask) && !dev->external) { + if ((apply_mask & (DEV_OPT_MACADDR | DEV_OPT_DEFAULT_MACADDR)) && !dev->external) { 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; } - if (s->flags & DEV_OPT_IPV6 & apply_mask) + if (apply_mask & DEV_OPT_IPV6) system_set_disable_ipv6(dev, s->ipv6 ? "0" : "1"); - if (s->flags & DEV_OPT_PROMISC & apply_mask) { + if (s->flags & DEV_OPT_IP6SEGMENTROUTING & apply_mask) { + struct device dummy = { + .ifname = "all", + }; + bool ip6segmentrouting = device_check_ip6segmentrouting(); + + system_set_ip6segmentrouting(dev, s->ip6segmentrouting ? "1" : "0"); + system_set_ip6segmentrouting(&dummy, ip6segmentrouting ? "1" : "0"); + } + if (apply_mask & DEV_OPT_PROMISC) { if (system_if_flags(dev->ifname, s->promisc ? IFF_PROMISC : 0, !s->promisc ? IFF_PROMISC : 0) < 0) s->flags &= ~DEV_OPT_PROMISC; } - if (s->flags & DEV_OPT_RPFILTER & apply_mask) { + if (apply_mask & DEV_OPT_RPFILTER) { snprintf(buf, sizeof(buf), "%u", s->rpfilter); system_set_rpfilter(dev, buf); } - if (s->flags & DEV_OPT_ACCEPTLOCAL & apply_mask) + if (apply_mask & DEV_OPT_ACCEPTLOCAL) system_set_acceptlocal(dev, s->acceptlocal ? "1" : "0"); - if (s->flags & DEV_OPT_IGMPVERSION & apply_mask) { + if (apply_mask & DEV_OPT_IGMPVERSION) { snprintf(buf, sizeof(buf), "%u", s->igmpversion); system_set_igmpversion(dev, buf); } - if (s->flags & DEV_OPT_MLDVERSION & apply_mask) { + if (apply_mask & DEV_OPT_MLDVERSION) { snprintf(buf, sizeof(buf), "%u", s->mldversion); system_set_mldversion(dev, buf); } - if (s->flags & DEV_OPT_NEIGHREACHABLETIME & apply_mask) { + if (apply_mask & DEV_OPT_NEIGHREACHABLETIME) { snprintf(buf, sizeof(buf), "%u", s->neigh4reachabletime); system_set_neigh4reachabletime(dev, buf); snprintf(buf, sizeof(buf), "%u", s->neigh6reachabletime); system_set_neigh6reachabletime(dev, buf); } - if (s->flags & DEV_OPT_NEIGHLOCKTIME & apply_mask) { + if (apply_mask & DEV_OPT_NEIGHLOCKTIME) { snprintf(buf, sizeof(buf), "%d", s->neigh4locktime); system_set_neigh4locktime(dev, buf); } - if (s->flags & DEV_OPT_NEIGHGCSTALETIME & apply_mask) { + if (apply_mask & DEV_OPT_NEIGHGCSTALETIME) { snprintf(buf, sizeof(buf), "%u", s->neigh4gcstaletime); system_set_neigh4gcstaletime(dev, buf); snprintf(buf, sizeof(buf), "%u", s->neigh6gcstaletime); system_set_neigh6gcstaletime(dev, buf); } - if (s->flags & DEV_OPT_DADTRANSMITS & apply_mask) { + if (apply_mask & DEV_OPT_DADTRANSMITS) { snprintf(buf, sizeof(buf), "%u", s->dadtransmits); system_set_dadtransmits(dev, buf); } - if (s->flags & DEV_OPT_MULTICAST & apply_mask) { + if (apply_mask & DEV_OPT_MULTICAST) { if (system_if_flags(dev->ifname, s->multicast ? IFF_MULTICAST : 0, !s->multicast ? IFF_MULTICAST : 0) < 0) s->flags &= ~DEV_OPT_MULTICAST; } - if (s->flags & DEV_OPT_SENDREDIRECTS & apply_mask) + if (apply_mask & DEV_OPT_SENDREDIRECTS) system_set_sendredirects(dev, s->sendredirects ? "1" : "0"); + if (apply_mask & DEV_OPT_DROP_V4_UNICAST_IN_L2_MULTICAST) + system_set_drop_v4_unicast_in_l2_multicast(dev, s->drop_v4_unicast_in_l2_multicast ? "1" : "0"); + if (apply_mask & DEV_OPT_DROP_V6_UNICAST_IN_L2_MULTICAST) + system_set_drop_v6_unicast_in_l2_multicast(dev, s->drop_v6_unicast_in_l2_multicast ? "1" : "0"); + if (apply_mask & DEV_OPT_DROP_GRATUITOUS_ARP) + system_set_drop_gratuitous_arp(dev, s->drop_gratuitous_arp ? "1" : "0"); + if (apply_mask & DEV_OPT_DROP_UNSOLICITED_NA) + system_set_drop_unsolicited_na(dev, s->drop_unsolicited_na ? "1" : "0"); + if (apply_mask & DEV_OPT_ARP_ACCEPT) + system_set_arp_accept(dev, s->arp_accept ? "1" : "0"); + system_set_ethtool_settings(dev, s); } -int system_if_up(struct device *dev) +void system_if_apply_settings_after_up(struct device *dev, struct device_settings *s) +{ + system_set_ethtool_settings_after_up(dev, s); +} + +int system_if_up(struct device *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); +} + +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; + + system_device_update_state(chk->dev, ifi->ifi_flags, ifi->ifi_index); + 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; + + if (chk->dev->type == &simple_device_type) + device_set_present(chk->dev, false); + device_set_link(chk->dev, false); + chk->pending = err->error; + + return NL_STOP; +} + +struct bridge_vlan_check_data { + struct device *check_dev; + int ifindex; + int ret; + bool pending; +}; + +static void bridge_vlan_check_port(struct bridge_vlan_check_data *data, + struct bridge_vlan_port *port, + struct bridge_vlan_info *vinfo) { - system_if_get_settings(dev, &dev->orig_settings); - /* Only keep orig settings based on what needs to be set */ - dev->orig_settings.valid_flags = dev->orig_settings.flags; - dev->orig_settings.flags &= dev->settings.flags; - system_if_apply_settings(dev, &dev->settings, dev->settings.flags); - return system_if_flags(dev->ifname, IFF_UP, 0); + uint16_t flags = 0, diff, mask; + + if (port->flags & BRVLAN_F_PVID) + flags |= BRIDGE_VLAN_INFO_PVID; + if (port->flags & BRVLAN_F_UNTAGGED) + flags |= BRIDGE_VLAN_INFO_UNTAGGED; + + diff = vinfo->flags ^ flags; + mask = BRVLAN_F_UNTAGGED | (flags & BRIDGE_VLAN_INFO_PVID); + if (diff & mask) { + data->ret = 1; + data->pending = false; + } + + port->check = 1; } -int system_if_down(struct device *dev) +static void bridge_vlan_check_attr(struct bridge_vlan_check_data *data, + struct rtattr *attr) { - int ret = system_if_flags(dev->ifname, 0, IFF_UP); - system_if_apply_settings(dev, &dev->orig_settings, dev->orig_settings.flags); - return ret; -} + struct bridge_vlan_hotplug_port *port; + struct bridge_vlan_info *vinfo; + struct bridge_vlan *vlan; + struct rtattr *cur; + int rem = RTA_PAYLOAD(attr); + int i; -struct if_check_data { - struct device *dev; - int pending; - int ret; -}; + for (cur = RTA_DATA(attr); RTA_OK(cur, rem); cur = RTA_NEXT(cur, rem)) { + if (cur->rta_type != IFLA_BRIDGE_VLAN_INFO) + continue; -#ifndef IFF_LOWER_UP -#define IFF_LOWER_UP 0x10000 -#endif + vinfo = RTA_DATA(cur); + vlan = vlist_find(&data->check_dev->vlans, &vinfo->vid, vlan, node); + if (!vlan) { + data->ret = 1; + data->pending = false; + return; + } -static int cb_if_check_valid(struct nl_msg *msg, void *arg) + for (i = 0; i < vlan->n_ports; i++) + if (!vlan->ports[i].check) + bridge_vlan_check_port(data, &vlan->ports[i], vinfo); + + list_for_each_entry(port, &vlan->hotplug_ports, list) + if (!port->port.check) + bridge_vlan_check_port(data, &port->port, vinfo); + } +} + +static int bridge_vlan_check_cb(struct nl_msg *msg, void *arg) { + struct bridge_vlan_check_data *data = arg; struct nlmsghdr *nh = nlmsg_hdr(msg); struct ifinfomsg *ifi = NLMSG_DATA(nh); - struct if_check_data *chk = (struct if_check_data *)arg; + struct rtattr *attr; + int rem; if (nh->nlmsg_type != RTM_NEWLINK) return NL_SKIP; - if (chk->dev->type == &simple_device_type) - device_set_present(chk->dev, ifi->ifi_index > 0 ? true : false); - device_set_link(chk->dev, ifi->ifi_flags & IFF_LOWER_UP ? true : false); + if (ifi->ifi_family != AF_BRIDGE) + return NL_SKIP; - return NL_OK; + if (ifi->ifi_index != data->ifindex) + return NL_SKIP; + + attr = IFLA_RTA(ifi); + rem = nh->nlmsg_len - NLMSG_LENGTH(sizeof(*ifi)); + while (RTA_OK(attr, rem)) { + if (attr->rta_type == IFLA_AF_SPEC) + bridge_vlan_check_attr(data, attr); + + attr = RTA_NEXT(attr, rem); + } + + return NL_SKIP; } -static int cb_if_check_ack(struct nl_msg *msg, void *arg) +static int bridge_vlan_ack_cb(struct nl_msg *msg, void *arg) { - struct if_check_data *chk = (struct if_check_data *)arg; - chk->pending = 0; + struct bridge_vlan_check_data *data = arg; + data->pending = false; return NL_STOP; } -static int cb_if_check_error(struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg) +static int bridge_vlan_error_cb(struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg) { - struct if_check_data *chk = (struct if_check_data *)arg; + struct bridge_vlan_check_data *data = arg; + data->pending = false; + return NL_STOP; +} - if (chk->dev->type == &simple_device_type) - device_set_present(chk->dev, false); - device_set_link(chk->dev, false); - chk->pending = err->error; +int system_bridge_vlan_check(struct device *dev, char *ifname) +{ + struct bridge_vlan_check_data data = { + .check_dev = dev, + .ifindex = if_nametoindex(ifname), + .ret = -1, + .pending = true, + }; + static struct ifinfomsg ifi = { + .ifi_family = AF_BRIDGE + }; + static struct rtattr ext_req = { + .rta_type = IFLA_EXT_MASK, + .rta_len = RTA_LENGTH(sizeof(uint32_t)), + }; + uint32_t filter = RTEXT_FILTER_BRVLAN; + struct nl_cb *cb = nl_cb_alloc(NL_CB_DEFAULT); + struct bridge_vlan *vlan; + struct nl_msg *msg; + int i; - return NL_STOP; + if (!data.ifindex) + return 0; + + msg = nlmsg_alloc_simple(RTM_GETLINK, NLM_F_DUMP); + + if (nlmsg_append(msg, &ifi, sizeof(ifi), 0) || + nlmsg_append(msg, &ext_req, sizeof(ext_req), NLMSG_ALIGNTO) || + nlmsg_append(msg, &filter, sizeof(filter), 0)) + goto free; + + vlist_for_each_element(&dev->vlans, vlan, node) { + struct bridge_vlan_hotplug_port *port; + + for (i = 0; i < vlan->n_ports; i++) { + if (!strcmp(vlan->ports[i].ifname, ifname)) + vlan->ports[i].check = 0; + else + vlan->ports[i].check = -1; + } + + list_for_each_entry(port, &vlan->hotplug_ports, list) { + if (!strcmp(port->port.ifname, ifname)) + port->port.check = 0; + else + port->port.check = -1; + } + } + + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, bridge_vlan_check_cb, &data); + nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, bridge_vlan_ack_cb, &data); + nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, bridge_vlan_ack_cb, &data); + nl_cb_err(cb, NL_CB_CUSTOM, bridge_vlan_error_cb, &data); + + if (nl_send_auto_complete(sock_rtnl, msg) < 0) + goto free; + + data.ret = 0; + while (data.pending) + nl_recvmsgs(sock_rtnl, cb); + + vlist_for_each_element(&dev->vlans, vlan, node) { + struct bridge_vlan_hotplug_port *port; + + for (i = 0; i < vlan->n_ports; i++) { + if (!vlan->ports[i].check) { + data.ret = 1; + break; + } + } + + list_for_each_entry(port, &vlan->hotplug_ports, list) { + if (!port->port.check) { + data.ret = 1; + break; + } + } + } + +free: + nlmsg_free(msg); + nl_cb_put(cb); + return data.ret; } int system_if_check(struct device *dev) @@ -1818,21 +2638,11 @@ struct device * system_if_get_parent(struct device *dev) { char buf[64], *devname; - int ifindex, iflink, len; - FILE *f; + int ifindex, iflink; - snprintf(buf, sizeof(buf), "/sys/class/net/%s/iflink", dev->ifname); - f = fopen(buf, "r"); - if (!f) + if (system_get_dev_sysfs("iflink", dev->ifname, buf, sizeof(buf)) < 0) 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) @@ -1889,90 +2699,392 @@ read_uint64_file(int dir_fd, const char *file, uint64_t *val) return ret; } -/* Assume advertised flags == supported flags */ -static const struct { - uint32_t mask; - const char *name; -} ethtool_link_modes[] = { - { ADVERTISED_10baseT_Half, "10baseT-H" }, - { ADVERTISED_10baseT_Full, "10baseT-F" }, - { ADVERTISED_100baseT_Half, "100baseT-H" }, - { ADVERTISED_100baseT_Full, "100baseT-F" }, - { ADVERTISED_1000baseT_Half, "1000baseT-H" }, - { ADVERTISED_1000baseT_Full, "1000baseT-F" }, - { ADVERTISED_1000baseKX_Full, "1000baseKX-F" }, - { ADVERTISED_2500baseX_Full, "2500baseX-F" }, - { ADVERTISED_10000baseT_Full, "10000baseT-F" }, - { ADVERTISED_10000baseKX4_Full, "10000baseKX4-F" }, - { ADVERTISED_10000baseKR_Full, "10000baseKR-F" }, - { ADVERTISED_20000baseMLD2_Full, "20000baseMLD2-F" }, - { ADVERTISED_20000baseKR2_Full, "20000baseKR2-F" }, - { ADVERTISED_40000baseKR4_Full, "40000baseKR4-F" }, - { ADVERTISED_40000baseCR4_Full, "40000baseCR4-F" }, - { ADVERTISED_40000baseSR4_Full, "40000baseSR4-F" }, - { ADVERTISED_40000baseLR4_Full, "40000baseLR4-F" }, -#ifdef ADVERTISED_56000baseKR4_Full - { ADVERTISED_56000baseKR4_Full, "56000baseKR4-F" }, - { ADVERTISED_56000baseCR4_Full, "56000baseCR4-F" }, - { ADVERTISED_56000baseSR4_Full, "56000baseSR4-F" }, - { ADVERTISED_56000baseLR4_Full, "56000baseLR4-F" }, -#endif -}; +bool +system_if_force_external(const char *ifname) +{ + struct stat s; + + return stat(dev_sysfs_path(ifname, "phy80211"), &s) == 0; +} -static void system_add_link_modes(struct blob_buf *b, __u32 mask) +static const char * +system_netdevtype_name(unsigned short dev_type) { - 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); + size_t i; + + for (i = 0; i < ARRAY_SIZE(netdev_types); i++) { + if (netdev_types[i].id == dev_type) + return netdev_types[i].name; } + + /* the last key is used by default */ + i = ARRAY_SIZE(netdev_types) - 1; + + return netdev_types[i].name; } -bool -system_if_force_external(const char *ifname) +static void +system_add_devtype(struct blob_buf *b, const char *ifname) { - char buf[64]; - struct stat s; + char buf[100]; + bool found = false; + + if (!system_get_dev_sysfs("uevent", ifname, buf, sizeof(buf))) { + const char *info = "DEVTYPE="; + char *context = NULL; + const char *line = strtok_r(buf, "\r\n", &context); + + while (line != NULL) { + char *index = strstr(line, info); + + if (index != NULL) { + blobmsg_add_string(b, "devtype", index + strlen(info)); + found = true; + break; + } + + line = strtok_r(NULL, "\r\n", &context); + } + } + + if (!found) { + unsigned short number = 0; + const char *name = NULL; + + if (!system_get_dev_sysfs("type", ifname, buf, sizeof(buf))) { + number = strtoul(buf, NULL, 0); + name = system_netdevtype_name(number); + blobmsg_add_string(b, "devtype", name); + } + } +} + +#define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d)) + +static int32_t +ethtool_feature_count(const char *ifname) +{ + struct { + struct ethtool_sset_info hdr; + uint32_t buf; + } req = { + .hdr = { + .cmd = ETHTOOL_GSSET_INFO, + .sset_mask = 1 << ETH_SS_FEATURES + } + }; - snprintf(buf, sizeof(buf), "/sys/class/net/%s/phy80211", ifname); - return stat(buf, &s) == 0; + struct ifreq ifr = { + .ifr_data = (void *)&req + }; + + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name) - 1); + + if (ioctl(sock_ioctl, SIOCETHTOOL, &ifr) != 0) + return -1; + + if (!req.hdr.sset_mask) + return 0; + + return req.buf; +} + +static int32_t +ethtool_feature_index(const char *ifname, const char *keyname) +{ + struct ethtool_gstrings *feature_names; + struct ifreq ifr = { 0 }; + int32_t n_features; + uint32_t i; + + n_features = ethtool_feature_count(ifname); + + if (n_features <= 0) + return -1; + + feature_names = calloc(1, sizeof(*feature_names) + n_features * ETH_GSTRING_LEN); + + if (!feature_names) + return -1; + + feature_names->cmd = ETHTOOL_GSTRINGS; + feature_names->string_set = ETH_SS_FEATURES; + feature_names->len = n_features; + + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name) - 1); + ifr.ifr_data = (void *)feature_names; + + if (ioctl(sock_ioctl, SIOCETHTOOL, &ifr) != 0) { + free(feature_names); + + return -1; + } + + for (i = 0; i < feature_names->len; i++) + if (!strcmp((char *)&feature_names->data[i * ETH_GSTRING_LEN], keyname)) + break; + + if (i >= feature_names->len) + i = -1; + + free(feature_names); + + return i; +} + +static bool +ethtool_feature_value(const char *ifname, const char *keyname) +{ + struct ethtool_get_features_block *feature_block; + struct ethtool_gfeatures *feature_values; + struct ifreq ifr = { 0 }; + int32_t feature_idx; + bool active; + + feature_idx = ethtool_feature_index(ifname, keyname); + + if (feature_idx < 0) + return false; + + feature_values = calloc(1, + sizeof(*feature_values) + + sizeof(feature_values->features[0]) * DIV_ROUND_UP(feature_idx, 32)); + + if (!feature_values) + return false; + + feature_values->cmd = ETHTOOL_GFEATURES; + feature_values->size = DIV_ROUND_UP(feature_idx, 32); + + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name) - 1); + ifr.ifr_data = (void *)feature_values; + + if (ioctl(sock_ioctl, SIOCETHTOOL, &ifr) != 0) { + free(feature_values); + + return false; + } + + feature_block = &feature_values->features[feature_idx / 32]; + active = feature_block->active & (1U << feature_idx % 32); + + free(feature_values); + + return active; +} + +static void +system_add_link_mode_name(struct blob_buf *b, int i, bool half) +{ + char *buf; + + /* allocate string buffer large enough for the mode name and a suffix + * "-F" or "-H" indicating full duplex or half duplex. + */ + buf = blobmsg_alloc_string_buffer(b, NULL, strlen(ethtool_modes[i].name) + 3); + if (!buf) + return; + + strcpy(buf, ethtool_modes[i].name); + if (half) + strcat(buf, "-H"); + else + strcat(buf, "-F"); + + blobmsg_add_string_buffer(b); +} + +static void +system_add_link_modes(__s8 nwords, struct blob_buf *b, __u32 *mask) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(ethtool_modes); i++) { + if (ethtool_link_mode_test_bit(nwords, ethtool_modes[i].bit_half, mask)) + system_add_link_mode_name(b, i, true); + + if (ethtool_link_mode_test_bit(nwords, ethtool_modes[i].bit_full, mask)) + system_add_link_mode_name(b, i, false); + } +} + +static void +system_add_pause_modes(__s8 nwords, struct blob_buf *b, __u32 *mask) +{ + if (ethtool_link_mode_test_bit(nwords, ETHTOOL_LINK_MODE_Pause_BIT, mask)) + blobmsg_add_string(b, NULL, "pause"); + + if (ethtool_link_mode_test_bit(nwords, ETHTOOL_LINK_MODE_Asym_Pause_BIT, mask)) + blobmsg_add_string(b, NULL, "asym_pause"); +} + + +static void +system_add_ethtool_pause_an(struct blob_buf *b, __s8 nwords, + __u32 *advertising, __u32 *lp_advertising) +{ + bool an_rx = false, an_tx = false; + void *d; + + d = blobmsg_open_array(b, "negotiated"); + + /* Work out negotiated pause frame usage per + * IEEE 802.3-2005 table 28B-3. + */ + if (ethtool_link_mode_test_bit(nwords, + ETHTOOL_LINK_MODE_Pause_BIT, + advertising) && + ethtool_link_mode_test_bit(nwords, + ETHTOOL_LINK_MODE_Pause_BIT, + lp_advertising)) { + an_tx = true; + an_rx = true; + } else if (ethtool_link_mode_test_bit(nwords, + ETHTOOL_LINK_MODE_Asym_Pause_BIT, + advertising) && + ethtool_link_mode_test_bit(nwords, + ETHTOOL_LINK_MODE_Asym_Pause_BIT, + lp_advertising)) { + if (ethtool_link_mode_test_bit(nwords, + ETHTOOL_LINK_MODE_Pause_BIT, + advertising)) + an_rx = true; + else if (ethtool_link_mode_test_bit(nwords, + ETHTOOL_LINK_MODE_Pause_BIT, + lp_advertising)) + an_tx = true; + } + if (an_tx) + blobmsg_add_string(b, NULL, "rx"); + + if (an_rx) + blobmsg_add_string(b, NULL, "tx"); + + blobmsg_close_array(b, d); +} + +static void +system_get_ethtool_pause(struct device *dev, bool *rx_pause, bool *tx_pause, bool *pause_autoneg) +{ + struct ethtool_pauseparam pp; + struct ifreq ifr = { + .ifr_data = (caddr_t)&pp, + }; + + strncpy(ifr.ifr_name, dev->ifname, sizeof(ifr.ifr_name) - 1); + memset(&pp, 0, sizeof(pp)); + pp.cmd = ETHTOOL_GPAUSEPARAM; + + /* may fail */ + if (ioctl(sock_ioctl, SIOCETHTOOL, &ifr) == -1) { + *pause_autoneg = true; + return; + } + + *rx_pause = pp.rx_pause; + *tx_pause = pp.tx_pause; + *pause_autoneg = pp.autoneg; } int system_if_dump_info(struct device *dev, struct blob_buf *b) { - struct ethtool_cmd ecmd; - struct ifreq ifr; + __u32 *supported, *advertising, *lp_advertising; + bool rx_pause, tx_pause, pause_autoneg; + struct { + struct ethtool_link_settings req; + __u32 link_mode_data[3 * 127]; + } ecmd; + struct ifreq ifr = { + .ifr_data = (caddr_t)&ecmd, + }; + __s8 nwords; + void *c, *d; char *s; - void *c; + + system_get_ethtool_pause(dev, &rx_pause, &tx_pause, &pause_autoneg); memset(&ecmd, 0, sizeof(ecmd)); - memset(&ifr, 0, sizeof(ifr)); + ecmd.req.cmd = ETHTOOL_GLINKSETTINGS; strncpy(ifr.ifr_name, dev->ifname, sizeof(ifr.ifr_name) - 1); - 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); + if (ioctl(sock_ioctl, SIOCETHTOOL, &ifr) < 0 || + ecmd.req.link_mode_masks_nwords >= 0 || + ecmd.req.cmd != ETHTOOL_GLINKSETTINGS) + return -EOPNOTSUPP; + + ecmd.req.link_mode_masks_nwords = -ecmd.req.link_mode_masks_nwords; + + if (ioctl(sock_ioctl, SIOCETHTOOL, &ifr) < 0 || + ecmd.req.link_mode_masks_nwords <= 0 || + ecmd.req.cmd != ETHTOOL_GLINKSETTINGS) + return -EIO; + + nwords = ecmd.req.link_mode_masks_nwords; + supported = &ecmd.link_mode_data[0]; + advertising = &ecmd.link_mode_data[nwords]; + lp_advertising = &ecmd.link_mode_data[2 * nwords]; + + c = blobmsg_open_array(b, "link-advertising"); + system_add_link_modes(nwords, b, advertising); + blobmsg_close_array(b, c); + + c = blobmsg_open_array(b, "link-partner-advertising"); + system_add_link_modes(nwords, b, lp_advertising); + blobmsg_close_array(b, c); + + c = blobmsg_open_array(b, "link-supported"); + system_add_link_modes(nwords, b, supported); + blobmsg_close_array(b, c); + + if (ethtool_validate_speed(ecmd.req.speed) && + (ecmd.req.speed != (__u32)SPEED_UNKNOWN) && + (ecmd.req.speed != 0)) { + s = blobmsg_alloc_string_buffer(b, "speed", 10); + snprintf(s, 8, "%d%c", ecmd.req.speed, + ecmd.req.duplex == DUPLEX_HALF ? 'H' : 'F'); + blobmsg_add_string_buffer(b); + } + blobmsg_add_u8(b, "autoneg", !!ecmd.req.autoneg); - c = blobmsg_open_array(b, "link-partner-advertising"); - system_add_link_modes(b, ecmd.lp_advertising); - blobmsg_close_array(b, c); + c = blobmsg_open_table(b, "flow-control"); + blobmsg_add_u8(b, "autoneg", pause_autoneg); - c = blobmsg_open_array(b, "link-supported"); - system_add_link_modes(b, ecmd.supported); - blobmsg_close_array(b, c); + d = blobmsg_open_array(b, "supported"); + system_add_pause_modes(nwords, b, supported); + blobmsg_close_array(b, d); - 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); + if (pause_autoneg) { + d = blobmsg_open_array(b, "link-advertising"); + system_add_pause_modes(nwords, b, advertising); + blobmsg_close_array(b, d); + } - blobmsg_add_u8(b, "autoneg", !!ecmd.autoneg); + d = blobmsg_open_array(b, "link-partner-advertising"); + system_add_pause_modes(nwords, b, lp_advertising); + blobmsg_close_array(b, d); + + if (pause_autoneg) { + system_add_ethtool_pause_an(b, nwords, advertising, + lp_advertising); + } else { + d = blobmsg_open_array(b, "selected"); + if (rx_pause) + blobmsg_add_string(b, NULL, "rx"); + + if (tx_pause) + blobmsg_add_string(b, NULL, "tx"); + + blobmsg_close_array(b, d); } + blobmsg_close_table(b, c); + + blobmsg_add_u8(b, "hw-tc-offload", + ethtool_feature_value(dev->ifname, "hw-tc-offload")); + + system_add_devtype(b, dev->ifname); + return 0; } @@ -1989,13 +3101,11 @@ system_if_dump_stats(struct device *dev, struct blob_buf *b) "rx_errors", "tx_bytes", "tx_window_errors", "rx_fifo_errors", "tx_carrier_errors", }; - char buf[64]; int stats_dir; - int i; + size_t i; uint64_t val = 0; - snprintf(buf, sizeof(buf), "/sys/class/net/%s/statistics", dev->ifname); - stats_dir = open(buf, O_DIRECTORY); + stats_dir = open(dev_sysfs_path(dev->ifname, "statistics"), O_DIRECTORY); if (stats_dir < 0) return -1; @@ -2179,6 +3289,9 @@ static int system_rt(struct device *dev, struct device_route *route, int cmd) } } + if (route->flags & DEVROUTE_NODEV) + dev = NULL; + msg = nlmsg_alloc_simple(cmd, flags); if (!msg) return -1; @@ -2237,14 +3350,13 @@ int system_del_route(struct device *dev, struct device_route *route) int system_flush_routes(void) { - const char *names[] = { - "/proc/sys/net/ipv4/route/flush", - "/proc/sys/net/ipv6/route/flush" - }; - int fd, i; + const char *names[] = { "ipv4", "ipv6" }; + size_t i; + int fd; for (i = 0; i < ARRAY_SIZE(names); i++) { - fd = open(names[i], O_WRONLY); + snprintf(dev_buf, sizeof(dev_buf), "%s/sys/net/%s/route/flush", proc_path, names[i]); + fd = open(dev_buf, O_WRONLY); if (fd < 0) continue; @@ -2447,6 +3559,15 @@ static int system_iprule(struct iprule *rule, int cmd) if (rule->flags & IPRULE_SUP_PREFIXLEN) nla_put_u32(msg, FRA_SUPPRESS_PREFIXLEN, rule->sup_prefixlen); + if (rule->flags & IPRULE_UIDRANGE) { + struct fib_rule_uid_range uidrange = { + .start = rule->uidrange_start, + .end = rule->uidrange_end + }; + + nla_put(msg, FRA_UID_RANGE, sizeof(uidrange), &uidrange); + } + if (rule->flags & IPRULE_GOTO) nla_put_u32(msg, FRA_GOTO, rule->gotoid); @@ -3278,7 +4399,7 @@ static int system_add_vxlan(const char *name, const unsigned int link, struct bl ret = system_rtnl_call(msg); if (ret) - D(SYSTEM, "Error adding vxlan '%s': %d\n", name, ret); + D(SYSTEM, "Error adding vxlan '%s': %d", name, ret); return ret; @@ -3337,7 +4458,7 @@ static int system_add_sit_tunnel(const char *name, const unsigned int link, stru return ret; failure: - __system_del_ip_tunnel(name, tb); + system_link_del(name); return ret; } @@ -3399,33 +4520,9 @@ static int system_add_proto_tunnel(const char *name, const uint8_t proto, const return -1; } -static int __system_del_ip_tunnel(const char *name, struct blob_attr **tb) -{ - struct blob_attr *cur; - const char *str; - - if (!(cur = tb[TUNNEL_ATTR_TYPE])) - return -EINVAL; - str = blobmsg_data(cur); - - if (!strcmp(str, "greip") || !strcmp(str, "gretapip") || - !strcmp(str, "greip6") || !strcmp(str, "gretapip6") || - !strcmp(str, "vtiip") || !strcmp(str, "vtiip6") || - !strcmp(str, "vxlan") || !strcmp(str, "vxlan6") || - !strcmp(str, "xfrm")) - return system_link_del(name); - else - return tunnel_ioctl(name, SIOCDELTUNNEL, NULL); -} - -int system_del_ip_tunnel(const char *name, struct blob_attr *attr) +int system_del_ip_tunnel(const struct device *dev) { - struct blob_attr *tb[__TUNNEL_ATTR_MAX]; - - blobmsg_parse(tunnel_attr_list.params, __TUNNEL_ATTR_MAX, tb, - blob_data(attr), blob_len(attr)); - - return __system_del_ip_tunnel(name, tb); + return system_link_del(dev->ifname); } int system_update_ipv6_mtu(struct device *dev, int mtu) @@ -3434,10 +4531,7 @@ int system_update_ipv6_mtu(struct device *dev, int mtu) char buf[64]; int fd; - snprintf(buf, sizeof(buf), "/proc/sys/net/ipv6/conf/%s/mtu", - dev->ifname); - - fd = open(buf, O_RDWR); + fd = open(dev_sysctl_path("ipv6/conf", dev->ifname, "mtu"), O_RDWR); if (fd < 0) return ret; @@ -3458,7 +4552,7 @@ out: return ret; } -int system_add_ip_tunnel(const char *name, struct blob_attr *attr) +int system_add_ip_tunnel(const struct device *dev, struct blob_attr *attr) { struct blob_attr *tb[__TUNNEL_ATTR_MAX]; struct blob_attr *cur; @@ -3467,7 +4561,7 @@ int system_add_ip_tunnel(const char *name, struct blob_attr *attr) blobmsg_parse(tunnel_attr_list.params, __TUNNEL_ATTR_MAX, tb, blob_data(attr), blob_len(attr)); - __system_del_ip_tunnel(name, tb); + system_link_del(dev->ifname); if (!(cur = tb[TUNNEL_ATTR_TYPE])) return -EINVAL; @@ -3491,37 +4585,37 @@ int system_add_ip_tunnel(const char *name, struct blob_attr *attr) } if (!strcmp(str, "sit")) - return system_add_sit_tunnel(name, link, tb); + return system_add_sit_tunnel(dev->ifname, link, tb); #ifdef IFLA_IPTUN_MAX else if (!strcmp(str, "ipip6")) { - return system_add_ip6_tunnel(name, link, tb); + return system_add_ip6_tunnel(dev->ifname, link, tb); } else if (!strcmp(str, "greip")) { - return system_add_gre_tunnel(name, "gre", link, tb, false); + return system_add_gre_tunnel(dev->ifname, "gre", link, tb, false); } else if (!strcmp(str, "gretapip")) { - return system_add_gre_tunnel(name, "gretap", link, tb, false); + return system_add_gre_tunnel(dev->ifname, "gretap", link, tb, false); } else if (!strcmp(str, "greip6")) { - return system_add_gre_tunnel(name, "ip6gre", link, tb, true); + return system_add_gre_tunnel(dev->ifname, "ip6gre", link, tb, true); } else if (!strcmp(str, "gretapip6")) { - return system_add_gre_tunnel(name, "ip6gretap", link, tb, true); + return system_add_gre_tunnel(dev->ifname, "ip6gretap", link, tb, true); #ifdef IFLA_VTI_MAX } else if (!strcmp(str, "vtiip")) { - return system_add_vti_tunnel(name, "vti", link, tb, false); + return system_add_vti_tunnel(dev->ifname, "vti", link, tb, false); } else if (!strcmp(str, "vtiip6")) { - return system_add_vti_tunnel(name, "vti6", link, tb, true); + return system_add_vti_tunnel(dev->ifname, "vti6", link, tb, true); #endif #ifdef IFLA_XFRM_MAX } else if (!strcmp(str, "xfrm")) { - return system_add_xfrm_tunnel(name, "xfrm", link, tb); + return system_add_xfrm_tunnel(dev->ifname, "xfrm", link, tb); #endif #ifdef IFLA_VXLAN_MAX } else if(!strcmp(str, "vxlan")) { - return system_add_vxlan(name, link, tb, false); + return system_add_vxlan(dev->ifname, link, tb, false); } else if(!strcmp(str, "vxlan6")) { - return system_add_vxlan(name, link, tb, true); + return system_add_vxlan(dev->ifname, link, tb, true); #endif #endif } else if (!strcmp(str, "ipip")) { - return system_add_proto_tunnel(name, IPPROTO_IPIP, link, tb); + return system_add_proto_tunnel(dev->ifname, IPPROTO_IPIP, link, tb); } else return -EINVAL;