| /* |
| * CDDL HEADER START |
| * |
| * The contents of this file are subject to the terms of the |
| * Common Development and Distribution License (the "License"). |
| * You may not use this file except in compliance with the License. |
| * |
| * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
| * or http://www.opensolaris.org/os/licensing. |
| * See the License for the specific language governing permissions |
| * and limitations under the License. |
| * |
| * When distributing Covered Code, include this CDDL HEADER in each |
| * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
| * If applicable, add the following below this CDDL HEADER, with the |
| * fields enclosed by brackets "[]" replaced with your own identifying |
| * information: Portions Copyright [yyyy] [name of copyright owner] |
| * |
| * CDDL HEADER END |
| */ |
| /* |
| * Copyright 2007 Sun Microsystems, Inc. All rights reserved. |
| * Use is subject to license terms. |
| */ |
| /* Copyright (c) 1990 Mentat Inc. */ |
| |
| #pragma ident "%Z%%M% %I% %E% SMI" |
| |
| #include <sys/types.h> |
| #include <sys/stream.h> |
| #include <sys/dlpi.h> |
| #include <sys/stropts.h> |
| #include <sys/strsun.h> |
| #include <sys/ddi.h> |
| #include <sys/cmn_err.h> |
| #include <sys/sdt.h> |
| #include <sys/zone.h> |
| |
| #include <sys/param.h> |
| #include <sys/socket.h> |
| #include <sys/sockio.h> |
| #include <net/if.h> |
| #include <sys/systm.h> |
| #include <net/route.h> |
| #include <netinet/in.h> |
| #include <net/if_dl.h> |
| #include <netinet/ip6.h> |
| #include <netinet/icmp6.h> |
| |
| #include <inet/common.h> |
| #include <inet/mi.h> |
| #include <inet/nd.h> |
| #include <inet/arp.h> |
| #include <inet/ip.h> |
| #include <inet/ip6.h> |
| #include <inet/ip_if.h> |
| #include <inet/ip_ndp.h> |
| #include <inet/ip_multi.h> |
| #include <inet/ipclassifier.h> |
| #include <inet/ipsec_impl.h> |
| #include <inet/sctp_ip.h> |
| #include <inet/ip_listutils.h> |
| #include <inet/udp_impl.h> |
| |
| /* igmpv3/mldv2 source filter manipulation */ |
| static void ilm_bld_flists(conn_t *conn, void *arg); |
| static void ilm_gen_filter(ilm_t *ilm, mcast_record_t *fmode, |
| slist_t *flist); |
| |
| static ilm_t *ilm_add_v6(ipif_t *ipif, const in6_addr_t *group, |
| ilg_stat_t ilgstat, mcast_record_t ilg_fmode, slist_t *ilg_flist, |
| int orig_ifindex, zoneid_t zoneid); |
| static void ilm_delete(ilm_t *ilm); |
| static int ip_ll_addmulti_v6(ipif_t *ipif, const in6_addr_t *group); |
| static int ip_ll_delmulti_v6(ipif_t *ipif, const in6_addr_t *group); |
| static ilg_t *ilg_lookup_ill_index_v6(conn_t *connp, |
| const in6_addr_t *v6group, int index); |
| static ilg_t *ilg_lookup_ipif(conn_t *connp, ipaddr_t group, |
| ipif_t *ipif); |
| static int ilg_add(conn_t *connp, ipaddr_t group, ipif_t *ipif, |
| mcast_record_t fmode, ipaddr_t src); |
| static int ilg_add_v6(conn_t *connp, const in6_addr_t *group, ill_t *ill, |
| mcast_record_t fmode, const in6_addr_t *v6src); |
| static void ilg_delete(conn_t *connp, ilg_t *ilg, const in6_addr_t *src); |
| static mblk_t *ill_create_dl(ill_t *ill, uint32_t dl_primitive, |
| uint32_t length, uint32_t *addr_lenp, uint32_t *addr_offp); |
| static mblk_t *ill_create_squery(ill_t *ill, ipaddr_t ipaddr, |
| uint32_t addrlen, uint32_t addroff, mblk_t *mp_tail); |
| static void conn_ilg_reap(conn_t *connp); |
| static int ip_opt_delete_group_excl(conn_t *connp, ipaddr_t group, |
| ipif_t *ipif, mcast_record_t fmode, ipaddr_t src); |
| static int ip_opt_delete_group_excl_v6(conn_t *connp, |
| const in6_addr_t *v6group, ill_t *ill, mcast_record_t fmode, |
| const in6_addr_t *v6src); |
| |
| /* |
| * MT notes: |
| * |
| * Multicast joins operate on both the ilg and ilm structures. Multiple |
| * threads operating on an conn (socket) trying to do multicast joins |
| * need to synchronize when operating on the ilg. Multiple threads |
| * potentially operating on different conn (socket endpoints) trying to |
| * do multicast joins could eventually end up trying to manipulate the |
| * ilm simulatenously and need to synchronize on the access to the ilm. |
| * Both are amenable to standard Solaris MT techniques, but it would be |
| * complex to handle a failover or failback which needs to manipulate |
| * ilg/ilms if an applications can also simultaenously join/leave |
| * multicast groups. Hence multicast join/leave also go through the ipsq_t |
| * serialization. |
| * |
| * Multicast joins and leaves are single-threaded per phyint/IPMP group |
| * using the ipsq serialization mechanism. |
| * |
| * An ilm is an IP data structure used to track multicast join/leave. |
| * An ilm is associated with a <multicast group, ipif> tuple in IPv4 and |
| * with just <multicast group> in IPv6. ilm_refcnt is the number of ilg's |
| * referencing the ilm. ilms are created / destroyed only as writer. ilms |
| * are not passed around, instead they are looked up and used under the |
| * ill_lock or as writer. So we don't need a dynamic refcount of the number |
| * of threads holding reference to an ilm. |
| * |
| * Multicast Join operation: |
| * |
| * The first step is to determine the ipif (v4) or ill (v6) on which |
| * the join operation is to be done. The join is done after becoming |
| * exclusive on the ipsq associated with the ipif or ill. The conn->conn_ilg |
| * and ill->ill_ilm are thus accessed and modified exclusively per ill. |
| * Multiple threads can attempt to join simultaneously on different ipif/ill |
| * on the same conn. In this case the ipsq serialization does not help in |
| * protecting the ilg. It is the conn_lock that is used to protect the ilg. |
| * The conn_lock also protects all the ilg_t members. |
| * |
| * Leave operation. |
| * |
| * Similar to the join operation, the first step is to determine the ipif |
| * or ill (v6) on which the leave operation is to be done. The leave operation |
| * is done after becoming exclusive on the ipsq associated with the ipif or ill. |
| * As with join ilg modification is done under the protection of the conn lock. |
| */ |
| |
| #define IPSQ_ENTER_IPIF(ipif, connp, first_mp, func, ipsq, type) \ |
| ASSERT(connp != NULL); \ |
| (ipsq) = ipsq_try_enter((ipif), NULL, CONNP_TO_WQ(connp), \ |
| (first_mp), (func), (type), B_TRUE); \ |
| if ((ipsq) == NULL) { \ |
| ipif_refrele(ipif); \ |
| return (EINPROGRESS); \ |
| } |
| |
| #define IPSQ_ENTER_ILL(ill, connp, first_mp, func, ipsq, type) \ |
| ASSERT(connp != NULL); \ |
| (ipsq) = ipsq_try_enter(NULL, ill, CONNP_TO_WQ(connp), \ |
| (first_mp), (func), (type), B_TRUE); \ |
| if ((ipsq) == NULL) { \ |
| ill_refrele(ill); \ |
| return (EINPROGRESS); \ |
| } |
| |
| #define IPSQ_EXIT(ipsq) \ |
| if (ipsq != NULL) \ |
| ipsq_exit(ipsq, B_TRUE, B_TRUE); |
| |
| #define ILG_WALKER_HOLD(connp) (connp)->conn_ilg_walker_cnt++ |
| |
| #define ILG_WALKER_RELE(connp) \ |
| { \ |
| (connp)->conn_ilg_walker_cnt--; \ |
| if ((connp)->conn_ilg_walker_cnt == 0) \ |
| conn_ilg_reap(connp); \ |
| } |
| |
| static void |
| conn_ilg_reap(conn_t *connp) |
| { |
| int to; |
| int from; |
| |
| ASSERT(MUTEX_HELD(&connp->conn_lock)); |
| |
| to = 0; |
| from = 0; |
| while (from < connp->conn_ilg_inuse) { |
| if (connp->conn_ilg[from].ilg_flags & ILG_DELETED) { |
| FREE_SLIST(connp->conn_ilg[from].ilg_filter); |
| from++; |
| continue; |
| } |
| if (to != from) |
| connp->conn_ilg[to] = connp->conn_ilg[from]; |
| to++; |
| from++; |
| } |
| |
| connp->conn_ilg_inuse = to; |
| |
| if (connp->conn_ilg_inuse == 0) { |
| mi_free((char *)connp->conn_ilg); |
| connp->conn_ilg = NULL; |
| cv_broadcast(&connp->conn_refcv); |
| } |
| } |
| |
| #define GETSTRUCT(structure, number) \ |
| ((structure *)mi_zalloc(sizeof (structure) * (number))) |
| |
| #define ILG_ALLOC_CHUNK 16 |
| |
| /* |
| * Returns a pointer to the next available ilg in conn_ilg. Allocs more |
| * buffers in size of ILG_ALLOC_CHUNK ilgs when needed, and updates conn's |
| * ilg tracking fields appropriately (conn_ilg_inuse reflects usage of the |
| * returned ilg). Returns NULL on failure (ENOMEM). |
| * |
| * Assumes connp->conn_lock is held. |
| */ |
| static ilg_t * |
| conn_ilg_alloc(conn_t *connp) |
| { |
| ilg_t *new; |
| int curcnt; |
| |
| ASSERT(MUTEX_HELD(&connp->conn_lock)); |
| ASSERT(connp->conn_ilg_inuse <= connp->conn_ilg_allocated); |
| |
| if (connp->conn_ilg == NULL) { |
| connp->conn_ilg = GETSTRUCT(ilg_t, ILG_ALLOC_CHUNK); |
| if (connp->conn_ilg == NULL) |
| return (NULL); |
| connp->conn_ilg_allocated = ILG_ALLOC_CHUNK; |
| connp->conn_ilg_inuse = 0; |
| } |
| if (connp->conn_ilg_inuse == connp->conn_ilg_allocated) { |
| curcnt = connp->conn_ilg_allocated; |
| new = GETSTRUCT(ilg_t, curcnt + ILG_ALLOC_CHUNK); |
| if (new == NULL) |
| return (NULL); |
| bcopy(connp->conn_ilg, new, sizeof (ilg_t) * curcnt); |
| mi_free((char *)connp->conn_ilg); |
| connp->conn_ilg = new; |
| connp->conn_ilg_allocated += ILG_ALLOC_CHUNK; |
| } |
| |
| return (&connp->conn_ilg[connp->conn_ilg_inuse++]); |
| } |
| |
| typedef struct ilm_fbld_s { |
| ilm_t *fbld_ilm; |
| int fbld_in_cnt; |
| int fbld_ex_cnt; |
| slist_t fbld_in; |
| slist_t fbld_ex; |
| boolean_t fbld_in_overflow; |
| } ilm_fbld_t; |
| |
| static void |
| ilm_bld_flists(conn_t *conn, void *arg) |
| { |
| int i; |
| ilm_fbld_t *fbld = (ilm_fbld_t *)(arg); |
| ilm_t *ilm = fbld->fbld_ilm; |
| in6_addr_t *v6group = &ilm->ilm_v6addr; |
| |
| if (conn->conn_ilg_inuse == 0) |
| return; |
| |
| /* |
| * Since we can't break out of the ipcl_walk once started, we still |
| * have to look at every conn. But if we've already found one |
| * (EXCLUDE, NULL) list, there's no need to keep checking individual |
| * ilgs--that will be our state. |
| */ |
| if (fbld->fbld_ex_cnt > 0 && fbld->fbld_ex.sl_numsrc == 0) |
| return; |
| |
| /* |
| * Check this conn's ilgs to see if any are interested in our |
| * ilm (group, interface match). If so, update the master |
| * include and exclude lists we're building in the fbld struct |
| * with this ilg's filter info. |
| */ |
| mutex_enter(&conn->conn_lock); |
| for (i = 0; i < conn->conn_ilg_inuse; i++) { |
| ilg_t *ilg = &conn->conn_ilg[i]; |
| if ((ilg->ilg_ill == ilm->ilm_ill) && |
| (ilg->ilg_ipif == ilm->ilm_ipif) && |
| IN6_ARE_ADDR_EQUAL(&ilg->ilg_v6group, v6group)) { |
| if (ilg->ilg_fmode == MODE_IS_INCLUDE) { |
| fbld->fbld_in_cnt++; |
| if (!fbld->fbld_in_overflow) |
| l_union_in_a(&fbld->fbld_in, |
| ilg->ilg_filter, |
| &fbld->fbld_in_overflow); |
| } else { |
| fbld->fbld_ex_cnt++; |
| /* |
| * On the first exclude list, don't try to do |
| * an intersection, as the master exclude list |
| * is intentionally empty. If the master list |
| * is still empty on later iterations, that |
| * means we have at least one ilg with an empty |
| * exclude list, so that should be reflected |
| * when we take the intersection. |
| */ |
| if (fbld->fbld_ex_cnt == 1) { |
| if (ilg->ilg_filter != NULL) |
| l_copy(ilg->ilg_filter, |
| &fbld->fbld_ex); |
| } else { |
| l_intersection_in_a(&fbld->fbld_ex, |
| ilg->ilg_filter); |
| } |
| } |
| /* there will only be one match, so break now. */ |
| break; |
| } |
| } |
| mutex_exit(&conn->conn_lock); |
| } |
| |
| static void |
| ilm_gen_filter(ilm_t *ilm, mcast_record_t *fmode, slist_t *flist) |
| { |
| ilm_fbld_t fbld; |
| ip_stack_t *ipst = ilm->ilm_ipst; |
| |
| fbld.fbld_ilm = ilm; |
| fbld.fbld_in_cnt = fbld.fbld_ex_cnt = 0; |
| fbld.fbld_in.sl_numsrc = fbld.fbld_ex.sl_numsrc = 0; |
| fbld.fbld_in_overflow = B_FALSE; |
| |
| /* first, construct our master include and exclude lists */ |
| ipcl_walk(ilm_bld_flists, (caddr_t)&fbld, ipst); |
| |
| /* now use those master lists to generate the interface filter */ |
| |
| /* if include list overflowed, filter is (EXCLUDE, NULL) */ |
| if (fbld.fbld_in_overflow) { |
| *fmode = MODE_IS_EXCLUDE; |
| flist->sl_numsrc = 0; |
| return; |
| } |
| |
| /* if nobody interested, interface filter is (INCLUDE, NULL) */ |
| if (fbld.fbld_in_cnt == 0 && fbld.fbld_ex_cnt == 0) { |
| *fmode = MODE_IS_INCLUDE; |
| flist->sl_numsrc = 0; |
| return; |
| } |
| |
| /* |
| * If there are no exclude lists, then the interface filter |
| * is INCLUDE, with its filter list equal to fbld_in. A single |
| * exclude list makes the interface filter EXCLUDE, with its |
| * filter list equal to (fbld_ex - fbld_in). |
| */ |
| if (fbld.fbld_ex_cnt == 0) { |
| *fmode = MODE_IS_INCLUDE; |
| l_copy(&fbld.fbld_in, flist); |
| } else { |
| *fmode = MODE_IS_EXCLUDE; |
| l_difference(&fbld.fbld_ex, &fbld.fbld_in, flist); |
| } |
| } |
| |
| /* |
| * If the given interface has failed, choose a new one to join on so |
| * that we continue to receive packets. ilg_orig_ifindex remembers |
| * what the application used to join on so that we know the ilg to |
| * delete even though we change the ill here. Callers will store the |
| * ilg returned from this function in ilg_ill. Thus when we receive |
| * a packet on ilg_ill, conn_wantpacket_v6 will deliver the packets. |
| * |
| * This function must be called as writer so we can walk the group |
| * list and examine flags without holding a lock. |
| */ |
| ill_t * |
| ip_choose_multi_ill(ill_t *ill, const in6_addr_t *grp) |
| { |
| ill_t *till; |
| ill_group_t *illgrp = ill->ill_group; |
| |
| ASSERT(IAM_WRITER_ILL(ill)); |
| |
| if (IN6_IS_ADDR_UNSPECIFIED(grp) || illgrp == NULL) |
| return (ill); |
| |
| if ((ill->ill_phyint->phyint_flags & (PHYI_FAILED|PHYI_INACTIVE)) == 0) |
| return (ill); |
| |
| till = illgrp->illgrp_ill; |
| while (till != NULL && |
| (till->ill_phyint->phyint_flags & (PHYI_FAILED|PHYI_INACTIVE))) { |
| till = till->ill_group_next; |
| } |
| if (till != NULL) |
| return (till); |
| |
| return (ill); |
| } |
| |
| static int |
| ilm_update_add(ilm_t *ilm, ilg_stat_t ilgstat, slist_t *ilg_flist, |
| boolean_t isv6) |
| { |
| mcast_record_t fmode; |
| slist_t *flist; |
| boolean_t fdefault; |
| char buf[INET6_ADDRSTRLEN]; |
| ill_t *ill = isv6 ? ilm->ilm_ill : ilm->ilm_ipif->ipif_ill; |
| |
| /* |
| * There are several cases where the ilm's filter state |
| * defaults to (EXCLUDE, NULL): |
| * - we've had previous joins without associated ilgs |
| * - this join has no associated ilg |
| * - the ilg's filter state is (EXCLUDE, NULL) |
| */ |
| fdefault = (ilm->ilm_no_ilg_cnt > 0) || |
| (ilgstat == ILGSTAT_NONE) || SLIST_IS_EMPTY(ilg_flist); |
| |
| /* attempt mallocs (if needed) before doing anything else */ |
| if ((flist = l_alloc()) == NULL) |
| return (ENOMEM); |
| if (!fdefault && ilm->ilm_filter == NULL) { |
| ilm->ilm_filter = l_alloc(); |
| if (ilm->ilm_filter == NULL) { |
| l_free(flist); |
| return (ENOMEM); |
| } |
| } |
| |
| if (ilgstat != ILGSTAT_CHANGE) |
| ilm->ilm_refcnt++; |
| |
| if (ilgstat == ILGSTAT_NONE) |
| ilm->ilm_no_ilg_cnt++; |
| |
| /* |
| * Determine new filter state. If it's not the default |
| * (EXCLUDE, NULL), we must walk the conn list to find |
| * any ilgs interested in this group, and re-build the |
| * ilm filter. |
| */ |
| if (fdefault) { |
| fmode = MODE_IS_EXCLUDE; |
| flist->sl_numsrc = 0; |
| } else { |
| ilm_gen_filter(ilm, &fmode, flist); |
| } |
| |
| /* make sure state actually changed; nothing to do if not. */ |
| if ((ilm->ilm_fmode == fmode) && |
| !lists_are_different(ilm->ilm_filter, flist)) { |
| l_free(flist); |
| return (0); |
| } |
| |
| /* send the state change report */ |
| if ((ill->ill_phyint->phyint_flags & PHYI_LOOPBACK) == 0) { |
| if (isv6) |
| mld_statechange(ilm, fmode, flist); |
| else |
| igmp_statechange(ilm, fmode, flist); |
| } |
| |
| /* update the ilm state */ |
| ilm->ilm_fmode = fmode; |
| if (flist->sl_numsrc > 0) |
| l_copy(flist, ilm->ilm_filter); |
| else |
| CLEAR_SLIST(ilm->ilm_filter); |
| |
| ip1dbg(("ilm_update: new if filter mode %d, group %s\n", ilm->ilm_fmode, |
| inet_ntop(AF_INET6, &ilm->ilm_v6addr, buf, sizeof (buf)))); |
| |
| l_free(flist); |
| return (0); |
| } |
| |
| static int |
| ilm_update_del(ilm_t *ilm, boolean_t isv6) |
| { |
| mcast_record_t fmode; |
| slist_t *flist; |
| ill_t *ill = isv6 ? ilm->ilm_ill : ilm->ilm_ipif->ipif_ill; |
| |
| ip1dbg(("ilm_update_del: still %d left; updating state\n", |
| ilm->ilm_refcnt)); |
| |
| if ((flist = l_alloc()) == NULL) |
| return (ENOMEM); |
| |
| /* |
| * If present, the ilg in question has already either been |
| * updated or removed from our list; so all we need to do |
| * now is walk the list to update the ilm filter state. |
| * |
| * Skip the list walk if we have any no-ilg joins, which |
| * cause the filter state to revert to (EXCLUDE, NULL). |
| */ |
| if (ilm->ilm_no_ilg_cnt != 0) { |
| fmode = MODE_IS_EXCLUDE; |
| flist->sl_numsrc = 0; |
| } else { |
| ilm_gen_filter(ilm, &fmode, flist); |
| } |
| |
| /* check to see if state needs to be updated */ |
| if ((ilm->ilm_fmode == fmode) && |
| (!lists_are_different(ilm->ilm_filter, flist))) { |
| l_free(flist); |
| return (0); |
| } |
| |
| if ((ill->ill_phyint->phyint_flags & PHYI_LOOPBACK) == 0) { |
| if (isv6) |
| mld_statechange(ilm, fmode, flist); |
| else |
| igmp_statechange(ilm, fmode, flist); |
| } |
| |
| ilm->ilm_fmode = fmode; |
| if (flist->sl_numsrc > 0) { |
| if (ilm->ilm_filter == NULL) { |
| ilm->ilm_filter = l_alloc(); |
| if (ilm->ilm_filter == NULL) { |
| char buf[INET6_ADDRSTRLEN]; |
| ip1dbg(("ilm_update_del: failed to alloc ilm " |
| "filter; no source filtering for %s on %s", |
| inet_ntop(AF_INET6, &ilm->ilm_v6addr, |
| buf, sizeof (buf)), ill->ill_name)); |
| ilm->ilm_fmode = MODE_IS_EXCLUDE; |
| l_free(flist); |
| return (0); |
| } |
| } |
| l_copy(flist, ilm->ilm_filter); |
| } else { |
| CLEAR_SLIST(ilm->ilm_filter); |
| } |
| |
| l_free(flist); |
| return (0); |
| } |
| |
| /* |
| * INADDR_ANY means all multicast addresses. This is only used |
| * by the multicast router. |
| * INADDR_ANY is stored as IPv6 unspecified addr. |
| */ |
| int |
| ip_addmulti(ipaddr_t group, ipif_t *ipif, ilg_stat_t ilgstat, |
| mcast_record_t ilg_fmode, slist_t *ilg_flist) |
| { |
| ill_t *ill = ipif->ipif_ill; |
| ilm_t *ilm; |
| in6_addr_t v6group; |
| int ret; |
| |
| ASSERT(IAM_WRITER_IPIF(ipif)); |
| |
| if (!CLASSD(group) && group != INADDR_ANY) |
| return (EINVAL); |
| |
| /* |
| * INADDR_ANY is represented as the IPv6 unspecifed addr. |
| */ |
| if (group == INADDR_ANY) |
| v6group = ipv6_all_zeros; |
| else |
| IN6_IPADDR_TO_V4MAPPED(group, &v6group); |
| |
| ilm = ilm_lookup_ipif(ipif, group); |
| if (ilm != NULL) |
| return (ilm_update_add(ilm, ilgstat, ilg_flist, B_FALSE)); |
| |
| /* |
| * ilms are associated with ipifs in IPv4. It moves with the |
| * ipif if the ipif moves to a new ill when the interface |
| * fails. Thus we really don't check whether the ipif_ill |
| * has failed like in IPv6. If it has FAILED the ipif |
| * will move (daemon will move it) and hence the ilm, if the |
| * ipif is not IPIF_NOFAILOVER. For the IPIF_NOFAILOVER ipifs, |
| * we continue to receive in the same place even if the |
| * interface fails. |
| */ |
| ilm = ilm_add_v6(ipif, &v6group, ilgstat, ilg_fmode, ilg_flist, |
| ill->ill_phyint->phyint_ifindex, ipif->ipif_zoneid); |
| if (ilm == NULL) |
| return (ENOMEM); |
| |
| if (group == INADDR_ANY) { |
| /* |
| * Check how many ipif's have members in this group - |
| * if more then one we should not tell the driver to join |
| * this time |
| */ |
| if (ilm_numentries_v6(ill, &v6group) > 1) |
| return (0); |
| if (ill->ill_group == NULL) |
| ret = ip_join_allmulti(ipif); |
| else |
| ret = ill_nominate_mcast_rcv(ill->ill_group); |
| if (ret != 0) |
| ilm_delete(ilm); |
| return (ret); |
| } |
| |
| if ((ill->ill_phyint->phyint_flags & PHYI_LOOPBACK) == 0) |
| igmp_joingroup(ilm); |
| |
| if (ilm_numentries_v6(ill, &v6group) > 1) |
| return (0); |
| |
| ret = ip_ll_addmulti_v6(ipif, &v6group); |
| if (ret != 0) |
| ilm_delete(ilm); |
| return (ret); |
| } |
| |
| /* |
| * The unspecified address means all multicast addresses. |
| * This is only used by the multicast router. |
| * |
| * ill identifies the interface to join on; it may not match the |
| * interface requested by the application of a failover has taken |
| * place. orig_ifindex always identifies the interface requested |
| * by the app. |
| * |
| * ilgstat tells us if there's an ilg associated with this join, |
| * and if so, if it's a new ilg or a change to an existing one. |
| * ilg_fmode and ilg_flist give us the current filter state of |
| * the ilg (and will be EXCLUDE {NULL} in the case of no ilg). |
| */ |
| int |
| ip_addmulti_v6(const in6_addr_t *v6group, ill_t *ill, int orig_ifindex, |
| zoneid_t zoneid, ilg_stat_t ilgstat, mcast_record_t ilg_fmode, |
| slist_t *ilg_flist) |
| { |
| ilm_t *ilm; |
| int ret; |
| |
| ASSERT(IAM_WRITER_ILL(ill)); |
| |
| if (!IN6_IS_ADDR_MULTICAST(v6group) && |
| !IN6_IS_ADDR_UNSPECIFIED(v6group)) { |
| return (EINVAL); |
| } |
| |
| /* |
| * An ilm is uniquely identified by the tuple of (group, ill, |
| * orig_ill). group is the multicast group address, ill is |
| * the interface on which it is currently joined, and orig_ill |
| * is the interface on which the application requested the |
| * join. orig_ill and ill are the same unless orig_ill has |
| * failed over. |
| * |
| * Both orig_ill and ill are required, which means we may have |
| * 2 ilms on an ill for the same group, but with different |
| * orig_ills. These must be kept separate, so that when failback |
| * occurs, the appropriate ilms are moved back to their orig_ill |
| * without disrupting memberships on the ill to which they had |
| * been moved. |
| * |
| * In order to track orig_ill, we store orig_ifindex in the |
| * ilm and ilg. |
| */ |
| ilm = ilm_lookup_ill_index_v6(ill, v6group, orig_ifindex, zoneid); |
| if (ilm != NULL) |
| return (ilm_update_add(ilm, ilgstat, ilg_flist, B_TRUE)); |
| |
| /* |
| * We need to remember where the application really wanted |
| * to join. This will be used later if we want to failback |
| * to the original interface. |
| */ |
| ilm = ilm_add_v6(ill->ill_ipif, v6group, ilgstat, ilg_fmode, |
| ilg_flist, orig_ifindex, zoneid); |
| if (ilm == NULL) |
| return (ENOMEM); |
| |
| if (IN6_IS_ADDR_UNSPECIFIED(v6group)) { |
| /* |
| * Check how many ipif's that have members in this group - |
| * if more then one we should not tell the driver to join |
| * this time |
| */ |
| if (ilm_numentries_v6(ill, v6group) > 1) |
| return (0); |
| if (ill->ill_group == NULL) |
| ret = ip_join_allmulti(ill->ill_ipif); |
| else |
| ret = ill_nominate_mcast_rcv(ill->ill_group); |
| |
| if (ret != 0) |
| ilm_delete(ilm); |
| return (ret); |
| } |
| |
| if ((ill->ill_phyint->phyint_flags & PHYI_LOOPBACK) == 0) |
| mld_joingroup(ilm); |
| |
| /* |
| * If we have more then one we should not tell the driver |
| * to join this time. |
| */ |
| if (ilm_numentries_v6(ill, v6group) > 1) |
| return (0); |
| |
| ret = ip_ll_addmulti_v6(ill->ill_ipif, v6group); |
| if (ret != 0) |
| ilm_delete(ilm); |
| return (ret); |
| } |
| |
| /* |
| * Send a multicast request to the driver for enabling multicast reception |
| * for v6groupp address. The caller has already checked whether it is |
| * appropriate to send one or not. |
| */ |
| int |
| ip_ll_send_enabmulti_req(ill_t *ill, const in6_addr_t *v6groupp) |
| { |
| mblk_t *mp; |
| uint32_t addrlen, addroff; |
| char group_buf[INET6_ADDRSTRLEN]; |
| |
| ASSERT(IAM_WRITER_ILL(ill)); |
| |
| /* |
| * Create a AR_ENTRY_SQUERY message with a dl_enabmulti_req tacked |
| * on. |
| */ |
| mp = ill_create_dl(ill, DL_ENABMULTI_REQ, sizeof (dl_enabmulti_req_t), |
| &addrlen, &addroff); |
| if (!mp) |
| return (ENOMEM); |
| if (IN6_IS_ADDR_V4MAPPED(v6groupp)) { |
| ipaddr_t v4group; |
| |
| IN6_V4MAPPED_TO_IPADDR(v6groupp, v4group); |
| /* |
| * NOTE!!! |
| * The "addroff" passed in here was calculated by |
| * ill_create_dl(), and will be used by ill_create_squery() |
| * to perform some twisted coding magic. It is the offset |
| * into the dl_xxx_req of the hw addr. Here, it will be |
| * added to b_wptr - b_rptr to create a magic number that |
| * is not an offset into this squery mblk. |
| * The actual hardware address will be accessed only in the |
| * dl_xxx_req, not in the squery. More importantly, |
| * that hardware address can *only* be accessed in this |
| * mblk chain by calling mi_offset_param_c(), which uses |
| * the magic number in the squery hw offset field to go |
| * to the *next* mblk (the dl_xxx_req), subtract the |
| * (b_wptr - b_rptr), and find the actual offset into |
| * the dl_xxx_req. |
| * Any method that depends on using the |
| * offset field in the dl_disabmulti_req or squery |
| * to find either hardware address will similarly fail. |
| * |
| * Look in ar_entry_squery() in arp.c to see how this offset |
| * is used. |
| */ |
| mp = ill_create_squery(ill, v4group, addrlen, addroff, mp); |
| if (!mp) |
| return (ENOMEM); |
| ip1dbg(("ip_ll_send_enabmulti_req: IPv4 putnext %s on %s\n", |
| inet_ntop(AF_INET6, v6groupp, group_buf, |
| sizeof (group_buf)), |
| ill->ill_name)); |
| putnext(ill->ill_rq, mp); |
| } else { |
| ip1dbg(("ip_ll_send_enabmulti_req: IPv6 ndp_squery_mp %s on" |
| " %s\n", |
| inet_ntop(AF_INET6, v6groupp, group_buf, |
| sizeof (group_buf)), |
| ill->ill_name)); |
| return (ndp_mcastreq(ill, v6groupp, addrlen, addroff, mp)); |
| } |
| return (0); |
| } |
| |
| /* |
| * Send a multicast request to the driver for enabling multicast |
| * membership for v6group if appropriate. |
| */ |
| static int |
| ip_ll_addmulti_v6(ipif_t *ipif, const in6_addr_t *v6groupp) |
| { |
| ill_t *ill = ipif->ipif_ill; |
| |
| ASSERT(IAM_WRITER_IPIF(ipif)); |
| |
| if (ill->ill_net_type != IRE_IF_RESOLVER || |
| ipif->ipif_flags & IPIF_POINTOPOINT) { |
| ip1dbg(("ip_ll_addmulti_v6: not resolver\n")); |
| return (0); /* Must be IRE_IF_NORESOLVER */ |
| } |
| |
| if (ill->ill_phyint->phyint_flags & PHYI_MULTI_BCAST) { |
| ip1dbg(("ip_ll_addmulti_v6: MULTI_BCAST\n")); |
| return (0); |
| } |
| if (ill->ill_ipif_up_count == 0) { |
| /* |
| * Nobody there. All multicast addresses will be re-joined |
| * when we get the DL_BIND_ACK bringing the interface up. |
| */ |
| ip1dbg(("ip_ll_addmulti_v6: nobody up\n")); |
| return (0); |
| } |
| return (ip_ll_send_enabmulti_req(ill, v6groupp)); |
| } |
| |
| /* |
| * INADDR_ANY means all multicast addresses. This is only used |
| * by the multicast router. |
| * INADDR_ANY is stored as the IPv6 unspecifed addr. |
| */ |
| int |
| ip_delmulti(ipaddr_t group, ipif_t *ipif, boolean_t no_ilg, boolean_t leaving) |
| { |
| ill_t *ill = ipif->ipif_ill; |
| ilm_t *ilm; |
| in6_addr_t v6group; |
| int ret; |
| |
| ASSERT(IAM_WRITER_IPIF(ipif)); |
| |
| if (!CLASSD(group) && group != INADDR_ANY) |
| return (EINVAL); |
| |
| /* |
| * INADDR_ANY is represented as the IPv6 unspecifed addr. |
| */ |
| if (group == INADDR_ANY) |
| v6group = ipv6_all_zeros; |
| else |
| IN6_IPADDR_TO_V4MAPPED(group, &v6group); |
| |
| /* |
| * Look for a match on the ipif. |
| * (IP_DROP_MEMBERSHIP specifies an ipif using an IP address). |
| */ |
| ilm = ilm_lookup_ipif(ipif, group); |
| if (ilm == NULL) |
| return (ENOENT); |
| |
| /* Update counters */ |
| if (no_ilg) |
| ilm->ilm_no_ilg_cnt--; |
| |
| if (leaving) |
| ilm->ilm_refcnt--; |
| |
| if (ilm->ilm_refcnt > 0) |
| return (ilm_update_del(ilm, B_FALSE)); |
| |
| if (group == INADDR_ANY) { |
| ilm_delete(ilm); |
| /* |
| * Check how many ipif's that have members in this group - |
| * if there are still some left then don't tell the driver |
| * to drop it. |
| */ |
| if (ilm_numentries_v6(ill, &v6group) != 0) |
| return (0); |
| |
| /* |
| * If we never joined, then don't leave. This can happen |
| * if we're in an IPMP group, since only one ill per IPMP |
| * group receives all multicast packets. |
| */ |
| if (!ill->ill_join_allmulti) { |
| ASSERT(ill->ill_group != NULL); |
| return (0); |
| } |
| |
| ret = ip_leave_allmulti(ipif); |
| if (ill->ill_group != NULL) |
| (void) ill_nominate_mcast_rcv(ill->ill_group); |
| return (ret); |
| } |
| |
| if ((ill->ill_phyint->phyint_flags & PHYI_LOOPBACK) == 0) |
| igmp_leavegroup(ilm); |
| |
| ilm_delete(ilm); |
| /* |
| * Check how many ipif's that have members in this group - |
| * if there are still some left then don't tell the driver |
| * to drop it. |
| */ |
| if (ilm_numentries_v6(ill, &v6group) != 0) |
| return (0); |
| return (ip_ll_delmulti_v6(ipif, &v6group)); |
| } |
| |
| /* |
| * The unspecified address means all multicast addresses. |
| * This is only used by the multicast router. |
| */ |
| int |
| ip_delmulti_v6(const in6_addr_t *v6group, ill_t *ill, int orig_ifindex, |
| zoneid_t zoneid, boolean_t no_ilg, boolean_t leaving) |
| { |
| ipif_t *ipif; |
| ilm_t *ilm; |
| int ret; |
| |
| ASSERT(IAM_WRITER_ILL(ill)); |
| |
| if (!IN6_IS_ADDR_MULTICAST(v6group) && |
| !IN6_IS_ADDR_UNSPECIFIED(v6group)) |
| return (EINVAL); |
| |
| /* |
| * Look for a match on the ill. |
| * (IPV6_LEAVE_GROUP specifies an ill using an ifindex). |
| * |
| * Similar to ip_addmulti_v6, we should always look using |
| * the orig_ifindex. |
| * |
| * 1) If orig_ifindex is different from ill's ifindex |
| * we should have an ilm with orig_ifindex created in |
| * ip_addmulti_v6. We should delete that here. |
| * |
| * 2) If orig_ifindex is same as ill's ifindex, we should |
| * not delete the ilm that is temporarily here because of |
| * a FAILOVER. Those ilms will have a ilm_orig_ifindex |
| * different from ill's ifindex. |
| * |
| * Thus, always lookup using orig_ifindex. |
| */ |
| ilm = ilm_lookup_ill_index_v6(ill, v6group, orig_ifindex, zoneid); |
| if (ilm == NULL) |
| return (ENOENT); |
| |
| ASSERT(ilm->ilm_ill == ill); |
| |
| ipif = ill->ill_ipif; |
| |
| /* Update counters */ |
| if (no_ilg) |
| ilm->ilm_no_ilg_cnt--; |
| |
| if (leaving) |
| ilm->ilm_refcnt--; |
| |
| if (ilm->ilm_refcnt > 0) |
| return (ilm_update_del(ilm, B_TRUE)); |
| |
| if (IN6_IS_ADDR_UNSPECIFIED(v6group)) { |
| ilm_delete(ilm); |
| /* |
| * Check how many ipif's that have members in this group - |
| * if there are still some left then don't tell the driver |
| * to drop it. |
| */ |
| if (ilm_numentries_v6(ill, v6group) != 0) |
| return (0); |
| |
| /* |
| * If we never joined, then don't leave. This can happen |
| * if we're in an IPMP group, since only one ill per IPMP |
| * group receives all multicast packets. |
| */ |
| if (!ill->ill_join_allmulti) { |
| ASSERT(ill->ill_group != NULL); |
| return (0); |
| } |
| |
| ret = ip_leave_allmulti(ipif); |
| if (ill->ill_group != NULL) |
| (void) ill_nominate_mcast_rcv(ill->ill_group); |
| return (ret); |
| } |
| |
| if ((ill->ill_phyint->phyint_flags & PHYI_LOOPBACK) == 0) |
| mld_leavegroup(ilm); |
| |
| ilm_delete(ilm); |
| /* |
| * Check how many ipif's that have members in this group - |
| * if there are still some left then don't tell the driver |
| * to drop it. |
| */ |
| if (ilm_numentries_v6(ill, v6group) != 0) |
| return (0); |
| return (ip_ll_delmulti_v6(ipif, v6group)); |
| } |
| |
| /* |
| * Send a multicast request to the driver for disabling multicast reception |
| * for v6groupp address. The caller has already checked whether it is |
| * appropriate to send one or not. |
| */ |
| int |
| ip_ll_send_disabmulti_req(ill_t *ill, const in6_addr_t *v6groupp) |
| { |
| mblk_t *mp; |
| char group_buf[INET6_ADDRSTRLEN]; |
| uint32_t addrlen, addroff; |
| |
| ASSERT(IAM_WRITER_ILL(ill)); |
| /* |
| * Create a AR_ENTRY_SQUERY message with a dl_disabmulti_req tacked |
| * on. |
| */ |
| mp = ill_create_dl(ill, DL_DISABMULTI_REQ, |
| sizeof (dl_disabmulti_req_t), &addrlen, &addroff); |
| |
| if (!mp) |
| return (ENOMEM); |
| |
| if (IN6_IS_ADDR_V4MAPPED(v6groupp)) { |
| ipaddr_t v4group; |
| |
| IN6_V4MAPPED_TO_IPADDR(v6groupp, v4group); |
| /* |
| * NOTE!!! |
| * The "addroff" passed in here was calculated by |
| * ill_create_dl(), and will be used by ill_create_squery() |
| * to perform some twisted coding magic. It is the offset |
| * into the dl_xxx_req of the hw addr. Here, it will be |
| * added to b_wptr - b_rptr to create a magic number that |
| * is not an offset into this mblk. |
| * |
| * Please see the comment in ip_ll_send)enabmulti_req() |
| * for a complete explanation. |
| * |
| * Look in ar_entry_squery() in arp.c to see how this offset |
| * is used. |
| */ |
| mp = ill_create_squery(ill, v4group, addrlen, addroff, mp); |
| if (!mp) |
| return (ENOMEM); |
| ip1dbg(("ip_ll_send_disabmulti_req: IPv4 putnext %s on %s\n", |
| inet_ntop(AF_INET6, v6groupp, group_buf, |
| sizeof (group_buf)), |
| ill->ill_name)); |
| putnext(ill->ill_rq, mp); |
| } else { |
| ip1dbg(("ip_ll_send_disabmulti_req: IPv6 ndp_squery_mp %s on" |
| " %s\n", |
| inet_ntop(AF_INET6, v6groupp, group_buf, |
| sizeof (group_buf)), |
| ill->ill_name)); |
| return (ndp_mcastreq(ill, v6groupp, addrlen, addroff, mp)); |
| } |
| return (0); |
| } |
| |
| /* |
| * Send a multicast request to the driver for disabling multicast |
| * membership for v6group if appropriate. |
| */ |
| static int |
| ip_ll_delmulti_v6(ipif_t *ipif, const in6_addr_t *v6group) |
| { |
| ill_t *ill = ipif->ipif_ill; |
| |
| ASSERT(IAM_WRITER_IPIF(ipif)); |
| |
| if (ill->ill_net_type != IRE_IF_RESOLVER || |
| ipif->ipif_flags & IPIF_POINTOPOINT) { |
| return (0); /* Must be IRE_IF_NORESOLVER */ |
| } |
| if (ill->ill_phyint->phyint_flags & PHYI_MULTI_BCAST) { |
| ip1dbg(("ip_ll_delmulti_v6: MULTI_BCAST\n")); |
| return (0); |
| } |
| if (ill->ill_ipif_up_count == 0) { |
| /* |
| * Nobody there. All multicast addresses will be re-joined |
| * when we get the DL_BIND_ACK bringing the interface up. |
| */ |
| ip1dbg(("ip_ll_delmulti_v6: nobody up\n")); |
| return (0); |
| } |
| return (ip_ll_send_disabmulti_req(ill, v6group)); |
| } |
| |
| /* |
| * Make the driver pass up all multicast packets |
| * |
| * With ill groups, the caller makes sure that there is only |
| * one ill joining the allmulti group. |
| */ |
| int |
| ip_join_allmulti(ipif_t *ipif) |
| { |
| ill_t *ill = ipif->ipif_ill; |
| mblk_t *mp; |
| uint32_t addrlen, addroff; |
| |
| ASSERT(IAM_WRITER_IPIF(ipif)); |
| |
| if (ill->ill_ipif_up_count == 0) { |
| /* |
| * Nobody there. All multicast addresses will be re-joined |
| * when we get the DL_BIND_ACK bringing the interface up. |
| */ |
| return (0); |
| } |
| |
| ASSERT(!ill->ill_join_allmulti); |
| |
| /* |
| * Create a DL_PROMISCON_REQ message and send it directly to |
| * the DLPI provider. We don't need to do this for certain |
| * media types for which we never need to turn promiscuous |
| * mode on. |
| */ |
| if ((ill->ill_net_type == IRE_IF_RESOLVER) && |
| !(ill->ill_phyint->phyint_flags & PHYI_MULTI_BCAST)) { |
| mp = ill_create_dl(ill, DL_PROMISCON_REQ, |
| sizeof (dl_promiscon_req_t), &addrlen, &addroff); |
| if (mp == NULL) |
| return (ENOMEM); |
| putnext(ill->ill_wq, mp); |
| } |
| |
| mutex_enter(&ill->ill_lock); |
| ill->ill_join_allmulti = B_TRUE; |
| mutex_exit(&ill->ill_lock); |
| return (0); |
| } |
| |
| /* |
| * Make the driver stop passing up all multicast packets |
| * |
| * With ill groups, we need to nominate some other ill as |
| * this ipif->ipif_ill is leaving the group. |
| */ |
| int |
| ip_leave_allmulti(ipif_t *ipif) |
| { |
| ill_t *ill = ipif->ipif_ill; |
| mblk_t *mp; |
| uint32_t addrlen, addroff; |
| |
| ASSERT(IAM_WRITER_IPIF(ipif)); |
| |
| if (ill->ill_ipif_up_count == 0) { |
| /* |
| * Nobody there. All multicast addresses will be re-joined |
| * when we get the DL_BIND_ACK bringing the interface up. |
| */ |
| return (0); |
| } |
| |
| ASSERT(ill->ill_join_allmulti); |
| |
| /* |
| * Create a DL_PROMISCOFF_REQ message and send it directly to |
| * the DLPI provider. We don't need to do this for certain |
| * media types for which we never need to turn promiscuous |
| * mode on. |
| */ |
| if ((ill->ill_net_type == IRE_IF_RESOLVER) && |
| !(ill->ill_phyint->phyint_flags & PHYI_MULTI_BCAST)) { |
| mp = ill_create_dl(ill, DL_PROMISCOFF_REQ, |
| sizeof (dl_promiscoff_req_t), &addrlen, &addroff); |
| if (mp == NULL) |
| return (ENOMEM); |
| putnext(ill->ill_wq, mp); |
| } |
| |
| mutex_enter(&ill->ill_lock); |
| ill->ill_join_allmulti = B_FALSE; |
| mutex_exit(&ill->ill_lock); |
| return (0); |
| } |
| |
| /* |
| * Copy mp_orig and pass it in as a local message. |
| */ |
| void |
| ip_multicast_loopback(queue_t *q, ill_t *ill, mblk_t *mp_orig, int fanout_flags, |
| zoneid_t zoneid) |
| { |
| mblk_t *mp; |
| mblk_t *ipsec_mp; |
| ipha_t *iph; |
| ip_stack_t *ipst = ill->ill_ipst; |
| |
| if (DB_TYPE(mp_orig) == M_DATA && |
| ((ipha_t *)mp_orig->b_rptr)->ipha_protocol == IPPROTO_UDP) { |
| uint_t hdrsz; |
| |
| hdrsz = IPH_HDR_LENGTH((ipha_t *)mp_orig->b_rptr) + |
| sizeof (udpha_t); |
| ASSERT(MBLKL(mp_orig) >= hdrsz); |
| |
| if (((mp = allocb(hdrsz, BPRI_MED)) != NULL) && |
| (mp_orig = dupmsg(mp_orig)) != NULL) { |
| bcopy(mp_orig->b_rptr, mp->b_rptr, hdrsz); |
| mp->b_wptr += hdrsz; |
| mp->b_cont = mp_orig; |
| mp_orig->b_rptr += hdrsz; |
| if (MBLKL(mp_orig) == 0) { |
| mp->b_cont = mp_orig->b_cont; |
| mp_orig->b_cont = NULL; |
| freeb(mp_orig); |
| } |
| } else if (mp != NULL) { |
| freeb(mp); |
| mp = NULL; |
| } |
| } else { |
| mp = ip_copymsg(mp_orig); /* No refcnt on ipsec_out netstack */ |
| } |
| |
| if (mp == NULL) |
| return; |
| if (DB_TYPE(mp) == M_CTL) { |
| ipsec_mp = mp; |
| mp = mp->b_cont; |
| } else { |
| ipsec_mp = mp; |
| } |
| |
| iph = (ipha_t *)mp->b_rptr; |
| |
| DTRACE_PROBE4(ip4__loopback__out__start, |
| ill_t *, NULL, ill_t *, ill, |
| ipha_t *, iph, mblk_t *, ipsec_mp); |
| |
| FW_HOOKS(ipst->ips_ip4_loopback_out_event, |
| ipst->ips_ipv4firewall_loopback_out, |
| NULL, ill, iph, ipsec_mp, mp, ipst); |
| |
| DTRACE_PROBE1(ip4__loopback__out__end, mblk_t *, ipsec_mp); |
| |
| if (ipsec_mp != NULL) |
| ip_wput_local(q, ill, iph, ipsec_mp, NULL, |
| fanout_flags, zoneid); |
| } |
| |
| static area_t ip_aresq_template = { |
| AR_ENTRY_SQUERY, /* cmd */ |
| sizeof (area_t)+IP_ADDR_LEN, /* name offset */ |
| sizeof (area_t), /* name len (filled by ill_arp_alloc) */ |
| IP_ARP_PROTO_TYPE, /* protocol, from arps perspective */ |
| sizeof (area_t), /* proto addr offset */ |
| IP_ADDR_LEN, /* proto addr_length */ |
| 0, /* proto mask offset */ |
| /* Rest is initialized when used */ |
| 0, /* flags */ |
| 0, /* hw addr offset */ |
| 0, /* hw addr length */ |
| }; |
| |
| static mblk_t * |
| ill_create_squery(ill_t *ill, ipaddr_t ipaddr, uint32_t addrlen, |
| uint32_t addroff, mblk_t *mp_tail) |
| { |
| mblk_t *mp; |
| area_t *area; |
| |
| mp = ill_arp_alloc(ill, (uchar_t *)&ip_aresq_template, |
| (caddr_t)&ipaddr); |
| if (!mp) { |
| freemsg(mp_tail); |
| return (NULL); |
| } |
| area = (area_t *)mp->b_rptr; |
| area->area_hw_addr_length = addrlen; |
| area->area_hw_addr_offset = mp->b_wptr - mp->b_rptr + addroff; |
| /* |
| * NOTE! |
| * |
| * The area_hw_addr_offset, as can be seen, does not hold the |
| * actual hardware address offset. Rather, it holds the offset |
| * to the hw addr in the dl_xxx_req in mp_tail, modified by |
| * adding (mp->b_wptr - mp->b_rptr). This allows the function |
| * mi_offset_paramc() to find the hardware address in the |
| * *second* mblk (dl_xxx_req), not this mblk. |
| * |
| * Using mi_offset_paramc() is thus the *only* way to access |
| * the dl_xxx_hw address. |
| * |
| * The squery hw address should *not* be accessed. |
| * |
| * See ar_entry_squery() in arp.c for an example of how all this works. |
| */ |
| |
| mp->b_cont = mp_tail; |
| return (mp); |
| } |
| |
| /* |
| * Create a dlpi message with room for phys+sap. When we come back in |
| * ip_wput_ctl() we will strip the sap for those primitives which |
| * only need a physical address. |
| */ |
| static mblk_t * |
| ill_create_dl(ill_t *ill, uint32_t dl_primitive, uint32_t length, |
| uint32_t *addr_lenp, uint32_t *addr_offp) |
| { |
| mblk_t *mp; |
| uint32_t hw_addr_length; |
| char *cp; |
| uint32_t offset; |
| uint32_t size; |
| |
| *addr_lenp = *addr_offp = 0; |
| |
| hw_addr_length = ill->ill_phys_addr_length; |
| if (!hw_addr_length) { |
| ip0dbg(("ip_create_dl: hw addr length = 0\n")); |
| return (NULL); |
| } |
| |
| size = length; |
| switch (dl_primitive) { |
| case DL_ENABMULTI_REQ: |
| case DL_DISABMULTI_REQ: |
| size += hw_addr_length; |
| break; |
| case DL_PROMISCON_REQ: |
| case DL_PROMISCOFF_REQ: |
| break; |
| default: |
| return (NULL); |
| } |
| mp = allocb(size, BPRI_HI); |
| if (!mp) |
| return (NULL); |
| mp->b_wptr += size; |
| mp->b_datap->db_type = M_PROTO; |
| |
| cp = (char *)mp->b_rptr; |
| offset = length; |
| |
| switch (dl_primitive) { |
| case DL_ENABMULTI_REQ: { |
| dl_enabmulti_req_t *dl = (dl_enabmulti_req_t *)cp; |
| |
| dl->dl_primitive = dl_primitive; |
| dl->dl_addr_offset = offset; |
| *addr_lenp = dl->dl_addr_length = hw_addr_length; |
| *addr_offp = offset; |
| break; |
| } |
| case DL_DISABMULTI_REQ: { |
| dl_disabmulti_req_t *dl = (dl_disabmulti_req_t *)cp; |
| |
| dl->dl_primitive = dl_primitive; |
| dl->dl_addr_offset = offset; |
| *addr_lenp = dl->dl_addr_length = hw_addr_length; |
| *addr_offp = offset; |
| break; |
| } |
| case DL_PROMISCON_REQ: |
| case DL_PROMISCOFF_REQ: { |
| dl_promiscon_req_t *dl = (dl_promiscon_req_t *)cp; |
| |
| dl->dl_primitive = dl_primitive; |
| dl->dl_level = DL_PROMISC_MULTI; |
| break; |
| } |
| } |
| ip1dbg(("ill_create_dl: addr_len %d, addr_off %d\n", |
| *addr_lenp, *addr_offp)); |
| return (mp); |
| } |
| |
| void |
| ip_wput_ctl(queue_t *q, mblk_t *mp_orig) |
| { |
| ill_t *ill = (ill_t *)q->q_ptr; |
| mblk_t *mp = mp_orig; |
| area_t *area; |
| |
| /* Check that we have a AR_ENTRY_SQUERY with a tacked on mblk */ |
| if ((mp->b_wptr - mp->b_rptr) < sizeof (area_t) || |
| mp->b_cont == NULL) { |
| putnext(q, mp); |
| return; |
| } |
| area = (area_t *)mp->b_rptr; |
| if (area->area_cmd != AR_ENTRY_SQUERY) { |
| putnext(q, mp); |
| return; |
| } |
| mp = mp->b_cont; |
| /* |
| * Update dl_addr_length and dl_addr_offset for primitives that |
| * have physical addresses as opposed to full saps |
| */ |
| switch (((union DL_primitives *)mp->b_rptr)->dl_primitive) { |
| case DL_ENABMULTI_REQ: |
| /* Track the state if this is the first enabmulti */ |
| if (ill->ill_dlpi_multicast_state == IDS_UNKNOWN) |
| ill->ill_dlpi_multicast_state = IDS_INPROGRESS; |
| ip1dbg(("ip_wput_ctl: ENABMULTI\n")); |
| break; |
| case DL_DISABMULTI_REQ: |
| ip1dbg(("ip_wput_ctl: DISABMULTI\n")); |
| break; |
| default: |
| ip1dbg(("ip_wput_ctl: default\n")); |
| break; |
| } |
| freeb(mp_orig); |
| putnext(q, mp); |
| } |
| |
| /* |
| * Rejoin any groups which have been explicitly joined by the application (we |
| * left all explicitly joined groups as part of ill_leave_multicast() prior to |
| * bringing the interface down). Note that because groups can be joined and |
| * left while an interface is down, this may not be the same set of groups |
| * that we left in ill_leave_multicast(). |
| */ |
| void |
| ill_recover_multicast(ill_t *ill) |
| { |
| ilm_t *ilm; |
| char addrbuf[INET6_ADDRSTRLEN]; |
| |
| ASSERT(IAM_WRITER_ILL(ill)); |
| |
| for (ilm = ill->ill_ilm; ilm; ilm = ilm->ilm_next) { |
| /* |
| * Check how many ipif's that have members in this group - |
| * if more then one we make sure that this entry is first |
| * in the list. |
| */ |
| if (ilm_numentries_v6(ill, &ilm->ilm_v6addr) > 1 && |
| ilm_lookup_ill_v6(ill, &ilm->ilm_v6addr, ALL_ZONES) != ilm) |
| continue; |
| ip1dbg(("ill_recover_multicast: %s\n", |
| inet_ntop(AF_INET6, &ilm->ilm_v6addr, addrbuf, |
| sizeof (addrbuf)))); |
| if (IN6_IS_ADDR_UNSPECIFIED(&ilm->ilm_v6addr)) { |
| if (ill->ill_group == NULL) { |
| (void) ip_join_allmulti(ill->ill_ipif); |
| } else { |
| /* |
| * We don't want to join on this ill, |
| * if somebody else in the group has |
| * already been nominated. |
| */ |
| (void) ill_nominate_mcast_rcv(ill->ill_group); |
| } |
| } else { |
| (void) ip_ll_addmulti_v6(ill->ill_ipif, |
| &ilm->ilm_v6addr); |
| } |
| } |
| } |
| |
| /* |
| * The opposite of ill_recover_multicast() -- leaves all multicast groups |
| * that were explicitly joined. Note that both these functions could be |
| * disposed of if we enhanced ARP to allow us to handle DL_DISABMULTI_REQ |
| * and DL_ENABMULTI_REQ messages when an interface is down. |
| */ |
| void |
| ill_leave_multicast(ill_t *ill) |
| { |
| ilm_t *ilm; |
| char addrbuf[INET6_ADDRSTRLEN]; |
| |
| ASSERT(IAM_WRITER_ILL(ill)); |
| |
| for (ilm = ill->ill_ilm; ilm; ilm = ilm->ilm_next) { |
| /* |
| * Check how many ipif's that have members in this group - |
| * if more then one we make sure that this entry is first |
| * in the list. |
| */ |
| if (ilm_numentries_v6(ill, &ilm->ilm_v6addr) > 1 && |
| ilm_lookup_ill_v6(ill, &ilm->ilm_v6addr, ALL_ZONES) != ilm) |
| continue; |
| ip1dbg(("ill_leave_multicast: %s\n", |
| inet_ntop(AF_INET6, &ilm->ilm_v6addr, addrbuf, |
| sizeof (addrbuf)))); |
| if (IN6_IS_ADDR_UNSPECIFIED(&ilm->ilm_v6addr)) { |
| (void) ip_leave_allmulti(ill->ill_ipif); |
| /* |
| * If we were part of an IPMP group, then |
| * ill_handoff_responsibility() has already |
| * nominated a new member (so we don't). |
| */ |
| ASSERT(ill->ill_group == NULL); |
| } else { |
| (void) ip_ll_send_disabmulti_req(ill, &ilm->ilm_v6addr); |
| } |
| } |
| } |
| |
| /* |
| * Find an ilm for matching the ill and which has the source in its |
| * INCLUDE list or does not have it in its EXCLUDE list |
| */ |
| ilm_t * |
| ilm_lookup_ill_withsrc(ill_t *ill, ipaddr_t group, ipaddr_t src) |
| { |
| in6_addr_t v6group, v6src; |
| |
| /* |
| * INADDR_ANY is represented as the IPv6 unspecified addr. |
| */ |
| if (group == INADDR_ANY) |
| v6group = ipv6_all_zeros; |
| else |
| IN6_IPADDR_TO_V4MAPPED(group, &v6group); |
| IN6_IPADDR_TO_V4MAPPED(src, &v6src); |
| |
| return (ilm_lookup_ill_withsrc_v6(ill, &v6group, &v6src)); |
| } |
| |
| ilm_t * |
| ilm_lookup_ill_withsrc_v6(ill_t *ill, const in6_addr_t *v6group, |
| const in6_addr_t *v6src) |
| { |
| ilm_t *ilm; |
| boolean_t isinlist; |
| int i, numsrc; |
| |
| /* |
| * If the source is in any ilm's INCLUDE list, or if |
| * it is not in any ilm's EXCLUDE list, we have a hit. |
| */ |
| for (ilm = ill->ill_ilm; ilm; ilm = ilm->ilm_next) { |
| if (IN6_ARE_ADDR_EQUAL(&ilm->ilm_v6addr, v6group)) { |
| |
| isinlist = B_FALSE; |
| numsrc = (ilm->ilm_filter == NULL) ? |
| 0 : ilm->ilm_filter->sl_numsrc; |
| for (i = 0; i < numsrc; i++) { |
| if (IN6_ARE_ADDR_EQUAL(v6src, |
| &ilm->ilm_filter->sl_addr[i])) { |
| isinlist = B_TRUE; |
| break; |
| } |
| } |
| if ((isinlist && ilm->ilm_fmode == MODE_IS_INCLUDE) || |
| (!isinlist && ilm->ilm_fmode == MODE_IS_EXCLUDE)) |
| return (ilm); |
| else |
| return (NULL); |
| } |
| } |
| return (NULL); |
| } |
| |
| |
| /* Find an ilm for matching the ill */ |
| ilm_t * |
| ilm_lookup_ill(ill_t *ill, ipaddr_t group, zoneid_t zoneid) |
| { |
| in6_addr_t v6group; |
| |
| ASSERT(ill->ill_ilm_walker_cnt != 0 || MUTEX_HELD(&ill->ill_lock) || |
| IAM_WRITER_ILL(ill)); |
| /* |
| * INADDR_ANY is represented as the IPv6 unspecifed addr. |
| */ |
| if (group == INADDR_ANY) |
| v6group = ipv6_all_zeros; |
| else |
| IN6_IPADDR_TO_V4MAPPED(group, &v6group); |
| |
| return (ilm_lookup_ill_v6(ill, &v6group, zoneid)); |
| } |
| |
| /* |
| * Find an ilm for matching the ill. All the ilm lookup functions |
| * ignore ILM_DELETED ilms. These have been logically deleted, and |
| * igmp and linklayer disable multicast have been done. Only mi_free |
| * yet to be done. Still there in the list due to ilm_walkers. The |
| * last walker will release it. |
| */ |
| ilm_t * |
| ilm_lookup_ill_v6(ill_t *ill, const in6_addr_t *v6group, zoneid_t zoneid) |
| { |
| ilm_t *ilm; |
| |
| ASSERT(ill->ill_ilm_walker_cnt != 0 || MUTEX_HELD(&ill->ill_lock) || |
| IAM_WRITER_ILL(ill)); |
| |
| for (ilm = ill->ill_ilm; ilm; ilm = ilm->ilm_next) { |
| if (ilm->ilm_flags & ILM_DELETED) |
| continue; |
| if (IN6_ARE_ADDR_EQUAL(&ilm->ilm_v6addr, v6group) && |
| (zoneid == ALL_ZONES || zoneid == ilm->ilm_zoneid)) |
| return (ilm); |
| } |
| return (NULL); |
| } |
| |
| ilm_t * |
| ilm_lookup_ill_index_v6(ill_t *ill, const in6_addr_t *v6group, int index, |
| zoneid_t zoneid) |
| { |
| ilm_t *ilm; |
| |
| ASSERT(ill->ill_ilm_walker_cnt != 0 || MUTEX_HELD(&ill->ill_lock) || |
| IAM_WRITER_ILL(ill)); |
| |
| for (ilm = ill->ill_ilm; ilm != NULL; ilm = ilm->ilm_next) { |
| if (ilm->ilm_flags & ILM_DELETED) |
| continue; |
| if (IN6_ARE_ADDR_EQUAL(&ilm->ilm_v6addr, v6group) && |
| (zoneid == ALL_ZONES || zoneid == ilm->ilm_zoneid) && |
| ilm->ilm_orig_ifindex == index) { |
| return (ilm); |
| } |
| } |
| return (NULL); |
| } |
| |
| ilm_t * |
| ilm_lookup_ill_index_v4(ill_t *ill, ipaddr_t group, int index, zoneid_t zoneid) |
| { |
| in6_addr_t v6group; |
| |
| ASSERT(ill->ill_ilm_walker_cnt != 0 || MUTEX_HELD(&ill->ill_lock) || |
| IAM_WRITER_ILL(ill)); |
| /* |
| * INADDR_ANY is represented as the IPv6 unspecifed addr. |
| */ |
| if (group == INADDR_ANY) |
| v6group = ipv6_all_zeros; |
| else |
| IN6_IPADDR_TO_V4MAPPED(group, &v6group); |
| |
| return (ilm_lookup_ill_index_v6(ill, &v6group, index, zoneid)); |
| } |
| |
| /* |
| * Found an ilm for the ipif. Only needed for IPv4 which does |
| * ipif specific socket options. |
| */ |
| ilm_t * |
| ilm_lookup_ipif(ipif_t *ipif, ipaddr_t group) |
| { |
| ill_t *ill = ipif->ipif_ill; |
| ilm_t *ilm; |
| in6_addr_t v6group; |
| |
| ASSERT(ill->ill_ilm_walker_cnt != 0 || MUTEX_HELD(&ill->ill_lock) || |
| IAM_WRITER_ILL(ill)); |
| |
| /* |
| * INADDR_ANY is represented as the IPv6 unspecifed addr. |
| */ |
| if (group == INADDR_ANY) |
| v6group = ipv6_all_zeros; |
| else |
| IN6_IPADDR_TO_V4MAPPED(group, &v6group); |
| |
| for (ilm = ill->ill_ilm; ilm; ilm = ilm->ilm_next) { |
| if (ilm->ilm_flags & ILM_DELETED) |
| continue; |
| if (ilm->ilm_ipif == ipif && |
| IN6_ARE_ADDR_EQUAL(&ilm->ilm_v6addr, &v6group)) |
| return (ilm); |
| } |
| return (NULL); |
| } |
| |
| /* |
| * How many members on this ill? |
| */ |
| int |
| ilm_numentries_v6(ill_t *ill, const in6_addr_t *v6group) |
| { |
| ilm_t *ilm; |
| int i = 0; |
| |
| ASSERT(ill->ill_ilm_walker_cnt != 0 || MUTEX_HELD(&ill->ill_lock) || |
| IAM_WRITER_ILL(ill)); |
| |
| for (ilm = ill->ill_ilm; ilm; ilm = ilm->ilm_next) { |
| if (ilm->ilm_flags & ILM_DELETED) |
| continue; |
| if (IN6_ARE_ADDR_EQUAL(&ilm->ilm_v6addr, v6group)) { |
| i++; |
| } |
| } |
| return (i); |
| } |
| |
| /* Caller guarantees that the group is not already on the list */ |
| static ilm_t * |
| ilm_add_v6(ipif_t *ipif, const in6_addr_t *v6group, ilg_stat_t ilgstat, |
| mcast_record_t ilg_fmode, slist_t *ilg_flist, int orig_ifindex, |
| zoneid_t zoneid) |
| { |
| ill_t *ill = ipif->ipif_ill; |
| ilm_t *ilm; |
| ilm_t *ilm_cur; |
| ilm_t **ilm_ptpn; |
| |
| ASSERT(IAM_WRITER_IPIF(ipif)); |
| |
| ilm = GETSTRUCT(ilm_t, 1); |
| if (ilm == NULL) |
| return (NULL); |
| if (ilgstat != ILGSTAT_NONE && !SLIST_IS_EMPTY(ilg_flist)) { |
| ilm->ilm_filter = l_alloc(); |
| if (ilm->ilm_filter == NULL) { |
| mi_free(ilm); |
| return (NULL); |
| } |
| } |
| ilm->ilm_v6addr = *v6group; |
| ilm->ilm_refcnt = 1; |
| ilm->ilm_zoneid = zoneid; |
| ilm->ilm_timer = INFINITY; |
| ilm->ilm_rtx.rtx_timer = INFINITY; |
| |
| /* |
| * IPv4 Multicast groups are joined using ipif. |
| * IPv6 Multicast groups are joined using ill. |
| */ |
| if (ill->ill_isv6) { |
| ilm->ilm_ill = ill; |
| ilm->ilm_ipif = NULL; |
| } else { |
| ASSERT(ilm->ilm_zoneid == ipif->ipif_zoneid); |
| ilm->ilm_ipif = ipif; |
| ilm->ilm_ill = NULL; |
| } |
| ASSERT(ill->ill_ipst); |
| ilm->ilm_ipst = ill->ill_ipst; /* No netstack_hold */ |
| |
| /* |
| * After this if ilm moves to a new ill, we don't change |
| * the ilm_orig_ifindex. Thus, if ill_index != ilm_orig_ifindex, |
| * it has been moved. Indexes don't match even when the application |
| * wants to join on a FAILED/INACTIVE interface because we choose |
| * a new interface to join in. This is considered as an implicit |
| * move. |
| */ |
| ilm->ilm_orig_ifindex = orig_ifindex; |
| |
| ASSERT(!(ipif->ipif_state_flags & IPIF_CONDEMNED)); |
| ASSERT(!(ill->ill_state_flags & ILL_CONDEMNED)); |
| |
| /* |
| * Grab lock to give consistent view to readers |
| */ |
| mutex_enter(&ill->ill_lock); |
| /* |
| * All ilms in the same zone are contiguous in the ill_ilm list. |
| * The loops in ip_proto_input() and ip_wput_local() use this to avoid |
| * sending duplicates up when two applications in the same zone join the |
| * same group on different logical interfaces. |
| */ |
| ilm_cur = ill->ill_ilm; |
| ilm_ptpn = &ill->ill_ilm; |
| while (ilm_cur != NULL && ilm_cur->ilm_zoneid != ilm->ilm_zoneid) { |
| ilm_ptpn = &ilm_cur->ilm_next; |
| ilm_cur = ilm_cur->ilm_next; |
| } |
| ilm->ilm_next = ilm_cur; |
| *ilm_ptpn = ilm; |
| |
| /* |
| * If we have an associated ilg, use its filter state; if not, |
| * default to (EXCLUDE, NULL) and set no_ilg_cnt to track this. |
| */ |
| if (ilgstat != ILGSTAT_NONE) { |
| if (!SLIST_IS_EMPTY(ilg_flist)) |
| l_copy(ilg_flist, ilm->ilm_filter); |
| ilm->ilm_fmode = ilg_fmode; |
| } else { |
| ilm->ilm_no_ilg_cnt = 1; |
| ilm->ilm_fmode = MODE_IS_EXCLUDE; |
| } |
| |
| mutex_exit(&ill->ill_lock); |
| return (ilm); |
| } |
| |
| void |
| ilm_walker_cleanup(ill_t *ill) |
| { |
| ilm_t **ilmp; |
| ilm_t *ilm; |
| |
| ASSERT(MUTEX_HELD(&ill->ill_lock)); |
| ASSERT(ill->ill_ilm_walker_cnt == 0); |
| |
| ilmp = &ill->ill_ilm; |
| while (*ilmp != NULL) { |
| if ((*ilmp)->ilm_flags & ILM_DELETED) { |
| ilm = *ilmp; |
| *ilmp = ilm->ilm_next; |
| FREE_SLIST(ilm->ilm_filter); |
| FREE_SLIST(ilm->ilm_pendsrcs); |
| FREE_SLIST(ilm->ilm_rtx.rtx_allow); |
| FREE_SLIST(ilm->ilm_rtx.rtx_block); |
| ilm->ilm_ipst = NULL; |
| mi_free((char *)ilm); |
| } else { |
| ilmp = &(*ilmp)->ilm_next; |
| } |
| } |
| ill->ill_ilm_cleanup_reqd = 0; |
| } |
| |
| /* |
| * Unlink ilm and free it. |
| */ |
| static void |
| ilm_delete(ilm_t *ilm) |
| { |
| ill_t *ill; |
| ilm_t **ilmp; |
| |
| if (ilm->ilm_ipif != NULL) { |
| ASSERT(IAM_WRITER_IPIF(ilm->ilm_ipif)); |
| ASSERT(ilm->ilm_ill == NULL); |
| ill = ilm->ilm_ipif->ipif_ill; |
| ASSERT(!ill->ill_isv6); |
| } else { |
| ASSERT(IAM_WRITER_ILL(ilm->ilm_ill)); |
| ASSERT(ilm->ilm_ipif == NULL); |
| ill = ilm->ilm_ill; |
| ASSERT(ill->ill_isv6); |
| } |
| /* |
| * Delete under lock protection so that readers don't stumble |
| * on bad ilm_next |
| */ |
| mutex_enter(&ill->ill_lock); |
| if (ill->ill_ilm_walker_cnt != 0) { |
| ilm->ilm_flags |= ILM_DELETED; |
| ill->ill_ilm_cleanup_reqd = 1; |
| mutex_exit(&ill->ill_lock); |
| return; |
| } |
| |
| for (ilmp = &ill->ill_ilm; *ilmp != ilm; ilmp = &(*ilmp)->ilm_next) |
| ; |
| *ilmp = ilm->ilm_next; |
| mutex_exit(&ill->ill_lock); |
| |
| FREE_SLIST(ilm->ilm_filter); |
| FREE_SLIST(ilm->ilm_pendsrcs); |
| FREE_SLIST(ilm->ilm_rtx.rtx_allow); |
| FREE_SLIST(ilm->ilm_rtx.rtx_block); |
| ilm->ilm_ipst = NULL; |
| mi_free((char *)ilm); |
| } |
| |
| /* Free all ilms for this ipif */ |
| void |
| ilm_free(ipif_t *ipif) |
| { |
| ill_t *ill = ipif->ipif_ill; |
| ilm_t *ilm; |
| ilm_t *next_ilm; |
| |
| ASSERT(IAM_WRITER_IPIF(ipif)); |
| |
| for (ilm = ill->ill_ilm; ilm; ilm = next_ilm) { |
| next_ilm = ilm->ilm_next; |
| if (ilm->ilm_ipif == ipif) |
| ilm_delete(ilm); |
| } |
| } |
| |
| /* |
| * Looks up the appropriate ipif given a v4 multicast group and interface |
| * address. On success, returns 0, with *ipifpp pointing to the found |
| * struct. On failure, returns an errno and *ipifpp is NULL. |
| */ |
| int |
| ip_opt_check(conn_t *connp, ipaddr_t group, ipaddr_t src, ipaddr_t ifaddr, |
| uint_t *ifindexp, mblk_t *first_mp, ipsq_func_t func, ipif_t **ipifpp) |
| { |
| ipif_t *ipif; |
| int err = 0; |
| zoneid_t zoneid; |
| ip_stack_t *ipst = connp->conn_netstack->netstack_ip; |
| |
| if (!CLASSD(group) || CLASSD(src)) { |
| return (EINVAL); |
| } |
| *ipifpp = NULL; |
| |
| zoneid = IPCL_ZONEID(connp); |
| |
| ASSERT(!(ifaddr != INADDR_ANY && ifindexp != NULL && *ifindexp != 0)); |
| if (ifaddr != INADDR_ANY) { |
| ipif = ipif_lookup_addr(ifaddr, NULL, zoneid, |
| CONNP_TO_WQ(connp), first_mp, func, &err, ipst); |
| if (err != 0 && err != EINPROGRESS) |
| err = EADDRNOTAVAIL; |
| } else if (ifindexp != NULL && *ifindexp != 0) { |
| ipif = ipif_lookup_on_ifindex(*ifindexp, B_FALSE, zoneid, |
| CONNP_TO_WQ(connp), first_mp, func, &err, ipst); |
| } else { |
| ipif = ipif_lookup_group(group, zoneid, ipst); |
| if (ipif == NULL) |
| return (EADDRNOTAVAIL); |
| } |
| if (ipif == NULL) |
| return (err); |
| |
| *ipifpp = ipif; |
| return (0); |
| } |
| |
| /* |
| * Looks up the appropriate ill (or ipif if v4mapped) given an interface |
| * index and IPv6 multicast group. On success, returns 0, with *illpp (or |
| * *ipifpp if v4mapped) pointing to the found struct. On failure, returns |
| * an errno and *illpp and *ipifpp are undefined. |
| */ |
| int |
| ip_opt_check_v6(conn_t *connp, const in6_addr_t *v6group, ipaddr_t *v4group, |
| const in6_addr_t *v6src, ipaddr_t *v4src, boolean_t *isv6, int ifindex, |
| mblk_t *first_mp, ipsq_func_t func, ill_t **illpp, ipif_t **ipifpp) |
| { |
| boolean_t src_unspec; |
| ill_t *ill = NULL; |
| ipif_t *ipif = NULL; |
| int err; |
| zoneid_t zoneid = connp->conn_zoneid; |
| queue_t *wq = CONNP_TO_WQ(connp); |
| ip_stack_t *ipst = connp->conn_netstack->netstack_ip; |
| |
| src_unspec = IN6_IS_ADDR_UNSPECIFIED(v6src); |
| |
| if (IN6_IS_ADDR_V4MAPPED(v6group)) { |
| if (!IN6_IS_ADDR_V4MAPPED(v6src) && !src_unspec) |
| return (EINVAL); |
| IN6_V4MAPPED_TO_IPADDR(v6group, *v4group); |
| if (src_unspec) { |
| *v4src = INADDR_ANY; |
| } else { |
| IN6_V4MAPPED_TO_IPADDR(v6src, *v4src); |
| } |
| if (!CLASSD(*v4group) || CLASSD(*v4src)) |
| return (EINVAL); |
| *ipifpp = NULL; |
| *isv6 = B_FALSE; |
| } else { |
| if (IN6_IS_ADDR_V4MAPPED(v6src) && !src_unspec) |
| return (EINVAL); |
| if (!IN6_IS_ADDR_MULTICAST(v6group) || |
| IN6_IS_ADDR_MULTICAST(v6src)) { |
| return (EINVAL); |
| } |
| *illpp = NULL; |
| *isv6 = B_TRUE; |
| } |
| |
| if (ifindex == 0) { |
| if (*isv6) |
| ill = ill_lookup_group_v6(v6group, zoneid, ipst); |
| else |
| ipif = ipif_lookup_group(*v4group, zoneid, ipst); |
| if (ill == NULL && ipif == NULL) |
| return (EADDRNOTAVAIL); |
| } else { |
| if (*isv6) { |
| ill = ill_lookup_on_ifindex(ifindex, B_TRUE, |
| wq, first_mp, func, &err, ipst); |
| if (ill != NULL && |
| !ipif_lookup_zoneid(ill, zoneid, 0, NULL)) { |
| ill_refrele(ill); |
| ill = NULL; |
| err = EADDRNOTAVAIL; |
| } |
| } else { |
| ipif = ipif_lookup_on_ifindex(ifindex, B_FALSE, |
| zoneid, wq, first_mp, func, &err, ipst); |
| } |
| if (ill == NULL && ipif == NULL) |
| return (err); |
| } |
| |
| *ipifpp = ipif; |
| *illpp = ill; |
| return (0); |
| } |
| |
| static int |
| ip_get_srcfilter(conn_t *connp, struct group_filter *gf, |
| struct ip_msfilter *imsf, ipaddr_t grp, ipif_t *ipif, boolean_t isv4mapped) |
| { |
| ilg_t *ilg; |
| int i, numsrc, fmode, outsrcs; |
| struct sockaddr_in *sin; |
| struct sockaddr_in6 *sin6; |
| struct in_addr *addrp; |
| slist_t *fp; |
| boolean_t is_v4only_api; |
| |
| mutex_enter(&connp->conn_lock); |
| |
| ilg = ilg_lookup_ipif(connp, grp, ipif); |
| if (ilg == NULL) { |
| mutex_exit(&connp->conn_lock); |
| return (EADDRNOTAVAIL); |
| } |
| |
| if (gf == NULL) { |
| ASSERT(imsf != NULL); |
| ASSERT(!isv4mapped); |
| is_v4only_api = B_TRUE; |
| outsrcs = imsf->imsf_numsrc; |
| } else { |
| ASSERT(imsf == NULL); |
| is_v4only_api = B_FALSE; |
| outsrcs = gf->gf_numsrc; |
| } |
| |
| /* |
| * In the kernel, we use the state definitions MODE_IS_[IN|EX]CLUDE |
| * to identify the filter mode; but the API uses MCAST_[IN|EX]CLUDE. |
| * So we need to translate here. |
| */ |
| fmode = (ilg->ilg_fmode == MODE_IS_INCLUDE) ? |
| MCAST_INCLUDE : MCAST_EXCLUDE; |
| if ((fp = ilg->ilg_filter) == NULL) { |
| numsrc = 0; |
| } else { |
| for (i = 0; i < outsrcs; i++) { |
| if (i == fp->sl_numsrc) |
| break; |
| if (isv4mapped) { |
| sin6 = (struct sockaddr_in6 *)&gf->gf_slist[i]; |
| sin6->sin6_family = AF_INET6; |
| sin6->sin6_addr = fp->sl_addr[i]; |
| } else { |
| if (is_v4only_api) { |
| addrp = &imsf->imsf_slist[i]; |
| } else { |
| sin = (struct sockaddr_in *) |
| &gf->gf_slist[i]; |
| sin->sin_family = AF_INET; |
| addrp = &sin->sin_addr; |
| } |
| IN6_V4MAPPED_TO_INADDR(&fp->sl_addr[i], addrp); |
| } |
| } |
| numsrc = fp->sl_numsrc; |
| } |
| |
| if (is_v4only_api) { |
| imsf->imsf_numsrc = numsrc; |
| imsf->imsf_fmode = fmode; |
| } else { |
| gf->gf_numsrc = numsrc; |
| gf->gf_fmode = fmode; |
| } |
| |
| mutex_exit(&connp->conn_lock); |
| |
| return (0); |
| } |
| |
| static int |
| ip_get_srcfilter_v6(conn_t *connp, struct group_filter *gf, |
| const struct in6_addr *grp, ill_t *ill) |
| { |
| ilg_t *ilg; |
| int i; |
| struct sockaddr_storage *sl; |
| struct sockaddr_in6 *sin6; |
| slist_t *fp; |
| |
| mutex_enter(&connp->conn_lock); |
| |
| ilg = ilg_lookup_ill_v6(connp, grp, ill); |
| if (ilg == NULL) { |
| mutex_exit(&connp->conn_lock); |
| return (EADDRNOTAVAIL); |
| } |
| |
| /* |
| * In the kernel, we use the state definitions MODE_IS_[IN|EX]CLUDE |
| * to identify the filter mode; but the API uses MCAST_[IN|EX]CLUDE. |
| * So we need to translate here. |
| */ |
| gf->gf_fmode = (ilg->ilg_fmode == MODE_IS_INCLUDE) ? |
| MCAST_INCLUDE : MCAST_EXCLUDE; |
| if ((fp = ilg->ilg_filter) == NULL) { |
| gf->gf_numsrc = 0; |
| } else { |
| for (i = 0, sl = gf->gf_slist; i < gf->gf_numsrc; i++, sl++) { |
| if (i == fp->sl_numsrc) |
| break; |
| sin6 = (struct sockaddr_in6 *)sl; |
| sin6->sin6_family = AF_INET6; |
| sin6->sin6_addr = fp->sl_addr[i]; |
| } |
| gf->gf_numsrc = fp->sl_numsrc; |
| } |
| |
| mutex_exit(&connp->conn_lock); |
| |
| return (0); |
| } |
| |
| static int |
| ip_set_srcfilter(conn_t *connp, struct group_filter *gf, |
| struct ip_msfilter *imsf, ipaddr_t grp, ipif_t *ipif, boolean_t isv4mapped) |
| { |
| ilg_t *ilg; |
| int i, err, insrcs, infmode, new_fmode; |
| struct sockaddr_in *sin; |
| struct sockaddr_in6 *sin6; |
| struct in_addr *addrp; |
| slist_t *orig_filter = NULL; |
| slist_t *new_filter = NULL; |
| mcast_record_t orig_fmode; |
| boolean_t leave_grp, is_v4only_api; |
| ilg_stat_t ilgstat; |
| |
| if (gf == NULL) { |
| ASSERT(imsf != NULL); |
| ASSERT(!isv4mapped); |
| is_v4only_api = B_TRUE; |
| insrcs = imsf->imsf_numsrc; |
| infmode = imsf->imsf_fmode; |
| } else { |
| ASSERT(imsf == NULL); |
| is_v4only_api = B_FALSE; |
| insrcs = gf->gf_numsrc; |
| infmode = gf->gf_fmode; |
| } |
| |
| /* Make sure we can handle the source list */ |
| if (insrcs > MAX_FILTER_SIZE) |
| return (ENOBUFS); |
| |
| /* |
| * setting the filter to (INCLUDE, NULL) is treated |
| * as a request to leave the group. |
| */ |
| leave_grp = (infmode == MCAST_INCLUDE && insrcs == 0); |
| |
| ASSERT(IAM_WRITER_IPIF(ipif)); |
| |
| mutex_enter(&connp->conn_lock); |
| |
| ilg = ilg_lookup_ipif(connp, grp, ipif); |
| if (ilg == NULL) { |
| /* |
| * if the request was actually to leave, and we |
| * didn't find an ilg, there's nothing to do. |
| */ |
| if (!leave_grp) |
| ilg = conn_ilg_alloc(connp); |
| if (leave_grp || ilg == NULL) { |
| mutex_exit(&connp->conn_lock); |
| return (leave_grp ? 0 : ENOMEM); |
| } |
| ilgstat = ILGSTAT_NEW; |
| IN6_IPADDR_TO_V4MAPPED(grp, &ilg->ilg_v6group); |
| ilg->ilg_ipif = ipif; |
| ilg->ilg_ill = NULL; |
| ilg->ilg_orig_ifindex = 0; |
| } else if (leave_grp) { |
| ilg_delete(connp, ilg, NULL); |
| mutex_exit(&connp->conn_lock); |
| (void) ip_delmulti(grp, ipif, B_FALSE, B_TRUE); |
| return (0); |
| } else { |
| ilgstat = ILGSTAT_CHANGE; |
| /* Preserve existing state in case ip_addmulti() fails */ |
| orig_fmode = ilg->ilg_fmode; |
| if (ilg->ilg_filter == NULL) { |
| orig_filter = NULL; |
| } else { |
| orig_filter = l_alloc_copy(ilg->ilg_filter); |
| if (orig_filter == NULL) { |
| mutex_exit(&connp->conn_lock); |
| return (ENOMEM); |
| } |
| } |
| } |
| |
| /* |
| * Alloc buffer to copy new state into (see below) before |
| * we make any changes, so we can bail if it fails. |
| */ |
| if ((new_filter = l_alloc()) == NULL) { |
| mutex_exit(&connp->conn_lock); |
| err = ENOMEM; |
| goto free_and_exit; |
| } |
| |
| if (insrcs == 0) { |
| CLEAR_SLIST(ilg->ilg_filter); |
| } else { |
| slist_t *fp; |
| if (ilg->ilg_filter == NULL) { |
| fp = l_alloc(); |
| if (fp == NULL) { |
| if (ilgstat == ILGSTAT_NEW) |
| ilg_delete(connp, ilg, NULL); |
| mutex_exit(&connp->conn_lock); |
| err = ENOMEM; |
| goto free_and_exit; |
| } |
| } else { |
| fp = ilg->ilg_filter; |
| } |
| for (i = 0; i < insrcs; i++) { |
| if (isv4mapped) { |
| sin6 = (struct sockaddr_in6 *)&gf->gf_slist[i]; |
| fp->sl_addr[i] = sin6->sin6_addr; |
| } else { |
| if (is_v4only_api) { |
| addrp = &imsf->imsf_slist[i]; |
| } else { |
| sin = (struct sockaddr_in *) |
| &gf->gf_slist[i]; |
| addrp = &sin->sin_addr; |
| } |
| IN6_INADDR_TO_V4MAPPED(addrp, &fp->sl_addr[i]); |
| } |
| } |
| fp->sl_numsrc = insrcs; |
| ilg->ilg_filter = fp; |
| } |
| /* |
| * In the kernel, we use the state definitions MODE_IS_[IN|EX]CLUDE |
| * to identify the filter mode; but the API uses MCAST_[IN|EX]CLUDE. |
| * So we need to translate here. |
| */ |
| ilg->ilg_fmode = (infmode == MCAST_INCLUDE) ? |
| MODE_IS_INCLUDE : MODE_IS_EXCLUDE; |
| |
| /* |
| * Save copy of ilg's filter state to pass to other functions, |
| * so we can release conn_lock now. |
| */ |
| new_fmode = ilg->ilg_fmode; |
| l_copy(ilg->ilg_filter, new_filter); |
| |
| mutex_exit(&connp->conn_lock); |
| |
| err = ip_addmulti(grp, ipif, ilgstat, new_fmode, new_filter); |
| if (err != 0) { |
| /* |
| * Restore the original filter state, or delete the |
| * newly-created ilg. We need to look up the ilg |
| * again, though, since we've not been holding the |
| * conn_lock. |
| */ |
| mutex_enter(&connp->conn_lock); |
| ilg = ilg_lookup_ipif(connp, grp, ipif); |
| ASSERT(ilg != NULL); |
| if (ilgstat == ILGSTAT_NEW) { |
| ilg_delete(connp, ilg, NULL); |
| } else { |
| ilg->ilg_fmode = orig_fmode; |
| if (SLIST_IS_EMPTY(orig_filter)) { |
| CLEAR_SLIST(ilg->ilg_filter); |
| } else { |
| /* |
| * We didn't free the filter, even if we |
| * were trying to make the source list empty; |
| * so if orig_filter isn't empty, the ilg |
| * must still have a filter alloc'd. |
| */ |
| l_copy(orig_filter, ilg->ilg_filter); |
| } |
| } |
| mutex_exit(&connp->conn_lock); |
| } |
| |
| free_and_exit: |
| l_free(orig_filter); |
| l_free(new_filter); |
| |
| return (err); |
| } |
| |
| static int |
| ip_set_srcfilter_v6(conn_t *connp, struct group_filter *gf, |
| const struct in6_addr *grp, ill_t *ill) |
| { |
| ilg_t *ilg; |
| int i, orig_ifindex, orig_fmode, new_fmode, err; |
| slist_t *orig_filter = NULL; |
| slist_t *new_filter = NULL; |
| struct sockaddr_storage *sl; |
| struct sockaddr_in6 *sin6; |
| boolean_t leave_grp; |
| ilg_stat_t ilgstat; |
| |
| /* Make sure we can handle the source list */ |
| if (gf->gf_numsrc > MAX_FILTER_SIZE) |
| return (ENOBUFS); |
| |
| /* |
| * setting the filter to (INCLUDE, NULL) is treated |
| * as a request to leave the group. |
| */ |
| leave_grp = (gf->gf_fmode == MCAST_INCLUDE && gf->gf_numsrc == 0); |
| |
| ASSERT(IAM_WRITER_ILL(ill)); |
| |
| /* |
| * Use the ifindex to do the lookup. We can't use the ill |
| * directly because ilg_ill could point to a different ill |
| * if things have moved. |
| */ |
| orig_ifindex = ill->ill_phyint->phyint_ifindex; |
| |
| mutex_enter(&connp->conn_lock); |
| ilg = ilg_lookup_ill_index_v6(connp, grp, orig_ifindex); |
| if (ilg == NULL) { |
| /* |
| * if the request was actually to leave, and we |
| * didn't find an ilg, there's nothing to do. |
| */ |
| if (!leave_grp) |
| ilg = conn_ilg_alloc(connp); |
| if (leave_grp || ilg == NULL) { |
| mutex_exit(&connp->conn_lock); |
| return (leave_grp ? 0 : ENOMEM); |
| } |
| ilgstat = ILGSTAT_NEW; |
| ilg->ilg_v6group = *grp; |
| ilg->ilg_ipif = NULL; |
| /* |
| * Choose our target ill to join on. This might be |
| * different from the ill we've been given if it's |
| * currently down and part of a group. |
| * |
| * new ill is not refheld; we are writer. |
| */ |
| ill = ip_choose_multi_ill(ill, grp); |
| ASSERT(!(ill->ill_state_flags & ILL_CONDEMNED)); |
| ilg->ilg_ill = ill; |
| /* |
| * Remember the index that we joined on, so that we can |
| * successfully delete them later on and also search for |
| * duplicates if the application wants to join again. |
| */ |
| ilg->ilg_orig_ifindex = orig_ifindex; |
| } else if (leave_grp) { |
| /* |
| * Use the ilg's current ill for the deletion, |
| * we might have failed over. |
| */ |
| ill = ilg->ilg_ill; |
| ilg_delete(connp, ilg, NULL); |
| mutex_exit(&connp->conn_lock); |
| (void) ip_delmulti_v6(grp, ill, orig_ifindex, |
| connp->conn_zoneid, B_FALSE, B_TRUE); |
| return (0); |
| } else { |
| ilgstat = ILGSTAT_CHANGE; |
| /* |
| * The current ill might be different from the one we were |
| * asked to join on (if failover has occurred); we should |
| * join on the ill stored in the ilg. The original ill |
| * is noted in ilg_orig_ifindex, which matched our request. |
| */ |
| ill = ilg->ilg_ill; |
| /* preserve existing state in case ip_addmulti() fails */ |
| orig_fmode = ilg->ilg_fmode; |
| if (ilg->ilg_filter == NULL) { |
| orig_filter = NULL; |
| } else { |
| orig_filter = l_alloc_copy(ilg->ilg_filter); |
| if (orig_filter == NULL) { |
| mutex_exit(&connp->conn_lock); |
| return (ENOMEM); |
| } |
| } |
| } |
| |
| /* |
| * Alloc buffer to copy new state into (see below) before |
| * we make any changes, so we can bail if it fails. |
| */ |
| if ((new_filter = l_alloc()) == NULL) { |
| mutex_exit(&connp->conn_lock); |
| err = ENOMEM; |
| goto free_and_exit; |
| } |
| |
| if (gf->gf_numsrc == 0) { |
| CLEAR_SLIST(ilg->ilg_filter); |
| } else { |
| slist_t *fp; |
| if (ilg->ilg_filter == NULL) { |
| fp = l_alloc(); |
| if (fp == NULL) { |
| if (ilgstat == ILGSTAT_NEW) |
| ilg_delete(connp, ilg, NULL); |
| mutex_exit(&connp->conn_lock); |
| err = ENOMEM; |
| goto free_and_exit; |
| } |
| } else { |
| fp = ilg->ilg_filter; |
| } |
| for (i = 0, sl = gf->gf_slist; i < gf->gf_numsrc; i++, sl++) { |
| sin6 = (struct sockaddr_in6 *)sl; |
| fp->sl_addr[i] = sin6->sin6_addr; |
| } |
| fp->sl_numsrc = gf->gf_numsrc; |
| ilg->ilg_filter = fp; |
| } |
| /* |
| * In the kernel, we use the state definitions MODE_IS_[IN|EX]CLUDE |
| * to identify the filter mode; but the API uses MCAST_[IN|EX]CLUDE. |
| * So we need to translate here. |
| */ |
| ilg->ilg_fmode = (gf->gf_fmode == MCAST_INCLUDE) ? |
| MODE_IS_INCLUDE : MODE_IS_EXCLUDE; |
| |
| /* |
| * Save copy of ilg's filter state to pass to other functions, |
| * so we can release conn_lock now. |
| */ |
| new_fmode = ilg->ilg_fmode; |
| l_copy(ilg->ilg_filter, new_filter); |
| |
| mutex_exit(&connp->conn_lock); |
| |
| err = ip_addmulti_v6(grp, ill, orig_ifindex, connp->conn_zoneid, |
| ilgstat, new_fmode, new_filter); |
| if (err != 0) { |
| /* |
| * Restore the original filter state, or delete the |
| * newly-created ilg. We need to look up the ilg |
| * again, though, since we've not been holding the |
| * conn_lock. |
| */ |
| mutex_enter(&connp->conn_lock); |
| ilg = ilg_lookup_ill_index_v6(connp, grp, orig_ifindex); |
| ASSERT(ilg != NULL); |
| if (ilgstat == ILGSTAT_NEW) { |
| ilg_delete(connp, ilg, NULL); |
| } else { |
| ilg->ilg_fmode = orig_fmode; |
| if (SLIST_IS_EMPTY(orig_filter)) { |
| CLEAR_SLIST(ilg->ilg_filter); |
| } else { |
| /* |
| * We didn't free the filter, even if we |
| * were trying to make the source list empty; |
| * so if orig_filter isn't empty, the ilg |
| * must still have a filter alloc'd. |
| */ |
| l_copy(orig_filter, ilg->ilg_filter); |
| } |
| } |
| mutex_exit(&connp->conn_lock); |
| } |
| |
| free_and_exit: |
| l_free(orig_filter); |
| l_free(new_filter); |
| |
| return (err); |
| } |
| |
| /* |
| * Process the SIOC[GS]MSFILTER and SIOC[GS]IPMSFILTER ioctls. |
| */ |
| /* ARGSUSED */ |
| int |
| ip_sioctl_msfilter(ipif_t *ipif, sin_t *dummy_sin, queue_t *q, mblk_t *mp, |
| ip_ioctl_cmd_t *ipip, void *ifreq) |
| { |
| struct iocblk *iocp = (struct iocblk *)mp->b_rptr; |
| /* existence verified in ip_wput_nondata() */ |
| mblk_t *data_mp = mp->b_cont->b_cont; |
| int datalen, err, cmd, minsize; |
| int expsize = 0; |
| conn_t *connp; |
| boolean_t isv6, is_v4only_api, getcmd; |
| struct sockaddr_in *gsin; |
| struct sockaddr_in6 *gsin6; |
| ipaddr_t v4grp; |
| in6_addr_t v6grp; |
| struct group_filter *gf = NULL; |
| struct ip_msfilter *imsf = NULL; |
| mblk_t *ndp; |
| |
| if (data_mp->b_cont != NULL) { |
| if ((ndp = msgpullup(data_mp, -1)) == NULL) |
| return (ENOMEM); |
| freemsg(data_mp); |
| data_mp = ndp; |
| mp->b_cont->b_cont = data_mp; |
| } |
| |
| cmd = iocp->ioc_cmd; |
| getcmd = (cmd == SIOCGIPMSFILTER || cmd == SIOCGMSFILTER); |
| is_v4only_api = (cmd == SIOCGIPMSFILTER || cmd == SIOCSIPMSFILTER); |
| minsize = (is_v4only_api) ? IP_MSFILTER_SIZE(0) : GROUP_FILTER_SIZE(0); |
| datalen = MBLKL(data_mp); |
| |
| if (datalen < minsize) |
| return (EINVAL); |
| |
| /* |
| * now we know we have at least have the initial structure, |
| * but need to check for the source list array. |
| */ |
| if (is_v4only_api) { |
| imsf = (struct ip_msfilter *)data_mp->b_rptr; |
| isv6 = B_FALSE; |
| expsize = IP_MSFILTER_SIZE(imsf->imsf_numsrc); |
| } else { |
| gf = (struct group_filter *)data_mp->b_rptr; |
| if (gf->gf_group.ss_family == AF_INET6) { |
| gsin6 = (struct sockaddr_in6 *)&gf->gf_group; |
| isv6 = !(IN6_IS_ADDR_V4MAPPED(&gsin6->sin6_addr)); |
| } else { |
| isv6 = B_FALSE; |
| } |
| expsize = GROUP_FILTER_SIZE(gf->gf_numsrc); |
| } |
| if (datalen < expsize) |
| return (EINVAL); |
| |
| connp = Q_TO_CONN(q); |
| |
| /* operation not supported on the virtual network interface */ |
| if (IS_VNI(ipif->ipif_ill)) |
| return (EINVAL); |
| |
| if (isv6) { |
| ill_t *ill = ipif->ipif_ill; |
| ill_refhold(ill); |
| |
| gsin6 = (struct sockaddr_in6 *)&gf->gf_group; |
| v6grp = gsin6->sin6_addr; |
| if (getcmd) |
| err = ip_get_srcfilter_v6(connp, gf, &v6grp, ill); |
| else |
| err = ip_set_srcfilter_v6(connp, gf, &v6grp, ill); |
| |
| ill_refrele(ill); |
| } else { |
| boolean_t isv4mapped = B_FALSE; |
| if (is_v4only_api) { |
| v4grp = (ipaddr_t)imsf->imsf_multiaddr.s_addr; |
| } else { |
| if (gf->gf_group.ss_family == AF_INET) { |
| gsin = (struct sockaddr_in *)&gf->gf_group; |
| v4grp = (ipaddr_t)gsin->sin_addr.s_addr; |
| } else { |
| gsin6 = (struct sockaddr_in6 *)&gf->gf_group; |
| IN6_V4MAPPED_TO_IPADDR(&gsin6->sin6_addr, |
| v4grp); |
| isv4mapped = B_TRUE; |
| } |
| } |
| if (getcmd) |
| err = ip_get_srcfilter(connp, gf, imsf, v4grp, ipif, |
| isv4mapped); |
| else |
| err = ip_set_srcfilter(connp, gf, imsf, v4grp, ipif, |
| isv4mapped); |
| } |
| |
| return (err); |
| } |
| |
| /* |
| * Finds the ipif based on information in the ioctl headers. Needed to make |
| * ip_process_ioctl() happy (it needs to know the ipif for IPI_WR-flagged |
| * ioctls prior to calling the ioctl's handler function). Somewhat analogous |
| * to ip_extract_lifreq_cmn() and ip_extract_tunreq(). |
| */ |
| int |
| ip_extract_msfilter(queue_t *q, mblk_t *mp, ipif_t **ipifpp, ipsq_func_t func) |
| { |
| struct iocblk *iocp = (struct iocblk *)mp->b_rptr; |
| int cmd = iocp->ioc_cmd, err = 0; |
| conn_t *connp; |
| ipif_t *ipif; |
| /* caller has verified this mblk exists */ |
| char *dbuf = (char *)mp->b_cont->b_cont->b_rptr; |
| struct ip_msfilter *imsf; |
| struct group_filter *gf; |
| ipaddr_t v4addr, v4grp; |
| in6_addr_t v6grp; |
| uint32_t index; |
| zoneid_t zoneid; |
| ip_stack_t *ipst; |
| |
| connp = Q_TO_CONN(q); |
| zoneid = connp->conn_zoneid; |
| ipst = connp->conn_netstack->netstack_ip; |
| |
| /* don't allow multicast operations on a tcp conn */ |
| if (IPCL_IS_TCP(connp)) |
| return (ENOPROTOOPT); |
| |
| if (cmd == SIOCSIPMSFILTER || cmd == SIOCGIPMSFILTER) { |
| /* don't allow v4-specific ioctls on v6 socket */ |
| if (connp->conn_af_isv6) |
| return (EAFNOSUPPORT); |
| |
| imsf = (struct ip_msfilter *)dbuf; |
| v4addr = imsf->imsf_interface.s_addr; |
| v4grp = imsf->imsf_multiaddr.s_addr; |
| if (v4addr == INADDR_ANY) { |
| ipif = ipif_lookup_group(v4grp, zoneid, ipst); |
| if (ipif == NULL) |
| err = EADDRNOTAVAIL; |
| } else { |
| ipif = ipif_lookup_addr(v4addr, NULL, zoneid, q, mp, |
| func, &err, ipst); |
| } |
| } else { |
| boolean_t isv6 = B_FALSE; |
| gf = (struct group_filter *)dbuf; |
| index = gf->gf_interface; |
| if (gf->gf_group.ss_family == AF_INET6) { |
| struct sockaddr_in6 *sin6; |
| sin6 = (struct sockaddr_in6 *)&gf->gf_group; |
| v6grp = sin6->sin6_addr; |
| if (IN6_IS_ADDR_V4MAPPED(&v6grp)) |
| IN6_V4MAPPED_TO_IPADDR(&v6grp, v4grp); |
| else |
| isv6 = B_TRUE; |
| } else if (gf->gf_group.ss_family == AF_INET) { |
| struct sockaddr_in *sin; |
| sin = (struct sockaddr_in *)&gf->gf_group; |
| v4grp = sin->sin_addr.s_addr; |
| } else { |
| return (EAFNOSUPPORT); |
| } |
| if (index == 0) { |
| if (isv6) { |
| ipif = ipif_lookup_group_v6(&v6grp, zoneid, |
| ipst); |
| } else { |
| ipif = ipif_lookup_group(v4grp, zoneid, ipst); |
| } |
| if (ipif == NULL) |
| err = EADDRNOTAVAIL; |
| } else { |
| ipif = ipif_lookup_on_ifindex(index, isv6, zoneid, |
| q, mp, func, &err, ipst); |
| } |
| } |
| |
| *ipifpp = ipif; |
| return (err); |
| } |
| |
| /* |
| * The structures used for the SIOC*MSFILTER ioctls usually must be copied |
| * in in two stages, as the first copyin tells us the size of the attached |
| * source buffer. This function is called by ip_wput_nondata() after the |
| * first copyin has completed; it figures out how big the second stage |
| * needs to be, and kicks it off. |
| * |
| * In some cases (numsrc < 2), the second copyin is not needed as the |
| * first one gets a complete structure containing 1 source addr. |
| * |
| * The function returns 0 if a second copyin has been started (i.e. there's |
| * no more work to be done right now), or 1 if the second copyin is not |
| * needed and ip_wput_nondata() can continue its processing. |
| */ |
| int |
| ip_copyin_msfilter(queue_t *q, mblk_t *mp) |
| { |
| struct iocblk *iocp = (struct iocblk *)mp->b_rptr; |
| int cmd = iocp->ioc_cmd; |
| /* validity of this checked in ip_wput_nondata() */ |
| mblk_t *mp1 = mp->b_cont->b_cont; |
| int copysize = 0; |
| int offset; |
| |
| if (cmd == SIOCSMSFILTER || cmd == SIOCGMSFILTER) { |
| struct group_filter *gf = (struct group_filter *)mp1->b_rptr; |
| if (gf->gf_numsrc >= 2) { |
| offset = sizeof (struct group_filter); |
| copysize = GROUP_FILTER_SIZE(gf->gf_numsrc) - offset; |
| } |
| } else { |
| struct ip_msfilter *imsf = (struct ip_msfilter *)mp1->b_rptr; |
| if (imsf->imsf_numsrc >= 2) { |
| offset = sizeof (struct ip_msfilter); |
| copysize = IP_MSFILTER_SIZE(imsf->imsf_numsrc) - offset; |
| } |
| } |
| if (copysize > 0) { |
| mi_copyin_n(q, mp, offset, copysize); |
| return (0); |
| } |
| return (1); |
| } |
| |
| /* |
| * Handle the following optmgmt: |
| * IP_ADD_MEMBERSHIP must not have joined already |
| * MCAST_JOIN_GROUP must not have joined already |
| * IP_BLOCK_SOURCE must have joined already |
| * MCAST_BLOCK_SOURCE must have joined already |
| * IP_JOIN_SOURCE_GROUP may have joined already |
| * MCAST_JOIN_SOURCE_GROUP may have joined already |
| * |
| * fmode and src parameters may be used to determine which option is |
| * being set, as follows (the IP_* and MCAST_* versions of each option |
| * are functionally equivalent): |
| * opt fmode src |
| * IP_ADD_MEMBERSHIP MODE_IS_EXCLUDE INADDR_ANY |
| * MCAST_JOIN_GROUP MODE_IS_EXCLUDE INADDR_ANY |
| * IP_BLOCK_SOURCE MODE_IS_EXCLUDE v4 addr |
| * MCAST_BLOCK_SOURCE MODE_IS_EXCLUDE v4 addr |
| * IP_JOIN_SOURCE_GROUP MODE_IS_INCLUDE v4 addr |
| * MCAST_JOIN_SOURCE_GROUP MODE_IS_INCLUDE v4 addr |
| * |
| * Changing the filter mode is not allowed; if a matching ilg already |
| * exists and fmode != ilg->ilg_fmode, EINVAL is returned. |
| * |
| * Verifies that there is a source address of appropriate scope for |
| * the group; if not, EADDRNOTAVAIL is returned. |
| * |
| * The interface to be used may be identified by an address or by an |
| * index. A pointer to the index is passed; if it is NULL, use the |
| * address, otherwise, use the index. |
| */ |
| int |
| ip_opt_add_group(conn_t *connp, boolean_t checkonly, ipaddr_t group, |
| ipaddr_t ifaddr, uint_t *ifindexp, mcast_record_t fmode, ipaddr_t src, |
| mblk_t *first_mp) |
| { |
| ipif_t *ipif; |
| ipsq_t *ipsq; |
| int err = 0; |
| ill_t *ill; |
| |
| err = ip_opt_check(connp, group, src, ifaddr, ifindexp, first_mp, |
| ip_restart_optmgmt, &ipif); |
| if (err != 0) { |
| if (err != EINPROGRESS) { |
| ip1dbg(("ip_opt_add_group: no ipif for group 0x%x, " |
| "ifaddr 0x%x, ifindex %d\n", ntohl(group), |
| ntohl(ifaddr), (ifindexp == NULL) ? 0 : *ifindexp)); |
| } |
| return (err); |
| } |
| ASSERT(ipif != NULL); |
| |
| ill = ipif->ipif_ill; |
| /* Operation not supported on a virtual network interface */ |
| if (IS_VNI(ill)) { |
| ipif_refrele(ipif); |
| return (EINVAL); |
| } |
| |
| if (checkonly) { |
| /* |
| * do not do operation, just pretend to - new T_CHECK |
| * semantics. The error return case above if encountered |
| * considered a good enough "check" here. |
| */ |
| ipif_refrele(ipif); |
| return (0); |
| } |
| |
| IPSQ_ENTER_IPIF(ipif, connp, first_mp, ip_restart_optmgmt, ipsq, |
| NEW_OP); |
| |
| /* unspecified source addr => no source filtering */ |
| err = ilg_add(connp, group, ipif, fmode, src); |
| |
| IPSQ_EXIT(ipsq); |
| |
| ipif_refrele(ipif); |
| return (err); |
| } |
|