interface-ip: fix IPv4 route target masking
[project/netifd.git] / system-linux.c
index e437377deb7bd96b00f23fc5f77ac9259d737e24..cc1b5e969132c41ceaa38744faa07b9d94125436 100644 (file)
@@ -692,34 +692,42 @@ static int system_get_arp_accept(struct device *dev, char *buf, const size_t buf
                        dev->ifname, buf, 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)
+{
+       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_sysfs("carrier", dev->ifname, buf, sizeof(buf)))
-               link_state = strtoul(buf, NULL, 0);
-
-       if (dev->type == &simple_device_type)
-               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;
 }
 
@@ -1704,6 +1712,182 @@ 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)
@@ -1726,6 +1910,40 @@ static bool ethtool_link_mode_test_bit(__s8 nwords, int nr, const __u32 *mask)
        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)
 {
@@ -1775,6 +1993,23 @@ system_set_ethtool_pause(struct device *dev, struct device_settings *s)
        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)
 {
@@ -1791,6 +2026,9 @@ system_set_ethtool_settings(struct device *dev, struct device_settings *s)
 
        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);
@@ -1850,11 +2088,19 @@ system_set_ethtool_settings(struct device *dev, struct device_settings *s)
        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);
@@ -1975,6 +2221,20 @@ system_if_get_settings(struct device *dev, struct device_settings *s)
                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
@@ -2073,9 +2333,22 @@ system_if_apply_settings(struct device *dev, struct device_settings *s, uint64_t
                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");
+       if (apply_mask & DEV_OPT_MASTER) {
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(6,1,0)
+               system_set_master(dev, s->master_ifindex);
+               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
+       }
        system_set_ethtool_settings(dev, s);
 }
 
+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);
@@ -2092,10 +2365,6 @@ struct if_check_data {
        int ret;
 };
 
-#ifndef IFF_LOWER_UP
-#define IFF_LOWER_UP   0x10000
-#endif
-
 static int cb_if_check_valid(struct nl_msg *msg, void *arg)
 {
        struct nlmsghdr *nh = nlmsg_hdr(msg);
@@ -2105,10 +2374,7 @@ static int cb_if_check_valid(struct nl_msg *msg, void *arg)
        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);
-
+       system_device_update_state(chk->dev, ifi->ifi_flags, ifi->ifi_index);
        return NL_OK;
 }
 
@@ -3019,6 +3285,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;