/* * sppp_dlpi.c - Solaris STREAMS PPP multiplexing pseudo-driver DLPI handlers * * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Permission to use, copy, modify, and distribute this software and its * documentation is hereby granted, provided that the above copyright * notice appears in all copies. * * SUN MAKES NO REPRESENTATION OR WARRANTIES ABOUT THE SUITABILITY OF * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES * * Copyright (c) 1994 The Australian National University. * All rights reserved. * * Permission to use, copy, modify, and distribute this software and its * documentation is hereby granted, provided that the above copyright * notice appears in all copies. This software is provided without any * warranty, express or implied. The Australian National University * makes no representations about the suitability of this software for * any purpose. * * IN NO EVENT SHALL THE AUSTRALIAN NATIONAL UNIVERSITY BE LIABLE TO ANY * PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF * THE AUSTRALIAN NATIONAL UNIVERSITY HAS BEEN ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * THE AUSTRALIAN NATIONAL UNIVERSITY SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE AUSTRALIAN NATIONAL UNIVERSITY HAS NO * OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, * OR MODIFICATIONS. * * This driver is derived from the original SVR4 STREAMS PPP driver * originally written by Paul Mackerras . * * Adi Masputra rewrote and restructured the code * for improved performance and scalability. */ #define RCSID "$Id: sppp_dlpi.c,v 1.0 2000/05/08 01:10:12 masputra Exp $" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "s_common.h" #include "sppp.h" static int sppp_dlattachreq(queue_t *, mblk_t *, spppstr_t *); static int sppp_dldetachreq(queue_t *, mblk_t *, spppstr_t *); static int sppp_dlbindreq(queue_t *, mblk_t *, spppstr_t *); static int sppp_dlunbindreq(queue_t *, mblk_t *, spppstr_t *); static int sppp_dlinforeq(queue_t *, mblk_t *, spppstr_t *); static int sppp_dlunitdatareq(queue_t *, mblk_t *, spppstr_t *); static int sppp_dlpromisconreq(queue_t *, mblk_t *, spppstr_t *); static int sppp_dlpromiscoffreq(queue_t *, mblk_t *, spppstr_t *); static int sppp_dlphyreq(queue_t *, mblk_t *, spppstr_t *); static void sppp_dl_attach_upper(queue_t *, mblk_t *); static void sppp_dl_detach_upper(queue_t *, mblk_t *); static void sppp_dl_bind(queue_t *, mblk_t *); static void sppp_dl_unbind(queue_t *, mblk_t *); static void sppp_dl_promiscon(queue_t *, mblk_t *); static void sppp_dl_promiscoff(queue_t *, mblk_t *); static mblk_t *sppp_dladdether(spppstr_t *, mblk_t *, t_scalar_t); static struct sppp_dlpi_pinfo_t dl_pinfo[DL_MAXPRIM + 1]; #if 0 #define DBGERROR(x) cmn_err x #else #define DBGERROR(x) ((void)0) #endif /* #define DBG_DLPI 1 */ #ifdef DBG_DLPI struct sppp_dlpi_entry { uint32_t sde_val; const char *sde_name; }; static const struct sppp_dlpi_entry sppp_dlpi_list[] = { { DL_INFO_REQ, "DL_INFO_REQ" }, { DL_INFO_ACK, "DL_INFO_ACK" }, { DL_ATTACH_REQ, "DL_ATTACH_REQ" }, { DL_DETACH_REQ, "DL_DETACH_REQ" }, { DL_BIND_REQ, "DL_BIND_REQ" }, { DL_BIND_ACK, "DL_BIND_ACK" }, { DL_UNBIND_REQ, "DL_UNBIND_REQ" }, { DL_OK_ACK, "DL_OK_ACK" }, { DL_ERROR_ACK, "DL_ERROR_ACK" }, { DL_SUBS_BIND_REQ, "DL_SUBS_BIND_REQ" }, { DL_SUBS_BIND_ACK, "DL_SUBS_BIND_ACK" }, { DL_SUBS_UNBIND_REQ, "DL_SUBS_UNBIND_REQ" }, { DL_ENABMULTI_REQ, "DL_ENABMULTI_REQ" }, { DL_DISABMULTI_REQ, "DL_DISABMULTI_REQ" }, { DL_PROMISCON_REQ, "DL_PROMISCON_REQ" }, { DL_PROMISCOFF_REQ, "DL_PROMISCOFF_REQ" }, { DL_UNITDATA_REQ, "DL_UNITDATA_REQ" }, { DL_UNITDATA_IND, "DL_UNITDATA_IND" }, { DL_UDERROR_IND, "DL_UDERROR_IND" }, { DL_UDQOS_REQ, "DL_UDQOS_REQ" }, { DL_CONNECT_REQ, "DL_CONNECT_REQ" }, { DL_CONNECT_IND, "DL_CONNECT_IND" }, { DL_CONNECT_RES, "DL_CONNECT_RES" }, { DL_CONNECT_CON, "DL_CONNECT_CON" }, { DL_TOKEN_REQ, "DL_TOKEN_REQ" }, { DL_TOKEN_ACK, "DL_TOKEN_ACK" }, { DL_DISCONNECT_REQ, "DL_DISCONNECT_REQ" }, { DL_DISCONNECT_IND, "DL_DISCONNECT_IND" }, { DL_RESET_REQ, "DL_RESET_REQ" }, { DL_RESET_IND, "DL_RESET_IND" }, { DL_RESET_RES, "DL_RESET_RES" }, { DL_RESET_CON, "DL_RESET_CON" }, { DL_DATA_ACK_REQ, "DL_DATA_ACK_REQ" }, { DL_DATA_ACK_IND, "DL_DATA_ACK_IND" }, { DL_DATA_ACK_STATUS_IND, "DL_DATA_ACK_STATUS_IND" }, { DL_REPLY_REQ, "DL_REPLY_REQ" }, { DL_REPLY_IND, "DL_REPLY_IND" }, { DL_REPLY_STATUS_IND, "DL_REPLY_STATUS_IND" }, { DL_REPLY_UPDATE_REQ, "DL_REPLY_UPDATE_REQ" }, { DL_REPLY_UPDATE_STATUS_IND, "DL_REPLY_UPDATE_STATUS_IND" }, { DL_XID_REQ, "DL_XID_REQ" }, { DL_XID_IND, "DL_XID_IND" }, { DL_XID_RES, "DL_XID_RES" }, { DL_XID_CON, "DL_XID_CON" }, { DL_TEST_REQ, "DL_TEST_REQ" }, { DL_TEST_IND, "DL_TEST_IND" }, { DL_TEST_RES, "DL_TEST_RES" }, { DL_TEST_CON, "DL_TEST_CON" }, { DL_PHYS_ADDR_REQ, "DL_PHYS_ADDR_REQ" }, { DL_PHYS_ADDR_ACK, "DL_PHYS_ADDR_ACK" }, { DL_SET_PHYS_ADDR_REQ, "DL_SET_PHYS_ADDR_REQ" }, { DL_GET_STATISTICS_REQ, "DL_GET_STATISTICS_REQ" }, { DL_GET_STATISTICS_ACK, "DL_GET_STATISTICS_ACK" }, { 0, NULL } }; static const struct sppp_dlpi_entry sppp_state_list[] = { { DL_UNBOUND, "DL_UNBOUND" }, { DL_BIND_PENDING, "DL_BIND_PENDING" }, { DL_UNBIND_PENDING, "DL_UNBIND_PENDING" }, { DL_IDLE, "DL_IDLE" }, { DL_UNATTACHED, "DL_UNATTACHED" }, { DL_ATTACH_PENDING, "DL_ATTACH_PENDING" }, { DL_DETACH_PENDING, "DL_DETACH_PENDING" }, { DL_UDQOS_PENDING, "DL_UDQOS_PENDING" }, { DL_OUTCON_PENDING, "DL_OUTCON_PENDING" }, { DL_INCON_PENDING, "DL_INCON_PENDING" }, { DL_CONN_RES_PENDING, "DL_CONN_RES_PENDING" }, { DL_DATAXFER, "DL_DATAXFER" }, { DL_USER_RESET_PENDING, "DL_USER_RESET_PENDING" }, { DL_PROV_RESET_PENDING, "DL_PROV_RESET_PENDING" }, { DL_RESET_RES_PENDING, "DL_RESET_RES_PENDING" }, { DL_DISCON8_PENDING, "DL_DISCON8_PENDING" }, { DL_DISCON9_PENDING, "DL_DISCON9_PENDING" }, { DL_DISCON11_PENDING, "DL_DISCON11_PENDING" }, { DL_DISCON12_PENDING, "DL_DISCON12_PENDING" }, { DL_DISCON13_PENDING, "DL_DISCON13_PENDING" }, { DL_SUBS_BIND_PND, "DL_SUBS_BIND_PND" }, { DL_SUBS_UNBIND_PND, "DL_SUBS_UNBIND_PND" }, { 0, NULL } }; static const char * prim2name(uint32_t prim) { const struct sppp_dlpi_entry *sde; for (sde = sppp_dlpi_list; sde->sde_name != NULL; sde++) if (sde->sde_val == prim) break; return (sde->sde_name); } static const char * state2name(uint32_t state) { const struct sppp_dlpi_entry *sde; for (sde = sppp_state_list; sde->sde_name != NULL; sde++) if (sde->sde_val == state) break; return (sde->sde_name); } #define DBGDLPI(x) cmn_err x #else #define DBGDLPI(x) ((void)0) #endif /* DBG_DLPI */ /* * DL_INFO_ACK template for point-to-point interface. */ static dl_info_ack_t sppp_infoack = { DL_INFO_ACK, /* dl_primitive */ PPP_MAXMTU, /* dl_max_sdu */ 0, /* dl_min_sdu */ SPPP_ADDRL, /* dl_addr_length */ /* * snoop et. al. don't know about DL_OTHER so this entry * was changed to DL_ETHER so ethernet tracing/snooping * facilities will work with PPP interfaces. */ DL_ETHER, /* dl_mac_type */ 0, /* dl_reserved */ 0, /* dl_current_state */ SPPP_SAPL, /* dl_sap_length */ DL_CLDLS, /* dl_service_mode */ 0, /* dl_qos_length */ 0, /* dl_qos_offset */ 0, /* dl_range_length */ 0, /* dl_range_offset */ DL_STYLE2, /* dl_provider_style */ sizeof (dl_info_ack_t), /* dl_addr_offset */ DL_VERSION_2, /* dl_version */ 0, /* dl_brdcst_addr_length */ 0, /* dl_brdcst_addr_offset */ 0 /* dl_growth */ }; /* * sppp_dlpi_pinfoinit() * * Description: * Initialize dl_pinfo[], called from sppp_attach. */ void sppp_dlpi_pinfoinit(void) { bzero(dl_pinfo, sizeof (dl_pinfo)); /* Just to be safe */ dl_pinfo[DL_ATTACH_REQ].pi_minlen = sizeof (dl_attach_req_t); dl_pinfo[DL_ATTACH_REQ].pi_state = DL_UNATTACHED; dl_pinfo[DL_ATTACH_REQ].pi_funcp = sppp_dlattachreq; dl_pinfo[DL_DETACH_REQ].pi_minlen = sizeof (dl_detach_req_t); dl_pinfo[DL_DETACH_REQ].pi_state = DL_UNBOUND; dl_pinfo[DL_DETACH_REQ].pi_funcp = sppp_dldetachreq; dl_pinfo[DL_BIND_REQ].pi_minlen = sizeof (dl_bind_req_t); dl_pinfo[DL_BIND_REQ].pi_state = DL_UNBOUND; dl_pinfo[DL_BIND_REQ].pi_funcp = sppp_dlbindreq; dl_pinfo[DL_UNBIND_REQ].pi_minlen = sizeof (dl_unbind_req_t); dl_pinfo[DL_UNBIND_REQ].pi_state = DL_IDLE; dl_pinfo[DL_UNBIND_REQ].pi_funcp = sppp_dlunbindreq; dl_pinfo[DL_INFO_REQ].pi_minlen = sizeof (dl_info_req_t); dl_pinfo[DL_INFO_REQ].pi_state = -1; /* special handling */ dl_pinfo[DL_INFO_REQ].pi_funcp = sppp_dlinforeq; dl_pinfo[DL_UNITDATA_REQ].pi_minlen = sizeof (dl_unitdata_req_t); dl_pinfo[DL_UNITDATA_REQ].pi_state = DL_IDLE; dl_pinfo[DL_UNITDATA_REQ].pi_funcp = sppp_dlunitdatareq; dl_pinfo[DL_PROMISCON_REQ].pi_minlen = sizeof (dl_promiscon_req_t); dl_pinfo[DL_PROMISCON_REQ].pi_state = -1; /* special handling */ dl_pinfo[DL_PROMISCON_REQ].pi_funcp = sppp_dlpromisconreq; dl_pinfo[DL_PROMISCOFF_REQ].pi_minlen = sizeof (dl_promiscoff_req_t); dl_pinfo[DL_PROMISCOFF_REQ].pi_state = -1; /* special handling */ dl_pinfo[DL_PROMISCOFF_REQ].pi_funcp = sppp_dlpromiscoffreq; dl_pinfo[DL_PHYS_ADDR_REQ].pi_minlen = sizeof (dl_phys_addr_req_t); dl_pinfo[DL_PHYS_ADDR_REQ].pi_state = -1; /* special handling */ dl_pinfo[DL_PHYS_ADDR_REQ].pi_funcp = sppp_dlphyreq; } /* * sppp_mproto() * * MT-Perimeters: * shared inner, shared outer. * * Description: * Handle M_PCPROTO/M_PROTO messages, called by sppp_uwput. */ int sppp_mproto(queue_t *q, mblk_t *mp, spppstr_t *sps) { union DL_primitives *dlp; struct sppp_dlpi_pinfo_t *dpi; t_uscalar_t prim; int len; int error = 0; ASSERT(!IS_SPS_CONTROL(sps)); if ((len = MBLKL(mp)) < sizeof (t_uscalar_t)) { DBGERROR((CE_CONT, "bad mproto: block length %d\n", len)); merror(q, mp, EPROTO); return (0); } dlp = (union DL_primitives *)mp->b_rptr; prim = dlp->dl_primitive; if (prim > DL_MAXPRIM) { DBGERROR((CE_CONT, "bad mproto: primitive %d > %d\n", prim, DL_MAXPRIM)); error = DL_BADPRIM; } else { dpi = &dl_pinfo[prim]; if (dpi->pi_funcp == NULL) { DBGERROR((CE_CONT, "bad mproto: primitive %d not supported\n", prim)); error = DL_NOTSUPPORTED; } else if (len < dpi->pi_minlen) { DBGERROR((CE_CONT, "bad mproto: primitive len %d < %d\n", len, dpi->pi_minlen)); error = DL_BADPRIM; } else if (dpi->pi_state != -1 && sps->sps_dlstate != dpi->pi_state) { DBGERROR((CE_CONT, "bad state %d != %d for primitive %d\n", sps->sps_dlstate, dpi->pi_state, prim)); error = DL_OUTSTATE; } } if (error != 0) { dlerrorack(q, mp, dlp->dl_primitive, error, 0); return (0); } #ifdef DBG_DLPI { const char *cp = prim2name(prim); if (cp != NULL) cmn_err(CE_CONT, "/%d: Dispatching %s\n", sps->sps_mn_id, cp); else cmn_err(CE_CONT, "/%d: Dispatching unknown primitive %d\n", sps->sps_mn_id, prim); } #endif return ((*dpi->pi_funcp)(q, mp, sps)); } /* * sppp_dlattachreq() * * MT-Perimeters: * shared inner, shared outer. * * Description: * Perform DL_ATTACH_REQ request, called by sppp_mproto. */ static int sppp_dlattachreq(queue_t *q, mblk_t *mp, spppstr_t *sps) { int error = 0; union DL_primitives *dlp; ASSERT(q != NULL && q->q_ptr != NULL); ASSERT(mp != NULL && mp->b_rptr != NULL); dlp = (union DL_primitives *)mp->b_rptr; ASSERT(sps != NULL); ASSERT(sps->sps_dlstate == DL_UNATTACHED); if (IS_SPS_PIOATTACH(sps)) { DBGERROR((CE_CONT, "DLPI attach: already attached\n")); error = EINVAL; } if (error != 0) { dlerrorack(q, mp, dlp->dl_primitive, DL_OUTSTATE, error); } else { qwriter(q, mp, sppp_dl_attach_upper, PERIM_OUTER); } return (0); } /* * sppp_dl_attach_upper() * * MT-Perimeters: * exclusive inner, exclusive outer. * * Description: * Called by qwriter (INNER) from sppp_dlattachreq as the result of * receiving a DL_ATTACH_REQ message. */ static void sppp_dl_attach_upper(queue_t *q, mblk_t *mp) { sppa_t *ppa; spppstr_t *sps = q->q_ptr; union DL_primitives *dlp; int err = ENOMEM; cred_t *cr; zoneid_t zoneid; ASSERT(!IS_SPS_PIOATTACH(sps)); dlp = (union DL_primitives *)mp->b_rptr; /* If there's something here, it's detached. */ if (sps->sps_ppa != NULL) { sppp_remove_ppa(sps); } if ((cr = msg_getcred(mp, NULL)) == NULL) zoneid = sps->sps_zoneid; else zoneid = crgetzoneid(cr); ppa = sppp_find_ppa(dlp->attach_req.dl_ppa); if (ppa == NULL) { ppa = sppp_create_ppa(dlp->attach_req.dl_ppa, zoneid); } else if (ppa->ppa_zoneid != zoneid) { ppa = NULL; err = EPERM; } /* * If we can't find or create it, then it's either because we're out of * memory or because the requested PPA is owned by a different zone. */ if (ppa == NULL) { DBGERROR((CE_CONT, "DLPI attach: cannot create ppa %u\n", dlp->attach_req.dl_ppa)); dlerrorack(q, mp, dlp->dl_primitive, DL_SYSERR, err); return; } /* * Preallocate the hangup message so that we're always able to * send this upstream in the event of a catastrophic failure. */ if ((sps->sps_hangup = allocb(1, BPRI_MED)) == NULL) { DBGERROR((CE_CONT, "DLPI attach: cannot allocate hangup\n")); dlerrorack(q, mp, dlp->dl_primitive, DL_SYSERR, ENOSR); return; } sps->sps_dlstate = DL_UNBOUND; sps->sps_ppa = ppa; /* * Add this stream to the head of the list of sibling streams * which belong to the specified ppa. */ rw_enter(&ppa->ppa_sib_lock, RW_WRITER); ppa->ppa_refcnt++; sps->sps_nextsib = ppa->ppa_streams; ppa->ppa_streams = sps; /* * And if this stream was marked as promiscuous (SPS_PROMISC), then we * need to update the promiscuous streams count. This should only * happen when DL_PROMISCON_REQ was issued prior to attachment. */ if (IS_SPS_PROMISC(sps)) { ppa->ppa_promicnt++; } rw_exit(&ppa->ppa_sib_lock); DBGDLPI((CE_CONT, "/%d: attached to ppa %d\n", sps->sps_mn_id, ppa->ppa_ppa_id)); dlokack(q, mp, DL_ATTACH_REQ); } /* * sppp_dldetachreq() * * MT-Perimeters: * shared inner, shared outer. * * Description: * Perform DL_DETACH_REQ request, called by sppp_mproto. */ /* ARGSUSED */ static int sppp_dldetachreq(queue_t *q, mblk_t *mp, spppstr_t *sps) { ASSERT(q != NULL && q->q_ptr != NULL); ASSERT(mp != NULL && mp->b_rptr != NULL); ASSERT(sps != NULL); ASSERT(sps->sps_dlstate == DL_UNBOUND); ASSERT(!IS_SPS_PIOATTACH(sps)); qwriter(q, mp, sppp_dl_detach_upper, PERIM_INNER); return (0); } /* * sppp_dl_detach_upper() * * MT-Perimeters: * exclusive inner, shared outer. * * Description: * Called by qwriter (INNER) from sppp_dldetachreq as the result of * receiving a DL_DETACH_REQ message. */ /* ARGSUSED */ static void sppp_dl_detach_upper(queue_t *q, mblk_t *mp) { spppstr_t *sps; ASSERT(q != NULL && q->q_ptr != NULL); ASSERT(mp != NULL && mp->b_rptr != NULL); sps = (spppstr_t *)q->q_ptr; /* * We don't actually detach from the PPA until closed or * reattached. */ sps->sps_flags &= ~SPS_PROMISC; /* clear flag anyway */ sps->sps_dlstate = DL_UNATTACHED; dlokack(q, mp, DL_DETACH_REQ); } /* * sppp_dlbindreq() * * MT-Perimeters: * shared inner, shared outer. * * Description: * Perform DL_BIND_REQ request, called by sppp_mproto. */ static int sppp_dlbindreq(queue_t *q, mblk_t *mp, spppstr_t *sps) { sppa_t *ppa; union DL_primitives *dlp; spppreqsap_t req_sap; int error = 0; ASSERT(q != NULL && q->q_ptr != NULL); ASSERT(mp != NULL && mp->b_rptr != NULL); dlp = (union DL_primitives *)mp->b_rptr; req_sap = dlp->bind_req.dl_sap; ASSERT(sps != NULL); ASSERT(!IS_SPS_PIOATTACH(sps)); ASSERT(sps->sps_dlstate == DL_UNBOUND); ppa = sps->sps_ppa; if (ppa == NULL) { DBGERROR((CE_CONT, "DLPI bind: no attached ppa\n")); error = DL_OUTSTATE; } else if ((req_sap != ETHERTYPE_IP) && (req_sap != ETHERTYPE_IPV6) && (req_sap != ETHERTYPE_ALLSAP)) { DBGERROR((CE_CONT, "DLPI bind: unknown SAP %x\n", req_sap)); error = DL_BADADDR; } if (error != 0) { dlerrorack(q, mp, dlp->dl_primitive, error, 0); } else { qwriter(q, mp, sppp_dl_bind, PERIM_INNER); } return (0); } /* * sppp_dl_bind() * * MT-Perimeters: * exclusive inner, shared outer. * * Description: * Called by qwriter (INNER) from sppp_dlbindreq as the result of * receiving a DL_BIND_REQ message. */ static void sppp_dl_bind(queue_t *q, mblk_t *mp) { spppstr_t *sps; sppa_t *ppa; union DL_primitives *dlp; t_scalar_t sap; spppreqsap_t req_sap; mblk_t *lsmp; ASSERT(q != NULL && q->q_ptr != NULL); sps = (spppstr_t *)q->q_ptr; ASSERT(mp != NULL && mp->b_rptr != NULL); dlp = (union DL_primitives *)mp->b_rptr; ppa = sps->sps_ppa; ASSERT(ppa != NULL); req_sap = dlp->bind_req.dl_sap; ASSERT((req_sap == ETHERTYPE_IP) || (req_sap == ETHERTYPE_IPV6) || (req_sap == ETHERTYPE_ALLSAP)); if (req_sap == ETHERTYPE_IP) { sap = PPP_IP; } else if (req_sap == ETHERTYPE_IPV6) { sap = PPP_IPV6; } else if (req_sap == ETHERTYPE_ALLSAP) { sap = PPP_ALLSAP; } /* * If there's another stream with the same sap has already been bound * to the same ppa, then return with DL_NOADDR. However, we do make an * exception for snoop (req_sap=0x00, sap=0xff) since multiple * instances of snoop may execute an a given device. */ lsmp = NULL; if (sap != PPP_ALLSAP) { if ((sap == PPP_IP) && (ppa->ppa_ip_cache == NULL)) { ppa->ppa_ip_cache = sps; if (ppa->ppa_ctl != NULL) { lsmp = create_lsmsg(PPP_LINKSTAT_IPV4_BOUND); } } else if ((sap == PPP_IPV6) && (ppa->ppa_ip6_cache == NULL)) { ppa->ppa_ip6_cache = sps; if (ppa->ppa_ctl != NULL) { lsmp = create_lsmsg(PPP_LINKSTAT_IPV6_BOUND); } } else { DBGERROR((CE_CONT, "DLPI bind: bad SAP %x\n", sap)); dlerrorack(q, mp, dlp->dl_primitive, DL_NOADDR, EEXIST); return; } sps->sps_flags |= SPS_CACHED; } /* * Tell the daemon that a DLPI bind has happened on this stream, * and we'll only do this for PPP_IP or PPP_IPV6 sap (not snoop). */ if (lsmp != NULL && ppa->ppa_ctl != NULL) { #ifdef DBG_DLPI cmn_err(CE_CONT, "sending up %s\n", ((sap == PPP_IP) ? "PPP_LINKSTAT_IPV4_BOUND" : "PPP_LINKSTAT_IPV6_BOUND")); #endif putnext(ppa->ppa_ctl->sps_rq, lsmp); } DBGDLPI((CE_CONT, "/%d: bound to sap %X (req %X)\n", sps->sps_mn_id, sap, req_sap)); sps->sps_req_sap = req_sap; sps->sps_sap = sap; sps->sps_dlstate = DL_IDLE; dlbindack(q, mp, req_sap, &sap, sizeof (int32_t), 0, 0); } /* * sppp_dlunbindreq() * * MT-Perimeters: * shared inner, shared outer. * * Description: * Perform DL_UNBIND_REQ request, called by sppp_mproto. */ /* ARGSUSED */ static int sppp_dlunbindreq(queue_t *q, mblk_t *mp, spppstr_t *sps) { ASSERT(q != NULL && q->q_ptr != NULL); ASSERT(mp != NULL && mp->b_rptr != NULL); ASSERT(sps != NULL); ASSERT(!IS_SPS_PIOATTACH(sps)); ASSERT(sps->sps_dlstate == DL_IDLE); qwriter(q, mp, sppp_dl_unbind, PERIM_INNER); return (0); } /* * sppp_dl_unbind() * * MT-Perimeters: * exclusive inner, shared outer. * * Description: * Called by qwriter (INNER) from sppp_dlunbindreq as the result of * receiving a DL_UNBIND_REQ message. */ static void sppp_dl_unbind(queue_t *q, mblk_t *mp) { spppstr_t *sps; sppa_t *ppa; t_scalar_t sap; mblk_t *msg; boolean_t saydown; ASSERT(q != NULL && q->q_ptr != NULL); sps = (spppstr_t *)q->q_ptr; ppa = sps->sps_ppa; ASSERT(mp != NULL && mp->b_rptr != NULL); sap = sps->sps_sap; ASSERT((sap == PPP_IP) || (sap == PPP_IPV6) || (sap == PPP_ALLSAP)); /* Flush messages on unbind, per DLPI specification. */ flushq(WR(q), FLUSHALL); flushq(RD(q), FLUSHALL); if ((ppa != NULL) && IS_SPS_CACHED(sps)) { sps->sps_flags &= ~SPS_CACHED; msg = NULL; saydown = (ppa->ppa_ctl != NULL && (sps->sps_npmode == NPMODE_PASS || sps->sps_npmode == NPMODE_QUEUE)); if (sap == PPP_IP) { ppa->ppa_ip_cache = NULL; if (saydown) msg = create_lsmsg(PPP_LINKSTAT_IPV4_UNBOUND); } else if (sap == PPP_IPV6) { ppa->ppa_ip6_cache = NULL; if (saydown) msg = create_lsmsg(PPP_LINKSTAT_IPV6_UNBOUND); } if (msg != NULL) { #ifdef DBG_DLPI cmn_err(CE_CONT, "sending up %s\n", ((sap == PPP_IP) ? "PPP_LINKSTAT_IPV4_UNBOUND" : "PPP_LINKSTAT_IPV6_UNBOUND")); #endif putnext(ppa->ppa_ctl->sps_rq, msg); } } DBGDLPI((CE_CONT, "/%d: unbound from sap %X (req %X)\n", sps->sps_mn_id, sps->sps_sap, sps->sps_req_sap)); sps->sps_req_sap = 0; sps->sps_sap = -1; sps->sps_dlstate = DL_UNBOUND; dlokack(q, mp, DL_UNBIND_REQ); } /* * sppp_dlinforeq() * * MT-Perimeters: * shared inner, shared outer. * * Description: * Perform DL_INFO_REQ request, called by sppp_mproto. */ static int sppp_dlinforeq(queue_t *q, mblk_t *mp, spppstr_t *sps) { dl_info_ack_t *dlip; uint32_t size; uint32_t addr_size; sppa_t *ppa; ASSERT(q != NULL && q->q_ptr != NULL); ASSERT(mp != NULL && mp->b_rptr != NULL); ASSERT(sps != NULL); ppa = sps->sps_ppa; /* Exchange current msg for a DL_INFO_ACK. */ addr_size = SPPP_ADDRL; size = sizeof (dl_info_ack_t) + addr_size; if ((mp = mexchange(q, mp, size, M_PCPROTO, DL_INFO_ACK)) == NULL) { DBGERROR((CE_CONT, "DLPI info: mexchange failed\n")); /* mexchange already sent up an merror ENOSR */ return (0); } /* Fill in DL_INFO_ACK fields and reply */ dlip = (dl_info_ack_t *)mp->b_rptr; *dlip = sppp_infoack; dlip->dl_current_state = sps->sps_dlstate; dlip->dl_max_sdu = ppa != NULL ? ppa->ppa_mtu : PPP_MAXMTU; #ifdef DBG_DLPI { const char *cp = state2name(dlip->dl_current_state); if (cp != NULL) cmn_err(CE_CONT, "info returns state %s, max sdu %d\n", cp, dlip->dl_max_sdu); else cmn_err(CE_CONT, "info returns state %d, max sdu %d\n", dlip->dl_current_state, dlip->dl_max_sdu); } #endif qreply(q, mp); return (0); } /* * sppp_dlunitdatareq() * * MT-Perimeters: * shared inner, shared outer. * * Description: * Handle DL_UNITDATA_REQ request, called by sppp_mproto. This procedure * gets called for M_PROTO (DLPI) style of transmission. The fact that we * have acknowledged IP's fastpath probing (DL_IOC_HDR_INFO) does not * guarantee that IP will always transmit via M_DATA, and it merely implies * that such situation _may_ happen. In other words, IP may decide to use * M_PROTO (DLPI) for data transmission should it decide to do so. * Therefore, we should never place any restrictions or checks against * streams marked with SPS_FASTPATH, since it is legal for this procedure * to be entered with or without the bit set. */ static int sppp_dlunitdatareq(queue_t *q, mblk_t *mp, spppstr_t *sps) { sppa_t *ppa; mblk_t *hdrmp; mblk_t *pktmp; dl_unitdata_req_t *dludp; int dladdroff; int dladdrlen; int msize; int error = 0; boolean_t is_promisc; ASSERT(q != NULL && q->q_ptr != NULL); ASSERT(mp != NULL && mp->b_rptr != NULL); ASSERT((MTYPE(mp) == M_PCPROTO) || (MTYPE(mp) == M_PROTO)); dludp = (dl_unitdata_req_t *)mp->b_rptr; dladdroff = dludp->dl_dest_addr_offset; dladdrlen = dludp->dl_dest_addr_length; ASSERT(sps != NULL); ASSERT(!IS_SPS_PIOATTACH(sps)); ASSERT(sps->sps_dlstate == DL_IDLE); ASSERT(q->q_ptr == sps); /* * If this stream is not attached to any ppas, then discard data * coming down through this stream. */ ppa = sps->sps_ppa; if (ppa == NULL) { DBGERROR((CE_CONT, "DLPI unitdata: no attached ppa\n")); error = ENOLINK; } else if (mp->b_cont == NULL) { DBGERROR((CE_CONT, "DLPI unitdata: missing data\n")); error = EPROTO; } if (error != 0) { dluderrorind(q, mp, mp->b_rptr + dladdroff, dladdrlen, DL_BADDATA, error); return (0); } ASSERT(mp->b_cont->b_rptr != NULL); /* * Check if outgoing packet size is larger than allowed. We use * msgdsize to count all of M_DATA blocks in the message. */ msize = msgdsize(mp); if (msize > ppa->ppa_mtu) { /* Log, and send it anyway */ mutex_enter(&ppa->ppa_sta_lock); ppa->ppa_otoolongs++; mutex_exit(&ppa->ppa_sta_lock); } if (IS_SPS_KDEBUG(sps)) { SPDEBUG(PPP_DRV_NAME "/%d: DL_UNITDATA_REQ (%d bytes) sps=0x%p flags=0x%b " "ppa=0x%p flags=0x%b\n", sps->sps_mn_id, msize, (void *)sps, sps->sps_flags, SPS_FLAGS_STR, (void *)ppa, ppa->ppa_flags, PPA_FLAGS_STR); } /* Allocate a message (M_DATA) to contain PPP header bytes. */ if ((hdrmp = allocb(PPP_HDRLEN, BPRI_MED)) == NULL) { mutex_enter(&ppa->ppa_sta_lock); ppa->ppa_allocbfail++; mutex_exit(&ppa->ppa_sta_lock); DBGERROR((CE_CONT, "DLPI unitdata: can't allocate header buffer\n")); dluderrorind(q, mp, mp->b_rptr + dladdroff, dladdrlen, DL_SYSERR, ENOSR); return (0); } /* * Should there be any promiscuous stream(s), send the data up * for each promiscuous stream that we recognize. */ rw_enter(&ppa->ppa_sib_lock, RW_READER); is_promisc = ppa->ppa_promicnt; if (is_promisc) { ASSERT(ppa->ppa_streams != NULL); sppp_dlprsendup(ppa->ppa_streams, mp->b_cont, sps->sps_sap, B_FALSE); } rw_exit(&ppa->ppa_sib_lock); /* Discard DLPI header and keep only IP payload (mp->b_cont). */ pktmp = mp->b_cont; mp->b_cont = NULL; freemsg(mp); mp = hdrmp; *(uchar_t *)mp->b_wptr++ = PPP_ALLSTATIONS; *(uchar_t *)mp->b_wptr++ = PPP_UI; *(uchar_t *)mp->b_wptr++ = ((uint16_t)sps->sps_sap >> 8) & 0xff; *(uchar_t *)mp->b_wptr++ = ((uint16_t)sps->sps_sap) & 0xff; ASSERT(MBLKL(mp) == PPP_HDRLEN); linkb(mp, pktmp); /* * Only time-stamp the packet with hrtime if the upper stream * is configured to do so. */ if (IS_PPA_TIMESTAMP(ppa)) { ppa->ppa_lasttx = gethrtime(); } /* * Just put this back on the queue and allow the write service * routine to handle it. We're nested too deeply here to * rewind the stack sufficiently to prevent overflow. This is * the slow path anyway. */ if (putq(q, mp) == 0) { mutex_enter(&ppa->ppa_sta_lock); ppa->ppa_oqdropped++; mutex_exit(&ppa->ppa_sta_lock); freemsg(mp); } else { qenable(q); } return (0); } /* * sppp_dlpromisconreq() * * MT-Perimeters: * shared inner, shared outer. * * Description: * Perform DL_PROMISCON_REQ request, called by sppp_mproto. */ static int sppp_dlpromisconreq(queue_t *q, mblk_t *mp, spppstr_t *sps) { t_uscalar_t level; ASSERT(q != NULL && q->q_ptr != NULL); ASSERT(mp != NULL && mp->b_rptr != NULL); level = ((dl_promiscon_req_t *)mp->b_rptr)->dl_level; ASSERT(sps != NULL); /* snoop issues DL_PROMISCON_REQ more than once. */ if (IS_SPS_PROMISC(sps)) { dlokack(q, mp, DL_PROMISCON_REQ); } else if ((level != DL_PROMISC_PHYS) && (level != DL_PROMISC_SAP) && (level != DL_PROMISC_MULTI)) { DBGERROR((CE_CONT, "DLPI promiscon: bad level %d\n", level)); dlerrorack(q, mp, DL_PROMISCON_REQ, DL_NOTSUPPORTED, 0); } else { qwriter(q, mp, sppp_dl_promiscon, PERIM_INNER); } return (0); } /* * sppp_dl_promiscon() * * MT-Perimeters: * exclusive inner, shared outer. * * Description: * Called by qwriter (INNER) from sppp_dlpromisconreq as the result of * receiving a DL_PROMISCON_REQ message. */ static void sppp_dl_promiscon(queue_t *q, mblk_t *mp) { spppstr_t *sps; sppa_t *ppa; ASSERT(q != NULL && q->q_ptr != NULL); sps = (spppstr_t *)q->q_ptr; ASSERT(!IS_SPS_PROMISC(sps)); ASSERT(mp != NULL && mp->b_rptr != NULL); ppa = sps->sps_ppa; sps->sps_flags |= SPS_PROMISC; /* * We can't be sure that the sps_ppa field is valid, since the DLPI * spec says that DL_PROMISCON_REQ can be issued at any state, i.e., * the request can be issued even before DL_ATTACH_REQ or PPPIO_ATTACH * be issued to associate this stream with a ppa. */ if (ppa != NULL) { rw_enter(&ppa->ppa_sib_lock, RW_WRITER); ppa->ppa_promicnt++; rw_exit(&ppa->ppa_sib_lock); } DBGDLPI((CE_CONT, "/%d: promiscuous mode on\n", sps->sps_mn_id)); dlokack(q, mp, DL_PROMISCON_REQ); } /* * sppp_dlpromiscoffreq() * * MT-Perimeters: * shared inner, shared outer. * * Description: * Perform DL_PROMISCOFF_REQ request, called by sppp_mproto. */ static int sppp_dlpromiscoffreq(queue_t *q, mblk_t *mp, spppstr_t *sps) { t_uscalar_t level; ASSERT(q != NULL && q->q_ptr != NULL); ASSERT(mp != NULL && mp->b_rptr != NULL); level = ((dl_promiscoff_req_t *)mp->b_rptr)->dl_level; ASSERT(sps != NULL); if (!IS_SPS_PROMISC(sps)) { DBGERROR((CE_CONT, "DLPI promiscoff: not promiscuous\n")); dlerrorack(q, mp, DL_PROMISCOFF_REQ, DL_NOTENAB, 0); } else if ((level != DL_PROMISC_PHYS) && (level != DL_PROMISC_SAP) && (level != DL_PROMISC_MULTI)) { dlerrorack(q, mp, DL_PROMISCOFF_REQ, DL_NOTSUPPORTED, 0); DBGERROR((CE_CONT, "DLPI promiscoff: bad level %d\n", level)); } else { qwriter(q, mp, sppp_dl_promiscoff, PERIM_INNER); } return (0); } /* * sppp_dl_promiscoff() * * MT-Perimeters: * exclusive inner, shared outer. * * Description: * Called by qwriter (INNER) from sppp_dlpromiscoffreq as the result of * receiving a DL_PROMISCOFF_REQ message. */ static void sppp_dl_promiscoff(queue_t *q, mblk_t *mp) { spppstr_t *sps; sppa_t *ppa; ASSERT(q != NULL && q->q_ptr != NULL); sps = (spppstr_t *)q->q_ptr; ASSERT(IS_SPS_PROMISC(sps)); ASSERT(mp != NULL && mp->b_rptr != NULL); ppa = sps->sps_ppa; sps->sps_flags &= ~SPS_PROMISC; /* * We can't be guaranteed that the sps_ppa field is still valid, since * the control stream might have been closed earlier, in which case * the close procedure would have NULL'd out the sps_ppa. */ if (ppa != NULL) { rw_enter(&ppa->ppa_sib_lock, RW_WRITER); ASSERT(ppa->ppa_promicnt > 0); ppa->ppa_promicnt--; rw_exit(&ppa->ppa_sib_lock); } DBGDLPI((CE_CONT, "/%d: promiscuous mode off\n", sps->sps_mn_id)); dlokack(q, mp, DL_PROMISCOFF_REQ); } /* * sppp_dlphyreq() * * MT-Perimeters: * shared inner, shared outer. * * Description: * Perform DL_PHYS_ADDR_REQ request, called by sppp_mproto. This doesn't * return anything useful, but it keeps ifconfig happy. */ /* ARGSUSED */ static int sppp_dlphyreq(queue_t *q, mblk_t *mp, spppstr_t *us) { static struct ether_addr addr = { 0 }; dlphysaddrack(q, mp, (char *)&addr, ETHERADDRL); return (0); } /* * sppp_dladdether() * * Description: * Prepend an empty Ethernet header to msg for snoop, et al. Free * the original mblk if alloc fails. Only called for the purpose of sending * packets up the promiscous stream. */ /* ARGSUSED */ static mblk_t * sppp_dladdether(spppstr_t *sps, mblk_t *mp, t_scalar_t proto) { mblk_t *eh; t_scalar_t type; if ((eh = allocb(sizeof (struct ether_header), BPRI_MED)) == NULL) { freemsg(mp); return (NULL); } if (proto == PPP_IP) { type = ETHERTYPE_IP; } else if (proto == PPP_IPV6) { type = ETHERTYPE_IPV6; } else { /* * For all other protocols, end this up as an ETHERTYPE_PPP * type of packet. Since we've skipped the PPP headers in the * caller, make sure that we restore it. We know for sure that * the PPP header still exists in the message (only skipped), * since the sender of this message is pppd and it must have * included the PPP header in front. */ type = ETHERTYPE_PPP; mp->b_rptr -= PPP_HDRLEN; ASSERT(mp->b_rptr >= mp->b_datap->db_base); } eh->b_wptr += sizeof (struct ether_header); bzero((caddr_t)eh->b_rptr, sizeof (struct ether_header)); ((struct ether_header *)eh->b_rptr)->ether_type = htons((int16_t)type); linkb(eh, mp); return (eh); } /* * sppp_dladdud() * * Description: * Prepend DL_UNITDATA_IND mblk to msg, free original alloc fails. */ /* ARGSUSED */ mblk_t * sppp_dladdud(spppstr_t *sps, mblk_t *mp, t_scalar_t proto, boolean_t promisc) { dl_unitdata_ind_t *dlu; mblk_t *dh; size_t size; t_scalar_t type; size = sizeof (dl_unitdata_ind_t) + (2 * SPPP_ADDRL); if ((dh = allocb(size, BPRI_MED)) == NULL) { freemsg(mp); return (NULL); } dh->b_datap->db_type = M_PROTO; dh->b_wptr = dh->b_datap->db_lim; dh->b_rptr = dh->b_wptr - size; dlu = (dl_unitdata_ind_t *)dh->b_rptr; dlu->dl_primitive = DL_UNITDATA_IND; dlu->dl_dest_addr_length = SPPP_ADDRL; dlu->dl_dest_addr_offset = sizeof (dl_unitdata_ind_t); dlu->dl_src_addr_length = SPPP_ADDRL; dlu->dl_src_addr_offset = sizeof (dl_unitdata_ind_t) + SPPP_ADDRL; dlu->dl_group_address = 0; if (promisc) { if (proto == PPP_IP) { type = ETHERTYPE_IP; } else if (proto == PPP_IPV6) { type = ETHERTYPE_IPV6; } else { /* * For all other protocols, send this up as an * ETHERTYPE_PPP type of packet. Since we've skipped * the PPP headers in the caller, make sure that we * restore it. We know for sure that the PPP header * still exists in the message (only skipped), since * the sender of this message is pppd and it must * have included the PPP header in front. */ type = ETHERTYPE_PPP; mp->b_rptr -= PPP_HDRLEN; ASSERT(mp->b_rptr >= mp->b_datap->db_base); } } else { type = sps->sps_req_sap; } /* * Send the DLPI client the data with the SAP they requested, * (e.g. ETHERTYPE_IP) rather than the PPP protocol (e.g. PPP_IP). */ ((spppreqsap_t *)(dlu + 1))[0] = type; ((spppreqsap_t *)(dlu + 1))[1] = type; linkb(dh, mp); return (dh); } /* * sppp_dlprsendup() * * Description: * For any valid promiscuous streams (marked with SPS_PROMISC and its * sps_dlstate is DL_IDLE), send data upstream. The caller is expected * to hold ppa_sib_lock when calling this procedure. */ void sppp_dlprsendup(spppstr_t *sps, mblk_t *mp, t_scalar_t proto, boolean_t header) { sppa_t *ppa; mblk_t *dmp; ASSERT(sps != NULL); ASSERT(mp != NULL && mp->b_rptr != NULL); ppa = sps->sps_ppa; ASSERT(ppa != NULL); /* NOTE: caller must hold ppa_sib_lock in RW_READER mode */ ASSERT(RW_READ_HELD(&ppa->ppa_sib_lock)); for (; sps != NULL; sps = sps->sps_nextsib) { /* * We specifically test to ensure that the DLPI state for the * promiscous stream is IDLE (DL_IDLE), since such state tells * us that the promiscous stream has been bound to PPP_ALLSAP. */ if (IS_SPS_PROMISC(sps) && (sps->sps_dlstate == DL_IDLE) && canputnext(sps->sps_rq)) { if ((dmp = dupmsg(mp)) == NULL) { mutex_enter(&ppa->ppa_sta_lock); ppa->ppa_allocbfail++; mutex_exit(&ppa->ppa_sta_lock); continue; } if (header) { dmp->b_rptr += PPP_HDRLEN; } if (IS_SPS_RAWDATA(sps)) { /* function frees original message if fails */ dmp = sppp_dladdether(sps, dmp, proto); } else { /* function frees original message if fails */ dmp = sppp_dladdud(sps, dmp, proto, B_TRUE); } if (dmp != NULL) { putnext(sps->sps_rq, dmp); } else { mutex_enter(&ppa->ppa_sta_lock); ppa->ppa_allocbfail++; mutex_exit(&ppa->ppa_sta_lock); } } } }