net/netdev: Support managing multiple IPv6 addresses by ioctl

1. Supporting `SIOCSIFADDR` and `SIOCDIFADDR` with Linux in6_ifreq struct to manage ipv6 addresses.
   Ref: https://man7.org/linux/man-pages/man7/netdevice.7.html
2. Supporting alias like 'eth0:0' for multiple IPv6 addresses, to keep previous ioctl `SIOCGLIFADDR`, `SIOCSLIFADDR`, `SIOCGLIFNETMASK` and `SIOCSLIFNETMASK` working.
   Ref: https://man7.org/linux/man-pages/man8/ifconfig.8.html

Signed-off-by: Zhe Weng <wengzhe@xiaomi.com>
This commit is contained in:
Zhe Weng 2023-10-20 17:08:10 +08:00 committed by Xiang Xiao
parent 7f421a46ca
commit 3e4d847f42
6 changed files with 178 additions and 70 deletions

View file

@ -1057,7 +1057,7 @@ static int net_rpmsg_drv_ioctl(FAR struct net_driver_s *dev, int cmd,
ssize_t len;
int ret;
len = net_ioctl_arglen(cmd);
len = net_ioctl_arglen(PF_RPMSG, cmd);
if (len >= 0)
{
FAR struct net_rpmsg_ioctl_s *msg;

View file

@ -335,6 +335,13 @@ struct in6_pktinfo
int ipi6_ifindex; /* send/recv interface index */
};
struct in6_ifreq
{
struct in6_addr ifr6_addr; /* The IPv6 address of the request */
uint32_t ifr6_prefixlen; /* The IPv6 prefix length */
int ifr6_ifindex; /* The interface index of the request */
};
/****************************************************************************
* Public Data
****************************************************************************/

View file

@ -301,7 +301,7 @@ void net_initialize(void);
* Calculate the ioctl argument buffer length.
*
* Input Parameters:
*
* domain The socket domain
* cmd The ioctl command
*
* Returned Value:
@ -309,7 +309,7 @@ void net_initialize(void);
*
****************************************************************************/
ssize_t net_ioctl_arglen(int cmd);
ssize_t net_ioctl_arglen(uint8_t domain, int cmd);
/****************************************************************************
* Critical section management.

View file

@ -26,7 +26,9 @@
#include <string.h>
#include <assert.h>
#include <debug.h>
#include <errno.h>
#include <stdio.h>
#include <net/if.h>
#include <nuttx/net/netdev.h>
@ -139,6 +141,77 @@ static int ifconf_ipv4_callback(FAR struct net_driver_s *dev, FAR void *arg)
}
#endif
/****************************************************************************
* Name: ifconf_ipv6_addr_callback
*
* Description:
* Callback from netdev_ipv6_foreach() that does the real implementation of
* netdev_ipv6_ifconf().
*
* Input Parameters:
* dev - The network device for this callback.
* addr - The IPv6 address.
* arg - User callback argument
*
* Returned Value:
* Zero is returned on success; a negated errno value is returned on any
* failure.
*
****************************************************************************/
#ifdef CONFIG_NET_IPv6
static int ifconf_ipv6_addr_callback(FAR struct net_driver_s *dev,
FAR struct netdev_ifaddr6_s *addr,
FAR void *arg)
{
FAR struct ifconf_ipv6_info_s *info = (FAR struct ifconf_ipv6_info_s *)arg;
FAR struct lifconf *lifc = info->lifc;
if (lifc->lifc_len + sizeof(struct lifreq) <= info->bufsize)
{
FAR struct lifreq *req =
(FAR struct lifreq *)&lifc->lifc_buf[lifc->lifc_len];
FAR struct sockaddr_in6 *inaddr =
(FAR struct sockaddr_in6 *)&req->lifr_addr;
#ifdef CONFIG_NETDEV_MULTIPLE_IPv6
int addr_idx = addr - dev->d_ipv6;
/* There is space for information about another adapter. Within
* each ifreq structure, lifr_name will receive the interface
* name and lifr_addr the address. The actual number of bytes
* transferred is returned in lifc_len.
*/
if (addr_idx > 0)
{
/* eth0:0 represents the second addr on eth0 */
if (snprintf(req->lifr_name, IFNAMSIZ,
"%s:%d", dev->d_ifname, addr_idx - 1) >= IFNAMSIZ)
{
nwarn("WARNING: ifname too long to print %s:%d\n",
dev->d_ifname, addr_idx - 1);
}
}
else
#endif
{
strlcpy(req->lifr_name, dev->d_ifname, IFNAMSIZ);
}
inaddr->sin6_family = AF_INET6;
inaddr->sin6_port = 0;
net_ipv6addr_copy(inaddr->sin6_addr.s6_addr16, addr->addr);
}
/* Increment the size of the buffer in any event */
lifc->lifc_len += sizeof(struct lifreq);
return OK;
}
#endif
/****************************************************************************
* Name: ifconf_ipv6_callback
*
@ -160,10 +233,8 @@ static int ifconf_ipv4_callback(FAR struct net_driver_s *dev, FAR void *arg)
static int ifconf_ipv6_callback(FAR struct net_driver_s *dev, FAR void *arg)
{
FAR struct ifconf_ipv6_info_s *info = (FAR struct ifconf_ipv6_info_s *)arg;
FAR struct lifconf *lifc;
DEBUGASSERT(dev != NULL && info != NULL && info->lifc != NULL);
lifc = info->lifc;
/* Check if this adapter has an IPv6 address assigned and is in the UP
* state.
@ -186,29 +257,7 @@ static int ifconf_ipv6_callback(FAR struct net_driver_s *dev, FAR void *arg)
* cases.
*/
if (lifc->lifc_len + sizeof(struct lifreq) <= info->bufsize)
{
FAR struct lifreq *req =
(FAR struct lifreq *)&lifc->lifc_buf[lifc->lifc_len];
FAR struct sockaddr_in6 *inaddr =
(FAR struct sockaddr_in6 *)&req->lifr_addr;
/* There is space for information about another adapter. Within
* each ifreq structure, lifr_name will receive the interface
* name and lifr_addr the address. The actual number of bytes
* transferred is returned in lifc_len.
*/
strlcpy(req->lifr_name, dev->d_ifname, IFNAMSIZ);
inaddr->sin6_family = AF_INET6;
inaddr->sin6_port = 0;
net_ipv6addr_copy(inaddr->sin6_addr.s6_addr16, dev->d_ipv6addr);
}
/* Increment the size of the buffer in any event */
lifc->lifc_len += sizeof(struct lifreq);
return netdev_ipv6_foreach(dev, ifconf_ipv6_addr_callback, arg);
}
return 0;

View file

@ -652,31 +652,32 @@ static int netdev_wifr_ioctl(FAR struct socket *psock, int cmd,
#endif
/****************************************************************************
* Name: netdev_ifr_dev
* Name: netdev_ifr_split_idx
*
* Description:
* Verify the struct ifreq and get the Ethernet device.
* Split the address index from device name like 'eth0:0'.
*
* Input Parameters:
* req - The argument of the ioctl cmd
*
* Returned Value:
* A pointer to the driver structure on success; NULL on failure.
* The address index from device name.
*
****************************************************************************/
static FAR struct net_driver_s *netdev_ifr_dev(FAR struct ifreq *req)
static unsigned int netdev_ifr_split_idx(FAR struct ifreq *req)
{
if (req != NULL)
{
/* Find the network device associated with the device name
* in the request data.
*/
FAR char *colon = strchr(req->ifr_name, ':');
int idx;
return netdev_findbyname(req->ifr_name);
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 NULL;
return 0;
}
/****************************************************************************
@ -686,7 +687,7 @@ static FAR struct net_driver_s *netdev_ifr_dev(FAR struct ifreq *req)
* Calculate the ioctl argument buffer length of ifreq.
*
* Input Parameters:
*
* domain The socket domain
* cmd The ioctl command
*
* Returned Value:
@ -694,12 +695,11 @@ static FAR struct net_driver_s *netdev_ifr_dev(FAR struct ifreq *req)
*
****************************************************************************/
static ssize_t net_ioctl_ifreq_arglen(int cmd)
static ssize_t net_ioctl_ifreq_arglen(uint8_t domain, int cmd)
{
switch (cmd)
{
case SIOCGIFADDR:
case SIOCSIFADDR:
case SIOCGIFDSTADDR:
case SIOCSIFDSTADDR:
case SIOCGIFBRDADDR:
@ -710,7 +710,6 @@ static ssize_t net_ioctl_ifreq_arglen(int cmd)
case SIOCGIFMTU:
case SIOCGIFHWADDR:
case SIOCSIFHWADDR:
case SIOCDIFADDR:
case SIOCGIFCOUNT:
case SIOCSIFFLAGS:
case SIOCGIFFLAGS:
@ -730,6 +729,11 @@ static ssize_t net_ioctl_ifreq_arglen(int cmd)
case SIOCGIFINDEX:
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:
@ -769,6 +773,7 @@ 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);
@ -827,15 +832,27 @@ static int netdev_ifr_ioctl(FAR struct socket *psock, int cmd,
break;
#endif
default:
if (net_ioctl_ifreq_arglen(cmd) > 0)
if (req == NULL)
{
dev = netdev_ifr_dev(req);
if (dev == NULL)
{
ret = -ENODEV;
}
net_unlock();
return -ENOTTY;
}
else
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;
}
@ -857,12 +874,6 @@ static int netdev_ifr_ioctl(FAR struct socket *psock, int cmd,
ioctl_get_ipv4addr(&req->ifr_addr, dev->d_ipaddr);
break;
case SIOCSIFADDR: /* Set IP address */
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));
break;
case SIOCGIFDSTADDR: /* Get P-to-P address */
ioctl_get_ipv4addr(&req->ifr_dstaddr, dev->d_draddr);
break;
@ -893,16 +904,18 @@ static int netdev_ifr_ioctl(FAR struct socket *psock, int cmd,
case SIOCGLIFADDR: /* Get IP address */
{
FAR struct lifreq *lreq = (FAR struct lifreq *)req;
ioctl_get_ipv6addr(&lreq->lifr_addr, dev->d_ipv6addr);
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;
ioctl_set_ipv6addr(dev->d_ipv6addr, &lreq->lifr_addr);
idx = MIN(idx, CONFIG_NETDEV_MAX_IPv6_ADDR - 1);
ioctl_set_ipv6addr(dev->d_ipv6[idx].addr, &lreq->lifr_addr);
netlink_device_notify_ipaddr(dev, RTM_NEWADDR, AF_INET6,
dev->d_ipv6addr, net_ipv6_mask2pref(dev->d_ipv6netmask));
dev->d_ipv6[idx].addr, net_ipv6_mask2pref(dev->d_ipv6[idx].mask));
}
break;
@ -928,14 +941,16 @@ static int netdev_ifr_ioctl(FAR struct socket *psock, int cmd,
case SIOCGLIFNETMASK: /* Get network mask */
{
FAR struct lifreq *lreq = (FAR struct lifreq *)req;
ioctl_get_ipv6addr(&lreq->lifr_addr, dev->d_ipv6netmask);
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;
ioctl_set_ipv6addr(dev->d_ipv6netmask, &lreq->lifr_addr);
idx = MIN(idx, CONFIG_NETDEV_MAX_IPv6_ADDR - 1);
ioctl_set_ipv6addr(dev->d_ipv6[idx].mask, &lreq->lifr_addr);
}
break;
#endif
@ -1057,16 +1072,53 @@ static int netdev_ifr_ioctl(FAR struct socket *psock, int cmd,
break;
#endif
case SIOCSIFADDR: /* Set IP address */
#ifdef CONFIG_NET_IPv4
if (psock->s_domain != PF_INET6)
{
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));
}
#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
netlink_device_notify_ipaddr(dev, RTM_DELADDR, AF_INET,
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;
dev->d_ipaddr = 0;
}
#endif
#ifdef CONFIG_NET_IPv6
netlink_device_notify_ipaddr(dev, RTM_DELADDR, AF_INET6,
dev->d_ipv6addr, net_ipv6_mask2pref(dev->d_ipv6netmask));
memset(&dev->d_ipv6addr, 0, sizeof(net_ipv6addr_t));
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;
@ -1589,7 +1641,7 @@ static int netdev_ioctl(FAR struct socket *psock, int cmd,
* Calculate the ioctl argument buffer length.
*
* Input Parameters:
*
* domain The socket domain
* cmd The ioctl command
*
* Returned Value:
@ -1597,11 +1649,11 @@ static int netdev_ioctl(FAR struct socket *psock, int cmd,
*
****************************************************************************/
ssize_t net_ioctl_arglen(int cmd)
ssize_t net_ioctl_arglen(uint8_t domain, int cmd)
{
ssize_t arglen;
arglen = net_ioctl_ifreq_arglen(cmd);
arglen = net_ioctl_ifreq_arglen(domain, cmd);
if (arglen > 0)
{
return arglen;

View file

@ -183,7 +183,7 @@ int usrsock_ioctl(FAR struct socket *psock, int cmd, unsigned long arg_)
return -ENOTTY;
}
arglen = net_ioctl_arglen(cmd);
arglen = net_ioctl_arglen(psock->s_domain, cmd);
if (arglen < 0)
{
return arglen;