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; ssize_t len;
int ret; int ret;
len = net_ioctl_arglen(cmd); len = net_ioctl_arglen(PF_RPMSG, cmd);
if (len >= 0) if (len >= 0)
{ {
FAR struct net_rpmsg_ioctl_s *msg; FAR struct net_rpmsg_ioctl_s *msg;

View File

@ -335,6 +335,13 @@ struct in6_pktinfo
int ipi6_ifindex; /* send/recv interface index */ 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 * Public Data
****************************************************************************/ ****************************************************************************/

View File

@ -301,7 +301,7 @@ void net_initialize(void);
* Calculate the ioctl argument buffer length. * Calculate the ioctl argument buffer length.
* *
* Input Parameters: * Input Parameters:
* * domain The socket domain
* cmd The ioctl command * cmd The ioctl command
* *
* Returned Value: * 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. * Critical section management.

View File

@ -26,7 +26,9 @@
#include <string.h> #include <string.h>
#include <assert.h> #include <assert.h>
#include <debug.h>
#include <errno.h> #include <errno.h>
#include <stdio.h>
#include <net/if.h> #include <net/if.h>
#include <nuttx/net/netdev.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 #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 * 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) 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 ifconf_ipv6_info_s *info = (FAR struct ifconf_ipv6_info_s *)arg;
FAR struct lifconf *lifc;
DEBUGASSERT(dev != NULL && info != NULL && info->lifc != NULL); 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 /* Check if this adapter has an IPv6 address assigned and is in the UP
* state. * state.
@ -186,29 +257,7 @@ static int ifconf_ipv6_callback(FAR struct net_driver_s *dev, FAR void *arg)
* cases. * cases.
*/ */
if (lifc->lifc_len + sizeof(struct lifreq) <= info->bufsize) return netdev_ipv6_foreach(dev, ifconf_ipv6_addr_callback, arg);
{
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 0; return 0;

View File

@ -652,31 +652,32 @@ static int netdev_wifr_ioctl(FAR struct socket *psock, int cmd,
#endif #endif
/**************************************************************************** /****************************************************************************
* Name: netdev_ifr_dev * Name: netdev_ifr_split_idx
* *
* Description: * Description:
* Verify the struct ifreq and get the Ethernet device. * Split the address index from device name like 'eth0:0'.
* *
* Input Parameters: * Input Parameters:
* req - The argument of the ioctl cmd * req - The argument of the ioctl cmd
* *
* Returned Value: * 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) FAR char *colon = strchr(req->ifr_name, ':');
{ int idx;
/* Find the network device associated with the device name
* in the request data.
*/
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. * Calculate the ioctl argument buffer length of ifreq.
* *
* Input Parameters: * Input Parameters:
* * domain The socket domain
* cmd The ioctl command * cmd The ioctl command
* *
* Returned Value: * 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) switch (cmd)
{ {
case SIOCGIFADDR: case SIOCGIFADDR:
case SIOCSIFADDR:
case SIOCGIFDSTADDR: case SIOCGIFDSTADDR:
case SIOCSIFDSTADDR: case SIOCSIFDSTADDR:
case SIOCGIFBRDADDR: case SIOCGIFBRDADDR:
@ -710,7 +710,6 @@ static ssize_t net_ioctl_ifreq_arglen(int cmd)
case SIOCGIFMTU: case SIOCGIFMTU:
case SIOCGIFHWADDR: case SIOCGIFHWADDR:
case SIOCSIFHWADDR: case SIOCSIFHWADDR:
case SIOCDIFADDR:
case SIOCGIFCOUNT: case SIOCGIFCOUNT:
case SIOCSIFFLAGS: case SIOCSIFFLAGS:
case SIOCGIFFLAGS: case SIOCGIFFLAGS:
@ -730,6 +729,11 @@ static ssize_t net_ioctl_ifreq_arglen(int cmd)
case SIOCGIFINDEX: case SIOCGIFINDEX:
return sizeof(struct ifreq); return sizeof(struct ifreq);
case SIOCSIFADDR:
case SIOCDIFADDR:
return domain == PF_INET6 ?
sizeof(struct in6_ifreq) : sizeof(struct ifreq);
case SIOCGLIFADDR: case SIOCGLIFADDR:
case SIOCSLIFADDR: case SIOCSLIFADDR:
case SIOCGLIFDSTADDR: case SIOCGLIFDSTADDR:
@ -769,6 +773,7 @@ static int netdev_ifr_ioctl(FAR struct socket *psock, int cmd,
FAR struct ifreq *req) FAR struct ifreq *req)
{ {
FAR struct net_driver_s *dev = NULL; FAR struct net_driver_s *dev = NULL;
unsigned int idx = 0;
int ret = OK; int ret = OK;
ninfo("cmd: %d\n", cmd); ninfo("cmd: %d\n", cmd);
@ -827,15 +832,27 @@ static int netdev_ifr_ioctl(FAR struct socket *psock, int cmd,
break; break;
#endif #endif
default: default:
if (net_ioctl_ifreq_arglen(cmd) > 0) if (req == NULL)
{ {
dev = netdev_ifr_dev(req); net_unlock();
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) if (dev == NULL)
{
ret = -ENODEV;
}
}
else
{ {
ret = -ENOTTY; 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); ioctl_get_ipv4addr(&req->ifr_addr, dev->d_ipaddr);
break; 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 */ case SIOCGIFDSTADDR: /* Get P-to-P address */
ioctl_get_ipv4addr(&req->ifr_dstaddr, dev->d_draddr); ioctl_get_ipv4addr(&req->ifr_dstaddr, dev->d_draddr);
break; break;
@ -893,16 +904,18 @@ static int netdev_ifr_ioctl(FAR struct socket *psock, int cmd,
case SIOCGLIFADDR: /* Get IP address */ case SIOCGLIFADDR: /* Get IP address */
{ {
FAR struct lifreq *lreq = (FAR struct lifreq *)req; 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; break;
case SIOCSLIFADDR: /* Set IP address */ case SIOCSLIFADDR: /* Set IP address */
{ {
FAR struct lifreq *lreq = (FAR struct lifreq *)req; 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, 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; break;
@ -928,14 +941,16 @@ static int netdev_ifr_ioctl(FAR struct socket *psock, int cmd,
case SIOCGLIFNETMASK: /* Get network mask */ case SIOCGLIFNETMASK: /* Get network mask */
{ {
FAR struct lifreq *lreq = (FAR struct lifreq *)req; 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; break;
case SIOCSLIFNETMASK: /* Set network mask */ case SIOCSLIFNETMASK: /* Set network mask */
{ {
FAR struct lifreq *lreq = (FAR struct lifreq *)req; 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; break;
#endif #endif
@ -1057,16 +1072,53 @@ static int netdev_ifr_ioctl(FAR struct socket *psock, int cmd,
break; break;
#endif #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 */ case SIOCDIFADDR: /* Delete IP address */
#ifdef CONFIG_NET_IPv4 #ifdef CONFIG_NET_IPv4
if (psock->s_domain != PF_INET6)
{
netlink_device_notify_ipaddr(dev, RTM_DELADDR, AF_INET, netlink_device_notify_ipaddr(dev, RTM_DELADDR, AF_INET,
&dev->d_ipaddr, net_ipv4_mask2pref(dev->d_netmask)); &dev->d_ipaddr, net_ipv4_mask2pref(dev->d_netmask));
dev->d_ipaddr = 0; dev->d_ipaddr = 0;
}
#endif #endif
#ifdef CONFIG_NET_IPv6 #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, netlink_device_notify_ipaddr(dev, RTM_DELADDR, AF_INET6,
dev->d_ipv6addr, net_ipv6_mask2pref(dev->d_ipv6netmask)); ifr6->ifr6_addr.in6_u.u6_addr16, ifr6->ifr6_prefixlen);
memset(&dev->d_ipv6addr, 0, sizeof(net_ipv6addr_t)); }
}
#endif #endif
break; break;
@ -1589,7 +1641,7 @@ static int netdev_ioctl(FAR struct socket *psock, int cmd,
* Calculate the ioctl argument buffer length. * Calculate the ioctl argument buffer length.
* *
* Input Parameters: * Input Parameters:
* * domain The socket domain
* cmd The ioctl command * cmd The ioctl command
* *
* Returned Value: * 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; ssize_t arglen;
arglen = net_ioctl_ifreq_arglen(cmd); arglen = net_ioctl_ifreq_arglen(domain, cmd);
if (arglen > 0) if (arglen > 0)
{ {
return arglen; return arglen;

View File

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