carp: support unicast

Allow users to configure the address to send carp messages to. This
allows carp to be used in unicast mode, which is useful in certain
virtual configurations (e.g. AWS, VMWare ESXi, ...)

Reviewed by:	melifaro
Sponsored by:	Rubicon Communications, LLC ("Netgate")
Differential Revision:	https://reviews.freebsd.org/D38940
This commit is contained in:
Kristof Provost 2023-03-15 13:31:45 +01:00
parent 3afba490c1
commit 137818006d
11 changed files with 229 additions and 36 deletions

View File

@ -287,6 +287,8 @@ struct ifconfig_carp {
int32_t carpr_advbase;
int32_t carpr_advskew;
uint8_t carpr_key[CARP_KEY_LEN];
struct in_addr carpr_addr;
struct in6_addr carpr_addr6;
};
int ifconfig_carp_get_vhid(ifconfig_handle_t *h, const char *name,

View File

@ -39,6 +39,7 @@
#include <netlink/netlink_generic.h>
#include <netlink/netlink_snl.h>
#include <netlink/netlink_snl_generic.h>
#include <netlink/netlink_snl_route.h>
#include <string.h>
#include <strings.h>
@ -55,6 +56,8 @@ static struct snl_attr_parser ap_carp_get[] = {
{ .type = CARP_NL_ADVBASE, .off = _OUT(carpr_advbase), .cb = snl_attr_get_int32 },
{ .type = CARP_NL_ADVSKEW, .off = _OUT(carpr_advskew), .cb = snl_attr_get_int32 },
{ .type = CARP_NL_KEY, .off = _OUT(carpr_key), .cb = snl_attr_get_string },
{ .type = CARP_NL_ADDR, .off = _OUT(carpr_addr), .cb = snl_attr_get_in_addr },
{ .type = CARP_NL_ADDR6, .off = _OUT(carpr_addr6), .cb = snl_attr_get_in6_addr },
};
#undef _OUT
@ -181,6 +184,10 @@ ifconfig_carp_set_info(ifconfig_handle_t *h, const char *name,
snl_add_msg_attr_s32(&nw, CARP_NL_ADVBASE, carpr->carpr_advbase);
snl_add_msg_attr_s32(&nw, CARP_NL_ADVSKEW, carpr->carpr_advskew);
snl_add_msg_attr_u32(&nw, CARP_NL_IFINDEX, ifindex);
snl_add_msg_attr(&nw, CARP_NL_ADDR, sizeof(carpr->carpr_addr),
&carpr->carpr_addr);
snl_add_msg_attr(&nw, CARP_NL_ADDR6, sizeof(carpr->carpr_addr6),
&carpr->carpr_addr6);
hdr = snl_finalize_msg(&nw);
if (hdr == NULL) {

View File

@ -42,13 +42,17 @@
#include <netinet/in_var.h>
#include <netinet/ip_carp.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <err.h>
#include <errno.h>
#include <netdb.h>
#include <libifconfig.h>
@ -67,12 +71,15 @@ static int carpr_vhid = -1;
static int carpr_advskew = -1;
static int carpr_advbase = -1;
static int carpr_state = -1;
static struct in_addr carp_addr;
static struct in6_addr carp_addr6;
static unsigned char const *carpr_key;
static void
carp_status(int s)
{
struct ifconfig_carp carpr[CARP_MAXVHID];
char addr_buf[NI_MAXHOST];
if (ifconfig_carp_get_info(lifh, name, carpr, CARP_MAXVHID) == -1)
return;
@ -85,6 +92,12 @@ carp_status(int s)
printf(" key \"%s\"\n", carpr[i].carpr_key);
else
printf("\n");
inet_ntop(AF_INET6, &carpr[i].carpr_addr6, addr_buf,
sizeof(addr_buf));
printf("\t peer %s peer6 %s\n",
inet_ntoa(carpr[i].carpr_addr), addr_buf);
}
}
@ -146,6 +159,11 @@ setcarp_callback(int s, void *arg __unused)
carpr.carpr_advbase = carpr_advbase;
if (carpr_state > -1)
carpr.carpr_state = carpr_state;
if (carp_addr.s_addr != INADDR_ANY)
carpr.carpr_addr = carp_addr;
if (! IN6_IS_ADDR_UNSPECIFIED(&carp_addr6))
memcpy(&carpr.carpr_addr6, &carp_addr6,
sizeof(carp_addr6));
if (ifconfig_carp_set_info(lifh, name, &carpr))
err(1, "SIOCSVH");
@ -198,12 +216,53 @@ setcarp_state(const char *val, int d, int s, const struct afswtch *afp)
errx(1, "unknown state");
}
static void
setcarp_peer(const char *val, int d, int s, const struct afswtch *afp)
{
carp_addr.s_addr = inet_addr(val);
}
static void
setcarp_mcast(const char *val, int d, int s, const struct afswtch *afp)
{
carp_addr.s_addr = htonl(INADDR_CARP_GROUP);
}
static void
setcarp_peer6(const char *val, int d, int s, const struct afswtch *afp)
{
struct addrinfo hints, *res;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET6;
hints.ai_flags = AI_NUMERICHOST;
if (getaddrinfo(val, NULL, &hints, &res) == 1)
errx(1, "Invalid IPv6 address %s", val);
memcpy(&carp_addr6, &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr,
sizeof(carp_addr6));
}
static void
setcarp_mcast6(const char *val, int d, int s, const struct afswtch *afp)
{
bzero(&carp_addr6, sizeof(carp_addr6));
carp_addr6.s6_addr[0] = 0xff;
carp_addr6.s6_addr[1] = 0x02;
carp_addr6.s6_addr[15] = 0x12;
}
static struct cmd carp_cmds[] = {
DEF_CMD_ARG("advbase", setcarp_advbase),
DEF_CMD_ARG("advskew", setcarp_advskew),
DEF_CMD_ARG("pass", setcarp_passwd),
DEF_CMD_ARG("vhid", setcarp_vhid),
DEF_CMD_ARG("state", setcarp_state),
DEF_CMD_ARG("peer", setcarp_peer),
DEF_CMD_ARG("mcast", setcarp_mcast),
DEF_CMD_ARG("peer6", setcarp_peer6),
DEF_CMD_ARG("mcast6", setcarp_mcast6),
};
static struct afswtch af_carp = {
.af_name = "af_carp",
@ -216,6 +275,10 @@ carp_ctor(void)
{
int i;
/* Default to multicast. */
setcarp_mcast(NULL, 0, 0, NULL);
setcarp_mcast6(NULL, 0, 0, NULL);
for (i = 0; i < nitems(carp_cmds); i++)
cmd_register(&carp_cmds[i]);
af_register(&af_carp);

View File

@ -110,6 +110,8 @@ struct carp_softc {
int sc_vhid;
int sc_advskew;
int sc_advbase;
struct in_addr sc_carpaddr;
struct in6_addr sc_carpaddr6;
int sc_naddrs;
int sc_naddrs6;
@ -154,6 +156,20 @@ struct carp_if {
#define CIF_PROMISC 0x00000001
};
/* Kernel equivalent of struct carpreq, but with more fields for new features.
* */
struct carpkreq {
int carpr_count;
int carpr_vhid;
int carpr_state;
int carpr_advskew;
int carpr_advbase;
unsigned char carpr_key[CARP_KEY_LEN];
/* Everything above this is identical to carpreq */
struct in_addr carpr_addr;
struct in6_addr carpr_addr6;
};
/*
* Brief design of carp(4).
*
@ -310,7 +326,7 @@ SYSCTL_VNET_PCPUSTAT(_net_inet_carp, OID_AUTO, stats, struct carpstats,
(((sc)->sc_advskew + V_carp_demotion < 0) ? \
0 : ((sc)->sc_advskew + V_carp_demotion)))
static void carp_input_c(struct mbuf *, struct carp_header *, sa_family_t);
static void carp_input_c(struct mbuf *, struct carp_header *, sa_family_t, int);
static struct carp_softc
*carp_alloc(struct ifnet *);
static void carp_destroy(struct carp_softc *);
@ -488,16 +504,6 @@ carp_input(struct mbuf **mp, int *offp, int proto)
return (IPPROTO_DONE);
}
/* verify that the IP TTL is 255. */
if (ip->ip_ttl != CARP_DFLTTL) {
CARPSTATS_INC(carps_badttl);
CARP_DEBUG("%s: received ttl %d != 255 on %s\n", __func__,
ip->ip_ttl,
m->m_pkthdr.rcvif->if_xname);
m_freem(m);
return (IPPROTO_DONE);
}
iplen = ip->ip_hl << 2;
if (m->m_pkthdr.len < iplen + sizeof(*ch)) {
@ -551,7 +557,7 @@ carp_input(struct mbuf **mp, int *offp, int proto)
}
m->m_data -= iplen;
carp_input_c(m, ch, AF_INET);
carp_input_c(m, ch, AF_INET, ip->ip_ttl);
return (IPPROTO_DONE);
}
#endif
@ -581,15 +587,6 @@ carp6_input(struct mbuf **mp, int *offp, int proto)
return (IPPROTO_DONE);
}
/* verify that the IP TTL is 255 */
if (ip6->ip6_hlim != CARP_DFLTTL) {
CARPSTATS_INC(carps_badttl);
CARP_DEBUG("%s: received ttl %d != 255 on %s\n", __func__,
ip6->ip6_hlim, m->m_pkthdr.rcvif->if_xname);
m_freem(m);
return (IPPROTO_DONE);
}
/* verify that we have a complete carp packet */
if (m->m_len < *offp + sizeof(*ch)) {
len = m->m_len;
@ -599,6 +596,7 @@ carp6_input(struct mbuf **mp, int *offp, int proto)
CARP_DEBUG("%s: packet size %u too small\n", __func__, len);
return (IPPROTO_DONE);
}
ip6 = mtod(m, struct ip6_hdr *);
}
ch = (struct carp_header *)(mtod(m, char *) + *offp);
@ -613,7 +611,7 @@ carp6_input(struct mbuf **mp, int *offp, int proto)
}
m->m_data -= *offp;
carp_input_c(m, ch, AF_INET6);
carp_input_c(m, ch, AF_INET6, ip6->ip6_hlim);
return (IPPROTO_DONE);
}
#endif /* INET6 */
@ -664,7 +662,7 @@ carp_source_is_self(struct mbuf *m, struct ifaddr *ifa, sa_family_t af)
}
static void
carp_input_c(struct mbuf *m, struct carp_header *ch, sa_family_t af)
carp_input_c(struct mbuf *m, struct carp_header *ch, sa_family_t af, int ttl)
{
struct ifnet *ifp = m->m_pkthdr.rcvif;
struct ifaddr *ifa, *match;
@ -672,6 +670,7 @@ carp_input_c(struct mbuf *m, struct carp_header *ch, sa_family_t af)
uint64_t tmp_counter;
struct timeval sc_tv, ch_tv;
int error;
bool multicast = false;
NET_EPOCH_ASSERT();
@ -724,8 +723,21 @@ carp_input_c(struct mbuf *m, struct carp_header *ch, sa_family_t af)
sc = ifa->ifa_carp;
CARP_LOCK(sc);
if (ifa->ifa_addr->sa_family == AF_INET) {
multicast = IN_MULTICAST(sc->sc_carpaddr.s_addr);
} else {
multicast = IN6_IS_ADDR_MULTICAST(&sc->sc_carpaddr6);
}
ifa_free(ifa);
/* verify that the IP TTL is 255, but only if we're not in unicast mode. */
if (multicast && ttl != CARP_DFLTTL) {
CARPSTATS_INC(carps_badttl);
CARP_DEBUG("%s: received ttl %d != 255 on %s\n", __func__,
ttl, m->m_pkthdr.rcvif->if_xname);
goto out;
}
if (carp_hmac_verify(sc, ch->carp_counter, ch->carp_md)) {
CARPSTATS_INC(carps_badauth);
CARP_DEBUG("%s: incorrect hash for VHID %u@%s\n", __func__,
@ -978,7 +990,8 @@ carp_send_ad_locked(struct carp_softc *sc)
m->m_pkthdr.rcvif = NULL;
m->m_len = len;
M_ALIGN(m, m->m_len);
m->m_flags |= M_MCAST;
if (IN_MULTICAST(sc->sc_carpaddr.s_addr))
m->m_flags |= M_MCAST;
ip = mtod(m, struct ip *);
ip->ip_v = IPVERSION;
ip->ip_hl = sizeof(*ip) >> 2;
@ -997,7 +1010,7 @@ carp_send_ad_locked(struct carp_softc *sc)
ifa_free(ifa);
} else
ip->ip_src.s_addr = 0;
ip->ip_dst.s_addr = htonl(INADDR_CARP_GROUP);
ip->ip_dst = sc->sc_carpaddr;
ch_ptr = (struct carp_header *)(&ip[1]);
bcopy(&ch, ch_ptr, sizeof(ch));
@ -1028,7 +1041,6 @@ carp_send_ad_locked(struct carp_softc *sc)
m->m_pkthdr.rcvif = NULL;
m->m_len = len;
M_ALIGN(m, m->m_len);
m->m_flags |= M_MCAST;
ip6 = mtod(m, struct ip6_hdr *);
bzero(ip6, sizeof(*ip6));
ip6->ip6_vfc |= IPV6_VERSION;
@ -1050,12 +1062,13 @@ carp_send_ad_locked(struct carp_softc *sc)
bzero(&ip6->ip6_src, sizeof(struct in6_addr));
/* Set the multicast destination. */
ip6->ip6_dst.s6_addr16[0] = htons(0xff02);
ip6->ip6_dst.s6_addr8[15] = 0x12;
if (in6_setscope(&ip6->ip6_dst, sc->sc_carpdev, NULL) != 0) {
m_freem(m);
CARP_DEBUG("%s: in6_setscope failed\n", __func__);
goto resched;
memcpy(&ip6->ip6_dst, &sc->sc_carpaddr6, sizeof(ip6->ip6_dst));
if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) {
if (in6_setscope(&ip6->ip6_dst, sc->sc_carpdev, NULL) != 0) {
m_freem(m);
CARP_DEBUG("%s: in6_setscope failed\n", __func__);
goto resched;
}
}
ch_ptr = (struct carp_header *)(&ip6[1]);
@ -1571,6 +1584,19 @@ carp_output(struct ifnet *ifp, struct mbuf *m, const struct sockaddr *sa)
bcopy(mtag + 1, &sc, sizeof(sc));
switch (sa->sa_family) {
case AF_INET:
if (! IN_MULTICAST(sc->sc_carpaddr.s_addr))
return (0);
break;
case AF_INET6:
if (! IN6_IS_ADDR_MULTICAST(&sc->sc_carpaddr6))
return (0);
break;
default:
panic("Unknown af");
}
/* Set the source MAC address to the Virtual Router MAC Address. */
switch (ifp->if_type) {
case IFT_ETHER:
@ -1618,6 +1644,10 @@ carp_alloc(struct ifnet *ifp)
sc->sc_ifas = malloc(sc->sc_ifasiz, M_CARP, M_WAITOK|M_ZERO);
sc->sc_carpdev = ifp;
sc->sc_carpaddr.s_addr = htonl(INADDR_CARP_GROUP);
sc->sc_carpaddr6.s6_addr16[0] = IPV6_ADDR_INT16_MLL;
sc->sc_carpaddr6.s6_addr8[15] = 0x12;
CARP_LOCK_INIT(sc);
#ifdef INET
callout_init_mtx(&sc->sc_md_tmo, &sc->sc_mtx, CALLOUT_RETURNUNLOCKED);
@ -1753,7 +1783,7 @@ carp_carprcp(void *arg, struct carp_softc *sc, int priv)
}
static int
carp_ioctl_set(if_t ifp, struct carpreq *carpr)
carp_ioctl_set(if_t ifp, struct carpkreq *carpr)
{
struct epoch_tracker et;
struct carp_softc *sc = NULL;
@ -1795,6 +1825,12 @@ carp_ioctl_set(if_t ifp, struct carpreq *carpr)
goto out;
}
sc->sc_advskew = carpr->carpr_advskew;
if (carpr->carpr_addr.s_addr != INADDR_ANY)
sc->sc_carpaddr = carpr->carpr_addr;
if (! IN6_IS_ADDR_UNSPECIFIED(&carpr->carpr_addr6)) {
memcpy(&sc->sc_carpaddr6, &carpr->carpr_addr6,
sizeof(sc->sc_carpaddr6));
}
if (carpr->carpr_key[0] != '\0') {
bcopy(carpr->carpr_key, sc->sc_key, sizeof(sc->sc_key));
carp_hmac_prepare(sc);
@ -1875,6 +1911,7 @@ int
carp_ioctl(struct ifreq *ifr, u_long cmd, struct thread *td)
{
struct carpreq carpr;
struct carpkreq carprk = { };
struct ifnet *ifp;
int error = 0;
@ -1896,7 +1933,8 @@ carp_ioctl(struct ifreq *ifr, u_long cmd, struct thread *td)
if ((error = priv_check(td, PRIV_NETINET_CARP)))
break;
error = carp_ioctl_set(ifp, &carpr);
memcpy(&carprk, &carpr, sizeof(carpr));
error = carp_ioctl_set(ifp, &carprk);
break;
case SIOCGVH:
@ -2243,6 +2281,8 @@ carp_nl_send(void *arg, struct carp_softc *sc, int priv)
nlattr_add_u32(nw, CARP_NL_STATE, sc->sc_state);
nlattr_add_s32(nw, CARP_NL_ADVBASE, sc->sc_advbase);
nlattr_add_s32(nw, CARP_NL_ADVSKEW, sc->sc_advskew);
nlattr_add_in_addr(nw, CARP_NL_ADDR, &sc->sc_carpaddr);
nlattr_add_in6_addr(nw, CARP_NL_ADDR6, &sc->sc_carpaddr6);
if (priv)
nlattr_add(nw, CARP_NL_KEY, sizeof(sc->sc_key), sc->sc_key);
@ -2264,6 +2304,8 @@ struct nl_carp_parsed {
int32_t advbase;
int32_t advskew;
char key[CARP_KEY_LEN];
struct in_addr addr;
struct in6_addr addr6;
};
#define _IN(_field) offsetof(struct genlmsghdr, _field)
@ -2276,6 +2318,8 @@ static const struct nlattr_parser nla_p_set[] = {
{ .type = CARP_NL_ADVSKEW, .off = _OUT(advskew), .cb = nlattr_get_uint32 },
{ .type = CARP_NL_KEY, .off = _OUT(key), .cb = nlattr_get_carp_key },
{ .type = CARP_NL_IFINDEX, .off = _OUT(ifindex), .cb = nlattr_get_uint32 },
{ .type = CARP_NL_ADDR, .off = _OUT(addr), .cb = nlattr_get_in_addr },
{ .type = CARP_NL_ADDR6, .off = _OUT(addr6), .cb = nlattr_get_in6_addr },
};
static const struct nlfield_parser nlf_p_set[] = {
};
@ -2331,7 +2375,7 @@ static int
carp_nl_set(struct nlmsghdr *hdr, struct nl_pstate *npt)
{
struct nl_carp_parsed attrs = { };
struct carpreq carpr;
struct carpkreq carpr;
struct epoch_tracker et;
if_t ifp;
int error;
@ -2368,6 +2412,9 @@ carp_nl_set(struct nlmsghdr *hdr, struct nl_pstate *npt)
carpr.carpr_state = attrs.state;
carpr.carpr_advbase = attrs.advbase;
carpr.carpr_advskew = attrs.advskew;
carpr.carpr_addr = attrs.addr;
carpr.carpr_addr6 = attrs.addr6;
memcpy(&carpr.carpr_key, &attrs.key, sizeof(attrs.key));
sx_xlock(&carp_sx);

View File

@ -29,6 +29,8 @@ enum carp_nl_type_t {
CARP_NL_ADVSKEW = 4, /* s32 */
CARP_NL_KEY = 5, /* byte array */
CARP_NL_IFINDEX = 6, /* u32 */
CARP_NL_ADDR = 7, /* in_addr_t */
CARP_NL_ADDR6 = 8, /* in6_addr_t */
};
#endif

View File

@ -41,6 +41,7 @@ __FBSDID("$FreeBSD$");
#include <net/route/nhop.h>
#include <net/route/route_ctl.h>
#include <netinet/in.h>
#include <netlink/netlink.h>
#include <netlink/netlink_ctl.h>
#include <netlink/netlink_var.h>
@ -349,6 +350,30 @@ nlattr_get_uint64(struct nlattr *nla, struct nl_pstate *npt, const void *arg, vo
return (0);
}
int
nlattr_get_in_addr(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target)
{
if (__predict_false(NLA_DATA_LEN(nla) != sizeof(in_addr_t))) {
NLMSG_REPORT_ERR_MSG(npt, "nla type %d size(%u) is not in_addr_t",
nla->nla_type, NLA_DATA_LEN(nla));
return (EINVAL);
}
memcpy(target, NLA_DATA_CONST(nla), sizeof(in_addr_t));
return (0);
}
int
nlattr_get_in6_addr(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target)
{
if (__predict_false(NLA_DATA_LEN(nla) != sizeof(struct in6_addr))) {
NLMSG_REPORT_ERR_MSG(npt, "nla type %d size(%u) is not struct in6_addr",
nla->nla_type, NLA_DATA_LEN(nla));
return (EINVAL);
}
memcpy(target, NLA_DATA_CONST(nla), sizeof(struct in6_addr));
return (0);
}
static int
nlattr_get_ifp_internal(struct nlattr *nla, struct nl_pstate *npt,
void *target, bool zero_ok)

View File

@ -175,6 +175,10 @@ int nlattr_get_uint32(struct nlattr *nla, struct nl_pstate *npt,
const void *arg, void *target);
int nlattr_get_uint64(struct nlattr *nla, struct nl_pstate *npt,
const void *arg, void *target);
int nlattr_get_in_addr(struct nlattr *nla, struct nl_pstate *npt,
const void *arg, void *target);
int nlattr_get_in6_addr(struct nlattr *nla, struct nl_pstate *npt,
const void *arg, void *target);
int nlattr_get_ifp(struct nlattr *nla, struct nl_pstate *npt,
const void *arg, void *target);
int nlattr_get_ifpz(struct nlattr *nla, struct nl_pstate *npt,

View File

@ -37,6 +37,8 @@ __FBSDID("$FreeBSD$");
#include <sys/socketvar.h>
#include <sys/syslog.h>
#include <netinet/in.h>
#include <netlink/netlink.h>
#include <netlink/netlink_ctl.h>
#include <netlink/netlink_linux.h>
@ -686,3 +688,15 @@ nlmsg_end_dump(struct nl_writer *nw, int error, struct nlmsghdr *hdr)
return (true);
}
bool
nlattr_add_in_addr(struct nl_writer *nw, int attrtype, const struct in_addr *in)
{
return (nlattr_add(nw, attrtype, sizeof(*in), in));
}
bool
nlattr_add_in6_addr(struct nl_writer *nw, int attrtype, const struct in6_addr *in6)
{
return (nlattr_add(nw, attrtype, sizeof(*in6), in6));
}

View File

@ -30,6 +30,7 @@
#define _NETLINK_NETLINK_MESSAGE_WRITER_H_
#ifdef _KERNEL
/*
* It is not meant to be included directly
*/
@ -248,6 +249,12 @@ nlattr_add_s64(struct nl_writer *nw, int attrtype, int64_t value)
return (nlattr_add(nw, attrtype, sizeof(int64_t), &value));
}
struct in_addr;
bool nlattr_add_in_addr(struct nl_writer *nw, int attrtype, const struct in_addr *in);
struct in6_addr;
bool nlattr_add_in6_addr(struct nl_writer *nw, int attrtype, const struct in6_addr *in6);
static inline bool
nlattr_add_flag(struct nl_writer *nw, int attrtype)
{

View File

@ -44,7 +44,6 @@
#include <sys/socket.h>
#include <netlink/netlink.h>
#define _roundup2(x, y) (((x)+((y)-1))&(~((y)-1)))
#define NETLINK_ALIGN_SIZE sizeof(uint32_t)

View File

@ -163,4 +163,27 @@ snl_add_msg_attr_ipvia(struct snl_writer *nw, int attrtype, const struct sockadd
return (false);
}
static inline bool
snl_attr_get_in_addr(struct snl_state *ss __unused, struct nlattr *nla,
const void *arg __unused, void *target)
{
if (NLA_DATA_LEN(nla) != sizeof(struct in_addr))
return (false);
memcpy(target, NLA_DATA_CONST(nla), sizeof(struct in_addr));
return (true);
}
static inline bool
snl_attr_get_in6_addr(struct snl_state *ss __unused, struct nlattr *nla,
const void *arg __unused, void *target)
{
if (NLA_DATA_LEN(nla) != sizeof(struct in6_addr))
return (false);
memcpy(target, NLA_DATA_CONST(nla), sizeof(struct in6_addr));
return (true);
}
#endif