diff --git a/include/netpacket/if_addr.h b/include/netpacket/if_addr.h index a86bd4eef8..7140c36ec5 100644 --- a/include/netpacket/if_addr.h +++ b/include/netpacket/if_addr.h @@ -85,6 +85,8 @@ struct ifaddrmsg * * IFA_FLAGS is a u32 attribute that extends the u8 field ifa_flags. * If present, the value from struct ifaddrmsg will be ignored. + * + * IFA_RT_PRIORITY is a u32 attribute, priority/metric for prefix route */ enum @@ -98,6 +100,7 @@ enum IFA_CACHEINFO, IFA_MULTICAST, IFA_FLAGS, + IFA_RT_PRIORITY, __IFA_MAX, }; diff --git a/include/netpacket/netlink.h b/include/netpacket/netlink.h index 4a96d18281..e91fb6df0d 100644 --- a/include/netpacket/netlink.h +++ b/include/netpacket/netlink.h @@ -389,6 +389,25 @@ #define RTNLGRP_NSID 28 #define RTNLGRP_MAX 29 +/** + * nla_type (16 bits) + * +---+---+-------------------------------+ + * | N | O | Attribute Type | + * +---+---+-------------------------------+ + * N := Carries nested attributes + * O := Payload stored in network byte order + * + * Note: The N and O flag are mutually exclusive. + */ + +#define NLA_F_NET_BYTEORDER (1 << 14) +#define NLA_F_NESTED (1 << 15) +#define NLA_TYPE_MASK ~(NLA_F_NESTED | NLA_F_NET_BYTEORDER) + +#define NLA_ALIGNTO 4 +#define NLA_ALIGN(len) (((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1)) +#define NLA_HDRLEN (NLA_ALIGN(sizeof(struct nlattr))) + /**************************************************************************** * Public Type Definitions ****************************************************************************/ @@ -503,6 +522,41 @@ struct rtmsg uint32_t rtm_flags; }; +/** + * <------- NLA_HDRLEN ------> <-- NLA_ALIGN(payload)--> + * +---------------------+- - -+- - - - - - - - - -+- - -+ + * | Header | Pad | Payload | Pad | + * | (struct nlattr) | ing | | ing | + * +---------------------+- - -+- - - - - - - - - -+- - -+ + * <-------------- nlattr->nla_len --------------> + */ + +struct nlattr +{ + uint16_t nla_len; + uint16_t nla_type; +}; + +/* Generic 32 bitflags attribute content sent to the kernel. + * + * The value is a bitmap that defines the values being set + * The selector is a bitmask that defines which value is legit + * + * Examples: + * value = 0x0, and selector = 0x1 + * implies we are selecting bit 1 and we want to set its value to 0. + * + * value = 0x2, and selector = 0x2 + * implies we are selecting bit 2 and we want to set its value to 1. + * + */ + +struct nla_bitfield32 +{ + uint32_t value; + uint32_t selector; +}; + /**************************************************************************** * Public Function Prototypes ****************************************************************************/ diff --git a/net/netdev/netdev_ioctl.c b/net/netdev/netdev_ioctl.c index 9db10e28b0..92aae1b3b1 100644 --- a/net/netdev/netdev_ioctl.c +++ b/net/netdev/netdev_ioctl.c @@ -839,6 +839,7 @@ static int netdev_ifr_ioctl(FAR struct socket *psock, int cmd, case SIOCSIFADDR: /* Set IP address */ ioctl_set_ipv4addr(&dev->d_ipaddr, &req->ifr_addr); + netlink_device_notify_ipaddr(dev, RTM_NEWADDR, AF_INET); break; case SIOCGIFDSTADDR: /* Get P-to-P address */ @@ -879,6 +880,7 @@ static int netdev_ifr_ioctl(FAR struct socket *psock, int cmd, { FAR struct lifreq *lreq = (FAR struct lifreq *)req; ioctl_set_ipv6addr(dev->d_ipv6addr, &lreq->lifr_addr); + netlink_device_notify_ipaddr(dev, RTM_NEWADDR, AF_INET6); } break; @@ -1040,9 +1042,11 @@ static int netdev_ifr_ioctl(FAR struct socket *psock, int cmd, case SIOCDIFADDR: /* Delete IP address */ #ifdef CONFIG_NET_IPv4 dev->d_ipaddr = 0; + netlink_device_notify_ipaddr(dev, RTM_DELADDR, AF_INET); #endif #ifdef CONFIG_NET_IPv6 memset(&dev->d_ipv6addr, 0, sizeof(net_ipv6addr_t)); + netlink_device_notify_ipaddr(dev, RTM_DELADDR, AF_INET6); #endif break; diff --git a/net/netlink/Kconfig b/net/netlink/Kconfig index 4963744d59..cacf2eaf91 100644 --- a/net/netlink/Kconfig +++ b/net/netlink/Kconfig @@ -91,6 +91,31 @@ config NETLINK_DISABLE_GETROUTE ---help--- RTM_GETROUTE is used to retrieve routing tables. +config NETLINK_DISABLE_NEWADDR + bool "Disable RTM_NEWADDR support" + default n + ---help--- + RTM_NEWADDR is used to set netdev address. + +config NETLINK_DISABLE_DELADDR + bool "Disable RTM_DELADDR support" + default n + ---help--- + RTM_DELADDR is used to delete netdev address. + +config NETLINK_DISABLE_GETADDR + bool "Disable RTM_GETADDR support" + default n + ---help--- + RTM_GETADDR is used to get netdev address. + +config NETLINK_VALIDATE_POLICY + bool "Enable netlink message policy verification" + default n + ---help--- + VALIDATE_POLICY is used to make sure the parameters + you pass in are valid. + endif # NETLINK_ROUTE endmenu # Netlink Protocols endif # NET_NETLINK diff --git a/net/netlink/Make.defs b/net/netlink/Make.defs index 7ef735f984..b18597cca3 100644 --- a/net/netlink/Make.defs +++ b/net/netlink/Make.defs @@ -26,7 +26,7 @@ SOCK_CSRCS += netlink_sockif.c NET_CSRCS += netlink_conn.c netlink_notifier.c ifeq ($(CONFIG_NETLINK_ROUTE),y) -NET_CSRCS += netlink_route.c +NET_CSRCS += netlink_route.c netlink_attr.c endif # Include netlink build support diff --git a/net/netlink/netlink.h b/net/netlink/netlink.h index 4119357c80..c14514f0f2 100644 --- a/net/netlink/netlink.h +++ b/net/netlink/netlink.h @@ -44,11 +44,142 @@ ****************************************************************************/ #ifndef CONFIG_NETLINK_ROUTE - #define netlink_device_notify(dev) +# define netlink_device_notify(dev) +# define netlink_device_notify_ipaddr(dev, type, domain) #endif #ifdef CONFIG_NET_NETLINK +/** + * nla_for_each_attr - iterate over a stream of attributes + * @pos: loop counter, set to current attribute + * @head: head of attribute stream + * @len: length of attribute stream + * @rem: initialized to len, holds bytes currently remaining in stream + */ + +#define nla_for_each_attr(pos, head, len, rem) \ + for (pos = head, rem = len; nla_ok(pos, rem); \ + pos = nla_next(pos, &(rem))) + +/* Always use this macro, this allows later putting the + * message into a separate section or such for things + * like translation or listing all possible messages. + * Currently string formatting is not supported (due + * to the lack of an output buffer.) + */ + +#define nl_set_err_msg_attr(extack, attr, msg) \ + do \ + { \ + static const char __msg[] = (msg); \ + FAR struct netlink_ext_ack *__extack = (extack); \ + if (__extack) \ + { \ + __extack->_msg = __msg; \ + __extack->bad_attr = (attr); \ + } \ + } \ + while (0) + +/** + * nla_data - head of payload + * @nla: netlink attribute + */ + +#define nla_data(nla) ((FAR void *)((FAR char *)(nla) + NLA_HDRLEN)) + +/** + * nla_len - length of payload + * @nla: netlink attribute + */ + +#define nla_len(nla) ((nla)->nla_len - NLA_HDRLEN) + +/** + * nla_type - attribute type + * @nla: netlink attribute + */ + +#define nla_type(nla) ((nla)->nla_type & NLA_TYPE_MASK) + +/** + * nla_ok - check if the netlink attribute fits into the remaining bytes + * @nla: netlink attribute + * @remaining: number of bytes remaining in attribute stream + */ + +#define nla_ok(nla, remaining) \ + ((remaining) >= sizeof(*(nla)) && \ + (nla)->nla_len >= sizeof(*(nla)) && \ + (nla)->nla_len <= (remaining)) + +/** + * nlmsg_msg_size - length of netlink message not including padding + * @payload: length of message payload + */ + +#define nlmsg_msg_size(payload) (NLMSG_HDRLEN + (payload)) + +/** + * nlmsg_len - length of message payload + * @nlh: netlink message header + */ + +#define nlmsg_len(nlh) ((nlh)->nlmsg_len - NLMSG_HDRLEN) + +/** + * nlmsg_attrlen - length of attributes data + * @nlh: netlink message header + * @hdrlen: length of family specific header + */ + +#define nlmsg_attrlen(nlh, hdrlen) (nlmsg_len(nlh) - NLMSG_ALIGN(hdrlen)) + +/** + * nlmsg_data - head of message payload + * @nlh: netlink message header + */ + +#define nlmsg_data(nlh) ((FAR void *)((FAR char *)(nlh) + NLMSG_HDRLEN)) + +/** + * nla_get_in_addr - return payload of IPv4 address attribute + * @nla: IPv4 address netlink attribute + */ + +#define nla_get_in_addr(nla) (*(FAR uint32_t *)nla_data(nla)) + +/** + * nlmsg_attrdata - head of attributes data + * @nlh: netlink message header + * @hdrlen: length of family specific header + */ + +#define nlmsg_attrdata(nlh, hdrlen) \ + ((FAR struct nlattr *)((FAR char *)nlmsg_data(nlh) + NLMSG_ALIGN(hdrlen))) + +/** + * nlmsg_parse - parse attributes of a netlink message + * @nlh: netlink message header + * @hdrlen: length of family specific header + * @tb: destination array with maxtype+1 elements + * @maxtype: maximum attribute type to be expected + * @policy: validation policy + * @extack: extended ACK report struct + * + * See nla_parse() + */ + +#define nlmsg_parse(nlh, hdrlen, tb, maxtype, policy, extack) \ + ((nlh)->nlmsg_len < nlmsg_msg_size(hdrlen) ? -EINVAL : \ + nla_parse(tb, maxtype, nlmsg_attrdata(nlh, hdrlen), \ + nlmsg_attrlen(nlh, hdrlen), policy, extack)) + +/* this can be increased when necessary - don't expose to userland */ + +#define NETLINK_MAX_COOKIE_LEN 20 + /**************************************************************************** * Public Type Definitions ****************************************************************************/ @@ -79,6 +210,92 @@ struct netlink_conn_s sq_queue_t resplist; /* Singly linked list of responses */ }; +/** + * Standard attribute types to specify validation policy + */ + +enum +{ + NLA_UNSPEC, + NLA_U8, + NLA_U16, + NLA_U32, + NLA_U64, + NLA_STRING, + NLA_FLAG, + NLA_MSECS, + NLA_NESTED, + NLA_NESTED_COMPAT, + NLA_NUL_STRING, + NLA_BINARY, + NLA_S8, + NLA_S16, + NLA_S32, + NLA_S64, + NLA_BITFIELD32, + NLA_TYPE_MAX = NLA_BITFIELD32, +}; + +/** + * struct netlink_ext_ack - netlink extended ACK report struct + * @_msg: message string to report - don't access directly, use + * %nl_set_err_msg_attr + * @bad_attr: attribute with error + * @cookie: cookie data to return to userspace (for success) + * @cookie_len: actual cookie data length + */ + +struct netlink_ext_ack +{ + FAR const char *_msg; + FAR const struct nlattr *bad_attr; + uint8_t cookie[NETLINK_MAX_COOKIE_LEN]; + uint8_t cookie_len; +}; + +/** + * struct nla_policy - attribute validation policy + * @type: Type of attribute or NLA_UNSPEC + * @len: Type specific length of payload + * + * Policies are defined as arrays of this struct, the array must be + * accessible by attribute type up to the highest identifier to be expected. + * + * Meaning of `len' field: + * NLA_STRING Maximum length of string + * NLA_NUL_STRING Maximum length of string (excluding NUL) + * NLA_FLAG Unused + * NLA_BINARY Maximum length of attribute payload + * NLA_NESTED Don't use `len' field -- length verification is + * done by checking len of nested header (or empty) + * NLA_NESTED_COMPAT Minimum length of structure payload + * NLA_U8, NLA_U16, + * NLA_U32, NLA_U64, + * NLA_S8, NLA_S16, + * NLA_S32, NLA_S64, + * NLA_MSECS Leaving the length field zero will verify the + * given type fits, using it verifies minimum length + * just like "All other" + * NLA_BITFIELD32 A 32-bit bitmap/bitselector attribute + * All other Minimum length of attribute payload + * + * Example: + * static const struct nla_policy my_policy[ATTR_MAX + 1] = { + * [ATTR_FOO] = { .type = NLA_U16 }, + * [ATTR_BAR] = { .type = NLA_STRING, .len = BARSIZ }, + * [ATTR_BAZ] = { .len = sizeof(struct mystruct) }, + * [ATTR_GOO] = { .type = NLA_BITFIELD32, .validation_data = + * &myvalidflags }, + * }; + */ + +struct nla_policy +{ + uint16_t type; + uint16_t len; + FAR void *validation_data; +}; + /**************************************************************************** * Public Data ****************************************************************************/ @@ -275,6 +492,49 @@ ssize_t netlink_route_sendto(NETLINK_HANDLE handle, ****************************************************************************/ void netlink_device_notify(FAR struct net_driver_s *dev); + +/**************************************************************************** + * Name: netlink_device_notify_ipaddr() + * + * Description: + * Perform the route broadcast for the NETLINK_ROUTE protocol. + * + ****************************************************************************/ + +void netlink_device_notify_ipaddr(FAR struct net_driver_s *dev, + int type, int domain); + +/**************************************************************************** + * Name: nla_next + * + * Description: + * Next netlink attribute in attribute stream. + * + * Input Parameters: + * nla - netlink attribute. + * remaining - number of bytes remaining in attribute stream. + * + * Returned Value: + * Returns the next netlink attribute in the attribute stream and + * decrements remaining by the size of the current attribute. + * + ****************************************************************************/ + +FAR struct nlattr *nla_next(FAR const struct nlattr *nla, + FAR int *remaining); + +/**************************************************************************** + * Name: nla_parse + * + * Description: + * Parse the nested netlink attribute. + * + ****************************************************************************/ + +int nla_parse(FAR struct nlattr **tb, int maxtype, + FAR const struct nlattr *head, + int len, FAR const struct nla_policy *policy, + FAR struct netlink_ext_ack *extack); #endif #undef EXTERN diff --git a/net/netlink/netlink_attr.c b/net/netlink/netlink_attr.c new file mode 100644 index 0000000000..4d82c10b26 --- /dev/null +++ b/net/netlink/netlink_attr.c @@ -0,0 +1,332 @@ +/**************************************************************************** + * net/netlink/netlink_attr.c + * + * 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 + +#include +#include +#include +#include +#include +#include + +#include + +#include "netlink.h" + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +#ifdef CONFIG_NETLINK_VALIDATE_POLICY +static const uint8_t g_nla_attr_len[NLA_TYPE_MAX + 1] = +{ + 0, + sizeof(uint8_t), + sizeof(uint16_t), + sizeof(uint32_t), + sizeof(uint64_t), + 0, 0, 0, 0, 0, 0, 0, + sizeof(int8_t), + sizeof(int16_t), + sizeof(int32_t), + sizeof(int64_t), +}; + +static const uint8_t g_nla_attr_minlen[NLA_TYPE_MAX + 1] = +{ + 0, + sizeof(uint8_t), + sizeof(uint16_t), + sizeof(uint32_t), + sizeof(uint64_t), + 0, 0, + sizeof(uint64_t), + NLA_HDRLEN, + 0, 0, 0, + sizeof(int8_t), + sizeof(int16_t), + sizeof(int32_t), + sizeof(int64_t), +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int validate_nla_bitfield32(FAR const struct nlattr *nla, + FAR uint32_t *valid_flags_mask) +{ + FAR const struct nla_bitfield32 *bf = nla_data(nla); + + if (!valid_flags_mask) + { + return -EINVAL; + } + + /* disallow invalid bit selector */ + + if (bf->selector & ~*valid_flags_mask) + { + return -EINVAL; + } + + /* disallow invalid bit values */ + + if (bf->value & ~*valid_flags_mask) + { + return -EINVAL; + } + + /* disallow valid bit values that are not selected */ + + if (bf->value & ~bf->selector) + { + return -EINVAL; + } + + return 0; +} + +static int validate_nla(FAR const struct nlattr *nla, int maxtype, + FAR const struct nla_policy *policy) +{ + FAR const struct nla_policy *pt; + int minlen = 0; + int attrlen = nla_len(nla); + int type = nla_type(nla); + + if (type <= 0 || type > maxtype) + { + return -EINVAL; + } + + pt = &policy[type]; + + DEBUGASSERT(pt->type <= NLA_TYPE_MAX); + + if (g_nla_attr_len[pt->type] && attrlen != g_nla_attr_len[pt->type]) + { + nwarn("netlink: '%d': attribute type %d has an invalid length.\n", + getpid(), type); + return -EINVAL; + } + + switch (pt->type) + { + case NLA_FLAG: + if (attrlen > 0) + { + return -ERANGE; + } + break; + + case NLA_BITFIELD32: + if (attrlen != sizeof(struct nla_bitfield32)) + { + return -ERANGE; + } + + if (validate_nla_bitfield32(nla, pt->validation_data) != 0) + { + return -EINVAL; + } + break; + + case NLA_NUL_STRING: + if (pt->len) + { + minlen = MIN(attrlen, pt->len + 1); + } + else + { + minlen = attrlen; + } + + if (!minlen || memchr(nla_data(nla), '\0', minlen) == NULL) + { + return -EINVAL; + } + + /* fall through */ + + case NLA_STRING: + if (attrlen < 1) + { + return -ERANGE; + } + + if (pt->len) + { + FAR char *buf = nla_data(nla); + + if (buf[attrlen - 1] == '\0') + { + attrlen--; + } + + if (attrlen > pt->len) + { + return -ERANGE; + } + } + break; + + case NLA_BINARY: + if (pt->len && attrlen > pt->len) + { + return -ERANGE; + } + break; + + case NLA_NESTED_COMPAT: + if (attrlen < pt->len) + { + return -ERANGE; + } + + if (attrlen < NLA_ALIGN(pt->len)) + { + break; + } + + if (attrlen < NLA_ALIGN(pt->len) + NLA_HDRLEN) + { + return -ERANGE; + } + + nla = nla_data(nla) + NLA_ALIGN(pt->len); + if (attrlen < NLA_ALIGN(pt->len) + NLA_HDRLEN + nla_len(nla)) + { + return -ERANGE; + } + break; + + case NLA_NESTED: + /* a nested attributes is allowed to be empty; if its not, + * it must have a size of at least NLA_HDRLEN. + */ + + if (attrlen == 0) + { + break; + } + + default: + if (pt->len) + { + minlen = pt->len; + } + else if (pt->type != NLA_UNSPEC) + { + minlen = g_nla_attr_minlen[pt->type]; + } + + if (attrlen < minlen) + { + return -ERANGE; + } + } + + return 0; +} +#else +# define validate_nla(nla, maxtype, policy) 0 +#endif + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: nla_next + * + * Description: + * Next netlink attribute in attribute stream. + * + * Input Parameters: + * nla - netlink attribute. + * remaining - number of bytes remaining in attribute stream. + * + * Returned Value: + * Returns the next netlink attribute in the attribute stream and + * decrements remaining by the size of the current attribute. + * + ****************************************************************************/ + +FAR struct nlattr *nla_next(FAR const struct nlattr *nla, + FAR int *remaining) +{ + unsigned int totlen = NLA_ALIGN(nla->nla_len); + + *remaining -= totlen; + return (FAR struct nlattr *)((FAR char *)nla + totlen); +} + +/**************************************************************************** + * Name: nla_parse + * + * Description: + * Parse the nested netlink attribute. + * + ****************************************************************************/ + +int nla_parse(FAR struct nlattr **tb, int maxtype, + FAR const struct nlattr *head, + int len, FAR const struct nla_policy *policy, + FAR struct netlink_ext_ack *extack) +{ + FAR const struct nlattr *nla; + int rem; + int err = 0; + + memset(tb, 0, sizeof(FAR struct nlattr *) * (maxtype + 1)); + + nla_for_each_attr(nla, head, len, rem) + { + uint16_t type = nla_type(nla); + + if (type > 0 && type <= maxtype) + { + err = validate_nla(nla, maxtype, policy); + if (err < 0) + { + nl_set_err_msg_attr(extack, nla, + "Attribute failed policy validation"); + goto errout; + } + + tb[type] = (FAR struct nlattr *)nla; + } + } + + if (rem > 0) + { + nwarn("netlink: %d bytes leftover after parsing attributes in " + "pid `%d'.\n", rem, getpid()); + } + +errout: + return err; +} diff --git a/net/netlink/netlink_route.c b/net/netlink/netlink_route.c index 9c614c8fa2..12182148b8 100644 --- a/net/netlink/netlink_route.c +++ b/net/netlink/netlink_route.c @@ -44,6 +44,7 @@ #include "neighbor/neighbor.h" #include "route/route.h" #include "netlink/netlink.h" +#include "utils/utils.h" #ifdef CONFIG_NETLINK_ROUTE @@ -150,6 +151,26 @@ struct getroute_recvfrom_ipv6resplist_s struct getroute_recvfrom_ipv6response_s payload; }; +/* RTM_GETADDR: Get the specified network device address info */ + +struct getaddr_recvfrom_response_s +{ + struct nlmsghdr hdr; + struct ifaddrmsg ifaddr; + struct rtattr attr; +#ifndef CONFIG_NET_IPv6 + struct in_addr local_addr; /* IFA_LOCAL is the only attribute supported */ +#else + struct in6_addr local_addr; /* IFA_LOCAL is the only attribute supported */ +#endif +}; + +struct getaddr_recvfrom_rsplist_s +{ + sq_entry_t flink; + struct getaddr_recvfrom_response_s payload; +}; + /* netdev_foreach() callback */ struct nlroute_sendto_request_s @@ -164,6 +185,54 @@ struct nlroute_info_s FAR const struct nlroute_sendto_request_s *req; }; +/**************************************************************************** + * Private Data + ****************************************************************************/ + +#ifdef CONFIG_NETLINK_VALIDATE_POLICY +# ifdef CONFIG_NET_IPv4 +static const struct nla_policy g_ifa_ipv4_policy[] = +{ + {0}, /* IFA_UNSPEC */ + {NLA_U32, 0, NULL}, /* IFA_ADDRESS */ + {NLA_U32, 0, NULL}, /* IFA_LOCAL */ + {NLA_STRING, IFNAMSIZ - 1, NULL}, /* IFA_LABEL */ + {NLA_U32, 0, NULL}, /* IFA_BROADCAST */ + {0}, /* IFA_ANYCAST */ + {NLA_UNSPEC, sizeof(struct ifa_cacheinfo), NULL}, /* IFA_CACHEINFO */ + {0}, /* IFA_MULTICAST */ + {NLA_U32, 0, NULL}, /* IFA_FLAGS */ + {NLA_U32, 0, NULL}, /* IFA_RT_PRIORITY */ +}; + +static_assert(sizeof(g_ifa_ipv4_policy) / sizeof(g_ifa_ipv4_policy[0]) == + IFA_MAX + 1, "The policy definition has changed," + " please check it"); +# endif +# ifdef CONFIG_NET_IPv6 +static const struct nla_policy g_ifa_ipv6_policy[] = +{ + {0}, /* IFA_UNSPEC */ + {0, sizeof(struct in6_addr), NULL}, /* IFA_ADDRESS */ + {0, sizeof(struct in6_addr), NULL}, /* IFA_LOCAL */ + {0}, /* IFA_LABEL */ + {0}, /* IFA_BROADCAST */ + {0}, /* IFA_ANYCAST */ + {NLA_UNSPEC, sizeof(struct ifa_cacheinfo), NULL}, /* IFA_CACHEINFO */ + {0}, /* IFA_MULTICAST */ + {0, sizeof(uint32_t), NULL}, /* IFA_FLAGS */ + {0, sizeof(uint32_t), NULL}, /* IFA_RT_PRIORITY */ +}; + +static_assert(sizeof(g_ifa_ipv6_policy) / sizeof(g_ifa_ipv6_policy[0]) == + IFA_MAX + 1, "The policy definition has changed," + " please check it"); +# endif +#else +# define g_ifa_ipv4_policy NULL +# define g_ifa_ipv6_policy NULL +#endif + /**************************************************************************** * Private Functions ****************************************************************************/ @@ -224,6 +293,96 @@ netlink_get_device(FAR struct net_driver_s *dev, } #endif +#ifdef CONFIG_NET_IPv4 +static uint32_t make_mask(int prefixlen) +{ + if (prefixlen) + { + return HTONL(UINT32_MAX << (32 - prefixlen)); + } + + return 0; +} + +static uint8_t mask_len(uint32_t nmask) +{ + uint8_t prefixlen = 0; + uint32_t hmask = NTOHL(nmask); + + for (; hmask; ++prefixlen, hmask <<= 1); + + return prefixlen; +} +#endif + +/**************************************************************************** + * Name: netlink_get_ifaddr + * + * Description: + * Generate one interface address response. + * + ****************************************************************************/ + +#ifndef CONFIG_NETLINK_DISABLE_GETLINK +static FAR struct netlink_response_s * +netlink_get_ifaddr(FAR struct net_driver_s *dev, int domain, int type, + FAR const struct nlroute_sendto_request_s *req) +{ + FAR struct getaddr_recvfrom_rsplist_s *alloc; + FAR struct getaddr_recvfrom_response_s *resp; + + /* Allocate the response buffer */ + + alloc = (FAR struct getaddr_recvfrom_rsplist_s *) + kmm_zalloc(sizeof(struct getaddr_recvfrom_rsplist_s)); + if (alloc == NULL) + { + nerr("ERROR: Failed to allocate response buffer.\n"); + return NULL; + } + + /* Initialize the response buffer */ + + resp = &alloc->payload; + + resp->hdr.nlmsg_len = sizeof(struct getaddr_recvfrom_response_s); + resp->hdr.nlmsg_type = type; + resp->hdr.nlmsg_flags = req ? req->hdr.nlmsg_flags : 0; + resp->hdr.nlmsg_seq = req ? req->hdr.nlmsg_seq : 0; + resp->hdr.nlmsg_pid = req ? req->hdr.nlmsg_pid : 0; + + resp->ifaddr.ifa_family = domain; +#ifdef CONFIG_NETDEV_IFINDEX + resp->ifaddr.ifa_index = dev->d_ifindex; +#endif + resp->ifaddr.ifa_flags = IFA_F_PERMANENT; + resp->ifaddr.ifa_scope = RT_SCOPE_UNIVERSE; + + resp->attr.rta_type = IFA_LOCAL; +#ifdef CONFIG_NET_IPv4 + if (domain == AF_INET) + { + resp->attr.rta_len = RTA_LENGTH(sizeof(struct in_addr)); + memcpy(&resp->local_addr, &dev->d_ipaddr, sizeof(struct in_addr)); + resp->ifaddr.ifa_prefixlen = mask_len(dev->d_netmask); + } +#endif + +#ifdef CONFIG_NET_IPv6 + if (domain == AF_INET6) + { + resp->attr.rta_len = RTA_LENGTH(sizeof(struct in6_addr)); + memcpy(&resp->local_addr, dev->d_ipv6addr, sizeof(struct in6_addr)); + resp->ifaddr.ifa_prefixlen = net_ipv6_mask2pref(dev->d_ipv6netmask); + } +#endif + + /* Finally, return the response */ + + return (FAR struct netlink_response_s *)alloc; +} +#endif + /**************************************************************************** * Name: netlink_get_terminator * @@ -684,6 +843,251 @@ static int netlink_get_ip6vroute(NETLINK_HANDLE handle, } #endif +/**************************************************************************** + * Name: netlink_new_ipv4addr + * + * Description: + * Set the ipv4 address. + * + ****************************************************************************/ + +#if defined(CONFIG_NET_IPv4) && !defined(CONFIG_NETLINK_DISABLE_NEWADDR) +static int netlink_new_ipv4addr(NETLINK_HANDLE handle, + FAR const struct nlmsghdr *nlmsg) +{ + FAR struct net_driver_s *dev; + FAR struct ifaddrmsg *ifm = NLMSG_DATA(nlmsg); + FAR struct nlattr *tb[IFA_MAX + 1]; + struct netlink_ext_ack extack; + int ret; + + ret = nlmsg_parse(nlmsg, sizeof(*ifm), tb, IFA_MAX, g_ifa_ipv4_policy, + &extack); + if (ret < 0) + { + return ret; + } + + if (ifm->ifa_prefixlen > 32 || !tb[IFA_LOCAL]) + { + return -EINVAL; + } + + net_lock(); + dev = netdev_findbyindex(ifm->ifa_index); + + if (dev == NULL) + { + net_unlock(); + return -ENODEV; + } + + dev->d_ipaddr = nla_get_in_addr(tb[IFA_LOCAL]); + dev->d_netmask = make_mask(ifm->ifa_prefixlen); + + netlink_device_notify_ipaddr(dev, RTM_NEWADDR, AF_INET); + net_unlock(); + + return OK; +} +#endif + +/**************************************************************************** + * Name: netlink_new_ipv6addr + * + * Description: + * Set the ipv6 address. + * + ****************************************************************************/ + +#if defined(CONFIG_NET_IPv6) && !defined(CONFIG_NETLINK_DISABLE_NEWADDR) +static int netlink_new_ipv6addr(NETLINK_HANDLE handle, + FAR const struct nlmsghdr *nlmsg) +{ + FAR struct net_driver_s *dev; + FAR struct ifaddrmsg *ifm = NLMSG_DATA(nlmsg); + FAR struct nlattr *tb[IFA_MAX + 1]; + struct netlink_ext_ack extack; + int ret; + + ret = nlmsg_parse(nlmsg, sizeof(*ifm), tb, IFA_MAX, g_ifa_ipv6_policy, + &extack); + if (ret < 0) + { + return ret; + } + + if (ifm->ifa_prefixlen > 128 || !tb[IFA_LOCAL]) + { + return -EINVAL; + } + + net_lock(); + dev = netdev_findbyindex(ifm->ifa_index); + + if (dev == NULL) + { + net_unlock(); + return -ENODEV; + } + + memcpy(dev->d_ipv6addr, nla_data(tb[IFA_LOCAL]), 16); + net_ipv6_pref2mask(ifm->ifa_prefixlen, dev->d_ipv6netmask); + + netlink_device_notify_ipaddr(dev, RTM_NEWADDR, AF_INET6); + net_unlock(); + + return OK; +} +#endif + +/**************************************************************************** + * Name: netlink_del_ipv4addr + * + * Description: + * Clear the ipv4 address. + * + ****************************************************************************/ + +#if defined(CONFIG_NET_IPv4) && !defined(CONFIG_NETLINK_DISABLE_DELADDR) +static int netlink_del_ipv4addr(NETLINK_HANDLE handle, + FAR const struct nlmsghdr *nlmsg) +{ + FAR struct net_driver_s *dev; + FAR struct ifaddrmsg *ifm = NLMSG_DATA(nlmsg); + FAR struct nlattr *tb[IFA_MAX + 1]; + struct netlink_ext_ack extack; + int ret; + + ret = nlmsg_parse(nlmsg, sizeof(*ifm), tb, IFA_MAX, g_ifa_ipv4_policy, + &extack); + if (ret < 0) + { + return ret; + } + + net_lock(); + dev = netdev_findbyindex(ifm->ifa_index); + + if (dev == NULL) + { + net_unlock(); + return -ENODEV; + } + + if (tb[IFA_LOCAL] && dev->d_ipaddr != nla_get_in_addr(tb[IFA_LOCAL])) + { + net_unlock(); + return -EADDRNOTAVAIL; + } + + dev->d_ipaddr = 0; + + netlink_device_notify_ipaddr(dev, RTM_DELADDR, AF_INET); + net_unlock(); + + return OK; +} +#endif + +/**************************************************************************** + * Name: netlink_del_ipv6addr + * + * Description: + * Clear the ipv6 address. + * + ****************************************************************************/ + +#if defined(CONFIG_NET_IPv6) && !defined(CONFIG_NETLINK_DISABLE_DELADDR) +static int netlink_del_ipv6addr(NETLINK_HANDLE handle, + FAR const struct nlmsghdr *nlmsg) +{ + FAR struct net_driver_s *dev; + FAR struct ifaddrmsg *ifm = NLMSG_DATA(nlmsg); + FAR struct nlattr *tb[IFA_MAX + 1]; + struct netlink_ext_ack extack; + int ret; + + ret = nlmsg_parse(nlmsg, sizeof(*ifm), tb, IFA_MAX, g_ifa_ipv6_policy, + &extack); + if (ret < 0) + { + return ret; + } + + net_lock(); + dev = netdev_findbyindex(ifm->ifa_index); + + if (dev == NULL) + { + net_unlock(); + return -ENODEV; + } + + if (tb[IFA_LOCAL] && dev->d_ipaddr != nla_get_in_addr(tb[IFA_LOCAL])) + { + net_unlock(); + return -EADDRNOTAVAIL; + } + + memset(&dev->d_ipv6addr, 0, sizeof(net_ipv6addr_t)); + + netlink_device_notify_ipaddr(dev, RTM_DELADDR, AF_INET6); + net_unlock(); + + return OK; +} +#endif + +/**************************************************************************** + * Name: netlink_get_addr + * + * Description: + * Clear the ipv4/ipv6 address. + * + ****************************************************************************/ + +#ifndef CONFIG_NETLINK_DISABLE_GETADDR +static int netlink_addr_callback(FAR struct net_driver_s *dev, + FAR void *arg) +{ + FAR struct nlroute_info_s *info = arg; + FAR struct netlink_response_s *resp; + + resp = netlink_get_ifaddr(dev, info->req->gen.rtgen_family, RTM_NEWADDR, + info->req); + if (resp == NULL) + { + return -ENOMEM; + } + + netlink_add_response(info->handle, resp); + return OK; +} + +static int netlink_get_addr(NETLINK_HANDLE handle, + FAR const struct nlroute_sendto_request_s *req) +{ + struct nlroute_info_s info; + int ret; + + /* Visit each device */ + + info.handle = handle; + info.req = req; + + net_lock(); + ret = netdev_foreach(netlink_addr_callback, &info); + net_unlock(); + if (ret < 0) + { + return ret; + } + + return netlink_add_terminator(handle, req); +} +#endif + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -780,6 +1184,78 @@ ssize_t netlink_route_sendto(NETLINK_HANDLE handle, break; #endif +#ifndef CONFIG_NETLINK_DISABLE_NEWADDR + /* Set the IPv4 or IPv6 address */ + + case RTM_NEWADDR: +#ifdef CONFIG_NET_IPv4 + if (req->gen.rtgen_family == AF_INET) + { + ret = netlink_new_ipv4addr(handle, nlmsg); + } + else +#endif +#ifdef CONFIG_NET_IPv6 + if (req->gen.rtgen_family == AF_INET6) + { + ret = netlink_new_ipv6addr(handle, nlmsg); + } + else +#endif + { + ret = -EAFNOSUPPORT; + } + break; +#endif + +#ifndef CONFIG_NETLINK_DISABLE_DELADDR + /* Clear the IPv4 or IPv6 address */ + + case RTM_DELADDR: +#ifdef CONFIG_NET_IPv4 + if (req->gen.rtgen_family == AF_INET) + { + ret = netlink_del_ipv4addr(handle, nlmsg); + } + else +#endif +#ifdef CONFIG_NET_IPv6 + if (req->gen.rtgen_family == AF_INET6) + { + ret = netlink_del_ipv6addr(handle, nlmsg); + } + else +#endif + { + ret = -EAFNOSUPPORT; + } + break; +#endif + +#ifndef CONFIG_NETLINK_DISABLE_GETADDR + /* Get the IPv4 or IPv6 address */ + + case RTM_GETADDR: +#ifdef CONFIG_NET_IPv4 + if (req->gen.rtgen_family == AF_INET) + { + ret = netlink_get_addr(handle, req); + } + else +#endif +#ifdef CONFIG_NET_IPv6 + if (req->gen.rtgen_family == AF_INET6) + { + ret = netlink_get_addr(handle, req); + } + else +#endif + { + ret = -EAFNOSUPPORT; + } + break; +#endif + default: ret = -ENOSYS; break; @@ -824,4 +1300,57 @@ void netlink_device_notify(FAR struct net_driver_s *dev) } #endif +/**************************************************************************** + * Name: netlink_device_notify_ipaddr() + * + * Description: + * Perform the route broadcast for the NETLINK_ROUTE protocol. + * + ****************************************************************************/ + +#if !defined(CONFIG_NETLINK_DISABLE_NEWADDR) || \ + !defined(CONFIG_NETLINK_DISABLE_DELADDR) || \ + !defined(CONFIG_NETLINK_DISABLE_GETADDR) +void netlink_device_notify_ipaddr(FAR struct net_driver_s *dev, + int type, int domain) +{ + FAR struct netlink_response_s *resp; + int group; + + DEBUGASSERT(dev != NULL); + + resp = netlink_get_ifaddr(dev, domain, type, NULL); + if (resp != NULL) + { +#ifdef CONFIG_NET_IPv4 + if (domain == AF_INET) + { + group = RTNLGRP_IPV4_IFADDR; + } + else +#endif +#ifdef CONFIG_NET_IPv6 + if (domain == AF_INET6) + { + group = RTNLGRP_IPV6_IFADDR; + } + else +#endif + { + nwarn("netlink_device_notify_ipaddr unknown type %d domain %d\n", + type, domain); + return; + } + + netlink_add_broadcast(group, resp); + + resp = netlink_get_terminator(NULL); + if (resp != NULL) + { + netlink_add_broadcast(group, resp); + } + } +} +#endif + #endif /* CONFIG_NETLINK_ROUTE */