ip_mroute.c revision 1eee170a5f6cf875d905524fea524c7c5c870aa0
/*
* 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 (c) 1991, 2010, Oracle and/or its affiliates. All rights reserved.
*/
/* Copyright (c) 1990 Mentat Inc. */
/*
* Procedures for the kernel part of DVMRP,
* a Distance-Vector Multicast Routing Protocol.
* (See RFC-1075)
* Written by David Waitzman, BBN Labs, August 1988.
* Modified by Steve Deering, Stanford, February 1989.
* Modified by Mark J. Steiglitz, Stanford, May, 1991
* Modified by Van Jacobson, LBL, January 1993
* Modified by Ajit Thyagarajan, PARC, August 1993
* Modified by Bill Fenner, PARC, April 1995
*
* MROUTING 3.5
*/
/*
* TODO
* - function pointer field in vif, void *vif_sendit()
*/
#include <sys/types.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/strlog.h>
#include <sys/systm.h>
#include <sys/ddi.h>
#include <sys/cmn_err.h>
#include <sys/zone.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/vtrace.h>
#include <sys/debug.h>
#include <net/if.h>
#include <sys/sockio.h>
#include <netinet/in.h>
#include <net/if_dl.h>
#include <inet/ipsec_impl.h>
#include <inet/common.h>
#include <inet/mi.h>
#include <inet/nd.h>
#include <inet/tunables.h>
#include <inet/mib2.h>
#include <netinet/ip6.h>
#include <inet/ip.h>
#include <inet/snmpcom.h>
#include <netinet/igmp.h>
#include <netinet/igmp_var.h>
#include <netinet/udp.h>
#include <netinet/ip_mroute.h>
#include <inet/ip_multi.h>
#include <inet/ip_ire.h>
#include <inet/ip_ndp.h>
#include <inet/ip_if.h>
#include <inet/ipclassifier.h>
#include <netinet/pim.h>
/*
* MT Design:
*
* There are three main data structures viftable, mfctable and tbftable that
* need to be protected against MT races.
*
* vitable is a fixed length array of vif structs. There is no lock to protect
* the whole array, instead each struct is protected by its own indiviual lock.
* The value of v_marks in conjuction with the value of v_refcnt determines the
* current state of a vif structure. One special state that needs mention
* is when the vif is marked VIF_MARK_NOTINUSE but refcnt != 0. This indicates
* that vif is being initalized.
* Each structure is freed when the refcnt goes down to zero. If a delete comes
* in when the recfnt is > 1, the vif structure is marked VIF_MARK_CONDEMNED
* which prevents the struct from further use. When the refcnt goes to zero
* the struct is freed and is marked VIF_MARK_NOTINUSE.
* vif struct stores a pointer to the ipif in v_ipif, to prevent ipif/ill
* from going away a refhold is put on the ipif before using it. see
* lock_good_vif() and unlock_good_vif().
*
* VIF_REFHOLD and VIF_REFRELE macros have been provided to manipulate refcnts
* of the vif struct.
*
* tbftable is also a fixed length array of tbf structs and is only accessed
* via v_tbf. It is protected by its own lock tbf_lock.
*
* Lock Ordering is
* v_lock --> tbf_lock
* v_lock --> ill_locK
*
* mfctable is a fixed size hash table of mfc buckets strcuts (struct mfcb).
* Each mfc bucket struct (struct mfcb) maintains a refcnt for each walker,
* it also maintains a state. These fields are protected by a lock (mfcb_lock).
* mfc structs only maintain a state and have no refcnt. mfc_mutex is used to
* protect the struct elements.
*
* mfc structs are dynamically allocated and are singly linked
* at the head of the chain. When an mfc structure is to be deleted
* it is marked condemned and so is the state in the bucket struct.
* When the last walker of the hash bucket exits all the mfc structs
* marked condemed are freed.
*
* Locking Hierarchy:
* The bucket lock should be acquired before the mfc struct lock.
* MFCB_REFHOLD and MFCB_REFRELE macros are provided for locking
* operations on the bucket struct.
*
* last_encap_lock and numvifs_mutex should be acquired after
* acquring vif or mfc locks. These locks protect some global variables.
*
* The statistics are not currently protected by a lock
* causing the stats be be approximate, not exact.
*/
#define NO_VIF MAXVIFS /* from mrouted, no route for src */
/*
* Timeouts:
* Upcall timeouts - BSD uses boolean_t mfc->expire and
* nexpire[MFCTBLSIZE], the number of times expire has been called.
* SunOS 5.x uses mfc->timeout for each mfc.
* Some Unixes are limited in the number of simultaneous timeouts
* that can be run, SunOS 5.x does not have this restriction.
*/
/*
* In BSD, EXPIRE_TIMEOUT is how often expire_upcalls() is called and
* UPCALL_EXPIRE is the nmber of timeouts before a particular upcall
* expires. Thus the time till expiration is EXPIRE_TIMEOUT * UPCALL_EXPIRE
*/
#define EXPIRE_TIMEOUT (hz/4) /* 4x / second */
#define UPCALL_EXPIRE 6 /* number of timeouts */
/*
* Hash function for a source, group entry
*/
#define MFCHASH(a, g) MFCHASHMOD(((a) >> 20) ^ ((a) >> 10) ^ (a) ^ \
((g) >> 20) ^ ((g) >> 10) ^ (g))
#define TBF_REPROCESS (hz / 100) /* 100x /second */
/* Identify PIM packet that came on a Register interface */
#define PIM_REGISTER_MARKER 0xffffffff
/* Function declarations */
static int add_mfc(struct mfcctl *, ip_stack_t *);
static int add_vif(struct vifctl *, conn_t *, ip_stack_t *);
static int del_mfc(struct mfcctl *, ip_stack_t *);
static int del_vif(vifi_t *, ip_stack_t *);
static void del_vifp(struct vif *);
static void encap_send(ipha_t *, mblk_t *, struct vif *, ipaddr_t);
static void expire_upcalls(void *);
static void fill_route(struct mfc *, struct mfcctl *, ip_stack_t *);
static void free_queue(struct mfc *);
static int get_assert(uchar_t *, ip_stack_t *);
static int get_lsg_cnt(struct sioc_lsg_req *, ip_stack_t *);
static int get_sg_cnt(struct sioc_sg_req *, ip_stack_t *);
static int get_version(uchar_t *);
static int get_vif_cnt(struct sioc_vif_req *, ip_stack_t *);
static int ip_mdq(mblk_t *, ipha_t *, ill_t *,
ipaddr_t, struct mfc *);
static int ip_mrouter_init(conn_t *, uchar_t *, int, ip_stack_t *);
static void phyint_send(ipha_t *, mblk_t *, struct vif *, ipaddr_t);
static int register_mforward(mblk_t *, ip_recv_attr_t *);
static void register_send(ipha_t *, mblk_t *, struct vif *, ipaddr_t);
static int set_assert(int *, ip_stack_t *);
/*
* Token Bucket Filter functions
*/
static int priority(struct vif *, ipha_t *);
static void tbf_control(struct vif *, mblk_t *, ipha_t *);
static int tbf_dq_sel(struct vif *, ipha_t *);
static void tbf_process_q(struct vif *);
static void tbf_queue(struct vif *, mblk_t *);
static void tbf_reprocess_q(void *);
static void tbf_send_packet(struct vif *, mblk_t *);
static void tbf_update_tokens(struct vif *);
static void release_mfc(struct mfcb *);
static boolean_t is_mrouter_off(ip_stack_t *);
/*
* Encapsulation packets
*/
#define ENCAP_TTL 64
/* prototype IP hdr for encapsulated packets */
static ipha_t multicast_encap_iphdr = {
IP_SIMPLE_HDR_VERSION,
0, /* tos */
sizeof (ipha_t), /* total length */
0, /* id */
0, /* frag offset */
ENCAP_TTL, IPPROTO_ENCAP,
0, /* checksum */
};
/*
* Rate limit for assert notification messages, in nsec.
*/
#define ASSERT_MSG_TIME 3000000000
#define VIF_REFHOLD(vifp) { \
mutex_enter(&(vifp)->v_lock); \
(vifp)->v_refcnt++; \
mutex_exit(&(vifp)->v_lock); \
}
#define VIF_REFRELE_LOCKED(vifp) { \
(vifp)->v_refcnt--; \
if ((vifp)->v_refcnt == 0 && \
((vifp)->v_marks & VIF_MARK_CONDEMNED)) { \
del_vifp(vifp); \
} else { \
mutex_exit(&(vifp)->v_lock); \
} \
}
#define VIF_REFRELE(vifp) { \
mutex_enter(&(vifp)->v_lock); \
(vifp)->v_refcnt--; \
if ((vifp)->v_refcnt == 0 && \
((vifp)->v_marks & VIF_MARK_CONDEMNED)) { \
del_vifp(vifp); \
} else { \
mutex_exit(&(vifp)->v_lock); \
} \
}
#define MFCB_REFHOLD(mfcb) { \
mutex_enter(&(mfcb)->mfcb_lock); \
(mfcb)->mfcb_refcnt++; \
ASSERT((mfcb)->mfcb_refcnt != 0); \
mutex_exit(&(mfcb)->mfcb_lock); \
}
#define MFCB_REFRELE(mfcb) { \
mutex_enter(&(mfcb)->mfcb_lock); \
ASSERT((mfcb)->mfcb_refcnt != 0); \
if (--(mfcb)->mfcb_refcnt == 0 && \
((mfcb)->mfcb_marks & MFCB_MARK_CONDEMNED)) { \
release_mfc(mfcb); \
} \
mutex_exit(&(mfcb)->mfcb_lock); \
}
/*
* MFCFIND:
* Find a route for a given origin IP address and multicast group address.
* Skip entries with pending upcalls.
* Type of service parameter to be added in the future!
*/
#define MFCFIND(mfcbp, o, g, rt) { \
struct mfc *_mb_rt = NULL; \
rt = NULL; \
_mb_rt = mfcbp->mfcb_mfc; \
while (_mb_rt) { \
if ((_mb_rt->mfc_origin.s_addr == o) && \
(_mb_rt->mfc_mcastgrp.s_addr == g) && \
(_mb_rt->mfc_rte == NULL) && \
(!(_mb_rt->mfc_marks & MFCB_MARK_CONDEMNED))) { \
rt = _mb_rt; \
break; \
} \
_mb_rt = _mb_rt->mfc_next; \
} \
}
/*
* BSD uses timeval with sec and usec. In SunOS 5.x uniqtime() and gethrtime()
* are inefficient. We use gethrestime() which returns a timespec_t with
* sec and nsec, the resolution is machine dependent.
* The following 2 macros have been changed to use nsec instead of usec.
*/
/*
* Macros to compute elapsed time efficiently.
* Borrowed from Van Jacobson's scheduling code.
* Delta should be a hrtime_t.
*/
#define TV_DELTA(a, b, delta) { \
int xxs; \
\
delta = (a).tv_nsec - (b).tv_nsec; \
if ((xxs = (a).tv_sec - (b).tv_sec) != 0) { \
switch (xxs) { \
case 2: \
delta += 1000000000; \
/*FALLTHROUGH*/ \
case 1: \
delta += 1000000000; \
break; \
default: \
delta += (1000000000 * xxs); \
} \
} \
}
#define TV_LT(a, b) (((a).tv_nsec < (b).tv_nsec && \
(a).tv_sec <= (b).tv_sec) || (a).tv_sec < (b).tv_sec)
/*
* Handle MRT setsockopt commands to modify the multicast routing tables.
*/
int
ip_mrouter_set(int cmd, conn_t *connp, int checkonly, uchar_t *data,
int datalen)
{
ip_stack_t *ipst = connp->conn_netstack->netstack_ip;
mutex_enter(&ipst->ips_ip_g_mrouter_mutex);
if (cmd != MRT_INIT && connp != ipst->ips_ip_g_mrouter) {
mutex_exit(&ipst->ips_ip_g_mrouter_mutex);
return (EACCES);
}
mutex_exit(&ipst->ips_ip_g_mrouter_mutex);
if (checkonly) {
/*
* do not do operation, just pretend to - new T_CHECK
* Note: Even routines further on can probably fail but
* this T_CHECK stuff is only to please XTI so it not
* necessary to be perfect.
*/
switch (cmd) {
case MRT_INIT:
case MRT_DONE:
case MRT_ADD_VIF:
case MRT_DEL_VIF:
case MRT_ADD_MFC:
case MRT_DEL_MFC:
case MRT_ASSERT:
return (0);
default:
return (EOPNOTSUPP);
}
}
/*
* make sure no command is issued after multicast routing has been
* turned off.
*/
if (cmd != MRT_INIT && cmd != MRT_DONE) {
if (is_mrouter_off(ipst))
return (EINVAL);
}
switch (cmd) {
case MRT_INIT: return (ip_mrouter_init(connp, data, datalen, ipst));
case MRT_DONE: return (ip_mrouter_done(ipst));
case MRT_ADD_VIF: return (add_vif((struct vifctl *)data, connp, ipst));
case MRT_DEL_VIF: return (del_vif((vifi_t *)data, ipst));
case MRT_ADD_MFC: return (add_mfc((struct mfcctl *)data, ipst));
case MRT_DEL_MFC: return (del_mfc((struct mfcctl *)data, ipst));
case MRT_ASSERT: return (set_assert((int *)data, ipst));
default: return (EOPNOTSUPP);
}
}
/*
* Handle MRT getsockopt commands
*/
int
ip_mrouter_get(int cmd, conn_t *connp, uchar_t *data)
{
ip_stack_t *ipst = connp->conn_netstack->netstack_ip;
if (connp != ipst->ips_ip_g_mrouter)
return (EACCES);
switch (cmd) {
case MRT_VERSION: return (get_version((uchar_t *)data));
case MRT_ASSERT: return (get_assert((uchar_t *)data, ipst));
default: return (EOPNOTSUPP);
}
}
/*
* Handle ioctl commands to obtain information from the cache.
* Called with shared access to IP. These are read_only ioctls.
*/
/* ARGSUSED */
int
mrt_ioctl(ipif_t *ipif, sin_t *sin, queue_t *q, mblk_t *mp,
ip_ioctl_cmd_t *ipip, void *if_req)
{
mblk_t *mp1;
struct iocblk *iocp = (struct iocblk *)mp->b_rptr;
conn_t *connp = Q_TO_CONN(q);
ip_stack_t *ipst = connp->conn_netstack->netstack_ip;
/* Existence verified in ip_wput_nondata */
mp1 = mp->b_cont->b_cont;
switch (iocp->ioc_cmd) {
case (SIOCGETVIFCNT):
return (get_vif_cnt((struct sioc_vif_req *)mp1->b_rptr, ipst));
case (SIOCGETSGCNT):
return (get_sg_cnt((struct sioc_sg_req *)mp1->b_rptr, ipst));
case (SIOCGETLSGCNT):
return (get_lsg_cnt((struct sioc_lsg_req *)mp1->b_rptr, ipst));
default:
return (EINVAL);
}
}
/*
* Returns the packet, byte, rpf-failure count for the source, group provided.
*/
static int
get_sg_cnt(struct sioc_sg_req *req, ip_stack_t *ipst)
{
struct mfc *rt;
struct mfcb *mfcbp;
mfcbp = &ipst->ips_mfcs[MFCHASH(req->src.s_addr, req->grp.s_addr)];
MFCB_REFHOLD(mfcbp);
MFCFIND(mfcbp, req->src.s_addr, req->grp.s_addr, rt);
if (rt != NULL) {
mutex_enter(&rt->mfc_mutex);
req->pktcnt = rt->mfc_pkt_cnt;
req->bytecnt = rt->mfc_byte_cnt;
req->wrong_if = rt->mfc_wrong_if;
mutex_exit(&rt->mfc_mutex);
} else
req->pktcnt = req->bytecnt = req->wrong_if = 0xffffffffU;
MFCB_REFRELE(mfcbp);
return (0);
}
/*
* Returns the packet, byte, rpf-failure count for the source, group provided.
* Uses larger counters and IPv6 addresses.
*/
/* ARGSUSED XXX until implemented */
static int
get_lsg_cnt(struct sioc_lsg_req *req, ip_stack_t *ipst)
{
/* XXX TODO SIOCGETLSGCNT */
return (ENXIO);
}
/*
* Returns the input and output packet and byte counts on the vif provided.
*/
static int
get_vif_cnt(struct sioc_vif_req *req, ip_stack_t *ipst)
{
vifi_t vifi = req->vifi;
if (vifi >= ipst->ips_numvifs)
return (EINVAL);
/*
* No locks here, an approximation is fine.
*/
req->icount = ipst->ips_vifs[vifi].v_pkt_in;
req->ocount = ipst->ips_vifs[vifi].v_pkt_out;
req->ibytes = ipst->ips_vifs[vifi].v_bytes_in;
req->obytes = ipst->ips_vifs[vifi].v_bytes_out;
return (0);
}
static int
get_version(uchar_t *data)
{
int *v = (int *)data;
*v = 0x0305; /* XXX !!!! */
return (0);
}
/*
* Set PIM assert processing global.
*/
static int
set_assert(int *i, ip_stack_t *ipst)
{
if ((*i != 1) && (*i != 0))
return (EINVAL);
ipst->ips_pim_assert = *i;
return (0);
}
/*
* Get PIM assert processing global.
*/
static int
get_assert(uchar_t *data, ip_stack_t *ipst)
{
int *i = (int *)data;
*i = ipst->ips_pim_assert;
return (0);
}
/*
* Enable multicast routing.
*/
static int
ip_mrouter_init(conn_t *connp, uchar_t *data, int datalen, ip_stack_t *ipst)
{
int *v;
if (data == NULL || (datalen != sizeof (int)))
return (ENOPROTOOPT);
v = (int *)data;
if (*v != 1)
return (ENOPROTOOPT);
mutex_enter(&ipst->ips_ip_g_mrouter_mutex);
if (ipst->ips_ip_g_mrouter != NULL) {
mutex_exit(&ipst->ips_ip_g_mrouter_mutex);
return (EADDRINUSE);
}
/*
* MRT_INIT should only be allowed for RAW sockets, but we double
* check.
*/
if (!IPCL_IS_RAWIP(connp)) {
mutex_exit(&ipst->ips_ip_g_mrouter_mutex);
return (EINVAL);
}
ipst->ips_ip_g_mrouter = connp;
connp->conn_multi_router = 1;
/* In order for tunnels to work we have to turn ip_g_forward on */
if (!WE_ARE_FORWARDING(ipst)) {
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(connp->conn_rq, 1, SL_TRACE,
"ip_mrouter_init: turning on forwarding");
}
ipst->ips_saved_ip_forwarding = ipst->ips_ip_forwarding;
ipst->ips_ip_forwarding = IP_FORWARD_ALWAYS;
}
mutex_exit(&ipst->ips_ip_g_mrouter_mutex);
return (0);
}
void
ip_mrouter_stack_init(ip_stack_t *ipst)
{
mutex_init(&ipst->ips_ip_g_mrouter_mutex, NULL, MUTEX_DEFAULT, NULL);
ipst->ips_vifs = kmem_zalloc(sizeof (struct vif) * (MAXVIFS+1),
KM_SLEEP);
ipst->ips_mrtstat = kmem_zalloc(sizeof (struct mrtstat), KM_SLEEP);
/*
* mfctable:
* Includes all mfcs, including waiting upcalls.
* Multiple mfcs per bucket.
*/
ipst->ips_mfcs = kmem_zalloc(sizeof (struct mfcb) * MFCTBLSIZ,
KM_SLEEP);
/*
* Define the token bucket filter structures.
* tbftable -> each vif has one of these for storing info.
*/
ipst->ips_tbfs = kmem_zalloc(sizeof (struct tbf) * MAXVIFS, KM_SLEEP);
mutex_init(&ipst->ips_last_encap_lock, NULL, MUTEX_DEFAULT, NULL);
ipst->ips_mrtstat->mrts_vifctlSize = sizeof (struct vifctl);
ipst->ips_mrtstat->mrts_mfcctlSize = sizeof (struct mfcctl);
}
/*
* Disable multicast routing.
* Didn't use global timeout_val (BSD version), instead check the mfctable.
*/
int
ip_mrouter_done(ip_stack_t *ipst)
{
conn_t *mrouter;
vifi_t vifi;
struct mfc *mfc_rt;
int i;
mutex_enter(&ipst->ips_ip_g_mrouter_mutex);
if (ipst->ips_ip_g_mrouter == NULL) {
mutex_exit(&ipst->ips_ip_g_mrouter_mutex);
return (EINVAL);
}
mrouter = ipst->ips_ip_g_mrouter;
if (ipst->ips_saved_ip_forwarding != -1) {
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"ip_mrouter_done: turning off forwarding");
}
ipst->ips_ip_forwarding = ipst->ips_saved_ip_forwarding;
ipst->ips_saved_ip_forwarding = -1;
}
/*
* Always clear cache when vifs change.
* No need to get ipst->ips_last_encap_lock since we are running as
* a writer.
*/
mutex_enter(&ipst->ips_last_encap_lock);
ipst->ips_last_encap_src = 0;
ipst->ips_last_encap_vif = NULL;
mutex_exit(&ipst->ips_last_encap_lock);
mrouter->conn_multi_router = 0;
mutex_exit(&ipst->ips_ip_g_mrouter_mutex);
/*
* For each phyint in use,
* disable promiscuous reception of all IP multicasts.
*/
for (vifi = 0; vifi < MAXVIFS; vifi++) {
struct vif *vifp = ipst->ips_vifs + vifi;
mutex_enter(&vifp->v_lock);
/*
* if the vif is active mark it condemned.
*/
if (vifp->v_marks & VIF_MARK_GOOD) {
ASSERT(vifp->v_ipif != NULL);
ipif_refhold(vifp->v_ipif);
/* Phyint only */
if (!(vifp->v_flags & (VIFF_TUNNEL | VIFF_REGISTER))) {
ipif_t *ipif = vifp->v_ipif;
ilm_t *ilm = vifp->v_ilm;
vifp->v_ilm = NULL;
vifp->v_marks &= ~VIF_MARK_GOOD;
vifp->v_marks |= VIF_MARK_CONDEMNED;
mutex_exit(&(vifp)->v_lock);
if (ilm != NULL) {
ill_t *ill = ipif->ipif_ill;
(void) ip_delmulti(ilm);
ASSERT(ill->ill_mrouter_cnt > 0);
atomic_dec_32(&ill->ill_mrouter_cnt);
}
mutex_enter(&vifp->v_lock);
}
ipif_refrele(vifp->v_ipif);
/*
* decreases the refcnt added in add_vif.
* and release v_lock.
*/
VIF_REFRELE_LOCKED(vifp);
} else {
mutex_exit(&vifp->v_lock);
continue;
}
}
mutex_enter(&ipst->ips_numvifs_mutex);
ipst->ips_numvifs = 0;
ipst->ips_pim_assert = 0;
ipst->ips_reg_vif_num = ALL_VIFS;
mutex_exit(&ipst->ips_numvifs_mutex);
/*
* Free upcall msgs.
* Go through mfctable and stop any outstanding upcall
* timeouts remaining on mfcs.
*/
for (i = 0; i < MFCTBLSIZ; i++) {
mutex_enter(&ipst->ips_mfcs[i].mfcb_lock);
ipst->ips_mfcs[i].mfcb_refcnt++;
ipst->ips_mfcs[i].mfcb_marks |= MFCB_MARK_CONDEMNED;
mutex_exit(&ipst->ips_mfcs[i].mfcb_lock);
mfc_rt = ipst->ips_mfcs[i].mfcb_mfc;
while (mfc_rt) {
/* Free upcalls */
mutex_enter(&mfc_rt->mfc_mutex);
if (mfc_rt->mfc_rte != NULL) {
if (mfc_rt->mfc_timeout_id != 0) {
/*
* OK to drop the lock as we have
* a refcnt on the bucket. timeout
* can fire but it will see that
* mfc_timeout_id == 0 and not do
* anything. see expire_upcalls().
*/
mfc_rt->mfc_timeout_id = 0;
mutex_exit(&mfc_rt->mfc_mutex);
(void) untimeout(
mfc_rt->mfc_timeout_id);
mfc_rt->mfc_timeout_id = 0;
mutex_enter(&mfc_rt->mfc_mutex);
/*
* all queued upcall packets
* and mblk will be freed in
* release_mfc().
*/
}
}
mfc_rt->mfc_marks |= MFCB_MARK_CONDEMNED;
mutex_exit(&mfc_rt->mfc_mutex);
mfc_rt = mfc_rt->mfc_next;
}
MFCB_REFRELE(&ipst->ips_mfcs[i]);
}
mutex_enter(&ipst->ips_ip_g_mrouter_mutex);
ipst->ips_ip_g_mrouter = NULL;
mutex_exit(&ipst->ips_ip_g_mrouter_mutex);
return (0);
}
void
ip_mrouter_stack_destroy(ip_stack_t *ipst)
{
struct mfcb *mfcbp;
struct mfc *rt;
int i;
for (i = 0; i < MFCTBLSIZ; i++) {
mfcbp = &ipst->ips_mfcs[i];
while ((rt = mfcbp->mfcb_mfc) != NULL) {
(void) printf("ip_mrouter_stack_destroy: free for %d\n",
i);
mfcbp->mfcb_mfc = rt->mfc_next;
free_queue(rt);
mi_free(rt);
}
}
kmem_free(ipst->ips_vifs, sizeof (struct vif) * (MAXVIFS+1));
ipst->ips_vifs = NULL;
kmem_free(ipst->ips_mrtstat, sizeof (struct mrtstat));
ipst->ips_mrtstat = NULL;
kmem_free(ipst->ips_mfcs, sizeof (struct mfcb) * MFCTBLSIZ);
ipst->ips_mfcs = NULL;
kmem_free(ipst->ips_tbfs, sizeof (struct tbf) * MAXVIFS);
ipst->ips_tbfs = NULL;
mutex_destroy(&ipst->ips_last_encap_lock);
mutex_destroy(&ipst->ips_ip_g_mrouter_mutex);
}
static boolean_t
is_mrouter_off(ip_stack_t *ipst)
{
conn_t *mrouter;
mutex_enter(&ipst->ips_ip_g_mrouter_mutex);
if (ipst->ips_ip_g_mrouter == NULL) {
mutex_exit(&ipst->ips_ip_g_mrouter_mutex);
return (B_TRUE);
}
mrouter = ipst->ips_ip_g_mrouter;
if (mrouter->conn_multi_router == 0) {
mutex_exit(&ipst->ips_ip_g_mrouter_mutex);
return (B_TRUE);
}
mutex_exit(&ipst->ips_ip_g_mrouter_mutex);
return (B_FALSE);
}
static void
unlock_good_vif(struct vif *vifp)
{
ASSERT(vifp->v_ipif != NULL);
ipif_refrele(vifp->v_ipif);
VIF_REFRELE(vifp);
}
static boolean_t
lock_good_vif(struct vif *vifp)
{
mutex_enter(&vifp->v_lock);
if (!(vifp->v_marks & VIF_MARK_GOOD)) {
mutex_exit(&vifp->v_lock);
return (B_FALSE);
}
ASSERT(vifp->v_ipif != NULL);
mutex_enter(&vifp->v_ipif->ipif_ill->ill_lock);
if (!IPIF_CAN_LOOKUP(vifp->v_ipif)) {
mutex_exit(&vifp->v_ipif->ipif_ill->ill_lock);
mutex_exit(&vifp->v_lock);
return (B_FALSE);
}
ipif_refhold_locked(vifp->v_ipif);
mutex_exit(&vifp->v_ipif->ipif_ill->ill_lock);
vifp->v_refcnt++;
mutex_exit(&vifp->v_lock);
return (B_TRUE);
}
/*
* Add a vif to the vif table.
*/
static int
add_vif(struct vifctl *vifcp, conn_t *connp, ip_stack_t *ipst)
{
struct vif *vifp = ipst->ips_vifs + vifcp->vifc_vifi;
ipif_t *ipif;
int error = 0;
struct tbf *v_tbf = ipst->ips_tbfs + vifcp->vifc_vifi;
conn_t *mrouter = ipst->ips_ip_g_mrouter;
ilm_t *ilm;
ill_t *ill;
ASSERT(connp != NULL);
if (vifcp->vifc_vifi >= MAXVIFS)
return (EINVAL);
if (is_mrouter_off(ipst))
return (EINVAL);
mutex_enter(&vifp->v_lock);
/*
* Viftable entry should be 0.
* if v_marks == 0 but v_refcnt != 0 means struct is being
* initialized.
*
* Also note that it is very unlikely that we will get a MRT_ADD_VIF
* request while the delete is in progress, mrouted only sends add
* requests when a new interface is added and the new interface cannot
* have the same vifi as an existing interface. We make sure that
* ill_delete will block till the vif is deleted by adding a refcnt
* to ipif in del_vif().
*/
if (vifp->v_lcl_addr.s_addr != 0 ||
vifp->v_marks != 0 ||
vifp->v_refcnt != 0) {
mutex_exit(&vifp->v_lock);
return (EADDRINUSE);
}
/* Incoming vif should not be 0 */
if (vifcp->vifc_lcl_addr.s_addr == 0) {
mutex_exit(&vifp->v_lock);
return (EINVAL);
}
vifp->v_refcnt++;
mutex_exit(&vifp->v_lock);
/* Find the interface with the local address */
ipif = ipif_lookup_addr((ipaddr_t)vifcp->vifc_lcl_addr.s_addr, NULL,
IPCL_ZONEID(connp), ipst);
if (ipif == NULL) {
VIF_REFRELE(vifp);
return (EADDRNOTAVAIL);
}
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"add_vif: src 0x%x enter",
vifcp->vifc_lcl_addr.s_addr);
}
mutex_enter(&vifp->v_lock);
/*
* Always clear cache when vifs change.
* Needed to ensure that src isn't left over from before vif was added.
* No need to get last_encap_lock, since we are running as a writer.
*/
mutex_enter(&ipst->ips_last_encap_lock);
ipst->ips_last_encap_src = 0;
ipst->ips_last_encap_vif = NULL;
mutex_exit(&ipst->ips_last_encap_lock);
if (vifcp->vifc_flags & VIFF_TUNNEL) {
if ((vifcp->vifc_flags & VIFF_SRCRT) != 0) {
cmn_err(CE_WARN,
"add_vif: source route tunnels not supported\n");
VIF_REFRELE_LOCKED(vifp);
ipif_refrele(ipif);
return (EOPNOTSUPP);
}
vifp->v_rmt_addr = vifcp->vifc_rmt_addr;
} else {
/* Phyint or Register vif */
if (vifcp->vifc_flags & VIFF_REGISTER) {
/*
* Note: Since all IPPROTO_IP level options (including
* MRT_ADD_VIF) are done exclusively via
* ip_optmgmt_writer(), a lock is not necessary to
* protect reg_vif_num.
*/
mutex_enter(&ipst->ips_numvifs_mutex);
if (ipst->ips_reg_vif_num == ALL_VIFS) {
ipst->ips_reg_vif_num = vifcp->vifc_vifi;
mutex_exit(&ipst->ips_numvifs_mutex);
} else {
mutex_exit(&ipst->ips_numvifs_mutex);
VIF_REFRELE_LOCKED(vifp);
ipif_refrele(ipif);
return (EADDRINUSE);
}
}
/* Make sure the interface supports multicast */
if ((ipif->ipif_ill->ill_flags & ILLF_MULTICAST) == 0) {
VIF_REFRELE_LOCKED(vifp);
ipif_refrele(ipif);
if (vifcp->vifc_flags & VIFF_REGISTER) {
mutex_enter(&ipst->ips_numvifs_mutex);
ipst->ips_reg_vif_num = ALL_VIFS;
mutex_exit(&ipst->ips_numvifs_mutex);
}
return (EOPNOTSUPP);
}
/* Enable promiscuous reception of all IP mcasts from the if */
mutex_exit(&vifp->v_lock);
ill = ipif->ipif_ill;
if (IS_UNDER_IPMP(ill))
ill = ipmp_ill_hold_ipmp_ill(ill);
if (ill == NULL) {
ilm = NULL;
} else {
ilm = ip_addmulti(&ipv6_all_zeros, ill,
ipif->ipif_zoneid, &error);
if (ilm != NULL)
atomic_inc_32(&ill->ill_mrouter_cnt);
if (IS_UNDER_IPMP(ipif->ipif_ill)) {
ill_refrele(ill);
ill = ipif->ipif_ill;
}
}
mutex_enter(&vifp->v_lock);
/*
* since we released the lock lets make sure that
* ip_mrouter_done() has not been called.
*/
if (ilm == NULL || is_mrouter_off(ipst)) {
if (ilm != NULL) {
(void) ip_delmulti(ilm);
ASSERT(ill->ill_mrouter_cnt > 0);
atomic_dec_32(&ill->ill_mrouter_cnt);
}
if (vifcp->vifc_flags & VIFF_REGISTER) {
mutex_enter(&ipst->ips_numvifs_mutex);
ipst->ips_reg_vif_num = ALL_VIFS;
mutex_exit(&ipst->ips_numvifs_mutex);
}
VIF_REFRELE_LOCKED(vifp);
ipif_refrele(ipif);
return (error?error:EINVAL);
}
vifp->v_ilm = ilm;
}
/* Define parameters for the tbf structure */
vifp->v_tbf = v_tbf;
gethrestime(&vifp->v_tbf->tbf_last_pkt_t);
vifp->v_tbf->tbf_n_tok = 0;
vifp->v_tbf->tbf_q_len = 0;
vifp->v_tbf->tbf_max_q_len = MAXQSIZE;
vifp->v_tbf->tbf_q = vifp->v_tbf->tbf_t = NULL;
vifp->v_flags = vifcp->vifc_flags;
vifp->v_threshold = vifcp->vifc_threshold;
vifp->v_lcl_addr = vifcp->vifc_lcl_addr;
vifp->v_ipif = ipif;
ipif_refrele(ipif);
/* Scaling up here, allows division by 1024 in critical code. */
vifp->v_rate_limit = vifcp->vifc_rate_limit * (1024/1000);
vifp->v_timeout_id = 0;
/* initialize per vif pkt counters */
vifp->v_pkt_in = 0;
vifp->v_pkt_out = 0;
vifp->v_bytes_in = 0;
vifp->v_bytes_out = 0;
mutex_init(&vifp->v_tbf->tbf_lock, NULL, MUTEX_DEFAULT, NULL);
/* Adjust numvifs up, if the vifi is higher than numvifs */
mutex_enter(&ipst->ips_numvifs_mutex);
if (ipst->ips_numvifs <= vifcp->vifc_vifi)
ipst->ips_numvifs = vifcp->vifc_vifi + 1;
mutex_exit(&ipst->ips_numvifs_mutex);
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"add_vif: #%d, lcladdr %x, %s %x, thresh %x, rate %d",
vifcp->vifc_vifi,
ntohl(vifcp->vifc_lcl_addr.s_addr),
(vifcp->vifc_flags & VIFF_TUNNEL) ? "rmtaddr" : "mask",
ntohl(vifcp->vifc_rmt_addr.s_addr),
vifcp->vifc_threshold, vifcp->vifc_rate_limit);
}
vifp->v_marks = VIF_MARK_GOOD;
mutex_exit(&vifp->v_lock);
return (0);
}
/* Delete a vif from the vif table. */
static void
del_vifp(struct vif *vifp)
{
struct tbf *t = vifp->v_tbf;
mblk_t *mp0;
vifi_t vifi;
ip_stack_t *ipst = vifp->v_ipif->ipif_ill->ill_ipst;
conn_t *mrouter = ipst->ips_ip_g_mrouter;
ASSERT(vifp->v_marks & VIF_MARK_CONDEMNED);
ASSERT(t != NULL);
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"del_vif: src 0x%x\n", vifp->v_lcl_addr.s_addr);
}
if (vifp->v_timeout_id != 0) {
(void) untimeout(vifp->v_timeout_id);
vifp->v_timeout_id = 0;
}
/*
* Free packets queued at the interface.
* Mrouted takes care of cleaning up mfcs - makes calls to del_mfc.
*/
mutex_enter(&t->tbf_lock);
while (t->tbf_q != NULL) {
mp0 = t->tbf_q;
t->tbf_q = t->tbf_q->b_next;
mp0->b_prev = mp0->b_next = NULL;
freemsg(mp0);
}
mutex_exit(&t->tbf_lock);
/*
* Always clear cache when vifs change.
* No need to get last_encap_lock since we are running as a writer.
*/
mutex_enter(&ipst->ips_last_encap_lock);
if (vifp == ipst->ips_last_encap_vif) {
ipst->ips_last_encap_vif = NULL;
ipst->ips_last_encap_src = 0;
}
mutex_exit(&ipst->ips_last_encap_lock);
mutex_destroy(&t->tbf_lock);
bzero(vifp->v_tbf, sizeof (*(vifp->v_tbf)));
/* Adjust numvifs down */
mutex_enter(&ipst->ips_numvifs_mutex);
for (vifi = ipst->ips_numvifs; vifi != 0; vifi--) /* vifi is unsigned */
if (ipst->ips_vifs[vifi - 1].v_lcl_addr.s_addr != 0)
break;
ipst->ips_numvifs = vifi;
mutex_exit(&ipst->ips_numvifs_mutex);
bzero(vifp, sizeof (*vifp));
}
static int
del_vif(vifi_t *vifip, ip_stack_t *ipst)
{
struct vif *vifp = ipst->ips_vifs + *vifip;
if (*vifip >= ipst->ips_numvifs)
return (EINVAL);
mutex_enter(&vifp->v_lock);
/*
* Not initialized
* Here we are not looking at the vif that is being initialized
* i.e vifp->v_marks == 0 and refcnt > 0.
*/
if (vifp->v_lcl_addr.s_addr == 0 ||
!(vifp->v_marks & VIF_MARK_GOOD)) {
mutex_exit(&vifp->v_lock);
return (EADDRNOTAVAIL);
}
/* Clear VIF_MARK_GOOD and set VIF_MARK_CONDEMNED. */
vifp->v_marks &= ~VIF_MARK_GOOD;
vifp->v_marks |= VIF_MARK_CONDEMNED;
/* Phyint only */
if (!(vifp->v_flags & (VIFF_TUNNEL | VIFF_REGISTER))) {
ipif_t *ipif = vifp->v_ipif;
ilm_t *ilm = vifp->v_ilm;
vifp->v_ilm = NULL;
ASSERT(ipif != NULL);
/*
* should be OK to drop the lock as we
* have marked this as CONDEMNED.
*/
mutex_exit(&(vifp)->v_lock);
if (ilm != NULL) {
(void) ip_delmulti(ilm);
ASSERT(ipif->ipif_ill->ill_mrouter_cnt > 0);
atomic_dec_32(&ipif->ipif_ill->ill_mrouter_cnt);
}
mutex_enter(&(vifp)->v_lock);
}
if (vifp->v_flags & VIFF_REGISTER) {
mutex_enter(&ipst->ips_numvifs_mutex);
ipst->ips_reg_vif_num = ALL_VIFS;
mutex_exit(&ipst->ips_numvifs_mutex);
}
/*
* decreases the refcnt added in add_vif.
*/
VIF_REFRELE_LOCKED(vifp);
return (0);
}
/*
* Add an mfc entry.
*/
static int
add_mfc(struct mfcctl *mfccp, ip_stack_t *ipst)
{
struct mfc *rt;
struct rtdetq *rte;
ushort_t nstl;
int i;
struct mfcb *mfcbp;
conn_t *mrouter = ipst->ips_ip_g_mrouter;
/*
* The value of vifi is NO_VIF (==MAXVIFS) if Mrouted
* did not have a real route for pkt.
* We want this pkt without rt installed in the mfctable to prevent
* multiiple tries, so go ahead and put it in mfctable, it will
* be discarded later in ip_mdq() because the child is NULL.
*/
/* Error checking, out of bounds? */
if (mfccp->mfcc_parent > MAXVIFS) {
ip0dbg(("ADD_MFC: mfcc_parent out of range %d",
(int)mfccp->mfcc_parent));
return (EINVAL);
}
if ((mfccp->mfcc_parent != NO_VIF) &&
(ipst->ips_vifs[mfccp->mfcc_parent].v_ipif == NULL)) {
ip0dbg(("ADD_MFC: NULL ipif for parent vif %d\n",
(int)mfccp->mfcc_parent));
return (EINVAL);
}
if (is_mrouter_off(ipst)) {
return (EINVAL);
}
mfcbp = &ipst->ips_mfcs[MFCHASH(mfccp->mfcc_origin.s_addr,
mfccp->mfcc_mcastgrp.s_addr)];
MFCB_REFHOLD(mfcbp);
MFCFIND(mfcbp, mfccp->mfcc_origin.s_addr,
mfccp->mfcc_mcastgrp.s_addr, rt);
/* If an entry already exists, just update the fields */
if (rt) {
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"add_mfc: update o %x grp %x parent %x",
ntohl(mfccp->mfcc_origin.s_addr),
ntohl(mfccp->mfcc_mcastgrp.s_addr),
mfccp->mfcc_parent);
}
mutex_enter(&rt->mfc_mutex);
rt->mfc_parent = mfccp->mfcc_parent;
mutex_enter(&ipst->ips_numvifs_mutex);
for (i = 0; i < (int)ipst->ips_numvifs; i++)
rt->mfc_ttls[i] = mfccp->mfcc_ttls[i];
mutex_exit(&ipst->ips_numvifs_mutex);
mutex_exit(&rt->mfc_mutex);
MFCB_REFRELE(mfcbp);
return (0);
}
/*
* Find the entry for which the upcall was made and update.
*/
for (rt = mfcbp->mfcb_mfc, nstl = 0; rt; rt = rt->mfc_next) {
mutex_enter(&rt->mfc_mutex);
if ((rt->mfc_origin.s_addr == mfccp->mfcc_origin.s_addr) &&
(rt->mfc_mcastgrp.s_addr == mfccp->mfcc_mcastgrp.s_addr) &&
(rt->mfc_rte != NULL) &&
!(rt->mfc_marks & MFCB_MARK_CONDEMNED)) {
if (nstl++ != 0)
cmn_err(CE_WARN,
"add_mfc: %s o %x g %x p %x",
"multiple kernel entries",
ntohl(mfccp->mfcc_origin.s_addr),
ntohl(mfccp->mfcc_mcastgrp.s_addr),
mfccp->mfcc_parent);
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1,
SL_TRACE,
"add_mfc: o %x g %x p %x",
ntohl(mfccp->mfcc_origin.s_addr),
ntohl(mfccp->mfcc_mcastgrp.s_addr),
mfccp->mfcc_parent);
}
fill_route(rt, mfccp, ipst);
/*
* Prevent cleanup of cache entry.
* Timer starts in ip_mforward.
*/
if (rt->mfc_timeout_id != 0) {
timeout_id_t id;
id = rt->mfc_timeout_id;
/*
* setting id to zero will avoid this
* entry from being cleaned up in
* expire_up_calls().
*/
rt->mfc_timeout_id = 0;
/*
* dropping the lock is fine as we
* have a refhold on the bucket.
* so mfc cannot be freed.
* The timeout can fire but it will see
* that mfc_timeout_id == 0 and not cleanup.
*/
mutex_exit(&rt->mfc_mutex);
(void) untimeout(id);
mutex_enter(&rt->mfc_mutex);
}
/*
* Send all pkts that are queued waiting for the upcall.
* ip_mdq param tun set to 0 -
* the return value of ip_mdq() isn't used here,
* so value we send doesn't matter.
*/
while (rt->mfc_rte != NULL) {
rte = rt->mfc_rte;
rt->mfc_rte = rte->rte_next;
mutex_exit(&rt->mfc_mutex);
(void) ip_mdq(rte->mp, (ipha_t *)
rte->mp->b_rptr, rte->ill, 0, rt);
freemsg(rte->mp);
mi_free((char *)rte);
mutex_enter(&rt->mfc_mutex);
}
}
mutex_exit(&rt->mfc_mutex);
}
/*
* It is possible that an entry is being inserted without an upcall
*/
if (nstl == 0) {
mutex_enter(&(mfcbp->mfcb_lock));
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"add_mfc: no upcall o %x g %x p %x",
ntohl(mfccp->mfcc_origin.s_addr),
ntohl(mfccp->mfcc_mcastgrp.s_addr),
mfccp->mfcc_parent);
}
if (is_mrouter_off(ipst)) {
mutex_exit(&mfcbp->mfcb_lock);
MFCB_REFRELE(mfcbp);
return (EINVAL);
}
for (rt = mfcbp->mfcb_mfc; rt; rt = rt->mfc_next) {
mutex_enter(&rt->mfc_mutex);
if ((rt->mfc_origin.s_addr ==
mfccp->mfcc_origin.s_addr) &&
(rt->mfc_mcastgrp.s_addr ==
mfccp->mfcc_mcastgrp.s_addr) &&
(!(rt->mfc_marks & MFCB_MARK_CONDEMNED))) {
fill_route(rt, mfccp, ipst);
mutex_exit(&rt->mfc_mutex);
break;
}
mutex_exit(&rt->mfc_mutex);
}
/* No upcall, so make a new entry into mfctable */
if (rt == NULL) {
rt = (struct mfc *)mi_zalloc(sizeof (struct mfc));
if (rt == NULL) {
ip1dbg(("add_mfc: out of memory\n"));
mutex_exit(&mfcbp->mfcb_lock);
MFCB_REFRELE(mfcbp);
return (ENOBUFS);
}
/* Insert new entry at head of hash chain */
mutex_enter(&rt->mfc_mutex);
fill_route(rt, mfccp, ipst);
/* Link into table */
rt->mfc_next = mfcbp->mfcb_mfc;
mfcbp->mfcb_mfc = rt;
mutex_exit(&rt->mfc_mutex);
}
mutex_exit(&mfcbp->mfcb_lock);
}
MFCB_REFRELE(mfcbp);
return (0);
}
/*
* Fills in mfc structure from mrouted mfcctl.
*/
static void
fill_route(struct mfc *rt, struct mfcctl *mfccp, ip_stack_t *ipst)
{
int i;
rt->mfc_origin = mfccp->mfcc_origin;
rt->mfc_mcastgrp = mfccp->mfcc_mcastgrp;
rt->mfc_parent = mfccp->mfcc_parent;
mutex_enter(&ipst->ips_numvifs_mutex);
for (i = 0; i < (int)ipst->ips_numvifs; i++) {
rt->mfc_ttls[i] = mfccp->mfcc_ttls[i];
}
mutex_exit(&ipst->ips_numvifs_mutex);
/* Initialize pkt counters per src-grp */
rt->mfc_pkt_cnt = 0;
rt->mfc_byte_cnt = 0;
rt->mfc_wrong_if = 0;
rt->mfc_last_assert.tv_sec = rt->mfc_last_assert.tv_nsec = 0;
}
static void
free_queue(struct mfc *mfcp)
{
struct rtdetq *rte0;
/*
* Drop all queued upcall packets.
* Free the mbuf with the pkt.
*/
while ((rte0 = mfcp->mfc_rte) != NULL) {
mfcp->mfc_rte = rte0->rte_next;
freemsg(rte0->mp);
mi_free((char *)rte0);
}
}
/*
* go thorugh the hash bucket and free all the entries marked condemned.
*/
void
release_mfc(struct mfcb *mfcbp)
{
struct mfc *current_mfcp;
struct mfc *prev_mfcp;
prev_mfcp = current_mfcp = mfcbp->mfcb_mfc;
while (current_mfcp != NULL) {
if (current_mfcp->mfc_marks & MFCB_MARK_CONDEMNED) {
if (current_mfcp == mfcbp->mfcb_mfc) {
mfcbp->mfcb_mfc = current_mfcp->mfc_next;
free_queue(current_mfcp);
mi_free(current_mfcp);
prev_mfcp = current_mfcp = mfcbp->mfcb_mfc;
continue;
}
ASSERT(prev_mfcp != NULL);
prev_mfcp->mfc_next = current_mfcp->mfc_next;
free_queue(current_mfcp);
mi_free(current_mfcp);
current_mfcp = NULL;
} else {
prev_mfcp = current_mfcp;
}
current_mfcp = prev_mfcp->mfc_next;
}
mfcbp->mfcb_marks &= ~MFCB_MARK_CONDEMNED;
ASSERT(mfcbp->mfcb_mfc != NULL || mfcbp->mfcb_marks == 0);
}
/*
* Delete an mfc entry.
*/
static int
del_mfc(struct mfcctl *mfccp, ip_stack_t *ipst)
{
struct in_addr origin;
struct in_addr mcastgrp;
struct mfc *rt;
uint_t hash;
conn_t *mrouter = ipst->ips_ip_g_mrouter;
origin = mfccp->mfcc_origin;
mcastgrp = mfccp->mfcc_mcastgrp;
hash = MFCHASH(origin.s_addr, mcastgrp.s_addr);
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"del_mfc: o %x g %x",
ntohl(origin.s_addr),
ntohl(mcastgrp.s_addr));
}
MFCB_REFHOLD(&ipst->ips_mfcs[hash]);
/* Find mfc in mfctable, finds only entries without upcalls */
for (rt = ipst->ips_mfcs[hash].mfcb_mfc; rt; rt = rt->mfc_next) {
mutex_enter(&rt->mfc_mutex);
if (origin.s_addr == rt->mfc_origin.s_addr &&
mcastgrp.s_addr == rt->mfc_mcastgrp.s_addr &&
rt->mfc_rte == NULL &&
!(rt->mfc_marks & MFCB_MARK_CONDEMNED))
break;
mutex_exit(&rt->mfc_mutex);
}
/*
* Return if there was an upcall (mfc_rte != NULL,
* or rt not in mfctable.
*/
if (rt == NULL) {
MFCB_REFRELE(&ipst->ips_mfcs[hash]);
return (EADDRNOTAVAIL);
}
/*
* no need to hold lock as we have a reference.
*/
ipst->ips_mfcs[hash].mfcb_marks |= MFCB_MARK_CONDEMNED;
/* error checking */
if (rt->mfc_timeout_id != 0) {
ip0dbg(("del_mfc: TIMEOUT NOT 0, rte not null"));
/*
* Its ok to drop the lock, the struct cannot be freed
* since we have a ref on the hash bucket.
*/
rt->mfc_timeout_id = 0;
mutex_exit(&rt->mfc_mutex);
(void) untimeout(rt->mfc_timeout_id);
mutex_enter(&rt->mfc_mutex);
}
ASSERT(rt->mfc_rte == NULL);
/*
* Delete the entry from the cache
*/
rt->mfc_marks |= MFCB_MARK_CONDEMNED;
mutex_exit(&rt->mfc_mutex);
MFCB_REFRELE(&ipst->ips_mfcs[hash]);
return (0);
}
#define TUNNEL_LEN 12 /* # bytes of IP option for tunnel encapsulation */
/*
* IP multicast forwarding function. This function assumes that the packet
* pointed to by ipha has arrived on (or is about to be sent to) the interface
* pointed to by "ill", and the packet is to be relayed to other networks
* that have members of the packet's destination IP multicast group.
*
* The packet is returned unscathed to the caller, unless it is
* erroneous, in which case a -1 value tells the caller (IP)
* to discard it.
*
* Unlike BSD, SunOS 5.x needs to return to IP info about
* whether pkt came in thru a tunnel, so it can be discarded, unless
* it's IGMP. In BSD, the ifp is bogus for tunnels, so pkt won't try
* to be delivered.
* Return values are 0 - pkt is okay and phyint
* -1 - pkt is malformed and to be tossed
* 1 - pkt came in on tunnel
*/
int
ip_mforward(mblk_t *mp, ip_recv_attr_t *ira)
{
ipha_t *ipha = (ipha_t *)mp->b_rptr;
ill_t *ill = ira->ira_ill;
struct mfc *rt;
ipaddr_t src, dst, tunnel_src = 0;
static int srctun = 0;
vifi_t vifi;
boolean_t pim_reg_packet = B_FALSE;
struct mfcb *mfcbp;
ip_stack_t *ipst = ill->ill_ipst;
conn_t *mrouter = ipst->ips_ip_g_mrouter;
ill_t *rill = ira->ira_rill;
ASSERT(ira->ira_pktlen == msgdsize(mp));
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"ip_mforward: RECV ipha_src %x, ipha_dst %x, ill %s",
ntohl(ipha->ipha_src), ntohl(ipha->ipha_dst),
ill->ill_name);
}
dst = ipha->ipha_dst;
if (ira->ira_flags & IRAF_PIM_REGISTER)
pim_reg_packet = B_TRUE;
else if (ira->ira_flags & IRAF_MROUTE_TUNNEL_SET)
tunnel_src = ira->ira_mroute_tunnel;
/*
* Don't forward a packet with time-to-live of zero or one,
* or a packet destined to a local-only group.
*/
if (CLASSD(dst) && (ipha->ipha_ttl <= 1 ||
(ipaddr_t)ntohl(dst) <= INADDR_MAX_LOCAL_GROUP)) {
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"ip_mforward: not forwarded ttl %d,"
" dst 0x%x ill %s",
ipha->ipha_ttl, ntohl(dst), ill->ill_name);
}
if (tunnel_src != 0)
return (1);
else
return (0);
}
if ((tunnel_src != 0) || pim_reg_packet) {
/*
* Packet arrived over an encapsulated tunnel or via a PIM
* register message.
*/
if (ipst->ips_ip_mrtdebug > 1) {
if (tunnel_src != 0) {
(void) mi_strlog(mrouter->conn_rq, 1,
SL_TRACE,
"ip_mforward: ill %s arrived via ENCAP TUN",
ill->ill_name);
} else if (pim_reg_packet) {
(void) mi_strlog(mrouter->conn_rq, 1,
SL_TRACE,
"ip_mforward: ill %s arrived via"
" REGISTER VIF",
ill->ill_name);
}
}
} else if ((ipha->ipha_version_and_hdr_length & 0xf) <
(uint_t)(IP_SIMPLE_HDR_LENGTH + TUNNEL_LEN) >> 2 ||
((uchar_t *)(ipha + 1))[1] != IPOPT_LSRR) {
/* Packet arrived via a physical interface. */
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"ip_mforward: ill %s arrived via PHYINT",
ill->ill_name);
}
} else {
/*
* Packet arrived through a SRCRT tunnel.
* Source-route tunnels are no longer supported.
* Error message printed every 1000 times.
*/
if ((srctun++ % 1000) == 0) {
cmn_err(CE_WARN,
"ip_mforward: received source-routed pkt from %x",
ntohl(ipha->ipha_src));
}
return (-1);
}
ipst->ips_mrtstat->mrts_fwd_in++;
src = ipha->ipha_src;
/* Find route in cache, return NULL if not there or upcalls q'ed. */
/*
* Lock the mfctable against changes made by ip_mforward.
* Note that only add_mfc and del_mfc can remove entries and
* they run with exclusive access to IP. So we do not need to
* guard against the rt being deleted, so release lock after reading.
*/
if (is_mrouter_off(ipst))
return (-1);
mfcbp = &ipst->ips_mfcs[MFCHASH(src, dst)];
MFCB_REFHOLD(mfcbp);
MFCFIND(mfcbp, src, dst, rt);
/* Entry exists, so forward if necessary */
if (rt != NULL) {
int ret = 0;
ipst->ips_mrtstat->mrts_mfc_hits++;
if (pim_reg_packet) {
ASSERT(ipst->ips_reg_vif_num != ALL_VIFS);
ret = ip_mdq(mp, ipha,
ipst->ips_vifs[ipst->ips_reg_vif_num].
v_ipif->ipif_ill,
0, rt);
} else {
ret = ip_mdq(mp, ipha, ill, tunnel_src, rt);
}
MFCB_REFRELE(mfcbp);
return (ret);
/*
* Don't forward if we don't have a cache entry. Mrouted will
* always provide a cache entry in response to an upcall.
*/
} else {
/*
* If we don't have a route for packet's origin, make a copy
* of the packet and send message to routing daemon.
*/
struct mfc *mfc_rt = NULL;
mblk_t *mp0 = NULL;
mblk_t *mp_copy = NULL;
struct rtdetq *rte = NULL;
struct rtdetq *rte_m, *rte1, *prev_rte;
uint_t hash;
int npkts;
boolean_t new_mfc = B_FALSE;
ipst->ips_mrtstat->mrts_mfc_misses++;
/* BSD uses mrts_no_route++ */
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"ip_mforward: no rte ill %s src %x g %x misses %d",
ill->ill_name, ntohl(src), ntohl(dst),
(int)ipst->ips_mrtstat->mrts_mfc_misses);
}
/*
* The order of the following code differs from the BSD code.
* Pre-mc3.5, the BSD code was incorrect and SunOS 5.x
* code works, so SunOS 5.x wasn't changed to conform to the
* BSD version.
*/
/* Lock mfctable. */
hash = MFCHASH(src, dst);
mutex_enter(&(ipst->ips_mfcs[hash].mfcb_lock));
/*
* If we are turning off mrouted return an error
*/
if (is_mrouter_off(ipst)) {
mutex_exit(&mfcbp->mfcb_lock);
MFCB_REFRELE(mfcbp);
return (-1);
}
/* Is there an upcall waiting for this packet? */
for (mfc_rt = ipst->ips_mfcs[hash].mfcb_mfc; mfc_rt;
mfc_rt = mfc_rt->mfc_next) {
mutex_enter(&mfc_rt->mfc_mutex);
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1,
SL_TRACE,
"ip_mforward: MFCTAB hash %d o 0x%x"
" g 0x%x\n",
hash, ntohl(mfc_rt->mfc_origin.s_addr),
ntohl(mfc_rt->mfc_mcastgrp.s_addr));
}
/* There is an upcall */
if ((src == mfc_rt->mfc_origin.s_addr) &&
(dst == mfc_rt->mfc_mcastgrp.s_addr) &&
(mfc_rt->mfc_rte != NULL) &&
!(mfc_rt->mfc_marks & MFCB_MARK_CONDEMNED)) {
break;
}
mutex_exit(&mfc_rt->mfc_mutex);
}
/* No upcall, so make a new entry into mfctable */
if (mfc_rt == NULL) {
mfc_rt = (struct mfc *)mi_zalloc(sizeof (struct mfc));
if (mfc_rt == NULL) {
ipst->ips_mrtstat->mrts_fwd_drop++;
ip1dbg(("ip_mforward: out of memory "
"for mfc, mfc_rt\n"));
goto error_return;
} else
new_mfc = B_TRUE;
/* Get resources */
/* TODO could copy header and dup rest */
mp_copy = copymsg(mp);
if (mp_copy == NULL) {
ipst->ips_mrtstat->mrts_fwd_drop++;
ip1dbg(("ip_mforward: out of memory for "
"mblk, mp_copy\n"));
goto error_return;
}
mutex_enter(&mfc_rt->mfc_mutex);
}
/* Get resources for rte, whether first rte or not first. */
/* Add this packet into rtdetq */
rte = (struct rtdetq *)mi_zalloc(sizeof (struct rtdetq));
if (rte == NULL) {
ipst->ips_mrtstat->mrts_fwd_drop++;
mutex_exit(&mfc_rt->mfc_mutex);
ip1dbg(("ip_mforward: out of memory for"
" rtdetq, rte\n"));
goto error_return;
}
mp0 = copymsg(mp);
if (mp0 == NULL) {
ipst->ips_mrtstat->mrts_fwd_drop++;
ip1dbg(("ip_mforward: out of memory for mblk, mp0\n"));
mutex_exit(&mfc_rt->mfc_mutex);
goto error_return;
}
rte->mp = mp0;
if (pim_reg_packet) {
ASSERT(ipst->ips_reg_vif_num != ALL_VIFS);
rte->ill =
ipst->ips_vifs[ipst->ips_reg_vif_num].
v_ipif->ipif_ill;
} else {
rte->ill = ill;
}
rte->rte_next = NULL;
/*
* Determine if upcall q (rtdetq) has overflowed.
* mfc_rt->mfc_rte is null by mi_zalloc
* if it is the first message.
*/
for (rte_m = mfc_rt->mfc_rte, npkts = 0; rte_m;
rte_m = rte_m->rte_next)
npkts++;
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"ip_mforward: upcalls %d\n", npkts);
}
if (npkts > MAX_UPQ) {
ipst->ips_mrtstat->mrts_upq_ovflw++;
mutex_exit(&mfc_rt->mfc_mutex);
goto error_return;
}
if (npkts == 0) { /* first upcall */
int i = 0;
/*
* Now finish installing the new mfc! Now that we have
* resources! Insert new entry at head of hash chain.
* Use src and dst which are ipaddr_t's.
*/
mfc_rt->mfc_origin.s_addr = src;
mfc_rt->mfc_mcastgrp.s_addr = dst;
mutex_enter(&ipst->ips_numvifs_mutex);
for (i = 0; i < (int)ipst->ips_numvifs; i++)
mfc_rt->mfc_ttls[i] = 0;
mutex_exit(&ipst->ips_numvifs_mutex);
mfc_rt->mfc_parent = ALL_VIFS;
/* Link into table */
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1,
SL_TRACE,
"ip_mforward: NEW MFCTAB hash %d o 0x%x "
"g 0x%x\n", hash,
ntohl(mfc_rt->mfc_origin.s_addr),
ntohl(mfc_rt->mfc_mcastgrp.s_addr));
}
mfc_rt->mfc_next = ipst->ips_mfcs[hash].mfcb_mfc;
ipst->ips_mfcs[hash].mfcb_mfc = mfc_rt;
mfc_rt->mfc_rte = NULL;
}
/* Link in the upcall */
/* First upcall */
if (mfc_rt->mfc_rte == NULL)
mfc_rt->mfc_rte = rte;
else {
/* not the first upcall */
prev_rte = mfc_rt->mfc_rte;
for (rte1 = mfc_rt->mfc_rte->rte_next; rte1;
prev_rte = rte1, rte1 = rte1->rte_next)
;
prev_rte->rte_next = rte;
}
/*
* No upcalls waiting, this is first one, so send a message to
* routing daemon to install a route into kernel table.
*/
if (npkts == 0) {
struct igmpmsg *im;
/* ipha_protocol is 0, for upcall */
ASSERT(mp_copy != NULL);
im = (struct igmpmsg *)mp_copy->b_rptr;
im->im_msgtype = IGMPMSG_NOCACHE;
im->im_mbz = 0;
mutex_enter(&ipst->ips_numvifs_mutex);
if (pim_reg_packet) {
im->im_vif = (uchar_t)ipst->ips_reg_vif_num;
mutex_exit(&ipst->ips_numvifs_mutex);
} else {
/*
* XXX do we need to hold locks here ?
*/
for (vifi = 0;
vifi < ipst->ips_numvifs;
vifi++) {
if (ipst->ips_vifs[vifi].v_ipif == NULL)
continue;
if (ipst->ips_vifs[vifi].
v_ipif->ipif_ill == ill) {
im->im_vif = (uchar_t)vifi;
break;
}
}
mutex_exit(&ipst->ips_numvifs_mutex);
ASSERT(vifi < ipst->ips_numvifs);
}
ipst->ips_mrtstat->mrts_upcalls++;
/* Timer to discard upcalls if mrouted is too slow */
mfc_rt->mfc_timeout_id = timeout(expire_upcalls,
mfc_rt, EXPIRE_TIMEOUT * UPCALL_EXPIRE);
mutex_exit(&mfc_rt->mfc_mutex);
mutex_exit(&(ipst->ips_mfcs[hash].mfcb_lock));
/* Pass to RAWIP */
ira->ira_ill = ira->ira_rill = NULL;
(mrouter->conn_recv)(mrouter, mp_copy, NULL, ira);
ira->ira_ill = ill;
ira->ira_rill = rill;
} else {
mutex_exit(&mfc_rt->mfc_mutex);
mutex_exit(&(ipst->ips_mfcs[hash].mfcb_lock));
BUMP_MIB(ill->ill_ip_mib, ipIfStatsInDiscards);
ip_drop_input("ip_mforward - upcall already waiting",
mp_copy, ill);
freemsg(mp_copy);
}
MFCB_REFRELE(mfcbp);
if (tunnel_src != 0)
return (1);
else
return (0);
error_return:
mutex_exit(&(ipst->ips_mfcs[hash].mfcb_lock));
MFCB_REFRELE(mfcbp);
if (mfc_rt != NULL && (new_mfc == B_TRUE))
mi_free((char *)mfc_rt);
if (rte != NULL)
mi_free((char *)rte);
if (mp_copy != NULL) {
BUMP_MIB(ill->ill_ip_mib, ipIfStatsInDiscards);
ip_drop_input("ip_mforward error", mp_copy, ill);
freemsg(mp_copy);
}
if (mp0 != NULL)
freemsg(mp0);
return (-1);
}
}
/*
* Clean up the mfctable cache entry if upcall is not serviced.
* SunOS 5.x has timeout per mfc, unlike BSD which has one timer.
*/
static void
expire_upcalls(void *arg)
{
struct mfc *mfc_rt = arg;
uint_t hash;
struct mfc *prev_mfc, *mfc0;
ip_stack_t *ipst;
conn_t *mrouter;
if (mfc_rt->mfc_rte == NULL || mfc_rt->mfc_rte->ill != NULL) {
cmn_err(CE_WARN, "expire_upcalls: no ILL\n");
return;
}
ipst = mfc_rt->mfc_rte->ill->ill_ipst;
mrouter = ipst->ips_ip_g_mrouter;
hash = MFCHASH(mfc_rt->mfc_origin.s_addr, mfc_rt->mfc_mcastgrp.s_addr);
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"expire_upcalls: hash %d s %x g %x",
hash, ntohl(mfc_rt->mfc_origin.s_addr),
ntohl(mfc_rt->mfc_mcastgrp.s_addr));
}
MFCB_REFHOLD(&ipst->ips_mfcs[hash]);
mutex_enter(&mfc_rt->mfc_mutex);
/*
* if timeout has been set to zero, than the
* entry has been filled, no need to delete it.
*/
if (mfc_rt->mfc_timeout_id == 0)
goto done;
ipst->ips_mrtstat->mrts_cache_cleanups++;
mfc_rt->mfc_timeout_id = 0;
/* Determine entry to be cleaned up in cache table. */
for (prev_mfc = mfc0 = ipst->ips_mfcs[hash].mfcb_mfc; mfc0;
prev_mfc = mfc0, mfc0 = mfc0->mfc_next)
if (mfc0 == mfc_rt)
break;
/* del_mfc takes care of gone mfcs */
ASSERT(prev_mfc != NULL);
ASSERT(mfc0 != NULL);
/*
* Delete the entry from the cache
*/
ipst->ips_mfcs[hash].mfcb_marks |= MFCB_MARK_CONDEMNED;
mfc_rt->mfc_marks |= MFCB_MARK_CONDEMNED;
/*
* release_mfc will drop all queued upcall packets.
* and will free the mbuf with the pkt, if, timing info.
*/
done:
mutex_exit(&mfc_rt->mfc_mutex);
MFCB_REFRELE(&ipst->ips_mfcs[hash]);
}
/*
* Packet forwarding routine once entry in the cache is made.
*/
static int
ip_mdq(mblk_t *mp, ipha_t *ipha, ill_t *ill, ipaddr_t tunnel_src,
struct mfc *rt)
{
vifi_t vifi;
struct vif *vifp;
ipaddr_t dst = ipha->ipha_dst;
size_t plen = msgdsize(mp);
vifi_t num_of_vifs;
ip_stack_t *ipst = ill->ill_ipst;
conn_t *mrouter = ipst->ips_ip_g_mrouter;
ip_recv_attr_t iras;
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"ip_mdq: SEND src %x, ipha_dst %x, ill %s",
ntohl(ipha->ipha_src), ntohl(ipha->ipha_dst),
ill->ill_name);
}
/* Macro to send packet on vif */
#define MC_SEND(ipha, mp, vifp, dst) { \
if ((vifp)->v_flags & VIFF_TUNNEL) \
encap_send((ipha), (mp), (vifp), (dst)); \
else if ((vifp)->v_flags & VIFF_REGISTER) \
register_send((ipha), (mp), (vifp), (dst)); \
else \
phyint_send((ipha), (mp), (vifp), (dst)); \
}
vifi = rt->mfc_parent;
/*
* The value of vifi is MAXVIFS if the pkt had no parent, i.e.,
* Mrouted had no route.
* We wanted the route installed in the mfctable to prevent multiple
* tries, so it passed add_mfc(), but is discarded here. The v_ipif is
* NULL so we don't want to check the ill. Still needed as of Mrouted
* 3.6.
*/
if (vifi == NO_VIF) {
ip1dbg(("ip_mdq: no route for origin ill %s, vifi is NO_VIF\n",
ill->ill_name));
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"ip_mdq: vifi is NO_VIF ill = %s", ill->ill_name);
}
return (-1); /* drop pkt */
}
if (!lock_good_vif(&ipst->ips_vifs[vifi]))
return (-1);
/*
* The MFC entries are not cleaned up when an ipif goes
* away thus this code has to guard against an MFC referencing
* an ipif that has been closed. Note: reset_mrt_vif_ipif
* sets the v_ipif to NULL when the ipif disappears.
*/
ASSERT(ipst->ips_vifs[vifi].v_ipif != NULL);
if (vifi >= ipst->ips_numvifs) {
cmn_err(CE_WARN, "ip_mdq: illegal vifi %d numvifs "
"%d ill %s viftable ill %s\n",
(int)vifi, (int)ipst->ips_numvifs, ill->ill_name,
ipst->ips_vifs[vifi].v_ipif->ipif_ill->ill_name);
unlock_good_vif(&ipst->ips_vifs[vifi]);
return (-1);
}
/*
* Don't forward if it didn't arrive from the parent vif for its
* origin.
*/
if ((ipst->ips_vifs[vifi].v_ipif->ipif_ill != ill) ||
(ipst->ips_vifs[vifi].v_rmt_addr.s_addr != tunnel_src)) {
/* Came in the wrong interface */
ip1dbg(("ip_mdq: arrived wrong if, vifi %d "
"numvifs %d ill %s viftable ill %s\n",
(int)vifi, (int)ipst->ips_numvifs, ill->ill_name,
ipst->ips_vifs[vifi].v_ipif->ipif_ill->ill_name));
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"ip_mdq: arrived wrong if, vifi %d ill "
"%s viftable ill %s\n",
(int)vifi, ill->ill_name,
ipst->ips_vifs[vifi].v_ipif->ipif_ill->ill_name);
}
ipst->ips_mrtstat->mrts_wrong_if++;
rt->mfc_wrong_if++;
/*
* If we are doing PIM assert processing and we are forwarding
* packets on this interface, and it is a broadcast medium
* interface (and not a tunnel), send a message to the routing.
*
* We use the first ipif on the list, since it's all we have.
* Chances are the ipif_flags are the same for ipifs on the ill.
*/
if (ipst->ips_pim_assert && rt->mfc_ttls[vifi] > 0 &&
(ill->ill_ipif->ipif_flags & IPIF_BROADCAST) &&
!(ipst->ips_vifs[vifi].v_flags & VIFF_TUNNEL)) {
mblk_t *mp_copy;
struct igmpmsg *im;
/* TODO could copy header and dup rest */
mp_copy = copymsg(mp);
if (mp_copy == NULL) {
ipst->ips_mrtstat->mrts_fwd_drop++;
ip1dbg(("ip_mdq: out of memory "
"for mblk, mp_copy\n"));
unlock_good_vif(&ipst->ips_vifs[vifi]);
return (-1);
}
im = (struct igmpmsg *)mp_copy->b_rptr;
im->im_msgtype = IGMPMSG_WRONGVIF;
im->im_mbz = 0;
im->im_vif = (ushort_t)vifi;
/* Pass to RAWIP */
bzero(&iras, sizeof (iras));
iras.ira_flags = IRAF_IS_IPV4;
iras.ira_ip_hdr_length =
IPH_HDR_LENGTH(mp_copy->b_rptr);
iras.ira_pktlen = msgdsize(mp_copy);
(mrouter->conn_recv)(mrouter, mp_copy, NULL, &iras);
ASSERT(!(iras.ira_flags & IRAF_IPSEC_SECURE));
}
unlock_good_vif(&ipst->ips_vifs[vifi]);
if (tunnel_src != 0)
return (1);
else
return (0);
}
/*
* If I sourced this packet, it counts as output, else it was input.
*/
if (ipha->ipha_src == ipst->ips_vifs[vifi].v_lcl_addr.s_addr) {
ipst->ips_vifs[vifi].v_pkt_out++;
ipst->ips_vifs[vifi].v_bytes_out += plen;
} else {
ipst->ips_vifs[vifi].v_pkt_in++;
ipst->ips_vifs[vifi].v_bytes_in += plen;
}
mutex_enter(&rt->mfc_mutex);
rt->mfc_pkt_cnt++;
rt->mfc_byte_cnt += plen;
mutex_exit(&rt->mfc_mutex);
unlock_good_vif(&ipst->ips_vifs[vifi]);
/*
* For each vif, decide if a copy of the packet should be forwarded.
* Forward if:
* - the vif threshold ttl is non-zero AND
* - the pkt ttl exceeds the vif's threshold
* A non-zero mfc_ttl indicates that the vif is part of
* the output set for the mfc entry.
*/
mutex_enter(&ipst->ips_numvifs_mutex);
num_of_vifs = ipst->ips_numvifs;
mutex_exit(&ipst->ips_numvifs_mutex);
for (vifp = ipst->ips_vifs, vifi = 0;
vifi < num_of_vifs;
vifp++, vifi++) {
if (!lock_good_vif(vifp))
continue;
if ((rt->mfc_ttls[vifi] > 0) &&
(ipha->ipha_ttl > rt->mfc_ttls[vifi])) {
/*
* lock_good_vif should not have succedded if
* v_ipif is null.
*/
ASSERT(vifp->v_ipif != NULL);
vifp->v_pkt_out++;
vifp->v_bytes_out += plen;
MC_SEND(ipha, mp, vifp, dst);
ipst->ips_mrtstat->mrts_fwd_out++;
}
unlock_good_vif(vifp);
}
if (tunnel_src != 0)
return (1);
else
return (0);
}
/*
* Send the packet on physical interface.
* Caller assumes can continue to use mp on return.
*/
/* ARGSUSED */
static void
phyint_send(ipha_t *ipha, mblk_t *mp, struct vif *vifp, ipaddr_t dst)
{
mblk_t *mp_copy;
ip_stack_t *ipst = vifp->v_ipif->ipif_ill->ill_ipst;
conn_t *mrouter = ipst->ips_ip_g_mrouter;
/* Make a new reference to the packet */
mp_copy = copymsg(mp); /* TODO could copy header and dup rest */
if (mp_copy == NULL) {
ipst->ips_mrtstat->mrts_fwd_drop++;
ip1dbg(("phyint_send: out of memory for mblk, mp_copy\n"));
return;
}
if (vifp->v_rate_limit <= 0)
tbf_send_packet(vifp, mp_copy);
else {
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"phyint_send: tbf_contr rate %d "
"vifp 0x%p mp 0x%p dst 0x%x",
vifp->v_rate_limit, (void *)vifp, (void *)mp, dst);
}
tbf_control(vifp, mp_copy, (ipha_t *)mp_copy->b_rptr);
}
}
/*
* Send the whole packet for REGISTER encapsulation to PIM daemon
* Caller assumes it can continue to use mp on return.
*/
/* ARGSUSED */
static void
register_send(ipha_t *ipha, mblk_t *mp, struct vif *vifp, ipaddr_t dst)
{
struct igmpmsg *im;
mblk_t *mp_copy;
ipha_t *ipha_copy;
ill_t *ill = vifp->v_ipif->ipif_ill;
ip_stack_t *ipst = ill->ill_ipst;
conn_t *mrouter = ipst->ips_ip_g_mrouter;
ip_recv_attr_t iras;
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"register_send: src %x, dst %x\n",
ntohl(ipha->ipha_src), ntohl(ipha->ipha_dst));
}
/*
* Copy the old packet & pullup its IP header into the new mblk_t so we
* can modify it. Try to fill the new mblk_t since if we don't the
* ethernet driver will.
*/
mp_copy = allocb(sizeof (struct igmpmsg) + sizeof (ipha_t), BPRI_MED);
if (mp_copy == NULL) {
++ipst->ips_mrtstat->mrts_pim_nomemory;
if (ipst->ips_ip_mrtdebug > 3) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"register_send: allocb failure.");
}
return;
}
/*
* Bump write pointer to account for igmpmsg being added.
*/
mp_copy->b_wptr = mp_copy->b_rptr + sizeof (struct igmpmsg);
/*
* Chain packet to new mblk_t.
*/
if ((mp_copy->b_cont = copymsg(mp)) == NULL) {
++ipst->ips_mrtstat->mrts_pim_nomemory;
if (ipst->ips_ip_mrtdebug > 3) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"register_send: copymsg failure.");
}
freeb(mp_copy);
return;
}
/*
* icmp_input() asserts that IP version field is set to an
* appropriate version. Hence, the struct igmpmsg that this really
* becomes, needs to have the correct IP version field.
*/
ipha_copy = (ipha_t *)mp_copy->b_rptr;
*ipha_copy = multicast_encap_iphdr;
/*
* The kernel uses the struct igmpmsg header to encode the messages to
* the multicast routing daemon. Fill in the fields in the header
* starting with the message type which is IGMPMSG_WHOLEPKT
*/
im = (struct igmpmsg *)mp_copy->b_rptr;
im->im_msgtype = IGMPMSG_WHOLEPKT;
im->im_src.s_addr = ipha->ipha_src;
im->im_dst.s_addr = ipha->ipha_dst;
/*
* Must Be Zero. This is because the struct igmpmsg is really an IP
* header with renamed fields and the multicast routing daemon uses
* an ipha_protocol (aka im_mbz) of 0 to distinguish these messages.
*/
im->im_mbz = 0;
++ipst->ips_mrtstat->mrts_upcalls;
if (IPCL_IS_NONSTR(mrouter) ? mrouter->conn_flow_cntrld :
!canputnext(mrouter->conn_rq)) {
++ipst->ips_mrtstat->mrts_pim_regsend_drops;
if (ipst->ips_ip_mrtdebug > 3) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"register_send: register upcall failure.");
}
BUMP_MIB(ill->ill_ip_mib, ipIfStatsInDiscards);
ip_drop_input("mrts_pim_regsend_drops", mp_copy, ill);
freemsg(mp_copy);
} else {
/* Pass to RAWIP */
bzero(&iras, sizeof (iras));
iras.ira_flags = IRAF_IS_IPV4;
iras.ira_ip_hdr_length = sizeof (ipha_t);
iras.ira_pktlen = msgdsize(mp_copy);
(mrouter->conn_recv)(mrouter, mp_copy, NULL, &iras);
ASSERT(!(iras.ira_flags & IRAF_IPSEC_SECURE));
}
}
/*
* pim_validate_cksum handles verification of the checksum in the
* pim header. For PIM Register packets, the checksum is calculated
* across the PIM header only. For all other packets, the checksum
* is for the PIM header and remainder of the packet.
*
* returns: B_TRUE, if checksum is okay.
* B_FALSE, if checksum is not valid.
*/
static boolean_t
pim_validate_cksum(mblk_t *mp, ipha_t *ip, struct pim *pimp)
{
mblk_t *mp_dup;
if ((mp_dup = dupmsg(mp)) == NULL)
return (B_FALSE);
mp_dup->b_rptr += IPH_HDR_LENGTH(ip);
if (pimp->pim_type == PIM_REGISTER)
mp_dup->b_wptr = mp_dup->b_rptr + PIM_MINLEN;
if (IP_CSUM(mp_dup, 0, 0)) {
freemsg(mp_dup);
return (B_FALSE);
}
freemsg(mp_dup);
return (B_TRUE);
}
/*
* Process PIM protocol packets i.e. IP Protocol 103.
* Register messages are decapsulated and sent onto multicast forwarding.
*
* Return NULL for a bad packet that is discarded here.
* Return mp if the message is OK and should be handed to "raw" receivers.
* Callers of pim_input() may need to reinitialize variables that were copied
* from the mblk as this calls pullupmsg().
*/
mblk_t *
pim_input(mblk_t *mp, ip_recv_attr_t *ira)
{
ipha_t *eip, *ip;
int iplen, pimlen, iphlen;
struct pim *pimp; /* pointer to a pim struct */
uint32_t *reghdr;
ill_t *ill = ira->ira_ill;
ip_stack_t *ipst = ill->ill_ipst;
conn_t *mrouter = ipst->ips_ip_g_mrouter;
/*
* Pullup the msg for PIM protocol processing.
*/
if (pullupmsg(mp, -1) == 0) {
++ipst->ips_mrtstat->mrts_pim_nomemory;
BUMP_MIB(ill->ill_ip_mib, ipIfStatsInDiscards);
ip_drop_input("mrts_pim_nomemory", mp, ill);
freemsg(mp);
return (NULL);
}
ip = (ipha_t *)mp->b_rptr;
iplen = ip->ipha_length;
iphlen = IPH_HDR_LENGTH(ip);
pimlen = ntohs(iplen) - iphlen;
/*
* Validate lengths
*/
if (pimlen < PIM_MINLEN) {
++ipst->ips_mrtstat->mrts_pim_malformed;
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"pim_input: length not at least minlen");
}
BUMP_MIB(ill->ill_ip_mib, ipIfStatsInDiscards);
ip_drop_input("mrts_pim_malformed", mp, ill);
freemsg(mp);
return (NULL);
}
/*
* Point to the PIM header.
*/
pimp = (struct pim *)((caddr_t)ip + iphlen);
/*
* Check the version number.
*/
if (pimp->pim_vers != PIM_VERSION) {
++ipst->ips_mrtstat->mrts_pim_badversion;
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"pim_input: unknown version of PIM");
}
BUMP_MIB(ill->ill_ip_mib, ipIfStatsInDiscards);
ip_drop_input("mrts_pim_badversion", mp, ill);
freemsg(mp);
return (NULL);
}
/*
* Validate the checksum
*/
if (!pim_validate_cksum(mp, ip, pimp)) {
++ipst->ips_mrtstat->mrts_pim_rcv_badcsum;
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"pim_input: invalid checksum");
}
BUMP_MIB(ill->ill_ip_mib, ipIfStatsInDiscards);
ip_drop_input("pim_rcv_badcsum", mp, ill);
freemsg(mp);
return (NULL);
}
if (pimp->pim_type != PIM_REGISTER)
return (mp);
reghdr = (uint32_t *)(pimp + 1);
eip = (ipha_t *)(reghdr + 1);
/*
* check if the inner packet is destined to mcast group
*/
if (!CLASSD(eip->ipha_dst)) {
++ipst->ips_mrtstat->mrts_pim_badregisters;
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"pim_input: Inner pkt not mcast .. !");
}
BUMP_MIB(ill->ill_ip_mib, ipIfStatsInDiscards);
ip_drop_input("mrts_pim_badregisters", mp, ill);
freemsg(mp);
return (NULL);
}
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"register from %x, to %x, len %d",
ntohl(eip->ipha_src),
ntohl(eip->ipha_dst),
ntohs(eip->ipha_length));
}
/*
* If the null register bit is not set, decapsulate
* the packet before forwarding it.
* Avoid this in no register vif
*/
if (!(ntohl(*reghdr) & PIM_NULL_REGISTER) &&
ipst->ips_reg_vif_num != ALL_VIFS) {
mblk_t *mp_copy;
uint_t saved_pktlen;
/* Copy the message */
if ((mp_copy = copymsg(mp)) == NULL) {
++ipst->ips_mrtstat->mrts_pim_nomemory;
BUMP_MIB(ill->ill_ip_mib, ipIfStatsInDiscards);
ip_drop_input("mrts_pim_nomemory", mp, ill);
freemsg(mp);
return (NULL);
}
/*
* Decapsulate the packet and give it to
* register_mforward.
*/
mp_copy->b_rptr += iphlen + sizeof (pim_t) + sizeof (*reghdr);
saved_pktlen = ira->ira_pktlen;
ira->ira_pktlen -= iphlen + sizeof (pim_t) + sizeof (*reghdr);
if (register_mforward(mp_copy, ira) != 0) {
/* register_mforward already called ip_drop_input */
freemsg(mp);
ira->ira_pktlen = saved_pktlen;
return (NULL);
}
ira->ira_pktlen = saved_pktlen;
}
/*
* Pass all valid PIM packets up to any process(es) listening on a raw
* PIM socket. For Solaris it is done right after pim_input() is
* called.
*/
return (mp);
}
/*
* PIM sparse mode hook. Called by pim_input after decapsulating
* the packet. Loop back the packet, as if we have received it.
* In pim_input() we have to check if the destination is a multicast address.
*/
static int
register_mforward(mblk_t *mp, ip_recv_attr_t *ira)
{
ire_t *ire;
ipha_t *ipha = (ipha_t *)mp->b_rptr;
ill_t *ill = ira->ira_ill;
ip_stack_t *ipst = ill->ill_ipst;
conn_t *mrouter = ipst->ips_ip_g_mrouter;
ASSERT(ipst->ips_reg_vif_num <= ipst->ips_numvifs);
if (ipst->ips_ip_mrtdebug > 3) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"register_mforward: src %x, dst %x\n",
ntohl(ipha->ipha_src), ntohl(ipha->ipha_dst));
}
/*
* Need to pass in to ip_mforward() the information that the
* packet has arrived on the register_vif. We mark it with
* the IRAF_PIM_REGISTER attribute.
* pim_input verified that the (inner) destination is multicast,
* hence we skip the generic code in ip_input.
*/
ira->ira_flags |= IRAF_PIM_REGISTER;
++ipst->ips_mrtstat->mrts_pim_regforwards;
if (!CLASSD(ipha->ipha_dst)) {
ire = ire_route_recursive_v4(ipha->ipha_dst, 0, NULL, ALL_ZONES,
ira->ira_tsl, MATCH_IRE_SECATTR, IRR_ALLOCATE, 0, ipst,
NULL, NULL, NULL);
} else {
ire = ire_multicast(ill);
}
ASSERT(ire != NULL);
/* Normally this will return the IRE_MULTICAST */
if (ire->ire_flags & (RTF_REJECT|RTF_BLACKHOLE)) {
BUMP_MIB(ill->ill_ip_mib, ipIfStatsInDiscards);
ip_drop_input("mrts_pim RTF_REJECT", mp, ill);
freemsg(mp);
ire_refrele(ire);
return (-1);
}
ASSERT(ire->ire_type & IRE_MULTICAST);
(*ire->ire_recvfn)(ire, mp, ipha, ira);
ire_refrele(ire);
return (0);
}
/*
* Send an encapsulated packet.
* Caller assumes can continue to use mp when routine returns.
*/
/* ARGSUSED */
static void
encap_send(ipha_t *ipha, mblk_t *mp, struct vif *vifp, ipaddr_t dst)
{
mblk_t *mp_copy;
ipha_t *ipha_copy;
size_t len;
ip_stack_t *ipst = vifp->v_ipif->ipif_ill->ill_ipst;
conn_t *mrouter = ipst->ips_ip_g_mrouter;
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"encap_send: vif %ld enter",
(ptrdiff_t)(vifp - ipst->ips_vifs));
}
len = ntohs(ipha->ipha_length);
/*
* Copy the old packet & pullup it's IP header into the
* new mbuf so we can modify it. Try to fill the new
* mbuf since if we don't the ethernet driver will.
*/
mp_copy = allocb(32 + sizeof (multicast_encap_iphdr), BPRI_MED);
if (mp_copy == NULL)
return;
mp_copy->b_rptr += 32;
mp_copy->b_wptr = mp_copy->b_rptr + sizeof (multicast_encap_iphdr);
if ((mp_copy->b_cont = copymsg(mp)) == NULL) {
freeb(mp_copy);
return;
}
/*
* Fill in the encapsulating IP header.
* Remote tunnel dst in rmt_addr, from add_vif().
*/
ipha_copy = (ipha_t *)mp_copy->b_rptr;
*ipha_copy = multicast_encap_iphdr;
ASSERT((len + sizeof (ipha_t)) <= IP_MAXPACKET);
ipha_copy->ipha_length = htons(len + sizeof (ipha_t));
ipha_copy->ipha_src = vifp->v_lcl_addr.s_addr;
ipha_copy->ipha_dst = vifp->v_rmt_addr.s_addr;
ASSERT(ipha_copy->ipha_ident == 0);
/* Turn the encapsulated IP header back into a valid one. */
ipha = (ipha_t *)mp_copy->b_cont->b_rptr;
ipha->ipha_ttl--;
ipha->ipha_hdr_checksum = 0;
ipha->ipha_hdr_checksum = ip_csum_hdr(ipha);
ipha_copy->ipha_ttl = ipha->ipha_ttl;
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"encap_send: group 0x%x", ntohl(ipha->ipha_dst));
}
if (vifp->v_rate_limit <= 0)
tbf_send_packet(vifp, mp_copy);
else
/* ipha is from the original header */
tbf_control(vifp, mp_copy, ipha);
}
/*
* De-encapsulate a packet and feed it back through IP input if it
* matches one of our multicast tunnels.
*
* This routine is called whenever IP gets a packet with prototype
* IPPROTO_ENCAP and a local destination address and the packet didn't
* match one of our configured IP-in-IP tunnels.
*/
void
ip_mroute_decap(mblk_t *mp, ip_recv_attr_t *ira)
{
ipha_t *ipha = (ipha_t *)mp->b_rptr;
ipha_t *ipha_encap;
int hlen = IPH_HDR_LENGTH(ipha);
int hlen_encap;
ipaddr_t src;
struct vif *vifp;
ire_t *ire;
ill_t *ill = ira->ira_ill;
ip_stack_t *ipst = ill->ill_ipst;
conn_t *mrouter = ipst->ips_ip_g_mrouter;
/* Make sure we have all of the inner header */
ipha_encap = (ipha_t *)((char *)ipha + hlen);
if (mp->b_wptr - mp->b_rptr < hlen + IP_SIMPLE_HDR_LENGTH) {
ipha = ip_pullup(mp, hlen + IP_SIMPLE_HDR_LENGTH, ira);
if (ipha == NULL) {
ipst->ips_mrtstat->mrts_bad_tunnel++;
BUMP_MIB(ill->ill_ip_mib, ipIfStatsInDiscards);
ip_drop_input("ip_mroute_decap: too short", mp, ill);
freemsg(mp);
return;
}
ipha_encap = (ipha_t *)((char *)ipha + hlen);
}
hlen_encap = IPH_HDR_LENGTH(ipha_encap);
if (mp->b_wptr - mp->b_rptr < hlen + hlen_encap) {
ipha = ip_pullup(mp, hlen + hlen_encap, ira);
if (ipha == NULL) {
ipst->ips_mrtstat->mrts_bad_tunnel++;
BUMP_MIB(ill->ill_ip_mib, ipIfStatsInDiscards);
ip_drop_input("ip_mroute_decap: too short", mp, ill);
freemsg(mp);
return;
}
ipha_encap = (ipha_t *)((char *)ipha + hlen);
}
/*
* Dump the packet if it's not to a multicast destination or if
* we don't have an encapsulating tunnel with the source.
* Note: This code assumes that the remote site IP address
* uniquely identifies the tunnel (i.e., that this site has
* at most one tunnel with the remote site).
*/
if (!CLASSD(ipha_encap->ipha_dst)) {
ipst->ips_mrtstat->mrts_bad_tunnel++;
ip1dbg(("ip_mroute_decap: bad tunnel\n"));
BUMP_MIB(ill->ill_ip_mib, ipIfStatsInDiscards);
ip_drop_input("mrts_bad_tunnel", mp, ill);
freemsg(mp);
return;
}
src = (ipaddr_t)ipha->ipha_src;
mutex_enter(&ipst->ips_last_encap_lock);
if (src != ipst->ips_last_encap_src) {
struct vif *vife;
vifp = ipst->ips_vifs;
vife = vifp + ipst->ips_numvifs;
ipst->ips_last_encap_src = src;
ipst->ips_last_encap_vif = 0;
for (; vifp < vife; ++vifp) {
if (!lock_good_vif(vifp))
continue;
if (vifp->v_rmt_addr.s_addr == src) {
if (vifp->v_flags & VIFF_TUNNEL)
ipst->ips_last_encap_vif = vifp;
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq,
1, SL_TRACE,
"ip_mroute_decap: good tun "
"vif %ld with %x",
(ptrdiff_t)(vifp - ipst->ips_vifs),
ntohl(src));
}
unlock_good_vif(vifp);
break;
}
unlock_good_vif(vifp);
}
}
if ((vifp = ipst->ips_last_encap_vif) == 0) {
mutex_exit(&ipst->ips_last_encap_lock);
ipst->ips_mrtstat->mrts_bad_tunnel++;
BUMP_MIB(ill->ill_ip_mib, ipIfStatsInDiscards);
ip_drop_input("mrts_bad_tunnel", mp, ill);
freemsg(mp);
ip1dbg(("ip_mroute_decap: vif %ld no tunnel with %x\n",
(ptrdiff_t)(vifp - ipst->ips_vifs), ntohl(src)));
return;
}
mutex_exit(&ipst->ips_last_encap_lock);
/*
* Need to pass in the tunnel source to ip_mforward (so that it can
* verify that the packet arrived over the correct vif.)
*/
ira->ira_flags |= IRAF_MROUTE_TUNNEL_SET;
ira->ira_mroute_tunnel = src;
mp->b_rptr += hlen;
ira->ira_pktlen -= hlen;
ira->ira_ip_hdr_length = hlen_encap;
/*
* We don't redo any of the filtering in ill_input_full_v4 and we
* have checked that all of ipha_encap and any IP options are
* pulled up. Hence we call ire_recv_multicast_v4 directly.
* However, we have to check for RSVP as in ip_input_full_v4
* and if so we pass it to ire_recv_broadcast_v4 for local delivery
* to the rsvpd.
*/
if (ipha_encap->ipha_protocol == IPPROTO_RSVP &&
ipst->ips_ipcl_proto_fanout_v4[IPPROTO_RSVP].connf_head != NULL) {
ire = ire_route_recursive_v4(INADDR_BROADCAST, 0, ill,
ALL_ZONES, ira->ira_tsl, MATCH_IRE_ILL|MATCH_IRE_SECATTR,
IRR_ALLOCATE, 0, ipst, NULL, NULL, NULL);
} else {
ire = ire_multicast(ill);
}
ASSERT(ire != NULL);
/* Normally this will return the IRE_MULTICAST or IRE_BROADCAST */
if (ire->ire_flags & (RTF_REJECT|RTF_BLACKHOLE)) {
BUMP_MIB(ill->ill_ip_mib, ipIfStatsInDiscards);
ip_drop_input("ip_mroute_decap: RTF_REJECT", mp, ill);
freemsg(mp);
ire_refrele(ire);
return;
}
ire->ire_ib_pkt_count++;
ASSERT(ire->ire_type & (IRE_MULTICAST|IRE_BROADCAST));
(*ire->ire_recvfn)(ire, mp, ipha_encap, ira);
ire_refrele(ire);
}
/*
* Remove all records with v_ipif == ipif. Called when an interface goes away
* (stream closed). Called as writer.
*/
void
reset_mrt_vif_ipif(ipif_t *ipif)
{
vifi_t vifi, tmp_vifi;
vifi_t num_of_vifs;
ip_stack_t *ipst = ipif->ipif_ill->ill_ipst;
/* Can't check vifi >= 0 since vifi_t is unsigned! */
mutex_enter(&ipst->ips_numvifs_mutex);
num_of_vifs = ipst->ips_numvifs;
mutex_exit(&ipst->ips_numvifs_mutex);
for (vifi = num_of_vifs; vifi != 0; vifi--) {
tmp_vifi = vifi - 1;
if (ipst->ips_vifs[tmp_vifi].v_ipif == ipif) {
(void) del_vif(&tmp_vifi, ipst);
}
}
}
/* Remove pending upcall msgs when ill goes away. Called by ill_delete. */
void
reset_mrt_ill(ill_t *ill)
{
struct mfc *rt;
struct rtdetq *rte;
int i;
ip_stack_t *ipst = ill->ill_ipst;
conn_t *mrouter = ipst->ips_ip_g_mrouter;
timeout_id_t id;
for (i = 0; i < MFCTBLSIZ; i++) {
MFCB_REFHOLD(&ipst->ips_mfcs[i]);
if ((rt = ipst->ips_mfcs[i].mfcb_mfc) != NULL) {
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1,
SL_TRACE,
"reset_mrt_ill: mfctable [%d]", i);
}
while (rt != NULL) {
mutex_enter(&rt->mfc_mutex);
while ((rte = rt->mfc_rte) != NULL) {
if (rte->ill == ill &&
(id = rt->mfc_timeout_id) != 0) {
/*
* Its ok to drop the lock, the
* struct cannot be freed since
* we have a ref on the hash
* bucket.
*/
mutex_exit(&rt->mfc_mutex);
(void) untimeout(id);
mutex_enter(&rt->mfc_mutex);
}
if (rte->ill == ill) {
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(
mrouter->conn_rq,
1, SL_TRACE,
"reset_mrt_ill: "
"ill 0x%p", (void *)ill);
}
rt->mfc_rte = rte->rte_next;
freemsg(rte->mp);
mi_free((char *)rte);
}
}
mutex_exit(&rt->mfc_mutex);
rt = rt->mfc_next;
}
}
MFCB_REFRELE(&ipst->ips_mfcs[i]);
}
}
/*
* Token bucket filter module.
* The ipha is for mcastgrp destination for phyint and encap.
*/
static void
tbf_control(struct vif *vifp, mblk_t *mp, ipha_t *ipha)
{
size_t p_len = msgdsize(mp);
struct tbf *t = vifp->v_tbf;
timeout_id_t id = 0;
ill_t *ill = vifp->v_ipif->ipif_ill;
ip_stack_t *ipst = ill->ill_ipst;
conn_t *mrouter = ipst->ips_ip_g_mrouter;
/* Drop if packet is too large */
if (p_len > MAX_BKT_SIZE) {
ipst->ips_mrtstat->mrts_pkt2large++;
BUMP_MIB(ill->ill_ip_mib, ipIfStatsOutDiscards);
ip_drop_output("tbf_control - too large", mp, ill);
freemsg(mp);
return;
}
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"tbf_ctrl: SEND vif %ld, qlen %d, ipha_dst 0x%x",
(ptrdiff_t)(vifp - ipst->ips_vifs), t->tbf_q_len,
ntohl(ipha->ipha_dst));
}
mutex_enter(&t->tbf_lock);
tbf_update_tokens(vifp);
/*
* If there are enough tokens,
* and the queue is empty, send this packet out.
*/
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"tbf_control: vif %ld, TOKENS %d, pkt len %lu, qlen %d",
(ptrdiff_t)(vifp - ipst->ips_vifs), t->tbf_n_tok, p_len,
t->tbf_q_len);
}
/* No packets are queued */
if (t->tbf_q_len == 0) {
/* queue empty, send packet if enough tokens */
if (p_len <= t->tbf_n_tok) {
t->tbf_n_tok -= p_len;
mutex_exit(&t->tbf_lock);
tbf_send_packet(vifp, mp);
return;
} else {
/* Queue packet and timeout till later */
tbf_queue(vifp, mp);
ASSERT(vifp->v_timeout_id == 0);
vifp->v_timeout_id = timeout(tbf_reprocess_q, vifp,
TBF_REPROCESS);
}
} else if (t->tbf_q_len < t->tbf_max_q_len) {
/* Finite queue length, so queue pkts and process queue */
tbf_queue(vifp, mp);
tbf_process_q(vifp);
} else {
/* Check that we have UDP header with IP header */
size_t hdr_length = IPH_HDR_LENGTH(ipha) +
sizeof (struct udphdr);
if ((mp->b_wptr - mp->b_rptr) < hdr_length) {
if (!pullupmsg(mp, hdr_length)) {
BUMP_MIB(ill->ill_ip_mib,
ipIfStatsOutDiscards);
ip_drop_output("tbf_control - pullup", mp, ill);
freemsg(mp);
ip1dbg(("tbf_ctl: couldn't pullup udp hdr, "
"vif %ld src 0x%x dst 0x%x\n",
(ptrdiff_t)(vifp - ipst->ips_vifs),
ntohl(ipha->ipha_src),
ntohl(ipha->ipha_dst)));
mutex_exit(&vifp->v_tbf->tbf_lock);
return;
} else
/* Have to reassign ipha after pullupmsg */
ipha = (ipha_t *)mp->b_rptr;
}
/*
* Queue length too much,
* try to selectively dq, or queue and process
*/
if (!tbf_dq_sel(vifp, ipha)) {
ipst->ips_mrtstat->mrts_q_overflow++;
BUMP_MIB(ill->ill_ip_mib, ipIfStatsOutDiscards);
ip_drop_output("mrts_q_overflow", mp, ill);
freemsg(mp);
} else {
tbf_queue(vifp, mp);
tbf_process_q(vifp);
}
}
if (t->tbf_q_len == 0) {
id = vifp->v_timeout_id;
vifp->v_timeout_id = 0;
}
mutex_exit(&vifp->v_tbf->tbf_lock);
if (id != 0)
(void) untimeout(id);
}
/*
* Adds a packet to the tbf queue at the interface.
* The ipha is for mcastgrp destination for phyint and encap.
*/
static void
tbf_queue(struct vif *vifp, mblk_t *mp)
{
struct tbf *t = vifp->v_tbf;
ip_stack_t *ipst = vifp->v_ipif->ipif_ill->ill_ipst;
conn_t *mrouter = ipst->ips_ip_g_mrouter;
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"tbf_queue: vif %ld", (ptrdiff_t)(vifp - ipst->ips_vifs));
}
ASSERT(MUTEX_HELD(&t->tbf_lock));
if (t->tbf_t == NULL) {
/* Queue was empty */
t->tbf_q = mp;
} else {
/* Insert at tail */
t->tbf_t->b_next = mp;
}
/* set new tail pointer */
t->tbf_t = mp;
mp->b_next = mp->b_prev = NULL;
t->tbf_q_len++;
}
/*
* Process the queue at the vif interface.
* Drops the tbf_lock when sending packets.
*
* NOTE : The caller should quntimeout if the queue length is 0.
*/
static void
tbf_process_q(struct vif *vifp)
{
mblk_t *mp;
struct tbf *t = vifp->v_tbf;
size_t len;
ip_stack_t *ipst = vifp->v_ipif->ipif_ill->ill_ipst;
conn_t *mrouter = ipst->ips_ip_g_mrouter;
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"tbf_process_q 1: vif %ld qlen = %d",
(ptrdiff_t)(vifp - ipst->ips_vifs), t->tbf_q_len);
}
/*
* Loop through the queue at the interface and send
* as many packets as possible.
*/
ASSERT(MUTEX_HELD(&t->tbf_lock));
while (t->tbf_q_len > 0) {
mp = t->tbf_q;
len = (size_t)msgdsize(mp); /* length of ip pkt */
/* Determine if the packet can be sent */
if (len <= t->tbf_n_tok) {
/*
* If so, reduce no. of tokens, dequeue the packet,
* send the packet.
*/
t->tbf_n_tok -= len;
t->tbf_q = mp->b_next;
if (--t->tbf_q_len == 0) {
t->tbf_t = NULL;
}
mp->b_next = NULL;
/* Exit mutex before sending packet, then re-enter */
mutex_exit(&t->tbf_lock);
tbf_send_packet(vifp, mp);
mutex_enter(&t->tbf_lock);
} else
break;
}
}
/* Called at tbf timeout to update tokens, process q and reset timer. */
static void
tbf_reprocess_q(void *arg)
{
struct vif *vifp = arg;
ip_stack_t *ipst = vifp->v_ipif->ipif_ill->ill_ipst;
conn_t *mrouter = ipst->ips_ip_g_mrouter;
mutex_enter(&vifp->v_tbf->tbf_lock);
vifp->v_timeout_id = 0;
tbf_update_tokens(vifp);
tbf_process_q(vifp);
if (vifp->v_tbf->tbf_q_len > 0) {
vifp->v_timeout_id = timeout(tbf_reprocess_q, vifp,
TBF_REPROCESS);
}
mutex_exit(&vifp->v_tbf->tbf_lock);
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"tbf_reprcess_q: vif %ld timeout id = %p",
(ptrdiff_t)(vifp - ipst->ips_vifs), vifp->v_timeout_id);
}
}
/*
* Function that will selectively discard a member of the tbf queue,
* based on the precedence value and the priority.
*
* NOTE : The caller should quntimeout if the queue length is 0.
*/
static int
tbf_dq_sel(struct vif *vifp, ipha_t *ipha)
{
uint_t p;
struct tbf *t = vifp->v_tbf;
mblk_t **np;
mblk_t *last, *mp;
ill_t *ill = vifp->v_ipif->ipif_ill;
ip_stack_t *ipst = ill->ill_ipst;
conn_t *mrouter = ipst->ips_ip_g_mrouter;
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"dq_sel: vif %ld dst 0x%x",
(ptrdiff_t)(vifp - ipst->ips_vifs), ntohl(ipha->ipha_dst));
}
ASSERT(MUTEX_HELD(&t->tbf_lock));
p = priority(vifp, ipha);
np = &t->tbf_q;
last = NULL;
while ((mp = *np) != NULL) {
if (p > (priority(vifp, (ipha_t *)mp->b_rptr))) {
*np = mp->b_next;
/* If removing the last packet, fix the tail pointer */
if (mp == t->tbf_t)
t->tbf_t = last;
mp->b_prev = mp->b_next = NULL;
BUMP_MIB(ill->ill_ip_mib, ipIfStatsOutDiscards);
ip_drop_output("tbf_dq_send", mp, ill);
freemsg(mp);
/*
* It's impossible for the queue to be empty, but
* we check anyway.
*/
if (--t->tbf_q_len == 0) {
t->tbf_t = NULL;
}
ipst->ips_mrtstat->mrts_drop_sel++;
return (1);
}
np = &mp->b_next;
last = mp;
}
return (0);
}
/* Sends packet, 2 cases - encap tunnel, phyint. */
static void
tbf_send_packet(struct vif *vifp, mblk_t *mp)
{
ipif_t *ipif = vifp->v_ipif;
ill_t *ill = ipif->ipif_ill;
ip_stack_t *ipst = ill->ill_ipst;
conn_t *mrouter = ipst->ips_ip_g_mrouter;
ipha_t *ipha;
ipha = (ipha_t *)mp->b_rptr;
/* If encap tunnel options */
if (vifp->v_flags & VIFF_TUNNEL) {
ip_xmit_attr_t ixas;
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"tbf_send_packet: ENCAP tunnel vif %ld",
(ptrdiff_t)(vifp - ipst->ips_vifs));
}
bzero(&ixas, sizeof (ixas));
ixas.ixa_flags =
IXAF_IS_IPV4 | IXAF_NO_TTL_CHANGE | IXAF_VERIFY_SOURCE;
ixas.ixa_ipst = ipst;
ixas.ixa_ifindex = 0;
ixas.ixa_cred = kcred;
ixas.ixa_cpid = NOPID;
ixas.ixa_tsl = NULL;
ixas.ixa_zoneid = GLOBAL_ZONEID; /* Multicast router in GZ */
ixas.ixa_pktlen = ntohs(ipha->ipha_length);
ixas.ixa_ip_hdr_length = IPH_HDR_LENGTH(ipha);
/*
* Feed into ip_output_simple which will set the ident field
* and checksum the encapsulating header.
* BSD gets the cached route vifp->v_route from ip_output()
* to speed up route table lookups. Not necessary in SunOS 5.x.
* One could make multicast forwarding faster by putting an
* ip_xmit_attr_t in each vif thereby caching the ire/nce.
*/
(void) ip_output_simple(mp, &ixas);
ixa_cleanup(&ixas);
return;
/* phyint */
} else {
/* Need to loop back to members on the outgoing interface. */
ipaddr_t dst;
ip_recv_attr_t iras;
nce_t *nce;
bzero(&iras, sizeof (iras));
iras.ira_flags = IRAF_IS_IPV4;
iras.ira_ill = iras.ira_rill = ill;
iras.ira_ruifindex = ill->ill_phyint->phyint_ifindex;
iras.ira_zoneid = GLOBAL_ZONEID; /* Multicast router in GZ */
iras.ira_pktlen = ntohs(ipha->ipha_length);
iras.ira_ip_hdr_length = IPH_HDR_LENGTH(ipha);
dst = ipha->ipha_dst;
if (ill_hasmembers_v4(ill, dst)) {
iras.ira_flags |= IRAF_LOOPBACK_COPY;
}
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"tbf_send_pkt: phyint forward vif %ld dst = 0x%x",
(ptrdiff_t)(vifp - ipst->ips_vifs), ntohl(dst));
}
/*
* Find an NCE which matches the nexthop.
* For a pt-pt interface we use the other end of the pt-pt
* link.
*/
if (ipif->ipif_flags & IPIF_POINTOPOINT) {
dst = ipif->ipif_pp_dst_addr;
nce = arp_nce_init(ill, dst, ill->ill_net_type);
} else {
nce = arp_nce_init(ill, dst, IRE_MULTICAST);
}
if (nce == NULL) {
BUMP_MIB(ill->ill_ip_mib, ipIfStatsOutDiscards);
ip_drop_output("tbf_send_packet - no nce", mp, ill);
freemsg(mp);
return;
}
/*
* We don't remeber the incoming ill. Thus we
* pretend the packet arrived on the outbound ill. This means
* statistics for input errors will be increased on the wrong
* ill but that isn't a big deal.
*/
ip_forward_xmit_v4(nce, ill, mp, ipha, &iras, ill->ill_mc_mtu,
0);
ASSERT(!(iras.ira_flags & IRAF_IPSEC_SECURE));
nce_refrele(nce);
}
}
/*
* Determine the current time and then the elapsed time (between the last time
* and time now). Update the no. of tokens in the bucket.
*/
static void
tbf_update_tokens(struct vif *vifp)
{
timespec_t tp;
hrtime_t tm;
struct tbf *t = vifp->v_tbf;
ip_stack_t *ipst = vifp->v_ipif->ipif_ill->ill_ipst;
conn_t *mrouter = ipst->ips_ip_g_mrouter;
ASSERT(MUTEX_HELD(&t->tbf_lock));
/* Time in secs and nsecs, rate limit in kbits/sec */
gethrestime(&tp);
/*LINTED*/
TV_DELTA(tp, t->tbf_last_pkt_t, tm);
/*
* This formula is actually
* "time in seconds" * "bytes/second". Scaled for nsec.
* (tm/1000000000) * (v_rate_limit * 1000 * (1000/1024) /8)
*
* The (1000/1024) was introduced in add_vif to optimize
* this divide into a shift.
*/
t->tbf_n_tok += (tm/1000) * vifp->v_rate_limit / 1024 / 8;
t->tbf_last_pkt_t = tp;
if (t->tbf_n_tok > MAX_BKT_SIZE)
t->tbf_n_tok = MAX_BKT_SIZE;
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"tbf_update_tok: tm %lld tok %d vif %ld",
tm, t->tbf_n_tok, (ptrdiff_t)(vifp - ipst->ips_vifs));
}
}
/*
* Priority currently is based on port nos.
* Different forwarding mechanisms have different ways
* of obtaining the port no. Hence, the vif must be
* given along with the packet itself.
*
*/
static int
priority(struct vif *vifp, ipha_t *ipha)
{
int prio;
ip_stack_t *ipst = vifp->v_ipif->ipif_ill->ill_ipst;
conn_t *mrouter = ipst->ips_ip_g_mrouter;
/* Temporary hack; may add general packet classifier some day */
ASSERT(MUTEX_HELD(&vifp->v_tbf->tbf_lock));
/*
* The UDP port space is divided up into four priority ranges:
* [0, 16384) : unclassified - lowest priority
* [16384, 32768) : audio - highest priority
* [32768, 49152) : whiteboard - medium priority
* [49152, 65536) : video - low priority
*/
if (ipha->ipha_protocol == IPPROTO_UDP) {
struct udphdr *udp =
(struct udphdr *)((char *)ipha + IPH_HDR_LENGTH(ipha));
switch (ntohs(udp->uh_dport) & 0xc000) {
case 0x4000:
prio = 70;
break;
case 0x8000:
prio = 60;
break;
case 0xc000:
prio = 55;
break;
default:
prio = 50;
break;
}
if (ipst->ips_ip_mrtdebug > 1) {
(void) mi_strlog(mrouter->conn_rq, 1, SL_TRACE,
"priority: port %x prio %d\n",
ntohs(udp->uh_dport), prio);
}
} else
prio = 50; /* default priority */
return (prio);
}
/*
* End of token bucket filter modifications
*/
/*
* Produces data for netstat -M.
*/
int
ip_mroute_stats(mblk_t *mp, ip_stack_t *ipst)
{
ipst->ips_mrtstat->mrts_vifctlSize = sizeof (struct vifctl);
ipst->ips_mrtstat->mrts_mfcctlSize = sizeof (struct mfcctl);
if (!snmp_append_data(mp, (char *)ipst->ips_mrtstat,
sizeof (struct mrtstat))) {
ip0dbg(("ip_mroute_stats: failed %ld bytes\n",
(size_t)sizeof (struct mrtstat)));
return (0);
}
return (1);
}
/*
* Sends info for SNMP's MIB.
*/
int
ip_mroute_vif(mblk_t *mp, ip_stack_t *ipst)
{
struct vifctl vi;
vifi_t vifi;
mutex_enter(&ipst->ips_numvifs_mutex);
for (vifi = 0; vifi < ipst->ips_numvifs; vifi++) {
if (ipst->ips_vifs[vifi].v_lcl_addr.s_addr == 0)
continue;
/*
* No locks here, an approximation is fine.
*/
vi.vifc_vifi = vifi;
vi.vifc_flags = ipst->ips_vifs[vifi].v_flags;
vi.vifc_threshold = ipst->ips_vifs[vifi].v_threshold;
vi.vifc_rate_limit = ipst->ips_vifs[vifi].v_rate_limit;
vi.vifc_lcl_addr = ipst->ips_vifs[vifi].v_lcl_addr;
vi.vifc_rmt_addr = ipst->ips_vifs[vifi].v_rmt_addr;
vi.vifc_pkt_in = ipst->ips_vifs[vifi].v_pkt_in;
vi.vifc_pkt_out = ipst->ips_vifs[vifi].v_pkt_out;
if (!snmp_append_data(mp, (char *)&vi, sizeof (vi))) {
ip0dbg(("ip_mroute_vif: failed %ld bytes\n",
(size_t)sizeof (vi)));
mutex_exit(&ipst->ips_numvifs_mutex);
return (0);
}
}
mutex_exit(&ipst->ips_numvifs_mutex);
return (1);
}
/*
* Called by ip_snmp_get to send up multicast routing table.
*/
int
ip_mroute_mrt(mblk_t *mp, ip_stack_t *ipst)
{
int i, j;
struct mfc *rt;
struct mfcctl mfcc;
/*
* Make sure multicast has not been turned off.
*/
if (is_mrouter_off(ipst))
return (1);
/* Loop over all hash buckets and their chains */
for (i = 0; i < MFCTBLSIZ; i++) {
MFCB_REFHOLD(&ipst->ips_mfcs[i]);
for (rt = ipst->ips_mfcs[i].mfcb_mfc; rt; rt = rt->mfc_next) {
mutex_enter(&rt->mfc_mutex);
if (rt->mfc_rte != NULL ||
(rt->mfc_marks & MFCB_MARK_CONDEMNED)) {
mutex_exit(&rt->mfc_mutex);
continue;
}
mfcc.mfcc_origin = rt->mfc_origin;
mfcc.mfcc_mcastgrp = rt->mfc_mcastgrp;
mfcc.mfcc_parent = rt->mfc_parent;
mfcc.mfcc_pkt_cnt = rt->mfc_pkt_cnt;
mutex_enter(&ipst->ips_numvifs_mutex);
for (j = 0; j < (int)ipst->ips_numvifs; j++)
mfcc.mfcc_ttls[j] = rt->mfc_ttls[j];
for (j = (int)ipst->ips_numvifs; j < MAXVIFS; j++)
mfcc.mfcc_ttls[j] = 0;
mutex_exit(&ipst->ips_numvifs_mutex);
mutex_exit(&rt->mfc_mutex);
if (!snmp_append_data(mp, (char *)&mfcc,
sizeof (mfcc))) {
MFCB_REFRELE(&ipst->ips_mfcs[i]);
ip0dbg(("ip_mroute_mrt: failed %ld bytes\n",
(size_t)sizeof (mfcc)));
return (0);
}
}
MFCB_REFRELE(&ipst->ips_mfcs[i]);
}
return (1);
}