diff --git a/lib/libifconfig/libifconfig.h b/lib/libifconfig/libifconfig.h --- a/lib/libifconfig/libifconfig.h +++ b/lib/libifconfig/libifconfig.h @@ -287,6 +287,8 @@ 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, diff --git a/lib/libifconfig/libifconfig_carp.c b/lib/libifconfig/libifconfig_carp.c --- a/lib/libifconfig/libifconfig_carp.c +++ b/lib/libifconfig/libifconfig_carp.c @@ -39,6 +39,7 @@ #include #include #include +#include #include #include @@ -55,6 +56,8 @@ { .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 @@ 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) { diff --git a/sbin/ifconfig/carp.c b/sbin/ifconfig/carp.c --- a/sbin/ifconfig/carp.c +++ b/sbin/ifconfig/carp.c @@ -42,13 +42,17 @@ #include #include +#include + #include +#include #include #include #include #include #include #include +#include #include @@ -67,12 +71,15 @@ 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 @@ 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 @@ 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 @@ 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 @@ { 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); diff --git a/sys/netinet/ip_carp.c b/sys/netinet/ip_carp.c --- a/sys/netinet/ip_carp.c +++ b/sys/netinet/ip_carp.c @@ -110,6 +110,8 @@ 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 @@ #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 @@ (((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 @@ 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 @@ } 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 @@ 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 @@ 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 @@ } 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 @@ } 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 @@ uint64_t tmp_counter; struct timeval sc_tv, ch_tv; int error; + bool multicast = false; NET_EPOCH_ASSERT(); @@ -724,8 +723,21 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ } 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ { .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 @@ 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 @@ 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); diff --git a/sys/netinet/ip_carp_nl.h b/sys/netinet/ip_carp_nl.h --- a/sys/netinet/ip_carp_nl.h +++ b/sys/netinet/ip_carp_nl.h @@ -29,6 +29,8 @@ 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 diff --git a/sys/netlink/netlink_message_parser.h b/sys/netlink/netlink_message_parser.h --- a/sys/netlink/netlink_message_parser.h +++ b/sys/netlink/netlink_message_parser.h @@ -175,6 +175,10 @@ 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, diff --git a/sys/netlink/netlink_message_parser.c b/sys/netlink/netlink_message_parser.c --- a/sys/netlink/netlink_message_parser.c +++ b/sys/netlink/netlink_message_parser.c @@ -41,6 +41,7 @@ #include #include +#include #include #include #include @@ -349,6 +350,30 @@ 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) diff --git a/sys/netlink/netlink_message_writer.h b/sys/netlink/netlink_message_writer.h --- a/sys/netlink/netlink_message_writer.h +++ b/sys/netlink/netlink_message_writer.h @@ -30,6 +30,7 @@ #define _NETLINK_NETLINK_MESSAGE_WRITER_H_ #ifdef _KERNEL + /* * It is not meant to be included directly */ @@ -248,6 +249,12 @@ 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) { diff --git a/sys/netlink/netlink_message_writer.c b/sys/netlink/netlink_message_writer.c --- a/sys/netlink/netlink_message_writer.c +++ b/sys/netlink/netlink_message_writer.c @@ -37,6 +37,8 @@ #include #include +#include + #include #include #include @@ -686,3 +688,15 @@ 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)); +} diff --git a/sys/netlink/netlink_snl.h b/sys/netlink/netlink_snl.h --- a/sys/netlink/netlink_snl.h +++ b/sys/netlink/netlink_snl.h @@ -44,7 +44,6 @@ #include #include - #define _roundup2(x, y) (((x)+((y)-1))&(~((y)-1))) #define NETLINK_ALIGN_SIZE sizeof(uint32_t) diff --git a/sys/netlink/netlink_snl_route.h b/sys/netlink/netlink_snl_route.h --- a/sys/netlink/netlink_snl_route.h +++ b/sys/netlink/netlink_snl_route.h @@ -163,4 +163,27 @@ 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