The branch main has been updated by kp:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=40e0435964094dfda21089d9989197999c06c4bc

commit 40e0435964094dfda21089d9989197999c06c4bc
Author:     Kristof Provost <[email protected]>
AuthorDate: 2023-03-07 18:17:09 +0000
Commit:     Kristof Provost <[email protected]>
CommitDate: 2023-03-20 09:52:27 +0000

    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     | 177 ++++++++++-
 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, 634 insertions(+), 156 deletions(-)

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 <net/if.h>
 
 #include <netinet/in.h>
+#include <netinet/ip_carp.h>
 #include <netinet6/in6_var.h>
 
 #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 <net/if.h>
 #include <netinet/ip_carp.h>
+#include <netinet/ip_carp_nl.h>
+
+#include <netlink/netlink.h>
+#include <netlink/netlink_generic.h>
+#include <netlink/netlink_snl.h>
+#include <netlink/netlink_snl_generic.h>
 
 #include <string.h>
 #include <strings.h>
@@ -40,21 +46,174 @@
 #include "libifconfig.h"
 #include "libifconfig_internal.h"
 
+#include <stdio.h>
+
+#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 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;
+
+       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);
+               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_get_info(ifconfig_handle_t *h, const char *name,
-    struct carpreq *carpr, int ncarpr)
+ifconfig_carp_set_info(ifconfig_handle_t *h, const char *name,
+    const struct ifconfig_carp *carpr)
 {
-       struct ifreq ifr;
+       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);
+       }
 
-       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;
+       snl_init_writer(&ss, &nw);
 
-       if (ifconfig_ioctlwrap(h, AF_LOCAL, SIOCGVH, &ifr) != 0) {
+       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;
+       }
 
-       return (0);
+       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 = { };
 
-       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 (ifconfig_carp_get_vhid(lifh, name, &carpr, carpr_vhid) == -1) {
+               if (ifconfig_err_errno(lifh) != ENOENT)
+                       return;
+       }
 
+       carpr.carpr_vhid = carpr_vhid;
        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 <netinet/in.h>
 #include <netinet/in_var.h>
 #include <netinet/ip_carp.h>
+#include <netinet/ip_carp_nl.h>
 #include <netinet/ip.h>
 #include <machine/in_cksum.h>
 #endif
@@ -84,6 +85,11 @@ __FBSDID("$FreeBSD$");
 #include <netinet6/nd6.h>
 #endif
 
+#include <netlink/netlink.h>
+#include <netlink/netlink_ctl.h>
+#include <netlink/netlink_generic.h>
+#include <netlink/netlink_message_parser.h>
+
 #include <crypto/sha1.h>
 
 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 <net/if.h>
+
+#include <netinet/ip_carp.h>
+#include <netlink/netlink_generic.h>
*** 27 LINES SKIPPED ***

Reply via email to