+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);
+
+ if (ioctl(sock_ioctl, SIOCGIFMTU, &ifr) == 0) {
+ s->mtu = ifr.ifr_mtu;
+ s->flags |= DEV_OPT_MTU;
+ }
+
+ s->mtu6 = system_update_ipv6_mtu(dev, 0);
+ if (s->mtu6 > 0)
+ s->flags |= DEV_OPT_MTU6;
+
+ if (ioctl(sock_ioctl, SIOCGIFTXQLEN, &ifr) == 0) {
+ s->txqueuelen = ifr.ifr_qlen;
+ s->flags |= DEV_OPT_TXQUEUELEN;
+ }
+
+ if (ioctl(sock_ioctl, SIOCGIFHWADDR, &ifr) == 0) {
+ memcpy(s->macaddr, &ifr.ifr_hwaddr.sa_data, sizeof(s->macaddr));
+ s->flags |= DEV_OPT_MACADDR;
+ }
+
+ if (!system_get_disable_ipv6(dev, buf, sizeof(buf))) {
+ s->ipv6 = !strtoul(buf, NULL, 0);
+ 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;
+
+ s->multicast = ifr.ifr_flags & IFF_MULTICAST;
+ s->flags |= DEV_OPT_MULTICAST;
+ }
+
+ if (!system_get_rpfilter(dev, buf, sizeof(buf))) {
+ s->rpfilter = strtoul(buf, NULL, 0);
+ s->flags |= DEV_OPT_RPFILTER;
+ }
+
+ if (!system_get_acceptlocal(dev, buf, sizeof(buf))) {
+ s->acceptlocal = strtoul(buf, NULL, 0);
+ s->flags |= DEV_OPT_ACCEPTLOCAL;
+ }
+
+ if (!system_get_igmpversion(dev, buf, sizeof(buf))) {
+ s->igmpversion = strtoul(buf, NULL, 0);
+ s->flags |= DEV_OPT_IGMPVERSION;
+ }
+
+ if (!system_get_mldversion(dev, buf, sizeof(buf))) {
+ s->mldversion = strtoul(buf, NULL, 0);
+ s->flags |= DEV_OPT_MLDVERSION;
+ }
+
+ if (!system_get_neigh4reachabletime(dev, buf, sizeof(buf))) {
+ s->neigh4reachabletime = strtoul(buf, NULL, 0);
+ s->flags |= DEV_OPT_NEIGHREACHABLETIME;
+ }
+
+ if (!system_get_neigh6reachabletime(dev, buf, sizeof(buf))) {
+ s->neigh6reachabletime = strtoul(buf, NULL, 0);
+ s->flags |= DEV_OPT_NEIGHREACHABLETIME;
+ }
+
+ if (!system_get_neigh4locktime(dev, buf, sizeof(buf))) {
+ s->neigh4locktime = strtol(buf, NULL, 0);
+ s->flags |= DEV_OPT_NEIGHLOCKTIME;
+ }
+
+ if (!system_get_neigh4gcstaletime(dev, buf, sizeof(buf))) {
+ s->neigh4gcstaletime = strtoul(buf, NULL, 0);
+ s->flags |= DEV_OPT_NEIGHGCSTALETIME;
+ }