/****************************************************************************
 * net/netdev/netdev_ioctl.c
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <sys/socket.h>
#include <sys/ioctl.h>

#include <assert.h>
#include <debug.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>

#include <nuttx/mutex.h>
#include <nuttx/net/net.h>
#include <nuttx/net/ip.h>

#include <net/if.h>
#include <net/route.h>
#include <net/ethernet.h>
#include <netinet/in.h>

#include <nuttx/net/netdev.h>
#include <nuttx/net/radiodev.h>
#include <nuttx/net/vlan.h>

#ifdef CONFIG_NET_6LOWPAN
#  include <nuttx/net/sixlowpan.h>
#endif

#include <sys/sockio.h>
#include <nuttx/net/igmp.h>

#ifdef CONFIG_NETDEV_WIRELESS_IOCTL
#  include <nuttx/wireless/wireless.h>
#endif

#ifdef CONFIG_WIRELESS_BLUETOOTH
#  include <nuttx/wireless/bluetooth/bt_ioctl.h>
#endif

#ifdef CONFIG_WIRELESS_IEEE802154
#  include <nuttx/wireless/ieee802154/ieee802154_mac.h>
#endif

#ifdef CONFIG_WIRELESS_PKTRADIO
#  include <nuttx/wireless/pktradio.h>
#endif

#ifdef CONFIG_NET_CELLULAR
#  include <nuttx/wireless/cellular/cellular.h>
#endif

#ifdef CONFIG_NETDEV_MODEM_LTE_IOCTL
#  include <nuttx/wireless/lte/lte_ioctl.h>
#endif

#include "arp/arp.h"
#include "socket/socket.h"
#include "netdev/netdev.h"
#include "devif/devif.h"
#include "igmp/igmp.h"
#include "icmpv6/icmpv6.h"
#include "route/route.h"
#include "netlink/netlink.h"
#include "utils/utils.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* Configuration */

#undef HAVE_WRITABLE_IPv4ROUTE
#undef HAVE_WRITABLE_IPv6ROUTE

#ifdef CONFIG_NET_ROUTE
#  if defined(CONFIG_NET_IPv4) && !defined(CONFIG_ROUTE_IPv4_ROMROUTE)
#    define HAVE_WRITABLE_IPv4ROUTE 1
#  endif

#  if defined(CONFIG_NET_IPv6) && !defined(CONFIG_ROUTE_IPv6_ROMROUTE)
#    define HAVE_WRITABLE_IPv6ROUTE 1
#  endif
#endif

#undef HAVE_IEEE802154_IOCTL
#undef HAVE_PKTRADIO_IOCTL
#undef HAVE_BLUETOOTH_IOCTL

#ifdef CONFIG_NETDEV_IOCTL
/* IEEE 802.15.4 6LoWPAN or raw packet support */

#if defined(CONFIG_NET_IEEE802154) || (defined(CONFIG_NET_6LOWPAN) && \
    defined(CONFIG_WIRELESS_IEEE802154))
#  define HAVE_IEEE802154_IOCTL 1
#endif

/* pktradio raw packet support not implemented */

#if defined(CONFIG_NET_6LOWPAN) && defined(CONFIG_WIRELESS_PKTRADIO)
#  define HAVE_PKTRADIO_IOCTL 1
#endif

/* Bluetooth 6LoWPAN support not implemented */

#if defined(CONFIG_NET_BLUETOOTH)
#  define HAVE_BLUETOOTH_IOCTL 1
#endif
#endif /* CONFIG_NETDEV_IOCTL */

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: ioctl_add_ipv4route
 *
 * Description:
 *   Add an IPv4 route to the routing table.
 *
 * Input Parameters:
 *   rentry - Describes the route to be added
 *
 ****************************************************************************/

#ifdef HAVE_WRITABLE_IPv4ROUTE
static int ioctl_add_ipv4route(FAR struct rtentry *rtentry)
{
  FAR struct sockaddr_in *addr;
  in_addr_t target;
  in_addr_t netmask;
  in_addr_t router;

  addr    = (FAR struct sockaddr_in *)&rtentry->rt_dst;
  target  = (in_addr_t)addr->sin_addr.s_addr;

  addr    = (FAR struct sockaddr_in *)&rtentry->rt_genmask;
  netmask = (in_addr_t)addr->sin_addr.s_addr;

  addr    = (FAR struct sockaddr_in *)&rtentry->rt_gateway;
  router  = (in_addr_t)addr->sin_addr.s_addr;

  return net_addroute_ipv4(target, netmask, router);
}
#endif /* HAVE_WRITABLE_IPv4ROUTE */

/****************************************************************************
 * Name: ioctl_add_ipv6route
 *
 * Description:
 *   Add an IPv6 route to the routing table.
 *
 * Input Parameters:
 *   rentry - Describes the route to be added
 *
 ****************************************************************************/

#ifdef HAVE_WRITABLE_IPv6ROUTE
static int ioctl_add_ipv6route(FAR struct rtentry *rtentry)
{
  FAR struct sockaddr_in6 *target;
  FAR struct sockaddr_in6 *netmask;
  FAR struct sockaddr_in6 *gateway;
  net_ipv6addr_t router;

  target  = (FAR struct sockaddr_in6 *)&rtentry->rt_dst;
  netmask = (FAR struct sockaddr_in6 *)&rtentry->rt_genmask;

  /* The router is an optional argument */

  gateway = (FAR struct sockaddr_in6 *)&rtentry->rt_gateway;
  net_ipv6addr_copy(router, gateway->sin6_addr.s6_addr16);

  return net_addroute_ipv6(target->sin6_addr.s6_addr16,
                           netmask->sin6_addr.s6_addr16, router);
}
#endif /* HAVE_WRITABLE_IPv6ROUTE */

/****************************************************************************
 * Name: ioctl_del_ipv4route
 *
 * Description:
 *   Delete an IPv4 route to the routing table.
 *
 * Input Parameters:
 *   rentry - Describes the route to be deleted
 *
 ****************************************************************************/

#ifdef HAVE_WRITABLE_IPv4ROUTE
static int ioctl_del_ipv4route(FAR struct rtentry *rtentry)
{
  FAR struct sockaddr_in *addr;
  in_addr_t target;
  in_addr_t netmask;

  addr    = (FAR struct sockaddr_in *)&rtentry->rt_dst;
  target  = (in_addr_t)addr->sin_addr.s_addr;

  addr    = (FAR struct sockaddr_in *)&rtentry->rt_genmask;
  netmask = (in_addr_t)addr->sin_addr.s_addr;

  return net_delroute_ipv4(target, netmask);
}
#endif /* HAVE_WRITABLE_IPv4ROUTE */

/****************************************************************************
 * Name: ioctl_del_ipv6route
 *
 * Description:
 *   Delete an IPv6 route to the routing table.
 *
 * Input Parameters:
 *   rentry - Describes the route to be deleted
 *
 ****************************************************************************/

#ifdef HAVE_WRITABLE_IPv6ROUTE
static int ioctl_del_ipv6route(FAR struct rtentry *rtentry)
{
  FAR struct sockaddr_in6 *target;
  FAR struct sockaddr_in6 *netmask;

  target  = (FAR struct sockaddr_in6 *)&rtentry->rt_dst;
  netmask = (FAR struct sockaddr_in6 *)&rtentry->rt_genmask;

  return net_delroute_ipv6(target->sin6_addr.s6_addr16,
                           netmask->sin6_addr.s6_addr16);
}
#endif /* HAVE_WRITABLE_IPv6ROUTE */

/****************************************************************************
 * Name: ioctl_get_ipv4addr
 *
 * Description:
 *   Copy IP addresses from device structure to user memory.
 *
 * Input Parameters:
 *   outaddr - Pointer to the user-provided memory to receive the address.
 *   inaddr - The source IP address in the device structure.
 *
 ****************************************************************************/

#ifdef CONFIG_NET_IPv4
static void ioctl_get_ipv4addr(FAR struct sockaddr *outaddr,
                               in_addr_t inaddr)
{
  FAR struct sockaddr_in *dest  = (FAR struct sockaddr_in *)outaddr;
  dest->sin_family              = AF_INET;
  dest->sin_port                = 0;
  dest->sin_addr.s_addr         = inaddr;
  memset(dest->sin_zero, 0, sizeof(dest->sin_zero));
}
#endif

/****************************************************************************
 * Name: ioctl_get_ipv4broadcast
 *
 * Description:
 *   Return the sub-net broadcast address to user memory.
 *
 * Input Parameters:
 *   outaddr - Pointer to the user-provided memory to receive the address.
 *   inaddr  - The source IP address in the device structure.
 *   netmask  - The netmask address mask in the device structure.
 *
 ****************************************************************************/

#ifdef CONFIG_NET_IPv4
static void ioctl_get_ipv4broadcast(FAR struct sockaddr *outaddr,
                                    in_addr_t inaddr, in_addr_t netmask)
{
  FAR struct sockaddr_in *dest  = (FAR struct sockaddr_in *)outaddr;
  dest->sin_family              = AF_INET;
  dest->sin_port                = 0;
  dest->sin_addr.s_addr         = net_ipv4addr_broadcast(inaddr, netmask);
  memset(dest->sin_zero, 0, sizeof(dest->sin_zero));
}
#endif

/****************************************************************************
 * Name: ioctl_get_ipv6addr
 *
 * Description:
 *   Copy IP addresses from device structure to user memory.
 *
 * Input Parameters:
 *   outaddr - Pointer to the user-provided memory to receive the address.
 *   inaddr - The source IP address in the device structure.
 *
 ****************************************************************************/

#ifdef CONFIG_NET_IPv6
static void ioctl_get_ipv6addr(FAR struct sockaddr_storage *outaddr,
                               FAR const net_ipv6addr_t inaddr)
{
  FAR struct sockaddr_in6 *dest = (FAR struct sockaddr_in6 *)outaddr;
  dest->sin6_family             = AF_INET6;
  dest->sin6_port               = 0;
  memcpy(dest->sin6_addr.in6_u.u6_addr8, inaddr, 16);
}
#endif

/****************************************************************************
 * Name: ioctl_set_ipv4addr
 *
 * Description:
 *   Copy IP addresses from user memory into the device structure
 *
 * Input Parameters:
 *   outaddr - Pointer to the source IP address in the device structure.
 *   inaddr - Pointer to the user-provided memory to containing the new IP
 *     address.
 *
 ****************************************************************************/

#ifdef CONFIG_NET_IPv4
static void ioctl_set_ipv4addr(FAR in_addr_t *outaddr,
                               FAR const struct sockaddr *inaddr)
{
  FAR const struct sockaddr_in *src = (FAR const struct sockaddr_in *)inaddr;
  *outaddr = src->sin_addr.s_addr;
}
#endif

/****************************************************************************
 * Name: ioctl_set_ipv6addr
 *
 * Description:
 *   Copy IP addresses from user memory into the device structure
 *
 * Input Parameters:
 *   outaddr - Pointer to the source IP address in the device structure.
 *   inaddr  - Pointer to the user-provided memory to containing the new IP
 *     address.
 *
 ****************************************************************************/

#ifdef CONFIG_NET_IPv6
static void ioctl_set_ipv6addr(FAR net_ipv6addr_t outaddr,
                               FAR const struct sockaddr_storage *inaddr)
{
  FAR const struct sockaddr_in6 *src =
    (FAR const struct sockaddr_in6 *)inaddr;
  memcpy(outaddr, src->sin6_addr.in6_u.u6_addr8, 16);
}
#endif

/****************************************************************************
 * Name: netdev_bluetooth_ioctl
 *
 * Description:
 *   Perform Bluetooth network device specific operations.
 *
 * Input Parameters:
 *   psock  - Socket structure
 *   dev    - Ethernet driver device structure
 *   cmd    - The ioctl command
 *   req    - The argument of the ioctl cmd
 *
 * Returned Value:
 *   >=0 on success (positive non-zero values are cmd-specific)
 *   Negated errno returned on failure.
 *
 ****************************************************************************/

#ifdef HAVE_BLUETOOTH_IOCTL
static int netdev_bluetooth_ioctl(FAR struct socket *psock, int cmd,
                                  unsigned long arg)
{
  FAR struct net_driver_s *dev;
  FAR char *ifname;
  int ret = -ENOTTY;

  if (_BLUETOOTHIOCVALID(cmd))
    {
      if (arg != 0ul)
        {
          /* Get the name of the Bluetooth device to receive the IOCTL
           * command
           */

          FAR struct btreq_s *btreq =
            (FAR struct btreq_s *)((uintptr_t)arg);

          ifname = btreq->btr_name;
        }
      else
        {
          /* Argument is invalid */

          return -EINVAL;
        }

      /* Find the device with this name */

      dev = netdev_findbyname(ifname);
      ret = -ENODEV;

      if (dev != NULL && dev->d_lltype == NET_LL_BLUETOOTH)
        {
          /* Perform the device IOCTL */

          ret = dev->d_ioctl(dev, cmd, arg);
        }
    }

  return ret;
}
#endif

/****************************************************************************
 * Name: netdev_iee802154_ioctl
 *
 * Description:
 *   Perform IEEE802.15.4 network device specific operations.
 *
 * Input Parameters:
 *   psock  - Socket structure
 *   dev    - Ethernet driver device structure
 *   cmd    - The ioctl command
 *   req    - The argument of the ioctl cmd
 *
 * Returned Value:
 *   >=0 on success (positive non-zero values are cmd-specific)
 *   Negated errno returned on failure.
 *
 ****************************************************************************/

#ifdef HAVE_IEEE802154_IOCTL
static int netdev_iee802154_ioctl(FAR struct socket *psock, int cmd,
                                  unsigned long arg)
{
  FAR struct net_driver_s *dev;
  FAR char *ifname;
  int ret = -ENOTTY;

  if (_MAC802154IOCVALID(cmd))
    {
      if (arg != 0ul)
        {
          /* Get the IEEE802.15.4 MAC device to receive the radio IOCTL
           * command
           */

          FAR struct ieee802154_netmac_s *netmac =
            (FAR struct ieee802154_netmac_s *)((uintptr_t)arg);

          ifname = netmac->ifr_name;
        }
      else
        {
          /* Argument is invalid */

          return -EINVAL;
        }

      /* Find the device with this name */

      dev = netdev_findbyname(ifname);
      if (dev != NULL && dev->d_lltype == NET_LL_IEEE802154)
        {
          /* Perform the device IOCTL */

          ret = dev->d_ioctl(dev, cmd, arg);
        }
    }

  return ret;
}
#endif /* HAVE_IEEE802154_IOCTL */

/****************************************************************************
 * Name: netdev_pktradio_ioctl
 *
 * Description:
 *   Perform non-IEEE802.15.4 packet radio network device specific operation.
 *
 * Input Parameters:
 *   psock  - Socket structure
 *   dev    - Ethernet driver device structure
 *   cmd    - The ioctl command
 *   req    - The argument of the ioctl cmd
 *
 * Returned Value:
 *   >=0 on success (positive non-zero values are cmd-specific)
 *   Negated errno returned on failure.
 *
 ****************************************************************************/

#ifdef HAVE_PKTRADIO_IOCTL
static int netdev_pktradio_ioctl(FAR struct socket *psock, int cmd,
                                 unsigned long arg)
{
  FAR struct net_driver_s *dev;
  FAR char *ifname;
  int ret = -ENOTTY;

  if (_PKRADIOIOCVALID(cmd))
    {
      if (arg != 0ul)
        {
          /* Get the packet radio device to receive the radio IOCTL
           * command
           */

          FAR struct pktradio_ifreq_s *cmddata =
            (FAR struct pktradio_ifreq_s *)((uintptr_t)arg);

          ifname = cmddata->pifr_name;
        }
      else
        {
          /* Argument is invalid */

          return -EINVAL;
        }

      /* Find the device with this name */

      dev = netdev_findbyname(ifname);
      if (dev != NULL && dev->d_lltype == NET_LL_PKTRADIO)
        {
          /* Perform the device IOCTL */

          ret = dev->d_ioctl(dev, cmd, arg);
        }
    }

  return ret;
}
#endif /* HAVE_PKTRADIO_IOCTL */

/****************************************************************************
 * Name: netdev_cell_ioctl
 *
 * Description:
 *   Perform cell ioctl operations.
 *
 * Parameters:
 *   psock    Socket structure
 *   cmd      The ioctl command
 *   arg      The argument of the ioctl cmd
 *
 * Return:
 *   >=0 on success (positive non-zero values are cmd-specific)
 *   Negated errno returned on failure.
 *
 ****************************************************************************/

#if defined(CONFIG_NETDEV_IOCTL) && defined(CONFIG_NET_CELLULAR)
static int netdev_cell_ioctl(FAR struct socket *psock, int cmd,
                             FAR struct icellreq *req)
{
  FAR struct net_driver_s *dev = NULL;
  int ret = -ENOTTY;

  ninfo("cmd: %d\n", cmd);

  if (_CELLIOCVALID(cmd))
    {
      dev = netdev_findbyname(req->ifr_name);
      if (dev && dev->d_ioctl)
        {
          netdev_lock(dev);
          ret = dev->d_ioctl(dev, cmd, (unsigned long)(uintptr_t)req);
          netdev_unlock(dev);
        }
    }

  return ret;
}
#endif

/****************************************************************************
 * Name: netdev_wifr_ioctl
 *
 * Description:
 *   Perform wireless network device specific operations.
 *
 * Input Parameters:
 *   psock    Socket structure
 *   dev      Ethernet driver device structure
 *   cmd      The ioctl command
 *   req      The argument of the ioctl cmd
 *
 * Returned Value:
 *   >=0 on success (positive non-zero values are cmd-specific)
 *   Negated errno returned on failure.
 *
 ****************************************************************************/

#if defined(CONFIG_NETDEV_IOCTL) && defined(CONFIG_NETDEV_WIRELESS_IOCTL)
static int netdev_wifr_ioctl(FAR struct socket *psock, int cmd,
                             FAR struct iwreq *req)
{
  FAR struct net_driver_s *dev;
  int ret = -ENOTTY;

  /* Verify that this is a valid wireless network IOCTL command */

  if (_WLIOCVALID(cmd))
    {
      /* Get the wireless device associated with the IOCTL command */

      dev = netdev_findbyname(req->ifr_name);
      if (cmd == SIOCGIWNAME)
        {
          if (dev == NULL || dev->d_lltype != NET_LL_IEEE80211)
            {
              ret = -ENODEV;
            }
          else
            {
              strcpy((FAR char *)&req->u, "IEEE 802.11");
              ret = OK;
            }
        }

      if (dev != NULL && ret == -ENOTTY)
        {
          /* Just forward the IOCTL to the wireless driver */

          ret = dev->d_ioctl(dev, cmd, (unsigned long)(uintptr_t)req);
        }
    }

  return ret;
}
#endif

/****************************************************************************
 * Name: netdev_vlan_ioctl
 *
 * Description:
 *   Perform VLAN network device specific operations.
 *
 * Input Parameters:
 *   psock    Socket structure
 *   cmd      The ioctl command
 *   req      The argument of the ioctl cmd
 *
 * Returned Value:
 *   >=0 on success (positive non-zero values are cmd-specific)
 *   Negated errno returned on failure.
 *
 ****************************************************************************/

#if defined(CONFIG_NETDEV_IOCTL) && defined(CONFIG_NET_VLAN)
static int netdev_vlan_ioctl(FAR struct socket *psock, int cmd,
                             FAR struct vlan_ioctl_args *req)
{
  FAR struct net_driver_s *dev;
  int ret = -ENOTTY;

  /* Verify that this is a valid VLAN IOCTL command */

  if (cmd == SIOCGIFVLAN || cmd == SIOCSIFVLAN)
    {
      /* Get the network device associated with the IOCTL command */

      dev = netdev_findbyname(req->device1);
      if (dev != NULL)
        {
          /* Just forward the IOCTL to the network driver */

          ret = dev->d_ioctl(dev, cmd, (unsigned long)(uintptr_t)req);
        }
      else
        {
          ret = -ENODEV;
        }
    }

  return ret;
}
#endif

/****************************************************************************
 * Name: netdev_ifr_split_idx
 *
 * Description:
 *   Split the address index from device name like 'eth0:0'.
 *
 * Input Parameters:
 *   req - The argument of the ioctl cmd
 *
 * Returned Value:
 *   The address index from device name.
 *
 ****************************************************************************/

static unsigned int netdev_ifr_split_idx(FAR struct ifreq *req)
{
  FAR char *colon = strchr(req->ifr_name, ':');
  int idx;

  if (colon)
    {
      *colon++ = '\0'; /* Remove suffix from device name */
      idx = atoi(colon);
      return idx >= 0 ? idx + 1 : 0; /* eth0:0 represents the second addr */
    }

  return 0;
}

/****************************************************************************
 * Name: net_ioctl_ifreq_arglen
 *
 * Description:
 *   Calculate the ioctl argument buffer length of ifreq.
 *
 * Input Parameters:
 *   domain   The socket domain
 *   cmd      The ioctl command
 *
 * Returned Value:
 *   The argument buffer length, or error code.
 *
 ****************************************************************************/

static ssize_t net_ioctl_ifreq_arglen(uint8_t domain, int cmd)
{
  switch (cmd)
    {
      case SIOCGIFADDR:
      case SIOCGIFDSTADDR:
      case SIOCSIFDSTADDR:
      case SIOCGIFBRDADDR:
      case SIOCSIFBRDADDR:
      case SIOCGIFNETMASK:
      case SIOCSIFNETMASK:
      case SIOCSIFMTU:
      case SIOCGIFMTU:
      case SIOCGIFHWADDR:
      case SIOCSIFHWADDR:
      case SIOCGIFCOUNT:
      case SIOCSIFFLAGS:
      case SIOCGIFFLAGS:
      case SIOCMIINOTIFY:
      case SIOCGMIIPHY:
      case SIOCGMIIREG:
      case SIOCSMIIREG:
#if defined(CONFIG_NETDEV_IOCTL) && defined(CONFIG_NETDEV_DEVPRIVATE_IOCTL)
      case SIOCDEVPRIVATE ... SIOCDEVPRIVATE + 15:
#endif
      case SIOCGCANBITRATE:
      case SIOCSCANBITRATE:
      case SIOCACANEXTFILTER:
      case SIOCDCANEXTFILTER:
      case SIOCACANSTDFILTER:
      case SIOCDCANSTDFILTER:
      case SIOCCANRECOVERY:
      case SIOCGCANSTATE:
      case SIOCSCANSTATE:
      case SIOCCANOFLUSH:
      case SIOCGCANTRSVSTATE:
      case SIOCSCANTRSVSTATE:
      case SIOCSIFNAME:
      case SIOCGIFNAME:
      case SIOCGIFINDEX:
      case SIOCETHTOOL:
        return sizeof(struct ifreq);

      case SIOCSIFADDR:
      case SIOCDIFADDR:
        return domain == PF_INET6 ?
                 sizeof(struct in6_ifreq) : sizeof(struct ifreq);

      case SIOCGLIFADDR:
      case SIOCSLIFADDR:
      case SIOCGLIFDSTADDR:
      case SIOCSLIFDSTADDR:
      case SIOCGLIFBRDADDR:
      case SIOCSLIFBRDADDR:
      case SIOCGLIFNETMASK:
      case SIOCSLIFNETMASK:
      case SIOCGLIFMTU:
      case SIOCIFAUTOCONF:
        return sizeof(struct lifreq);
      default:
        break;
    }

  return -ENOTTY;
}

/****************************************************************************
 * Name: netdev_ifr_ioctl
 *
 * Description:
 *   Perform network device specific operations.
 *
 * Input Parameters:
 *   psock    Socket structure
 *   cmd      The ioctl command
 *   req      The argument of the ioctl cmd
 *
 * Returned Value:
 *   >=0 on success (positive non-zero values are cmd-specific)
 *   Negated errno returned on failure.
 *
 ****************************************************************************/

static int netdev_ifr_ioctl(FAR struct socket *psock, int cmd,
                            FAR struct ifreq *req)
{
  FAR struct net_driver_s *dev = NULL;
  unsigned int idx = 0;
  int ret = OK;

  ninfo("cmd: %d\n", cmd);

  /* Execute commands that do not need ifr_name or lifr_name */

  switch (cmd)
    {
      case SIOCGIFCOUNT:  /* Get number of devices */
        req->ifr_count = netdev_count();
        break;

#ifdef CONFIG_NET_IPv4
      case SIOCGIFCONF:  /* Return an interface list (IPv4) */
        ret = netdev_ipv4_ifconf((FAR struct ifconf *)req);
        break;
#endif

#ifdef CONFIG_NET_IPv6
      case SIOCGLIFCONF:  /* Return an interface list (IPv6) */
        ret = netdev_ipv6_ifconf((FAR struct lifconf *)req);
        break;
#endif

#ifdef CONFIG_NETDEV_IFINDEX
      case SIOCSIFNAME:   /* Set interface name */
        {
          FAR struct net_driver_s *tmpdev;
          tmpdev = netdev_findbyindex(req->ifr_ifindex);
          if (tmpdev != NULL)
            {
              strlcpy(tmpdev->d_ifname, req->ifr_name, IFNAMSIZ);
            }
          else
            {
              ret = -ENODEV;
            }
        }
        break;

      case SIOCGIFNAME:  /* Get interface name */
        {
          FAR struct net_driver_s *tmpdev;
          tmpdev = netdev_findbyindex(req->ifr_ifindex);
          if (tmpdev != NULL)
            {
              strlcpy(req->ifr_name, tmpdev->d_ifname, IFNAMSIZ);
            }
          else
            {
              ret = -ENODEV;
            }
        }
        break;
#endif
      default:
        if (req == NULL)
          {
            return -ENOTTY;
          }

        if (net_ioctl_ifreq_arglen(psock->s_domain, cmd)
            >= (ssize_t)sizeof(struct ifreq))
          {
            idx = netdev_ifr_split_idx(req);
            UNUSED(idx);
            dev = netdev_findbyname(req->ifr_name);
          }
        else if (net_ioctl_ifreq_arglen(psock->s_domain, cmd)
                 == (ssize_t)sizeof(struct in6_ifreq))
          {
            FAR struct in6_ifreq *ifr6 = (FAR struct in6_ifreq *)req;
            dev = netdev_findbyindex(ifr6->ifr6_ifindex);
          }

        if (dev == NULL)
          {
            ret = -ENOTTY;
          }
        break;
    }

  if (dev == NULL)
    {
      return ret;
    }

  netdev_lock(dev);

  /* Execute commands that need ifr_name or lifr_name */

  switch (cmd)
    {
#ifdef CONFIG_NET_IPv4
      case SIOCGIFADDR:  /* Get IP address */
        ioctl_get_ipv4addr(&req->ifr_addr, dev->d_ipaddr);
        break;

      case SIOCGIFDSTADDR:  /* Get P-to-P address */
        ioctl_get_ipv4addr(&req->ifr_dstaddr, dev->d_draddr);
        break;

      case SIOCSIFDSTADDR:  /* Set P-to-P address */
        ioctl_set_ipv4addr(&dev->d_draddr, &req->ifr_dstaddr);
        break;

      case SIOCGIFBRDADDR:  /* Get broadcast IP address */
        ioctl_get_ipv4broadcast(&req->ifr_broadaddr, dev->d_ipaddr,
                                dev->d_netmask);
        break;

      case SIOCSIFBRDADDR:  /* Set broadcast IP address */
        ret = -ENOSYS;
        break;

      case SIOCGIFNETMASK:  /* Get network mask */
        ioctl_get_ipv4addr(&req->ifr_addr, dev->d_netmask);
        break;

      case SIOCSIFNETMASK:  /* Set network mask */
        ioctl_set_ipv4addr(&dev->d_netmask, &req->ifr_addr);
        break;
#endif

#ifdef CONFIG_NET_IPv6
      case SIOCGLIFADDR:  /* Get IP address */
        {
          FAR struct lifreq *lreq = (FAR struct lifreq *)req;
          idx = MIN(idx, CONFIG_NETDEV_MAX_IPv6_ADDR - 1);
          ioctl_get_ipv6addr(&lreq->lifr_addr, dev->d_ipv6[idx].addr);
        }
        break;

      case SIOCSLIFADDR:  /* Set IP address */
        {
          FAR struct lifreq *lreq = (FAR struct lifreq *)req;
          idx = MIN(idx, CONFIG_NETDEV_MAX_IPv6_ADDR - 1);

          netdev_ipv6_removemcastmac(dev, dev->d_ipv6[idx].addr);
          ioctl_set_ipv6addr(dev->d_ipv6[idx].addr, &lreq->lifr_addr);
          netdev_ipv6_addmcastmac(dev, dev->d_ipv6[idx].addr);

          netlink_device_notify_ipaddr(dev, RTM_NEWADDR, AF_INET6,
           dev->d_ipv6[idx].addr, net_ipv6_mask2pref(dev->d_ipv6[idx].mask));
        }
        break;

      case SIOCGLIFDSTADDR:  /* Get P-to-P address */
        {
          FAR struct lifreq *lreq = (FAR struct lifreq *)req;
          ioctl_get_ipv6addr(&lreq->lifr_dstaddr, dev->d_ipv6draddr);
        }
        break;

      case SIOCSLIFDSTADDR:  /* Set P-to-P address */
        {
          FAR struct lifreq *lreq = (FAR struct lifreq *)req;
          ioctl_set_ipv6addr(dev->d_ipv6draddr, &lreq->lifr_dstaddr);
        }
        break;

      case SIOCGLIFBRDADDR:  /* Get broadcast IP address */
      case SIOCSLIFBRDADDR:  /* Set broadcast IP address */
        ret = -ENOSYS;
        break;

      case SIOCGLIFNETMASK:  /* Get network mask */
        {
          FAR struct lifreq *lreq = (FAR struct lifreq *)req;
          idx = MIN(idx, CONFIG_NETDEV_MAX_IPv6_ADDR - 1);
          ioctl_get_ipv6addr(&lreq->lifr_addr, dev->d_ipv6[idx].mask);
        }
        break;

      case SIOCSLIFNETMASK:  /* Set network mask */
        {
          FAR struct lifreq *lreq = (FAR struct lifreq *)req;
          idx = MIN(idx, CONFIG_NETDEV_MAX_IPv6_ADDR - 1);
          ioctl_set_ipv6addr(dev->d_ipv6[idx].mask, &lreq->lifr_addr);
        }
        break;
#endif

      case SIOCGLIFMTU:  /* Get MTU size */
      case SIOCGIFMTU:   /* Get MTU size */
        req->ifr_mtu = NETDEV_PKTSIZE(dev) - dev->d_llhdrlen;
        break;
      case SIOCSIFMTU:   /* Set MTU size */
        NETDEV_PKTSIZE(dev) = req->ifr_mtu + dev->d_llhdrlen;
        break;

#ifdef CONFIG_NET_ICMPv6_AUTOCONF
      case SIOCIFAUTOCONF:  /* Perform ICMPv6 auto-configuration */
        ret = icmpv6_autoconfig(dev);
        break;
#endif

      case SIOCSIFFLAGS:  /* Sets the interface flags */
#ifdef CONFIG_NET_ARP

        /* Is this a request to set the IFF_NOARP flag? */

        if (IFF_IS_NOARP(req->ifr_flags) != IFF_IS_NOARP(dev->d_flags))
          {
            if (IFF_IS_NOARP(req->ifr_flags))
              {
                /* Yes. Set the IFF_NOARP flag */

                IFF_SET_NOARP(dev->d_flags);
              }
            else
              {
                /* No. Clear the IFF_NOARP flag */

                IFF_CLR_NOARP(dev->d_flags);
              }
          }
#endif

        /* Is this a request to bring the interface up/down? */

        if (IFF_IS_UP(req->ifr_flags) != IFF_IS_UP(dev->d_flags))
          {
            if (IFF_IS_UP(req->ifr_flags))
              {
                /* Yes.. bring the interface up */

                ret = netdev_ifup(dev);
#ifdef CONFIG_NET_ARP_ACD
                /* having address then start acd */

                arp_acd_setup(dev);
#endif /* CONFIG_NET_ARP_ACD */
              }
            else
              {
                /* Yes.. take the interface down */

                ret = netdev_ifdown(dev);
              }
          }

        break;

      case SIOCGIFFLAGS:  /* Gets the interface flags */
        req->ifr_flags = dev->d_flags;
        break;

      /* MAC address operations only make sense if Ethernet or 6LoWPAN are
       * supported.
       */

#if defined(CONFIG_NET_ETHERNET) || defined(CONFIG_NET_6LOWPAN)
      case SIOCGIFHWADDR:  /* Get hardware address */
#ifdef CONFIG_NET_ETHERNET
        if (dev->d_lltype == NET_LL_ETHERNET ||
            dev->d_lltype == NET_LL_IEEE80211)
          {
            req->ifr_hwaddr.sa_family = ARPHRD_ETHER;
            memcpy(req->ifr_hwaddr.sa_data,
                   dev->d_mac.ether.ether_addr_octet, IFHWADDRLEN);
          }
        else
#endif
#ifdef CONFIG_NET_6LOWPAN
        if (dev->d_lltype == NET_LL_IEEE802154 ||
            dev->d_lltype == NET_LL_PKTRADIO)
          {
            req->ifr_hwaddr.sa_family = ARPHRD_IEEE802154;
            memcpy(req->ifr_hwaddr.sa_data,
                   dev->d_mac.radio.nv_addr,
                   dev->d_mac.radio.nv_addrlen);
          }
        else
#endif
          {
            nwarn("WARNING: Unsupported link layer\n");
            ret = -EAFNOSUPPORT;
          }
        break;

      case SIOCSIFHWADDR:  /* Set hardware address -- will not take effect until ifup */
#ifdef CONFIG_NET_ETHERNET
        if (dev->d_lltype == NET_LL_ETHERNET ||
            dev->d_lltype == NET_LL_IEEE80211)
          {
            memcpy(dev->d_mac.ether.ether_addr_octet,
                   req->ifr_hwaddr.sa_data, IFHWADDRLEN);
          }
        else
#endif
#ifdef CONFIG_NET_6LOWPAN
        if (dev->d_lltype == NET_LL_IEEE802154 ||
            dev->d_lltype == NET_LL_PKTRADIO)
          {
            FAR struct radio_driver_s *radio;
            struct radiodev_properties_s properties;

            /* Get the radio properties */

            radio = (FAR struct radio_driver_s *)dev;
            DEBUGASSERT(radio->r_properties != NULL);

            ret = radio->r_properties(radio, &properties);
            if (ret >= 0)
              {
                dev->d_mac.radio.nv_addrlen = properties.sp_addrlen;

                DEBUGASSERT(dev->d_mac.radio.nv_addrlen <=
                            sizeof(dev->d_mac.radio.nv_addr));
                DEBUGASSERT(dev->d_mac.radio.nv_addrlen <=
                            sizeof(req->ifr_hwaddr.sa_data));

                memcpy(dev->d_mac.radio.nv_addr,
                       req->ifr_hwaddr.sa_data, dev->d_mac.radio.nv_addrlen);
              }
          }
        else
#endif
          {
            nerr("Unsupported link layer\n");
            ret = -EAFNOSUPPORT;
          }
        break;
#endif

      case SIOCSIFADDR:  /* Set IP address */
#ifdef CONFIG_NET_IPv4
        if (psock->s_domain != PF_INET6)
          {
            if (net_ipv4addr_cmp(dev->d_ipaddr,
                ((FAR struct sockaddr_in *)&req->ifr_addr)->sin_addr.s_addr))
              {
                break;
              }

            ioctl_set_ipv4addr(&dev->d_ipaddr, &req->ifr_addr);
            netlink_device_notify_ipaddr(dev, RTM_NEWADDR, AF_INET,
                         &dev->d_ipaddr, net_ipv4_mask2pref(dev->d_netmask));

#ifdef CONFIG_NET_ARP_ACD
            arp_acd_set_addr(dev);
#endif /* CONFIG_NET_ARP_ACD */
          }
#endif

#ifdef CONFIG_NET_IPv6
        if (psock->s_domain == PF_INET6)
          {
            FAR struct in6_ifreq *ifr6 = (FAR struct in6_ifreq *)req;
            ret = netdev_ipv6_add(dev, ifr6->ifr6_addr.in6_u.u6_addr16,
                                  ifr6->ifr6_prefixlen);
            if (ret == OK)
              {
                netlink_device_notify_ipaddr(dev, RTM_NEWADDR, AF_INET6,
                      ifr6->ifr6_addr.in6_u.u6_addr16, ifr6->ifr6_prefixlen);
              }
          }
#endif
        break;

      case SIOCDIFADDR:  /* Delete IP address */
#ifdef CONFIG_NET_IPv4
        if (psock->s_domain != PF_INET6)
          {
            netlink_device_notify_ipaddr(dev, RTM_DELADDR, AF_INET,
                         &dev->d_ipaddr, net_ipv4_mask2pref(dev->d_netmask));
            dev->d_ipaddr = 0;
          }
#endif

#ifdef CONFIG_NET_IPv6
        if (psock->s_domain == PF_INET6)
          {
            FAR struct in6_ifreq *ifr6 = (FAR struct in6_ifreq *)req;
            ret = netdev_ipv6_del(dev, ifr6->ifr6_addr.in6_u.u6_addr16,
                                  ifr6->ifr6_prefixlen);
            if (ret == OK)
              {
                netlink_device_notify_ipaddr(dev, RTM_DELADDR, AF_INET6,
                      ifr6->ifr6_addr.in6_u.u6_addr16, ifr6->ifr6_prefixlen);
              }
          }
#endif
        break;

#if defined(CONFIG_NETDEV_IOCTL) && defined(CONFIG_NETDEV_PHY_IOCTL)
#ifdef CONFIG_ARCH_PHY_INTERRUPT
      case SIOCMIINOTIFY: /* Set up for PHY event notifications */
        if (dev->d_ioctl)
          {
            FAR struct mii_ioctl_notify_s *notify =
              &req->ifr_ifru.ifru_mii_notify;
            ret = dev->d_ioctl(dev, cmd, (unsigned long)(uintptr_t)notify);
          }
        else
          {
            ret = -ENOSYS;
          }
        break;
#endif

      case SIOCGMIIPHY: /* Get address of MII PHY in use */
      case SIOCGMIIREG: /* Get MII register via MDIO */
      case SIOCSMIIREG: /* Set MII register via MDIO */
        if (dev->d_ioctl)
          {
            FAR struct mii_ioctl_data_s *mii_data =
              &req->ifr_ifru.ifru_mii_data;
            ret = dev->d_ioctl(dev, cmd,
                               (unsigned long)(uintptr_t)mii_data);
          }
        else
          {
            ret = -ENOSYS;
          }
        break;
#endif

#if defined(CONFIG_NETDEV_IOCTL) && defined(CONFIG_NETDEV_CAN_BITRATE_IOCTL)
      case SIOCGCANBITRATE:  /* Get bitrate from a CAN controller */
      case SIOCSCANBITRATE:  /* Set bitrate of a CAN controller */
        if (dev->d_ioctl)
          {
            FAR struct can_ioctl_data_s *can_bitrate_data =
              &req->ifr_ifru.ifru_can_data;
            ret = dev->d_ioctl(dev, cmd,
                          (unsigned long)(uintptr_t)can_bitrate_data);
          }
        else
          {
            ret = -ENOSYS;
          }
        break;
#endif

#if defined(CONFIG_NETDEV_IOCTL) && defined(CONFIG_NETDEV_DEVPRIVATE_IOCTL)
      case SIOCDEVPRIVATE ... SIOCDEVPRIVATE + 15:  /* Private ioctl */
        if (dev->d_ioctl)
          {
            FAR void *data = req->ifr_ifru.ifru_data;
            ret = dev->d_ioctl(dev, cmd, (unsigned long)(uintptr_t)data);
          }
        else
          {
            ret = -ENOSYS;
          }
        break;
#endif

#if defined(CONFIG_NETDEV_IOCTL) && defined(CONFIG_NETDEV_CAN_FILTER_IOCTL)
      case SIOCACANEXTFILTER:  /* Add an extended-ID filter */
      case SIOCDCANEXTFILTER:  /* Delete an extended-ID filter */
      case SIOCACANSTDFILTER:  /* Add a standard-ID filter */
      case SIOCDCANSTDFILTER:  /* Delete a standard-ID filter */
      case SIOCCANRECOVERY:    /* Recovery can controller when bus-off */
      case SIOCCANOFLUSH:      /* Flush data written but not transmitted */
        if (dev->d_ioctl)
          {
            FAR struct can_ioctl_filter_s *can_filter =
              &req->ifr_ifru.ifru_can_filter;
            ret = dev->d_ioctl(dev, cmd,
                          (unsigned long)(uintptr_t)can_filter);
          }
        else
          {
            ret = -ENOSYS;
          }
        break;
#endif

#if defined(CONFIG_NETDEV_IOCTL) && defined(CONFIG_NETDEV_CAN_STATE_IOCTL)
      case SIOCGCANSTATE:  /* Get state from a CAN/LIN controller */
      case SIOCSCANSTATE:  /* Set the LIN/CAN controller state */
        if (dev->d_ioctl)
          {
            FAR struct can_ioctl_state_s *can_state =
              &req->ifr_ifru.ifru_can_state;
            ret = dev->d_ioctl(dev, cmd,
                          (unsigned long)(uintptr_t)can_state);
          }
        else
          {
            ret = -ENOSYS;
          }
        break;

      case SIOCGCANTRSVSTATE:  /* Get state from CAN transceiver */
      case SIOCSCANTRSVSTATE:  /* Set the CAN transceiver state */
        if (dev->d_ioctl)
          {
            FAR struct can_ioctl_transv_state_s *can_transv_state =
              &req->ifr_ifru.lifru_can_transv_state;
            ret = dev->d_ioctl(dev, cmd,
                          (unsigned long)(uintptr_t)can_transv_state);
          }
        else
          {
            ret = -ENOSYS;
          }
        break;
#endif

#ifdef CONFIG_NETDEV_ETHTOOL_IOCTL
      case SIOCETHTOOL:  /* Ethtool ioctl */
        if (dev->d_ioctl)
          {
            ret = dev->d_ioctl(dev, cmd,
                          (unsigned long)(uintptr_t)req->ifr_data);
          }
        else
          {
            ret = -ENOSYS;
          }
        break;
#endif

#ifdef CONFIG_NETDEV_IFINDEX
      case SIOCGIFINDEX:  /* Index to name mapping */
        req->ifr_ifindex = dev->d_ifindex;
        break;
#endif

      default:
        ret = -ENOTTY;
        break;
    }

  netdev_unlock(dev);
  return ret;
}

/****************************************************************************
 * Name: netdev_imsfdev
 *
 * Description:
 *   Verify the struct ip_msfilter and get the Ethernet device.
 *
 * Input Parameters:
 *   req - The argument of the ioctl cmd
 *
 * Returned Value:
 *  A pointer to the driver structure on success; NULL on failure.
 *
 ****************************************************************************/

#ifdef CONFIG_NET_IGMP
static FAR struct net_driver_s *netdev_imsfdev(FAR struct ip_msfilter *imsf)
{
  if (imsf == NULL)
    {
      return NULL;
    }

  /* Find the network device associated with the address of the IP address
   * of the local device.
   */

  return netdev_findby_lipv4addr(imsf->imsf_interface.s_addr);
}
#endif

/****************************************************************************
 * Name: netdev_imsf_ioctl
 *
 * Description:
 *   Perform network device specific operations.
 *
 * Input Parameters:
 *   psock    Socket structure
 *   dev      Ethernet driver device structure
 *   cmd      The ioctl command
 *   imsf     The argument of the ioctl cmd
 *
 * Returned Value:
 *   >=0 on success (positive non-zero values are cmd-specific)
 *   Negated errno returned on failure.
 *
 ****************************************************************************/

#ifdef CONFIG_NET_IGMP
static int netdev_imsf_ioctl(FAR struct socket *psock, int cmd,
                             FAR struct ip_msfilter *imsf)
{
  FAR struct net_driver_s *dev;
  int ret = -EINVAL;

  ninfo("cmd: %d\n", cmd);

  /* Execute the command */

  switch (cmd)
    {
      case SIOCSIPMSFILTER:  /* Set source filter content */
        {
          dev = netdev_imsfdev(imsf);
          if (dev)
            {
              netdev_lock(dev);
              if (imsf->imsf_fmode == MCAST_INCLUDE)
                {
                  ret = igmp_joingroup(dev, &imsf->imsf_multiaddr);
                }
              else
                {
                  DEBUGASSERT(imsf->imsf_fmode == MCAST_EXCLUDE);
                  ret = igmp_leavegroup(dev, &imsf->imsf_multiaddr);
                }

              netdev_unlock(dev);
            }
        }
        break;

      case SIOCGIPMSFILTER:  /* Retrieve source filter addresses */
      default:
        ret = -ENOTTY;
        break;
    }

  return ret;
}
#endif

/****************************************************************************
 * Name: ioctl_arpreq_parse
 *
 * Description:
 *   Parse arpreq into netdev and sockaddr.
 *
 * Input Parameters:
 *   req    The argument of the ioctl cmd
 *   dev    The pointer to get ethernet driver device structure
 *   addr   The pointer to get address in the request
 *
 * Returned Value:
 *   true on success and false on failure.
 *
 ****************************************************************************/

#ifdef CONFIG_NET_ARP
static bool ioctl_arpreq_parse(FAR struct arpreq *req,
                               FAR struct net_driver_s **dev,
                               FAR struct sockaddr_in **addr)
{
  if (req != NULL)
    {
      *addr = (FAR struct sockaddr_in *)&req->arp_pa;
      *dev  = req->arp_dev[0] != '\0' ?
              netdev_findbyname(req->arp_dev) :
              netdev_findby_ripv4addr(INADDR_ANY, (*addr)->sin_addr.s_addr);
      return true;
    }

  return false;
}
#endif

/****************************************************************************
 * Name: netdev_arp_ioctl
 *
 * Description:
 *   Perform ARP table specific operations.
 *
 * Input Parameters:
 *   psock  Socket structure
 *   dev    Ethernet driver device structure
 *   cmd    The ioctl command
 *   req    The argument of the ioctl cmd
 *
 * Returned Value:
 *   >=0 on success (positive non-zero values are cmd-specific)
 *   Negated errno returned on failure.
 *
 ****************************************************************************/

#ifdef CONFIG_NET_ARP
static int netdev_arp_ioctl(FAR struct socket *psock, int cmd,
                            FAR struct arpreq *req)
{
  FAR struct net_driver_s *dev;
  FAR struct sockaddr_in  *addr;
  int ret;

  /* Execute the command */

  switch (cmd)
    {
      case SIOCSARP:  /* Set an ARP mapping */
        {
          if (ioctl_arpreq_parse(req, &dev, &addr) && dev != NULL &&
              req->arp_pa.sa_family == AF_INET &&
              req->arp_ha.sa_family == ARPHRD_ETHER)
            {
              /* Update any existing ARP table entry for this protocol
               * address -OR- add a new ARP table entry if there is not.
               */

              ret = arp_update(dev, addr->sin_addr.s_addr,
                               (FAR const uint8_t *)req->arp_ha.sa_data,
                               req->arp_flags);
            }
          else
            {
              ret = -EINVAL;
            }
        }
        break;

      case SIOCDARP:  /* Delete an ARP mapping */
        {
          if (ioctl_arpreq_parse(req, &dev, &addr) && dev != NULL &&
              req->arp_pa.sa_family == AF_INET)
            {
              /* Delete the ARP entry for this protocol address. */

              ret = arp_delete(addr->sin_addr.s_addr, dev);
            }
          else
            {
              ret = -EINVAL;
            }
        }
        break;

      case SIOCGARP:  /* Get an ARP mapping */
        {
          if (ioctl_arpreq_parse(req, &dev, &addr) &&
              req->arp_pa.sa_family == AF_INET)
            {
              ret = arp_find(addr->sin_addr.s_addr,
                            (FAR uint8_t *)req->arp_ha.sa_data, dev);
              if (ret >= 0)
                {
                  /* Return the mapped hardware address. */

                  req->arp_ha.sa_family = ARPHRD_ETHER;
                  ret = OK;
                }
            }
          else
            {
              ret = -EINVAL;
            }
        }
        break;

      default:
        ret = -ENOTTY;
        break;
    }

  return ret;
}
#endif

/****************************************************************************
 * Name: netdev_rt_ioctl
 *
 * Description:
 *   Perform routing table specific operations.
 *
 * Input Parameters:
 *   psock    Socket structure
 *   dev      Ethernet driver device structure
 *   cmd      The ioctl command
 *   rtentry  The argument of the ioctl cmd
 *
 * Returned Value:
 *   >=0 on success (positive non-zero values are cmd-specific)
 *   Negated errno returned on failure.
 *
 ****************************************************************************/

#ifdef CONFIG_NET_ROUTE
static int netdev_rt_ioctl(FAR struct socket *psock, int cmd,
                           FAR struct rtentry *rtentry)
{
  int ret = -EAFNOSUPPORT;

  /* Execute the command */

  switch (cmd)
    {
      case SIOCADDRT:  /* Add an entry to the routing table */
        {
          /* The target address and the netmask are required values */

          if (rtentry == NULL)
            {
              return -EINVAL;
            }

#ifdef CONFIG_NET_IPv4
#ifdef CONFIG_NET_IPv6
          if (rtentry->rt_dst.ss_family == AF_INET)
#endif
            {
#ifdef HAVE_WRITABLE_IPv4ROUTE
              ret = ioctl_add_ipv4route(rtentry);
#else
              ret = -EACCES;
#endif
            }
#endif /* CONFIG_NET_IPv4 */

#ifdef CONFIG_NET_IPv6
#ifdef CONFIG_NET_IPv4
          else
#endif
            {
#ifdef HAVE_WRITABLE_IPv6ROUTE
              ret = ioctl_add_ipv6route(rtentry);
#else
              ret = -EACCES;
#endif
            }
#endif /* CONFIG_NET_IPv6 */
        }
        break;

      case SIOCDELRT:  /* Delete an entry from the routing table */
        {
          /* The target address and the netmask are required values */

          if (rtentry == NULL)
            {
              return -EINVAL;
            }

#ifdef CONFIG_NET_IPv4
#ifdef CONFIG_NET_IPv6
          if (rtentry->rt_dst.ss_family == AF_INET)
#endif
            {
#ifdef HAVE_WRITABLE_IPv4ROUTE
              ret = ioctl_del_ipv4route(rtentry);
#else
              ret = -EACCES;
#endif
            }
#endif /* CONFIG_NET_IPv4 */

#ifdef CONFIG_NET_IPv6
#ifdef CONFIG_NET_IPv4
          else
#endif
            {
#ifdef HAVE_WRITABLE_IPv6ROUTE
              ret = ioctl_del_ipv6route(rtentry);
#else
              ret = -EACCES;
#endif
            }
#endif /* CONFIG_NET_IPv6 */
        }
        break;

      default:
        ret = -ENOTTY;
        break;
    }

  return ret;
}
#endif

/****************************************************************************
 * Name: netdev_ioctl
 *
 * Description:
 *   Perform user private ioctl operations.
 *
 * Parameters:
 *   psock    Socket structure
 *   cmd      The ioctl command
 *   arg      The argument of the ioctl cmd
 *
 * Return:
 *   >=0 on success (positive non-zero values are cmd-specific)
 *   Negated errno returned on failure.
 *
 ****************************************************************************/

static int netdev_ioctl(FAR struct socket *psock, int cmd,
                        unsigned long arg)
{
  int ret = -ENOTTY;

  if (psock->s_sockif && psock->s_sockif->si_ioctl)
    {
      ret = psock->s_sockif->si_ioctl(psock, cmd, arg);
    }

  if (ret != OK && ret != -ENOTTY)
    {
      return ret;
    }

  switch (cmd)
    {
      case FIONBIO:
        {
          FAR struct socket_conn_s *conn = psock->s_conn;
          FAR int *nonblock = (FAR int *)(uintptr_t)arg;
          sockcaps_t sockcaps;

          /* Non-blocking is the only configurable option.  And it applies
           * only Unix domain sockets and to read operations on TCP/IP
           * and UDP/IP sockets when read-ahead is enabled.
           */

          DEBUGASSERT(psock->s_sockif != NULL &&
                      psock->s_sockif->si_sockcaps != NULL);
          sockcaps = psock->s_sockif->si_sockcaps(psock);

          if ((sockcaps & SOCKCAP_NONBLOCKING) != 0)
            {
              if (nonblock && *nonblock)
                {
                  conn->s_flags |= _SF_NONBLOCK;
                }
              else
                {
                  conn->s_flags &= ~_SF_NONBLOCK;
                }

              ret = OK;
            }
          else
            {
              nerr("ERROR: Non-blocking not supported for this socket\n");
              ret = -ENOSYS;
            }
        }

        break;

      case FIOC_FILEPATH:
        if (ret == -ENOTTY)
          {
            snprintf((FAR char *)(uintptr_t)arg, PATH_MAX, "socket:["
                     "domain %" PRIu8 ", type %" PRIu8 ", proto %" PRIu8 "]",
                     psock->s_domain, psock->s_type, psock->s_proto);
            ret = OK;
          }

        break;

    default:
        break;
    }

  return ret;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: net_ioctl_arglen
 *
 * Description:
 *   Calculate the ioctl argument buffer length.
 *
 * Input Parameters:
 *   domain   The socket domain
 *   cmd      The ioctl command
 *
 * Returned Value:
 *   The argument buffer length, or error code.
 *
 ****************************************************************************/

ssize_t net_ioctl_arglen(uint8_t domain, int cmd)
{
  ssize_t arglen;

  arglen = net_ioctl_ifreq_arglen(domain, cmd);
  if (arglen > 0)
    {
      return arglen;
    }

  switch (cmd)
    {
      case FIONBIO:
      case FIONSPACE:
      case FIONREAD:
        return sizeof(int);

      case FIOC_FILEPATH:
        return PATH_MAX;

      case SIOCGIFCONF:
        return sizeof(struct ifconf);

      case SIOCGLIFCONF:
        return sizeof(struct lifconf);

      case SIOCGIPMSFILTER:
      case SIOCSIPMSFILTER:
        return sizeof(struct ip_msfilter);

      case SIOCSARP:
      case SIOCDARP:
      case SIOCGARP:
        return sizeof(struct arpreq);

      case SIOCADDRT:
      case SIOCDELRT:
        return sizeof(struct rtentry);

      case SIOCDENYINETSOCK:
        return sizeof(uint8_t);

      default:
#ifdef CONFIG_NETDEV_IOCTL
#  ifdef CONFIG_NETDEV_WIRELESS_IOCTL
        if (_WLIOCVALID(cmd))
          {
            return sizeof(struct iwreq);
          }
#  endif

#  ifdef CONFIG_WIRELESS_IEEE802154
        if (_MAC802154IOCVALID(cmd))
          {
            return sizeof(struct ieee802154_netmac_s);
          }
#  endif

#  ifdef CONFIG_WIRELESS_PKTRADIO
        if (_PKRADIOIOCVALID(cmd))
          {
            return sizeof(struct pktradio_ifreq_s);
          }
#  endif

#  ifdef CONFIG_WIRELESS_BLUETOOTH
        if (_BLUETOOTHIOCVALID(cmd))
          {
            return sizeof(struct btreq_s);
          }
#  endif

#  ifdef CONFIG_NETDEV_MODEM_LTE_IOCTL
        if (_LTEIOCVALID(cmd))
          {
            switch (cmd)
              {
                case SIOCLTECMD:
                  return sizeof(struct lte_ioctl_data_s);

                default:
                  return sizeof(struct lte_smsreq_s);
              }
          }
#  endif
#endif

        return -ENOTTY;
    }
}

/****************************************************************************
 * Name: psock_ioctl and psock_vioctl
 *
 * Description:
 *   Perform network device specific operations.
 *
 * Input Parameters:
 *   psock    A pointer to a NuttX-specific, internal socket structure
 *   cmd      The ioctl command
 *   arg      The argument of the ioctl cmd
 *
 * Returned Value:
 *   A non-negative value is returned on success; a negated errno value is
 *   returned on any failure to indicate the nature of the failure:
 *
 *   EBADF
 *     'psock' is not a valid, connected socket structure.
 *   EFAULT
 *     'arg' references an inaccessible memory area.
 *   ENOTTY
 *     'cmd' not valid.
 *   EINVAL
 *     'arg' is not valid.
 *   ENOTTY
 *     'sockfd' is not associated with a network device.
 *   ENOTTY
 *      The specified request does not apply to the kind of object that the
 *      descriptor 'sockfd' references.
 *
 ****************************************************************************/

int psock_vioctl(FAR struct socket *psock, int cmd, va_list ap)
{
  unsigned long arg;
  int ret;

  /* Verify that the psock corresponds to valid, allocated socket */

  if (psock == NULL || psock->s_conn == NULL)
    {
      return -EBADF;
    }

  arg = va_arg(ap, unsigned long);

  /* Check for socket specific ioctl command */

  ret = netdev_ioctl(psock, cmd, arg);

  /* Check for a standard network IOCTL command. */

  if (ret == -ENOTTY)
    {
      ret = netdev_ifr_ioctl(psock, cmd, (FAR struct ifreq *)(uintptr_t)arg);
    }

#if defined(CONFIG_NETDEV_IOCTL) && defined(CONFIG_NETDEV_WIRELESS_IOCTL)
  /* Check for a wireless network command */

  if (ret == -ENOTTY)
    {
      FAR struct iwreq *wifrreq;

      wifrreq = (FAR struct iwreq *)((uintptr_t)arg);
      ret     = netdev_wifr_ioctl(psock, cmd, wifrreq);
    }
#endif

#if defined(CONFIG_NETDEV_IOCTL) && defined(CONFIG_NET_CELLULAR)
  /* Check for a cellular network command */

  if (ret == -ENOTTY)
    {
      ret = netdev_cell_ioctl(psock, cmd,
                              (FAR struct icellreq *)(uintptr_t)arg);
    }
#endif

#if defined(CONFIG_NETDEV_IOCTL) && defined(CONFIG_NET_VLAN)
  /* Check for a VLAN command */

  if (ret == -ENOTTY)
    {
      ret = netdev_vlan_ioctl(psock, cmd,
                              (FAR struct vlan_ioctl_args *)(uintptr_t)arg);
    }
#endif

#ifdef HAVE_IEEE802154_IOCTL
  /* Check for a IEEE802.15.4 network device IOCTL command */

  if (ret == -ENOTTY)
    {
      ret = netdev_iee802154_ioctl(psock, cmd, arg);
    }
#endif

#ifdef HAVE_PKTRADIO_IOCTL
  /* Check for a non-IEEE802.15.4 packet radio network device IOCTL command */

  if (ret == -ENOTTY)
    {
      ret = netdev_pktradio_ioctl(psock, cmd, arg);
    }
#endif

#ifdef HAVE_BLUETOOTH_IOCTL
  /* Check for Bluetooth network device IOCTL command */

  if (ret == -ENOTTY)
    {
      ret = netdev_bluetooth_ioctl(psock, cmd, arg);
    }
#endif

#ifdef CONFIG_NET_IGMP
  /* Check for address filtering commands */

  if (ret == -ENOTTY)
    {
      ret = netdev_imsf_ioctl(psock, cmd,
                              (FAR struct ip_msfilter *)(uintptr_t)arg);
    }
#endif

#ifdef CONFIG_NET_ARP
  /* Check for ARP table IOCTL commands */

  if (ret == -ENOTTY)
    {
      ret = netdev_arp_ioctl(psock, cmd,
                             (FAR struct arpreq *)(uintptr_t)arg);
    }
#endif

#ifdef CONFIG_NET_ROUTE
  /* Check for Routing table IOCTL commands */

  if (ret == -ENOTTY)
    {
      ret = netdev_rt_ioctl(psock, cmd,
                            (FAR struct rtentry *)(uintptr_t)arg);
    }
#endif

  return ret;
}

int psock_ioctl(FAR struct socket *psock, int cmd, ...)
{
  va_list ap;
  int ret;

  /* Setup to access the variable argument list */

  va_start(ap, cmd);

  /* Let psock_vfcntl() do the real work.  The errno is not set on
   * failures.
   */

  ret = psock_vioctl(psock, cmd, ap);

  va_end(ap);
  return ret;
}

/****************************************************************************
 * Name: netdev_ifup / netdev_ifdown
 *
 * Description:
 *   Bring the interface up/down
 *
 ****************************************************************************/

int netdev_ifup(FAR struct net_driver_s *dev)
{
  int ret = -ENOSYS;

  /* Make sure that the device supports the d_ifup() method */

  if (dev->d_ifup != NULL)
    {
      /* Is the interface already up? */

      if ((dev->d_flags & IFF_UP) == 0)
        {
          /* No, bring the interface up now */

          if ((ret = dev->d_ifup(dev)) == OK)
            {
              /* Mark the interface as up */

              dev->d_flags |= IFF_UP;

              /* Update the driver status */

              netlink_device_notify(dev);
            }
        }
      else
        {
          ret = OK;
        }
    }

  return ret;
}

int netdev_ifdown(FAR struct net_driver_s *dev)
{
  int ret = -ENOSYS;

  /* Check sure that the device supports the d_ifdown() method */

  if (dev->d_ifdown != NULL)
    {
      /* Is the interface already down? */

      if ((dev->d_flags & IFF_UP) != 0)
        {
          /* No, take the interface down now */

          if ((ret = dev->d_ifdown(dev)) == OK)
            {
              /* Mark the interface as down */

              dev->d_flags &= ~(IFF_UP | IFF_RUNNING);

              /* Update the driver status */

              netlink_device_notify(dev);

              /* Notify clients that the network has been taken down */

              devif_dev_event(dev, NETDEV_DOWN);

#ifdef CONFIG_NETDOWN_NOTIFIER
              /* Provide signal notifications to threads that want to be
               * notified of the network down state via signal.
               */

              netdown_notifier_signal(dev);
#endif
            }
        }
      else
        {
          ret = OK;
        }
    }

  return ret;
}