| // SPDX-License-Identifier: GPL-2.0 | |
| #include <linux/kmod.h> | |
| #include <linux/netdevice.h> | |
| #include <linux/etherdevice.h> | |
| #include <linux/rtnetlink.h> | |
| #include <linux/net_tstamp.h> | |
| #include <linux/wireless.h> | |
| #include <net/dsa.h> | |
| #include <net/wext.h> | |
| /* | |
| * Map an interface index to its name (SIOCGIFNAME) | |
| */ | |
| /* | |
| * We need this ioctl for efficient implementation of the | |
| * if_indextoname() function required by the IPv6 API. Without | |
| * it, we would have to search all the interfaces to find a | |
| * match. --pb | |
| */ | |
| static int dev_ifname(struct net *net, struct ifreq *ifr) | |
| { | |
| ifr->ifr_name[IFNAMSIZ-1] = 0; | |
| return netdev_get_name(net, ifr->ifr_name, ifr->ifr_ifindex); | |
| } | |
| static gifconf_func_t *gifconf_list[NPROTO]; | |
| /** | |
| * register_gifconf - register a SIOCGIF handler | |
| * @family: Address family | |
| * @gifconf: Function handler | |
| * | |
| * Register protocol dependent address dumping routines. The handler | |
| * that is passed must not be freed or reused until it has been replaced | |
| * by another handler. | |
| */ | |
| int register_gifconf(unsigned int family, gifconf_func_t *gifconf) | |
| { | |
| if (family >= NPROTO) | |
| return -EINVAL; | |
| gifconf_list[family] = gifconf; | |
| return 0; | |
| } | |
| EXPORT_SYMBOL(register_gifconf); | |
| /* | |
| * Perform a SIOCGIFCONF call. This structure will change | |
| * size eventually, and there is nothing I can do about it. | |
| * Thus we will need a 'compatibility mode'. | |
| */ | |
| int dev_ifconf(struct net *net, struct ifconf *ifc, int size) | |
| { | |
| struct net_device *dev; | |
| char __user *pos; | |
| int len; | |
| int total; | |
| int i; | |
| /* | |
| * Fetch the caller's info block. | |
| */ | |
| pos = ifc->ifc_buf; | |
| len = ifc->ifc_len; | |
| /* | |
| * Loop over the interfaces, and write an info block for each. | |
| */ | |
| total = 0; | |
| for_each_netdev(net, dev) { | |
| for (i = 0; i < NPROTO; i++) { | |
| if (gifconf_list[i]) { | |
| int done; | |
| if (!pos) | |
| done = gifconf_list[i](dev, NULL, 0, size); | |
| else | |
| done = gifconf_list[i](dev, pos + total, | |
| len - total, size); | |
| if (done < 0) | |
| return -EFAULT; | |
| total += done; | |
| } | |
| } | |
| } | |
| /* | |
| * All done. Write the updated control block back to the caller. | |
| */ | |
| ifc->ifc_len = total; | |
| /* | |
| * Both BSD and Solaris return 0 here, so we do too. | |
| */ | |
| return 0; | |
| } | |
| /* | |
| * Perform the SIOCxIFxxx calls, inside rcu_read_lock() | |
| */ | |
| static int dev_ifsioc_locked(struct net *net, struct ifreq *ifr, unsigned int cmd) | |
| { | |
| int err; | |
| struct net_device *dev = dev_get_by_name_rcu(net, ifr->ifr_name); | |
| if (!dev) | |
| return -ENODEV; | |
| switch (cmd) { | |
| case SIOCGIFFLAGS: /* Get interface flags */ | |
| ifr->ifr_flags = (short) dev_get_flags(dev); | |
| return 0; | |
| case SIOCGIFMETRIC: /* Get the metric on the interface | |
| (currently unused) */ | |
| ifr->ifr_metric = 0; | |
| return 0; | |
| case SIOCGIFMTU: /* Get the MTU of a device */ | |
| ifr->ifr_mtu = dev->mtu; | |
| return 0; | |
| case SIOCGIFHWADDR: | |
| if (!dev->addr_len) | |
| memset(ifr->ifr_hwaddr.sa_data, 0, | |
| sizeof(ifr->ifr_hwaddr.sa_data)); | |
| else | |
| memcpy(ifr->ifr_hwaddr.sa_data, dev->dev_addr, | |
| min(sizeof(ifr->ifr_hwaddr.sa_data), | |
| (size_t)dev->addr_len)); | |
| ifr->ifr_hwaddr.sa_family = dev->type; | |
| return 0; | |
| case SIOCGIFSLAVE: | |
| err = -EINVAL; | |
| break; | |
| case SIOCGIFMAP: | |
| ifr->ifr_map.mem_start = dev->mem_start; | |
| ifr->ifr_map.mem_end = dev->mem_end; | |
| ifr->ifr_map.base_addr = dev->base_addr; | |
| ifr->ifr_map.irq = dev->irq; | |
| ifr->ifr_map.dma = dev->dma; | |
| ifr->ifr_map.port = dev->if_port; | |
| return 0; | |
| case SIOCGIFINDEX: | |
| ifr->ifr_ifindex = dev->ifindex; | |
| return 0; | |
| case SIOCGIFTXQLEN: | |
| ifr->ifr_qlen = dev->tx_queue_len; | |
| return 0; | |
| default: | |
| /* dev_ioctl() should ensure this case | |
| * is never reached | |
| */ | |
| WARN_ON(1); | |
| err = -ENOTTY; | |
| break; | |
| } | |
| return err; | |
| } | |
| static int net_hwtstamp_validate(struct ifreq *ifr) | |
| { | |
| struct hwtstamp_config cfg; | |
| enum hwtstamp_tx_types tx_type; | |
| enum hwtstamp_rx_filters rx_filter; | |
| int tx_type_valid = 0; | |
| int rx_filter_valid = 0; | |
| if (copy_from_user(&cfg, ifr->ifr_data, sizeof(cfg))) | |
| return -EFAULT; | |
| if (cfg.flags) /* reserved for future extensions */ | |
| return -EINVAL; | |
| tx_type = cfg.tx_type; | |
| rx_filter = cfg.rx_filter; | |
| switch (tx_type) { | |
| case HWTSTAMP_TX_OFF: | |
| case HWTSTAMP_TX_ON: | |
| case HWTSTAMP_TX_ONESTEP_SYNC: | |
| case HWTSTAMP_TX_ONESTEP_P2P: | |
| tx_type_valid = 1; | |
| break; | |
| case __HWTSTAMP_TX_CNT: | |
| /* not a real value */ | |
| break; | |
| } | |
| switch (rx_filter) { | |
| case HWTSTAMP_FILTER_NONE: | |
| case HWTSTAMP_FILTER_ALL: | |
| case HWTSTAMP_FILTER_SOME: | |
| case HWTSTAMP_FILTER_PTP_V1_L4_EVENT: | |
| case HWTSTAMP_FILTER_PTP_V1_L4_SYNC: | |
| case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ: | |
| case HWTSTAMP_FILTER_PTP_V2_L4_EVENT: | |
| case HWTSTAMP_FILTER_PTP_V2_L4_SYNC: | |
| case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ: | |
| case HWTSTAMP_FILTER_PTP_V2_L2_EVENT: | |
| case HWTSTAMP_FILTER_PTP_V2_L2_SYNC: | |
| case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ: | |
| case HWTSTAMP_FILTER_PTP_V2_EVENT: | |
| case HWTSTAMP_FILTER_PTP_V2_SYNC: | |
| case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ: | |
| case HWTSTAMP_FILTER_NTP_ALL: | |
| rx_filter_valid = 1; | |
| break; | |
| case __HWTSTAMP_FILTER_CNT: | |
| /* not a real value */ | |
| break; | |
| } | |
| if (!tx_type_valid || !rx_filter_valid) | |
| return -ERANGE; | |
| return 0; | |
| } | |
| static int dev_do_ioctl(struct net_device *dev, | |
| struct ifreq *ifr, unsigned int cmd) | |
| { | |
| const struct net_device_ops *ops = dev->netdev_ops; | |
| int err; | |
| err = dsa_ndo_do_ioctl(dev, ifr, cmd); | |
| if (err == 0 || err != -EOPNOTSUPP) | |
| return err; | |
| if (ops->ndo_do_ioctl) { | |
| if (netif_device_present(dev)) | |
| err = ops->ndo_do_ioctl(dev, ifr, cmd); | |
| else | |
| err = -ENODEV; | |
| } | |
| return err; | |
| } | |
| /* | |
| * Perform the SIOCxIFxxx calls, inside rtnl_lock() | |
| */ | |
| static int dev_ifsioc(struct net *net, struct ifreq *ifr, unsigned int cmd) | |
| { | |
| int err; | |
| struct net_device *dev = __dev_get_by_name(net, ifr->ifr_name); | |
| const struct net_device_ops *ops; | |
| if (!dev) | |
| return -ENODEV; | |
| ops = dev->netdev_ops; | |
| switch (cmd) { | |
| case SIOCSIFFLAGS: /* Set interface flags */ | |
| return dev_change_flags(dev, ifr->ifr_flags, NULL); | |
| case SIOCSIFMETRIC: /* Set the metric on the interface | |
| (currently unused) */ | |
| return -EOPNOTSUPP; | |
| case SIOCSIFMTU: /* Set the MTU of a device */ | |
| return dev_set_mtu(dev, ifr->ifr_mtu); | |
| case SIOCSIFHWADDR: | |
| if (dev->addr_len > sizeof(struct sockaddr)) | |
| return -EINVAL; | |
| return dev_set_mac_address(dev, &ifr->ifr_hwaddr, NULL); | |
| case SIOCSIFHWBROADCAST: | |
| if (ifr->ifr_hwaddr.sa_family != dev->type) | |
| return -EINVAL; | |
| memcpy(dev->broadcast, ifr->ifr_hwaddr.sa_data, | |
| min(sizeof(ifr->ifr_hwaddr.sa_data), | |
| (size_t)dev->addr_len)); | |
| call_netdevice_notifiers(NETDEV_CHANGEADDR, dev); | |
| return 0; | |
| case SIOCSIFMAP: | |
| if (ops->ndo_set_config) { | |
| if (!netif_device_present(dev)) | |
| return -ENODEV; | |
| return ops->ndo_set_config(dev, &ifr->ifr_map); | |
| } | |
| return -EOPNOTSUPP; | |
| case SIOCADDMULTI: | |
| if (!ops->ndo_set_rx_mode || | |
| ifr->ifr_hwaddr.sa_family != AF_UNSPEC) | |
| return -EINVAL; | |
| if (!netif_device_present(dev)) | |
| return -ENODEV; | |
| return dev_mc_add_global(dev, ifr->ifr_hwaddr.sa_data); | |
| case SIOCDELMULTI: | |
| if (!ops->ndo_set_rx_mode || | |
| ifr->ifr_hwaddr.sa_family != AF_UNSPEC) | |
| return -EINVAL; | |
| if (!netif_device_present(dev)) | |
| return -ENODEV; | |
| return dev_mc_del_global(dev, ifr->ifr_hwaddr.sa_data); | |
| case SIOCSIFTXQLEN: | |
| if (ifr->ifr_qlen < 0) | |
| return -EINVAL; | |
| return dev_change_tx_queue_len(dev, ifr->ifr_qlen); | |
| case SIOCSIFNAME: | |
| ifr->ifr_newname[IFNAMSIZ-1] = '\0'; | |
| return dev_change_name(dev, ifr->ifr_newname); | |
| case SIOCSHWTSTAMP: | |
| err = net_hwtstamp_validate(ifr); | |
| if (err) | |
| return err; | |
| fallthrough; | |
| /* | |
| * Unknown or private ioctl | |
| */ | |
| default: | |
| if ((cmd >= SIOCDEVPRIVATE && | |
| cmd <= SIOCDEVPRIVATE + 15) || | |
| cmd == SIOCBONDENSLAVE || | |
| cmd == SIOCBONDRELEASE || | |
| cmd == SIOCBONDSETHWADDR || | |
| cmd == SIOCBONDSLAVEINFOQUERY || | |
| cmd == SIOCBONDINFOQUERY || | |
| cmd == SIOCBONDCHANGEACTIVE || | |
| cmd == SIOCGMIIPHY || | |
| cmd == SIOCGMIIREG || | |
| cmd == SIOCSMIIREG || | |
| cmd == SIOCBRADDIF || | |
| cmd == SIOCBRDELIF || | |
| cmd == SIOCSHWTSTAMP || | |
| cmd == SIOCGHWTSTAMP || | |
| cmd == SIOCWANDEV) { | |
| err = dev_do_ioctl(dev, ifr, cmd); | |
| } else | |
| err = -EINVAL; | |
| } | |
| return err; | |
| } | |
| /** | |
| * dev_load - load a network module | |
| * @net: the applicable net namespace | |
| * @name: name of interface | |
| * | |
| * If a network interface is not present and the process has suitable | |
| * privileges this function loads the module. If module loading is not | |
| * available in this kernel then it becomes a nop. | |
| */ | |
| void dev_load(struct net *net, const char *name) | |
| { | |
| struct net_device *dev; | |
| int no_module; | |
| rcu_read_lock(); | |
| dev = dev_get_by_name_rcu(net, name); | |
| rcu_read_unlock(); | |
| no_module = !dev; | |
| if (no_module && capable(CAP_NET_ADMIN)) | |
| no_module = request_module("netdev-%s", name); | |
| if (no_module && capable(CAP_SYS_MODULE)) | |
| request_module("%s", name); | |
| } | |
| EXPORT_SYMBOL(dev_load); | |
| /* | |
| * This function handles all "interface"-type I/O control requests. The actual | |
| * 'doing' part of this is dev_ifsioc above. | |
| */ | |
| /** | |
| * dev_ioctl - network device ioctl | |
| * @net: the applicable net namespace | |
| * @cmd: command to issue | |
| * @ifr: pointer to a struct ifreq in user space | |
| * @need_copyout: whether or not copy_to_user() should be called | |
| * | |
| * Issue ioctl functions to devices. This is normally called by the | |
| * user space syscall interfaces but can sometimes be useful for | |
| * other purposes. The return value is the return from the syscall if | |
| * positive or a negative errno code on error. | |
| */ | |
| int dev_ioctl(struct net *net, unsigned int cmd, struct ifreq *ifr, bool *need_copyout) | |
| { | |
| int ret; | |
| char *colon; | |
| if (need_copyout) | |
| *need_copyout = true; | |
| if (cmd == SIOCGIFNAME) | |
| return dev_ifname(net, ifr); | |
| ifr->ifr_name[IFNAMSIZ-1] = 0; | |
| colon = strchr(ifr->ifr_name, ':'); | |
| if (colon) | |
| *colon = 0; | |
| /* | |
| * See which interface the caller is talking about. | |
| */ | |
| switch (cmd) { | |
| /* | |
| * These ioctl calls: | |
| * - can be done by all. | |
| * - atomic and do not require locking. | |
| * - return a value | |
| */ | |
| case SIOCGIFFLAGS: | |
| case SIOCGIFMETRIC: | |
| case SIOCGIFMTU: | |
| case SIOCGIFHWADDR: | |
| case SIOCGIFSLAVE: | |
| case SIOCGIFMAP: | |
| case SIOCGIFINDEX: | |
| case SIOCGIFTXQLEN: | |
| dev_load(net, ifr->ifr_name); | |
| rcu_read_lock(); | |
| ret = dev_ifsioc_locked(net, ifr, cmd); | |
| rcu_read_unlock(); | |
| if (colon) | |
| *colon = ':'; | |
| return ret; | |
| case SIOCETHTOOL: | |
| dev_load(net, ifr->ifr_name); | |
| rtnl_lock(); | |
| ret = dev_ethtool(net, ifr); | |
| rtnl_unlock(); | |
| if (colon) | |
| *colon = ':'; | |
| return ret; | |
| /* | |
| * These ioctl calls: | |
| * - require superuser power. | |
| * - require strict serialization. | |
| * - return a value | |
| */ | |
| case SIOCGMIIPHY: | |
| case SIOCGMIIREG: | |
| case SIOCSIFNAME: | |
| dev_load(net, ifr->ifr_name); | |
| if (!ns_capable(net->user_ns, CAP_NET_ADMIN)) | |
| return -EPERM; | |
| rtnl_lock(); | |
| ret = dev_ifsioc(net, ifr, cmd); | |
| rtnl_unlock(); | |
| if (colon) | |
| *colon = ':'; | |
| return ret; | |
| /* | |
| * These ioctl calls: | |
| * - require superuser power. | |
| * - require strict serialization. | |
| * - do not return a value | |
| */ | |
| case SIOCSIFMAP: | |
| case SIOCSIFTXQLEN: | |
| if (!capable(CAP_NET_ADMIN)) | |
| return -EPERM; | |
| fallthrough; | |
| /* | |
| * These ioctl calls: | |
| * - require local superuser power. | |
| * - require strict serialization. | |
| * - do not return a value | |
| */ | |
| case SIOCSIFFLAGS: | |
| case SIOCSIFMETRIC: | |
| case SIOCSIFMTU: | |
| case SIOCSIFHWADDR: | |
| case SIOCSIFSLAVE: | |
| case SIOCADDMULTI: | |
| case SIOCDELMULTI: | |
| case SIOCSIFHWBROADCAST: | |
| case SIOCSMIIREG: | |
| case SIOCBONDENSLAVE: | |
| case SIOCBONDRELEASE: | |
| case SIOCBONDSETHWADDR: | |
| case SIOCBONDCHANGEACTIVE: | |
| case SIOCBRADDIF: | |
| case SIOCBRDELIF: | |
| case SIOCSHWTSTAMP: | |
| if (!ns_capable(net->user_ns, CAP_NET_ADMIN)) | |
| return -EPERM; | |
| fallthrough; | |
| case SIOCBONDSLAVEINFOQUERY: | |
| case SIOCBONDINFOQUERY: | |
| dev_load(net, ifr->ifr_name); | |
| rtnl_lock(); | |
| ret = dev_ifsioc(net, ifr, cmd); | |
| rtnl_unlock(); | |
| if (need_copyout) | |
| *need_copyout = false; | |
| return ret; | |
| case SIOCGIFMEM: | |
| /* Get the per device memory space. We can add this but | |
| * currently do not support it */ | |
| case SIOCSIFMEM: | |
| /* Set the per device memory buffer space. | |
| * Not applicable in our case */ | |
| case SIOCSIFLINK: | |
| return -ENOTTY; | |
| /* | |
| * Unknown or private ioctl. | |
| */ | |
| default: | |
| if (cmd == SIOCWANDEV || | |
| cmd == SIOCGHWTSTAMP || | |
| (cmd >= SIOCDEVPRIVATE && | |
| cmd <= SIOCDEVPRIVATE + 15)) { | |
| dev_load(net, ifr->ifr_name); | |
| rtnl_lock(); | |
| ret = dev_ifsioc(net, ifr, cmd); | |
| rtnl_unlock(); | |
| return ret; | |
| } | |
| return -ENOTTY; | |
| } | |
| } |