From 40e0435964094dfda21089d9989197999c06c4bc Mon Sep 17 00:00:00 2001 From: Kristof Provost Date: Tue, 7 Mar 2023 19:17:09 +0100 Subject: [PATCH] carp: add netlink interface Allow carp configuration information to be supplied and retrieved via netlink. Reviewed by: melifaro Sponsored by: Rubicon Communications, LLC ("Netgate") Differential Revision: https://reviews.freebsd.org/D39048 --- lib/libifconfig/libifconfig.h | 20 +- lib/libifconfig/libifconfig_carp.c | 179 ++++++++- lib/libifconfig/libifconfig_internal.c | 14 + lib/libifconfig/libifconfig_internal.h | 3 + sbin/ifconfig/carp.c | 18 +- sys/netinet/ip_carp.c | 524 ++++++++++++++++++------- sys/netinet/ip_carp_nl.h | 34 ++ 7 files changed, 635 insertions(+), 157 deletions(-) create mode 100644 sys/netinet/ip_carp_nl.h diff --git a/lib/libifconfig/libifconfig.h b/lib/libifconfig/libifconfig.h index c8bbb5edd3bb..64a61af0708c 100644 --- a/lib/libifconfig/libifconfig.h +++ b/lib/libifconfig/libifconfig.h @@ -33,6 +33,7 @@ #include #include +#include #include #define ND6_IFF_DEFAULTIF 0x8000 @@ -41,7 +42,8 @@ typedef enum { OK = 0, OTHER, IOCTL, - SOCKET + SOCKET, + NETLINK } ifconfig_errtype; /* @@ -51,7 +53,6 @@ typedef enum { struct ifconfig_handle; typedef struct ifconfig_handle ifconfig_handle_t; -struct carpreq; struct ifaddrs; struct ifbropreq; struct ifbreq; @@ -279,8 +280,21 @@ ifmedia_t *ifconfig_media_lookup_options(ifmedia_t media, const char **opts, int ifconfig_media_get_downreason(ifconfig_handle_t *h, const char *name, struct ifdownreason *ifdr); +struct ifconfig_carp { + size_t carpr_count; + uint32_t carpr_vhid; + uint32_t carpr_state; + int32_t carpr_advbase; + int32_t carpr_advskew; + uint8_t carpr_key[CARP_KEY_LEN]; +}; + +int ifconfig_carp_get_vhid(ifconfig_handle_t *h, const char *name, + struct ifconfig_carp *carpr, uint32_t vhid); int ifconfig_carp_get_info(ifconfig_handle_t *h, const char *name, - struct carpreq *carpr, int ncarpr); + struct ifconfig_carp *carpr, size_t ncarp); +int ifconfig_carp_set_info(ifconfig_handle_t *h, const char *name, + const struct ifconfig_carp *carpr); /** Retrieve additional information about an inet address * @param h An open ifconfig state object diff --git a/lib/libifconfig/libifconfig_carp.c b/lib/libifconfig/libifconfig_carp.c index cd31a85dfd9a..ffc497590aaa 100644 --- a/lib/libifconfig/libifconfig_carp.c +++ b/lib/libifconfig/libifconfig_carp.c @@ -33,6 +33,12 @@ #include #include +#include + +#include +#include +#include +#include #include #include @@ -40,21 +46,174 @@ #include "libifconfig.h" #include "libifconfig_internal.h" +#include -int -ifconfig_carp_get_info(ifconfig_handle_t *h, const char *name, - struct carpreq *carpr, int ncarpr) +#define _OUT(_field) offsetof(struct ifconfig_carp, _field) +static struct snl_attr_parser ap_carp_get[] = { + { .type = CARP_NL_VHID, .off = _OUT(carpr_vhid), .cb = snl_attr_get_uint32 }, + { .type = CARP_NL_STATE, .off = _OUT(carpr_state), .cb = snl_attr_get_uint32 }, + { .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 }, +}; +#undef _OUT + +SNL_DECLARE_GENL_PARSER(carp_get_parser, ap_carp_get); + +static int +_ifconfig_carp_get(ifconfig_handle_t *h, const char *name, + struct ifconfig_carp *carp, size_t ncarp, uint32_t vhid) { - struct ifreq ifr; + struct snl_state ss = {}; + struct snl_errmsg_data e = {}; + struct snl_writer nw; + struct nlmsghdr *hdr; + size_t i = 0; + uint32_t seq_id; + unsigned int ifindex; + int family_id; - bzero(carpr, sizeof(struct carpreq) * ncarpr); - carpr[0].carpr_count = ncarpr; - strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name)); - ifr.ifr_data = (caddr_t)carpr; + ifconfig_error_clear(h); - if (ifconfig_ioctlwrap(h, AF_LOCAL, SIOCGVH, &ifr) != 0) { + ifindex = if_nametoindex(name); + if (ifindex == 0) { + ifconfig_error(h, NETLINK, ENOENT); return (-1); } - return (0); + if (! snl_init(&ss, NETLINK_GENERIC)) { + ifconfig_error(h, NETLINK, ENOTSUP); + return (-1); + } + + snl_init_writer(&ss, &nw); + + family_id = snl_get_genl_family(&ss, CARP_NL_FAMILY_NAME); + if (family_id == 0) { + ifconfig_error(h, NETLINK, EPROTONOSUPPORT); + goto out; + } + + hdr = snl_create_genl_msg_request(&nw, family_id, CARP_NL_CMD_GET); + hdr->nlmsg_flags |= NLM_F_DUMP; + + snl_add_msg_attr_u32(&nw, CARP_NL_IFINDEX, ifindex); + + if (vhid != 0) + snl_add_msg_attr_u32(&nw, CARP_NL_VHID, vhid); + + hdr = snl_finalize_msg(&nw); + if (hdr == NULL) { + ifconfig_error(h, NETLINK, ENOMEM); + goto out; + } + seq_id = hdr->nlmsg_seq; + if (! snl_send_message(&ss, hdr)) { + ifconfig_error(h, NETLINK, EIO); + goto out; + } + + while ((hdr = snl_read_reply_multi(&ss, seq_id, &e)) != NULL) { + if (e.error != 0) { + ifconfig_error(h, NETLINK, e.error); + break; + } + + if (i >= ncarp) { + ifconfig_error(h, NETLINK, E2BIG); + break; + } + + memset(&carp[i], 0, sizeof(carp[0])); + if (! snl_parse_nlmsg(&ss, hdr, &carp_get_parser, &carp[i])) + continue; + + i++; + carp[0].carpr_count = i; + + if (i > ncarp) { + ifconfig_error(h, NETLINK, E2BIG); + break; + } + } + +out: + snl_free(&ss); + + return (h->error.errcode ? -1 : 0); +} + +int +ifconfig_carp_set_info(ifconfig_handle_t *h, const char *name, + const struct ifconfig_carp *carpr) +{ + struct snl_state ss = {}; + struct snl_writer nw; + struct nlmsghdr *hdr; + unsigned int ifindex; + int family_id; + uint32_t seq_id; + + ifconfig_error_clear(h); + + ifindex = if_nametoindex(name); + if (ifindex == 0) { + ifconfig_error(h, NETLINK, ENOENT); + return (-1); + } + + if (! snl_init(&ss, NETLINK_GENERIC)) { + ifconfig_error(h, NETLINK, ENOTSUP); + return (-1); + } + + snl_init_writer(&ss, &nw); + + family_id = snl_get_genl_family(&ss, CARP_NL_FAMILY_NAME); + if (family_id == 0) { + ifconfig_error(h, NETLINK, EPROTONOSUPPORT); + return (-1); + } + hdr = snl_create_genl_msg_request(&nw, family_id, CARP_NL_CMD_SET); + + snl_add_msg_attr_u32(&nw, CARP_NL_VHID, carpr->carpr_vhid); + snl_add_msg_attr_u32(&nw, CARP_NL_STATE, carpr->carpr_state); + 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); + + hdr = snl_finalize_msg(&nw); + if (hdr == NULL) { + ifconfig_error(h, NETLINK, ENOMEM); + goto out; + } + + seq_id = hdr->nlmsg_seq; + if (! snl_send_message(&ss, hdr)) { + ifconfig_error(h, NETLINK, EIO); + goto out; + } + + struct snl_errmsg_data e = { }; + if (! snl_read_reply_code(&ss, seq_id, &e)) + ifconfig_error(h, NETLINK, e.error); + +out: + snl_free(&ss); + + return (h->error.errcode ? -1 : 0); +} + +int +ifconfig_carp_get_vhid(ifconfig_handle_t *h, const char *name, + struct ifconfig_carp *carp, uint32_t vhid) +{ + return (_ifconfig_carp_get(h, name, carp, 1, vhid)); +} + +int +ifconfig_carp_get_info(ifconfig_handle_t *h, const char *name, + struct ifconfig_carp *carp, size_t ncarp) +{ + return (_ifconfig_carp_get(h, name, carp, ncarp, 0)); } diff --git a/lib/libifconfig/libifconfig_internal.c b/lib/libifconfig/libifconfig_internal.c index 6b7f767e23e0..7d40a30e07cb 100644 --- a/lib/libifconfig/libifconfig_internal.c +++ b/lib/libifconfig/libifconfig_internal.c @@ -102,3 +102,17 @@ ifconfig_socket(ifconfig_handle_t *h, const int addressfamily, int *s) *s = h->sockets[addressfamily]; return (0); } + +void +ifconfig_error_clear(ifconfig_handle_t *h) +{ + h->error.errtype = OK; + h->error.errcode = 0; +} + +void +ifconfig_error(ifconfig_handle_t *h, ifconfig_errtype type, int error) +{ + h->error.errtype = type; + h->error.errcode = error; +} diff --git a/lib/libifconfig/libifconfig_internal.h b/lib/libifconfig/libifconfig_internal.h index 77add7574fa8..0096f7ca94ea 100644 --- a/lib/libifconfig/libifconfig_internal.h +++ b/lib/libifconfig/libifconfig_internal.h @@ -83,3 +83,6 @@ int ifconfig_socket(ifconfig_handle_t *h, const int addressfamily, int *s); /** Function to wrap ioctl() and automatically populate ifconfig_errstate when appropriate.*/ int ifconfig_ioctlwrap(ifconfig_handle_t *h, const int addressfamily, unsigned long request, void *data); + +void ifconfig_error_clear(ifconfig_handle_t *h); +void ifconfig_error(ifconfig_handle_t *h, ifconfig_errtype type, int error); diff --git a/sbin/ifconfig/carp.c b/sbin/ifconfig/carp.c index 23a119e3b9d7..3fa6f3ac269d 100644 --- a/sbin/ifconfig/carp.c +++ b/sbin/ifconfig/carp.c @@ -72,7 +72,7 @@ static unsigned char const *carpr_key; static void carp_status(int s) { - struct carpreq carpr[CARP_MAXVHID]; + struct ifconfig_carp carpr[CARP_MAXVHID]; if (ifconfig_carp_get_info(lifh, name, carpr, CARP_MAXVHID) == -1) return; @@ -129,16 +129,14 @@ setcarp_vhid(const char *val, int d, int s, const struct afswtch *afp) static void setcarp_callback(int s, void *arg __unused) { - struct carpreq carpr; + struct ifconfig_carp carpr = { }; + + if (ifconfig_carp_get_vhid(lifh, name, &carpr, carpr_vhid) == -1) { + if (ifconfig_err_errno(lifh) != ENOENT) + return; + } - bzero(&carpr, sizeof(struct carpreq)); carpr.carpr_vhid = carpr_vhid; - carpr.carpr_count = 1; - ifr.ifr_data = (caddr_t)&carpr; - - if (ioctl(s, SIOCGVH, (caddr_t)&ifr) == -1 && errno != ENOENT) - err(1, "SIOCGVH"); - if (carpr_key != NULL) /* XXX Should hash the password into the key here? */ strlcpy(carpr.carpr_key, carpr_key, CARP_KEY_LEN); @@ -149,7 +147,7 @@ setcarp_callback(int s, void *arg __unused) if (carpr_state > -1) carpr.carpr_state = carpr_state; - if (ioctl(s, SIOCSVH, (caddr_t)&ifr) == -1) + if (ifconfig_carp_set_info(lifh, name, &carpr)) err(1, "SIOCSVH"); } diff --git a/sys/netinet/ip_carp.c b/sys/netinet/ip_carp.c index 1b8b10b06168..78bc6d312abb 100644 --- a/sys/netinet/ip_carp.c +++ b/sys/netinet/ip_carp.c @@ -67,6 +67,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include #endif @@ -84,6 +85,11 @@ __FBSDID("$FreeBSD$"); #include #endif +#include +#include +#include +#include + #include static MALLOC_DEFINE(M_CARP, "CARP", "CARP addresses"); @@ -332,6 +338,24 @@ static struct sx carp_sx; static struct task carp_sendall_task = TASK_INITIALIZER(0, carp_send_ad_all, NULL); +static int +carp_is_supported_if(if_t ifp) +{ + if (ifp == NULL) + return (ENXIO); + + switch (ifp->if_type) { + case IFT_ETHER: + case IFT_L2VLAN: + case IFT_BRIDGE: + break; + default: + return (EOPNOTSUPP); + } + + return (0); +} + static void carp_hmac_prepare(struct carp_softc *sc) { @@ -1709,9 +1733,10 @@ carp_free_if(struct carp_if *cif) free(cif, M_CARP); } -static void -carp_carprcp(struct carpreq *carpr, struct carp_softc *sc, int priv) +static bool +carp_carprcp(void *arg, struct carp_softc *sc, int priv) { + struct carpreq *carpr = arg; CARP_LOCK(sc); carpr->carpr_state = sc->sc_state; @@ -1723,33 +1748,142 @@ carp_carprcp(struct carpreq *carpr, struct carp_softc *sc, int priv) else bzero(carpr->carpr_key, sizeof(carpr->carpr_key)); CARP_UNLOCK(sc); + + return (true); +} + +static int +carp_ioctl_set(if_t ifp, struct carpreq *carpr) +{ + struct epoch_tracker et; + struct carp_softc *sc = NULL; + int error = 0; + + + if (carpr->carpr_vhid <= 0 || carpr->carpr_vhid > CARP_MAXVHID || + carpr->carpr_advbase < 0 || carpr->carpr_advskew < 0) { + return (EINVAL); + } + + if (ifp->if_carp) { + IFNET_FOREACH_CARP(ifp, sc) + if (sc->sc_vhid == carpr->carpr_vhid) + break; + } + if (sc == NULL) { + sc = carp_alloc(ifp); + CARP_LOCK(sc); + sc->sc_vhid = carpr->carpr_vhid; + LLADDR(&sc->sc_addr)[0] = 0; + LLADDR(&sc->sc_addr)[1] = 0; + LLADDR(&sc->sc_addr)[2] = 0x5e; + LLADDR(&sc->sc_addr)[3] = 0; + LLADDR(&sc->sc_addr)[4] = 1; + LLADDR(&sc->sc_addr)[5] = sc->sc_vhid; + } else + CARP_LOCK(sc); + if (carpr->carpr_advbase > 0) { + if (carpr->carpr_advbase > 255 || + carpr->carpr_advbase < CARP_DFLTINTV) { + error = EINVAL; + goto out; + } + sc->sc_advbase = carpr->carpr_advbase; + } + if (carpr->carpr_advskew >= 255) { + error = EINVAL; + goto out; + } + sc->sc_advskew = carpr->carpr_advskew; + if (carpr->carpr_key[0] != '\0') { + bcopy(carpr->carpr_key, sc->sc_key, sizeof(sc->sc_key)); + carp_hmac_prepare(sc); + } + if (sc->sc_state != INIT && + carpr->carpr_state != sc->sc_state) { + switch (carpr->carpr_state) { + case BACKUP: + callout_stop(&sc->sc_ad_tmo); + carp_set_state(sc, BACKUP, + "user requested via ifconfig"); + carp_setrun(sc, 0); + carp_delroute(sc); + break; + case MASTER: + NET_EPOCH_ENTER(et); + carp_master_down_locked(sc, + "user requested via ifconfig"); + NET_EPOCH_EXIT(et); + break; + default: + break; + } + } + +out: + CARP_UNLOCK(sc); + + return (error); +} + +static int +carp_ioctl_get(if_t ifp, struct ucred *cred, struct carpreq *carpr, + bool (*outfn)(void *, struct carp_softc *, int), void *arg) +{ + int priveleged; + struct carp_softc *sc; + + if (carpr->carpr_vhid < 0 || carpr->carpr_vhid > CARP_MAXVHID) + return (EINVAL); + if (carpr->carpr_count < 1) + return (EMSGSIZE); + if (ifp->if_carp == NULL) + return (ENOENT); + + priveleged = (priv_check_cred(cred, PRIV_NETINET_CARP) == 0); + if (carpr->carpr_vhid != 0) { + IFNET_FOREACH_CARP(ifp, sc) + if (sc->sc_vhid == carpr->carpr_vhid) + break; + if (sc == NULL) + return (ENOENT); + + if (! outfn(arg, sc, priveleged)) + return (ENOMEM); + carpr->carpr_count = 1; + } else { + int count; + + count = 0; + IFNET_FOREACH_CARP(ifp, sc) + count++; + + if (count > carpr->carpr_count) + return (EMSGSIZE); + + IFNET_FOREACH_CARP(ifp, sc) { + if (! outfn(arg, sc, priveleged)) + return (ENOMEM); + carpr->carpr_count = count; + } + } + + return (0); } int carp_ioctl(struct ifreq *ifr, u_long cmd, struct thread *td) { - struct epoch_tracker et; struct carpreq carpr; struct ifnet *ifp; - struct carp_softc *sc = NULL; - int error = 0, locked = 0; + int error = 0; if ((error = copyin(ifr_data_get_ptr(ifr), &carpr, sizeof carpr))) return (error); ifp = ifunit_ref(ifr->ifr_name); - if (ifp == NULL) - return (ENXIO); - - switch (ifp->if_type) { - case IFT_ETHER: - case IFT_L2VLAN: - case IFT_BRIDGE: - break; - default: - error = EOPNOTSUPP; + if ((error = carp_is_supported_if(ifp)) != 0) goto out; - } if ((ifp->if_flags & IFF_MULTICAST) == 0) { error = EADDRNOTAVAIL; @@ -1761,136 +1895,27 @@ carp_ioctl(struct ifreq *ifr, u_long cmd, struct thread *td) case SIOCSVH: if ((error = priv_check(td, PRIV_NETINET_CARP))) break; - if (carpr.carpr_vhid <= 0 || carpr.carpr_vhid > CARP_MAXVHID || - carpr.carpr_advbase < 0 || carpr.carpr_advskew < 0) { - error = EINVAL; - break; - } - if (ifp->if_carp) { - IFNET_FOREACH_CARP(ifp, sc) - if (sc->sc_vhid == carpr.carpr_vhid) - break; - } - if (sc == NULL) { - sc = carp_alloc(ifp); - CARP_LOCK(sc); - sc->sc_vhid = carpr.carpr_vhid; - LLADDR(&sc->sc_addr)[0] = 0; - LLADDR(&sc->sc_addr)[1] = 0; - LLADDR(&sc->sc_addr)[2] = 0x5e; - LLADDR(&sc->sc_addr)[3] = 0; - LLADDR(&sc->sc_addr)[4] = 1; - LLADDR(&sc->sc_addr)[5] = sc->sc_vhid; - } else - CARP_LOCK(sc); - locked = 1; - if (carpr.carpr_advbase > 0) { - if (carpr.carpr_advbase > 255 || - carpr.carpr_advbase < CARP_DFLTINTV) { - error = EINVAL; - break; - } - sc->sc_advbase = carpr.carpr_advbase; - } - if (carpr.carpr_advskew >= 255) { - error = EINVAL; - break; - } - sc->sc_advskew = carpr.carpr_advskew; - if (carpr.carpr_key[0] != '\0') { - bcopy(carpr.carpr_key, sc->sc_key, sizeof(sc->sc_key)); - carp_hmac_prepare(sc); - } - if (sc->sc_state != INIT && - carpr.carpr_state != sc->sc_state) { - switch (carpr.carpr_state) { - case BACKUP: - callout_stop(&sc->sc_ad_tmo); - carp_set_state(sc, BACKUP, - "user requested via ifconfig"); - carp_setrun(sc, 0); - carp_delroute(sc); - break; - case MASTER: - NET_EPOCH_ENTER(et); - carp_master_down_locked(sc, - "user requested via ifconfig"); - NET_EPOCH_EXIT(et); - break; - default: - break; - } - } + error = carp_ioctl_set(ifp, &carpr); break; case SIOCGVH: - { - int priveleged; - - if (carpr.carpr_vhid < 0 || carpr.carpr_vhid > CARP_MAXVHID) { - error = EINVAL; - break; - } - if (carpr.carpr_count < 1) { - error = EMSGSIZE; - break; - } - if (ifp->if_carp == NULL) { - error = ENOENT; - break; - } - - priveleged = (priv_check(td, PRIV_NETINET_CARP) == 0); - if (carpr.carpr_vhid != 0) { - IFNET_FOREACH_CARP(ifp, sc) - if (sc->sc_vhid == carpr.carpr_vhid) - break; - if (sc == NULL) { - error = ENOENT; - break; - } - carp_carprcp(&carpr, sc, priveleged); - error = copyout(&carpr, ifr_data_get_ptr(ifr), - sizeof(carpr)); - } else { - int i, count; - - count = 0; - IFNET_FOREACH_CARP(ifp, sc) - count++; - - if (count > carpr.carpr_count) { - CIF_UNLOCK(ifp->if_carp); - error = EMSGSIZE; - break; - } - - i = 0; - IFNET_FOREACH_CARP(ifp, sc) { - carp_carprcp(&carpr, sc, priveleged); - carpr.carpr_count = count; - error = copyout(&carpr, - (char *)ifr_data_get_ptr(ifr) + - (i * sizeof(carpr)), sizeof(carpr)); - if (error) { - CIF_UNLOCK(ifp->if_carp); - break; - } - i++; - } + error = carp_ioctl_get(ifp, td->td_ucred, &carpr, + carp_carprcp, &carpr); + if (error == 0) { + error = copyout(&carpr, + (char *)ifr_data_get_ptr(ifr), + carpr.carpr_count * sizeof(carpr)); } break; - } default: error = EINVAL; } sx_xunlock(&carp_sx); out: - if (locked) - CARP_UNLOCK(sc); - if_rele(ifp); + if (ifp != NULL) + if_rele(ifp); return (error); } @@ -2173,10 +2198,238 @@ carp_demote_adj_sysctl(SYSCTL_HANDLER_ARGS) return (0); } +static int +nlattr_get_carp_key(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target) +{ + if (__predict_false(NLA_DATA_LEN(nla) > CARP_KEY_LEN)) + return (EINVAL); + + memcpy(target, NLA_DATA_CONST(nla), NLA_DATA_LEN(nla)); + return (0); +} + +struct carp_nl_send_args { + struct nlmsghdr *hdr; + struct nl_pstate *npt; +}; + +static bool +carp_nl_send(void *arg, struct carp_softc *sc, int priv) +{ + struct carp_nl_send_args *nlsa = arg; + struct nlmsghdr *hdr = nlsa->hdr; + struct nl_pstate *npt = nlsa->npt; + struct nl_writer *nw = npt->nw; + struct genlmsghdr *ghdr_new; + + if (!nlmsg_reply(nw, hdr, sizeof(struct genlmsghdr))) { + nlmsg_abort(nw); + return (false); + } + + ghdr_new = nlmsg_reserve_object(nw, struct genlmsghdr); + if (ghdr_new == NULL) { + nlmsg_abort(nw); + return (false); + } + + ghdr_new->cmd = CARP_NL_CMD_GET; + ghdr_new->version = 0; + ghdr_new->reserved = 0; + + CARP_LOCK(sc); + + nlattr_add_u32(nw, CARP_NL_VHID, sc->sc_vhid); + 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); + + if (priv) + nlattr_add(nw, CARP_NL_KEY, sizeof(sc->sc_key), sc->sc_key); + + CARP_UNLOCK(sc); + + if (! nlmsg_end(nw)) { + nlmsg_abort(nw); + return (false); + } + + return (true); +} + +struct nl_carp_parsed { + unsigned int ifindex; + uint32_t state; + uint32_t vhid; + int32_t advbase; + int32_t advskew; + char key[CARP_KEY_LEN]; +}; + +#define _IN(_field) offsetof(struct genlmsghdr, _field) +#define _OUT(_field) offsetof(struct nl_carp_parsed, _field) + +static const struct nlattr_parser nla_p_set[] = { + { .type = CARP_NL_VHID, .off = _OUT(vhid), .cb = nlattr_get_uint32 }, + { .type = CARP_NL_STATE, .off = _OUT(state), .cb = nlattr_get_uint32 }, + { .type = CARP_NL_ADVBASE, .off = _OUT(advbase), .cb = nlattr_get_uint32 }, + { .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 }, +}; +static const struct nlfield_parser nlf_p_set[] = { +}; +NL_DECLARE_PARSER(carp_parser, struct genlmsghdr, nlf_p_set, nla_p_set); +#undef _IN +#undef _OUT + + +static int +carp_nl_get(struct nlmsghdr *hdr, struct nl_pstate *npt) +{ + struct nl_carp_parsed attrs = { }; + struct carp_nl_send_args args; + struct carpreq carpr = { }; + struct epoch_tracker et; + if_t ifp; + int error; + + error = nl_parse_nlmsg(hdr, &carp_parser, npt, &attrs); + if (error != 0) + return (error); + + NET_EPOCH_ENTER(et); + ifp = ifnet_byindex_ref(attrs.ifindex); + NET_EPOCH_EXIT(et); + + if ((error = carp_is_supported_if(ifp)) != 0) + goto out; + + hdr->nlmsg_flags |= NLM_F_MULTI; + args.hdr = hdr; + args.npt = npt; + + carpr.carpr_vhid = attrs.vhid; + carpr.carpr_count = CARP_MAXVHID; + + sx_xlock(&carp_sx); + error = carp_ioctl_get(ifp, nlp_get_cred(npt->nlp), &carpr, + carp_nl_send, &args); + sx_xunlock(&carp_sx); + + if (! nlmsg_end_dump(npt->nw, error, hdr)) + error = ENOMEM; + +out: + if (ifp != NULL) + if_rele(ifp); + + return (error); +} + +static int +carp_nl_set(struct nlmsghdr *hdr, struct nl_pstate *npt) +{ + struct nl_carp_parsed attrs = { }; + struct carpreq carpr; + struct epoch_tracker et; + if_t ifp; + int error; + + error = nl_parse_nlmsg(hdr, &carp_parser, npt, &attrs); + if (error != 0) + return (error); + + if (attrs.vhid <= 0 || attrs.vhid > CARP_MAXVHID) + return (EINVAL); + if (attrs.state > CARP_MAXSTATE) + return (EINVAL); + if (attrs.advbase < 0 || attrs.advskew < 0) + return (EINVAL); + if (attrs.advbase > 255) + return (EINVAL); + if (attrs.advskew >= 255) + return (EINVAL); + + NET_EPOCH_ENTER(et); + ifp = ifnet_byindex_ref(attrs.ifindex); + NET_EPOCH_EXIT(et); + + if ((error = carp_is_supported_if(ifp)) != 0) + goto out; + + if ((ifp->if_flags & IFF_MULTICAST) == 0) { + error = EADDRNOTAVAIL; + goto out; + } + + carpr.carpr_count = 1; + carpr.carpr_vhid = attrs.vhid; + carpr.carpr_state = attrs.state; + carpr.carpr_advbase = attrs.advbase; + carpr.carpr_advskew = attrs.advskew; + memcpy(&carpr.carpr_key, &attrs.key, sizeof(attrs.key)); + + sx_xlock(&carp_sx); + error = carp_ioctl_set(ifp, &carpr); + sx_xunlock(&carp_sx); + +out: + if (ifp != NULL) + if_rele(ifp); + + return (error); +} + +static const struct nlhdr_parser *all_parsers[] = { + &carp_parser +}; + +static const struct genl_cmd carp_cmds[] = { + { + .cmd_num = CARP_NL_CMD_GET, + .cmd_name = "SIOCGVH", + .cmd_cb = carp_nl_get, + .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_DUMP | + GENL_CMD_CAP_HASPOL, + }, + { + .cmd_num = CARP_NL_CMD_SET, + .cmd_name = "SIOCSVH", + .cmd_cb = carp_nl_set, + .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_HASPOL, + .cmd_priv = PRIV_NETINET_CARP, + }, +}; + +static void +carp_nl_register(void) +{ + bool ret __diagused; + int family_id __diagused; + + NL_VERIFY_PARSERS(all_parsers); + family_id = genl_register_family(CARP_NL_FAMILY_NAME, 0, 2, + CARP_NL_CMD_MAX); + MPASS(family_id != 0); + + ret = genl_register_cmds(CARP_NL_FAMILY_NAME, carp_cmds, + NL_ARRAY_LEN(carp_cmds)); + MPASS(ret); +} + +static void +carp_nl_unregister(void) +{ + genl_unregister_family(CARP_NL_FAMILY_NAME); +} + static void carp_mod_cleanup(void) { + carp_nl_unregister(); + #ifdef INET (void)ipproto_unregister(IPPROTO_CARP); carp_iamatch_p = NULL; @@ -2246,6 +2499,9 @@ carp_mod_load(void) return (err); } #endif + + carp_nl_register(); + return (0); } diff --git a/sys/netinet/ip_carp_nl.h b/sys/netinet/ip_carp_nl.h new file mode 100644 index 000000000000..ffb7c1ef5aae --- /dev/null +++ b/sys/netinet/ip_carp_nl.h @@ -0,0 +1,34 @@ +#ifndef _IP_CARP_NL_H +#define _IP_CARP_NL_H + +#include + +#include +#include + +/* + * Netlink interface to carp(4). + */ + +#define CARP_NL_FAMILY_NAME "carp" + +/* commands */ +enum { + CARP_NL_CMD_UNSPEC = 0, + CARP_NL_CMD_GET = 1, + CARP_NL_CMD_SET = 2, + __CARP_NL_CMD_MAX, +}; +#define CARP_NL_CMD_MAX (__CARP_NL_CMD_MAX - 1) + +enum carp_nl_type_t { + CARP_NL_UNSPEC, + CARP_NL_VHID = 1, /* u32 */ + CARP_NL_STATE = 2, /* u32 */ + CARP_NL_ADVBASE = 3, /* s32 */ + CARP_NL_ADVSKEW = 4, /* s32 */ + CARP_NL_KEY = 5, /* byte array */ + CARP_NL_IFINDEX = 6, /* u32 */ +}; + +#endif