Index: head/sys/netgraph/atm/ng_atm.c =================================================================== --- head/sys/netgraph/atm/ng_atm.c (revision 298812) +++ head/sys/netgraph/atm/ng_atm.c (revision 298813) @@ -1,1448 +1,1448 @@ /*- * Copyright (c) 2001-2003 * Fraunhofer Institute for Open Communication Systems (FhG Fokus). * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * Author: Hartmut Brandt */ /* * Netgraph module to connect NATM interfaces to netgraph. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Hooks in the NATM code */ extern void (*ng_atm_attach_p)(struct ifnet *); extern void (*ng_atm_detach_p)(struct ifnet *); extern int (*ng_atm_output_p)(struct ifnet *, struct mbuf **); extern void (*ng_atm_input_p)(struct ifnet *, struct mbuf **, struct atm_pseudohdr *, void *); extern void (*ng_atm_input_orphan_p)(struct ifnet *, struct mbuf *, struct atm_pseudohdr *, void *); extern void (*ng_atm_event_p)(struct ifnet *, uint32_t, void *); /* * Sysctl stuff. */ static SYSCTL_NODE(_net_graph, OID_AUTO, atm, CTLFLAG_RW, 0, "atm related stuff"); #ifdef NGATM_DEBUG static int allow_shutdown; SYSCTL_INT(_net_graph_atm, OID_AUTO, allow_shutdown, CTLFLAG_RW, &allow_shutdown, 0, "allow ng_atm nodes to shutdown"); #endif /* * Hook private data */ struct ngvcc { uint16_t vpi; /* VPI of this hook */ uint16_t vci; /* VCI of this hook, 0 if none */ uint32_t flags; /* private flags */ hook_p hook; /* the connected hook */ LIST_ENTRY(ngvcc) link; }; #define VCC_OPEN 0x0001 /* open */ /* * Node private data */ struct priv { struct ifnet *ifp; /* the ATM interface */ hook_p input; /* raw input hook */ hook_p orphans; /* packets to nowhere */ hook_p output; /* catch output packets */ hook_p manage; /* has also entry in vccs */ uint64_t in_packets; uint64_t in_errors; uint64_t out_packets; uint64_t out_errors; LIST_HEAD(, ngvcc) vccs; }; /* * Parse ifstate state */ static const struct ng_parse_struct_field ng_atm_if_change_info[] = NGM_ATM_IF_CHANGE_INFO; static const struct ng_parse_type ng_atm_if_change_type = { &ng_parse_struct_type, &ng_atm_if_change_info }; /* * Parse vcc state change */ static const struct ng_parse_struct_field ng_atm_vcc_change_info[] = NGM_ATM_VCC_CHANGE_INFO; static const struct ng_parse_type ng_atm_vcc_change_type = { &ng_parse_struct_type, &ng_atm_vcc_change_info }; /* * Parse acr change */ static const struct ng_parse_struct_field ng_atm_acr_change_info[] = NGM_ATM_ACR_CHANGE_INFO; static const struct ng_parse_type ng_atm_acr_change_type = { &ng_parse_struct_type, &ng_atm_acr_change_info }; /* * Parse the configuration structure ng_atm_config */ static const struct ng_parse_struct_field ng_atm_config_type_info[] = NGM_ATM_CONFIG_INFO; static const struct ng_parse_type ng_atm_config_type = { &ng_parse_struct_type, &ng_atm_config_type_info }; /* * Parse a single vcc structure and a variable array of these ng_atm_vccs */ static const struct ng_parse_struct_field ng_atm_tparam_type_info[] = NGM_ATM_TPARAM_INFO; static const struct ng_parse_type ng_atm_tparam_type = { &ng_parse_struct_type, &ng_atm_tparam_type_info }; static const struct ng_parse_struct_field ng_atm_vcc_type_info[] = NGM_ATM_VCC_INFO; static const struct ng_parse_type ng_atm_vcc_type = { &ng_parse_struct_type, &ng_atm_vcc_type_info }; static int ng_atm_vccarray_getlen(const struct ng_parse_type *type, const u_char *start, const u_char *buf) { const struct atmio_vcctable *vp; vp = (const struct atmio_vcctable *) (buf - offsetof(struct atmio_vcctable, vccs)); return (vp->count); } static const struct ng_parse_array_info ng_atm_vccarray_info = NGM_ATM_VCCARRAY_INFO; static const struct ng_parse_type ng_atm_vccarray_type = { &ng_parse_array_type, &ng_atm_vccarray_info }; static const struct ng_parse_struct_field ng_atm_vcctable_type_info[] = NGM_ATM_VCCTABLE_INFO; static const struct ng_parse_type ng_atm_vcctable_type = { &ng_parse_struct_type, &ng_atm_vcctable_type_info }; /* * Parse CPCS INIT structure ng_atm_cpcs_init */ static const struct ng_parse_struct_field ng_atm_cpcs_init_type_info[] = NGM_ATM_CPCS_INIT_INFO; static const struct ng_parse_type ng_atm_cpcs_init_type = { &ng_parse_struct_type, &ng_atm_cpcs_init_type_info }; /* * Parse CPCS TERM structure ng_atm_cpcs_term */ static const struct ng_parse_struct_field ng_atm_cpcs_term_type_info[] = NGM_ATM_CPCS_TERM_INFO; static const struct ng_parse_type ng_atm_cpcs_term_type = { &ng_parse_struct_type, &ng_atm_cpcs_term_type_info }; /* * Parse statistic struct */ static const struct ng_parse_struct_field ng_atm_stats_type_info[] = NGM_ATM_STATS_INFO; static const struct ng_parse_type ng_atm_stats_type = { &ng_parse_struct_type, &ng_atm_stats_type_info }; static const struct ng_cmdlist ng_atm_cmdlist[] = { { NGM_ATM_COOKIE, NGM_ATM_GET_IFNAME, "getifname", NULL, &ng_parse_string_type }, { NGM_ATM_COOKIE, NGM_ATM_GET_CONFIG, "getconfig", NULL, &ng_atm_config_type }, { NGM_ATM_COOKIE, NGM_ATM_GET_VCCS, "getvccs", NULL, &ng_atm_vcctable_type }, { NGM_ATM_COOKIE, NGM_ATM_CPCS_INIT, "cpcsinit", &ng_atm_cpcs_init_type, NULL }, { NGM_ATM_COOKIE, NGM_ATM_CPCS_TERM, "cpcsterm", &ng_atm_cpcs_term_type, NULL }, { NGM_ATM_COOKIE, NGM_ATM_GET_VCC, "getvcc", &ng_parse_hookbuf_type, &ng_atm_vcc_type }, { NGM_ATM_COOKIE, NGM_ATM_GET_VCCID, "getvccid", &ng_atm_vcc_type, &ng_atm_vcc_type }, { NGM_ATM_COOKIE, NGM_ATM_GET_STATS, "getstats", NULL, &ng_atm_stats_type }, /* events */ { NGM_ATM_COOKIE, NGM_ATM_IF_CHANGE, "if_change", &ng_atm_if_change_type, &ng_atm_if_change_type, }, { NGM_ATM_COOKIE, NGM_ATM_VCC_CHANGE, "vcc_change", &ng_atm_vcc_change_type, &ng_atm_vcc_change_type, }, { NGM_ATM_COOKIE, NGM_ATM_ACR_CHANGE, "acr_change", &ng_atm_acr_change_type, &ng_atm_acr_change_type, }, { 0 } }; static int ng_atm_mod_event(module_t, int, void *); static ng_constructor_t ng_atm_constructor; static ng_shutdown_t ng_atm_shutdown; static ng_rcvmsg_t ng_atm_rcvmsg; static ng_newhook_t ng_atm_newhook; static ng_connect_t ng_atm_connect; static ng_disconnect_t ng_atm_disconnect; static ng_rcvdata_t ng_atm_rcvdata; static ng_rcvdata_t ng_atm_rcvdrop; static struct ng_type ng_atm_typestruct = { .version = NG_ABI_VERSION, .name = NG_ATM_NODE_TYPE, .mod_event = ng_atm_mod_event, .constructor = ng_atm_constructor, .rcvmsg = ng_atm_rcvmsg, .shutdown = ng_atm_shutdown, .newhook = ng_atm_newhook, .connect = ng_atm_connect, .rcvdata = ng_atm_rcvdata, .disconnect = ng_atm_disconnect, .cmdlist = ng_atm_cmdlist, }; NETGRAPH_INIT(atm, &ng_atm_typestruct); static const struct { u_int media; const char *name; } atmmedia[] = IFM_SUBTYPE_ATM_DESCRIPTIONS; #define IFP2NG(IFP) ((node_p)((struct ifatm *)(IFP)->if_softc)->ngpriv) #define IFP2NG_SET(IFP, val) (((struct ifatm *)(IFP)->if_softc)->ngpriv = (val)) #define IFFLAGS "\020\001UP\002BROADCAST\003DEBUG\004LOOPBACK" \ "\005POINTOPOINT\006SMART\007RUNNING\010NOARP" \ "\011PROMISC\012ALLMULTI\013OACTIVE\014SIMPLEX" \ "\015LINK0\016LINK1\017LINK2\020MULTICAST" /************************************************************/ /* * INPUT */ /* * A packet is received from an interface. * If we have an input hook, prepend the pseudoheader to the data and * deliver it out to that hook. If not, look whether it is destined for * use. If so locate the appropriate hook, deliver the packet without the * header and we are done. If it is not for us, leave it alone. */ static void ng_atm_input(struct ifnet *ifp, struct mbuf **mp, struct atm_pseudohdr *ah, void *rxhand) { node_p node = IFP2NG(ifp); struct priv *priv; const struct ngvcc *vcc; int error; if (node == NULL) return; priv = NG_NODE_PRIVATE(node); if (priv->input != NULL) { /* * Prepend the atm_pseudoheader. */ M_PREPEND(*mp, sizeof(*ah), M_NOWAIT); if (*mp == NULL) return; memcpy(mtod(*mp, struct atm_pseudohdr *), ah, sizeof(*ah)); NG_SEND_DATA_ONLY(error, priv->input, *mp); if (error == 0) { priv->in_packets++; *mp = NULL; } else { #ifdef NGATM_DEBUG printf("%s: error=%d\n", __func__, error); #endif priv->in_errors++; } return; } if ((ATM_PH_FLAGS(ah) & ATMIO_FLAG_NG) == 0) return; vcc = (struct ngvcc *)rxhand; NG_SEND_DATA_ONLY(error, vcc->hook, *mp); if (error == 0) { priv->in_packets++; *mp = NULL; } else { #ifdef NGATM_DEBUG printf("%s: error=%d\n", __func__, error); #endif priv->in_errors++; } } /* * ATM packet is about to be output. The atm_pseudohdr is already prepended. * If the hook is set, reroute the packet to the hook. */ static int ng_atm_output(struct ifnet *ifp, struct mbuf **mp) { const node_p node = IFP2NG(ifp); const struct priv *priv; int error = 0; if (node == NULL) return (0); priv = NG_NODE_PRIVATE(node); if (priv->output) { NG_SEND_DATA_ONLY(error, priv->output, *mp); *mp = NULL; } return (error); } /* * Well, this doesn't make much sense for ATM. */ static void ng_atm_input_orphans(struct ifnet *ifp, struct mbuf *m, struct atm_pseudohdr *ah, void *rxhand) { node_p node = IFP2NG(ifp); struct priv *priv; int error; if (node == NULL) { m_freem(m); return; } priv = NG_NODE_PRIVATE(node); if (priv->orphans == NULL) { m_freem(m); return; } /* * Prepend the atm_pseudoheader. */ M_PREPEND(m, sizeof(*ah), M_NOWAIT); if (m == NULL) return; memcpy(mtod(m, struct atm_pseudohdr *), ah, sizeof(*ah)); NG_SEND_DATA_ONLY(error, priv->orphans, m); if (error == 0) priv->in_packets++; else { priv->in_errors++; #ifdef NGATM_DEBUG printf("%s: error=%d\n", __func__, error); #endif } } /************************************************************/ /* * OUTPUT */ static int ng_atm_rcvdata(hook_p hook, item_p item) { node_p node = NG_HOOK_NODE(hook); struct priv *priv = NG_NODE_PRIVATE(node); const struct ngvcc *vcc = NG_HOOK_PRIVATE(hook); struct mbuf *m; struct atm_pseudohdr *aph; int error; if (vcc->vci == 0) { NG_FREE_ITEM(item); return (ENOTCONN); } NGI_GET_M(item, m); NG_FREE_ITEM(item); /* * Prepend pseudo-hdr. Drivers don't care about the flags. */ M_PREPEND(m, sizeof(*aph), M_NOWAIT); if (m == NULL) { NG_FREE_M(m); return (ENOMEM); } aph = mtod(m, struct atm_pseudohdr *); ATM_PH_VPI(aph) = vcc->vpi; ATM_PH_SETVCI(aph, vcc->vci); ATM_PH_FLAGS(aph) = 0; if ((error = atm_output(priv->ifp, m, NULL, NULL)) == 0) priv->out_packets++; else priv->out_errors++; return (error); } static int ng_atm_rcvdrop(hook_p hook, item_p item) { NG_FREE_ITEM(item); return (0); } /************************************************************ * * Event from driver. */ static void ng_atm_event_func(node_p node, hook_p hook, void *arg, int event) { const struct priv *priv = NG_NODE_PRIVATE(node); struct ngvcc *vcc; struct ng_mesg *mesg; int error; switch (event) { case ATMEV_FLOW_CONTROL: { struct atmev_flow_control *ev = arg; struct ngm_queue_state *qstate; /* find the connection */ LIST_FOREACH(vcc, &priv->vccs, link) if (vcc->vci == ev->vci && vcc->vpi == ev->vpi) break; if (vcc == NULL) break; /* convert into a flow control message */ NG_MKMESSAGE(mesg, NGM_FLOW_COOKIE, ev->busy ? NGM_HIGH_WATER_PASSED : NGM_LOW_WATER_PASSED, sizeof(struct ngm_queue_state), M_NOWAIT); if (mesg == NULL) break; qstate = (struct ngm_queue_state *)mesg->data; /* XXX have to figure out how to get that info */ NG_SEND_MSG_HOOK(error, node, mesg, vcc->hook, 0); break; } case ATMEV_VCC_CHANGED: { struct atmev_vcc_changed *ev = arg; struct ngm_atm_vcc_change *chg; if (priv->manage == NULL) break; NG_MKMESSAGE(mesg, NGM_ATM_COOKIE, NGM_ATM_VCC_CHANGE, sizeof(struct ngm_atm_vcc_change), M_NOWAIT); if (mesg == NULL) break; chg = (struct ngm_atm_vcc_change *)mesg->data; chg->vci = ev->vci; chg->vpi = ev->vpi; chg->state = (ev->up != 0); chg->node = NG_NODE_ID(node); NG_SEND_MSG_HOOK(error, node, mesg, priv->manage, 0); break; } case ATMEV_IFSTATE_CHANGED: { struct atmev_ifstate_changed *ev = arg; struct ngm_atm_if_change *chg; if (priv->manage == NULL) break; NG_MKMESSAGE(mesg, NGM_ATM_COOKIE, NGM_ATM_IF_CHANGE, sizeof(struct ngm_atm_if_change), M_NOWAIT); if (mesg == NULL) break; chg = (struct ngm_atm_if_change *)mesg->data; chg->carrier = (ev->carrier != 0); chg->running = (ev->running != 0); chg->node = NG_NODE_ID(node); NG_SEND_MSG_HOOK(error, node, mesg, priv->manage, 0); break; } case ATMEV_ACR_CHANGED: { struct atmev_acr_changed *ev = arg; struct ngm_atm_acr_change *acr; /* find the connection */ LIST_FOREACH(vcc, &priv->vccs, link) if (vcc->vci == ev->vci && vcc->vpi == ev->vpi) break; if (vcc == NULL) break; /* convert into a flow control message */ NG_MKMESSAGE(mesg, NGM_ATM_COOKIE, NGM_ATM_ACR_CHANGE, sizeof(struct ngm_atm_acr_change), M_NOWAIT); if (mesg == NULL) break; acr = (struct ngm_atm_acr_change *)mesg->data; acr->node = NG_NODE_ID(node); acr->vci = ev->vci; acr->vpi = ev->vpi; acr->acr = ev->acr; NG_SEND_MSG_HOOK(error, node, mesg, vcc->hook, 0); break; } } } /* * Use send_fn to get the right lock */ static void ng_atm_event(struct ifnet *ifp, uint32_t event, void *arg) { const node_p node = IFP2NG(ifp); if (node != NULL) /* may happen during attach/detach */ (void)ng_send_fn(node, NULL, ng_atm_event_func, arg, event); } /************************************************************ * * CPCS */ /* * Open a channel for the user */ static int ng_atm_cpcs_init(node_p node, const struct ngm_atm_cpcs_init *arg) { struct priv *priv = NG_NODE_PRIVATE(node); const struct ifatm_mib *mib; struct ngvcc *vcc; struct atmio_openvcc data; int err; if(priv->ifp->if_ioctl == NULL) return (ENXIO); mib = (const struct ifatm_mib *)(priv->ifp->if_linkmib); LIST_FOREACH(vcc, &priv->vccs, link) if (strcmp(arg->name, NG_HOOK_NAME(vcc->hook)) == 0) break; if (vcc == NULL) return (ENOTCONN); if (vcc->flags & VCC_OPEN) return (EISCONN); /* * Check user arguments and construct ioctl argument */ memset(&data, 0, sizeof(data)); data.rxhand = vcc; switch (data.param.aal = arg->aal) { case ATMIO_AAL_34: case ATMIO_AAL_5: case ATMIO_AAL_0: case ATMIO_AAL_RAW: break; default: return (EINVAL); } if (arg->vpi > 0xff) return (EINVAL); data.param.vpi = arg->vpi; /* allow 0.0 as catch all receive channel */ if (arg->vci == 0 && (arg->vpi != 0 || !(arg->flags & ATMIO_FLAG_NOTX))) return (EINVAL); data.param.vci = arg->vci; data.param.tparam.pcr = arg->pcr; if (arg->mcr > arg->pcr) return (EINVAL); data.param.tparam.mcr = arg->mcr; if (!(arg->flags & ATMIO_FLAG_NOTX)) { if (arg->tmtu == 0) data.param.tmtu = priv->ifp->if_mtu; else { data.param.tmtu = arg->tmtu; } } if (!(arg->flags & ATMIO_FLAG_NORX)) { if (arg->rmtu == 0) data.param.rmtu = priv->ifp->if_mtu; else { data.param.rmtu = arg->rmtu; } } switch (data.param.traffic = arg->traffic) { case ATMIO_TRAFFIC_UBR: case ATMIO_TRAFFIC_CBR: break; case ATMIO_TRAFFIC_VBR: if (arg->scr > arg->pcr) return (EINVAL); data.param.tparam.scr = arg->scr; if (arg->mbs > (1 << 24)) return (EINVAL); data.param.tparam.mbs = arg->mbs; break; case ATMIO_TRAFFIC_ABR: if (arg->icr > arg->pcr || arg->icr < arg->mcr) return (EINVAL); data.param.tparam.icr = arg->icr; if (arg->tbe == 0 || arg->tbe > (1 << 24)) return (EINVAL); data.param.tparam.tbe = arg->tbe; if (arg->nrm > 0x7) return (EINVAL); data.param.tparam.nrm = arg->nrm; if (arg->trm > 0x7) return (EINVAL); data.param.tparam.trm = arg->trm; if (arg->adtf > 0x3ff) return (EINVAL); data.param.tparam.adtf = arg->adtf; if (arg->rif > 0xf) return (EINVAL); data.param.tparam.rif = arg->rif; if (arg->rdf > 0xf) return (EINVAL); data.param.tparam.rdf = arg->rdf; if (arg->cdf > 0x7) return (EINVAL); data.param.tparam.cdf = arg->cdf; break; default: return (EINVAL); } if ((arg->flags & ATMIO_FLAG_NORX) && (arg->flags & ATMIO_FLAG_NOTX)) return (EINVAL); data.param.flags = arg->flags & ~(ATM_PH_AAL5 | ATM_PH_LLCSNAP); data.param.flags |= ATMIO_FLAG_NG; err = (*priv->ifp->if_ioctl)(priv->ifp, SIOCATMOPENVCC, (caddr_t)&data); if (err == 0) { vcc->vci = data.param.vci; vcc->vpi = data.param.vpi; vcc->flags = VCC_OPEN; } return (err); } /* * Issue the close command to the driver */ static int cpcs_term(const struct priv *priv, u_int vpi, u_int vci) { struct atmio_closevcc data; if (priv->ifp->if_ioctl == NULL) return ENXIO; data.vpi = vpi; data.vci = vci; return ((*priv->ifp->if_ioctl)(priv->ifp, SIOCATMCLOSEVCC, (caddr_t)&data)); } /* * Close a channel by request of the user */ static int ng_atm_cpcs_term(node_p node, const struct ngm_atm_cpcs_term *arg) { struct priv *priv = NG_NODE_PRIVATE(node); struct ngvcc *vcc; int error; LIST_FOREACH(vcc, &priv->vccs, link) if(strcmp(arg->name, NG_HOOK_NAME(vcc->hook)) == 0) break; if (vcc == NULL) return (ENOTCONN); if (!(vcc->flags & VCC_OPEN)) return (ENOTCONN); error = cpcs_term(priv, vcc->vpi, vcc->vci); vcc->vci = 0; vcc->vpi = 0; vcc->flags = 0; return (error); } /************************************************************/ /* * CONTROL MESSAGES */ /* * Produce a textual description of the current status */ static int text_status(node_p node, char *arg, u_int len) { const struct priv *priv = NG_NODE_PRIVATE(node); const struct ifatm_mib *mib; struct sbuf sbuf; u_int i; static const struct { const char *name; const char *vendor; } devices[] = { ATM_DEVICE_NAMES }; mib = (const struct ifatm_mib *)(priv->ifp->if_linkmib); sbuf_new(&sbuf, arg, len, SBUF_FIXEDLEN); sbuf_printf(&sbuf, "interface: %s\n", priv->ifp->if_xname); if (mib->device >= nitems(devices)) sbuf_printf(&sbuf, "device=unknown\nvendor=unknown\n"); else sbuf_printf(&sbuf, "device=%s\nvendor=%s\n", devices[mib->device].name, devices[mib->device].vendor); for (i = 0; atmmedia[i].name; i++) if(mib->media == atmmedia[i].media) { sbuf_printf(&sbuf, "media=%s\n", atmmedia[i].name); break; } if(atmmedia[i].name == NULL) sbuf_printf(&sbuf, "media=unknown\n"); sbuf_printf(&sbuf, "serial=%u esi=%6D hardware=%u software=%u\n", mib->serial, mib->esi, ":", mib->hw_version, mib->sw_version); sbuf_printf(&sbuf, "pcr=%u vpi_bits=%u vci_bits=%u max_vpcs=%u " "max_vccs=%u\n", mib->pcr, mib->vpi_bits, mib->vci_bits, mib->max_vpcs, mib->max_vccs); sbuf_printf(&sbuf, "ifflags=%b\n", priv->ifp->if_flags, IFFLAGS); sbuf_finish(&sbuf); return (sbuf_len(&sbuf)); } /* * Get control message */ static int ng_atm_rcvmsg(node_p node, item_p item, hook_p lasthook) { const struct priv *priv = NG_NODE_PRIVATE(node); struct ng_mesg *resp = NULL; struct ng_mesg *msg; struct ifatm_mib *mib = (struct ifatm_mib *)(priv->ifp->if_linkmib); int error = 0; NGI_GET_MSG(item, msg); switch (msg->header.typecookie) { case NGM_GENERIC_COOKIE: switch (msg->header.cmd) { case NGM_TEXT_STATUS: NG_MKRESPONSE(resp, msg, NG_TEXTRESPONSE, M_NOWAIT); if(resp == NULL) { error = ENOMEM; break; } resp->header.arglen = text_status(node, (char *)resp->data, resp->header.arglen) + 1; break; default: error = EINVAL; break; } break; case NGM_ATM_COOKIE: switch (msg->header.cmd) { case NGM_ATM_GET_IFNAME: NG_MKRESPONSE(resp, msg, IFNAMSIZ, M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } strlcpy(resp->data, priv->ifp->if_xname, IFNAMSIZ); break; case NGM_ATM_GET_CONFIG: { struct ngm_atm_config *config; NG_MKRESPONSE(resp, msg, sizeof(*config), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } config = (struct ngm_atm_config *)resp->data; config->pcr = mib->pcr; config->vpi_bits = mib->vpi_bits; config->vci_bits = mib->vci_bits; config->max_vpcs = mib->max_vpcs; config->max_vccs = mib->max_vccs; break; } case NGM_ATM_GET_VCCS: { struct atmio_vcctable *vccs; size_t len; if (priv->ifp->if_ioctl == NULL) { error = ENXIO; break; } error = (*priv->ifp->if_ioctl)(priv->ifp, SIOCATMGETVCCS, (caddr_t)&vccs); if (error) break; len = sizeof(*vccs) + vccs->count * sizeof(vccs->vccs[0]); NG_MKRESPONSE(resp, msg, len, M_NOWAIT); if (resp == NULL) { error = ENOMEM; free(vccs, M_DEVBUF); break; } (void)memcpy(resp->data, vccs, len); free(vccs, M_DEVBUF); break; } case NGM_ATM_GET_VCC: { char hook[NG_HOOKSIZ]; struct atmio_vcctable *vccs; struct ngvcc *vcc; u_int i; if (priv->ifp->if_ioctl == NULL) { error = ENXIO; break; } if (msg->header.arglen != NG_HOOKSIZ) { error = EINVAL; break; } strncpy(hook, msg->data, NG_HOOKSIZ); hook[NG_HOOKSIZ - 1] = '\0'; LIST_FOREACH(vcc, &priv->vccs, link) if (strcmp(NG_HOOK_NAME(vcc->hook), hook) == 0) break; if (vcc == NULL) { error = ENOTCONN; break; } error = (*priv->ifp->if_ioctl)(priv->ifp, SIOCATMGETVCCS, (caddr_t)&vccs); if (error) break; for (i = 0; i < vccs->count; i++) if (vccs->vccs[i].vpi == vcc->vpi && vccs->vccs[i].vci == vcc->vci) break; if (i == vccs->count) { error = ENOTCONN; free(vccs, M_DEVBUF); break; } NG_MKRESPONSE(resp, msg, sizeof(vccs->vccs[0]), M_NOWAIT); if (resp == NULL) { error = ENOMEM; free(vccs, M_DEVBUF); break; } *(struct atmio_vcc *)resp->data = vccs->vccs[i]; free(vccs, M_DEVBUF); break; } case NGM_ATM_GET_VCCID: { struct atmio_vcc *arg; struct atmio_vcctable *vccs; u_int i; if (priv->ifp->if_ioctl == NULL) { error = ENXIO; break; } if (msg->header.arglen != sizeof(*arg)) { error = EINVAL; break; } arg = (struct atmio_vcc *)msg->data; error = (*priv->ifp->if_ioctl)(priv->ifp, SIOCATMGETVCCS, (caddr_t)&vccs); if (error) break; for (i = 0; i < vccs->count; i++) if (vccs->vccs[i].vpi == arg->vpi && vccs->vccs[i].vci == arg->vci) break; if (i == vccs->count) { error = ENOTCONN; free(vccs, M_DEVBUF); break; } NG_MKRESPONSE(resp, msg, sizeof(vccs->vccs[0]), M_NOWAIT); if (resp == NULL) { error = ENOMEM; free(vccs, M_DEVBUF); break; } *(struct atmio_vcc *)resp->data = vccs->vccs[i]; free(vccs, M_DEVBUF); break; } case NGM_ATM_CPCS_INIT: if (msg->header.arglen != sizeof(struct ngm_atm_cpcs_init)) { error = EINVAL; break; } error = ng_atm_cpcs_init(node, (struct ngm_atm_cpcs_init *)msg->data); break; case NGM_ATM_CPCS_TERM: if (msg->header.arglen != sizeof(struct ngm_atm_cpcs_term)) { error = EINVAL; break; } error = ng_atm_cpcs_term(node, (struct ngm_atm_cpcs_term *)msg->data); break; case NGM_ATM_GET_STATS: { struct ngm_atm_stats *p; if (msg->header.arglen != 0) { error = EINVAL; break; } NG_MKRESPONSE(resp, msg, sizeof(*p), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } p = (struct ngm_atm_stats *)resp->data; p->in_packets = priv->in_packets; p->out_packets = priv->out_packets; p->in_errors = priv->in_errors; p->out_errors = priv->out_errors; break; } default: error = EINVAL; break; } break; default: error = EINVAL; break; } NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } /************************************************************/ /* * HOOK MANAGEMENT */ /* * A new hook is create that will be connected to the node. * Check, whether the name is one of the predefined ones. * If not, create a new entry into the vcc list. */ static int ng_atm_newhook(node_p node, hook_p hook, const char *name) { struct priv *priv = NG_NODE_PRIVATE(node); struct ngvcc *vcc; if (strcmp(name, "input") == 0) { priv->input = hook; NG_HOOK_SET_RCVDATA(hook, ng_atm_rcvdrop); return (0); } if (strcmp(name, "output") == 0) { priv->output = hook; NG_HOOK_SET_RCVDATA(hook, ng_atm_rcvdrop); return (0); } if (strcmp(name, "orphans") == 0) { priv->orphans = hook; NG_HOOK_SET_RCVDATA(hook, ng_atm_rcvdrop); return (0); } /* * Allocate a new entry */ vcc = malloc(sizeof(*vcc), M_NETGRAPH, M_NOWAIT | M_ZERO); if (vcc == NULL) return (ENOMEM); vcc->hook = hook; NG_HOOK_SET_PRIVATE(hook, vcc); LIST_INSERT_HEAD(&priv->vccs, vcc, link); if (strcmp(name, "manage") == 0) priv->manage = hook; return (0); } /* * Connect. Set the peer to queuing. */ static int ng_atm_connect(hook_p hook) { if (NG_HOOK_PRIVATE(hook) != NULL) NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook)); return (0); } /* * Disconnect a HOOK */ static int ng_atm_disconnect(hook_p hook) { struct priv *priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); struct ngvcc *vcc = NG_HOOK_PRIVATE(hook); if (vcc == NULL) { if (hook == priv->output) { priv->output = NULL; return (0); } if (hook == priv->input) { priv->input = NULL; return (0); } if (hook == priv->orphans) { priv->orphans = NULL; return (0); } log(LOG_ERR, "ng_atm: bad hook '%s'", NG_HOOK_NAME(hook)); return (0); } /* don't terminate if we are detaching from the interface */ if ((vcc->flags & VCC_OPEN) && priv->ifp != NULL) (void)cpcs_term(priv, vcc->vpi, vcc->vci); NG_HOOK_SET_PRIVATE(hook, NULL); LIST_REMOVE(vcc, link); free(vcc, M_NETGRAPH); if (hook == priv->manage) priv->manage = NULL; return (0); } /************************************************************/ /* * NODE MANAGEMENT */ /* * ATM interface attached - create a node and name it like the interface. */ static void ng_atm_attach(struct ifnet *ifp) { node_p node; struct priv *priv; KASSERT(IFP2NG(ifp) == 0, ("%s: node alreay exists?", __func__)); if (ng_make_node_common(&ng_atm_typestruct, &node) != 0) { log(LOG_ERR, "%s: can't create node for %s\n", __func__, ifp->if_xname); return; } priv = malloc(sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO); if (priv == NULL) { log(LOG_ERR, "%s: can't allocate memory for %s\n", __func__, ifp->if_xname); NG_NODE_UNREF(node); return; } NG_NODE_SET_PRIVATE(node, priv); priv->ifp = ifp; LIST_INIT(&priv->vccs); IFP2NG_SET(ifp, node); if (ng_name_node(node, ifp->if_xname) != 0) { log(LOG_WARNING, "%s: can't name node %s\n", __func__, ifp->if_xname); } } /* * ATM interface detached - destroy node. */ static void ng_atm_detach(struct ifnet *ifp) { const node_p node = IFP2NG(ifp); struct priv *priv; if(node == NULL) return; NG_NODE_REALLY_DIE(node); priv = NG_NODE_PRIVATE(node); IFP2NG_SET(priv->ifp, NULL); priv->ifp = NULL; ng_rmnode_self(node); } /* * Shutdown the node. This is called from the shutdown message processing. */ static int ng_atm_shutdown(node_p node) { struct priv *priv = NG_NODE_PRIVATE(node); if (node->nd_flags & NGF_REALLY_DIE) { /* * We are called from unloading the ATM driver. Really, * really need to shutdown this node. The ifp was * already handled in the detach routine. */ NG_NODE_SET_PRIVATE(node, NULL); free(priv, M_NETGRAPH); NG_NODE_UNREF(node); return (0); } #ifdef NGATM_DEBUG if (!allow_shutdown) NG_NODE_REVIVE(node); /* we persist */ else { IFP2NG_SET(priv->ifp, NULL); NG_NODE_SET_PRIVATE(node, NULL); free(priv, M_NETGRAPH); NG_NODE_UNREF(node); } #else /* - * We are persistant - reinitialize + * We are persistent - reinitialize. */ NG_NODE_REVIVE(node); #endif return (0); } /* * Nodes are constructed only via interface attaches. */ static int ng_atm_constructor(node_p nodep) { return (EINVAL); } /************************************************************/ /* * INITIALISATION */ /* * Loading and unloading of node type * * The assignments to the globals for the hooks should be ok without * a special hook. The use pattern is generally: check that the pointer * is not NULL, call the function. In the attach case this is no problem. * In the detach case we can detach only when no ATM node exists. That * means that there is no ATM interface anymore. So we are sure that * we are not in the code path in if_atmsubr.c. To prevent someone * from adding an interface after we have started to unload the node, we * take the iflist lock so an if_attach will be blocked until we are done. * XXX: perhaps the function pointers should be 'volatile' for this to work * properly. */ static int ng_atm_mod_event(module_t mod, int event, void *data) { VNET_ITERATOR_DECL(vnet_iter); struct ifnet *ifp; int error = 0; switch (event) { case MOD_LOAD: /* * Register function hooks */ if (ng_atm_attach_p != NULL) { error = EEXIST; break; } IFNET_RLOCK(); ng_atm_attach_p = ng_atm_attach; ng_atm_detach_p = ng_atm_detach; ng_atm_output_p = ng_atm_output; ng_atm_input_p = ng_atm_input; ng_atm_input_orphan_p = ng_atm_input_orphans; ng_atm_event_p = ng_atm_event; /* Create nodes for existing ATM interfaces */ VNET_LIST_RLOCK(); VNET_FOREACH(vnet_iter) { CURVNET_SET_QUIET(vnet_iter); TAILQ_FOREACH(ifp, &V_ifnet, if_link) { if (ifp->if_type == IFT_ATM) ng_atm_attach(ifp); } CURVNET_RESTORE(); } VNET_LIST_RUNLOCK(); IFNET_RUNLOCK(); break; case MOD_UNLOAD: IFNET_RLOCK(); ng_atm_attach_p = NULL; ng_atm_detach_p = NULL; ng_atm_output_p = NULL; ng_atm_input_p = NULL; ng_atm_input_orphan_p = NULL; ng_atm_event_p = NULL; VNET_LIST_RLOCK(); VNET_FOREACH(vnet_iter) { CURVNET_SET_QUIET(vnet_iter); TAILQ_FOREACH(ifp, &V_ifnet, if_link) { if (ifp->if_type == IFT_ATM) ng_atm_detach(ifp); } CURVNET_RESTORE(); } VNET_LIST_RUNLOCK(); IFNET_RUNLOCK(); break; default: error = EOPNOTSUPP; break; } return (error); } Index: head/sys/netgraph/bluetooth/drivers/ubt/ng_ubt.c =================================================================== --- head/sys/netgraph/bluetooth/drivers/ubt/ng_ubt.c (revision 298812) +++ head/sys/netgraph/bluetooth/drivers/ubt/ng_ubt.c (revision 298813) @@ -1,1875 +1,1875 @@ /* * ng_ubt.c */ /*- * Copyright (c) 2001-2009 Maksim Yevmenkin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id: ng_ubt.c,v 1.16 2003/10/10 19:15:06 max Exp $ * $FreeBSD$ */ /* * NOTE: ng_ubt2 driver has a split personality. On one side it is * a USB device driver and on the other it is a Netgraph node. This * driver will *NOT* create traditional /dev/ enties, only Netgraph * node. * * NOTE ON LOCKS USED: ng_ubt2 drives uses 2 locks (mutexes) * * 1) sc_if_mtx - lock for device's interface #0 and #1. This lock is used * by USB for any USB request going over device's interface #0 and #1, * i.e. interrupt, control, bulk and isoc. transfers. * * 2) sc_ng_mtx - this lock is used to protect shared (between USB, Netgraph * and Taskqueue) data, such as outgoing mbuf queues, task flags and hook * pointer. This lock *SHOULD NOT* be grabbed for a long time. In fact, * think of it as a spin lock. * * NOTE ON LOCKING STRATEGY: ng_ubt2 driver operates in 3 different contexts. * * 1) USB context. This is where all the USB related stuff happens. All * callbacks run in this context. All callbacks are called (by USB) with * appropriate interface lock held. It is (generally) allowed to grab * any additional locks. * * 2) Netgraph context. This is where all the Netgraph related stuff happens. * Since we mark node as WRITER, the Netgraph node will be "locked" (from * Netgraph point of view). Any variable that is only modified from the - * Netgraph context does not require any additonal locking. It is generally + * Netgraph context does not require any additional locking. It is generally * *NOT* allowed to grab *ANY* additional locks. Whatever you do, *DO NOT* * grab any lock in the Netgraph context that could cause de-scheduling of * the Netgraph thread for significant amount of time. In fact, the only * lock that is allowed in the Netgraph context is the sc_ng_mtx lock. * Also make sure that any code that is called from the Netgraph context * follows the rule above. * * 3) Taskqueue context. This is where ubt_task runs. Since we are generally * NOT allowed to grab any lock that could cause de-scheduling in the * Netgraph context, and, USB requires us to grab interface lock before * doing things with transfers, it is safer to transition from the Netgraph * context to the Taskqueue context before we can call into USB subsystem. * * So, to put everything together, the rules are as follows. * It is OK to call from the USB context or the Taskqueue context into * the Netgraph context (i.e. call NG_SEND_xxx functions). In other words * it is allowed to call into the Netgraph context with locks held. * Is it *NOT* OK to call from the Netgraph context into the USB context, * because USB requires us to grab interface locks, and, it is safer to * avoid it. So, to make things safer we set task flags to indicate which * actions we want to perform and schedule ubt_task which would run in the * Taskqueue context. * Is is OK to call from the Taskqueue context into the USB context, * and, ubt_task does just that (i.e. grabs appropriate interface locks * before calling into USB). * Access to the outgoing queues, task flags and hook pointer is * controlled by the sc_ng_mtx lock. It is an unavoidable evil. Again, * sc_ng_mtx should really be a spin lock (and it is very likely to an * equivalent of spin lock due to adaptive nature of FreeBSD mutexes). * All USB callbacks accept softc pointer as a private data. USB ensures * that this pointer is valid. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #include #include #include #define USB_DEBUG_VAR usb_debug #include #include #include #include #include #include #include #include #include #include #include static int ubt_modevent(module_t, int, void *); static device_probe_t ubt_probe; static device_attach_t ubt_attach; static device_detach_t ubt_detach; static void ubt_task_schedule(ubt_softc_p, int); static task_fn_t ubt_task; #define ubt_xfer_start(sc, i) usbd_transfer_start((sc)->sc_xfer[(i)]) /* Netgraph methods */ static ng_constructor_t ng_ubt_constructor; static ng_shutdown_t ng_ubt_shutdown; static ng_newhook_t ng_ubt_newhook; static ng_connect_t ng_ubt_connect; static ng_disconnect_t ng_ubt_disconnect; static ng_rcvmsg_t ng_ubt_rcvmsg; static ng_rcvdata_t ng_ubt_rcvdata; /* Queue length */ static const struct ng_parse_struct_field ng_ubt_node_qlen_type_fields[] = { { "queue", &ng_parse_int32_type, }, { "qlen", &ng_parse_int32_type, }, { NULL, } }; static const struct ng_parse_type ng_ubt_node_qlen_type = { &ng_parse_struct_type, &ng_ubt_node_qlen_type_fields }; /* Stat info */ static const struct ng_parse_struct_field ng_ubt_node_stat_type_fields[] = { { "pckts_recv", &ng_parse_uint32_type, }, { "bytes_recv", &ng_parse_uint32_type, }, { "pckts_sent", &ng_parse_uint32_type, }, { "bytes_sent", &ng_parse_uint32_type, }, { "oerrors", &ng_parse_uint32_type, }, { "ierrors", &ng_parse_uint32_type, }, { NULL, } }; static const struct ng_parse_type ng_ubt_node_stat_type = { &ng_parse_struct_type, &ng_ubt_node_stat_type_fields }; /* Netgraph node command list */ static const struct ng_cmdlist ng_ubt_cmdlist[] = { { NGM_UBT_COOKIE, NGM_UBT_NODE_SET_DEBUG, "set_debug", &ng_parse_uint16_type, NULL }, { NGM_UBT_COOKIE, NGM_UBT_NODE_GET_DEBUG, "get_debug", NULL, &ng_parse_uint16_type }, { NGM_UBT_COOKIE, NGM_UBT_NODE_SET_QLEN, "set_qlen", &ng_ubt_node_qlen_type, NULL }, { NGM_UBT_COOKIE, NGM_UBT_NODE_GET_QLEN, "get_qlen", &ng_ubt_node_qlen_type, &ng_ubt_node_qlen_type }, { NGM_UBT_COOKIE, NGM_UBT_NODE_GET_STAT, "get_stat", NULL, &ng_ubt_node_stat_type }, { NGM_UBT_COOKIE, NGM_UBT_NODE_RESET_STAT, "reset_stat", NULL, NULL }, { 0, } }; /* Netgraph node type */ static struct ng_type typestruct = { .version = NG_ABI_VERSION, .name = NG_UBT_NODE_TYPE, .constructor = ng_ubt_constructor, .rcvmsg = ng_ubt_rcvmsg, .shutdown = ng_ubt_shutdown, .newhook = ng_ubt_newhook, .connect = ng_ubt_connect, .rcvdata = ng_ubt_rcvdata, .disconnect = ng_ubt_disconnect, .cmdlist = ng_ubt_cmdlist }; /**************************************************************************** **************************************************************************** ** USB specific **************************************************************************** ****************************************************************************/ /* USB methods */ static usb_callback_t ubt_ctrl_write_callback; static usb_callback_t ubt_intr_read_callback; static usb_callback_t ubt_bulk_read_callback; static usb_callback_t ubt_bulk_write_callback; static usb_callback_t ubt_isoc_read_callback; static usb_callback_t ubt_isoc_write_callback; static int ubt_fwd_mbuf_up(ubt_softc_p, struct mbuf **); static int ubt_isoc_read_one_frame(struct usb_xfer *, int); /* * USB config * * The following desribes usb transfers that could be submitted on USB device. * * Interface 0 on the USB device must present the following endpoints * 1) Interrupt endpoint to receive HCI events * 2) Bulk IN endpoint to receive ACL data * 3) Bulk OUT endpoint to send ACL data * * Interface 1 on the USB device must present the following endpoints * 1) Isochronous IN endpoint to receive SCO data * 2) Isochronous OUT endpoint to send SCO data */ static const struct usb_config ubt_config[UBT_N_TRANSFER] = { /* * Interface #0 */ /* Outgoing bulk transfer - ACL packets */ [UBT_IF_0_BULK_DT_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .if_index = 0, .bufsize = UBT_BULK_WRITE_BUFFER_SIZE, .flags = { .pipe_bof = 1, .force_short_xfer = 1, }, .callback = &ubt_bulk_write_callback, }, /* Incoming bulk transfer - ACL packets */ [UBT_IF_0_BULK_DT_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .if_index = 0, .bufsize = UBT_BULK_READ_BUFFER_SIZE, .flags = { .pipe_bof = 1, .short_xfer_ok = 1, }, .callback = &ubt_bulk_read_callback, }, /* Incoming interrupt transfer - HCI events */ [UBT_IF_0_INTR_DT_RD] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .if_index = 0, .flags = { .pipe_bof = 1, .short_xfer_ok = 1, }, .bufsize = UBT_INTR_BUFFER_SIZE, .callback = &ubt_intr_read_callback, }, /* Outgoing control transfer - HCI commands */ [UBT_IF_0_CTRL_DT_WR] = { .type = UE_CONTROL, .endpoint = 0x00, /* control pipe */ .direction = UE_DIR_ANY, .if_index = 0, .bufsize = UBT_CTRL_BUFFER_SIZE, .callback = &ubt_ctrl_write_callback, .timeout = 5000, /* 5 seconds */ }, /* * Interface #1 */ /* Incoming isochronous transfer #1 - SCO packets */ [UBT_IF_1_ISOC_DT_RD1] = { .type = UE_ISOCHRONOUS, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .if_index = 1, .bufsize = 0, /* use "wMaxPacketSize * frames" */ .frames = UBT_ISOC_NFRAMES, .flags = { .short_xfer_ok = 1, }, .callback = &ubt_isoc_read_callback, }, /* Incoming isochronous transfer #2 - SCO packets */ [UBT_IF_1_ISOC_DT_RD2] = { .type = UE_ISOCHRONOUS, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .if_index = 1, .bufsize = 0, /* use "wMaxPacketSize * frames" */ .frames = UBT_ISOC_NFRAMES, .flags = { .short_xfer_ok = 1, }, .callback = &ubt_isoc_read_callback, }, /* Outgoing isochronous transfer #1 - SCO packets */ [UBT_IF_1_ISOC_DT_WR1] = { .type = UE_ISOCHRONOUS, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .if_index = 1, .bufsize = 0, /* use "wMaxPacketSize * frames" */ .frames = UBT_ISOC_NFRAMES, .flags = { .short_xfer_ok = 1, }, .callback = &ubt_isoc_write_callback, }, /* Outgoing isochronous transfer #2 - SCO packets */ [UBT_IF_1_ISOC_DT_WR2] = { .type = UE_ISOCHRONOUS, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .if_index = 1, .bufsize = 0, /* use "wMaxPacketSize * frames" */ .frames = UBT_ISOC_NFRAMES, .flags = { .short_xfer_ok = 1, }, .callback = &ubt_isoc_write_callback, }, }; /* * If for some reason device should not be attached then put * VendorID/ProductID pair into the list below. The format is * as follows: * * { USB_VPI(VENDOR_ID, PRODUCT_ID, 0) }, * * where VENDOR_ID and PRODUCT_ID are hex numbers. */ static const STRUCT_USB_HOST_ID ubt_ignore_devs[] = { /* AVM USB Bluetooth-Adapter BlueFritz! v1.0 */ { USB_VPI(USB_VENDOR_AVM, 0x2200, 0) }, /* Atheros 3011 with sflash firmware */ { USB_VPI(0x0cf3, 0x3002, 0) }, { USB_VPI(0x0cf3, 0xe019, 0) }, { USB_VPI(0x13d3, 0x3304, 0) }, { USB_VPI(0x0930, 0x0215, 0) }, { USB_VPI(0x0489, 0xe03d, 0) }, { USB_VPI(0x0489, 0xe027, 0) }, /* Atheros AR9285 Malbec with sflash firmware */ { USB_VPI(0x03f0, 0x311d, 0) }, /* Atheros 3012 with sflash firmware */ { USB_VPI(0x0cf3, 0x3004, 0), USB_DEV_BCD_LTEQ(1) }, { USB_VPI(0x0cf3, 0x311d, 0), USB_DEV_BCD_LTEQ(1) }, { USB_VPI(0x13d3, 0x3375, 0), USB_DEV_BCD_LTEQ(1) }, { USB_VPI(0x04ca, 0x3005, 0), USB_DEV_BCD_LTEQ(1) }, { USB_VPI(0x04ca, 0x3006, 0), USB_DEV_BCD_LTEQ(1) }, { USB_VPI(0x04ca, 0x3008, 0), USB_DEV_BCD_LTEQ(1) }, { USB_VPI(0x13d3, 0x3362, 0), USB_DEV_BCD_LTEQ(1) }, { USB_VPI(0x0cf3, 0xe004, 0), USB_DEV_BCD_LTEQ(1) }, { USB_VPI(0x0930, 0x0219, 0), USB_DEV_BCD_LTEQ(1) }, { USB_VPI(0x0489, 0xe057, 0), USB_DEV_BCD_LTEQ(1) }, { USB_VPI(0x13d3, 0x3393, 0), USB_DEV_BCD_LTEQ(1) }, { USB_VPI(0x0489, 0xe04e, 0), USB_DEV_BCD_LTEQ(1) }, { USB_VPI(0x0489, 0xe056, 0), USB_DEV_BCD_LTEQ(1) }, /* Atheros AR5BBU12 with sflash firmware */ { USB_VPI(0x0489, 0xe02c, 0), USB_DEV_BCD_LTEQ(1) }, /* Atheros AR5BBU12 with sflash firmware */ { USB_VPI(0x0489, 0xe03c, 0), USB_DEV_BCD_LTEQ(1) }, { USB_VPI(0x0489, 0xe036, 0), USB_DEV_BCD_LTEQ(1) }, }; /* List of supported bluetooth devices */ static const STRUCT_USB_HOST_ID ubt_devs[] = { /* Generic Bluetooth class devices */ { USB_IFACE_CLASS(UDCLASS_WIRELESS), USB_IFACE_SUBCLASS(UDSUBCLASS_RF), USB_IFACE_PROTOCOL(UDPROTO_BLUETOOTH) }, /* AVM USB Bluetooth-Adapter BlueFritz! v2.0 */ { USB_VPI(USB_VENDOR_AVM, 0x3800, 0) }, /* Broadcom USB dongles, mostly BCM20702 and BCM20702A0 */ { USB_VENDOR(USB_VENDOR_BROADCOM), USB_IFACE_CLASS(UICLASS_VENDOR), USB_IFACE_SUBCLASS(UDSUBCLASS_RF), USB_IFACE_PROTOCOL(UDPROTO_BLUETOOTH) }, /* Apple-specific (Broadcom) devices */ { USB_VENDOR(USB_VENDOR_APPLE), USB_IFACE_CLASS(UICLASS_VENDOR), USB_IFACE_SUBCLASS(UDSUBCLASS_RF), USB_IFACE_PROTOCOL(UDPROTO_BLUETOOTH) }, /* Foxconn - Hon Hai */ { USB_VENDOR(USB_VENDOR_FOXCONN), USB_IFACE_CLASS(UICLASS_VENDOR), USB_IFACE_SUBCLASS(UDSUBCLASS_RF), USB_IFACE_PROTOCOL(UDPROTO_BLUETOOTH) }, /* MediaTek MT76x0E */ { USB_VPI(USB_VENDOR_MEDIATEK, 0x763f, 0) }, /* Broadcom SoftSailing reporting vendor specific */ { USB_VPI(USB_VENDOR_BROADCOM, 0x21e1, 0) }, /* Apple MacBookPro 7,1 */ { USB_VPI(USB_VENDOR_APPLE, 0x8213, 0) }, /* Apple iMac11,1 */ { USB_VPI(USB_VENDOR_APPLE, 0x8215, 0) }, /* Apple MacBookPro6,2 */ { USB_VPI(USB_VENDOR_APPLE, 0x8218, 0) }, /* Apple MacBookAir3,1, MacBookAir3,2 */ { USB_VPI(USB_VENDOR_APPLE, 0x821b, 0) }, /* Apple MacBookAir4,1 */ { USB_VPI(USB_VENDOR_APPLE, 0x821f, 0) }, /* MacBookAir6,1 */ { USB_VPI(USB_VENDOR_APPLE, 0x828f, 0) }, /* Apple MacBookPro8,2 */ { USB_VPI(USB_VENDOR_APPLE, 0x821a, 0) }, /* Apple MacMini5,1 */ { USB_VPI(USB_VENDOR_APPLE, 0x8281, 0) }, /* Bluetooth Ultraport Module from IBM */ { USB_VPI(USB_VENDOR_TDK, 0x030a, 0) }, /* ALPS Modules with non-standard ID */ { USB_VPI(USB_VENDOR_ALPS, 0x3001, 0) }, { USB_VPI(USB_VENDOR_ALPS, 0x3002, 0) }, { USB_VPI(USB_VENDOR_ERICSSON2, 0x1002, 0) }, /* Canyon CN-BTU1 with HID interfaces */ { USB_VPI(USB_VENDOR_CANYON, 0x0000, 0) }, /* Broadcom BCM20702A0 */ { USB_VPI(USB_VENDOR_ASUS, 0x17b5, 0) }, { USB_VPI(USB_VENDOR_ASUS, 0x17cb, 0) }, { USB_VPI(USB_VENDOR_LITEON, 0x2003, 0) }, { USB_VPI(USB_VENDOR_FOXCONN, 0xe042, 0) }, { USB_VPI(USB_VENDOR_DELL, 0x8197, 0) }, }; /* * Probe for a USB Bluetooth device. * USB context. */ static int ubt_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); int error; if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bIfaceIndex != 0) return (ENXIO); if (usbd_lookup_id_by_uaa(ubt_ignore_devs, sizeof(ubt_ignore_devs), uaa) == 0) return (ENXIO); error = usbd_lookup_id_by_uaa(ubt_devs, sizeof(ubt_devs), uaa); if (error == 0) return (BUS_PROBE_GENERIC); return (error); } /* ubt_probe */ /* * Attach the device. * USB context. */ static int ubt_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct ubt_softc *sc = device_get_softc(dev); struct usb_endpoint_descriptor *ed; struct usb_interface_descriptor *id; struct usb_interface *iface; uint16_t wMaxPacketSize; uint8_t alt_index, i, j; uint8_t iface_index[2] = { 0, 1 }; device_set_usb_desc(dev); sc->sc_dev = dev; sc->sc_debug = NG_UBT_WARN_LEVEL; /* * Create Netgraph node */ if (ng_make_node_common(&typestruct, &sc->sc_node) != 0) { UBT_ALERT(sc, "could not create Netgraph node\n"); return (ENXIO); } /* Name Netgraph node */ if (ng_name_node(sc->sc_node, device_get_nameunit(dev)) != 0) { UBT_ALERT(sc, "could not name Netgraph node\n"); NG_NODE_UNREF(sc->sc_node); return (ENXIO); } NG_NODE_SET_PRIVATE(sc->sc_node, sc); NG_NODE_FORCE_WRITER(sc->sc_node); /* * Initialize device softc structure */ /* initialize locks */ mtx_init(&sc->sc_ng_mtx, "ubt ng", NULL, MTX_DEF); mtx_init(&sc->sc_if_mtx, "ubt if", NULL, MTX_DEF | MTX_RECURSE); /* initialize packet queues */ NG_BT_MBUFQ_INIT(&sc->sc_cmdq, UBT_DEFAULT_QLEN); NG_BT_MBUFQ_INIT(&sc->sc_aclq, UBT_DEFAULT_QLEN); NG_BT_MBUFQ_INIT(&sc->sc_scoq, UBT_DEFAULT_QLEN); /* initialize glue task */ TASK_INIT(&sc->sc_task, 0, ubt_task, sc); /* * Configure Bluetooth USB device. Discover all required USB * interfaces and endpoints. * * USB device must present two interfaces: * 1) Interface 0 that has 3 endpoints * 1) Interrupt endpoint to receive HCI events * 2) Bulk IN endpoint to receive ACL data * 3) Bulk OUT endpoint to send ACL data * * 2) Interface 1 then has 2 endpoints * 1) Isochronous IN endpoint to receive SCO data * 2) Isochronous OUT endpoint to send SCO data * * Interface 1 (with isochronous endpoints) has several alternate * configurations with different packet size. */ /* * For interface #1 search alternate settings, and find * the descriptor with the largest wMaxPacketSize */ wMaxPacketSize = 0; alt_index = 0; i = 0; j = 0; ed = NULL; /* * Search through all the descriptors looking for the largest * packet size: */ while ((ed = (struct usb_endpoint_descriptor *)usb_desc_foreach( usbd_get_config_descriptor(uaa->device), (struct usb_descriptor *)ed))) { if ((ed->bDescriptorType == UDESC_INTERFACE) && (ed->bLength >= sizeof(*id))) { id = (struct usb_interface_descriptor *)ed; i = id->bInterfaceNumber; j = id->bAlternateSetting; } if ((ed->bDescriptorType == UDESC_ENDPOINT) && (ed->bLength >= sizeof(*ed)) && (i == 1)) { uint16_t temp; temp = UGETW(ed->wMaxPacketSize); if (temp > wMaxPacketSize) { wMaxPacketSize = temp; alt_index = j; } } } /* Set alt configuration on interface #1 only if we found it */ if (wMaxPacketSize > 0 && usbd_set_alt_interface_index(uaa->device, 1, alt_index)) { UBT_ALERT(sc, "could not set alternate setting %d " \ "for interface 1!\n", alt_index); goto detach; } /* Setup transfers for both interfaces */ if (usbd_transfer_setup(uaa->device, iface_index, sc->sc_xfer, ubt_config, UBT_N_TRANSFER, sc, &sc->sc_if_mtx)) { UBT_ALERT(sc, "could not allocate transfers\n"); goto detach; } /* Claim all interfaces belonging to the Bluetooth part */ for (i = 1;; i++) { iface = usbd_get_iface(uaa->device, i); if (iface == NULL) break; id = usbd_get_interface_descriptor(iface); if ((id != NULL) && (id->bInterfaceClass == UICLASS_WIRELESS) && (id->bInterfaceSubClass == UISUBCLASS_RF) && (id->bInterfaceProtocol == UIPROTO_BLUETOOTH)) { usbd_set_parent_iface(uaa->device, i, uaa->info.bIfaceIndex); } } return (0); /* success */ detach: ubt_detach(dev); return (ENXIO); } /* ubt_attach */ /* * Detach the device. * USB context. */ int ubt_detach(device_t dev) { struct ubt_softc *sc = device_get_softc(dev); node_p node = sc->sc_node; /* Destroy Netgraph node */ if (node != NULL) { sc->sc_node = NULL; NG_NODE_REALLY_DIE(node); ng_rmnode_self(node); } /* Make sure ubt_task in gone */ taskqueue_drain(taskqueue_swi, &sc->sc_task); /* Free USB transfers, if any */ usbd_transfer_unsetup(sc->sc_xfer, UBT_N_TRANSFER); /* Destroy queues */ UBT_NG_LOCK(sc); NG_BT_MBUFQ_DESTROY(&sc->sc_cmdq); NG_BT_MBUFQ_DESTROY(&sc->sc_aclq); NG_BT_MBUFQ_DESTROY(&sc->sc_scoq); UBT_NG_UNLOCK(sc); mtx_destroy(&sc->sc_if_mtx); mtx_destroy(&sc->sc_ng_mtx); return (0); } /* ubt_detach */ /* * Called when outgoing control request (HCI command) has completed, i.e. * HCI command was sent to the device. * USB context. */ static void ubt_ctrl_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct ubt_softc *sc = usbd_xfer_softc(xfer); struct usb_device_request req; struct mbuf *m; struct usb_page_cache *pc; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: UBT_INFO(sc, "sent %d bytes to control pipe\n", actlen); UBT_STAT_BYTES_SENT(sc, actlen); UBT_STAT_PCKTS_SENT(sc); /* FALLTHROUGH */ case USB_ST_SETUP: send_next: /* Get next command mbuf, if any */ UBT_NG_LOCK(sc); NG_BT_MBUFQ_DEQUEUE(&sc->sc_cmdq, m); UBT_NG_UNLOCK(sc); if (m == NULL) { UBT_INFO(sc, "HCI command queue is empty\n"); break; /* transfer complete */ } /* Initialize a USB control request and then schedule it */ bzero(&req, sizeof(req)); req.bmRequestType = UBT_HCI_REQUEST; USETW(req.wLength, m->m_pkthdr.len); UBT_INFO(sc, "Sending control request, " \ "bmRequestType=0x%02x, wLength=%d\n", req.bmRequestType, UGETW(req.wLength)); pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, &req, sizeof(req)); pc = usbd_xfer_get_frame(xfer, 1); usbd_m_copy_in(pc, 0, m, 0, m->m_pkthdr.len); usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); usbd_xfer_set_frame_len(xfer, 1, m->m_pkthdr.len); usbd_xfer_set_frames(xfer, 2); NG_FREE_M(m); usbd_transfer_submit(xfer); break; default: /* Error */ if (error != USB_ERR_CANCELLED) { UBT_WARN(sc, "control transfer failed: %s\n", usbd_errstr(error)); UBT_STAT_OERROR(sc); goto send_next; } /* transfer cancelled */ break; } } /* ubt_ctrl_write_callback */ /* * Called when incoming interrupt transfer (HCI event) has completed, i.e. * HCI event was received from the device. * USB context. */ static void ubt_intr_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct ubt_softc *sc = usbd_xfer_softc(xfer); struct mbuf *m; ng_hci_event_pkt_t *hdr; struct usb_page_cache *pc; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); m = NULL; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: /* Allocate a new mbuf */ MGETHDR(m, M_NOWAIT, MT_DATA); if (m == NULL) { UBT_STAT_IERROR(sc); goto submit_next; } if (!(MCLGET(m, M_NOWAIT))) { UBT_STAT_IERROR(sc); goto submit_next; } /* Add HCI packet type */ *mtod(m, uint8_t *)= NG_HCI_EVENT_PKT; m->m_pkthdr.len = m->m_len = 1; if (actlen > MCLBYTES - 1) actlen = MCLBYTES - 1; pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, mtod(m, uint8_t *) + 1, actlen); m->m_pkthdr.len += actlen; m->m_len += actlen; UBT_INFO(sc, "got %d bytes from interrupt pipe\n", actlen); /* Validate packet and send it up the stack */ if (m->m_pkthdr.len < (int)sizeof(*hdr)) { UBT_INFO(sc, "HCI event packet is too short\n"); UBT_STAT_IERROR(sc); goto submit_next; } hdr = mtod(m, ng_hci_event_pkt_t *); if (hdr->length != (m->m_pkthdr.len - sizeof(*hdr))) { UBT_ERR(sc, "Invalid HCI event packet size, " \ "length=%d, pktlen=%d\n", hdr->length, m->m_pkthdr.len); UBT_STAT_IERROR(sc); goto submit_next; } UBT_INFO(sc, "got complete HCI event frame, pktlen=%d, " \ "length=%d\n", m->m_pkthdr.len, hdr->length); UBT_STAT_PCKTS_RECV(sc); UBT_STAT_BYTES_RECV(sc, m->m_pkthdr.len); ubt_fwd_mbuf_up(sc, &m); /* m == NULL at this point */ /* FALLTHROUGH */ case USB_ST_SETUP: submit_next: NG_FREE_M(m); /* checks for m != NULL */ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: /* Error */ if (error != USB_ERR_CANCELLED) { UBT_WARN(sc, "interrupt transfer failed: %s\n", usbd_errstr(error)); /* Try to clear stall first */ usbd_xfer_set_stall(xfer); goto submit_next; } /* transfer cancelled */ break; } } /* ubt_intr_read_callback */ /* * Called when incoming bulk transfer (ACL packet) has completed, i.e. * ACL packet was received from the device. * USB context. */ static void ubt_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct ubt_softc *sc = usbd_xfer_softc(xfer); struct mbuf *m; ng_hci_acldata_pkt_t *hdr; struct usb_page_cache *pc; int len; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); m = NULL; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: /* Allocate new mbuf */ MGETHDR(m, M_NOWAIT, MT_DATA); if (m == NULL) { UBT_STAT_IERROR(sc); goto submit_next; } if (!(MCLGET(m, M_NOWAIT))) { UBT_STAT_IERROR(sc); goto submit_next; } /* Add HCI packet type */ *mtod(m, uint8_t *)= NG_HCI_ACL_DATA_PKT; m->m_pkthdr.len = m->m_len = 1; if (actlen > MCLBYTES - 1) actlen = MCLBYTES - 1; pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, mtod(m, uint8_t *) + 1, actlen); m->m_pkthdr.len += actlen; m->m_len += actlen; UBT_INFO(sc, "got %d bytes from bulk-in pipe\n", actlen); /* Validate packet and send it up the stack */ if (m->m_pkthdr.len < (int)sizeof(*hdr)) { UBT_INFO(sc, "HCI ACL packet is too short\n"); UBT_STAT_IERROR(sc); goto submit_next; } hdr = mtod(m, ng_hci_acldata_pkt_t *); len = le16toh(hdr->length); if (len != (int)(m->m_pkthdr.len - sizeof(*hdr))) { UBT_ERR(sc, "Invalid ACL packet size, length=%d, " \ "pktlen=%d\n", len, m->m_pkthdr.len); UBT_STAT_IERROR(sc); goto submit_next; } UBT_INFO(sc, "got complete ACL data packet, pktlen=%d, " \ "length=%d\n", m->m_pkthdr.len, len); UBT_STAT_PCKTS_RECV(sc); UBT_STAT_BYTES_RECV(sc, m->m_pkthdr.len); ubt_fwd_mbuf_up(sc, &m); /* m == NULL at this point */ /* FALLTHOUGH */ case USB_ST_SETUP: submit_next: NG_FREE_M(m); /* checks for m != NULL */ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: /* Error */ if (error != USB_ERR_CANCELLED) { UBT_WARN(sc, "bulk-in transfer failed: %s\n", usbd_errstr(error)); /* Try to clear stall first */ usbd_xfer_set_stall(xfer); goto submit_next; } /* transfer cancelled */ break; } } /* ubt_bulk_read_callback */ /* * Called when outgoing bulk transfer (ACL packet) has completed, i.e. * ACL packet was sent to the device. * USB context. */ static void ubt_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct ubt_softc *sc = usbd_xfer_softc(xfer); struct mbuf *m; struct usb_page_cache *pc; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: UBT_INFO(sc, "sent %d bytes to bulk-out pipe\n", actlen); UBT_STAT_BYTES_SENT(sc, actlen); UBT_STAT_PCKTS_SENT(sc); /* FALLTHROUGH */ case USB_ST_SETUP: send_next: /* Get next mbuf, if any */ UBT_NG_LOCK(sc); NG_BT_MBUFQ_DEQUEUE(&sc->sc_aclq, m); UBT_NG_UNLOCK(sc); if (m == NULL) { UBT_INFO(sc, "ACL data queue is empty\n"); break; /* transfer completed */ } /* * Copy ACL data frame back to a linear USB transfer buffer * and schedule transfer */ pc = usbd_xfer_get_frame(xfer, 0); usbd_m_copy_in(pc, 0, m, 0, m->m_pkthdr.len); usbd_xfer_set_frame_len(xfer, 0, m->m_pkthdr.len); UBT_INFO(sc, "bulk-out transfer has been started, len=%d\n", m->m_pkthdr.len); NG_FREE_M(m); usbd_transfer_submit(xfer); break; default: /* Error */ if (error != USB_ERR_CANCELLED) { UBT_WARN(sc, "bulk-out transfer failed: %s\n", usbd_errstr(error)); UBT_STAT_OERROR(sc); /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto send_next; } /* transfer cancelled */ break; } } /* ubt_bulk_write_callback */ /* * Called when incoming isoc transfer (SCO packet) has completed, i.e. * SCO packet was received from the device. * USB context. */ static void ubt_isoc_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct ubt_softc *sc = usbd_xfer_softc(xfer); int n; int actlen, nframes; usbd_xfer_status(xfer, &actlen, NULL, NULL, &nframes); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: for (n = 0; n < nframes; n ++) if (ubt_isoc_read_one_frame(xfer, n) < 0) break; /* FALLTHROUGH */ case USB_ST_SETUP: read_next: for (n = 0; n < nframes; n ++) usbd_xfer_set_frame_len(xfer, n, usbd_xfer_max_framelen(xfer)); usbd_transfer_submit(xfer); break; default: /* Error */ if (error != USB_ERR_CANCELLED) { UBT_STAT_IERROR(sc); goto read_next; } /* transfer cancelled */ break; } } /* ubt_isoc_read_callback */ /* * Helper function. Called from ubt_isoc_read_callback() to read * SCO data from one frame. * USB context. */ static int ubt_isoc_read_one_frame(struct usb_xfer *xfer, int frame_no) { struct ubt_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; struct mbuf *m; int len, want, got, total; /* Get existing SCO reassembly buffer */ pc = usbd_xfer_get_frame(xfer, 0); m = sc->sc_isoc_in_buffer; total = usbd_xfer_frame_len(xfer, frame_no); /* While we have data in the frame */ while (total > 0) { if (m == NULL) { /* Start new reassembly buffer */ MGETHDR(m, M_NOWAIT, MT_DATA); if (m == NULL) { UBT_STAT_IERROR(sc); return (-1); /* XXX out of sync! */ } if (!(MCLGET(m, M_NOWAIT))) { UBT_STAT_IERROR(sc); NG_FREE_M(m); return (-1); /* XXX out of sync! */ } /* Expect SCO header */ *mtod(m, uint8_t *) = NG_HCI_SCO_DATA_PKT; m->m_pkthdr.len = m->m_len = got = 1; want = sizeof(ng_hci_scodata_pkt_t); } else { /* * Check if we have SCO header and if so * adjust amount of data we want */ got = m->m_pkthdr.len; want = sizeof(ng_hci_scodata_pkt_t); if (got >= want) want += mtod(m, ng_hci_scodata_pkt_t *)->length; } /* Append frame data to the SCO reassembly buffer */ len = total; if (got + len > want) len = want - got; usbd_copy_out(pc, frame_no * usbd_xfer_max_framelen(xfer), mtod(m, uint8_t *) + m->m_pkthdr.len, len); m->m_pkthdr.len += len; m->m_len += len; total -= len; /* Check if we got everything we wanted, if not - continue */ if (got != want) continue; /* If we got here then we got complete SCO frame */ UBT_INFO(sc, "got complete SCO data frame, pktlen=%d, " \ "length=%d\n", m->m_pkthdr.len, mtod(m, ng_hci_scodata_pkt_t *)->length); UBT_STAT_PCKTS_RECV(sc); UBT_STAT_BYTES_RECV(sc, m->m_pkthdr.len); ubt_fwd_mbuf_up(sc, &m); /* m == NULL at this point */ } /* Put SCO reassembly buffer back */ sc->sc_isoc_in_buffer = m; return (0); } /* ubt_isoc_read_one_frame */ /* * Called when outgoing isoc transfer (SCO packet) has completed, i.e. * SCO packet was sent to the device. * USB context. */ static void ubt_isoc_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct ubt_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; struct mbuf *m; int n, space, offset; int actlen, nframes; usbd_xfer_status(xfer, &actlen, NULL, NULL, &nframes); pc = usbd_xfer_get_frame(xfer, 0); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: UBT_INFO(sc, "sent %d bytes to isoc-out pipe\n", actlen); UBT_STAT_BYTES_SENT(sc, actlen); UBT_STAT_PCKTS_SENT(sc); /* FALLTHROUGH */ case USB_ST_SETUP: send_next: offset = 0; space = usbd_xfer_max_framelen(xfer) * nframes; m = NULL; while (space > 0) { if (m == NULL) { UBT_NG_LOCK(sc); NG_BT_MBUFQ_DEQUEUE(&sc->sc_scoq, m); UBT_NG_UNLOCK(sc); if (m == NULL) break; } n = min(space, m->m_pkthdr.len); if (n > 0) { usbd_m_copy_in(pc, offset, m,0, n); m_adj(m, n); offset += n; space -= n; } if (m->m_pkthdr.len == 0) NG_FREE_M(m); /* sets m = NULL */ } /* Put whatever is left from mbuf back on queue */ if (m != NULL) { UBT_NG_LOCK(sc); NG_BT_MBUFQ_PREPEND(&sc->sc_scoq, m); UBT_NG_UNLOCK(sc); } /* * Calculate sizes for isoc frames. * Note that offset could be 0 at this point (i.e. we have * nothing to send). That is fine, as we have isoc. transfers * going in both directions all the time. In this case it * would be just empty isoc. transfer. */ for (n = 0; n < nframes; n ++) { usbd_xfer_set_frame_len(xfer, n, min(offset, usbd_xfer_max_framelen(xfer))); offset -= usbd_xfer_frame_len(xfer, n); } usbd_transfer_submit(xfer); break; default: /* Error */ if (error != USB_ERR_CANCELLED) { UBT_STAT_OERROR(sc); goto send_next; } /* transfer cancelled */ break; } } /* * Utility function to forward provided mbuf upstream (i.e. up the stack). * Modifies value of the mbuf pointer (sets it to NULL). * Save to call from any context. */ static int ubt_fwd_mbuf_up(ubt_softc_p sc, struct mbuf **m) { hook_p hook; int error; /* * Close the race with Netgraph hook newhook/disconnect methods. * Save the hook pointer atomically. Two cases are possible: * * 1) The hook pointer is NULL. It means disconnect method got * there first. In this case we are done. * * 2) The hook pointer is not NULL. It means that hook pointer * could be either in valid or invalid (i.e. in the process * of disconnect) state. In any case grab an extra reference * to protect the hook pointer. * * It is ok to pass hook in invalid state to NG_SEND_DATA_ONLY() as * it checks for it. Drop extra reference after NG_SEND_DATA_ONLY(). */ UBT_NG_LOCK(sc); if ((hook = sc->sc_hook) != NULL) NG_HOOK_REF(hook); UBT_NG_UNLOCK(sc); if (hook == NULL) { NG_FREE_M(*m); return (ENETDOWN); } NG_SEND_DATA_ONLY(error, hook, *m); NG_HOOK_UNREF(hook); if (error != 0) UBT_STAT_IERROR(sc); return (error); } /* ubt_fwd_mbuf_up */ /**************************************************************************** **************************************************************************** ** Glue **************************************************************************** ****************************************************************************/ /* * Schedule glue task. Should be called with sc_ng_mtx held. * Netgraph context. */ static void ubt_task_schedule(ubt_softc_p sc, int action) { mtx_assert(&sc->sc_ng_mtx, MA_OWNED); /* * Try to handle corner case when "start all" and "stop all" * actions can both be set before task is executed. * * The rules are * * sc_task_flags action new sc_task_flags * ------------------------------------------------------ * 0 start start * 0 stop stop * start start start * start stop stop * stop start stop|start * stop stop stop * stop|start start stop|start * stop|start stop stop */ if (action != 0) { if ((action & UBT_FLAG_T_STOP_ALL) != 0) sc->sc_task_flags &= ~UBT_FLAG_T_START_ALL; sc->sc_task_flags |= action; } if (sc->sc_task_flags & UBT_FLAG_T_PENDING) return; if (taskqueue_enqueue(taskqueue_swi, &sc->sc_task) == 0) { sc->sc_task_flags |= UBT_FLAG_T_PENDING; return; } /* XXX: i think this should never happen */ } /* ubt_task_schedule */ /* * Glue task. Examines sc_task_flags and does things depending on it. * Taskqueue context. */ static void ubt_task(void *context, int pending) { ubt_softc_p sc = context; int task_flags, i; UBT_NG_LOCK(sc); task_flags = sc->sc_task_flags; sc->sc_task_flags = 0; UBT_NG_UNLOCK(sc); /* * Stop all USB transfers synchronously. * Stop interface #0 and #1 transfers at the same time and in the * same loop. usbd_transfer_drain() will do appropriate locking. */ if (task_flags & UBT_FLAG_T_STOP_ALL) for (i = 0; i < UBT_N_TRANSFER; i ++) usbd_transfer_drain(sc->sc_xfer[i]); /* Start incoming interrupt and bulk, and all isoc. USB transfers */ if (task_flags & UBT_FLAG_T_START_ALL) { /* * Interface #0 */ mtx_lock(&sc->sc_if_mtx); ubt_xfer_start(sc, UBT_IF_0_INTR_DT_RD); ubt_xfer_start(sc, UBT_IF_0_BULK_DT_RD); /* * Interface #1 * Start both read and write isoc. transfers by default. * Get them going all the time even if we have nothing * to send to avoid any delays. */ ubt_xfer_start(sc, UBT_IF_1_ISOC_DT_RD1); ubt_xfer_start(sc, UBT_IF_1_ISOC_DT_RD2); ubt_xfer_start(sc, UBT_IF_1_ISOC_DT_WR1); ubt_xfer_start(sc, UBT_IF_1_ISOC_DT_WR2); mtx_unlock(&sc->sc_if_mtx); } /* Start outgoing control transfer */ if (task_flags & UBT_FLAG_T_START_CTRL) { mtx_lock(&sc->sc_if_mtx); ubt_xfer_start(sc, UBT_IF_0_CTRL_DT_WR); mtx_unlock(&sc->sc_if_mtx); } /* Start outgoing bulk transfer */ if (task_flags & UBT_FLAG_T_START_BULK) { mtx_lock(&sc->sc_if_mtx); ubt_xfer_start(sc, UBT_IF_0_BULK_DT_WR); mtx_unlock(&sc->sc_if_mtx); } } /* ubt_task */ /**************************************************************************** **************************************************************************** ** Netgraph specific **************************************************************************** ****************************************************************************/ /* * Netgraph node constructor. Do not allow to create node of this type. * Netgraph context. */ static int ng_ubt_constructor(node_p node) { return (EINVAL); } /* ng_ubt_constructor */ /* * Netgraph node destructor. Destroy node only when device has been detached. * Netgraph context. */ static int ng_ubt_shutdown(node_p node) { if (node->nd_flags & NGF_REALLY_DIE) { /* * We came here because the USB device is being - * detached, so stop being persistant. + * detached, so stop being persistent. */ NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(node); } else NG_NODE_REVIVE(node); /* tell ng_rmnode we are persisant */ return (0); } /* ng_ubt_shutdown */ /* * Create new hook. There can only be one. * Netgraph context. */ static int ng_ubt_newhook(node_p node, hook_p hook, char const *name) { struct ubt_softc *sc = NG_NODE_PRIVATE(node); if (strcmp(name, NG_UBT_HOOK) != 0) return (EINVAL); UBT_NG_LOCK(sc); if (sc->sc_hook != NULL) { UBT_NG_UNLOCK(sc); return (EISCONN); } sc->sc_hook = hook; UBT_NG_UNLOCK(sc); return (0); } /* ng_ubt_newhook */ /* * Connect hook. Start incoming USB transfers. * Netgraph context. */ static int ng_ubt_connect(hook_p hook) { struct ubt_softc *sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook)); UBT_NG_LOCK(sc); ubt_task_schedule(sc, UBT_FLAG_T_START_ALL); UBT_NG_UNLOCK(sc); return (0); } /* ng_ubt_connect */ /* * Disconnect hook. * Netgraph context. */ static int ng_ubt_disconnect(hook_p hook) { struct ubt_softc *sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); UBT_NG_LOCK(sc); if (hook != sc->sc_hook) { UBT_NG_UNLOCK(sc); return (EINVAL); } sc->sc_hook = NULL; /* Kick off task to stop all USB xfers */ ubt_task_schedule(sc, UBT_FLAG_T_STOP_ALL); /* Drain queues */ NG_BT_MBUFQ_DRAIN(&sc->sc_cmdq); NG_BT_MBUFQ_DRAIN(&sc->sc_aclq); NG_BT_MBUFQ_DRAIN(&sc->sc_scoq); UBT_NG_UNLOCK(sc); return (0); } /* ng_ubt_disconnect */ /* * Process control message. * Netgraph context. */ static int ng_ubt_rcvmsg(node_p node, item_p item, hook_p lasthook) { struct ubt_softc *sc = NG_NODE_PRIVATE(node); struct ng_mesg *msg, *rsp = NULL; struct ng_bt_mbufq *q; int error = 0, queue, qlen; NGI_GET_MSG(item, msg); switch (msg->header.typecookie) { case NGM_GENERIC_COOKIE: switch (msg->header.cmd) { case NGM_TEXT_STATUS: NG_MKRESPONSE(rsp, msg, NG_TEXTRESPONSE, M_NOWAIT); if (rsp == NULL) { error = ENOMEM; break; } snprintf(rsp->data, NG_TEXTRESPONSE, "Hook: %s\n" \ "Task flags: %#x\n" \ "Debug: %d\n" \ "CMD queue: [have:%d,max:%d]\n" \ "ACL queue: [have:%d,max:%d]\n" \ "SCO queue: [have:%d,max:%d]", (sc->sc_hook != NULL) ? NG_UBT_HOOK : "", sc->sc_task_flags, sc->sc_debug, sc->sc_cmdq.len, sc->sc_cmdq.maxlen, sc->sc_aclq.len, sc->sc_aclq.maxlen, sc->sc_scoq.len, sc->sc_scoq.maxlen); break; default: error = EINVAL; break; } break; case NGM_UBT_COOKIE: switch (msg->header.cmd) { case NGM_UBT_NODE_SET_DEBUG: if (msg->header.arglen != sizeof(ng_ubt_node_debug_ep)){ error = EMSGSIZE; break; } sc->sc_debug = *((ng_ubt_node_debug_ep *) (msg->data)); break; case NGM_UBT_NODE_GET_DEBUG: NG_MKRESPONSE(rsp, msg, sizeof(ng_ubt_node_debug_ep), M_NOWAIT); if (rsp == NULL) { error = ENOMEM; break; } *((ng_ubt_node_debug_ep *) (rsp->data)) = sc->sc_debug; break; case NGM_UBT_NODE_SET_QLEN: if (msg->header.arglen != sizeof(ng_ubt_node_qlen_ep)) { error = EMSGSIZE; break; } queue = ((ng_ubt_node_qlen_ep *) (msg->data))->queue; qlen = ((ng_ubt_node_qlen_ep *) (msg->data))->qlen; switch (queue) { case NGM_UBT_NODE_QUEUE_CMD: q = &sc->sc_cmdq; break; case NGM_UBT_NODE_QUEUE_ACL: q = &sc->sc_aclq; break; case NGM_UBT_NODE_QUEUE_SCO: q = &sc->sc_scoq; break; default: error = EINVAL; goto done; /* NOT REACHED */ } q->maxlen = qlen; break; case NGM_UBT_NODE_GET_QLEN: if (msg->header.arglen != sizeof(ng_ubt_node_qlen_ep)) { error = EMSGSIZE; break; } queue = ((ng_ubt_node_qlen_ep *) (msg->data))->queue; switch (queue) { case NGM_UBT_NODE_QUEUE_CMD: q = &sc->sc_cmdq; break; case NGM_UBT_NODE_QUEUE_ACL: q = &sc->sc_aclq; break; case NGM_UBT_NODE_QUEUE_SCO: q = &sc->sc_scoq; break; default: error = EINVAL; goto done; /* NOT REACHED */ } NG_MKRESPONSE(rsp, msg, sizeof(ng_ubt_node_qlen_ep), M_NOWAIT); if (rsp == NULL) { error = ENOMEM; break; } ((ng_ubt_node_qlen_ep *) (rsp->data))->queue = queue; ((ng_ubt_node_qlen_ep *) (rsp->data))->qlen = q->maxlen; break; case NGM_UBT_NODE_GET_STAT: NG_MKRESPONSE(rsp, msg, sizeof(ng_ubt_node_stat_ep), M_NOWAIT); if (rsp == NULL) { error = ENOMEM; break; } bcopy(&sc->sc_stat, rsp->data, sizeof(ng_ubt_node_stat_ep)); break; case NGM_UBT_NODE_RESET_STAT: UBT_STAT_RESET(sc); break; default: error = EINVAL; break; } break; default: error = EINVAL; break; } done: NG_RESPOND_MSG(error, node, item, rsp); NG_FREE_MSG(msg); return (error); } /* ng_ubt_rcvmsg */ /* * Process data. * Netgraph context. */ static int ng_ubt_rcvdata(hook_p hook, item_p item) { struct ubt_softc *sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); struct mbuf *m; struct ng_bt_mbufq *q; int action, error = 0; if (hook != sc->sc_hook) { error = EINVAL; goto done; } /* Deatch mbuf and get HCI frame type */ NGI_GET_M(item, m); /* * Minimal size of the HCI frame is 4 bytes: 1 byte frame type, * 2 bytes connection handle and at least 1 byte of length. * Panic on data frame that has size smaller than 4 bytes (it * should not happen) */ if (m->m_pkthdr.len < 4) panic("HCI frame size is too small! pktlen=%d\n", m->m_pkthdr.len); /* Process HCI frame */ switch (*mtod(m, uint8_t *)) { /* XXX call m_pullup ? */ case NG_HCI_CMD_PKT: if (m->m_pkthdr.len - 1 > (int)UBT_CTRL_BUFFER_SIZE) panic("HCI command frame size is too big! " \ "buffer size=%zd, packet len=%d\n", UBT_CTRL_BUFFER_SIZE, m->m_pkthdr.len); q = &sc->sc_cmdq; action = UBT_FLAG_T_START_CTRL; break; case NG_HCI_ACL_DATA_PKT: if (m->m_pkthdr.len - 1 > UBT_BULK_WRITE_BUFFER_SIZE) panic("ACL data frame size is too big! " \ "buffer size=%d, packet len=%d\n", UBT_BULK_WRITE_BUFFER_SIZE, m->m_pkthdr.len); q = &sc->sc_aclq; action = UBT_FLAG_T_START_BULK; break; case NG_HCI_SCO_DATA_PKT: q = &sc->sc_scoq; action = 0; break; default: UBT_ERR(sc, "Dropping unsupported HCI frame, type=0x%02x, " \ "pktlen=%d\n", *mtod(m, uint8_t *), m->m_pkthdr.len); NG_FREE_M(m); error = EINVAL; goto done; /* NOT REACHED */ } UBT_NG_LOCK(sc); if (NG_BT_MBUFQ_FULL(q)) { NG_BT_MBUFQ_DROP(q); UBT_NG_UNLOCK(sc); UBT_ERR(sc, "Dropping HCI frame 0x%02x, len=%d. Queue full\n", *mtod(m, uint8_t *), m->m_pkthdr.len); NG_FREE_M(m); } else { /* Loose HCI packet type, enqueue mbuf and kick off task */ m_adj(m, sizeof(uint8_t)); NG_BT_MBUFQ_ENQUEUE(q, m); ubt_task_schedule(sc, action); UBT_NG_UNLOCK(sc); } done: NG_FREE_ITEM(item); return (error); } /* ng_ubt_rcvdata */ /**************************************************************************** **************************************************************************** ** Module **************************************************************************** ****************************************************************************/ /* * Load/Unload the driver module */ static int ubt_modevent(module_t mod, int event, void *data) { int error; switch (event) { case MOD_LOAD: error = ng_newtype(&typestruct); if (error != 0) printf("%s: Could not register Netgraph node type, " \ "error=%d\n", NG_UBT_NODE_TYPE, error); break; case MOD_UNLOAD: error = ng_rmtype(&typestruct); break; default: error = EOPNOTSUPP; break; } return (error); } /* ubt_modevent */ static devclass_t ubt_devclass; static device_method_t ubt_methods[] = { DEVMETHOD(device_probe, ubt_probe), DEVMETHOD(device_attach, ubt_attach), DEVMETHOD(device_detach, ubt_detach), DEVMETHOD_END }; static driver_t ubt_driver = { .name = "ubt", .methods = ubt_methods, .size = sizeof(struct ubt_softc), }; DRIVER_MODULE(ng_ubt, uhub, ubt_driver, ubt_devclass, ubt_modevent, 0); MODULE_VERSION(ng_ubt, NG_BLUETOOTH_VERSION); MODULE_DEPEND(ng_ubt, netgraph, NG_ABI_VERSION, NG_ABI_VERSION, NG_ABI_VERSION); MODULE_DEPEND(ng_ubt, ng_hci, NG_BLUETOOTH_VERSION, NG_BLUETOOTH_VERSION, NG_BLUETOOTH_VERSION); MODULE_DEPEND(ng_ubt, usb, 1, 1, 1); USB_PNP_HOST_INFO(ubt_devs); Index: head/sys/netgraph/bluetooth/hci/ng_hci_cmds.c =================================================================== --- head/sys/netgraph/bluetooth/hci/ng_hci_cmds.c (revision 298812) +++ head/sys/netgraph/bluetooth/hci/ng_hci_cmds.c (revision 298813) @@ -1,1037 +1,1037 @@ /* * ng_hci_cmds.c */ /*- * Copyright (c) Maksim Yevmenkin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id: ng_hci_cmds.c,v 1.4 2003/09/08 18:57:51 max Exp $ * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /****************************************************************************** ****************************************************************************** ** HCI commands processing module ****************************************************************************** ******************************************************************************/ #undef min #define min(a, b) ((a) < (b))? (a) : (b) static int complete_command (ng_hci_unit_p, int, struct mbuf **); static int process_link_control_params (ng_hci_unit_p, u_int16_t, struct mbuf *, struct mbuf *); static int process_link_policy_params (ng_hci_unit_p, u_int16_t, struct mbuf *, struct mbuf *); static int process_hc_baseband_params (ng_hci_unit_p, u_int16_t, struct mbuf *, struct mbuf *); static int process_info_params (ng_hci_unit_p, u_int16_t, struct mbuf *, struct mbuf *); static int process_status_params (ng_hci_unit_p, u_int16_t, struct mbuf *, struct mbuf *); static int process_testing_params (ng_hci_unit_p, u_int16_t, struct mbuf *, struct mbuf *); static int process_le_params (ng_hci_unit_p, u_int16_t, struct mbuf *, struct mbuf *); static int process_link_control_status (ng_hci_unit_p, ng_hci_command_status_ep *, struct mbuf *); static int process_link_policy_status (ng_hci_unit_p, ng_hci_command_status_ep *, struct mbuf *); static int process_le_status (ng_hci_unit_p, ng_hci_command_status_ep *, struct mbuf *); /* * Send HCI command to the driver. */ int ng_hci_send_command(ng_hci_unit_p unit) { struct mbuf *m0 = NULL, *m = NULL; int free, error = 0; /* Check if other command is pending */ if (unit->state & NG_HCI_UNIT_COMMAND_PENDING) return (0); /* Check if unit can accept our command */ NG_HCI_BUFF_CMD_GET(unit->buffer, free); if (free == 0) return (0); /* Check if driver hook is still ok */ if (unit->drv == NULL || NG_HOOK_NOT_VALID(unit->drv)) { NG_HCI_WARN( "%s: %s - hook \"%s\" is not connected or valid\n", __func__, NG_NODE_NAME(unit->node), NG_HCI_HOOK_DRV); NG_BT_MBUFQ_DRAIN(&unit->cmdq); return (ENOTCONN); } /* * Get first command from queue, give it to RAW hook then * make copy of it and send it to the driver */ m0 = NG_BT_MBUFQ_FIRST(&unit->cmdq); if (m0 == NULL) return (0); ng_hci_mtap(unit, m0); m = m_dup(m0, M_NOWAIT); if (m != NULL) NG_SEND_DATA_ONLY(error, unit->drv, m); else error = ENOBUFS; if (error != 0) NG_HCI_ERR( "%s: %s - could not send HCI command, error=%d\n", __func__, NG_NODE_NAME(unit->node), error); /* * Even if we were not able to send command we still pretend * that everything is OK and let timeout handle that. */ NG_HCI_BUFF_CMD_USE(unit->buffer, 1); NG_HCI_STAT_CMD_SENT(unit->stat); NG_HCI_STAT_BYTES_SENT(unit->stat, m0->m_pkthdr.len); /* * Note: ng_hci_command_timeout() will set * NG_HCI_UNIT_COMMAND_PENDING flag */ ng_hci_command_timeout(unit); return (0); } /* ng_hci_send_command */ /* * Process HCI Command_Compete event. Complete HCI command, and do post * processing on the command parameters (cp) and command return parameters * (e) if required (for example adjust state). */ int ng_hci_process_command_complete(ng_hci_unit_p unit, struct mbuf *e) { ng_hci_command_compl_ep *ep = NULL; struct mbuf *cp = NULL; int error = 0; /* Get event packet and update command buffer info */ NG_HCI_M_PULLUP(e, sizeof(*ep)); if (e == NULL) return (ENOBUFS); /* XXX this is bad */ ep = mtod(e, ng_hci_command_compl_ep *); NG_HCI_BUFF_CMD_SET(unit->buffer, ep->num_cmd_pkts); /* Check for special NOOP command */ if (ep->opcode == 0x0000) { NG_FREE_M(e); goto out; } /* Try to match first command item in the queue */ error = complete_command(unit, ep->opcode, &cp); if (error != 0) { NG_FREE_M(e); goto out; } /* * Perform post processing on command parameters and return parameters * do it only if status is OK (status == 0). Status is the first byte * of any command return parameters. */ ep->opcode = le16toh(ep->opcode); m_adj(e, sizeof(*ep)); if (*mtod(e, u_int8_t *) == 0) { /* XXX m_pullup here? */ switch (NG_HCI_OGF(ep->opcode)) { case NG_HCI_OGF_LINK_CONTROL: error = process_link_control_params(unit, NG_HCI_OCF(ep->opcode), cp, e); break; case NG_HCI_OGF_LINK_POLICY: error = process_link_policy_params(unit, NG_HCI_OCF(ep->opcode), cp, e); break; case NG_HCI_OGF_HC_BASEBAND: error = process_hc_baseband_params(unit, NG_HCI_OCF(ep->opcode), cp, e); break; case NG_HCI_OGF_INFO: error = process_info_params(unit, NG_HCI_OCF(ep->opcode), cp, e); break; case NG_HCI_OGF_STATUS: error = process_status_params(unit, NG_HCI_OCF(ep->opcode), cp, e); break; case NG_HCI_OGF_TESTING: error = process_testing_params(unit, NG_HCI_OCF(ep->opcode), cp, e); break; case NG_HCI_OGF_LE: error = process_le_params(unit, NG_HCI_OCF(ep->opcode), cp, e); break; case NG_HCI_OGF_BT_LOGO: case NG_HCI_OGF_VENDOR: NG_FREE_M(cp); NG_FREE_M(e); break; default: NG_FREE_M(cp); NG_FREE_M(e); error = EINVAL; break; } } else { NG_HCI_ERR( "%s: %s - HCI command failed, OGF=%#x, OCF=%#x, status=%#x\n", __func__, NG_NODE_NAME(unit->node), NG_HCI_OGF(ep->opcode), NG_HCI_OCF(ep->opcode), *mtod(e, u_int8_t *)); NG_FREE_M(cp); NG_FREE_M(e); } out: ng_hci_send_command(unit); return (error); } /* ng_hci_process_command_complete */ /* * Process HCI Command_Status event. Check the status (mst) and do post * processing (if required). */ int ng_hci_process_command_status(ng_hci_unit_p unit, struct mbuf *e) { ng_hci_command_status_ep *ep = NULL; struct mbuf *cp = NULL; int error = 0; /* Update command buffer info */ NG_HCI_M_PULLUP(e, sizeof(*ep)); if (e == NULL) return (ENOBUFS); /* XXX this is bad */ ep = mtod(e, ng_hci_command_status_ep *); NG_HCI_BUFF_CMD_SET(unit->buffer, ep->num_cmd_pkts); /* Check for special NOOP command */ if (ep->opcode == 0x0000) goto out; /* Try to match first command item in the queue */ error = complete_command(unit, ep->opcode, &cp); if (error != 0) goto out; /* * Perform post processing on HCI Command_Status event */ ep->opcode = le16toh(ep->opcode); switch (NG_HCI_OGF(ep->opcode)) { case NG_HCI_OGF_LINK_CONTROL: error = process_link_control_status(unit, ep, cp); break; case NG_HCI_OGF_LINK_POLICY: error = process_link_policy_status(unit, ep, cp); break; case NG_HCI_OGF_LE: error = process_le_status(unit, ep, cp); break; case NG_HCI_OGF_BT_LOGO: case NG_HCI_OGF_VENDOR: NG_FREE_M(cp); break; case NG_HCI_OGF_HC_BASEBAND: case NG_HCI_OGF_INFO: case NG_HCI_OGF_STATUS: case NG_HCI_OGF_TESTING: default: NG_FREE_M(cp); error = EINVAL; break; } out: NG_FREE_M(e); ng_hci_send_command(unit); return (error); } /* ng_hci_process_command_status */ /* * Complete queued HCI command. */ static int complete_command(ng_hci_unit_p unit, int opcode, struct mbuf **cp) { struct mbuf *m = NULL; /* Check unit state */ if (!(unit->state & NG_HCI_UNIT_COMMAND_PENDING)) { NG_HCI_ALERT( "%s: %s - no pending command, state=%#x\n", __func__, NG_NODE_NAME(unit->node), unit->state); return (EINVAL); } /* Get first command in the queue */ m = NG_BT_MBUFQ_FIRST(&unit->cmdq); if (m == NULL) { NG_HCI_ALERT( "%s: %s - empty command queue?!\n", __func__, NG_NODE_NAME(unit->node)); return (EINVAL); } /* * Match command opcode, if does not match - do nothing and * let timeout handle that. */ if (mtod(m, ng_hci_cmd_pkt_t *)->opcode != opcode) { NG_HCI_ALERT( "%s: %s - command queue is out of sync\n", __func__, NG_NODE_NAME(unit->node)); return (EINVAL); } /* * Now we can remove command timeout, dequeue completed command * and return command parameters. ng_hci_command_untimeout will * drop NG_HCI_UNIT_COMMAND_PENDING flag. * Note: if ng_hci_command_untimeout() fails (returns non-zero) - * then timeout aready happened and timeout message went info node + * then timeout already happened and timeout message went info node * queue. In this case we ignore command completion and pretend * there is a timeout. */ if (ng_hci_command_untimeout(unit) != 0) return (ETIMEDOUT); NG_BT_MBUFQ_DEQUEUE(&unit->cmdq, *cp); m_adj(*cp, sizeof(ng_hci_cmd_pkt_t)); return (0); } /* complete_command */ /* * Process HCI command timeout */ void ng_hci_process_command_timeout(node_p node, hook_p hook, void *arg1, int arg2) { ng_hci_unit_p unit = NULL; struct mbuf *m = NULL; u_int16_t opcode; if (NG_NODE_NOT_VALID(node)) { printf("%s: Netgraph node is not valid\n", __func__); return; } unit = (ng_hci_unit_p) NG_NODE_PRIVATE(node); if (unit->state & NG_HCI_UNIT_COMMAND_PENDING) { unit->state &= ~NG_HCI_UNIT_COMMAND_PENDING; NG_BT_MBUFQ_DEQUEUE(&unit->cmdq, m); if (m == NULL) { NG_HCI_ALERT( "%s: %s - command queue is out of sync!\n", __func__, NG_NODE_NAME(unit->node)); return; } opcode = le16toh(mtod(m, ng_hci_cmd_pkt_t *)->opcode); NG_FREE_M(m); NG_HCI_ERR( "%s: %s - unable to complete HCI command OGF=%#x, OCF=%#x. Timeout\n", __func__, NG_NODE_NAME(unit->node), NG_HCI_OGF(opcode), NG_HCI_OCF(opcode)); /* Try to send more commands */ NG_HCI_BUFF_CMD_SET(unit->buffer, 1); ng_hci_send_command(unit); } else NG_HCI_ALERT( "%s: %s - no pending command\n", __func__, NG_NODE_NAME(unit->node)); } /* ng_hci_process_command_timeout */ /* * Process link command return parameters */ static int process_link_control_params(ng_hci_unit_p unit, u_int16_t ocf, struct mbuf *mcp, struct mbuf *mrp) { int error = 0; switch (ocf) { case NG_HCI_OCF_INQUIRY_CANCEL: case NG_HCI_OCF_PERIODIC_INQUIRY: case NG_HCI_OCF_EXIT_PERIODIC_INQUIRY: case NG_HCI_OCF_LINK_KEY_REP: case NG_HCI_OCF_LINK_KEY_NEG_REP: case NG_HCI_OCF_PIN_CODE_REP: case NG_HCI_OCF_PIN_CODE_NEG_REP: /* These do not need post processing */ break; case NG_HCI_OCF_INQUIRY: case NG_HCI_OCF_CREATE_CON: case NG_HCI_OCF_DISCON: case NG_HCI_OCF_ADD_SCO_CON: case NG_HCI_OCF_ACCEPT_CON: case NG_HCI_OCF_REJECT_CON: case NG_HCI_OCF_CHANGE_CON_PKT_TYPE: case NG_HCI_OCF_AUTH_REQ: case NG_HCI_OCF_SET_CON_ENCRYPTION: case NG_HCI_OCF_CHANGE_CON_LINK_KEY: case NG_HCI_OCF_MASTER_LINK_KEY: case NG_HCI_OCF_REMOTE_NAME_REQ: case NG_HCI_OCF_READ_REMOTE_FEATURES: case NG_HCI_OCF_READ_REMOTE_VER_INFO: case NG_HCI_OCF_READ_CLOCK_OFFSET: default: /* * None of these command was supposed to generate * Command_Complete event. Instead Command_Status event * should have been generated and then appropriate event * should have been sent to indicate the final result. */ error = EINVAL; break; } NG_FREE_M(mcp); NG_FREE_M(mrp); return (error); } /* process_link_control_params */ /* * Process link policy command return parameters */ static int process_link_policy_params(ng_hci_unit_p unit, u_int16_t ocf, struct mbuf *mcp, struct mbuf *mrp) { int error = 0; switch (ocf){ case NG_HCI_OCF_ROLE_DISCOVERY: { ng_hci_role_discovery_rp *rp = NULL; ng_hci_unit_con_t *con = NULL; u_int16_t h; NG_HCI_M_PULLUP(mrp, sizeof(*rp)); if (mrp != NULL) { rp = mtod(mrp, ng_hci_role_discovery_rp *); h = NG_HCI_CON_HANDLE(le16toh(rp->con_handle)); con = ng_hci_con_by_handle(unit, h); if (con == NULL) { NG_HCI_ALERT( "%s: %s - invalid connection handle=%d\n", __func__, NG_NODE_NAME(unit->node), h); error = ENOENT; } else if (con->link_type != NG_HCI_LINK_ACL) { NG_HCI_ALERT( "%s: %s - invalid link type=%d\n", __func__, NG_NODE_NAME(unit->node), con->link_type); error = EINVAL; } else con->role = rp->role; } else error = ENOBUFS; } break; case NG_HCI_OCF_READ_LINK_POLICY_SETTINGS: case NG_HCI_OCF_WRITE_LINK_POLICY_SETTINGS: /* These do not need post processing */ break; case NG_HCI_OCF_HOLD_MODE: case NG_HCI_OCF_SNIFF_MODE: case NG_HCI_OCF_EXIT_SNIFF_MODE: case NG_HCI_OCF_PARK_MODE: case NG_HCI_OCF_EXIT_PARK_MODE: case NG_HCI_OCF_QOS_SETUP: case NG_HCI_OCF_SWITCH_ROLE: default: /* * None of these command was supposed to generate * Command_Complete event. Instead Command_Status event * should have been generated and then appropriate event * should have been sent to indicate the final result. */ error = EINVAL; break; } NG_FREE_M(mcp); NG_FREE_M(mrp); return (error); } /* process_link_policy_params */ /* * Process HC and baseband command return parameters */ int process_hc_baseband_params(ng_hci_unit_p unit, u_int16_t ocf, struct mbuf *mcp, struct mbuf *mrp) { int error = 0; switch (ocf) { case NG_HCI_OCF_SET_EVENT_MASK: case NG_HCI_OCF_SET_EVENT_FILTER: case NG_HCI_OCF_FLUSH: /* XXX Do we need to handle that? */ case NG_HCI_OCF_READ_PIN_TYPE: case NG_HCI_OCF_WRITE_PIN_TYPE: case NG_HCI_OCF_CREATE_NEW_UNIT_KEY: case NG_HCI_OCF_WRITE_STORED_LINK_KEY: case NG_HCI_OCF_WRITE_CON_ACCEPT_TIMO: case NG_HCI_OCF_WRITE_PAGE_TIMO: case NG_HCI_OCF_READ_SCAN_ENABLE: case NG_HCI_OCF_WRITE_SCAN_ENABLE: case NG_HCI_OCF_WRITE_PAGE_SCAN_ACTIVITY: case NG_HCI_OCF_WRITE_INQUIRY_SCAN_ACTIVITY: case NG_HCI_OCF_READ_AUTH_ENABLE: case NG_HCI_OCF_WRITE_AUTH_ENABLE: case NG_HCI_OCF_READ_ENCRYPTION_MODE: case NG_HCI_OCF_WRITE_ENCRYPTION_MODE: case NG_HCI_OCF_WRITE_VOICE_SETTINGS: case NG_HCI_OCF_READ_NUM_BROADCAST_RETRANS: case NG_HCI_OCF_WRITE_NUM_BROADCAST_RETRANS: case NG_HCI_OCF_READ_HOLD_MODE_ACTIVITY: case NG_HCI_OCF_WRITE_HOLD_MODE_ACTIVITY: case NG_HCI_OCF_READ_SCO_FLOW_CONTROL: case NG_HCI_OCF_WRITE_SCO_FLOW_CONTROL: case NG_HCI_OCF_H2HC_FLOW_CONTROL: /* XXX Not supported this time */ case NG_HCI_OCF_HOST_BUFFER_SIZE: case NG_HCI_OCF_READ_IAC_LAP: case NG_HCI_OCF_WRITE_IAC_LAP: case NG_HCI_OCF_READ_PAGE_SCAN_PERIOD: case NG_HCI_OCF_WRITE_PAGE_SCAN_PERIOD: case NG_HCI_OCF_READ_PAGE_SCAN: case NG_HCI_OCF_WRITE_PAGE_SCAN: case NG_HCI_OCF_READ_LINK_SUPERVISION_TIMO: case NG_HCI_OCF_WRITE_LINK_SUPERVISION_TIMO: case NG_HCI_OCF_READ_SUPPORTED_IAC_NUM: case NG_HCI_OCF_READ_STORED_LINK_KEY: case NG_HCI_OCF_DELETE_STORED_LINK_KEY: case NG_HCI_OCF_READ_CON_ACCEPT_TIMO: case NG_HCI_OCF_READ_PAGE_TIMO: case NG_HCI_OCF_READ_PAGE_SCAN_ACTIVITY: case NG_HCI_OCF_READ_INQUIRY_SCAN_ACTIVITY: case NG_HCI_OCF_READ_VOICE_SETTINGS: case NG_HCI_OCF_READ_AUTO_FLUSH_TIMO: case NG_HCI_OCF_WRITE_AUTO_FLUSH_TIMO: case NG_HCI_OCF_READ_XMIT_LEVEL: case NG_HCI_OCF_HOST_NUM_COMPL_PKTS: /* XXX Can get here? */ case NG_HCI_OCF_CHANGE_LOCAL_NAME: case NG_HCI_OCF_READ_LOCAL_NAME: case NG_HCI_OCF_READ_UNIT_CLASS: case NG_HCI_OCF_WRITE_UNIT_CLASS: case NG_HCI_OCF_READ_LE_HOST_SUPPORTED: case NG_HCI_OCF_WRITE_LE_HOST_SUPPORTED: /* These do not need post processing */ break; case NG_HCI_OCF_RESET: { ng_hci_unit_con_p con = NULL; int size; /* * XXX * * After RESET command unit goes into standby mode * and all operational state is lost. Host controller * will revert to default values for all parameters. * * For now we shall terminate all connections and drop * inited bit. After RESET unit must be re-initialized. */ while (!LIST_EMPTY(&unit->con_list)) { con = LIST_FIRST(&unit->con_list); /* Remove all timeouts (if any) */ if (con->flags & NG_HCI_CON_TIMEOUT_PENDING) ng_hci_con_untimeout(con); /* Connection terminated by local host */ ng_hci_lp_discon_ind(con, 0x16); ng_hci_free_con(con); } NG_HCI_BUFF_ACL_TOTAL(unit->buffer, size); NG_HCI_BUFF_ACL_FREE(unit->buffer, size); NG_HCI_BUFF_SCO_TOTAL(unit->buffer, size); NG_HCI_BUFF_SCO_FREE(unit->buffer, size); unit->state &= ~NG_HCI_UNIT_INITED; } break; default: error = EINVAL; break; } NG_FREE_M(mcp); NG_FREE_M(mrp); return (error); } /* process_hc_baseband_params */ /* * Process info command return parameters */ static int process_info_params(ng_hci_unit_p unit, u_int16_t ocf, struct mbuf *mcp, struct mbuf *mrp) { int error = 0, len; switch (ocf) { case NG_HCI_OCF_READ_LOCAL_VER: case NG_HCI_OCF_READ_COUNTRY_CODE: break; case NG_HCI_OCF_READ_LOCAL_FEATURES: m_adj(mrp, sizeof(u_int8_t)); len = min(mrp->m_pkthdr.len, sizeof(unit->features)); m_copydata(mrp, 0, len, (caddr_t) unit->features); break; case NG_HCI_OCF_READ_BUFFER_SIZE: { ng_hci_read_buffer_size_rp *rp = NULL; /* Do not update buffer descriptor if node was initialized */ if ((unit->state & NG_HCI_UNIT_READY) == NG_HCI_UNIT_READY) break; NG_HCI_M_PULLUP(mrp, sizeof(*rp)); if (mrp != NULL) { rp = mtod(mrp, ng_hci_read_buffer_size_rp *); NG_HCI_BUFF_ACL_SET( unit->buffer, le16toh(rp->num_acl_pkt), /* number */ le16toh(rp->max_acl_size), /* size */ le16toh(rp->num_acl_pkt) /* free */ ); NG_HCI_BUFF_SCO_SET( unit->buffer, le16toh(rp->num_sco_pkt), /* number */ rp->max_sco_size, /* size */ le16toh(rp->num_sco_pkt) /* free */ ); /* Let upper layers know */ ng_hci_node_is_up(unit->node, unit->acl, NULL, 0); ng_hci_node_is_up(unit->node, unit->sco, NULL, 0); } else error = ENOBUFS; } break; case NG_HCI_OCF_READ_BDADDR: /* Do not update BD_ADDR if node was initialized */ if ((unit->state & NG_HCI_UNIT_READY) == NG_HCI_UNIT_READY) break; m_adj(mrp, sizeof(u_int8_t)); len = min(mrp->m_pkthdr.len, sizeof(unit->bdaddr)); m_copydata(mrp, 0, len, (caddr_t) &unit->bdaddr); /* Let upper layers know */ ng_hci_node_is_up(unit->node, unit->acl, NULL, 0); ng_hci_node_is_up(unit->node, unit->sco, NULL, 0); break; default: error = EINVAL; break; } NG_FREE_M(mcp); NG_FREE_M(mrp); return (error); } /* process_info_params */ /* * Process status command return parameters */ static int process_status_params(ng_hci_unit_p unit, u_int16_t ocf, struct mbuf *mcp, struct mbuf *mrp) { int error = 0; switch (ocf) { case NG_HCI_OCF_READ_FAILED_CONTACT_CNTR: case NG_HCI_OCF_RESET_FAILED_CONTACT_CNTR: case NG_HCI_OCF_GET_LINK_QUALITY: case NG_HCI_OCF_READ_RSSI: /* These do not need post processing */ break; default: error = EINVAL; break; } NG_FREE_M(mcp); NG_FREE_M(mrp); return (error); } /* process_status_params */ /* * Process testing command return parameters */ int process_testing_params(ng_hci_unit_p unit, u_int16_t ocf, struct mbuf *mcp, struct mbuf *mrp) { int error = 0; switch (ocf) { /* * XXX FIXME * We do not support these features at this time. However, * HCI node could support this and do something smart. At least * node can change unit state. */ case NG_HCI_OCF_READ_LOOPBACK_MODE: case NG_HCI_OCF_WRITE_LOOPBACK_MODE: case NG_HCI_OCF_ENABLE_UNIT_UNDER_TEST: break; default: error = EINVAL; break; } NG_FREE_M(mcp); NG_FREE_M(mrp); return (error); } /* process_testing_params */ /* * Process LE command return parameters */ static int process_le_params(ng_hci_unit_p unit, u_int16_t ocf, struct mbuf *mcp, struct mbuf *mrp) { int error = 0; switch (ocf){ case NG_HCI_OCF_LE_SET_EVENT_MASK: case NG_HCI_OCF_LE_READ_BUFFER_SIZE: case NG_HCI_OCF_LE_READ_LOCAL_SUPPORTED_FEATURES: case NG_HCI_OCF_LE_SET_RANDOM_ADDRESS: case NG_HCI_OCF_LE_SET_ADVERTISING_PARAMETERS: case NG_HCI_OCF_LE_READ_ADVERTISING_CHANNEL_TX_POWER: case NG_HCI_OCF_LE_SET_ADVERTISING_DATA: case NG_HCI_OCF_LE_SET_SCAN_RESPONSE_DATA: case NG_HCI_OCF_LE_SET_ADVERTISE_ENABLE: case NG_HCI_OCF_LE_SET_SCAN_PARAMETERS: case NG_HCI_OCF_LE_SET_SCAN_ENABLE: case NG_HCI_OCF_LE_CREATE_CONNECTION_CANCEL: case NG_HCI_OCF_LE_CLEAR_WHITE_LIST: case NG_HCI_OCF_LE_READ_WHITE_LIST_SIZE: case NG_HCI_OCF_LE_ADD_DEVICE_TO_WHITE_LIST: case NG_HCI_OCF_LE_REMOVE_DEVICE_FROM_WHITE_LIST: case NG_HCI_OCF_LE_SET_HOST_CHANNEL_CLASSIFICATION: case NG_HCI_OCF_LE_READ_CHANNEL_MAP: case NG_HCI_OCF_LE_ENCRYPT: case NG_HCI_OCF_LE_RAND: case NG_HCI_OCF_LE_LONG_TERM_KEY_REQUEST_REPLY: case NG_HCI_OCF_LE_LONG_TERM_KEY_REQUEST_NEGATIVE_REPLY: case NG_HCI_OCF_LE_READ_SUPPORTED_STATUS: case NG_HCI_OCF_LE_RECEIVER_TEST: case NG_HCI_OCF_LE_TRANSMITTER_TEST: case NG_HCI_OCF_LE_TEST_END: /* These do not need post processing */ break; case NG_HCI_OCF_LE_CREATE_CONNECTION: case NG_HCI_OCF_LE_CONNECTION_UPDATE: case NG_HCI_OCF_LE_READ_REMOTE_USED_FEATURES: case NG_HCI_OCF_LE_START_ENCRYPTION: default: /* * None of these command was supposed to generate * Command_Complete event. Instead Command_Status event * should have been generated and then appropriate event * should have been sent to indicate the final result. */ error = EINVAL; break; } NG_FREE_M(mcp); NG_FREE_M(mrp); return (error); } static int process_le_status(ng_hci_unit_p unit,ng_hci_command_status_ep *ep, struct mbuf *mcp) { int error = 0; switch (NG_HCI_OCF(ep->opcode)){ case NG_HCI_OCF_LE_CREATE_CONNECTION: case NG_HCI_OCF_LE_CONNECTION_UPDATE: case NG_HCI_OCF_LE_READ_REMOTE_USED_FEATURES: case NG_HCI_OCF_LE_START_ENCRYPTION: /* These do not need post processing */ break; case NG_HCI_OCF_LE_SET_EVENT_MASK: case NG_HCI_OCF_LE_READ_BUFFER_SIZE: case NG_HCI_OCF_LE_READ_LOCAL_SUPPORTED_FEATURES: case NG_HCI_OCF_LE_SET_RANDOM_ADDRESS: case NG_HCI_OCF_LE_SET_ADVERTISING_PARAMETERS: case NG_HCI_OCF_LE_READ_ADVERTISING_CHANNEL_TX_POWER: case NG_HCI_OCF_LE_SET_ADVERTISING_DATA: case NG_HCI_OCF_LE_SET_SCAN_RESPONSE_DATA: case NG_HCI_OCF_LE_SET_ADVERTISE_ENABLE: case NG_HCI_OCF_LE_SET_SCAN_PARAMETERS: case NG_HCI_OCF_LE_SET_SCAN_ENABLE: case NG_HCI_OCF_LE_CREATE_CONNECTION_CANCEL: case NG_HCI_OCF_LE_CLEAR_WHITE_LIST: case NG_HCI_OCF_LE_READ_WHITE_LIST_SIZE: case NG_HCI_OCF_LE_ADD_DEVICE_TO_WHITE_LIST: case NG_HCI_OCF_LE_REMOVE_DEVICE_FROM_WHITE_LIST: case NG_HCI_OCF_LE_SET_HOST_CHANNEL_CLASSIFICATION: case NG_HCI_OCF_LE_READ_CHANNEL_MAP: case NG_HCI_OCF_LE_ENCRYPT: case NG_HCI_OCF_LE_RAND: case NG_HCI_OCF_LE_LONG_TERM_KEY_REQUEST_REPLY: case NG_HCI_OCF_LE_LONG_TERM_KEY_REQUEST_NEGATIVE_REPLY: case NG_HCI_OCF_LE_READ_SUPPORTED_STATUS: case NG_HCI_OCF_LE_RECEIVER_TEST: case NG_HCI_OCF_LE_TRANSMITTER_TEST: case NG_HCI_OCF_LE_TEST_END: default: /* * None of these command was supposed to generate * Command_Stutus event. Command Complete instead. */ error = EINVAL; break; } NG_FREE_M(mcp); return (error); } /* * Process link control command status */ static int process_link_control_status(ng_hci_unit_p unit, ng_hci_command_status_ep *ep, struct mbuf *mcp) { int error = 0; switch (NG_HCI_OCF(ep->opcode)) { case NG_HCI_OCF_INQUIRY: case NG_HCI_OCF_DISCON: /* XXX */ case NG_HCI_OCF_REJECT_CON: /* XXX */ case NG_HCI_OCF_CHANGE_CON_PKT_TYPE: case NG_HCI_OCF_AUTH_REQ: case NG_HCI_OCF_SET_CON_ENCRYPTION: case NG_HCI_OCF_CHANGE_CON_LINK_KEY: case NG_HCI_OCF_MASTER_LINK_KEY: case NG_HCI_OCF_REMOTE_NAME_REQ: case NG_HCI_OCF_READ_REMOTE_FEATURES: case NG_HCI_OCF_READ_REMOTE_VER_INFO: case NG_HCI_OCF_READ_CLOCK_OFFSET: /* These do not need post processing */ break; case NG_HCI_OCF_CREATE_CON: break; case NG_HCI_OCF_ADD_SCO_CON: break; case NG_HCI_OCF_ACCEPT_CON: break; case NG_HCI_OCF_INQUIRY_CANCEL: case NG_HCI_OCF_PERIODIC_INQUIRY: case NG_HCI_OCF_EXIT_PERIODIC_INQUIRY: case NG_HCI_OCF_LINK_KEY_REP: case NG_HCI_OCF_LINK_KEY_NEG_REP: case NG_HCI_OCF_PIN_CODE_REP: case NG_HCI_OCF_PIN_CODE_NEG_REP: default: /* * None of these command was supposed to generate * Command_Status event. Instead Command_Complete event * should have been sent. */ error = EINVAL; break; } NG_FREE_M(mcp); return (error); } /* process_link_control_status */ /* * Process link policy command status */ static int process_link_policy_status(ng_hci_unit_p unit, ng_hci_command_status_ep *ep, struct mbuf *mcp) { int error = 0; switch (NG_HCI_OCF(ep->opcode)) { case NG_HCI_OCF_HOLD_MODE: case NG_HCI_OCF_SNIFF_MODE: case NG_HCI_OCF_EXIT_SNIFF_MODE: case NG_HCI_OCF_PARK_MODE: case NG_HCI_OCF_EXIT_PARK_MODE: case NG_HCI_OCF_SWITCH_ROLE: /* These do not need post processing */ break; case NG_HCI_OCF_QOS_SETUP: break; case NG_HCI_OCF_ROLE_DISCOVERY: case NG_HCI_OCF_READ_LINK_POLICY_SETTINGS: case NG_HCI_OCF_WRITE_LINK_POLICY_SETTINGS: default: /* * None of these command was supposed to generate * Command_Status event. Instead Command_Complete event * should have been sent. */ error = EINVAL; break; } NG_FREE_M(mcp); return (error); } /* process_link_policy_status */ Index: head/sys/netgraph/bluetooth/include/ng_btsocket_rfcomm.h =================================================================== --- head/sys/netgraph/bluetooth/include/ng_btsocket_rfcomm.h (revision 298812) +++ head/sys/netgraph/bluetooth/include/ng_btsocket_rfcomm.h (revision 298813) @@ -1,340 +1,340 @@ /* * ng_btsocket_rfcomm.h */ /*- * Copyright (c) 2001-2003 Maksim Yevmenkin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id: ng_btsocket_rfcomm.h,v 1.10 2003/03/29 22:27:42 max Exp $ * $FreeBSD$ */ #ifndef _NETGRAPH_BTSOCKET_RFCOMM_H_ #define _NETGRAPH_BTSOCKET_RFCOMM_H_ /***************************************************************************** ***************************************************************************** ** RFCOMM ** ***************************************************************************** *****************************************************************************/ /* XXX FIXME this does not belong here */ #define RFCOMM_DEFAULT_MTU 667 #define RFCOMM_MAX_MTU 1024 #define RFCOMM_DEFAULT_CREDITS 7 #define RFCOMM_MAX_CREDITS 40 /* RFCOMM frame types */ #define RFCOMM_FRAME_SABM 0x2f #define RFCOMM_FRAME_DISC 0x43 #define RFCOMM_FRAME_UA 0x63 #define RFCOMM_FRAME_DM 0x0f #define RFCOMM_FRAME_UIH 0xef /* RFCOMM MCC commands */ #define RFCOMM_MCC_TEST 0x08 /* Test */ #define RFCOMM_MCC_FCON 0x28 /* Flow Control on */ #define RFCOMM_MCC_FCOFF 0x18 /* Flow Control off */ #define RFCOMM_MCC_MSC 0x38 /* Modem Status Command */ #define RFCOMM_MCC_RPN 0x24 /* Remote Port Negotiation */ #define RFCOMM_MCC_RLS 0x14 /* Remote Line Status */ #define RFCOMM_MCC_PN 0x20 /* Port Negotiation */ #define RFCOMM_MCC_NSC 0x04 /* Non Supported Command */ /* RFCOMM modem signals */ #define RFCOMM_MODEM_FC 0x02 /* Flow Control asserted */ #define RFCOMM_MODEM_RTC 0x04 /* Ready To Communicate */ #define RFCOMM_MODEM_RTR 0x08 /* Ready To Receive */ -#define RFCOMM_MODEM_IC 0x40 /* Incomming Call */ +#define RFCOMM_MODEM_IC 0x40 /* Incoming Call */ #define RFCOMM_MODEM_DV 0x80 /* Data Valid */ /* RPN parameters - baud rate */ #define RFCOMM_RPN_BR_2400 0x0 #define RFCOMM_RPN_BR_4800 0x1 #define RFCOMM_RPN_BR_7200 0x2 #define RFCOMM_RPN_BR_9600 0x3 #define RFCOMM_RPN_BR_19200 0x4 #define RFCOMM_RPN_BR_38400 0x5 #define RFCOMM_RPN_BR_57600 0x6 #define RFCOMM_RPN_BR_115200 0x7 #define RFCOMM_RPN_BR_230400 0x8 /* RPN parameters - data bits */ #define RFCOMM_RPN_DATA_5 0x0 #define RFCOMM_RPN_DATA_6 0x2 #define RFCOMM_RPN_DATA_7 0x1 #define RFCOMM_RPN_DATA_8 0x3 /* RPN parameters - stop bit */ #define RFCOMM_RPN_STOP_1 0 #define RFCOMM_RPN_STOP_15 1 /* RPN parameters - parity */ #define RFCOMM_RPN_PARITY_NONE 0x0 #define RFCOMM_RPN_PARITY_ODD 0x4 #define RFCOMM_RPN_PARITY_EVEN 0x5 #define RFCOMM_RPN_PARITY_MARK 0x6 #define RFCOMM_RPN_PARITY_SPACE 0x7 /* RPN parameters - flow control */ #define RFCOMM_RPN_FLOW_NONE 0x00 #define RFCOMM_RPN_XON_CHAR 0x11 #define RFCOMM_RPN_XOFF_CHAR 0x13 /* RPN parameters - mask */ #define RFCOMM_RPN_PM_BITRATE 0x0001 #define RFCOMM_RPN_PM_DATA 0x0002 #define RFCOMM_RPN_PM_STOP 0x0004 #define RFCOMM_RPN_PM_PARITY 0x0008 #define RFCOMM_RPN_PM_PARITY_TYPE 0x0010 #define RFCOMM_RPN_PM_XON 0x0020 #define RFCOMM_RPN_PM_XOFF 0x0040 #define RFCOMM_RPN_PM_FLOW 0x3F00 #define RFCOMM_RPN_PM_ALL 0x3F7F /* RFCOMM frame header */ struct rfcomm_frame_hdr { u_int8_t address; u_int8_t control; u_int8_t length; /* Actual size could be 2 bytes */ } __attribute__ ((packed)); /* RFCOMM command frame header */ struct rfcomm_cmd_hdr { u_int8_t address; u_int8_t control; u_int8_t length; u_int8_t fcs; } __attribute__ ((packed)); /* RFCOMM MCC command header */ struct rfcomm_mcc_hdr { u_int8_t type; u_int8_t length; /* XXX FIXME Can actual size be 2 bytes?? */ } __attribute__ ((packed)); /* RFCOMM MSC command */ struct rfcomm_mcc_msc { u_int8_t address; u_int8_t modem; } __attribute__ ((packed)); /* RFCOMM RPN command */ struct rfcomm_mcc_rpn { u_int8_t dlci; u_int8_t bit_rate; u_int8_t line_settings; u_int8_t flow_control; u_int8_t xon_char; u_int8_t xoff_char; u_int16_t param_mask; } __attribute__ ((packed)); /* RFCOMM RLS command */ struct rfcomm_mcc_rls { u_int8_t address; u_int8_t status; } __attribute__ ((packed)); /* RFCOMM PN command */ struct rfcomm_mcc_pn { u_int8_t dlci; u_int8_t flow_control; u_int8_t priority; u_int8_t ack_timer; u_int16_t mtu; u_int8_t max_retrans; u_int8_t credits; } __attribute__ ((packed)); /* RFCOMM frame parsing macros */ #define RFCOMM_DLCI(b) (((b) & 0xfc) >> 2) #define RFCOMM_CHANNEL(b) (((b) & 0xf8) >> 3) #define RFCOMM_DIRECTION(b) (((b) & 0x04) >> 2) #define RFCOMM_TYPE(b) (((b) & 0xef)) #define RFCOMM_EA(b) (((b) & 0x01)) #define RFCOMM_CR(b) (((b) & 0x02) >> 1) #define RFCOMM_PF(b) (((b) & 0x10) >> 4) #define RFCOMM_SRVCHANNEL(dlci) ((dlci) >> 1) #define RFCOMM_MKADDRESS(cr, dlci) \ ((((dlci) & 0x3f) << 2) | ((cr) << 1) | 0x01) #define RFCOMM_MKCONTROL(type, pf) ((((type) & 0xef) | ((pf) << 4))) #define RFCOMM_MKDLCI(dir, channel) ((((channel) & 0x1f) << 1) | (dir)) #define RFCOMM_MKLEN8(len) (((len) << 1) | 1) #define RFCOMM_MKLEN16(len) ((len) << 1) /* RFCOMM MCC macros */ #define RFCOMM_MCC_TYPE(b) (((b) & 0xfc) >> 2) #define RFCOMM_MCC_LENGTH(b) (((b) & 0xfe) >> 1) #define RFCOMM_MKMCC_TYPE(cr, type) ((((type) << 2) | ((cr) << 1) | 0x01)) /* RPN macros */ #define RFCOMM_RPN_DATA_BITS(line) ((line) & 0x3) #define RFCOMM_RPN_STOP_BITS(line) (((line) >> 2) & 0x1) #define RFCOMM_RPN_PARITY(line) (((line) >> 3) & 0x3) #define RFCOMM_MKRPN_LINE_SETTINGS(data, stop, parity) \ (((data) & 0x3) | (((stop) & 0x1) << 2) | (((parity) & 0x3) << 3)) /***************************************************************************** ***************************************************************************** ** SOCK_STREAM RFCOMM sockets ** ***************************************************************************** *****************************************************************************/ #define NG_BTSOCKET_RFCOMM_SENDSPACE \ (RFCOMM_MAX_CREDITS * RFCOMM_DEFAULT_MTU * 2) #define NG_BTSOCKET_RFCOMM_RECVSPACE \ (RFCOMM_MAX_CREDITS * RFCOMM_DEFAULT_MTU * 2) /* * Bluetooth RFCOMM session. One L2CAP connection == one RFCOMM session */ struct ng_btsocket_rfcomm_pcb; struct ng_btsocket_rfcomm_session; struct ng_btsocket_rfcomm_session { struct socket *l2so; /* L2CAP socket */ u_int16_t state; /* session state */ #define NG_BTSOCKET_RFCOMM_SESSION_CLOSED 0 #define NG_BTSOCKET_RFCOMM_SESSION_LISTENING 1 #define NG_BTSOCKET_RFCOMM_SESSION_CONNECTING 2 #define NG_BTSOCKET_RFCOMM_SESSION_CONNECTED 3 #define NG_BTSOCKET_RFCOMM_SESSION_OPEN 4 #define NG_BTSOCKET_RFCOMM_SESSION_DISCONNECTING 5 u_int16_t flags; /* session flags */ #define NG_BTSOCKET_RFCOMM_SESSION_INITIATOR (1 << 0) /* initiator */ #define NG_BTSOCKET_RFCOMM_SESSION_LFC (1 << 1) /* local flow */ #define NG_BTSOCKET_RFCOMM_SESSION_RFC (1 << 2) /* remote flow */ #define INITIATOR(s) \ (((s)->flags & NG_BTSOCKET_RFCOMM_SESSION_INITIATOR)? 1 : 0) u_int16_t mtu; /* default MTU */ struct ng_bt_mbufq outq; /* outgoing queue */ struct mtx session_mtx; /* session lock */ LIST_HEAD(, ng_btsocket_rfcomm_pcb) dlcs; /* active DLC */ LIST_ENTRY(ng_btsocket_rfcomm_session) next; /* link to next */ }; typedef struct ng_btsocket_rfcomm_session ng_btsocket_rfcomm_session_t; typedef struct ng_btsocket_rfcomm_session * ng_btsocket_rfcomm_session_p; /* * Bluetooth RFCOMM socket PCB (DLC) */ struct ng_btsocket_rfcomm_pcb { struct socket *so; /* RFCOMM socket */ struct ng_btsocket_rfcomm_session *session; /* RFCOMM session */ u_int16_t flags; /* DLC flags */ #define NG_BTSOCKET_RFCOMM_DLC_TIMO (1 << 0) /* timeout pending */ #define NG_BTSOCKET_RFCOMM_DLC_CFC (1 << 1) /* credit flow ctrl */ -#define NG_BTSOCKET_RFCOMM_DLC_TIMEDOUT (1 << 2) /* timeout happend */ +#define NG_BTSOCKET_RFCOMM_DLC_TIMEDOUT (1 << 2) /* timeout happened */ #define NG_BTSOCKET_RFCOMM_DLC_DETACHED (1 << 3) /* DLC detached */ #define NG_BTSOCKET_RFCOMM_DLC_SENDING (1 << 4) /* send pending */ u_int16_t state; /* DLC state */ #define NG_BTSOCKET_RFCOMM_DLC_CLOSED 0 #define NG_BTSOCKET_RFCOMM_DLC_W4_CONNECT 1 #define NG_BTSOCKET_RFCOMM_DLC_CONFIGURING 2 #define NG_BTSOCKET_RFCOMM_DLC_CONNECTING 3 #define NG_BTSOCKET_RFCOMM_DLC_CONNECTED 4 #define NG_BTSOCKET_RFCOMM_DLC_DISCONNECTING 5 bdaddr_t src; /* source address */ bdaddr_t dst; /* dest. address */ u_int8_t channel; /* RFCOMM channel */ u_int8_t dlci; /* RFCOMM DLCI */ u_int8_t lmodem; /* local mdm signls */ u_int8_t rmodem; /* remote -/- */ u_int16_t mtu; /* MTU */ int16_t rx_cred; /* RX credits */ int16_t tx_cred; /* TX credits */ struct mtx pcb_mtx; /* PCB lock */ struct callout timo; /* timeout */ LIST_ENTRY(ng_btsocket_rfcomm_pcb) session_next;/* link to next */ LIST_ENTRY(ng_btsocket_rfcomm_pcb) next; /* link to next */ }; typedef struct ng_btsocket_rfcomm_pcb ng_btsocket_rfcomm_pcb_t; typedef struct ng_btsocket_rfcomm_pcb * ng_btsocket_rfcomm_pcb_p; #define so2rfcomm_pcb(so) \ ((struct ng_btsocket_rfcomm_pcb *)((so)->so_pcb)) /* * Bluetooth RFCOMM socket methods */ #ifdef _KERNEL void ng_btsocket_rfcomm_init (void); void ng_btsocket_rfcomm_abort (struct socket *); void ng_btsocket_rfcomm_close (struct socket *); int ng_btsocket_rfcomm_accept (struct socket *, struct sockaddr **); int ng_btsocket_rfcomm_attach (struct socket *, int, struct thread *); int ng_btsocket_rfcomm_bind (struct socket *, struct sockaddr *, struct thread *); int ng_btsocket_rfcomm_connect (struct socket *, struct sockaddr *, struct thread *); int ng_btsocket_rfcomm_control (struct socket *, u_long, caddr_t, struct ifnet *, struct thread *); int ng_btsocket_rfcomm_ctloutput (struct socket *, struct sockopt *); void ng_btsocket_rfcomm_detach (struct socket *); int ng_btsocket_rfcomm_disconnect (struct socket *); int ng_btsocket_rfcomm_listen (struct socket *, int, struct thread *); int ng_btsocket_rfcomm_peeraddr (struct socket *, struct sockaddr **); int ng_btsocket_rfcomm_send (struct socket *, int, struct mbuf *, struct sockaddr *, struct mbuf *, struct thread *); int ng_btsocket_rfcomm_sockaddr (struct socket *, struct sockaddr **); #endif /* _KERNEL */ #endif /* _NETGRAPH_BTSOCKET_RFCOMM_H_ */ Index: head/sys/netgraph/bluetooth/include/ng_hci.h =================================================================== --- head/sys/netgraph/bluetooth/include/ng_hci.h (revision 298812) +++ head/sys/netgraph/bluetooth/include/ng_hci.h (revision 298813) @@ -1,1989 +1,1989 @@ /* * ng_hci.h */ /*- * Copyright (c) 2001 Maksim Yevmenkin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id: ng_hci.h,v 1.2 2003/03/18 00:09:37 max Exp $ * $FreeBSD$ */ /* * This file contains everything that application needs to know about * Host Controller Interface (HCI). All information was obtained from * Bluetooth Specification Book v1.1. * * This file can be included by both kernel and userland applications. * * NOTE: Here and after Bluetooth device is called a "unit". Bluetooth * specification refers to both devices and units. They are the * same thing (i think), so to be consistent word "unit" will be * used. */ #ifndef _NETGRAPH_HCI_H_ #define _NETGRAPH_HCI_H_ /************************************************************************** ************************************************************************** ** Netgraph node hook name, type name and type cookie and commands ************************************************************************** **************************************************************************/ /* Node type name and type cookie */ #define NG_HCI_NODE_TYPE "hci" #define NGM_HCI_COOKIE 1000774184 /* Netgraph node hook names */ #define NG_HCI_HOOK_DRV "drv" /* Driver <-> HCI */ #define NG_HCI_HOOK_ACL "acl" /* HCI <-> Upper */ #define NG_HCI_HOOK_SCO "sco" /* HCI <-> Upper */ #define NG_HCI_HOOK_RAW "raw" /* HCI <-> Upper */ /************************************************************************** ************************************************************************** ** Common defines and types (HCI) ************************************************************************** **************************************************************************/ /* All sizes are in bytes */ #define NG_HCI_BDADDR_SIZE 6 /* unit address */ #define NG_HCI_LAP_SIZE 3 /* unit LAP */ #define NG_HCI_KEY_SIZE 16 /* link key */ #define NG_HCI_PIN_SIZE 16 /* link PIN */ #define NG_HCI_EVENT_MASK_SIZE 8 /* event mask */ #define NG_HCI_LE_EVENT_MASK_SIZE 8 /* event mask */ #define NG_HCI_CLASS_SIZE 3 /* unit class */ #define NG_HCI_FEATURES_SIZE 8 /* LMP features */ #define NG_HCI_UNIT_NAME_SIZE 248 /* unit name size */ #define NG_HCI_COMMANDS_SIZE 64 /*Command list BMP size*/ /* HCI specification */ #define NG_HCI_SPEC_V10 0x00 /* v1.0 */ #define NG_HCI_SPEC_V11 0x01 /* v1.1 */ /* 0x02 - 0xFF - reserved for future use */ /* LMP features */ /* ------------------- byte 0 --------------------*/ #define NG_HCI_LMP_3SLOT 0x01 #define NG_HCI_LMP_5SLOT 0x02 #define NG_HCI_LMP_ENCRYPTION 0x04 #define NG_HCI_LMP_SLOT_OFFSET 0x08 #define NG_HCI_LMP_TIMING_ACCURACY 0x10 #define NG_HCI_LMP_SWITCH 0x20 #define NG_HCI_LMP_HOLD_MODE 0x40 #define NG_HCI_LMP_SNIFF_MODE 0x80 /* ------------------- byte 1 --------------------*/ #define NG_HCI_LMP_PARK_MODE 0x01 #define NG_HCI_LMP_RSSI 0x02 #define NG_HCI_LMP_CHANNEL_QUALITY 0x04 #define NG_HCI_LMP_SCO_LINK 0x08 #define NG_HCI_LMP_HV2_PKT 0x10 #define NG_HCI_LMP_HV3_PKT 0x20 #define NG_HCI_LMP_ULAW_LOG 0x40 #define NG_HCI_LMP_ALAW_LOG 0x80 /* ------------------- byte 2 --------------------*/ #define NG_HCI_LMP_CVSD 0x01 #define NG_HCI_LMP_PAGING_SCHEME 0x02 #define NG_HCI_LMP_POWER_CONTROL 0x04 #define NG_HCI_LMP_TRANSPARENT_SCO 0x08 #define NG_HCI_LMP_FLOW_CONTROL_LAG0 0x10 #define NG_HCI_LMP_FLOW_CONTROL_LAG1 0x20 #define NG_HCI_LMP_FLOW_CONTROL_LAG2 0x40 /* Link types */ #define NG_HCI_LINK_SCO 0x00 /* Voice */ #define NG_HCI_LINK_ACL 0x01 /* Data */ #define NG_HCI_LINK_LE_PUBLIC 0x02 /* LE Public*/ #define NG_HCI_LINK_LE_RANDOM 0x03 /* LE Random*/ /* 0x02 - 0xFF - reserved for future use */ /* Packet types */ /* 0x0001 - 0x0004 - reserved for future use */ #define NG_HCI_PKT_DM1 0x0008 /* ACL link */ #define NG_HCI_PKT_DH1 0x0010 /* ACL link */ #define NG_HCI_PKT_HV1 0x0020 /* SCO link */ #define NG_HCI_PKT_HV2 0x0040 /* SCO link */ #define NG_HCI_PKT_HV3 0x0080 /* SCO link */ /* 0x0100 - 0x0200 - reserved for future use */ #define NG_HCI_PKT_DM3 0x0400 /* ACL link */ #define NG_HCI_PKT_DH3 0x0800 /* ACL link */ /* 0x1000 - 0x2000 - reserved for future use */ #define NG_HCI_PKT_DM5 0x4000 /* ACL link */ #define NG_HCI_PKT_DH5 0x8000 /* ACL link */ /* * Connection modes/Unit modes * * This is confusing. It means that one of the units change its mode * for the specific connection. For example one connection was put on * hold (but i could be wrong :) */ #define NG_HCI_UNIT_MODE_ACTIVE 0x00 #define NG_HCI_UNIT_MODE_HOLD 0x01 #define NG_HCI_UNIT_MODE_SNIFF 0x02 #define NG_HCI_UNIT_MODE_PARK 0x03 /* 0x04 - 0xFF - reserved for future use */ /* Page scan modes */ #define NG_HCI_MANDATORY_PAGE_SCAN_MODE 0x00 #define NG_HCI_OPTIONAL_PAGE_SCAN_MODE1 0x01 #define NG_HCI_OPTIONAL_PAGE_SCAN_MODE2 0x02 #define NG_HCI_OPTIONAL_PAGE_SCAN_MODE3 0x03 /* 0x04 - 0xFF - reserved for future use */ /* Page scan repetition modes */ #define NG_HCI_SCAN_REP_MODE0 0x00 #define NG_HCI_SCAN_REP_MODE1 0x01 #define NG_HCI_SCAN_REP_MODE2 0x02 /* 0x03 - 0xFF - reserved for future use */ /* Page scan period modes */ #define NG_HCI_PAGE_SCAN_PERIOD_MODE0 0x00 #define NG_HCI_PAGE_SCAN_PERIOD_MODE1 0x01 #define NG_HCI_PAGE_SCAN_PERIOD_MODE2 0x02 /* 0x03 - 0xFF - reserved for future use */ /* Scan enable */ #define NG_HCI_NO_SCAN_ENABLE 0x00 #define NG_HCI_INQUIRY_ENABLE_PAGE_DISABLE 0x01 #define NG_HCI_INQUIRY_DISABLE_PAGE_ENABLE 0x02 #define NG_HCI_INQUIRY_ENABLE_PAGE_ENABLE 0x03 /* 0x04 - 0xFF - reserved for future use */ /* Hold mode activities */ #define NG_HCI_HOLD_MODE_NO_CHANGE 0x00 #define NG_HCI_HOLD_MODE_SUSPEND_PAGE_SCAN 0x01 #define NG_HCI_HOLD_MODE_SUSPEND_INQUIRY_SCAN 0x02 #define NG_HCI_HOLD_MODE_SUSPEND_PERIOD_INQUIRY 0x04 /* 0x08 - 0x80 - reserved for future use */ /* Connection roles */ #define NG_HCI_ROLE_MASTER 0x00 #define NG_HCI_ROLE_SLAVE 0x01 /* 0x02 - 0xFF - reserved for future use */ /* Key flags */ #define NG_HCI_USE_SEMI_PERMANENT_LINK_KEYS 0x00 #define NG_HCI_USE_TEMPORARY_LINK_KEY 0x01 /* 0x02 - 0xFF - reserved for future use */ /* Pin types */ #define NG_HCI_PIN_TYPE_VARIABLE 0x00 #define NG_HCI_PIN_TYPE_FIXED 0x01 /* Link key types */ #define NG_HCI_LINK_KEY_TYPE_COMBINATION_KEY 0x00 #define NG_HCI_LINK_KEY_TYPE_LOCAL_UNIT_KEY 0x01 #define NG_HCI_LINK_KEY_TYPE_REMOTE_UNIT_KEY 0x02 /* 0x03 - 0xFF - reserved for future use */ /* Encryption modes */ #define NG_HCI_ENCRYPTION_MODE_NONE 0x00 #define NG_HCI_ENCRYPTION_MODE_P2P 0x01 #define NG_HCI_ENCRYPTION_MODE_ALL 0x02 /* 0x03 - 0xFF - reserved for future use */ /* Quality of service types */ #define NG_HCI_SERVICE_TYPE_NO_TRAFFIC 0x00 #define NG_HCI_SERVICE_TYPE_BEST_EFFORT 0x01 #define NG_HCI_SERVICE_TYPE_GUARANTEED 0x02 /* 0x03 - 0xFF - reserved for future use */ /* Link policy settings */ #define NG_HCI_LINK_POLICY_DISABLE_ALL_LM_MODES 0x0000 #define NG_HCI_LINK_POLICY_ENABLE_ROLE_SWITCH 0x0001 /* Master/Slave switch */ #define NG_HCI_LINK_POLICY_ENABLE_HOLD_MODE 0x0002 #define NG_HCI_LINK_POLICY_ENABLE_SNIFF_MODE 0x0004 #define NG_HCI_LINK_POLICY_ENABLE_PARK_MODE 0x0008 /* 0x0010 - 0x8000 - reserved for future use */ /* Event masks */ #define NG_HCI_EVMSK_ALL 0x00000000ffffffff #define NG_HCI_EVMSK_NONE 0x0000000000000000 #define NG_HCI_EVMSK_INQUIRY_COMPL 0x0000000000000001 #define NG_HCI_EVMSK_INQUIRY_RESULT 0x0000000000000002 #define NG_HCI_EVMSK_CON_COMPL 0x0000000000000004 #define NG_HCI_EVMSK_CON_REQ 0x0000000000000008 #define NG_HCI_EVMSK_DISCON_COMPL 0x0000000000000010 #define NG_HCI_EVMSK_AUTH_COMPL 0x0000000000000020 #define NG_HCI_EVMSK_REMOTE_NAME_REQ_COMPL 0x0000000000000040 #define NG_HCI_EVMSK_ENCRYPTION_CHANGE 0x0000000000000080 #define NG_HCI_EVMSK_CHANGE_CON_LINK_KEY_COMPL 0x0000000000000100 #define NG_HCI_EVMSK_MASTER_LINK_KEY_COMPL 0x0000000000000200 #define NG_HCI_EVMSK_READ_REMOTE_FEATURES_COMPL 0x0000000000000400 #define NG_HCI_EVMSK_READ_REMOTE_VER_INFO_COMPL 0x0000000000000800 #define NG_HCI_EVMSK_QOS_SETUP_COMPL 0x0000000000001000 #define NG_HCI_EVMSK_COMMAND_COMPL 0x0000000000002000 #define NG_HCI_EVMSK_COMMAND_STATUS 0x0000000000004000 #define NG_HCI_EVMSK_HARDWARE_ERROR 0x0000000000008000 #define NG_HCI_EVMSK_FLUSH_OCCUR 0x0000000000010000 #define NG_HCI_EVMSK_ROLE_CHANGE 0x0000000000020000 #define NG_HCI_EVMSK_NUM_COMPL_PKTS 0x0000000000040000 #define NG_HCI_EVMSK_MODE_CHANGE 0x0000000000080000 #define NG_HCI_EVMSK_RETURN_LINK_KEYS 0x0000000000100000 #define NG_HCI_EVMSK_PIN_CODE_REQ 0x0000000000200000 #define NG_HCI_EVMSK_LINK_KEY_REQ 0x0000000000400000 #define NG_HCI_EVMSK_LINK_KEY_NOTIFICATION 0x0000000000800000 #define NG_HCI_EVMSK_LOOPBACK_COMMAND 0x0000000001000000 #define NG_HCI_EVMSK_DATA_BUFFER_OVERFLOW 0x0000000002000000 #define NG_HCI_EVMSK_MAX_SLOT_CHANGE 0x0000000004000000 #define NG_HCI_EVMSK_READ_CLOCK_OFFSET_COMLETE 0x0000000008000000 #define NG_HCI_EVMSK_CON_PKT_TYPE_CHANGED 0x0000000010000000 #define NG_HCI_EVMSK_QOS_VIOLATION 0x0000000020000000 #define NG_HCI_EVMSK_PAGE_SCAN_MODE_CHANGE 0x0000000040000000 #define NG_HCI_EVMSK_PAGE_SCAN_REP_MODE_CHANGE 0x0000000080000000 /* 0x0000000100000000 - 0x8000000000000000 - reserved for future use */ /* Filter types */ #define NG_HCI_FILTER_TYPE_NONE 0x00 #define NG_HCI_FILTER_TYPE_INQUIRY_RESULT 0x01 #define NG_HCI_FILTER_TYPE_CON_SETUP 0x02 /* 0x03 - 0xFF - reserved for future use */ /* Filter condition types for NG_HCI_FILTER_TYPE_INQUIRY_RESULT */ #define NG_HCI_FILTER_COND_INQUIRY_NEW_UNIT 0x00 #define NG_HCI_FILTER_COND_INQUIRY_UNIT_CLASS 0x01 #define NG_HCI_FILTER_COND_INQUIRY_BDADDR 0x02 /* 0x03 - 0xFF - reserved for future use */ /* Filter condition types for NG_HCI_FILTER_TYPE_CON_SETUP */ #define NG_HCI_FILTER_COND_CON_ANY_UNIT 0x00 #define NG_HCI_FILTER_COND_CON_UNIT_CLASS 0x01 #define NG_HCI_FILTER_COND_CON_BDADDR 0x02 /* 0x03 - 0xFF - reserved for future use */ /* Xmit level types */ #define NG_HCI_XMIT_LEVEL_CURRENT 0x00 #define NG_HCI_XMIT_LEVEL_MAXIMUM 0x01 /* 0x02 - 0xFF - reserved for future use */ /* Host to Host Controller flow control */ #define NG_HCI_H2HC_FLOW_CONTROL_NONE 0x00 #define NG_HCI_H2HC_FLOW_CONTROL_ACL 0x01 #define NG_HCI_H2HC_FLOW_CONTROL_SCO 0x02 #define NG_HCI_H2HC_FLOW_CONTROL_BOTH 0x03 /* ACL and SCO */ /* 0x04 - 0xFF - reserved future use */ /* Country codes */ #define NG_HCI_COUNTRY_CODE_NAM_EUR_JP 0x00 #define NG_HCI_COUNTRY_CODE_FRANCE 0x01 /* 0x02 - 0xFF - reserved future use */ /* Loopback modes */ #define NG_HCI_LOOPBACK_NONE 0x00 #define NG_HCI_LOOPBACK_LOCAL 0x01 #define NG_HCI_LOOPBACK_REMOTE 0x02 /* 0x03 - 0xFF - reserved future use */ /************************************************************************** ************************************************************************** ** Link level defines, headers and types ************************************************************************** **************************************************************************/ /* * Macro(s) to combine OpCode and extract OGF (OpCode Group Field) * and OCF (OpCode Command Field) from OpCode. */ #define NG_HCI_OPCODE(gf,cf) ((((gf) & 0x3f) << 10) | ((cf) & 0x3ff)) #define NG_HCI_OCF(op) ((op) & 0x3ff) #define NG_HCI_OGF(op) (((op) >> 10) & 0x3f) /* * Marco(s) to extract/combine connection handle, BC (Broadcast) and * PB (Packet boundary) flags. */ #define NG_HCI_CON_HANDLE(h) ((h) & 0x0fff) #define NG_HCI_PB_FLAG(h) (((h) & 0x3000) >> 12) #define NG_HCI_BC_FLAG(h) (((h) & 0xc000) >> 14) #define NG_HCI_MK_CON_HANDLE(h, pb, bc) \ (((h) & 0x0fff) | (((pb) & 3) << 12) | (((bc) & 3) << 14)) /* PB flag values */ /* 00 - reserved for future use */ #define NG_HCI_PACKET_FRAGMENT 0x1 #define NG_HCI_PACKET_START 0x2 /* 11 - reserved for future use */ /* BC flag values */ #define NG_HCI_POINT2POINT 0x0 /* only Host controller to Host */ #define NG_HCI_BROADCAST_ACTIVE 0x1 /* both directions */ #define NG_HCI_BROADCAST_PICONET 0x2 /* both directions */ /* 11 - reserved for future use */ /* HCI command packet header */ #define NG_HCI_CMD_PKT 0x01 #define NG_HCI_CMD_PKT_SIZE 0xff /* without header */ typedef struct { u_int8_t type; /* MUST be 0x1 */ u_int16_t opcode; /* OpCode */ u_int8_t length; /* parameter(s) length in bytes */ } __attribute__ ((packed)) ng_hci_cmd_pkt_t; /* ACL data packet header */ #define NG_HCI_ACL_DATA_PKT 0x02 #define NG_HCI_ACL_PKT_SIZE 0xffff /* without header */ typedef struct { u_int8_t type; /* MUST be 0x2 */ u_int16_t con_handle; /* connection handle + PB + BC flags */ u_int16_t length; /* payload length in bytes */ } __attribute__ ((packed)) ng_hci_acldata_pkt_t; /* SCO data packet header */ #define NG_HCI_SCO_DATA_PKT 0x03 #define NG_HCI_SCO_PKT_SIZE 0xff /* without header */ typedef struct { u_int8_t type; /* MUST be 0x3 */ u_int16_t con_handle; /* connection handle + reserved bits */ u_int8_t length; /* payload length in bytes */ } __attribute__ ((packed)) ng_hci_scodata_pkt_t; /* HCI event packet header */ #define NG_HCI_EVENT_PKT 0x04 #define NG_HCI_EVENT_PKT_SIZE 0xff /* without header */ typedef struct { u_int8_t type; /* MUST be 0x4 */ u_int8_t event; /* event */ u_int8_t length; /* parameter(s) length in bytes */ } __attribute__ ((packed)) ng_hci_event_pkt_t; /* Bluetooth unit address */ typedef struct { u_int8_t b[NG_HCI_BDADDR_SIZE]; } __attribute__ ((packed)) bdaddr_t; typedef bdaddr_t * bdaddr_p; /* Any BD_ADDR. Note: This is actually 7 bytes (count '\0' terminator) */ #define NG_HCI_BDADDR_ANY ((bdaddr_p) "\000\000\000\000\000\000") /* HCI status return parameter */ typedef struct { u_int8_t status; /* 0x00 - success */ } __attribute__ ((packed)) ng_hci_status_rp; /************************************************************************** ************************************************************************** ** Upper layer protocol interface. LP_xxx event parameters ************************************************************************** **************************************************************************/ /* Connection Request Event */ #define NGM_HCI_LP_CON_REQ 1 /* Upper -> HCI */ typedef struct { u_int16_t link_type; /* type of connection */ bdaddr_t bdaddr; /* remote unit address */ } ng_hci_lp_con_req_ep; /* * XXX XXX XXX * * NOTE: This request is not defined by Bluetooth specification, * but i find it useful :) */ #define NGM_HCI_LP_DISCON_REQ 2 /* Upper -> HCI */ typedef struct { u_int16_t con_handle; /* connection handle */ u_int16_t reason; /* reason to disconnect (only low byte) */ } ng_hci_lp_discon_req_ep; /* Connection Confirmation Event */ #define NGM_HCI_LP_CON_CFM 3 /* HCI -> Upper */ typedef struct { u_int8_t status; /* 0x00 - success */ u_int8_t link_type; /* link type */ u_int16_t con_handle; /* con_handle */ bdaddr_t bdaddr; /* remote unit address */ } ng_hci_lp_con_cfm_ep; /* Connection Indication Event */ #define NGM_HCI_LP_CON_IND 4 /* HCI -> Upper */ typedef struct { u_int8_t link_type; /* link type */ u_int8_t uclass[NG_HCI_CLASS_SIZE]; /* unit class */ bdaddr_t bdaddr; /* remote unit address */ } ng_hci_lp_con_ind_ep; /* Connection Response Event */ #define NGM_HCI_LP_CON_RSP 5 /* Upper -> HCI */ typedef struct { u_int8_t status; /* 0x00 - accept connection */ u_int8_t link_type; /* link type */ bdaddr_t bdaddr; /* remote unit address */ } ng_hci_lp_con_rsp_ep; /* Disconnection Indication Event */ #define NGM_HCI_LP_DISCON_IND 6 /* HCI -> Upper */ typedef struct { u_int8_t reason; /* reason to disconnect (only low byte) */ u_int8_t link_type; /* link type */ u_int16_t con_handle; /* connection handle */ } ng_hci_lp_discon_ind_ep; /* QoS Setup Request Event */ #define NGM_HCI_LP_QOS_REQ 7 /* Upper -> HCI */ typedef struct { u_int16_t con_handle; /* connection handle */ u_int8_t flags; /* reserved */ u_int8_t service_type; /* service type */ u_int32_t token_rate; /* bytes/sec */ u_int32_t peak_bandwidth; /* bytes/sec */ u_int32_t latency; /* msec */ u_int32_t delay_variation; /* msec */ } ng_hci_lp_qos_req_ep; /* QoS Conformition Event */ #define NGM_HCI_LP_QOS_CFM 8 /* HCI -> Upper */ typedef struct { u_int16_t status; /* 0x00 - success (only low byte) */ u_int16_t con_handle; /* connection handle */ } ng_hci_lp_qos_cfm_ep; /* QoS Violation Indication Event */ #define NGM_HCI_LP_QOS_IND 9 /* HCI -> Upper */ typedef struct { u_int16_t con_handle; /* connection handle */ } ng_hci_lp_qos_ind_ep; /*Encryption Change event*/ #define NGM_HCI_LP_ENC_CHG 10 /* HCI->Upper*/ typedef struct { uint16_t con_handle; uint8_t status; uint8_t link_type; }ng_hci_lp_enc_change_ep; /************************************************************************** ************************************************************************** ** HCI node command/event parameters ************************************************************************** **************************************************************************/ /* Debug levels */ #define NG_HCI_ALERT_LEVEL 1 #define NG_HCI_ERR_LEVEL 2 #define NG_HCI_WARN_LEVEL 3 #define NG_HCI_INFO_LEVEL 4 /* Unit states */ #define NG_HCI_UNIT_CONNECTED (1 << 0) #define NG_HCI_UNIT_INITED (1 << 1) #define NG_HCI_UNIT_READY (NG_HCI_UNIT_CONNECTED|NG_HCI_UNIT_INITED) #define NG_HCI_UNIT_COMMAND_PENDING (1 << 2) /* Connection state */ #define NG_HCI_CON_CLOSED 0 /* connection closed */ #define NG_HCI_CON_W4_LP_CON_RSP 1 /* wait for LP_ConnectRsp */ #define NG_HCI_CON_W4_CONN_COMPLETE 2 /* wait for Connection_Complete evt */ #define NG_HCI_CON_OPEN 3 /* connection open */ /* Get HCI node (unit) state (see states above) */ #define NGM_HCI_NODE_GET_STATE 100 /* HCI -> User */ typedef u_int16_t ng_hci_node_state_ep; /* Turn on "inited" bit */ #define NGM_HCI_NODE_INIT 101 /* User -> HCI */ /* No parameters */ /* Get/Set node debug level (see debug levels above) */ #define NGM_HCI_NODE_GET_DEBUG 102 /* HCI -> User */ #define NGM_HCI_NODE_SET_DEBUG 103 /* User -> HCI */ typedef u_int16_t ng_hci_node_debug_ep; /* Get node buffer info */ #define NGM_HCI_NODE_GET_BUFFER 104 /* HCI -> User */ typedef struct { u_int8_t cmd_free; /* number of free command packets */ u_int8_t sco_size; /* max. size of SCO packet */ u_int16_t sco_pkts; /* number of SCO packets */ u_int16_t sco_free; /* number of free SCO packets */ u_int16_t acl_size; /* max. size of ACL packet */ u_int16_t acl_pkts; /* number of ACL packets */ u_int16_t acl_free; /* number of free ACL packets */ } ng_hci_node_buffer_ep; /* Get BDADDR */ #define NGM_HCI_NODE_GET_BDADDR 105 /* HCI -> User */ /* bdaddr_t -- BDADDR */ /* Get features */ #define NGM_HCI_NODE_GET_FEATURES 106 /* HCI -> User */ /* features[NG_HCI_FEATURES_SIZE] -- features */ #define NGM_HCI_NODE_GET_STAT 107 /* HCI -> User */ typedef struct { u_int32_t cmd_sent; /* number of HCI commands sent */ u_int32_t evnt_recv; /* number of HCI events received */ u_int32_t acl_recv; /* number of ACL packets received */ u_int32_t acl_sent; /* number of ACL packets sent */ u_int32_t sco_recv; /* number of SCO packets received */ u_int32_t sco_sent; /* number of SCO packets sent */ u_int32_t bytes_recv; /* total number of bytes received */ u_int32_t bytes_sent; /* total number of bytes sent */ } ng_hci_node_stat_ep; #define NGM_HCI_NODE_RESET_STAT 108 /* User -> HCI */ /* No parameters */ #define NGM_HCI_NODE_FLUSH_NEIGHBOR_CACHE 109 /* User -> HCI */ #define NGM_HCI_NODE_GET_NEIGHBOR_CACHE 110 /* HCI -> User */ typedef struct { u_int32_t num_entries; /* number of entries */ } ng_hci_node_get_neighbor_cache_ep; typedef struct { u_int16_t page_scan_rep_mode; /* page rep scan mode */ u_int16_t page_scan_mode; /* page scan mode */ u_int16_t clock_offset; /* clock offset */ bdaddr_t bdaddr; /* bdaddr */ u_int8_t features[NG_HCI_FEATURES_SIZE]; /* features */ } ng_hci_node_neighbor_cache_entry_ep; #define NG_HCI_MAX_NEIGHBOR_NUM \ ((0xffff - sizeof(ng_hci_node_get_neighbor_cache_ep))/sizeof(ng_hci_node_neighbor_cache_entry_ep)) #define NGM_HCI_NODE_GET_CON_LIST 111 /* HCI -> User */ typedef struct { u_int32_t num_connections; /* number of connections */ } ng_hci_node_con_list_ep; typedef struct { u_int8_t link_type; /* ACL or SCO */ u_int8_t encryption_mode; /* none, p2p, ... */ u_int8_t mode; /* ACTIVE, HOLD ... */ u_int8_t role; /* MASTER/SLAVE */ u_int16_t state; /* connection state */ u_int16_t reserved; /* place holder */ u_int16_t pending; /* number of pending packets */ u_int16_t queue_len; /* number of packets in queue */ u_int16_t con_handle; /* connection handle */ bdaddr_t bdaddr; /* remote bdaddr */ } ng_hci_node_con_ep; #define NG_HCI_MAX_CON_NUM \ ((0xffff - sizeof(ng_hci_node_con_list_ep))/sizeof(ng_hci_node_con_ep)) #define NGM_HCI_NODE_UP 112 /* HCI -> Upper */ typedef struct { u_int16_t pkt_size; /* max. ACL/SCO packet size (w/out header) */ u_int16_t num_pkts; /* ACL/SCO packet queue size */ u_int16_t reserved; /* place holder */ bdaddr_t bdaddr; /* bdaddr */ } ng_hci_node_up_ep; #define NGM_HCI_SYNC_CON_QUEUE 113 /* HCI -> Upper */ typedef struct { u_int16_t con_handle; /* connection handle */ u_int16_t completed; /* number of completed packets */ } ng_hci_sync_con_queue_ep; #define NGM_HCI_NODE_GET_LINK_POLICY_SETTINGS_MASK 114 /* HCI -> User */ #define NGM_HCI_NODE_SET_LINK_POLICY_SETTINGS_MASK 115 /* User -> HCI */ typedef u_int16_t ng_hci_node_link_policy_mask_ep; #define NGM_HCI_NODE_GET_PACKET_MASK 116 /* HCI -> User */ #define NGM_HCI_NODE_SET_PACKET_MASK 117 /* User -> HCI */ typedef u_int16_t ng_hci_node_packet_mask_ep; #define NGM_HCI_NODE_GET_ROLE_SWITCH 118 /* HCI -> User */ #define NGM_HCI_NODE_SET_ROLE_SWITCH 119 /* User -> HCI */ typedef u_int16_t ng_hci_node_role_switch_ep; #define NGM_HCI_NODE_LIST_NAMES 200 /* HCI -> User */ /************************************************************************** ************************************************************************** ** Link control commands and return parameters ************************************************************************** **************************************************************************/ #define NG_HCI_OGF_LINK_CONTROL 0x01 /* OpCode Group Field */ #define NG_HCI_OCF_INQUIRY 0x0001 typedef struct { u_int8_t lap[NG_HCI_LAP_SIZE]; /* LAP */ u_int8_t inquiry_length; /* (N x 1.28) sec */ u_int8_t num_responses; /* Max. # of responses before halted */ } __attribute__ ((packed)) ng_hci_inquiry_cp; /* No return parameter(s) */ #define NG_HCI_OCF_INQUIRY_CANCEL 0x0002 /* No command parameter(s) */ typedef ng_hci_status_rp ng_hci_inquiry_cancel_rp; #define NG_HCI_OCF_PERIODIC_INQUIRY 0x0003 typedef struct { u_int16_t max_period_length; /* Max. and min. amount of time */ u_int16_t min_period_length; /* between consecutive inquiries */ u_int8_t lap[NG_HCI_LAP_SIZE]; /* LAP */ u_int8_t inquiry_length; /* (inquiry_length * 1.28) sec */ u_int8_t num_responses; /* Max. # of responses */ } __attribute__ ((packed)) ng_hci_periodic_inquiry_cp; typedef ng_hci_status_rp ng_hci_periodic_inquiry_rp; #define NG_HCI_OCF_EXIT_PERIODIC_INQUIRY 0x0004 /* No command parameter(s) */ typedef ng_hci_status_rp ng_hci_exit_periodic_inquiry_rp; #define NG_HCI_OCF_CREATE_CON 0x0005 typedef struct { bdaddr_t bdaddr; /* destination address */ u_int16_t pkt_type; /* packet type */ u_int8_t page_scan_rep_mode; /* page scan repetition mode */ u_int8_t page_scan_mode; /* page scan mode */ u_int16_t clock_offset; /* clock offset */ u_int8_t accept_role_switch; /* accept role switch? 0x00 - no */ } __attribute__ ((packed)) ng_hci_create_con_cp; /* No return parameter(s) */ #define NG_HCI_OCF_DISCON 0x0006 typedef struct { u_int16_t con_handle; /* connection handle */ u_int8_t reason; /* reason to disconnect */ } __attribute__ ((packed)) ng_hci_discon_cp; /* No return parameter(s) */ #define NG_HCI_OCF_ADD_SCO_CON 0x0007 typedef struct { u_int16_t con_handle; /* connection handle */ u_int16_t pkt_type; /* packet type */ } __attribute__ ((packed)) ng_hci_add_sco_con_cp; /* No return parameter(s) */ #define NG_HCI_OCF_ACCEPT_CON 0x0009 typedef struct { bdaddr_t bdaddr; /* address of unit to be connected */ u_int8_t role; /* connection role */ } __attribute__ ((packed)) ng_hci_accept_con_cp; /* No return parameter(s) */ #define NG_HCI_OCF_REJECT_CON 0x000a typedef struct { bdaddr_t bdaddr; /* remote address */ u_int8_t reason; /* reason to reject */ } __attribute__ ((packed)) ng_hci_reject_con_cp; /* No return parameter(s) */ #define NG_HCI_OCF_LINK_KEY_REP 0x000b typedef struct { bdaddr_t bdaddr; /* remote address */ u_int8_t key[NG_HCI_KEY_SIZE]; /* key */ } __attribute__ ((packed)) ng_hci_link_key_rep_cp; typedef struct { u_int8_t status; /* 0x00 - success */ bdaddr_t bdaddr; /* unit address */ } __attribute__ ((packed)) ng_hci_link_key_rep_rp; #define NG_HCI_OCF_LINK_KEY_NEG_REP 0x000c typedef struct { bdaddr_t bdaddr; /* remote address */ } __attribute__ ((packed)) ng_hci_link_key_neg_rep_cp; typedef struct { u_int8_t status; /* 0x00 - success */ bdaddr_t bdaddr; /* unit address */ } __attribute__ ((packed)) ng_hci_link_key_neg_rep_rp; #define NG_HCI_OCF_PIN_CODE_REP 0x000d typedef struct { bdaddr_t bdaddr; /* remote address */ u_int8_t pin_size; /* pin code length (in bytes) */ u_int8_t pin[NG_HCI_PIN_SIZE]; /* pin code */ } __attribute__ ((packed)) ng_hci_pin_code_rep_cp; typedef struct { u_int8_t status; /* 0x00 - success */ bdaddr_t bdaddr; /* unit address */ } __attribute__ ((packed)) ng_hci_pin_code_rep_rp; #define NG_HCI_OCF_PIN_CODE_NEG_REP 0x000e typedef struct { bdaddr_t bdaddr; /* remote address */ } __attribute__ ((packed)) ng_hci_pin_code_neg_rep_cp; typedef struct { u_int8_t status; /* 0x00 - success */ bdaddr_t bdaddr; /* unit address */ } __attribute__ ((packed)) ng_hci_pin_code_neg_rep_rp; #define NG_HCI_OCF_CHANGE_CON_PKT_TYPE 0x000f typedef struct { u_int16_t con_handle; /* connection handle */ u_int16_t pkt_type; /* packet type */ } __attribute__ ((packed)) ng_hci_change_con_pkt_type_cp; /* No return parameter(s) */ #define NG_HCI_OCF_AUTH_REQ 0x0011 typedef struct { u_int16_t con_handle; /* connection handle */ } __attribute__ ((packed)) ng_hci_auth_req_cp; /* No return parameter(s) */ #define NG_HCI_OCF_SET_CON_ENCRYPTION 0x0013 typedef struct { u_int16_t con_handle; /* connection handle */ u_int8_t encryption_enable; /* 0x00 - disable, 0x01 - enable */ } __attribute__ ((packed)) ng_hci_set_con_encryption_cp; /* No return parameter(s) */ #define NG_HCI_OCF_CHANGE_CON_LINK_KEY 0x0015 typedef struct { u_int16_t con_handle; /* connection handle */ } __attribute__ ((packed)) ng_hci_change_con_link_key_cp; /* No return parameter(s) */ #define NG_HCI_OCF_MASTER_LINK_KEY 0x0017 typedef struct { u_int8_t key_flag; /* key flag */ } __attribute__ ((packed)) ng_hci_master_link_key_cp; /* No return parameter(s) */ #define NG_HCI_OCF_REMOTE_NAME_REQ 0x0019 typedef struct { bdaddr_t bdaddr; /* remote address */ u_int8_t page_scan_rep_mode; /* page scan repetition mode */ u_int8_t page_scan_mode; /* page scan mode */ u_int16_t clock_offset; /* clock offset */ } __attribute__ ((packed)) ng_hci_remote_name_req_cp; /* No return parameter(s) */ #define NG_HCI_OCF_READ_REMOTE_FEATURES 0x001b typedef struct { u_int16_t con_handle; /* connection handle */ } __attribute__ ((packed)) ng_hci_read_remote_features_cp; /* No return parameter(s) */ #define NG_HCI_OCF_READ_REMOTE_VER_INFO 0x001d typedef struct { u_int16_t con_handle; /* connection handle */ } __attribute__ ((packed)) ng_hci_read_remote_ver_info_cp; /* No return parameter(s) */ #define NG_HCI_OCF_READ_CLOCK_OFFSET 0x001f typedef struct { u_int16_t con_handle; /* connection handle */ } __attribute__ ((packed)) ng_hci_read_clock_offset_cp; /* No return parameter(s) */ /************************************************************************** ************************************************************************** ** Link policy commands and return parameters ************************************************************************** **************************************************************************/ #define NG_HCI_OGF_LINK_POLICY 0x02 /* OpCode Group Field */ #define NG_HCI_OCF_HOLD_MODE 0x0001 typedef struct { u_int16_t con_handle; /* connection handle */ u_int16_t max_interval; /* (max_interval * 0.625) msec */ u_int16_t min_interval; /* (max_interval * 0.625) msec */ } __attribute__ ((packed)) ng_hci_hold_mode_cp; /* No return parameter(s) */ #define NG_HCI_OCF_SNIFF_MODE 0x0003 typedef struct { u_int16_t con_handle; /* connection handle */ u_int16_t max_interval; /* (max_interval * 0.625) msec */ u_int16_t min_interval; /* (max_interval * 0.625) msec */ u_int16_t attempt; /* (2 * attempt - 1) * 0.625 msec */ u_int16_t timeout; /* (2 * attempt - 1) * 0.625 msec */ } __attribute__ ((packed)) ng_hci_sniff_mode_cp; /* No return parameter(s) */ #define NG_HCI_OCF_EXIT_SNIFF_MODE 0x0004 typedef struct { u_int16_t con_handle; /* connection handle */ } __attribute__ ((packed)) ng_hci_exit_sniff_mode_cp; /* No return parameter(s) */ #define NG_HCI_OCF_PARK_MODE 0x0005 typedef struct { u_int16_t con_handle; /* connection handle */ u_int16_t max_interval; /* (max_interval * 0.625) msec */ u_int16_t min_interval; /* (max_interval * 0.625) msec */ } __attribute__ ((packed)) ng_hci_park_mode_cp; /* No return parameter(s) */ #define NG_HCI_OCF_EXIT_PARK_MODE 0x0006 typedef struct { u_int16_t con_handle; /* connection handle */ } __attribute__ ((packed)) ng_hci_exit_park_mode_cp; /* No return parameter(s) */ #define NG_HCI_OCF_QOS_SETUP 0x0007 typedef struct { u_int16_t con_handle; /* connection handle */ u_int8_t flags; /* reserved for future use */ u_int8_t service_type; /* service type */ u_int32_t token_rate; /* bytes per second */ u_int32_t peak_bandwidth; /* bytes per second */ u_int32_t latency; /* microseconds */ u_int32_t delay_variation; /* microseconds */ } __attribute__ ((packed)) ng_hci_qos_setup_cp; /* No return parameter(s) */ #define NG_HCI_OCF_ROLE_DISCOVERY 0x0009 typedef struct { u_int16_t con_handle; /* connection handle */ } __attribute__ ((packed)) ng_hci_role_discovery_cp; typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t con_handle; /* connection handle */ u_int8_t role; /* role for the connection handle */ } __attribute__ ((packed)) ng_hci_role_discovery_rp; #define NG_HCI_OCF_SWITCH_ROLE 0x000b typedef struct { bdaddr_t bdaddr; /* remote address */ u_int8_t role; /* new local role */ } __attribute__ ((packed)) ng_hci_switch_role_cp; /* No return parameter(s) */ #define NG_HCI_OCF_READ_LINK_POLICY_SETTINGS 0x000c typedef struct { u_int16_t con_handle; /* connection handle */ } __attribute__ ((packed)) ng_hci_read_link_policy_settings_cp; typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t con_handle; /* connection handle */ u_int16_t settings; /* link policy settings */ } __attribute__ ((packed)) ng_hci_read_link_policy_settings_rp; #define NG_HCI_OCF_WRITE_LINK_POLICY_SETTINGS 0x000d typedef struct { u_int16_t con_handle; /* connection handle */ u_int16_t settings; /* link policy settings */ } __attribute__ ((packed)) ng_hci_write_link_policy_settings_cp; typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t con_handle; /* connection handle */ } __attribute__ ((packed)) ng_hci_write_link_policy_settings_rp; /************************************************************************** ************************************************************************** ** Host controller and baseband commands and return parameters ************************************************************************** **************************************************************************/ #define NG_HCI_OGF_HC_BASEBAND 0x03 /* OpCode Group Field */ #define NG_HCI_OCF_SET_EVENT_MASK 0x0001 typedef struct { u_int8_t event_mask[NG_HCI_EVENT_MASK_SIZE]; /* event_mask */ } __attribute__ ((packed)) ng_hci_set_event_mask_cp; typedef ng_hci_status_rp ng_hci_set_event_mask_rp; #define NG_HCI_EVENT_MASK_DEFAULT 0x1fffffffffff #define NG_HCI_EVENT_MASK_LE 0x2000000000000000 #define NG_HCI_OCF_RESET 0x0003 /* No command parameter(s) */ typedef ng_hci_status_rp ng_hci_reset_rp; #define NG_HCI_OCF_SET_EVENT_FILTER 0x0005 typedef struct { u_int8_t filter_type; /* filter type */ u_int8_t filter_condition_type; /* filter condition type */ u_int8_t condition[0]; /* conditions - variable size */ } __attribute__ ((packed)) ng_hci_set_event_filter_cp; typedef ng_hci_status_rp ng_hci_set_event_filter_rp; #define NG_HCI_OCF_FLUSH 0x0008 typedef struct { u_int16_t con_handle; /* connection handle */ } __attribute__ ((packed)) ng_hci_flush_cp; typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t con_handle; /* connection handle */ } __attribute__ ((packed)) ng_hci_flush_rp; #define NG_HCI_OCF_READ_PIN_TYPE 0x0009 /* No command parameter(s) */ typedef struct { u_int8_t status; /* 0x00 - success */ u_int8_t pin_type; /* PIN type */ } __attribute__ ((packed)) ng_hci_read_pin_type_rp; #define NG_HCI_OCF_WRITE_PIN_TYPE 0x000a typedef struct { u_int8_t pin_type; /* PIN type */ } __attribute__ ((packed)) ng_hci_write_pin_type_cp; typedef ng_hci_status_rp ng_hci_write_pin_type_rp; #define NG_HCI_OCF_CREATE_NEW_UNIT_KEY 0x000b /* No command parameter(s) */ typedef ng_hci_status_rp ng_hci_create_new_unit_key_rp; #define NG_HCI_OCF_READ_STORED_LINK_KEY 0x000d typedef struct { bdaddr_t bdaddr; /* address */ u_int8_t read_all; /* read all keys? 0x01 - yes */ } __attribute__ ((packed)) ng_hci_read_stored_link_key_cp; typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t max_num_keys; /* Max. number of keys */ u_int16_t num_keys_read; /* Number of stored keys */ } __attribute__ ((packed)) ng_hci_read_stored_link_key_rp; #define NG_HCI_OCF_WRITE_STORED_LINK_KEY 0x0011 typedef struct { u_int8_t num_keys_write; /* # of keys to write */ /* these are repeated "num_keys_write" times bdaddr_t bdaddr; --- remote address(es) u_int8_t key[NG_HCI_KEY_SIZE]; --- key(s) */ } __attribute__ ((packed)) ng_hci_write_stored_link_key_cp; typedef struct { u_int8_t status; /* 0x00 - success */ u_int8_t num_keys_written; /* # of keys successfully written */ } __attribute__ ((packed)) ng_hci_write_stored_link_key_rp; #define NG_HCI_OCF_DELETE_STORED_LINK_KEY 0x0012 typedef struct { bdaddr_t bdaddr; /* address */ u_int8_t delete_all; /* delete all keys? 0x01 - yes */ } __attribute__ ((packed)) ng_hci_delete_stored_link_key_cp; typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t num_keys_deleted; /* Number of keys deleted */ } __attribute__ ((packed)) ng_hci_delete_stored_link_key_rp; #define NG_HCI_OCF_CHANGE_LOCAL_NAME 0x0013 typedef struct { char name[NG_HCI_UNIT_NAME_SIZE]; /* new unit name */ } __attribute__ ((packed)) ng_hci_change_local_name_cp; typedef ng_hci_status_rp ng_hci_change_local_name_rp; #define NG_HCI_OCF_READ_LOCAL_NAME 0x0014 /* No command parameter(s) */ typedef struct { u_int8_t status; /* 0x00 - success */ char name[NG_HCI_UNIT_NAME_SIZE]; /* unit name */ } __attribute__ ((packed)) ng_hci_read_local_name_rp; #define NG_HCI_OCF_READ_CON_ACCEPT_TIMO 0x0015 /* No command parameter(s) */ typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t timeout; /* (timeout * 0.625) msec */ } __attribute__ ((packed)) ng_hci_read_con_accept_timo_rp; #define NG_HCI_OCF_WRITE_CON_ACCEPT_TIMO 0x0016 typedef struct { u_int16_t timeout; /* (timeout * 0.625) msec */ } __attribute__ ((packed)) ng_hci_write_con_accept_timo_cp; typedef ng_hci_status_rp ng_hci_write_con_accept_timo_rp; #define NG_HCI_OCF_READ_PAGE_TIMO 0x0017 /* No command parameter(s) */ typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t timeout; /* (timeout * 0.625) msec */ } __attribute__ ((packed)) ng_hci_read_page_timo_rp; #define NG_HCI_OCF_WRITE_PAGE_TIMO 0x0018 typedef struct { u_int16_t timeout; /* (timeout * 0.625) msec */ } __attribute__ ((packed)) ng_hci_write_page_timo_cp; typedef ng_hci_status_rp ng_hci_write_page_timo_rp; #define NG_HCI_OCF_READ_SCAN_ENABLE 0x0019 /* No command parameter(s) */ typedef struct { u_int8_t status; /* 0x00 - success */ u_int8_t scan_enable; /* Scan enable */ } __attribute__ ((packed)) ng_hci_read_scan_enable_rp; #define NG_HCI_OCF_WRITE_SCAN_ENABLE 0x001a typedef struct { u_int8_t scan_enable; /* Scan enable */ } __attribute__ ((packed)) ng_hci_write_scan_enable_cp; typedef ng_hci_status_rp ng_hci_write_scan_enable_rp; #define NG_HCI_OCF_READ_PAGE_SCAN_ACTIVITY 0x001b /* No command parameter(s) */ typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t page_scan_interval; /* interval * 0.625 msec */ u_int16_t page_scan_window; /* window * 0.625 msec */ } __attribute__ ((packed)) ng_hci_read_page_scan_activity_rp; #define NG_HCI_OCF_WRITE_PAGE_SCAN_ACTIVITY 0x001c typedef struct { u_int16_t page_scan_interval; /* interval * 0.625 msec */ u_int16_t page_scan_window; /* window * 0.625 msec */ } __attribute__ ((packed)) ng_hci_write_page_scan_activity_cp; typedef ng_hci_status_rp ng_hci_write_page_scan_activity_rp; #define NG_HCI_OCF_READ_INQUIRY_SCAN_ACTIVITY 0x001d /* No command parameter(s) */ typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t inquiry_scan_interval; /* interval * 0.625 msec */ u_int16_t inquiry_scan_window; /* window * 0.625 msec */ } __attribute__ ((packed)) ng_hci_read_inquiry_scan_activity_rp; #define NG_HCI_OCF_WRITE_INQUIRY_SCAN_ACTIVITY 0x001e typedef struct { u_int16_t inquiry_scan_interval; /* interval * 0.625 msec */ u_int16_t inquiry_scan_window; /* window * 0.625 msec */ } __attribute__ ((packed)) ng_hci_write_inquiry_scan_activity_cp; typedef ng_hci_status_rp ng_hci_write_inquiry_scan_activity_rp; #define NG_HCI_OCF_READ_AUTH_ENABLE 0x001f /* No command parameter(s) */ typedef struct { u_int8_t status; /* 0x00 - success */ u_int8_t auth_enable; /* 0x01 - enabled */ } __attribute__ ((packed)) ng_hci_read_auth_enable_rp; #define NG_HCI_OCF_WRITE_AUTH_ENABLE 0x0020 typedef struct { u_int8_t auth_enable; /* 0x01 - enabled */ } __attribute__ ((packed)) ng_hci_write_auth_enable_cp; typedef ng_hci_status_rp ng_hci_write_auth_enable_rp; #define NG_HCI_OCF_READ_ENCRYPTION_MODE 0x0021 /* No command parameter(s) */ typedef struct { u_int8_t status; /* 0x00 - success */ u_int8_t encryption_mode; /* encryption mode */ } __attribute__ ((packed)) ng_hci_read_encryption_mode_rp; #define NG_HCI_OCF_WRITE_ENCRYPTION_MODE 0x0022 typedef struct { u_int8_t encryption_mode; /* encryption mode */ } __attribute__ ((packed)) ng_hci_write_encryption_mode_cp; typedef ng_hci_status_rp ng_hci_write_encryption_mode_rp; #define NG_HCI_OCF_READ_UNIT_CLASS 0x0023 /* No command parameter(s) */ typedef struct { u_int8_t status; /* 0x00 - success */ u_int8_t uclass[NG_HCI_CLASS_SIZE]; /* unit class */ } __attribute__ ((packed)) ng_hci_read_unit_class_rp; #define NG_HCI_OCF_WRITE_UNIT_CLASS 0x0024 typedef struct { u_int8_t uclass[NG_HCI_CLASS_SIZE]; /* unit class */ } __attribute__ ((packed)) ng_hci_write_unit_class_cp; typedef ng_hci_status_rp ng_hci_write_unit_class_rp; #define NG_HCI_OCF_READ_VOICE_SETTINGS 0x0025 /* No command parameter(s) */ typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t settings; /* voice settings */ } __attribute__ ((packed)) ng_hci_read_voice_settings_rp; #define NG_HCI_OCF_WRITE_VOICE_SETTINGS 0x0026 typedef struct { u_int16_t settings; /* voice settings */ } __attribute__ ((packed)) ng_hci_write_voice_settings_cp; typedef ng_hci_status_rp ng_hci_write_voice_settings_rp; #define NG_HCI_OCF_READ_AUTO_FLUSH_TIMO 0x0027 typedef struct { u_int16_t con_handle; /* connection handle */ } __attribute__ ((packed)) ng_hci_read_auto_flush_timo_cp; typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t con_handle; /* connection handle */ u_int16_t timeout; /* 0x00 - no flush, timeout * 0.625 msec */ } __attribute__ ((packed)) ng_hci_read_auto_flush_timo_rp; #define NG_HCI_OCF_WRITE_AUTO_FLUSH_TIMO 0x0028 typedef struct { u_int16_t con_handle; /* connection handle */ u_int16_t timeout; /* 0x00 - no flush, timeout * 0.625 msec */ } __attribute__ ((packed)) ng_hci_write_auto_flush_timo_cp; typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t con_handle; /* connection handle */ } __attribute__ ((packed)) ng_hci_write_auto_flush_timo_rp; #define NG_HCI_OCF_READ_NUM_BROADCAST_RETRANS 0x0029 /* No command parameter(s) */ typedef struct { u_int8_t status; /* 0x00 - success */ u_int8_t counter; /* number of broadcast retransmissions */ } __attribute__ ((packed)) ng_hci_read_num_broadcast_retrans_rp; #define NG_HCI_OCF_WRITE_NUM_BROADCAST_RETRANS 0x002a typedef struct { u_int8_t counter; /* number of broadcast retransmissions */ } __attribute__ ((packed)) ng_hci_write_num_broadcast_retrans_cp; typedef ng_hci_status_rp ng_hci_write_num_broadcast_retrans_rp; #define NG_HCI_OCF_READ_HOLD_MODE_ACTIVITY 0x002b /* No command parameter(s) */ typedef struct { u_int8_t status; /* 0x00 - success */ u_int8_t hold_mode_activity; /* Hold mode activities */ } __attribute__ ((packed)) ng_hci_read_hold_mode_activity_rp; #define NG_HCI_OCF_WRITE_HOLD_MODE_ACTIVITY 0x002c typedef struct { u_int8_t hold_mode_activity; /* Hold mode activities */ } __attribute__ ((packed)) ng_hci_write_hold_mode_activity_cp; typedef ng_hci_status_rp ng_hci_write_hold_mode_activity_rp; #define NG_HCI_OCF_READ_XMIT_LEVEL 0x002d typedef struct { u_int16_t con_handle; /* connection handle */ u_int8_t type; /* Xmit level type */ } __attribute__ ((packed)) ng_hci_read_xmit_level_cp; typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t con_handle; /* connection handle */ char level; /* -30 <= level <= 30 dBm */ } __attribute__ ((packed)) ng_hci_read_xmit_level_rp; #define NG_HCI_OCF_READ_SCO_FLOW_CONTROL 0x002e /* No command parameter(s) */ typedef struct { u_int8_t status; /* 0x00 - success */ u_int8_t flow_control; /* 0x00 - disabled */ } __attribute__ ((packed)) ng_hci_read_sco_flow_control_rp; #define NG_HCI_OCF_WRITE_SCO_FLOW_CONTROL 0x002f typedef struct { u_int8_t flow_control; /* 0x00 - disabled */ } __attribute__ ((packed)) ng_hci_write_sco_flow_control_cp; typedef ng_hci_status_rp ng_hci_write_sco_flow_control_rp; #define NG_HCI_OCF_H2HC_FLOW_CONTROL 0x0031 typedef struct { u_int8_t h2hc_flow; /* Host to Host controller flow control */ } __attribute__ ((packed)) ng_hci_h2hc_flow_control_cp; typedef ng_hci_status_rp ng_hci_h2hc_flow_control_rp; #define NG_HCI_OCF_HOST_BUFFER_SIZE 0x0033 typedef struct { u_int16_t max_acl_size; /* Max. size of ACL packet (bytes) */ u_int8_t max_sco_size; /* Max. size of SCO packet (bytes) */ u_int16_t num_acl_pkt; /* Max. number of ACL packets */ u_int16_t num_sco_pkt; /* Max. number of SCO packets */ } __attribute__ ((packed)) ng_hci_host_buffer_size_cp; typedef ng_hci_status_rp ng_hci_host_buffer_size_rp; #define NG_HCI_OCF_HOST_NUM_COMPL_PKTS 0x0035 typedef struct { u_int8_t num_con_handles; /* # of connection handles */ /* these are repeated "num_con_handles" times u_int16_t con_handle; --- connection handle(s) u_int16_t compl_pkt; --- # of completed packets */ } __attribute__ ((packed)) ng_hci_host_num_compl_pkts_cp; /* No return parameter(s) */ #define NG_HCI_OCF_READ_LINK_SUPERVISION_TIMO 0x0036 typedef struct { u_int16_t con_handle; /* connection handle */ } __attribute__ ((packed)) ng_hci_read_link_supervision_timo_cp; typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t con_handle; /* connection handle */ u_int16_t timeout; /* Link supervision timeout * 0.625 msec */ } __attribute__ ((packed)) ng_hci_read_link_supervision_timo_rp; #define NG_HCI_OCF_WRITE_LINK_SUPERVISION_TIMO 0x0037 typedef struct { u_int16_t con_handle; /* connection handle */ u_int16_t timeout; /* Link supervision timeout * 0.625 msec */ } __attribute__ ((packed)) ng_hci_write_link_supervision_timo_cp; typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t con_handle; /* connection handle */ } __attribute__ ((packed)) ng_hci_write_link_supervision_timo_rp; #define NG_HCI_OCF_READ_SUPPORTED_IAC_NUM 0x0038 /* No command parameter(s) */ typedef struct { u_int8_t status; /* 0x00 - success */ u_int8_t num_iac; /* # of supported IAC during scan */ } __attribute__ ((packed)) ng_hci_read_supported_iac_num_rp; #define NG_HCI_OCF_READ_IAC_LAP 0x0039 /* No command parameter(s) */ typedef struct { u_int8_t status; /* 0x00 - success */ u_int8_t num_iac; /* # of IAC */ /* these are repeated "num_iac" times u_int8_t laps[NG_HCI_LAP_SIZE]; --- LAPs */ } __attribute__ ((packed)) ng_hci_read_iac_lap_rp; #define NG_HCI_OCF_WRITE_IAC_LAP 0x003a typedef struct { u_int8_t num_iac; /* # of IAC */ /* these are repeated "num_iac" times u_int8_t laps[NG_HCI_LAP_SIZE]; --- LAPs */ } __attribute__ ((packed)) ng_hci_write_iac_lap_cp; typedef ng_hci_status_rp ng_hci_write_iac_lap_rp; /*0x003b-0x003e commands are depricated v2.0 or later*/ #define NG_HCI_OCF_READ_PAGE_SCAN_PERIOD 0x003b /* No command parameter(s) */ typedef struct { u_int8_t status; /* 0x00 - success */ u_int8_t page_scan_period_mode; /* Page scan period mode */ } __attribute__ ((packed)) ng_hci_read_page_scan_period_rp; #define NG_HCI_OCF_WRITE_PAGE_SCAN_PERIOD 0x003c typedef struct { u_int8_t page_scan_period_mode; /* Page scan period mode */ } __attribute__ ((packed)) ng_hci_write_page_scan_period_cp; typedef ng_hci_status_rp ng_hci_write_page_scan_period_rp; #define NG_HCI_OCF_READ_PAGE_SCAN 0x003d /* No command parameter(s) */ typedef struct { u_int8_t status; /* 0x00 - success */ u_int8_t page_scan_mode; /* Page scan mode */ } __attribute__ ((packed)) ng_hci_read_page_scan_rp; #define NG_HCI_OCF_WRITE_PAGE_SCAN 0x003e typedef struct { u_int8_t page_scan_mode; /* Page scan mode */ } __attribute__ ((packed)) ng_hci_write_page_scan_cp; typedef ng_hci_status_rp ng_hci_write_page_scan_rp; #define NG_HCI_OCF_READ_LE_HOST_SUPPORTED 0x6c typedef struct { u_int8_t status; /* 0x00 - success */ u_int8_t le_supported_host ;/* LE host supported?*/ u_int8_t simultaneous_le_host; /* BR/LE simulateneous? */ } __attribute__ ((packed)) ng_hci_read_le_host_supported_rp; #define NG_HCI_OCF_WRITE_LE_HOST_SUPPORTED 0x6d typedef struct { u_int8_t le_supported_host; /* LE host supported?*/ u_int8_t simultaneous_le_host; /* LE host supported?*/ } __attribute__ ((packed)) ng_hci_write_le_host_supported_cp; typedef ng_hci_status_rp ng_hci_write_le_host_supported_rp; /************************************************************************** ************************************************************************** ** Informational commands and return parameters ** All commands in this category do not accept any parameters ************************************************************************** **************************************************************************/ #define NG_HCI_OGF_INFO 0x04 /* OpCode Group Field */ #define NG_HCI_OCF_READ_LOCAL_VER 0x0001 typedef struct { u_int8_t status; /* 0x00 - success */ u_int8_t hci_version; /* HCI version */ u_int16_t hci_revision; /* HCI revision */ u_int8_t lmp_version; /* LMP version */ u_int16_t manufacturer; /* Hardware manufacturer name */ u_int16_t lmp_subversion; /* LMP sub-version */ } __attribute__ ((packed)) ng_hci_read_local_ver_rp; #define NG_HCI_OCF_READ_LOCAL_COMMANDS 0x0002 typedef struct { u_int8_t status; /* 0x00 - success */ u_int8_t features[NG_HCI_COMMANDS_SIZE]; /* command bitmsk*/ } __attribute__ ((packed)) ng_hci_read_local_commands_rp; #define NG_HCI_OCF_READ_LOCAL_FEATURES 0x0003 typedef struct { u_int8_t status; /* 0x00 - success */ u_int8_t features[NG_HCI_FEATURES_SIZE]; /* LMP features bitmsk*/ } __attribute__ ((packed)) ng_hci_read_local_features_rp; #define NG_HCI_OCF_READ_BUFFER_SIZE 0x0005 typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t max_acl_size; /* Max. size of ACL packet (bytes) */ u_int8_t max_sco_size; /* Max. size of SCO packet (bytes) */ u_int16_t num_acl_pkt; /* Max. number of ACL packets */ u_int16_t num_sco_pkt; /* Max. number of SCO packets */ } __attribute__ ((packed)) ng_hci_read_buffer_size_rp; #define NG_HCI_OCF_READ_COUNTRY_CODE 0x0007 typedef struct { u_int8_t status; /* 0x00 - success */ u_int8_t country_code; /* 0x00 - NAM, EUR, JP; 0x01 - France */ } __attribute__ ((packed)) ng_hci_read_country_code_rp; #define NG_HCI_OCF_READ_BDADDR 0x0009 typedef struct { u_int8_t status; /* 0x00 - success */ bdaddr_t bdaddr; /* unit address */ } __attribute__ ((packed)) ng_hci_read_bdaddr_rp; /************************************************************************** ************************************************************************** ** Status commands and return parameters ************************************************************************** **************************************************************************/ #define NG_HCI_OGF_STATUS 0x05 /* OpCode Group Field */ #define NG_HCI_OCF_READ_FAILED_CONTACT_CNTR 0x0001 typedef struct { u_int16_t con_handle; /* connection handle */ } __attribute__ ((packed)) ng_hci_read_failed_contact_cntr_cp; typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t con_handle; /* connection handle */ u_int16_t counter; /* number of consecutive failed contacts */ } __attribute__ ((packed)) ng_hci_read_failed_contact_cntr_rp; #define NG_HCI_OCF_RESET_FAILED_CONTACT_CNTR 0x0002 typedef struct { u_int16_t con_handle; /* connection handle */ } __attribute__ ((packed)) ng_hci_reset_failed_contact_cntr_cp; typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t con_handle; /* connection handle */ } __attribute__ ((packed)) ng_hci_reset_failed_contact_cntr_rp; #define NG_HCI_OCF_GET_LINK_QUALITY 0x0003 typedef struct { u_int16_t con_handle; /* connection handle */ } __attribute__ ((packed)) ng_hci_get_link_quality_cp; typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t con_handle; /* connection handle */ u_int8_t quality; /* higher value means better quality */ } __attribute__ ((packed)) ng_hci_get_link_quality_rp; #define NG_HCI_OCF_READ_RSSI 0x0005 typedef struct { u_int16_t con_handle; /* connection handle */ } __attribute__ ((packed)) ng_hci_read_rssi_cp; typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t con_handle; /* connection handle */ char rssi; /* -127 <= rssi <= 127 dB */ } __attribute__ ((packed)) ng_hci_read_rssi_rp; /************************************************************************** ************************************************************************** ** Testing commands and return parameters ************************************************************************** **************************************************************************/ #define NG_HCI_OGF_TESTING 0x06 /* OpCode Group Field */ #define NG_HCI_OCF_READ_LOOPBACK_MODE 0x0001 /* No command parameter(s) */ typedef struct { u_int8_t status; /* 0x00 - success */ u_int8_t lbmode; /* loopback mode */ } __attribute__ ((packed)) ng_hci_read_loopback_mode_rp; #define NG_HCI_OCF_WRITE_LOOPBACK_MODE 0x0002 typedef struct { u_int8_t lbmode; /* loopback mode */ } __attribute__ ((packed)) ng_hci_write_loopback_mode_cp; typedef ng_hci_status_rp ng_hci_write_loopback_mode_rp; #define NG_HCI_OCF_ENABLE_UNIT_UNDER_TEST 0x0003 /* No command parameter(s) */ typedef ng_hci_status_rp ng_hci_enable_unit_under_test_rp; /************************************************************************** ************************************************************************** ** LE OpCode group field ************************************************************************** **************************************************************************/ #define NG_HCI_OGF_LE 0x08 /* OpCode Group Field */ #define NG_HCI_OCF_LE_SET_EVENT_MASK 0x0001 typedef struct { u_int8_t event_mask[NG_HCI_LE_EVENT_MASK_SIZE]; /* event_mask*/ } __attribute__ ((packed)) ng_hci_le_set_event_mask_cp; typedef ng_hci_status_rp ng_hci_le_set_event_mask_rp; #define NG_HCI_LE_EVENT_MASK_ALL 0x1f #define NG_HCI_OCF_LE_READ_BUFFER_SIZE 0x0002 /*No command parameter */ typedef struct { u_int8_t status; /*status*/ u_int16_t hc_le_data_packet_length; u_int8_t hc_total_num_le_data_packets; } __attribute__ ((packed)) ng_hci_le_read_buffer_size_rp; #define NG_HCI_OCF_LE_READ_LOCAL_SUPPORTED_FEATURES 0x0003 /*No command parameter */ typedef struct { u_int8_t status; /*status*/ u_int64_t le_features; } __attribute__ ((packed)) ng_hci_le_read_local_supported_features_rp; #define NG_HCI_OCF_LE_SET_RANDOM_ADDRESS 0x0005 typedef struct { bdaddr_t random_address; } __attribute__ ((packed)) ng_hci_le_set_random_address_cp_; typedef ng_hci_status_rp ng_hci_le_set_random_address_rp; #define NG_HCI_OCF_LE_SET_ADVERTISING_PARAMETERS 0x0006 typedef struct { u_int16_t advertising_interval_min; u_int16_t advertising_interval_max; u_int8_t advertising_type; u_int8_t own_address_type; u_int8_t direct_address_type; bdaddr_t direct_address; u_int8_t advertising_channel_map; u_int8_t advertising_filter_policy; } __attribute__ ((packed)) ng_hci_le_set_advertising_parameters_cp; typedef ng_hci_status_rp ng_hci_le_set_advertising_parameters_rp; #define NG_HCI_OCF_LE_READ_ADVERTISING_CHANNEL_TX_POWER 0x0007 /*No command parameter*/ typedef struct { u_int8_t status; u_int8_t transmit_power_level; } __attribute__ ((packed)) ng_hci_le_read_advertising_channel_tx_power_rp; #define NG_HCI_OCF_LE_SET_ADVERTISING_DATA 0x0008 #define NG_HCI_ADVERTISING_DATA_SIZE 31 typedef struct { u_int8_t advertising_data_length; char advertising_data[NG_HCI_ADVERTISING_DATA_SIZE]; } __attribute__ ((packed)) ng_hci_le_set_advertising_data_cp; typedef ng_hci_status_rp ng_hci_le_set_advertising_data_rp; #define NG_HCI_OCF_LE_SET_SCAN_RESPONSE_DATA 0x0009 typedef struct { u_int8_t scan_response_data_length; char scan_response_data[NG_HCI_ADVERTISING_DATA_SIZE]; } __attribute__ ((packed)) ng_hci_le_set_scan_response_data_cp; typedef ng_hci_status_rp ng_hci_le_set_scan_response_data_rp; #define NG_HCI_OCF_LE_SET_ADVERTISE_ENABLE 0x000a typedef struct { u_int8_t advertising_enable; }__attribute__ ((packed)) ng_hci_le_set_advertise_enable_cp; typedef ng_hci_status_rp ng_hci_le_set_advertise_enable_rp; #define NG_HCI_OCF_LE_SET_SCAN_PARAMETERS 0x000b typedef struct { u_int8_t le_scan_type; u_int16_t le_scan_interval; u_int16_t le_scan_window; u_int8_t own_address_type; u_int8_t scanning_filter_policy; }__attribute__ ((packed)) ng_hci_le_set_scan_parameters_cp; typedef ng_hci_status_rp ng_hci_le_set_scan_parameters_rp; #define NG_HCI_OCF_LE_SET_SCAN_ENABLE 0x000c typedef struct { u_int8_t le_scan_enable; u_int8_t filter_duplicates; }__attribute__ ((packed)) ng_hci_le_set_scan_enable_cp; typedef ng_hci_status_rp ng_hci_le_set_scan_enable_rp; #define NG_HCI_OCF_LE_CREATE_CONNECTION 0x000d typedef struct { u_int16_t scan_interval; u_int16_t scan_window; u_int8_t filter_policy; u_int8_t peer_addr_type; bdaddr_t peer_addr; u_int8_t own_address_type; u_int16_t conn_interval_min; u_int16_t conn_interval_max; u_int16_t conn_latency; u_int16_t supervision_timeout; u_int16_t min_ce_length; u_int16_t max_ce_length; }__attribute__((packed)) ng_hci_le_create_connection_cp; -/* no return paramters*/ +/* No return parameters. */ #define NG_HCI_OCF_LE_CREATE_CONNECTION_CANCEL 0x000e /*No command parameter*/ typedef ng_hci_status_rp ng_hci_le_create_connection_cancel_rp; #define NG_HCI_OCF_LE_READ_WHITE_LIST_SIZE 0x000f /*No command parameter*/ typedef struct { u_int8_t status; u_int8_t white_list_size; } __attribute__ ((packed)) ng_hci_le_read_white_list_size_rp; #define NG_HCI_OCF_LE_CLEAR_WHITE_LIST 0x0010 -/*No command paramters*/ +/* No command parameters. */ typedef ng_hci_status_rp ng_hci_le_clear_white_list_rp; #define NG_HCI_OCF_LE_ADD_DEVICE_TO_WHITE_LIST 0x0011 typedef struct { u_int8_t address_type; bdaddr_t address; } __attribute__ ((packed)) ng_hci_le_add_device_to_white_list_cp; typedef ng_hci_status_rp ng_hci_le_add_device_to_white_list_rp; #define NG_HCI_OCF_LE_REMOVE_DEVICE_FROM_WHITE_LIST 0x0012 typedef struct { u_int8_t address_type; bdaddr_t address; } __attribute__ ((packed)) ng_hci_le_remove_device_from_white_list_cp; typedef ng_hci_status_rp ng_hci_le_remove_device_from_white_list_rp; #define NG_HCI_OCF_LE_CONNECTION_UPDATE 0x0013 typedef struct { u_int16_t connection_handle; u_int16_t conn_interval_min; u_int16_t conn_interval_max; u_int16_t conn_latency; u_int16_t supervision_timeout; u_int16_t minimum_ce_length; u_int16_t maximum_ce_length; }__attribute__ ((packed)) ng_hci_le_connection_update_cp; /*no return parameter*/ #define NG_HCI_OCF_LE_SET_HOST_CHANNEL_CLASSIFICATION 0x0014 typedef struct{ u_int8_t le_channel_map[5]; }__attribute__ ((packed)) ng_hci_le_set_host_channel_classification_cp; typedef ng_hci_status_rp ng_hci_le_set_host_channel_classification_rp; #define NG_HCI_OCF_LE_READ_CHANNEL_MAP 0x0015 typedef struct { u_int16_t connection_handle; }__attribute__ ((packed)) ng_hci_le_read_channel_map_cp; typedef struct { u_int8_t status; u_int16_t connection_handle; u_int8_t le_channel_map[5]; } __attribute__ ((packed)) ng_hci_le_read_channel_map_rp; #define NG_HCI_OCF_LE_READ_REMOTE_USED_FEATURES 0x0016 typedef struct { u_int16_t connection_handle; }__attribute__ ((packed)) ng_hci_le_read_remote_used_features_cp; /*No return parameter*/ #define NG_HCI_128BIT 16 #define NG_HCI_OCF_LE_ENCRYPT 0x0017 typedef struct { u_int8_t key[NG_HCI_128BIT]; u_int8_t plaintext_data[NG_HCI_128BIT]; }__attribute__ ((packed)) ng_hci_le_encrypt_cp; typedef struct { u_int8_t status; u_int8_t plaintext_data[NG_HCI_128BIT]; }__attribute__ ((packed)) ng_hci_le_encrypt_rp; #define NG_HCI_OCF_LE_RAND 0x0018 /*No command parameter*/ typedef struct { u_int8_t status; u_int64_t random_number; }__attribute__ ((packed)) ng_hci_le_rand_rp; #define NG_HCI_OCF_LE_START_ENCRYPTION 0x0019 typedef struct { u_int16_t connection_handle; u_int64_t random_number; u_int16_t encrypted_diversifier; u_int8_t long_term_key[NG_HCI_128BIT]; }__attribute__ ((packed)) ng_hci_le_start_encryption_cp; /*No return parameter*/ #define NG_HCI_OCF_LE_LONG_TERM_KEY_REQUEST_REPLY 0x001a typedef struct { u_int16_t connection_handle; u_int8_t long_term_key[NG_HCI_128BIT]; }__attribute__ ((packed)) ng_hci_le_long_term_key_request_reply_cp; typedef struct { u_int8_t status; u_int16_t connection_handle; }__attribute__ ((packed)) ng_hci_le_long_term_key_request_reply_rp; #define NG_HCI_OCF_LE_LONG_TERM_KEY_REQUEST_NEGATIVE_REPLY 0x001b typedef struct{ u_int16_t connection_handle; }ng_hci_le_long_term_key_request_negative_reply_cp; typedef struct { u_int8_t status; u_int16_t connection_handle; }__attribute__ ((packed)) ng_hci_le_long_term_key_request_negative_reply_rp; #define NG_HCI_OCF_LE_READ_SUPPORTED_STATUS 0x001c /*No command parameter*/ typedef struct { u_int8_t status; u_int64_t le_status; }__attribute__ ((packed)) ng_hci_le_read_supported_status_rp; #define NG_HCI_OCF_LE_RECEIVER_TEST 0x001d typedef struct{ u_int8_t rx_frequency; } __attribute__((packed)) ng_le_receiver_test_cp; typedef ng_hci_status_rp ng_hci_le_receiver_test_rp; #define NG_HCI_OCF_LE_TRANSMITTER_TEST 0x001e typedef struct{ u_int8_t tx_frequency; u_int8_t length_of_test_data; u_int8_t packet_payload; } __attribute__((packed)) ng_le_transmitter_test_cp; typedef ng_hci_status_rp ng_hci_le_transmitter_test_rp; #define NG_HCI_OCF_LE_TEST_END 0x001f -/*No command paramter*/ +/* No command parameter. */ typedef struct { u_int8_t status; u_int16_t number_of_packets; }__attribute__ ((packed)) ng_hci_le_test_end_rp; /************************************************************************** ************************************************************************** ** Special HCI OpCode group field values ************************************************************************** **************************************************************************/ #define NG_HCI_OGF_BT_LOGO 0x3e #define NG_HCI_OGF_VENDOR 0x3f /************************************************************************** ************************************************************************** ** Events and event parameters ************************************************************************** **************************************************************************/ #define NG_HCI_EVENT_INQUIRY_COMPL 0x01 typedef struct { u_int8_t status; /* 0x00 - success */ } __attribute__ ((packed)) ng_hci_inquiry_compl_ep; #define NG_HCI_EVENT_INQUIRY_RESULT 0x02 typedef struct { u_int8_t num_responses; /* number of responses */ /* ng_hci_inquiry_response[num_responses] -- see below */ } __attribute__ ((packed)) ng_hci_inquiry_result_ep; typedef struct { bdaddr_t bdaddr; /* unit address */ u_int8_t page_scan_rep_mode; /* page scan rep. mode */ u_int8_t page_scan_period_mode; /* page scan period mode */ u_int8_t page_scan_mode; /* page scan mode */ u_int8_t uclass[NG_HCI_CLASS_SIZE];/* unit class */ u_int16_t clock_offset; /* clock offset */ } __attribute__ ((packed)) ng_hci_inquiry_response; #define NG_HCI_EVENT_CON_COMPL 0x03 typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t con_handle; /* Connection handle */ bdaddr_t bdaddr; /* remote unit address */ u_int8_t link_type; /* Link type */ u_int8_t encryption_mode; /* Encryption mode */ } __attribute__ ((packed)) ng_hci_con_compl_ep; #define NG_HCI_EVENT_CON_REQ 0x04 typedef struct { bdaddr_t bdaddr; /* remote unit address */ u_int8_t uclass[NG_HCI_CLASS_SIZE]; /* remote unit class */ u_int8_t link_type; /* link type */ } __attribute__ ((packed)) ng_hci_con_req_ep; #define NG_HCI_EVENT_DISCON_COMPL 0x05 typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t con_handle; /* connection handle */ u_int8_t reason; /* reason to disconnect */ } __attribute__ ((packed)) ng_hci_discon_compl_ep; #define NG_HCI_EVENT_AUTH_COMPL 0x06 typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t con_handle; /* connection handle */ } __attribute__ ((packed)) ng_hci_auth_compl_ep; #define NG_HCI_EVENT_REMOTE_NAME_REQ_COMPL 0x7 typedef struct { u_int8_t status; /* 0x00 - success */ bdaddr_t bdaddr; /* remote unit address */ char name[NG_HCI_UNIT_NAME_SIZE]; /* remote unit name */ } __attribute__ ((packed)) ng_hci_remote_name_req_compl_ep; #define NG_HCI_EVENT_ENCRYPTION_CHANGE 0x08 typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t con_handle; /* Connection handle */ u_int8_t encryption_enable; /* 0x00 - disable */ } __attribute__ ((packed)) ng_hci_encryption_change_ep; #define NG_HCI_EVENT_CHANGE_CON_LINK_KEY_COMPL 0x09 typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t con_handle; /* Connection handle */ } __attribute__ ((packed)) ng_hci_change_con_link_key_compl_ep; #define NG_HCI_EVENT_MASTER_LINK_KEY_COMPL 0x0a typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t con_handle; /* Connection handle */ u_int8_t key_flag; /* Key flag */ } __attribute__ ((packed)) ng_hci_master_link_key_compl_ep; #define NG_HCI_EVENT_READ_REMOTE_FEATURES_COMPL 0x0b typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t con_handle; /* Connection handle */ u_int8_t features[NG_HCI_FEATURES_SIZE]; /* LMP features bitmsk*/ } __attribute__ ((packed)) ng_hci_read_remote_features_compl_ep; #define NG_HCI_EVENT_READ_REMOTE_VER_INFO_COMPL 0x0c typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t con_handle; /* Connection handle */ u_int8_t lmp_version; /* LMP version */ u_int16_t manufacturer; /* Hardware manufacturer name */ u_int16_t lmp_subversion; /* LMP sub-version */ } __attribute__ ((packed)) ng_hci_read_remote_ver_info_compl_ep; #define NG_HCI_EVENT_QOS_SETUP_COMPL 0x0d typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t con_handle; /* connection handle */ u_int8_t flags; /* reserved for future use */ u_int8_t service_type; /* service type */ u_int32_t token_rate; /* bytes per second */ u_int32_t peak_bandwidth; /* bytes per second */ u_int32_t latency; /* microseconds */ u_int32_t delay_variation; /* microseconds */ } __attribute__ ((packed)) ng_hci_qos_setup_compl_ep; #define NG_HCI_EVENT_COMMAND_COMPL 0x0e typedef struct { u_int8_t num_cmd_pkts; /* # of HCI command packets */ u_int16_t opcode; /* command OpCode */ /* command return parameters (if any) */ } __attribute__ ((packed)) ng_hci_command_compl_ep; #define NG_HCI_EVENT_COMMAND_STATUS 0x0f typedef struct { u_int8_t status; /* 0x00 - pending */ u_int8_t num_cmd_pkts; /* # of HCI command packets */ u_int16_t opcode; /* command OpCode */ } __attribute__ ((packed)) ng_hci_command_status_ep; #define NG_HCI_EVENT_HARDWARE_ERROR 0x10 typedef struct { u_int8_t hardware_code; /* hardware error code */ } __attribute__ ((packed)) ng_hci_hardware_error_ep; #define NG_HCI_EVENT_FLUSH_OCCUR 0x11 typedef struct { u_int16_t con_handle; /* connection handle */ } __attribute__ ((packed)) ng_hci_flush_occur_ep; #define NG_HCI_EVENT_ROLE_CHANGE 0x12 typedef struct { u_int8_t status; /* 0x00 - success */ bdaddr_t bdaddr; /* address of remote unit */ u_int8_t role; /* new connection role */ } __attribute__ ((packed)) ng_hci_role_change_ep; #define NG_HCI_EVENT_NUM_COMPL_PKTS 0x13 typedef struct { u_int8_t num_con_handles; /* # of connection handles */ /* these are repeated "num_con_handles" times u_int16_t con_handle; --- connection handle(s) u_int16_t compl_pkt; --- # of completed packets */ } __attribute__ ((packed)) ng_hci_num_compl_pkts_ep; #define NG_HCI_EVENT_MODE_CHANGE 0x14 typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t con_handle; /* connection handle */ u_int8_t unit_mode; /* remote unit mode */ u_int16_t interval; /* interval * 0.625 msec */ } __attribute__ ((packed)) ng_hci_mode_change_ep; #define NG_HCI_EVENT_RETURN_LINK_KEYS 0x15 typedef struct { u_int8_t num_keys; /* # of keys */ /* these are repeated "num_keys" times bdaddr_t bdaddr; --- remote address(es) u_int8_t key[NG_HCI_KEY_SIZE]; --- key(s) */ } __attribute__ ((packed)) ng_hci_return_link_keys_ep; #define NG_HCI_EVENT_PIN_CODE_REQ 0x16 typedef struct { bdaddr_t bdaddr; /* remote unit address */ } __attribute__ ((packed)) ng_hci_pin_code_req_ep; #define NG_HCI_EVENT_LINK_KEY_REQ 0x17 typedef struct { bdaddr_t bdaddr; /* remote unit address */ } __attribute__ ((packed)) ng_hci_link_key_req_ep; #define NG_HCI_EVENT_LINK_KEY_NOTIFICATION 0x18 typedef struct { bdaddr_t bdaddr; /* remote unit address */ u_int8_t key[NG_HCI_KEY_SIZE]; /* link key */ u_int8_t key_type; /* type of the key */ } __attribute__ ((packed)) ng_hci_link_key_notification_ep; #define NG_HCI_EVENT_LOOPBACK_COMMAND 0x19 typedef struct { u_int8_t command[0]; /* Command packet */ } __attribute__ ((packed)) ng_hci_loopback_command_ep; #define NG_HCI_EVENT_DATA_BUFFER_OVERFLOW 0x1a typedef struct { u_int8_t link_type; /* Link type */ } __attribute__ ((packed)) ng_hci_data_buffer_overflow_ep; #define NG_HCI_EVENT_MAX_SLOT_CHANGE 0x1b typedef struct { u_int16_t con_handle; /* connection handle */ u_int8_t lmp_max_slots; /* Max. # of slots allowed */ } __attribute__ ((packed)) ng_hci_max_slot_change_ep; #define NG_HCI_EVENT_READ_CLOCK_OFFSET_COMPL 0x1c typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t con_handle; /* Connection handle */ u_int16_t clock_offset; /* Clock offset */ } __attribute__ ((packed)) ng_hci_read_clock_offset_compl_ep; #define NG_HCI_EVENT_CON_PKT_TYPE_CHANGED 0x1d typedef struct { u_int8_t status; /* 0x00 - success */ u_int16_t con_handle; /* connection handle */ u_int16_t pkt_type; /* packet type */ } __attribute__ ((packed)) ng_hci_con_pkt_type_changed_ep; #define NG_HCI_EVENT_QOS_VIOLATION 0x1e typedef struct { u_int16_t con_handle; /* connection handle */ } __attribute__ ((packed)) ng_hci_qos_violation_ep; #define NG_HCI_EVENT_PAGE_SCAN_MODE_CHANGE 0x1f typedef struct { bdaddr_t bdaddr; /* destination address */ u_int8_t page_scan_mode; /* page scan mode */ } __attribute__ ((packed)) ng_hci_page_scan_mode_change_ep; #define NG_HCI_EVENT_PAGE_SCAN_REP_MODE_CHANGE 0x20 typedef struct { bdaddr_t bdaddr; /* destination address */ u_int8_t page_scan_rep_mode; /* page scan repetition mode */ } __attribute__ ((packed)) ng_hci_page_scan_rep_mode_change_ep; #define NG_HCI_EVENT_LE 0x3e typedef struct { u_int8_t subevent_code; }__attribute__ ((packed)) ng_hci_le_ep; #define NG_HCI_LEEV_CON_COMPL 0x01 typedef struct { u_int8_t status; u_int16_t handle; u_int8_t role; u_int8_t address_type; bdaddr_t address; u_int16_t interval; u_int8_t latency; u_int16_t supervision_timeout; u_int8_t master_clock_accracy; } __attribute__ ((packed)) ng_hci_le_connection_complete_ep; #define NG_HCI_LEEV_ADVREP 0x02 typedef struct { u_int8_t num_reports; }__attribute__ ((packed)) ng_hci_le_advertising_report_ep; #define NG_HCI_SCAN_RESPONSE_DATA_MAX 0x1f typedef struct { u_int8_t event_type; u_int8_t addr_type; bdaddr_t bdaddr; u_int8_t length_data; u_int8_t data[NG_HCI_SCAN_RESPONSE_DATA_MAX]; }__attribute__((packed)) ng_hci_le_advreport; #define NG_HCI_LEEV_CON_UPDATE_COMPL 0x03 typedef struct { u_int8_t status; u_int16_t connection_handle; u_int16_t conn_interval; u_int16_t conn_latency; u_int16_t supervision_timeout; }__attribute__((packed)) ng_hci_connection_update_complete_ep; #define NG_HCI_LEEV_READ_REMOTE_FEATURES_COMPL 0x04 //TBD #define NG_HCI_LEEV_LONG_TERM_KEY_REQUEST 0x05 //TBD #define NG_HCI_EVENT_BT_LOGO 0xfe #define NG_HCI_EVENT_VENDOR 0xff #endif /* ndef _NETGRAPH_HCI_H_ */ Index: head/sys/netgraph/bluetooth/include/ng_l2cap.h =================================================================== --- head/sys/netgraph/bluetooth/include/ng_l2cap.h (revision 298812) +++ head/sys/netgraph/bluetooth/include/ng_l2cap.h (revision 298813) @@ -1,706 +1,706 @@ /* * ng_l2cap.h */ /*- * Copyright (c) Maksim Yevmenkin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id: ng_l2cap.h,v 1.2 2003/04/27 00:52:26 max Exp $ * $FreeBSD$ */ /* * This file contains everything that application needs to know about * Link Layer Control and Adaptation Protocol (L2CAP). All information * was obtained from Bluetooth Specification Book v1.1. * * This file can be included by both kernel and userland applications. */ #ifndef _NETGRAPH_L2CAP_H_ #define _NETGRAPH_L2CAP_H_ /************************************************************************** ************************************************************************** ** Netgraph node hook name, type name and type cookie and commands ************************************************************************** **************************************************************************/ /* Netgraph node hook names */ #define NG_L2CAP_HOOK_HCI "hci" /* HCI <-> L2CAP */ #define NG_L2CAP_HOOK_L2C "l2c" /* L2CAP <-> Upper */ #define NG_L2CAP_HOOK_CTL "ctl" /* L2CAP <-> User */ /* Node type name and type cookie */ #define NG_L2CAP_NODE_TYPE "l2cap" #define NGM_L2CAP_COOKIE 1000774185 /************************************************************************** ************************************************************************** ** Common defines and types (L2CAP) ************************************************************************** **************************************************************************/ /* * Channel IDs are assigned relative to the instance of L2CAP node, i.e. * relative to the unit. So the total number of channels that unit can have * open at the same time is 0xffff - 0x0040 = 0xffbf (65471). This number * does not depend on number of connections. */ #define NG_L2CAP_NULL_CID 0x0000 /* DO NOT USE THIS CID */ #define NG_L2CAP_SIGNAL_CID 0x0001 /* signaling channel ID */ #define NG_L2CAP_CLT_CID 0x0002 /* connectionless channel ID */ #define NG_L2CAP_A2MP_CID 0x0003 #define NG_L2CAP_ATT_CID 0x0004 #define NG_L2CAP_LESIGNAL_CID 0x0005 #define NG_L2CAP_SMP_CID 0x0006 /* 0x0007 - 0x003f Reserved */ #define NG_L2CAP_FIRST_CID 0x0040 /* dynamically alloc. (start) */ #define NG_L2CAP_LAST_CID 0xffff /* dynamically alloc. (end) */ #define NG_L2CAP_LELAST_CID 0x007f /* L2CAP MTU */ #define NG_L2CAP_MTU_LE_MINIMAM 23 #define NG_L2CAP_MTU_MINIMUM 48 #define NG_L2CAP_MTU_DEFAULT 672 #define NG_L2CAP_MTU_MAXIMUM 0xffff /* L2CAP flush and link timeouts */ #define NG_L2CAP_FLUSH_TIMO_DEFAULT 0xffff /* always retransmit */ #define NG_L2CAP_LINK_TIMO_DEFAULT 0xffff /* L2CAP Command Reject reasons */ #define NG_L2CAP_REJ_NOT_UNDERSTOOD 0x0000 #define NG_L2CAP_REJ_MTU_EXCEEDED 0x0001 #define NG_L2CAP_REJ_INVALID_CID 0x0002 /* 0x0003 - 0xffff - reserved for future use */ /* Protocol/Service Multioplexor (PSM) values */ #define NG_L2CAP_PSM_ANY 0x0000 /* Any/Invalid PSM */ #define NG_L2CAP_PSM_SDP 0x0001 /* Service Discovery Protocol */ #define NG_L2CAP_PSM_RFCOMM 0x0003 /* RFCOMM protocol */ #define NG_L2CAP_PSM_TCP 0x0005 /* Telephony Control Protocol */ /* 0x0006 - 0x1000 - reserved for future use */ /* L2CAP Connection response command result codes */ #define NG_L2CAP_SUCCESS 0x0000 #define NG_L2CAP_PENDING 0x0001 #define NG_L2CAP_PSM_NOT_SUPPORTED 0x0002 #define NG_L2CAP_SEQUIRY_BLOCK 0x0003 #define NG_L2CAP_NO_RESOURCES 0x0004 #define NG_L2CAP_TIMEOUT 0xeeee #define NG_L2CAP_UNKNOWN 0xffff /* 0x0005 - 0xffff - reserved for future use */ /* L2CAP Connection response status codes */ #define NG_L2CAP_NO_INFO 0x0000 #define NG_L2CAP_AUTH_PENDING 0x0001 #define NG_L2CAP_AUTZ_PENDING 0x0002 /* 0x0003 - 0xffff - reserved for future use */ /* L2CAP Configuration response result codes */ #define NG_L2CAP_UNACCEPTABLE_PARAMS 0x0001 #define NG_L2CAP_REJECT 0x0002 #define NG_L2CAP_UNKNOWN_OPTION 0x0003 /* 0x0003 - 0xffff - reserved for future use */ /* L2CAP Configuration options */ #define NG_L2CAP_OPT_CFLAG_BIT 0x0001 #define NG_L2CAP_OPT_CFLAG(flags) ((flags) & NG_L2CAP_OPT_CFLAG_BIT) #define NG_L2CAP_OPT_HINT_BIT 0x80 #define NG_L2CAP_OPT_HINT(type) ((type) & NG_L2CAP_OPT_HINT_BIT) #define NG_L2CAP_OPT_HINT_MASK 0x7f #define NG_L2CAP_OPT_MTU 0x01 #define NG_L2CAP_OPT_MTU_SIZE sizeof(u_int16_t) #define NG_L2CAP_OPT_FLUSH_TIMO 0x02 #define NG_L2CAP_OPT_FLUSH_TIMO_SIZE sizeof(u_int16_t) #define NG_L2CAP_OPT_QOS 0x03 #define NG_L2CAP_OPT_QOS_SIZE sizeof(ng_l2cap_flow_t) /* 0x4 - 0xff - reserved for future use */ /* L2CAP Information request type codes */ #define NG_L2CAP_CONNLESS_MTU 0x0001 /* 0x0002 - 0xffff - reserved for future use */ /* L2CAP Information response codes */ #define NG_L2CAP_NOT_SUPPORTED 0x0001 /* 0x0002 - 0xffff - reserved for future use */ /* L2CAP flow control */ typedef struct { u_int8_t flags; /* reserved for future use */ u_int8_t service_type; /* service type */ u_int32_t token_rate; /* bytes per second */ u_int32_t token_bucket_size; /* bytes */ u_int32_t peak_bandwidth; /* bytes per second */ u_int32_t latency; /* microseconds */ u_int32_t delay_variation; /* microseconds */ } __attribute__ ((packed)) ng_l2cap_flow_t; typedef ng_l2cap_flow_t * ng_l2cap_flow_p; /************************************************************************** ************************************************************************** ** Link level defines, headers and types ************************************************************************** **************************************************************************/ /* L2CAP header */ typedef struct { u_int16_t length; /* payload size */ u_int16_t dcid; /* destination channel ID */ } __attribute__ ((packed)) ng_l2cap_hdr_t; /* L2CAP ConnectionLess Traffic (CLT) (if destination cid == 0x2) */ typedef struct { u_int16_t psm; /* Protocol/Service Multiplexor */ } __attribute__ ((packed)) ng_l2cap_clt_hdr_t; #define NG_L2CAP_CLT_MTU_MAXIMUM \ (NG_L2CAP_MTU_MAXIMUM - sizeof(ng_l2cap_clt_hdr_t)) /* L2CAP command header */ typedef struct { u_int8_t code; /* command OpCode */ u_int8_t ident; /* identifier to match request and response */ u_int16_t length; /* command parameters length */ } __attribute__ ((packed)) ng_l2cap_cmd_hdr_t; /* L2CAP Command Reject */ #define NG_L2CAP_CMD_REJ 0x01 typedef struct { u_int16_t reason; /* reason to reject command */ /* u_int8_t data[]; -- optional data (depends on reason) */ } __attribute__ ((packed)) ng_l2cap_cmd_rej_cp; /* CommandReject data */ typedef union { /* NG_L2CAP_REJ_MTU_EXCEEDED */ struct { u_int16_t mtu; /* actual signaling MTU */ } __attribute__ ((packed)) mtu; /* NG_L2CAP_REJ_INVALID_CID */ struct { u_int16_t scid; /* local CID */ u_int16_t dcid; /* remote CID */ } __attribute__ ((packed)) cid; } ng_l2cap_cmd_rej_data_t; typedef ng_l2cap_cmd_rej_data_t * ng_l2cap_cmd_rej_data_p; /* L2CAP Connection Request */ #define NG_L2CAP_CON_REQ 0x02 typedef struct { u_int16_t psm; /* Protocol/Service Multiplexor (PSM) */ u_int16_t scid; /* source channel ID */ } __attribute__ ((packed)) ng_l2cap_con_req_cp; /* L2CAP Connection Response */ #define NG_L2CAP_CON_RSP 0x03 typedef struct { u_int16_t dcid; /* destination channel ID */ u_int16_t scid; /* source channel ID */ u_int16_t result; /* 0x00 - success */ u_int16_t status; /* more info if result != 0x00 */ } __attribute__ ((packed)) ng_l2cap_con_rsp_cp; /* L2CAP Configuration Request */ #define NG_L2CAP_CFG_REQ 0x04 typedef struct { u_int16_t dcid; /* destination channel ID */ u_int16_t flags; /* flags */ /* u_int8_t options[] -- options */ } __attribute__ ((packed)) ng_l2cap_cfg_req_cp; /* L2CAP Configuration Response */ #define NG_L2CAP_CFG_RSP 0x05 typedef struct { u_int16_t scid; /* source channel ID */ u_int16_t flags; /* flags */ u_int16_t result; /* 0x00 - success */ /* u_int8_t options[] -- options */ } __attribute__ ((packed)) ng_l2cap_cfg_rsp_cp; /* L2CAP configuration option */ typedef struct { u_int8_t type; u_int8_t length; /* u_int8_t value[] -- option value (depends on type) */ } __attribute__ ((packed)) ng_l2cap_cfg_opt_t; typedef ng_l2cap_cfg_opt_t * ng_l2cap_cfg_opt_p; /* L2CAP configuration option value */ typedef union { u_int16_t mtu; /* NG_L2CAP_OPT_MTU */ u_int16_t flush_timo; /* NG_L2CAP_OPT_FLUSH_TIMO */ ng_l2cap_flow_t flow; /* NG_L2CAP_OPT_QOS */ uint16_t encryption; } ng_l2cap_cfg_opt_val_t; typedef ng_l2cap_cfg_opt_val_t * ng_l2cap_cfg_opt_val_p; /* L2CAP Disconnect Request */ #define NG_L2CAP_DISCON_REQ 0x06 typedef struct { u_int16_t dcid; /* destination channel ID */ u_int16_t scid; /* source channel ID */ } __attribute__ ((packed)) ng_l2cap_discon_req_cp; /* L2CAP Disconnect Response */ #define NG_L2CAP_DISCON_RSP 0x07 typedef ng_l2cap_discon_req_cp ng_l2cap_discon_rsp_cp; /* L2CAP Echo Request */ #define NG_L2CAP_ECHO_REQ 0x08 /* No command parameters, only optional data */ /* L2CAP Echo Response */ #define NG_L2CAP_ECHO_RSP 0x09 #define NG_L2CAP_MAX_ECHO_SIZE \ (NG_L2CAP_MTU_MAXIMUM - sizeof(ng_l2cap_cmd_hdr_t)) /* No command parameters, only optional data */ /* L2CAP Information Request */ #define NG_L2CAP_INFO_REQ 0x0a typedef struct { u_int16_t type; /* requested information type */ } __attribute__ ((packed)) ng_l2cap_info_req_cp; /* L2CAP Information Response */ #define NG_L2CAP_INFO_RSP 0x0b typedef struct { u_int16_t type; /* requested information type */ u_int16_t result; /* 0x00 - success */ /* u_int8_t info[] -- info data (depends on type) * * NG_L2CAP_CONNLESS_MTU - 2 bytes connectionless MTU */ } __attribute__ ((packed)) ng_l2cap_info_rsp_cp; typedef union { /* NG_L2CAP_CONNLESS_MTU */ struct { u_int16_t mtu; } __attribute__ ((packed)) mtu; } ng_l2cap_info_rsp_data_t; typedef ng_l2cap_info_rsp_data_t * ng_l2cap_info_rsp_data_p; #define NG_L2CAP_CMD_PARAM_UPDATE_REQUEST 0x12 typedef struct { uint16_t interval_min; uint16_t interval_max; uint16_t slave_latency; uint16_t timeout_mpl; } __attribute__ ((packed)) ng_l2cap_param_update_req_cp; #define NG_L2CAP_CMD_PARAM_UPDATE_RESPONSE 0x13 #define NG_L2CAP_UPDATE_PARAM_ACCEPT 0 #define NG_L2CAP_UPDATE_PARAM_REJECT 1 //typedef uint16_t update_response; /************************************************************************** ************************************************************************** ** Upper layer protocol interface. L2CA_xxx messages ************************************************************************** **************************************************************************/ /* * NOTE! NOTE! NOTE! * * Bluetooth specification says that L2CA_xxx request must block until * response is ready. We are not allowed to block in Netgraph, so we * need to queue request and save some information that can be used * later and help match request and response. * * The idea is to use "token" field from Netgraph message header. The * upper layer protocol _MUST_ populate "token". L2CAP will queue request * (using L2CAP command descriptor) and start processing. Later, when * response is ready or timeout has occur L2CAP layer will create new * Netgraph message, set "token" and RESP flag and send the message to * the upper layer protocol. * * L2CA_xxx_Ind messages _WILL_NOT_ populate "token" and _WILL_NOT_ * set RESP flag. There is no reason for this, because they are just * notifications and do not require acknowlegment. * * NOTE: This is _NOT_ what NG_MKRESPONSE and NG_RESPOND_MSG do, however * it is somewhat similar. */ /* L2CA data packet header */ typedef struct { u_int32_t token; /* token to use in L2CAP_L2CA_WRITE */ u_int16_t length; /* length of the data */ u_int16_t lcid; /* local channel ID */ uint16_t idtype; } __attribute__ ((packed)) ng_l2cap_l2ca_hdr_t; #define NG_L2CAP_L2CA_IDTYPE_BREDR 0 #define NG_L2CAP_L2CA_IDTYPE_ATT 1 #define NG_L2CAP_L2CA_IDTYPE_LE 2 #define NG_L2CAP_L2CA_IDTYPE_SMP 3 /* L2CA_Connect */ #define NGM_L2CAP_L2CA_CON 0x80 /* Upper -> L2CAP */ typedef struct { u_int16_t psm; /* Protocol/Service Multiplexor */ bdaddr_t bdaddr; /* remote unit address */ uint8_t linktype; uint8_t idtype; } ng_l2cap_l2ca_con_ip; /* L2CAP -> Upper */ typedef struct { u_int16_t lcid; /* local channel ID */ uint16_t idtype; /*ID type*/ u_int16_t result; /* 0x00 - success */ u_int16_t status; /* if result != 0x00 */ uint8_t encryption; } ng_l2cap_l2ca_con_op; /* L2CA_ConnectInd */ #define NGM_L2CAP_L2CA_CON_IND 0x81 /* L2CAP -> Upper */ typedef struct { bdaddr_t bdaddr; /* remote unit address */ u_int16_t lcid; /* local channel ID */ u_int16_t psm; /* Procotol/Service Multiplexor */ - u_int8_t ident; /* indentifier */ + u_int8_t ident; /* identifier */ u_int8_t linktype; /* link type*/ } ng_l2cap_l2ca_con_ind_ip; /* No output parameters */ /* L2CA_ConnectRsp */ #define NGM_L2CAP_L2CA_CON_RSP 0x82 /* Upper -> L2CAP */ typedef struct { bdaddr_t bdaddr; /* remote unit address */ u_int8_t ident; /* "ident" from L2CAP_ConnectInd event */ u_int8_t linktype; /*link type */ u_int16_t lcid; /* local channel ID */ u_int16_t result; /* 0x00 - success */ u_int16_t status; /* if response != 0x00 */ } ng_l2cap_l2ca_con_rsp_ip; /* L2CAP -> Upper */ typedef struct { u_int16_t result; /* 0x00 - success */ } ng_l2cap_l2ca_con_rsp_op; /* L2CA_Config */ #define NGM_L2CAP_L2CA_CFG 0x83 /* Upper -> L2CAP */ typedef struct { u_int16_t lcid; /* local channel ID */ u_int16_t imtu; /* receiving MTU for the local channel */ ng_l2cap_flow_t oflow; /* out flow */ u_int16_t flush_timo; /* flush timeout (msec) */ u_int16_t link_timo; /* link timeout (msec) */ } ng_l2cap_l2ca_cfg_ip; /* L2CAP -> Upper */ typedef struct { u_int16_t result; /* 0x00 - success */ u_int16_t imtu; /* sending MTU for the remote channel */ ng_l2cap_flow_t oflow; /* out flow */ u_int16_t flush_timo; /* flush timeout (msec) */ } ng_l2cap_l2ca_cfg_op; /* L2CA_ConfigRsp */ #define NGM_L2CAP_L2CA_CFG_RSP 0x84 /* Upper -> L2CAP */ typedef struct { u_int16_t lcid; /* local channel ID */ u_int16_t omtu; /* sending MTU for the local channel */ ng_l2cap_flow_t iflow; /* in FLOW */ } ng_l2cap_l2ca_cfg_rsp_ip; /* L2CAP -> Upper */ typedef struct { u_int16_t result; /* 0x00 - sucsess */ } ng_l2cap_l2ca_cfg_rsp_op; /* L2CA_ConfigInd */ #define NGM_L2CAP_L2CA_CFG_IND 0x85 /* L2CAP -> Upper */ typedef struct { u_int16_t lcid; /* local channel ID */ u_int16_t omtu; /* outgoing MTU for the local channel */ ng_l2cap_flow_t iflow; /* in flow */ u_int16_t flush_timo; /* flush timeout (msec) */ } ng_l2cap_l2ca_cfg_ind_ip; /* No output parameters */ /* L2CA_QoSViolationInd */ #define NGM_L2CAP_L2CA_QOS_IND 0x86 /* L2CAP -> Upper */ typedef struct { bdaddr_t bdaddr; /* remote unit address */ } ng_l2cap_l2ca_qos_ind_ip; /* No output parameters */ /* L2CA_Disconnect */ #define NGM_L2CAP_L2CA_DISCON 0x87 /* Upper -> L2CAP */ typedef struct { u_int16_t lcid; /* local channel ID */ u_int16_t idtype; } ng_l2cap_l2ca_discon_ip; /* L2CAP -> Upper */ typedef struct { u_int16_t result; /* 0x00 - sucsess */ } ng_l2cap_l2ca_discon_op; /* L2CA_DisconnectInd */ #define NGM_L2CAP_L2CA_DISCON_IND 0x88 /* L2CAP -> Upper */ typedef ng_l2cap_l2ca_discon_ip ng_l2cap_l2ca_discon_ind_ip; /* No output parameters */ /* L2CA_Write response */ #define NGM_L2CAP_L2CA_WRITE 0x89 /* No input parameters */ /* L2CAP -> Upper */ typedef struct { int result; /* result (0x00 - success) */ u_int16_t length; /* amount of data written */ u_int16_t lcid; /* local channel ID */ uint16_t idtype; } ng_l2cap_l2ca_write_op; /* L2CA_GroupCreate */ #define NGM_L2CAP_L2CA_GRP_CREATE 0x8a /* Upper -> L2CAP */ typedef struct { u_int16_t psm; /* Protocol/Service Multiplexor */ } ng_l2cap_l2ca_grp_create_ip; /* L2CAP -> Upper */ typedef struct { u_int16_t lcid; /* local group channel ID */ } ng_l2cap_l2ca_grp_create_op; /* L2CA_GroupClose */ #define NGM_L2CAP_L2CA_GRP_CLOSE 0x8b /* Upper -> L2CAP */ typedef struct { u_int16_t lcid; /* local group channel ID */ } ng_l2cap_l2ca_grp_close_ip; #if 0 /* L2CAP -> Upper */ * typedef struct { * u_int16_t result; /* 0x00 - success */ * } ng_l2cap_l2ca_grp_close_op; #endif /* L2CA_GroupAddMember */ #define NGM_L2CAP_L2CA_GRP_ADD_MEMBER 0x8c /* Upper -> L2CAP */ typedef struct { u_int16_t lcid; /* local group channel ID */ bdaddr_t bdaddr; /* remote unit address */ } ng_l2cap_l2ca_grp_add_member_ip; /* L2CAP -> Upper */ typedef struct { u_int16_t result; /* 0x00 - success */ } ng_l2cap_l2ca_grp_add_member_op; /* L2CA_GroupRemoveMember */ #define NGM_L2CAP_L2CA_GRP_REM_MEMBER 0x8d /* Upper -> L2CAP */ typedef ng_l2cap_l2ca_grp_add_member_ip ng_l2cap_l2ca_grp_rem_member_ip; /* L2CAP -> Upper */ #if 0 * typedef ng_l2cap_l2ca_grp_add_member_op ng_l2cap_l2ca_grp_rem_member_op; #endif /* L2CA_GroupMembeship */ #define NGM_L2CAP_L2CA_GRP_MEMBERSHIP 0x8e /* Upper -> L2CAP */ typedef struct { u_int16_t lcid; /* local group channel ID */ } ng_l2cap_l2ca_grp_get_members_ip; /* L2CAP -> Upper */ typedef struct { u_int16_t result; /* 0x00 - success */ u_int16_t nmembers; /* number of group members */ /* bdaddr_t members[] -- group memebers */ } ng_l2cap_l2ca_grp_get_members_op; /* L2CA_Ping */ #define NGM_L2CAP_L2CA_PING 0x8f /* Upper -> L2CAP */ typedef struct { bdaddr_t bdaddr; /* remote unit address */ u_int16_t echo_size; /* size of echo data in bytes */ /* u_int8_t echo_data[] -- echo data */ } ng_l2cap_l2ca_ping_ip; /* L2CAP -> Upper */ typedef struct { u_int16_t result; /* 0x00 - success */ bdaddr_t bdaddr; /* remote unit address */ u_int16_t echo_size; /* size of echo data in bytes */ /* u_int8_t echo_data[] -- echo data */ } ng_l2cap_l2ca_ping_op; /* L2CA_GetInfo */ #define NGM_L2CAP_L2CA_GET_INFO 0x90 /* Upper -> L2CAP */ typedef struct { bdaddr_t bdaddr; /* remote unit address */ u_int16_t info_type; /* info type */ uint8_t linktype; uint8_t unused; } ng_l2cap_l2ca_get_info_ip; /* L2CAP -> Upper */ typedef struct { u_int16_t result; /* 0x00 - success */ u_int16_t info_size; /* size of info data in bytes */ /* u_int8_t info_data[] -- info data */ } ng_l2cap_l2ca_get_info_op; /* L2CA_EnableCLT/L2CA_DisableCLT */ #define NGM_L2CAP_L2CA_ENABLE_CLT 0x91 /* Upper -> L2CAP */ typedef struct { u_int16_t psm; /* Protocol/Service Multiplexor */ u_int16_t enable; /* 0x00 - disable */ } ng_l2cap_l2ca_enable_clt_ip; #if 0 /* L2CAP -> Upper */ * typedef struct { * u_int16_t result; /* 0x00 - success */ * } ng_l2cap_l2ca_enable_clt_op; #endif #define NGM_L2CAP_L2CA_ENC_CHANGE 0x92 typedef struct { uint16_t lcid; uint16_t result; uint8_t idtype; } ng_l2cap_l2ca_enc_chg_op; /************************************************************************** ************************************************************************** ** L2CAP node messages ************************************************************************** **************************************************************************/ /* L2CAP connection states */ #define NG_L2CAP_CON_CLOSED 0 /* connection closed */ #define NG_L2CAP_W4_LP_CON_CFM 1 /* waiting... */ #define NG_L2CAP_CON_OPEN 2 /* connection open */ /* L2CAP channel states */ #define NG_L2CAP_CLOSED 0 /* channel closed */ #define NG_L2CAP_W4_L2CAP_CON_RSP 1 /* wait for L2CAP resp. */ #define NG_L2CAP_W4_L2CA_CON_RSP 2 /* wait for upper resp. */ #define NG_L2CAP_CONFIG 3 /* L2CAP configuration */ #define NG_L2CAP_OPEN 4 /* channel open */ #define NG_L2CAP_W4_L2CAP_DISCON_RSP 5 /* wait for L2CAP discon. */ #define NG_L2CAP_W4_L2CA_DISCON_RSP 6 /* wait for upper discon. */ /* Node flags */ #define NG_L2CAP_CLT_SDP_DISABLED (1 << 0) /* disable SDP CLT */ #define NG_L2CAP_CLT_RFCOMM_DISABLED (1 << 1) /* disable RFCOMM CLT */ #define NG_L2CAP_CLT_TCP_DISABLED (1 << 2) /* disable TCP CLT */ /* Debug levels */ #define NG_L2CAP_ALERT_LEVEL 1 #define NG_L2CAP_ERR_LEVEL 2 #define NG_L2CAP_WARN_LEVEL 3 #define NG_L2CAP_INFO_LEVEL 4 /* Get node flags (see flags above) */ #define NGM_L2CAP_NODE_GET_FLAGS 0x400 /* L2CAP -> User */ typedef u_int16_t ng_l2cap_node_flags_ep; /* Get/Set debug level (see levels above) */ #define NGM_L2CAP_NODE_GET_DEBUG 0x401 /* L2CAP -> User */ #define NGM_L2CAP_NODE_SET_DEBUG 0x402 /* User -> L2CAP */ typedef u_int16_t ng_l2cap_node_debug_ep; #define NGM_L2CAP_NODE_HOOK_INFO 0x409 /* L2CAP -> Upper */ typedef struct { bdaddr_t addr; }ng_l2cap_node_hook_info_ep; #define NGM_L2CAP_NODE_GET_CON_LIST 0x40a /* L2CAP -> User */ typedef struct { u_int32_t num_connections; /* number of connections */ } ng_l2cap_node_con_list_ep; /* Connection flags */ #define NG_L2CAP_CON_TX (1 << 0) /* sending data */ #define NG_L2CAP_CON_RX (1 << 1) /* receiving data */ #define NG_L2CAP_CON_OUTGOING (1 << 2) /* outgoing connection */ #define NG_L2CAP_CON_LP_TIMO (1 << 3) /* LP timeout */ #define NG_L2CAP_CON_AUTO_DISCON_TIMO (1 << 4) /* auto discon. timeout */ #define NG_L2CAP_CON_DYING (1 << 5) /* connection is dying */ typedef struct { u_int8_t state; /* connection state */ u_int8_t flags; /* flags */ int16_t pending; /* num. pending packets */ u_int16_t con_handle; /* connection handle */ bdaddr_t remote; /* remote bdaddr */ } ng_l2cap_node_con_ep; #define NG_L2CAP_MAX_CON_NUM \ ((0xffff - sizeof(ng_l2cap_node_con_list_ep))/sizeof(ng_l2cap_node_con_ep)) #define NGM_L2CAP_NODE_GET_CHAN_LIST 0x40b /* L2CAP -> User */ typedef struct { u_int32_t num_channels; /* number of channels */ } ng_l2cap_node_chan_list_ep; typedef struct { u_int32_t state; /* channel state */ u_int16_t scid; /* source (local) channel ID */ u_int16_t dcid; /* destination (remote) channel ID */ - u_int16_t imtu; /* incomming MTU */ + u_int16_t imtu; /* incoming MTU */ u_int16_t omtu; /* outgoing MTU */ u_int16_t psm; /* PSM */ bdaddr_t remote; /* remote bdaddr */ } ng_l2cap_node_chan_ep; #define NG_L2CAP_MAX_CHAN_NUM \ ((0xffff - sizeof(ng_l2cap_node_chan_list_ep))/sizeof(ng_l2cap_node_chan_ep)) #define NGM_L2CAP_NODE_GET_AUTO_DISCON_TIMO 0x40c /* L2CAP -> User */ #define NGM_L2CAP_NODE_SET_AUTO_DISCON_TIMO 0x40d /* User -> L2CAP */ typedef u_int16_t ng_l2cap_node_auto_discon_ep; #endif /* ndef _NETGRAPH_L2CAP_H_ */ Index: head/sys/netgraph/bluetooth/l2cap/ng_l2cap_cmds.c =================================================================== --- head/sys/netgraph/bluetooth/l2cap/ng_l2cap_cmds.c (revision 298812) +++ head/sys/netgraph/bluetooth/l2cap/ng_l2cap_cmds.c (revision 298813) @@ -1,411 +1,411 @@ /* * ng_l2cap_cmds.c */ /*- * Copyright (c) Maksim Yevmenkin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id: ng_l2cap_cmds.c,v 1.2 2003/09/08 19:11:45 max Exp $ * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /****************************************************************************** ****************************************************************************** ** L2CAP commands processing module ****************************************************************************** ******************************************************************************/ /* * Process L2CAP command queue on connection */ void ng_l2cap_con_wakeup(ng_l2cap_con_p con) { ng_l2cap_cmd_p cmd = NULL; struct mbuf *m = NULL; int error = 0; /* Find first non-pending command in the queue */ TAILQ_FOREACH(cmd, &con->cmd_list, next) { KASSERT((cmd->con == con), ("%s: %s - invalid connection pointer!\n", __func__, NG_NODE_NAME(con->l2cap->node))); if (!(cmd->flags & NG_L2CAP_CMD_PENDING)) break; } if (cmd == NULL) return; /* Detach command packet */ m = cmd->aux; cmd->aux = NULL; /* Process command */ switch (cmd->code) { case NG_L2CAP_DISCON_RSP: case NG_L2CAP_ECHO_RSP: case NG_L2CAP_INFO_RSP: /* * Do not check return ng_l2cap_lp_send() value, because * in these cases we do not really have a graceful way out. * ECHO and INFO responses are internal to the stack and not * visible to user. REJect is just being nice to remote end * (otherwise remote end will timeout anyway). DISCON is * probably most interesting here, however, if it fails * there is nothing we can do anyway. */ (void) ng_l2cap_lp_send(con, NG_L2CAP_SIGNAL_CID, m); ng_l2cap_unlink_cmd(cmd); ng_l2cap_free_cmd(cmd); break; case NG_L2CAP_CMD_REJ: (void) ng_l2cap_lp_send(con, (con->linktype == NG_HCI_LINK_ACL)? NG_L2CAP_SIGNAL_CID: NG_L2CAP_LESIGNAL_CID , m); ng_l2cap_unlink_cmd(cmd); ng_l2cap_free_cmd(cmd); break; case NG_L2CAP_CON_REQ: error = ng_l2cap_lp_send(con, NG_L2CAP_SIGNAL_CID, m); if (error != 0) { ng_l2cap_l2ca_con_rsp(cmd->ch, cmd->token, NG_L2CAP_NO_RESOURCES, 0); ng_l2cap_free_chan(cmd->ch); /* will free commands */ } else ng_l2cap_command_timeout(cmd, bluetooth_l2cap_rtx_timeout()); break; case NG_L2CAP_CON_RSP: error = ng_l2cap_lp_send(con, NG_L2CAP_SIGNAL_CID, m); ng_l2cap_unlink_cmd(cmd); if (cmd->ch != NULL) { ng_l2cap_l2ca_con_rsp_rsp(cmd->ch, cmd->token, (error == 0)? NG_L2CAP_SUCCESS : NG_L2CAP_NO_RESOURCES); if (error != 0) ng_l2cap_free_chan(cmd->ch); } ng_l2cap_free_cmd(cmd); break; case NG_L2CAP_CFG_REQ: error = ng_l2cap_lp_send(con, NG_L2CAP_SIGNAL_CID, m); if (error != 0) { ng_l2cap_l2ca_cfg_rsp(cmd->ch, cmd->token, NG_L2CAP_NO_RESOURCES); ng_l2cap_unlink_cmd(cmd); ng_l2cap_free_cmd(cmd); } else ng_l2cap_command_timeout(cmd, bluetooth_l2cap_rtx_timeout()); break; case NG_L2CAP_CFG_RSP: error = ng_l2cap_lp_send(con, NG_L2CAP_SIGNAL_CID, m); ng_l2cap_unlink_cmd(cmd); if (cmd->ch != NULL) ng_l2cap_l2ca_cfg_rsp_rsp(cmd->ch, cmd->token, (error == 0)? NG_L2CAP_SUCCESS : NG_L2CAP_NO_RESOURCES); ng_l2cap_free_cmd(cmd); break; case NG_L2CAP_DISCON_REQ: error = ng_l2cap_lp_send(con, NG_L2CAP_SIGNAL_CID, m); ng_l2cap_l2ca_discon_rsp(cmd->ch, cmd->token, (error == 0)? NG_L2CAP_SUCCESS : NG_L2CAP_NO_RESOURCES); if (error != 0) ng_l2cap_free_chan(cmd->ch); /* XXX free channel */ else ng_l2cap_command_timeout(cmd, bluetooth_l2cap_rtx_timeout()); break; case NG_L2CAP_ECHO_REQ: error = ng_l2cap_lp_send(con, NG_L2CAP_SIGNAL_CID, m); if (error != 0) { ng_l2cap_l2ca_ping_rsp(con, cmd->token, NG_L2CAP_NO_RESOURCES, NULL); ng_l2cap_unlink_cmd(cmd); ng_l2cap_free_cmd(cmd); } else ng_l2cap_command_timeout(cmd, bluetooth_l2cap_rtx_timeout()); break; case NG_L2CAP_INFO_REQ: error = ng_l2cap_lp_send(con, NG_L2CAP_SIGNAL_CID, m); if (error != 0) { ng_l2cap_l2ca_get_info_rsp(con, cmd->token, NG_L2CAP_NO_RESOURCES, NULL); ng_l2cap_unlink_cmd(cmd); ng_l2cap_free_cmd(cmd); } else ng_l2cap_command_timeout(cmd, bluetooth_l2cap_rtx_timeout()); break; case NGM_L2CAP_L2CA_WRITE: { int length = m->m_pkthdr.len; if (cmd->ch->dcid == NG_L2CAP_CLT_CID) { m = ng_l2cap_prepend(m, sizeof(ng_l2cap_clt_hdr_t)); if (m == NULL) error = ENOBUFS; else mtod(m, ng_l2cap_clt_hdr_t *)->psm = htole16(cmd->ch->psm); } if (error == 0) error = ng_l2cap_lp_send(con, cmd->ch->dcid, m); ng_l2cap_l2ca_write_rsp(cmd->ch, cmd->token, (error == 0)? NG_L2CAP_SUCCESS : NG_L2CAP_NO_RESOURCES, length); ng_l2cap_unlink_cmd(cmd); ng_l2cap_free_cmd(cmd); } break; case NG_L2CAP_CMD_PARAM_UPDATE_RESPONSE: error = ng_l2cap_lp_send(con, NG_L2CAP_LESIGNAL_CID, m); ng_l2cap_unlink_cmd(cmd); ng_l2cap_free_cmd(cmd); break; case NG_L2CAP_CMD_PARAM_UPDATE_REQUEST: /*TBD.*/ /* XXX FIXME add other commands */ default: panic( "%s: %s - unknown command code=%d\n", __func__, NG_NODE_NAME(con->l2cap->node), cmd->code); break; } } /* ng_l2cap_con_wakeup */ /* * We have failed to open ACL connection to the remote unit. Could be negative * confirmation or timeout. So fail any "delayed" commands, notify upper layer, * remove all channels and remove connection descriptor. */ void ng_l2cap_con_fail(ng_l2cap_con_p con, u_int16_t result) { ng_l2cap_p l2cap = con->l2cap; ng_l2cap_cmd_p cmd = NULL; ng_l2cap_chan_p ch = NULL; NG_L2CAP_INFO( "%s: %s - ACL connection failed, result=%d\n", __func__, NG_NODE_NAME(l2cap->node), result); /* Connection is dying */ con->flags |= NG_L2CAP_CON_DYING; /* Clean command queue */ while (!TAILQ_EMPTY(&con->cmd_list)) { cmd = TAILQ_FIRST(&con->cmd_list); ng_l2cap_unlink_cmd(cmd); if(cmd->flags & NG_L2CAP_CMD_PENDING) ng_l2cap_command_untimeout(cmd); KASSERT((cmd->con == con), ("%s: %s - invalid connection pointer!\n", __func__, NG_NODE_NAME(l2cap->node))); switch (cmd->code) { case NG_L2CAP_CMD_REJ: case NG_L2CAP_DISCON_RSP: case NG_L2CAP_ECHO_RSP: case NG_L2CAP_INFO_RSP: case NG_L2CAP_CMD_PARAM_UPDATE_RESPONSE: break; case NG_L2CAP_CON_REQ: ng_l2cap_l2ca_con_rsp(cmd->ch, cmd->token, result, 0); break; case NG_L2CAP_CON_RSP: if (cmd->ch != NULL) ng_l2cap_l2ca_con_rsp_rsp(cmd->ch, cmd->token, result); break; case NG_L2CAP_CFG_REQ: case NG_L2CAP_CFG_RSP: case NGM_L2CAP_L2CA_WRITE: ng_l2cap_l2ca_discon_ind(cmd->ch); break; case NG_L2CAP_DISCON_REQ: ng_l2cap_l2ca_discon_rsp(cmd->ch, cmd->token, NG_L2CAP_SUCCESS); break; case NG_L2CAP_ECHO_REQ: ng_l2cap_l2ca_ping_rsp(cmd->con, cmd->token, result, NULL); break; case NG_L2CAP_INFO_REQ: ng_l2cap_l2ca_get_info_rsp(cmd->con, cmd->token, result, NULL); break; /* XXX FIXME add other commands */ default: panic( "%s: %s - unexpected command code=%d\n", __func__, NG_NODE_NAME(l2cap->node), cmd->code); break; } if (cmd->ch != NULL) ng_l2cap_free_chan(cmd->ch); ng_l2cap_free_cmd(cmd); } /* * There still might be channels (in OPEN state?) that * did not submit any commands, so disconnect them */ LIST_FOREACH(ch, &l2cap->chan_list, next) if (ch->con == con) ng_l2cap_l2ca_discon_ind(ch); /* Free connection descriptor */ ng_l2cap_free_con(con); } /* ng_l2cap_con_fail */ /* * Process L2CAP command timeout. In general - notify upper layer and destroy - * channel. Do not pay much attension to return code, just do our best. + * channel. Do not pay much attention to return code, just do our best. */ void ng_l2cap_process_command_timeout(node_p node, hook_p hook, void *arg1, int arg2) { ng_l2cap_p l2cap = NULL; ng_l2cap_con_p con = NULL; ng_l2cap_cmd_p cmd = NULL; u_int16_t con_handle = (arg2 & 0x0ffff); u_int8_t ident = ((arg2 >> 16) & 0xff); if (NG_NODE_NOT_VALID(node)) { printf("%s: Netgraph node is not valid\n", __func__); return; } l2cap = (ng_l2cap_p) NG_NODE_PRIVATE(node); con = ng_l2cap_con_by_handle(l2cap, con_handle); if (con == NULL) { NG_L2CAP_ALERT( "%s: %s - could not find connection, con_handle=%d\n", __func__, NG_NODE_NAME(node), con_handle); return; } cmd = ng_l2cap_cmd_by_ident(con, ident); if (cmd == NULL) { NG_L2CAP_ALERT( "%s: %s - could not find command, con_handle=%d, ident=%d\n", __func__, NG_NODE_NAME(node), con_handle, ident); return; } cmd->flags &= ~NG_L2CAP_CMD_PENDING; ng_l2cap_unlink_cmd(cmd); switch (cmd->code) { case NG_L2CAP_CON_REQ: ng_l2cap_l2ca_con_rsp(cmd->ch, cmd->token, NG_L2CAP_TIMEOUT, 0); ng_l2cap_free_chan(cmd->ch); break; case NG_L2CAP_CFG_REQ: ng_l2cap_l2ca_cfg_rsp(cmd->ch, cmd->token, NG_L2CAP_TIMEOUT); break; case NG_L2CAP_DISCON_REQ: ng_l2cap_l2ca_discon_rsp(cmd->ch, cmd->token, NG_L2CAP_TIMEOUT); ng_l2cap_free_chan(cmd->ch); /* XXX free channel */ break; case NG_L2CAP_ECHO_REQ: /* Echo request timed out. Let the upper layer know */ ng_l2cap_l2ca_ping_rsp(cmd->con, cmd->token, NG_L2CAP_TIMEOUT, NULL); break; case NG_L2CAP_INFO_REQ: /* Info request timed out. Let the upper layer know */ ng_l2cap_l2ca_get_info_rsp(cmd->con, cmd->token, NG_L2CAP_TIMEOUT, NULL); break; /* XXX FIXME add other commands */ default: panic( "%s: %s - unexpected command code=%d\n", __func__, NG_NODE_NAME(l2cap->node), cmd->code); break; } ng_l2cap_free_cmd(cmd); } /* ng_l2cap_process_command_timeout */ Index: head/sys/netgraph/bluetooth/l2cap/ng_l2cap_evnt.c =================================================================== --- head/sys/netgraph/bluetooth/l2cap/ng_l2cap_evnt.c (revision 298812) +++ head/sys/netgraph/bluetooth/l2cap/ng_l2cap_evnt.c (revision 298813) @@ -1,1476 +1,1476 @@ /* * ng_l2cap_evnt.c */ /*- * Copyright (c) Maksim Yevmenkin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id: ng_l2cap_evnt.c,v 1.5 2003/09/08 19:11:45 max Exp $ * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /****************************************************************************** ****************************************************************************** ** L2CAP events processing module ****************************************************************************** ******************************************************************************/ static int ng_l2cap_process_signal_cmd (ng_l2cap_con_p); static int ng_l2cap_process_lesignal_cmd (ng_l2cap_con_p); static int ng_l2cap_process_cmd_rej (ng_l2cap_con_p, u_int8_t); static int ng_l2cap_process_cmd_urq (ng_l2cap_con_p, u_int8_t); static int ng_l2cap_process_cmd_urs (ng_l2cap_con_p, u_int8_t); static int ng_l2cap_process_con_req (ng_l2cap_con_p, u_int8_t); static int ng_l2cap_process_con_rsp (ng_l2cap_con_p, u_int8_t); static int ng_l2cap_process_cfg_req (ng_l2cap_con_p, u_int8_t); static int ng_l2cap_process_cfg_rsp (ng_l2cap_con_p, u_int8_t); static int ng_l2cap_process_discon_req (ng_l2cap_con_p, u_int8_t); static int ng_l2cap_process_discon_rsp (ng_l2cap_con_p, u_int8_t); static int ng_l2cap_process_echo_req (ng_l2cap_con_p, u_int8_t); static int ng_l2cap_process_echo_rsp (ng_l2cap_con_p, u_int8_t); static int ng_l2cap_process_info_req (ng_l2cap_con_p, u_int8_t); static int ng_l2cap_process_info_rsp (ng_l2cap_con_p, u_int8_t); static int send_l2cap_reject (ng_l2cap_con_p, u_int8_t, u_int16_t, u_int16_t, u_int16_t, u_int16_t); static int send_l2cap_con_rej (ng_l2cap_con_p, u_int8_t, u_int16_t, u_int16_t, u_int16_t); static int send_l2cap_cfg_rsp (ng_l2cap_con_p, u_int8_t, u_int16_t, u_int16_t, struct mbuf *); static int send_l2cap_param_urs (ng_l2cap_con_p , u_int8_t , u_int16_t); static int get_next_l2cap_opt (struct mbuf *, int *, ng_l2cap_cfg_opt_p, ng_l2cap_cfg_opt_val_p); /* * Receive L2CAP packet. First get L2CAP header and verify packet. Than * get destination channel and process packet. */ int ng_l2cap_receive(ng_l2cap_con_p con) { ng_l2cap_p l2cap = con->l2cap; ng_l2cap_hdr_t *hdr = NULL; int error = 0; /* Check packet */ if (con->rx_pkt->m_pkthdr.len < sizeof(*hdr)) { NG_L2CAP_ERR( "%s: %s - invalid L2CAP packet. Packet too small, len=%d\n", __func__, NG_NODE_NAME(l2cap->node), con->rx_pkt->m_pkthdr.len); error = EMSGSIZE; goto drop; } /* Get L2CAP header */ NG_L2CAP_M_PULLUP(con->rx_pkt, sizeof(*hdr)); if (con->rx_pkt == NULL) return (ENOBUFS); hdr = mtod(con->rx_pkt, ng_l2cap_hdr_t *); hdr->length = le16toh(hdr->length); hdr->dcid = le16toh(hdr->dcid); /* Check payload size */ if (hdr->length != con->rx_pkt->m_pkthdr.len - sizeof(*hdr)) { NG_L2CAP_ERR( "%s: %s - invalid L2CAP packet. Payload length mismatch, length=%d, len=%zd\n", __func__, NG_NODE_NAME(l2cap->node), hdr->length, con->rx_pkt->m_pkthdr.len - sizeof(*hdr)); error = EMSGSIZE; goto drop; } /* Process packet */ switch (hdr->dcid) { case NG_L2CAP_SIGNAL_CID: /* L2CAP command */ m_adj(con->rx_pkt, sizeof(*hdr)); error = ng_l2cap_process_signal_cmd(con); break; case NG_L2CAP_LESIGNAL_CID: m_adj(con->rx_pkt, sizeof(*hdr)); error = ng_l2cap_process_lesignal_cmd(con); break; case NG_L2CAP_CLT_CID: /* Connectionless packet */ error = ng_l2cap_l2ca_clt_receive(con); break; default: /* Data packet */ error = ng_l2cap_l2ca_receive(con); break; } return (error); drop: NG_FREE_M(con->rx_pkt); return (error); } /* ng_l2cap_receive */ /* * Process L2CAP signaling command. We already know that destination channel ID * is 0x1 that means we have received signaling command from peer's L2CAP layer. * So get command header, decode and process it. * * XXX do we need to check signaling MTU here? */ static int ng_l2cap_process_signal_cmd(ng_l2cap_con_p con) { ng_l2cap_p l2cap = con->l2cap; ng_l2cap_cmd_hdr_t *hdr = NULL; struct mbuf *m = NULL; while (con->rx_pkt != NULL) { /* Verify packet length */ if (con->rx_pkt->m_pkthdr.len < sizeof(*hdr)) { NG_L2CAP_ERR( "%s: %s - invalid L2CAP signaling command. Packet too small, len=%d\n", __func__, NG_NODE_NAME(l2cap->node), con->rx_pkt->m_pkthdr.len); NG_FREE_M(con->rx_pkt); return (EMSGSIZE); } /* Get signaling command */ NG_L2CAP_M_PULLUP(con->rx_pkt, sizeof(*hdr)); if (con->rx_pkt == NULL) return (ENOBUFS); hdr = mtod(con->rx_pkt, ng_l2cap_cmd_hdr_t *); hdr->length = le16toh(hdr->length); m_adj(con->rx_pkt, sizeof(*hdr)); /* Verify command length */ if (con->rx_pkt->m_pkthdr.len < hdr->length) { NG_L2CAP_ERR( "%s: %s - invalid L2CAP signaling command, code=%#x, ident=%d. " \ "Invalid command length=%d, m_pkthdr.len=%d\n", __func__, NG_NODE_NAME(l2cap->node), hdr->code, hdr->ident, hdr->length, con->rx_pkt->m_pkthdr.len); NG_FREE_M(con->rx_pkt); return (EMSGSIZE); } /* Get the command, save the rest (if any) */ if (con->rx_pkt->m_pkthdr.len > hdr->length) m = m_split(con->rx_pkt, hdr->length, M_NOWAIT); else m = NULL; /* Process command */ switch (hdr->code) { case NG_L2CAP_CMD_REJ: ng_l2cap_process_cmd_rej(con, hdr->ident); break; case NG_L2CAP_CON_REQ: ng_l2cap_process_con_req(con, hdr->ident); break; case NG_L2CAP_CON_RSP: ng_l2cap_process_con_rsp(con, hdr->ident); break; case NG_L2CAP_CFG_REQ: ng_l2cap_process_cfg_req(con, hdr->ident); break; case NG_L2CAP_CFG_RSP: ng_l2cap_process_cfg_rsp(con, hdr->ident); break; case NG_L2CAP_DISCON_REQ: ng_l2cap_process_discon_req(con, hdr->ident); break; case NG_L2CAP_DISCON_RSP: ng_l2cap_process_discon_rsp(con, hdr->ident); break; case NG_L2CAP_ECHO_REQ: ng_l2cap_process_echo_req(con, hdr->ident); break; case NG_L2CAP_ECHO_RSP: ng_l2cap_process_echo_rsp(con, hdr->ident); break; case NG_L2CAP_INFO_REQ: ng_l2cap_process_info_req(con, hdr->ident); break; case NG_L2CAP_INFO_RSP: ng_l2cap_process_info_rsp(con, hdr->ident); break; default: NG_L2CAP_ERR( "%s: %s - unknown L2CAP signaling command, code=%#x, ident=%d, length=%d\n", __func__, NG_NODE_NAME(l2cap->node), hdr->code, hdr->ident, hdr->length); /* * Send L2CAP_CommandRej. Do not really care * about the result */ send_l2cap_reject(con, hdr->ident, NG_L2CAP_REJ_NOT_UNDERSTOOD, 0, 0, 0); NG_FREE_M(con->rx_pkt); break; } con->rx_pkt = m; } return (0); } /* ng_l2cap_process_signal_cmd */ static int ng_l2cap_process_lesignal_cmd(ng_l2cap_con_p con) { ng_l2cap_p l2cap = con->l2cap; ng_l2cap_cmd_hdr_t *hdr = NULL; struct mbuf *m = NULL; while (con->rx_pkt != NULL) { /* Verify packet length */ if (con->rx_pkt->m_pkthdr.len < sizeof(*hdr)) { NG_L2CAP_ERR( "%s: %s - invalid L2CAP signaling command. Packet too small, len=%d\n", __func__, NG_NODE_NAME(l2cap->node), con->rx_pkt->m_pkthdr.len); NG_FREE_M(con->rx_pkt); return (EMSGSIZE); } /* Get signaling command */ NG_L2CAP_M_PULLUP(con->rx_pkt, sizeof(*hdr)); if (con->rx_pkt == NULL) return (ENOBUFS); hdr = mtod(con->rx_pkt, ng_l2cap_cmd_hdr_t *); hdr->length = le16toh(hdr->length); m_adj(con->rx_pkt, sizeof(*hdr)); /* Verify command length */ if (con->rx_pkt->m_pkthdr.len < hdr->length) { NG_L2CAP_ERR( "%s: %s - invalid L2CAP signaling command, code=%#x, ident=%d. " \ "Invalid command length=%d, m_pkthdr.len=%d\n", __func__, NG_NODE_NAME(l2cap->node), hdr->code, hdr->ident, hdr->length, con->rx_pkt->m_pkthdr.len); NG_FREE_M(con->rx_pkt); return (EMSGSIZE); } /* Get the command, save the rest (if any) */ if (con->rx_pkt->m_pkthdr.len > hdr->length) m = m_split(con->rx_pkt, hdr->length, M_NOWAIT); else m = NULL; /* Process command */ switch (hdr->code) { case NG_L2CAP_CMD_REJ: ng_l2cap_process_cmd_rej(con, hdr->ident); break; case NG_L2CAP_CMD_PARAM_UPDATE_REQUEST: ng_l2cap_process_cmd_urq(con, hdr->ident); break; case NG_L2CAP_CMD_PARAM_UPDATE_RESPONSE: ng_l2cap_process_cmd_urs(con, hdr->ident); break; default: NG_L2CAP_ERR( "%s: %s - unknown L2CAP signaling command, code=%#x, ident=%d, length=%d\n", __func__, NG_NODE_NAME(l2cap->node), hdr->code, hdr->ident, hdr->length); /* * Send L2CAP_CommandRej. Do not really care * about the result */ send_l2cap_reject(con, hdr->ident, NG_L2CAP_REJ_NOT_UNDERSTOOD, 0, 0, 0); NG_FREE_M(con->rx_pkt); break; } con->rx_pkt = m; } return (0); } /* ng_l2cap_process_signal_cmd */ /*Update Paramater Request*/ static int ng_l2cap_process_cmd_urq(ng_l2cap_con_p con, uint8_t ident) { - /*We do not implement paramter negotiasion for now*/ + /* We do not implement parameter negotiation for now. */ send_l2cap_param_urs(con, ident, NG_L2CAP_UPDATE_PARAM_ACCEPT); NG_FREE_M(con->rx_pkt); return 0; } static int ng_l2cap_process_cmd_urs(ng_l2cap_con_p con, uint8_t ident) { /* We only support master side yet .*/ //send_l2cap_reject(con,ident ... ); NG_FREE_M(con->rx_pkt); return 0; } /* * Process L2CAP_CommandRej command */ static int ng_l2cap_process_cmd_rej(ng_l2cap_con_p con, u_int8_t ident) { ng_l2cap_p l2cap = con->l2cap; ng_l2cap_cmd_rej_cp *cp = NULL; ng_l2cap_cmd_p cmd = NULL; /* Get command parameters */ NG_L2CAP_M_PULLUP(con->rx_pkt, sizeof(*cp)); if (con->rx_pkt == NULL) return (ENOBUFS); cp = mtod(con->rx_pkt, ng_l2cap_cmd_rej_cp *); cp->reason = le16toh(cp->reason); /* Check if we have pending command descriptor */ cmd = ng_l2cap_cmd_by_ident(con, ident); if (cmd != NULL) { /* If command timeout already happened then ignore reject */ if (ng_l2cap_command_untimeout(cmd) != 0) { NG_FREE_M(con->rx_pkt); return (ETIMEDOUT); } ng_l2cap_unlink_cmd(cmd); switch (cmd->code) { case NG_L2CAP_CON_REQ: ng_l2cap_l2ca_con_rsp(cmd->ch,cmd->token,cp->reason,0); ng_l2cap_free_chan(cmd->ch); break; case NG_L2CAP_CFG_REQ: ng_l2cap_l2ca_cfg_rsp(cmd->ch, cmd->token, cp->reason); break; case NG_L2CAP_DISCON_REQ: ng_l2cap_l2ca_discon_rsp(cmd->ch,cmd->token,cp->reason); ng_l2cap_free_chan(cmd->ch); /* XXX free channel */ break; case NG_L2CAP_ECHO_REQ: ng_l2cap_l2ca_ping_rsp(cmd->con, cmd->token, cp->reason, NULL); break; case NG_L2CAP_INFO_REQ: ng_l2cap_l2ca_get_info_rsp(cmd->con, cmd->token, cp->reason, NULL); break; default: NG_L2CAP_ALERT( "%s: %s - unexpected L2CAP_CommandRej. Unexpected L2CAP command opcode=%d\n", __func__, NG_NODE_NAME(l2cap->node), cmd->code); break; } ng_l2cap_free_cmd(cmd); } else NG_L2CAP_ERR( "%s: %s - unexpected L2CAP_CommandRej command. " \ "Requested ident does not exist, ident=%d\n", __func__, NG_NODE_NAME(l2cap->node), ident); NG_FREE_M(con->rx_pkt); return (0); } /* ng_l2cap_process_cmd_rej */ /* * Process L2CAP_ConnectReq command */ static int ng_l2cap_process_con_req(ng_l2cap_con_p con, u_int8_t ident) { ng_l2cap_p l2cap = con->l2cap; struct mbuf *m = con->rx_pkt; ng_l2cap_con_req_cp *cp = NULL; ng_l2cap_chan_p ch = NULL; int error = 0; u_int16_t dcid, psm; int idtype; /* Get command parameters */ NG_L2CAP_M_PULLUP(m, sizeof(*cp)); if (m == NULL) return (ENOBUFS); cp = mtod(m, ng_l2cap_con_req_cp *); psm = le16toh(cp->psm); dcid = le16toh(cp->scid); NG_FREE_M(m); con->rx_pkt = NULL; if(dcid == NG_L2CAP_ATT_CID) idtype = NG_L2CAP_L2CA_IDTYPE_ATT; else if(dcid == NG_L2CAP_SMP_CID) idtype = NG_L2CAP_L2CA_IDTYPE_SMP; else if( con->linktype != NG_HCI_LINK_ACL) idtype = NG_L2CAP_L2CA_IDTYPE_LE; else idtype = NG_L2CAP_L2CA_IDTYPE_BREDR; /* * Create new channel and send L2CA_ConnectInd notification * to the upper layer protocol. */ ch = ng_l2cap_new_chan(l2cap, con, psm, idtype); if (ch == NULL) return (send_l2cap_con_rej(con, ident, 0, dcid, NG_L2CAP_NO_RESOURCES)); /* Update channel IDs */ ch->dcid = dcid; /* Sent L2CA_ConnectInd notification to the upper layer */ ch->ident = ident; ch->state = NG_L2CAP_W4_L2CA_CON_RSP; error = ng_l2cap_l2ca_con_ind(ch); if (error != 0) { send_l2cap_con_rej(con, ident, ch->scid, dcid, (error == ENOMEM)? NG_L2CAP_NO_RESOURCES : NG_L2CAP_PSM_NOT_SUPPORTED); ng_l2cap_free_chan(ch); } return (error); } /* ng_l2cap_process_con_req */ /* * Process L2CAP_ConnectRsp command */ static int ng_l2cap_process_con_rsp(ng_l2cap_con_p con, u_int8_t ident) { ng_l2cap_p l2cap = con->l2cap; struct mbuf *m = con->rx_pkt; ng_l2cap_con_rsp_cp *cp = NULL; ng_l2cap_cmd_p cmd = NULL; u_int16_t scid, dcid, result, status; int error = 0; /* Get command parameters */ NG_L2CAP_M_PULLUP(m, sizeof(*cp)); if (m == NULL) return (ENOBUFS); cp = mtod(m, ng_l2cap_con_rsp_cp *); dcid = le16toh(cp->dcid); scid = le16toh(cp->scid); result = le16toh(cp->result); status = le16toh(cp->status); NG_FREE_M(m); con->rx_pkt = NULL; /* Check if we have pending command descriptor */ cmd = ng_l2cap_cmd_by_ident(con, ident); if (cmd == NULL) { NG_L2CAP_ERR( "%s: %s - unexpected L2CAP_ConnectRsp command. ident=%d, con_handle=%d\n", __func__, NG_NODE_NAME(l2cap->node), ident, con->con_handle); return (ENOENT); } /* Verify channel state, if invalid - do nothing */ if (cmd->ch->state != NG_L2CAP_W4_L2CAP_CON_RSP) { NG_L2CAP_ERR( "%s: %s - unexpected L2CAP_ConnectRsp. " \ "Invalid channel state, cid=%d, state=%d\n", __func__, NG_NODE_NAME(l2cap->node), scid, cmd->ch->state); goto reject; } /* Verify CIDs and send reject if does not match */ if (cmd->ch->scid != scid) { NG_L2CAP_ERR( "%s: %s - unexpected L2CAP_ConnectRsp. Channel IDs do not match, scid=%d(%d)\n", __func__, NG_NODE_NAME(l2cap->node), cmd->ch->scid, scid); goto reject; } /* * Looks good. We got confirmation from our peer. Now process * it. First disable RTX timer. Then check the result and send * notification to the upper layer. If command timeout already * happened then ignore response. */ if ((error = ng_l2cap_command_untimeout(cmd)) != 0) return (error); if (result == NG_L2CAP_PENDING) { /* * Our peer wants more time to complete connection. We shall * start ERTX timer and wait. Keep command in the list. */ cmd->ch->dcid = dcid; ng_l2cap_command_timeout(cmd, bluetooth_l2cap_ertx_timeout()); error = ng_l2cap_l2ca_con_rsp(cmd->ch, cmd->token, result, status); if (error != 0) ng_l2cap_free_chan(cmd->ch); } else { ng_l2cap_unlink_cmd(cmd); if (result == NG_L2CAP_SUCCESS) { /* * Channel is open. Complete command and move to CONFIG * state. Since we have sent positive confirmation we * expect to receive L2CA_Config request from the upper * layer protocol. */ cmd->ch->dcid = dcid; cmd->ch->state = ((cmd->ch->scid == NG_L2CAP_ATT_CID)|| (cmd->ch->scid == NG_L2CAP_SMP_CID)) ? NG_L2CAP_OPEN : NG_L2CAP_CONFIG; } else /* There was an error, so close the channel */ NG_L2CAP_INFO( "%s: %s - failed to open L2CAP channel, result=%d, status=%d\n", __func__, NG_NODE_NAME(l2cap->node), result, status); error = ng_l2cap_l2ca_con_rsp(cmd->ch, cmd->token, result, status); /* XXX do we have to remove the channel on error? */ if (error != 0 || result != NG_L2CAP_SUCCESS) ng_l2cap_free_chan(cmd->ch); ng_l2cap_free_cmd(cmd); } return (error); reject: /* Send reject. Do not really care about the result */ send_l2cap_reject(con, ident, NG_L2CAP_REJ_INVALID_CID, 0, scid, dcid); return (0); } /* ng_l2cap_process_con_rsp */ /* * Process L2CAP_ConfigReq command */ static int ng_l2cap_process_cfg_req(ng_l2cap_con_p con, u_int8_t ident) { ng_l2cap_p l2cap = con->l2cap; struct mbuf *m = con->rx_pkt; ng_l2cap_cfg_req_cp *cp = NULL; ng_l2cap_chan_p ch = NULL; u_int16_t dcid, respond, result; ng_l2cap_cfg_opt_t hdr; ng_l2cap_cfg_opt_val_t val; int off, error = 0; /* Get command parameters */ con->rx_pkt = NULL; NG_L2CAP_M_PULLUP(m, sizeof(*cp)); if (m == NULL) return (ENOBUFS); cp = mtod(m, ng_l2cap_cfg_req_cp *); dcid = le16toh(cp->dcid); respond = NG_L2CAP_OPT_CFLAG(le16toh(cp->flags)); m_adj(m, sizeof(*cp)); /* Check if we have this channel and it is in valid state */ ch = ng_l2cap_chan_by_scid(l2cap, dcid, NG_L2CAP_L2CA_IDTYPE_BREDR); if (ch == NULL) { NG_L2CAP_ERR( "%s: %s - unexpected L2CAP_ConfigReq command. " \ "Channel does not exist, cid=%d\n", __func__, NG_NODE_NAME(l2cap->node), dcid); goto reject; } /* Verify channel state */ if (ch->state != NG_L2CAP_CONFIG && ch->state != NG_L2CAP_OPEN) { NG_L2CAP_ERR( "%s: %s - unexpected L2CAP_ConfigReq. " \ "Invalid channel state, cid=%d, state=%d\n", __func__, NG_NODE_NAME(l2cap->node), dcid, ch->state); goto reject; } if (ch->state == NG_L2CAP_OPEN) { /* Re-configuration */ ch->cfg_state = 0; ch->state = NG_L2CAP_CONFIG; } for (result = 0, off = 0; ; ) { error = get_next_l2cap_opt(m, &off, &hdr, &val); if (error == 0) { /* We done with this packet */ NG_FREE_M(m); break; } else if (error > 0) { /* Got option */ switch (hdr.type) { case NG_L2CAP_OPT_MTU: ch->omtu = val.mtu; break; case NG_L2CAP_OPT_FLUSH_TIMO: ch->flush_timo = val.flush_timo; break; case NG_L2CAP_OPT_QOS: bcopy(&val.flow, &ch->iflow, sizeof(ch->iflow)); break; default: /* Ignore unknown hint option */ break; } } else { /* Oops, something is wrong */ respond = 1; if (error == -3) { /* * Adjust mbuf so we can get to the start * of the first option we did not like. */ m_adj(m, off - sizeof(hdr)); m->m_pkthdr.len = sizeof(hdr) + hdr.length; result = NG_L2CAP_UNKNOWN_OPTION; } else { /* XXX FIXME Send other reject codes? */ NG_FREE_M(m); result = NG_L2CAP_REJECT; } break; } } /* * Now check and see if we have to respond. If everything was OK then * respond contain "C flag" and (if set) we will respond with empty * packet and will wait for more options. * * Other case is that we did not like peer's options and will respond * with L2CAP_Config response command with Reject error code. * * When "respond == 0" than we have received all options and we will * sent L2CA_ConfigInd event to the upper layer protocol. */ if (respond) { error = send_l2cap_cfg_rsp(con, ident, ch->dcid, result, m); if (error != 0) { ng_l2cap_l2ca_discon_ind(ch); ng_l2cap_free_chan(ch); } } else { /* Send L2CA_ConfigInd event to the upper layer protocol */ ch->ident = ident; error = ng_l2cap_l2ca_cfg_ind(ch); if (error != 0) ng_l2cap_free_chan(ch); } return (error); reject: /* Send reject. Do not really care about the result */ NG_FREE_M(m); send_l2cap_reject(con, ident, NG_L2CAP_REJ_INVALID_CID, 0, 0, dcid); return (0); } /* ng_l2cap_process_cfg_req */ /* * Process L2CAP_ConfigRsp command */ static int ng_l2cap_process_cfg_rsp(ng_l2cap_con_p con, u_int8_t ident) { ng_l2cap_p l2cap = con->l2cap; struct mbuf *m = con->rx_pkt; ng_l2cap_cfg_rsp_cp *cp = NULL; ng_l2cap_cmd_p cmd = NULL; u_int16_t scid, cflag, result; ng_l2cap_cfg_opt_t hdr; ng_l2cap_cfg_opt_val_t val; int off, error = 0; /* Get command parameters */ con->rx_pkt = NULL; NG_L2CAP_M_PULLUP(m, sizeof(*cp)); if (m == NULL) return (ENOBUFS); cp = mtod(m, ng_l2cap_cfg_rsp_cp *); scid = le16toh(cp->scid); cflag = NG_L2CAP_OPT_CFLAG(le16toh(cp->flags)); result = le16toh(cp->result); m_adj(m, sizeof(*cp)); /* Check if we have this command */ cmd = ng_l2cap_cmd_by_ident(con, ident); if (cmd == NULL) { NG_L2CAP_ERR( "%s: %s - unexpected L2CAP_ConfigRsp command. ident=%d, con_handle=%d\n", __func__, NG_NODE_NAME(l2cap->node), ident, con->con_handle); NG_FREE_M(m); return (ENOENT); } /* Verify CIDs and send reject if does not match */ if (cmd->ch->scid != scid) { NG_L2CAP_ERR( "%s: %s - unexpected L2CAP_ConfigRsp. " \ "Channel ID does not match, scid=%d(%d)\n", __func__, NG_NODE_NAME(l2cap->node), cmd->ch->scid, scid); goto reject; } /* Verify channel state and reject if invalid */ if (cmd->ch->state != NG_L2CAP_CONFIG) { NG_L2CAP_ERR( "%s: %s - unexpected L2CAP_ConfigRsp. " \ "Invalid channel state, scid=%d, state=%d\n", __func__, NG_NODE_NAME(l2cap->node), cmd->ch->scid, cmd->ch->state); goto reject; } /* * Looks like it is our response, so process it. First parse options, * then verify C flag. If it is set then we shall expect more * configuration options from the peer and we will wait. Otherwise we * have received all options and we will send L2CA_ConfigRsp event to * the upper layer protocol. If command timeout already happened then * ignore response. */ if ((error = ng_l2cap_command_untimeout(cmd)) != 0) { NG_FREE_M(m); return (error); } for (off = 0; ; ) { error = get_next_l2cap_opt(m, &off, &hdr, &val); if (error == 0) /* We done with this packet */ break; else if (error > 0) { /* Got option */ switch (hdr.type) { case NG_L2CAP_OPT_MTU: cmd->ch->imtu = val.mtu; break; case NG_L2CAP_OPT_FLUSH_TIMO: cmd->ch->flush_timo = val.flush_timo; break; case NG_L2CAP_OPT_QOS: bcopy(&val.flow, &cmd->ch->oflow, sizeof(cmd->ch->oflow)); break; default: /* Ignore unknown hint option */ break; } } else { /* * XXX FIXME What to do here? * * This is really BAD :( options packet was broken, or * peer sent us option that we did not understand. Let * upper layer know and do not wait for more options. */ NG_L2CAP_ALERT( "%s: %s - failed to parse configuration options, error=%d\n", __func__, NG_NODE_NAME(l2cap->node), error); result = NG_L2CAP_UNKNOWN; cflag = 0; break; } } NG_FREE_M(m); if (cflag) /* Restart timer and wait for more options */ ng_l2cap_command_timeout(cmd, bluetooth_l2cap_rtx_timeout()); else { ng_l2cap_unlink_cmd(cmd); /* Send L2CA_Config response to the upper layer protocol */ error = ng_l2cap_l2ca_cfg_rsp(cmd->ch, cmd->token, result); if (error != 0) { /* * XXX FIXME what to do here? we were not able to send * response to the upper layer protocol, so for now * just close the channel. Send L2CAP_Disconnect to * remote peer? */ NG_L2CAP_ERR( "%s: %s - failed to send L2CA_Config response, error=%d\n", __func__, NG_NODE_NAME(l2cap->node), error); ng_l2cap_free_chan(cmd->ch); } ng_l2cap_free_cmd(cmd); } return (error); reject: /* Send reject. Do not really care about the result */ NG_FREE_M(m); send_l2cap_reject(con, ident, NG_L2CAP_REJ_INVALID_CID, 0, scid, 0); return (0); } /* ng_l2cap_process_cfg_rsp */ /* * Process L2CAP_DisconnectReq command */ static int ng_l2cap_process_discon_req(ng_l2cap_con_p con, u_int8_t ident) { ng_l2cap_p l2cap = con->l2cap; ng_l2cap_discon_req_cp *cp = NULL; ng_l2cap_chan_p ch = NULL; ng_l2cap_cmd_p cmd = NULL; u_int16_t scid, dcid; /* Get command parameters */ NG_L2CAP_M_PULLUP(con->rx_pkt, sizeof(*cp)); if (con->rx_pkt == NULL) return (ENOBUFS); cp = mtod(con->rx_pkt, ng_l2cap_discon_req_cp *); dcid = le16toh(cp->dcid); scid = le16toh(cp->scid); NG_FREE_M(con->rx_pkt); /* Check if we have this channel and it is in valid state */ ch = ng_l2cap_chan_by_scid(l2cap, dcid, NG_L2CAP_L2CA_IDTYPE_BREDR); if (ch == NULL) { NG_L2CAP_ERR( "%s: %s - unexpected L2CAP_DisconnectReq message. " \ "Channel does not exist, cid=%d\n", __func__, NG_NODE_NAME(l2cap->node), dcid); goto reject; } /* XXX Verify channel state and reject if invalid -- is that true? */ if (ch->state != NG_L2CAP_OPEN && ch->state != NG_L2CAP_CONFIG && ch->state != NG_L2CAP_W4_L2CAP_DISCON_RSP) { NG_L2CAP_ERR( "%s: %s - unexpected L2CAP_DisconnectReq. " \ "Invalid channel state, cid=%d, state=%d\n", __func__, NG_NODE_NAME(l2cap->node), dcid, ch->state); goto reject; } /* Match destination channel ID */ if (ch->dcid != scid || ch->scid != dcid) { NG_L2CAP_ERR( "%s: %s - unexpected L2CAP_DisconnectReq. " \ "Channel IDs does not match, channel: scid=%d, dcid=%d, " \ "request: scid=%d, dcid=%d\n", __func__, NG_NODE_NAME(l2cap->node), ch->scid, ch->dcid, scid, dcid); goto reject; } /* * Looks good, so notify upper layer protocol that channel is about * to be disconnected and send L2CA_DisconnectInd message. Then respond * with L2CAP_DisconnectRsp. */ if (ch->state != NG_L2CAP_W4_L2CAP_DISCON_RSP) { ng_l2cap_l2ca_discon_ind(ch); /* do not care about result */ ng_l2cap_free_chan(ch); } /* Send L2CAP_DisconnectRsp */ cmd = ng_l2cap_new_cmd(con, NULL, ident, NG_L2CAP_DISCON_RSP, 0); if (cmd == NULL) return (ENOMEM); _ng_l2cap_discon_rsp(cmd->aux, ident, dcid, scid); if (cmd->aux == NULL) { ng_l2cap_free_cmd(cmd); return (ENOBUFS); } /* Link command to the queue */ ng_l2cap_link_cmd(con, cmd); ng_l2cap_lp_deliver(con); return (0); reject: /* Send reject. Do not really care about the result */ send_l2cap_reject(con, ident, NG_L2CAP_REJ_INVALID_CID, 0, scid, dcid); return (0); } /* ng_l2cap_process_discon_req */ /* * Process L2CAP_DisconnectRsp command */ static int ng_l2cap_process_discon_rsp(ng_l2cap_con_p con, u_int8_t ident) { ng_l2cap_p l2cap = con->l2cap; ng_l2cap_discon_rsp_cp *cp = NULL; ng_l2cap_cmd_p cmd = NULL; u_int16_t scid, dcid; int error = 0; /* Get command parameters */ NG_L2CAP_M_PULLUP(con->rx_pkt, sizeof(*cp)); if (con->rx_pkt == NULL) return (ENOBUFS); cp = mtod(con->rx_pkt, ng_l2cap_discon_rsp_cp *); dcid = le16toh(cp->dcid); scid = le16toh(cp->scid); NG_FREE_M(con->rx_pkt); /* Check if we have pending command descriptor */ cmd = ng_l2cap_cmd_by_ident(con, ident); if (cmd == NULL) { NG_L2CAP_ERR( "%s: %s - unexpected L2CAP_DisconnectRsp command. ident=%d, con_handle=%d\n", __func__, NG_NODE_NAME(l2cap->node), ident, con->con_handle); goto out; } /* Verify channel state, do nothing if invalid */ if (cmd->ch->state != NG_L2CAP_W4_L2CAP_DISCON_RSP) { NG_L2CAP_ERR( "%s: %s - unexpected L2CAP_DisconnectRsp. " \ "Invalid channel state, cid=%d, state=%d\n", __func__, NG_NODE_NAME(l2cap->node), scid, cmd->ch->state); goto out; } /* Verify CIDs and send reject if does not match */ if (cmd->ch->scid != scid || cmd->ch->dcid != dcid) { NG_L2CAP_ERR( "%s: %s - unexpected L2CAP_DisconnectRsp. " \ "Channel IDs do not match, scid=%d(%d), dcid=%d(%d)\n", __func__, NG_NODE_NAME(l2cap->node), cmd->ch->scid, scid, cmd->ch->dcid, dcid); goto out; } /* - * Looks like we have successfuly disconnected channel, so notify + * Looks like we have successfully disconnected channel, so notify * upper layer. If command timeout already happened then ignore * response. */ if ((error = ng_l2cap_command_untimeout(cmd)) != 0) goto out; error = ng_l2cap_l2ca_discon_rsp(cmd->ch, cmd->token, NG_L2CAP_SUCCESS); ng_l2cap_free_chan(cmd->ch); /* this will free commands too */ out: return (error); } /* ng_l2cap_process_discon_rsp */ /* * Process L2CAP_EchoReq command */ static int ng_l2cap_process_echo_req(ng_l2cap_con_p con, u_int8_t ident) { ng_l2cap_p l2cap = con->l2cap; ng_l2cap_cmd_hdr_t *hdr = NULL; ng_l2cap_cmd_p cmd = NULL; con->rx_pkt = ng_l2cap_prepend(con->rx_pkt, sizeof(*hdr)); if (con->rx_pkt == NULL) { NG_L2CAP_ALERT( "%s: %s - ng_l2cap_prepend() failed, size=%zd\n", __func__, NG_NODE_NAME(l2cap->node), sizeof(*hdr)); return (ENOBUFS); } hdr = mtod(con->rx_pkt, ng_l2cap_cmd_hdr_t *); hdr->code = NG_L2CAP_ECHO_RSP; hdr->ident = ident; hdr->length = htole16(con->rx_pkt->m_pkthdr.len - sizeof(*hdr)); cmd = ng_l2cap_new_cmd(con, NULL, ident, NG_L2CAP_ECHO_RSP, 0); if (cmd == NULL) { NG_FREE_M(con->rx_pkt); return (ENOBUFS); } /* Attach data and link command to the queue */ cmd->aux = con->rx_pkt; con->rx_pkt = NULL; ng_l2cap_link_cmd(con, cmd); ng_l2cap_lp_deliver(con); return (0); } /* ng_l2cap_process_echo_req */ /* * Process L2CAP_EchoRsp command */ static int ng_l2cap_process_echo_rsp(ng_l2cap_con_p con, u_int8_t ident) { ng_l2cap_p l2cap = con->l2cap; ng_l2cap_cmd_p cmd = NULL; int error = 0; /* Check if we have this command */ cmd = ng_l2cap_cmd_by_ident(con, ident); if (cmd != NULL) { /* If command timeout already happened then ignore response */ if ((error = ng_l2cap_command_untimeout(cmd)) != 0) { NG_FREE_M(con->rx_pkt); return (error); } ng_l2cap_unlink_cmd(cmd); error = ng_l2cap_l2ca_ping_rsp(cmd->con, cmd->token, NG_L2CAP_SUCCESS, con->rx_pkt); ng_l2cap_free_cmd(cmd); con->rx_pkt = NULL; } else { NG_L2CAP_ERR( "%s: %s - unexpected L2CAP_EchoRsp command. " \ "Requested ident does not exist, ident=%d\n", __func__, NG_NODE_NAME(l2cap->node), ident); NG_FREE_M(con->rx_pkt); } return (error); } /* ng_l2cap_process_echo_rsp */ /* * Process L2CAP_InfoReq command */ static int ng_l2cap_process_info_req(ng_l2cap_con_p con, u_int8_t ident) { ng_l2cap_p l2cap = con->l2cap; ng_l2cap_cmd_p cmd = NULL; u_int16_t type; /* Get command parameters */ NG_L2CAP_M_PULLUP(con->rx_pkt, sizeof(ng_l2cap_info_req_cp)); if (con->rx_pkt == NULL) return (ENOBUFS); type = le16toh(mtod(con->rx_pkt, ng_l2cap_info_req_cp *)->type); NG_FREE_M(con->rx_pkt); cmd = ng_l2cap_new_cmd(con, NULL, ident, NG_L2CAP_INFO_RSP, 0); if (cmd == NULL) return (ENOMEM); switch (type) { case NG_L2CAP_CONNLESS_MTU: _ng_l2cap_info_rsp(cmd->aux, ident, NG_L2CAP_CONNLESS_MTU, NG_L2CAP_SUCCESS, NG_L2CAP_MTU_DEFAULT); break; default: _ng_l2cap_info_rsp(cmd->aux, ident, type, NG_L2CAP_NOT_SUPPORTED, 0); break; } if (cmd->aux == NULL) { ng_l2cap_free_cmd(cmd); return (ENOBUFS); } /* Link command to the queue */ ng_l2cap_link_cmd(con, cmd); ng_l2cap_lp_deliver(con); return (0); } /* ng_l2cap_process_info_req */ /* * Process L2CAP_InfoRsp command */ static int ng_l2cap_process_info_rsp(ng_l2cap_con_p con, u_int8_t ident) { ng_l2cap_p l2cap = con->l2cap; ng_l2cap_info_rsp_cp *cp = NULL; ng_l2cap_cmd_p cmd = NULL; int error = 0; /* Get command parameters */ NG_L2CAP_M_PULLUP(con->rx_pkt, sizeof(*cp)); if (con->rx_pkt == NULL) return (ENOBUFS); cp = mtod(con->rx_pkt, ng_l2cap_info_rsp_cp *); cp->type = le16toh(cp->type); cp->result = le16toh(cp->result); m_adj(con->rx_pkt, sizeof(*cp)); /* Check if we have pending command descriptor */ cmd = ng_l2cap_cmd_by_ident(con, ident); if (cmd == NULL) { NG_L2CAP_ERR( "%s: %s - unexpected L2CAP_InfoRsp command. " \ "Requested ident does not exist, ident=%d\n", __func__, NG_NODE_NAME(l2cap->node), ident); NG_FREE_M(con->rx_pkt); return (ENOENT); } /* If command timeout already happened then ignore response */ if ((error = ng_l2cap_command_untimeout(cmd)) != 0) { NG_FREE_M(con->rx_pkt); return (error); } ng_l2cap_unlink_cmd(cmd); if (cp->result == NG_L2CAP_SUCCESS) { switch (cp->type) { case NG_L2CAP_CONNLESS_MTU: if (con->rx_pkt->m_pkthdr.len == sizeof(u_int16_t)) *mtod(con->rx_pkt, u_int16_t *) = le16toh(*mtod(con->rx_pkt,u_int16_t *)); else { cp->result = NG_L2CAP_UNKNOWN; /* XXX */ NG_L2CAP_ERR( "%s: %s - invalid L2CAP_InfoRsp command. " \ "Bad connectionless MTU parameter, len=%d\n", __func__, NG_NODE_NAME(l2cap->node), con->rx_pkt->m_pkthdr.len); } break; default: NG_L2CAP_WARN( "%s: %s - invalid L2CAP_InfoRsp command. Unknown info type=%d\n", __func__, NG_NODE_NAME(l2cap->node), cp->type); break; } } error = ng_l2cap_l2ca_get_info_rsp(cmd->con, cmd->token, cp->result, con->rx_pkt); ng_l2cap_free_cmd(cmd); con->rx_pkt = NULL; return (error); } /* ng_l2cap_process_info_rsp */ /* * Send L2CAP reject */ static int send_l2cap_reject(ng_l2cap_con_p con, u_int8_t ident, u_int16_t reason, u_int16_t mtu, u_int16_t scid, u_int16_t dcid) { ng_l2cap_cmd_p cmd = NULL; cmd = ng_l2cap_new_cmd(con, NULL, ident, NG_L2CAP_CMD_REJ, 0); if (cmd == NULL) return (ENOMEM); _ng_l2cap_cmd_rej(cmd->aux, cmd->ident, reason, mtu, scid, dcid); if (cmd->aux == NULL) { ng_l2cap_free_cmd(cmd); return (ENOBUFS); } /* Link command to the queue */ ng_l2cap_link_cmd(con, cmd); ng_l2cap_lp_deliver(con); return (0); } /* send_l2cap_reject */ /* * Send L2CAP connection reject */ static int send_l2cap_con_rej(ng_l2cap_con_p con, u_int8_t ident, u_int16_t scid, u_int16_t dcid, u_int16_t result) { ng_l2cap_cmd_p cmd = NULL; cmd = ng_l2cap_new_cmd(con, NULL, ident, NG_L2CAP_CON_RSP, 0); if (cmd == NULL) return (ENOMEM); _ng_l2cap_con_rsp(cmd->aux, cmd->ident, scid, dcid, result, 0); if (cmd->aux == NULL) { ng_l2cap_free_cmd(cmd); return (ENOBUFS); } /* Link command to the queue */ ng_l2cap_link_cmd(con, cmd); ng_l2cap_lp_deliver(con); return (0); } /* send_l2cap_con_rej */ /* * Send L2CAP config response */ static int send_l2cap_cfg_rsp(ng_l2cap_con_p con, u_int8_t ident, u_int16_t scid, u_int16_t result, struct mbuf *opt) { ng_l2cap_cmd_p cmd = NULL; cmd = ng_l2cap_new_cmd(con, NULL, ident, NG_L2CAP_CFG_RSP, 0); if (cmd == NULL) { NG_FREE_M(opt); return (ENOMEM); } _ng_l2cap_cfg_rsp(cmd->aux, cmd->ident, scid, 0, result, opt); if (cmd->aux == NULL) { ng_l2cap_free_cmd(cmd); return (ENOBUFS); } /* Link command to the queue */ ng_l2cap_link_cmd(con, cmd); ng_l2cap_lp_deliver(con); return (0); } /* send_l2cap_cfg_rsp */ static int send_l2cap_param_urs(ng_l2cap_con_p con, u_int8_t ident, u_int16_t result) { ng_l2cap_cmd_p cmd = NULL; cmd = ng_l2cap_new_cmd(con, NULL, ident, NG_L2CAP_CMD_PARAM_UPDATE_RESPONSE, 0); if (cmd == NULL) { return (ENOMEM); } _ng_l2cap_cmd_urs(cmd->aux, cmd->ident, result); if (cmd->aux == NULL) { ng_l2cap_free_cmd(cmd); return (ENOBUFS); } /* Link command to the queue */ ng_l2cap_link_cmd(con, cmd); ng_l2cap_lp_deliver(con); return (0); } /* send_l2cap_cfg_rsp */ /* * Get next L2CAP configuration option * * Return codes: * 0 no option * 1 we have got option * -1 header too short * -2 bad option value or length * -3 unknown option */ static int get_next_l2cap_opt(struct mbuf *m, int *off, ng_l2cap_cfg_opt_p hdr, ng_l2cap_cfg_opt_val_p val) { int hint, len = m->m_pkthdr.len - (*off); if (len == 0) return (0); if (len < 0 || len < sizeof(*hdr)) return (-1); m_copydata(m, *off, sizeof(*hdr), (caddr_t) hdr); *off += sizeof(*hdr); len -= sizeof(*hdr); hint = NG_L2CAP_OPT_HINT(hdr->type); hdr->type &= NG_L2CAP_OPT_HINT_MASK; switch (hdr->type) { case NG_L2CAP_OPT_MTU: if (hdr->length != NG_L2CAP_OPT_MTU_SIZE || len < hdr->length) return (-2); m_copydata(m, *off, NG_L2CAP_OPT_MTU_SIZE, (caddr_t) val); val->mtu = le16toh(val->mtu); *off += NG_L2CAP_OPT_MTU_SIZE; break; case NG_L2CAP_OPT_FLUSH_TIMO: if (hdr->length != NG_L2CAP_OPT_FLUSH_TIMO_SIZE || len < hdr->length) return (-2); m_copydata(m, *off, NG_L2CAP_OPT_FLUSH_TIMO_SIZE, (caddr_t)val); val->flush_timo = le16toh(val->flush_timo); *off += NG_L2CAP_OPT_FLUSH_TIMO_SIZE; break; case NG_L2CAP_OPT_QOS: if (hdr->length != NG_L2CAP_OPT_QOS_SIZE || len < hdr->length) return (-2); m_copydata(m, *off, NG_L2CAP_OPT_QOS_SIZE, (caddr_t) val); val->flow.token_rate = le32toh(val->flow.token_rate); val->flow.token_bucket_size = le32toh(val->flow.token_bucket_size); val->flow.peak_bandwidth = le32toh(val->flow.peak_bandwidth); val->flow.latency = le32toh(val->flow.latency); val->flow.delay_variation = le32toh(val->flow.delay_variation); *off += NG_L2CAP_OPT_QOS_SIZE; break; default: if (hint) *off += hdr->length; else return (-3); break; } return (1); } /* get_next_l2cap_opt */ Index: head/sys/netgraph/bluetooth/l2cap/ng_l2cap_misc.c =================================================================== --- head/sys/netgraph/bluetooth/l2cap/ng_l2cap_misc.c (revision 298812) +++ head/sys/netgraph/bluetooth/l2cap/ng_l2cap_misc.c (revision 298813) @@ -1,697 +1,697 @@ /* * ng_l2cap_misc.c */ /*- * Copyright (c) Maksim Yevmenkin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id: ng_l2cap_misc.c,v 1.5 2003/09/08 19:11:45 max Exp $ * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static u_int16_t ng_l2cap_get_cid (ng_l2cap_p, int); /****************************************************************************** ****************************************************************************** ** Utility routines ****************************************************************************** ******************************************************************************/ /* * Send hook information to the upper layer */ void ng_l2cap_send_hook_info(node_p node, hook_p hook, void *arg1, int arg2) { ng_l2cap_p l2cap = NULL; struct ng_mesg *msg = NULL; int error = 0; ng_l2cap_node_hook_info_ep *ep ; if (node == NULL || NG_NODE_NOT_VALID(node) || hook == NULL || NG_HOOK_NOT_VALID(hook)) return; l2cap = (ng_l2cap_p) NG_NODE_PRIVATE(node); if (l2cap->hci == NULL || NG_HOOK_NOT_VALID(l2cap->hci) || bcmp(&l2cap->bdaddr, NG_HCI_BDADDR_ANY, sizeof(l2cap->bdaddr)) == 0) return; NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_NODE_HOOK_INFO, sizeof(*ep), M_NOWAIT); if (msg != NULL) { ep = (ng_l2cap_node_hook_info_ep *) &msg->data; bcopy(&l2cap->bdaddr, &ep->addr, sizeof(bdaddr_t)); NG_SEND_MSG_HOOK(error, node, msg, hook, 0); } else error = ENOMEM; if (error != 0) NG_L2CAP_INFO( "%s: %s - failed to send HOOK_INFO message to hook \"%s\", error=%d\n", __func__, NG_NODE_NAME(l2cap->node), NG_HOOK_NAME(hook), error); } /* ng_l2cap_send_hook_info */ /* * Create new connection descriptor for the "remote" unit. * Will link connection descriptor to the l2cap node. */ ng_l2cap_con_p ng_l2cap_new_con(ng_l2cap_p l2cap, bdaddr_p bdaddr, int type) { static int fake_con_handle = 0x0f00; ng_l2cap_con_p con = NULL; /* Create new connection descriptor */ con = malloc(sizeof(*con), M_NETGRAPH_L2CAP, M_NOWAIT|M_ZERO); if (con == NULL) return (NULL); con->l2cap = l2cap; con->state = NG_L2CAP_CON_CLOSED; con->encryption = 0; /* * XXX * * Assign fake connection handle to the connection descriptor. * Bluetooth specification marks 0x0f00 - 0x0fff connection * handles as reserved. We need this fake connection handles * for timeouts. Connection handle will be passed as argument * to timeout so when timeout happens we can find the right * connection descriptor. We can not pass pointers, because * timeouts are external (to Netgraph) events and there might * be a race when node/hook goes down and timeout event already * went into node's queue */ con->con_handle = fake_con_handle ++; if (fake_con_handle > 0x0fff) fake_con_handle = 0x0f00; bcopy(bdaddr, &con->remote, sizeof(con->remote)); con->linktype = type; ng_callout_init(&con->con_timo); con->ident = NG_L2CAP_FIRST_IDENT - 1; TAILQ_INIT(&con->cmd_list); /* Link connection */ LIST_INSERT_HEAD(&l2cap->con_list, con, next); return (con); } /* ng_l2cap_new_con */ /* * Add reference to the connection descriptor */ void ng_l2cap_con_ref(ng_l2cap_con_p con) { con->refcnt ++; if (con->flags & NG_L2CAP_CON_AUTO_DISCON_TIMO) { if ((con->state != NG_L2CAP_CON_OPEN) || (con->flags & NG_L2CAP_CON_OUTGOING) == 0) panic( "%s: %s - bad auto disconnect timeout, state=%d, flags=%#x\n", __func__, NG_NODE_NAME(con->l2cap->node), con->state, con->flags); ng_l2cap_discon_untimeout(con); } } /* ng_l2cap_con_ref */ /* * Remove reference from the connection descriptor */ void ng_l2cap_con_unref(ng_l2cap_con_p con) { con->refcnt --; if (con->refcnt < 0) panic( "%s: %s - con->refcnt < 0\n", __func__, NG_NODE_NAME(con->l2cap->node)); /* * Set auto disconnect timer only if the following conditions are met: * 1) we have no reference on the connection * 2) connection is in OPEN state * 3) it is an outgoing connection * 4) disconnect timeout > 0 * 5) connection is not dying */ if ((con->refcnt == 0) && (con->state == NG_L2CAP_CON_OPEN) && (con->flags & NG_L2CAP_CON_OUTGOING) && (con->l2cap->discon_timo > 0) && ((con->flags & NG_L2CAP_CON_DYING) == 0)) ng_l2cap_discon_timeout(con); } /* ng_l2cap_con_unref */ /* * Set auto disconnect timeout * XXX FIXME: check return code from ng_callout */ int ng_l2cap_discon_timeout(ng_l2cap_con_p con) { if (con->flags & (NG_L2CAP_CON_LP_TIMO|NG_L2CAP_CON_AUTO_DISCON_TIMO)) panic( "%s: %s - invalid timeout, state=%d, flags=%#x\n", __func__, NG_NODE_NAME(con->l2cap->node), con->state, con->flags); con->flags |= NG_L2CAP_CON_AUTO_DISCON_TIMO; ng_callout(&con->con_timo, con->l2cap->node, NULL, con->l2cap->discon_timo * hz, ng_l2cap_process_discon_timeout, NULL, con->con_handle); return (0); } /* ng_l2cap_discon_timeout */ /* * Unset auto disconnect timeout */ int ng_l2cap_discon_untimeout(ng_l2cap_con_p con) { if (!(con->flags & NG_L2CAP_CON_AUTO_DISCON_TIMO)) panic( "%s: %s - no disconnect timeout, state=%d, flags=%#x\n", __func__, NG_NODE_NAME(con->l2cap->node), con->state, con->flags); if (ng_uncallout(&con->con_timo, con->l2cap->node) == 0) return (ETIMEDOUT); con->flags &= ~NG_L2CAP_CON_AUTO_DISCON_TIMO; return (0); } /* ng_l2cap_discon_untimeout */ /* * Free connection descriptor. Will unlink connection and free everything. */ void ng_l2cap_free_con(ng_l2cap_con_p con) { ng_l2cap_chan_p f = NULL, n = NULL; con->state = NG_L2CAP_CON_CLOSED; while (con->tx_pkt != NULL) { struct mbuf *m = con->tx_pkt->m_nextpkt; m_freem(con->tx_pkt); con->tx_pkt = m; } NG_FREE_M(con->rx_pkt); for (f = LIST_FIRST(&con->l2cap->chan_list); f != NULL; ) { n = LIST_NEXT(f, next); if (f->con == con) ng_l2cap_free_chan(f); f = n; } while (!TAILQ_EMPTY(&con->cmd_list)) { ng_l2cap_cmd_p cmd = TAILQ_FIRST(&con->cmd_list); ng_l2cap_unlink_cmd(cmd); if (cmd->flags & NG_L2CAP_CMD_PENDING) ng_l2cap_command_untimeout(cmd); ng_l2cap_free_cmd(cmd); } if (con->flags & (NG_L2CAP_CON_AUTO_DISCON_TIMO|NG_L2CAP_CON_LP_TIMO)) panic( "%s: %s - timeout pending! state=%d, flags=%#x\n", __func__, NG_NODE_NAME(con->l2cap->node), con->state, con->flags); LIST_REMOVE(con, next); bzero(con, sizeof(*con)); free(con, M_NETGRAPH_L2CAP); } /* ng_l2cap_free_con */ /* * Get connection by "remote" address */ ng_l2cap_con_p ng_l2cap_con_by_addr(ng_l2cap_p l2cap, bdaddr_p bdaddr, unsigned int type) { ng_l2cap_con_p con = NULL; LIST_FOREACH(con, &l2cap->con_list, next) if ((bcmp(bdaddr, &con->remote, sizeof(con->remote)) == 0)&& (con->linktype == type)) break; return (con); } /* ng_l2cap_con_by_addr */ /* * Get connection by "handle" */ ng_l2cap_con_p ng_l2cap_con_by_handle(ng_l2cap_p l2cap, u_int16_t con_handle) { ng_l2cap_con_p con = NULL; LIST_FOREACH(con, &l2cap->con_list, next) if (con->con_handle == con_handle) break; return (con); } /* ng_l2cap_con_by_handle */ /* - * Allocate new L2CAP channel descriptor on "con" conection with "psm". + * Allocate new L2CAP channel descriptor on "con" connection with "psm". * Will link the channel to the l2cap node */ ng_l2cap_chan_p ng_l2cap_new_chan(ng_l2cap_p l2cap, ng_l2cap_con_p con, u_int16_t psm, int idtype) { ng_l2cap_chan_p ch = NULL; ch = malloc(sizeof(*ch), M_NETGRAPH_L2CAP, M_NOWAIT|M_ZERO); if (ch == NULL) return (NULL); if(idtype == NG_L2CAP_L2CA_IDTYPE_ATT){ ch->scid = ch->dcid = NG_L2CAP_ATT_CID; }else if(idtype == NG_L2CAP_L2CA_IDTYPE_SMP){ ch->scid = ch->dcid = NG_L2CAP_SMP_CID; }else{ ch->scid = ng_l2cap_get_cid(l2cap, (con->linktype!= NG_HCI_LINK_ACL)); } if (ch->scid != NG_L2CAP_NULL_CID) { /* Initialize channel */ ch->psm = psm; ch->con = con; ch->state = NG_L2CAP_CLOSED; /* Set MTU and flow control settings to defaults */ ch->imtu = NG_L2CAP_MTU_DEFAULT; bcopy(ng_l2cap_default_flow(), &ch->iflow, sizeof(ch->iflow)); ch->omtu = NG_L2CAP_MTU_DEFAULT; bcopy(ng_l2cap_default_flow(), &ch->oflow, sizeof(ch->oflow)); ch->flush_timo = NG_L2CAP_FLUSH_TIMO_DEFAULT; ch->link_timo = NG_L2CAP_LINK_TIMO_DEFAULT; LIST_INSERT_HEAD(&l2cap->chan_list, ch, next); ng_l2cap_con_ref(con); } else { bzero(ch, sizeof(*ch)); free(ch, M_NETGRAPH_L2CAP); ch = NULL; } return (ch); } /* ng_l2cap_new_chan */ ng_l2cap_chan_p ng_l2cap_chan_by_scid(ng_l2cap_p l2cap, u_int16_t scid, int idtype) { ng_l2cap_chan_p ch = NULL; if((idtype == NG_L2CAP_L2CA_IDTYPE_ATT)|| (idtype == NG_L2CAP_L2CA_IDTYPE_SMP)){ return NULL; } LIST_FOREACH(ch, &l2cap->chan_list, next){ if((idtype != NG_L2CAP_L2CA_IDTYPE_BREDR)&& (ch->con->linktype == NG_HCI_LINK_ACL )) continue; if((idtype != NG_L2CAP_L2CA_IDTYPE_LE)&& (ch->con->linktype != NG_HCI_LINK_ACL )) continue; if (ch->scid == scid) break; } return (ch); } /* ng_l2cap_chan_by_scid */ ng_l2cap_chan_p ng_l2cap_chan_by_conhandle(ng_l2cap_p l2cap, uint16_t scid, u_int16_t con_handle) { ng_l2cap_chan_p ch = NULL; LIST_FOREACH(ch, &l2cap->chan_list, next){ if ((ch->scid == scid) && (ch->con->con_handle == con_handle)) break; } return (ch); } /* ng_l2cap_chan_by_scid */ /* * Free channel descriptor. */ void ng_l2cap_free_chan(ng_l2cap_chan_p ch) { ng_l2cap_cmd_p f = NULL, n = NULL; f = TAILQ_FIRST(&ch->con->cmd_list); while (f != NULL) { n = TAILQ_NEXT(f, next); if (f->ch == ch) { ng_l2cap_unlink_cmd(f); if (f->flags & NG_L2CAP_CMD_PENDING) ng_l2cap_command_untimeout(f); ng_l2cap_free_cmd(f); } f = n; } LIST_REMOVE(ch, next); ng_l2cap_con_unref(ch->con); bzero(ch, sizeof(*ch)); free(ch, M_NETGRAPH_L2CAP); } /* ng_l2cap_free_chan */ /* * Create new L2CAP command descriptor. WILL NOT add command to the queue. */ ng_l2cap_cmd_p ng_l2cap_new_cmd(ng_l2cap_con_p con, ng_l2cap_chan_p ch, u_int8_t ident, u_int8_t code, u_int32_t token) { ng_l2cap_cmd_p cmd = NULL; KASSERT((ch == NULL || ch->con == con), ("%s: %s - invalid channel pointer!\n", __func__, NG_NODE_NAME(con->l2cap->node))); cmd = malloc(sizeof(*cmd), M_NETGRAPH_L2CAP, M_NOWAIT|M_ZERO); if (cmd == NULL) return (NULL); cmd->con = con; cmd->ch = ch; cmd->ident = ident; cmd->code = code; cmd->token = token; ng_callout_init(&cmd->timo); return (cmd); } /* ng_l2cap_new_cmd */ /* * Get pending (i.e. initiated by local side) L2CAP command descriptor by ident */ ng_l2cap_cmd_p ng_l2cap_cmd_by_ident(ng_l2cap_con_p con, u_int8_t ident) { ng_l2cap_cmd_p cmd = NULL; TAILQ_FOREACH(cmd, &con->cmd_list, next) { if ((cmd->flags & NG_L2CAP_CMD_PENDING) && cmd->ident == ident) { KASSERT((cmd->con == con), ("%s: %s - invalid connection pointer!\n", __func__, NG_NODE_NAME(con->l2cap->node))); break; } } return (cmd); } /* ng_l2cap_cmd_by_ident */ /* * Set LP timeout * XXX FIXME: check return code from ng_callout */ int ng_l2cap_lp_timeout(ng_l2cap_con_p con) { if (con->flags & (NG_L2CAP_CON_LP_TIMO|NG_L2CAP_CON_AUTO_DISCON_TIMO)) panic( "%s: %s - invalid timeout, state=%d, flags=%#x\n", __func__, NG_NODE_NAME(con->l2cap->node), con->state, con->flags); con->flags |= NG_L2CAP_CON_LP_TIMO; ng_callout(&con->con_timo, con->l2cap->node, NULL, bluetooth_hci_connect_timeout(), ng_l2cap_process_lp_timeout, NULL, con->con_handle); return (0); } /* ng_l2cap_lp_timeout */ /* * Unset LP timeout */ int ng_l2cap_lp_untimeout(ng_l2cap_con_p con) { if (!(con->flags & NG_L2CAP_CON_LP_TIMO)) panic( "%s: %s - no LP connection timeout, state=%d, flags=%#x\n", __func__, NG_NODE_NAME(con->l2cap->node), con->state, con->flags); if (ng_uncallout(&con->con_timo, con->l2cap->node) == 0) return (ETIMEDOUT); con->flags &= ~NG_L2CAP_CON_LP_TIMO; return (0); } /* ng_l2cap_lp_untimeout */ /* * Set L2CAP command timeout * XXX FIXME: check return code from ng_callout */ int ng_l2cap_command_timeout(ng_l2cap_cmd_p cmd, int timo) { int arg; if (cmd->flags & NG_L2CAP_CMD_PENDING) panic( "%s: %s - duplicated command timeout, code=%#x, flags=%#x\n", __func__, NG_NODE_NAME(cmd->con->l2cap->node), cmd->code, cmd->flags); arg = ((cmd->ident << 16) | cmd->con->con_handle); cmd->flags |= NG_L2CAP_CMD_PENDING; ng_callout(&cmd->timo, cmd->con->l2cap->node, NULL, timo, ng_l2cap_process_command_timeout, NULL, arg); return (0); } /* ng_l2cap_command_timeout */ /* * Unset L2CAP command timeout */ int ng_l2cap_command_untimeout(ng_l2cap_cmd_p cmd) { if (!(cmd->flags & NG_L2CAP_CMD_PENDING)) panic( "%s: %s - no command timeout, code=%#x, flags=%#x\n", __func__, NG_NODE_NAME(cmd->con->l2cap->node), cmd->code, cmd->flags); if (ng_uncallout(&cmd->timo, cmd->con->l2cap->node) == 0) return (ETIMEDOUT); cmd->flags &= ~NG_L2CAP_CMD_PENDING; return (0); } /* ng_l2cap_command_untimeout */ /* * Prepend "m"buf with "size" bytes */ struct mbuf * ng_l2cap_prepend(struct mbuf *m, int size) { M_PREPEND(m, size, M_NOWAIT); if (m == NULL || (m->m_len < size && (m = m_pullup(m, size)) == NULL)) return (NULL); return (m); } /* ng_l2cap_prepend */ /* * Default flow settings */ ng_l2cap_flow_p ng_l2cap_default_flow(void) { static ng_l2cap_flow_t default_flow = { /* flags */ 0x0, /* service_type */ NG_HCI_SERVICE_TYPE_BEST_EFFORT, /* token_rate */ 0xffffffff, /* maximum */ /* token_bucket_size */ 0xffffffff, /* maximum */ /* peak_bandwidth */ 0x00000000, /* maximum */ /* latency */ 0xffffffff, /* don't care */ /* delay_variation */ 0xffffffff /* don't care */ }; return (&default_flow); } /* ng_l2cap_default_flow */ /* * Get next available channel ID * XXX FIXME this is *UGLY* but will do for now */ static u_int16_t ng_l2cap_get_cid(ng_l2cap_p l2cap,int isle) { u_int16_t cid ; u_int16_t endcid; uint16_t mask; int idtype; if(isle){ endcid = l2cap->lecid; /*Assume Last CID is 2^n-1 */ mask = NG_L2CAP_LELAST_CID; idtype = NG_L2CAP_L2CA_IDTYPE_LE; }else{ endcid = l2cap->cid; /*Assume Last CID is 2^n-1 */ mask = NG_L2CAP_LAST_CID; idtype = NG_L2CAP_L2CA_IDTYPE_BREDR; } cid = (endcid+1) & mask; if (cid < NG_L2CAP_FIRST_CID) cid = NG_L2CAP_FIRST_CID; while (cid != endcid) { if (ng_l2cap_chan_by_scid(l2cap, cid, idtype) == NULL) { if(!isle){ l2cap->cid = cid; }else{ l2cap->lecid = cid; } return (cid); } cid ++; cid &= mask; if (cid < NG_L2CAP_FIRST_CID) cid = NG_L2CAP_FIRST_CID; } return (NG_L2CAP_NULL_CID); } /* ng_l2cap_get_cid */ /* * Get next available command ident * XXX FIXME this is *UGLY* but will do for now */ u_int8_t ng_l2cap_get_ident(ng_l2cap_con_p con) { u_int8_t ident = con->ident + 1; if (ident < NG_L2CAP_FIRST_IDENT) ident = NG_L2CAP_FIRST_IDENT; while (ident != con->ident) { if (ng_l2cap_cmd_by_ident(con, ident) == NULL) { con->ident = ident; return (ident); } ident ++; if (ident < NG_L2CAP_FIRST_IDENT) ident = NG_L2CAP_FIRST_IDENT; } return (NG_L2CAP_NULL_IDENT); } /* ng_l2cap_get_ident */ Index: head/sys/netgraph/bluetooth/socket/ng_btsocket_l2cap.c =================================================================== --- head/sys/netgraph/bluetooth/socket/ng_btsocket_l2cap.c (revision 298812) +++ head/sys/netgraph/bluetooth/socket/ng_btsocket_l2cap.c (revision 298813) @@ -1,2979 +1,2979 @@ /* * ng_btsocket_l2cap.c */ /*- * Copyright (c) 2001-2002 Maksim Yevmenkin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id: ng_btsocket_l2cap.c,v 1.16 2003/09/14 23:29:06 max Exp $ * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* MALLOC define */ #ifdef NG_SEPARATE_MALLOC static MALLOC_DEFINE(M_NETGRAPH_BTSOCKET_L2CAP, "netgraph_btsocks_l2cap", "Netgraph Bluetooth L2CAP sockets"); #else #define M_NETGRAPH_BTSOCKET_L2CAP M_NETGRAPH #endif /* NG_SEPARATE_MALLOC */ /* Netgraph node methods */ static ng_constructor_t ng_btsocket_l2cap_node_constructor; static ng_rcvmsg_t ng_btsocket_l2cap_node_rcvmsg; static ng_shutdown_t ng_btsocket_l2cap_node_shutdown; static ng_newhook_t ng_btsocket_l2cap_node_newhook; static ng_connect_t ng_btsocket_l2cap_node_connect; static ng_rcvdata_t ng_btsocket_l2cap_node_rcvdata; static ng_disconnect_t ng_btsocket_l2cap_node_disconnect; static void ng_btsocket_l2cap_input (void *, int); static void ng_btsocket_l2cap_rtclean (void *, int); /* Netgraph type descriptor */ static struct ng_type typestruct = { .version = NG_ABI_VERSION, .name = NG_BTSOCKET_L2CAP_NODE_TYPE, .constructor = ng_btsocket_l2cap_node_constructor, .rcvmsg = ng_btsocket_l2cap_node_rcvmsg, .shutdown = ng_btsocket_l2cap_node_shutdown, .newhook = ng_btsocket_l2cap_node_newhook, .connect = ng_btsocket_l2cap_node_connect, .rcvdata = ng_btsocket_l2cap_node_rcvdata, .disconnect = ng_btsocket_l2cap_node_disconnect, }; /* Globals */ extern int ifqmaxlen; static u_int32_t ng_btsocket_l2cap_debug_level; static node_p ng_btsocket_l2cap_node; static struct ng_bt_itemq ng_btsocket_l2cap_queue; static struct mtx ng_btsocket_l2cap_queue_mtx; static struct task ng_btsocket_l2cap_queue_task; static LIST_HEAD(, ng_btsocket_l2cap_pcb) ng_btsocket_l2cap_sockets; static struct mtx ng_btsocket_l2cap_sockets_mtx; static LIST_HEAD(, ng_btsocket_l2cap_rtentry) ng_btsocket_l2cap_rt; static struct mtx ng_btsocket_l2cap_rt_mtx; static struct task ng_btsocket_l2cap_rt_task; static struct timeval ng_btsocket_l2cap_lasttime; static int ng_btsocket_l2cap_curpps; /* Sysctl tree */ SYSCTL_DECL(_net_bluetooth_l2cap_sockets); static SYSCTL_NODE(_net_bluetooth_l2cap_sockets, OID_AUTO, seq, CTLFLAG_RW, 0, "Bluetooth SEQPACKET L2CAP sockets family"); SYSCTL_UINT(_net_bluetooth_l2cap_sockets_seq, OID_AUTO, debug_level, CTLFLAG_RW, &ng_btsocket_l2cap_debug_level, NG_BTSOCKET_WARN_LEVEL, "Bluetooth SEQPACKET L2CAP sockets debug level"); SYSCTL_UINT(_net_bluetooth_l2cap_sockets_seq, OID_AUTO, queue_len, CTLFLAG_RD, &ng_btsocket_l2cap_queue.len, 0, "Bluetooth SEQPACKET L2CAP sockets input queue length"); SYSCTL_UINT(_net_bluetooth_l2cap_sockets_seq, OID_AUTO, queue_maxlen, CTLFLAG_RD, &ng_btsocket_l2cap_queue.maxlen, 0, "Bluetooth SEQPACKET L2CAP sockets input queue max. length"); SYSCTL_UINT(_net_bluetooth_l2cap_sockets_seq, OID_AUTO, queue_drops, CTLFLAG_RD, &ng_btsocket_l2cap_queue.drops, 0, "Bluetooth SEQPACKET L2CAP sockets input queue drops"); /* Debug */ #define NG_BTSOCKET_L2CAP_INFO \ if (ng_btsocket_l2cap_debug_level >= NG_BTSOCKET_INFO_LEVEL && \ ppsratecheck(&ng_btsocket_l2cap_lasttime, &ng_btsocket_l2cap_curpps, 1)) \ printf #define NG_BTSOCKET_L2CAP_WARN \ if (ng_btsocket_l2cap_debug_level >= NG_BTSOCKET_WARN_LEVEL && \ ppsratecheck(&ng_btsocket_l2cap_lasttime, &ng_btsocket_l2cap_curpps, 1)) \ printf #define NG_BTSOCKET_L2CAP_ERR \ if (ng_btsocket_l2cap_debug_level >= NG_BTSOCKET_ERR_LEVEL && \ ppsratecheck(&ng_btsocket_l2cap_lasttime, &ng_btsocket_l2cap_curpps, 1)) \ printf #define NG_BTSOCKET_L2CAP_ALERT \ if (ng_btsocket_l2cap_debug_level >= NG_BTSOCKET_ALERT_LEVEL && \ ppsratecheck(&ng_btsocket_l2cap_lasttime, &ng_btsocket_l2cap_curpps, 1)) \ printf /* * Netgraph message processing routines */ static int ng_btsocket_l2cap_process_l2ca_con_req_rsp (struct ng_mesg *, ng_btsocket_l2cap_rtentry_p); static int ng_btsocket_l2cap_process_l2ca_con_rsp_rsp (struct ng_mesg *, ng_btsocket_l2cap_rtentry_p); static int ng_btsocket_l2cap_process_l2ca_con_ind (struct ng_mesg *, ng_btsocket_l2cap_rtentry_p); static int ng_btsocket_l2cap_process_l2ca_cfg_req_rsp (struct ng_mesg *, ng_btsocket_l2cap_rtentry_p); static int ng_btsocket_l2cap_process_l2ca_cfg_rsp_rsp (struct ng_mesg *, ng_btsocket_l2cap_rtentry_p); static int ng_btsocket_l2cap_process_l2ca_cfg_ind (struct ng_mesg *, ng_btsocket_l2cap_rtentry_p); static int ng_btsocket_l2cap_process_l2ca_discon_rsp (struct ng_mesg *, ng_btsocket_l2cap_rtentry_p); static int ng_btsocket_l2cap_process_l2ca_discon_ind (struct ng_mesg *, ng_btsocket_l2cap_rtentry_p); static int ng_btsocket_l2cap_process_l2ca_write_rsp (struct ng_mesg *, ng_btsocket_l2cap_rtentry_p); /* * Send L2CA_xxx messages to the lower layer */ static int ng_btsocket_l2cap_send_l2ca_con_req (ng_btsocket_l2cap_pcb_p); static int ng_btsocket_l2cap_send_l2ca_con_rsp_req (u_int32_t, ng_btsocket_l2cap_rtentry_p, bdaddr_p, int, int, int, int); static int ng_btsocket_l2cap_send_l2ca_cfg_req (ng_btsocket_l2cap_pcb_p); static int ng_btsocket_l2cap_send_l2ca_cfg_rsp (ng_btsocket_l2cap_pcb_p); static int ng_btsocket_l2cap_send_l2ca_discon_req (u_int32_t, ng_btsocket_l2cap_pcb_p); static int ng_btsocket_l2cap_send2 (ng_btsocket_l2cap_pcb_p); /* * Timeout processing routines */ static void ng_btsocket_l2cap_timeout (ng_btsocket_l2cap_pcb_p); static void ng_btsocket_l2cap_untimeout (ng_btsocket_l2cap_pcb_p); static void ng_btsocket_l2cap_process_timeout (void *); /* * Other stuff */ static ng_btsocket_l2cap_pcb_p ng_btsocket_l2cap_pcb_by_addr(bdaddr_p, int); static ng_btsocket_l2cap_pcb_p ng_btsocket_l2cap_pcb_by_token(u_int32_t); static ng_btsocket_l2cap_pcb_p ng_btsocket_l2cap_pcb_by_cid (bdaddr_p, int,int); static int ng_btsocket_l2cap_result2errno(int); static int ng_btsock_l2cap_addrtype_to_linktype(int addrtype); #define ng_btsocket_l2cap_wakeup_input_task() \ taskqueue_enqueue(taskqueue_swi_giant, &ng_btsocket_l2cap_queue_task) #define ng_btsocket_l2cap_wakeup_route_task() \ taskqueue_enqueue(taskqueue_swi_giant, &ng_btsocket_l2cap_rt_task) int ng_btsock_l2cap_addrtype_to_linktype(int addrtype) { switch(addrtype){ case BDADDR_LE_PUBLIC: return NG_HCI_LINK_LE_PUBLIC; case BDADDR_LE_RANDOM: return NG_HCI_LINK_LE_RANDOM; default: return NG_HCI_LINK_ACL; } } /***************************************************************************** ***************************************************************************** ** Netgraph node interface ***************************************************************************** *****************************************************************************/ /* * Netgraph node constructor. Do not allow to create node of this type. */ static int ng_btsocket_l2cap_node_constructor(node_p node) { return (EINVAL); } /* ng_btsocket_l2cap_node_constructor */ /* * Do local shutdown processing. Let old node go and create new fresh one. */ static int ng_btsocket_l2cap_node_shutdown(node_p node) { int error = 0; NG_NODE_UNREF(node); /* Create new node */ error = ng_make_node_common(&typestruct, &ng_btsocket_l2cap_node); if (error != 0) { NG_BTSOCKET_L2CAP_ALERT( "%s: Could not create Netgraph node, error=%d\n", __func__, error); ng_btsocket_l2cap_node = NULL; return (error); } error = ng_name_node(ng_btsocket_l2cap_node, NG_BTSOCKET_L2CAP_NODE_TYPE); if (error != 0) { NG_BTSOCKET_L2CAP_ALERT( "%s: Could not name Netgraph node, error=%d\n", __func__, error); NG_NODE_UNREF(ng_btsocket_l2cap_node); ng_btsocket_l2cap_node = NULL; return (error); } return (0); } /* ng_btsocket_l2cap_node_shutdown */ /* * We allow any hook to be connected to the node. */ static int ng_btsocket_l2cap_node_newhook(node_p node, hook_p hook, char const *name) { return (0); } /* ng_btsocket_l2cap_node_newhook */ /* * Just say "YEP, that's OK by me!" */ static int ng_btsocket_l2cap_node_connect(hook_p hook) { NG_HOOK_SET_PRIVATE(hook, NULL); NG_HOOK_REF(hook); /* Keep extra reference to the hook */ #if 0 NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook)); NG_HOOK_FORCE_QUEUE(hook); #endif return (0); } /* ng_btsocket_l2cap_node_connect */ /* * Hook disconnection. Schedule route cleanup task */ static int ng_btsocket_l2cap_node_disconnect(hook_p hook) { /* * If hook has private information than we must have this hook in * the routing table and must schedule cleaning for the routing table. * Otherwise hook was connected but we never got "hook_info" message, * so we have never added this hook to the routing table and it save * to just delete it. */ if (NG_HOOK_PRIVATE(hook) != NULL) return (ng_btsocket_l2cap_wakeup_route_task()); NG_HOOK_UNREF(hook); /* Remove extra reference */ return (0); } /* ng_btsocket_l2cap_node_disconnect */ /* * Process incoming messages */ static int ng_btsocket_l2cap_node_rcvmsg(node_p node, item_p item, hook_p hook) { struct ng_mesg *msg = NGI_MSG(item); /* item still has message */ int error = 0; if (msg != NULL && msg->header.typecookie == NGM_L2CAP_COOKIE) { mtx_lock(&ng_btsocket_l2cap_queue_mtx); if (NG_BT_ITEMQ_FULL(&ng_btsocket_l2cap_queue)) { NG_BTSOCKET_L2CAP_ERR( "%s: Input queue is full (msg)\n", __func__); NG_BT_ITEMQ_DROP(&ng_btsocket_l2cap_queue); NG_FREE_ITEM(item); error = ENOBUFS; } else { if (hook != NULL) { NG_HOOK_REF(hook); NGI_SET_HOOK(item, hook); } NG_BT_ITEMQ_ENQUEUE(&ng_btsocket_l2cap_queue, item); error = ng_btsocket_l2cap_wakeup_input_task(); } mtx_unlock(&ng_btsocket_l2cap_queue_mtx); } else { NG_FREE_ITEM(item); error = EINVAL; } return (error); } /* ng_btsocket_l2cap_node_rcvmsg */ /* * Receive data on a hook */ static int ng_btsocket_l2cap_node_rcvdata(hook_p hook, item_p item) { int error = 0; mtx_lock(&ng_btsocket_l2cap_queue_mtx); if (NG_BT_ITEMQ_FULL(&ng_btsocket_l2cap_queue)) { NG_BTSOCKET_L2CAP_ERR( "%s: Input queue is full (data)\n", __func__); NG_BT_ITEMQ_DROP(&ng_btsocket_l2cap_queue); NG_FREE_ITEM(item); error = ENOBUFS; } else { NG_HOOK_REF(hook); NGI_SET_HOOK(item, hook); NG_BT_ITEMQ_ENQUEUE(&ng_btsocket_l2cap_queue, item); error = ng_btsocket_l2cap_wakeup_input_task(); } mtx_unlock(&ng_btsocket_l2cap_queue_mtx); return (error); } /* ng_btsocket_l2cap_node_rcvdata */ /* * Process L2CA_Connect respose. Socket layer must have initiated connection, * so we have to have a socket associated with message token. */ static int ng_btsocket_l2cap_process_l2ca_con_req_rsp(struct ng_mesg *msg, ng_btsocket_l2cap_rtentry_p rt) { ng_l2cap_l2ca_con_op *op = NULL; ng_btsocket_l2cap_pcb_t *pcb = NULL; int error = 0; if (msg->header.arglen != sizeof(*op)) return (EMSGSIZE); op = (ng_l2cap_l2ca_con_op *)(msg->data); mtx_lock(&ng_btsocket_l2cap_sockets_mtx); /* Look for the socket with the token */ pcb = ng_btsocket_l2cap_pcb_by_token(msg->header.token); if (pcb == NULL) { mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (ENOENT); } mtx_lock(&pcb->pcb_mtx); NG_BTSOCKET_L2CAP_INFO( "%s: Got L2CA_Connect response, token=%d, src bdaddr=%x:%x:%x:%x:%x:%x, " \ "dst bdaddr=%x:%x:%x:%x:%x:%x, psm=%d, lcid=%d, result=%d, status=%d, " \ "state=%d\n", __func__, msg->header.token, pcb->src.b[5], pcb->src.b[4], pcb->src.b[3], pcb->src.b[2], pcb->src.b[1], pcb->src.b[0], pcb->dst.b[5], pcb->dst.b[4], pcb->dst.b[3], pcb->dst.b[2], pcb->dst.b[1], pcb->dst.b[0], pcb->psm, op->lcid, op->result, op->status, pcb->state); if (pcb->state != NG_BTSOCKET_L2CAP_CONNECTING) { mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (ENOENT); } ng_btsocket_l2cap_untimeout(pcb); if (op->result == NG_L2CAP_PENDING) { ng_btsocket_l2cap_timeout(pcb); mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (0); } if (op->result == NG_L2CAP_SUCCESS){ if((pcb->idtype == NG_L2CAP_L2CA_IDTYPE_ATT)|| (pcb->idtype == NG_L2CAP_L2CA_IDTYPE_SMP)){ pcb->encryption = op->encryption; pcb->cid = op->lcid; if(pcb->need_encrypt && !(pcb->encryption)){ ng_btsocket_l2cap_timeout(pcb); pcb->state = NG_BTSOCKET_L2CAP_W4_ENC_CHANGE; }else{ pcb->state = NG_BTSOCKET_L2CAP_OPEN; soisconnected(pcb->so); } }else{ /* * Channel is now open, so update local channel ID and * start configuration process. Source and destination * addresses as well as route must be already set. */ pcb->cid = op->lcid; pcb->encryption = op->encryption; error = ng_btsocket_l2cap_send_l2ca_cfg_req(pcb); if (error != 0) { /* Send disconnect request with "zero" token */ ng_btsocket_l2cap_send_l2ca_discon_req(0, pcb); /* ... and close the socket */ pcb->state = NG_BTSOCKET_L2CAP_CLOSED; soisdisconnected(pcb->so); } else { pcb->cfg_state = NG_BTSOCKET_L2CAP_CFG_IN_SENT; pcb->state = NG_BTSOCKET_L2CAP_CONFIGURING; ng_btsocket_l2cap_timeout(pcb); } } } else { /* * We have failed to open connection, so convert result * code to "errno" code and disconnect the socket. Channel * already has been closed. */ pcb->so->so_error = ng_btsocket_l2cap_result2errno(op->result); pcb->state = NG_BTSOCKET_L2CAP_CLOSED; soisdisconnected(pcb->so); } mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (error); } /* ng_btsocket_l2cap_process_l2ca_con_req_rsp */ /* * Process L2CA_ConnectRsp response */ static int ng_btsocket_l2cap_process_l2ca_con_rsp_rsp(struct ng_mesg *msg, ng_btsocket_l2cap_rtentry_p rt) { ng_l2cap_l2ca_con_rsp_op *op = NULL; ng_btsocket_l2cap_pcb_t *pcb = NULL; if (msg->header.arglen != sizeof(*op)) return (EMSGSIZE); op = (ng_l2cap_l2ca_con_rsp_op *)(msg->data); mtx_lock(&ng_btsocket_l2cap_sockets_mtx); /* Look for the socket with the token */ pcb = ng_btsocket_l2cap_pcb_by_token(msg->header.token); if (pcb == NULL) { mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (ENOENT); } mtx_lock(&pcb->pcb_mtx); NG_BTSOCKET_L2CAP_INFO( "%s: Got L2CA_ConnectRsp response, token=%d, src bdaddr=%x:%x:%x:%x:%x:%x, " \ "dst bdaddr=%x:%x:%x:%x:%x:%x, psm=%d, lcid=%d, result=%d, state=%d\n", __func__, msg->header.token, pcb->src.b[5], pcb->src.b[4], pcb->src.b[3], pcb->src.b[2], pcb->src.b[1], pcb->src.b[0], pcb->dst.b[5], pcb->dst.b[4], pcb->dst.b[3], pcb->dst.b[2], pcb->dst.b[1], pcb->dst.b[0], pcb->psm, pcb->cid, op->result, pcb->state); if (pcb->state != NG_BTSOCKET_L2CAP_CONNECTING) { mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (ENOENT); } ng_btsocket_l2cap_untimeout(pcb); /* Check the result and disconnect the socket on failure */ if (op->result != NG_L2CAP_SUCCESS) { /* Close the socket - channel already closed */ pcb->so->so_error = ng_btsocket_l2cap_result2errno(op->result); pcb->state = NG_BTSOCKET_L2CAP_CLOSED; soisdisconnected(pcb->so); } else { /* Move to CONFIGURING state and wait for CONFIG_IND */ pcb->cfg_state = 0; pcb->state = NG_BTSOCKET_L2CAP_CONFIGURING; ng_btsocket_l2cap_timeout(pcb); } mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (0); } /* ng_btsocket_process_l2ca_con_rsp_rsp */ /* * Process L2CA_Connect indicator. Find socket that listens on address * and PSM. Find exact or closest match. Create new socket and initiate * connection. */ static int ng_btsocket_l2cap_process_l2ca_con_ind(struct ng_mesg *msg, ng_btsocket_l2cap_rtentry_p rt) { ng_l2cap_l2ca_con_ind_ip *ip = NULL; ng_btsocket_l2cap_pcb_t *pcb = NULL, *pcb1 = NULL; int error = 0; u_int32_t token = 0; u_int16_t result = 0; if (msg->header.arglen != sizeof(*ip)) return (EMSGSIZE); ip = (ng_l2cap_l2ca_con_ind_ip *)(msg->data); NG_BTSOCKET_L2CAP_INFO( "%s: Got L2CA_Connect indicator, src bdaddr=%x:%x:%x:%x:%x:%x, " \ "dst bdaddr=%x:%x:%x:%x:%x:%x, psm=%d, lcid=%d, ident=%d\n", __func__, rt->src.b[5], rt->src.b[4], rt->src.b[3], rt->src.b[2], rt->src.b[1], rt->src.b[0], ip->bdaddr.b[5], ip->bdaddr.b[4], ip->bdaddr.b[3], ip->bdaddr.b[2], ip->bdaddr.b[1], ip->bdaddr.b[0], ip->psm, ip->lcid, ip->ident); mtx_lock(&ng_btsocket_l2cap_sockets_mtx); pcb = ng_btsocket_l2cap_pcb_by_addr(&rt->src, ip->psm); if (pcb != NULL) { struct socket *so1 = NULL; mtx_lock(&pcb->pcb_mtx); /* * First check the pending connections queue and if we have * space then create new socket and set proper source address. */ if (pcb->so->so_qlen <= pcb->so->so_qlimit) { CURVNET_SET(pcb->so->so_vnet); so1 = sonewconn(pcb->so, 0); CURVNET_RESTORE(); } if (so1 == NULL) { result = NG_L2CAP_NO_RESOURCES; goto respond; } /* * If we got here than we have created new socket. So complete * connection. If we we listening on specific address then copy * source address from listening socket, otherwise copy source * address from hook's routing information. */ pcb1 = so2l2cap_pcb(so1); KASSERT((pcb1 != NULL), ("%s: pcb1 == NULL\n", __func__)); mtx_lock(&pcb1->pcb_mtx); if (bcmp(&pcb->src, NG_HCI_BDADDR_ANY, sizeof(pcb->src)) != 0) bcopy(&pcb->src, &pcb1->src, sizeof(pcb1->src)); else bcopy(&rt->src, &pcb1->src, sizeof(pcb1->src)); pcb1->flags &= ~NG_BTSOCKET_L2CAP_CLIENT; bcopy(&ip->bdaddr, &pcb1->dst, sizeof(pcb1->dst)); pcb1->psm = ip->psm; pcb1->cid = ip->lcid; pcb1->rt = rt; /* Copy socket settings */ pcb1->imtu = pcb->imtu; bcopy(&pcb->oflow, &pcb1->oflow, sizeof(pcb1->oflow)); pcb1->flush_timo = pcb->flush_timo; token = pcb1->token; } else /* Nobody listens on requested BDADDR/PSM */ result = NG_L2CAP_PSM_NOT_SUPPORTED; respond: error = ng_btsocket_l2cap_send_l2ca_con_rsp_req(token, rt, &ip->bdaddr, ip->ident, ip->lcid, result,ip->linktype); if (pcb1 != NULL) { if (error != 0) { pcb1->so->so_error = error; pcb1->state = NG_BTSOCKET_L2CAP_CLOSED; soisdisconnected(pcb1->so); } else { pcb1->state = NG_BTSOCKET_L2CAP_CONNECTING; soisconnecting(pcb1->so); ng_btsocket_l2cap_timeout(pcb1); } mtx_unlock(&pcb1->pcb_mtx); } if (pcb != NULL) mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (error); } /* ng_btsocket_l2cap_process_l2ca_con_ind */ /*Encryption Change*/ static int ng_btsocket_l2cap_process_l2ca_enc_change(struct ng_mesg *msg, ng_btsocket_l2cap_rtentry_p rt) { ng_l2cap_l2ca_enc_chg_op *op = NULL; ng_btsocket_l2cap_pcb_t *pcb = NULL; if (msg->header.arglen != sizeof(*op)) return (EMSGSIZE); op = (ng_l2cap_l2ca_enc_chg_op *)(msg->data); mtx_lock(&ng_btsocket_l2cap_sockets_mtx); pcb = ng_btsocket_l2cap_pcb_by_cid(&rt->src, op->lcid, op->idtype); if (pcb == NULL) { mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (ENOENT); } mtx_lock(&pcb->pcb_mtx); pcb->encryption = op->result; if(pcb->need_encrypt){ ng_btsocket_l2cap_untimeout(pcb); if(pcb->state != NG_BTSOCKET_L2CAP_W4_ENC_CHANGE){ NG_BTSOCKET_L2CAP_WARN("%s: Invalid pcb status %d", __func__, pcb->state); }else if(pcb->encryption){ pcb->state = NG_BTSOCKET_L2CAP_OPEN; soisconnected(pcb->so); }else{ pcb->so->so_error = EPERM; ng_btsocket_l2cap_send_l2ca_discon_req(0, pcb); pcb->state = NG_BTSOCKET_L2CAP_CLOSED; soisdisconnected(pcb->so); } } mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return 0; } /* * Process L2CA_Config response */ static int ng_btsocket_l2cap_process_l2ca_cfg_req_rsp(struct ng_mesg *msg, ng_btsocket_l2cap_rtentry_p rt) { ng_l2cap_l2ca_cfg_op *op = NULL; ng_btsocket_l2cap_pcb_p pcb = NULL; if (msg->header.arglen != sizeof(*op)) return (EMSGSIZE); op = (ng_l2cap_l2ca_cfg_op *)(msg->data); mtx_lock(&ng_btsocket_l2cap_sockets_mtx); /* * Socket must have issued a Configure request, so we must have a * socket that wants to be configured. Use Netgraph message token * to find it */ pcb = ng_btsocket_l2cap_pcb_by_token(msg->header.token); if (pcb == NULL) { /* * XXX FIXME what to do here? We could not find a * socket with requested token. We even can not send * Disconnect, because we do not know channel ID */ mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (ENOENT); } mtx_lock(&pcb->pcb_mtx); NG_BTSOCKET_L2CAP_INFO( "%s: Got L2CA_Config response, token=%d, src bdaddr=%x:%x:%x:%x:%x:%x, " \ "dst bdaddr=%x:%x:%x:%x:%x:%x, psm=%d, lcid=%d, result=%d, state=%d, " \ "cfg_state=%x\n", __func__, msg->header.token, pcb->src.b[5], pcb->src.b[4], pcb->src.b[3], pcb->src.b[2], pcb->src.b[1], pcb->src.b[0], pcb->dst.b[5], pcb->dst.b[4], pcb->dst.b[3], pcb->dst.b[2], pcb->dst.b[1], pcb->dst.b[0], pcb->psm, pcb->cid, op->result, pcb->state, pcb->cfg_state); if (pcb->state != NG_BTSOCKET_L2CAP_CONFIGURING) { mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (ENOENT); } if (op->result == NG_L2CAP_SUCCESS) { /* * XXX FIXME Actually set flush and link timeout. * Set QoS here if required. Resolve conficts (flush_timo). * Save incoming MTU (peer's outgoing MTU) and outgoing flow * spec. */ pcb->imtu = op->imtu; bcopy(&op->oflow, &pcb->oflow, sizeof(pcb->oflow)); pcb->flush_timo = op->flush_timo; /* * We have configured incoming side, so record it and check * if configuration is complete. If complete then mark socket * as connected, otherwise wait for the peer. */ pcb->cfg_state &= ~NG_BTSOCKET_L2CAP_CFG_IN_SENT; pcb->cfg_state |= NG_BTSOCKET_L2CAP_CFG_IN; if (pcb->cfg_state == NG_BTSOCKET_L2CAP_CFG_BOTH) { /* Configuration complete - mark socket as open */ ng_btsocket_l2cap_untimeout(pcb); pcb->state = NG_BTSOCKET_L2CAP_OPEN; soisconnected(pcb->so); } } else { /* * Something went wrong. Could be unacceptable parameters, * reject or unknown option. That's too bad, but we will * not negotiate. Send Disconnect and close the channel. */ ng_btsocket_l2cap_untimeout(pcb); switch (op->result) { case NG_L2CAP_UNACCEPTABLE_PARAMS: case NG_L2CAP_UNKNOWN_OPTION: pcb->so->so_error = EINVAL; break; default: pcb->so->so_error = ECONNRESET; break; } /* Send disconnect with "zero" token */ ng_btsocket_l2cap_send_l2ca_discon_req(0, pcb); /* ... and close the socket */ pcb->state = NG_BTSOCKET_L2CAP_CLOSED; soisdisconnected(pcb->so); } mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (0); } /* ng_btsocket_l2cap_process_l2ca_cfg_req_rsp */ /* * Process L2CA_ConfigRsp response */ static int ng_btsocket_l2cap_process_l2ca_cfg_rsp_rsp(struct ng_mesg *msg, ng_btsocket_l2cap_rtentry_p rt) { ng_l2cap_l2ca_cfg_rsp_op *op = NULL; ng_btsocket_l2cap_pcb_t *pcb = NULL; int error = 0; if (msg->header.arglen != sizeof(*op)) return (EMSGSIZE); op = (ng_l2cap_l2ca_cfg_rsp_op *)(msg->data); mtx_lock(&ng_btsocket_l2cap_sockets_mtx); /* Look for the socket with the token */ pcb = ng_btsocket_l2cap_pcb_by_token(msg->header.token); if (pcb == NULL) { mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (ENOENT); } mtx_lock(&pcb->pcb_mtx); NG_BTSOCKET_L2CAP_INFO( "%s: Got L2CA_ConfigRsp response, token=%d, src bdaddr=%x:%x:%x:%x:%x:%x, " \ "dst bdaddr=%x:%x:%x:%x:%x:%x, psm=%d, lcid=%d, result=%d, state=%d, " \ "cfg_state=%x\n", __func__, msg->header.token, pcb->src.b[5], pcb->src.b[4], pcb->src.b[3], pcb->src.b[2], pcb->src.b[1], pcb->src.b[0], pcb->dst.b[5], pcb->dst.b[4], pcb->dst.b[3], pcb->dst.b[2], pcb->dst.b[1], pcb->dst.b[0], pcb->psm, pcb->cid, op->result, pcb->state, pcb->cfg_state); if (pcb->state != NG_BTSOCKET_L2CAP_CONFIGURING) { mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (ENOENT); } /* Check the result and disconnect socket of failure */ if (op->result != NG_L2CAP_SUCCESS) goto disconnect; /* * Now we done with remote side configuration. Configure local * side if we have not done it yet. */ pcb->cfg_state &= ~NG_BTSOCKET_L2CAP_CFG_OUT_SENT; pcb->cfg_state |= NG_BTSOCKET_L2CAP_CFG_OUT; if (pcb->cfg_state == NG_BTSOCKET_L2CAP_CFG_BOTH) { /* Configuration complete - mask socket as open */ ng_btsocket_l2cap_untimeout(pcb); pcb->state = NG_BTSOCKET_L2CAP_OPEN; soisconnected(pcb->so); } else { if (!(pcb->cfg_state & NG_BTSOCKET_L2CAP_CFG_IN_SENT)) { /* Send L2CA_Config request - incoming path */ error = ng_btsocket_l2cap_send_l2ca_cfg_req(pcb); if (error != 0) goto disconnect; pcb->cfg_state |= NG_BTSOCKET_L2CAP_CFG_IN_SENT; } } mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (error); disconnect: ng_btsocket_l2cap_untimeout(pcb); /* Send disconnect with "zero" token */ ng_btsocket_l2cap_send_l2ca_discon_req(0, pcb); /* ... and close the socket */ pcb->state = NG_BTSOCKET_L2CAP_CLOSED; soisdisconnected(pcb->so); mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (error); } /* ng_btsocket_l2cap_process_l2ca_cfg_rsp_rsp */ /* * Process L2CA_Config indicator */ static int ng_btsocket_l2cap_process_l2ca_cfg_ind(struct ng_mesg *msg, ng_btsocket_l2cap_rtentry_p rt) { ng_l2cap_l2ca_cfg_ind_ip *ip = NULL; ng_btsocket_l2cap_pcb_t *pcb = NULL; int error = 0; if (msg->header.arglen != sizeof(*ip)) return (EMSGSIZE); ip = (ng_l2cap_l2ca_cfg_ind_ip *)(msg->data); mtx_lock(&ng_btsocket_l2cap_sockets_mtx); /* Check for the open socket that has given channel ID */ pcb = ng_btsocket_l2cap_pcb_by_cid(&rt->src, ip->lcid, NG_L2CAP_L2CA_IDTYPE_BREDR); if (pcb == NULL) { mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (ENOENT); } mtx_lock(&pcb->pcb_mtx); NG_BTSOCKET_L2CAP_INFO( "%s: Got L2CA_Config indicator, src bdaddr=%x:%x:%x:%x:%x:%x, " \ "dst bdaddr=%x:%x:%x:%x:%x:%x, psm=%d, lcid=%d, state=%d, cfg_state=%x\n", __func__, pcb->src.b[5], pcb->src.b[4], pcb->src.b[3], pcb->src.b[2], pcb->src.b[1], pcb->src.b[0], pcb->dst.b[5], pcb->dst.b[4], pcb->dst.b[3], pcb->dst.b[2], pcb->dst.b[1], pcb->dst.b[0], pcb->psm, pcb->cid, pcb->state, pcb->cfg_state); /* XXX FIXME re-configuration on open socket */ if (pcb->state != NG_BTSOCKET_L2CAP_CONFIGURING) { mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (ENOENT); } /* * XXX FIXME Actually set flush and link timeout. Set QoS here if * required. Resolve conficts (flush_timo). Note outgoing MTU (peer's * incoming MTU) and incoming flow spec. */ pcb->omtu = ip->omtu; bcopy(&ip->iflow, &pcb->iflow, sizeof(pcb->iflow)); pcb->flush_timo = ip->flush_timo; /* * Send L2CA_Config response to our peer and check for the errors, * if any send disconnect to close the channel. */ if (!(pcb->cfg_state & NG_BTSOCKET_L2CAP_CFG_OUT_SENT)) { error = ng_btsocket_l2cap_send_l2ca_cfg_rsp(pcb); if (error != 0) { ng_btsocket_l2cap_untimeout(pcb); pcb->so->so_error = error; /* Send disconnect with "zero" token */ ng_btsocket_l2cap_send_l2ca_discon_req(0, pcb); /* ... and close the socket */ pcb->state = NG_BTSOCKET_L2CAP_CLOSED; soisdisconnected(pcb->so); } else pcb->cfg_state |= NG_BTSOCKET_L2CAP_CFG_OUT_SENT; } mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (error); } /* ng_btsocket_l2cap_process_l2cap_cfg_ind */ /* * Process L2CA_Disconnect response */ static int ng_btsocket_l2cap_process_l2ca_discon_rsp(struct ng_mesg *msg, ng_btsocket_l2cap_rtentry_p rt) { ng_l2cap_l2ca_discon_op *op = NULL; ng_btsocket_l2cap_pcb_t *pcb = NULL; /* Check message */ if (msg->header.arglen != sizeof(*op)) return (EMSGSIZE); op = (ng_l2cap_l2ca_discon_op *)(msg->data); mtx_lock(&ng_btsocket_l2cap_sockets_mtx); /* * Socket layer must have issued L2CA_Disconnect request, so there * must be a socket that wants to be disconnected. Use Netgraph * message token to find it. */ pcb = ng_btsocket_l2cap_pcb_by_token(msg->header.token); if (pcb == NULL) { mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (0); } mtx_lock(&pcb->pcb_mtx); /* XXX Close socket no matter what op->result says */ if (pcb->state != NG_BTSOCKET_L2CAP_CLOSED) { NG_BTSOCKET_L2CAP_INFO( "%s: Got L2CA_Disconnect response, token=%d, src bdaddr=%x:%x:%x:%x:%x:%x, " \ "dst bdaddr=%x:%x:%x:%x:%x:%x, psm=%d, lcid=%d, result=%d, state=%d\n", __func__, msg->header.token, pcb->src.b[5], pcb->src.b[4], pcb->src.b[3], pcb->src.b[2], pcb->src.b[1], pcb->src.b[0], pcb->dst.b[5], pcb->dst.b[4], pcb->dst.b[3], pcb->dst.b[2], pcb->dst.b[1], pcb->dst.b[0], pcb->psm, pcb->cid, op->result, pcb->state); ng_btsocket_l2cap_untimeout(pcb); pcb->state = NG_BTSOCKET_L2CAP_CLOSED; soisdisconnected(pcb->so); } mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (0); } /* ng_btsocket_l2cap_process_l2ca_discon_rsp */ /* * Process L2CA_Disconnect indicator */ static int ng_btsocket_l2cap_process_l2ca_discon_ind(struct ng_mesg *msg, ng_btsocket_l2cap_rtentry_p rt) { ng_l2cap_l2ca_discon_ind_ip *ip = NULL; ng_btsocket_l2cap_pcb_t *pcb = NULL; /* Check message */ if (msg->header.arglen != sizeof(*ip)) return (EMSGSIZE); ip = (ng_l2cap_l2ca_discon_ind_ip *)(msg->data); mtx_lock(&ng_btsocket_l2cap_sockets_mtx); /* Look for the socket with given channel ID */ pcb = ng_btsocket_l2cap_pcb_by_cid(&rt->src, ip->lcid, NG_L2CAP_L2CA_IDTYPE_BREDR); if (pcb == NULL) { mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (0); } /* * Channel has already been destroyed, so disconnect the socket * and be done with it. If there was any pending request we can * not do anything here anyway. */ mtx_lock(&pcb->pcb_mtx); NG_BTSOCKET_L2CAP_INFO( "%s: Got L2CA_Disconnect indicator, src bdaddr=%x:%x:%x:%x:%x:%x, " \ "dst bdaddr=%x:%x:%x:%x:%x:%x, psm=%d, lcid=%d, state=%d\n", __func__, pcb->src.b[5], pcb->src.b[4], pcb->src.b[3], pcb->src.b[2], pcb->src.b[1], pcb->src.b[0], pcb->dst.b[5], pcb->dst.b[4], pcb->dst.b[3], pcb->dst.b[2], pcb->dst.b[1], pcb->dst.b[0], pcb->psm, pcb->cid, pcb->state); if (pcb->flags & NG_BTSOCKET_L2CAP_TIMO) ng_btsocket_l2cap_untimeout(pcb); pcb->state = NG_BTSOCKET_L2CAP_CLOSED; soisdisconnected(pcb->so); mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (0); } /* ng_btsocket_l2cap_process_l2ca_discon_ind */ /* * Process L2CA_Write response */ static int ng_btsocket_l2cap_process_l2ca_write_rsp(struct ng_mesg *msg, ng_btsocket_l2cap_rtentry_p rt) { ng_l2cap_l2ca_write_op *op = NULL; ng_btsocket_l2cap_pcb_t *pcb = NULL; /* Check message */ if (msg->header.arglen != sizeof(*op)) return (EMSGSIZE); op = (ng_l2cap_l2ca_write_op *)(msg->data); mtx_lock(&ng_btsocket_l2cap_sockets_mtx); /* Look for the socket with given token */ pcb = ng_btsocket_l2cap_pcb_by_token(msg->header.token); if (pcb == NULL) { mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (ENOENT); } mtx_lock(&pcb->pcb_mtx); NG_BTSOCKET_L2CAP_INFO( "%s: Got L2CA_Write response, src bdaddr=%x:%x:%x:%x:%x:%x, " \ "dst bdaddr=%x:%x:%x:%x:%x:%x, psm=%d, lcid=%d, result=%d, length=%d, " \ "state=%d\n", __func__, pcb->src.b[5], pcb->src.b[4], pcb->src.b[3], pcb->src.b[2], pcb->src.b[1], pcb->src.b[0], pcb->dst.b[5], pcb->dst.b[4], pcb->dst.b[3], pcb->dst.b[2], pcb->dst.b[1], pcb->dst.b[0], pcb->psm, pcb->cid, op->result, op->length, pcb->state); if (pcb->state != NG_BTSOCKET_L2CAP_OPEN) { mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (ENOENT); } ng_btsocket_l2cap_untimeout(pcb); /* * Check if we have more data to send */ sbdroprecord(&pcb->so->so_snd); if (sbavail(&pcb->so->so_snd) > 0) { if (ng_btsocket_l2cap_send2(pcb) == 0) ng_btsocket_l2cap_timeout(pcb); else sbdroprecord(&pcb->so->so_snd); /* XXX */ } /* * Now set the result, drop packet from the socket send queue and * ask for more (wakeup sender) */ pcb->so->so_error = ng_btsocket_l2cap_result2errno(op->result); sowwakeup(pcb->so); mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (0); } /* ng_btsocket_l2cap_process_l2ca_write_rsp */ /* * Send L2CA_Connect request */ static int ng_btsocket_l2cap_send_l2ca_con_req(ng_btsocket_l2cap_pcb_p pcb) { struct ng_mesg *msg = NULL; ng_l2cap_l2ca_con_ip *ip = NULL; int error = 0; mtx_assert(&pcb->pcb_mtx, MA_OWNED); if (pcb->rt == NULL || pcb->rt->hook == NULL || NG_HOOK_NOT_VALID(pcb->rt->hook)) return (ENETDOWN); NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_L2CA_CON, sizeof(*ip), M_NOWAIT); if (msg == NULL) return (ENOMEM); msg->header.token = pcb->token; ip = (ng_l2cap_l2ca_con_ip *)(msg->data); bcopy(&pcb->dst, &ip->bdaddr, sizeof(ip->bdaddr)); ip->psm = pcb->psm; ip->linktype = ng_btsock_l2cap_addrtype_to_linktype(pcb->dsttype); ip->idtype = pcb->idtype; NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_node, msg,pcb->rt->hook, 0); return (error); } /* ng_btsocket_l2cap_send_l2ca_con_req */ /* * Send L2CA_Connect response */ static int ng_btsocket_l2cap_send_l2ca_con_rsp_req(u_int32_t token, ng_btsocket_l2cap_rtentry_p rt, bdaddr_p dst, int ident, int lcid, int result, int linktype) { struct ng_mesg *msg = NULL; ng_l2cap_l2ca_con_rsp_ip *ip = NULL; int error = 0; if (rt == NULL || rt->hook == NULL || NG_HOOK_NOT_VALID(rt->hook)) return (ENETDOWN); NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_L2CA_CON_RSP, sizeof(*ip), M_NOWAIT); if (msg == NULL) return (ENOMEM); msg->header.token = token; ip = (ng_l2cap_l2ca_con_rsp_ip *)(msg->data); bcopy(dst, &ip->bdaddr, sizeof(ip->bdaddr)); ip->ident = ident; ip->lcid = lcid; ip->linktype = linktype; ip->result = result; ip->status = 0; NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_node, msg, rt->hook, 0); return (error); } /* ng_btsocket_l2cap_send_l2ca_con_rsp_req */ /* * Send L2CA_Config request */ static int ng_btsocket_l2cap_send_l2ca_cfg_req(ng_btsocket_l2cap_pcb_p pcb) { struct ng_mesg *msg = NULL; ng_l2cap_l2ca_cfg_ip *ip = NULL; int error = 0; mtx_assert(&pcb->pcb_mtx, MA_OWNED); if (pcb->rt == NULL || pcb->rt->hook == NULL || NG_HOOK_NOT_VALID(pcb->rt->hook)) return (ENETDOWN); NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_L2CA_CFG, sizeof(*ip), M_NOWAIT); if (msg == NULL) return (ENOMEM); msg->header.token = pcb->token; ip = (ng_l2cap_l2ca_cfg_ip *)(msg->data); ip->lcid = pcb->cid; ip->imtu = pcb->imtu; bcopy(&pcb->oflow, &ip->oflow, sizeof(ip->oflow)); ip->flush_timo = pcb->flush_timo; ip->link_timo = pcb->link_timo; NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_node, msg,pcb->rt->hook, 0); return (error); } /* ng_btsocket_l2cap_send_l2ca_cfg_req */ /* * Send L2CA_Config response */ static int ng_btsocket_l2cap_send_l2ca_cfg_rsp(ng_btsocket_l2cap_pcb_p pcb) { struct ng_mesg *msg = NULL; ng_l2cap_l2ca_cfg_rsp_ip *ip = NULL; int error = 0; mtx_assert(&pcb->pcb_mtx, MA_OWNED); if (pcb->rt == NULL || pcb->rt->hook == NULL || NG_HOOK_NOT_VALID(pcb->rt->hook)) return (ENETDOWN); NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_L2CA_CFG_RSP, sizeof(*ip), M_NOWAIT); if (msg == NULL) return (ENOMEM); msg->header.token = pcb->token; ip = (ng_l2cap_l2ca_cfg_rsp_ip *)(msg->data); ip->lcid = pcb->cid; ip->omtu = pcb->omtu; bcopy(&pcb->iflow, &ip->iflow, sizeof(ip->iflow)); NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_node, msg, pcb->rt->hook, 0); return (error); } /* ng_btsocket_l2cap_send_l2ca_cfg_rsp */ /* * Send L2CA_Disconnect request */ static int ng_btsocket_l2cap_send_l2ca_discon_req(u_int32_t token, ng_btsocket_l2cap_pcb_p pcb) { struct ng_mesg *msg = NULL; ng_l2cap_l2ca_discon_ip *ip = NULL; int error = 0; mtx_assert(&pcb->pcb_mtx, MA_OWNED); if (pcb->rt == NULL || pcb->rt->hook == NULL || NG_HOOK_NOT_VALID(pcb->rt->hook)) return (ENETDOWN); NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_L2CA_DISCON, sizeof(*ip), M_NOWAIT); if (msg == NULL) return (ENOMEM); msg->header.token = token; ip = (ng_l2cap_l2ca_discon_ip *)(msg->data); ip->lcid = pcb->cid; ip->idtype = pcb->idtype; NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_node, msg,pcb->rt->hook, 0); return (error); } /* ng_btsocket_l2cap_send_l2ca_discon_req */ /***************************************************************************** ***************************************************************************** ** Socket interface ***************************************************************************** *****************************************************************************/ /* * L2CAP sockets data input routine */ static void ng_btsocket_l2cap_data_input(struct mbuf *m, hook_p hook) { ng_l2cap_hdr_t *hdr = NULL; ng_l2cap_clt_hdr_t *clt_hdr = NULL; ng_btsocket_l2cap_pcb_t *pcb = NULL; ng_btsocket_l2cap_rtentry_t *rt = NULL; uint16_t idtype; if (hook == NULL) { NG_BTSOCKET_L2CAP_ALERT( "%s: Invalid source hook for L2CAP data packet\n", __func__); goto drop; } rt = (ng_btsocket_l2cap_rtentry_t *) NG_HOOK_PRIVATE(hook); if (rt == NULL) { NG_BTSOCKET_L2CAP_ALERT( "%s: Could not find out source bdaddr for L2CAP data packet\n", __func__); goto drop; } m = m_pullup(m, sizeof(uint16_t)); idtype = *mtod(m, uint16_t *); m_adj(m, sizeof(uint16_t)); /* Make sure we can access header */ if (m->m_pkthdr.len < sizeof(*hdr)) { NG_BTSOCKET_L2CAP_ERR( "%s: L2CAP data packet too small, len=%d\n", __func__, m->m_pkthdr.len); goto drop; } if (m->m_len < sizeof(*hdr)) { m = m_pullup(m, sizeof(*hdr)); if (m == NULL) goto drop; } /* Strip L2CAP packet header and verify packet length */ hdr = mtod(m, ng_l2cap_hdr_t *); m_adj(m, sizeof(*hdr)); if (hdr->length != m->m_pkthdr.len) { NG_BTSOCKET_L2CAP_ERR( "%s: Bad L2CAP data packet length, len=%d, length=%d\n", __func__, m->m_pkthdr.len, hdr->length); goto drop; } /* * Now process packet. Two cases: * * 1) Normal packet (cid != 2) then find connected socket and append * mbuf to the socket queue. Wakeup socket. * * 2) Broadcast packet (cid == 2) then find all sockets that connected * to the given PSM and have SO_BROADCAST bit set and append mbuf * to the socket queue. Wakeup socket. */ NG_BTSOCKET_L2CAP_INFO( "%s: Received L2CAP data packet: src bdaddr=%x:%x:%x:%x:%x:%x, " \ "dcid=%d, length=%d\n", __func__, rt->src.b[5], rt->src.b[4], rt->src.b[3], rt->src.b[2], rt->src.b[1], rt->src.b[0], hdr->dcid, hdr->length); if ((hdr->dcid >= NG_L2CAP_FIRST_CID) || (idtype == NG_L2CAP_L2CA_IDTYPE_ATT)|| (idtype == NG_L2CAP_L2CA_IDTYPE_SMP) ){ mtx_lock(&ng_btsocket_l2cap_sockets_mtx); /* Normal packet: find connected socket */ pcb = ng_btsocket_l2cap_pcb_by_cid(&rt->src, hdr->dcid,idtype); if (pcb == NULL) { mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); goto drop; } mtx_lock(&pcb->pcb_mtx); if (pcb->state != NG_BTSOCKET_L2CAP_OPEN) { NG_BTSOCKET_L2CAP_ERR( "%s: No connected socket found, src bdaddr=%x:%x:%x:%x:%x:%x, dcid=%d, " \ "state=%d\n", __func__, rt->src.b[5], rt->src.b[4], rt->src.b[3], rt->src.b[2], rt->src.b[1], rt->src.b[0], hdr->dcid, pcb->state); mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); goto drop; } /* Check packet size against socket's incoming MTU */ if (hdr->length > pcb->imtu) { NG_BTSOCKET_L2CAP_ERR( "%s: L2CAP data packet too big, src bdaddr=%x:%x:%x:%x:%x:%x, " \ "dcid=%d, length=%d, imtu=%d\n", __func__, rt->src.b[5], rt->src.b[4], rt->src.b[3], rt->src.b[2], rt->src.b[1], rt->src.b[0], hdr->dcid, hdr->length, pcb->imtu); mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); goto drop; } /* Check if we have enough space in socket receive queue */ if (m->m_pkthdr.len > sbspace(&pcb->so->so_rcv)) { /* * This is really bad. Receive queue on socket does * not have enough space for the packet. We do not * have any other choice but drop the packet. L2CAP * does not provide any flow control. */ NG_BTSOCKET_L2CAP_ERR( "%s: Not enough space in socket receive queue. Dropping L2CAP data packet, " \ "src bdaddr=%x:%x:%x:%x:%x:%x, dcid=%d, len=%d, space=%ld\n", __func__, rt->src.b[5], rt->src.b[4], rt->src.b[3], rt->src.b[2], rt->src.b[1], rt->src.b[0], hdr->dcid, m->m_pkthdr.len, sbspace(&pcb->so->so_rcv)); mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); goto drop; } /* Append packet to the socket receive queue and wakeup */ sbappendrecord(&pcb->so->so_rcv, m); m = NULL; sorwakeup(pcb->so); mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); } else if (hdr->dcid == NG_L2CAP_CLT_CID) { /* Broadcast packet: give packet to all sockets */ /* Check packet size against connectionless MTU */ if (hdr->length > NG_L2CAP_MTU_DEFAULT) { NG_BTSOCKET_L2CAP_ERR( "%s: Connectionless L2CAP data packet too big, " \ "src bdaddr=%x:%x:%x:%x:%x:%x, length=%d\n", __func__, rt->src.b[5], rt->src.b[4], rt->src.b[3], rt->src.b[2], rt->src.b[1], rt->src.b[0], hdr->length); goto drop; } /* Make sure we can access connectionless header */ if (m->m_pkthdr.len < sizeof(*clt_hdr)) { NG_BTSOCKET_L2CAP_ERR( "%s: Can not get L2CAP connectionless packet header, " \ "src bdaddr=%x:%x:%x:%x:%x:%x, length=%d\n", __func__, rt->src.b[5], rt->src.b[4], rt->src.b[3], rt->src.b[2], rt->src.b[1], rt->src.b[0], hdr->length); goto drop; } if (m->m_len < sizeof(*clt_hdr)) { m = m_pullup(m, sizeof(*clt_hdr)); if (m == NULL) goto drop; } /* Strip connectionless header and deliver packet */ clt_hdr = mtod(m, ng_l2cap_clt_hdr_t *); m_adj(m, sizeof(*clt_hdr)); NG_BTSOCKET_L2CAP_INFO( "%s: Got L2CAP connectionless data packet, " \ "src bdaddr=%x:%x:%x:%x:%x:%x, psm=%d, length=%d\n", __func__, rt->src.b[5], rt->src.b[4], rt->src.b[3], rt->src.b[2], rt->src.b[1], rt->src.b[0], clt_hdr->psm, hdr->length); mtx_lock(&ng_btsocket_l2cap_sockets_mtx); LIST_FOREACH(pcb, &ng_btsocket_l2cap_sockets, next) { struct mbuf *copy = NULL; mtx_lock(&pcb->pcb_mtx); if (bcmp(&rt->src, &pcb->src, sizeof(pcb->src)) != 0 || pcb->psm != clt_hdr->psm || pcb->state != NG_BTSOCKET_L2CAP_OPEN || (pcb->so->so_options & SO_BROADCAST) == 0 || m->m_pkthdr.len > sbspace(&pcb->so->so_rcv)) goto next; /* * Create a copy of the packet and append it to the * socket's queue. If m_dup() failed - no big deal * it is a broadcast traffic after all */ copy = m_dup(m, M_NOWAIT); if (copy != NULL) { sbappendrecord(&pcb->so->so_rcv, copy); sorwakeup(pcb->so); } next: mtx_unlock(&pcb->pcb_mtx); } mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); } drop: NG_FREE_M(m); /* checks for m != NULL */ } /* ng_btsocket_l2cap_data_input */ /* * L2CAP sockets default message input routine */ static void ng_btsocket_l2cap_default_msg_input(struct ng_mesg *msg, hook_p hook) { switch (msg->header.cmd) { case NGM_L2CAP_NODE_HOOK_INFO: { ng_btsocket_l2cap_rtentry_t *rt = NULL; ng_l2cap_node_hook_info_ep *ep = (ng_l2cap_node_hook_info_ep *)msg->data; if (hook == NULL || msg->header.arglen != sizeof(*ep)) break; if (bcmp(&ep->addr, NG_HCI_BDADDR_ANY, sizeof(bdaddr_t)) == 0) break; mtx_lock(&ng_btsocket_l2cap_rt_mtx); rt = (ng_btsocket_l2cap_rtentry_t *) NG_HOOK_PRIVATE(hook); if (rt == NULL) { rt = malloc(sizeof(*rt), M_NETGRAPH_BTSOCKET_L2CAP, M_NOWAIT|M_ZERO); if (rt == NULL) { mtx_unlock(&ng_btsocket_l2cap_rt_mtx); break; } LIST_INSERT_HEAD(&ng_btsocket_l2cap_rt, rt, next); NG_HOOK_SET_PRIVATE(hook, rt); } bcopy(&ep->addr, &rt->src, sizeof(rt->src)); rt->hook = hook; mtx_unlock(&ng_btsocket_l2cap_rt_mtx); NG_BTSOCKET_L2CAP_INFO( "%s: Updating hook \"%s\", src bdaddr=%x:%x:%x:%x:%x:%x\n", __func__, NG_HOOK_NAME(hook), rt->src.b[5], rt->src.b[4], rt->src.b[3], rt->src.b[2], rt->src.b[1], rt->src.b[0]); } break; default: NG_BTSOCKET_L2CAP_WARN( "%s: Unknown message, cmd=%d\n", __func__, msg->header.cmd); break; } NG_FREE_MSG(msg); /* Checks for msg != NULL */ } /* ng_btsocket_l2cap_default_msg_input */ /* * L2CAP sockets L2CA message input routine */ static void ng_btsocket_l2cap_l2ca_msg_input(struct ng_mesg *msg, hook_p hook) { ng_btsocket_l2cap_rtentry_p rt = NULL; if (hook == NULL) { NG_BTSOCKET_L2CAP_ALERT( "%s: Invalid source hook for L2CA message\n", __func__); goto drop; } rt = (ng_btsocket_l2cap_rtentry_p) NG_HOOK_PRIVATE(hook); if (rt == NULL) { NG_BTSOCKET_L2CAP_ALERT( "%s: Could not find out source bdaddr for L2CA message\n", __func__); goto drop; } switch (msg->header.cmd) { case NGM_L2CAP_L2CA_CON: /* L2CA_Connect response */ ng_btsocket_l2cap_process_l2ca_con_req_rsp(msg, rt); break; case NGM_L2CAP_L2CA_CON_RSP: /* L2CA_ConnectRsp response */ ng_btsocket_l2cap_process_l2ca_con_rsp_rsp(msg, rt); break; case NGM_L2CAP_L2CA_CON_IND: /* L2CA_Connect indicator */ ng_btsocket_l2cap_process_l2ca_con_ind(msg, rt); break; case NGM_L2CAP_L2CA_CFG: /* L2CA_Config response */ ng_btsocket_l2cap_process_l2ca_cfg_req_rsp(msg, rt); break; case NGM_L2CAP_L2CA_CFG_RSP: /* L2CA_ConfigRsp response */ ng_btsocket_l2cap_process_l2ca_cfg_rsp_rsp(msg, rt); break; case NGM_L2CAP_L2CA_CFG_IND: /* L2CA_Config indicator */ ng_btsocket_l2cap_process_l2ca_cfg_ind(msg, rt); break; case NGM_L2CAP_L2CA_DISCON: /* L2CA_Disconnect response */ ng_btsocket_l2cap_process_l2ca_discon_rsp(msg, rt); break; case NGM_L2CAP_L2CA_DISCON_IND: /* L2CA_Disconnect indicator */ ng_btsocket_l2cap_process_l2ca_discon_ind(msg, rt); break; case NGM_L2CAP_L2CA_WRITE: /* L2CA_Write response */ ng_btsocket_l2cap_process_l2ca_write_rsp(msg, rt); break; case NGM_L2CAP_L2CA_ENC_CHANGE: ng_btsocket_l2cap_process_l2ca_enc_change(msg, rt); break; /* XXX FIXME add other L2CA messages */ default: NG_BTSOCKET_L2CAP_WARN( "%s: Unknown L2CA message, cmd=%d\n", __func__, msg->header.cmd); break; } drop: NG_FREE_MSG(msg); } /* ng_btsocket_l2cap_l2ca_msg_input */ /* * L2CAP sockets input routine */ static void ng_btsocket_l2cap_input(void *context, int pending) { item_p item = NULL; hook_p hook = NULL; for (;;) { mtx_lock(&ng_btsocket_l2cap_queue_mtx); NG_BT_ITEMQ_DEQUEUE(&ng_btsocket_l2cap_queue, item); mtx_unlock(&ng_btsocket_l2cap_queue_mtx); if (item == NULL) break; NGI_GET_HOOK(item, hook); if (hook != NULL && NG_HOOK_NOT_VALID(hook)) goto drop; switch(item->el_flags & NGQF_TYPE) { case NGQF_DATA: { struct mbuf *m = NULL; NGI_GET_M(item, m); ng_btsocket_l2cap_data_input(m, hook); } break; case NGQF_MESG: { struct ng_mesg *msg = NULL; NGI_GET_MSG(item, msg); switch (msg->header.cmd) { case NGM_L2CAP_L2CA_CON: case NGM_L2CAP_L2CA_CON_RSP: case NGM_L2CAP_L2CA_CON_IND: case NGM_L2CAP_L2CA_CFG: case NGM_L2CAP_L2CA_CFG_RSP: case NGM_L2CAP_L2CA_CFG_IND: case NGM_L2CAP_L2CA_DISCON: case NGM_L2CAP_L2CA_DISCON_IND: case NGM_L2CAP_L2CA_WRITE: case NGM_L2CAP_L2CA_ENC_CHANGE: /* XXX FIXME add other L2CA messages */ ng_btsocket_l2cap_l2ca_msg_input(msg, hook); break; default: ng_btsocket_l2cap_default_msg_input(msg, hook); break; } } break; default: KASSERT(0, ("%s: invalid item type=%ld\n", __func__, (item->el_flags & NGQF_TYPE))); break; } drop: if (hook != NULL) NG_HOOK_UNREF(hook); NG_FREE_ITEM(item); } } /* ng_btsocket_l2cap_input */ /* * Route cleanup task. Gets scheduled when hook is disconnected. Here we * will find all sockets that use "invalid" hook and disconnect them. */ static void ng_btsocket_l2cap_rtclean(void *context, int pending) { ng_btsocket_l2cap_pcb_p pcb = NULL, pcb_next = NULL; ng_btsocket_l2cap_rtentry_p rt = NULL; mtx_lock(&ng_btsocket_l2cap_rt_mtx); mtx_lock(&ng_btsocket_l2cap_sockets_mtx); /* * First disconnect all sockets that use "invalid" hook */ for (pcb = LIST_FIRST(&ng_btsocket_l2cap_sockets); pcb != NULL; ) { mtx_lock(&pcb->pcb_mtx); pcb_next = LIST_NEXT(pcb, next); if (pcb->rt != NULL && pcb->rt->hook != NULL && NG_HOOK_NOT_VALID(pcb->rt->hook)) { if (pcb->flags & NG_BTSOCKET_L2CAP_TIMO) ng_btsocket_l2cap_untimeout(pcb); pcb->so->so_error = ENETDOWN; pcb->state = NG_BTSOCKET_L2CAP_CLOSED; soisdisconnected(pcb->so); pcb->token = 0; pcb->cid = 0; pcb->rt = NULL; } mtx_unlock(&pcb->pcb_mtx); pcb = pcb_next; } /* * Now cleanup routing table */ for (rt = LIST_FIRST(&ng_btsocket_l2cap_rt); rt != NULL; ) { ng_btsocket_l2cap_rtentry_p rt_next = LIST_NEXT(rt, next); if (rt->hook != NULL && NG_HOOK_NOT_VALID(rt->hook)) { LIST_REMOVE(rt, next); NG_HOOK_SET_PRIVATE(rt->hook, NULL); NG_HOOK_UNREF(rt->hook); /* Remove extra reference */ bzero(rt, sizeof(*rt)); free(rt, M_NETGRAPH_BTSOCKET_L2CAP); } rt = rt_next; } mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); mtx_unlock(&ng_btsocket_l2cap_rt_mtx); } /* ng_btsocket_l2cap_rtclean */ /* * Initialize everything */ void ng_btsocket_l2cap_init(void) { int error = 0; /* Skip initialization of globals for non-default instances. */ if (!IS_DEFAULT_VNET(curvnet)) return; ng_btsocket_l2cap_node = NULL; ng_btsocket_l2cap_debug_level = NG_BTSOCKET_WARN_LEVEL; /* Register Netgraph node type */ error = ng_newtype(&typestruct); if (error != 0) { NG_BTSOCKET_L2CAP_ALERT( "%s: Could not register Netgraph node type, error=%d\n", __func__, error); return; } /* Create Netgrapg node */ error = ng_make_node_common(&typestruct, &ng_btsocket_l2cap_node); if (error != 0) { NG_BTSOCKET_L2CAP_ALERT( "%s: Could not create Netgraph node, error=%d\n", __func__, error); ng_btsocket_l2cap_node = NULL; return; } error = ng_name_node(ng_btsocket_l2cap_node, NG_BTSOCKET_L2CAP_NODE_TYPE); if (error != 0) { NG_BTSOCKET_L2CAP_ALERT( "%s: Could not name Netgraph node, error=%d\n", __func__, error); NG_NODE_UNREF(ng_btsocket_l2cap_node); ng_btsocket_l2cap_node = NULL; return; } /* Create input queue */ NG_BT_ITEMQ_INIT(&ng_btsocket_l2cap_queue, ifqmaxlen); mtx_init(&ng_btsocket_l2cap_queue_mtx, "btsocks_l2cap_queue_mtx", NULL, MTX_DEF); TASK_INIT(&ng_btsocket_l2cap_queue_task, 0, ng_btsocket_l2cap_input, NULL); /* Create list of sockets */ LIST_INIT(&ng_btsocket_l2cap_sockets); mtx_init(&ng_btsocket_l2cap_sockets_mtx, "btsocks_l2cap_sockets_mtx", NULL, MTX_DEF); /* Routing table */ LIST_INIT(&ng_btsocket_l2cap_rt); mtx_init(&ng_btsocket_l2cap_rt_mtx, "btsocks_l2cap_rt_mtx", NULL, MTX_DEF); TASK_INIT(&ng_btsocket_l2cap_rt_task, 0, ng_btsocket_l2cap_rtclean, NULL); } /* ng_btsocket_l2cap_init */ /* * Abort connection on socket */ void ng_btsocket_l2cap_abort(struct socket *so) { so->so_error = ECONNABORTED; (void)ng_btsocket_l2cap_disconnect(so); } /* ng_btsocket_l2cap_abort */ void ng_btsocket_l2cap_close(struct socket *so) { (void)ng_btsocket_l2cap_disconnect(so); } /* ng_btsocket_l2cap_close */ /* * Accept connection on socket. Nothing to do here, socket must be connected * and ready, so just return peer address and be done with it. */ int ng_btsocket_l2cap_accept(struct socket *so, struct sockaddr **nam) { if (ng_btsocket_l2cap_node == NULL) return (EINVAL); return (ng_btsocket_l2cap_peeraddr(so, nam)); } /* ng_btsocket_l2cap_accept */ /* * Create and attach new socket */ int ng_btsocket_l2cap_attach(struct socket *so, int proto, struct thread *td) { static u_int32_t token = 0; ng_btsocket_l2cap_pcb_p pcb = so2l2cap_pcb(so); int error; /* Check socket and protocol */ if (ng_btsocket_l2cap_node == NULL) return (EPROTONOSUPPORT); if (so->so_type != SOCK_SEQPACKET) return (ESOCKTNOSUPPORT); #if 0 /* XXX sonewconn() calls "pru_attach" with proto == 0 */ if (proto != 0) if (proto != BLUETOOTH_PROTO_L2CAP) return (EPROTONOSUPPORT); #endif /* XXX */ if (pcb != NULL) return (EISCONN); /* Reserve send and receive space if it is not reserved yet */ if ((so->so_snd.sb_hiwat == 0) || (so->so_rcv.sb_hiwat == 0)) { error = soreserve(so, NG_BTSOCKET_L2CAP_SENDSPACE, NG_BTSOCKET_L2CAP_RECVSPACE); if (error != 0) return (error); } /* Allocate the PCB */ pcb = malloc(sizeof(*pcb), M_NETGRAPH_BTSOCKET_L2CAP, M_NOWAIT | M_ZERO); if (pcb == NULL) return (ENOMEM); /* Link the PCB and the socket */ so->so_pcb = (caddr_t) pcb; pcb->so = so; pcb->state = NG_BTSOCKET_L2CAP_CLOSED; /* Initialize PCB */ pcb->imtu = pcb->omtu = NG_L2CAP_MTU_DEFAULT; /* Default flow */ pcb->iflow.flags = 0x0; pcb->iflow.service_type = NG_HCI_SERVICE_TYPE_BEST_EFFORT; pcb->iflow.token_rate = 0xffffffff; /* maximum */ pcb->iflow.token_bucket_size = 0xffffffff; /* maximum */ pcb->iflow.peak_bandwidth = 0x00000000; /* maximum */ pcb->iflow.latency = 0xffffffff; /* don't care */ pcb->iflow.delay_variation = 0xffffffff; /* don't care */ bcopy(&pcb->iflow, &pcb->oflow, sizeof(pcb->oflow)); pcb->flush_timo = NG_L2CAP_FLUSH_TIMO_DEFAULT; pcb->link_timo = NG_L2CAP_LINK_TIMO_DEFAULT; /* * XXX Mark PCB mutex as DUPOK to prevent "duplicated lock of * the same type" message. When accepting new L2CAP connection * ng_btsocket_l2cap_process_l2ca_con_ind() holds both PCB mutexes * for "old" (accepting) PCB and "new" (created) PCB. */ mtx_init(&pcb->pcb_mtx, "btsocks_l2cap_pcb_mtx", NULL, MTX_DEF|MTX_DUPOK); callout_init_mtx(&pcb->timo, &pcb->pcb_mtx, 0); /* * Add the PCB to the list * * XXX FIXME VERY IMPORTANT! * * This is totally FUBAR. We could get here in two cases: * * 1) When user calls socket() - * 2) When we need to accept new incomming connection and call + * 2) When we need to accept new incoming connection and call * sonewconn() * * In the first case we must acquire ng_btsocket_l2cap_sockets_mtx. * In the second case we hold ng_btsocket_l2cap_sockets_mtx already. * So we now need to distinguish between these cases. From reading * /sys/kern/uipc_socket.c we can find out that sonewconn() calls * pru_attach with proto == 0 and td == NULL. For now use this fact * to figure out if we were called from socket() or from sonewconn(). */ if (td != NULL) mtx_lock(&ng_btsocket_l2cap_sockets_mtx); else mtx_assert(&ng_btsocket_l2cap_sockets_mtx, MA_OWNED); /* Set PCB token. Use ng_btsocket_l2cap_sockets_mtx for protection */ if (++ token == 0) token ++; pcb->token = token; LIST_INSERT_HEAD(&ng_btsocket_l2cap_sockets, pcb, next); if (td != NULL) mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (0); } /* ng_btsocket_l2cap_attach */ /* * Bind socket */ int ng_btsocket_l2cap_bind(struct socket *so, struct sockaddr *nam, struct thread *td) { ng_btsocket_l2cap_pcb_t *pcb = NULL; struct sockaddr_l2cap *sa = (struct sockaddr_l2cap *) nam; int psm, error = 0; if (ng_btsocket_l2cap_node == NULL) return (EINVAL); /* Verify address */ if (sa == NULL) return (EINVAL); if (sa->l2cap_family != AF_BLUETOOTH) return (EAFNOSUPPORT); /*For the time being, Not support LE binding.*/ if ((sa->l2cap_len != sizeof(*sa))&& (sa->l2cap_len != sizeof(struct sockaddr_l2cap_compat))) return (EINVAL); psm = le16toh(sa->l2cap_psm); /* * Check if other socket has this address already (look for exact * match PSM and bdaddr) and assign socket address if it's available. * * Note: socket can be bound to ANY PSM (zero) thus allowing several * channels with the same PSM between the same pair of BD_ADDR'es. */ mtx_lock(&ng_btsocket_l2cap_sockets_mtx); LIST_FOREACH(pcb, &ng_btsocket_l2cap_sockets, next) if (psm != 0 && psm == pcb->psm && bcmp(&pcb->src, &sa->l2cap_bdaddr, sizeof(bdaddr_t)) == 0) break; if (pcb == NULL) { /* Set socket address */ pcb = so2l2cap_pcb(so); if (pcb != NULL) { bcopy(&sa->l2cap_bdaddr, &pcb->src, sizeof(pcb->src)); pcb->psm = psm; } else error = EINVAL; } else error = EADDRINUSE; mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (error); } /* ng_btsocket_l2cap_bind */ /* * Connect socket */ int ng_btsocket_l2cap_connect(struct socket *so, struct sockaddr *nam, struct thread *td) { ng_btsocket_l2cap_pcb_t *pcb = so2l2cap_pcb(so); struct sockaddr_l2cap_compat *sal = (struct sockaddr_l2cap_compat *) nam; struct sockaddr_l2cap *sa = (struct sockaddr_l2cap *)nam; struct sockaddr_l2cap ba; ng_btsocket_l2cap_rtentry_t *rt = NULL; int have_src, error = 0; int idtype = NG_L2CAP_L2CA_IDTYPE_BREDR; /* Check socket */ if (pcb == NULL) return (EINVAL); if (ng_btsocket_l2cap_node == NULL) return (EINVAL); if (pcb->state == NG_BTSOCKET_L2CAP_CONNECTING) return (EINPROGRESS); /* Verify address */ if (sa == NULL) return (EINVAL); if (sa->l2cap_family != AF_BLUETOOTH) return (EAFNOSUPPORT); if (sa->l2cap_len == sizeof(*sal)){ bcopy(sal, &ba, sizeof(*sal)); sa = &ba; sa->l2cap_len = sizeof(*sa); sa->l2cap_bdaddr_type = BDADDR_BREDR; } if (sa->l2cap_len != sizeof(*sa)) return (EINVAL); if ((sa->l2cap_psm && sa->l2cap_cid)) return EINVAL; if (bcmp(&sa->l2cap_bdaddr, NG_HCI_BDADDR_ANY, sizeof(bdaddr_t)) == 0) return (EDESTADDRREQ); if((sa->l2cap_bdaddr_type == BDADDR_BREDR)&& (sa->l2cap_psm == 0)) return EDESTADDRREQ; if(sa->l2cap_bdaddr_type != BDADDR_BREDR){ if(sa->l2cap_cid == NG_L2CAP_ATT_CID){ idtype = NG_L2CAP_L2CA_IDTYPE_ATT; }else if (sa->l2cap_cid == NG_L2CAP_SMP_CID){ idtype =NG_L2CAP_L2CA_IDTYPE_SMP; }else{ //if cid == 0 idtype = NG_L2CAP_L2CA_IDTYPE_LE; // Not supported yet return EINVAL; } } if (pcb->psm != 0 && pcb->psm != le16toh(sa->l2cap_psm)) return (EINVAL); /* * Routing. Socket should be bound to some source address. The source * address can be ANY. Destination address must be set and it must not * be ANY. If source address is ANY then find first rtentry that has * src != dst. */ mtx_lock(&ng_btsocket_l2cap_rt_mtx); mtx_lock(&ng_btsocket_l2cap_sockets_mtx); mtx_lock(&pcb->pcb_mtx); /* Send destination address and PSM */ bcopy(&sa->l2cap_bdaddr, &pcb->dst, sizeof(pcb->dst)); pcb->psm = le16toh(sa->l2cap_psm); pcb->dsttype = sa->l2cap_bdaddr_type; pcb->cid = 0; pcb->idtype = idtype; pcb->rt = NULL; have_src = bcmp(&pcb->src, NG_HCI_BDADDR_ANY, sizeof(pcb->src)); LIST_FOREACH(rt, &ng_btsocket_l2cap_rt, next) { if (rt->hook == NULL || NG_HOOK_NOT_VALID(rt->hook)) continue; /* Match src and dst */ if (have_src) { if (bcmp(&pcb->src, &rt->src, sizeof(rt->src)) == 0) break; } else { if (bcmp(&pcb->dst, &rt->src, sizeof(rt->src)) != 0) break; } } if (rt != NULL) { pcb->rt = rt; if (!have_src){ bcopy(&rt->src, &pcb->src, sizeof(pcb->src)); pcb->srctype = (sa->l2cap_bdaddr_type == BDADDR_BREDR)? BDADDR_BREDR : BDADDR_LE_PUBLIC; } } else error = EHOSTUNREACH; /* * Send L2CA_Connect request */ if (error == 0) { error = ng_btsocket_l2cap_send_l2ca_con_req(pcb); if (error == 0) { pcb->flags |= NG_BTSOCKET_L2CAP_CLIENT; pcb->state = NG_BTSOCKET_L2CAP_CONNECTING; soisconnecting(pcb->so); ng_btsocket_l2cap_timeout(pcb); } } mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); mtx_unlock(&ng_btsocket_l2cap_rt_mtx); return (error); } /* ng_btsocket_l2cap_connect */ /* * Process ioctl's calls on socket */ int ng_btsocket_l2cap_control(struct socket *so, u_long cmd, caddr_t data, struct ifnet *ifp, struct thread *td) { return (EINVAL); } /* ng_btsocket_l2cap_control */ /* * Process getsockopt/setsockopt system calls */ int ng_btsocket_l2cap_ctloutput(struct socket *so, struct sockopt *sopt) { ng_btsocket_l2cap_pcb_p pcb = so2l2cap_pcb(so); int error = 0; ng_l2cap_cfg_opt_val_t v; if (pcb == NULL) return (EINVAL); if (ng_btsocket_l2cap_node == NULL) return (EINVAL); if (sopt->sopt_level != SOL_L2CAP) return (0); mtx_lock(&pcb->pcb_mtx); switch (sopt->sopt_dir) { case SOPT_GET: switch (sopt->sopt_name) { case SO_L2CAP_IMTU: /* get incoming MTU */ error = sooptcopyout(sopt, &pcb->imtu, sizeof(pcb->imtu)); break; case SO_L2CAP_OMTU: /* get outgoing (peer incoming) MTU */ error = sooptcopyout(sopt, &pcb->omtu, sizeof(pcb->omtu)); break; case SO_L2CAP_IFLOW: /* get incoming flow spec. */ error = sooptcopyout(sopt, &pcb->iflow, sizeof(pcb->iflow)); break; case SO_L2CAP_OFLOW: /* get outgoing flow spec. */ error = sooptcopyout(sopt, &pcb->oflow, sizeof(pcb->oflow)); break; case SO_L2CAP_FLUSH: /* get flush timeout */ error = sooptcopyout(sopt, &pcb->flush_timo, sizeof(pcb->flush_timo)); break; case SO_L2CAP_ENCRYPTED: /* get encrypt required */ error = sooptcopyout(sopt, &pcb->need_encrypt, sizeof(pcb->need_encrypt)); break; default: error = ENOPROTOOPT; break; } break; case SOPT_SET: /* * XXX * We do not allow to change these parameters while socket is * connected or we are in the process of creating a connection. * May be this should indicate re-configuration of the open * channel? */ if (pcb->state != NG_BTSOCKET_L2CAP_CLOSED) { error = EACCES; break; } switch (sopt->sopt_name) { case SO_L2CAP_IMTU: /* set incoming MTU */ error = sooptcopyin(sopt, &v, sizeof(v), sizeof(v.mtu)); if (error == 0) pcb->imtu = v.mtu; break; case SO_L2CAP_OFLOW: /* set outgoing flow spec. */ error = sooptcopyin(sopt, &v, sizeof(v),sizeof(v.flow)); if (error == 0) bcopy(&v.flow, &pcb->oflow, sizeof(pcb->oflow)); break; case SO_L2CAP_FLUSH: /* set flush timeout */ error = sooptcopyin(sopt, &v, sizeof(v), sizeof(v.flush_timo)); if (error == 0) pcb->flush_timo = v.flush_timo; break; case SO_L2CAP_ENCRYPTED: /*set connect encryption opt*/ if((pcb->state != NG_BTSOCKET_L2CAP_OPEN) && (pcb->state != NG_BTSOCKET_L2CAP_W4_ENC_CHANGE)){ error = sooptcopyin(sopt, &v, sizeof(v), sizeof(v.encryption)); if(error == 0) pcb->need_encrypt = (v.encryption)?1:0; }else{ error = EINVAL; } break; default: error = ENOPROTOOPT; break; } break; default: error = EINVAL; break; } mtx_unlock(&pcb->pcb_mtx); return (error); } /* ng_btsocket_l2cap_ctloutput */ /* * Detach and destroy socket */ void ng_btsocket_l2cap_detach(struct socket *so) { ng_btsocket_l2cap_pcb_p pcb = so2l2cap_pcb(so); KASSERT(pcb != NULL, ("ng_btsocket_l2cap_detach: pcb == NULL")); if (ng_btsocket_l2cap_node == NULL) return; mtx_lock(&ng_btsocket_l2cap_sockets_mtx); mtx_lock(&pcb->pcb_mtx); /* XXX what to do with pending request? */ if (pcb->flags & NG_BTSOCKET_L2CAP_TIMO) ng_btsocket_l2cap_untimeout(pcb); if (pcb->state != NG_BTSOCKET_L2CAP_CLOSED && pcb->state != NG_BTSOCKET_L2CAP_DISCONNECTING) /* Send disconnect request with "zero" token */ ng_btsocket_l2cap_send_l2ca_discon_req(0, pcb); pcb->state = NG_BTSOCKET_L2CAP_CLOSED; LIST_REMOVE(pcb, next); mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); mtx_destroy(&pcb->pcb_mtx); bzero(pcb, sizeof(*pcb)); free(pcb, M_NETGRAPH_BTSOCKET_L2CAP); soisdisconnected(so); so->so_pcb = NULL; } /* ng_btsocket_l2cap_detach */ /* * Disconnect socket */ int ng_btsocket_l2cap_disconnect(struct socket *so) { ng_btsocket_l2cap_pcb_p pcb = so2l2cap_pcb(so); int error = 0; if (pcb == NULL) return (EINVAL); if (ng_btsocket_l2cap_node == NULL) return (EINVAL); mtx_lock(&pcb->pcb_mtx); if (pcb->state == NG_BTSOCKET_L2CAP_DISCONNECTING) { mtx_unlock(&pcb->pcb_mtx); return (EINPROGRESS); } if (pcb->state != NG_BTSOCKET_L2CAP_CLOSED) { /* XXX FIXME what to do with pending request? */ if (pcb->flags & NG_BTSOCKET_L2CAP_TIMO) ng_btsocket_l2cap_untimeout(pcb); error = ng_btsocket_l2cap_send_l2ca_discon_req(pcb->token, pcb); if (error == 0) { pcb->state = NG_BTSOCKET_L2CAP_DISCONNECTING; soisdisconnecting(so); ng_btsocket_l2cap_timeout(pcb); } /* XXX FIXME what to do if error != 0 */ } mtx_unlock(&pcb->pcb_mtx); return (error); } /* ng_btsocket_l2cap_disconnect */ /* * Listen on socket */ int ng_btsocket_l2cap_listen(struct socket *so, int backlog, struct thread *td) { ng_btsocket_l2cap_pcb_p pcb = so2l2cap_pcb(so); int error; SOCK_LOCK(so); error = solisten_proto_check(so); if (error != 0) goto out; if (pcb == NULL) { error = EINVAL; goto out; } if (ng_btsocket_l2cap_node == NULL) { error = EINVAL; goto out; } if (pcb->psm == 0) { error = EADDRNOTAVAIL; goto out; } solisten_proto(so, backlog); out: SOCK_UNLOCK(so); return (error); } /* ng_btsocket_listen */ /* * Get peer address */ int ng_btsocket_l2cap_peeraddr(struct socket *so, struct sockaddr **nam) { ng_btsocket_l2cap_pcb_p pcb = so2l2cap_pcb(so); struct sockaddr_l2cap sa; if (pcb == NULL) return (EINVAL); if (ng_btsocket_l2cap_node == NULL) return (EINVAL); bcopy(&pcb->dst, &sa.l2cap_bdaddr, sizeof(sa.l2cap_bdaddr)); sa.l2cap_psm = htole16(pcb->psm); sa.l2cap_len = sizeof(sa); sa.l2cap_family = AF_BLUETOOTH; switch(pcb->idtype){ case NG_L2CAP_L2CA_IDTYPE_ATT: sa.l2cap_cid = NG_L2CAP_ATT_CID; break; case NG_L2CAP_L2CA_IDTYPE_SMP: sa.l2cap_cid = NG_L2CAP_SMP_CID; break; default: sa.l2cap_cid = 0; break; } sa.l2cap_bdaddr_type = pcb->dsttype; *nam = sodupsockaddr((struct sockaddr *) &sa, M_NOWAIT); return ((*nam == NULL)? ENOMEM : 0); } /* ng_btsocket_l2cap_peeraddr */ /* * Send data to socket */ int ng_btsocket_l2cap_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *nam, struct mbuf *control, struct thread *td) { ng_btsocket_l2cap_pcb_t *pcb = so2l2cap_pcb(so); int error = 0; if (ng_btsocket_l2cap_node == NULL) { error = ENETDOWN; goto drop; } /* Check socket and input */ if (pcb == NULL || m == NULL || control != NULL) { error = EINVAL; goto drop; } mtx_lock(&pcb->pcb_mtx); /* Make sure socket is connected */ if (pcb->state != NG_BTSOCKET_L2CAP_OPEN) { mtx_unlock(&pcb->pcb_mtx); error = ENOTCONN; goto drop; } /* Check route */ if (pcb->rt == NULL || pcb->rt->hook == NULL || NG_HOOK_NOT_VALID(pcb->rt->hook)) { mtx_unlock(&pcb->pcb_mtx); error = ENETDOWN; goto drop; } - /* Check packet size agains outgoing (peer's incoming) MTU) */ + /* Check packet size against outgoing (peer's incoming) MTU) */ if (m->m_pkthdr.len > pcb->omtu) { NG_BTSOCKET_L2CAP_ERR( "%s: Packet too big, len=%d, omtu=%d\n", __func__, m->m_pkthdr.len, pcb->omtu); mtx_unlock(&pcb->pcb_mtx); error = EMSGSIZE; goto drop; } /* * First put packet on socket send queue. Then check if we have * pending timeout. If we do not have timeout then we must send * packet and schedule timeout. Otherwise do nothing and wait for * L2CA_WRITE_RSP. */ sbappendrecord(&pcb->so->so_snd, m); m = NULL; if (!(pcb->flags & NG_BTSOCKET_L2CAP_TIMO)) { error = ng_btsocket_l2cap_send2(pcb); if (error == 0) ng_btsocket_l2cap_timeout(pcb); else sbdroprecord(&pcb->so->so_snd); /* XXX */ } mtx_unlock(&pcb->pcb_mtx); drop: NG_FREE_M(m); /* checks for != NULL */ NG_FREE_M(control); return (error); } /* ng_btsocket_l2cap_send */ /* * Send first packet in the socket queue to the L2CAP layer */ static int ng_btsocket_l2cap_send2(ng_btsocket_l2cap_pcb_p pcb) { struct mbuf *m = NULL; ng_l2cap_l2ca_hdr_t *hdr = NULL; int error = 0; mtx_assert(&pcb->pcb_mtx, MA_OWNED); if (sbavail(&pcb->so->so_snd) == 0) return (EINVAL); /* XXX */ m = m_dup(pcb->so->so_snd.sb_mb, M_NOWAIT); if (m == NULL) return (ENOBUFS); /* Create L2CA packet header */ M_PREPEND(m, sizeof(*hdr), M_NOWAIT); if (m != NULL) if (m->m_len < sizeof(*hdr)) m = m_pullup(m, sizeof(*hdr)); if (m == NULL) { NG_BTSOCKET_L2CAP_ERR( "%s: Failed to create L2CA packet header\n", __func__); return (ENOBUFS); } hdr = mtod(m, ng_l2cap_l2ca_hdr_t *); hdr->token = pcb->token; hdr->length = m->m_pkthdr.len - sizeof(*hdr); hdr->lcid = pcb->cid; hdr->idtype = pcb->idtype; NG_BTSOCKET_L2CAP_INFO( "%s: Sending packet: len=%d, length=%d, lcid=%d, token=%d, state=%d\n", __func__, m->m_pkthdr.len, hdr->length, hdr->lcid, hdr->token, pcb->state); /* - * If we got here than we have successfuly creates new L2CAP + * If we got here than we have successfully creates new L2CAP * data packet and now we can send it to the L2CAP layer */ NG_SEND_DATA_ONLY(error, pcb->rt->hook, m); return (error); } /* ng_btsocket_l2cap_send2 */ /* * Get socket address */ int ng_btsocket_l2cap_sockaddr(struct socket *so, struct sockaddr **nam) { ng_btsocket_l2cap_pcb_p pcb = so2l2cap_pcb(so); struct sockaddr_l2cap sa; if (pcb == NULL) return (EINVAL); if (ng_btsocket_l2cap_node == NULL) return (EINVAL); bcopy(&pcb->src, &sa.l2cap_bdaddr, sizeof(sa.l2cap_bdaddr)); sa.l2cap_psm = htole16(pcb->psm); sa.l2cap_len = sizeof(sa); sa.l2cap_family = AF_BLUETOOTH; sa.l2cap_cid = 0; sa.l2cap_bdaddr_type = pcb->srctype; *nam = sodupsockaddr((struct sockaddr *) &sa, M_NOWAIT); return ((*nam == NULL)? ENOMEM : 0); } /* ng_btsocket_l2cap_sockaddr */ /***************************************************************************** ***************************************************************************** ** Misc. functions ***************************************************************************** *****************************************************************************/ /* * Look for the socket that listens on given PSM and bdaddr. Returns exact or * close match (if any). Caller must hold ng_btsocket_l2cap_sockets_mtx. */ static ng_btsocket_l2cap_pcb_p ng_btsocket_l2cap_pcb_by_addr(bdaddr_p bdaddr, int psm) { ng_btsocket_l2cap_pcb_p p = NULL, p1 = NULL; mtx_assert(&ng_btsocket_l2cap_sockets_mtx, MA_OWNED); LIST_FOREACH(p, &ng_btsocket_l2cap_sockets, next) { if (p->so == NULL || !(p->so->so_options & SO_ACCEPTCONN) || p->psm != psm) continue; if (bcmp(&p->src, bdaddr, sizeof(p->src)) == 0) break; if (bcmp(&p->src, NG_HCI_BDADDR_ANY, sizeof(p->src)) == 0) p1 = p; } return ((p != NULL)? p : p1); } /* ng_btsocket_l2cap_pcb_by_addr */ /* * Look for the socket that has given token. * Caller must hold ng_btsocket_l2cap_sockets_mtx. */ static ng_btsocket_l2cap_pcb_p ng_btsocket_l2cap_pcb_by_token(u_int32_t token) { ng_btsocket_l2cap_pcb_p p = NULL; if (token == 0) return (NULL); mtx_assert(&ng_btsocket_l2cap_sockets_mtx, MA_OWNED); LIST_FOREACH(p, &ng_btsocket_l2cap_sockets, next) if (p->token == token) break; return (p); } /* ng_btsocket_l2cap_pcb_by_token */ /* * Look for the socket that assigned to given source address and channel ID. * Caller must hold ng_btsocket_l2cap_sockets_mtx */ static ng_btsocket_l2cap_pcb_p ng_btsocket_l2cap_pcb_by_cid(bdaddr_p src, int cid, int idtype) { ng_btsocket_l2cap_pcb_p p = NULL; mtx_assert(&ng_btsocket_l2cap_sockets_mtx, MA_OWNED); LIST_FOREACH(p, &ng_btsocket_l2cap_sockets, next){ if (p->cid == cid && bcmp(src, &p->src, sizeof(p->src)) == 0&& p->idtype == idtype) break; } return (p); } /* ng_btsocket_l2cap_pcb_by_cid */ /* * Set timeout on socket */ static void ng_btsocket_l2cap_timeout(ng_btsocket_l2cap_pcb_p pcb) { mtx_assert(&pcb->pcb_mtx, MA_OWNED); if (!(pcb->flags & NG_BTSOCKET_L2CAP_TIMO)) { pcb->flags |= NG_BTSOCKET_L2CAP_TIMO; callout_reset(&pcb->timo, bluetooth_l2cap_ertx_timeout(), ng_btsocket_l2cap_process_timeout, pcb); } else KASSERT(0, ("%s: Duplicated socket timeout?!\n", __func__)); } /* ng_btsocket_l2cap_timeout */ /* * Unset timeout on socket */ static void ng_btsocket_l2cap_untimeout(ng_btsocket_l2cap_pcb_p pcb) { mtx_assert(&pcb->pcb_mtx, MA_OWNED); if (pcb->flags & NG_BTSOCKET_L2CAP_TIMO) { callout_stop(&pcb->timo); pcb->flags &= ~NG_BTSOCKET_L2CAP_TIMO; } else KASSERT(0, ("%s: No socket timeout?!\n", __func__)); } /* ng_btsocket_l2cap_untimeout */ /* * Process timeout on socket */ static void ng_btsocket_l2cap_process_timeout(void *xpcb) { ng_btsocket_l2cap_pcb_p pcb = (ng_btsocket_l2cap_pcb_p) xpcb; mtx_assert(&pcb->pcb_mtx, MA_OWNED); pcb->flags &= ~NG_BTSOCKET_L2CAP_TIMO; pcb->so->so_error = ETIMEDOUT; switch (pcb->state) { case NG_BTSOCKET_L2CAP_CONNECTING: case NG_BTSOCKET_L2CAP_CONFIGURING: case NG_BTSOCKET_L2CAP_W4_ENC_CHANGE: /* Send disconnect request with "zero" token */ if (pcb->cid != 0) ng_btsocket_l2cap_send_l2ca_discon_req(0, pcb); /* ... and close the socket */ pcb->state = NG_BTSOCKET_L2CAP_CLOSED; soisdisconnected(pcb->so); break; case NG_BTSOCKET_L2CAP_OPEN: /* Send timeout - drop packet and wakeup sender */ sbdroprecord(&pcb->so->so_snd); sowwakeup(pcb->so); break; case NG_BTSOCKET_L2CAP_DISCONNECTING: /* Disconnect timeout - disconnect the socket anyway */ pcb->state = NG_BTSOCKET_L2CAP_CLOSED; soisdisconnected(pcb->so); break; default: NG_BTSOCKET_L2CAP_ERR( "%s: Invalid socket state=%d\n", __func__, pcb->state); break; } } /* ng_btsocket_l2cap_process_timeout */ /* * Translate HCI/L2CAP error code into "errno" code * XXX Note: Some L2CAP and HCI error codes have the same value, but * different meaning */ static int ng_btsocket_l2cap_result2errno(int result) { switch (result) { case 0x00: /* No error */ return (0); case 0x01: /* Unknown HCI command */ return (ENODEV); case 0x02: /* No connection */ return (ENOTCONN); case 0x03: /* Hardware failure */ return (EIO); case 0x04: /* Page timeout */ return (EHOSTDOWN); case 0x05: /* Authentication failure */ case 0x06: /* Key missing */ case 0x18: /* Pairing not allowed */ case 0x21: /* Role change not allowed */ case 0x24: /* LMP PSU not allowed */ case 0x25: /* Encryption mode not acceptable */ case 0x26: /* Unit key used */ return (EACCES); case 0x07: /* Memory full */ return (ENOMEM); case 0x08: /* Connection timeout */ case 0x10: /* Host timeout */ case 0x22: /* LMP response timeout */ case 0xee: /* HCI timeout */ case 0xeeee: /* L2CAP timeout */ return (ETIMEDOUT); case 0x09: /* Max number of connections */ case 0x0a: /* Max number of SCO connections to a unit */ return (EMLINK); case 0x0b: /* ACL connection already exists */ return (EEXIST); case 0x0c: /* Command disallowed */ return (EBUSY); case 0x0d: /* Host rejected due to limited resources */ case 0x0e: /* Host rejected due to securiity reasons */ case 0x0f: /* Host rejected due to remote unit is a personal unit */ case 0x1b: /* SCO offset rejected */ case 0x1c: /* SCO interval rejected */ case 0x1d: /* SCO air mode rejected */ return (ECONNREFUSED); case 0x11: /* Unsupported feature or parameter value */ case 0x19: /* Unknown LMP PDU */ case 0x1a: /* Unsupported remote feature */ case 0x20: /* Unsupported LMP parameter value */ case 0x27: /* QoS is not supported */ case 0x29: /* Paring with unit key not supported */ return (EOPNOTSUPP); case 0x12: /* Invalid HCI command parameter */ case 0x1e: /* Invalid LMP parameters */ return (EINVAL); case 0x13: /* Other end terminated connection: User ended connection */ case 0x14: /* Other end terminated connection: Low resources */ case 0x15: /* Other end terminated connection: About to power off */ return (ECONNRESET); case 0x16: /* Connection terminated by local host */ return (ECONNABORTED); #if 0 /* XXX not yet */ case 0x17: /* Repeated attempts */ case 0x1f: /* Unspecified error */ case 0x23: /* LMP error transaction collision */ case 0x28: /* Instant passed */ #endif } return (ENOSYS); } /* ng_btsocket_l2cap_result2errno */ Index: head/sys/netgraph/bluetooth/socket/ng_btsocket_rfcomm.c =================================================================== --- head/sys/netgraph/bluetooth/socket/ng_btsocket_rfcomm.c (revision 298812) +++ head/sys/netgraph/bluetooth/socket/ng_btsocket_rfcomm.c (revision 298813) @@ -1,3582 +1,3582 @@ /* * ng_btsocket_rfcomm.c */ /*- * Copyright (c) 2001-2003 Maksim Yevmenkin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id: ng_btsocket_rfcomm.c,v 1.28 2003/09/14 23:29:06 max Exp $ * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* MALLOC define */ #ifdef NG_SEPARATE_MALLOC static MALLOC_DEFINE(M_NETGRAPH_BTSOCKET_RFCOMM, "netgraph_btsocks_rfcomm", "Netgraph Bluetooth RFCOMM sockets"); #else #define M_NETGRAPH_BTSOCKET_RFCOMM M_NETGRAPH #endif /* NG_SEPARATE_MALLOC */ /* Debug */ #define NG_BTSOCKET_RFCOMM_INFO \ if (ng_btsocket_rfcomm_debug_level >= NG_BTSOCKET_INFO_LEVEL && \ ppsratecheck(&ng_btsocket_rfcomm_lasttime, &ng_btsocket_rfcomm_curpps, 1)) \ printf #define NG_BTSOCKET_RFCOMM_WARN \ if (ng_btsocket_rfcomm_debug_level >= NG_BTSOCKET_WARN_LEVEL && \ ppsratecheck(&ng_btsocket_rfcomm_lasttime, &ng_btsocket_rfcomm_curpps, 1)) \ printf #define NG_BTSOCKET_RFCOMM_ERR \ if (ng_btsocket_rfcomm_debug_level >= NG_BTSOCKET_ERR_LEVEL && \ ppsratecheck(&ng_btsocket_rfcomm_lasttime, &ng_btsocket_rfcomm_curpps, 1)) \ printf #define NG_BTSOCKET_RFCOMM_ALERT \ if (ng_btsocket_rfcomm_debug_level >= NG_BTSOCKET_ALERT_LEVEL && \ ppsratecheck(&ng_btsocket_rfcomm_lasttime, &ng_btsocket_rfcomm_curpps, 1)) \ printf #define ALOT 0x7fff /* Local prototypes */ static int ng_btsocket_rfcomm_upcall (struct socket *so, void *arg, int waitflag); static void ng_btsocket_rfcomm_sessions_task (void *ctx, int pending); static void ng_btsocket_rfcomm_session_task (ng_btsocket_rfcomm_session_p s); #define ng_btsocket_rfcomm_task_wakeup() \ taskqueue_enqueue(taskqueue_swi_giant, &ng_btsocket_rfcomm_task) static ng_btsocket_rfcomm_pcb_p ng_btsocket_rfcomm_connect_ind (ng_btsocket_rfcomm_session_p s, int channel); static void ng_btsocket_rfcomm_connect_cfm (ng_btsocket_rfcomm_session_p s); static int ng_btsocket_rfcomm_session_create (ng_btsocket_rfcomm_session_p *sp, struct socket *l2so, bdaddr_p src, bdaddr_p dst, struct thread *td); static int ng_btsocket_rfcomm_session_accept (ng_btsocket_rfcomm_session_p s0); static int ng_btsocket_rfcomm_session_connect (ng_btsocket_rfcomm_session_p s); static int ng_btsocket_rfcomm_session_receive (ng_btsocket_rfcomm_session_p s); static int ng_btsocket_rfcomm_session_send (ng_btsocket_rfcomm_session_p s); static void ng_btsocket_rfcomm_session_clean (ng_btsocket_rfcomm_session_p s); static void ng_btsocket_rfcomm_session_process_pcb (ng_btsocket_rfcomm_session_p s); static ng_btsocket_rfcomm_session_p ng_btsocket_rfcomm_session_by_addr (bdaddr_p src, bdaddr_p dst); static int ng_btsocket_rfcomm_receive_frame (ng_btsocket_rfcomm_session_p s, struct mbuf *m0); static int ng_btsocket_rfcomm_receive_sabm (ng_btsocket_rfcomm_session_p s, int dlci); static int ng_btsocket_rfcomm_receive_disc (ng_btsocket_rfcomm_session_p s, int dlci); static int ng_btsocket_rfcomm_receive_ua (ng_btsocket_rfcomm_session_p s, int dlci); static int ng_btsocket_rfcomm_receive_dm (ng_btsocket_rfcomm_session_p s, int dlci); static int ng_btsocket_rfcomm_receive_uih (ng_btsocket_rfcomm_session_p s, int dlci, int pf, struct mbuf *m0); static int ng_btsocket_rfcomm_receive_mcc (ng_btsocket_rfcomm_session_p s, struct mbuf *m0); static int ng_btsocket_rfcomm_receive_test (ng_btsocket_rfcomm_session_p s, struct mbuf *m0); static int ng_btsocket_rfcomm_receive_fc (ng_btsocket_rfcomm_session_p s, struct mbuf *m0); static int ng_btsocket_rfcomm_receive_msc (ng_btsocket_rfcomm_session_p s, struct mbuf *m0); static int ng_btsocket_rfcomm_receive_rpn (ng_btsocket_rfcomm_session_p s, struct mbuf *m0); static int ng_btsocket_rfcomm_receive_rls (ng_btsocket_rfcomm_session_p s, struct mbuf *m0); static int ng_btsocket_rfcomm_receive_pn (ng_btsocket_rfcomm_session_p s, struct mbuf *m0); static void ng_btsocket_rfcomm_set_pn (ng_btsocket_rfcomm_pcb_p pcb, u_int8_t cr, u_int8_t flow_control, u_int8_t credits, u_int16_t mtu); static int ng_btsocket_rfcomm_send_command (ng_btsocket_rfcomm_session_p s, u_int8_t type, u_int8_t dlci); static int ng_btsocket_rfcomm_send_uih (ng_btsocket_rfcomm_session_p s, u_int8_t address, u_int8_t pf, u_int8_t credits, struct mbuf *data); static int ng_btsocket_rfcomm_send_msc (ng_btsocket_rfcomm_pcb_p pcb); static int ng_btsocket_rfcomm_send_pn (ng_btsocket_rfcomm_pcb_p pcb); static int ng_btsocket_rfcomm_send_credits (ng_btsocket_rfcomm_pcb_p pcb); static int ng_btsocket_rfcomm_pcb_send (ng_btsocket_rfcomm_pcb_p pcb, int limit); static void ng_btsocket_rfcomm_pcb_kill (ng_btsocket_rfcomm_pcb_p pcb, int error); static ng_btsocket_rfcomm_pcb_p ng_btsocket_rfcomm_pcb_by_dlci (ng_btsocket_rfcomm_session_p s, int dlci); static ng_btsocket_rfcomm_pcb_p ng_btsocket_rfcomm_pcb_listener (bdaddr_p src, int channel); static void ng_btsocket_rfcomm_timeout (ng_btsocket_rfcomm_pcb_p pcb); static void ng_btsocket_rfcomm_untimeout (ng_btsocket_rfcomm_pcb_p pcb); static void ng_btsocket_rfcomm_process_timeout (void *xpcb); static struct mbuf * ng_btsocket_rfcomm_prepare_packet (struct sockbuf *sb, int length); /* Globals */ extern int ifqmaxlen; static u_int32_t ng_btsocket_rfcomm_debug_level; static u_int32_t ng_btsocket_rfcomm_timo; struct task ng_btsocket_rfcomm_task; static LIST_HEAD(, ng_btsocket_rfcomm_session) ng_btsocket_rfcomm_sessions; static struct mtx ng_btsocket_rfcomm_sessions_mtx; static LIST_HEAD(, ng_btsocket_rfcomm_pcb) ng_btsocket_rfcomm_sockets; static struct mtx ng_btsocket_rfcomm_sockets_mtx; static struct timeval ng_btsocket_rfcomm_lasttime; static int ng_btsocket_rfcomm_curpps; /* Sysctl tree */ SYSCTL_DECL(_net_bluetooth_rfcomm_sockets); static SYSCTL_NODE(_net_bluetooth_rfcomm_sockets, OID_AUTO, stream, CTLFLAG_RW, 0, "Bluetooth STREAM RFCOMM sockets family"); SYSCTL_UINT(_net_bluetooth_rfcomm_sockets_stream, OID_AUTO, debug_level, CTLFLAG_RW, &ng_btsocket_rfcomm_debug_level, NG_BTSOCKET_INFO_LEVEL, "Bluetooth STREAM RFCOMM sockets debug level"); SYSCTL_UINT(_net_bluetooth_rfcomm_sockets_stream, OID_AUTO, timeout, CTLFLAG_RW, &ng_btsocket_rfcomm_timo, 60, "Bluetooth STREAM RFCOMM sockets timeout"); /***************************************************************************** ***************************************************************************** ** RFCOMM CRC ***************************************************************************** *****************************************************************************/ static u_int8_t ng_btsocket_rfcomm_crc_table[256] = { 0x00, 0x91, 0xe3, 0x72, 0x07, 0x96, 0xe4, 0x75, 0x0e, 0x9f, 0xed, 0x7c, 0x09, 0x98, 0xea, 0x7b, 0x1c, 0x8d, 0xff, 0x6e, 0x1b, 0x8a, 0xf8, 0x69, 0x12, 0x83, 0xf1, 0x60, 0x15, 0x84, 0xf6, 0x67, 0x38, 0xa9, 0xdb, 0x4a, 0x3f, 0xae, 0xdc, 0x4d, 0x36, 0xa7, 0xd5, 0x44, 0x31, 0xa0, 0xd2, 0x43, 0x24, 0xb5, 0xc7, 0x56, 0x23, 0xb2, 0xc0, 0x51, 0x2a, 0xbb, 0xc9, 0x58, 0x2d, 0xbc, 0xce, 0x5f, 0x70, 0xe1, 0x93, 0x02, 0x77, 0xe6, 0x94, 0x05, 0x7e, 0xef, 0x9d, 0x0c, 0x79, 0xe8, 0x9a, 0x0b, 0x6c, 0xfd, 0x8f, 0x1e, 0x6b, 0xfa, 0x88, 0x19, 0x62, 0xf3, 0x81, 0x10, 0x65, 0xf4, 0x86, 0x17, 0x48, 0xd9, 0xab, 0x3a, 0x4f, 0xde, 0xac, 0x3d, 0x46, 0xd7, 0xa5, 0x34, 0x41, 0xd0, 0xa2, 0x33, 0x54, 0xc5, 0xb7, 0x26, 0x53, 0xc2, 0xb0, 0x21, 0x5a, 0xcb, 0xb9, 0x28, 0x5d, 0xcc, 0xbe, 0x2f, 0xe0, 0x71, 0x03, 0x92, 0xe7, 0x76, 0x04, 0x95, 0xee, 0x7f, 0x0d, 0x9c, 0xe9, 0x78, 0x0a, 0x9b, 0xfc, 0x6d, 0x1f, 0x8e, 0xfb, 0x6a, 0x18, 0x89, 0xf2, 0x63, 0x11, 0x80, 0xf5, 0x64, 0x16, 0x87, 0xd8, 0x49, 0x3b, 0xaa, 0xdf, 0x4e, 0x3c, 0xad, 0xd6, 0x47, 0x35, 0xa4, 0xd1, 0x40, 0x32, 0xa3, 0xc4, 0x55, 0x27, 0xb6, 0xc3, 0x52, 0x20, 0xb1, 0xca, 0x5b, 0x29, 0xb8, 0xcd, 0x5c, 0x2e, 0xbf, 0x90, 0x01, 0x73, 0xe2, 0x97, 0x06, 0x74, 0xe5, 0x9e, 0x0f, 0x7d, 0xec, 0x99, 0x08, 0x7a, 0xeb, 0x8c, 0x1d, 0x6f, 0xfe, 0x8b, 0x1a, 0x68, 0xf9, 0x82, 0x13, 0x61, 0xf0, 0x85, 0x14, 0x66, 0xf7, 0xa8, 0x39, 0x4b, 0xda, 0xaf, 0x3e, 0x4c, 0xdd, 0xa6, 0x37, 0x45, 0xd4, 0xa1, 0x30, 0x42, 0xd3, 0xb4, 0x25, 0x57, 0xc6, 0xb3, 0x22, 0x50, 0xc1, 0xba, 0x2b, 0x59, 0xc8, 0xbd, 0x2c, 0x5e, 0xcf }; /* CRC */ static u_int8_t ng_btsocket_rfcomm_crc(u_int8_t *data, int length) { u_int8_t crc = 0xff; while (length --) crc = ng_btsocket_rfcomm_crc_table[crc ^ *data++]; return (crc); } /* ng_btsocket_rfcomm_crc */ /* FCS on 2 bytes */ static u_int8_t ng_btsocket_rfcomm_fcs2(u_int8_t *data) { return (0xff - ng_btsocket_rfcomm_crc(data, 2)); } /* ng_btsocket_rfcomm_fcs2 */ /* FCS on 3 bytes */ static u_int8_t ng_btsocket_rfcomm_fcs3(u_int8_t *data) { return (0xff - ng_btsocket_rfcomm_crc(data, 3)); } /* ng_btsocket_rfcomm_fcs3 */ /* * Check FCS * * From Bluetooth spec * * "... In 07.10, the frame check sequence (FCS) is calculated on different * sets of fields for different frame types. These are the fields that the * FCS are calculated on: * * For SABM, DISC, UA, DM frames: on Address, Control and length field. * For UIH frames: on Address and Control field. * * (This is stated here for clarification, and to set the standard for RFCOMM; * the fields included in FCS calculation have actually changed in version * 7.0.0 of TS 07.10, but RFCOMM will not change the FCS calculation scheme * from the one above.) ..." */ static int ng_btsocket_rfcomm_check_fcs(u_int8_t *data, int type, u_int8_t fcs) { if (type != RFCOMM_FRAME_UIH) return (ng_btsocket_rfcomm_fcs3(data) != fcs); return (ng_btsocket_rfcomm_fcs2(data) != fcs); } /* ng_btsocket_rfcomm_check_fcs */ /***************************************************************************** ***************************************************************************** ** Socket interface ***************************************************************************** *****************************************************************************/ /* * Initialize everything */ void ng_btsocket_rfcomm_init(void) { /* Skip initialization of globals for non-default instances. */ if (!IS_DEFAULT_VNET(curvnet)) return; ng_btsocket_rfcomm_debug_level = NG_BTSOCKET_WARN_LEVEL; ng_btsocket_rfcomm_timo = 60; /* RFCOMM task */ TASK_INIT(&ng_btsocket_rfcomm_task, 0, ng_btsocket_rfcomm_sessions_task, NULL); /* RFCOMM sessions list */ LIST_INIT(&ng_btsocket_rfcomm_sessions); mtx_init(&ng_btsocket_rfcomm_sessions_mtx, "btsocks_rfcomm_sessions_mtx", NULL, MTX_DEF); /* RFCOMM sockets list */ LIST_INIT(&ng_btsocket_rfcomm_sockets); mtx_init(&ng_btsocket_rfcomm_sockets_mtx, "btsocks_rfcomm_sockets_mtx", NULL, MTX_DEF); } /* ng_btsocket_rfcomm_init */ /* * Abort connection on socket */ void ng_btsocket_rfcomm_abort(struct socket *so) { so->so_error = ECONNABORTED; (void)ng_btsocket_rfcomm_disconnect(so); } /* ng_btsocket_rfcomm_abort */ void ng_btsocket_rfcomm_close(struct socket *so) { (void)ng_btsocket_rfcomm_disconnect(so); } /* ng_btsocket_rfcomm_close */ /* * Accept connection on socket. Nothing to do here, socket must be connected * and ready, so just return peer address and be done with it. */ int ng_btsocket_rfcomm_accept(struct socket *so, struct sockaddr **nam) { return (ng_btsocket_rfcomm_peeraddr(so, nam)); } /* ng_btsocket_rfcomm_accept */ /* * Create and attach new socket */ int ng_btsocket_rfcomm_attach(struct socket *so, int proto, struct thread *td) { ng_btsocket_rfcomm_pcb_p pcb = so2rfcomm_pcb(so); int error; /* Check socket and protocol */ if (so->so_type != SOCK_STREAM) return (ESOCKTNOSUPPORT); #if 0 /* XXX sonewconn() calls "pru_attach" with proto == 0 */ if (proto != 0) if (proto != BLUETOOTH_PROTO_RFCOMM) return (EPROTONOSUPPORT); #endif /* XXX */ if (pcb != NULL) return (EISCONN); /* Reserve send and receive space if it is not reserved yet */ if ((so->so_snd.sb_hiwat == 0) || (so->so_rcv.sb_hiwat == 0)) { error = soreserve(so, NG_BTSOCKET_RFCOMM_SENDSPACE, NG_BTSOCKET_RFCOMM_RECVSPACE); if (error != 0) return (error); } /* Allocate the PCB */ pcb = malloc(sizeof(*pcb), M_NETGRAPH_BTSOCKET_RFCOMM, M_NOWAIT | M_ZERO); if (pcb == NULL) return (ENOMEM); /* Link the PCB and the socket */ so->so_pcb = (caddr_t) pcb; pcb->so = so; /* Initialize PCB */ pcb->state = NG_BTSOCKET_RFCOMM_DLC_CLOSED; pcb->flags = NG_BTSOCKET_RFCOMM_DLC_CFC; pcb->lmodem = pcb->rmodem = (RFCOMM_MODEM_RTC | RFCOMM_MODEM_RTR | RFCOMM_MODEM_DV); pcb->mtu = RFCOMM_DEFAULT_MTU; pcb->tx_cred = 0; pcb->rx_cred = RFCOMM_DEFAULT_CREDITS; mtx_init(&pcb->pcb_mtx, "btsocks_rfcomm_pcb_mtx", NULL, MTX_DEF); callout_init_mtx(&pcb->timo, &pcb->pcb_mtx, 0); /* Add the PCB to the list */ mtx_lock(&ng_btsocket_rfcomm_sockets_mtx); LIST_INSERT_HEAD(&ng_btsocket_rfcomm_sockets, pcb, next); mtx_unlock(&ng_btsocket_rfcomm_sockets_mtx); return (0); } /* ng_btsocket_rfcomm_attach */ /* * Bind socket */ int ng_btsocket_rfcomm_bind(struct socket *so, struct sockaddr *nam, struct thread *td) { ng_btsocket_rfcomm_pcb_t *pcb = so2rfcomm_pcb(so), *pcb1; struct sockaddr_rfcomm *sa = (struct sockaddr_rfcomm *) nam; if (pcb == NULL) return (EINVAL); /* Verify address */ if (sa == NULL) return (EINVAL); if (sa->rfcomm_family != AF_BLUETOOTH) return (EAFNOSUPPORT); if (sa->rfcomm_len != sizeof(*sa)) return (EINVAL); if (sa->rfcomm_channel > 30) return (EINVAL); mtx_lock(&pcb->pcb_mtx); if (sa->rfcomm_channel != 0) { mtx_lock(&ng_btsocket_rfcomm_sockets_mtx); LIST_FOREACH(pcb1, &ng_btsocket_rfcomm_sockets, next) { if (pcb1->channel == sa->rfcomm_channel && bcmp(&pcb1->src, &sa->rfcomm_bdaddr, sizeof(pcb1->src)) == 0) { mtx_unlock(&ng_btsocket_rfcomm_sockets_mtx); mtx_unlock(&pcb->pcb_mtx); return (EADDRINUSE); } } mtx_unlock(&ng_btsocket_rfcomm_sockets_mtx); } bcopy(&sa->rfcomm_bdaddr, &pcb->src, sizeof(pcb->src)); pcb->channel = sa->rfcomm_channel; mtx_unlock(&pcb->pcb_mtx); return (0); } /* ng_btsocket_rfcomm_bind */ /* * Connect socket */ int ng_btsocket_rfcomm_connect(struct socket *so, struct sockaddr *nam, struct thread *td) { ng_btsocket_rfcomm_pcb_t *pcb = so2rfcomm_pcb(so); struct sockaddr_rfcomm *sa = (struct sockaddr_rfcomm *) nam; ng_btsocket_rfcomm_session_t *s = NULL; struct socket *l2so = NULL; int dlci, error = 0; if (pcb == NULL) return (EINVAL); /* Verify address */ if (sa == NULL) return (EINVAL); if (sa->rfcomm_family != AF_BLUETOOTH) return (EAFNOSUPPORT); if (sa->rfcomm_len != sizeof(*sa)) return (EINVAL); if (sa->rfcomm_channel > 30) return (EINVAL); if (sa->rfcomm_channel == 0 || bcmp(&sa->rfcomm_bdaddr, NG_HCI_BDADDR_ANY, sizeof(bdaddr_t)) == 0) return (EDESTADDRREQ); /* * Note that we will not check for errors in socreate() because * if we failed to create L2CAP socket at this point we still * might have already open session. */ error = socreate(PF_BLUETOOTH, &l2so, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP, td->td_ucred, td); /* * Look for session between "pcb->src" and "sa->rfcomm_bdaddr" (dst) */ mtx_lock(&ng_btsocket_rfcomm_sessions_mtx); s = ng_btsocket_rfcomm_session_by_addr(&pcb->src, &sa->rfcomm_bdaddr); if (s == NULL) { /* * We need to create new RFCOMM session. Check if we have L2CAP * socket. If l2so == NULL then error has the error code from * socreate() */ if (l2so == NULL) { mtx_unlock(&ng_btsocket_rfcomm_sessions_mtx); return (error); } error = ng_btsocket_rfcomm_session_create(&s, l2so, &pcb->src, &sa->rfcomm_bdaddr, td); if (error != 0) { mtx_unlock(&ng_btsocket_rfcomm_sessions_mtx); soclose(l2so); return (error); } } else if (l2so != NULL) soclose(l2so); /* we don't need new L2CAP socket */ /* * Check if we already have the same DLCI the same session */ mtx_lock(&s->session_mtx); mtx_lock(&pcb->pcb_mtx); dlci = RFCOMM_MKDLCI(!INITIATOR(s), sa->rfcomm_channel); if (ng_btsocket_rfcomm_pcb_by_dlci(s, dlci) != NULL) { mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&s->session_mtx); mtx_unlock(&ng_btsocket_rfcomm_sessions_mtx); return (EBUSY); } /* * Check session state and if its not acceptable then refuse connection */ switch (s->state) { case NG_BTSOCKET_RFCOMM_SESSION_CONNECTING: case NG_BTSOCKET_RFCOMM_SESSION_CONNECTED: case NG_BTSOCKET_RFCOMM_SESSION_OPEN: /* * Update destination address and channel and attach * DLC to the session */ bcopy(&sa->rfcomm_bdaddr, &pcb->dst, sizeof(pcb->dst)); pcb->channel = sa->rfcomm_channel; pcb->dlci = dlci; LIST_INSERT_HEAD(&s->dlcs, pcb, session_next); pcb->session = s; ng_btsocket_rfcomm_timeout(pcb); soisconnecting(pcb->so); if (s->state == NG_BTSOCKET_RFCOMM_SESSION_OPEN) { pcb->mtu = s->mtu; bcopy(&so2l2cap_pcb(s->l2so)->src, &pcb->src, sizeof(pcb->src)); pcb->state = NG_BTSOCKET_RFCOMM_DLC_CONFIGURING; error = ng_btsocket_rfcomm_send_pn(pcb); if (error == 0) error = ng_btsocket_rfcomm_task_wakeup(); } else pcb->state = NG_BTSOCKET_RFCOMM_DLC_W4_CONNECT; break; default: error = ECONNRESET; break; } mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&s->session_mtx); mtx_unlock(&ng_btsocket_rfcomm_sessions_mtx); return (error); } /* ng_btsocket_rfcomm_connect */ /* * Process ioctl's calls on socket. * XXX FIXME this should provide interface to the RFCOMM multiplexor channel */ int ng_btsocket_rfcomm_control(struct socket *so, u_long cmd, caddr_t data, struct ifnet *ifp, struct thread *td) { return (EINVAL); } /* ng_btsocket_rfcomm_control */ /* * Process getsockopt/setsockopt system calls */ int ng_btsocket_rfcomm_ctloutput(struct socket *so, struct sockopt *sopt) { ng_btsocket_rfcomm_pcb_p pcb = so2rfcomm_pcb(so); struct ng_btsocket_rfcomm_fc_info fcinfo; int error = 0; if (pcb == NULL) return (EINVAL); if (sopt->sopt_level != SOL_RFCOMM) return (0); mtx_lock(&pcb->pcb_mtx); switch (sopt->sopt_dir) { case SOPT_GET: switch (sopt->sopt_name) { case SO_RFCOMM_MTU: error = sooptcopyout(sopt, &pcb->mtu, sizeof(pcb->mtu)); break; case SO_RFCOMM_FC_INFO: fcinfo.lmodem = pcb->lmodem; fcinfo.rmodem = pcb->rmodem; fcinfo.tx_cred = pcb->tx_cred; fcinfo.rx_cred = pcb->rx_cred; fcinfo.cfc = (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_CFC)? 1 : 0; fcinfo.reserved = 0; error = sooptcopyout(sopt, &fcinfo, sizeof(fcinfo)); break; default: error = ENOPROTOOPT; break; } break; case SOPT_SET: switch (sopt->sopt_name) { default: error = ENOPROTOOPT; break; } break; default: error = EINVAL; break; } mtx_unlock(&pcb->pcb_mtx); return (error); } /* ng_btsocket_rfcomm_ctloutput */ /* * Detach and destroy socket */ void ng_btsocket_rfcomm_detach(struct socket *so) { ng_btsocket_rfcomm_pcb_p pcb = so2rfcomm_pcb(so); KASSERT(pcb != NULL, ("ng_btsocket_rfcomm_detach: pcb == NULL")); mtx_lock(&pcb->pcb_mtx); switch (pcb->state) { case NG_BTSOCKET_RFCOMM_DLC_W4_CONNECT: case NG_BTSOCKET_RFCOMM_DLC_CONFIGURING: case NG_BTSOCKET_RFCOMM_DLC_CONNECTING: case NG_BTSOCKET_RFCOMM_DLC_CONNECTED: /* XXX What to do with pending request? */ if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_TIMO) ng_btsocket_rfcomm_untimeout(pcb); if (pcb->state == NG_BTSOCKET_RFCOMM_DLC_W4_CONNECT) pcb->flags |= NG_BTSOCKET_RFCOMM_DLC_DETACHED; else pcb->state = NG_BTSOCKET_RFCOMM_DLC_DISCONNECTING; ng_btsocket_rfcomm_task_wakeup(); break; case NG_BTSOCKET_RFCOMM_DLC_DISCONNECTING: ng_btsocket_rfcomm_task_wakeup(); break; } while (pcb->state != NG_BTSOCKET_RFCOMM_DLC_CLOSED) msleep(&pcb->state, &pcb->pcb_mtx, PZERO, "rf_det", 0); if (pcb->session != NULL) panic("%s: pcb->session != NULL\n", __func__); if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_TIMO) panic("%s: timeout on closed DLC, flags=%#x\n", __func__, pcb->flags); mtx_lock(&ng_btsocket_rfcomm_sockets_mtx); LIST_REMOVE(pcb, next); mtx_unlock(&ng_btsocket_rfcomm_sockets_mtx); mtx_unlock(&pcb->pcb_mtx); mtx_destroy(&pcb->pcb_mtx); bzero(pcb, sizeof(*pcb)); free(pcb, M_NETGRAPH_BTSOCKET_RFCOMM); soisdisconnected(so); so->so_pcb = NULL; } /* ng_btsocket_rfcomm_detach */ /* * Disconnect socket */ int ng_btsocket_rfcomm_disconnect(struct socket *so) { ng_btsocket_rfcomm_pcb_p pcb = so2rfcomm_pcb(so); if (pcb == NULL) return (EINVAL); mtx_lock(&pcb->pcb_mtx); if (pcb->state == NG_BTSOCKET_RFCOMM_DLC_DISCONNECTING) { mtx_unlock(&pcb->pcb_mtx); return (EINPROGRESS); } /* XXX What to do with pending request? */ if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_TIMO) ng_btsocket_rfcomm_untimeout(pcb); switch (pcb->state) { case NG_BTSOCKET_RFCOMM_DLC_CONFIGURING: /* XXX can we get here? */ case NG_BTSOCKET_RFCOMM_DLC_CONNECTING: /* XXX can we get here? */ case NG_BTSOCKET_RFCOMM_DLC_CONNECTED: /* * Just change DLC state and enqueue RFCOMM task. It will * queue and send DISC on the DLC. */ pcb->state = NG_BTSOCKET_RFCOMM_DLC_DISCONNECTING; soisdisconnecting(so); ng_btsocket_rfcomm_task_wakeup(); break; case NG_BTSOCKET_RFCOMM_DLC_CLOSED: case NG_BTSOCKET_RFCOMM_DLC_W4_CONNECT: break; default: panic("%s: Invalid DLC state=%d, flags=%#x\n", __func__, pcb->state, pcb->flags); break; } mtx_unlock(&pcb->pcb_mtx); return (0); } /* ng_btsocket_rfcomm_disconnect */ /* * Listen on socket. First call to listen() will create listening RFCOMM session */ int ng_btsocket_rfcomm_listen(struct socket *so, int backlog, struct thread *td) { ng_btsocket_rfcomm_pcb_p pcb = so2rfcomm_pcb(so), pcb1; ng_btsocket_rfcomm_session_p s = NULL; struct socket *l2so = NULL; int error, socreate_error, usedchannels; if (pcb == NULL) return (EINVAL); if (pcb->channel > 30) return (EADDRNOTAVAIL); usedchannels = 0; mtx_lock(&pcb->pcb_mtx); if (pcb->channel == 0) { mtx_lock(&ng_btsocket_rfcomm_sockets_mtx); LIST_FOREACH(pcb1, &ng_btsocket_rfcomm_sockets, next) if (pcb1->channel != 0 && bcmp(&pcb1->src, &pcb->src, sizeof(pcb->src)) == 0) usedchannels |= (1 << (pcb1->channel - 1)); for (pcb->channel = 30; pcb->channel > 0; pcb->channel --) if (!(usedchannels & (1 << (pcb->channel - 1)))) break; if (pcb->channel == 0) { mtx_unlock(&ng_btsocket_rfcomm_sockets_mtx); mtx_unlock(&pcb->pcb_mtx); return (EADDRNOTAVAIL); } mtx_unlock(&ng_btsocket_rfcomm_sockets_mtx); } mtx_unlock(&pcb->pcb_mtx); /* * Note that we will not check for errors in socreate() because * if we failed to create L2CAP socket at this point we still * might have already open session. */ socreate_error = socreate(PF_BLUETOOTH, &l2so, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP, td->td_ucred, td); /* * Transition the socket and session into the LISTENING state. Check * for collisions first, as there can only be one. */ mtx_lock(&ng_btsocket_rfcomm_sessions_mtx); SOCK_LOCK(so); error = solisten_proto_check(so); SOCK_UNLOCK(so); if (error != 0) goto out; LIST_FOREACH(s, &ng_btsocket_rfcomm_sessions, next) if (s->state == NG_BTSOCKET_RFCOMM_SESSION_LISTENING) break; if (s == NULL) { /* * We need to create default RFCOMM session. Check if we have * L2CAP socket. If l2so == NULL then error has the error code * from socreate() */ if (l2so == NULL) { error = socreate_error; goto out; } /* * Create default listen RFCOMM session. The default RFCOMM * session will listen on ANY address. * * XXX FIXME Note that currently there is no way to adjust MTU * for the default session. */ error = ng_btsocket_rfcomm_session_create(&s, l2so, NG_HCI_BDADDR_ANY, NULL, td); if (error != 0) goto out; l2so = NULL; } SOCK_LOCK(so); solisten_proto(so, backlog); SOCK_UNLOCK(so); out: mtx_unlock(&ng_btsocket_rfcomm_sessions_mtx); /* * If we still have an l2so reference here, it's unneeded, so release * it. */ if (l2so != NULL) soclose(l2so); return (error); } /* ng_btsocket_listen */ /* * Get peer address */ int ng_btsocket_rfcomm_peeraddr(struct socket *so, struct sockaddr **nam) { ng_btsocket_rfcomm_pcb_p pcb = so2rfcomm_pcb(so); struct sockaddr_rfcomm sa; if (pcb == NULL) return (EINVAL); bcopy(&pcb->dst, &sa.rfcomm_bdaddr, sizeof(sa.rfcomm_bdaddr)); sa.rfcomm_channel = pcb->channel; sa.rfcomm_len = sizeof(sa); sa.rfcomm_family = AF_BLUETOOTH; *nam = sodupsockaddr((struct sockaddr *) &sa, M_NOWAIT); return ((*nam == NULL)? ENOMEM : 0); } /* ng_btsocket_rfcomm_peeraddr */ /* * Send data to socket */ int ng_btsocket_rfcomm_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *nam, struct mbuf *control, struct thread *td) { ng_btsocket_rfcomm_pcb_t *pcb = so2rfcomm_pcb(so); int error = 0; /* Check socket and input */ if (pcb == NULL || m == NULL || control != NULL) { error = EINVAL; goto drop; } mtx_lock(&pcb->pcb_mtx); /* Make sure DLC is connected */ if (pcb->state != NG_BTSOCKET_RFCOMM_DLC_CONNECTED) { mtx_unlock(&pcb->pcb_mtx); error = ENOTCONN; goto drop; } /* Put the packet on the socket's send queue and wakeup RFCOMM task */ sbappend(&pcb->so->so_snd, m, flags); m = NULL; if (!(pcb->flags & NG_BTSOCKET_RFCOMM_DLC_SENDING)) { pcb->flags |= NG_BTSOCKET_RFCOMM_DLC_SENDING; error = ng_btsocket_rfcomm_task_wakeup(); } mtx_unlock(&pcb->pcb_mtx); drop: NG_FREE_M(m); /* checks for != NULL */ NG_FREE_M(control); return (error); } /* ng_btsocket_rfcomm_send */ /* * Get socket address */ int ng_btsocket_rfcomm_sockaddr(struct socket *so, struct sockaddr **nam) { ng_btsocket_rfcomm_pcb_p pcb = so2rfcomm_pcb(so); struct sockaddr_rfcomm sa; if (pcb == NULL) return (EINVAL); bcopy(&pcb->src, &sa.rfcomm_bdaddr, sizeof(sa.rfcomm_bdaddr)); sa.rfcomm_channel = pcb->channel; sa.rfcomm_len = sizeof(sa); sa.rfcomm_family = AF_BLUETOOTH; *nam = sodupsockaddr((struct sockaddr *) &sa, M_NOWAIT); return ((*nam == NULL)? ENOMEM : 0); } /* ng_btsocket_rfcomm_sockaddr */ /* * Upcall function for L2CAP sockets. Enqueue RFCOMM task. */ static int ng_btsocket_rfcomm_upcall(struct socket *so, void *arg, int waitflag) { int error; if (so == NULL) panic("%s: so == NULL\n", __func__); if ((error = ng_btsocket_rfcomm_task_wakeup()) != 0) NG_BTSOCKET_RFCOMM_ALERT( "%s: Could not enqueue RFCOMM task, error=%d\n", __func__, error); return (SU_OK); } /* ng_btsocket_rfcomm_upcall */ /* * RFCOMM task. Will handle all RFCOMM sessions in one pass. * XXX FIXME does not scale very well */ static void ng_btsocket_rfcomm_sessions_task(void *ctx, int pending) { ng_btsocket_rfcomm_session_p s = NULL, s_next = NULL; mtx_lock(&ng_btsocket_rfcomm_sessions_mtx); for (s = LIST_FIRST(&ng_btsocket_rfcomm_sessions); s != NULL; ) { mtx_lock(&s->session_mtx); s_next = LIST_NEXT(s, next); ng_btsocket_rfcomm_session_task(s); if (s->state == NG_BTSOCKET_RFCOMM_SESSION_CLOSED) { /* Unlink and clean the session */ LIST_REMOVE(s, next); NG_BT_MBUFQ_DRAIN(&s->outq); if (!LIST_EMPTY(&s->dlcs)) panic("%s: DLC list is not empty\n", __func__); /* Close L2CAP socket */ SOCKBUF_LOCK(&s->l2so->so_rcv); soupcall_clear(s->l2so, SO_RCV); SOCKBUF_UNLOCK(&s->l2so->so_rcv); SOCKBUF_LOCK(&s->l2so->so_snd); soupcall_clear(s->l2so, SO_SND); SOCKBUF_UNLOCK(&s->l2so->so_snd); soclose(s->l2so); mtx_unlock(&s->session_mtx); mtx_destroy(&s->session_mtx); bzero(s, sizeof(*s)); free(s, M_NETGRAPH_BTSOCKET_RFCOMM); } else mtx_unlock(&s->session_mtx); s = s_next; } mtx_unlock(&ng_btsocket_rfcomm_sessions_mtx); } /* ng_btsocket_rfcomm_sessions_task */ /* * Process RFCOMM session. Will handle all RFCOMM sockets in one pass. */ static void ng_btsocket_rfcomm_session_task(ng_btsocket_rfcomm_session_p s) { mtx_assert(&s->session_mtx, MA_OWNED); if (s->l2so->so_rcv.sb_state & SBS_CANTRCVMORE) { NG_BTSOCKET_RFCOMM_INFO( "%s: L2CAP connection has been terminated, so=%p, so_state=%#x, so_count=%d, " \ "state=%d, flags=%#x\n", __func__, s->l2so, s->l2so->so_state, s->l2so->so_count, s->state, s->flags); s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; ng_btsocket_rfcomm_session_clean(s); } /* Now process upcall */ switch (s->state) { /* Try to accept new L2CAP connection(s) */ case NG_BTSOCKET_RFCOMM_SESSION_LISTENING: while (ng_btsocket_rfcomm_session_accept(s) == 0) ; break; /* Process the results of the L2CAP connect */ case NG_BTSOCKET_RFCOMM_SESSION_CONNECTING: ng_btsocket_rfcomm_session_process_pcb(s); if (ng_btsocket_rfcomm_session_connect(s) != 0) { s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; ng_btsocket_rfcomm_session_clean(s); } break; /* Try to receive/send more data */ case NG_BTSOCKET_RFCOMM_SESSION_CONNECTED: case NG_BTSOCKET_RFCOMM_SESSION_OPEN: case NG_BTSOCKET_RFCOMM_SESSION_DISCONNECTING: ng_btsocket_rfcomm_session_process_pcb(s); if (ng_btsocket_rfcomm_session_receive(s) != 0) { s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; ng_btsocket_rfcomm_session_clean(s); } else if (ng_btsocket_rfcomm_session_send(s) != 0) { s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; ng_btsocket_rfcomm_session_clean(s); } break; case NG_BTSOCKET_RFCOMM_SESSION_CLOSED: break; default: panic("%s: Invalid session state=%d, flags=%#x\n", __func__, s->state, s->flags); break; } } /* ng_btsocket_rfcomm_session_task */ /* * Process RFCOMM connection indicator. Caller must hold s->session_mtx */ static ng_btsocket_rfcomm_pcb_p ng_btsocket_rfcomm_connect_ind(ng_btsocket_rfcomm_session_p s, int channel) { ng_btsocket_rfcomm_pcb_p pcb = NULL, pcb1 = NULL; ng_btsocket_l2cap_pcb_p l2pcb = NULL; struct socket *so1 = NULL; mtx_assert(&s->session_mtx, MA_OWNED); /* * Try to find RFCOMM socket that listens on given source address * and channel. This will return the best possible match. */ l2pcb = so2l2cap_pcb(s->l2so); pcb = ng_btsocket_rfcomm_pcb_listener(&l2pcb->src, channel); if (pcb == NULL) return (NULL); /* * Check the pending connections queue and if we have space then * create new socket and set proper source and destination address, * and channel. */ mtx_lock(&pcb->pcb_mtx); if (pcb->so->so_qlen <= pcb->so->so_qlimit) { CURVNET_SET(pcb->so->so_vnet); so1 = sonewconn(pcb->so, 0); CURVNET_RESTORE(); } mtx_unlock(&pcb->pcb_mtx); if (so1 == NULL) return (NULL); /* * If we got here than we have created new socket. So complete the * connection. Set source and destination address from the session. */ pcb1 = so2rfcomm_pcb(so1); if (pcb1 == NULL) panic("%s: pcb1 == NULL\n", __func__); mtx_lock(&pcb1->pcb_mtx); bcopy(&l2pcb->src, &pcb1->src, sizeof(pcb1->src)); bcopy(&l2pcb->dst, &pcb1->dst, sizeof(pcb1->dst)); pcb1->channel = channel; /* Link new DLC to the session. We already hold s->session_mtx */ LIST_INSERT_HEAD(&s->dlcs, pcb1, session_next); pcb1->session = s; mtx_unlock(&pcb1->pcb_mtx); return (pcb1); } /* ng_btsocket_rfcomm_connect_ind */ /* * Process RFCOMM connect confirmation. Caller must hold s->session_mtx. */ static void ng_btsocket_rfcomm_connect_cfm(ng_btsocket_rfcomm_session_p s) { ng_btsocket_rfcomm_pcb_p pcb = NULL, pcb_next = NULL; int error; mtx_assert(&s->session_mtx, MA_OWNED); /* * Wake up all waiting sockets and send PN request for each of them. * Note that timeout already been set in ng_btsocket_rfcomm_connect() * * Note: cannot use LIST_FOREACH because ng_btsocket_rfcomm_pcb_kill * will unlink DLC from the session */ for (pcb = LIST_FIRST(&s->dlcs); pcb != NULL; ) { mtx_lock(&pcb->pcb_mtx); pcb_next = LIST_NEXT(pcb, session_next); if (pcb->state == NG_BTSOCKET_RFCOMM_DLC_W4_CONNECT) { pcb->mtu = s->mtu; bcopy(&so2l2cap_pcb(s->l2so)->src, &pcb->src, sizeof(pcb->src)); error = ng_btsocket_rfcomm_send_pn(pcb); if (error == 0) pcb->state = NG_BTSOCKET_RFCOMM_DLC_CONFIGURING; else ng_btsocket_rfcomm_pcb_kill(pcb, error); } mtx_unlock(&pcb->pcb_mtx); pcb = pcb_next; } } /* ng_btsocket_rfcomm_connect_cfm */ /***************************************************************************** ***************************************************************************** ** RFCOMM sessions ***************************************************************************** *****************************************************************************/ /* * Create new RFCOMM session. That function WILL NOT take ownership over l2so. * Caller MUST free l2so if function failed. */ static int ng_btsocket_rfcomm_session_create(ng_btsocket_rfcomm_session_p *sp, struct socket *l2so, bdaddr_p src, bdaddr_p dst, struct thread *td) { ng_btsocket_rfcomm_session_p s = NULL; struct sockaddr_l2cap l2sa; struct sockopt l2sopt; int error; u_int16_t mtu; mtx_assert(&ng_btsocket_rfcomm_sessions_mtx, MA_OWNED); /* Allocate the RFCOMM session */ s = malloc(sizeof(*s), M_NETGRAPH_BTSOCKET_RFCOMM, M_NOWAIT | M_ZERO); if (s == NULL) return (ENOMEM); /* Set defaults */ s->mtu = RFCOMM_DEFAULT_MTU; s->flags = 0; s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; NG_BT_MBUFQ_INIT(&s->outq, ifqmaxlen); /* * XXX Mark session mutex as DUPOK to prevent "duplicated lock of * the same type" message. When accepting new L2CAP connection * ng_btsocket_rfcomm_session_accept() holds both session mutexes * for "old" (accepting) session and "new" (created) session. */ mtx_init(&s->session_mtx, "btsocks_rfcomm_session_mtx", NULL, MTX_DEF|MTX_DUPOK); LIST_INIT(&s->dlcs); /* Prepare L2CAP socket */ SOCKBUF_LOCK(&l2so->so_rcv); soupcall_set(l2so, SO_RCV, ng_btsocket_rfcomm_upcall, NULL); SOCKBUF_UNLOCK(&l2so->so_rcv); SOCKBUF_LOCK(&l2so->so_snd); soupcall_set(l2so, SO_SND, ng_btsocket_rfcomm_upcall, NULL); SOCKBUF_UNLOCK(&l2so->so_snd); l2so->so_state |= SS_NBIO; s->l2so = l2so; mtx_lock(&s->session_mtx); /* * "src" == NULL and "dst" == NULL means just create session. * caller must do the rest */ if (src == NULL && dst == NULL) goto done; /* * Set incoming MTU on L2CAP socket. It is RFCOMM session default MTU * plus 5 bytes: RFCOMM frame header, one extra byte for length and one * extra byte for credits. */ mtu = s->mtu + sizeof(struct rfcomm_frame_hdr) + 1 + 1; l2sopt.sopt_dir = SOPT_SET; l2sopt.sopt_level = SOL_L2CAP; l2sopt.sopt_name = SO_L2CAP_IMTU; l2sopt.sopt_val = (void *) &mtu; l2sopt.sopt_valsize = sizeof(mtu); l2sopt.sopt_td = NULL; error = sosetopt(s->l2so, &l2sopt); if (error != 0) goto bad; /* Bind socket to "src" address */ l2sa.l2cap_len = sizeof(l2sa); l2sa.l2cap_family = AF_BLUETOOTH; l2sa.l2cap_psm = (dst == NULL)? htole16(NG_L2CAP_PSM_RFCOMM) : 0; bcopy(src, &l2sa.l2cap_bdaddr, sizeof(l2sa.l2cap_bdaddr)); l2sa.l2cap_cid = 0; l2sa.l2cap_bdaddr_type = BDADDR_BREDR; error = sobind(s->l2so, (struct sockaddr *) &l2sa, td); if (error != 0) goto bad; /* If "dst" is not NULL then initiate connect(), otherwise listen() */ if (dst == NULL) { s->flags = 0; s->state = NG_BTSOCKET_RFCOMM_SESSION_LISTENING; error = solisten(s->l2so, 10, td); if (error != 0) goto bad; } else { s->flags = NG_BTSOCKET_RFCOMM_SESSION_INITIATOR; s->state = NG_BTSOCKET_RFCOMM_SESSION_CONNECTING; l2sa.l2cap_len = sizeof(l2sa); l2sa.l2cap_family = AF_BLUETOOTH; l2sa.l2cap_psm = htole16(NG_L2CAP_PSM_RFCOMM); bcopy(dst, &l2sa.l2cap_bdaddr, sizeof(l2sa.l2cap_bdaddr)); l2sa.l2cap_cid = 0; l2sa.l2cap_bdaddr_type = BDADDR_BREDR; error = soconnect(s->l2so, (struct sockaddr *) &l2sa, td); if (error != 0) goto bad; } done: LIST_INSERT_HEAD(&ng_btsocket_rfcomm_sessions, s, next); *sp = s; mtx_unlock(&s->session_mtx); return (0); bad: mtx_unlock(&s->session_mtx); /* Return L2CAP socket back to its original state */ SOCKBUF_LOCK(&l2so->so_rcv); soupcall_clear(s->l2so, SO_RCV); SOCKBUF_UNLOCK(&l2so->so_rcv); SOCKBUF_LOCK(&l2so->so_snd); soupcall_clear(s->l2so, SO_SND); SOCKBUF_UNLOCK(&l2so->so_snd); l2so->so_state &= ~SS_NBIO; mtx_destroy(&s->session_mtx); bzero(s, sizeof(*s)); free(s, M_NETGRAPH_BTSOCKET_RFCOMM); return (error); } /* ng_btsocket_rfcomm_session_create */ /* * Process accept() on RFCOMM session * XXX FIXME locking for "l2so"? */ static int ng_btsocket_rfcomm_session_accept(ng_btsocket_rfcomm_session_p s0) { struct socket *l2so = NULL; struct sockaddr_l2cap *l2sa = NULL; ng_btsocket_l2cap_pcb_t *l2pcb = NULL; ng_btsocket_rfcomm_session_p s = NULL; int error = 0; mtx_assert(&ng_btsocket_rfcomm_sessions_mtx, MA_OWNED); mtx_assert(&s0->session_mtx, MA_OWNED); /* Check if there is a complete L2CAP connection in the queue */ if ((error = s0->l2so->so_error) != 0) { NG_BTSOCKET_RFCOMM_ERR( "%s: Could not accept connection on L2CAP socket, error=%d\n", __func__, error); s0->l2so->so_error = 0; return (error); } ACCEPT_LOCK(); if (TAILQ_EMPTY(&s0->l2so->so_comp)) { ACCEPT_UNLOCK(); if (s0->l2so->so_rcv.sb_state & SBS_CANTRCVMORE) return (ECONNABORTED); return (EWOULDBLOCK); } /* Accept incoming L2CAP connection */ l2so = TAILQ_FIRST(&s0->l2so->so_comp); if (l2so == NULL) panic("%s: l2so == NULL\n", __func__); TAILQ_REMOVE(&s0->l2so->so_comp, l2so, so_list); s0->l2so->so_qlen --; l2so->so_qstate &= ~SQ_COMP; l2so->so_head = NULL; SOCK_LOCK(l2so); soref(l2so); l2so->so_state |= SS_NBIO; SOCK_UNLOCK(l2so); ACCEPT_UNLOCK(); error = soaccept(l2so, (struct sockaddr **) &l2sa); if (error != 0) { NG_BTSOCKET_RFCOMM_ERR( "%s: soaccept() on L2CAP socket failed, error=%d\n", __func__, error); soclose(l2so); return (error); } /* * Check if there is already active RFCOMM session between two devices. * If so then close L2CAP connection. We only support one RFCOMM session * between each pair of devices. Note that here we assume session in any * state. The session even could be in the middle of disconnecting. */ l2pcb = so2l2cap_pcb(l2so); s = ng_btsocket_rfcomm_session_by_addr(&l2pcb->src, &l2pcb->dst); if (s == NULL) { /* Create a new RFCOMM session */ error = ng_btsocket_rfcomm_session_create(&s, l2so, NULL, NULL, curthread /* XXX */); if (error == 0) { mtx_lock(&s->session_mtx); s->flags = 0; s->state = NG_BTSOCKET_RFCOMM_SESSION_CONNECTED; /* - * Adjust MTU on incomming connection. Reserve 5 bytes: + * Adjust MTU on incoming connection. Reserve 5 bytes: * RFCOMM frame header, one extra byte for length and * one extra byte for credits. */ s->mtu = min(l2pcb->imtu, l2pcb->omtu) - sizeof(struct rfcomm_frame_hdr) - 1 - 1; mtx_unlock(&s->session_mtx); } else { NG_BTSOCKET_RFCOMM_ALERT( "%s: Failed to create new RFCOMM session, error=%d\n", __func__, error); soclose(l2so); } } else { NG_BTSOCKET_RFCOMM_WARN( "%s: Rejecting duplicating RFCOMM session between src=%x:%x:%x:%x:%x:%x and " \ "dst=%x:%x:%x:%x:%x:%x, state=%d, flags=%#x\n", __func__, l2pcb->src.b[5], l2pcb->src.b[4], l2pcb->src.b[3], l2pcb->src.b[2], l2pcb->src.b[1], l2pcb->src.b[0], l2pcb->dst.b[5], l2pcb->dst.b[4], l2pcb->dst.b[3], l2pcb->dst.b[2], l2pcb->dst.b[1], l2pcb->dst.b[0], s->state, s->flags); error = EBUSY; soclose(l2so); } return (error); } /* ng_btsocket_rfcomm_session_accept */ /* * Process connect() on RFCOMM session * XXX FIXME locking for "l2so"? */ static int ng_btsocket_rfcomm_session_connect(ng_btsocket_rfcomm_session_p s) { ng_btsocket_l2cap_pcb_p l2pcb = so2l2cap_pcb(s->l2so); int error; mtx_assert(&s->session_mtx, MA_OWNED); /* First check if connection has failed */ if ((error = s->l2so->so_error) != 0) { s->l2so->so_error = 0; NG_BTSOCKET_RFCOMM_ERR( "%s: Could not connect RFCOMM session, error=%d, state=%d, flags=%#x\n", __func__, error, s->state, s->flags); return (error); } /* Is connection still in progress? */ if (s->l2so->so_state & SS_ISCONNECTING) return (0); /* * If we got here then we are connected. Send SABM on DLCI 0 to * open multiplexor channel. */ if (error == 0) { s->state = NG_BTSOCKET_RFCOMM_SESSION_CONNECTED; /* * Adjust MTU on outgoing connection. Reserve 5 bytes: RFCOMM * frame header, one extra byte for length and one extra byte * for credits. */ s->mtu = min(l2pcb->imtu, l2pcb->omtu) - sizeof(struct rfcomm_frame_hdr) - 1 - 1; error = ng_btsocket_rfcomm_send_command(s,RFCOMM_FRAME_SABM,0); if (error == 0) error = ng_btsocket_rfcomm_task_wakeup(); } return (error); }/* ng_btsocket_rfcomm_session_connect */ /* * Receive data on RFCOMM session * XXX FIXME locking for "l2so"? */ static int ng_btsocket_rfcomm_session_receive(ng_btsocket_rfcomm_session_p s) { struct mbuf *m = NULL; struct uio uio; int more, flags, error; mtx_assert(&s->session_mtx, MA_OWNED); /* Can we read from the L2CAP socket? */ if (!soreadable(s->l2so)) return (0); /* First check for error on L2CAP socket */ if ((error = s->l2so->so_error) != 0) { s->l2so->so_error = 0; NG_BTSOCKET_RFCOMM_ERR( "%s: Could not receive data from L2CAP socket, error=%d, state=%d, flags=%#x\n", __func__, error, s->state, s->flags); return (error); } /* * Read all packets from the L2CAP socket. * XXX FIXME/VERIFY is that correct? For now use m->m_nextpkt as * indication that there is more packets on the socket's buffer. * Also what should we use in uio.uio_resid? * May be s->mtu + sizeof(struct rfcomm_frame_hdr) + 1 + 1? */ for (more = 1; more; ) { /* Try to get next packet from socket */ bzero(&uio, sizeof(uio)); /* uio.uio_td = NULL; */ uio.uio_resid = 1000000000; flags = MSG_DONTWAIT; m = NULL; error = soreceive(s->l2so, NULL, &uio, &m, (struct mbuf **) NULL, &flags); if (error != 0) { if (error == EWOULDBLOCK) return (0); /* XXX can happen? */ NG_BTSOCKET_RFCOMM_ERR( "%s: Could not receive data from L2CAP socket, error=%d\n", __func__, error); return (error); } more = (m->m_nextpkt != NULL); m->m_nextpkt = NULL; ng_btsocket_rfcomm_receive_frame(s, m); } return (0); } /* ng_btsocket_rfcomm_session_receive */ /* * Send data on RFCOMM session * XXX FIXME locking for "l2so"? */ static int ng_btsocket_rfcomm_session_send(ng_btsocket_rfcomm_session_p s) { struct mbuf *m = NULL; int error; mtx_assert(&s->session_mtx, MA_OWNED); /* Send as much as we can from the session queue */ while (sowriteable(s->l2so)) { /* Check if socket still OK */ if ((error = s->l2so->so_error) != 0) { s->l2so->so_error = 0; NG_BTSOCKET_RFCOMM_ERR( "%s: Detected error=%d on L2CAP socket, state=%d, flags=%#x\n", __func__, error, s->state, s->flags); return (error); } NG_BT_MBUFQ_DEQUEUE(&s->outq, m); if (m == NULL) return (0); /* we are done */ /* Call send function on the L2CAP socket */ error = (*s->l2so->so_proto->pr_usrreqs->pru_send)(s->l2so, 0, m, NULL, NULL, curthread /* XXX */); if (error != 0) { NG_BTSOCKET_RFCOMM_ERR( "%s: Could not send data to L2CAP socket, error=%d\n", __func__, error); return (error); } } return (0); } /* ng_btsocket_rfcomm_session_send */ /* * Close and disconnect all DLCs for the given session. Caller must hold * s->sesson_mtx. Will wakeup session. */ static void ng_btsocket_rfcomm_session_clean(ng_btsocket_rfcomm_session_p s) { ng_btsocket_rfcomm_pcb_p pcb = NULL, pcb_next = NULL; int error; mtx_assert(&s->session_mtx, MA_OWNED); /* * Note: cannot use LIST_FOREACH because ng_btsocket_rfcomm_pcb_kill * will unlink DLC from the session */ for (pcb = LIST_FIRST(&s->dlcs); pcb != NULL; ) { mtx_lock(&pcb->pcb_mtx); pcb_next = LIST_NEXT(pcb, session_next); NG_BTSOCKET_RFCOMM_INFO( "%s: Disconnecting dlci=%d, state=%d, flags=%#x\n", __func__, pcb->dlci, pcb->state, pcb->flags); if (pcb->state == NG_BTSOCKET_RFCOMM_DLC_CONNECTED) error = ECONNRESET; else error = ECONNREFUSED; ng_btsocket_rfcomm_pcb_kill(pcb, error); mtx_unlock(&pcb->pcb_mtx); pcb = pcb_next; } } /* ng_btsocket_rfcomm_session_clean */ /* * Process all DLCs on the session. Caller MUST hold s->session_mtx. */ static void ng_btsocket_rfcomm_session_process_pcb(ng_btsocket_rfcomm_session_p s) { ng_btsocket_rfcomm_pcb_p pcb = NULL, pcb_next = NULL; int error; mtx_assert(&s->session_mtx, MA_OWNED); /* * Note: cannot use LIST_FOREACH because ng_btsocket_rfcomm_pcb_kill * will unlink DLC from the session */ for (pcb = LIST_FIRST(&s->dlcs); pcb != NULL; ) { mtx_lock(&pcb->pcb_mtx); pcb_next = LIST_NEXT(pcb, session_next); switch (pcb->state) { /* * If DLC in W4_CONNECT state then we should check for both * timeout and detach. */ case NG_BTSOCKET_RFCOMM_DLC_W4_CONNECT: if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_DETACHED) ng_btsocket_rfcomm_pcb_kill(pcb, 0); else if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_TIMEDOUT) ng_btsocket_rfcomm_pcb_kill(pcb, ETIMEDOUT); break; /* * If DLC in CONFIGURING or CONNECTING state then we only * should check for timeout. If detach() was called then * DLC will be moved into DISCONNECTING state. */ case NG_BTSOCKET_RFCOMM_DLC_CONFIGURING: case NG_BTSOCKET_RFCOMM_DLC_CONNECTING: if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_TIMEDOUT) ng_btsocket_rfcomm_pcb_kill(pcb, ETIMEDOUT); break; /* * If DLC in CONNECTED state then we need to send data (if any) * from the socket's send queue. Note that we will send data * from either all sockets or none. This may overload session's * outgoing queue (but we do not check for that). * * XXX FIXME need scheduler for RFCOMM sockets */ case NG_BTSOCKET_RFCOMM_DLC_CONNECTED: error = ng_btsocket_rfcomm_pcb_send(pcb, ALOT); if (error != 0) ng_btsocket_rfcomm_pcb_kill(pcb, error); break; /* * If DLC in DISCONNECTING state then we must send DISC frame. * Note that if DLC has timeout set then we do not need to * resend DISC frame. * * XXX FIXME need to drain all data from the socket's queue * if LINGER option was set */ case NG_BTSOCKET_RFCOMM_DLC_DISCONNECTING: if (!(pcb->flags & NG_BTSOCKET_RFCOMM_DLC_TIMO)) { error = ng_btsocket_rfcomm_send_command( pcb->session, RFCOMM_FRAME_DISC, pcb->dlci); if (error == 0) ng_btsocket_rfcomm_timeout(pcb); else ng_btsocket_rfcomm_pcb_kill(pcb, error); } else if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_TIMEDOUT) ng_btsocket_rfcomm_pcb_kill(pcb, ETIMEDOUT); break; /* case NG_BTSOCKET_RFCOMM_DLC_CLOSED: */ default: panic("%s: Invalid DLC state=%d, flags=%#x\n", __func__, pcb->state, pcb->flags); break; } mtx_unlock(&pcb->pcb_mtx); pcb = pcb_next; } } /* ng_btsocket_rfcomm_session_process_pcb */ /* * Find RFCOMM session between "src" and "dst". * Caller MUST hold ng_btsocket_rfcomm_sessions_mtx. */ static ng_btsocket_rfcomm_session_p ng_btsocket_rfcomm_session_by_addr(bdaddr_p src, bdaddr_p dst) { ng_btsocket_rfcomm_session_p s = NULL; ng_btsocket_l2cap_pcb_p l2pcb = NULL; int any_src; mtx_assert(&ng_btsocket_rfcomm_sessions_mtx, MA_OWNED); any_src = (bcmp(src, NG_HCI_BDADDR_ANY, sizeof(*src)) == 0); LIST_FOREACH(s, &ng_btsocket_rfcomm_sessions, next) { l2pcb = so2l2cap_pcb(s->l2so); if ((any_src || bcmp(&l2pcb->src, src, sizeof(*src)) == 0) && bcmp(&l2pcb->dst, dst, sizeof(*dst)) == 0) break; } return (s); } /* ng_btsocket_rfcomm_session_by_addr */ /***************************************************************************** ***************************************************************************** ** RFCOMM ***************************************************************************** *****************************************************************************/ /* * Process incoming RFCOMM frame. Caller must hold s->session_mtx. * XXX FIXME check frame length */ static int ng_btsocket_rfcomm_receive_frame(ng_btsocket_rfcomm_session_p s, struct mbuf *m0) { struct rfcomm_frame_hdr *hdr = NULL; struct mbuf *m = NULL; u_int16_t length; u_int8_t dlci, type; int error = 0; mtx_assert(&s->session_mtx, MA_OWNED); /* Pullup as much as we can into first mbuf (for direct access) */ length = min(m0->m_pkthdr.len, MHLEN); if (m0->m_len < length) { if ((m0 = m_pullup(m0, length)) == NULL) { NG_BTSOCKET_RFCOMM_ALERT( "%s: m_pullup(%d) failed\n", __func__, length); return (ENOBUFS); } } hdr = mtod(m0, struct rfcomm_frame_hdr *); dlci = RFCOMM_DLCI(hdr->address); type = RFCOMM_TYPE(hdr->control); /* Test EA bit in length. If not set then we have 2 bytes of length */ if (!RFCOMM_EA(hdr->length)) { bcopy(&hdr->length, &length, sizeof(length)); length = le16toh(length) >> 1; m_adj(m0, sizeof(*hdr) + 1); } else { length = hdr->length >> 1; m_adj(m0, sizeof(*hdr)); } NG_BTSOCKET_RFCOMM_INFO( "%s: Got frame type=%#x, dlci=%d, length=%d, cr=%d, pf=%d, len=%d\n", __func__, type, dlci, length, RFCOMM_CR(hdr->address), RFCOMM_PF(hdr->control), m0->m_pkthdr.len); /* * Get FCS (the last byte in the frame) * XXX this will not work if mbuf chain ends with empty mbuf. * XXX let's hope it never happens :) */ for (m = m0; m->m_next != NULL; m = m->m_next) ; if (m->m_len <= 0) panic("%s: Empty mbuf at the end of the chain, len=%d\n", __func__, m->m_len); /* * Check FCS. We only need to calculate FCS on first 2 or 3 bytes * and already m_pullup'ed mbuf chain, so it should be safe. */ if (ng_btsocket_rfcomm_check_fcs((u_int8_t *) hdr, type, m->m_data[m->m_len - 1])) { NG_BTSOCKET_RFCOMM_ERR( "%s: Invalid RFCOMM packet. Bad checksum\n", __func__); NG_FREE_M(m0); return (EINVAL); } m_adj(m0, -1); /* Trim FCS byte */ /* * Process RFCOMM frame. * * From TS 07.10 spec * * "... In the case where a SABM or DISC command with the P bit set * to 0 is received then the received frame shall be discarded..." * * "... If a unsolicited DM response is received then the frame shall * be processed irrespective of the P/F setting... " * * "... The station may transmit response frames with the F bit set * to 0 at any opportunity on an asynchronous basis. However, in the * case where a UA response is received with the F bit set to 0 then * the received frame shall be discarded..." * * From Bluetooth spec * * "... When credit based flow control is being used, the meaning of * the P/F bit in the control field of the RFCOMM header is redefined * for UIH frames..." */ switch (type) { case RFCOMM_FRAME_SABM: if (RFCOMM_PF(hdr->control)) error = ng_btsocket_rfcomm_receive_sabm(s, dlci); break; case RFCOMM_FRAME_DISC: if (RFCOMM_PF(hdr->control)) error = ng_btsocket_rfcomm_receive_disc(s, dlci); break; case RFCOMM_FRAME_UA: if (RFCOMM_PF(hdr->control)) error = ng_btsocket_rfcomm_receive_ua(s, dlci); break; case RFCOMM_FRAME_DM: error = ng_btsocket_rfcomm_receive_dm(s, dlci); break; case RFCOMM_FRAME_UIH: if (dlci == 0) error = ng_btsocket_rfcomm_receive_mcc(s, m0); else error = ng_btsocket_rfcomm_receive_uih(s, dlci, RFCOMM_PF(hdr->control), m0); return (error); /* NOT REACHED */ default: NG_BTSOCKET_RFCOMM_ERR( "%s: Invalid RFCOMM packet. Unknown type=%#x\n", __func__, type); error = EINVAL; break; } NG_FREE_M(m0); return (error); } /* ng_btsocket_rfcomm_receive_frame */ /* * Process RFCOMM SABM frame */ static int ng_btsocket_rfcomm_receive_sabm(ng_btsocket_rfcomm_session_p s, int dlci) { ng_btsocket_rfcomm_pcb_p pcb = NULL; int error = 0; mtx_assert(&s->session_mtx, MA_OWNED); NG_BTSOCKET_RFCOMM_INFO( "%s: Got SABM, session state=%d, flags=%#x, mtu=%d, dlci=%d\n", __func__, s->state, s->flags, s->mtu, dlci); /* DLCI == 0 means open multiplexor channel */ if (dlci == 0) { switch (s->state) { case NG_BTSOCKET_RFCOMM_SESSION_CONNECTED: case NG_BTSOCKET_RFCOMM_SESSION_OPEN: error = ng_btsocket_rfcomm_send_command(s, RFCOMM_FRAME_UA, dlci); if (error == 0) { s->state = NG_BTSOCKET_RFCOMM_SESSION_OPEN; ng_btsocket_rfcomm_connect_cfm(s); } else { s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; ng_btsocket_rfcomm_session_clean(s); } break; default: NG_BTSOCKET_RFCOMM_WARN( "%s: Got SABM for session in invalid state state=%d, flags=%#x\n", __func__, s->state, s->flags); error = EINVAL; break; } return (error); } /* Make sure multiplexor channel is open */ if (s->state != NG_BTSOCKET_RFCOMM_SESSION_OPEN) { NG_BTSOCKET_RFCOMM_ERR( "%s: Got SABM for dlci=%d with mulitplexor channel closed, state=%d, " \ "flags=%#x\n", __func__, dlci, s->state, s->flags); return (EINVAL); } /* * Check if we have this DLCI. This might happen when remote * peer uses PN command before actual open (SABM) happens. */ pcb = ng_btsocket_rfcomm_pcb_by_dlci(s, dlci); if (pcb != NULL) { mtx_lock(&pcb->pcb_mtx); if (pcb->state != NG_BTSOCKET_RFCOMM_DLC_CONNECTING) { NG_BTSOCKET_RFCOMM_ERR( "%s: Got SABM for dlci=%d in invalid state=%d, flags=%#x\n", __func__, dlci, pcb->state, pcb->flags); mtx_unlock(&pcb->pcb_mtx); return (ENOENT); } ng_btsocket_rfcomm_untimeout(pcb); error = ng_btsocket_rfcomm_send_command(s,RFCOMM_FRAME_UA,dlci); if (error == 0) error = ng_btsocket_rfcomm_send_msc(pcb); if (error == 0) { pcb->state = NG_BTSOCKET_RFCOMM_DLC_CONNECTED; soisconnected(pcb->so); } else ng_btsocket_rfcomm_pcb_kill(pcb, error); mtx_unlock(&pcb->pcb_mtx); return (error); } /* * We do not have requested DLCI, so it must be an incoming connection * with default parameters. Try to accept it. */ pcb = ng_btsocket_rfcomm_connect_ind(s, RFCOMM_SRVCHANNEL(dlci)); if (pcb != NULL) { mtx_lock(&pcb->pcb_mtx); pcb->dlci = dlci; error = ng_btsocket_rfcomm_send_command(s,RFCOMM_FRAME_UA,dlci); if (error == 0) error = ng_btsocket_rfcomm_send_msc(pcb); if (error == 0) { pcb->state = NG_BTSOCKET_RFCOMM_DLC_CONNECTED; soisconnected(pcb->so); } else ng_btsocket_rfcomm_pcb_kill(pcb, error); mtx_unlock(&pcb->pcb_mtx); } else /* Nobody is listen()ing on the requested DLCI */ error = ng_btsocket_rfcomm_send_command(s,RFCOMM_FRAME_DM,dlci); return (error); } /* ng_btsocket_rfcomm_receive_sabm */ /* * Process RFCOMM DISC frame */ static int ng_btsocket_rfcomm_receive_disc(ng_btsocket_rfcomm_session_p s, int dlci) { ng_btsocket_rfcomm_pcb_p pcb = NULL; int error = 0; mtx_assert(&s->session_mtx, MA_OWNED); NG_BTSOCKET_RFCOMM_INFO( "%s: Got DISC, session state=%d, flags=%#x, mtu=%d, dlci=%d\n", __func__, s->state, s->flags, s->mtu, dlci); /* DLCI == 0 means close multiplexor channel */ if (dlci == 0) { /* XXX FIXME assume that remote side will close the socket */ error = ng_btsocket_rfcomm_send_command(s, RFCOMM_FRAME_UA, 0); if (error == 0) { if (s->state == NG_BTSOCKET_RFCOMM_SESSION_DISCONNECTING) s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; /* XXX */ else s->state = NG_BTSOCKET_RFCOMM_SESSION_DISCONNECTING; } else s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; /* XXX */ ng_btsocket_rfcomm_session_clean(s); } else { pcb = ng_btsocket_rfcomm_pcb_by_dlci(s, dlci); if (pcb != NULL) { int err; mtx_lock(&pcb->pcb_mtx); NG_BTSOCKET_RFCOMM_INFO( "%s: Got DISC for dlci=%d, state=%d, flags=%#x\n", __func__, dlci, pcb->state, pcb->flags); error = ng_btsocket_rfcomm_send_command(s, RFCOMM_FRAME_UA, dlci); if (pcb->state == NG_BTSOCKET_RFCOMM_DLC_CONNECTED) err = 0; else err = ECONNREFUSED; ng_btsocket_rfcomm_pcb_kill(pcb, err); mtx_unlock(&pcb->pcb_mtx); } else { NG_BTSOCKET_RFCOMM_WARN( "%s: Got DISC for non-existing dlci=%d\n", __func__, dlci); error = ng_btsocket_rfcomm_send_command(s, RFCOMM_FRAME_DM, dlci); } } return (error); } /* ng_btsocket_rfcomm_receive_disc */ /* * Process RFCOMM UA frame */ static int ng_btsocket_rfcomm_receive_ua(ng_btsocket_rfcomm_session_p s, int dlci) { ng_btsocket_rfcomm_pcb_p pcb = NULL; int error = 0; mtx_assert(&s->session_mtx, MA_OWNED); NG_BTSOCKET_RFCOMM_INFO( "%s: Got UA, session state=%d, flags=%#x, mtu=%d, dlci=%d\n", __func__, s->state, s->flags, s->mtu, dlci); /* dlci == 0 means multiplexor channel */ if (dlci == 0) { switch (s->state) { case NG_BTSOCKET_RFCOMM_SESSION_CONNECTED: s->state = NG_BTSOCKET_RFCOMM_SESSION_OPEN; ng_btsocket_rfcomm_connect_cfm(s); break; case NG_BTSOCKET_RFCOMM_SESSION_DISCONNECTING: s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; ng_btsocket_rfcomm_session_clean(s); break; default: NG_BTSOCKET_RFCOMM_WARN( "%s: Got UA for session in invalid state=%d(%d), flags=%#x, mtu=%d\n", __func__, s->state, INITIATOR(s), s->flags, s->mtu); error = ENOENT; break; } return (error); } /* Check if we have this DLCI */ pcb = ng_btsocket_rfcomm_pcb_by_dlci(s, dlci); if (pcb != NULL) { mtx_lock(&pcb->pcb_mtx); NG_BTSOCKET_RFCOMM_INFO( "%s: Got UA for dlci=%d, state=%d, flags=%#x\n", __func__, dlci, pcb->state, pcb->flags); switch (pcb->state) { case NG_BTSOCKET_RFCOMM_DLC_CONNECTING: ng_btsocket_rfcomm_untimeout(pcb); error = ng_btsocket_rfcomm_send_msc(pcb); if (error == 0) { pcb->state = NG_BTSOCKET_RFCOMM_DLC_CONNECTED; soisconnected(pcb->so); } break; case NG_BTSOCKET_RFCOMM_DLC_DISCONNECTING: ng_btsocket_rfcomm_pcb_kill(pcb, 0); break; default: NG_BTSOCKET_RFCOMM_WARN( "%s: Got UA for dlci=%d in invalid state=%d, flags=%#x\n", __func__, dlci, pcb->state, pcb->flags); error = ENOENT; break; } mtx_unlock(&pcb->pcb_mtx); } else { NG_BTSOCKET_RFCOMM_WARN( "%s: Got UA for non-existing dlci=%d\n", __func__, dlci); error = ng_btsocket_rfcomm_send_command(s,RFCOMM_FRAME_DM,dlci); } return (error); } /* ng_btsocket_rfcomm_receive_ua */ /* * Process RFCOMM DM frame */ static int ng_btsocket_rfcomm_receive_dm(ng_btsocket_rfcomm_session_p s, int dlci) { ng_btsocket_rfcomm_pcb_p pcb = NULL; int error; mtx_assert(&s->session_mtx, MA_OWNED); NG_BTSOCKET_RFCOMM_INFO( "%s: Got DM, session state=%d, flags=%#x, mtu=%d, dlci=%d\n", __func__, s->state, s->flags, s->mtu, dlci); /* DLCI == 0 means multiplexor channel */ if (dlci == 0) { /* Disconnect all dlc's on the session */ s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; ng_btsocket_rfcomm_session_clean(s); } else { pcb = ng_btsocket_rfcomm_pcb_by_dlci(s, dlci); if (pcb != NULL) { mtx_lock(&pcb->pcb_mtx); NG_BTSOCKET_RFCOMM_INFO( "%s: Got DM for dlci=%d, state=%d, flags=%#x\n", __func__, dlci, pcb->state, pcb->flags); if (pcb->state == NG_BTSOCKET_RFCOMM_DLC_CONNECTED) error = ECONNRESET; else error = ECONNREFUSED; ng_btsocket_rfcomm_pcb_kill(pcb, error); mtx_unlock(&pcb->pcb_mtx); } else NG_BTSOCKET_RFCOMM_WARN( "%s: Got DM for non-existing dlci=%d\n", __func__, dlci); } return (0); } /* ng_btsocket_rfcomm_receive_dm */ /* * Process RFCOMM UIH frame (data) */ static int ng_btsocket_rfcomm_receive_uih(ng_btsocket_rfcomm_session_p s, int dlci, int pf, struct mbuf *m0) { ng_btsocket_rfcomm_pcb_p pcb = NULL; int error = 0; mtx_assert(&s->session_mtx, MA_OWNED); NG_BTSOCKET_RFCOMM_INFO( "%s: Got UIH, session state=%d, flags=%#x, mtu=%d, dlci=%d, pf=%d, len=%d\n", __func__, s->state, s->flags, s->mtu, dlci, pf, m0->m_pkthdr.len); /* XXX should we do it here? Check for session flow control */ if (s->flags & NG_BTSOCKET_RFCOMM_SESSION_LFC) { NG_BTSOCKET_RFCOMM_WARN( "%s: Got UIH with session flow control asserted, state=%d, flags=%#x\n", __func__, s->state, s->flags); goto drop; } /* Check if we have this dlci */ pcb = ng_btsocket_rfcomm_pcb_by_dlci(s, dlci); if (pcb == NULL) { NG_BTSOCKET_RFCOMM_WARN( "%s: Got UIH for non-existing dlci=%d\n", __func__, dlci); error = ng_btsocket_rfcomm_send_command(s,RFCOMM_FRAME_DM,dlci); goto drop; } mtx_lock(&pcb->pcb_mtx); /* Check dlci state */ if (pcb->state != NG_BTSOCKET_RFCOMM_DLC_CONNECTED) { NG_BTSOCKET_RFCOMM_WARN( "%s: Got UIH for dlci=%d in invalid state=%d, flags=%#x\n", __func__, dlci, pcb->state, pcb->flags); error = EINVAL; goto drop1; } /* Check dlci flow control */ if (((pcb->flags & NG_BTSOCKET_RFCOMM_DLC_CFC) && pcb->rx_cred <= 0) || (pcb->lmodem & RFCOMM_MODEM_FC)) { NG_BTSOCKET_RFCOMM_ERR( "%s: Got UIH for dlci=%d with asserted flow control, state=%d, " \ "flags=%#x, rx_cred=%d, lmodem=%#x\n", __func__, dlci, pcb->state, pcb->flags, pcb->rx_cred, pcb->lmodem); goto drop1; } /* Did we get any credits? */ if ((pcb->flags & NG_BTSOCKET_RFCOMM_DLC_CFC) && pf) { NG_BTSOCKET_RFCOMM_INFO( "%s: Got %d more credits for dlci=%d, state=%d, flags=%#x, " \ "rx_cred=%d, tx_cred=%d\n", __func__, *mtod(m0, u_int8_t *), dlci, pcb->state, pcb->flags, pcb->rx_cred, pcb->tx_cred); pcb->tx_cred += *mtod(m0, u_int8_t *); m_adj(m0, 1); /* Send more from the DLC. XXX check for errors? */ ng_btsocket_rfcomm_pcb_send(pcb, ALOT); } /* OK the of the rest of the mbuf is the data */ if (m0->m_pkthdr.len > 0) { /* If we are using credit flow control decrease rx_cred here */ if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_CFC) { /* Give remote peer more credits (if needed) */ if (-- pcb->rx_cred <= RFCOMM_MAX_CREDITS / 2) ng_btsocket_rfcomm_send_credits(pcb); else NG_BTSOCKET_RFCOMM_INFO( "%s: Remote side still has credits, dlci=%d, state=%d, flags=%#x, " \ "rx_cred=%d, tx_cred=%d\n", __func__, dlci, pcb->state, pcb->flags, pcb->rx_cred, pcb->tx_cred); } /* Check packet against mtu on dlci */ if (m0->m_pkthdr.len > pcb->mtu) { NG_BTSOCKET_RFCOMM_ERR( "%s: Got oversized UIH for dlci=%d, state=%d, flags=%#x, mtu=%d, len=%d\n", __func__, dlci, pcb->state, pcb->flags, pcb->mtu, m0->m_pkthdr.len); error = EMSGSIZE; } else if (m0->m_pkthdr.len > sbspace(&pcb->so->so_rcv)) { /* * This is really bad. Receive queue on socket does * not have enough space for the packet. We do not * have any other choice but drop the packet. */ NG_BTSOCKET_RFCOMM_ERR( "%s: Not enough space in socket receive queue. Dropping UIH for dlci=%d, " \ "state=%d, flags=%#x, len=%d, space=%ld\n", __func__, dlci, pcb->state, pcb->flags, m0->m_pkthdr.len, sbspace(&pcb->so->so_rcv)); error = ENOBUFS; } else { /* Append packet to the socket receive queue */ sbappend(&pcb->so->so_rcv, m0, 0); m0 = NULL; sorwakeup(pcb->so); } } drop1: mtx_unlock(&pcb->pcb_mtx); drop: NG_FREE_M(m0); /* checks for != NULL */ return (error); } /* ng_btsocket_rfcomm_receive_uih */ /* * Process RFCOMM MCC command (Multiplexor) * * From TS 07.10 spec * * "5.4.3.1 Information Data * * ...The frames (UIH) sent by the initiating station have the C/R bit set * to 1 and those sent by the responding station have the C/R bit set to 0..." * * "5.4.6.2 Operating procedures * * Messages always exist in pairs; a command message and a corresponding * response message. If the C/R bit is set to 1 the message is a command, * if it is set to 0 the message is a response... * * ... * * NOTE: Notice that when UIH frames are used to convey information on DLCI 0 * there are at least two different fields that contain a C/R bit, and the * bits are set of different form. The C/R bit in the Type field shall be set * as it is stated above, while the C/R bit in the Address field (see subclause * 5.2.1.2) shall be set as it is described in subclause 5.4.3.1." */ static int ng_btsocket_rfcomm_receive_mcc(ng_btsocket_rfcomm_session_p s, struct mbuf *m0) { struct rfcomm_mcc_hdr *hdr = NULL; u_int8_t cr, type, length; mtx_assert(&s->session_mtx, MA_OWNED); /* * We can access data directly in the first mbuf, because we have * m_pullup()'ed mbuf chain in ng_btsocket_rfcomm_receive_frame(). * All MCC commands should fit into single mbuf (except probably TEST). */ hdr = mtod(m0, struct rfcomm_mcc_hdr *); cr = RFCOMM_CR(hdr->type); type = RFCOMM_MCC_TYPE(hdr->type); length = RFCOMM_MCC_LENGTH(hdr->length); /* Check MCC frame length */ if (sizeof(*hdr) + length != m0->m_pkthdr.len) { NG_BTSOCKET_RFCOMM_ERR( "%s: Invalid MCC frame length=%d, len=%d\n", __func__, length, m0->m_pkthdr.len); NG_FREE_M(m0); return (EMSGSIZE); } switch (type) { case RFCOMM_MCC_TEST: return (ng_btsocket_rfcomm_receive_test(s, m0)); /* NOT REACHED */ case RFCOMM_MCC_FCON: case RFCOMM_MCC_FCOFF: return (ng_btsocket_rfcomm_receive_fc(s, m0)); /* NOT REACHED */ case RFCOMM_MCC_MSC: return (ng_btsocket_rfcomm_receive_msc(s, m0)); /* NOT REACHED */ case RFCOMM_MCC_RPN: return (ng_btsocket_rfcomm_receive_rpn(s, m0)); /* NOT REACHED */ case RFCOMM_MCC_RLS: return (ng_btsocket_rfcomm_receive_rls(s, m0)); /* NOT REACHED */ case RFCOMM_MCC_PN: return (ng_btsocket_rfcomm_receive_pn(s, m0)); /* NOT REACHED */ case RFCOMM_MCC_NSC: NG_BTSOCKET_RFCOMM_ERR( "%s: Got MCC NSC, type=%#x, cr=%d, length=%d, session state=%d, flags=%#x, " \ "mtu=%d, len=%d\n", __func__, RFCOMM_MCC_TYPE(*((u_int8_t *)(hdr + 1))), cr, length, s->state, s->flags, s->mtu, m0->m_pkthdr.len); NG_FREE_M(m0); break; default: NG_BTSOCKET_RFCOMM_ERR( "%s: Got unknown MCC, type=%#x, cr=%d, length=%d, session state=%d, " \ "flags=%#x, mtu=%d, len=%d\n", __func__, type, cr, length, s->state, s->flags, s->mtu, m0->m_pkthdr.len); /* Reuse mbuf to send NSC */ hdr = mtod(m0, struct rfcomm_mcc_hdr *); m0->m_pkthdr.len = m0->m_len = sizeof(*hdr); /* Create MCC NSC header */ hdr->type = RFCOMM_MKMCC_TYPE(0, RFCOMM_MCC_NSC); hdr->length = RFCOMM_MKLEN8(1); /* Put back MCC command type we did not like */ m0->m_data[m0->m_len] = RFCOMM_MKMCC_TYPE(cr, type); m0->m_pkthdr.len ++; m0->m_len ++; /* Send UIH frame */ return (ng_btsocket_rfcomm_send_uih(s, RFCOMM_MKADDRESS(INITIATOR(s), 0), 0, 0, m0)); /* NOT REACHED */ } return (0); } /* ng_btsocket_rfcomm_receive_mcc */ /* * Receive RFCOMM TEST MCC command */ static int ng_btsocket_rfcomm_receive_test(ng_btsocket_rfcomm_session_p s, struct mbuf *m0) { struct rfcomm_mcc_hdr *hdr = mtod(m0, struct rfcomm_mcc_hdr *); int error = 0; mtx_assert(&s->session_mtx, MA_OWNED); NG_BTSOCKET_RFCOMM_INFO( "%s: Got MCC TEST, cr=%d, length=%d, session state=%d, flags=%#x, mtu=%d, " \ "len=%d\n", __func__, RFCOMM_CR(hdr->type), RFCOMM_MCC_LENGTH(hdr->length), s->state, s->flags, s->mtu, m0->m_pkthdr.len); if (RFCOMM_CR(hdr->type)) { hdr->type = RFCOMM_MKMCC_TYPE(0, RFCOMM_MCC_TEST); error = ng_btsocket_rfcomm_send_uih(s, RFCOMM_MKADDRESS(INITIATOR(s), 0), 0, 0, m0); } else NG_FREE_M(m0); /* XXX ignore response */ return (error); } /* ng_btsocket_rfcomm_receive_test */ /* * Receive RFCOMM FCON/FCOFF MCC command */ static int ng_btsocket_rfcomm_receive_fc(ng_btsocket_rfcomm_session_p s, struct mbuf *m0) { struct rfcomm_mcc_hdr *hdr = mtod(m0, struct rfcomm_mcc_hdr *); u_int8_t type = RFCOMM_MCC_TYPE(hdr->type); int error = 0; mtx_assert(&s->session_mtx, MA_OWNED); /* * Turn ON/OFF aggregate flow on the entire session. When remote peer * asserted flow control no transmission shall occur except on dlci 0 * (control channel). */ NG_BTSOCKET_RFCOMM_INFO( "%s: Got MCC FC%s, cr=%d, length=%d, session state=%d, flags=%#x, mtu=%d, " \ "len=%d\n", __func__, (type == RFCOMM_MCC_FCON)? "ON" : "OFF", RFCOMM_CR(hdr->type), RFCOMM_MCC_LENGTH(hdr->length), s->state, s->flags, s->mtu, m0->m_pkthdr.len); if (RFCOMM_CR(hdr->type)) { if (type == RFCOMM_MCC_FCON) s->flags &= ~NG_BTSOCKET_RFCOMM_SESSION_RFC; else s->flags |= NG_BTSOCKET_RFCOMM_SESSION_RFC; hdr->type = RFCOMM_MKMCC_TYPE(0, type); error = ng_btsocket_rfcomm_send_uih(s, RFCOMM_MKADDRESS(INITIATOR(s), 0), 0, 0, m0); } else NG_FREE_M(m0); /* XXX ignore response */ return (error); } /* ng_btsocket_rfcomm_receive_fc */ /* * Receive RFCOMM MSC MCC command */ static int ng_btsocket_rfcomm_receive_msc(ng_btsocket_rfcomm_session_p s, struct mbuf *m0) { struct rfcomm_mcc_hdr *hdr = mtod(m0, struct rfcomm_mcc_hdr*); struct rfcomm_mcc_msc *msc = (struct rfcomm_mcc_msc *)(hdr+1); ng_btsocket_rfcomm_pcb_t *pcb = NULL; int error = 0; mtx_assert(&s->session_mtx, MA_OWNED); NG_BTSOCKET_RFCOMM_INFO( "%s: Got MCC MSC, dlci=%d, cr=%d, length=%d, session state=%d, flags=%#x, " \ "mtu=%d, len=%d\n", __func__, RFCOMM_DLCI(msc->address), RFCOMM_CR(hdr->type), RFCOMM_MCC_LENGTH(hdr->length), s->state, s->flags, s->mtu, m0->m_pkthdr.len); if (RFCOMM_CR(hdr->type)) { pcb = ng_btsocket_rfcomm_pcb_by_dlci(s, RFCOMM_DLCI(msc->address)); if (pcb == NULL) { NG_BTSOCKET_RFCOMM_WARN( "%s: Got MSC command for non-existing dlci=%d\n", __func__, RFCOMM_DLCI(msc->address)); NG_FREE_M(m0); return (ENOENT); } mtx_lock(&pcb->pcb_mtx); if (pcb->state != NG_BTSOCKET_RFCOMM_DLC_CONNECTING && pcb->state != NG_BTSOCKET_RFCOMM_DLC_CONNECTED) { NG_BTSOCKET_RFCOMM_WARN( "%s: Got MSC on dlci=%d in invalid state=%d\n", __func__, RFCOMM_DLCI(msc->address), pcb->state); mtx_unlock(&pcb->pcb_mtx); NG_FREE_M(m0); return (EINVAL); } pcb->rmodem = msc->modem; /* Update remote port signals */ hdr->type = RFCOMM_MKMCC_TYPE(0, RFCOMM_MCC_MSC); error = ng_btsocket_rfcomm_send_uih(s, RFCOMM_MKADDRESS(INITIATOR(s), 0), 0, 0, m0); #if 0 /* YYY */ /* Send more data from DLC. XXX check for errors? */ if (!(pcb->rmodem & RFCOMM_MODEM_FC) && !(pcb->flags & NG_BTSOCKET_RFCOMM_DLC_CFC)) ng_btsocket_rfcomm_pcb_send(pcb, ALOT); #endif /* YYY */ mtx_unlock(&pcb->pcb_mtx); } else NG_FREE_M(m0); /* XXX ignore response */ return (error); } /* ng_btsocket_rfcomm_receive_msc */ /* * Receive RFCOMM RPN MCC command * XXX FIXME do we need htole16/le16toh for RPN param_mask? */ static int ng_btsocket_rfcomm_receive_rpn(ng_btsocket_rfcomm_session_p s, struct mbuf *m0) { struct rfcomm_mcc_hdr *hdr = mtod(m0, struct rfcomm_mcc_hdr *); struct rfcomm_mcc_rpn *rpn = (struct rfcomm_mcc_rpn *)(hdr + 1); int error = 0; u_int16_t param_mask; u_int8_t bit_rate, data_bits, stop_bits, parity, flow_control, xon_char, xoff_char; mtx_assert(&s->session_mtx, MA_OWNED); NG_BTSOCKET_RFCOMM_INFO( "%s: Got MCC RPN, dlci=%d, cr=%d, length=%d, session state=%d, flags=%#x, " \ "mtu=%d, len=%d\n", __func__, RFCOMM_DLCI(rpn->dlci), RFCOMM_CR(hdr->type), RFCOMM_MCC_LENGTH(hdr->length), s->state, s->flags, s->mtu, m0->m_pkthdr.len); if (RFCOMM_CR(hdr->type)) { param_mask = RFCOMM_RPN_PM_ALL; if (RFCOMM_MCC_LENGTH(hdr->length) == 1) { /* Request - return default setting */ bit_rate = RFCOMM_RPN_BR_115200; data_bits = RFCOMM_RPN_DATA_8; stop_bits = RFCOMM_RPN_STOP_1; parity = RFCOMM_RPN_PARITY_NONE; flow_control = RFCOMM_RPN_FLOW_NONE; xon_char = RFCOMM_RPN_XON_CHAR; xoff_char = RFCOMM_RPN_XOFF_CHAR; } else { /* * Ignore/accept bit_rate, 8 bits, 1 stop bit, no * parity, no flow control lines, default XON/XOFF * chars. */ bit_rate = rpn->bit_rate; rpn->param_mask = le16toh(rpn->param_mask); /* XXX */ data_bits = RFCOMM_RPN_DATA_BITS(rpn->line_settings); if (rpn->param_mask & RFCOMM_RPN_PM_DATA && data_bits != RFCOMM_RPN_DATA_8) { data_bits = RFCOMM_RPN_DATA_8; param_mask ^= RFCOMM_RPN_PM_DATA; } stop_bits = RFCOMM_RPN_STOP_BITS(rpn->line_settings); if (rpn->param_mask & RFCOMM_RPN_PM_STOP && stop_bits != RFCOMM_RPN_STOP_1) { stop_bits = RFCOMM_RPN_STOP_1; param_mask ^= RFCOMM_RPN_PM_STOP; } parity = RFCOMM_RPN_PARITY(rpn->line_settings); if (rpn->param_mask & RFCOMM_RPN_PM_PARITY && parity != RFCOMM_RPN_PARITY_NONE) { parity = RFCOMM_RPN_PARITY_NONE; param_mask ^= RFCOMM_RPN_PM_PARITY; } flow_control = rpn->flow_control; if (rpn->param_mask & RFCOMM_RPN_PM_FLOW && flow_control != RFCOMM_RPN_FLOW_NONE) { flow_control = RFCOMM_RPN_FLOW_NONE; param_mask ^= RFCOMM_RPN_PM_FLOW; } xon_char = rpn->xon_char; if (rpn->param_mask & RFCOMM_RPN_PM_XON && xon_char != RFCOMM_RPN_XON_CHAR) { xon_char = RFCOMM_RPN_XON_CHAR; param_mask ^= RFCOMM_RPN_PM_XON; } xoff_char = rpn->xoff_char; if (rpn->param_mask & RFCOMM_RPN_PM_XOFF && xoff_char != RFCOMM_RPN_XOFF_CHAR) { xoff_char = RFCOMM_RPN_XOFF_CHAR; param_mask ^= RFCOMM_RPN_PM_XOFF; } } rpn->bit_rate = bit_rate; rpn->line_settings = RFCOMM_MKRPN_LINE_SETTINGS(data_bits, stop_bits, parity); rpn->flow_control = flow_control; rpn->xon_char = xon_char; rpn->xoff_char = xoff_char; rpn->param_mask = htole16(param_mask); /* XXX */ m0->m_pkthdr.len = m0->m_len = sizeof(*hdr) + sizeof(*rpn); hdr->type = RFCOMM_MKMCC_TYPE(0, RFCOMM_MCC_RPN); error = ng_btsocket_rfcomm_send_uih(s, RFCOMM_MKADDRESS(INITIATOR(s), 0), 0, 0, m0); } else NG_FREE_M(m0); /* XXX ignore response */ return (error); } /* ng_btsocket_rfcomm_receive_rpn */ /* * Receive RFCOMM RLS MCC command */ static int ng_btsocket_rfcomm_receive_rls(ng_btsocket_rfcomm_session_p s, struct mbuf *m0) { struct rfcomm_mcc_hdr *hdr = mtod(m0, struct rfcomm_mcc_hdr *); struct rfcomm_mcc_rls *rls = (struct rfcomm_mcc_rls *)(hdr + 1); int error = 0; mtx_assert(&s->session_mtx, MA_OWNED); /* * XXX FIXME Do we have to do anything else here? Remote peer tries to * tell us something about DLCI. Just report what we have received and * return back received values as required by TS 07.10 spec. */ NG_BTSOCKET_RFCOMM_INFO( "%s: Got MCC RLS, dlci=%d, status=%#x, cr=%d, length=%d, session state=%d, " \ "flags=%#x, mtu=%d, len=%d\n", __func__, RFCOMM_DLCI(rls->address), rls->status, RFCOMM_CR(hdr->type), RFCOMM_MCC_LENGTH(hdr->length), s->state, s->flags, s->mtu, m0->m_pkthdr.len); if (RFCOMM_CR(hdr->type)) { if (rls->status & 0x1) NG_BTSOCKET_RFCOMM_ERR( "%s: Got RLS dlci=%d, error=%#x\n", __func__, RFCOMM_DLCI(rls->address), rls->status >> 1); hdr->type = RFCOMM_MKMCC_TYPE(0, RFCOMM_MCC_RLS); error = ng_btsocket_rfcomm_send_uih(s, RFCOMM_MKADDRESS(INITIATOR(s), 0), 0, 0, m0); } else NG_FREE_M(m0); /* XXX ignore responses */ return (error); } /* ng_btsocket_rfcomm_receive_rls */ /* * Receive RFCOMM PN MCC command */ static int ng_btsocket_rfcomm_receive_pn(ng_btsocket_rfcomm_session_p s, struct mbuf *m0) { struct rfcomm_mcc_hdr *hdr = mtod(m0, struct rfcomm_mcc_hdr*); struct rfcomm_mcc_pn *pn = (struct rfcomm_mcc_pn *)(hdr+1); ng_btsocket_rfcomm_pcb_t *pcb = NULL; int error = 0; mtx_assert(&s->session_mtx, MA_OWNED); NG_BTSOCKET_RFCOMM_INFO( "%s: Got MCC PN, dlci=%d, cr=%d, length=%d, flow_control=%#x, priority=%d, " \ "ack_timer=%d, mtu=%d, max_retrans=%d, credits=%d, session state=%d, " \ "flags=%#x, session mtu=%d, len=%d\n", __func__, pn->dlci, RFCOMM_CR(hdr->type), RFCOMM_MCC_LENGTH(hdr->length), pn->flow_control, pn->priority, pn->ack_timer, le16toh(pn->mtu), pn->max_retrans, pn->credits, s->state, s->flags, s->mtu, m0->m_pkthdr.len); if (pn->dlci == 0) { NG_BTSOCKET_RFCOMM_ERR("%s: Zero dlci in MCC PN\n", __func__); NG_FREE_M(m0); return (EINVAL); } /* Check if we have this dlci */ pcb = ng_btsocket_rfcomm_pcb_by_dlci(s, pn->dlci); if (pcb != NULL) { mtx_lock(&pcb->pcb_mtx); if (RFCOMM_CR(hdr->type)) { /* PN Request */ ng_btsocket_rfcomm_set_pn(pcb, 1, pn->flow_control, pn->credits, pn->mtu); if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_CFC) { pn->flow_control = 0xe0; pn->credits = RFCOMM_DEFAULT_CREDITS; } else { pn->flow_control = 0; pn->credits = 0; } hdr->type = RFCOMM_MKMCC_TYPE(0, RFCOMM_MCC_PN); error = ng_btsocket_rfcomm_send_uih(s, RFCOMM_MKADDRESS(INITIATOR(s), 0), 0, 0, m0); } else { /* PN Response - proceed with SABM. Timeout still set */ if (pcb->state == NG_BTSOCKET_RFCOMM_DLC_CONFIGURING) { ng_btsocket_rfcomm_set_pn(pcb, 0, pn->flow_control, pn->credits, pn->mtu); pcb->state = NG_BTSOCKET_RFCOMM_DLC_CONNECTING; error = ng_btsocket_rfcomm_send_command(s, RFCOMM_FRAME_SABM, pn->dlci); } else NG_BTSOCKET_RFCOMM_WARN( "%s: Got PN response for dlci=%d in invalid state=%d\n", __func__, pn->dlci, pcb->state); NG_FREE_M(m0); } mtx_unlock(&pcb->pcb_mtx); } else if (RFCOMM_CR(hdr->type)) { - /* PN request to non-existing dlci - incomming connection */ + /* PN request to non-existing dlci - incoming connection */ pcb = ng_btsocket_rfcomm_connect_ind(s, RFCOMM_SRVCHANNEL(pn->dlci)); if (pcb != NULL) { mtx_lock(&pcb->pcb_mtx); pcb->dlci = pn->dlci; ng_btsocket_rfcomm_set_pn(pcb, 1, pn->flow_control, pn->credits, pn->mtu); if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_CFC) { pn->flow_control = 0xe0; pn->credits = RFCOMM_DEFAULT_CREDITS; } else { pn->flow_control = 0; pn->credits = 0; } hdr->type = RFCOMM_MKMCC_TYPE(0, RFCOMM_MCC_PN); error = ng_btsocket_rfcomm_send_uih(s, RFCOMM_MKADDRESS(INITIATOR(s), 0), 0, 0, m0); if (error == 0) { ng_btsocket_rfcomm_timeout(pcb); pcb->state = NG_BTSOCKET_RFCOMM_DLC_CONNECTING; soisconnecting(pcb->so); } else ng_btsocket_rfcomm_pcb_kill(pcb, error); mtx_unlock(&pcb->pcb_mtx); } else { /* Nobody is listen()ing on this channel */ error = ng_btsocket_rfcomm_send_command(s, RFCOMM_FRAME_DM, pn->dlci); NG_FREE_M(m0); } } else NG_FREE_M(m0); /* XXX ignore response to non-existing dlci */ return (error); } /* ng_btsocket_rfcomm_receive_pn */ /* * Set PN parameters for dlci. Caller must hold pcb->pcb_mtx. * * From Bluetooth spec. * * "... The CL1 - CL4 field is completely redefined. (In TS07.10 this defines * the convergence layer to use, which is not applicable to RFCOMM. In RFCOMM, * in Bluetooth versions up to 1.0B, this field was forced to 0). * * In the PN request sent prior to a DLC establishment, this field must contain * the value 15 (0xF), indicating support of credit based flow control in the * sender. See Table 5.3 below. If the PN response contains any other value * than 14 (0xE) in this field, it is inferred that the peer RFCOMM entity is * not supporting the credit based flow control feature. (This is only possible * if the peer RFCOMM implementation is only conforming to Bluetooth version * 1.0B.) If a PN request is sent on an already open DLC, then this field must * contain the value zero; it is not possible to set initial credits more * than once per DLC activation. A responding implementation must set this * field in the PN response to 14 (0xE), if (and only if) the value in the PN * request was 15..." */ static void ng_btsocket_rfcomm_set_pn(ng_btsocket_rfcomm_pcb_p pcb, u_int8_t cr, u_int8_t flow_control, u_int8_t credits, u_int16_t mtu) { mtx_assert(&pcb->pcb_mtx, MA_OWNED); pcb->mtu = le16toh(mtu); if (cr) { if (flow_control == 0xf0) { pcb->flags |= NG_BTSOCKET_RFCOMM_DLC_CFC; pcb->tx_cred = credits; } else { pcb->flags &= ~NG_BTSOCKET_RFCOMM_DLC_CFC; pcb->tx_cred = 0; } } else { if (flow_control == 0xe0) { pcb->flags |= NG_BTSOCKET_RFCOMM_DLC_CFC; pcb->tx_cred = credits; } else { pcb->flags &= ~NG_BTSOCKET_RFCOMM_DLC_CFC; pcb->tx_cred = 0; } } NG_BTSOCKET_RFCOMM_INFO( "%s: cr=%d, dlci=%d, state=%d, flags=%#x, mtu=%d, rx_cred=%d, tx_cred=%d\n", __func__, cr, pcb->dlci, pcb->state, pcb->flags, pcb->mtu, pcb->rx_cred, pcb->tx_cred); } /* ng_btsocket_rfcomm_set_pn */ /* * Send RFCOMM SABM/DISC/UA/DM frames. Caller must hold s->session_mtx */ static int ng_btsocket_rfcomm_send_command(ng_btsocket_rfcomm_session_p s, u_int8_t type, u_int8_t dlci) { struct rfcomm_cmd_hdr *hdr = NULL; struct mbuf *m = NULL; int cr; mtx_assert(&s->session_mtx, MA_OWNED); NG_BTSOCKET_RFCOMM_INFO( "%s: Sending command type %#x, session state=%d, flags=%#x, mtu=%d, dlci=%d\n", __func__, type, s->state, s->flags, s->mtu, dlci); switch (type) { case RFCOMM_FRAME_SABM: case RFCOMM_FRAME_DISC: cr = INITIATOR(s); break; case RFCOMM_FRAME_UA: case RFCOMM_FRAME_DM: cr = !INITIATOR(s); break; default: panic("%s: Invalid frame type=%#x\n", __func__, type); return (EINVAL); /* NOT REACHED */ } MGETHDR(m, M_NOWAIT, MT_DATA); if (m == NULL) return (ENOBUFS); m->m_pkthdr.len = m->m_len = sizeof(*hdr); hdr = mtod(m, struct rfcomm_cmd_hdr *); hdr->address = RFCOMM_MKADDRESS(cr, dlci); hdr->control = RFCOMM_MKCONTROL(type, 1); hdr->length = RFCOMM_MKLEN8(0); hdr->fcs = ng_btsocket_rfcomm_fcs3((u_int8_t *) hdr); NG_BT_MBUFQ_ENQUEUE(&s->outq, m); return (0); } /* ng_btsocket_rfcomm_send_command */ /* * Send RFCOMM UIH frame. Caller must hold s->session_mtx */ static int ng_btsocket_rfcomm_send_uih(ng_btsocket_rfcomm_session_p s, u_int8_t address, u_int8_t pf, u_int8_t credits, struct mbuf *data) { struct rfcomm_frame_hdr *hdr = NULL; struct mbuf *m = NULL, *mcrc = NULL; u_int16_t length; mtx_assert(&s->session_mtx, MA_OWNED); MGETHDR(m, M_NOWAIT, MT_DATA); if (m == NULL) { NG_FREE_M(data); return (ENOBUFS); } m->m_pkthdr.len = m->m_len = sizeof(*hdr); MGET(mcrc, M_NOWAIT, MT_DATA); if (mcrc == NULL) { NG_FREE_M(data); return (ENOBUFS); } mcrc->m_len = 1; /* Fill UIH frame header */ hdr = mtod(m, struct rfcomm_frame_hdr *); hdr->address = address; hdr->control = RFCOMM_MKCONTROL(RFCOMM_FRAME_UIH, pf); /* Calculate FCS */ mcrc->m_data[0] = ng_btsocket_rfcomm_fcs2((u_int8_t *) hdr); /* Put length back */ length = (data != NULL)? data->m_pkthdr.len : 0; if (length > 127) { u_int16_t l = htole16(RFCOMM_MKLEN16(length)); bcopy(&l, &hdr->length, sizeof(l)); m->m_pkthdr.len ++; m->m_len ++; } else hdr->length = RFCOMM_MKLEN8(length); if (pf) { m->m_data[m->m_len] = credits; m->m_pkthdr.len ++; m->m_len ++; } /* Add payload */ if (data != NULL) { m_cat(m, data); m->m_pkthdr.len += length; } /* Put FCS back */ m_cat(m, mcrc); m->m_pkthdr.len ++; NG_BTSOCKET_RFCOMM_INFO( "%s: Sending UIH state=%d, flags=%#x, address=%d, length=%d, pf=%d, " \ "credits=%d, len=%d\n", __func__, s->state, s->flags, address, length, pf, credits, m->m_pkthdr.len); NG_BT_MBUFQ_ENQUEUE(&s->outq, m); return (0); } /* ng_btsocket_rfcomm_send_uih */ /* * Send MSC request. Caller must hold pcb->pcb_mtx and pcb->session->session_mtx */ static int ng_btsocket_rfcomm_send_msc(ng_btsocket_rfcomm_pcb_p pcb) { struct mbuf *m = NULL; struct rfcomm_mcc_hdr *hdr = NULL; struct rfcomm_mcc_msc *msc = NULL; mtx_assert(&pcb->session->session_mtx, MA_OWNED); mtx_assert(&pcb->pcb_mtx, MA_OWNED); MGETHDR(m, M_NOWAIT, MT_DATA); if (m == NULL) return (ENOBUFS); m->m_pkthdr.len = m->m_len = sizeof(*hdr) + sizeof(*msc); hdr = mtod(m, struct rfcomm_mcc_hdr *); msc = (struct rfcomm_mcc_msc *)(hdr + 1); hdr->type = RFCOMM_MKMCC_TYPE(1, RFCOMM_MCC_MSC); hdr->length = RFCOMM_MKLEN8(sizeof(*msc)); msc->address = RFCOMM_MKADDRESS(1, pcb->dlci); msc->modem = pcb->lmodem; NG_BTSOCKET_RFCOMM_INFO( "%s: Sending MSC dlci=%d, state=%d, flags=%#x, address=%d, modem=%#x\n", __func__, pcb->dlci, pcb->state, pcb->flags, msc->address, msc->modem); return (ng_btsocket_rfcomm_send_uih(pcb->session, RFCOMM_MKADDRESS(INITIATOR(pcb->session), 0), 0, 0, m)); } /* ng_btsocket_rfcomm_send_msc */ /* * Send PN request. Caller must hold pcb->pcb_mtx and pcb->session->session_mtx */ static int ng_btsocket_rfcomm_send_pn(ng_btsocket_rfcomm_pcb_p pcb) { struct mbuf *m = NULL; struct rfcomm_mcc_hdr *hdr = NULL; struct rfcomm_mcc_pn *pn = NULL; mtx_assert(&pcb->session->session_mtx, MA_OWNED); mtx_assert(&pcb->pcb_mtx, MA_OWNED); MGETHDR(m, M_NOWAIT, MT_DATA); if (m == NULL) return (ENOBUFS); m->m_pkthdr.len = m->m_len = sizeof(*hdr) + sizeof(*pn); hdr = mtod(m, struct rfcomm_mcc_hdr *); pn = (struct rfcomm_mcc_pn *)(hdr + 1); hdr->type = RFCOMM_MKMCC_TYPE(1, RFCOMM_MCC_PN); hdr->length = RFCOMM_MKLEN8(sizeof(*pn)); pn->dlci = pcb->dlci; /* * Set default DLCI priority as described in GSM 07.10 * (ETSI TS 101 369) clause 5.6 page 42 */ pn->priority = (pcb->dlci < 56)? (((pcb->dlci >> 3) << 3) + 7) : 61; pn->ack_timer = 0; pn->mtu = htole16(pcb->mtu); pn->max_retrans = 0; if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_CFC) { pn->flow_control = 0xf0; pn->credits = pcb->rx_cred; } else { pn->flow_control = 0; pn->credits = 0; } NG_BTSOCKET_RFCOMM_INFO( "%s: Sending PN dlci=%d, state=%d, flags=%#x, mtu=%d, flow_control=%#x, " \ "credits=%d\n", __func__, pcb->dlci, pcb->state, pcb->flags, pcb->mtu, pn->flow_control, pn->credits); return (ng_btsocket_rfcomm_send_uih(pcb->session, RFCOMM_MKADDRESS(INITIATOR(pcb->session), 0), 0, 0, m)); } /* ng_btsocket_rfcomm_send_pn */ /* * Calculate and send credits based on available space in receive buffer */ static int ng_btsocket_rfcomm_send_credits(ng_btsocket_rfcomm_pcb_p pcb) { int error = 0; u_int8_t credits; mtx_assert(&pcb->pcb_mtx, MA_OWNED); mtx_assert(&pcb->session->session_mtx, MA_OWNED); NG_BTSOCKET_RFCOMM_INFO( "%s: Sending more credits, dlci=%d, state=%d, flags=%#x, mtu=%d, " \ "space=%ld, tx_cred=%d, rx_cred=%d\n", __func__, pcb->dlci, pcb->state, pcb->flags, pcb->mtu, sbspace(&pcb->so->so_rcv), pcb->tx_cred, pcb->rx_cred); credits = sbspace(&pcb->so->so_rcv) / pcb->mtu; if (credits > 0) { if (pcb->rx_cred + credits > RFCOMM_MAX_CREDITS) credits = RFCOMM_MAX_CREDITS - pcb->rx_cred; error = ng_btsocket_rfcomm_send_uih( pcb->session, RFCOMM_MKADDRESS(INITIATOR(pcb->session), pcb->dlci), 1, credits, NULL); if (error == 0) { pcb->rx_cred += credits; NG_BTSOCKET_RFCOMM_INFO( "%s: Gave remote side %d more credits, dlci=%d, state=%d, flags=%#x, " \ "rx_cred=%d, tx_cred=%d\n", __func__, credits, pcb->dlci, pcb->state, pcb->flags, pcb->rx_cred, pcb->tx_cred); } else NG_BTSOCKET_RFCOMM_ERR( "%s: Could not send credits, error=%d, dlci=%d, state=%d, flags=%#x, " \ "mtu=%d, space=%ld, tx_cred=%d, rx_cred=%d\n", __func__, error, pcb->dlci, pcb->state, pcb->flags, pcb->mtu, sbspace(&pcb->so->so_rcv), pcb->tx_cred, pcb->rx_cred); } return (error); } /* ng_btsocket_rfcomm_send_credits */ /***************************************************************************** ***************************************************************************** ** RFCOMM DLCs ***************************************************************************** *****************************************************************************/ /* * Send data from socket send buffer * Caller must hold pcb->pcb_mtx and pcb->session->session_mtx */ static int ng_btsocket_rfcomm_pcb_send(ng_btsocket_rfcomm_pcb_p pcb, int limit) { struct mbuf *m = NULL; int sent, length, error; mtx_assert(&pcb->session->session_mtx, MA_OWNED); mtx_assert(&pcb->pcb_mtx, MA_OWNED); if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_CFC) limit = min(limit, pcb->tx_cred); else if (!(pcb->rmodem & RFCOMM_MODEM_FC)) limit = min(limit, RFCOMM_MAX_CREDITS); /* XXX ??? */ else limit = 0; if (limit == 0) { NG_BTSOCKET_RFCOMM_INFO( "%s: Could not send - remote flow control asserted, dlci=%d, flags=%#x, " \ "rmodem=%#x, tx_cred=%d\n", __func__, pcb->dlci, pcb->flags, pcb->rmodem, pcb->tx_cred); return (0); } for (error = 0, sent = 0; sent < limit; sent ++) { length = min(pcb->mtu, sbavail(&pcb->so->so_snd)); if (length == 0) break; /* Get the chunk from the socket's send buffer */ m = ng_btsocket_rfcomm_prepare_packet(&pcb->so->so_snd, length); if (m == NULL) { error = ENOBUFS; break; } sbdrop(&pcb->so->so_snd, length); error = ng_btsocket_rfcomm_send_uih(pcb->session, RFCOMM_MKADDRESS(INITIATOR(pcb->session), pcb->dlci), 0, 0, m); if (error != 0) break; } if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_CFC) pcb->tx_cred -= sent; if (error == 0 && sent > 0) { pcb->flags &= ~NG_BTSOCKET_RFCOMM_DLC_SENDING; sowwakeup(pcb->so); } return (error); } /* ng_btsocket_rfcomm_pcb_send */ /* * Unlink and disconnect DLC. If ng_btsocket_rfcomm_pcb_kill() returns * non zero value than socket has no reference and has to be detached. * Caller must hold pcb->pcb_mtx and pcb->session->session_mtx */ static void ng_btsocket_rfcomm_pcb_kill(ng_btsocket_rfcomm_pcb_p pcb, int error) { ng_btsocket_rfcomm_session_p s = pcb->session; NG_BTSOCKET_RFCOMM_INFO( "%s: Killing DLC, so=%p, dlci=%d, state=%d, flags=%#x, error=%d\n", __func__, pcb->so, pcb->dlci, pcb->state, pcb->flags, error); if (pcb->session == NULL) panic("%s: DLC without session, pcb=%p, state=%d, flags=%#x\n", __func__, pcb, pcb->state, pcb->flags); mtx_assert(&pcb->session->session_mtx, MA_OWNED); mtx_assert(&pcb->pcb_mtx, MA_OWNED); if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_TIMO) ng_btsocket_rfcomm_untimeout(pcb); /* Detach DLC from the session. Does not matter which state DLC in */ LIST_REMOVE(pcb, session_next); pcb->session = NULL; /* Change DLC state and wakeup all sleepers */ pcb->state = NG_BTSOCKET_RFCOMM_DLC_CLOSED; pcb->so->so_error = error; soisdisconnected(pcb->so); wakeup(&pcb->state); /* Check if we have any DLCs left on the session */ if (LIST_EMPTY(&s->dlcs) && INITIATOR(s)) { NG_BTSOCKET_RFCOMM_INFO( "%s: Disconnecting session, state=%d, flags=%#x, mtu=%d\n", __func__, s->state, s->flags, s->mtu); switch (s->state) { case NG_BTSOCKET_RFCOMM_SESSION_CLOSED: case NG_BTSOCKET_RFCOMM_SESSION_DISCONNECTING: /* * Do not have to do anything here. We can get here * when L2CAP connection was terminated or we have * received DISC on multiplexor channel */ break; case NG_BTSOCKET_RFCOMM_SESSION_OPEN: /* Send DISC on multiplexor channel */ error = ng_btsocket_rfcomm_send_command(s, RFCOMM_FRAME_DISC, 0); if (error == 0) { s->state = NG_BTSOCKET_RFCOMM_SESSION_DISCONNECTING; break; } /* FALL THROUGH */ case NG_BTSOCKET_RFCOMM_SESSION_CONNECTING: case NG_BTSOCKET_RFCOMM_SESSION_CONNECTED: s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; break; /* case NG_BTSOCKET_RFCOMM_SESSION_LISTENING: */ default: panic("%s: Invalid session state=%d, flags=%#x\n", __func__, s->state, s->flags); break; } ng_btsocket_rfcomm_task_wakeup(); } } /* ng_btsocket_rfcomm_pcb_kill */ /* * Look for given dlci for given RFCOMM session. Caller must hold s->session_mtx */ static ng_btsocket_rfcomm_pcb_p ng_btsocket_rfcomm_pcb_by_dlci(ng_btsocket_rfcomm_session_p s, int dlci) { ng_btsocket_rfcomm_pcb_p pcb = NULL; mtx_assert(&s->session_mtx, MA_OWNED); LIST_FOREACH(pcb, &s->dlcs, session_next) if (pcb->dlci == dlci) break; return (pcb); } /* ng_btsocket_rfcomm_pcb_by_dlci */ /* * Look for socket that listens on given src address and given channel */ static ng_btsocket_rfcomm_pcb_p ng_btsocket_rfcomm_pcb_listener(bdaddr_p src, int channel) { ng_btsocket_rfcomm_pcb_p pcb = NULL, pcb1 = NULL; mtx_lock(&ng_btsocket_rfcomm_sockets_mtx); LIST_FOREACH(pcb, &ng_btsocket_rfcomm_sockets, next) { if (pcb->channel != channel || !(pcb->so->so_options & SO_ACCEPTCONN)) continue; if (bcmp(&pcb->src, src, sizeof(*src)) == 0) break; if (bcmp(&pcb->src, NG_HCI_BDADDR_ANY, sizeof(bdaddr_t)) == 0) pcb1 = pcb; } mtx_unlock(&ng_btsocket_rfcomm_sockets_mtx); return ((pcb != NULL)? pcb : pcb1); } /* ng_btsocket_rfcomm_pcb_listener */ /***************************************************************************** ***************************************************************************** ** Misc. functions ***************************************************************************** *****************************************************************************/ /* * Set timeout. Caller MUST hold pcb_mtx */ static void ng_btsocket_rfcomm_timeout(ng_btsocket_rfcomm_pcb_p pcb) { mtx_assert(&pcb->pcb_mtx, MA_OWNED); if (!(pcb->flags & NG_BTSOCKET_RFCOMM_DLC_TIMO)) { pcb->flags |= NG_BTSOCKET_RFCOMM_DLC_TIMO; pcb->flags &= ~NG_BTSOCKET_RFCOMM_DLC_TIMEDOUT; callout_reset(&pcb->timo, ng_btsocket_rfcomm_timo * hz, ng_btsocket_rfcomm_process_timeout, pcb); } else panic("%s: Duplicated socket timeout?!\n", __func__); } /* ng_btsocket_rfcomm_timeout */ /* * Unset pcb timeout. Caller MUST hold pcb_mtx */ static void ng_btsocket_rfcomm_untimeout(ng_btsocket_rfcomm_pcb_p pcb) { mtx_assert(&pcb->pcb_mtx, MA_OWNED); if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_TIMO) { callout_stop(&pcb->timo); pcb->flags &= ~NG_BTSOCKET_RFCOMM_DLC_TIMO; pcb->flags &= ~NG_BTSOCKET_RFCOMM_DLC_TIMEDOUT; } else panic("%s: No socket timeout?!\n", __func__); } /* ng_btsocket_rfcomm_timeout */ /* * Process pcb timeout */ static void ng_btsocket_rfcomm_process_timeout(void *xpcb) { ng_btsocket_rfcomm_pcb_p pcb = (ng_btsocket_rfcomm_pcb_p) xpcb; mtx_assert(&pcb->pcb_mtx, MA_OWNED); NG_BTSOCKET_RFCOMM_INFO( "%s: Timeout, so=%p, dlci=%d, state=%d, flags=%#x\n", __func__, pcb->so, pcb->dlci, pcb->state, pcb->flags); pcb->flags &= ~NG_BTSOCKET_RFCOMM_DLC_TIMO; pcb->flags |= NG_BTSOCKET_RFCOMM_DLC_TIMEDOUT; switch (pcb->state) { case NG_BTSOCKET_RFCOMM_DLC_CONFIGURING: case NG_BTSOCKET_RFCOMM_DLC_CONNECTING: pcb->state = NG_BTSOCKET_RFCOMM_DLC_DISCONNECTING; break; case NG_BTSOCKET_RFCOMM_DLC_W4_CONNECT: case NG_BTSOCKET_RFCOMM_DLC_DISCONNECTING: break; default: panic( "%s: DLC timeout in invalid state, dlci=%d, state=%d, flags=%#x\n", __func__, pcb->dlci, pcb->state, pcb->flags); break; } ng_btsocket_rfcomm_task_wakeup(); } /* ng_btsocket_rfcomm_process_timeout */ /* * Get up to length bytes from the socket buffer */ static struct mbuf * ng_btsocket_rfcomm_prepare_packet(struct sockbuf *sb, int length) { struct mbuf *top = NULL, *m = NULL, *n = NULL, *nextpkt = NULL; int mlen, noff, len; MGETHDR(top, M_NOWAIT, MT_DATA); if (top == NULL) return (NULL); top->m_pkthdr.len = length; top->m_len = 0; mlen = MHLEN; m = top; n = sb->sb_mb; nextpkt = n->m_nextpkt; noff = 0; while (length > 0 && n != NULL) { len = min(mlen - m->m_len, n->m_len - noff); if (len > length) len = length; bcopy(mtod(n, caddr_t)+noff, mtod(m, caddr_t)+m->m_len, len); m->m_len += len; noff += len; length -= len; if (length > 0 && m->m_len == mlen) { MGET(m->m_next, M_NOWAIT, MT_DATA); if (m->m_next == NULL) { NG_FREE_M(top); return (NULL); } m = m->m_next; m->m_len = 0; mlen = MLEN; } if (noff == n->m_len) { noff = 0; n = n->m_next; if (n == NULL) n = nextpkt; nextpkt = (n != NULL)? n->m_nextpkt : NULL; } } if (length < 0) panic("%s: length=%d\n", __func__, length); if (length > 0 && n == NULL) panic("%s: bogus length=%d, n=%p\n", __func__, length, n); return (top); } /* ng_btsocket_rfcomm_prepare_packet */ Index: head/sys/netgraph/bluetooth/socket/ng_btsocket_sco.c =================================================================== --- head/sys/netgraph/bluetooth/socket/ng_btsocket_sco.c (revision 298812) +++ head/sys/netgraph/bluetooth/socket/ng_btsocket_sco.c (revision 298813) @@ -1,1987 +1,1987 @@ /* * ng_btsocket_sco.c */ /*- * Copyright (c) 2001-2002 Maksim Yevmenkin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id: ng_btsocket_sco.c,v 1.2 2005/10/31 18:08:51 max Exp $ * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* MALLOC define */ #ifdef NG_SEPARATE_MALLOC static MALLOC_DEFINE(M_NETGRAPH_BTSOCKET_SCO, "netgraph_btsocks_sco", "Netgraph Bluetooth SCO sockets"); #else #define M_NETGRAPH_BTSOCKET_SCO M_NETGRAPH #endif /* NG_SEPARATE_MALLOC */ /* Netgraph node methods */ static ng_constructor_t ng_btsocket_sco_node_constructor; static ng_rcvmsg_t ng_btsocket_sco_node_rcvmsg; static ng_shutdown_t ng_btsocket_sco_node_shutdown; static ng_newhook_t ng_btsocket_sco_node_newhook; static ng_connect_t ng_btsocket_sco_node_connect; static ng_rcvdata_t ng_btsocket_sco_node_rcvdata; static ng_disconnect_t ng_btsocket_sco_node_disconnect; static void ng_btsocket_sco_input (void *, int); static void ng_btsocket_sco_rtclean (void *, int); /* Netgraph type descriptor */ static struct ng_type typestruct = { .version = NG_ABI_VERSION, .name = NG_BTSOCKET_SCO_NODE_TYPE, .constructor = ng_btsocket_sco_node_constructor, .rcvmsg = ng_btsocket_sco_node_rcvmsg, .shutdown = ng_btsocket_sco_node_shutdown, .newhook = ng_btsocket_sco_node_newhook, .connect = ng_btsocket_sco_node_connect, .rcvdata = ng_btsocket_sco_node_rcvdata, .disconnect = ng_btsocket_sco_node_disconnect, }; /* Globals */ static u_int32_t ng_btsocket_sco_debug_level; static node_p ng_btsocket_sco_node; static struct ng_bt_itemq ng_btsocket_sco_queue; static struct mtx ng_btsocket_sco_queue_mtx; static struct task ng_btsocket_sco_queue_task; static struct mtx ng_btsocket_sco_sockets_mtx; static LIST_HEAD(, ng_btsocket_sco_pcb) ng_btsocket_sco_sockets; static LIST_HEAD(, ng_btsocket_sco_rtentry) ng_btsocket_sco_rt; static struct mtx ng_btsocket_sco_rt_mtx; static struct task ng_btsocket_sco_rt_task; static struct timeval ng_btsocket_sco_lasttime; static int ng_btsocket_sco_curpps; /* Sysctl tree */ SYSCTL_DECL(_net_bluetooth_sco_sockets); static SYSCTL_NODE(_net_bluetooth_sco_sockets, OID_AUTO, seq, CTLFLAG_RW, 0, "Bluetooth SEQPACKET SCO sockets family"); SYSCTL_UINT(_net_bluetooth_sco_sockets_seq, OID_AUTO, debug_level, CTLFLAG_RW, &ng_btsocket_sco_debug_level, NG_BTSOCKET_WARN_LEVEL, "Bluetooth SEQPACKET SCO sockets debug level"); SYSCTL_UINT(_net_bluetooth_sco_sockets_seq, OID_AUTO, queue_len, CTLFLAG_RD, &ng_btsocket_sco_queue.len, 0, "Bluetooth SEQPACKET SCO sockets input queue length"); SYSCTL_UINT(_net_bluetooth_sco_sockets_seq, OID_AUTO, queue_maxlen, CTLFLAG_RD, &ng_btsocket_sco_queue.maxlen, 0, "Bluetooth SEQPACKET SCO sockets input queue max. length"); SYSCTL_UINT(_net_bluetooth_sco_sockets_seq, OID_AUTO, queue_drops, CTLFLAG_RD, &ng_btsocket_sco_queue.drops, 0, "Bluetooth SEQPACKET SCO sockets input queue drops"); /* Debug */ #define NG_BTSOCKET_SCO_INFO \ if (ng_btsocket_sco_debug_level >= NG_BTSOCKET_INFO_LEVEL && \ ppsratecheck(&ng_btsocket_sco_lasttime, &ng_btsocket_sco_curpps, 1)) \ printf #define NG_BTSOCKET_SCO_WARN \ if (ng_btsocket_sco_debug_level >= NG_BTSOCKET_WARN_LEVEL && \ ppsratecheck(&ng_btsocket_sco_lasttime, &ng_btsocket_sco_curpps, 1)) \ printf #define NG_BTSOCKET_SCO_ERR \ if (ng_btsocket_sco_debug_level >= NG_BTSOCKET_ERR_LEVEL && \ ppsratecheck(&ng_btsocket_sco_lasttime, &ng_btsocket_sco_curpps, 1)) \ printf #define NG_BTSOCKET_SCO_ALERT \ if (ng_btsocket_sco_debug_level >= NG_BTSOCKET_ALERT_LEVEL && \ ppsratecheck(&ng_btsocket_sco_lasttime, &ng_btsocket_sco_curpps, 1)) \ printf /* * Netgraph message processing routines */ static int ng_btsocket_sco_process_lp_con_cfm (struct ng_mesg *, ng_btsocket_sco_rtentry_p); static int ng_btsocket_sco_process_lp_con_ind (struct ng_mesg *, ng_btsocket_sco_rtentry_p); static int ng_btsocket_sco_process_lp_discon_ind (struct ng_mesg *, ng_btsocket_sco_rtentry_p); /* * Send LP messages to the lower layer */ static int ng_btsocket_sco_send_lp_con_req (ng_btsocket_sco_pcb_p); static int ng_btsocket_sco_send_lp_con_rsp (ng_btsocket_sco_rtentry_p, bdaddr_p, int); static int ng_btsocket_sco_send_lp_discon_req (ng_btsocket_sco_pcb_p); static int ng_btsocket_sco_send2 (ng_btsocket_sco_pcb_p); /* * Timeout processing routines */ static void ng_btsocket_sco_timeout (ng_btsocket_sco_pcb_p); static void ng_btsocket_sco_untimeout (ng_btsocket_sco_pcb_p); static void ng_btsocket_sco_process_timeout (void *); /* * Other stuff */ static ng_btsocket_sco_pcb_p ng_btsocket_sco_pcb_by_addr(bdaddr_p); static ng_btsocket_sco_pcb_p ng_btsocket_sco_pcb_by_handle(bdaddr_p, int); static ng_btsocket_sco_pcb_p ng_btsocket_sco_pcb_by_addrs(bdaddr_p, bdaddr_p); #define ng_btsocket_sco_wakeup_input_task() \ taskqueue_enqueue(taskqueue_swi, &ng_btsocket_sco_queue_task) #define ng_btsocket_sco_wakeup_route_task() \ taskqueue_enqueue(taskqueue_swi, &ng_btsocket_sco_rt_task) /***************************************************************************** ***************************************************************************** ** Netgraph node interface ***************************************************************************** *****************************************************************************/ /* * Netgraph node constructor. Do not allow to create node of this type. */ static int ng_btsocket_sco_node_constructor(node_p node) { return (EINVAL); } /* ng_btsocket_sco_node_constructor */ /* * Do local shutdown processing. Let old node go and create new fresh one. */ static int ng_btsocket_sco_node_shutdown(node_p node) { int error = 0; NG_NODE_UNREF(node); /* Create new node */ error = ng_make_node_common(&typestruct, &ng_btsocket_sco_node); if (error != 0) { NG_BTSOCKET_SCO_ALERT( "%s: Could not create Netgraph node, error=%d\n", __func__, error); ng_btsocket_sco_node = NULL; return (error); } error = ng_name_node(ng_btsocket_sco_node, NG_BTSOCKET_SCO_NODE_TYPE); if (error != 0) { NG_BTSOCKET_SCO_ALERT( "%s: Could not name Netgraph node, error=%d\n", __func__, error); NG_NODE_UNREF(ng_btsocket_sco_node); ng_btsocket_sco_node = NULL; return (error); } return (0); } /* ng_btsocket_sco_node_shutdown */ /* * We allow any hook to be connected to the node. */ static int ng_btsocket_sco_node_newhook(node_p node, hook_p hook, char const *name) { return (0); } /* ng_btsocket_sco_node_newhook */ /* * Just say "YEP, that's OK by me!" */ static int ng_btsocket_sco_node_connect(hook_p hook) { NG_HOOK_SET_PRIVATE(hook, NULL); NG_HOOK_REF(hook); /* Keep extra reference to the hook */ #if 0 NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook)); NG_HOOK_FORCE_QUEUE(hook); #endif return (0); } /* ng_btsocket_sco_node_connect */ /* * Hook disconnection. Schedule route cleanup task */ static int ng_btsocket_sco_node_disconnect(hook_p hook) { /* * If hook has private information than we must have this hook in * the routing table and must schedule cleaning for the routing table. * Otherwise hook was connected but we never got "hook_info" message, * so we have never added this hook to the routing table and it save * to just delete it. */ if (NG_HOOK_PRIVATE(hook) != NULL) return (ng_btsocket_sco_wakeup_route_task()); NG_HOOK_UNREF(hook); /* Remove extra reference */ return (0); } /* ng_btsocket_sco_node_disconnect */ /* * Process incoming messages */ static int ng_btsocket_sco_node_rcvmsg(node_p node, item_p item, hook_p hook) { struct ng_mesg *msg = NGI_MSG(item); /* item still has message */ int error = 0; if (msg != NULL && msg->header.typecookie == NGM_HCI_COOKIE) { mtx_lock(&ng_btsocket_sco_queue_mtx); if (NG_BT_ITEMQ_FULL(&ng_btsocket_sco_queue)) { NG_BTSOCKET_SCO_ERR( "%s: Input queue is full (msg)\n", __func__); NG_BT_ITEMQ_DROP(&ng_btsocket_sco_queue); NG_FREE_ITEM(item); error = ENOBUFS; } else { if (hook != NULL) { NG_HOOK_REF(hook); NGI_SET_HOOK(item, hook); } NG_BT_ITEMQ_ENQUEUE(&ng_btsocket_sco_queue, item); error = ng_btsocket_sco_wakeup_input_task(); } mtx_unlock(&ng_btsocket_sco_queue_mtx); } else { NG_FREE_ITEM(item); error = EINVAL; } return (error); } /* ng_btsocket_sco_node_rcvmsg */ /* * Receive data on a hook */ static int ng_btsocket_sco_node_rcvdata(hook_p hook, item_p item) { int error = 0; mtx_lock(&ng_btsocket_sco_queue_mtx); if (NG_BT_ITEMQ_FULL(&ng_btsocket_sco_queue)) { NG_BTSOCKET_SCO_ERR( "%s: Input queue is full (data)\n", __func__); NG_BT_ITEMQ_DROP(&ng_btsocket_sco_queue); NG_FREE_ITEM(item); error = ENOBUFS; } else { NG_HOOK_REF(hook); NGI_SET_HOOK(item, hook); NG_BT_ITEMQ_ENQUEUE(&ng_btsocket_sco_queue, item); error = ng_btsocket_sco_wakeup_input_task(); } mtx_unlock(&ng_btsocket_sco_queue_mtx); return (error); } /* ng_btsocket_sco_node_rcvdata */ /* * Process LP_ConnectCfm event from the lower layer protocol */ static int ng_btsocket_sco_process_lp_con_cfm(struct ng_mesg *msg, ng_btsocket_sco_rtentry_p rt) { ng_hci_lp_con_cfm_ep *ep = NULL; ng_btsocket_sco_pcb_t *pcb = NULL; int error = 0; if (msg->header.arglen != sizeof(*ep)) return (EMSGSIZE); ep = (ng_hci_lp_con_cfm_ep *)(msg->data); mtx_lock(&ng_btsocket_sco_sockets_mtx); /* Look for the socket with the token */ pcb = ng_btsocket_sco_pcb_by_addrs(&rt->src, &ep->bdaddr); if (pcb == NULL) { mtx_unlock(&ng_btsocket_sco_sockets_mtx); return (ENOENT); } /* pcb is locked */ NG_BTSOCKET_SCO_INFO( "%s: Got LP_ConnectCfm response, src bdaddr=%x:%x:%x:%x:%x:%x, " \ "dst bdaddr=%x:%x:%x:%x:%x:%x, status=%d, handle=%d, state=%d\n", __func__, pcb->src.b[5], pcb->src.b[4], pcb->src.b[3], pcb->src.b[2], pcb->src.b[1], pcb->src.b[0], pcb->dst.b[5], pcb->dst.b[4], pcb->dst.b[3], pcb->dst.b[2], pcb->dst.b[1], pcb->dst.b[0], ep->status, ep->con_handle, pcb->state); if (pcb->state != NG_BTSOCKET_SCO_CONNECTING) { mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_sco_sockets_mtx); return (ENOENT); } ng_btsocket_sco_untimeout(pcb); if (ep->status == 0) { /* * Connection is open. Update connection handle and * socket state */ pcb->con_handle = ep->con_handle; pcb->state = NG_BTSOCKET_SCO_OPEN; soisconnected(pcb->so); } else { /* * We have failed to open connection, so disconnect the socket */ pcb->so->so_error = ECONNREFUSED; /* XXX convert status ??? */ pcb->state = NG_BTSOCKET_SCO_CLOSED; soisdisconnected(pcb->so); } mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_sco_sockets_mtx); return (error); } /* ng_btsocket_sco_process_lp_con_cfm */ /* * Process LP_ConnectInd indicator. Find socket that listens on address. * Find exact or closest match. */ static int ng_btsocket_sco_process_lp_con_ind(struct ng_mesg *msg, ng_btsocket_sco_rtentry_p rt) { ng_hci_lp_con_ind_ep *ep = NULL; ng_btsocket_sco_pcb_t *pcb = NULL, *pcb1 = NULL; int error = 0; u_int16_t status = 0; if (msg->header.arglen != sizeof(*ep)) return (EMSGSIZE); ep = (ng_hci_lp_con_ind_ep *)(msg->data); NG_BTSOCKET_SCO_INFO( "%s: Got LP_ConnectInd indicator, src bdaddr=%x:%x:%x:%x:%x:%x, " \ "dst bdaddr=%x:%x:%x:%x:%x:%x\n", __func__, rt->src.b[5], rt->src.b[4], rt->src.b[3], rt->src.b[2], rt->src.b[1], rt->src.b[0], ep->bdaddr.b[5], ep->bdaddr.b[4], ep->bdaddr.b[3], ep->bdaddr.b[2], ep->bdaddr.b[1], ep->bdaddr.b[0]); mtx_lock(&ng_btsocket_sco_sockets_mtx); pcb = ng_btsocket_sco_pcb_by_addr(&rt->src); if (pcb != NULL) { struct socket *so1 = NULL; /* pcb is locked */ /* * First check the pending connections queue and if we have * space then create new socket and set proper source address. */ if (pcb->so->so_qlen <= pcb->so->so_qlimit) { CURVNET_SET(pcb->so->so_vnet); so1 = sonewconn(pcb->so, 0); CURVNET_RESTORE(); } if (so1 == NULL) { status = 0x0d; /* Rejected due to limited resources */ goto respond; } /* * If we got here than we have created new socket. So complete * connection. If we we listening on specific address then copy * source address from listening socket, otherwise copy source * address from hook's routing information. */ pcb1 = so2sco_pcb(so1); KASSERT((pcb1 != NULL), ("%s: pcb1 == NULL\n", __func__)); mtx_lock(&pcb1->pcb_mtx); if (bcmp(&pcb->src, NG_HCI_BDADDR_ANY, sizeof(pcb->src)) != 0) bcopy(&pcb->src, &pcb1->src, sizeof(pcb1->src)); else bcopy(&rt->src, &pcb1->src, sizeof(pcb1->src)); pcb1->flags &= ~NG_BTSOCKET_SCO_CLIENT; bcopy(&ep->bdaddr, &pcb1->dst, sizeof(pcb1->dst)); pcb1->rt = rt; } else /* Nobody listens on requested BDADDR */ status = 0x1f; /* Unspecified Error */ respond: error = ng_btsocket_sco_send_lp_con_rsp(rt, &ep->bdaddr, status); if (pcb1 != NULL) { if (error != 0) { pcb1->so->so_error = error; pcb1->state = NG_BTSOCKET_SCO_CLOSED; soisdisconnected(pcb1->so); } else { pcb1->state = NG_BTSOCKET_SCO_CONNECTING; soisconnecting(pcb1->so); ng_btsocket_sco_timeout(pcb1); } mtx_unlock(&pcb1->pcb_mtx); } if (pcb != NULL) mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_sco_sockets_mtx); return (error); } /* ng_btsocket_sco_process_lp_con_ind */ /* * Process LP_DisconnectInd indicator */ static int ng_btsocket_sco_process_lp_discon_ind(struct ng_mesg *msg, ng_btsocket_sco_rtentry_p rt) { ng_hci_lp_discon_ind_ep *ep = NULL; ng_btsocket_sco_pcb_t *pcb = NULL; /* Check message */ if (msg->header.arglen != sizeof(*ep)) return (EMSGSIZE); ep = (ng_hci_lp_discon_ind_ep *)(msg->data); mtx_lock(&ng_btsocket_sco_sockets_mtx); /* Look for the socket with given channel ID */ pcb = ng_btsocket_sco_pcb_by_handle(&rt->src, ep->con_handle); if (pcb == NULL) { mtx_unlock(&ng_btsocket_sco_sockets_mtx); return (0); } /* * Disconnect the socket. If there was any pending request we can * not do anything here anyway. */ /* pcb is locked */ NG_BTSOCKET_SCO_INFO( "%s: Got LP_DisconnectInd indicator, src bdaddr=%x:%x:%x:%x:%x:%x, " \ "dst bdaddr=%x:%x:%x:%x:%x:%x, handle=%d, state=%d\n", __func__, pcb->src.b[5], pcb->src.b[4], pcb->src.b[3], pcb->src.b[2], pcb->src.b[1], pcb->src.b[0], pcb->dst.b[5], pcb->dst.b[4], pcb->dst.b[3], pcb->dst.b[2], pcb->dst.b[1], pcb->dst.b[0], pcb->con_handle, pcb->state); if (pcb->flags & NG_BTSOCKET_SCO_TIMO) ng_btsocket_sco_untimeout(pcb); pcb->state = NG_BTSOCKET_SCO_CLOSED; soisdisconnected(pcb->so); mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_sco_sockets_mtx); return (0); } /* ng_btsocket_sco_process_lp_discon_ind */ /* * Send LP_ConnectReq request */ static int ng_btsocket_sco_send_lp_con_req(ng_btsocket_sco_pcb_p pcb) { struct ng_mesg *msg = NULL; ng_hci_lp_con_req_ep *ep = NULL; int error = 0; mtx_assert(&pcb->pcb_mtx, MA_OWNED); if (pcb->rt == NULL || pcb->rt->hook == NULL || NG_HOOK_NOT_VALID(pcb->rt->hook)) return (ENETDOWN); NG_MKMESSAGE(msg, NGM_HCI_COOKIE, NGM_HCI_LP_CON_REQ, sizeof(*ep), M_NOWAIT); if (msg == NULL) return (ENOMEM); ep = (ng_hci_lp_con_req_ep *)(msg->data); ep->link_type = NG_HCI_LINK_SCO; bcopy(&pcb->dst, &ep->bdaddr, sizeof(ep->bdaddr)); NG_SEND_MSG_HOOK(error, ng_btsocket_sco_node, msg, pcb->rt->hook, 0); return (error); } /* ng_btsocket_sco_send_lp_con_req */ /* * Send LP_ConnectRsp response */ static int ng_btsocket_sco_send_lp_con_rsp(ng_btsocket_sco_rtentry_p rt, bdaddr_p dst, int status) { struct ng_mesg *msg = NULL; ng_hci_lp_con_rsp_ep *ep = NULL; int error = 0; if (rt == NULL || rt->hook == NULL || NG_HOOK_NOT_VALID(rt->hook)) return (ENETDOWN); NG_MKMESSAGE(msg, NGM_HCI_COOKIE, NGM_HCI_LP_CON_RSP, sizeof(*ep), M_NOWAIT); if (msg == NULL) return (ENOMEM); ep = (ng_hci_lp_con_rsp_ep *)(msg->data); ep->status = status; ep->link_type = NG_HCI_LINK_SCO; bcopy(dst, &ep->bdaddr, sizeof(ep->bdaddr)); NG_SEND_MSG_HOOK(error, ng_btsocket_sco_node, msg, rt->hook, 0); return (error); } /* ng_btsocket_sco_send_lp_con_rsp */ /* * Send LP_DisconReq request */ static int ng_btsocket_sco_send_lp_discon_req(ng_btsocket_sco_pcb_p pcb) { struct ng_mesg *msg = NULL; ng_hci_lp_discon_req_ep *ep = NULL; int error = 0; mtx_assert(&pcb->pcb_mtx, MA_OWNED); if (pcb->rt == NULL || pcb->rt->hook == NULL || NG_HOOK_NOT_VALID(pcb->rt->hook)) return (ENETDOWN); NG_MKMESSAGE(msg, NGM_HCI_COOKIE, NGM_HCI_LP_DISCON_REQ, sizeof(*ep), M_NOWAIT); if (msg == NULL) return (ENOMEM); ep = (ng_hci_lp_discon_req_ep *)(msg->data); ep->con_handle = pcb->con_handle; ep->reason = 0x13; /* User Ended Connection */ NG_SEND_MSG_HOOK(error, ng_btsocket_sco_node, msg, pcb->rt->hook, 0); return (error); } /* ng_btsocket_sco_send_lp_discon_req */ /***************************************************************************** ***************************************************************************** ** Socket interface ***************************************************************************** *****************************************************************************/ /* * SCO sockets data input routine */ static void ng_btsocket_sco_data_input(struct mbuf *m, hook_p hook) { ng_hci_scodata_pkt_t *hdr = NULL; ng_btsocket_sco_pcb_t *pcb = NULL; ng_btsocket_sco_rtentry_t *rt = NULL; u_int16_t con_handle; if (hook == NULL) { NG_BTSOCKET_SCO_ALERT( "%s: Invalid source hook for SCO data packet\n", __func__); goto drop; } rt = (ng_btsocket_sco_rtentry_t *) NG_HOOK_PRIVATE(hook); if (rt == NULL) { NG_BTSOCKET_SCO_ALERT( "%s: Could not find out source bdaddr for SCO data packet\n", __func__); goto drop; } /* Make sure we can access header */ if (m->m_pkthdr.len < sizeof(*hdr)) { NG_BTSOCKET_SCO_ERR( "%s: SCO data packet too small, len=%d\n", __func__, m->m_pkthdr.len); goto drop; } if (m->m_len < sizeof(*hdr)) { m = m_pullup(m, sizeof(*hdr)); if (m == NULL) goto drop; } /* Strip SCO packet header and verify packet length */ hdr = mtod(m, ng_hci_scodata_pkt_t *); m_adj(m, sizeof(*hdr)); if (hdr->length != m->m_pkthdr.len) { NG_BTSOCKET_SCO_ERR( "%s: Bad SCO data packet length, len=%d, length=%d\n", __func__, m->m_pkthdr.len, hdr->length); goto drop; } /* * Now process packet */ con_handle = NG_HCI_CON_HANDLE(le16toh(hdr->con_handle)); NG_BTSOCKET_SCO_INFO( "%s: Received SCO data packet: src bdaddr=%x:%x:%x:%x:%x:%x, handle=%d, " \ "length=%d\n", __func__, rt->src.b[5], rt->src.b[4], rt->src.b[3], rt->src.b[2], rt->src.b[1], rt->src.b[0], con_handle, hdr->length); mtx_lock(&ng_btsocket_sco_sockets_mtx); /* Find socket */ pcb = ng_btsocket_sco_pcb_by_handle(&rt->src, con_handle); if (pcb == NULL) { mtx_unlock(&ng_btsocket_sco_sockets_mtx); goto drop; } /* pcb is locked */ if (pcb->state != NG_BTSOCKET_SCO_OPEN) { NG_BTSOCKET_SCO_ERR( "%s: No connected socket found, src bdaddr=%x:%x:%x:%x:%x:%x, state=%d\n", __func__, rt->src.b[5], rt->src.b[4], rt->src.b[3], rt->src.b[2], rt->src.b[1], rt->src.b[0], pcb->state); mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_sco_sockets_mtx); goto drop; } /* Check if we have enough space in socket receive queue */ if (m->m_pkthdr.len > sbspace(&pcb->so->so_rcv)) { NG_BTSOCKET_SCO_ERR( "%s: Not enough space in socket receive queue. Dropping SCO data packet, " \ "src bdaddr=%x:%x:%x:%x:%x:%x, len=%d, space=%ld\n", __func__, rt->src.b[5], rt->src.b[4], rt->src.b[3], rt->src.b[2], rt->src.b[1], rt->src.b[0], m->m_pkthdr.len, sbspace(&pcb->so->so_rcv)); mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_sco_sockets_mtx); goto drop; } /* Append packet to the socket receive queue and wakeup */ sbappendrecord(&pcb->so->so_rcv, m); m = NULL; sorwakeup(pcb->so); mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_sco_sockets_mtx); drop: NG_FREE_M(m); /* checks for m != NULL */ } /* ng_btsocket_sco_data_input */ /* * SCO sockets default message input routine */ static void ng_btsocket_sco_default_msg_input(struct ng_mesg *msg, hook_p hook) { ng_btsocket_sco_rtentry_t *rt = NULL; if (hook == NULL || NG_HOOK_NOT_VALID(hook)) return; rt = (ng_btsocket_sco_rtentry_t *) NG_HOOK_PRIVATE(hook); switch (msg->header.cmd) { case NGM_HCI_NODE_UP: { ng_hci_node_up_ep *ep = NULL; if (msg->header.arglen != sizeof(*ep)) break; ep = (ng_hci_node_up_ep *)(msg->data); if (bcmp(&ep->bdaddr, NG_HCI_BDADDR_ANY, sizeof(bdaddr_t)) == 0) break; if (rt == NULL) { rt = malloc(sizeof(*rt), M_NETGRAPH_BTSOCKET_SCO, M_NOWAIT|M_ZERO); if (rt == NULL) break; NG_HOOK_SET_PRIVATE(hook, rt); mtx_lock(&ng_btsocket_sco_rt_mtx); LIST_INSERT_HEAD(&ng_btsocket_sco_rt, rt, next); } else mtx_lock(&ng_btsocket_sco_rt_mtx); bcopy(&ep->bdaddr, &rt->src, sizeof(rt->src)); rt->pkt_size = (ep->pkt_size == 0)? 60 : ep->pkt_size; rt->num_pkts = ep->num_pkts; rt->hook = hook; mtx_unlock(&ng_btsocket_sco_rt_mtx); NG_BTSOCKET_SCO_INFO( "%s: Updating hook \"%s\", src bdaddr=%x:%x:%x:%x:%x:%x, pkt_size=%d, " \ "num_pkts=%d\n", __func__, NG_HOOK_NAME(hook), rt->src.b[5], rt->src.b[4], rt->src.b[3], rt->src.b[2], rt->src.b[1], rt->src.b[0], rt->pkt_size, rt->num_pkts); } break; case NGM_HCI_SYNC_CON_QUEUE: { ng_hci_sync_con_queue_ep *ep = NULL; ng_btsocket_sco_pcb_t *pcb = NULL; if (rt == NULL || msg->header.arglen != sizeof(*ep)) break; ep = (ng_hci_sync_con_queue_ep *)(msg->data); rt->pending -= ep->completed; if (rt->pending < 0) { NG_BTSOCKET_SCO_WARN( "%s: Pending packet counter is out of sync! bdaddr=%x:%x:%x:%x:%x:%x, " \ "handle=%d, pending=%d, completed=%d\n", __func__, rt->src.b[5], rt->src.b[4], rt->src.b[3], rt->src.b[2], rt->src.b[1], rt->src.b[0], ep->con_handle, rt->pending, ep->completed); rt->pending = 0; } mtx_lock(&ng_btsocket_sco_sockets_mtx); /* Find socket */ pcb = ng_btsocket_sco_pcb_by_handle(&rt->src, ep->con_handle); if (pcb == NULL) { mtx_unlock(&ng_btsocket_sco_sockets_mtx); break; } /* pcb is locked */ /* Check state */ if (pcb->state == NG_BTSOCKET_SCO_OPEN) { /* Remove timeout */ ng_btsocket_sco_untimeout(pcb); /* Drop completed packets from the send queue */ for (; ep->completed > 0; ep->completed --) sbdroprecord(&pcb->so->so_snd); /* Send more if we have any */ if (sbavail(&pcb->so->so_snd) > 0) if (ng_btsocket_sco_send2(pcb) == 0) ng_btsocket_sco_timeout(pcb); /* Wake up writers */ sowwakeup(pcb->so); } mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_sco_sockets_mtx); } break; default: NG_BTSOCKET_SCO_WARN( "%s: Unknown message, cmd=%d\n", __func__, msg->header.cmd); break; } NG_FREE_MSG(msg); /* Checks for msg != NULL */ } /* ng_btsocket_sco_default_msg_input */ /* * SCO sockets LP message input routine */ static void ng_btsocket_sco_lp_msg_input(struct ng_mesg *msg, hook_p hook) { ng_btsocket_sco_rtentry_p rt = NULL; if (hook == NULL) { NG_BTSOCKET_SCO_ALERT( "%s: Invalid source hook for LP message\n", __func__); goto drop; } rt = (ng_btsocket_sco_rtentry_p) NG_HOOK_PRIVATE(hook); if (rt == NULL) { NG_BTSOCKET_SCO_ALERT( "%s: Could not find out source bdaddr for LP message\n", __func__); goto drop; } switch (msg->header.cmd) { case NGM_HCI_LP_CON_CFM: /* Connection Confirmation Event */ ng_btsocket_sco_process_lp_con_cfm(msg, rt); break; case NGM_HCI_LP_CON_IND: /* Connection Indication Event */ ng_btsocket_sco_process_lp_con_ind(msg, rt); break; case NGM_HCI_LP_DISCON_IND: /* Disconnection Indication Event */ ng_btsocket_sco_process_lp_discon_ind(msg, rt); break; /* XXX FIXME add other LP messages */ default: NG_BTSOCKET_SCO_WARN( "%s: Unknown LP message, cmd=%d\n", __func__, msg->header.cmd); break; } drop: NG_FREE_MSG(msg); } /* ng_btsocket_sco_lp_msg_input */ /* * SCO sockets input routine */ static void ng_btsocket_sco_input(void *context, int pending) { item_p item = NULL; hook_p hook = NULL; for (;;) { mtx_lock(&ng_btsocket_sco_queue_mtx); NG_BT_ITEMQ_DEQUEUE(&ng_btsocket_sco_queue, item); mtx_unlock(&ng_btsocket_sco_queue_mtx); if (item == NULL) break; NGI_GET_HOOK(item, hook); if (hook != NULL && NG_HOOK_NOT_VALID(hook)) goto drop; switch(item->el_flags & NGQF_TYPE) { case NGQF_DATA: { struct mbuf *m = NULL; NGI_GET_M(item, m); ng_btsocket_sco_data_input(m, hook); } break; case NGQF_MESG: { struct ng_mesg *msg = NULL; NGI_GET_MSG(item, msg); switch (msg->header.cmd) { case NGM_HCI_LP_CON_CFM: case NGM_HCI_LP_CON_IND: case NGM_HCI_LP_DISCON_IND: /* XXX FIXME add other LP messages */ ng_btsocket_sco_lp_msg_input(msg, hook); break; default: ng_btsocket_sco_default_msg_input(msg, hook); break; } } break; default: KASSERT(0, ("%s: invalid item type=%ld\n", __func__, (item->el_flags & NGQF_TYPE))); break; } drop: if (hook != NULL) NG_HOOK_UNREF(hook); NG_FREE_ITEM(item); } } /* ng_btsocket_sco_input */ /* * Route cleanup task. Gets scheduled when hook is disconnected. Here we * will find all sockets that use "invalid" hook and disconnect them. */ static void ng_btsocket_sco_rtclean(void *context, int pending) { ng_btsocket_sco_pcb_p pcb = NULL, pcb_next = NULL; ng_btsocket_sco_rtentry_p rt = NULL; /* * First disconnect all sockets that use "invalid" hook */ mtx_lock(&ng_btsocket_sco_sockets_mtx); for(pcb = LIST_FIRST(&ng_btsocket_sco_sockets); pcb != NULL; ) { mtx_lock(&pcb->pcb_mtx); pcb_next = LIST_NEXT(pcb, next); if (pcb->rt != NULL && pcb->rt->hook != NULL && NG_HOOK_NOT_VALID(pcb->rt->hook)) { if (pcb->flags & NG_BTSOCKET_SCO_TIMO) ng_btsocket_sco_untimeout(pcb); pcb->rt = NULL; pcb->so->so_error = ENETDOWN; pcb->state = NG_BTSOCKET_SCO_CLOSED; soisdisconnected(pcb->so); } mtx_unlock(&pcb->pcb_mtx); pcb = pcb_next; } mtx_unlock(&ng_btsocket_sco_sockets_mtx); /* * Now cleanup routing table */ mtx_lock(&ng_btsocket_sco_rt_mtx); for (rt = LIST_FIRST(&ng_btsocket_sco_rt); rt != NULL; ) { ng_btsocket_sco_rtentry_p rt_next = LIST_NEXT(rt, next); if (rt->hook != NULL && NG_HOOK_NOT_VALID(rt->hook)) { LIST_REMOVE(rt, next); NG_HOOK_SET_PRIVATE(rt->hook, NULL); NG_HOOK_UNREF(rt->hook); /* Remove extra reference */ bzero(rt, sizeof(*rt)); free(rt, M_NETGRAPH_BTSOCKET_SCO); } rt = rt_next; } mtx_unlock(&ng_btsocket_sco_rt_mtx); } /* ng_btsocket_sco_rtclean */ /* * Initialize everything */ void ng_btsocket_sco_init(void) { int error = 0; /* Skip initialization of globals for non-default instances. */ if (!IS_DEFAULT_VNET(curvnet)) return; ng_btsocket_sco_node = NULL; ng_btsocket_sco_debug_level = NG_BTSOCKET_WARN_LEVEL; /* Register Netgraph node type */ error = ng_newtype(&typestruct); if (error != 0) { NG_BTSOCKET_SCO_ALERT( "%s: Could not register Netgraph node type, error=%d\n", __func__, error); return; } /* Create Netgrapg node */ error = ng_make_node_common(&typestruct, &ng_btsocket_sco_node); if (error != 0) { NG_BTSOCKET_SCO_ALERT( "%s: Could not create Netgraph node, error=%d\n", __func__, error); ng_btsocket_sco_node = NULL; return; } error = ng_name_node(ng_btsocket_sco_node, NG_BTSOCKET_SCO_NODE_TYPE); if (error != 0) { NG_BTSOCKET_SCO_ALERT( "%s: Could not name Netgraph node, error=%d\n", __func__, error); NG_NODE_UNREF(ng_btsocket_sco_node); ng_btsocket_sco_node = NULL; return; } /* Create input queue */ NG_BT_ITEMQ_INIT(&ng_btsocket_sco_queue, 300); mtx_init(&ng_btsocket_sco_queue_mtx, "btsocks_sco_queue_mtx", NULL, MTX_DEF); TASK_INIT(&ng_btsocket_sco_queue_task, 0, ng_btsocket_sco_input, NULL); /* Create list of sockets */ LIST_INIT(&ng_btsocket_sco_sockets); mtx_init(&ng_btsocket_sco_sockets_mtx, "btsocks_sco_sockets_mtx", NULL, MTX_DEF); /* Routing table */ LIST_INIT(&ng_btsocket_sco_rt); mtx_init(&ng_btsocket_sco_rt_mtx, "btsocks_sco_rt_mtx", NULL, MTX_DEF); TASK_INIT(&ng_btsocket_sco_rt_task, 0, ng_btsocket_sco_rtclean, NULL); } /* ng_btsocket_sco_init */ /* * Abort connection on socket */ void ng_btsocket_sco_abort(struct socket *so) { so->so_error = ECONNABORTED; (void) ng_btsocket_sco_disconnect(so); } /* ng_btsocket_sco_abort */ void ng_btsocket_sco_close(struct socket *so) { (void) ng_btsocket_sco_disconnect(so); } /* ng_btsocket_sco_close */ /* * Accept connection on socket. Nothing to do here, socket must be connected * and ready, so just return peer address and be done with it. */ int ng_btsocket_sco_accept(struct socket *so, struct sockaddr **nam) { if (ng_btsocket_sco_node == NULL) return (EINVAL); return (ng_btsocket_sco_peeraddr(so, nam)); } /* ng_btsocket_sco_accept */ /* * Create and attach new socket */ int ng_btsocket_sco_attach(struct socket *so, int proto, struct thread *td) { ng_btsocket_sco_pcb_p pcb = so2sco_pcb(so); int error; /* Check socket and protocol */ if (ng_btsocket_sco_node == NULL) return (EPROTONOSUPPORT); if (so->so_type != SOCK_SEQPACKET) return (ESOCKTNOSUPPORT); #if 0 /* XXX sonewconn() calls "pru_attach" with proto == 0 */ if (proto != 0) if (proto != BLUETOOTH_PROTO_SCO) return (EPROTONOSUPPORT); #endif /* XXX */ if (pcb != NULL) return (EISCONN); /* Reserve send and receive space if it is not reserved yet */ if ((so->so_snd.sb_hiwat == 0) || (so->so_rcv.sb_hiwat == 0)) { error = soreserve(so, NG_BTSOCKET_SCO_SENDSPACE, NG_BTSOCKET_SCO_RECVSPACE); if (error != 0) return (error); } /* Allocate the PCB */ pcb = malloc(sizeof(*pcb), M_NETGRAPH_BTSOCKET_SCO, M_NOWAIT | M_ZERO); if (pcb == NULL) return (ENOMEM); /* Link the PCB and the socket */ so->so_pcb = (caddr_t) pcb; pcb->so = so; pcb->state = NG_BTSOCKET_SCO_CLOSED; callout_init(&pcb->timo, 1); /* * Mark PCB mutex as DUPOK to prevent "duplicated lock of * the same type" message. When accepting new SCO connection * ng_btsocket_sco_process_lp_con_ind() holds both PCB mutexes * for "old" (accepting) PCB and "new" (created) PCB. */ mtx_init(&pcb->pcb_mtx, "btsocks_sco_pcb_mtx", NULL, MTX_DEF|MTX_DUPOK); /* * Add the PCB to the list * * XXX FIXME VERY IMPORTANT! * * This is totally FUBAR. We could get here in two cases: * * 1) When user calls socket() - * 2) When we need to accept new incomming connection and call + * 2) When we need to accept new incoming connection and call * sonewconn() * - * In the first case we must aquire ng_btsocket_sco_sockets_mtx. + * In the first case we must acquire ng_btsocket_sco_sockets_mtx. * In the second case we hold ng_btsocket_sco_sockets_mtx already. * So we now need to distinguish between these cases. From reading * /sys/kern/uipc_socket2.c we can find out that sonewconn() calls * pru_attach with proto == 0 and td == NULL. For now use this fact * to figure out if we were called from socket() or from sonewconn(). */ if (td != NULL) mtx_lock(&ng_btsocket_sco_sockets_mtx); else mtx_assert(&ng_btsocket_sco_sockets_mtx, MA_OWNED); LIST_INSERT_HEAD(&ng_btsocket_sco_sockets, pcb, next); if (td != NULL) mtx_unlock(&ng_btsocket_sco_sockets_mtx); return (0); } /* ng_btsocket_sco_attach */ /* * Bind socket */ int ng_btsocket_sco_bind(struct socket *so, struct sockaddr *nam, struct thread *td) { ng_btsocket_sco_pcb_t *pcb = NULL; struct sockaddr_sco *sa = (struct sockaddr_sco *) nam; if (ng_btsocket_sco_node == NULL) return (EINVAL); /* Verify address */ if (sa == NULL) return (EINVAL); if (sa->sco_family != AF_BLUETOOTH) return (EAFNOSUPPORT); if (sa->sco_len != sizeof(*sa)) return (EINVAL); mtx_lock(&ng_btsocket_sco_sockets_mtx); /* * Check if other socket has this address already (look for exact * match in bdaddr) and assign socket address if it's available. */ if (bcmp(&sa->sco_bdaddr, NG_HCI_BDADDR_ANY, sizeof(sa->sco_bdaddr)) != 0) { LIST_FOREACH(pcb, &ng_btsocket_sco_sockets, next) { mtx_lock(&pcb->pcb_mtx); if (bcmp(&pcb->src, &sa->sco_bdaddr, sizeof(bdaddr_t)) == 0) { mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_sco_sockets_mtx); return (EADDRINUSE); } mtx_unlock(&pcb->pcb_mtx); } } pcb = so2sco_pcb(so); if (pcb == NULL) { mtx_unlock(&ng_btsocket_sco_sockets_mtx); return (EINVAL); } mtx_lock(&pcb->pcb_mtx); bcopy(&sa->sco_bdaddr, &pcb->src, sizeof(pcb->src)); mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_sco_sockets_mtx); return (0); } /* ng_btsocket_sco_bind */ /* * Connect socket */ int ng_btsocket_sco_connect(struct socket *so, struct sockaddr *nam, struct thread *td) { ng_btsocket_sco_pcb_t *pcb = so2sco_pcb(so); struct sockaddr_sco *sa = (struct sockaddr_sco *) nam; ng_btsocket_sco_rtentry_t *rt = NULL; int have_src, error = 0; /* Check socket */ if (pcb == NULL) return (EINVAL); if (ng_btsocket_sco_node == NULL) return (EINVAL); /* Verify address */ if (sa == NULL) return (EINVAL); if (sa->sco_family != AF_BLUETOOTH) return (EAFNOSUPPORT); if (sa->sco_len != sizeof(*sa)) return (EINVAL); if (bcmp(&sa->sco_bdaddr, NG_HCI_BDADDR_ANY, sizeof(bdaddr_t)) == 0) return (EDESTADDRREQ); /* * Routing. Socket should be bound to some source address. The source * address can be ANY. Destination address must be set and it must not * be ANY. If source address is ANY then find first rtentry that has * src != dst. */ mtx_lock(&ng_btsocket_sco_rt_mtx); mtx_lock(&pcb->pcb_mtx); if (pcb->state == NG_BTSOCKET_SCO_CONNECTING) { mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_sco_rt_mtx); return (EINPROGRESS); } if (bcmp(&sa->sco_bdaddr, &pcb->src, sizeof(pcb->src)) == 0) { mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_sco_rt_mtx); return (EINVAL); } /* Send destination address and PSM */ bcopy(&sa->sco_bdaddr, &pcb->dst, sizeof(pcb->dst)); pcb->rt = NULL; have_src = bcmp(&pcb->src, NG_HCI_BDADDR_ANY, sizeof(pcb->src)); LIST_FOREACH(rt, &ng_btsocket_sco_rt, next) { if (rt->hook == NULL || NG_HOOK_NOT_VALID(rt->hook)) continue; /* Match src and dst */ if (have_src) { if (bcmp(&pcb->src, &rt->src, sizeof(rt->src)) == 0) break; } else { if (bcmp(&pcb->dst, &rt->src, sizeof(rt->src)) != 0) break; } } if (rt != NULL) { pcb->rt = rt; if (!have_src) bcopy(&rt->src, &pcb->src, sizeof(pcb->src)); } else error = EHOSTUNREACH; /* * Send LP_Connect request */ if (error == 0) { error = ng_btsocket_sco_send_lp_con_req(pcb); if (error == 0) { pcb->flags |= NG_BTSOCKET_SCO_CLIENT; pcb->state = NG_BTSOCKET_SCO_CONNECTING; soisconnecting(pcb->so); ng_btsocket_sco_timeout(pcb); } } mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_sco_rt_mtx); return (error); } /* ng_btsocket_sco_connect */ /* * Process ioctl's calls on socket */ int ng_btsocket_sco_control(struct socket *so, u_long cmd, caddr_t data, struct ifnet *ifp, struct thread *td) { return (EINVAL); } /* ng_btsocket_sco_control */ /* * Process getsockopt/setsockopt system calls */ int ng_btsocket_sco_ctloutput(struct socket *so, struct sockopt *sopt) { ng_btsocket_sco_pcb_p pcb = so2sco_pcb(so); int error, tmp; if (ng_btsocket_sco_node == NULL) return (EINVAL); if (pcb == NULL) return (EINVAL); if (sopt->sopt_level != SOL_SCO) return (0); mtx_lock(&pcb->pcb_mtx); switch (sopt->sopt_dir) { case SOPT_GET: if (pcb->state != NG_BTSOCKET_SCO_OPEN) { error = ENOTCONN; break; } switch (sopt->sopt_name) { case SO_SCO_MTU: tmp = pcb->rt->pkt_size; error = sooptcopyout(sopt, &tmp, sizeof(tmp)); break; case SO_SCO_CONNINFO: tmp = pcb->con_handle; error = sooptcopyout(sopt, &tmp, sizeof(tmp)); break; default: error = EINVAL; break; } break; case SOPT_SET: error = ENOPROTOOPT; break; default: error = EINVAL; break; } mtx_unlock(&pcb->pcb_mtx); return (error); } /* ng_btsocket_sco_ctloutput */ /* * Detach and destroy socket */ void ng_btsocket_sco_detach(struct socket *so) { ng_btsocket_sco_pcb_p pcb = so2sco_pcb(so); KASSERT(pcb != NULL, ("ng_btsocket_sco_detach: pcb == NULL")); if (ng_btsocket_sco_node == NULL) return; mtx_lock(&ng_btsocket_sco_sockets_mtx); mtx_lock(&pcb->pcb_mtx); if (pcb->flags & NG_BTSOCKET_SCO_TIMO) ng_btsocket_sco_untimeout(pcb); if (pcb->state == NG_BTSOCKET_SCO_OPEN) ng_btsocket_sco_send_lp_discon_req(pcb); pcb->state = NG_BTSOCKET_SCO_CLOSED; LIST_REMOVE(pcb, next); mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_sco_sockets_mtx); mtx_destroy(&pcb->pcb_mtx); bzero(pcb, sizeof(*pcb)); free(pcb, M_NETGRAPH_BTSOCKET_SCO); soisdisconnected(so); so->so_pcb = NULL; } /* ng_btsocket_sco_detach */ /* * Disconnect socket */ int ng_btsocket_sco_disconnect(struct socket *so) { ng_btsocket_sco_pcb_p pcb = so2sco_pcb(so); if (pcb == NULL) return (EINVAL); if (ng_btsocket_sco_node == NULL) return (EINVAL); mtx_lock(&pcb->pcb_mtx); if (pcb->state == NG_BTSOCKET_SCO_DISCONNECTING) { mtx_unlock(&pcb->pcb_mtx); return (EINPROGRESS); } if (pcb->flags & NG_BTSOCKET_SCO_TIMO) ng_btsocket_sco_untimeout(pcb); if (pcb->state == NG_BTSOCKET_SCO_OPEN) { ng_btsocket_sco_send_lp_discon_req(pcb); pcb->state = NG_BTSOCKET_SCO_DISCONNECTING; soisdisconnecting(so); ng_btsocket_sco_timeout(pcb); } else { pcb->state = NG_BTSOCKET_SCO_CLOSED; soisdisconnected(so); } mtx_unlock(&pcb->pcb_mtx); return (0); } /* ng_btsocket_sco_disconnect */ /* * Listen on socket */ int ng_btsocket_sco_listen(struct socket *so, int backlog, struct thread *td) { ng_btsocket_sco_pcb_p pcb = so2sco_pcb(so); int error; if (pcb == NULL) return (EINVAL); if (ng_btsocket_sco_node == NULL) return (EINVAL); SOCK_LOCK(so); mtx_lock(&pcb->pcb_mtx); error = solisten_proto_check(so); if (error != 0) goto out; #if 0 if (bcmp(&pcb->src, NG_HCI_BDADDR_ANY, sizeof(bdaddr_t)) == 0) { error = EDESTADDRREQ; goto out; } #endif solisten_proto(so, backlog); out: mtx_unlock(&pcb->pcb_mtx); SOCK_UNLOCK(so); return (error); } /* ng_btsocket_listen */ /* * Get peer address */ int ng_btsocket_sco_peeraddr(struct socket *so, struct sockaddr **nam) { ng_btsocket_sco_pcb_p pcb = so2sco_pcb(so); struct sockaddr_sco sa; if (pcb == NULL) return (EINVAL); if (ng_btsocket_sco_node == NULL) return (EINVAL); mtx_lock(&pcb->pcb_mtx); bcopy(&pcb->dst, &sa.sco_bdaddr, sizeof(sa.sco_bdaddr)); mtx_unlock(&pcb->pcb_mtx); sa.sco_len = sizeof(sa); sa.sco_family = AF_BLUETOOTH; *nam = sodupsockaddr((struct sockaddr *) &sa, M_NOWAIT); return ((*nam == NULL)? ENOMEM : 0); } /* ng_btsocket_sco_peeraddr */ /* * Send data to socket */ int ng_btsocket_sco_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *nam, struct mbuf *control, struct thread *td) { ng_btsocket_sco_pcb_t *pcb = so2sco_pcb(so); int error = 0; if (ng_btsocket_sco_node == NULL) { error = ENETDOWN; goto drop; } /* Check socket and input */ if (pcb == NULL || m == NULL || control != NULL) { error = EINVAL; goto drop; } mtx_lock(&pcb->pcb_mtx); /* Make sure socket is connected */ if (pcb->state != NG_BTSOCKET_SCO_OPEN) { mtx_unlock(&pcb->pcb_mtx); error = ENOTCONN; goto drop; } /* Check route */ if (pcb->rt == NULL || pcb->rt->hook == NULL || NG_HOOK_NOT_VALID(pcb->rt->hook)) { mtx_unlock(&pcb->pcb_mtx); error = ENETDOWN; goto drop; } /* Check packet size */ if (m->m_pkthdr.len > pcb->rt->pkt_size) { NG_BTSOCKET_SCO_ERR( "%s: Packet too big, len=%d, pkt_size=%d\n", __func__, m->m_pkthdr.len, pcb->rt->pkt_size); mtx_unlock(&pcb->pcb_mtx); error = EMSGSIZE; goto drop; } /* * First put packet on socket send queue. Then check if we have * pending timeout. If we do not have timeout then we must send * packet and schedule timeout. Otherwise do nothing and wait for * NGM_HCI_SYNC_CON_QUEUE message. */ sbappendrecord(&pcb->so->so_snd, m); m = NULL; if (!(pcb->flags & NG_BTSOCKET_SCO_TIMO)) { error = ng_btsocket_sco_send2(pcb); if (error == 0) ng_btsocket_sco_timeout(pcb); else sbdroprecord(&pcb->so->so_snd); /* XXX */ } mtx_unlock(&pcb->pcb_mtx); drop: NG_FREE_M(m); /* checks for != NULL */ NG_FREE_M(control); return (error); } /* ng_btsocket_sco_send */ /* * Send first packet in the socket queue to the SCO layer */ static int ng_btsocket_sco_send2(ng_btsocket_sco_pcb_p pcb) { struct mbuf *m = NULL; ng_hci_scodata_pkt_t *hdr = NULL; int error = 0; mtx_assert(&pcb->pcb_mtx, MA_OWNED); while (pcb->rt->pending < pcb->rt->num_pkts && sbavail(&pcb->so->so_snd) > 0) { /* Get a copy of the first packet on send queue */ m = m_dup(pcb->so->so_snd.sb_mb, M_NOWAIT); if (m == NULL) { error = ENOBUFS; break; } /* Create SCO packet header */ M_PREPEND(m, sizeof(*hdr), M_NOWAIT); if (m != NULL) if (m->m_len < sizeof(*hdr)) m = m_pullup(m, sizeof(*hdr)); if (m == NULL) { error = ENOBUFS; break; } /* Fill in the header */ hdr = mtod(m, ng_hci_scodata_pkt_t *); hdr->type = NG_HCI_SCO_DATA_PKT; hdr->con_handle = htole16(NG_HCI_MK_CON_HANDLE(pcb->con_handle, 0, 0)); hdr->length = m->m_pkthdr.len - sizeof(*hdr); /* Send packet */ NG_SEND_DATA_ONLY(error, pcb->rt->hook, m); if (error != 0) break; pcb->rt->pending ++; } return ((pcb->rt->pending > 0)? 0 : error); } /* ng_btsocket_sco_send2 */ /* * Get socket address */ int ng_btsocket_sco_sockaddr(struct socket *so, struct sockaddr **nam) { ng_btsocket_sco_pcb_p pcb = so2sco_pcb(so); struct sockaddr_sco sa; if (pcb == NULL) return (EINVAL); if (ng_btsocket_sco_node == NULL) return (EINVAL); mtx_lock(&pcb->pcb_mtx); bcopy(&pcb->src, &sa.sco_bdaddr, sizeof(sa.sco_bdaddr)); mtx_unlock(&pcb->pcb_mtx); sa.sco_len = sizeof(sa); sa.sco_family = AF_BLUETOOTH; *nam = sodupsockaddr((struct sockaddr *) &sa, M_NOWAIT); return ((*nam == NULL)? ENOMEM : 0); } /* ng_btsocket_sco_sockaddr */ /***************************************************************************** ***************************************************************************** ** Misc. functions ***************************************************************************** *****************************************************************************/ /* * Look for the socket that listens on given bdaddr. * Returns exact or close match (if any). * Caller must hold ng_btsocket_sco_sockets_mtx. * Returns with locked pcb. */ static ng_btsocket_sco_pcb_p ng_btsocket_sco_pcb_by_addr(bdaddr_p bdaddr) { ng_btsocket_sco_pcb_p p = NULL, p1 = NULL; mtx_assert(&ng_btsocket_sco_sockets_mtx, MA_OWNED); LIST_FOREACH(p, &ng_btsocket_sco_sockets, next) { mtx_lock(&p->pcb_mtx); if (p->so == NULL || !(p->so->so_options & SO_ACCEPTCONN)) { mtx_unlock(&p->pcb_mtx); continue; } if (bcmp(&p->src, bdaddr, sizeof(p->src)) == 0) return (p); /* return with locked pcb */ if (bcmp(&p->src, NG_HCI_BDADDR_ANY, sizeof(p->src)) == 0) p1 = p; mtx_unlock(&p->pcb_mtx); } if (p1 != NULL) mtx_lock(&p1->pcb_mtx); return (p1); } /* ng_btsocket_sco_pcb_by_addr */ /* * Look for the socket that assigned to given source address and handle. * Caller must hold ng_btsocket_sco_sockets_mtx. * Returns with locked pcb. */ static ng_btsocket_sco_pcb_p ng_btsocket_sco_pcb_by_handle(bdaddr_p src, int con_handle) { ng_btsocket_sco_pcb_p p = NULL; mtx_assert(&ng_btsocket_sco_sockets_mtx, MA_OWNED); LIST_FOREACH(p, &ng_btsocket_sco_sockets, next) { mtx_lock(&p->pcb_mtx); if (p->con_handle == con_handle && bcmp(src, &p->src, sizeof(p->src)) == 0) return (p); /* return with locked pcb */ mtx_unlock(&p->pcb_mtx); } return (NULL); } /* ng_btsocket_sco_pcb_by_handle */ /* * Look for the socket in CONNECTING state with given source and destination * addresses. Caller must hold ng_btsocket_sco_sockets_mtx. * Returns with locked pcb. */ static ng_btsocket_sco_pcb_p ng_btsocket_sco_pcb_by_addrs(bdaddr_p src, bdaddr_p dst) { ng_btsocket_sco_pcb_p p = NULL; mtx_assert(&ng_btsocket_sco_sockets_mtx, MA_OWNED); LIST_FOREACH(p, &ng_btsocket_sco_sockets, next) { mtx_lock(&p->pcb_mtx); if (p->state == NG_BTSOCKET_SCO_CONNECTING && bcmp(src, &p->src, sizeof(p->src)) == 0 && bcmp(dst, &p->dst, sizeof(p->dst)) == 0) return (p); /* return with locked pcb */ mtx_unlock(&p->pcb_mtx); } return (NULL); } /* ng_btsocket_sco_pcb_by_addrs */ /* * Set timeout on socket */ static void ng_btsocket_sco_timeout(ng_btsocket_sco_pcb_p pcb) { mtx_assert(&pcb->pcb_mtx, MA_OWNED); if (!(pcb->flags & NG_BTSOCKET_SCO_TIMO)) { pcb->flags |= NG_BTSOCKET_SCO_TIMO; callout_reset(&pcb->timo, bluetooth_sco_rtx_timeout(), ng_btsocket_sco_process_timeout, pcb); } else KASSERT(0, ("%s: Duplicated socket timeout?!\n", __func__)); } /* ng_btsocket_sco_timeout */ /* * Unset timeout on socket */ static void ng_btsocket_sco_untimeout(ng_btsocket_sco_pcb_p pcb) { mtx_assert(&pcb->pcb_mtx, MA_OWNED); if (pcb->flags & NG_BTSOCKET_SCO_TIMO) { callout_stop(&pcb->timo); pcb->flags &= ~NG_BTSOCKET_SCO_TIMO; } else KASSERT(0, ("%s: No socket timeout?!\n", __func__)); } /* ng_btsocket_sco_untimeout */ /* * Process timeout on socket */ static void ng_btsocket_sco_process_timeout(void *xpcb) { ng_btsocket_sco_pcb_p pcb = (ng_btsocket_sco_pcb_p) xpcb; mtx_lock(&pcb->pcb_mtx); pcb->flags &= ~NG_BTSOCKET_SCO_TIMO; pcb->so->so_error = ETIMEDOUT; switch (pcb->state) { case NG_BTSOCKET_SCO_CONNECTING: /* Connect timeout - close the socket */ pcb->state = NG_BTSOCKET_SCO_CLOSED; soisdisconnected(pcb->so); break; case NG_BTSOCKET_SCO_OPEN: /* Send timeout - did not get NGM_HCI_SYNC_CON_QUEUE */ sbdroprecord(&pcb->so->so_snd); sowwakeup(pcb->so); /* XXX FIXME what to do with pcb->rt->pending??? */ break; case NG_BTSOCKET_SCO_DISCONNECTING: /* Disconnect timeout - disconnect the socket anyway */ pcb->state = NG_BTSOCKET_SCO_CLOSED; soisdisconnected(pcb->so); break; default: NG_BTSOCKET_SCO_ERR( "%s: Invalid socket state=%d\n", __func__, pcb->state); break; } mtx_unlock(&pcb->pcb_mtx); } /* ng_btsocket_sco_process_timeout */ Index: head/sys/netgraph/netflow/ng_netflow.c =================================================================== --- head/sys/netgraph/netflow/ng_netflow.c (revision 298812) +++ head/sys/netgraph/netflow/ng_netflow.c (revision 298813) @@ -1,1039 +1,1039 @@ /*- * Copyright (c) 2010-2011 Alexander V. Chernikov * Copyright (c) 2004-2005 Gleb Smirnoff * Copyright (c) 2001-2003 Roman V. Palagin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $SourceForge: ng_netflow.c,v 1.30 2004/09/05 11:37:43 glebius Exp $ */ #include __FBSDID("$FreeBSD$"); #include "opt_inet6.h" #include "opt_route.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Netgraph methods */ static ng_constructor_t ng_netflow_constructor; static ng_rcvmsg_t ng_netflow_rcvmsg; static ng_close_t ng_netflow_close; static ng_shutdown_t ng_netflow_rmnode; static ng_newhook_t ng_netflow_newhook; static ng_rcvdata_t ng_netflow_rcvdata; static ng_disconnect_t ng_netflow_disconnect; /* Parse type for struct ng_netflow_info */ static const struct ng_parse_struct_field ng_netflow_info_type_fields[] = NG_NETFLOW_INFO_TYPE; static const struct ng_parse_type ng_netflow_info_type = { &ng_parse_struct_type, &ng_netflow_info_type_fields }; /* Parse type for struct ng_netflow_ifinfo */ static const struct ng_parse_struct_field ng_netflow_ifinfo_type_fields[] = NG_NETFLOW_IFINFO_TYPE; static const struct ng_parse_type ng_netflow_ifinfo_type = { &ng_parse_struct_type, &ng_netflow_ifinfo_type_fields }; /* Parse type for struct ng_netflow_setdlt */ static const struct ng_parse_struct_field ng_netflow_setdlt_type_fields[] = NG_NETFLOW_SETDLT_TYPE; static const struct ng_parse_type ng_netflow_setdlt_type = { &ng_parse_struct_type, &ng_netflow_setdlt_type_fields }; /* Parse type for ng_netflow_setifindex */ static const struct ng_parse_struct_field ng_netflow_setifindex_type_fields[] = NG_NETFLOW_SETIFINDEX_TYPE; static const struct ng_parse_type ng_netflow_setifindex_type = { &ng_parse_struct_type, &ng_netflow_setifindex_type_fields }; /* Parse type for ng_netflow_settimeouts */ static const struct ng_parse_struct_field ng_netflow_settimeouts_type_fields[] = NG_NETFLOW_SETTIMEOUTS_TYPE; static const struct ng_parse_type ng_netflow_settimeouts_type = { &ng_parse_struct_type, &ng_netflow_settimeouts_type_fields }; /* Parse type for ng_netflow_setconfig */ static const struct ng_parse_struct_field ng_netflow_setconfig_type_fields[] = NG_NETFLOW_SETCONFIG_TYPE; static const struct ng_parse_type ng_netflow_setconfig_type = { &ng_parse_struct_type, &ng_netflow_setconfig_type_fields }; /* Parse type for ng_netflow_settemplate */ static const struct ng_parse_struct_field ng_netflow_settemplate_type_fields[] = NG_NETFLOW_SETTEMPLATE_TYPE; static const struct ng_parse_type ng_netflow_settemplate_type = { &ng_parse_struct_type, &ng_netflow_settemplate_type_fields }; /* Parse type for ng_netflow_setmtu */ static const struct ng_parse_struct_field ng_netflow_setmtu_type_fields[] = NG_NETFLOW_SETMTU_TYPE; static const struct ng_parse_type ng_netflow_setmtu_type = { &ng_parse_struct_type, &ng_netflow_setmtu_type_fields }; /* Parse type for struct ng_netflow_v9info */ static const struct ng_parse_struct_field ng_netflow_v9info_type_fields[] = NG_NETFLOW_V9INFO_TYPE; static const struct ng_parse_type ng_netflow_v9info_type = { &ng_parse_struct_type, &ng_netflow_v9info_type_fields }; /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_netflow_cmds[] = { { NGM_NETFLOW_COOKIE, NGM_NETFLOW_INFO, "info", NULL, &ng_netflow_info_type }, { NGM_NETFLOW_COOKIE, NGM_NETFLOW_IFINFO, "ifinfo", &ng_parse_uint16_type, &ng_netflow_ifinfo_type }, { NGM_NETFLOW_COOKIE, NGM_NETFLOW_SETDLT, "setdlt", &ng_netflow_setdlt_type, NULL }, { NGM_NETFLOW_COOKIE, NGM_NETFLOW_SETIFINDEX, "setifindex", &ng_netflow_setifindex_type, NULL }, { NGM_NETFLOW_COOKIE, NGM_NETFLOW_SETTIMEOUTS, "settimeouts", &ng_netflow_settimeouts_type, NULL }, { NGM_NETFLOW_COOKIE, NGM_NETFLOW_SETCONFIG, "setconfig", &ng_netflow_setconfig_type, NULL }, { NGM_NETFLOW_COOKIE, NGM_NETFLOW_SETTEMPLATE, "settemplate", &ng_netflow_settemplate_type, NULL }, { NGM_NETFLOW_COOKIE, NGM_NETFLOW_SETMTU, "setmtu", &ng_netflow_setmtu_type, NULL }, { NGM_NETFLOW_COOKIE, NGM_NETFLOW_V9INFO, "v9info", NULL, &ng_netflow_v9info_type }, { 0 } }; /* Netgraph node type descriptor */ static struct ng_type ng_netflow_typestruct = { .version = NG_ABI_VERSION, .name = NG_NETFLOW_NODE_TYPE, .constructor = ng_netflow_constructor, .rcvmsg = ng_netflow_rcvmsg, .close = ng_netflow_close, .shutdown = ng_netflow_rmnode, .newhook = ng_netflow_newhook, .rcvdata = ng_netflow_rcvdata, .disconnect = ng_netflow_disconnect, .cmdlist = ng_netflow_cmds, }; NETGRAPH_INIT(netflow, &ng_netflow_typestruct); /* Called at node creation */ static int ng_netflow_constructor(node_p node) { priv_p priv; int i; /* Initialize private data */ priv = malloc(sizeof(*priv), M_NETGRAPH, M_WAITOK | M_ZERO); /* Initialize fib data */ priv->maxfibs = rt_numfibs; priv->fib_data = malloc(sizeof(fib_export_p) * priv->maxfibs, M_NETGRAPH, M_WAITOK | M_ZERO); /* Make node and its data point at each other */ NG_NODE_SET_PRIVATE(node, priv); priv->node = node; /* Initialize timeouts to default values */ priv->nfinfo_inact_t = INACTIVE_TIMEOUT; priv->nfinfo_act_t = ACTIVE_TIMEOUT; /* Set default config */ for (i = 0; i < NG_NETFLOW_MAXIFACES; i++) priv->ifaces[i].info.conf = NG_NETFLOW_CONF_INGRESS; /* Initialize callout handle */ callout_init(&priv->exp_callout, 1); /* Allocate memory and set up flow cache */ ng_netflow_cache_init(priv); return (0); } /* * ng_netflow supports two hooks: data and export. * Incoming traffic is expected on data, and expired * netflow datagrams are sent to export. */ static int ng_netflow_newhook(node_p node, hook_p hook, const char *name) { const priv_p priv = NG_NODE_PRIVATE(node); if (strncmp(name, NG_NETFLOW_HOOK_DATA, /* an iface hook? */ strlen(NG_NETFLOW_HOOK_DATA)) == 0) { iface_p iface; int ifnum = -1; const char *cp; char *eptr; cp = name + strlen(NG_NETFLOW_HOOK_DATA); if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0')) return (EINVAL); ifnum = (int)strtoul(cp, &eptr, 10); if (*eptr != '\0' || ifnum < 0 || ifnum >= NG_NETFLOW_MAXIFACES) return (EINVAL); /* See if hook is already connected */ if (priv->ifaces[ifnum].hook != NULL) return (EISCONN); iface = &priv->ifaces[ifnum]; /* Link private info and hook together */ NG_HOOK_SET_PRIVATE(hook, iface); iface->hook = hook; /* * In most cases traffic accounting is done on an * Ethernet interface, so default data link type * will be DLT_EN10MB. */ iface->info.ifinfo_dlt = DLT_EN10MB; } else if (strncmp(name, NG_NETFLOW_HOOK_OUT, strlen(NG_NETFLOW_HOOK_OUT)) == 0) { iface_p iface; int ifnum = -1; const char *cp; char *eptr; cp = name + strlen(NG_NETFLOW_HOOK_OUT); if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0')) return (EINVAL); ifnum = (int)strtoul(cp, &eptr, 10); if (*eptr != '\0' || ifnum < 0 || ifnum >= NG_NETFLOW_MAXIFACES) return (EINVAL); /* See if hook is already connected */ if (priv->ifaces[ifnum].out != NULL) return (EISCONN); iface = &priv->ifaces[ifnum]; /* Link private info and hook together */ NG_HOOK_SET_PRIVATE(hook, iface); iface->out = hook; } else if (strcmp(name, NG_NETFLOW_HOOK_EXPORT) == 0) { if (priv->export != NULL) return (EISCONN); /* Netflow version 5 supports 32-bit counters only */ if (CNTR_MAX == UINT64_MAX) return (EINVAL); priv->export = hook; /* Exporter is ready. Let's schedule expiry. */ callout_reset(&priv->exp_callout, (1*hz), &ng_netflow_expire, (void *)priv); } else if (strcmp(name, NG_NETFLOW_HOOK_EXPORT9) == 0) { if (priv->export9 != NULL) return (EISCONN); priv->export9 = hook; /* Exporter is ready. Let's schedule expiry. */ callout_reset(&priv->exp_callout, (1*hz), &ng_netflow_expire, (void *)priv); } else return (EINVAL); return (0); } /* Get a netgraph control message. */ static int ng_netflow_rcvmsg (node_p node, item_p item, hook_p lasthook) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_mesg *resp = NULL; int error = 0; struct ng_mesg *msg; NGI_GET_MSG(item, msg); /* Deal with message according to cookie and command */ switch (msg->header.typecookie) { case NGM_NETFLOW_COOKIE: switch (msg->header.cmd) { case NGM_NETFLOW_INFO: { struct ng_netflow_info *i; NG_MKRESPONSE(resp, msg, sizeof(struct ng_netflow_info), M_NOWAIT); i = (struct ng_netflow_info *)resp->data; ng_netflow_copyinfo(priv, i); break; } case NGM_NETFLOW_IFINFO: { struct ng_netflow_ifinfo *i; const uint16_t *index; if (msg->header.arglen != sizeof(uint16_t)) ERROUT(EINVAL); index = (uint16_t *)msg->data; if (*index >= NG_NETFLOW_MAXIFACES) ERROUT(EINVAL); /* connected iface? */ if (priv->ifaces[*index].hook == NULL) ERROUT(EINVAL); NG_MKRESPONSE(resp, msg, sizeof(struct ng_netflow_ifinfo), M_NOWAIT); i = (struct ng_netflow_ifinfo *)resp->data; memcpy((void *)i, (void *)&priv->ifaces[*index].info, sizeof(priv->ifaces[*index].info)); break; } case NGM_NETFLOW_SETDLT: { struct ng_netflow_setdlt *set; struct ng_netflow_iface *iface; if (msg->header.arglen != sizeof(struct ng_netflow_setdlt)) ERROUT(EINVAL); set = (struct ng_netflow_setdlt *)msg->data; if (set->iface >= NG_NETFLOW_MAXIFACES) ERROUT(EINVAL); iface = &priv->ifaces[set->iface]; /* connected iface? */ if (iface->hook == NULL) ERROUT(EINVAL); switch (set->dlt) { case DLT_EN10MB: iface->info.ifinfo_dlt = DLT_EN10MB; break; case DLT_RAW: iface->info.ifinfo_dlt = DLT_RAW; break; default: ERROUT(EINVAL); } break; } case NGM_NETFLOW_SETIFINDEX: { struct ng_netflow_setifindex *set; struct ng_netflow_iface *iface; if (msg->header.arglen != sizeof(struct ng_netflow_setifindex)) ERROUT(EINVAL); set = (struct ng_netflow_setifindex *)msg->data; if (set->iface >= NG_NETFLOW_MAXIFACES) ERROUT(EINVAL); iface = &priv->ifaces[set->iface]; /* connected iface? */ if (iface->hook == NULL) ERROUT(EINVAL); iface->info.ifinfo_index = set->index; break; } case NGM_NETFLOW_SETTIMEOUTS: { struct ng_netflow_settimeouts *set; if (msg->header.arglen != sizeof(struct ng_netflow_settimeouts)) ERROUT(EINVAL); set = (struct ng_netflow_settimeouts *)msg->data; priv->nfinfo_inact_t = set->inactive_timeout; priv->nfinfo_act_t = set->active_timeout; break; } case NGM_NETFLOW_SETCONFIG: { struct ng_netflow_setconfig *set; if (msg->header.arglen != sizeof(struct ng_netflow_setconfig)) ERROUT(EINVAL); set = (struct ng_netflow_setconfig *)msg->data; if (set->iface >= NG_NETFLOW_MAXIFACES) ERROUT(EINVAL); priv->ifaces[set->iface].info.conf = set->conf; break; } case NGM_NETFLOW_SETTEMPLATE: { struct ng_netflow_settemplate *set; if (msg->header.arglen != sizeof(struct ng_netflow_settemplate)) ERROUT(EINVAL); set = (struct ng_netflow_settemplate *)msg->data; priv->templ_packets = set->packets; priv->templ_time = set->time; break; } case NGM_NETFLOW_SETMTU: { struct ng_netflow_setmtu *set; if (msg->header.arglen != sizeof(struct ng_netflow_setmtu)) ERROUT(EINVAL); set = (struct ng_netflow_setmtu *)msg->data; if ((set->mtu < MIN_MTU) || (set->mtu > MAX_MTU)) ERROUT(EINVAL); priv->mtu = set->mtu; break; } case NGM_NETFLOW_SHOW: if (msg->header.arglen != sizeof(struct ngnf_show_header)) ERROUT(EINVAL); NG_MKRESPONSE(resp, msg, NGRESP_SIZE, M_NOWAIT); if (!resp) ERROUT(ENOMEM); error = ng_netflow_flow_show(priv, (struct ngnf_show_header *)msg->data, (struct ngnf_show_header *)resp->data); if (error) NG_FREE_MSG(resp); break; case NGM_NETFLOW_V9INFO: { struct ng_netflow_v9info *i; NG_MKRESPONSE(resp, msg, sizeof(struct ng_netflow_v9info), M_NOWAIT); i = (struct ng_netflow_v9info *)resp->data; ng_netflow_copyv9info(priv, i); break; } default: ERROUT(EINVAL); /* unknown command */ break; } break; default: ERROUT(EINVAL); /* incorrect cookie */ break; } /* * Take care of synchronous response, if any. * Free memory and return. */ done: NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } /* Receive data on hook. */ static int ng_netflow_rcvdata (hook_p hook, item_p item) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); const iface_p iface = NG_HOOK_PRIVATE(hook); hook_p out; struct mbuf *m = NULL, *m_old = NULL; struct ip *ip = NULL; struct ip6_hdr *ip6 = NULL; struct m_tag *mtag; int pullup_len = 0, off; uint8_t acct = 0, bypass = 0, flags = 0, upper_proto = 0; int error = 0, l3_off = 0; unsigned int src_if_index; caddr_t upper_ptr = NULL; fib_export_p fe; uint32_t fib; if ((hook == priv->export) || (hook == priv->export9)) { /* * Data arrived on export hook. * This must not happen. */ log(LOG_ERR, "ng_netflow: incoming data on export hook!\n"); ERROUT(EINVAL); } if (hook == iface->hook) { if ((iface->info.conf & NG_NETFLOW_CONF_INGRESS) == 0) bypass = 1; out = iface->out; } else if (hook == iface->out) { if ((iface->info.conf & NG_NETFLOW_CONF_EGRESS) == 0) bypass = 1; out = iface->hook; } else ERROUT(EINVAL); if ((!bypass) && (iface->info.conf & (NG_NETFLOW_CONF_ONCE | NG_NETFLOW_CONF_THISONCE))) { mtag = m_tag_locate(NGI_M(item), MTAG_NETFLOW, MTAG_NETFLOW_CALLED, NULL); while (mtag != NULL) { if ((iface->info.conf & NG_NETFLOW_CONF_ONCE) || ((ng_ID_t *)(mtag + 1))[0] == NG_NODE_ID(node)) { bypass = 1; break; } mtag = m_tag_locate(NGI_M(item), MTAG_NETFLOW, MTAG_NETFLOW_CALLED, mtag); } } if (bypass) { if (out == NULL) ERROUT(ENOTCONN); NG_FWD_ITEM_HOOK(error, item, out); return (error); } if (iface->info.conf & (NG_NETFLOW_CONF_ONCE | NG_NETFLOW_CONF_THISONCE)) { mtag = m_tag_alloc(MTAG_NETFLOW, MTAG_NETFLOW_CALLED, sizeof(ng_ID_t), M_NOWAIT); if (mtag) { ((ng_ID_t *)(mtag + 1))[0] = NG_NODE_ID(node); m_tag_prepend(NGI_M(item), mtag); } } /* Import configuration flags related to flow creation */ flags = iface->info.conf & NG_NETFLOW_FLOW_FLAGS; NGI_GET_M(item, m); m_old = m; /* Increase counters. */ iface->info.ifinfo_packets++; /* * Depending on interface data link type and packet contents * we pullup enough data, so that ng_netflow_flow_add() does not * need to know about mbuf at all. We keep current length of data * needed to be contiguous in pullup_len. mtod() is done at the * very end one more time, since m can had changed after pulluping. * * In case of unrecognized data we don't return error, but just * pass data to downstream hook, if it is available. */ #define M_CHECK(length) do { \ pullup_len += length; \ if (((m)->m_pkthdr.len < (pullup_len)) || \ ((pullup_len) > MHLEN)) { \ error = EINVAL; \ goto bypass; \ } \ if ((m)->m_len < (pullup_len) && \ (((m) = m_pullup((m),(pullup_len))) == NULL)) { \ error = ENOBUFS; \ goto done; \ } \ } while (0) switch (iface->info.ifinfo_dlt) { case DLT_EN10MB: /* Ethernet */ { struct ether_header *eh; uint16_t etype; M_CHECK(sizeof(struct ether_header)); eh = mtod(m, struct ether_header *); /* Make sure this is IP frame. */ etype = ntohs(eh->ether_type); switch (etype) { case ETHERTYPE_IP: M_CHECK(sizeof(struct ip)); eh = mtod(m, struct ether_header *); ip = (struct ip *)(eh + 1); l3_off = sizeof(struct ether_header); break; #ifdef INET6 case ETHERTYPE_IPV6: /* * m_pullup() called by M_CHECK() pullups * kern.ipc.max_protohdr (default 60 bytes) * which is enough. */ M_CHECK(sizeof(struct ip6_hdr)); eh = mtod(m, struct ether_header *); ip6 = (struct ip6_hdr *)(eh + 1); l3_off = sizeof(struct ether_header); break; #endif case ETHERTYPE_VLAN: { struct ether_vlan_header *evh; M_CHECK(sizeof(struct ether_vlan_header) - sizeof(struct ether_header)); evh = mtod(m, struct ether_vlan_header *); etype = ntohs(evh->evl_proto); l3_off = sizeof(struct ether_vlan_header); if (etype == ETHERTYPE_IP) { M_CHECK(sizeof(struct ip)); ip = (struct ip *)(evh + 1); break; #ifdef INET6 } else if (etype == ETHERTYPE_IPV6) { M_CHECK(sizeof(struct ip6_hdr)); ip6 = (struct ip6_hdr *)(evh + 1); break; #endif } } default: goto bypass; /* pass this frame */ } break; } case DLT_RAW: /* IP packets */ M_CHECK(sizeof(struct ip)); ip = mtod(m, struct ip *); /* l3_off is already zero */ #ifdef INET6 /* * If INET6 is not defined IPv6 packets * will be discarded in ng_netflow_flow_add(). */ if (ip->ip_v == IP6VERSION) { ip = NULL; M_CHECK(sizeof(struct ip6_hdr) - sizeof(struct ip)); ip6 = mtod(m, struct ip6_hdr *); } #endif break; default: goto bypass; break; } off = pullup_len; if ((ip != NULL) && ((ip->ip_off & htons(IP_OFFMASK)) == 0)) { if ((ip->ip_v != IPVERSION) || ((ip->ip_hl << 2) < sizeof(struct ip))) goto bypass; /* * In case of IPv4 header with options, we haven't pulled * up enough, yet. */ M_CHECK((ip->ip_hl << 2) - sizeof(struct ip)); /* Save upper layer offset and proto */ off = pullup_len; upper_proto = ip->ip_p; /* * XXX: in case of wrong upper layer header we will * forward this packet but skip this record in netflow. */ switch (ip->ip_p) { case IPPROTO_TCP: M_CHECK(sizeof(struct tcphdr)); break; case IPPROTO_UDP: M_CHECK(sizeof(struct udphdr)); break; case IPPROTO_SCTP: M_CHECK(sizeof(struct sctphdr)); break; } } else if (ip != NULL) { /* * Nothing to save except upper layer proto, * since this is a packet fragment. */ flags |= NG_NETFLOW_IS_FRAG; upper_proto = ip->ip_p; if ((ip->ip_v != IPVERSION) || ((ip->ip_hl << 2) < sizeof(struct ip))) goto bypass; #ifdef INET6 } else if (ip6 != NULL) { int cur = ip6->ip6_nxt, hdr_off = 0; struct ip6_ext *ip6e; struct ip6_frag *ip6f; if (priv->export9 == NULL) goto bypass; /* Save upper layer info. */ off = pullup_len; upper_proto = cur; if ((ip6->ip6_vfc & IPV6_VERSION_MASK) != IPV6_VERSION) goto bypass; /* - * Loop thru IPv6 extended headers to get upper + * Loop through IPv6 extended headers to get upper * layer header / frag. */ for (;;) { switch (cur) { /* * Same as in IPv4, we can forward a 'bad' * packet without accounting. */ case IPPROTO_TCP: M_CHECK(sizeof(struct tcphdr)); goto loopend; case IPPROTO_UDP: M_CHECK(sizeof(struct udphdr)); goto loopend; case IPPROTO_SCTP: M_CHECK(sizeof(struct sctphdr)); goto loopend; /* Loop until 'real' upper layer headers */ case IPPROTO_HOPOPTS: case IPPROTO_ROUTING: case IPPROTO_DSTOPTS: M_CHECK(sizeof(struct ip6_ext)); ip6e = (struct ip6_ext *)(mtod(m, caddr_t) + off); upper_proto = ip6e->ip6e_nxt; hdr_off = (ip6e->ip6e_len + 1) << 3; break; /* RFC4302, can be before DSTOPTS */ case IPPROTO_AH: M_CHECK(sizeof(struct ip6_ext)); ip6e = (struct ip6_ext *)(mtod(m, caddr_t) + off); upper_proto = ip6e->ip6e_nxt; hdr_off = (ip6e->ip6e_len + 2) << 2; break; case IPPROTO_FRAGMENT: M_CHECK(sizeof(struct ip6_frag)); ip6f = (struct ip6_frag *)(mtod(m, caddr_t) + off); upper_proto = ip6f->ip6f_nxt; hdr_off = sizeof(struct ip6_frag); off += hdr_off; flags |= NG_NETFLOW_IS_FRAG; goto loopend; #if 0 case IPPROTO_NONE: goto loopend; #endif /* * Any unknown header (new extension or IPv6/IPv4 * header for tunnels) ends loop. */ default: goto loopend; } off += hdr_off; cur = upper_proto; } #endif } #undef M_CHECK #ifdef INET6 loopend: #endif /* Just in case of real reallocation in M_CHECK() / m_pullup() */ if (m != m_old) { priv->nfinfo_realloc_mbuf++; /* Restore ip/ipv6 pointer */ if (ip != NULL) ip = (struct ip *)(mtod(m, caddr_t) + l3_off); else if (ip6 != NULL) ip6 = (struct ip6_hdr *)(mtod(m, caddr_t) + l3_off); } upper_ptr = (caddr_t)(mtod(m, caddr_t) + off); /* Determine packet input interface. Prefer configured. */ src_if_index = 0; if (hook == iface->out || iface->info.ifinfo_index == 0) { if (m->m_pkthdr.rcvif != NULL) src_if_index = m->m_pkthdr.rcvif->if_index; } else src_if_index = iface->info.ifinfo_index; /* Check packet FIB */ fib = M_GETFIB(m); if (fib >= priv->maxfibs) { CTR2(KTR_NET, "ng_netflow_rcvdata(): packet fib %d is out of " "range of available fibs: 0 .. %d", fib, priv->maxfibs); goto bypass; } if ((fe = priv_to_fib(priv, fib)) == NULL) { /* Setup new FIB */ if (ng_netflow_fib_init(priv, fib) != 0) { /* malloc() failed */ goto bypass; } fe = priv_to_fib(priv, fib); } if (ip != NULL) error = ng_netflow_flow_add(priv, fe, ip, upper_ptr, upper_proto, flags, src_if_index); #ifdef INET6 else if (ip6 != NULL) error = ng_netflow_flow6_add(priv, fe, ip6, upper_ptr, upper_proto, flags, src_if_index); #endif else goto bypass; acct = 1; bypass: if (out != NULL) { if (acct == 0) { /* Accounting failure */ if (ip != NULL) { counter_u64_add(priv->nfinfo_spackets, 1); counter_u64_add(priv->nfinfo_sbytes, m->m_pkthdr.len); } else if (ip6 != NULL) { counter_u64_add(priv->nfinfo_spackets6, 1); counter_u64_add(priv->nfinfo_sbytes6, m->m_pkthdr.len); } } /* XXX: error gets overwritten here */ NG_FWD_NEW_DATA(error, item, out, m); return (error); } done: if (item) NG_FREE_ITEM(item); if (m) NG_FREE_M(m); return (error); } /* We will be shut down in a moment */ static int ng_netflow_close(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); callout_drain(&priv->exp_callout); ng_netflow_cache_flush(priv); return (0); } /* Do local shutdown processing. */ static int ng_netflow_rmnode(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(priv->node); free(priv->fib_data, M_NETGRAPH); free(priv, M_NETGRAPH); return (0); } /* Hook disconnection. */ static int ng_netflow_disconnect(hook_p hook) { node_p node = NG_HOOK_NODE(hook); priv_p priv = NG_NODE_PRIVATE(node); iface_p iface = NG_HOOK_PRIVATE(hook); if (iface != NULL) { if (iface->hook == hook) iface->hook = NULL; if (iface->out == hook) iface->out = NULL; } /* if export hook disconnected stop running expire(). */ if (hook == priv->export) { if (priv->export9 == NULL) callout_drain(&priv->exp_callout); priv->export = NULL; } if (hook == priv->export9) { if (priv->export == NULL) callout_drain(&priv->exp_callout); priv->export9 = NULL; } /* Removal of the last link destroys the node. */ if (NG_NODE_NUMHOOKS(node) == 0) ng_rmnode_self(node); return (0); } Index: head/sys/netgraph/netflow/ng_netflow.h =================================================================== --- head/sys/netgraph/netflow/ng_netflow.h (revision 298812) +++ head/sys/netgraph/netflow/ng_netflow.h (revision 298813) @@ -1,542 +1,542 @@ /*- * Copyright (c) 2010-2011 Alexander V. Chernikov * Copyright (c) 2004-2005 Gleb Smirnoff * Copyright (c) 2001-2003 Roman V. Palagin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $SourceForge: ng_netflow.h,v 1.26 2004/09/04 15:44:55 glebius Exp $ * $FreeBSD$ */ #ifndef _NG_NETFLOW_H_ #define _NG_NETFLOW_H_ #define NG_NETFLOW_NODE_TYPE "netflow" #define NGM_NETFLOW_COOKIE 1365756954 #define NGM_NETFLOW_V9_COOKIE 1349865386 #define NG_NETFLOW_MAXIFACES USHRT_MAX /* Hook names */ #define NG_NETFLOW_HOOK_DATA "iface" #define NG_NETFLOW_HOOK_OUT "out" #define NG_NETFLOW_HOOK_EXPORT "export" #define NG_NETFLOW_HOOK_EXPORT9 "export9" /* This define effectively disable (v5) netflow export hook! */ /* #define COUNTERS_64 */ /* Netgraph commands understood by netflow node */ enum { NGM_NETFLOW_INFO = 1|NGM_READONLY|NGM_HASREPLY, /* get node info */ NGM_NETFLOW_IFINFO = 2|NGM_READONLY|NGM_HASREPLY, /* get iface info */ NGM_NETFLOW_SHOW = 3|NGM_READONLY|NGM_HASREPLY, /* show ip cache flow */ NGM_NETFLOW_SETDLT = 4, /* set data-link type */ NGM_NETFLOW_SETIFINDEX = 5, /* set interface index */ NGM_NETFLOW_SETTIMEOUTS = 6, /* set active/inactive flow timeouts */ NGM_NETFLOW_SETCONFIG = 7, /* set flow generation options */ NGM_NETFLOW_SETTEMPLATE = 8, /* set v9 flow template periodic */ NGM_NETFLOW_SETMTU = 9, /* set outgoing interface MTU */ NGM_NETFLOW_V9INFO = 10|NGM_READONLY|NGM_HASREPLY, /* get v9 info */ }; /* This structure is returned by the NGM_NETFLOW_INFO message */ struct ng_netflow_info { uint64_t nfinfo_bytes; /* accounted IPv4 bytes */ uint64_t nfinfo_packets; /* accounted IPv4 packets */ uint64_t nfinfo_bytes6; /* accounted IPv6 bytes */ uint64_t nfinfo_packets6; /* accounted IPv6 packets */ uint64_t nfinfo_sbytes; /* skipped IPv4 bytes */ uint64_t nfinfo_spackets; /* skipped IPv4 packets */ uint64_t nfinfo_sbytes6; /* skipped IPv6 bytes */ uint64_t nfinfo_spackets6; /* skipped IPv6 packets */ uint64_t nfinfo_act_exp; /* active expiries */ uint64_t nfinfo_inact_exp; /* inactive expiries */ uint32_t nfinfo_used; /* used cache records */ uint32_t nfinfo_used6; /* used IPv6 cache records */ uint32_t nfinfo_alloc_failed; /* failed allocations */ uint32_t nfinfo_export_failed; /* failed exports */ uint32_t nfinfo_export9_failed; /* failed exports */ uint32_t nfinfo_realloc_mbuf; /* reallocated mbufs */ uint32_t nfinfo_alloc_fibs; /* fibs allocated */ uint32_t nfinfo_inact_t; /* flow inactive timeout */ uint32_t nfinfo_act_t; /* flow active timeout */ }; /* Parse the info structure */ #define NG_NETFLOW_INFO_TYPE { \ { "IPv4 bytes", &ng_parse_uint64_type },\ { "IPv4 packets", &ng_parse_uint64_type },\ { "IPv6 bytes", &ng_parse_uint64_type },\ { "IPv6 packets", &ng_parse_uint64_type },\ { "IPv4 skipped bytes", &ng_parse_uint64_type },\ { "IPv4 skipped packets", &ng_parse_uint64_type },\ { "IPv6 skipped bytes", &ng_parse_uint64_type },\ { "IPv6 skipped packets", &ng_parse_uint64_type },\ { "Active expiries", &ng_parse_uint64_type },\ { "Inactive expiries", &ng_parse_uint64_type },\ { "IPv4 records used", &ng_parse_uint32_type },\ { "IPv6 records used", &ng_parse_uint32_type },\ { "Failed allocations", &ng_parse_uint32_type },\ { "V5 failed exports", &ng_parse_uint32_type },\ { "V9 failed exports", &ng_parse_uint32_type },\ { "mbuf reallocations", &ng_parse_uint32_type },\ { "fibs allocated", &ng_parse_uint32_type },\ { "Inactive timeout", &ng_parse_uint32_type },\ { "Active timeout", &ng_parse_uint32_type },\ { NULL } \ } /* This structure is returned by the NGM_NETFLOW_IFINFO message */ struct ng_netflow_ifinfo { uint32_t ifinfo_packets; /* number of packets for this iface */ uint8_t ifinfo_dlt; /* Data Link Type, DLT_XXX */ #define MAXDLTNAMELEN 20 uint16_t ifinfo_index; /* connected iface index */ uint32_t conf; }; /* This structure is passed to NGM_NETFLOW_SETDLT message */ struct ng_netflow_setdlt { uint16_t iface; /* which iface dlt change */ uint8_t dlt; /* DLT_XXX from bpf.h */ }; /* This structure is passed to NGM_NETFLOW_SETIFINDEX */ struct ng_netflow_setifindex { uint16_t iface; /* which iface index change */ uint16_t index; /* new index */ }; /* This structure is passed to NGM_NETFLOW_SETTIMEOUTS */ struct ng_netflow_settimeouts { uint32_t inactive_timeout; /* flow inactive timeout */ uint32_t active_timeout; /* flow active timeout */ }; #define NG_NETFLOW_CONF_INGRESS 0x01 /* Account on ingress */ #define NG_NETFLOW_CONF_EGRESS 0x02 /* Account on egress */ #define NG_NETFLOW_CONF_ONCE 0x04 /* Add tag to account only once */ #define NG_NETFLOW_CONF_THISONCE 0x08 /* Account once in current node */ #define NG_NETFLOW_CONF_NOSRCLOOKUP 0x10 /* No radix lookup on src */ #define NG_NETFLOW_CONF_NODSTLOOKUP 0x20 /* No radix lookup on dst */ #define NG_NETFLOW_IS_FRAG 0x01 #define NG_NETFLOW_FLOW_FLAGS (NG_NETFLOW_CONF_NOSRCLOOKUP|\ NG_NETFLOW_CONF_NODSTLOOKUP) /* This structure is passed to NGM_NETFLOW_SETCONFIG */ struct ng_netflow_setconfig { uint16_t iface; /* which iface config change */ uint32_t conf; /* new config */ }; /* This structure is passed to NGM_NETFLOW_SETTEMPLATE */ struct ng_netflow_settemplate { uint16_t time; /* max time between announce */ uint16_t packets; /* max packets between announce */ }; /* This structure is passed to NGM_NETFLOW_SETMTU */ struct ng_netflow_setmtu { uint16_t mtu; /* MTU for packet */ }; -/* This structure is used in NGM_NETFLOW_SHOW request/responce */ +/* This structure is used in NGM_NETFLOW_SHOW request/response */ struct ngnf_show_header { u_char version; /* IPv4 or IPv6 */ uint32_t hash_id; /* current hash index */ uint32_t list_id; /* current record number in hash */ uint32_t nentries; /* number of records in response */ }; /* This structure is used in NGM_NETFLOW_V9INFO message */ struct ng_netflow_v9info { uint16_t templ_packets; /* v9 template packets */ uint16_t templ_time; /* v9 template time */ uint16_t mtu; /* v9 MTU */ }; /* XXXGL * Somewhere flow_rec6 is casted to flow_rec, and flow6_entry_data is * casted to flow_entry_data. After casting, fle->r.fib is accessed. * So beginning of these structs up to fib should be kept common. */ /* This is unique data, which identifies flow */ struct flow_rec { uint16_t flow_type; uint16_t fib; struct in_addr r_src; struct in_addr r_dst; union { struct { uint16_t s_port; /* source TCP/UDP port */ uint16_t d_port; /* destination TCP/UDP port */ } dir; uint32_t both; } ports; union { struct { u_char prot; /* IP protocol */ u_char tos; /* IP TOS */ uint16_t i_ifx; /* input interface index */ } i; uint32_t all; } misc; }; /* This is unique data, which identifies flow */ struct flow6_rec { uint16_t flow_type; uint16_t fib; union { struct in_addr r_src; struct in6_addr r_src6; } src; union { struct in_addr r_dst; struct in6_addr r_dst6; } dst; union { struct { uint16_t s_port; /* source TCP/UDP port */ uint16_t d_port; /* destination TCP/UDP port */ } dir; uint32_t both; } ports; union { struct { u_char prot; /* IP protocol */ u_char tos; /* IP TOS */ uint16_t i_ifx; /* input interface index */ } i; uint32_t all; } misc; }; #define r_ip_p misc.i.prot #define r_tos misc.i.tos #define r_i_ifx misc.i.i_ifx #define r_misc misc.all #define r_ports ports.both #define r_sport ports.dir.s_port #define r_dport ports.dir.d_port /* A flow entry which accumulates statistics */ struct flow_entry_data { uint16_t version; /* Protocol version */ struct flow_rec r; struct in_addr next_hop; uint16_t fle_o_ifx; /* output interface index */ #define fle_i_ifx r.misc.i.i_ifx uint8_t dst_mask; /* destination route mask bits */ uint8_t src_mask; /* source route mask bits */ u_long packets; u_long bytes; long first; /* uptime on first packet */ long last; /* uptime on last packet */ u_char tcp_flags; /* cumulative OR */ }; struct flow6_entry_data { uint16_t version; /* Protocol version */ struct flow6_rec r; union { struct in_addr next_hop; struct in6_addr next_hop6; } n; uint16_t fle_o_ifx; /* output interface index */ #define fle_i_ifx r.misc.i.i_ifx uint8_t dst_mask; /* destination route mask bits */ uint8_t src_mask; /* source route mask bits */ u_long packets; u_long bytes; long first; /* uptime on first packet */ long last; /* uptime on last packet */ u_char tcp_flags; /* cumulative OR */ }; /* * How many flow records we will transfer at once * without overflowing socket receive buffer */ #define NREC_AT_ONCE 1000 #define NREC6_AT_ONCE (NREC_AT_ONCE * sizeof(struct flow_entry_data) / \ sizeof(struct flow6_entry_data)) #define NGRESP_SIZE (sizeof(struct ngnf_show_header) + (NREC_AT_ONCE * \ sizeof(struct flow_entry_data))) #define SORCVBUF_SIZE (NGRESP_SIZE + 2 * sizeof(struct ng_mesg)) /* Everything below is for kernel */ #ifdef _KERNEL struct flow_entry { TAILQ_ENTRY(flow_entry) fle_hash; /* entries in hash slot */ struct flow_entry_data f; }; struct flow6_entry { TAILQ_ENTRY(flow_entry) fle_hash; /* entries in hash slot */ struct flow6_entry_data f; }; /* Parsing declarations */ /* Parse the ifinfo structure */ #define NG_NETFLOW_IFINFO_TYPE { \ { "packets", &ng_parse_uint32_type },\ { "data link type", &ng_parse_uint8_type }, \ { "index", &ng_parse_uint16_type },\ { "conf", &ng_parse_uint32_type },\ { NULL } \ } /* Parse the setdlt structure */ #define NG_NETFLOW_SETDLT_TYPE { \ { "iface", &ng_parse_uint16_type }, \ { "dlt", &ng_parse_uint8_type }, \ { NULL } \ } /* Parse the setifindex structure */ #define NG_NETFLOW_SETIFINDEX_TYPE { \ { "iface", &ng_parse_uint16_type }, \ { "index", &ng_parse_uint16_type }, \ { NULL } \ } /* Parse the settimeouts structure */ #define NG_NETFLOW_SETTIMEOUTS_TYPE { \ { "inactive", &ng_parse_uint32_type }, \ { "active", &ng_parse_uint32_type }, \ { NULL } \ } /* Parse the setifindex structure */ #define NG_NETFLOW_SETCONFIG_TYPE { \ { "iface", &ng_parse_uint16_type }, \ { "conf", &ng_parse_uint32_type }, \ { NULL } \ } /* Parse the settemplate structure */ #define NG_NETFLOW_SETTEMPLATE_TYPE { \ { "time", &ng_parse_uint16_type }, \ { "packets", &ng_parse_uint16_type }, \ { NULL } \ } /* Parse the setmtu structure */ #define NG_NETFLOW_SETMTU_TYPE { \ { "mtu", &ng_parse_uint16_type }, \ { NULL } \ } /* Parse the v9info structure */ #define NG_NETFLOW_V9INFO_TYPE { \ { "v9 template packets", &ng_parse_uint16_type },\ { "v9 template time", &ng_parse_uint16_type },\ { "v9 MTU", &ng_parse_uint16_type },\ { NULL } \ } /* Private hook data */ struct ng_netflow_iface { hook_p hook; /* NULL when disconnected */ hook_p out; /* NULL when no bypass hook */ struct ng_netflow_ifinfo info; }; typedef struct ng_netflow_iface *iface_p; typedef struct ng_netflow_ifinfo *ifinfo_p; struct netflow_export_item { item_p item; item_p item9; struct netflow_v9_packet_opt *item9_opt; }; /* Structure contatining fib-specific data */ struct fib_export { uint32_t fib; /* kernel fib id */ /* Various data used for export */ struct netflow_export_item exp; struct mtx export_mtx; /* exp.item mutex */ struct mtx export9_mtx; /* exp.item9 mutex */ uint32_t flow_seq; /* current V5 flow sequence */ uint32_t flow9_seq; /* current V9 flow sequence */ uint32_t domain_id; /* Observartion domain id */ /* Netflow V9 counters */ uint32_t templ_last_ts; /* unixtime of last template announce */ uint32_t templ_last_pkt; /* packet count on last announce */ uint32_t sent_packets; /* packets sent by exporter; */ /* Current packet specific options */ struct netflow_v9_packet_opt *export9_opt; }; typedef struct fib_export *fib_export_p; /* Structure describing our flow engine */ struct netflow { node_p node; /* link to the node itself */ hook_p export; /* export data goes there */ hook_p export9; /* Netflow V9 export data goes there */ struct callout exp_callout; /* expiry periodic job */ /* * Flow entries are allocated in uma(9) zone zone. They are * indexed by hash hash. Each hash element consist of tailqueue * head and mutex to protect this element. */ #define CACHESIZE (65536*16) #define CACHELOWAT (CACHESIZE * 3/4) #define CACHEHIGHWAT (CACHESIZE * 9/10) uma_zone_t zone; struct flow_hash_entry *hash; /* * NetFlow data export * * export_item is a data item, it has an mbuf with cluster * attached to it. A thread detaches export_item from priv * and works with it. If the export is full it is sent, and * a new one is allocated. Before exiting thread re-attaches * its current item back to priv. If there is item already, * current incomplete datagram is sent. * export_mtx is used for attaching/detaching. */ /* IPv6 support */ #ifdef INET6 uma_zone_t zone6; struct flow_hash_entry *hash6; #endif /* Statistics. */ counter_u64_t nfinfo_bytes; /* accounted IPv4 bytes */ counter_u64_t nfinfo_packets; /* accounted IPv4 packets */ counter_u64_t nfinfo_bytes6; /* accounted IPv6 bytes */ counter_u64_t nfinfo_packets6; /* accounted IPv6 packets */ counter_u64_t nfinfo_sbytes; /* skipped IPv4 bytes */ counter_u64_t nfinfo_spackets; /* skipped IPv4 packets */ counter_u64_t nfinfo_sbytes6; /* skipped IPv6 bytes */ counter_u64_t nfinfo_spackets6; /* skipped IPv6 packets */ counter_u64_t nfinfo_act_exp; /* active expiries */ counter_u64_t nfinfo_inact_exp; /* inactive expiries */ uint32_t nfinfo_alloc_failed; /* failed allocations */ uint32_t nfinfo_export_failed; /* failed exports */ uint32_t nfinfo_export9_failed; /* failed exports */ uint32_t nfinfo_realloc_mbuf; /* reallocated mbufs */ uint32_t nfinfo_alloc_fibs; /* fibs allocated */ uint32_t nfinfo_inact_t; /* flow inactive timeout */ uint32_t nfinfo_act_t; /* flow active timeout */ /* Multiple FIB support */ fib_export_p *fib_data; /* vector to per-fib data */ uint16_t maxfibs; /* number of allocated fibs */ /* Netflow v9 configuration options */ /* * RFC 3954 clause 7.3 * "Both options MUST be configurable by the user on the Exporter." */ uint16_t templ_time; /* time between sending templates */ uint16_t templ_packets; /* packets between sending templates */ #define NETFLOW_V9_MAX_FLOWSETS 2 u_char flowsets_count; /* current flowsets used */ /* Count of records in each flowset */ u_char flowset_records[NETFLOW_V9_MAX_FLOWSETS - 1]; uint16_t mtu; /* export interface MTU */ /* Pointers to pre-compiled flowsets */ struct netflow_v9_flowset_header *v9_flowsets[NETFLOW_V9_MAX_FLOWSETS - 1]; struct ng_netflow_iface ifaces[NG_NETFLOW_MAXIFACES]; }; typedef struct netflow *priv_p; /* Header of a small list in hash cell */ struct flow_hash_entry { struct mtx mtx; TAILQ_HEAD(fhead, flow_entry) head; }; #define ERROUT(x) { error = (x); goto done; } #define MTAG_NETFLOW 1221656444 #define MTAG_NETFLOW_CALLED 0 #define m_pktlen(m) ((m)->m_pkthdr.len) #define IP6VERSION 6 #define priv_to_fib(priv, fib) (priv)->fib_data[(fib)] /* * Cisco uses milliseconds for uptime. Bad idea, since it overflows * every 48+ days. But we will do same to keep compatibility. This macro * does overflowable multiplication to 1000. */ #define MILLIUPTIME(t) (((t) << 9) + /* 512 */ \ ((t) << 8) + /* 256 */ \ ((t) << 7) + /* 128 */ \ ((t) << 6) + /* 64 */ \ ((t) << 5) + /* 32 */ \ ((t) << 3)) /* 8 */ /* Prototypes for netflow.c */ void ng_netflow_cache_init(priv_p); void ng_netflow_cache_flush(priv_p); int ng_netflow_fib_init(priv_p priv, int fib); void ng_netflow_copyinfo(priv_p, struct ng_netflow_info *); void ng_netflow_copyv9info(priv_p, struct ng_netflow_v9info *); timeout_t ng_netflow_expire; int ng_netflow_flow_add(priv_p, fib_export_p, struct ip *, caddr_t, uint8_t, uint8_t, unsigned int); int ng_netflow_flow6_add(priv_p, fib_export_p, struct ip6_hdr *, caddr_t, uint8_t, uint8_t, unsigned int); int ng_netflow_flow_show(priv_p, struct ngnf_show_header *req, struct ngnf_show_header *resp); void ng_netflow_v9_cache_init(priv_p); void ng_netflow_v9_cache_flush(priv_p); item_p get_export9_dgram(priv_p, fib_export_p, struct netflow_v9_packet_opt **); void return_export9_dgram(priv_p, fib_export_p, item_p, struct netflow_v9_packet_opt *, int); int export9_add(item_p, struct netflow_v9_packet_opt *, struct flow_entry *); int export9_send(priv_p, fib_export_p, item_p, struct netflow_v9_packet_opt *, int); #endif /* _KERNEL */ #endif /* _NG_NETFLOW_H_ */ Index: head/sys/netgraph/netgraph.h =================================================================== --- head/sys/netgraph/netgraph.h (revision 298812) +++ head/sys/netgraph/netgraph.h (revision 298813) @@ -1,1219 +1,1219 @@ /* * netgraph.h */ /*- * Copyright (c) 1996-1999 Whistle Communications, Inc. * All rights reserved. * * Subject to the following obligations and disclaimer of warranty, use and * redistribution of this software, in source or object code forms, with or * without modifications are expressly permitted by Whistle Communications; * provided, however, that: * 1. Any and all reproductions of the source or object code must include the * copyright notice above and the following disclaimer of warranties; and * 2. No rights are granted, in any manner or form, to use Whistle * Communications, Inc. trademarks, including the mark "WHISTLE * COMMUNICATIONS" on advertising, endorsements, or otherwise except as * such appears in the above copyright notice or in the software. * * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * Author: Julian Elischer * * $FreeBSD$ * $Whistle: netgraph.h,v 1.29 1999/11/01 07:56:13 julian Exp $ */ #ifndef _NETGRAPH_NETGRAPH_H_ #define _NETGRAPH_NETGRAPH_H_ #ifndef _KERNEL #error "This file should not be included in user level programs" #endif #include #include #include #include #include #include #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_netgraph.h" #include "opt_kdb.h" #endif /* debugging options */ #define NG_SEPARATE_MALLOC /* make modules use their own malloc types */ /* * This defines the in-kernel binary interface version. * It is possible to change this but leave the external message * API the same. Each type also has it's own cookies for versioning as well. * Change it for NETGRAPH_DEBUG version so we cannot mix debug and non debug * modules. */ #define _NG_ABI_VERSION 12 #ifdef NETGRAPH_DEBUG /*----------------------------------------------*/ #define NG_ABI_VERSION (_NG_ABI_VERSION + 0x10000) #else /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ #define NG_ABI_VERSION _NG_ABI_VERSION #endif /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ /* * Forward references for the basic structures so we can * define the typedefs and use them in the structures themselves. */ struct ng_hook ; struct ng_node ; struct ng_item ; typedef struct ng_item *item_p; typedef struct ng_node *node_p; typedef struct ng_hook *hook_p; /* node method definitions */ typedef int ng_constructor_t(node_p node); typedef int ng_close_t(node_p node); typedef int ng_shutdown_t(node_p node); typedef int ng_newhook_t(node_p node, hook_p hook, const char *name); typedef hook_p ng_findhook_t(node_p node, const char *name); typedef int ng_connect_t(hook_p hook); typedef int ng_rcvmsg_t(node_p node, item_p item, hook_p lasthook); typedef int ng_rcvdata_t(hook_p hook, item_p item); typedef int ng_disconnect_t(hook_p hook); typedef int ng_rcvitem (node_p node, hook_p hook, item_p item); /*********************************************************************** ***************** Hook Structure and Methods ************************** *********************************************************************** * * Structure of a hook */ struct ng_hook { char hk_name[NG_HOOKSIZ]; /* what this node knows this link as */ - void *hk_private; /* node dependant ID for this hook */ + void *hk_private; /* node dependent ID for this hook */ int hk_flags; /* info about this hook/link */ int hk_type; /* tbd: hook data link type */ struct ng_hook *hk_peer; /* the other end of this link */ struct ng_node *hk_node; /* The node this hook is attached to */ LIST_ENTRY(ng_hook) hk_hooks; /* linked list of all hooks on node */ ng_rcvmsg_t *hk_rcvmsg; /* control messages come here */ ng_rcvdata_t *hk_rcvdata; /* data comes here */ int hk_refs; /* dont actually free this till 0 */ #ifdef NETGRAPH_DEBUG /*----------------------------------------------*/ #define HK_MAGIC 0x78573011 int hk_magic; char *lastfile; int lastline; SLIST_ENTRY(ng_hook) hk_all; /* all existing items */ #endif /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ }; /* Flags for a hook */ #define HK_INVALID 0x0001 /* don't trust it! */ #define HK_QUEUE 0x0002 /* queue for later delivery */ #define HK_FORCE_WRITER 0x0004 /* Incoming data queued as a writer */ #define HK_DEAD 0x0008 /* This is the dead hook.. don't free */ #define HK_HI_STACK 0x0010 /* Hook has hi stack usage */ #define HK_TO_INBOUND 0x0020 /* Hook on ntw. stack inbound path. */ /* * Public Methods for hook * If you can't do it with these you probably shouldn;t be doing it. */ void ng_unref_hook(hook_p hook); /* don't move this */ #define _NG_HOOK_REF(hook) refcount_acquire(&(hook)->hk_refs) #define _NG_HOOK_NAME(hook) ((hook)->hk_name) #define _NG_HOOK_UNREF(hook) ng_unref_hook(hook) #define _NG_HOOK_SET_PRIVATE(hook, val) do {(hook)->hk_private = val;} while (0) #define _NG_HOOK_SET_RCVMSG(hook, val) do {(hook)->hk_rcvmsg = val;} while (0) #define _NG_HOOK_SET_RCVDATA(hook, val) do {(hook)->hk_rcvdata = val;} while (0) #define _NG_HOOK_PRIVATE(hook) ((hook)->hk_private) #define _NG_HOOK_NOT_VALID(hook) ((hook)->hk_flags & HK_INVALID) #define _NG_HOOK_IS_VALID(hook) (!((hook)->hk_flags & HK_INVALID)) #define _NG_HOOK_NODE(hook) ((hook)->hk_node) /* only rvalue! */ #define _NG_HOOK_PEER(hook) ((hook)->hk_peer) /* only rvalue! */ #define _NG_HOOK_FORCE_WRITER(hook) \ do { hook->hk_flags |= HK_FORCE_WRITER; } while (0) #define _NG_HOOK_FORCE_QUEUE(hook) do { hook->hk_flags |= HK_QUEUE; } while (0) #define _NG_HOOK_SET_TO_INBOUND(hook) \ do { hook->hk_flags |= HK_TO_INBOUND; } while (0) #define _NG_HOOK_HI_STACK(hook) do { hook->hk_flags |= HK_HI_STACK; } while (0) /* Some shortcuts */ #define NG_PEER_NODE(hook) NG_HOOK_NODE(NG_HOOK_PEER(hook)) #define NG_PEER_HOOK_NAME(hook) NG_HOOK_NAME(NG_HOOK_PEER(hook)) #define NG_PEER_NODE_NAME(hook) NG_NODE_NAME(NG_PEER_NODE(hook)) #ifdef NETGRAPH_DEBUG /*----------------------------------------------*/ #define _NN_ __FILE__,__LINE__ void dumphook (hook_p hook, char *file, int line); static __inline void _chkhook(hook_p hook, char *file, int line); static __inline void _ng_hook_ref(hook_p hook, char * file, int line); static __inline char * _ng_hook_name(hook_p hook, char * file, int line); static __inline void _ng_hook_unref(hook_p hook, char * file, int line); static __inline void _ng_hook_set_private(hook_p hook, void * val, char * file, int line); static __inline void _ng_hook_set_rcvmsg(hook_p hook, ng_rcvmsg_t *val, char * file, int line); static __inline void _ng_hook_set_rcvdata(hook_p hook, ng_rcvdata_t *val, char * file, int line); static __inline void * _ng_hook_private(hook_p hook, char * file, int line); static __inline int _ng_hook_not_valid(hook_p hook, char * file, int line); static __inline int _ng_hook_is_valid(hook_p hook, char * file, int line); static __inline node_p _ng_hook_node(hook_p hook, char * file, int line); static __inline hook_p _ng_hook_peer(hook_p hook, char * file, int line); static __inline void _ng_hook_force_writer(hook_p hook, char * file, int line); static __inline void _ng_hook_force_queue(hook_p hook, char * file, int line); static __inline void _ng_hook_set_to_inbound(hook_p hook, char * file, int line); static __inline void _chkhook(hook_p hook, char *file, int line) { if (hook->hk_magic != HK_MAGIC) { printf("Accessing freed "); dumphook(hook, file, line); } hook->lastline = line; hook->lastfile = file; } static __inline void _ng_hook_ref(hook_p hook, char * file, int line) { _chkhook(hook, file, line); _NG_HOOK_REF(hook); } static __inline char * _ng_hook_name(hook_p hook, char * file, int line) { _chkhook(hook, file, line); return (_NG_HOOK_NAME(hook)); } static __inline void _ng_hook_unref(hook_p hook, char * file, int line) { _chkhook(hook, file, line); _NG_HOOK_UNREF(hook); } static __inline void _ng_hook_set_private(hook_p hook, void *val, char * file, int line) { _chkhook(hook, file, line); _NG_HOOK_SET_PRIVATE(hook, val); } static __inline void _ng_hook_set_rcvmsg(hook_p hook, ng_rcvmsg_t *val, char * file, int line) { _chkhook(hook, file, line); _NG_HOOK_SET_RCVMSG(hook, val); } static __inline void _ng_hook_set_rcvdata(hook_p hook, ng_rcvdata_t *val, char * file, int line) { _chkhook(hook, file, line); _NG_HOOK_SET_RCVDATA(hook, val); } static __inline void * _ng_hook_private(hook_p hook, char * file, int line) { _chkhook(hook, file, line); return (_NG_HOOK_PRIVATE(hook)); } static __inline int _ng_hook_not_valid(hook_p hook, char * file, int line) { _chkhook(hook, file, line); return (_NG_HOOK_NOT_VALID(hook)); } static __inline int _ng_hook_is_valid(hook_p hook, char * file, int line) { _chkhook(hook, file, line); return (_NG_HOOK_IS_VALID(hook)); } static __inline node_p _ng_hook_node(hook_p hook, char * file, int line) { _chkhook(hook, file, line); return (_NG_HOOK_NODE(hook)); } static __inline hook_p _ng_hook_peer(hook_p hook, char * file, int line) { _chkhook(hook, file, line); return (_NG_HOOK_PEER(hook)); } static __inline void _ng_hook_force_writer(hook_p hook, char * file, int line) { _chkhook(hook, file, line); _NG_HOOK_FORCE_WRITER(hook); } static __inline void _ng_hook_force_queue(hook_p hook, char * file, int line) { _chkhook(hook, file, line); _NG_HOOK_FORCE_QUEUE(hook); } static __inline void _ng_hook_set_to_inbound(hook_p hook, char * file, int line) { _chkhook(hook, file, line); _NG_HOOK_SET_TO_INBOUND(hook); } static __inline void _ng_hook_hi_stack(hook_p hook, char * file, int line) { _chkhook(hook, file, line); _NG_HOOK_HI_STACK(hook); } #define NG_HOOK_REF(hook) _ng_hook_ref(hook, _NN_) #define NG_HOOK_NAME(hook) _ng_hook_name(hook, _NN_) #define NG_HOOK_UNREF(hook) _ng_hook_unref(hook, _NN_) #define NG_HOOK_SET_PRIVATE(hook, val) _ng_hook_set_private(hook, val, _NN_) #define NG_HOOK_SET_RCVMSG(hook, val) _ng_hook_set_rcvmsg(hook, val, _NN_) #define NG_HOOK_SET_RCVDATA(hook, val) _ng_hook_set_rcvdata(hook, val, _NN_) #define NG_HOOK_PRIVATE(hook) _ng_hook_private(hook, _NN_) #define NG_HOOK_NOT_VALID(hook) _ng_hook_not_valid(hook, _NN_) #define NG_HOOK_IS_VALID(hook) _ng_hook_is_valid(hook, _NN_) #define NG_HOOK_NODE(hook) _ng_hook_node(hook, _NN_) #define NG_HOOK_PEER(hook) _ng_hook_peer(hook, _NN_) #define NG_HOOK_FORCE_WRITER(hook) _ng_hook_force_writer(hook, _NN_) #define NG_HOOK_FORCE_QUEUE(hook) _ng_hook_force_queue(hook, _NN_) #define NG_HOOK_SET_TO_INBOUND(hook) _ng_hook_set_to_inbound(hook, _NN_) #define NG_HOOK_HI_STACK(hook) _ng_hook_hi_stack(hook, _NN_) #else /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ #define NG_HOOK_REF(hook) _NG_HOOK_REF(hook) #define NG_HOOK_NAME(hook) _NG_HOOK_NAME(hook) #define NG_HOOK_UNREF(hook) _NG_HOOK_UNREF(hook) #define NG_HOOK_SET_PRIVATE(hook, val) _NG_HOOK_SET_PRIVATE(hook, val) #define NG_HOOK_SET_RCVMSG(hook, val) _NG_HOOK_SET_RCVMSG(hook, val) #define NG_HOOK_SET_RCVDATA(hook, val) _NG_HOOK_SET_RCVDATA(hook, val) #define NG_HOOK_PRIVATE(hook) _NG_HOOK_PRIVATE(hook) #define NG_HOOK_NOT_VALID(hook) _NG_HOOK_NOT_VALID(hook) #define NG_HOOK_IS_VALID(hook) _NG_HOOK_IS_VALID(hook) #define NG_HOOK_NODE(hook) _NG_HOOK_NODE(hook) #define NG_HOOK_PEER(hook) _NG_HOOK_PEER(hook) #define NG_HOOK_FORCE_WRITER(hook) _NG_HOOK_FORCE_WRITER(hook) #define NG_HOOK_FORCE_QUEUE(hook) _NG_HOOK_FORCE_QUEUE(hook) #define NG_HOOK_SET_TO_INBOUND(hook) _NG_HOOK_SET_TO_INBOUND(hook) #define NG_HOOK_HI_STACK(hook) _NG_HOOK_HI_STACK(hook) #endif /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ /*********************************************************************** ***************** Node Structure and Methods ************************** *********************************************************************** * Structure of a node * including the eembedded queue structure. * * The structure for queueing Netgraph request items * embedded in the node structure */ struct ng_queue { u_int q_flags; /* Current r/w/q lock flags */ u_int q_flags2; /* Other queue flags */ struct mtx q_mtx; STAILQ_ENTRY(ng_node) q_work; /* nodes with work to do */ STAILQ_HEAD(, ng_item) queue; /* actually items queue */ }; struct ng_node { char nd_name[NG_NODESIZ]; /* optional globally unique name */ struct ng_type *nd_type; /* the installed 'type' */ int nd_flags; /* see below for bit definitions */ int nd_numhooks; /* number of hooks */ - void *nd_private; /* node type dependant node ID */ + void *nd_private; /* node type dependent node ID */ ng_ID_t nd_ID; /* Unique per node */ LIST_HEAD(hooks, ng_hook) nd_hooks; /* linked list of node hooks */ LIST_ENTRY(ng_node) nd_nodes; /* name hash collision list */ LIST_ENTRY(ng_node) nd_idnodes; /* ID hash collision list */ struct ng_queue nd_input_queue; /* input queue for locking */ int nd_refs; /* # of references to this node */ struct vnet *nd_vnet; /* network stack instance */ #ifdef NETGRAPH_DEBUG /*----------------------------------------------*/ #define ND_MAGIC 0x59264837 int nd_magic; char *lastfile; int lastline; SLIST_ENTRY(ng_node) nd_all; /* all existing nodes */ #endif /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ }; /* Flags for a node */ #define NGF_INVALID 0x00000001 /* free when refs go to 0 */ #define NG_INVALID NGF_INVALID /* compat for old code */ #define NGF_FORCE_WRITER 0x00000004 /* Never multithread this node */ #define NG_FORCE_WRITER NGF_FORCE_WRITER /* compat for old code */ #define NGF_CLOSING 0x00000008 /* ng_rmnode() at work */ #define NG_CLOSING NGF_CLOSING /* compat for old code */ #define NGF_REALLY_DIE 0x00000010 /* "persistent" node is unloading */ #define NG_REALLY_DIE NGF_REALLY_DIE /* compat for old code */ #define NGF_HI_STACK 0x00000020 /* node has hi stack usage */ #define NGF_TYPE1 0x10000000 /* reserved for type specific storage */ #define NGF_TYPE2 0x20000000 /* reserved for type specific storage */ #define NGF_TYPE3 0x40000000 /* reserved for type specific storage */ #define NGF_TYPE4 0x80000000 /* reserved for type specific storage */ /* * Public methods for nodes. * If you can't do it with these you probably shouldn't be doing it. */ void ng_unref_node(node_p node); /* don't move this */ #define _NG_NODE_NAME(node) ((node)->nd_name + 0) #define _NG_NODE_HAS_NAME(node) ((node)->nd_name[0] + 0) #define _NG_NODE_ID(node) ((node)->nd_ID + 0) #define _NG_NODE_REF(node) refcount_acquire(&(node)->nd_refs) #define _NG_NODE_UNREF(node) ng_unref_node(node) #define _NG_NODE_SET_PRIVATE(node, val) do {(node)->nd_private = val;} while (0) #define _NG_NODE_PRIVATE(node) ((node)->nd_private) #define _NG_NODE_IS_VALID(node) (!((node)->nd_flags & NGF_INVALID)) #define _NG_NODE_NOT_VALID(node) ((node)->nd_flags & NGF_INVALID) #define _NG_NODE_NUMHOOKS(node) ((node)->nd_numhooks + 0) /* rvalue */ #define _NG_NODE_FORCE_WRITER(node) \ do{ node->nd_flags |= NGF_FORCE_WRITER; }while (0) #define _NG_NODE_HI_STACK(node) \ do{ node->nd_flags |= NGF_HI_STACK; }while (0) #define _NG_NODE_REALLY_DIE(node) \ do{ node->nd_flags |= (NGF_REALLY_DIE|NGF_INVALID); }while (0) #define _NG_NODE_REVIVE(node) \ do { node->nd_flags &= ~NGF_INVALID; } while (0) /* * The hook iterator. * This macro will call a function of type ng_fn_eachhook for each * hook attached to the node. If the function returns 0, then the * iterator will stop and return a pointer to the hook that returned 0. */ typedef int ng_fn_eachhook(hook_p hook, void* arg); #define _NG_NODE_FOREACH_HOOK(node, fn, arg, rethook) \ do { \ hook_p _hook; \ (rethook) = NULL; \ LIST_FOREACH(_hook, &((node)->nd_hooks), hk_hooks) { \ if ((fn)(_hook, arg) == 0) { \ (rethook) = _hook; \ break; \ } \ } \ } while (0) #ifdef NETGRAPH_DEBUG /*----------------------------------------------*/ void dumpnode(node_p node, char *file, int line); static __inline void _chknode(node_p node, char *file, int line); static __inline char * _ng_node_name(node_p node, char *file, int line); static __inline int _ng_node_has_name(node_p node, char *file, int line); static __inline ng_ID_t _ng_node_id(node_p node, char *file, int line); static __inline void _ng_node_ref(node_p node, char *file, int line); static __inline void _ng_node_unref(node_p node, char *file, int line); static __inline void _ng_node_set_private(node_p node, void * val, char *file, int line); static __inline void * _ng_node_private(node_p node, char *file, int line); static __inline int _ng_node_is_valid(node_p node, char *file, int line); static __inline int _ng_node_not_valid(node_p node, char *file, int line); static __inline int _ng_node_numhooks(node_p node, char *file, int line); static __inline void _ng_node_force_writer(node_p node, char *file, int line); static __inline hook_p _ng_node_foreach_hook(node_p node, ng_fn_eachhook *fn, void *arg, char *file, int line); static __inline void _ng_node_revive(node_p node, char *file, int line); static __inline void _chknode(node_p node, char *file, int line) { if (node->nd_magic != ND_MAGIC) { printf("Accessing freed "); dumpnode(node, file, line); } node->lastline = line; node->lastfile = file; } static __inline char * _ng_node_name(node_p node, char *file, int line) { _chknode(node, file, line); return(_NG_NODE_NAME(node)); } static __inline int _ng_node_has_name(node_p node, char *file, int line) { _chknode(node, file, line); return(_NG_NODE_HAS_NAME(node)); } static __inline ng_ID_t _ng_node_id(node_p node, char *file, int line) { _chknode(node, file, line); return(_NG_NODE_ID(node)); } static __inline void _ng_node_ref(node_p node, char *file, int line) { _chknode(node, file, line); _NG_NODE_REF(node); } static __inline void _ng_node_unref(node_p node, char *file, int line) { _chknode(node, file, line); _NG_NODE_UNREF(node); } static __inline void _ng_node_set_private(node_p node, void * val, char *file, int line) { _chknode(node, file, line); _NG_NODE_SET_PRIVATE(node, val); } static __inline void * _ng_node_private(node_p node, char *file, int line) { _chknode(node, file, line); return (_NG_NODE_PRIVATE(node)); } static __inline int _ng_node_is_valid(node_p node, char *file, int line) { _chknode(node, file, line); return(_NG_NODE_IS_VALID(node)); } static __inline int _ng_node_not_valid(node_p node, char *file, int line) { _chknode(node, file, line); return(_NG_NODE_NOT_VALID(node)); } static __inline int _ng_node_numhooks(node_p node, char *file, int line) { _chknode(node, file, line); return(_NG_NODE_NUMHOOKS(node)); } static __inline void _ng_node_force_writer(node_p node, char *file, int line) { _chknode(node, file, line); _NG_NODE_FORCE_WRITER(node); } static __inline void _ng_node_hi_stack(node_p node, char *file, int line) { _chknode(node, file, line); _NG_NODE_HI_STACK(node); } static __inline void _ng_node_really_die(node_p node, char *file, int line) { _chknode(node, file, line); _NG_NODE_REALLY_DIE(node); } static __inline void _ng_node_revive(node_p node, char *file, int line) { _chknode(node, file, line); _NG_NODE_REVIVE(node); } static __inline hook_p _ng_node_foreach_hook(node_p node, ng_fn_eachhook *fn, void *arg, char *file, int line) { hook_p hook; _chknode(node, file, line); _NG_NODE_FOREACH_HOOK(node, fn, arg, hook); return (hook); } #define NG_NODE_NAME(node) _ng_node_name(node, _NN_) #define NG_NODE_HAS_NAME(node) _ng_node_has_name(node, _NN_) #define NG_NODE_ID(node) _ng_node_id(node, _NN_) #define NG_NODE_REF(node) _ng_node_ref(node, _NN_) #define NG_NODE_UNREF(node) _ng_node_unref(node, _NN_) #define NG_NODE_SET_PRIVATE(node, val) _ng_node_set_private(node, val, _NN_) #define NG_NODE_PRIVATE(node) _ng_node_private(node, _NN_) #define NG_NODE_IS_VALID(node) _ng_node_is_valid(node, _NN_) #define NG_NODE_NOT_VALID(node) _ng_node_not_valid(node, _NN_) #define NG_NODE_FORCE_WRITER(node) _ng_node_force_writer(node, _NN_) #define NG_NODE_HI_STACK(node) _ng_node_hi_stack(node, _NN_) #define NG_NODE_REALLY_DIE(node) _ng_node_really_die(node, _NN_) #define NG_NODE_NUMHOOKS(node) _ng_node_numhooks(node, _NN_) #define NG_NODE_REVIVE(node) _ng_node_revive(node, _NN_) #define NG_NODE_FOREACH_HOOK(node, fn, arg, rethook) \ do { \ rethook = _ng_node_foreach_hook(node, fn, (void *)arg, _NN_); \ } while (0) #else /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ #define NG_NODE_NAME(node) _NG_NODE_NAME(node) #define NG_NODE_HAS_NAME(node) _NG_NODE_HAS_NAME(node) #define NG_NODE_ID(node) _NG_NODE_ID(node) #define NG_NODE_REF(node) _NG_NODE_REF(node) #define NG_NODE_UNREF(node) _NG_NODE_UNREF(node) #define NG_NODE_SET_PRIVATE(node, val) _NG_NODE_SET_PRIVATE(node, val) #define NG_NODE_PRIVATE(node) _NG_NODE_PRIVATE(node) #define NG_NODE_IS_VALID(node) _NG_NODE_IS_VALID(node) #define NG_NODE_NOT_VALID(node) _NG_NODE_NOT_VALID(node) #define NG_NODE_FORCE_WRITER(node) _NG_NODE_FORCE_WRITER(node) #define NG_NODE_HI_STACK(node) _NG_NODE_HI_STACK(node) #define NG_NODE_REALLY_DIE(node) _NG_NODE_REALLY_DIE(node) #define NG_NODE_NUMHOOKS(node) _NG_NODE_NUMHOOKS(node) #define NG_NODE_REVIVE(node) _NG_NODE_REVIVE(node) #define NG_NODE_FOREACH_HOOK(node, fn, arg, rethook) \ _NG_NODE_FOREACH_HOOK(node, fn, arg, rethook) #endif /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ /*********************************************************************** ************* Node Queue and Item Structures and Methods ************** *********************************************************************** * */ typedef void ng_item_fn(node_p node, hook_p hook, void *arg1, int arg2); typedef int ng_item_fn2(node_p node, struct ng_item *item, hook_p hook); typedef void ng_apply_t(void *context, int error); struct ng_apply_info { ng_apply_t *apply; void *context; int refs; int error; }; struct ng_item { u_long el_flags; STAILQ_ENTRY(ng_item) el_next; node_p el_dest; /* The node it will be applied against (or NULL) */ hook_p el_hook; /* Entering hook. Optional in Control messages */ union { struct mbuf *da_m; struct { struct ng_mesg *msg_msg; ng_ID_t msg_retaddr; } msg; struct { union { ng_item_fn *fn_fn; ng_item_fn2 *fn_fn2; } fn_fn; void *fn_arg1; int fn_arg2; } fn; } body; /* * Optional callback called when item is being applied, * and its context. */ struct ng_apply_info *apply; u_int depth; #ifdef NETGRAPH_DEBUG /*----------------------------------------------*/ char *lastfile; int lastline; TAILQ_ENTRY(ng_item) all; /* all existing items */ #endif /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ }; #define NGQF_TYPE 0x03 /* MASK of content definition */ #define NGQF_MESG 0x00 /* the queue element is a message */ #define NGQF_DATA 0x01 /* the queue element is data */ #define NGQF_FN 0x02 /* the queue element is a function */ #define NGQF_FN2 0x03 /* the queue element is a new function */ #define NGQF_RW 0x04 /* MASK for wanted queue mode */ #define NGQF_READER 0x04 /* wants to be a reader */ #define NGQF_WRITER 0x00 /* wants to be a writer */ #define NGQF_QMODE 0x08 /* MASK for how it was queued */ #define NGQF_QREADER 0x08 /* was queued as a reader */ #define NGQF_QWRITER 0x00 /* was queued as a writer */ /* * Get the mbuf (etc) out of an item. * Sets the value in the item to NULL in case we need to call NG_FREE_ITEM() * with it, (to avoid freeing the things twice). * If you don't want to zero out the item then realise that the * item still owns it. * Retaddr is different. There are no references on that. It's just a number. * The debug versions must be either all used everywhere or not at all. */ #define _NGI_M(i) ((i)->body.da_m) #define _NGI_MSG(i) ((i)->body.msg.msg_msg) #define _NGI_RETADDR(i) ((i)->body.msg.msg_retaddr) #define _NGI_FN(i) ((i)->body.fn.fn_fn.fn_fn) #define _NGI_FN2(i) ((i)->body.fn.fn_fn.fn_fn2) #define _NGI_ARG1(i) ((i)->body.fn.fn_arg1) #define _NGI_ARG2(i) ((i)->body.fn.fn_arg2) #define _NGI_NODE(i) ((i)->el_dest) #define _NGI_HOOK(i) ((i)->el_hook) #define _NGI_SET_HOOK(i,h) do { _NGI_HOOK(i) = h; h = NULL;} while (0) #define _NGI_CLR_HOOK(i) do { \ hook_p _hook = _NGI_HOOK(i); \ if (_hook) { \ _NG_HOOK_UNREF(_hook); \ _NGI_HOOK(i) = NULL; \ } \ } while (0) #define _NGI_SET_NODE(i,n) do { _NGI_NODE(i) = n; n = NULL;} while (0) #define _NGI_CLR_NODE(i) do { \ node_p _node = _NGI_NODE(i); \ if (_node) { \ _NG_NODE_UNREF(_node); \ _NGI_NODE(i) = NULL; \ } \ } while (0) #ifdef NETGRAPH_DEBUG /*----------------------------------------------*/ void dumpitem(item_p item, char *file, int line); static __inline void _ngi_check(item_p item, char *file, int line) ; static __inline struct mbuf ** _ngi_m(item_p item, char *file, int line) ; static __inline ng_ID_t * _ngi_retaddr(item_p item, char *file, int line); static __inline struct ng_mesg ** _ngi_msg(item_p item, char *file, int line) ; static __inline ng_item_fn ** _ngi_fn(item_p item, char *file, int line) ; static __inline ng_item_fn2 ** _ngi_fn2(item_p item, char *file, int line) ; static __inline void ** _ngi_arg1(item_p item, char *file, int line) ; static __inline int * _ngi_arg2(item_p item, char *file, int line) ; static __inline node_p _ngi_node(item_p item, char *file, int line); static __inline hook_p _ngi_hook(item_p item, char *file, int line); static __inline void _ngi_check(item_p item, char *file, int line) { (item)->lastline = line; (item)->lastfile = file; } static __inline struct mbuf ** _ngi_m(item_p item, char *file, int line) { _ngi_check(item, file, line); return (&_NGI_M(item)); } static __inline struct ng_mesg ** _ngi_msg(item_p item, char *file, int line) { _ngi_check(item, file, line); return (&_NGI_MSG(item)); } static __inline ng_ID_t * _ngi_retaddr(item_p item, char *file, int line) { _ngi_check(item, file, line); return (&_NGI_RETADDR(item)); } static __inline ng_item_fn ** _ngi_fn(item_p item, char *file, int line) { _ngi_check(item, file, line); return (&_NGI_FN(item)); } static __inline ng_item_fn2 ** _ngi_fn2(item_p item, char *file, int line) { _ngi_check(item, file, line); return (&_NGI_FN2(item)); } static __inline void ** _ngi_arg1(item_p item, char *file, int line) { _ngi_check(item, file, line); return (&_NGI_ARG1(item)); } static __inline int * _ngi_arg2(item_p item, char *file, int line) { _ngi_check(item, file, line); return (&_NGI_ARG2(item)); } static __inline node_p _ngi_node(item_p item, char *file, int line) { _ngi_check(item, file, line); return (_NGI_NODE(item)); } static __inline hook_p _ngi_hook(item_p item, char *file, int line) { _ngi_check(item, file, line); return (_NGI_HOOK(item)); } #define NGI_M(i) (*_ngi_m(i, _NN_)) #define NGI_MSG(i) (*_ngi_msg(i, _NN_)) #define NGI_RETADDR(i) (*_ngi_retaddr(i, _NN_)) #define NGI_FN(i) (*_ngi_fn(i, _NN_)) #define NGI_FN2(i) (*_ngi_fn2(i, _NN_)) #define NGI_ARG1(i) (*_ngi_arg1(i, _NN_)) #define NGI_ARG2(i) (*_ngi_arg2(i, _NN_)) #define NGI_HOOK(i) _ngi_hook(i, _NN_) #define NGI_NODE(i) _ngi_node(i, _NN_) #define NGI_SET_HOOK(i,h) \ do { _ngi_check(i, _NN_); _NGI_SET_HOOK(i, h); } while (0) #define NGI_CLR_HOOK(i) \ do { _ngi_check(i, _NN_); _NGI_CLR_HOOK(i); } while (0) #define NGI_SET_NODE(i,n) \ do { _ngi_check(i, _NN_); _NGI_SET_NODE(i, n); } while (0) #define NGI_CLR_NODE(i) \ do { _ngi_check(i, _NN_); _NGI_CLR_NODE(i); } while (0) #define NG_FREE_ITEM(item) \ do { \ _ngi_check(item, _NN_); \ ng_free_item((item)); \ } while (0) #define SAVE_LINE(item) \ do { \ (item)->lastline = __LINE__; \ (item)->lastfile = __FILE__; \ } while (0) #else /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ #define NGI_M(i) _NGI_M(i) #define NGI_MSG(i) _NGI_MSG(i) #define NGI_RETADDR(i) _NGI_RETADDR(i) #define NGI_FN(i) _NGI_FN(i) #define NGI_FN2(i) _NGI_FN2(i) #define NGI_ARG1(i) _NGI_ARG1(i) #define NGI_ARG2(i) _NGI_ARG2(i) #define NGI_NODE(i) _NGI_NODE(i) #define NGI_HOOK(i) _NGI_HOOK(i) #define NGI_SET_HOOK(i,h) _NGI_SET_HOOK(i,h) #define NGI_CLR_HOOK(i) _NGI_CLR_HOOK(i) #define NGI_SET_NODE(i,n) _NGI_SET_NODE(i,n) #define NGI_CLR_NODE(i) _NGI_CLR_NODE(i) #define NG_FREE_ITEM(item) ng_free_item((item)) #define SAVE_LINE(item) do {} while (0) #endif /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ #define NGI_GET_M(i,m) \ do { \ (m) = NGI_M(i); \ _NGI_M(i) = NULL; \ } while (0) #define NGI_GET_MSG(i,m) \ do { \ (m) = NGI_MSG(i); \ _NGI_MSG(i) = NULL; \ } while (0) #define NGI_GET_NODE(i,n) /* YOU NOW HAVE THE REFERENCE */ \ do { \ (n) = NGI_NODE(i); \ _NGI_NODE(i) = NULL; \ } while (0) #define NGI_GET_HOOK(i,h) \ do { \ (h) = NGI_HOOK(i); \ _NGI_HOOK(i) = NULL; \ } while (0) #define NGI_SET_WRITER(i) ((i)->el_flags &= ~NGQF_QMODE) #define NGI_SET_READER(i) ((i)->el_flags |= NGQF_QREADER) #define NGI_QUEUED_READER(i) ((i)->el_flags & NGQF_QREADER) #define NGI_QUEUED_WRITER(i) (((i)->el_flags & NGQF_QMODE) == NGQF_QWRITER) /********************************************************************** * Data macros. Send, manipulate and free. **********************************************************************/ /* * Assuming the data is already ok, just set the new address and send */ #define NG_FWD_ITEM_HOOK_FLAGS(error, item, hook, flags) \ do { \ (error) = \ ng_address_hook(NULL, (item), (hook), NG_NOFLAGS); \ if (error == 0) { \ SAVE_LINE(item); \ (error) = ng_snd_item((item), (flags)); \ } \ (item) = NULL; \ } while (0) #define NG_FWD_ITEM_HOOK(error, item, hook) \ NG_FWD_ITEM_HOOK_FLAGS(error, item, hook, NG_NOFLAGS) /* * Forward a data packet. Mbuf pointer is updated to new value. We * presume you dealt with the old one when you update it to the new one * (or it maybe the old one). We got a packet and possibly had to modify * the mbuf. You should probably use NGI_GET_M() if you are going to use * this too. */ #define NG_FWD_NEW_DATA_FLAGS(error, item, hook, m, flags) \ do { \ NGI_M(item) = (m); \ (m) = NULL; \ NG_FWD_ITEM_HOOK_FLAGS(error, item, hook, flags); \ } while (0) #define NG_FWD_NEW_DATA(error, item, hook, m) \ NG_FWD_NEW_DATA_FLAGS(error, item, hook, m, NG_NOFLAGS) /* Send a previously unpackaged mbuf. XXX: This should be called * NG_SEND_DATA in future, but this name is kept for compatibility * reasons. */ #define NG_SEND_DATA_FLAGS(error, hook, m, flags) \ do { \ item_p _item; \ if ((_item = ng_package_data((m), flags))) { \ NG_FWD_ITEM_HOOK_FLAGS(error, _item, hook, flags);\ } else { \ (error) = ENOMEM; \ } \ (m) = NULL; \ } while (0) #define NG_SEND_DATA_ONLY(error, hook, m) \ NG_SEND_DATA_FLAGS(error, hook, m, NG_NOFLAGS) /* NG_SEND_DATA() compat for meta-data times */ #define NG_SEND_DATA(error, hook, m, x) \ NG_SEND_DATA_FLAGS(error, hook, m, NG_NOFLAGS) #define NG_FREE_MSG(msg) \ do { \ if ((msg)) { \ free((msg), M_NETGRAPH_MSG); \ (msg) = NULL; \ } \ } while (0) #define NG_FREE_M(m) \ do { \ if ((m)) { \ m_freem((m)); \ (m) = NULL; \ } \ } while (0) /***************************************** * Message macros *****************************************/ #define NG_SEND_MSG_HOOK(error, here, msg, hook, retaddr) \ do { \ item_p _item; \ if ((_item = ng_package_msg(msg, NG_NOFLAGS)) == NULL) {\ (msg) = NULL; \ (error) = ENOMEM; \ break; \ } \ if (((error) = ng_address_hook((here), (_item), \ (hook), (retaddr))) == 0) { \ SAVE_LINE(_item); \ (error) = ng_snd_item((_item), 0); \ } \ (msg) = NULL; \ } while (0) #define NG_SEND_MSG_PATH(error, here, msg, path, retaddr) \ do { \ item_p _item; \ if ((_item = ng_package_msg(msg, NG_NOFLAGS)) == NULL) {\ (msg) = NULL; \ (error) = ENOMEM; \ break; \ } \ if (((error) = ng_address_path((here), (_item), \ (path), (retaddr))) == 0) { \ SAVE_LINE(_item); \ (error) = ng_snd_item((_item), 0); \ } \ (msg) = NULL; \ } while (0) #define NG_SEND_MSG_ID(error, here, msg, ID, retaddr) \ do { \ item_p _item; \ if ((_item = ng_package_msg(msg, NG_NOFLAGS)) == NULL) {\ (msg) = NULL; \ (error) = ENOMEM; \ break; \ } \ if (((error) = ng_address_ID((here), (_item), \ (ID), (retaddr))) == 0) { \ SAVE_LINE(_item); \ (error) = ng_snd_item((_item), 0); \ } \ (msg) = NULL; \ } while (0) /* * Redirect the message to the next hop using the given hook. * ng_retarget_msg() frees the item if there is an error * and returns an error code. It returns 0 on success. */ #define NG_FWD_MSG_HOOK(error, here, item, hook, retaddr) \ do { \ if (((error) = ng_address_hook((here), (item), \ (hook), (retaddr))) == 0) { \ SAVE_LINE(item); \ (error) = ng_snd_item((item), 0); \ } \ (item) = NULL; \ } while (0) /* * Send a queue item back to it's originator with a response message. * Assume original message was removed and freed separatly. */ #define NG_RESPOND_MSG(error, here, item, resp) \ do { \ if (resp) { \ ng_ID_t _dest = NGI_RETADDR(item); \ NGI_RETADDR(item) = 0; \ NGI_MSG(item) = resp; \ if ((error = ng_address_ID((here), (item), \ _dest, 0)) == 0) { \ SAVE_LINE(item); \ (error) = ng_snd_item((item), NG_QUEUE);\ } \ } else \ NG_FREE_ITEM(item); \ (item) = NULL; \ } while (0) /*********************************************************************** ******** Structures Definitions and Macros for defining a node ******* *********************************************************************** * * Here we define the structures needed to actually define a new node * type. */ /* * Command list -- each node type specifies the command that it knows * how to convert between ASCII and binary using an array of these. * The last element in the array must be a terminator with cookie=0. */ struct ng_cmdlist { u_int32_t cookie; /* command typecookie */ int cmd; /* command number */ const char *name; /* command name */ const struct ng_parse_type *mesgType; /* args if !NGF_RESP */ const struct ng_parse_type *respType; /* args if NGF_RESP */ }; /* * Structure of a node type * If data is sent to the "rcvdata()" entrypoint then the system * may decide to defer it until later by queing it with the normal netgraph * input queuing system. This is decidde by the HK_QUEUE flag being set in * the flags word of the peer (receiving) hook. The dequeuing mechanism will * ensure it is not requeued again. * Note the input queueing system is to allow modules * to 'release the stack' or to pass data across spl layers. * The data will be redelivered as soon as the NETISR code runs - * which may be almost immediatly. A node may also do it's own queueing + * which may be almost immediately. A node may also do it's own queueing * for other reasons (e.g. device output queuing). */ struct ng_type { u_int32_t version; /* must equal NG_API_VERSION */ const char *name; /* Unique type name */ modeventhand_t mod_event; /* Module event handler (optional) */ ng_constructor_t *constructor; /* Node constructor */ ng_rcvmsg_t *rcvmsg; /* control messages come here */ ng_close_t *close; /* warn about forthcoming shutdown */ ng_shutdown_t *shutdown; /* reset, and free resources */ ng_newhook_t *newhook; /* first notification of new hook */ ng_findhook_t *findhook; /* only if you have lots of hooks */ ng_connect_t *connect; /* final notification of new hook */ ng_rcvdata_t *rcvdata; /* data comes here */ ng_disconnect_t *disconnect; /* notify on disconnect */ const struct ng_cmdlist *cmdlist; /* commands we can convert */ /* R/W data private to the base netgraph code DON'T TOUCH! */ LIST_ENTRY(ng_type) types; /* linked list of all types */ int refs; /* number of instances */ }; /* * Use the NETGRAPH_INIT() macro to link a node type into the * netgraph system. This works for types compiled into the kernel * as well as KLD modules. The first argument should be the type * name (eg, echo) and the second a pointer to the type struct. * * If a different link time is desired, e.g., a device driver that * needs to install its netgraph type before probing, use the * NETGRAPH_INIT_ORDERED() macro instead. Device drivers probably * want to use SI_SUB_DRIVERS/SI_ORDER_FIRST. */ #define NETGRAPH_INIT_ORDERED(typename, typestructp, sub, order) \ static moduledata_t ng_##typename##_mod = { \ "ng_" #typename, \ ng_mod_event, \ (typestructp) \ }; \ DECLARE_MODULE(ng_##typename, ng_##typename##_mod, sub, order); \ MODULE_DEPEND(ng_##typename, netgraph, NG_ABI_VERSION, \ NG_ABI_VERSION, \ NG_ABI_VERSION) #define NETGRAPH_INIT(tn, tp) \ NETGRAPH_INIT_ORDERED(tn, tp, SI_SUB_PSEUDO, SI_ORDER_MIDDLE) /* Special malloc() type for netgraph structs and ctrl messages */ /* Only these two types should be visible to nodes */ MALLOC_DECLARE(M_NETGRAPH); MALLOC_DECLARE(M_NETGRAPH_MSG); /* declare the base of the netgraph sysclt hierarchy */ /* but only if this file cares about sysctls */ #ifdef SYSCTL_DECL SYSCTL_DECL(_net_graph); #endif /* * Methods that the nodes can use. * Many of these methods should usually NOT be used directly but via * Macros above. */ int ng_address_ID(node_p here, item_p item, ng_ID_t ID, ng_ID_t retaddr); int ng_address_hook(node_p here, item_p item, hook_p hook, ng_ID_t retaddr); int ng_address_path(node_p here, item_p item, const char *address, ng_ID_t raddr); int ng_bypass(hook_p hook1, hook_p hook2); hook_p ng_findhook(node_p node, const char *name); struct ng_type *ng_findtype(const char *type); int ng_make_node_common(struct ng_type *typep, node_p *nodep); int ng_name_node(node_p node, const char *name); node_p ng_name2noderef(node_p node, const char *name); int ng_newtype(struct ng_type *tp); ng_ID_t ng_node2ID(node_p node); item_p ng_package_data(struct mbuf *m, int flags); item_p ng_package_msg(struct ng_mesg *msg, int flags); item_p ng_package_msg_self(node_p here, hook_p hook, struct ng_mesg *msg); void ng_replace_retaddr(node_p here, item_p item, ng_ID_t retaddr); int ng_rmhook_self(hook_p hook); /* if a node wants to kill a hook */ int ng_rmnode_self(node_p here); /* if a node wants to suicide */ int ng_rmtype(struct ng_type *tp); int ng_snd_item(item_p item, int queue); int ng_send_fn(node_p node, hook_p hook, ng_item_fn *fn, void *arg1, int arg2); int ng_send_fn1(node_p node, hook_p hook, ng_item_fn *fn, void *arg1, int arg2, int flags); int ng_send_fn2(node_p node, hook_p hook, item_p pitem, ng_item_fn2 *fn, void *arg1, int arg2, int flags); int ng_uncallout(struct callout *c, node_p node); int ng_callout(struct callout *c, node_p node, hook_p hook, int ticks, ng_item_fn *fn, void * arg1, int arg2); #define ng_callout_init(c) callout_init(c, 1) /* Flags for netgraph functions. */ #define NG_NOFLAGS 0x00000000 /* no special options */ #define NG_QUEUE 0x00000001 /* enqueue item, don't dispatch */ #define NG_WAITOK 0x00000002 /* use M_WAITOK, etc. */ /* XXXGL: NG_PROGRESS unused since ng_base.c rev. 1.136. Should be deleted? */ #define NG_PROGRESS 0x00000004 /* return EINPROGRESS if queued */ #define NG_REUSE_ITEM 0x00000008 /* supplied item should be reused */ /* * prototypes the user should DEFINITELY not use directly */ void ng_free_item(item_p item); /* Use NG_FREE_ITEM instead */ int ng_mod_event(module_t mod, int what, void *arg); /* * Tag definitions and constants */ #define NG_TAG_PRIO 1 struct ng_tag_prio { struct m_tag tag; char priority; char discardability; }; #define NG_PRIO_CUTOFF 32 #define NG_PRIO_LINKSTATE 64 /* Macros and declarations to keep compatibility with metadata, which * is obsoleted now. To be deleted. */ typedef void *meta_p; #define _NGI_META(i) NULL #define NGI_META(i) NULL #define NG_FREE_META(meta) #define NGI_GET_META(i,m) #define ng_copy_meta(meta) NULL /* * Mark the current thread when called from the outbound path of the * network stack, in order to enforce queuing on ng nodes calling into * the inbound network stack path. */ #define NG_OUTBOUND_THREAD_REF() \ curthread->td_ng_outbound++ #define NG_OUTBOUND_THREAD_UNREF() \ do { \ curthread->td_ng_outbound--; \ KASSERT(curthread->td_ng_outbound >= 0, \ ("%s: negative td_ng_outbound", __func__)); \ } while (0) #endif /* _NETGRAPH_NETGRAPH_H_ */ Index: head/sys/netgraph/ng_base.c =================================================================== --- head/sys/netgraph/ng_base.c (revision 298812) +++ head/sys/netgraph/ng_base.c (revision 298813) @@ -1,3846 +1,3846 @@ /*- * Copyright (c) 1996-1999 Whistle Communications, Inc. * All rights reserved. * * Subject to the following obligations and disclaimer of warranty, use and * redistribution of this software, in source or object code forms, with or * without modifications are expressly permitted by Whistle Communications; * provided, however, that: * 1. Any and all reproductions of the source or object code must include the * copyright notice above and the following disclaimer of warranties; and * 2. No rights are granted, in any manner or form, to use Whistle * Communications, Inc. trademarks, including the mark "WHISTLE * COMMUNICATIONS" on advertising, endorsements, or otherwise except as * such appears in the above copyright notice or in the software. * * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * Authors: Julian Elischer * Archie Cobbs * * $FreeBSD$ * $Whistle: ng_base.c,v 1.39 1999/01/28 23:54:53 julian Exp $ */ /* * This file implements the base netgraph code. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MODULE_VERSION(netgraph, NG_ABI_VERSION); /* Mutex to protect topology events. */ static struct rwlock ng_topo_lock; #define TOPOLOGY_RLOCK() rw_rlock(&ng_topo_lock) #define TOPOLOGY_RUNLOCK() rw_runlock(&ng_topo_lock) #define TOPOLOGY_WLOCK() rw_wlock(&ng_topo_lock) #define TOPOLOGY_WUNLOCK() rw_wunlock(&ng_topo_lock) #define TOPOLOGY_NOTOWNED() rw_assert(&ng_topo_lock, RA_UNLOCKED) #ifdef NETGRAPH_DEBUG static struct mtx ng_nodelist_mtx; /* protects global node/hook lists */ static struct mtx ngq_mtx; /* protects the queue item list */ static SLIST_HEAD(, ng_node) ng_allnodes; static LIST_HEAD(, ng_node) ng_freenodes; /* in debug, we never free() them */ static SLIST_HEAD(, ng_hook) ng_allhooks; static LIST_HEAD(, ng_hook) ng_freehooks; /* in debug, we never free() them */ static void ng_dumpitems(void); static void ng_dumpnodes(void); static void ng_dumphooks(void); #endif /* NETGRAPH_DEBUG */ /* * DEAD versions of the structures. * In order to avoid races, it is sometimes necessary to point * at SOMETHING even though theoretically, the current entity is * INVALID. Use these to avoid these races. */ struct ng_type ng_deadtype = { NG_ABI_VERSION, "dead", NULL, /* modevent */ NULL, /* constructor */ NULL, /* rcvmsg */ NULL, /* shutdown */ NULL, /* newhook */ NULL, /* findhook */ NULL, /* connect */ NULL, /* rcvdata */ NULL, /* disconnect */ NULL, /* cmdlist */ }; struct ng_node ng_deadnode = { "dead", &ng_deadtype, NGF_INVALID, 0, /* numhooks */ NULL, /* private */ 0, /* ID */ LIST_HEAD_INITIALIZER(ng_deadnode.nd_hooks), {}, /* all_nodes list entry */ {}, /* id hashtable list entry */ { 0, 0, {}, /* should never use! (should hang) */ {}, /* workqueue entry */ STAILQ_HEAD_INITIALIZER(ng_deadnode.nd_input_queue.queue), }, 1, /* refs */ NULL, /* vnet */ #ifdef NETGRAPH_DEBUG ND_MAGIC, __FILE__, __LINE__, {NULL} #endif /* NETGRAPH_DEBUG */ }; struct ng_hook ng_deadhook = { "dead", NULL, /* private */ HK_INVALID | HK_DEAD, 0, /* undefined data link type */ &ng_deadhook, /* Peer is self */ &ng_deadnode, /* attached to deadnode */ {}, /* hooks list */ NULL, /* override rcvmsg() */ NULL, /* override rcvdata() */ 1, /* refs always >= 1 */ #ifdef NETGRAPH_DEBUG HK_MAGIC, __FILE__, __LINE__, {NULL} #endif /* NETGRAPH_DEBUG */ }; /* * END DEAD STRUCTURES */ /* List nodes with unallocated work */ static STAILQ_HEAD(, ng_node) ng_worklist = STAILQ_HEAD_INITIALIZER(ng_worklist); static struct mtx ng_worklist_mtx; /* MUST LOCK NODE FIRST */ /* List of installed types */ static LIST_HEAD(, ng_type) ng_typelist; static struct rwlock ng_typelist_lock; #define TYPELIST_RLOCK() rw_rlock(&ng_typelist_lock) #define TYPELIST_RUNLOCK() rw_runlock(&ng_typelist_lock) #define TYPELIST_WLOCK() rw_wlock(&ng_typelist_lock) #define TYPELIST_WUNLOCK() rw_wunlock(&ng_typelist_lock) /* Hash related definitions. */ LIST_HEAD(nodehash, ng_node); static VNET_DEFINE(struct nodehash *, ng_ID_hash); static VNET_DEFINE(u_long, ng_ID_hmask); static VNET_DEFINE(u_long, ng_nodes); static VNET_DEFINE(struct nodehash *, ng_name_hash); static VNET_DEFINE(u_long, ng_name_hmask); static VNET_DEFINE(u_long, ng_named_nodes); #define V_ng_ID_hash VNET(ng_ID_hash) #define V_ng_ID_hmask VNET(ng_ID_hmask) #define V_ng_nodes VNET(ng_nodes) #define V_ng_name_hash VNET(ng_name_hash) #define V_ng_name_hmask VNET(ng_name_hmask) #define V_ng_named_nodes VNET(ng_named_nodes) static struct rwlock ng_idhash_lock; #define IDHASH_RLOCK() rw_rlock(&ng_idhash_lock) #define IDHASH_RUNLOCK() rw_runlock(&ng_idhash_lock) #define IDHASH_WLOCK() rw_wlock(&ng_idhash_lock) #define IDHASH_WUNLOCK() rw_wunlock(&ng_idhash_lock) /* Method to find a node.. used twice so do it here */ #define NG_IDHASH_FN(ID) ((ID) % (V_ng_ID_hmask + 1)) #define NG_IDHASH_FIND(ID, node) \ do { \ rw_assert(&ng_idhash_lock, RA_LOCKED); \ LIST_FOREACH(node, &V_ng_ID_hash[NG_IDHASH_FN(ID)], \ nd_idnodes) { \ if (NG_NODE_IS_VALID(node) \ && (NG_NODE_ID(node) == ID)) { \ break; \ } \ } \ } while (0) static struct rwlock ng_namehash_lock; #define NAMEHASH_RLOCK() rw_rlock(&ng_namehash_lock) #define NAMEHASH_RUNLOCK() rw_runlock(&ng_namehash_lock) #define NAMEHASH_WLOCK() rw_wlock(&ng_namehash_lock) #define NAMEHASH_WUNLOCK() rw_wunlock(&ng_namehash_lock) /* Internal functions */ static int ng_add_hook(node_p node, const char *name, hook_p * hookp); static int ng_generic_msg(node_p here, item_p item, hook_p lasthook); static ng_ID_t ng_decodeidname(const char *name); static int ngb_mod_event(module_t mod, int event, void *data); static void ng_worklist_add(node_p node); static void ngthread(void *); static int ng_apply_item(node_p node, item_p item, int rw); static void ng_flush_input_queue(node_p node); static node_p ng_ID2noderef(ng_ID_t ID); static int ng_con_nodes(item_p item, node_p node, const char *name, node_p node2, const char *name2); static int ng_con_part2(node_p node, item_p item, hook_p hook); static int ng_con_part3(node_p node, item_p item, hook_p hook); static int ng_mkpeer(node_p node, const char *name, const char *name2, char *type); static void ng_name_rehash(void); static void ng_ID_rehash(void); /* Imported, these used to be externally visible, some may go back. */ void ng_destroy_hook(hook_p hook); int ng_path2noderef(node_p here, const char *path, node_p *dest, hook_p *lasthook); int ng_make_node(const char *type, node_p *nodepp); int ng_path_parse(char *addr, char **node, char **path, char **hook); void ng_rmnode(node_p node, hook_p dummy1, void *dummy2, int dummy3); void ng_unname(node_p node); /* Our own netgraph malloc type */ MALLOC_DEFINE(M_NETGRAPH, "netgraph", "netgraph structures and ctrl messages"); MALLOC_DEFINE(M_NETGRAPH_MSG, "netgraph_msg", "netgraph name storage"); static MALLOC_DEFINE(M_NETGRAPH_HOOK, "netgraph_hook", "netgraph hook structures"); static MALLOC_DEFINE(M_NETGRAPH_NODE, "netgraph_node", "netgraph node structures"); static MALLOC_DEFINE(M_NETGRAPH_ITEM, "netgraph_item", "netgraph item structures"); /* Should not be visible outside this file */ #define _NG_ALLOC_HOOK(hook) \ hook = malloc(sizeof(*hook), M_NETGRAPH_HOOK, M_NOWAIT | M_ZERO) #define _NG_ALLOC_NODE(node) \ node = malloc(sizeof(*node), M_NETGRAPH_NODE, M_NOWAIT | M_ZERO) #define NG_QUEUE_LOCK_INIT(n) \ mtx_init(&(n)->q_mtx, "ng_node", NULL, MTX_DEF) #define NG_QUEUE_LOCK(n) \ mtx_lock(&(n)->q_mtx) #define NG_QUEUE_UNLOCK(n) \ mtx_unlock(&(n)->q_mtx) #define NG_WORKLIST_LOCK_INIT() \ mtx_init(&ng_worklist_mtx, "ng_worklist", NULL, MTX_DEF) #define NG_WORKLIST_LOCK() \ mtx_lock(&ng_worklist_mtx) #define NG_WORKLIST_UNLOCK() \ mtx_unlock(&ng_worklist_mtx) #define NG_WORKLIST_SLEEP() \ mtx_sleep(&ng_worklist, &ng_worklist_mtx, PI_NET, "sleep", 0) #define NG_WORKLIST_WAKEUP() \ wakeup_one(&ng_worklist) #ifdef NETGRAPH_DEBUG /*----------------------------------------------*/ /* * In debug mode: * In an attempt to help track reference count screwups * we do not free objects back to the malloc system, but keep them * in a local cache where we can examine them and keep information safely * after they have been freed. * We use this scheme for nodes and hooks, and to some extent for items. */ static __inline hook_p ng_alloc_hook(void) { hook_p hook; SLIST_ENTRY(ng_hook) temp; mtx_lock(&ng_nodelist_mtx); hook = LIST_FIRST(&ng_freehooks); if (hook) { LIST_REMOVE(hook, hk_hooks); bcopy(&hook->hk_all, &temp, sizeof(temp)); bzero(hook, sizeof(struct ng_hook)); bcopy(&temp, &hook->hk_all, sizeof(temp)); mtx_unlock(&ng_nodelist_mtx); hook->hk_magic = HK_MAGIC; } else { mtx_unlock(&ng_nodelist_mtx); _NG_ALLOC_HOOK(hook); if (hook) { hook->hk_magic = HK_MAGIC; mtx_lock(&ng_nodelist_mtx); SLIST_INSERT_HEAD(&ng_allhooks, hook, hk_all); mtx_unlock(&ng_nodelist_mtx); } } return (hook); } static __inline node_p ng_alloc_node(void) { node_p node; SLIST_ENTRY(ng_node) temp; mtx_lock(&ng_nodelist_mtx); node = LIST_FIRST(&ng_freenodes); if (node) { LIST_REMOVE(node, nd_nodes); bcopy(&node->nd_all, &temp, sizeof(temp)); bzero(node, sizeof(struct ng_node)); bcopy(&temp, &node->nd_all, sizeof(temp)); mtx_unlock(&ng_nodelist_mtx); node->nd_magic = ND_MAGIC; } else { mtx_unlock(&ng_nodelist_mtx); _NG_ALLOC_NODE(node); if (node) { node->nd_magic = ND_MAGIC; mtx_lock(&ng_nodelist_mtx); SLIST_INSERT_HEAD(&ng_allnodes, node, nd_all); mtx_unlock(&ng_nodelist_mtx); } } return (node); } #define NG_ALLOC_HOOK(hook) do { (hook) = ng_alloc_hook(); } while (0) #define NG_ALLOC_NODE(node) do { (node) = ng_alloc_node(); } while (0) #define NG_FREE_HOOK(hook) \ do { \ mtx_lock(&ng_nodelist_mtx); \ LIST_INSERT_HEAD(&ng_freehooks, hook, hk_hooks); \ hook->hk_magic = 0; \ mtx_unlock(&ng_nodelist_mtx); \ } while (0) #define NG_FREE_NODE(node) \ do { \ mtx_lock(&ng_nodelist_mtx); \ LIST_INSERT_HEAD(&ng_freenodes, node, nd_nodes); \ node->nd_magic = 0; \ mtx_unlock(&ng_nodelist_mtx); \ } while (0) #else /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ #define NG_ALLOC_HOOK(hook) _NG_ALLOC_HOOK(hook) #define NG_ALLOC_NODE(node) _NG_ALLOC_NODE(node) #define NG_FREE_HOOK(hook) do { free((hook), M_NETGRAPH_HOOK); } while (0) #define NG_FREE_NODE(node) do { free((node), M_NETGRAPH_NODE); } while (0) #endif /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ /* Set this to kdb_enter("X") to catch all errors as they occur */ #ifndef TRAP_ERROR #define TRAP_ERROR() #endif static VNET_DEFINE(ng_ID_t, nextID) = 1; #define V_nextID VNET(nextID) #ifdef INVARIANTS #define CHECK_DATA_MBUF(m) do { \ struct mbuf *n; \ int total; \ \ M_ASSERTPKTHDR(m); \ for (total = 0, n = (m); n != NULL; n = n->m_next) { \ total += n->m_len; \ if (n->m_nextpkt != NULL) \ panic("%s: m_nextpkt", __func__); \ } \ \ if ((m)->m_pkthdr.len != total) { \ panic("%s: %d != %d", \ __func__, (m)->m_pkthdr.len, total); \ } \ } while (0) #else #define CHECK_DATA_MBUF(m) #endif #define ERROUT(x) do { error = (x); goto done; } while (0) /************************************************************************ Parse type definitions for generic messages ************************************************************************/ /* Handy structure parse type defining macro */ #define DEFINE_PARSE_STRUCT_TYPE(lo, up, args) \ static const struct ng_parse_struct_field \ ng_ ## lo ## _type_fields[] = NG_GENERIC_ ## up ## _INFO args; \ static const struct ng_parse_type ng_generic_ ## lo ## _type = { \ &ng_parse_struct_type, \ &ng_ ## lo ## _type_fields \ } DEFINE_PARSE_STRUCT_TYPE(mkpeer, MKPEER, ()); DEFINE_PARSE_STRUCT_TYPE(connect, CONNECT, ()); DEFINE_PARSE_STRUCT_TYPE(name, NAME, ()); DEFINE_PARSE_STRUCT_TYPE(rmhook, RMHOOK, ()); DEFINE_PARSE_STRUCT_TYPE(nodeinfo, NODEINFO, ()); DEFINE_PARSE_STRUCT_TYPE(typeinfo, TYPEINFO, ()); DEFINE_PARSE_STRUCT_TYPE(linkinfo, LINKINFO, (&ng_generic_nodeinfo_type)); /* Get length of an array when the length is stored as a 32 bit value immediately preceding the array -- as with struct namelist and struct typelist. */ static int ng_generic_list_getLength(const struct ng_parse_type *type, const u_char *start, const u_char *buf) { return *((const u_int32_t *)(buf - 4)); } /* Get length of the array of struct linkinfo inside a struct hooklist */ static int ng_generic_linkinfo_getLength(const struct ng_parse_type *type, const u_char *start, const u_char *buf) { const struct hooklist *hl = (const struct hooklist *)start; return hl->nodeinfo.hooks; } /* Array type for a variable length array of struct namelist */ static const struct ng_parse_array_info ng_nodeinfoarray_type_info = { &ng_generic_nodeinfo_type, &ng_generic_list_getLength }; static const struct ng_parse_type ng_generic_nodeinfoarray_type = { &ng_parse_array_type, &ng_nodeinfoarray_type_info }; /* Array type for a variable length array of struct typelist */ static const struct ng_parse_array_info ng_typeinfoarray_type_info = { &ng_generic_typeinfo_type, &ng_generic_list_getLength }; static const struct ng_parse_type ng_generic_typeinfoarray_type = { &ng_parse_array_type, &ng_typeinfoarray_type_info }; /* Array type for array of struct linkinfo in struct hooklist */ static const struct ng_parse_array_info ng_generic_linkinfo_array_type_info = { &ng_generic_linkinfo_type, &ng_generic_linkinfo_getLength }; static const struct ng_parse_type ng_generic_linkinfo_array_type = { &ng_parse_array_type, &ng_generic_linkinfo_array_type_info }; DEFINE_PARSE_STRUCT_TYPE(typelist, TYPELIST, (&ng_generic_typeinfoarray_type)); DEFINE_PARSE_STRUCT_TYPE(hooklist, HOOKLIST, (&ng_generic_nodeinfo_type, &ng_generic_linkinfo_array_type)); DEFINE_PARSE_STRUCT_TYPE(listnodes, LISTNODES, (&ng_generic_nodeinfoarray_type)); /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_generic_cmds[] = { { NGM_GENERIC_COOKIE, NGM_SHUTDOWN, "shutdown", NULL, NULL }, { NGM_GENERIC_COOKIE, NGM_MKPEER, "mkpeer", &ng_generic_mkpeer_type, NULL }, { NGM_GENERIC_COOKIE, NGM_CONNECT, "connect", &ng_generic_connect_type, NULL }, { NGM_GENERIC_COOKIE, NGM_NAME, "name", &ng_generic_name_type, NULL }, { NGM_GENERIC_COOKIE, NGM_RMHOOK, "rmhook", &ng_generic_rmhook_type, NULL }, { NGM_GENERIC_COOKIE, NGM_NODEINFO, "nodeinfo", NULL, &ng_generic_nodeinfo_type }, { NGM_GENERIC_COOKIE, NGM_LISTHOOKS, "listhooks", NULL, &ng_generic_hooklist_type }, { NGM_GENERIC_COOKIE, NGM_LISTNAMES, "listnames", NULL, &ng_generic_listnodes_type /* same as NGM_LISTNODES */ }, { NGM_GENERIC_COOKIE, NGM_LISTNODES, "listnodes", NULL, &ng_generic_listnodes_type }, { NGM_GENERIC_COOKIE, NGM_LISTTYPES, "listtypes", NULL, &ng_generic_typelist_type }, { NGM_GENERIC_COOKIE, NGM_TEXT_CONFIG, "textconfig", NULL, &ng_parse_string_type }, { NGM_GENERIC_COOKIE, NGM_TEXT_STATUS, "textstatus", NULL, &ng_parse_string_type }, { NGM_GENERIC_COOKIE, NGM_ASCII2BINARY, "ascii2binary", &ng_parse_ng_mesg_type, &ng_parse_ng_mesg_type }, { NGM_GENERIC_COOKIE, NGM_BINARY2ASCII, "binary2ascii", &ng_parse_ng_mesg_type, &ng_parse_ng_mesg_type }, { 0 } }; /************************************************************************ Node routines ************************************************************************/ /* * Instantiate a node of the requested type */ int ng_make_node(const char *typename, node_p *nodepp) { struct ng_type *type; int error; /* Check that the type makes sense */ if (typename == NULL) { TRAP_ERROR(); return (EINVAL); } /* Locate the node type. If we fail we return. Do not try to load * module. */ if ((type = ng_findtype(typename)) == NULL) return (ENXIO); /* * If we have a constructor, then make the node and * call the constructor to do type specific initialisation. */ if (type->constructor != NULL) { if ((error = ng_make_node_common(type, nodepp)) == 0) { if ((error = ((*type->constructor)(*nodepp))) != 0) { NG_NODE_UNREF(*nodepp); } } } else { /* * Node has no constructor. We cannot ask for one * to be made. It must be brought into existence by * some external agency. The external agency should * call ng_make_node_common() directly to get the * netgraph part initialised. */ TRAP_ERROR(); error = EINVAL; } return (error); } /* * Generic node creation. Called by node initialisation for externally * instantiated nodes (e.g. hardware, sockets, etc ). * The returned node has a reference count of 1. */ int ng_make_node_common(struct ng_type *type, node_p *nodepp) { node_p node; /* Require the node type to have been already installed */ if (ng_findtype(type->name) == NULL) { TRAP_ERROR(); return (EINVAL); } /* Make a node and try attach it to the type */ NG_ALLOC_NODE(node); if (node == NULL) { TRAP_ERROR(); return (ENOMEM); } node->nd_type = type; #ifdef VIMAGE node->nd_vnet = curvnet; #endif NG_NODE_REF(node); /* note reference */ type->refs++; NG_QUEUE_LOCK_INIT(&node->nd_input_queue); STAILQ_INIT(&node->nd_input_queue.queue); node->nd_input_queue.q_flags = 0; /* Initialize hook list for new node */ LIST_INIT(&node->nd_hooks); /* Get an ID and put us in the hash chain. */ IDHASH_WLOCK(); for (;;) { /* wrap protection, even if silly */ node_p node2 = NULL; node->nd_ID = V_nextID++; /* 137/sec for 1 year before wrap */ /* Is there a problem with the new number? */ NG_IDHASH_FIND(node->nd_ID, node2); /* already taken? */ if ((node->nd_ID != 0) && (node2 == NULL)) { break; } } V_ng_nodes++; if (V_ng_nodes * 2 > V_ng_ID_hmask) ng_ID_rehash(); LIST_INSERT_HEAD(&V_ng_ID_hash[NG_IDHASH_FN(node->nd_ID)], node, nd_idnodes); IDHASH_WUNLOCK(); /* Done */ *nodepp = node; return (0); } /* * Forceably start the shutdown process on a node. Either call * its shutdown method, or do the default shutdown if there is * no type-specific method. * * We can only be called from a shutdown message, so we know we have * a writer lock, and therefore exclusive access. It also means * that we should not be on the work queue, but we check anyhow. * * Persistent node types must have a type-specific method which * allocates a new node in which case, this one is irretrievably going away, * or cleans up anything it needs, and just makes the node valid again, * in which case we allow the node to survive. * * XXX We need to think of how to tell a persistent node that we * REALLY need to go away because the hardware has gone or we * are rebooting.... etc. */ void ng_rmnode(node_p node, hook_p dummy1, void *dummy2, int dummy3) { hook_p hook; /* Check if it's already shutting down */ if ((node->nd_flags & NGF_CLOSING) != 0) return; if (node == &ng_deadnode) { printf ("shutdown called on deadnode\n"); return; } /* Add an extra reference so it doesn't go away during this */ NG_NODE_REF(node); /* * Mark it invalid so any newcomers know not to try use it * Also add our own mark so we can't recurse * note that NGF_INVALID does not do this as it's also set during * creation */ node->nd_flags |= NGF_INVALID|NGF_CLOSING; /* If node has its pre-shutdown method, then call it first*/ if (node->nd_type && node->nd_type->close) (*node->nd_type->close)(node); /* Notify all remaining connected nodes to disconnect */ while ((hook = LIST_FIRST(&node->nd_hooks)) != NULL) ng_destroy_hook(hook); /* * Drain the input queue forceably. * it has no hooks so what's it going to do, bleed on someone? * Theoretically we came here from a queue entry that was added * Just before the queue was closed, so it should be empty anyway. * Also removes us from worklist if needed. */ ng_flush_input_queue(node); /* Ask the type if it has anything to do in this case */ if (node->nd_type && node->nd_type->shutdown) { (*node->nd_type->shutdown)(node); if (NG_NODE_IS_VALID(node)) { /* * Well, blow me down if the node code hasn't declared * that it doesn't want to die. - * Presumably it is a persistant node. + * Presumably it is a persistent node. * If we REALLY want it to go away, * e.g. hardware going away, * Our caller should set NGF_REALLY_DIE in nd_flags. */ node->nd_flags &= ~(NGF_INVALID|NGF_CLOSING); NG_NODE_UNREF(node); /* Assume they still have theirs */ return; } } else { /* do the default thing */ NG_NODE_UNREF(node); } ng_unname(node); /* basically a NOP these days */ /* * Remove extra reference, possibly the last * Possible other holders of references may include * timeout callouts, but theoretically the node's supposed to * have cancelled them. Possibly hardware dependencies may * force a driver to 'linger' with a reference. */ NG_NODE_UNREF(node); } /* * Remove a reference to the node, possibly the last. * deadnode always acts as it it were the last. */ void ng_unref_node(node_p node) { if (node == &ng_deadnode) return; CURVNET_SET(node->nd_vnet); if (refcount_release(&node->nd_refs)) { /* we were the last */ node->nd_type->refs--; /* XXX maybe should get types lock? */ NAMEHASH_WLOCK(); if (NG_NODE_HAS_NAME(node)) { V_ng_named_nodes--; LIST_REMOVE(node, nd_nodes); } NAMEHASH_WUNLOCK(); IDHASH_WLOCK(); V_ng_nodes--; LIST_REMOVE(node, nd_idnodes); IDHASH_WUNLOCK(); mtx_destroy(&node->nd_input_queue.q_mtx); NG_FREE_NODE(node); } CURVNET_RESTORE(); } /************************************************************************ Node ID handling ************************************************************************/ static node_p ng_ID2noderef(ng_ID_t ID) { node_p node; IDHASH_RLOCK(); NG_IDHASH_FIND(ID, node); if (node) NG_NODE_REF(node); IDHASH_RUNLOCK(); return(node); } ng_ID_t ng_node2ID(node_p node) { return (node ? NG_NODE_ID(node) : 0); } /************************************************************************ Node name handling ************************************************************************/ /* * Assign a node a name. */ int ng_name_node(node_p node, const char *name) { uint32_t hash; node_p node2; int i; /* Check the name is valid */ for (i = 0; i < NG_NODESIZ; i++) { if (name[i] == '\0' || name[i] == '.' || name[i] == ':') break; } if (i == 0 || name[i] != '\0') { TRAP_ERROR(); return (EINVAL); } if (ng_decodeidname(name) != 0) { /* valid IDs not allowed here */ TRAP_ERROR(); return (EINVAL); } NAMEHASH_WLOCK(); if (V_ng_named_nodes * 2 > V_ng_name_hmask) ng_name_rehash(); hash = hash32_str(name, HASHINIT) & V_ng_name_hmask; /* Check the name isn't already being used. */ LIST_FOREACH(node2, &V_ng_name_hash[hash], nd_nodes) if (NG_NODE_IS_VALID(node2) && (strcmp(NG_NODE_NAME(node2), name) == 0)) { NAMEHASH_WUNLOCK(); return (EADDRINUSE); } if (NG_NODE_HAS_NAME(node)) LIST_REMOVE(node, nd_nodes); else V_ng_named_nodes++; /* Copy it. */ strlcpy(NG_NODE_NAME(node), name, NG_NODESIZ); /* Update name hash. */ LIST_INSERT_HEAD(&V_ng_name_hash[hash], node, nd_nodes); NAMEHASH_WUNLOCK(); return (0); } /* * Find a node by absolute name. The name should NOT end with ':' * The name "." means "this node" and "[xxx]" means "the node * with ID (ie, at address) xxx". * * Returns the node if found, else NULL. * Eventually should add something faster than a sequential search. * Note it acquires a reference on the node so you can be sure it's still * there. */ node_p ng_name2noderef(node_p here, const char *name) { node_p node; ng_ID_t temp; int hash; /* "." means "this node" */ if (strcmp(name, ".") == 0) { NG_NODE_REF(here); return(here); } /* Check for name-by-ID */ if ((temp = ng_decodeidname(name)) != 0) { return (ng_ID2noderef(temp)); } /* Find node by name. */ hash = hash32_str(name, HASHINIT) & V_ng_name_hmask; NAMEHASH_RLOCK(); LIST_FOREACH(node, &V_ng_name_hash[hash], nd_nodes) if (NG_NODE_IS_VALID(node) && (strcmp(NG_NODE_NAME(node), name) == 0)) { NG_NODE_REF(node); break; } NAMEHASH_RUNLOCK(); return (node); } /* * Decode an ID name, eg. "[f03034de]". Returns 0 if the * string is not valid, otherwise returns the value. */ static ng_ID_t ng_decodeidname(const char *name) { const int len = strlen(name); char *eptr; u_long val; /* Check for proper length, brackets, no leading junk */ if ((len < 3) || (name[0] != '[') || (name[len - 1] != ']') || (!isxdigit(name[1]))) return ((ng_ID_t)0); /* Decode number */ val = strtoul(name + 1, &eptr, 16); if ((eptr - name != len - 1) || (val == ULONG_MAX) || (val == 0)) return ((ng_ID_t)0); return ((ng_ID_t)val); } /* * Remove a name from a node. This should only be called * when shutting down and removing the node. */ void ng_unname(node_p node) { } /* * Allocate a bigger name hash. */ static void ng_name_rehash() { struct nodehash *new; uint32_t hash; u_long hmask; node_p node, node2; int i; new = hashinit_flags((V_ng_name_hmask + 1) * 2, M_NETGRAPH_NODE, &hmask, HASH_NOWAIT); if (new == NULL) return; for (i = 0; i <= V_ng_name_hmask; i++) LIST_FOREACH_SAFE(node, &V_ng_name_hash[i], nd_nodes, node2) { #ifdef INVARIANTS LIST_REMOVE(node, nd_nodes); #endif hash = hash32_str(NG_NODE_NAME(node), HASHINIT) & hmask; LIST_INSERT_HEAD(&new[hash], node, nd_nodes); } hashdestroy(V_ng_name_hash, M_NETGRAPH_NODE, V_ng_name_hmask); V_ng_name_hash = new; V_ng_name_hmask = hmask; } /* * Allocate a bigger ID hash. */ static void ng_ID_rehash() { struct nodehash *new; uint32_t hash; u_long hmask; node_p node, node2; int i; new = hashinit_flags((V_ng_ID_hmask + 1) * 2, M_NETGRAPH_NODE, &hmask, HASH_NOWAIT); if (new == NULL) return; for (i = 0; i <= V_ng_ID_hmask; i++) LIST_FOREACH_SAFE(node, &V_ng_ID_hash[i], nd_idnodes, node2) { #ifdef INVARIANTS LIST_REMOVE(node, nd_idnodes); #endif hash = (node->nd_ID % (hmask + 1)); LIST_INSERT_HEAD(&new[hash], node, nd_idnodes); } hashdestroy(V_ng_ID_hash, M_NETGRAPH_NODE, V_ng_name_hmask); V_ng_ID_hash = new; V_ng_ID_hmask = hmask; } /************************************************************************ Hook routines Names are not optional. Hooks are always connected, except for a brief moment within these routines. On invalidation or during creation they are connected to the 'dead' hook. ************************************************************************/ /* * Remove a hook reference */ void ng_unref_hook(hook_p hook) { if (hook == &ng_deadhook) return; if (refcount_release(&hook->hk_refs)) { /* we were the last */ if (_NG_HOOK_NODE(hook)) /* it'll probably be ng_deadnode */ _NG_NODE_UNREF((_NG_HOOK_NODE(hook))); NG_FREE_HOOK(hook); } } /* * Add an unconnected hook to a node. Only used internally. * Assumes node is locked. (XXX not yet true ) */ static int ng_add_hook(node_p node, const char *name, hook_p *hookp) { hook_p hook; int error = 0; /* Check that the given name is good */ if (name == NULL) { TRAP_ERROR(); return (EINVAL); } if (ng_findhook(node, name) != NULL) { TRAP_ERROR(); return (EEXIST); } /* Allocate the hook and link it up */ NG_ALLOC_HOOK(hook); if (hook == NULL) { TRAP_ERROR(); return (ENOMEM); } hook->hk_refs = 1; /* add a reference for us to return */ hook->hk_flags = HK_INVALID; hook->hk_peer = &ng_deadhook; /* start off this way */ hook->hk_node = node; NG_NODE_REF(node); /* each hook counts as a reference */ /* Set hook name */ strlcpy(NG_HOOK_NAME(hook), name, NG_HOOKSIZ); /* * Check if the node type code has something to say about it * If it fails, the unref of the hook will also unref the node. */ if (node->nd_type->newhook != NULL) { if ((error = (*node->nd_type->newhook)(node, hook, name))) { NG_HOOK_UNREF(hook); /* this frees the hook */ return (error); } } /* * The 'type' agrees so far, so go ahead and link it in. * We'll ask again later when we actually connect the hooks. */ LIST_INSERT_HEAD(&node->nd_hooks, hook, hk_hooks); node->nd_numhooks++; NG_HOOK_REF(hook); /* one for the node */ if (hookp) *hookp = hook; return (0); } /* * Find a hook * * Node types may supply their own optimized routines for finding * hooks. If none is supplied, we just do a linear search. * XXX Possibly we should add a reference to the hook? */ hook_p ng_findhook(node_p node, const char *name) { hook_p hook; if (node->nd_type->findhook != NULL) return (*node->nd_type->findhook)(node, name); LIST_FOREACH(hook, &node->nd_hooks, hk_hooks) { if (NG_HOOK_IS_VALID(hook) && (strcmp(NG_HOOK_NAME(hook), name) == 0)) return (hook); } return (NULL); } /* * Destroy a hook * * As hooks are always attached, this really destroys two hooks. * The one given, and the one attached to it. Disconnect the hooks * from each other first. We reconnect the peer hook to the 'dead' * hook so that it can still exist after we depart. We then * send the peer its own destroy message. This ensures that we only * interact with the peer's structures when it is locked processing that * message. We hold a reference to the peer hook so we are guaranteed that * the peer hook and node are still going to exist until * we are finished there as the hook holds a ref on the node. * We run this same code again on the peer hook, but that time it is already * attached to the 'dead' hook. * * This routine is called at all stages of hook creation * on error detection and must be able to handle any such stage. */ void ng_destroy_hook(hook_p hook) { hook_p peer; node_p node; if (hook == &ng_deadhook) { /* better safe than sorry */ printf("ng_destroy_hook called on deadhook\n"); return; } /* * Protect divorce process with mutex, to avoid races on * simultaneous disconnect. */ TOPOLOGY_WLOCK(); hook->hk_flags |= HK_INVALID; peer = NG_HOOK_PEER(hook); node = NG_HOOK_NODE(hook); if (peer && (peer != &ng_deadhook)) { /* * Set the peer to point to ng_deadhook * from this moment on we are effectively independent it. * send it an rmhook message of it's own. */ peer->hk_peer = &ng_deadhook; /* They no longer know us */ hook->hk_peer = &ng_deadhook; /* Nor us, them */ if (NG_HOOK_NODE(peer) == &ng_deadnode) { /* * If it's already divorced from a node, * just free it. */ TOPOLOGY_WUNLOCK(); } else { TOPOLOGY_WUNLOCK(); ng_rmhook_self(peer); /* Send it a surprise */ } NG_HOOK_UNREF(peer); /* account for peer link */ NG_HOOK_UNREF(hook); /* account for peer link */ } else TOPOLOGY_WUNLOCK(); TOPOLOGY_NOTOWNED(); /* * Remove the hook from the node's list to avoid possible recursion * in case the disconnection results in node shutdown. */ if (node == &ng_deadnode) { /* happens if called from ng_con_nodes() */ return; } LIST_REMOVE(hook, hk_hooks); node->nd_numhooks--; if (node->nd_type->disconnect) { /* * The type handler may elect to destroy the node so don't * trust its existence after this point. (except * that we still hold a reference on it. (which we * inherrited from the hook we are destroying) */ (*node->nd_type->disconnect) (hook); } /* * Note that because we will point to ng_deadnode, the original node * is not decremented automatically so we do that manually. */ _NG_HOOK_NODE(hook) = &ng_deadnode; NG_NODE_UNREF(node); /* We no longer point to it so adjust count */ NG_HOOK_UNREF(hook); /* Account for linkage (in list) to node */ } /* * Take two hooks on a node and merge the connection so that the given node * is effectively bypassed. */ int ng_bypass(hook_p hook1, hook_p hook2) { if (hook1->hk_node != hook2->hk_node) { TRAP_ERROR(); return (EINVAL); } TOPOLOGY_WLOCK(); if (NG_HOOK_NOT_VALID(hook1) || NG_HOOK_NOT_VALID(hook2)) { TOPOLOGY_WUNLOCK(); return (EINVAL); } hook1->hk_peer->hk_peer = hook2->hk_peer; hook2->hk_peer->hk_peer = hook1->hk_peer; hook1->hk_peer = &ng_deadhook; hook2->hk_peer = &ng_deadhook; TOPOLOGY_WUNLOCK(); NG_HOOK_UNREF(hook1); NG_HOOK_UNREF(hook2); /* XXX If we ever cache methods on hooks update them as well */ ng_destroy_hook(hook1); ng_destroy_hook(hook2); return (0); } /* * Install a new netgraph type */ int ng_newtype(struct ng_type *tp) { const size_t namelen = strlen(tp->name); /* Check version and type name fields */ if ((tp->version != NG_ABI_VERSION) || (namelen == 0) || (namelen >= NG_TYPESIZ)) { TRAP_ERROR(); if (tp->version != NG_ABI_VERSION) { printf("Netgraph: Node type rejected. ABI mismatch. " "Suggest recompile\n"); } return (EINVAL); } /* Check for name collision */ if (ng_findtype(tp->name) != NULL) { TRAP_ERROR(); return (EEXIST); } /* Link in new type */ TYPELIST_WLOCK(); LIST_INSERT_HEAD(&ng_typelist, tp, types); tp->refs = 1; /* first ref is linked list */ TYPELIST_WUNLOCK(); return (0); } /* * unlink a netgraph type * If no examples exist */ int ng_rmtype(struct ng_type *tp) { /* Check for name collision */ if (tp->refs != 1) { TRAP_ERROR(); return (EBUSY); } /* Unlink type */ TYPELIST_WLOCK(); LIST_REMOVE(tp, types); TYPELIST_WUNLOCK(); return (0); } /* * Look for a type of the name given */ struct ng_type * ng_findtype(const char *typename) { struct ng_type *type; TYPELIST_RLOCK(); LIST_FOREACH(type, &ng_typelist, types) { if (strcmp(type->name, typename) == 0) break; } TYPELIST_RUNLOCK(); return (type); } /************************************************************************ Composite routines ************************************************************************/ /* * Connect two nodes using the specified hooks, using queued functions. */ static int ng_con_part3(node_p node, item_p item, hook_p hook) { int error = 0; /* * When we run, we know that the node 'node' is locked for us. * Our caller has a reference on the hook. * Our caller has a reference on the node. * (In this case our caller is ng_apply_item() ). * The peer hook has a reference on the hook. * We are all set up except for the final call to the node, and * the clearing of the INVALID flag. */ if (NG_HOOK_NODE(hook) == &ng_deadnode) { /* * The node must have been freed again since we last visited * here. ng_destry_hook() has this effect but nothing else does. * We should just release our references and * free anything we can think of. * Since we know it's been destroyed, and it's our caller * that holds the references, just return. */ ERROUT(ENOENT); } if (hook->hk_node->nd_type->connect) { if ((error = (*hook->hk_node->nd_type->connect) (hook))) { ng_destroy_hook(hook); /* also zaps peer */ printf("failed in ng_con_part3()\n"); ERROUT(error); } } /* * XXX this is wrong for SMP. Possibly we need * to separate out 'create' and 'invalid' flags. * should only set flags on hooks we have locked under our node. */ hook->hk_flags &= ~HK_INVALID; done: NG_FREE_ITEM(item); return (error); } static int ng_con_part2(node_p node, item_p item, hook_p hook) { hook_p peer; int error = 0; /* * When we run, we know that the node 'node' is locked for us. * Our caller has a reference on the hook. * Our caller has a reference on the node. * (In this case our caller is ng_apply_item() ). * The peer hook has a reference on the hook. * our node pointer points to the 'dead' node. * First check the hook name is unique. * Should not happen because we checked before queueing this. */ if (ng_findhook(node, NG_HOOK_NAME(hook)) != NULL) { TRAP_ERROR(); ng_destroy_hook(hook); /* should destroy peer too */ printf("failed in ng_con_part2()\n"); ERROUT(EEXIST); } /* * Check if the node type code has something to say about it * If it fails, the unref of the hook will also unref the attached node, * however since that node is 'ng_deadnode' this will do nothing. * The peer hook will also be destroyed. */ if (node->nd_type->newhook != NULL) { if ((error = (*node->nd_type->newhook)(node, hook, hook->hk_name))) { ng_destroy_hook(hook); /* should destroy peer too */ printf("failed in ng_con_part2()\n"); ERROUT(error); } } /* * The 'type' agrees so far, so go ahead and link it in. * We'll ask again later when we actually connect the hooks. */ hook->hk_node = node; /* just overwrite ng_deadnode */ NG_NODE_REF(node); /* each hook counts as a reference */ LIST_INSERT_HEAD(&node->nd_hooks, hook, hk_hooks); node->nd_numhooks++; NG_HOOK_REF(hook); /* one for the node */ /* * We now have a symmetrical situation, where both hooks have been * linked to their nodes, the newhook methods have been called * And the references are all correct. The hooks are still marked * as invalid, as we have not called the 'connect' methods * yet. * We can call the local one immediately as we have the * node locked, but we need to queue the remote one. */ if (hook->hk_node->nd_type->connect) { if ((error = (*hook->hk_node->nd_type->connect) (hook))) { ng_destroy_hook(hook); /* also zaps peer */ printf("failed in ng_con_part2(A)\n"); ERROUT(error); } } /* * Acquire topo mutex to avoid race with ng_destroy_hook(). */ TOPOLOGY_RLOCK(); peer = hook->hk_peer; if (peer == &ng_deadhook) { TOPOLOGY_RUNLOCK(); printf("failed in ng_con_part2(B)\n"); ng_destroy_hook(hook); ERROUT(ENOENT); } TOPOLOGY_RUNLOCK(); if ((error = ng_send_fn2(peer->hk_node, peer, item, &ng_con_part3, NULL, 0, NG_REUSE_ITEM))) { printf("failed in ng_con_part2(C)\n"); ng_destroy_hook(hook); /* also zaps peer */ return (error); /* item was consumed. */ } hook->hk_flags &= ~HK_INVALID; /* need both to be able to work */ return (0); /* item was consumed. */ done: NG_FREE_ITEM(item); return (error); } /* * Connect this node with another node. We assume that this node is * currently locked, as we are only called from an NGM_CONNECT message. */ static int ng_con_nodes(item_p item, node_p node, const char *name, node_p node2, const char *name2) { int error; hook_p hook; hook_p hook2; if (ng_findhook(node2, name2) != NULL) { return(EEXIST); } if ((error = ng_add_hook(node, name, &hook))) /* gives us a ref */ return (error); /* Allocate the other hook and link it up */ NG_ALLOC_HOOK(hook2); if (hook2 == NULL) { TRAP_ERROR(); ng_destroy_hook(hook); /* XXX check ref counts so far */ NG_HOOK_UNREF(hook); /* including our ref */ return (ENOMEM); } hook2->hk_refs = 1; /* start with a reference for us. */ hook2->hk_flags = HK_INVALID; hook2->hk_peer = hook; /* Link the two together */ hook->hk_peer = hook2; NG_HOOK_REF(hook); /* Add a ref for the peer to each*/ NG_HOOK_REF(hook2); hook2->hk_node = &ng_deadnode; strlcpy(NG_HOOK_NAME(hook2), name2, NG_HOOKSIZ); /* * Queue the function above. * Procesing continues in that function in the lock context of * the other node. */ if ((error = ng_send_fn2(node2, hook2, item, &ng_con_part2, NULL, 0, NG_NOFLAGS))) { printf("failed in ng_con_nodes(): %d\n", error); ng_destroy_hook(hook); /* also zaps peer */ } NG_HOOK_UNREF(hook); /* Let each hook go if it wants to */ NG_HOOK_UNREF(hook2); return (error); } /* * Make a peer and connect. * We assume that the local node is locked. * The new node probably doesn't need a lock until * it has a hook, because it cannot really have any work until then, * but we should think about it a bit more. * * The problem may come if the other node also fires up * some hardware or a timer or some other source of activation, * also it may already get a command msg via it's ID. * * We could use the same method as ng_con_nodes() but we'd have * to add ability to remove the node when failing. (Not hard, just * make arg1 point to the node to remove). * Unless of course we just ignore failure to connect and leave * an unconnected node? */ static int ng_mkpeer(node_p node, const char *name, const char *name2, char *type) { node_p node2; hook_p hook1, hook2; int error; if ((error = ng_make_node(type, &node2))) { return (error); } if ((error = ng_add_hook(node, name, &hook1))) { /* gives us a ref */ ng_rmnode(node2, NULL, NULL, 0); return (error); } if ((error = ng_add_hook(node2, name2, &hook2))) { ng_rmnode(node2, NULL, NULL, 0); ng_destroy_hook(hook1); NG_HOOK_UNREF(hook1); return (error); } /* * Actually link the two hooks together. */ hook1->hk_peer = hook2; hook2->hk_peer = hook1; /* Each hook is referenced by the other */ NG_HOOK_REF(hook1); NG_HOOK_REF(hook2); /* Give each node the opportunity to veto the pending connection */ if (hook1->hk_node->nd_type->connect) { error = (*hook1->hk_node->nd_type->connect) (hook1); } if ((error == 0) && hook2->hk_node->nd_type->connect) { error = (*hook2->hk_node->nd_type->connect) (hook2); } /* * drop the references we were holding on the two hooks. */ if (error) { ng_destroy_hook(hook2); /* also zaps hook1 */ ng_rmnode(node2, NULL, NULL, 0); } else { /* As a last act, allow the hooks to be used */ hook1->hk_flags &= ~HK_INVALID; hook2->hk_flags &= ~HK_INVALID; } NG_HOOK_UNREF(hook1); NG_HOOK_UNREF(hook2); return (error); } /************************************************************************ Utility routines to send self messages ************************************************************************/ /* Shut this node down as soon as everyone is clear of it */ /* Should add arg "immediately" to jump the queue */ int ng_rmnode_self(node_p node) { int error; if (node == &ng_deadnode) return (0); node->nd_flags |= NGF_INVALID; if (node->nd_flags & NGF_CLOSING) return (0); error = ng_send_fn(node, NULL, &ng_rmnode, NULL, 0); return (error); } static void ng_rmhook_part2(node_p node, hook_p hook, void *arg1, int arg2) { ng_destroy_hook(hook); return ; } int ng_rmhook_self(hook_p hook) { int error; node_p node = NG_HOOK_NODE(hook); if (node == &ng_deadnode) return (0); error = ng_send_fn(node, hook, &ng_rmhook_part2, NULL, 0); return (error); } /*********************************************************************** * Parse and verify a string of the form: * * Such a string can refer to a specific node or a specific hook * on a specific node, depending on how you look at it. In the * latter case, the PATH component must not end in a dot. * * Both and are optional. The is a string * of hook names separated by dots. This breaks out the original * string, setting *nodep to "NODE" (or NULL if none) and *pathp * to "PATH" (or NULL if degenerate). Also, *hookp will point to * the final hook component of , if any, otherwise NULL. * * This returns -1 if the path is malformed. The char ** are optional. ***********************************************************************/ int ng_path_parse(char *addr, char **nodep, char **pathp, char **hookp) { char *node, *path, *hook; int k; /* * Extract absolute NODE, if any */ for (path = addr; *path && *path != ':'; path++); if (*path) { node = addr; /* Here's the NODE */ *path++ = '\0'; /* Here's the PATH */ /* Node name must not be empty */ if (!*node) return -1; /* A name of "." is OK; otherwise '.' not allowed */ if (strcmp(node, ".") != 0) { for (k = 0; node[k]; k++) if (node[k] == '.') return -1; } } else { node = NULL; /* No absolute NODE */ path = addr; /* Here's the PATH */ } /* Snoop for illegal characters in PATH */ for (k = 0; path[k]; k++) if (path[k] == ':') return -1; /* Check for no repeated dots in PATH */ for (k = 0; path[k]; k++) if (path[k] == '.' && path[k + 1] == '.') return -1; /* Remove extra (degenerate) dots from beginning or end of PATH */ if (path[0] == '.') path++; if (*path && path[strlen(path) - 1] == '.') path[strlen(path) - 1] = 0; /* If PATH has a dot, then we're not talking about a hook */ if (*path) { for (hook = path, k = 0; path[k]; k++) if (path[k] == '.') { hook = NULL; break; } } else path = hook = NULL; /* Done */ if (nodep) *nodep = node; if (pathp) *pathp = path; if (hookp) *hookp = hook; return (0); } /* * Given a path, which may be absolute or relative, and a starting node, * return the destination node. */ int ng_path2noderef(node_p here, const char *address, node_p *destp, hook_p *lasthook) { char fullpath[NG_PATHSIZ]; char *nodename, *path; node_p node, oldnode; /* Initialize */ if (destp == NULL) { TRAP_ERROR(); return EINVAL; } *destp = NULL; /* Make a writable copy of address for ng_path_parse() */ strncpy(fullpath, address, sizeof(fullpath) - 1); fullpath[sizeof(fullpath) - 1] = '\0'; /* Parse out node and sequence of hooks */ if (ng_path_parse(fullpath, &nodename, &path, NULL) < 0) { TRAP_ERROR(); return EINVAL; } /* * For an absolute address, jump to the starting node. * Note that this holds a reference on the node for us. * Don't forget to drop the reference if we don't need it. */ if (nodename) { node = ng_name2noderef(here, nodename); if (node == NULL) { TRAP_ERROR(); return (ENOENT); } } else { if (here == NULL) { TRAP_ERROR(); return (EINVAL); } node = here; NG_NODE_REF(node); } if (path == NULL) { if (lasthook != NULL) *lasthook = NULL; *destp = node; return (0); } /* * Now follow the sequence of hooks * * XXXGL: The path may demolish as we go the sequence, but if * we hold the topology mutex at critical places, then, I hope, * we would always have valid pointers in hand, although the * path behind us may no longer exist. */ for (;;) { hook_p hook; char *segment; /* * Break out the next path segment. Replace the dot we just * found with a NUL; "path" points to the next segment (or the * NUL at the end). */ for (segment = path; *path != '\0'; path++) { if (*path == '.') { *path++ = '\0'; break; } } /* We have a segment, so look for a hook by that name */ hook = ng_findhook(node, segment); TOPOLOGY_WLOCK(); /* Can't get there from here... */ if (hook == NULL || NG_HOOK_PEER(hook) == NULL || NG_HOOK_NOT_VALID(hook) || NG_HOOK_NOT_VALID(NG_HOOK_PEER(hook))) { TRAP_ERROR(); NG_NODE_UNREF(node); TOPOLOGY_WUNLOCK(); return (ENOENT); } /* * Hop on over to the next node * XXX * Big race conditions here as hooks and nodes go away * *** Idea.. store an ng_ID_t in each hook and use that * instead of the direct hook in this crawl? */ oldnode = node; if ((node = NG_PEER_NODE(hook))) NG_NODE_REF(node); /* XXX RACE */ NG_NODE_UNREF(oldnode); /* XXX another race */ if (NG_NODE_NOT_VALID(node)) { NG_NODE_UNREF(node); /* XXX more races */ TOPOLOGY_WUNLOCK(); TRAP_ERROR(); return (ENXIO); } if (*path == '\0') { if (lasthook != NULL) { if (hook != NULL) { *lasthook = NG_HOOK_PEER(hook); NG_HOOK_REF(*lasthook); } else *lasthook = NULL; } TOPOLOGY_WUNLOCK(); *destp = node; return (0); } TOPOLOGY_WUNLOCK(); } } /***************************************************************\ * Input queue handling. * All activities are submitted to the node via the input queue * which implements a multiple-reader/single-writer gate. * Items which cannot be handled immediately are queued. * * read-write queue locking inline functions * \***************************************************************/ static __inline void ng_queue_rw(node_p node, item_p item, int rw); static __inline item_p ng_dequeue(node_p node, int *rw); static __inline item_p ng_acquire_read(node_p node, item_p item); static __inline item_p ng_acquire_write(node_p node, item_p item); static __inline void ng_leave_read(node_p node); static __inline void ng_leave_write(node_p node); /* * Definition of the bits fields in the ng_queue flag word. * Defined here rather than in netgraph.h because no-one should fiddle * with them. * * The ordering here may be important! don't shuffle these. */ /*- Safety Barrier--------+ (adjustable to suit taste) (not used yet) | V +-------+-------+-------+-------+-------+-------+-------+-------+ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |A|c|t|i|v|e| |R|e|a|d|e|r| |C|o|u|n|t| | | | | | | | | |P|A| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |O|W| +-------+-------+-------+-------+-------+-------+-------+-------+ \___________________________ ____________________________/ | | V | | [active reader count] | | | | Operation Pending -------------------------------+ | | Active Writer ---------------------------------------+ Node queue has such semantics: - All flags modifications are atomic. - Reader count can be incremented only if there is no writer or pending flags. As soon as this can't be done with single operation, it is implemented with spin loop and atomic_cmpset(). - Writer flag can be set only if there is no any bits set. It is implemented with atomic_cmpset(). - Pending flag can be set any time, but to avoid collision on queue processing all queue fields are protected by the mutex. - Queue processing thread reads queue holding the mutex, but releases it while processing. When queue is empty pending flag is removed. */ #define WRITER_ACTIVE 0x00000001 #define OP_PENDING 0x00000002 #define READER_INCREMENT 0x00000004 #define READER_MASK 0xfffffffc /* Not valid if WRITER_ACTIVE is set */ #define SAFETY_BARRIER 0x00100000 /* 128K items queued should be enough */ /* Defines of more elaborate states on the queue */ /* Mask of bits a new read cares about */ #define NGQ_RMASK (WRITER_ACTIVE|OP_PENDING) /* Mask of bits a new write cares about */ #define NGQ_WMASK (NGQ_RMASK|READER_MASK) /* Test to decide if there is something on the queue. */ #define QUEUE_ACTIVE(QP) ((QP)->q_flags & OP_PENDING) /* How to decide what the next queued item is. */ #define HEAD_IS_READER(QP) NGI_QUEUED_READER(STAILQ_FIRST(&(QP)->queue)) #define HEAD_IS_WRITER(QP) NGI_QUEUED_WRITER(STAILQ_FIRST(&(QP)->queue)) /* notused */ /* Read the status to decide if the next item on the queue can now run. */ #define QUEUED_READER_CAN_PROCEED(QP) \ (((QP)->q_flags & (NGQ_RMASK & ~OP_PENDING)) == 0) #define QUEUED_WRITER_CAN_PROCEED(QP) \ (((QP)->q_flags & (NGQ_WMASK & ~OP_PENDING)) == 0) /* Is there a chance of getting ANY work off the queue? */ #define NEXT_QUEUED_ITEM_CAN_PROCEED(QP) \ ((HEAD_IS_READER(QP)) ? QUEUED_READER_CAN_PROCEED(QP) : \ QUEUED_WRITER_CAN_PROCEED(QP)) #define NGQRW_R 0 #define NGQRW_W 1 #define NGQ2_WORKQ 0x00000001 /* * Taking into account the current state of the queue and node, possibly take * the next entry off the queue and return it. Return NULL if there was * nothing we could return, either because there really was nothing there, or * because the node was in a state where it cannot yet process the next item * on the queue. */ static __inline item_p ng_dequeue(node_p node, int *rw) { item_p item; struct ng_queue *ngq = &node->nd_input_queue; /* This MUST be called with the mutex held. */ mtx_assert(&ngq->q_mtx, MA_OWNED); /* If there is nothing queued, then just return. */ if (!QUEUE_ACTIVE(ngq)) { CTR4(KTR_NET, "%20s: node [%x] (%p) queue empty; " "queue flags 0x%lx", __func__, node->nd_ID, node, ngq->q_flags); return (NULL); } /* * From here, we can assume there is a head item. * We need to find out what it is and if it can be dequeued, given * the current state of the node. */ if (HEAD_IS_READER(ngq)) { while (1) { long t = ngq->q_flags; if (t & WRITER_ACTIVE) { /* There is writer, reader can't proceed. */ CTR4(KTR_NET, "%20s: node [%x] (%p) queued " "reader can't proceed; queue flags 0x%lx", __func__, node->nd_ID, node, t); return (NULL); } if (atomic_cmpset_acq_int(&ngq->q_flags, t, t + READER_INCREMENT)) break; cpu_spinwait(); } /* We have got reader lock for the node. */ *rw = NGQRW_R; } else if (atomic_cmpset_acq_int(&ngq->q_flags, OP_PENDING, OP_PENDING + WRITER_ACTIVE)) { /* We have got writer lock for the node. */ *rw = NGQRW_W; } else { /* There is somebody other, writer can't proceed. */ CTR4(KTR_NET, "%20s: node [%x] (%p) queued writer can't " "proceed; queue flags 0x%lx", __func__, node->nd_ID, node, ngq->q_flags); return (NULL); } /* * Now we dequeue the request (whatever it may be) and correct the * pending flags and the next and last pointers. */ item = STAILQ_FIRST(&ngq->queue); STAILQ_REMOVE_HEAD(&ngq->queue, el_next); if (STAILQ_EMPTY(&ngq->queue)) atomic_clear_int(&ngq->q_flags, OP_PENDING); CTR6(KTR_NET, "%20s: node [%x] (%p) returning item %p as %s; queue " "flags 0x%lx", __func__, node->nd_ID, node, item, *rw ? "WRITER" : "READER", ngq->q_flags); return (item); } /* * Queue a packet to be picked up later by someone else. * If the queue could be run now, add node to the queue handler's worklist. */ static __inline void ng_queue_rw(node_p node, item_p item, int rw) { struct ng_queue *ngq = &node->nd_input_queue; if (rw == NGQRW_W) NGI_SET_WRITER(item); else NGI_SET_READER(item); item->depth = 1; NG_QUEUE_LOCK(ngq); /* Set OP_PENDING flag and enqueue the item. */ atomic_set_int(&ngq->q_flags, OP_PENDING); STAILQ_INSERT_TAIL(&ngq->queue, item, el_next); CTR5(KTR_NET, "%20s: node [%x] (%p) queued item %p as %s", __func__, node->nd_ID, node, item, rw ? "WRITER" : "READER" ); /* * We can take the worklist lock with the node locked * BUT NOT THE REVERSE! */ if (NEXT_QUEUED_ITEM_CAN_PROCEED(ngq)) ng_worklist_add(node); NG_QUEUE_UNLOCK(ngq); } /* Acquire reader lock on node. If node is busy, queue the packet. */ static __inline item_p ng_acquire_read(node_p node, item_p item) { KASSERT(node != &ng_deadnode, ("%s: working on deadnode", __func__)); /* Reader needs node without writer and pending items. */ for (;;) { long t = node->nd_input_queue.q_flags; if (t & NGQ_RMASK) break; /* Node is not ready for reader. */ if (atomic_cmpset_acq_int(&node->nd_input_queue.q_flags, t, t + READER_INCREMENT)) { /* Successfully grabbed node */ CTR4(KTR_NET, "%20s: node [%x] (%p) acquired item %p", __func__, node->nd_ID, node, item); return (item); } cpu_spinwait(); } /* Queue the request for later. */ ng_queue_rw(node, item, NGQRW_R); return (NULL); } /* Acquire writer lock on node. If node is busy, queue the packet. */ static __inline item_p ng_acquire_write(node_p node, item_p item) { KASSERT(node != &ng_deadnode, ("%s: working on deadnode", __func__)); /* Writer needs completely idle node. */ if (atomic_cmpset_acq_int(&node->nd_input_queue.q_flags, 0, WRITER_ACTIVE)) { /* Successfully grabbed node */ CTR4(KTR_NET, "%20s: node [%x] (%p) acquired item %p", __func__, node->nd_ID, node, item); return (item); } /* Queue the request for later. */ ng_queue_rw(node, item, NGQRW_W); return (NULL); } #if 0 static __inline item_p ng_upgrade_write(node_p node, item_p item) { struct ng_queue *ngq = &node->nd_input_queue; KASSERT(node != &ng_deadnode, ("%s: working on deadnode", __func__)); NGI_SET_WRITER(item); NG_QUEUE_LOCK(ngq); /* * There will never be no readers as we are there ourselves. * Set the WRITER_ACTIVE flags ASAP to block out fast track readers. * The caller we are running from will call ng_leave_read() * soon, so we must account for that. We must leave again with the * READER lock. If we find other readers, then * queue the request for later. However "later" may be rignt now * if there are no readers. We don't really care if there are queued * items as we will bypass them anyhow. */ atomic_add_int(&ngq->q_flags, WRITER_ACTIVE - READER_INCREMENT); if ((ngq->q_flags & (NGQ_WMASK & ~OP_PENDING)) == WRITER_ACTIVE) { NG_QUEUE_UNLOCK(ngq); /* It's just us, act on the item. */ /* will NOT drop writer lock when done */ ng_apply_item(node, item, 0); /* * Having acted on the item, atomically * downgrade back to READER and finish up. */ atomic_add_int(&ngq->q_flags, READER_INCREMENT - WRITER_ACTIVE); /* Our caller will call ng_leave_read() */ return; } /* * It's not just us active, so queue us AT THE HEAD. * "Why?" I hear you ask. * Put us at the head of the queue as we've already been * through it once. If there is nothing else waiting, * set the correct flags. */ if (STAILQ_EMPTY(&ngq->queue)) { /* We've gone from, 0 to 1 item in the queue */ atomic_set_int(&ngq->q_flags, OP_PENDING); CTR3(KTR_NET, "%20s: node [%x] (%p) set OP_PENDING", __func__, node->nd_ID, node); }; STAILQ_INSERT_HEAD(&ngq->queue, item, el_next); CTR4(KTR_NET, "%20s: node [%x] (%p) requeued item %p as WRITER", __func__, node->nd_ID, node, item ); /* Reverse what we did above. That downgrades us back to reader */ atomic_add_int(&ngq->q_flags, READER_INCREMENT - WRITER_ACTIVE); if (QUEUE_ACTIVE(ngq) && NEXT_QUEUED_ITEM_CAN_PROCEED(ngq)) ng_worklist_add(node); NG_QUEUE_UNLOCK(ngq); return; } #endif /* Release reader lock. */ static __inline void ng_leave_read(node_p node) { atomic_subtract_rel_int(&node->nd_input_queue.q_flags, READER_INCREMENT); } /* Release writer lock. */ static __inline void ng_leave_write(node_p node) { atomic_clear_rel_int(&node->nd_input_queue.q_flags, WRITER_ACTIVE); } /* Purge node queue. Called on node shutdown. */ static void ng_flush_input_queue(node_p node) { struct ng_queue *ngq = &node->nd_input_queue; item_p item; NG_QUEUE_LOCK(ngq); while ((item = STAILQ_FIRST(&ngq->queue)) != NULL) { STAILQ_REMOVE_HEAD(&ngq->queue, el_next); if (STAILQ_EMPTY(&ngq->queue)) atomic_clear_int(&ngq->q_flags, OP_PENDING); NG_QUEUE_UNLOCK(ngq); /* If the item is supplying a callback, call it with an error */ if (item->apply != NULL) { if (item->depth == 1) item->apply->error = ENOENT; if (refcount_release(&item->apply->refs)) { (*item->apply->apply)(item->apply->context, item->apply->error); } } NG_FREE_ITEM(item); NG_QUEUE_LOCK(ngq); } NG_QUEUE_UNLOCK(ngq); } /*********************************************************************** * Externally visible method for sending or queueing messages or data. ***********************************************************************/ /* * The module code should have filled out the item correctly by this stage: * Common: * reference to destination node. * Reference to destination rcv hook if relevant. * apply pointer must be or NULL or reference valid struct ng_apply_info. * Data: * pointer to mbuf * Control_Message: * pointer to msg. * ID of original sender node. (return address) * Function: * Function pointer * void * argument * integer argument * * The nodes have several routines and macros to help with this task: */ int ng_snd_item(item_p item, int flags) { hook_p hook; node_p node; int queue, rw; struct ng_queue *ngq; int error = 0; /* We are sending item, so it must be present! */ KASSERT(item != NULL, ("ng_snd_item: item is NULL")); #ifdef NETGRAPH_DEBUG _ngi_check(item, __FILE__, __LINE__); #endif /* Item was sent once more, postpone apply() call. */ if (item->apply) refcount_acquire(&item->apply->refs); node = NGI_NODE(item); /* Node is never optional. */ KASSERT(node != NULL, ("ng_snd_item: node is NULL")); hook = NGI_HOOK(item); /* Valid hook and mbuf are mandatory for data. */ if ((item->el_flags & NGQF_TYPE) == NGQF_DATA) { KASSERT(hook != NULL, ("ng_snd_item: hook for data is NULL")); if (NGI_M(item) == NULL) ERROUT(EINVAL); CHECK_DATA_MBUF(NGI_M(item)); } /* * If the item or the node specifies single threading, force * writer semantics. Similarly, the node may say one hook always * produces writers. These are overrides. */ if (((item->el_flags & NGQF_RW) == NGQF_WRITER) || (node->nd_flags & NGF_FORCE_WRITER) || (hook && (hook->hk_flags & HK_FORCE_WRITER))) { rw = NGQRW_W; } else { rw = NGQRW_R; } /* * If sender or receiver requests queued delivery, or call graph * loops back from outbound to inbound path, or stack usage * level is dangerous - enqueue message. */ if ((flags & NG_QUEUE) || (hook && (hook->hk_flags & HK_QUEUE))) { queue = 1; } else if (hook && (hook->hk_flags & HK_TO_INBOUND) && curthread->td_ng_outbound) { queue = 1; } else { queue = 0; #ifdef GET_STACK_USAGE /* * Most of netgraph nodes have small stack consumption and * for them 25% of free stack space is more than enough. * Nodes/hooks with higher stack usage should be marked as * HI_STACK. For them 50% of stack will be guaranteed then. * XXX: Values 25% and 50% are completely empirical. */ size_t st, su, sl; GET_STACK_USAGE(st, su); sl = st - su; if ((sl * 4 < st) || ((sl * 2 < st) && ((node->nd_flags & NGF_HI_STACK) || (hook && (hook->hk_flags & HK_HI_STACK))))) queue = 1; #endif } if (queue) { /* Put it on the queue for that node*/ ng_queue_rw(node, item, rw); return ((flags & NG_PROGRESS) ? EINPROGRESS : 0); } /* * We already decided how we will be queueud or treated. * Try get the appropriate operating permission. */ if (rw == NGQRW_R) item = ng_acquire_read(node, item); else item = ng_acquire_write(node, item); /* Item was queued while trying to get permission. */ if (item == NULL) return ((flags & NG_PROGRESS) ? EINPROGRESS : 0); NGI_GET_NODE(item, node); /* zaps stored node */ item->depth++; error = ng_apply_item(node, item, rw); /* drops r/w lock when done */ /* If something is waiting on queue and ready, schedule it. */ ngq = &node->nd_input_queue; if (QUEUE_ACTIVE(ngq)) { NG_QUEUE_LOCK(ngq); if (QUEUE_ACTIVE(ngq) && NEXT_QUEUED_ITEM_CAN_PROCEED(ngq)) ng_worklist_add(node); NG_QUEUE_UNLOCK(ngq); } /* * Node may go away as soon as we remove the reference. * Whatever we do, DO NOT access the node again! */ NG_NODE_UNREF(node); return (error); done: /* If was not sent, apply callback here. */ if (item->apply != NULL) { if (item->depth == 0 && error != 0) item->apply->error = error; if (refcount_release(&item->apply->refs)) { (*item->apply->apply)(item->apply->context, item->apply->error); } } NG_FREE_ITEM(item); return (error); } /* * We have an item that was possibly queued somewhere. * It should contain all the information needed * to run it on the appropriate node/hook. * If there is apply pointer and we own the last reference, call apply(). */ static int ng_apply_item(node_p node, item_p item, int rw) { hook_p hook; ng_rcvdata_t *rcvdata; ng_rcvmsg_t *rcvmsg; struct ng_apply_info *apply; int error = 0, depth; /* Node and item are never optional. */ KASSERT(node != NULL, ("ng_apply_item: node is NULL")); KASSERT(item != NULL, ("ng_apply_item: item is NULL")); NGI_GET_HOOK(item, hook); /* clears stored hook */ #ifdef NETGRAPH_DEBUG _ngi_check(item, __FILE__, __LINE__); #endif apply = item->apply; depth = item->depth; switch (item->el_flags & NGQF_TYPE) { case NGQF_DATA: /* * Check things are still ok as when we were queued. */ KASSERT(hook != NULL, ("ng_apply_item: hook for data is NULL")); if (NG_HOOK_NOT_VALID(hook) || NG_NODE_NOT_VALID(node)) { error = EIO; NG_FREE_ITEM(item); break; } /* * If no receive method, just silently drop it. * Give preference to the hook over-ride method. */ if ((!(rcvdata = hook->hk_rcvdata)) && (!(rcvdata = NG_HOOK_NODE(hook)->nd_type->rcvdata))) { error = 0; NG_FREE_ITEM(item); break; } error = (*rcvdata)(hook, item); break; case NGQF_MESG: if (hook && NG_HOOK_NOT_VALID(hook)) { /* * The hook has been zapped then we can't use it. * Immediately drop its reference. * The message may not need it. */ NG_HOOK_UNREF(hook); hook = NULL; } /* * Similarly, if the node is a zombie there is * nothing we can do with it, drop everything. */ if (NG_NODE_NOT_VALID(node)) { TRAP_ERROR(); error = EINVAL; NG_FREE_ITEM(item); break; } /* * Call the appropriate message handler for the object. * It is up to the message handler to free the message. * If it's a generic message, handle it generically, * otherwise call the type's message handler (if it exists). * XXX (race). Remember that a queued message may * reference a node or hook that has just been * invalidated. It will exist as the queue code * is holding a reference, but.. */ if ((NGI_MSG(item)->header.typecookie == NGM_GENERIC_COOKIE) && ((NGI_MSG(item)->header.flags & NGF_RESP) == 0)) { error = ng_generic_msg(node, item, hook); break; } if (((!hook) || (!(rcvmsg = hook->hk_rcvmsg))) && (!(rcvmsg = node->nd_type->rcvmsg))) { TRAP_ERROR(); error = 0; NG_FREE_ITEM(item); break; } error = (*rcvmsg)(node, item, hook); break; case NGQF_FN: case NGQF_FN2: /* * In the case of the shutdown message we allow it to hit * even if the node is invalid. */ if (NG_NODE_NOT_VALID(node) && NGI_FN(item) != &ng_rmnode) { TRAP_ERROR(); error = EINVAL; NG_FREE_ITEM(item); break; } /* Same is about some internal functions and invalid hook. */ if (hook && NG_HOOK_NOT_VALID(hook) && NGI_FN2(item) != &ng_con_part2 && NGI_FN2(item) != &ng_con_part3 && NGI_FN(item) != &ng_rmhook_part2) { TRAP_ERROR(); error = EINVAL; NG_FREE_ITEM(item); break; } if ((item->el_flags & NGQF_TYPE) == NGQF_FN) { (*NGI_FN(item))(node, hook, NGI_ARG1(item), NGI_ARG2(item)); NG_FREE_ITEM(item); } else /* it is NGQF_FN2 */ error = (*NGI_FN2(item))(node, item, hook); break; } /* * We held references on some of the resources * that we took from the item. Now that we have * finished doing everything, drop those references. */ if (hook) NG_HOOK_UNREF(hook); if (rw == NGQRW_R) ng_leave_read(node); else ng_leave_write(node); /* Apply callback. */ if (apply != NULL) { if (depth == 1 && error != 0) apply->error = error; if (refcount_release(&apply->refs)) (*apply->apply)(apply->context, apply->error); } return (error); } /*********************************************************************** * Implement the 'generic' control messages ***********************************************************************/ static int ng_generic_msg(node_p here, item_p item, hook_p lasthook) { int error = 0; struct ng_mesg *msg; struct ng_mesg *resp = NULL; NGI_GET_MSG(item, msg); if (msg->header.typecookie != NGM_GENERIC_COOKIE) { TRAP_ERROR(); error = EINVAL; goto out; } switch (msg->header.cmd) { case NGM_SHUTDOWN: ng_rmnode(here, NULL, NULL, 0); break; case NGM_MKPEER: { struct ngm_mkpeer *const mkp = (struct ngm_mkpeer *) msg->data; if (msg->header.arglen != sizeof(*mkp)) { TRAP_ERROR(); error = EINVAL; break; } mkp->type[sizeof(mkp->type) - 1] = '\0'; mkp->ourhook[sizeof(mkp->ourhook) - 1] = '\0'; mkp->peerhook[sizeof(mkp->peerhook) - 1] = '\0'; error = ng_mkpeer(here, mkp->ourhook, mkp->peerhook, mkp->type); break; } case NGM_CONNECT: { struct ngm_connect *const con = (struct ngm_connect *) msg->data; node_p node2; if (msg->header.arglen != sizeof(*con)) { TRAP_ERROR(); error = EINVAL; break; } con->path[sizeof(con->path) - 1] = '\0'; con->ourhook[sizeof(con->ourhook) - 1] = '\0'; con->peerhook[sizeof(con->peerhook) - 1] = '\0'; /* Don't forget we get a reference.. */ error = ng_path2noderef(here, con->path, &node2, NULL); if (error) break; error = ng_con_nodes(item, here, con->ourhook, node2, con->peerhook); NG_NODE_UNREF(node2); break; } case NGM_NAME: { struct ngm_name *const nam = (struct ngm_name *) msg->data; if (msg->header.arglen != sizeof(*nam)) { TRAP_ERROR(); error = EINVAL; break; } nam->name[sizeof(nam->name) - 1] = '\0'; error = ng_name_node(here, nam->name); break; } case NGM_RMHOOK: { struct ngm_rmhook *const rmh = (struct ngm_rmhook *) msg->data; hook_p hook; if (msg->header.arglen != sizeof(*rmh)) { TRAP_ERROR(); error = EINVAL; break; } rmh->ourhook[sizeof(rmh->ourhook) - 1] = '\0'; if ((hook = ng_findhook(here, rmh->ourhook)) != NULL) ng_destroy_hook(hook); break; } case NGM_NODEINFO: { struct nodeinfo *ni; NG_MKRESPONSE(resp, msg, sizeof(*ni), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } /* Fill in node info */ ni = (struct nodeinfo *) resp->data; if (NG_NODE_HAS_NAME(here)) strcpy(ni->name, NG_NODE_NAME(here)); strcpy(ni->type, here->nd_type->name); ni->id = ng_node2ID(here); ni->hooks = here->nd_numhooks; break; } case NGM_LISTHOOKS: { const int nhooks = here->nd_numhooks; struct hooklist *hl; struct nodeinfo *ni; hook_p hook; /* Get response struct */ NG_MKRESPONSE(resp, msg, sizeof(*hl) + (nhooks * sizeof(struct linkinfo)), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } hl = (struct hooklist *) resp->data; ni = &hl->nodeinfo; /* Fill in node info */ if (NG_NODE_HAS_NAME(here)) strcpy(ni->name, NG_NODE_NAME(here)); strcpy(ni->type, here->nd_type->name); ni->id = ng_node2ID(here); /* Cycle through the linked list of hooks */ ni->hooks = 0; LIST_FOREACH(hook, &here->nd_hooks, hk_hooks) { struct linkinfo *const link = &hl->link[ni->hooks]; if (ni->hooks >= nhooks) { log(LOG_ERR, "%s: number of %s changed\n", __func__, "hooks"); break; } if (NG_HOOK_NOT_VALID(hook)) continue; strcpy(link->ourhook, NG_HOOK_NAME(hook)); strcpy(link->peerhook, NG_PEER_HOOK_NAME(hook)); if (NG_PEER_NODE_NAME(hook)[0] != '\0') strcpy(link->nodeinfo.name, NG_PEER_NODE_NAME(hook)); strcpy(link->nodeinfo.type, NG_PEER_NODE(hook)->nd_type->name); link->nodeinfo.id = ng_node2ID(NG_PEER_NODE(hook)); link->nodeinfo.hooks = NG_PEER_NODE(hook)->nd_numhooks; ni->hooks++; } break; } case NGM_LISTNODES: { struct namelist *nl; node_p node; int i; IDHASH_RLOCK(); /* Get response struct. */ NG_MKRESPONSE(resp, msg, sizeof(*nl) + (V_ng_nodes * sizeof(struct nodeinfo)), M_NOWAIT | M_ZERO); if (resp == NULL) { IDHASH_RUNLOCK(); error = ENOMEM; break; } nl = (struct namelist *) resp->data; /* Cycle through the lists of nodes. */ nl->numnames = 0; for (i = 0; i <= V_ng_ID_hmask; i++) { LIST_FOREACH(node, &V_ng_ID_hash[i], nd_idnodes) { struct nodeinfo *const np = &nl->nodeinfo[nl->numnames]; if (NG_NODE_NOT_VALID(node)) continue; if (NG_NODE_HAS_NAME(node)) strcpy(np->name, NG_NODE_NAME(node)); strcpy(np->type, node->nd_type->name); np->id = ng_node2ID(node); np->hooks = node->nd_numhooks; KASSERT(nl->numnames < V_ng_nodes, ("%s: no space", __func__)); nl->numnames++; } } IDHASH_RUNLOCK(); break; } case NGM_LISTNAMES: { struct namelist *nl; node_p node; int i; NAMEHASH_RLOCK(); /* Get response struct. */ NG_MKRESPONSE(resp, msg, sizeof(*nl) + (V_ng_named_nodes * sizeof(struct nodeinfo)), M_NOWAIT); if (resp == NULL) { NAMEHASH_RUNLOCK(); error = ENOMEM; break; } nl = (struct namelist *) resp->data; /* Cycle through the lists of nodes. */ nl->numnames = 0; for (i = 0; i <= V_ng_name_hmask; i++) { LIST_FOREACH(node, &V_ng_name_hash[i], nd_nodes) { struct nodeinfo *const np = &nl->nodeinfo[nl->numnames]; if (NG_NODE_NOT_VALID(node)) continue; strcpy(np->name, NG_NODE_NAME(node)); strcpy(np->type, node->nd_type->name); np->id = ng_node2ID(node); np->hooks = node->nd_numhooks; KASSERT(nl->numnames < V_ng_named_nodes, ("%s: no space", __func__)); nl->numnames++; } } NAMEHASH_RUNLOCK(); break; } case NGM_LISTTYPES: { struct typelist *tl; struct ng_type *type; int num = 0; TYPELIST_RLOCK(); /* Count number of types */ LIST_FOREACH(type, &ng_typelist, types) num++; /* Get response struct */ NG_MKRESPONSE(resp, msg, sizeof(*tl) + (num * sizeof(struct typeinfo)), M_NOWAIT); if (resp == NULL) { TYPELIST_RUNLOCK(); error = ENOMEM; break; } tl = (struct typelist *) resp->data; /* Cycle through the linked list of types */ tl->numtypes = 0; LIST_FOREACH(type, &ng_typelist, types) { struct typeinfo *const tp = &tl->typeinfo[tl->numtypes]; strcpy(tp->type_name, type->name); tp->numnodes = type->refs - 1; /* don't count list */ KASSERT(tl->numtypes < num, ("%s: no space", __func__)); tl->numtypes++; } TYPELIST_RUNLOCK(); break; } case NGM_BINARY2ASCII: { int bufSize = 20 * 1024; /* XXX hard coded constant */ const struct ng_parse_type *argstype; const struct ng_cmdlist *c; struct ng_mesg *binary, *ascii; /* Data area must contain a valid netgraph message */ binary = (struct ng_mesg *)msg->data; if (msg->header.arglen < sizeof(struct ng_mesg) || (msg->header.arglen - sizeof(struct ng_mesg) < binary->header.arglen)) { TRAP_ERROR(); error = EINVAL; break; } /* Get a response message with lots of room */ NG_MKRESPONSE(resp, msg, sizeof(*ascii) + bufSize, M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } ascii = (struct ng_mesg *)resp->data; /* Copy binary message header to response message payload */ bcopy(binary, ascii, sizeof(*binary)); /* Find command by matching typecookie and command number */ for (c = here->nd_type->cmdlist; c != NULL && c->name != NULL; c++) { if (binary->header.typecookie == c->cookie && binary->header.cmd == c->cmd) break; } if (c == NULL || c->name == NULL) { for (c = ng_generic_cmds; c->name != NULL; c++) { if (binary->header.typecookie == c->cookie && binary->header.cmd == c->cmd) break; } if (c->name == NULL) { NG_FREE_MSG(resp); error = ENOSYS; break; } } /* Convert command name to ASCII */ snprintf(ascii->header.cmdstr, sizeof(ascii->header.cmdstr), "%s", c->name); /* Convert command arguments to ASCII */ argstype = (binary->header.flags & NGF_RESP) ? c->respType : c->mesgType; if (argstype == NULL) { *ascii->data = '\0'; } else { if ((error = ng_unparse(argstype, (u_char *)binary->data, ascii->data, bufSize)) != 0) { NG_FREE_MSG(resp); break; } } /* Return the result as struct ng_mesg plus ASCII string */ bufSize = strlen(ascii->data) + 1; ascii->header.arglen = bufSize; resp->header.arglen = sizeof(*ascii) + bufSize; break; } case NGM_ASCII2BINARY: { int bufSize = 20 * 1024; /* XXX hard coded constant */ const struct ng_cmdlist *c; const struct ng_parse_type *argstype; struct ng_mesg *ascii, *binary; int off = 0; /* Data area must contain at least a struct ng_mesg + '\0' */ ascii = (struct ng_mesg *)msg->data; if ((msg->header.arglen < sizeof(*ascii) + 1) || (ascii->header.arglen < 1) || (msg->header.arglen < sizeof(*ascii) + ascii->header.arglen)) { TRAP_ERROR(); error = EINVAL; break; } ascii->data[ascii->header.arglen - 1] = '\0'; /* Get a response message with lots of room */ NG_MKRESPONSE(resp, msg, sizeof(*binary) + bufSize, M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } binary = (struct ng_mesg *)resp->data; /* Copy ASCII message header to response message payload */ bcopy(ascii, binary, sizeof(*ascii)); /* Find command by matching ASCII command string */ for (c = here->nd_type->cmdlist; c != NULL && c->name != NULL; c++) { if (strcmp(ascii->header.cmdstr, c->name) == 0) break; } if (c == NULL || c->name == NULL) { for (c = ng_generic_cmds; c->name != NULL; c++) { if (strcmp(ascii->header.cmdstr, c->name) == 0) break; } if (c->name == NULL) { NG_FREE_MSG(resp); error = ENOSYS; break; } } /* Convert command name to binary */ binary->header.cmd = c->cmd; binary->header.typecookie = c->cookie; /* Convert command arguments to binary */ argstype = (binary->header.flags & NGF_RESP) ? c->respType : c->mesgType; if (argstype == NULL) { bufSize = 0; } else { if ((error = ng_parse(argstype, ascii->data, &off, (u_char *)binary->data, &bufSize)) != 0) { NG_FREE_MSG(resp); break; } } /* Return the result */ binary->header.arglen = bufSize; resp->header.arglen = sizeof(*binary) + bufSize; break; } case NGM_TEXT_CONFIG: case NGM_TEXT_STATUS: /* * This one is tricky as it passes the command down to the * actual node, even though it is a generic type command. * This means we must assume that the item/msg is already freed * when control passes back to us. */ if (here->nd_type->rcvmsg != NULL) { NGI_MSG(item) = msg; /* put it back as we found it */ return((*here->nd_type->rcvmsg)(here, item, lasthook)); } /* Fall through if rcvmsg not supported */ default: TRAP_ERROR(); error = EINVAL; } /* * Sometimes a generic message may be statically allocated * to avoid problems with allocating when in tight memory situations. * Don't free it if it is so. - * I break them appart here, because erros may cause a free if the item + * I break them apart here, because erros may cause a free if the item * in which case we'd be doing it twice. * they are kept together above, to simplify freeing. */ out: NG_RESPOND_MSG(error, here, item, resp); NG_FREE_MSG(msg); return (error); } /************************************************************************ Queue element get/free routines ************************************************************************/ uma_zone_t ng_qzone; uma_zone_t ng_qdzone; static int numthreads = 0; /* number of queue threads */ static int maxalloc = 4096;/* limit the damage of a leak */ static int maxdata = 4096; /* limit the damage of a DoS */ SYSCTL_INT(_net_graph, OID_AUTO, threads, CTLFLAG_RDTUN, &numthreads, 0, "Number of queue processing threads"); SYSCTL_INT(_net_graph, OID_AUTO, maxalloc, CTLFLAG_RDTUN, &maxalloc, 0, "Maximum number of non-data queue items to allocate"); SYSCTL_INT(_net_graph, OID_AUTO, maxdata, CTLFLAG_RDTUN, &maxdata, 0, "Maximum number of data queue items to allocate"); #ifdef NETGRAPH_DEBUG static TAILQ_HEAD(, ng_item) ng_itemlist = TAILQ_HEAD_INITIALIZER(ng_itemlist); static int allocated; /* number of items malloc'd */ #endif /* * Get a queue entry. * This is usually called when a packet first enters netgraph. * By definition, this is usually from an interrupt, or from a user. * Users are not so important, but try be quick for the times that it's * an interrupt. */ static __inline item_p ng_alloc_item(int type, int flags) { item_p item; KASSERT(((type & ~NGQF_TYPE) == 0), ("%s: incorrect item type: %d", __func__, type)); item = uma_zalloc((type == NGQF_DATA) ? ng_qdzone : ng_qzone, ((flags & NG_WAITOK) ? M_WAITOK : M_NOWAIT) | M_ZERO); if (item) { item->el_flags = type; #ifdef NETGRAPH_DEBUG mtx_lock(&ngq_mtx); TAILQ_INSERT_TAIL(&ng_itemlist, item, all); allocated++; mtx_unlock(&ngq_mtx); #endif } return (item); } /* * Release a queue entry */ void ng_free_item(item_p item) { /* * The item may hold resources on it's own. We need to free * these before we can free the item. What they are depends upon * what kind of item it is. it is important that nodes zero * out pointers to resources that they remove from the item * or we release them again here. */ switch (item->el_flags & NGQF_TYPE) { case NGQF_DATA: /* If we have an mbuf still attached.. */ NG_FREE_M(_NGI_M(item)); break; case NGQF_MESG: _NGI_RETADDR(item) = 0; NG_FREE_MSG(_NGI_MSG(item)); break; case NGQF_FN: case NGQF_FN2: /* nothing to free really, */ _NGI_FN(item) = NULL; _NGI_ARG1(item) = NULL; _NGI_ARG2(item) = 0; break; } /* If we still have a node or hook referenced... */ _NGI_CLR_NODE(item); _NGI_CLR_HOOK(item); #ifdef NETGRAPH_DEBUG mtx_lock(&ngq_mtx); TAILQ_REMOVE(&ng_itemlist, item, all); allocated--; mtx_unlock(&ngq_mtx); #endif uma_zfree(((item->el_flags & NGQF_TYPE) == NGQF_DATA) ? ng_qdzone : ng_qzone, item); } /* * Change type of the queue entry. * Possibly reallocates it from another UMA zone. */ static __inline item_p ng_realloc_item(item_p pitem, int type, int flags) { item_p item; int from, to; KASSERT((pitem != NULL), ("%s: can't reallocate NULL", __func__)); KASSERT(((type & ~NGQF_TYPE) == 0), ("%s: incorrect item type: %d", __func__, type)); from = ((pitem->el_flags & NGQF_TYPE) == NGQF_DATA); to = (type == NGQF_DATA); if (from != to) { /* If reallocation is required do it and copy item. */ if ((item = ng_alloc_item(type, flags)) == NULL) { ng_free_item(pitem); return (NULL); } *item = *pitem; ng_free_item(pitem); } else item = pitem; item->el_flags = (item->el_flags & ~NGQF_TYPE) | type; return (item); } /************************************************************************ Module routines ************************************************************************/ /* * Handle the loading/unloading of a netgraph node type module */ int ng_mod_event(module_t mod, int event, void *data) { struct ng_type *const type = data; int error = 0; switch (event) { case MOD_LOAD: /* Register new netgraph node type */ if ((error = ng_newtype(type)) != 0) break; /* Call type specific code */ if (type->mod_event != NULL) if ((error = (*type->mod_event)(mod, event, data))) { TYPELIST_WLOCK(); type->refs--; /* undo it */ LIST_REMOVE(type, types); TYPELIST_WUNLOCK(); } break; case MOD_UNLOAD: if (type->refs > 1) { /* make sure no nodes exist! */ error = EBUSY; } else { if (type->refs == 0) /* failed load, nothing to undo */ break; if (type->mod_event != NULL) { /* check with type */ error = (*type->mod_event)(mod, event, data); if (error != 0) /* type refuses.. */ break; } TYPELIST_WLOCK(); LIST_REMOVE(type, types); TYPELIST_WUNLOCK(); } break; default: if (type->mod_event != NULL) error = (*type->mod_event)(mod, event, data); else error = EOPNOTSUPP; /* XXX ? */ break; } return (error); } static void vnet_netgraph_init(const void *unused __unused) { /* We start with small hashes, but they can grow. */ V_ng_ID_hash = hashinit(16, M_NETGRAPH_NODE, &V_ng_ID_hmask); V_ng_name_hash = hashinit(16, M_NETGRAPH_NODE, &V_ng_name_hmask); } VNET_SYSINIT(vnet_netgraph_init, SI_SUB_NETGRAPH, SI_ORDER_FIRST, vnet_netgraph_init, NULL); #ifdef VIMAGE static void vnet_netgraph_uninit(const void *unused __unused) { node_p node = NULL, last_killed = NULL; int i; do { /* Find a node to kill */ IDHASH_RLOCK(); for (i = 0; i <= V_ng_ID_hmask; i++) { LIST_FOREACH(node, &V_ng_ID_hash[i], nd_idnodes) { if (node != &ng_deadnode) { NG_NODE_REF(node); break; } } if (node != NULL) break; } IDHASH_RUNLOCK(); /* Attempt to kill it only if it is a regular node */ if (node != NULL) { if (node == last_killed) { /* This should never happen */ printf("ng node %s needs NGF_REALLY_DIE\n", node->nd_name); if (node->nd_flags & NGF_REALLY_DIE) panic("ng node %s won't die", node->nd_name); node->nd_flags |= NGF_REALLY_DIE; } ng_rmnode(node, NULL, NULL, 0); NG_NODE_UNREF(node); last_killed = node; } } while (node != NULL); hashdestroy(V_ng_name_hash, M_NETGRAPH_NODE, V_ng_name_hmask); hashdestroy(V_ng_ID_hash, M_NETGRAPH_NODE, V_ng_ID_hmask); } VNET_SYSUNINIT(vnet_netgraph_uninit, SI_SUB_NETGRAPH, SI_ORDER_FIRST, vnet_netgraph_uninit, NULL); #endif /* VIMAGE */ /* * Handle loading and unloading for this code. * The only thing we need to link into is the NETISR strucure. */ static int ngb_mod_event(module_t mod, int event, void *data) { struct proc *p; struct thread *td; int i, error = 0; switch (event) { case MOD_LOAD: /* Initialize everything. */ NG_WORKLIST_LOCK_INIT(); rw_init(&ng_typelist_lock, "netgraph types"); rw_init(&ng_idhash_lock, "netgraph idhash"); rw_init(&ng_namehash_lock, "netgraph namehash"); rw_init(&ng_topo_lock, "netgraph topology mutex"); #ifdef NETGRAPH_DEBUG mtx_init(&ng_nodelist_mtx, "netgraph nodelist mutex", NULL, MTX_DEF); mtx_init(&ngq_mtx, "netgraph item list mutex", NULL, MTX_DEF); #endif ng_qzone = uma_zcreate("NetGraph items", sizeof(struct ng_item), NULL, NULL, NULL, NULL, UMA_ALIGN_CACHE, 0); uma_zone_set_max(ng_qzone, maxalloc); ng_qdzone = uma_zcreate("NetGraph data items", sizeof(struct ng_item), NULL, NULL, NULL, NULL, UMA_ALIGN_CACHE, 0); uma_zone_set_max(ng_qdzone, maxdata); /* Autoconfigure number of threads. */ if (numthreads <= 0) numthreads = mp_ncpus; /* Create threads. */ p = NULL; /* start with no process */ for (i = 0; i < numthreads; i++) { if (kproc_kthread_add(ngthread, NULL, &p, &td, RFHIGHPID, 0, "ng_queue", "ng_queue%d", i)) { numthreads = i; break; } } break; case MOD_UNLOAD: /* You can't unload it because an interface may be using it. */ error = EBUSY; break; default: error = EOPNOTSUPP; break; } return (error); } static moduledata_t netgraph_mod = { "netgraph", ngb_mod_event, (NULL) }; DECLARE_MODULE(netgraph, netgraph_mod, SI_SUB_NETGRAPH, SI_ORDER_FIRST); SYSCTL_NODE(_net, OID_AUTO, graph, CTLFLAG_RW, 0, "netgraph Family"); SYSCTL_INT(_net_graph, OID_AUTO, abi_version, CTLFLAG_RD, SYSCTL_NULL_INT_PTR, NG_ABI_VERSION,""); SYSCTL_INT(_net_graph, OID_AUTO, msg_version, CTLFLAG_RD, SYSCTL_NULL_INT_PTR, NG_VERSION, ""); #ifdef NETGRAPH_DEBUG void dumphook (hook_p hook, char *file, int line) { printf("hook: name %s, %d refs, Last touched:\n", _NG_HOOK_NAME(hook), hook->hk_refs); printf(" Last active @ %s, line %d\n", hook->lastfile, hook->lastline); if (line) { printf(" problem discovered at file %s, line %d\n", file, line); #ifdef KDB kdb_backtrace(); #endif } } void dumpnode(node_p node, char *file, int line) { printf("node: ID [%x]: type '%s', %d hooks, flags 0x%x, %d refs, %s:\n", _NG_NODE_ID(node), node->nd_type->name, node->nd_numhooks, node->nd_flags, node->nd_refs, node->nd_name); printf(" Last active @ %s, line %d\n", node->lastfile, node->lastline); if (line) { printf(" problem discovered at file %s, line %d\n", file, line); #ifdef KDB kdb_backtrace(); #endif } } void dumpitem(item_p item, char *file, int line) { printf(" ACTIVE item, last used at %s, line %d", item->lastfile, item->lastline); switch(item->el_flags & NGQF_TYPE) { case NGQF_DATA: printf(" - [data]\n"); break; case NGQF_MESG: printf(" - retaddr[%d]:\n", _NGI_RETADDR(item)); break; case NGQF_FN: printf(" - fn@%p (%p, %p, %p, %d (%x))\n", _NGI_FN(item), _NGI_NODE(item), _NGI_HOOK(item), item->body.fn.fn_arg1, item->body.fn.fn_arg2, item->body.fn.fn_arg2); break; case NGQF_FN2: printf(" - fn2@%p (%p, %p, %p, %d (%x))\n", _NGI_FN2(item), _NGI_NODE(item), _NGI_HOOK(item), item->body.fn.fn_arg1, item->body.fn.fn_arg2, item->body.fn.fn_arg2); break; } if (line) { printf(" problem discovered at file %s, line %d\n", file, line); if (_NGI_NODE(item)) { printf("node %p ([%x])\n", _NGI_NODE(item), ng_node2ID(_NGI_NODE(item))); } } } static void ng_dumpitems(void) { item_p item; int i = 1; TAILQ_FOREACH(item, &ng_itemlist, all) { printf("[%d] ", i++); dumpitem(item, NULL, 0); } } static void ng_dumpnodes(void) { node_p node; int i = 1; mtx_lock(&ng_nodelist_mtx); SLIST_FOREACH(node, &ng_allnodes, nd_all) { printf("[%d] ", i++); dumpnode(node, NULL, 0); } mtx_unlock(&ng_nodelist_mtx); } static void ng_dumphooks(void) { hook_p hook; int i = 1; mtx_lock(&ng_nodelist_mtx); SLIST_FOREACH(hook, &ng_allhooks, hk_all) { printf("[%d] ", i++); dumphook(hook, NULL, 0); } mtx_unlock(&ng_nodelist_mtx); } static int sysctl_debug_ng_dump_items(SYSCTL_HANDLER_ARGS) { int error; int val; int i; val = allocated; i = 1; error = sysctl_handle_int(oidp, &val, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (val == 42) { ng_dumpitems(); ng_dumpnodes(); ng_dumphooks(); } return (0); } SYSCTL_PROC(_debug, OID_AUTO, ng_dump_items, CTLTYPE_INT | CTLFLAG_RW, 0, sizeof(int), sysctl_debug_ng_dump_items, "I", "Number of allocated items"); #endif /* NETGRAPH_DEBUG */ /*********************************************************************** * Worklist routines **********************************************************************/ /* * Pick a node off the list of nodes with work, * try get an item to process off it. Remove the node from the list. */ static void ngthread(void *arg) { for (;;) { node_p node; /* Get node from the worklist. */ NG_WORKLIST_LOCK(); while ((node = STAILQ_FIRST(&ng_worklist)) == NULL) NG_WORKLIST_SLEEP(); STAILQ_REMOVE_HEAD(&ng_worklist, nd_input_queue.q_work); NG_WORKLIST_UNLOCK(); CURVNET_SET(node->nd_vnet); CTR3(KTR_NET, "%20s: node [%x] (%p) taken off worklist", __func__, node->nd_ID, node); /* * We have the node. We also take over the reference * that the list had on it. * Now process as much as you can, until it won't * let you have another item off the queue. * All this time, keep the reference * that lets us be sure that the node still exists. * Let the reference go at the last minute. */ for (;;) { item_p item; int rw; NG_QUEUE_LOCK(&node->nd_input_queue); item = ng_dequeue(node, &rw); if (item == NULL) { node->nd_input_queue.q_flags2 &= ~NGQ2_WORKQ; NG_QUEUE_UNLOCK(&node->nd_input_queue); break; /* go look for another node */ } else { NG_QUEUE_UNLOCK(&node->nd_input_queue); NGI_GET_NODE(item, node); /* zaps stored node */ ng_apply_item(node, item, rw); NG_NODE_UNREF(node); } } NG_NODE_UNREF(node); CURVNET_RESTORE(); } } /* * XXX * It's posible that a debugging NG_NODE_REF may need * to be outside the mutex zone */ static void ng_worklist_add(node_p node) { mtx_assert(&node->nd_input_queue.q_mtx, MA_OWNED); if ((node->nd_input_queue.q_flags2 & NGQ2_WORKQ) == 0) { /* * If we are not already on the work queue, * then put us on. */ node->nd_input_queue.q_flags2 |= NGQ2_WORKQ; NG_NODE_REF(node); /* XXX safe in mutex? */ NG_WORKLIST_LOCK(); STAILQ_INSERT_TAIL(&ng_worklist, node, nd_input_queue.q_work); NG_WORKLIST_UNLOCK(); CTR3(KTR_NET, "%20s: node [%x] (%p) put on worklist", __func__, node->nd_ID, node); NG_WORKLIST_WAKEUP(); } else { CTR3(KTR_NET, "%20s: node [%x] (%p) already on worklist", __func__, node->nd_ID, node); } } /*********************************************************************** * Externally useable functions to set up a queue item ready for sending ***********************************************************************/ #ifdef NETGRAPH_DEBUG #define ITEM_DEBUG_CHECKS \ do { \ if (NGI_NODE(item) ) { \ printf("item already has node"); \ kdb_enter(KDB_WHY_NETGRAPH, "has node"); \ NGI_CLR_NODE(item); \ } \ if (NGI_HOOK(item) ) { \ printf("item already has hook"); \ kdb_enter(KDB_WHY_NETGRAPH, "has hook"); \ NGI_CLR_HOOK(item); \ } \ } while (0) #else #define ITEM_DEBUG_CHECKS #endif /* * Put mbuf into the item. * Hook and node references will be removed when the item is dequeued. * (or equivalent) * (XXX) Unsafe because no reference held by peer on remote node. * remote node might go away in this timescale. * We know the hooks can't go away because that would require getting * a writer item on both nodes and we must have at least a reader * here to be able to do this. * Note that the hook loaded is the REMOTE hook. * * This is possibly in the critical path for new data. */ item_p ng_package_data(struct mbuf *m, int flags) { item_p item; if ((item = ng_alloc_item(NGQF_DATA, flags)) == NULL) { NG_FREE_M(m); return (NULL); } ITEM_DEBUG_CHECKS; item->el_flags |= NGQF_READER; NGI_M(item) = m; return (item); } /* * Allocate a queue item and put items into it.. * Evaluate the address as this will be needed to queue it and * to work out what some of the fields should be. * Hook and node references will be removed when the item is dequeued. * (or equivalent) */ item_p ng_package_msg(struct ng_mesg *msg, int flags) { item_p item; if ((item = ng_alloc_item(NGQF_MESG, flags)) == NULL) { NG_FREE_MSG(msg); return (NULL); } ITEM_DEBUG_CHECKS; /* Messages items count as writers unless explicitly exempted. */ if (msg->header.cmd & NGM_READONLY) item->el_flags |= NGQF_READER; else item->el_flags |= NGQF_WRITER; /* * Set the current lasthook into the queue item */ NGI_MSG(item) = msg; NGI_RETADDR(item) = 0; return (item); } #define SET_RETADDR(item, here, retaddr) \ do { /* Data or fn items don't have retaddrs */ \ if ((item->el_flags & NGQF_TYPE) == NGQF_MESG) { \ if (retaddr) { \ NGI_RETADDR(item) = retaddr; \ } else { \ /* \ * The old return address should be ok. \ * If there isn't one, use the address \ * here. \ */ \ if (NGI_RETADDR(item) == 0) { \ NGI_RETADDR(item) \ = ng_node2ID(here); \ } \ } \ } \ } while (0) int ng_address_hook(node_p here, item_p item, hook_p hook, ng_ID_t retaddr) { hook_p peer; node_p peernode; ITEM_DEBUG_CHECKS; /* * Quick sanity check.. * Since a hook holds a reference on it's node, once we know * that the peer is still connected (even if invalid,) we know * that the peer node is present, though maybe invalid. */ TOPOLOGY_RLOCK(); if ((hook == NULL) || NG_HOOK_NOT_VALID(hook) || NG_HOOK_NOT_VALID(peer = NG_HOOK_PEER(hook)) || NG_NODE_NOT_VALID(peernode = NG_PEER_NODE(hook))) { NG_FREE_ITEM(item); TRAP_ERROR(); TOPOLOGY_RUNLOCK(); return (ENETDOWN); } /* * Transfer our interest to the other (peer) end. */ NG_HOOK_REF(peer); NG_NODE_REF(peernode); NGI_SET_HOOK(item, peer); NGI_SET_NODE(item, peernode); SET_RETADDR(item, here, retaddr); TOPOLOGY_RUNLOCK(); return (0); } int ng_address_path(node_p here, item_p item, const char *address, ng_ID_t retaddr) { node_p dest = NULL; hook_p hook = NULL; int error; ITEM_DEBUG_CHECKS; /* * Note that ng_path2noderef increments the reference count * on the node for us if it finds one. So we don't have to. */ error = ng_path2noderef(here, address, &dest, &hook); if (error) { NG_FREE_ITEM(item); return (error); } NGI_SET_NODE(item, dest); if (hook) NGI_SET_HOOK(item, hook); SET_RETADDR(item, here, retaddr); return (0); } int ng_address_ID(node_p here, item_p item, ng_ID_t ID, ng_ID_t retaddr) { node_p dest; ITEM_DEBUG_CHECKS; /* * Find the target node. */ dest = ng_ID2noderef(ID); /* GETS REFERENCE! */ if (dest == NULL) { NG_FREE_ITEM(item); TRAP_ERROR(); return(EINVAL); } /* Fill out the contents */ NGI_SET_NODE(item, dest); NGI_CLR_HOOK(item); SET_RETADDR(item, here, retaddr); return (0); } /* * special case to send a message to self (e.g. destroy node) * Possibly indicate an arrival hook too. * Useful for removing that hook :-) */ item_p ng_package_msg_self(node_p here, hook_p hook, struct ng_mesg *msg) { item_p item; /* * Find the target node. * If there is a HOOK argument, then use that in preference * to the address. */ if ((item = ng_alloc_item(NGQF_MESG, NG_NOFLAGS)) == NULL) { NG_FREE_MSG(msg); return (NULL); } /* Fill out the contents */ item->el_flags |= NGQF_WRITER; NG_NODE_REF(here); NGI_SET_NODE(item, here); if (hook) { NG_HOOK_REF(hook); NGI_SET_HOOK(item, hook); } NGI_MSG(item) = msg; NGI_RETADDR(item) = ng_node2ID(here); return (item); } /* * Send ng_item_fn function call to the specified node. */ int ng_send_fn(node_p node, hook_p hook, ng_item_fn *fn, void * arg1, int arg2) { return ng_send_fn1(node, hook, fn, arg1, arg2, NG_NOFLAGS); } int ng_send_fn1(node_p node, hook_p hook, ng_item_fn *fn, void * arg1, int arg2, int flags) { item_p item; if ((item = ng_alloc_item(NGQF_FN, flags)) == NULL) { return (ENOMEM); } item->el_flags |= NGQF_WRITER; NG_NODE_REF(node); /* and one for the item */ NGI_SET_NODE(item, node); if (hook) { NG_HOOK_REF(hook); NGI_SET_HOOK(item, hook); } NGI_FN(item) = fn; NGI_ARG1(item) = arg1; NGI_ARG2(item) = arg2; return(ng_snd_item(item, flags)); } /* * Send ng_item_fn2 function call to the specified node. * * If an optional pitem parameter is supplied, its apply * callback will be copied to the new item. If also NG_REUSE_ITEM * flag is set, no new item will be allocated, but pitem will * be used. */ int ng_send_fn2(node_p node, hook_p hook, item_p pitem, ng_item_fn2 *fn, void *arg1, int arg2, int flags) { item_p item; KASSERT((pitem != NULL || (flags & NG_REUSE_ITEM) == 0), ("%s: NG_REUSE_ITEM but no pitem", __func__)); /* * Allocate a new item if no supplied or * if we can't use supplied one. */ if (pitem == NULL || (flags & NG_REUSE_ITEM) == 0) { if ((item = ng_alloc_item(NGQF_FN2, flags)) == NULL) return (ENOMEM); if (pitem != NULL) item->apply = pitem->apply; } else { if ((item = ng_realloc_item(pitem, NGQF_FN2, flags)) == NULL) return (ENOMEM); } item->el_flags = (item->el_flags & ~NGQF_RW) | NGQF_WRITER; NG_NODE_REF(node); /* and one for the item */ NGI_SET_NODE(item, node); if (hook) { NG_HOOK_REF(hook); NGI_SET_HOOK(item, hook); } NGI_FN2(item) = fn; NGI_ARG1(item) = arg1; NGI_ARG2(item) = arg2; return(ng_snd_item(item, flags)); } /* * Official timeout routines for Netgraph nodes. */ static void ng_callout_trampoline(void *arg) { item_p item = arg; CURVNET_SET(NGI_NODE(item)->nd_vnet); ng_snd_item(item, 0); CURVNET_RESTORE(); } int ng_callout(struct callout *c, node_p node, hook_p hook, int ticks, ng_item_fn *fn, void * arg1, int arg2) { item_p item, oitem; if ((item = ng_alloc_item(NGQF_FN, NG_NOFLAGS)) == NULL) return (ENOMEM); item->el_flags |= NGQF_WRITER; NG_NODE_REF(node); /* and one for the item */ NGI_SET_NODE(item, node); if (hook) { NG_HOOK_REF(hook); NGI_SET_HOOK(item, hook); } NGI_FN(item) = fn; NGI_ARG1(item) = arg1; NGI_ARG2(item) = arg2; oitem = c->c_arg; if (callout_reset(c, ticks, &ng_callout_trampoline, item) == 1 && oitem != NULL) NG_FREE_ITEM(oitem); return (0); } /* A special modified version of untimeout() */ int ng_uncallout(struct callout *c, node_p node) { item_p item; int rval; KASSERT(c != NULL, ("ng_uncallout: NULL callout")); KASSERT(node != NULL, ("ng_uncallout: NULL node")); rval = callout_stop(c); item = c->c_arg; /* Do an extra check */ if ((rval > 0) && (c->c_func == &ng_callout_trampoline) && (NGI_NODE(item) == node)) { /* * We successfully removed it from the queue before it ran * So now we need to unreference everything that was * given extra references. (NG_FREE_ITEM does this). */ NG_FREE_ITEM(item); } c->c_arg = NULL; return (rval); } /* * Set the address, if none given, give the node here. */ void ng_replace_retaddr(node_p here, item_p item, ng_ID_t retaddr) { if (retaddr) { NGI_RETADDR(item) = retaddr; } else { /* * The old return address should be ok. * If there isn't one, use the address here. */ NGI_RETADDR(item) = ng_node2ID(here); } } Index: head/sys/netgraph/ng_bridge.c =================================================================== --- head/sys/netgraph/ng_bridge.c (revision 298812) +++ head/sys/netgraph/ng_bridge.c (revision 298813) @@ -1,1055 +1,1055 @@ /* * ng_bridge.c */ /*- * Copyright (c) 2000 Whistle Communications, Inc. * All rights reserved. * * Subject to the following obligations and disclaimer of warranty, use and * redistribution of this software, in source or object code forms, with or * without modifications are expressly permitted by Whistle Communications; * provided, however, that: * 1. Any and all reproductions of the source or object code must include the * copyright notice above and the following disclaimer of warranties; and * 2. No rights are granted, in any manner or form, to use Whistle * Communications, Inc. trademarks, including the mark "WHISTLE * COMMUNICATIONS" on advertising, endorsements, or otherwise except as * such appears in the above copyright notice or in the software. * * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * Author: Archie Cobbs * * $FreeBSD$ */ /* * ng_bridge(4) netgraph node type * * The node performs standard intelligent Ethernet bridging over * each of its connected hooks, or links. A simple loop detection * algorithm is included which disables a link for priv->conf.loopTimeout * seconds when a host is seen to have jumped from one link to * another within priv->conf.minStableAge seconds. * * We keep a hashtable that maps Ethernet addresses to host info, * which is contained in struct ng_bridge_host's. These structures * tell us on which link the host may be found. A host's entry will * expire after priv->conf.maxStaleness seconds. * * This node is optimzed for stable networks, where machines jump * from one port to the other only rarely. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if 0 /* not used yet */ #include #endif #include #include #include #include #ifdef NG_SEPARATE_MALLOC static MALLOC_DEFINE(M_NETGRAPH_BRIDGE, "netgraph_bridge", "netgraph bridge node"); #else #define M_NETGRAPH_BRIDGE M_NETGRAPH #endif /* Per-link private data */ struct ng_bridge_link { hook_p hook; /* netgraph hook */ u_int16_t loopCount; /* loop ignore timer */ struct ng_bridge_link_stats stats; /* link stats */ }; /* Per-node private data */ struct ng_bridge_private { struct ng_bridge_bucket *tab; /* hash table bucket array */ struct ng_bridge_link *links[NG_BRIDGE_MAX_LINKS]; struct ng_bridge_config conf; /* node configuration */ node_p node; /* netgraph node */ u_int numHosts; /* num entries in table */ u_int numBuckets; /* num buckets in table */ u_int hashMask; /* numBuckets - 1 */ int numLinks; /* num connected links */ int persistent; /* can exist w/o hooks */ struct callout timer; /* one second periodic timer */ }; typedef struct ng_bridge_private *priv_p; /* Information about a host, stored in a hash table entry */ struct ng_bridge_hent { struct ng_bridge_host host; /* actual host info */ SLIST_ENTRY(ng_bridge_hent) next; /* next entry in bucket */ }; /* Hash table bucket declaration */ SLIST_HEAD(ng_bridge_bucket, ng_bridge_hent); /* Netgraph node methods */ static ng_constructor_t ng_bridge_constructor; static ng_rcvmsg_t ng_bridge_rcvmsg; static ng_shutdown_t ng_bridge_shutdown; static ng_newhook_t ng_bridge_newhook; static ng_rcvdata_t ng_bridge_rcvdata; static ng_disconnect_t ng_bridge_disconnect; /* Other internal functions */ static struct ng_bridge_host *ng_bridge_get(priv_p priv, const u_char *addr); static int ng_bridge_put(priv_p priv, const u_char *addr, int linkNum); static void ng_bridge_rehash(priv_p priv); static void ng_bridge_remove_hosts(priv_p priv, int linkNum); static void ng_bridge_timeout(node_p node, hook_p hook, void *arg1, int arg2); static const char *ng_bridge_nodename(node_p node); /* Ethernet broadcast */ static const u_char ng_bridge_bcast_addr[ETHER_ADDR_LEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; /* Store each hook's link number in the private field */ #define LINK_NUM(hook) (*(u_int16_t *)(&(hook)->private)) /* Compare Ethernet addresses using 32 and 16 bit words instead of bytewise */ #define ETHER_EQUAL(a,b) (((const u_int32_t *)(a))[0] \ == ((const u_int32_t *)(b))[0] \ && ((const u_int16_t *)(a))[2] \ == ((const u_int16_t *)(b))[2]) /* Minimum and maximum number of hash buckets. Must be a power of two. */ #define MIN_BUCKETS (1 << 5) /* 32 */ #define MAX_BUCKETS (1 << 14) /* 16384 */ /* Configuration default values */ #define DEFAULT_LOOP_TIMEOUT 60 #define DEFAULT_MAX_STALENESS (15 * 60) /* same as ARP timeout */ #define DEFAULT_MIN_STABLE_AGE 1 /****************************************************************** NETGRAPH PARSE TYPES ******************************************************************/ /* * How to determine the length of the table returned by NGM_BRIDGE_GET_TABLE */ static int ng_bridge_getTableLength(const struct ng_parse_type *type, const u_char *start, const u_char *buf) { const struct ng_bridge_host_ary *const hary = (const struct ng_bridge_host_ary *)(buf - sizeof(u_int32_t)); return hary->numHosts; } /* Parse type for struct ng_bridge_host_ary */ static const struct ng_parse_struct_field ng_bridge_host_type_fields[] = NG_BRIDGE_HOST_TYPE_INFO(&ng_parse_enaddr_type); static const struct ng_parse_type ng_bridge_host_type = { &ng_parse_struct_type, &ng_bridge_host_type_fields }; static const struct ng_parse_array_info ng_bridge_hary_type_info = { &ng_bridge_host_type, ng_bridge_getTableLength }; static const struct ng_parse_type ng_bridge_hary_type = { &ng_parse_array_type, &ng_bridge_hary_type_info }; static const struct ng_parse_struct_field ng_bridge_host_ary_type_fields[] = NG_BRIDGE_HOST_ARY_TYPE_INFO(&ng_bridge_hary_type); static const struct ng_parse_type ng_bridge_host_ary_type = { &ng_parse_struct_type, &ng_bridge_host_ary_type_fields }; /* Parse type for struct ng_bridge_config */ static const struct ng_parse_fixedarray_info ng_bridge_ipfwary_type_info = { &ng_parse_uint8_type, NG_BRIDGE_MAX_LINKS }; static const struct ng_parse_type ng_bridge_ipfwary_type = { &ng_parse_fixedarray_type, &ng_bridge_ipfwary_type_info }; static const struct ng_parse_struct_field ng_bridge_config_type_fields[] = NG_BRIDGE_CONFIG_TYPE_INFO(&ng_bridge_ipfwary_type); static const struct ng_parse_type ng_bridge_config_type = { &ng_parse_struct_type, &ng_bridge_config_type_fields }; /* Parse type for struct ng_bridge_link_stat */ static const struct ng_parse_struct_field ng_bridge_stats_type_fields[] = NG_BRIDGE_STATS_TYPE_INFO; static const struct ng_parse_type ng_bridge_stats_type = { &ng_parse_struct_type, &ng_bridge_stats_type_fields }; /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_bridge_cmdlist[] = { { NGM_BRIDGE_COOKIE, NGM_BRIDGE_SET_CONFIG, "setconfig", &ng_bridge_config_type, NULL }, { NGM_BRIDGE_COOKIE, NGM_BRIDGE_GET_CONFIG, "getconfig", NULL, &ng_bridge_config_type }, { NGM_BRIDGE_COOKIE, NGM_BRIDGE_RESET, "reset", NULL, NULL }, { NGM_BRIDGE_COOKIE, NGM_BRIDGE_GET_STATS, "getstats", &ng_parse_uint32_type, &ng_bridge_stats_type }, { NGM_BRIDGE_COOKIE, NGM_BRIDGE_CLR_STATS, "clrstats", &ng_parse_uint32_type, NULL }, { NGM_BRIDGE_COOKIE, NGM_BRIDGE_GETCLR_STATS, "getclrstats", &ng_parse_uint32_type, &ng_bridge_stats_type }, { NGM_BRIDGE_COOKIE, NGM_BRIDGE_GET_TABLE, "gettable", NULL, &ng_bridge_host_ary_type }, { NGM_BRIDGE_COOKIE, NGM_BRIDGE_SET_PERSISTENT, "setpersistent", NULL, NULL }, { 0 } }; /* Node type descriptor */ static struct ng_type ng_bridge_typestruct = { .version = NG_ABI_VERSION, .name = NG_BRIDGE_NODE_TYPE, .constructor = ng_bridge_constructor, .rcvmsg = ng_bridge_rcvmsg, .shutdown = ng_bridge_shutdown, .newhook = ng_bridge_newhook, .rcvdata = ng_bridge_rcvdata, .disconnect = ng_bridge_disconnect, .cmdlist = ng_bridge_cmdlist, }; NETGRAPH_INIT(bridge, &ng_bridge_typestruct); /****************************************************************** NETGRAPH NODE METHODS ******************************************************************/ /* * Node constructor */ static int ng_bridge_constructor(node_p node) { priv_p priv; /* Allocate and initialize private info */ priv = malloc(sizeof(*priv), M_NETGRAPH_BRIDGE, M_WAITOK | M_ZERO); ng_callout_init(&priv->timer); /* Allocate and initialize hash table, etc. */ priv->tab = malloc(MIN_BUCKETS * sizeof(*priv->tab), M_NETGRAPH_BRIDGE, M_WAITOK | M_ZERO); priv->numBuckets = MIN_BUCKETS; priv->hashMask = MIN_BUCKETS - 1; priv->conf.debugLevel = 1; priv->conf.loopTimeout = DEFAULT_LOOP_TIMEOUT; priv->conf.maxStaleness = DEFAULT_MAX_STALENESS; priv->conf.minStableAge = DEFAULT_MIN_STABLE_AGE; /* * This node has all kinds of stuff that could be screwed by SMP. * Until it gets it's own internal protection, we go through in - * single file. This could hurt a machine bridging beteen two + * single file. This could hurt a machine bridging between two * GB ethernets so it should be fixed. * When it's fixed the process SHOULD NOT SLEEP, spinlocks please! * (and atomic ops ) */ NG_NODE_FORCE_WRITER(node); NG_NODE_SET_PRIVATE(node, priv); priv->node = node; /* Start timer; timer is always running while node is alive */ ng_callout(&priv->timer, node, NULL, hz, ng_bridge_timeout, NULL, 0); /* Done */ return (0); } /* * Method for attaching a new hook */ static int ng_bridge_newhook(node_p node, hook_p hook, const char *name) { const priv_p priv = NG_NODE_PRIVATE(node); /* Check for a link hook */ if (strncmp(name, NG_BRIDGE_HOOK_LINK_PREFIX, strlen(NG_BRIDGE_HOOK_LINK_PREFIX)) == 0) { const char *cp; char *eptr; u_long linkNum; cp = name + strlen(NG_BRIDGE_HOOK_LINK_PREFIX); if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0')) return (EINVAL); linkNum = strtoul(cp, &eptr, 10); if (*eptr != '\0' || linkNum >= NG_BRIDGE_MAX_LINKS) return (EINVAL); if (priv->links[linkNum] != NULL) return (EISCONN); priv->links[linkNum] = malloc(sizeof(*priv->links[linkNum]), M_NETGRAPH_BRIDGE, M_NOWAIT|M_ZERO); if (priv->links[linkNum] == NULL) return (ENOMEM); priv->links[linkNum]->hook = hook; NG_HOOK_SET_PRIVATE(hook, (void *)linkNum); priv->numLinks++; return (0); } /* Unknown hook name */ return (EINVAL); } /* * Receive a control message */ static int ng_bridge_rcvmsg(node_p node, item_p item, hook_p lasthook) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_mesg *resp = NULL; int error = 0; struct ng_mesg *msg; NGI_GET_MSG(item, msg); switch (msg->header.typecookie) { case NGM_BRIDGE_COOKIE: switch (msg->header.cmd) { case NGM_BRIDGE_GET_CONFIG: { struct ng_bridge_config *conf; NG_MKRESPONSE(resp, msg, sizeof(struct ng_bridge_config), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } conf = (struct ng_bridge_config *)resp->data; *conf = priv->conf; /* no sanity checking needed */ break; } case NGM_BRIDGE_SET_CONFIG: { struct ng_bridge_config *conf; int i; if (msg->header.arglen != sizeof(struct ng_bridge_config)) { error = EINVAL; break; } conf = (struct ng_bridge_config *)msg->data; priv->conf = *conf; for (i = 0; i < NG_BRIDGE_MAX_LINKS; i++) priv->conf.ipfw[i] = !!priv->conf.ipfw[i]; break; } case NGM_BRIDGE_RESET: { int i; /* Flush all entries in the hash table */ ng_bridge_remove_hosts(priv, -1); /* Reset all loop detection counters and stats */ for (i = 0; i < NG_BRIDGE_MAX_LINKS; i++) { if (priv->links[i] == NULL) continue; priv->links[i]->loopCount = 0; bzero(&priv->links[i]->stats, sizeof(priv->links[i]->stats)); } break; } case NGM_BRIDGE_GET_STATS: case NGM_BRIDGE_CLR_STATS: case NGM_BRIDGE_GETCLR_STATS: { struct ng_bridge_link *link; int linkNum; /* Get link number */ if (msg->header.arglen != sizeof(u_int32_t)) { error = EINVAL; break; } linkNum = *((u_int32_t *)msg->data); if (linkNum < 0 || linkNum >= NG_BRIDGE_MAX_LINKS) { error = EINVAL; break; } if ((link = priv->links[linkNum]) == NULL) { error = ENOTCONN; break; } /* Get/clear stats */ if (msg->header.cmd != NGM_BRIDGE_CLR_STATS) { NG_MKRESPONSE(resp, msg, sizeof(link->stats), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } bcopy(&link->stats, resp->data, sizeof(link->stats)); } if (msg->header.cmd != NGM_BRIDGE_GET_STATS) bzero(&link->stats, sizeof(link->stats)); break; } case NGM_BRIDGE_GET_TABLE: { struct ng_bridge_host_ary *ary; struct ng_bridge_hent *hent; int i = 0, bucket; NG_MKRESPONSE(resp, msg, sizeof(*ary) + (priv->numHosts * sizeof(*ary->hosts)), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } ary = (struct ng_bridge_host_ary *)resp->data; ary->numHosts = priv->numHosts; for (bucket = 0; bucket < priv->numBuckets; bucket++) { SLIST_FOREACH(hent, &priv->tab[bucket], next) ary->hosts[i++] = hent->host; } break; } case NGM_BRIDGE_SET_PERSISTENT: { priv->persistent = 1; break; } default: error = EINVAL; break; } break; default: error = EINVAL; break; } /* Done */ NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } /* * Receive data on a hook */ static int ng_bridge_rcvdata(hook_p hook, item_p item) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); struct ng_bridge_host *host; struct ng_bridge_link *link; struct ether_header *eh; int error = 0, linkNum, linksSeen; int manycast; struct mbuf *m; struct ng_bridge_link *firstLink; NGI_GET_M(item, m); /* Get link number */ linkNum = (intptr_t)NG_HOOK_PRIVATE(hook); KASSERT(linkNum >= 0 && linkNum < NG_BRIDGE_MAX_LINKS, ("%s: linkNum=%u", __func__, linkNum)); link = priv->links[linkNum]; KASSERT(link != NULL, ("%s: link%d null", __func__, linkNum)); /* Sanity check packet and pull up header */ if (m->m_pkthdr.len < ETHER_HDR_LEN) { link->stats.recvRunts++; NG_FREE_ITEM(item); NG_FREE_M(m); return (EINVAL); } if (m->m_len < ETHER_HDR_LEN && !(m = m_pullup(m, ETHER_HDR_LEN))) { link->stats.memoryFailures++; NG_FREE_ITEM(item); return (ENOBUFS); } eh = mtod(m, struct ether_header *); if ((eh->ether_shost[0] & 1) != 0) { link->stats.recvInvalid++; NG_FREE_ITEM(item); NG_FREE_M(m); return (EINVAL); } /* Is link disabled due to a loopback condition? */ if (link->loopCount != 0) { link->stats.loopDrops++; NG_FREE_ITEM(item); NG_FREE_M(m); return (ELOOP); /* XXX is this an appropriate error? */ } /* Update stats */ link->stats.recvPackets++; link->stats.recvOctets += m->m_pkthdr.len; if ((manycast = (eh->ether_dhost[0] & 1)) != 0) { if (ETHER_EQUAL(eh->ether_dhost, ng_bridge_bcast_addr)) { link->stats.recvBroadcasts++; manycast = 2; } else link->stats.recvMulticasts++; } /* Look up packet's source Ethernet address in hashtable */ if ((host = ng_bridge_get(priv, eh->ether_shost)) != NULL) { /* Update time since last heard from this host */ host->staleness = 0; /* Did host jump to a different link? */ if (host->linkNum != linkNum) { /* * If the host's old link was recently established * on the old link and it's already jumped to a new * link, declare a loopback condition. */ if (host->age < priv->conf.minStableAge) { /* Log the problem */ if (priv->conf.debugLevel >= 2) { struct ifnet *ifp = m->m_pkthdr.rcvif; char suffix[32]; if (ifp != NULL) snprintf(suffix, sizeof(suffix), " (%s)", ifp->if_xname); else *suffix = '\0'; log(LOG_WARNING, "ng_bridge: %s:" " loopback detected on %s%s\n", ng_bridge_nodename(node), NG_HOOK_NAME(hook), suffix); } /* Mark link as linka non grata */ link->loopCount = priv->conf.loopTimeout; link->stats.loopDetects++; /* Forget all hosts on this link */ ng_bridge_remove_hosts(priv, linkNum); /* Drop packet */ link->stats.loopDrops++; NG_FREE_ITEM(item); NG_FREE_M(m); return (ELOOP); /* XXX appropriate? */ } /* Move host over to new link */ host->linkNum = linkNum; host->age = 0; } } else { if (!ng_bridge_put(priv, eh->ether_shost, linkNum)) { link->stats.memoryFailures++; NG_FREE_ITEM(item); NG_FREE_M(m); return (ENOMEM); } } /* Run packet through ipfw processing, if enabled */ #if 0 if (priv->conf.ipfw[linkNum] && V_fw_enable && V_ip_fw_chk_ptr != NULL) { /* XXX not implemented yet */ } #endif /* * If unicast and destination host known, deliver to host's link, * unless it is the same link as the packet came in on. */ if (!manycast) { /* Determine packet destination link */ if ((host = ng_bridge_get(priv, eh->ether_dhost)) != NULL) { struct ng_bridge_link *const destLink = priv->links[host->linkNum]; /* If destination same as incoming link, do nothing */ KASSERT(destLink != NULL, ("%s: link%d null", __func__, host->linkNum)); if (destLink == link) { NG_FREE_ITEM(item); NG_FREE_M(m); return (0); } /* Deliver packet out the destination link */ destLink->stats.xmitPackets++; destLink->stats.xmitOctets += m->m_pkthdr.len; NG_FWD_NEW_DATA(error, item, destLink->hook, m); return (error); } /* Destination host is not known */ link->stats.recvUnknown++; } /* Distribute unknown, multicast, broadcast pkts to all other links */ firstLink = NULL; for (linkNum = linksSeen = 0; linksSeen <= priv->numLinks; linkNum++) { struct ng_bridge_link *destLink; struct mbuf *m2 = NULL; /* * If we have checked all the links then now * send the original on its reserved link */ if (linksSeen == priv->numLinks) { /* If we never saw a good link, leave. */ if (firstLink == NULL) { NG_FREE_ITEM(item); NG_FREE_M(m); return (0); } destLink = firstLink; } else { destLink = priv->links[linkNum]; if (destLink != NULL) linksSeen++; /* Skip incoming link and disconnected links */ if (destLink == NULL || destLink == link) { continue; } if (firstLink == NULL) { /* * This is the first usable link we have found. * Reserve it for the originals. * If we never find another we save a copy. */ firstLink = destLink; continue; } /* * It's usable link but not the reserved (first) one. * Copy mbuf info for sending. */ m2 = m_dup(m, M_NOWAIT); /* XXX m_copypacket() */ if (m2 == NULL) { link->stats.memoryFailures++; NG_FREE_ITEM(item); NG_FREE_M(m); return (ENOBUFS); } } /* Update stats */ destLink->stats.xmitPackets++; destLink->stats.xmitOctets += m->m_pkthdr.len; switch (manycast) { case 0: /* unicast */ break; case 1: /* multicast */ destLink->stats.xmitMulticasts++; break; case 2: /* broadcast */ destLink->stats.xmitBroadcasts++; break; } /* Send packet */ if (destLink == firstLink) { /* * If we've sent all the others, send the original * on the first link we found. */ NG_FWD_NEW_DATA(error, item, destLink->hook, m); break; /* always done last - not really needed. */ } else { NG_SEND_DATA_ONLY(error, destLink->hook, m2); } } return (error); } /* * Shutdown node */ static int ng_bridge_shutdown(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); /* * Shut down everything including the timer. Even if the * callout has already been dequeued and is about to be * run, ng_bridge_timeout() won't be fired as the node * is already marked NGF_INVALID, so we're safe to free * the node now. */ KASSERT(priv->numLinks == 0 && priv->numHosts == 0, ("%s: numLinks=%d numHosts=%d", __func__, priv->numLinks, priv->numHosts)); ng_uncallout(&priv->timer, node); NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(node); free(priv->tab, M_NETGRAPH_BRIDGE); free(priv, M_NETGRAPH_BRIDGE); return (0); } /* * Hook disconnection. */ static int ng_bridge_disconnect(hook_p hook) { const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); int linkNum; /* Get link number */ linkNum = (intptr_t)NG_HOOK_PRIVATE(hook); KASSERT(linkNum >= 0 && linkNum < NG_BRIDGE_MAX_LINKS, ("%s: linkNum=%u", __func__, linkNum)); /* Remove all hosts associated with this link */ ng_bridge_remove_hosts(priv, linkNum); /* Free associated link information */ KASSERT(priv->links[linkNum] != NULL, ("%s: no link", __func__)); free(priv->links[linkNum], M_NETGRAPH_BRIDGE); priv->links[linkNum] = NULL; priv->numLinks--; /* If no more hooks, go away */ if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook))) && !priv->persistent) { ng_rmnode_self(NG_HOOK_NODE(hook)); } return (0); } /****************************************************************** HASH TABLE FUNCTIONS ******************************************************************/ /* * Hash algorithm */ #define HASH(addr,mask) ( (((const u_int16_t *)(addr))[0] \ ^ ((const u_int16_t *)(addr))[1] \ ^ ((const u_int16_t *)(addr))[2]) & (mask) ) /* * Find a host entry in the table. */ static struct ng_bridge_host * ng_bridge_get(priv_p priv, const u_char *addr) { const int bucket = HASH(addr, priv->hashMask); struct ng_bridge_hent *hent; SLIST_FOREACH(hent, &priv->tab[bucket], next) { if (ETHER_EQUAL(hent->host.addr, addr)) return (&hent->host); } return (NULL); } /* * Add a new host entry to the table. This assumes the host doesn't * already exist in the table. Returns 1 on success, 0 if there * was a memory allocation failure. */ static int ng_bridge_put(priv_p priv, const u_char *addr, int linkNum) { const int bucket = HASH(addr, priv->hashMask); struct ng_bridge_hent *hent; #ifdef INVARIANTS /* Assert that entry does not already exist in hashtable */ SLIST_FOREACH(hent, &priv->tab[bucket], next) { KASSERT(!ETHER_EQUAL(hent->host.addr, addr), ("%s: entry %6D exists in table", __func__, addr, ":")); } #endif /* Allocate and initialize new hashtable entry */ hent = malloc(sizeof(*hent), M_NETGRAPH_BRIDGE, M_NOWAIT); if (hent == NULL) return (0); bcopy(addr, hent->host.addr, ETHER_ADDR_LEN); hent->host.linkNum = linkNum; hent->host.staleness = 0; hent->host.age = 0; /* Add new element to hash bucket */ SLIST_INSERT_HEAD(&priv->tab[bucket], hent, next); priv->numHosts++; /* Resize table if necessary */ ng_bridge_rehash(priv); return (1); } /* * Resize the hash table. We try to maintain the number of buckets * such that the load factor is in the range 0.25 to 1.0. * * If we can't get the new memory then we silently fail. This is OK * because things will still work and we'll try again soon anyway. */ static void ng_bridge_rehash(priv_p priv) { struct ng_bridge_bucket *newTab; int oldBucket, newBucket; int newNumBuckets; u_int newMask; /* Is table too full or too empty? */ if (priv->numHosts > priv->numBuckets && (priv->numBuckets << 1) <= MAX_BUCKETS) newNumBuckets = priv->numBuckets << 1; else if (priv->numHosts < (priv->numBuckets >> 2) && (priv->numBuckets >> 2) >= MIN_BUCKETS) newNumBuckets = priv->numBuckets >> 2; else return; newMask = newNumBuckets - 1; /* Allocate and initialize new table */ newTab = malloc(newNumBuckets * sizeof(*newTab), M_NETGRAPH_BRIDGE, M_NOWAIT | M_ZERO); if (newTab == NULL) return; /* Move all entries from old table to new table */ for (oldBucket = 0; oldBucket < priv->numBuckets; oldBucket++) { struct ng_bridge_bucket *const oldList = &priv->tab[oldBucket]; while (!SLIST_EMPTY(oldList)) { struct ng_bridge_hent *const hent = SLIST_FIRST(oldList); SLIST_REMOVE_HEAD(oldList, next); newBucket = HASH(hent->host.addr, newMask); SLIST_INSERT_HEAD(&newTab[newBucket], hent, next); } } /* Replace old table with new one */ if (priv->conf.debugLevel >= 3) { log(LOG_INFO, "ng_bridge: %s: table size %d -> %d\n", ng_bridge_nodename(priv->node), priv->numBuckets, newNumBuckets); } free(priv->tab, M_NETGRAPH_BRIDGE); priv->numBuckets = newNumBuckets; priv->hashMask = newMask; priv->tab = newTab; return; } /****************************************************************** MISC FUNCTIONS ******************************************************************/ /* * Remove all hosts associated with a specific link from the hashtable. * If linkNum == -1, then remove all hosts in the table. */ static void ng_bridge_remove_hosts(priv_p priv, int linkNum) { int bucket; for (bucket = 0; bucket < priv->numBuckets; bucket++) { struct ng_bridge_hent **hptr = &SLIST_FIRST(&priv->tab[bucket]); while (*hptr != NULL) { struct ng_bridge_hent *const hent = *hptr; if (linkNum == -1 || hent->host.linkNum == linkNum) { *hptr = SLIST_NEXT(hent, next); free(hent, M_NETGRAPH_BRIDGE); priv->numHosts--; } else hptr = &SLIST_NEXT(hent, next); } } } /* * Handle our once-per-second timeout event. We do two things: * we decrement link->loopCount for those links being muted due to * a detected loopback condition, and we remove any hosts from * the hashtable whom we haven't heard from in a long while. */ static void ng_bridge_timeout(node_p node, hook_p hook, void *arg1, int arg2) { const priv_p priv = NG_NODE_PRIVATE(node); int bucket; int counter = 0; int linkNum; /* Update host time counters and remove stale entries */ for (bucket = 0; bucket < priv->numBuckets; bucket++) { struct ng_bridge_hent **hptr = &SLIST_FIRST(&priv->tab[bucket]); while (*hptr != NULL) { struct ng_bridge_hent *const hent = *hptr; /* Make sure host's link really exists */ KASSERT(priv->links[hent->host.linkNum] != NULL, ("%s: host %6D on nonexistent link %d\n", __func__, hent->host.addr, ":", hent->host.linkNum)); /* Remove hosts we haven't heard from in a while */ if (++hent->host.staleness >= priv->conf.maxStaleness) { *hptr = SLIST_NEXT(hent, next); free(hent, M_NETGRAPH_BRIDGE); priv->numHosts--; } else { if (hent->host.age < 0xffff) hent->host.age++; hptr = &SLIST_NEXT(hent, next); counter++; } } } KASSERT(priv->numHosts == counter, ("%s: hosts: %d != %d", __func__, priv->numHosts, counter)); /* Decrease table size if necessary */ ng_bridge_rehash(priv); /* Decrease loop counter on muted looped back links */ for (counter = linkNum = 0; linkNum < NG_BRIDGE_MAX_LINKS; linkNum++) { struct ng_bridge_link *const link = priv->links[linkNum]; if (link != NULL) { if (link->loopCount != 0) { link->loopCount--; if (link->loopCount == 0 && priv->conf.debugLevel >= 2) { log(LOG_INFO, "ng_bridge: %s:" " restoring looped back link%d\n", ng_bridge_nodename(node), linkNum); } } counter++; } } KASSERT(priv->numLinks == counter, ("%s: links: %d != %d", __func__, priv->numLinks, counter)); /* Register a new timeout, keeping the existing node reference */ ng_callout(&priv->timer, node, NULL, hz, ng_bridge_timeout, NULL, 0); } /* * Return node's "name", even if it doesn't have one. */ static const char * ng_bridge_nodename(node_p node) { static char name[NG_NODESIZ]; if (NG_NODE_HAS_NAME(node)) snprintf(name, sizeof(name), "%s", NG_NODE_NAME(node)); else snprintf(name, sizeof(name), "[%x]", ng_node2ID(node)); return name; } Index: head/sys/netgraph/ng_car.c =================================================================== --- head/sys/netgraph/ng_car.c (revision 298812) +++ head/sys/netgraph/ng_car.c (revision 298813) @@ -1,764 +1,764 @@ /*- * Copyright (c) 2005 Nuno Antunes * Copyright (c) 2007 Alexander Motin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ /* - * ng_car - An implementation of commited access rate for netgraph + * ng_car - An implementation of committed access rate for netgraph * * TODO: * - Sanitize input config values (impose some limits) * - Implement internal packet painting (possibly using mbuf tags) * - Implement color-aware mode * - Implement DSCP marking for IPv4 */ #include #include #include #include #include #include #include #include #include #define NG_CAR_QUEUE_SIZE 100 /* Maximum queue size for SHAPE mode */ -#define NG_CAR_QUEUE_MIN_TH 8 /* Minimum RED threshhold for SHAPE mode */ +#define NG_CAR_QUEUE_MIN_TH 8 /* Minimum RED threshold for SHAPE mode */ /* Hook private info */ struct hookinfo { hook_p hook; /* this (source) hook */ hook_p dest; /* destination hook */ - int64_t tc; /* commited token bucket counter */ + int64_t tc; /* committed token bucket counter */ int64_t te; /* exceeded/peak token bucket counter */ struct bintime lastRefill; /* last token refill time */ struct ng_car_hookconf conf; /* hook configuration */ struct ng_car_hookstats stats; /* hook stats */ struct mbuf *q[NG_CAR_QUEUE_SIZE]; /* circular packet queue */ u_int q_first; /* first queue element */ u_int q_last; /* last queue element */ struct callout q_callout; /* periodic queue processing routine */ struct mtx q_mtx; /* queue mutex */ }; /* Private information for each node instance */ struct privdata { node_p node; /* the node itself */ struct hookinfo upper; /* hook to upper layers */ struct hookinfo lower; /* hook to lower layers */ }; typedef struct privdata *priv_p; static ng_constructor_t ng_car_constructor; static ng_rcvmsg_t ng_car_rcvmsg; static ng_shutdown_t ng_car_shutdown; static ng_newhook_t ng_car_newhook; static ng_rcvdata_t ng_car_rcvdata; static ng_disconnect_t ng_car_disconnect; static void ng_car_refillhook(struct hookinfo *h); static void ng_car_schedule(struct hookinfo *h); void ng_car_q_event(node_p node, hook_p hook, void *arg, int arg2); static void ng_car_enqueue(struct hookinfo *h, item_p item); /* Parse type for struct ng_car_hookstats */ static const struct ng_parse_struct_field ng_car_hookstats_type_fields[] = NG_CAR_HOOKSTATS; static const struct ng_parse_type ng_car_hookstats_type = { &ng_parse_struct_type, &ng_car_hookstats_type_fields }; /* Parse type for struct ng_car_bulkstats */ static const struct ng_parse_struct_field ng_car_bulkstats_type_fields[] = NG_CAR_BULKSTATS(&ng_car_hookstats_type); static const struct ng_parse_type ng_car_bulkstats_type = { &ng_parse_struct_type, &ng_car_bulkstats_type_fields }; /* Parse type for struct ng_car_hookconf */ static const struct ng_parse_struct_field ng_car_hookconf_type_fields[] = NG_CAR_HOOKCONF; static const struct ng_parse_type ng_car_hookconf_type = { &ng_parse_struct_type, &ng_car_hookconf_type_fields }; /* Parse type for struct ng_car_bulkconf */ static const struct ng_parse_struct_field ng_car_bulkconf_type_fields[] = NG_CAR_BULKCONF(&ng_car_hookconf_type); static const struct ng_parse_type ng_car_bulkconf_type = { &ng_parse_struct_type, &ng_car_bulkconf_type_fields }; /* Command list */ static struct ng_cmdlist ng_car_cmdlist[] = { { NGM_CAR_COOKIE, NGM_CAR_GET_STATS, "getstats", NULL, &ng_car_bulkstats_type, }, { NGM_CAR_COOKIE, NGM_CAR_CLR_STATS, "clrstats", NULL, NULL, }, { NGM_CAR_COOKIE, NGM_CAR_GETCLR_STATS, "getclrstats", NULL, &ng_car_bulkstats_type, }, { NGM_CAR_COOKIE, NGM_CAR_GET_CONF, "getconf", NULL, &ng_car_bulkconf_type, }, { NGM_CAR_COOKIE, NGM_CAR_SET_CONF, "setconf", &ng_car_bulkconf_type, NULL, }, { 0 } }; /* Netgraph node type descriptor */ static struct ng_type ng_car_typestruct = { .version = NG_ABI_VERSION, .name = NG_CAR_NODE_TYPE, .constructor = ng_car_constructor, .rcvmsg = ng_car_rcvmsg, .shutdown = ng_car_shutdown, .newhook = ng_car_newhook, .rcvdata = ng_car_rcvdata, .disconnect = ng_car_disconnect, .cmdlist = ng_car_cmdlist, }; NETGRAPH_INIT(car, &ng_car_typestruct); /* * Node constructor */ static int ng_car_constructor(node_p node) { priv_p priv; /* Initialize private descriptor. */ priv = malloc(sizeof(*priv), M_NETGRAPH, M_WAITOK | M_ZERO); NG_NODE_SET_PRIVATE(node, priv); priv->node = node; /* * Arbitrary default values */ priv->upper.hook = NULL; priv->upper.dest = NULL; priv->upper.tc = priv->upper.conf.cbs = NG_CAR_CBS_MIN; priv->upper.te = priv->upper.conf.ebs = NG_CAR_EBS_MIN; priv->upper.conf.cir = NG_CAR_CIR_DFLT; priv->upper.conf.green_action = NG_CAR_ACTION_FORWARD; priv->upper.conf.yellow_action = NG_CAR_ACTION_FORWARD; priv->upper.conf.red_action = NG_CAR_ACTION_DROP; priv->upper.conf.mode = 0; getbinuptime(&priv->upper.lastRefill); priv->upper.q_first = 0; priv->upper.q_last = 0; ng_callout_init(&priv->upper.q_callout); mtx_init(&priv->upper.q_mtx, "ng_car_u", NULL, MTX_DEF); priv->lower.hook = NULL; priv->lower.dest = NULL; priv->lower.tc = priv->lower.conf.cbs = NG_CAR_CBS_MIN; priv->lower.te = priv->lower.conf.ebs = NG_CAR_EBS_MIN; priv->lower.conf.cir = NG_CAR_CIR_DFLT; priv->lower.conf.green_action = NG_CAR_ACTION_FORWARD; priv->lower.conf.yellow_action = NG_CAR_ACTION_FORWARD; priv->lower.conf.red_action = NG_CAR_ACTION_DROP; priv->lower.conf.mode = 0; priv->lower.lastRefill = priv->upper.lastRefill; priv->lower.q_first = 0; priv->lower.q_last = 0; ng_callout_init(&priv->lower.q_callout); mtx_init(&priv->lower.q_mtx, "ng_car_l", NULL, MTX_DEF); return (0); } /* * Add a hook. */ static int ng_car_newhook(node_p node, hook_p hook, const char *name) { const priv_p priv = NG_NODE_PRIVATE(node); if (strcmp(name, NG_CAR_HOOK_LOWER) == 0) { priv->lower.hook = hook; priv->upper.dest = hook; bzero(&priv->lower.stats, sizeof(priv->lower.stats)); NG_HOOK_SET_PRIVATE(hook, &priv->lower); } else if (strcmp(name, NG_CAR_HOOK_UPPER) == 0) { priv->upper.hook = hook; priv->lower.dest = hook; bzero(&priv->upper.stats, sizeof(priv->upper.stats)); NG_HOOK_SET_PRIVATE(hook, &priv->upper); } else return (EINVAL); return(0); } /* * Data has arrived. */ static int ng_car_rcvdata(hook_p hook, item_p item ) { struct hookinfo *const hinfo = NG_HOOK_PRIVATE(hook); struct mbuf *m; int error = 0; u_int len; /* If queue is not empty now then enqueue packet. */ if (hinfo->q_first != hinfo->q_last) { ng_car_enqueue(hinfo, item); return (0); } m = NGI_M(item); #define NG_CAR_PERFORM_MATCH_ACTION(a) \ do { \ switch (a) { \ case NG_CAR_ACTION_FORWARD: \ /* Do nothing. */ \ break; \ case NG_CAR_ACTION_MARK: \ /* XXX find a way to mark packets (mbuf tag?) */ \ ++hinfo->stats.errors; \ break; \ case NG_CAR_ACTION_DROP: \ default: \ /* Drop packet and return. */ \ NG_FREE_ITEM(item); \ ++hinfo->stats.droped_pkts; \ return (0); \ } \ } while (0) /* Packet is counted as 128 tokens for better resolution */ if (hinfo->conf.opt & NG_CAR_COUNT_PACKETS) { len = 128; } else { len = m->m_pkthdr.len; } - /* Check commited token bucket. */ + /* Check committed token bucket. */ if (hinfo->tc - len >= 0) { /* This packet is green. */ ++hinfo->stats.green_pkts; hinfo->tc -= len; NG_CAR_PERFORM_MATCH_ACTION(hinfo->conf.green_action); } else { /* Refill only if not green without it. */ ng_car_refillhook(hinfo); - /* Check commited token bucket again after refill. */ + /* Check committed token bucket again after refill. */ if (hinfo->tc - len >= 0) { /* This packet is green */ ++hinfo->stats.green_pkts; hinfo->tc -= len; NG_CAR_PERFORM_MATCH_ACTION(hinfo->conf.green_action); /* If not green and mode is SHAPE, enqueue packet. */ } else if (hinfo->conf.mode == NG_CAR_SHAPE) { ng_car_enqueue(hinfo, item); return (0); /* If not green and mode is RED, calculate probability. */ } else if (hinfo->conf.mode == NG_CAR_RED) { /* Is packet is bigger then extended burst? */ if (len - (hinfo->tc - len) > hinfo->conf.ebs) { /* This packet is definitely red. */ ++hinfo->stats.red_pkts; hinfo->te = 0; NG_CAR_PERFORM_MATCH_ACTION(hinfo->conf.red_action); /* Use token bucket to simulate RED-like drop probability. */ } else if (hinfo->te + (len - hinfo->tc) < hinfo->conf.ebs) { /* This packet is yellow */ ++hinfo->stats.yellow_pkts; hinfo->te += len - hinfo->tc; /* Go to negative tokens. */ hinfo->tc -= len; NG_CAR_PERFORM_MATCH_ACTION(hinfo->conf.yellow_action); } else { - /* This packet is probaly red. */ + /* This packet is probably red. */ ++hinfo->stats.red_pkts; hinfo->te = 0; NG_CAR_PERFORM_MATCH_ACTION(hinfo->conf.red_action); } /* If not green and mode is SINGLE/DOUBLE RATE. */ } else { /* Check extended token bucket. */ if (hinfo->te - len >= 0) { /* This packet is yellow */ ++hinfo->stats.yellow_pkts; hinfo->te -= len; NG_CAR_PERFORM_MATCH_ACTION(hinfo->conf.yellow_action); } else { /* This packet is red */ ++hinfo->stats.red_pkts; NG_CAR_PERFORM_MATCH_ACTION(hinfo->conf.red_action); } } } #undef NG_CAR_PERFORM_MATCH_ACTION NG_FWD_ITEM_HOOK(error, item, hinfo->dest); if (error != 0) ++hinfo->stats.errors; ++hinfo->stats.passed_pkts; return (error); } /* * Receive a control message. */ static int ng_car_rcvmsg(node_p node, item_p item, hook_p lasthook) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_mesg *resp = NULL; int error = 0; struct ng_mesg *msg; NGI_GET_MSG(item, msg); switch (msg->header.typecookie) { case NGM_CAR_COOKIE: switch (msg->header.cmd) { case NGM_CAR_GET_STATS: case NGM_CAR_GETCLR_STATS: { struct ng_car_bulkstats *bstats; NG_MKRESPONSE(resp, msg, sizeof(*bstats), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } bstats = (struct ng_car_bulkstats *)resp->data; bcopy(&priv->upper.stats, &bstats->downstream, sizeof(bstats->downstream)); bcopy(&priv->lower.stats, &bstats->upstream, sizeof(bstats->upstream)); } if (msg->header.cmd == NGM_CAR_GET_STATS) break; case NGM_CAR_CLR_STATS: bzero(&priv->upper.stats, sizeof(priv->upper.stats)); bzero(&priv->lower.stats, sizeof(priv->lower.stats)); break; case NGM_CAR_GET_CONF: { struct ng_car_bulkconf *bconf; NG_MKRESPONSE(resp, msg, sizeof(*bconf), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } bconf = (struct ng_car_bulkconf *)resp->data; bcopy(&priv->upper.conf, &bconf->downstream, sizeof(bconf->downstream)); bcopy(&priv->lower.conf, &bconf->upstream, sizeof(bconf->upstream)); /* Convert internal 1/(8*128) of pps into pps */ if (bconf->downstream.opt & NG_CAR_COUNT_PACKETS) { bconf->downstream.cir /= 1024; bconf->downstream.pir /= 1024; bconf->downstream.cbs /= 128; bconf->downstream.ebs /= 128; } if (bconf->upstream.opt & NG_CAR_COUNT_PACKETS) { bconf->upstream.cir /= 1024; bconf->upstream.pir /= 1024; bconf->upstream.cbs /= 128; bconf->upstream.ebs /= 128; } } break; case NGM_CAR_SET_CONF: { struct ng_car_bulkconf *const bconf = (struct ng_car_bulkconf *)msg->data; /* Check for invalid or illegal config. */ if (msg->header.arglen != sizeof(*bconf)) { error = EINVAL; break; } /* Convert pps into internal 1/(8*128) of pps */ if (bconf->downstream.opt & NG_CAR_COUNT_PACKETS) { bconf->downstream.cir *= 1024; bconf->downstream.pir *= 1024; bconf->downstream.cbs *= 125; bconf->downstream.ebs *= 125; } if (bconf->upstream.opt & NG_CAR_COUNT_PACKETS) { bconf->upstream.cir *= 1024; bconf->upstream.pir *= 1024; bconf->upstream.cbs *= 125; bconf->upstream.ebs *= 125; } if ((bconf->downstream.cir > 1000000000) || (bconf->downstream.pir > 1000000000) || (bconf->upstream.cir > 1000000000) || (bconf->upstream.pir > 1000000000) || (bconf->downstream.cbs == 0 && bconf->downstream.ebs == 0) || (bconf->upstream.cbs == 0 && bconf->upstream.ebs == 0)) { error = EINVAL; break; } if ((bconf->upstream.mode == NG_CAR_SHAPE) && (bconf->upstream.cir == 0)) { error = EINVAL; break; } if ((bconf->downstream.mode == NG_CAR_SHAPE) && (bconf->downstream.cir == 0)) { error = EINVAL; break; } /* Copy downstream config. */ bcopy(&bconf->downstream, &priv->upper.conf, sizeof(priv->upper.conf)); priv->upper.tc = priv->upper.conf.cbs; if (priv->upper.conf.mode == NG_CAR_RED || priv->upper.conf.mode == NG_CAR_SHAPE) { priv->upper.te = 0; } else { priv->upper.te = priv->upper.conf.ebs; } /* Copy upstream config. */ bcopy(&bconf->upstream, &priv->lower.conf, sizeof(priv->lower.conf)); priv->lower.tc = priv->lower.conf.cbs; if (priv->lower.conf.mode == NG_CAR_RED || priv->lower.conf.mode == NG_CAR_SHAPE) { priv->lower.te = 0; } else { priv->lower.te = priv->lower.conf.ebs; } } break; default: error = EINVAL; break; } break; default: error = EINVAL; break; } NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } /* * Do local shutdown processing. */ static int ng_car_shutdown(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); ng_uncallout(&priv->upper.q_callout, node); ng_uncallout(&priv->lower.q_callout, node); mtx_destroy(&priv->upper.q_mtx); mtx_destroy(&priv->lower.q_mtx); NG_NODE_UNREF(priv->node); free(priv, M_NETGRAPH); return (0); } /* * Hook disconnection. * * For this type, removal of the last link destroys the node. */ static int ng_car_disconnect(hook_p hook) { struct hookinfo *const hinfo = NG_HOOK_PRIVATE(hook); const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); if (hinfo) { /* Purge queue if not empty. */ while (hinfo->q_first != hinfo->q_last) { NG_FREE_M(hinfo->q[hinfo->q_first]); hinfo->q_first++; if (hinfo->q_first >= NG_CAR_QUEUE_SIZE) hinfo->q_first = 0; } /* Remove hook refs. */ if (hinfo->hook == priv->upper.hook) priv->lower.dest = NULL; else priv->upper.dest = NULL; hinfo->hook = NULL; } /* Already shutting down? */ if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) ng_rmnode_self(NG_HOOK_NODE(hook)); return (0); } /* * Hook's token buckets refillment. */ static void ng_car_refillhook(struct hookinfo *h) { struct bintime newt, deltat; unsigned int deltat_us; /* Get current time. */ getbinuptime(&newt); /* Get time delta since last refill. */ deltat = newt; bintime_sub(&deltat, &h->lastRefill); /* Time must go forward. */ if (deltat.sec < 0) { h->lastRefill = newt; return; } /* But not too far forward. */ if (deltat.sec >= 1000) { deltat_us = (1000 << 20); } else { /* convert bintime to the 1/(2^20) of sec */ deltat_us = (deltat.sec << 20) + (deltat.frac >> 44); } if (h->conf.mode == NG_CAR_SINGLE_RATE) { int64_t delta; - /* Refill commited token bucket. */ + /* Refill committed token bucket. */ h->tc += (h->conf.cir * deltat_us) >> 23; delta = h->tc - h->conf.cbs; if (delta > 0) { h->tc = h->conf.cbs; /* Refill exceeded token bucket. */ h->te += delta; if (h->te > ((int64_t)h->conf.ebs)) h->te = h->conf.ebs; } } else if (h->conf.mode == NG_CAR_DOUBLE_RATE) { - /* Refill commited token bucket. */ + /* Refill committed token bucket. */ h->tc += (h->conf.cir * deltat_us) >> 23; if (h->tc > ((int64_t)h->conf.cbs)) h->tc = h->conf.cbs; /* Refill peak token bucket. */ h->te += (h->conf.pir * deltat_us) >> 23; if (h->te > ((int64_t)h->conf.ebs)) h->te = h->conf.ebs; } else { /* RED or SHAPE mode. */ - /* Refill commited token bucket. */ + /* Refill committed token bucket. */ h->tc += (h->conf.cir * deltat_us) >> 23; if (h->tc > ((int64_t)h->conf.cbs)) h->tc = h->conf.cbs; } /* Remember this moment. */ h->lastRefill = newt; } /* * Schedule callout when we will have required tokens. */ static void ng_car_schedule(struct hookinfo *hinfo) { int delay; delay = (-(hinfo->tc)) * hz * 8 / hinfo->conf.cir + 1; ng_callout(&hinfo->q_callout, NG_HOOK_NODE(hinfo->hook), hinfo->hook, delay, &ng_car_q_event, NULL, 0); } /* * Queue processing callout handler. */ void ng_car_q_event(node_p node, hook_p hook, void *arg, int arg2) { struct hookinfo *hinfo = NG_HOOK_PRIVATE(hook); struct mbuf *m; int error; /* Refill tokens for time we have slept. */ ng_car_refillhook(hinfo); /* If we have some tokens */ while (hinfo->tc >= 0) { /* Send packet. */ m = hinfo->q[hinfo->q_first]; NG_SEND_DATA_ONLY(error, hinfo->dest, m); if (error != 0) ++hinfo->stats.errors; ++hinfo->stats.passed_pkts; /* Get next one. */ hinfo->q_first++; if (hinfo->q_first >= NG_CAR_QUEUE_SIZE) hinfo->q_first = 0; /* Stop if none left. */ if (hinfo->q_first == hinfo->q_last) break; /* If we have more packet, try it. */ m = hinfo->q[hinfo->q_first]; if (hinfo->conf.opt & NG_CAR_COUNT_PACKETS) { hinfo->tc -= 128; } else { hinfo->tc -= m->m_pkthdr.len; } } /* If something left */ if (hinfo->q_first != hinfo->q_last) /* Schedule queue processing. */ ng_car_schedule(hinfo); } /* * Enqueue packet. */ static void ng_car_enqueue(struct hookinfo *hinfo, item_p item) { struct mbuf *m; int len; NGI_GET_M(item, m); NG_FREE_ITEM(item); /* Lock queue mutex. */ mtx_lock(&hinfo->q_mtx); /* Calculate used queue length. */ len = hinfo->q_last - hinfo->q_first; if (len < 0) len += NG_CAR_QUEUE_SIZE; /* If queue is overflowed or we have no RED tokens. */ if ((len >= (NG_CAR_QUEUE_SIZE - 1)) || (hinfo->te + len >= NG_CAR_QUEUE_SIZE)) { /* Drop packet. */ ++hinfo->stats.red_pkts; ++hinfo->stats.droped_pkts; NG_FREE_M(m); hinfo->te = 0; } else { /* This packet is yellow. */ ++hinfo->stats.yellow_pkts; /* Enqueue packet. */ hinfo->q[hinfo->q_last] = m; hinfo->q_last++; if (hinfo->q_last >= NG_CAR_QUEUE_SIZE) hinfo->q_last = 0; /* Use RED tokens. */ if (len > NG_CAR_QUEUE_MIN_TH) hinfo->te += len - NG_CAR_QUEUE_MIN_TH; /* If this is a first packet in the queue. */ if (len == 0) { if (hinfo->conf.opt & NG_CAR_COUNT_PACKETS) { hinfo->tc -= 128; } else { hinfo->tc -= m->m_pkthdr.len; } /* Schedule queue processing. */ ng_car_schedule(hinfo); } } /* Unlock queue mutex. */ mtx_unlock(&hinfo->q_mtx); } Index: head/sys/netgraph/ng_car.h =================================================================== --- head/sys/netgraph/ng_car.h (revision 298812) +++ head/sys/netgraph/ng_car.h (revision 298813) @@ -1,140 +1,140 @@ /*- * Copyright (c) 2005 Nuno Antunes * Copyright (c) 2007 Alexander Motin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #ifndef _NETGRAPH_NG_CAR_H_ #define _NETGRAPH_NG_CAR_H_ #define NG_CAR_NODE_TYPE "car" #define NGM_CAR_COOKIE 1173648034 /* Hook names */ #define NG_CAR_HOOK_UPPER "upper" #define NG_CAR_HOOK_LOWER "lower" /* Per hook statistics counters */ struct ng_car_hookstats { u_int64_t passed_pkts; /* Counter for passed packets */ u_int64_t droped_pkts; /* Counter for droped packets */ u_int64_t green_pkts; /* Counter for green packets */ u_int64_t yellow_pkts; /* Counter for yellow packets */ u_int64_t red_pkts; /* Counter for red packets */ u_int64_t errors; /* Counter for operation errors */ }; #define NG_CAR_HOOKSTATS { \ { "passed", &ng_parse_uint64_type }, \ { "droped", &ng_parse_uint64_type }, \ { "green", &ng_parse_uint64_type }, \ { "yellow", &ng_parse_uint64_type }, \ { "red", &ng_parse_uint64_type }, \ { "errors", &ng_parse_uint64_type }, \ { NULL } \ } /* Bulk statistics */ struct ng_car_bulkstats { struct ng_car_hookstats upstream; struct ng_car_hookstats downstream; }; #define NG_CAR_BULKSTATS(hstatstype) { \ { "upstream", (hstatstype) }, \ { "downstream", (hstatstype) }, \ { NULL } \ } /* Per hook configuration */ struct ng_car_hookconf { - u_int64_t cbs; /* Commited burst size (bytes) */ + u_int64_t cbs; /* Committed burst size (bytes) */ u_int64_t ebs; /* Exceeded/Peak burst size (bytes) */ - u_int64_t cir; /* Commited information rate (bits/s) */ + u_int64_t cir; /* Committed information rate (bits/s) */ u_int64_t pir; /* Peak information rate (bits/s) */ u_int8_t green_action; /* Action for green packets */ u_int8_t yellow_action; /* Action for yellow packets */ u_int8_t red_action; /* Action for red packets */ u_int8_t mode; /* single/double rate, ... */ u_int8_t opt; /* color-aware or color-blind */ }; /* Keep this definition in sync with the above structure */ #define NG_CAR_HOOKCONF { \ { "cbs", &ng_parse_uint64_type }, \ { "ebs", &ng_parse_uint64_type }, \ { "cir", &ng_parse_uint64_type }, \ { "pir", &ng_parse_uint64_type }, \ { "greenAction", &ng_parse_uint8_type }, \ { "yellowAction", &ng_parse_uint8_type }, \ { "redAction", &ng_parse_uint8_type }, \ { "mode", &ng_parse_uint8_type }, \ { "opt", &ng_parse_uint8_type }, \ { NULL } \ } #define NG_CAR_CBS_MIN 8192 #define NG_CAR_EBS_MIN 8192 #define NG_CAR_CIR_DFLT 10240 /* possible actions (...Action) */ enum { NG_CAR_ACTION_FORWARD = 1, NG_CAR_ACTION_DROP, NG_CAR_ACTION_MARK, NG_CAR_ACTION_SET_TOS }; /* operation modes (mode) */ enum { NG_CAR_SINGLE_RATE = 0, NG_CAR_DOUBLE_RATE, NG_CAR_RED, NG_CAR_SHAPE }; /* mode options (opt) */ #define NG_CAR_COLOR_AWARE 1 #define NG_CAR_COUNT_PACKETS 2 /* Bulk config */ struct ng_car_bulkconf { struct ng_car_hookconf upstream; struct ng_car_hookconf downstream; }; #define NG_CAR_BULKCONF(hconftype) { \ { "upstream", (hconftype) }, \ { "downstream", (hconftype) }, \ { NULL } \ } /* Commands */ enum { NGM_CAR_GET_STATS = 1, /* Get statistics */ NGM_CAR_CLR_STATS, /* Clear statistics */ NGM_CAR_GETCLR_STATS, /* Get and clear statistics */ NGM_CAR_GET_CONF, /* Get bulk configuration */ NGM_CAR_SET_CONF, /* Set bulk configuration */ }; #endif /* _NETGRAPH_NG_CAR_H_ */ Index: head/sys/netgraph/ng_etf.c =================================================================== --- head/sys/netgraph/ng_etf.c (revision 298812) +++ head/sys/netgraph/ng_etf.c (revision 298813) @@ -1,486 +1,486 @@ /*- * ng_etf.c Ethertype filter */ /*- * Copyright (c) 2001, FreeBSD Incorporated * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice unmodified, this list of conditions, and the following * disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * Author: Julian Elischer * * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* If you do complicated mallocs you may want to do this */ /* and use it for your mallocs */ #ifdef NG_SEPARATE_MALLOC static MALLOC_DEFINE(M_NETGRAPH_ETF, "netgraph_etf", "netgraph etf node "); #else #define M_NETGRAPH_ETF M_NETGRAPH #endif /* * This section contains the netgraph method declarations for the * etf node. These methods define the netgraph 'type'. */ static ng_constructor_t ng_etf_constructor; static ng_rcvmsg_t ng_etf_rcvmsg; static ng_shutdown_t ng_etf_shutdown; static ng_newhook_t ng_etf_newhook; static ng_rcvdata_t ng_etf_rcvdata; /* note these are both ng_rcvdata_t */ static ng_disconnect_t ng_etf_disconnect; /* Parse type for struct ng_etfstat */ static const struct ng_parse_struct_field ng_etf_stat_type_fields[] = NG_ETF_STATS_TYPE_INFO; static const struct ng_parse_type ng_etf_stat_type = { &ng_parse_struct_type, &ng_etf_stat_type_fields }; /* Parse type for struct ng_setfilter */ static const struct ng_parse_struct_field ng_etf_filter_type_fields[] = NG_ETF_FILTER_TYPE_INFO; static const struct ng_parse_type ng_etf_filter_type = { &ng_parse_struct_type, &ng_etf_filter_type_fields }; /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_etf_cmdlist[] = { { NGM_ETF_COOKIE, NGM_ETF_GET_STATUS, "getstatus", NULL, &ng_etf_stat_type, }, { NGM_ETF_COOKIE, NGM_ETF_SET_FLAG, "setflag", &ng_parse_int32_type, NULL }, { NGM_ETF_COOKIE, NGM_ETF_SET_FILTER, "setfilter", &ng_etf_filter_type, NULL }, { 0 } }; /* Netgraph node type descriptor */ static struct ng_type typestruct = { .version = NG_ABI_VERSION, .name = NG_ETF_NODE_TYPE, .constructor = ng_etf_constructor, .rcvmsg = ng_etf_rcvmsg, .shutdown = ng_etf_shutdown, .newhook = ng_etf_newhook, .rcvdata = ng_etf_rcvdata, .disconnect = ng_etf_disconnect, .cmdlist = ng_etf_cmdlist, }; NETGRAPH_INIT(etf, &typestruct); /* Information we store for each hook on each node */ struct ETF_hookinfo { hook_p hook; }; struct filter { LIST_ENTRY(filter) next; u_int16_t ethertype; /* network order ethertype */ hook_p match_hook; /* Hook to use on a match */ }; #define HASHSIZE 16 /* Dont change this without changing HASH() */ #define HASH(et) ((((et)>>12)+((et)>>8)+((et)>>4)+(et)) & 0x0f) LIST_HEAD(filterhead, filter); /* Information we store for each node */ struct ETF { struct ETF_hookinfo downstream_hook; struct ETF_hookinfo nomatch_hook; node_p node; /* back pointer to node */ u_int packets_in; /* packets in from downstream */ u_int packets_out; /* packets out towards downstream */ u_int32_t flags; struct filterhead hashtable[HASHSIZE]; }; typedef struct ETF *etf_p; static struct filter * ng_etf_findentry(etf_p etfp, u_int16_t ethertype) { struct filterhead *chain = etfp->hashtable + HASH(ethertype); struct filter *fil; LIST_FOREACH(fil, chain, next) { if (fil->ethertype == ethertype) { return (fil); } } return (NULL); } /* * Allocate the private data structure. The generic node has already * been created. Link them together. We arrive with a reference to the node * i.e. the reference count is incremented for us already. */ static int ng_etf_constructor(node_p node) { etf_p privdata; int i; /* Initialize private descriptor */ privdata = malloc(sizeof(*privdata), M_NETGRAPH_ETF, M_WAITOK | M_ZERO); for (i = 0; i < HASHSIZE; i++) { LIST_INIT((privdata->hashtable + i)); } /* Link structs together; this counts as our one reference to node */ NG_NODE_SET_PRIVATE(node, privdata); privdata->node = node; return (0); } /* * Give our ok for a hook to be added... * All names are ok. Two names are special. */ static int ng_etf_newhook(node_p node, hook_p hook, const char *name) { const etf_p etfp = NG_NODE_PRIVATE(node); struct ETF_hookinfo *hpriv; if (strcmp(name, NG_ETF_HOOK_DOWNSTREAM) == 0) { etfp->downstream_hook.hook = hook; NG_HOOK_SET_PRIVATE(hook, &etfp->downstream_hook); etfp->packets_in = 0; etfp->packets_out = 0; } else if (strcmp(name, NG_ETF_HOOK_NOMATCH) == 0) { etfp->nomatch_hook.hook = hook; NG_HOOK_SET_PRIVATE(hook, &etfp->nomatch_hook); } else { /* * Any other hook name is valid and can * later be associated with a filter rule. */ hpriv = malloc(sizeof(*hpriv), M_NETGRAPH_ETF, M_NOWAIT | M_ZERO); if (hpriv == NULL) { return (ENOMEM); } NG_HOOK_SET_PRIVATE(hook, hpriv); hpriv->hook = hook; } return(0); } /* * Get a netgraph control message. - * We actually recieve a queue item that has a pointer to the message. + * We actually receive a queue item that has a pointer to the message. * If we free the item, the message will be freed too, unless we remove * it from the item using NGI_GET_MSG(); * The return address is also stored in the item, as an ng_ID_t, * accessible as NGI_RETADDR(item); * Check it is one we understand. If needed, send a response. * We could save the address for an async action later, but don't here. * Always free the message. * The response should be in a malloc'd region that the caller can 'free'. * The NG_MKRESPONSE macro does all this for us. * A response is not required. * Theoretically you could respond defferently to old message types if * the cookie in the header didn't match what we consider to be current * (so that old userland programs could continue to work). */ static int ng_etf_rcvmsg(node_p node, item_p item, hook_p lasthook) { const etf_p etfp = NG_NODE_PRIVATE(node); struct ng_mesg *resp = NULL; int error = 0; struct ng_mesg *msg; NGI_GET_MSG(item, msg); /* Deal with message according to cookie and command */ switch (msg->header.typecookie) { case NGM_ETF_COOKIE: switch (msg->header.cmd) { case NGM_ETF_GET_STATUS: { struct ng_etfstat *stats; NG_MKRESPONSE(resp, msg, sizeof(*stats), M_NOWAIT); if (!resp) { error = ENOMEM; break; } stats = (struct ng_etfstat *) resp->data; stats->packets_in = etfp->packets_in; stats->packets_out = etfp->packets_out; break; } case NGM_ETF_SET_FLAG: if (msg->header.arglen != sizeof(u_int32_t)) { error = EINVAL; break; } etfp->flags = *((u_int32_t *) msg->data); break; case NGM_ETF_SET_FILTER: { struct ng_etffilter *f; struct filter *fil; hook_p hook; /* Check message long enough for this command */ if (msg->header.arglen != sizeof(*f)) { error = EINVAL; break; } /* Make sure hook referenced exists */ f = (struct ng_etffilter *)msg->data; hook = ng_findhook(node, f->matchhook); if (hook == NULL) { error = ENOENT; break; } /* and is not the downstream hook */ if (hook == etfp->downstream_hook.hook) { error = EINVAL; break; } /* Check we don't already trap this ethertype */ if (ng_etf_findentry(etfp, htons(f->ethertype))) { error = EEXIST; break; } /* * Ok, make the filter and put it in the * hashtable ready for matching. */ fil = malloc(sizeof(*fil), M_NETGRAPH_ETF, M_NOWAIT | M_ZERO); if (fil == NULL) { error = ENOMEM; break; } fil->match_hook = hook; fil->ethertype = htons(f->ethertype); LIST_INSERT_HEAD( etfp->hashtable + HASH(fil->ethertype), fil, next); } break; default: error = EINVAL; /* unknown command */ break; } break; default: error = EINVAL; /* unknown cookie type */ break; } /* Take care of synchronous response, if any */ NG_RESPOND_MSG(error, node, item, resp); /* Free the message and return */ NG_FREE_MSG(msg); return(error); } /* * Receive data, and do something with it. * Actually we receive a queue item which holds the data. * If we free the item it will also free the data unless we have previously * disassociated it using the NGI_GET_etf() macro. * Possibly send it out on another link after processing. * Possibly do something different if it comes from different * hooks. The caller will never free m , so if we use up this data * or abort we must free it. * * If we want, we may decide to force this data to be queued and reprocessed * at the netgraph NETISR time. * We would do that by setting the HK_QUEUE flag on our hook. We would do that * in the connect() method. */ static int ng_etf_rcvdata(hook_p hook, item_p item ) { const etf_p etfp = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); struct ether_header *eh; int error = 0; struct mbuf *m; u_int16_t ethertype; struct filter *fil; if (NG_HOOK_PRIVATE(hook) == NULL) { /* Shouldn't happen but.. */ NG_FREE_ITEM(item); } /* * Everything not from the downstream hook goes to the * downstream hook. But only if it matches the ethertype * of the source hook. Un matching must go to/from 'nomatch'. */ /* Make sure we have an entire header */ NGI_GET_M(item, m); if (m->m_len < sizeof(*eh) ) { m = m_pullup(m, sizeof(*eh)); if (m == NULL) { NG_FREE_ITEM(item); return(EINVAL); } } eh = mtod(m, struct ether_header *); ethertype = eh->ether_type; fil = ng_etf_findentry(etfp, ethertype); /* * if from downstream, select between a match hook or * the nomatch hook */ if (hook == etfp->downstream_hook.hook) { etfp->packets_in++; if (fil && fil->match_hook) { NG_FWD_NEW_DATA(error, item, fil->match_hook, m); } else { NG_FWD_NEW_DATA(error, item,etfp->nomatch_hook.hook, m); } } else { /* * It must be heading towards the downstream. * Check that it's ethertype matches * the filters for it's input hook. * If it doesn't have one, check it's from nomatch. */ if ((fil && (fil->match_hook != hook)) || ((fil == NULL) && (hook != etfp->nomatch_hook.hook))) { NG_FREE_ITEM(item); NG_FREE_M(m); return (EPROTOTYPE); } NG_FWD_NEW_DATA( error, item, etfp->downstream_hook.hook, m); if (error == 0) { etfp->packets_out++; } } return (error); } /* * Do local shutdown processing.. * All our links and the name have already been removed. */ static int ng_etf_shutdown(node_p node) { const etf_p privdata = NG_NODE_PRIVATE(node); NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(privdata->node); free(privdata, M_NETGRAPH_ETF); return (0); } /* * Hook disconnection * * For this type, removal of the last link destroys the node */ static int ng_etf_disconnect(hook_p hook) { const etf_p etfp = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); int i; struct filter *fil1, *fil2; /* purge any rules that refer to this filter */ for (i = 0; i < HASHSIZE; i++) { fil1 = LIST_FIRST(&etfp->hashtable[i]); while (fil1 != NULL) { fil2 = LIST_NEXT(fil1, next); if (fil1->match_hook == hook) { LIST_REMOVE(fil1, next); free(fil1, M_NETGRAPH_ETF); } fil1 = fil2; } } /* If it's not one of the special hooks, then free it */ if (hook == etfp->downstream_hook.hook) { etfp->downstream_hook.hook = NULL; } else if (hook == etfp->nomatch_hook.hook) { etfp->nomatch_hook.hook = NULL; } else { if (NG_HOOK_PRIVATE(hook)) /* Paranoia */ free(NG_HOOK_PRIVATE(hook), M_NETGRAPH_ETF); } NG_HOOK_SET_PRIVATE(hook, NULL); if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) /* already shutting down? */ ng_rmnode_self(NG_HOOK_NODE(hook)); return (0); } Index: head/sys/netgraph/ng_ether.c =================================================================== --- head/sys/netgraph/ng_ether.c (revision 298812) +++ head/sys/netgraph/ng_ether.c (revision 298813) @@ -1,879 +1,879 @@ /* * ng_ether.c */ /*- * Copyright (c) 1996-2000 Whistle Communications, Inc. * All rights reserved. * * Subject to the following obligations and disclaimer of warranty, use and * redistribution of this software, in source or object code forms, with or * without modifications are expressly permitted by Whistle Communications; * provided, however, that: * 1. Any and all reproductions of the source or object code must include the * copyright notice above and the following disclaimer of warranties; and * 2. No rights are granted, in any manner or form, to use Whistle * Communications, Inc. trademarks, including the mark "WHISTLE * COMMUNICATIONS" on advertising, endorsements, or otherwise except as * such appears in the above copyright notice or in the software. * * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * Authors: Archie Cobbs * Julian Elischer * * $FreeBSD$ */ /* * ng_ether(4) netgraph node type */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MODULE_VERSION(ng_ether, 1); #define IFP2NG(ifp) ((ifp)->if_l2com) /* Per-node private data */ struct private { struct ifnet *ifp; /* associated interface */ hook_p upper; /* upper hook connection */ hook_p lower; /* lower hook connection */ hook_p orphan; /* orphan hook connection */ u_char autoSrcAddr; /* always overwrite source address */ u_char promisc; /* promiscuous mode enabled */ u_long hwassist; /* hardware checksum capabilities */ u_int flags; /* flags e.g. really die */ }; typedef struct private *priv_p; /* Hook pointers used by if_ethersubr.c to callback to netgraph */ extern void (*ng_ether_input_p)(struct ifnet *ifp, struct mbuf **mp); extern void (*ng_ether_input_orphan_p)(struct ifnet *ifp, struct mbuf *m); extern int (*ng_ether_output_p)(struct ifnet *ifp, struct mbuf **mp); extern void (*ng_ether_attach_p)(struct ifnet *ifp); extern void (*ng_ether_detach_p)(struct ifnet *ifp); extern void (*ng_ether_link_state_p)(struct ifnet *ifp, int state); /* Functional hooks called from if_ethersubr.c */ static void ng_ether_input(struct ifnet *ifp, struct mbuf **mp); static void ng_ether_input_orphan(struct ifnet *ifp, struct mbuf *m); static int ng_ether_output(struct ifnet *ifp, struct mbuf **mp); static void ng_ether_attach(struct ifnet *ifp); static void ng_ether_detach(struct ifnet *ifp); static void ng_ether_link_state(struct ifnet *ifp, int state); /* Other functions */ static int ng_ether_rcv_lower(hook_p node, item_p item); static int ng_ether_rcv_upper(hook_p node, item_p item); /* Netgraph node methods */ static ng_constructor_t ng_ether_constructor; static ng_rcvmsg_t ng_ether_rcvmsg; static ng_shutdown_t ng_ether_shutdown; static ng_newhook_t ng_ether_newhook; static ng_rcvdata_t ng_ether_rcvdata; static ng_disconnect_t ng_ether_disconnect; static int ng_ether_mod_event(module_t mod, int event, void *data); static eventhandler_tag ng_ether_ifnet_arrival_cookie; /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_ether_cmdlist[] = { { NGM_ETHER_COOKIE, NGM_ETHER_GET_IFNAME, "getifname", NULL, &ng_parse_string_type }, { NGM_ETHER_COOKIE, NGM_ETHER_GET_IFINDEX, "getifindex", NULL, &ng_parse_int32_type }, { NGM_ETHER_COOKIE, NGM_ETHER_GET_ENADDR, "getenaddr", NULL, &ng_parse_enaddr_type }, { NGM_ETHER_COOKIE, NGM_ETHER_SET_ENADDR, "setenaddr", &ng_parse_enaddr_type, NULL }, { NGM_ETHER_COOKIE, NGM_ETHER_GET_PROMISC, "getpromisc", NULL, &ng_parse_int32_type }, { NGM_ETHER_COOKIE, NGM_ETHER_SET_PROMISC, "setpromisc", &ng_parse_int32_type, NULL }, { NGM_ETHER_COOKIE, NGM_ETHER_GET_AUTOSRC, "getautosrc", NULL, &ng_parse_int32_type }, { NGM_ETHER_COOKIE, NGM_ETHER_SET_AUTOSRC, "setautosrc", &ng_parse_int32_type, NULL }, { NGM_ETHER_COOKIE, NGM_ETHER_ADD_MULTI, "addmulti", &ng_parse_enaddr_type, NULL }, { NGM_ETHER_COOKIE, NGM_ETHER_DEL_MULTI, "delmulti", &ng_parse_enaddr_type, NULL }, { NGM_ETHER_COOKIE, NGM_ETHER_DETACH, "detach", NULL, NULL }, { 0 } }; static struct ng_type ng_ether_typestruct = { .version = NG_ABI_VERSION, .name = NG_ETHER_NODE_TYPE, .mod_event = ng_ether_mod_event, .constructor = ng_ether_constructor, .rcvmsg = ng_ether_rcvmsg, .shutdown = ng_ether_shutdown, .newhook = ng_ether_newhook, .rcvdata = ng_ether_rcvdata, .disconnect = ng_ether_disconnect, .cmdlist = ng_ether_cmdlist, }; NETGRAPH_INIT(ether, &ng_ether_typestruct); /****************************************************************** UTILITY FUNCTIONS ******************************************************************/ static void ng_ether_sanitize_ifname(const char *ifname, char *name) { int i; for (i = 0; i < IFNAMSIZ; i++) { if (ifname[i] == '.' || ifname[i] == ':') name[i] = '_'; else name[i] = ifname[i]; if (name[i] == '\0') break; } } /****************************************************************** ETHERNET FUNCTION HOOKS ******************************************************************/ /* * Handle a packet that has come in on an interface. We get to * look at it here before any upper layer protocols do. */ static void ng_ether_input(struct ifnet *ifp, struct mbuf **mp) { const node_p node = IFP2NG(ifp); const priv_p priv = NG_NODE_PRIVATE(node); int error; /* If "lower" hook not connected, let packet continue */ if (priv->lower == NULL) return; NG_SEND_DATA_ONLY(error, priv->lower, *mp); /* sets *mp = NULL */ } /* * Handle a packet that has come in on an interface, and which * does not match any of our known protocols (an ``orphan''). */ static void ng_ether_input_orphan(struct ifnet *ifp, struct mbuf *m) { const node_p node = IFP2NG(ifp); const priv_p priv = NG_NODE_PRIVATE(node); int error; /* If "orphan" hook not connected, discard packet */ if (priv->orphan == NULL) { m_freem(m); return; } NG_SEND_DATA_ONLY(error, priv->orphan, m); } /* * Handle a packet that is going out on an interface. * The Ethernet header is already attached to the mbuf. */ static int ng_ether_output(struct ifnet *ifp, struct mbuf **mp) { const node_p node = IFP2NG(ifp); const priv_p priv = NG_NODE_PRIVATE(node); int error = 0; /* If "upper" hook not connected, let packet continue */ if (priv->upper == NULL) return (0); /* Send it out "upper" hook */ NG_OUTBOUND_THREAD_REF(); NG_SEND_DATA_ONLY(error, priv->upper, *mp); NG_OUTBOUND_THREAD_UNREF(); return (error); } /* * A new Ethernet interface has been attached. * Create a new node for it, etc. */ static void ng_ether_attach(struct ifnet *ifp) { char name[IFNAMSIZ]; priv_p priv; node_p node; /* * Do not create / attach an ether node to this ifnet if * a netgraph node with the same name already exists. * This should prevent ether nodes to become attached to * eiface nodes, which may be problematic due to naming * clashes. */ if ((node = ng_name2noderef(NULL, ifp->if_xname)) != NULL) { NG_NODE_UNREF(node); return; } /* Create node */ KASSERT(!IFP2NG(ifp), ("%s: node already exists?", __func__)); if (ng_make_node_common(&ng_ether_typestruct, &node) != 0) { log(LOG_ERR, "%s: can't %s for %s\n", __func__, "create node", ifp->if_xname); return; } /* Allocate private data */ priv = malloc(sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO); if (priv == NULL) { log(LOG_ERR, "%s: can't %s for %s\n", __func__, "allocate memory", ifp->if_xname); NG_NODE_UNREF(node); return; } NG_NODE_SET_PRIVATE(node, priv); priv->ifp = ifp; IFP2NG(ifp) = node; priv->hwassist = ifp->if_hwassist; /* Try to give the node the same name as the interface */ ng_ether_sanitize_ifname(ifp->if_xname, name); if (ng_name_node(node, name) != 0) log(LOG_WARNING, "%s: can't name node %s\n", __func__, name); } /* * An Ethernet interface is being detached. * REALLY Destroy its node. */ static void ng_ether_detach(struct ifnet *ifp) { const node_p node = IFP2NG(ifp); const priv_p priv = NG_NODE_PRIVATE(node); taskqueue_drain(taskqueue_swi, &ifp->if_linktask); NG_NODE_REALLY_DIE(node); /* Force real removal of node */ /* * We can't assume the ifnet is still around when we run shutdown * So zap it now. XXX We HOPE that anything running at this time * handles it (as it should in the non netgraph case). */ IFP2NG(ifp) = NULL; priv->ifp = NULL; /* XXX race if interrupted an output packet */ ng_rmnode_self(node); /* remove all netgraph parts */ } /* * Notify graph about link event. * if_link_state_change() has already checked that the state has changed. */ static void ng_ether_link_state(struct ifnet *ifp, int state) { const node_p node = IFP2NG(ifp); const priv_p priv = NG_NODE_PRIVATE(node); struct ng_mesg *msg; int cmd, dummy_error = 0; if (state == LINK_STATE_UP) cmd = NGM_LINK_IS_UP; else if (state == LINK_STATE_DOWN) cmd = NGM_LINK_IS_DOWN; else return; if (priv->lower != NULL) { NG_MKMESSAGE(msg, NGM_FLOW_COOKIE, cmd, 0, M_NOWAIT); if (msg != NULL) NG_SEND_MSG_HOOK(dummy_error, node, msg, priv->lower, 0); } if (priv->orphan != NULL) { NG_MKMESSAGE(msg, NGM_FLOW_COOKIE, cmd, 0, M_NOWAIT); if (msg != NULL) NG_SEND_MSG_HOOK(dummy_error, node, msg, priv->orphan, 0); } } /* * Interface arrival notification handler. * The notification is produced in two cases: * o a new interface arrives * o an existing interface got renamed * Currently the first case is handled by ng_ether_attach via special * hook ng_ether_attach_p. */ static void ng_ether_ifnet_arrival_event(void *arg __unused, struct ifnet *ifp) { char name[IFNAMSIZ]; node_p node; /* Only ethernet interfaces are of interest. */ if (ifp->if_type != IFT_ETHER && ifp->if_type != IFT_L2VLAN) return; /* * Just return if it's a new interface without an ng_ether companion. */ node = IFP2NG(ifp); if (node == NULL) return; /* Try to give the node the same name as the new interface name */ ng_ether_sanitize_ifname(ifp->if_xname, name); if (ng_name_node(node, name) != 0) log(LOG_WARNING, "%s: can't re-name node %s\n", __func__, name); } /****************************************************************** NETGRAPH NODE METHODS ******************************************************************/ /* * It is not possible or allowable to create a node of this type. * Nodes get created when the interface is attached (or, when * this node type's KLD is loaded). */ static int ng_ether_constructor(node_p node) { return (EINVAL); } /* * Check for attaching a new hook. */ static int ng_ether_newhook(node_p node, hook_p hook, const char *name) { const priv_p priv = NG_NODE_PRIVATE(node); hook_p *hookptr; /* Divert hook is an alias for lower */ if (strcmp(name, NG_ETHER_HOOK_DIVERT) == 0) name = NG_ETHER_HOOK_LOWER; /* Which hook? */ if (strcmp(name, NG_ETHER_HOOK_UPPER) == 0) { hookptr = &priv->upper; NG_HOOK_SET_RCVDATA(hook, ng_ether_rcv_upper); NG_HOOK_SET_TO_INBOUND(hook); } else if (strcmp(name, NG_ETHER_HOOK_LOWER) == 0) { hookptr = &priv->lower; NG_HOOK_SET_RCVDATA(hook, ng_ether_rcv_lower); } else if (strcmp(name, NG_ETHER_HOOK_ORPHAN) == 0) { hookptr = &priv->orphan; NG_HOOK_SET_RCVDATA(hook, ng_ether_rcv_lower); } else return (EINVAL); /* Check if already connected (shouldn't be, but doesn't hurt) */ if (*hookptr != NULL) return (EISCONN); /* Disable hardware checksums while 'upper' hook is connected */ if (hookptr == &priv->upper) priv->ifp->if_hwassist = 0; NG_HOOK_HI_STACK(hook); /* OK */ *hookptr = hook; return (0); } /* * Receive an incoming control message. */ static int ng_ether_rcvmsg(node_p node, item_p item, hook_p lasthook) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_mesg *resp = NULL; int error = 0; struct ng_mesg *msg; NGI_GET_MSG(item, msg); switch (msg->header.typecookie) { case NGM_ETHER_COOKIE: switch (msg->header.cmd) { case NGM_ETHER_GET_IFNAME: NG_MKRESPONSE(resp, msg, IFNAMSIZ, M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } strlcpy(resp->data, priv->ifp->if_xname, IFNAMSIZ); break; case NGM_ETHER_GET_IFINDEX: NG_MKRESPONSE(resp, msg, sizeof(u_int32_t), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } *((u_int32_t *)resp->data) = priv->ifp->if_index; break; case NGM_ETHER_GET_ENADDR: NG_MKRESPONSE(resp, msg, ETHER_ADDR_LEN, M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } bcopy(IF_LLADDR(priv->ifp), resp->data, ETHER_ADDR_LEN); break; case NGM_ETHER_SET_ENADDR: { if (msg->header.arglen != ETHER_ADDR_LEN) { error = EINVAL; break; } error = if_setlladdr(priv->ifp, (u_char *)msg->data, ETHER_ADDR_LEN); break; } case NGM_ETHER_GET_PROMISC: NG_MKRESPONSE(resp, msg, sizeof(u_int32_t), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } *((u_int32_t *)resp->data) = priv->promisc; break; case NGM_ETHER_SET_PROMISC: { u_char want; if (msg->header.arglen != sizeof(u_int32_t)) { error = EINVAL; break; } want = !!*((u_int32_t *)msg->data); if (want ^ priv->promisc) { if ((error = ifpromisc(priv->ifp, want)) != 0) break; priv->promisc = want; } break; } case NGM_ETHER_GET_AUTOSRC: NG_MKRESPONSE(resp, msg, sizeof(u_int32_t), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } *((u_int32_t *)resp->data) = priv->autoSrcAddr; break; case NGM_ETHER_SET_AUTOSRC: if (msg->header.arglen != sizeof(u_int32_t)) { error = EINVAL; break; } priv->autoSrcAddr = !!*((u_int32_t *)msg->data); break; case NGM_ETHER_ADD_MULTI: { struct sockaddr_dl sa_dl; struct ifmultiaddr *ifma; if (msg->header.arglen != ETHER_ADDR_LEN) { error = EINVAL; break; } bzero(&sa_dl, sizeof(struct sockaddr_dl)); sa_dl.sdl_len = sizeof(struct sockaddr_dl); sa_dl.sdl_family = AF_LINK; sa_dl.sdl_alen = ETHER_ADDR_LEN; bcopy((void *)msg->data, LLADDR(&sa_dl), ETHER_ADDR_LEN); /* * Netgraph is only permitted to join groups once * via the if_addmulti() KPI, because it cannot hold * struct ifmultiaddr * between calls. It may also * lose a race while we check if the membership * already exists. */ if_maddr_rlock(priv->ifp); ifma = if_findmulti(priv->ifp, (struct sockaddr *)&sa_dl); if_maddr_runlock(priv->ifp); if (ifma != NULL) { error = EADDRINUSE; } else { error = if_addmulti(priv->ifp, (struct sockaddr *)&sa_dl, &ifma); } break; } case NGM_ETHER_DEL_MULTI: { struct sockaddr_dl sa_dl; if (msg->header.arglen != ETHER_ADDR_LEN) { error = EINVAL; break; } bzero(&sa_dl, sizeof(struct sockaddr_dl)); sa_dl.sdl_len = sizeof(struct sockaddr_dl); sa_dl.sdl_family = AF_LINK; sa_dl.sdl_alen = ETHER_ADDR_LEN; bcopy((void *)msg->data, LLADDR(&sa_dl), ETHER_ADDR_LEN); error = if_delmulti(priv->ifp, (struct sockaddr *)&sa_dl); break; } case NGM_ETHER_DETACH: ng_ether_detach(priv->ifp); break; default: error = EINVAL; break; } break; default: error = EINVAL; break; } NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } /* * Receive data on a hook. * Since we use per-hook recveive methods this should never be called. */ static int ng_ether_rcvdata(hook_p hook, item_p item) { NG_FREE_ITEM(item); panic("%s: weird hook", __func__); } /* * Handle an mbuf received on the "lower" or "orphan" hook. */ static int ng_ether_rcv_lower(hook_p hook, item_p item) { struct mbuf *m; const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); struct ifnet *const ifp = priv->ifp; NGI_GET_M(item, m); NG_FREE_ITEM(item); /* Check whether interface is ready for packets */ if (!((ifp->if_flags & IFF_UP) && (ifp->if_drv_flags & IFF_DRV_RUNNING))) { NG_FREE_M(m); return (ENETDOWN); } /* Make sure header is fully pulled up */ if (m->m_pkthdr.len < sizeof(struct ether_header)) { NG_FREE_M(m); return (EINVAL); } if (m->m_len < sizeof(struct ether_header) && (m = m_pullup(m, sizeof(struct ether_header))) == NULL) return (ENOBUFS); /* Drop in the MAC address if desired */ if (priv->autoSrcAddr) { /* Make the mbuf writable if it's not already */ if (!M_WRITABLE(m) && (m = m_pullup(m, sizeof(struct ether_header))) == NULL) return (ENOBUFS); /* Overwrite source MAC address */ bcopy(IF_LLADDR(ifp), mtod(m, struct ether_header *)->ether_shost, ETHER_ADDR_LEN); } /* Send it on its way */ return ether_output_frame(ifp, m); } /* * Handle an mbuf received on the "upper" hook. */ static int ng_ether_rcv_upper(hook_p hook, item_p item) { struct mbuf *m; const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); struct ifnet *ifp = priv->ifp; NGI_GET_M(item, m); NG_FREE_ITEM(item); /* Check length and pull off header */ if (m->m_pkthdr.len < sizeof(struct ether_header)) { NG_FREE_M(m); return (EINVAL); } if (m->m_len < sizeof(struct ether_header) && (m = m_pullup(m, sizeof(struct ether_header))) == NULL) return (ENOBUFS); m->m_pkthdr.rcvif = ifp; /* Pass the packet to the bridge, it may come back to us */ if (ifp->if_bridge) { BRIDGE_INPUT(ifp, m); if (m == NULL) return (0); } /* Route packet back in */ ether_demux(ifp, m); return (0); } /* * Shutdown node. This resets the node but does not remove it * unless the REALLY_DIE flag is set. */ static int ng_ether_shutdown(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); if (node->nd_flags & NGF_REALLY_DIE) { /* * WE came here because the ethernet card is being unloaded, - * so stop being persistant. + * so stop being persistent. * Actually undo all the things we did on creation. * Assume the ifp has already been freed. */ NG_NODE_SET_PRIVATE(node, NULL); free(priv, M_NETGRAPH); NG_NODE_UNREF(node); /* free node itself */ return (0); } if (priv->promisc) { /* disable promiscuous mode */ (void)ifpromisc(priv->ifp, 0); priv->promisc = 0; } NG_NODE_REVIVE(node); /* Signal ng_rmnode we are persisant */ return (0); } /* * Hook disconnection. */ static int ng_ether_disconnect(hook_p hook) { const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); if (hook == priv->upper) { priv->upper = NULL; if (priv->ifp != NULL) /* restore h/w csum */ priv->ifp->if_hwassist = priv->hwassist; } else if (hook == priv->lower) priv->lower = NULL; else if (hook == priv->orphan) priv->orphan = NULL; else panic("%s: weird hook", __func__); if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) ng_rmnode_self(NG_HOOK_NODE(hook)); /* reset node */ return (0); } /****************************************************************** INITIALIZATION ******************************************************************/ /* * Handle loading and unloading for this node type. */ static int ng_ether_mod_event(module_t mod, int event, void *data) { int error = 0; switch (event) { case MOD_LOAD: /* Register function hooks */ if (ng_ether_attach_p != NULL) { error = EEXIST; break; } ng_ether_attach_p = ng_ether_attach; ng_ether_detach_p = ng_ether_detach; ng_ether_output_p = ng_ether_output; ng_ether_input_p = ng_ether_input; ng_ether_input_orphan_p = ng_ether_input_orphan; ng_ether_link_state_p = ng_ether_link_state; ng_ether_ifnet_arrival_cookie = EVENTHANDLER_REGISTER(ifnet_arrival_event, ng_ether_ifnet_arrival_event, NULL, EVENTHANDLER_PRI_ANY); break; case MOD_UNLOAD: /* * Note that the base code won't try to unload us until * all nodes have been removed, and that can't happen * until all Ethernet interfaces are removed. In any * case, we know there are no nodes left if the action * is MOD_UNLOAD, so there's no need to detach any nodes. */ EVENTHANDLER_DEREGISTER(ifnet_arrival_event, ng_ether_ifnet_arrival_cookie); /* Unregister function hooks */ ng_ether_attach_p = NULL; ng_ether_detach_p = NULL; ng_ether_output_p = NULL; ng_ether_input_p = NULL; ng_ether_input_orphan_p = NULL; ng_ether_link_state_p = NULL; break; default: error = EOPNOTSUPP; break; } return (error); } static void vnet_ng_ether_init(const void *unused) { struct ifnet *ifp; /* If module load was rejected, don't attach to vnets. */ if (ng_ether_attach_p != ng_ether_attach) return; /* Create nodes for any already-existing Ethernet interfaces. */ IFNET_RLOCK(); TAILQ_FOREACH(ifp, &V_ifnet, if_link) { if (ifp->if_type == IFT_ETHER || ifp->if_type == IFT_L2VLAN) ng_ether_attach(ifp); } IFNET_RUNLOCK(); } VNET_SYSINIT(vnet_ng_ether_init, SI_SUB_PSEUDO, SI_ORDER_ANY, vnet_ng_ether_init, NULL); Index: head/sys/netgraph/ng_frame_relay.c =================================================================== --- head/sys/netgraph/ng_frame_relay.c (revision 298812) +++ head/sys/netgraph/ng_frame_relay.c (revision 298813) @@ -1,504 +1,504 @@ /* * ng_frame_relay.c */ /*- * Copyright (c) 1996-1999 Whistle Communications, Inc. * All rights reserved. * * Subject to the following obligations and disclaimer of warranty, use and * redistribution of this software, in source or object code forms, with or * without modifications are expressly permitted by Whistle Communications; * provided, however, that: * 1. Any and all reproductions of the source or object code must include the * copyright notice above and the following disclaimer of warranties; and * 2. No rights are granted, in any manner or form, to use Whistle * Communications, Inc. trademarks, including the mark "WHISTLE * COMMUNICATIONS" on advertising, endorsements, or otherwise except as * such appears in the above copyright notice or in the software. * * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * Author: Julian Elischer * * $FreeBSD$ * $Whistle: ng_frame_relay.c,v 1.20 1999/11/01 09:24:51 julian Exp $ */ /* * This node implements the frame relay protocol, not including * the LMI line management. This means basically keeping track * of which DLCI's are active, doing frame (de)multiplexing, etc. * * It has a 'downstream' hook that goes to the line, and a * hook for each DLCI (eg, 'dlci16'). */ #include #include #include #include #include #include #include #include #include #include #include /* * Line info, and status per channel. */ struct ctxinfo { /* one per active hook */ u_int flags; #define CHAN_VALID 0x01 /* assigned to a channel */ #define CHAN_ACTIVE 0x02 /* bottom level active */ int dlci; /* the dlci assigned to this context */ hook_p hook; /* if there's a hook assigned.. */ }; #define MAX_CT 16 /* # of dlci's active at a time (POWER OF 2!) */ struct frmrel_softc { int unit; /* which card are we? */ int datahooks; /* number of data hooks attached */ node_p node; /* netgraph node */ int addrlen; /* address header length */ int flags; /* state */ int mtu; /* guess */ u_char remote_seq; /* sequence number the remote sent */ u_char local_seq; /* sequence number the remote rcvd */ u_short ALT[1024]; /* map DLCIs to CTX */ #define CTX_VALID 0x8000 /* this bit means it's a valid CTX */ #define CTX_VALUE (MAX_CT - 1) /* mask for context part */ struct ctxinfo channel[MAX_CT]; struct ctxinfo downstream; }; typedef struct frmrel_softc *sc_p; #define BYTEX_EA 0x01 /* End Address. Always 0 on byte1 */ #define BYTE1_C_R 0x02 #define BYTE2_FECN 0x08 /* forwards congestion notification */ #define BYTE2_BECN 0x04 /* Backward congestion notification */ #define BYTE2_DE 0x02 /* Discard elligability */ #define LASTBYTE_D_C 0x02 /* last byte is dl_core or dlci info */ /* Used to do headers */ const static struct segment { u_char mask; u_char shift; u_char width; } makeup[] = { { 0xfc, 2, 6 }, { 0xf0, 4, 4 }, { 0xfe, 1, 7 }, { 0xfc, 2, 6 } }; #define SHIFTIN(segment, byte, dlci) \ { \ (dlci) <<= (segment)->width; \ (dlci) |= \ (((byte) & (segment)->mask) >> (segment)->shift); \ } #define SHIFTOUT(segment, byte, dlci) \ { \ (byte) |= (((dlci) << (segment)->shift) & (segment)->mask); \ (dlci) >>= (segment)->width; \ } /* Netgraph methods */ static ng_constructor_t ngfrm_constructor; static ng_shutdown_t ngfrm_shutdown; static ng_newhook_t ngfrm_newhook; static ng_rcvdata_t ngfrm_rcvdata; static ng_disconnect_t ngfrm_disconnect; /* Other internal functions */ static int ngfrm_decode(node_p node, item_p item); static int ngfrm_addrlen(char *hdr); static int ngfrm_allocate_CTX(sc_p sc, int dlci); /* Netgraph type */ static struct ng_type typestruct = { .version = NG_ABI_VERSION, .name = NG_FRAMERELAY_NODE_TYPE, .constructor = ngfrm_constructor, .shutdown = ngfrm_shutdown, .newhook = ngfrm_newhook, .rcvdata = ngfrm_rcvdata, .disconnect = ngfrm_disconnect, }; NETGRAPH_INIT(framerelay, &typestruct); #define ERROUT(x) do { error = (x); goto done; } while (0) /* * Given a DLCI, return the index of the context table entry for it, * Allocating a new one if needs be, or -1 if none available. */ static int ngfrm_allocate_CTX(sc_p sc, int dlci) { u_int ctxnum = -1; /* what ctx number we are using */ volatile struct ctxinfo *CTXp = NULL; /* Sanity check the dlci value */ if (dlci > 1023) return (-1); /* Check to see if we already have an entry for this DLCI */ if (sc->ALT[dlci]) { if ((ctxnum = sc->ALT[dlci] & CTX_VALUE) < MAX_CT) { CTXp = sc->channel + ctxnum; } else { ctxnum = -1; sc->ALT[dlci] = 0; /* paranoid but... */ } } /* * If the index has no valid entry yet, then we need to allocate a * CTX number to it */ if (CTXp == NULL) { for (ctxnum = 0; ctxnum < MAX_CT; ctxnum++) { /* * If the VALID flag is empty it is unused */ if ((sc->channel[ctxnum].flags & CHAN_VALID) == 0) { bzero(sc->channel + ctxnum, sizeof(struct ctxinfo)); CTXp = sc->channel + ctxnum; sc->ALT[dlci] = ctxnum | CTX_VALID; sc->channel[ctxnum].dlci = dlci; sc->channel[ctxnum].flags = CHAN_VALID; break; } } } /* * If we still don't have a CTX pointer, then we never found a free * spot so give up now.. */ if (!CTXp) { log(LOG_ERR, "No CTX available for dlci %d\n", dlci); return (-1); } return (ctxnum); } /* * Node constructor */ static int ngfrm_constructor(node_p node) { sc_p sc; sc = malloc(sizeof(*sc), M_NETGRAPH, M_WAITOK | M_ZERO); sc->addrlen = 2; /* default */ /* Link the node and our private info */ NG_NODE_SET_PRIVATE(node, sc); sc->node = node; return (0); } /* * Add a new hook * * We allow hooks called "debug", "downstream" and dlci[0-1023] * The hook's private info points to our stash of info about that * channel. A NULL pointer is debug and a DLCI of -1 means downstream. */ static int ngfrm_newhook(node_p node, hook_p hook, const char *name) { const sc_p sc = NG_NODE_PRIVATE(node); const char *cp; char *eptr; int dlci = 0; int ctxnum; /* Check if it's our friend the control hook */ if (strcmp(name, NG_FRAMERELAY_HOOK_DEBUG) == 0) { NG_HOOK_SET_PRIVATE(hook, NULL); /* paranoid */ return (0); } /* * All other hooks either start with 'dlci' and have a decimal * trailing channel number up to 4 digits, or are the downstream * hook. */ if (strncmp(name, NG_FRAMERELAY_HOOK_DLCI, strlen(NG_FRAMERELAY_HOOK_DLCI)) != 0) { /* It must be the downstream connection */ if (strcmp(name, NG_FRAMERELAY_HOOK_DOWNSTREAM) != 0) return EINVAL; /* Make sure we haven't already got one (paranoid) */ if (sc->downstream.hook) return (EADDRINUSE); /* OK add it */ NG_HOOK_SET_PRIVATE(hook, &sc->downstream); sc->downstream.hook = hook; sc->downstream.dlci = -1; sc->downstream.flags |= CHAN_ACTIVE; sc->datahooks++; return (0); } /* Must be a dlci hook at this point */ cp = name + strlen(NG_FRAMERELAY_HOOK_DLCI); if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0')) return (EINVAL); dlci = (int)strtoul(cp, &eptr, 10); if (*eptr != '\0' || dlci < 0 || dlci > 1023) return (EINVAL); /* * We have a dlci, now either find it, or allocate it. It's possible * that we might have seen packets for it already and made an entry * for it. */ ctxnum = ngfrm_allocate_CTX(sc, dlci); if (ctxnum == -1) return (ENOBUFS); /* * Be paranoid: if it's got a hook already, that dlci is in use . * Generic code can not catch all the synonyms (e.g. dlci016 vs * dlci16) */ if (sc->channel[ctxnum].hook != NULL) return (EADDRINUSE); /* * Put our hooks into it (pun not intended) */ sc->channel[ctxnum].flags |= CHAN_ACTIVE; NG_HOOK_SET_PRIVATE(hook, sc->channel + ctxnum); sc->channel[ctxnum].hook = hook; sc->datahooks++; return (0); } /* * Count up the size of the address header if we don't already know */ int ngfrm_addrlen(char *hdr) { if (hdr[0] & BYTEX_EA) return 0; if (hdr[1] & BYTEX_EA) return 2; if (hdr[2] & BYTEX_EA) return 3; if (hdr[3] & BYTEX_EA) return 4; return 0; } /* * Receive data packet */ static int ngfrm_rcvdata(hook_p hook, item_p item) { struct ctxinfo *const ctxp = NG_HOOK_PRIVATE(hook); struct mbuf *m = NULL; int error = 0; int dlci; sc_p sc; int alen; char *data; /* Data doesn't come in from just anywhere (e.g debug hook) */ if (ctxp == NULL) ERROUT(ENETDOWN); /* If coming from downstream, decode it to a channel */ dlci = ctxp->dlci; if (dlci == -1) return (ngfrm_decode(NG_HOOK_NODE(hook), item)); NGI_GET_M(item, m); /* Derive the softc we will need */ sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); /* If there is no live channel, throw it away */ if ((sc->downstream.hook == NULL) || ((ctxp->flags & CHAN_ACTIVE) == 0)) ERROUT(ENETDOWN); /* Store the DLCI on the front of the packet */ alen = sc->addrlen; if (alen == 0) alen = 2; /* default value for transmit */ M_PREPEND(m, alen, M_NOWAIT); if (m == NULL) ERROUT(ENOBUFS); data = mtod(m, char *); /* - * Shift the lowest bits into the address field untill we are done. + * Shift the lowest bits into the address field until we are done. * First byte is MSBits of addr so work backwards. */ switch (alen) { case 2: data[0] = data[1] = '\0'; SHIFTOUT(makeup + 1, data[1], dlci); SHIFTOUT(makeup + 0, data[0], dlci); data[1] |= BYTEX_EA; break; case 3: data[0] = data[1] = data[2] = '\0'; SHIFTOUT(makeup + 3, data[2], dlci); /* 3 and 2 is correct */ SHIFTOUT(makeup + 1, data[1], dlci); SHIFTOUT(makeup + 0, data[0], dlci); data[2] |= BYTEX_EA; break; case 4: data[0] = data[1] = data[2] = data[3] = '\0'; SHIFTOUT(makeup + 3, data[3], dlci); SHIFTOUT(makeup + 2, data[2], dlci); SHIFTOUT(makeup + 1, data[1], dlci); SHIFTOUT(makeup + 0, data[0], dlci); data[3] |= BYTEX_EA; break; default: panic("%s", __func__); } /* Send it */ NG_FWD_NEW_DATA(error, item, sc->downstream.hook, m); return (error); done: NG_FREE_ITEM(item); NG_FREE_M(m); return (error); } /* * Decode an incoming frame coming from the switch */ static int ngfrm_decode(node_p node, item_p item) { const sc_p sc = NG_NODE_PRIVATE(node); char *data; int alen; u_int dlci = 0; int error = 0; int ctxnum; struct mbuf *m; NGI_GET_M(item, m); if (m->m_len < 4 && (m = m_pullup(m, 4)) == NULL) ERROUT(ENOBUFS); data = mtod(m, char *); if ((alen = sc->addrlen) == 0) { sc->addrlen = alen = ngfrm_addrlen(data); } switch (alen) { case 2: SHIFTIN(makeup + 0, data[0], dlci); SHIFTIN(makeup + 1, data[1], dlci); break; case 3: SHIFTIN(makeup + 0, data[0], dlci); SHIFTIN(makeup + 1, data[1], dlci); SHIFTIN(makeup + 3, data[2], dlci); /* 3 and 2 is correct */ break; case 4: SHIFTIN(makeup + 0, data[0], dlci); SHIFTIN(makeup + 1, data[1], dlci); SHIFTIN(makeup + 2, data[2], dlci); SHIFTIN(makeup + 3, data[3], dlci); break; default: ERROUT(EINVAL); } if (dlci > 1023) ERROUT(EINVAL); ctxnum = sc->ALT[dlci]; if ((ctxnum & CTX_VALID) && sc->channel[ctxnum &= CTX_VALUE].hook) { /* Send it */ m_adj(m, alen); NG_FWD_NEW_DATA(error, item, sc->channel[ctxnum].hook, m); return (error); } else { error = ENETDOWN; } done: NG_FREE_ITEM(item); NG_FREE_M(m); return (error); } /* * Shutdown node */ static int ngfrm_shutdown(node_p node) { const sc_p sc = NG_NODE_PRIVATE(node); NG_NODE_SET_PRIVATE(node, NULL); free(sc, M_NETGRAPH); NG_NODE_UNREF(node); return (0); } /* * Hook disconnection * * Invalidate the private data associated with this dlci. * For this type, removal of the last link resets tries to destroy the node. */ static int ngfrm_disconnect(hook_p hook) { const sc_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); struct ctxinfo *const cp = NG_HOOK_PRIVATE(hook); int dlci; /* If it's a regular dlci hook, then free resources etc.. */ if (cp != NULL) { cp->hook = NULL; dlci = cp->dlci; if (dlci != -1) sc->ALT[dlci] = 0; cp->flags = 0; sc->datahooks--; } if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) ng_rmnode_self(NG_HOOK_NODE(hook)); return (0); } Index: head/sys/netgraph/ng_gif.c =================================================================== --- head/sys/netgraph/ng_gif.c (revision 298812) +++ head/sys/netgraph/ng_gif.c (revision 298813) @@ -1,594 +1,594 @@ /* * ng_gif.c */ /*- * Copyright 2001 The Aerospace Corporation. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions, and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of The Aerospace Corporation may not be used to endorse or * promote products derived from this software. * * THIS SOFTWARE IS PROVIDED BY THE AEROSPACE CORPORATION ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AEROSPACE CORPORATION BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * * Copyright (c) 1996-2000 Whistle Communications, Inc. * All rights reserved. * * Subject to the following obligations and disclaimer of warranty, use and * redistribution of this software, in source or object code forms, with or * without modifications are expressly permitted by Whistle Communications; * provided, however, that: * 1. Any and all reproductions of the source or object code must include the * copyright notice above and the following disclaimer of warranties; and * 2. No rights are granted, in any manner or form, to use Whistle * Communications, Inc. trademarks, including the mark "WHISTLE * COMMUNICATIONS" on advertising, endorsements, or otherwise except as * such appears in the above copyright notice or in the software. * * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * $FreeBSD$ */ /* * ng_gif(4) netgraph node type */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define IFP2NG(ifp) ((struct ng_node *)((struct gif_softc *)(ifp->if_softc))->gif_netgraph) #define IFP2NG_SET(ifp, val) (((struct gif_softc *)(ifp->if_softc))->gif_netgraph = (val)) /* Per-node private data */ struct private { struct ifnet *ifp; /* associated interface */ hook_p lower; /* lower OR orphan hook connection */ u_char lowerOrphan; /* whether lower is lower or orphan */ }; typedef struct private *priv_p; /* Functional hooks called from if_gif.c */ static void ng_gif_input(struct ifnet *ifp, struct mbuf **mp, int af); static void ng_gif_input_orphan(struct ifnet *ifp, struct mbuf *m, int af); static void ng_gif_attach(struct ifnet *ifp); static void ng_gif_detach(struct ifnet *ifp); /* Other functions */ static void ng_gif_input2(node_p node, struct mbuf **mp, int af); static int ng_gif_glue_af(struct mbuf **mp, int af); static int ng_gif_rcv_lower(node_p node, struct mbuf *m); /* Netgraph node methods */ static ng_constructor_t ng_gif_constructor; static ng_rcvmsg_t ng_gif_rcvmsg; static ng_shutdown_t ng_gif_shutdown; static ng_newhook_t ng_gif_newhook; static ng_connect_t ng_gif_connect; static ng_rcvdata_t ng_gif_rcvdata; static ng_disconnect_t ng_gif_disconnect; static int ng_gif_mod_event(module_t mod, int event, void *data); /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_gif_cmdlist[] = { { NGM_GIF_COOKIE, NGM_GIF_GET_IFNAME, "getifname", NULL, &ng_parse_string_type }, { NGM_GIF_COOKIE, NGM_GIF_GET_IFINDEX, "getifindex", NULL, &ng_parse_int32_type }, { 0 } }; static struct ng_type ng_gif_typestruct = { .version = NG_ABI_VERSION, .name = NG_GIF_NODE_TYPE, .mod_event = ng_gif_mod_event, .constructor = ng_gif_constructor, .rcvmsg = ng_gif_rcvmsg, .shutdown = ng_gif_shutdown, .newhook = ng_gif_newhook, .connect = ng_gif_connect, .rcvdata = ng_gif_rcvdata, .disconnect = ng_gif_disconnect, .cmdlist = ng_gif_cmdlist, }; MODULE_DEPEND(ng_gif, if_gif, 1,1,1); NETGRAPH_INIT(gif, &ng_gif_typestruct); /****************************************************************** GIF FUNCTION HOOKS ******************************************************************/ /* * Handle a packet that has come in on an interface. We get to * look at it here before any upper layer protocols do. */ static void ng_gif_input(struct ifnet *ifp, struct mbuf **mp, int af) { const node_p node = IFP2NG(ifp); const priv_p priv = NG_NODE_PRIVATE(node); /* If "lower" hook not connected, let packet continue */ if (priv->lower == NULL || priv->lowerOrphan) return; ng_gif_input2(node, mp, af); } /* * Handle a packet that has come in on an interface, and which * does not match any of our known protocols (an ``orphan''). */ static void ng_gif_input_orphan(struct ifnet *ifp, struct mbuf *m, int af) { const node_p node = IFP2NG(ifp); const priv_p priv = NG_NODE_PRIVATE(node); /* If "orphan" hook not connected, let packet continue */ if (priv->lower == NULL || !priv->lowerOrphan) { m_freem(m); return; } ng_gif_input2(node, &m, af); if (m != NULL) m_freem(m); } /* * Handle a packet that has come in on a gif interface. * Attach the address family to the mbuf for later use. */ static void ng_gif_input2(node_p node, struct mbuf **mp, int af) { const priv_p priv = NG_NODE_PRIVATE(node); int error; /* Glue address family on */ if ((error = ng_gif_glue_af(mp, af)) != 0) return; /* Send out lower/orphan hook */ NG_SEND_DATA_ONLY(error, priv->lower, *mp); *mp = NULL; } /* * A new gif interface has been attached. * Create a new node for it, etc. */ static void ng_gif_attach(struct ifnet *ifp) { priv_p priv; node_p node; /* Create node */ KASSERT(!IFP2NG(ifp), ("%s: node already exists?", __func__)); if (ng_make_node_common(&ng_gif_typestruct, &node) != 0) { log(LOG_ERR, "%s: can't %s for %s\n", __func__, "create node", ifp->if_xname); return; } /* Allocate private data */ priv = malloc(sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO); if (priv == NULL) { log(LOG_ERR, "%s: can't %s for %s\n", __func__, "allocate memory", ifp->if_xname); NG_NODE_UNREF(node); return; } NG_NODE_SET_PRIVATE(node, priv); priv->ifp = ifp; IFP2NG_SET(ifp, node); /* Try to give the node the same name as the interface */ if (ng_name_node(node, ifp->if_xname) != 0) { log(LOG_WARNING, "%s: can't name node %s\n", __func__, ifp->if_xname); } } /* * An interface is being detached. * REALLY Destroy its node. */ static void ng_gif_detach(struct ifnet *ifp) { const node_p node = IFP2NG(ifp); priv_p priv; if (node == NULL) /* no node (why not?), ignore */ return; priv = NG_NODE_PRIVATE(node); NG_NODE_REALLY_DIE(node); /* Force real removal of node */ /* * We can't assume the ifnet is still around when we run shutdown * So zap it now. XXX We HOPE that anything running at this time * handles it (as it should in the non netgraph case). */ IFP2NG_SET(ifp, NULL); priv->ifp = NULL; /* XXX race if interrupted an output packet */ ng_rmnode_self(node); /* remove all netgraph parts */ } /* * Optimization for gluing the address family onto * the front of an incoming packet. */ static int ng_gif_glue_af(struct mbuf **mp, int af) { struct mbuf *m = *mp; int error = 0; sa_family_t tmp_af; tmp_af = (sa_family_t) af; /* * XXX: should try to bring back some of the optimizations from * ng_ether.c */ /* * Doing anything more is likely to get more * expensive than it's worth.. * it's probable that everything else is in one * big lump. The next node will do an m_pullup() * for exactly the amount of data it needs and * hopefully everything after that will not * need one. So let's just use M_PREPEND. */ M_PREPEND(m, sizeof (tmp_af), M_NOWAIT); if (m == NULL) { error = ENOBUFS; goto done; } #if 0 copy: #endif /* Copy header and return (possibly new) mbuf */ *mtod(m, sa_family_t *) = tmp_af; #if 0 bcopy((caddr_t)&tmp_af, mtod(m, sa_family_t *), sizeof(tmp_af)); #endif done: *mp = m; return error; } /****************************************************************** NETGRAPH NODE METHODS ******************************************************************/ /* * It is not possible or allowable to create a node of this type. * Nodes get created when the interface is attached (or, when * this node type's KLD is loaded). */ static int ng_gif_constructor(node_p node) { return (EINVAL); } /* * Check for attaching a new hook. */ static int ng_gif_newhook(node_p node, hook_p hook, const char *name) { const priv_p priv = NG_NODE_PRIVATE(node); u_char orphan = priv->lowerOrphan; hook_p *hookptr; /* Divert hook is an alias for lower */ if (strcmp(name, NG_GIF_HOOK_DIVERT) == 0) name = NG_GIF_HOOK_LOWER; /* Which hook? */ if (strcmp(name, NG_GIF_HOOK_LOWER) == 0) { hookptr = &priv->lower; orphan = 0; } else if (strcmp(name, NG_GIF_HOOK_ORPHAN) == 0) { hookptr = &priv->lower; orphan = 1; } else return (EINVAL); /* Check if already connected (shouldn't be, but doesn't hurt) */ if (*hookptr != NULL) return (EISCONN); /* OK */ *hookptr = hook; priv->lowerOrphan = orphan; return (0); } /* * Hooks are attached, adjust to force queueing. * We don't really care which hook it is. * they should all be queuing for outgoing data. */ static int ng_gif_connect(hook_p hook) { NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook)); return (0); } /* * Receive an incoming control message. */ static int ng_gif_rcvmsg(node_p node, item_p item, hook_p lasthook) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_mesg *resp = NULL; int error = 0; struct ng_mesg *msg; NGI_GET_MSG(item, msg); switch (msg->header.typecookie) { case NGM_GIF_COOKIE: switch (msg->header.cmd) { case NGM_GIF_GET_IFNAME: NG_MKRESPONSE(resp, msg, IFNAMSIZ, M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } strlcpy(resp->data, priv->ifp->if_xname, IFNAMSIZ); break; case NGM_GIF_GET_IFINDEX: NG_MKRESPONSE(resp, msg, sizeof(u_int32_t), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } *((u_int32_t *)resp->data) = priv->ifp->if_index; break; default: error = EINVAL; break; } break; default: error = EINVAL; break; } NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } /* * Receive data on a hook. */ static int ng_gif_rcvdata(hook_p hook, item_p item) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); struct mbuf *m; NGI_GET_M(item, m); NG_FREE_ITEM(item); if (hook == priv->lower) return ng_gif_rcv_lower(node, m); panic("%s: weird hook", __func__); } /* * Handle an mbuf received on the "lower" hook. */ static int ng_gif_rcv_lower(node_p node, struct mbuf *m) { struct sockaddr dst; const priv_p priv = NG_NODE_PRIVATE(node); bzero(&dst, sizeof(dst)); /* Make sure header is fully pulled up */ if (m->m_pkthdr.len < sizeof(sa_family_t)) { NG_FREE_M(m); return (EINVAL); } if (m->m_len < sizeof(sa_family_t) && (m = m_pullup(m, sizeof(sa_family_t))) == NULL) { return (ENOBUFS); } dst.sa_family = *mtod(m, sa_family_t *); m_adj(m, sizeof(sa_family_t)); /* Send it on its way */ /* * XXX: gif_output only uses dst for the family and passes the * fourth argument (rt) to in{,6}_gif_output which ignore it. * If this changes ng_gif will probably break. */ return gif_output(priv->ifp, m, &dst, NULL); } /* * Shutdown node. This resets the node but does not remove it * unless the REALLY_DIE flag is set. */ static int ng_gif_shutdown(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); if (node->nd_flags & NGF_REALLY_DIE) { /* * WE came here because the gif interface is being destroyed, - * so stop being persistant. + * so stop being persistent. * Actually undo all the things we did on creation. * Assume the ifp has already been freed. */ NG_NODE_SET_PRIVATE(node, NULL); free(priv, M_NETGRAPH); NG_NODE_UNREF(node); /* free node itself */ return (0); } NG_NODE_REVIVE(node); /* Signal ng_rmnode we are persisant */ return (0); } /* * Hook disconnection. */ static int ng_gif_disconnect(hook_p hook) { const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); if (hook == priv->lower) { priv->lower = NULL; priv->lowerOrphan = 0; } else panic("%s: weird hook", __func__); if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) ng_rmnode_self(NG_HOOK_NODE(hook)); /* reset node */ return (0); } /****************************************************************** INITIALIZATION ******************************************************************/ /* * Handle loading and unloading for this node type. */ static int ng_gif_mod_event(module_t mod, int event, void *data) { VNET_ITERATOR_DECL(vnet_iter); struct ifnet *ifp; int error = 0; switch (event) { case MOD_LOAD: /* Register function hooks */ if (ng_gif_attach_p != NULL) { error = EEXIST; break; } ng_gif_attach_p = ng_gif_attach; ng_gif_detach_p = ng_gif_detach; ng_gif_input_p = ng_gif_input; ng_gif_input_orphan_p = ng_gif_input_orphan; /* Create nodes for any already-existing gif interfaces */ VNET_LIST_RLOCK(); IFNET_RLOCK(); VNET_FOREACH(vnet_iter) { CURVNET_SET_QUIET(vnet_iter); /* XXX revisit quiet */ TAILQ_FOREACH(ifp, &V_ifnet, if_link) { if (ifp->if_type == IFT_GIF) ng_gif_attach(ifp); } CURVNET_RESTORE(); } IFNET_RUNLOCK(); VNET_LIST_RUNLOCK(); break; case MOD_UNLOAD: /* * Note that the base code won't try to unload us until * all nodes have been removed, and that can't happen * until all gif interfaces are destroyed. In any * case, we know there are no nodes left if the action * is MOD_UNLOAD, so there's no need to detach any nodes. * * XXX: what about manual unloads?!? */ /* Unregister function hooks */ ng_gif_attach_p = NULL; ng_gif_detach_p = NULL; ng_gif_input_p = NULL; ng_gif_input_orphan_p = NULL; break; default: error = EOPNOTSUPP; break; } return (error); } Index: head/sys/netgraph/ng_gif_demux.c =================================================================== --- head/sys/netgraph/ng_gif_demux.c (revision 298812) +++ head/sys/netgraph/ng_gif_demux.c (revision 298813) @@ -1,396 +1,396 @@ /* * ng_gif_demux.c */ /*- * Copyright 2001 The Aerospace Corporation. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions, and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of The Aerospace Corporation may not be used to endorse or * promote products derived from this software. * * THIS SOFTWARE IS PROVIDED BY THE AEROSPACE CORPORATION ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AEROSPACE CORPORATION BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * * Copyright (c) 1996-1999 Whistle Communications, Inc. * All rights reserved. * * Subject to the following obligations and disclaimer of warranty, use and * redistribution of this software, in source or object code forms, with or * without modifications are expressly permitted by Whistle Communications; * provided, however, that: * 1. Any and all reproductions of the source or object code must include the * copyright notice above and the following disclaimer of warranties; and * 2. No rights are granted, in any manner or form, to use Whistle * Communications, Inc. trademarks, including the mark "WHISTLE * COMMUNICATIONS" on advertising, endorsements, or otherwise except as * such appears in the above copyright notice or in the software. * * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * $FreeBSD$ */ /* * ng_gif_demux(4) netgraph node type * * Packets received on the "gif" hook have their type header removed * and are passed to the appropriate hook protocol hook. Packets - * recieved on a protocol hook have a type header added back and are + * received on a protocol hook have a type header added back and are * passed out the gif hook. The currently supported protocol hooks are: */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef NG_SEPARATE_MALLOC static MALLOC_DEFINE(M_NETGRAPH_GIF_DEMUX, "netgraph_gif_demux", "netgraph gif demux node"); #else #define M_NETGRAPH_GIF_DEMUX M_NETGRAPH #endif /* This struct describes one address family */ struct iffam { sa_family_t family; /* Address family */ const char *hookname; /* Name for hook */ }; typedef const struct iffam *iffam_p; /* List of address families supported by our interface */ const static struct iffam gFamilies[] = { { AF_INET, NG_GIF_DEMUX_HOOK_INET }, { AF_INET6, NG_GIF_DEMUX_HOOK_INET6 }, { AF_APPLETALK, NG_GIF_DEMUX_HOOK_ATALK }, { AF_IPX, NG_GIF_DEMUX_HOOK_IPX }, { AF_ATM, NG_GIF_DEMUX_HOOK_ATM }, { AF_NATM, NG_GIF_DEMUX_HOOK_NATM }, }; #define NUM_FAMILIES nitems(gFamilies) /* Per-node private data */ struct ng_gif_demux_private { node_p node; /* Our netgraph node */ hook_p gif; /* The gif hook */ hook_p hooks[NUM_FAMILIES]; /* The protocol hooks */ }; typedef struct ng_gif_demux_private *priv_p; /* Netgraph node methods */ static ng_constructor_t ng_gif_demux_constructor; static ng_rcvmsg_t ng_gif_demux_rcvmsg; static ng_shutdown_t ng_gif_demux_shutdown; static ng_newhook_t ng_gif_demux_newhook; static ng_rcvdata_t ng_gif_demux_rcvdata; static ng_disconnect_t ng_gif_demux_disconnect; /* Helper stuff */ static iffam_p get_iffam_from_af(sa_family_t family); static iffam_p get_iffam_from_hook(priv_p priv, hook_p hook); static iffam_p get_iffam_from_name(const char *name); static hook_p *get_hook_from_iffam(priv_p priv, iffam_p iffam); /****************************************************************** NETGRAPH PARSE TYPES ******************************************************************/ /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_gif_demux_cmdlist[] = { { 0 } }; /* Node type descriptor */ static struct ng_type ng_gif_demux_typestruct = { .version = NG_ABI_VERSION, .name = NG_GIF_DEMUX_NODE_TYPE, .constructor = ng_gif_demux_constructor, .rcvmsg = ng_gif_demux_rcvmsg, .shutdown = ng_gif_demux_shutdown, .newhook = ng_gif_demux_newhook, .rcvdata = ng_gif_demux_rcvdata, .disconnect = ng_gif_demux_disconnect, .cmdlist = ng_gif_demux_cmdlist, }; NETGRAPH_INIT(gif_demux, &ng_gif_demux_typestruct); /************************************************************************ HELPER STUFF ************************************************************************/ /* * Get the family descriptor from the family ID */ static __inline iffam_p get_iffam_from_af(sa_family_t family) { iffam_p iffam; int k; for (k = 0; k < NUM_FAMILIES; k++) { iffam = &gFamilies[k]; if (iffam->family == family) return (iffam); } return (NULL); } /* * Get the family descriptor from the hook */ static __inline iffam_p get_iffam_from_hook(priv_p priv, hook_p hook) { int k; for (k = 0; k < NUM_FAMILIES; k++) if (priv->hooks[k] == hook) return (&gFamilies[k]); return (NULL); } /* * Get the hook from the iffam descriptor */ static __inline hook_p * get_hook_from_iffam(priv_p priv, iffam_p iffam) { return (&priv->hooks[iffam - gFamilies]); } /* * Get the iffam descriptor from the name */ static __inline iffam_p get_iffam_from_name(const char *name) { iffam_p iffam; int k; for (k = 0; k < NUM_FAMILIES; k++) { iffam = &gFamilies[k]; if (!strcmp(iffam->hookname, name)) return (iffam); } return (NULL); } /****************************************************************** NETGRAPH NODE METHODS ******************************************************************/ /* * Node constructor */ static int ng_gif_demux_constructor(node_p node) { priv_p priv; /* Allocate and initialize private info */ priv = malloc(sizeof(*priv), M_NETGRAPH_GIF_DEMUX, M_WAITOK | M_ZERO); priv->node = node; NG_NODE_SET_PRIVATE(node, priv); /* Done */ return (0); } /* * Method for attaching a new hook */ static int ng_gif_demux_newhook(node_p node, hook_p hook, const char *name) { const priv_p priv = NG_NODE_PRIVATE(node); iffam_p iffam; hook_p *hookptr; if (strcmp(NG_GIF_DEMUX_HOOK_GIF, name) == 0) hookptr = &priv->gif; else { iffam = get_iffam_from_name(name); if (iffam == NULL) return (EPFNOSUPPORT); hookptr = get_hook_from_iffam(NG_NODE_PRIVATE(node), iffam); } if (*hookptr != NULL) return (EISCONN); *hookptr = hook; return (0); } /* * Receive a control message */ static int ng_gif_demux_rcvmsg(node_p node, item_p item, hook_p lasthook) { struct ng_mesg *resp = NULL; int error = 0; struct ng_mesg *msg; NGI_GET_MSG(item, msg); switch (msg->header.typecookie) { case NGM_GIF_DEMUX_COOKIE: switch (msg->header.cmd) { /* XXX: Add commands here. */ default: error = EINVAL; break; } break; default: error = EINVAL; break; } /* Done */ NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } /* * Receive data on a hook */ static int ng_gif_demux_rcvdata(hook_p hook, item_p item) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); iffam_p iffam; hook_p outhook; int error = 0; struct mbuf *m; /* Pull the mbuf out of the item for processing. */ NGI_GET_M(item, m); if (hook == priv->gif) { /* * Pull off the address family header and find the * output hook. */ if (m->m_pkthdr.len < sizeof(sa_family_t)) { NG_FREE_M(m); NG_FREE_ITEM(item); return (EINVAL); } if (m->m_len < sizeof(sa_family_t) && (m = m_pullup(m, sizeof(sa_family_t))) == NULL) { NG_FREE_ITEM(item); return (ENOBUFS); } iffam = get_iffam_from_af(*mtod(m, sa_family_t *)); if (iffam == NULL) { NG_FREE_M(m); NG_FREE_ITEM(item); return (EINVAL); } outhook = *get_hook_from_iffam(priv, iffam); m_adj(m, sizeof(sa_family_t)); } else { /* * Add address family header and set the output hook. */ iffam = get_iffam_from_hook(priv, hook); M_PREPEND(m, sizeof (iffam->family), M_NOWAIT); if (m == NULL) { NG_FREE_M(m); NG_FREE_ITEM(item); return (ENOBUFS); } bcopy(&iffam->family, mtod(m, sa_family_t *), sizeof(iffam->family)); outhook = priv->gif; } /* Stuff the mbuf back in. */ NGI_M(item) = m; /* Deliver packet */ NG_FWD_ITEM_HOOK(error, item, outhook); return (error); } /* * Shutdown node */ static int ng_gif_demux_shutdown(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); free(priv, M_NETGRAPH_GIF_DEMUX); NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(node); return (0); } /* * Hook disconnection. */ static int ng_gif_demux_disconnect(hook_p hook) { const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); iffam_p iffam; if (hook == priv->gif) priv->gif = NULL; else { iffam = get_iffam_from_hook(priv, hook); if (iffam == NULL) panic("%s", __func__); *get_hook_from_iffam(priv, iffam) = NULL; } return (0); } Index: head/sys/netgraph/ng_ksocket.c =================================================================== --- head/sys/netgraph/ng_ksocket.c (revision 298812) +++ head/sys/netgraph/ng_ksocket.c (revision 298813) @@ -1,1316 +1,1316 @@ /* * ng_ksocket.c */ /*- * Copyright (c) 1996-1999 Whistle Communications, Inc. * All rights reserved. * * Subject to the following obligations and disclaimer of warranty, use and * redistribution of this software, in source or object code forms, with or * without modifications are expressly permitted by Whistle Communications; * provided, however, that: * 1. Any and all reproductions of the source or object code must include the * copyright notice above and the following disclaimer of warranties; and * 2. No rights are granted, in any manner or form, to use Whistle * Communications, Inc. trademarks, including the mark "WHISTLE * COMMUNICATIONS" on advertising, endorsements, or otherwise except as * such appears in the above copyright notice or in the software. * * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * Author: Archie Cobbs * * $FreeBSD$ * $Whistle: ng_ksocket.c,v 1.1 1999/11/16 20:04:40 archie Exp $ */ /* * Kernel socket node type. This node type is basically a kernel-mode * version of a socket... kindof like the reverse of the socket node type. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef NG_SEPARATE_MALLOC static MALLOC_DEFINE(M_NETGRAPH_KSOCKET, "netgraph_ksock", "netgraph ksock node"); #else #define M_NETGRAPH_KSOCKET M_NETGRAPH #endif #define OFFSETOF(s, e) ((char *)&((s *)0)->e - (char *)((s *)0)) #define SADATA_OFFSET (OFFSETOF(struct sockaddr, sa_data)) /* Node private data */ struct ng_ksocket_private { node_p node; hook_p hook; struct socket *so; int fn_sent; /* FN call on incoming event was sent */ LIST_HEAD(, ng_ksocket_private) embryos; LIST_ENTRY(ng_ksocket_private) siblings; u_int32_t flags; u_int32_t response_token; ng_ID_t response_addr; }; typedef struct ng_ksocket_private *priv_p; /* Flags for priv_p */ #define KSF_CONNECTING 0x00000001 /* Waiting for connection complete */ #define KSF_ACCEPTING 0x00000002 /* Waiting for accept complete */ #define KSF_EOFSEEN 0x00000004 /* Have sent 0-length EOF mbuf */ #define KSF_CLONED 0x00000008 /* Cloned from an accepting socket */ #define KSF_EMBRYONIC 0x00000010 /* Cloned node with no hooks yet */ /* Netgraph node methods */ static ng_constructor_t ng_ksocket_constructor; static ng_rcvmsg_t ng_ksocket_rcvmsg; static ng_shutdown_t ng_ksocket_shutdown; static ng_newhook_t ng_ksocket_newhook; static ng_rcvdata_t ng_ksocket_rcvdata; static ng_connect_t ng_ksocket_connect; static ng_disconnect_t ng_ksocket_disconnect; /* Alias structure */ struct ng_ksocket_alias { const char *name; const int value; const int family; }; /* Protocol family aliases */ static const struct ng_ksocket_alias ng_ksocket_families[] = { { "local", PF_LOCAL }, { "inet", PF_INET }, { "inet6", PF_INET6 }, { "atm", PF_ATM }, { NULL, -1 }, }; /* Socket type aliases */ static const struct ng_ksocket_alias ng_ksocket_types[] = { { "stream", SOCK_STREAM }, { "dgram", SOCK_DGRAM }, { "raw", SOCK_RAW }, { "rdm", SOCK_RDM }, { "seqpacket", SOCK_SEQPACKET }, { NULL, -1 }, }; /* Protocol aliases */ static const struct ng_ksocket_alias ng_ksocket_protos[] = { { "ip", IPPROTO_IP, PF_INET }, { "raw", IPPROTO_RAW, PF_INET }, { "icmp", IPPROTO_ICMP, PF_INET }, { "igmp", IPPROTO_IGMP, PF_INET }, { "tcp", IPPROTO_TCP, PF_INET }, { "udp", IPPROTO_UDP, PF_INET }, { "gre", IPPROTO_GRE, PF_INET }, { "esp", IPPROTO_ESP, PF_INET }, { "ah", IPPROTO_AH, PF_INET }, { "swipe", IPPROTO_SWIPE, PF_INET }, { "encap", IPPROTO_ENCAP, PF_INET }, { "divert", IPPROTO_DIVERT, PF_INET }, { "pim", IPPROTO_PIM, PF_INET }, { NULL, -1 }, }; /* Helper functions */ static int ng_ksocket_check_accept(priv_p); static void ng_ksocket_finish_accept(priv_p); static int ng_ksocket_incoming(struct socket *so, void *arg, int waitflag); static int ng_ksocket_parse(const struct ng_ksocket_alias *aliases, const char *s, int family); static void ng_ksocket_incoming2(node_p node, hook_p hook, void *arg1, int arg2); /************************************************************************ STRUCT SOCKADDR PARSE TYPE ************************************************************************/ /* Get the length of the data portion of a generic struct sockaddr */ static int ng_parse_generic_sockdata_getLength(const struct ng_parse_type *type, const u_char *start, const u_char *buf) { const struct sockaddr *sa; sa = (const struct sockaddr *)(buf - SADATA_OFFSET); return (sa->sa_len < SADATA_OFFSET) ? 0 : sa->sa_len - SADATA_OFFSET; } /* Type for the variable length data portion of a generic struct sockaddr */ static const struct ng_parse_type ng_ksocket_generic_sockdata_type = { &ng_parse_bytearray_type, &ng_parse_generic_sockdata_getLength }; /* Type for a generic struct sockaddr */ static const struct ng_parse_struct_field ng_parse_generic_sockaddr_type_fields[] = { { "len", &ng_parse_uint8_type }, { "family", &ng_parse_uint8_type }, { "data", &ng_ksocket_generic_sockdata_type }, { NULL } }; static const struct ng_parse_type ng_ksocket_generic_sockaddr_type = { &ng_parse_struct_type, &ng_parse_generic_sockaddr_type_fields }; /* Convert a struct sockaddr from ASCII to binary. If its a protocol family that we specially handle, do that, otherwise defer to the generic parse type ng_ksocket_generic_sockaddr_type. */ static int ng_ksocket_sockaddr_parse(const struct ng_parse_type *type, const char *s, int *off, const u_char *const start, u_char *const buf, int *buflen) { struct sockaddr *const sa = (struct sockaddr *)buf; enum ng_parse_token tok; char fambuf[32]; int family, len; char *t; /* If next token is a left curly brace, use generic parse type */ if ((tok = ng_parse_get_token(s, off, &len)) == T_LBRACE) { return (*ng_ksocket_generic_sockaddr_type.supertype->parse) (&ng_ksocket_generic_sockaddr_type, s, off, start, buf, buflen); } /* Get socket address family followed by a slash */ while (isspace(s[*off])) (*off)++; if ((t = strchr(s + *off, '/')) == NULL) return (EINVAL); if ((len = t - (s + *off)) > sizeof(fambuf) - 1) return (EINVAL); strncpy(fambuf, s + *off, len); fambuf[len] = '\0'; *off += len + 1; if ((family = ng_ksocket_parse(ng_ksocket_families, fambuf, 0)) == -1) return (EINVAL); /* Set family */ if (*buflen < SADATA_OFFSET) return (ERANGE); sa->sa_family = family; /* Set family-specific data and length */ switch (sa->sa_family) { case PF_LOCAL: /* Get pathname */ { const int pathoff = OFFSETOF(struct sockaddr_un, sun_path); struct sockaddr_un *const sun = (struct sockaddr_un *)sa; int toklen, pathlen; char *path; if ((path = ng_get_string_token(s, off, &toklen, NULL)) == NULL) return (EINVAL); pathlen = strlen(path); if (pathlen > SOCK_MAXADDRLEN) { free(path, M_NETGRAPH_KSOCKET); return (E2BIG); } if (*buflen < pathoff + pathlen) { free(path, M_NETGRAPH_KSOCKET); return (ERANGE); } *off += toklen; bcopy(path, sun->sun_path, pathlen); sun->sun_len = pathoff + pathlen; free(path, M_NETGRAPH_KSOCKET); break; } case PF_INET: /* Get an IP address with optional port */ { struct sockaddr_in *const sin = (struct sockaddr_in *)sa; int i; /* Parse this: [:port] */ for (i = 0; i < 4; i++) { u_long val; char *eptr; val = strtoul(s + *off, &eptr, 10); if (val > 0xff || eptr == s + *off) return (EINVAL); *off += (eptr - (s + *off)); ((u_char *)&sin->sin_addr)[i] = (u_char)val; if (i < 3) { if (s[*off] != '.') return (EINVAL); (*off)++; } else if (s[*off] == ':') { (*off)++; val = strtoul(s + *off, &eptr, 10); if (val > 0xffff || eptr == s + *off) return (EINVAL); *off += (eptr - (s + *off)); sin->sin_port = htons(val); } else sin->sin_port = 0; } bzero(&sin->sin_zero, sizeof(sin->sin_zero)); sin->sin_len = sizeof(*sin); break; } #if 0 case PF_INET6: /* XXX implement this someday */ #endif default: return (EINVAL); } /* Done */ *buflen = sa->sa_len; return (0); } /* Convert a struct sockaddr from binary to ASCII */ static int ng_ksocket_sockaddr_unparse(const struct ng_parse_type *type, const u_char *data, int *off, char *cbuf, int cbuflen) { const struct sockaddr *sa = (const struct sockaddr *)(data + *off); int slen = 0; /* Output socket address, either in special or generic format */ switch (sa->sa_family) { case PF_LOCAL: { const int pathoff = OFFSETOF(struct sockaddr_un, sun_path); const struct sockaddr_un *sun = (const struct sockaddr_un *)sa; const int pathlen = sun->sun_len - pathoff; char pathbuf[SOCK_MAXADDRLEN + 1]; char *pathtoken; bcopy(sun->sun_path, pathbuf, pathlen); if ((pathtoken = ng_encode_string(pathbuf, pathlen)) == NULL) return (ENOMEM); slen += snprintf(cbuf, cbuflen, "local/%s", pathtoken); free(pathtoken, M_NETGRAPH_KSOCKET); if (slen >= cbuflen) return (ERANGE); *off += sun->sun_len; return (0); } case PF_INET: { const struct sockaddr_in *sin = (const struct sockaddr_in *)sa; slen += snprintf(cbuf, cbuflen, "inet/%d.%d.%d.%d", ((const u_char *)&sin->sin_addr)[0], ((const u_char *)&sin->sin_addr)[1], ((const u_char *)&sin->sin_addr)[2], ((const u_char *)&sin->sin_addr)[3]); if (sin->sin_port != 0) { slen += snprintf(cbuf + strlen(cbuf), cbuflen - strlen(cbuf), ":%d", (u_int)ntohs(sin->sin_port)); } if (slen >= cbuflen) return (ERANGE); *off += sizeof(*sin); return(0); } #if 0 case PF_INET6: /* XXX implement this someday */ #endif default: return (*ng_ksocket_generic_sockaddr_type.supertype->unparse) (&ng_ksocket_generic_sockaddr_type, data, off, cbuf, cbuflen); } } /* Parse type for struct sockaddr */ static const struct ng_parse_type ng_ksocket_sockaddr_type = { NULL, NULL, NULL, &ng_ksocket_sockaddr_parse, &ng_ksocket_sockaddr_unparse, NULL /* no such thing as a default struct sockaddr */ }; /************************************************************************ STRUCT NG_KSOCKET_SOCKOPT PARSE TYPE ************************************************************************/ /* Get length of the struct ng_ksocket_sockopt value field, which is the just the excess of the message argument portion over the length of the struct ng_ksocket_sockopt. */ static int ng_parse_sockoptval_getLength(const struct ng_parse_type *type, const u_char *start, const u_char *buf) { static const int offset = OFFSETOF(struct ng_ksocket_sockopt, value); const struct ng_ksocket_sockopt *sopt; const struct ng_mesg *msg; sopt = (const struct ng_ksocket_sockopt *)(buf - offset); msg = (const struct ng_mesg *)((const u_char *)sopt - sizeof(*msg)); return msg->header.arglen - sizeof(*sopt); } /* Parse type for the option value part of a struct ng_ksocket_sockopt XXX Eventually, we should handle the different socket options specially. XXX This would avoid byte order problems, eg an integer value of 1 is XXX going to be "[1]" for little endian or "[3=1]" for big endian. */ static const struct ng_parse_type ng_ksocket_sockoptval_type = { &ng_parse_bytearray_type, &ng_parse_sockoptval_getLength }; /* Parse type for struct ng_ksocket_sockopt */ static const struct ng_parse_struct_field ng_ksocket_sockopt_type_fields[] = NG_KSOCKET_SOCKOPT_INFO(&ng_ksocket_sockoptval_type); static const struct ng_parse_type ng_ksocket_sockopt_type = { &ng_parse_struct_type, &ng_ksocket_sockopt_type_fields }; /* Parse type for struct ng_ksocket_accept */ static const struct ng_parse_struct_field ng_ksocket_accept_type_fields[] = NGM_KSOCKET_ACCEPT_INFO; static const struct ng_parse_type ng_ksocket_accept_type = { &ng_parse_struct_type, &ng_ksocket_accept_type_fields }; /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_ksocket_cmds[] = { { NGM_KSOCKET_COOKIE, NGM_KSOCKET_BIND, "bind", &ng_ksocket_sockaddr_type, NULL }, { NGM_KSOCKET_COOKIE, NGM_KSOCKET_LISTEN, "listen", &ng_parse_int32_type, NULL }, { NGM_KSOCKET_COOKIE, NGM_KSOCKET_ACCEPT, "accept", NULL, &ng_ksocket_accept_type }, { NGM_KSOCKET_COOKIE, NGM_KSOCKET_CONNECT, "connect", &ng_ksocket_sockaddr_type, &ng_parse_int32_type }, { NGM_KSOCKET_COOKIE, NGM_KSOCKET_GETNAME, "getname", NULL, &ng_ksocket_sockaddr_type }, { NGM_KSOCKET_COOKIE, NGM_KSOCKET_GETPEERNAME, "getpeername", NULL, &ng_ksocket_sockaddr_type }, { NGM_KSOCKET_COOKIE, NGM_KSOCKET_SETOPT, "setopt", &ng_ksocket_sockopt_type, NULL }, { NGM_KSOCKET_COOKIE, NGM_KSOCKET_GETOPT, "getopt", &ng_ksocket_sockopt_type, &ng_ksocket_sockopt_type }, { 0 } }; /* Node type descriptor */ static struct ng_type ng_ksocket_typestruct = { .version = NG_ABI_VERSION, .name = NG_KSOCKET_NODE_TYPE, .constructor = ng_ksocket_constructor, .rcvmsg = ng_ksocket_rcvmsg, .shutdown = ng_ksocket_shutdown, .newhook = ng_ksocket_newhook, .connect = ng_ksocket_connect, .rcvdata = ng_ksocket_rcvdata, .disconnect = ng_ksocket_disconnect, .cmdlist = ng_ksocket_cmds, }; NETGRAPH_INIT(ksocket, &ng_ksocket_typestruct); #define ERROUT(x) do { error = (x); goto done; } while (0) /************************************************************************ NETGRAPH NODE STUFF ************************************************************************/ /* * Node type constructor * The NODE part is assumed to be all set up. * There is already a reference to the node for us. */ static int ng_ksocket_constructor(node_p node) { priv_p priv; /* Allocate private structure */ priv = malloc(sizeof(*priv), M_NETGRAPH_KSOCKET, M_NOWAIT | M_ZERO); if (priv == NULL) return (ENOMEM); LIST_INIT(&priv->embryos); /* cross link them */ priv->node = node; NG_NODE_SET_PRIVATE(node, priv); /* Done */ return (0); } /* * Give our OK for a hook to be added. The hook name is of the * form "//" where the three components may * be decimal numbers or else aliases from the above lists. * * Connecting a hook amounts to opening the socket. Disconnecting * the hook closes the socket and destroys the node as well. */ static int ng_ksocket_newhook(node_p node, hook_p hook, const char *name0) { struct thread *td = curthread; /* XXX broken */ const priv_p priv = NG_NODE_PRIVATE(node); char *s1, *s2, name[NG_HOOKSIZ]; int family, type, protocol, error; /* Check if we're already connected */ if (priv->hook != NULL) return (EISCONN); if (priv->flags & KSF_CLONED) { if (priv->flags & KSF_EMBRYONIC) { /* Remove ourselves from our parent's embryo list */ LIST_REMOVE(priv, siblings); priv->flags &= ~KSF_EMBRYONIC; } } else { /* Extract family, type, and protocol from hook name */ snprintf(name, sizeof(name), "%s", name0); s1 = name; if ((s2 = strchr(s1, '/')) == NULL) return (EINVAL); *s2++ = '\0'; family = ng_ksocket_parse(ng_ksocket_families, s1, 0); if (family == -1) return (EINVAL); s1 = s2; if ((s2 = strchr(s1, '/')) == NULL) return (EINVAL); *s2++ = '\0'; type = ng_ksocket_parse(ng_ksocket_types, s1, 0); if (type == -1) return (EINVAL); s1 = s2; protocol = ng_ksocket_parse(ng_ksocket_protos, s1, family); if (protocol == -1) return (EINVAL); /* Create the socket */ error = socreate(family, &priv->so, type, protocol, td->td_ucred, td); if (error != 0) return (error); /* XXX call soreserve() ? */ } /* OK */ priv->hook = hook; /* * In case of misconfigured routing a packet may reenter * ksocket node recursively. Decouple stack to avoid possible * panics about sleeping with locks held. */ NG_HOOK_FORCE_QUEUE(hook); return(0); } static int ng_ksocket_connect(hook_p hook) { node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); struct socket *const so = priv->so; /* Add our hook for incoming data and other events */ SOCKBUF_LOCK(&priv->so->so_rcv); soupcall_set(priv->so, SO_RCV, ng_ksocket_incoming, node); SOCKBUF_UNLOCK(&priv->so->so_rcv); SOCKBUF_LOCK(&priv->so->so_snd); soupcall_set(priv->so, SO_SND, ng_ksocket_incoming, node); SOCKBUF_UNLOCK(&priv->so->so_snd); SOCK_LOCK(priv->so); priv->so->so_state |= SS_NBIO; SOCK_UNLOCK(priv->so); /* * --Original comment-- * On a cloned socket we may have already received one or more * upcalls which we couldn't handle without a hook. Handle * those now. * We cannot call the upcall function directly * from here, because until this function has returned our * hook isn't connected. * * ---meta comment for -current --- * XXX This is dubius. * Upcalls between the time that the hook was * first created and now (on another processesor) will * be earlier on the queue than the request to finalise the hook. * By the time the hook is finalised, - * The queued upcalls will have happenned and the code + * The queued upcalls will have happened and the code * will have discarded them because of a lack of a hook. * (socket not open). * * This is a bad byproduct of the complicated way in which hooks * are now created (3 daisy chained async events). * * Since we are a netgraph operation * We know that we hold a lock on this node. This forces the * request we make below to be queued rather than implemented - * immediatly which will cause the upcall function to be called a bit + * immediately which will cause the upcall function to be called a bit * later. - * However, as we will run any waiting queued operations immediatly + * However, as we will run any waiting queued operations immediately * after doing this one, if we have not finalised the other end * of the hook, those queued operations will fail. */ if (priv->flags & KSF_CLONED) { ng_send_fn(node, NULL, &ng_ksocket_incoming2, so, M_NOWAIT); } return (0); } /* * Receive a control message */ static int ng_ksocket_rcvmsg(node_p node, item_p item, hook_p lasthook) { struct thread *td = curthread; /* XXX broken */ const priv_p priv = NG_NODE_PRIVATE(node); struct socket *const so = priv->so; struct ng_mesg *resp = NULL; int error = 0; struct ng_mesg *msg; ng_ID_t raddr; NGI_GET_MSG(item, msg); switch (msg->header.typecookie) { case NGM_KSOCKET_COOKIE: switch (msg->header.cmd) { case NGM_KSOCKET_BIND: { struct sockaddr *const sa = (struct sockaddr *)msg->data; /* Sanity check */ if (msg->header.arglen < SADATA_OFFSET || msg->header.arglen < sa->sa_len) ERROUT(EINVAL); if (so == NULL) ERROUT(ENXIO); /* Bind */ error = sobind(so, sa, td); break; } case NGM_KSOCKET_LISTEN: { /* Sanity check */ if (msg->header.arglen != sizeof(int32_t)) ERROUT(EINVAL); if (so == NULL) ERROUT(ENXIO); /* Listen */ error = solisten(so, *((int32_t *)msg->data), td); break; } case NGM_KSOCKET_ACCEPT: { /* Sanity check */ if (msg->header.arglen != 0) ERROUT(EINVAL); if (so == NULL) ERROUT(ENXIO); /* Make sure the socket is capable of accepting */ if (!(so->so_options & SO_ACCEPTCONN)) ERROUT(EINVAL); if (priv->flags & KSF_ACCEPTING) ERROUT(EALREADY); error = ng_ksocket_check_accept(priv); if (error != 0 && error != EWOULDBLOCK) ERROUT(error); /* * If a connection is already complete, take it. * Otherwise let the upcall function deal with * the connection when it comes in. */ priv->response_token = msg->header.token; raddr = priv->response_addr = NGI_RETADDR(item); if (error == 0) { ng_ksocket_finish_accept(priv); } else priv->flags |= KSF_ACCEPTING; break; } case NGM_KSOCKET_CONNECT: { struct sockaddr *const sa = (struct sockaddr *)msg->data; /* Sanity check */ if (msg->header.arglen < SADATA_OFFSET || msg->header.arglen < sa->sa_len) ERROUT(EINVAL); if (so == NULL) ERROUT(ENXIO); /* Do connect */ if ((so->so_state & SS_ISCONNECTING) != 0) ERROUT(EALREADY); if ((error = soconnect(so, sa, td)) != 0) { so->so_state &= ~SS_ISCONNECTING; ERROUT(error); } if ((so->so_state & SS_ISCONNECTING) != 0) { /* We will notify the sender when we connect */ priv->response_token = msg->header.token; raddr = priv->response_addr = NGI_RETADDR(item); priv->flags |= KSF_CONNECTING; ERROUT(EINPROGRESS); } break; } case NGM_KSOCKET_GETNAME: case NGM_KSOCKET_GETPEERNAME: { int (*func)(struct socket *so, struct sockaddr **nam); struct sockaddr *sa = NULL; int len; /* Sanity check */ if (msg->header.arglen != 0) ERROUT(EINVAL); if (so == NULL) ERROUT(ENXIO); /* Get function */ if (msg->header.cmd == NGM_KSOCKET_GETPEERNAME) { if ((so->so_state & (SS_ISCONNECTED|SS_ISCONFIRMING)) == 0) ERROUT(ENOTCONN); func = so->so_proto->pr_usrreqs->pru_peeraddr; } else func = so->so_proto->pr_usrreqs->pru_sockaddr; /* Get local or peer address */ if ((error = (*func)(so, &sa)) != 0) goto bail; len = (sa == NULL) ? 0 : sa->sa_len; /* Send it back in a response */ NG_MKRESPONSE(resp, msg, len, M_NOWAIT); if (resp == NULL) { error = ENOMEM; goto bail; } bcopy(sa, resp->data, len); bail: /* Cleanup */ if (sa != NULL) free(sa, M_SONAME); break; } case NGM_KSOCKET_GETOPT: { struct ng_ksocket_sockopt *ksopt = (struct ng_ksocket_sockopt *)msg->data; struct sockopt sopt; /* Sanity check */ if (msg->header.arglen != sizeof(*ksopt)) ERROUT(EINVAL); if (so == NULL) ERROUT(ENXIO); /* Get response with room for option value */ NG_MKRESPONSE(resp, msg, sizeof(*ksopt) + NG_KSOCKET_MAX_OPTLEN, M_NOWAIT); if (resp == NULL) ERROUT(ENOMEM); /* Get socket option, and put value in the response */ sopt.sopt_dir = SOPT_GET; sopt.sopt_level = ksopt->level; sopt.sopt_name = ksopt->name; sopt.sopt_td = NULL; sopt.sopt_valsize = NG_KSOCKET_MAX_OPTLEN; ksopt = (struct ng_ksocket_sockopt *)resp->data; sopt.sopt_val = ksopt->value; if ((error = sogetopt(so, &sopt)) != 0) { NG_FREE_MSG(resp); break; } /* Set actual value length */ resp->header.arglen = sizeof(*ksopt) + sopt.sopt_valsize; break; } case NGM_KSOCKET_SETOPT: { struct ng_ksocket_sockopt *const ksopt = (struct ng_ksocket_sockopt *)msg->data; const int valsize = msg->header.arglen - sizeof(*ksopt); struct sockopt sopt; /* Sanity check */ if (valsize < 0) ERROUT(EINVAL); if (so == NULL) ERROUT(ENXIO); /* Set socket option */ sopt.sopt_dir = SOPT_SET; sopt.sopt_level = ksopt->level; sopt.sopt_name = ksopt->name; sopt.sopt_val = ksopt->value; sopt.sopt_valsize = valsize; sopt.sopt_td = NULL; error = sosetopt(so, &sopt); break; } default: error = EINVAL; break; } break; default: error = EINVAL; break; } done: NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } /* * Receive incoming data on our hook. Send it out the socket. */ static int ng_ksocket_rcvdata(hook_p hook, item_p item) { struct thread *td = curthread; /* XXX broken */ const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); struct socket *const so = priv->so; struct sockaddr *sa = NULL; int error; struct mbuf *m; #ifdef ALIGNED_POINTER struct mbuf *n; #endif /* ALIGNED_POINTER */ struct sa_tag *stag; /* Extract data */ NGI_GET_M(item, m); NG_FREE_ITEM(item); #ifdef ALIGNED_POINTER if (!ALIGNED_POINTER(mtod(m, caddr_t), uint32_t)) { n = m_defrag(m, M_NOWAIT); if (n == NULL) { m_freem(m); return (ENOBUFS); } m = n; } #endif /* ALIGNED_POINTER */ /* * Look if socket address is stored in packet tags. * If sockaddr is ours, or provided by a third party (zero id), * then we accept it. */ if (((stag = (struct sa_tag *)m_tag_locate(m, NGM_KSOCKET_COOKIE, NG_KSOCKET_TAG_SOCKADDR, NULL)) != NULL) && (stag->id == NG_NODE_ID(node) || stag->id == 0)) sa = &stag->sa; /* Reset specific mbuf flags to prevent addressing problems. */ m->m_flags &= ~(M_BCAST|M_MCAST); /* Send packet */ error = sosend(so, sa, 0, m, 0, 0, td); return (error); } /* * Destroy node */ static int ng_ksocket_shutdown(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); priv_p embryo; /* Close our socket (if any) */ if (priv->so != NULL) { SOCKBUF_LOCK(&priv->so->so_rcv); soupcall_clear(priv->so, SO_RCV); SOCKBUF_UNLOCK(&priv->so->so_rcv); SOCKBUF_LOCK(&priv->so->so_snd); soupcall_clear(priv->so, SO_SND); SOCKBUF_UNLOCK(&priv->so->so_snd); soclose(priv->so); priv->so = NULL; } /* If we are an embryo, take ourselves out of the parent's list */ if (priv->flags & KSF_EMBRYONIC) { LIST_REMOVE(priv, siblings); priv->flags &= ~KSF_EMBRYONIC; } /* Remove any embryonic children we have */ while (!LIST_EMPTY(&priv->embryos)) { embryo = LIST_FIRST(&priv->embryos); ng_rmnode_self(embryo->node); } /* Take down netgraph node */ bzero(priv, sizeof(*priv)); free(priv, M_NETGRAPH_KSOCKET); NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(node); /* let the node escape */ return (0); } /* * Hook disconnection */ static int ng_ksocket_disconnect(hook_p hook) { KASSERT(NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0, ("%s: numhooks=%d?", __func__, NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)))); if (NG_NODE_IS_VALID(NG_HOOK_NODE(hook))) ng_rmnode_self(NG_HOOK_NODE(hook)); return (0); } /************************************************************************ HELPER STUFF ************************************************************************/ /* * You should not "just call" a netgraph node function from an external * asynchronous event. This is because in doing so you are ignoring the * locking on the netgraph nodes. Instead call your function via ng_send_fn(). * This will call the function you chose, but will first do all the * locking rigmarole. Your function MAY only be called at some distant future * time (several millisecs away) so don't give it any arguments * that may be revoked soon (e.g. on your stack). * * To decouple stack, we use queue version of ng_send_fn(). */ static int ng_ksocket_incoming(struct socket *so, void *arg, int waitflag) { const node_p node = arg; const priv_p priv = NG_NODE_PRIVATE(node); int wait = ((waitflag & M_WAITOK) ? NG_WAITOK : 0) | NG_QUEUE; /* * Even if node is not locked, as soon as we are called, we assume * it exist and it's private area is valid. With some care we can * access it. Mark node that incoming event for it was sent to * avoid unneded queue trashing. */ if (atomic_cmpset_int(&priv->fn_sent, 0, 1) && ng_send_fn1(node, NULL, &ng_ksocket_incoming2, so, 0, wait)) { atomic_store_rel_int(&priv->fn_sent, 0); } return (SU_OK); } /* * When incoming data is appended to the socket, we get notified here. * This is also called whenever a significant event occurs for the socket. * Our original caller may have queued this even some time ago and * we cannot trust that he even still exists. The node however is being * held with a reference by the queueing code and guarantied to be valid. */ static void ng_ksocket_incoming2(node_p node, hook_p hook, void *arg1, int arg2) { struct socket *so = arg1; const priv_p priv = NG_NODE_PRIVATE(node); struct ng_mesg *response; int error; KASSERT(so == priv->so, ("%s: wrong socket", __func__)); /* Allow next incoming event to be queued. */ atomic_store_rel_int(&priv->fn_sent, 0); /* Check whether a pending connect operation has completed */ if (priv->flags & KSF_CONNECTING) { if ((error = so->so_error) != 0) { so->so_error = 0; so->so_state &= ~SS_ISCONNECTING; } if (!(so->so_state & SS_ISCONNECTING)) { NG_MKMESSAGE(response, NGM_KSOCKET_COOKIE, NGM_KSOCKET_CONNECT, sizeof(int32_t), M_NOWAIT); if (response != NULL) { response->header.flags |= NGF_RESP; response->header.token = priv->response_token; *(int32_t *)response->data = error; /* * send an async "response" message * to the node that set us up * (if it still exists) */ NG_SEND_MSG_ID(error, node, response, priv->response_addr, 0); } priv->flags &= ~KSF_CONNECTING; } } /* Check whether a pending accept operation has completed */ if (priv->flags & KSF_ACCEPTING) { error = ng_ksocket_check_accept(priv); if (error != EWOULDBLOCK) priv->flags &= ~KSF_ACCEPTING; if (error == 0) ng_ksocket_finish_accept(priv); } /* * If we don't have a hook, we must handle data events later. When * the hook gets created and is connected, this upcall function * will be called again. */ if (priv->hook == NULL) return; /* Read and forward available mbufs. */ while (1) { struct uio uio; struct sockaddr *sa; struct mbuf *m; int flags; /* Try to get next packet from socket. */ uio.uio_td = NULL; uio.uio_resid = IP_MAXPACKET; flags = MSG_DONTWAIT; sa = NULL; if ((error = soreceive(so, (so->so_state & SS_ISCONNECTED) ? NULL : &sa, &uio, &m, NULL, &flags)) != 0) break; /* See if we got anything. */ if (flags & MSG_TRUNC) { m_freem(m); m = NULL; } if (m == NULL) { if (sa != NULL) free(sa, M_SONAME); break; } KASSERT(m->m_nextpkt == NULL, ("%s: nextpkt", __func__)); /* * Stream sockets do not have packet boundaries, so * we have to allocate a header mbuf and attach the * stream of data to it. */ if (so->so_type == SOCK_STREAM) { struct mbuf *mh; mh = m_gethdr(M_NOWAIT, MT_DATA); if (mh == NULL) { m_freem(m); if (sa != NULL) free(sa, M_SONAME); break; } mh->m_next = m; for (; m; m = m->m_next) mh->m_pkthdr.len += m->m_len; m = mh; } /* Put peer's socket address (if any) into a tag */ if (sa != NULL) { struct sa_tag *stag; stag = (struct sa_tag *)m_tag_alloc(NGM_KSOCKET_COOKIE, NG_KSOCKET_TAG_SOCKADDR, sizeof(ng_ID_t) + sa->sa_len, M_NOWAIT); if (stag == NULL) { free(sa, M_SONAME); goto sendit; } bcopy(sa, &stag->sa, sa->sa_len); free(sa, M_SONAME); stag->id = NG_NODE_ID(node); m_tag_prepend(m, &stag->tag); } sendit: /* Forward data with optional peer sockaddr as packet tag */ NG_SEND_DATA_ONLY(error, priv->hook, m); } /* * If the peer has closed the connection, forward a 0-length mbuf * to indicate end-of-file. */ if (so->so_rcv.sb_state & SBS_CANTRCVMORE && !(priv->flags & KSF_EOFSEEN)) { struct mbuf *m; m = m_gethdr(M_NOWAIT, MT_DATA); if (m != NULL) NG_SEND_DATA_ONLY(error, priv->hook, m); priv->flags |= KSF_EOFSEEN; } } /* * Check for a completed incoming connection and return 0 if one is found. * Otherwise return the appropriate error code. */ static int ng_ksocket_check_accept(priv_p priv) { struct socket *const head = priv->so; int error; if ((error = head->so_error) != 0) { head->so_error = 0; return error; } /* Unlocked read. */ if (TAILQ_EMPTY(&head->so_comp)) { if (head->so_rcv.sb_state & SBS_CANTRCVMORE) return ECONNABORTED; return EWOULDBLOCK; } return 0; } /* * Handle the first completed incoming connection, assumed to be already * on the socket's so_comp queue. */ static void ng_ksocket_finish_accept(priv_p priv) { struct socket *const head = priv->so; struct socket *so; struct sockaddr *sa = NULL; struct ng_mesg *resp; struct ng_ksocket_accept *resp_data; node_p node; priv_p priv2; int len; int error; ACCEPT_LOCK(); so = TAILQ_FIRST(&head->so_comp); if (so == NULL) { /* Should never happen */ ACCEPT_UNLOCK(); return; } TAILQ_REMOVE(&head->so_comp, so, so_list); head->so_qlen--; so->so_qstate &= ~SQ_COMP; so->so_head = NULL; SOCK_LOCK(so); soref(so); so->so_state |= SS_NBIO; SOCK_UNLOCK(so); ACCEPT_UNLOCK(); /* XXX KNOTE_UNLOCKED(&head->so_rcv.sb_sel.si_note, 0); */ soaccept(so, &sa); len = OFFSETOF(struct ng_ksocket_accept, addr); if (sa != NULL) len += sa->sa_len; NG_MKMESSAGE(resp, NGM_KSOCKET_COOKIE, NGM_KSOCKET_ACCEPT, len, M_NOWAIT); if (resp == NULL) { soclose(so); goto out; } resp->header.flags |= NGF_RESP; resp->header.token = priv->response_token; /* Clone a ksocket node to wrap the new socket */ error = ng_make_node_common(&ng_ksocket_typestruct, &node); if (error) { free(resp, M_NETGRAPH); soclose(so); goto out; } if (ng_ksocket_constructor(node) != 0) { NG_NODE_UNREF(node); free(resp, M_NETGRAPH); soclose(so); goto out; } priv2 = NG_NODE_PRIVATE(node); priv2->so = so; priv2->flags |= KSF_CLONED | KSF_EMBRYONIC; /* * Insert the cloned node into a list of embryonic children * on the parent node. When a hook is created on the cloned * node it will be removed from this list. When the parent * is destroyed it will destroy any embryonic children it has. */ LIST_INSERT_HEAD(&priv->embryos, priv2, siblings); SOCKBUF_LOCK(&so->so_rcv); soupcall_set(so, SO_RCV, ng_ksocket_incoming, node); SOCKBUF_UNLOCK(&so->so_rcv); SOCKBUF_LOCK(&so->so_snd); soupcall_set(so, SO_SND, ng_ksocket_incoming, node); SOCKBUF_UNLOCK(&so->so_snd); /* Fill in the response data and send it or return it to the caller */ resp_data = (struct ng_ksocket_accept *)resp->data; resp_data->nodeid = NG_NODE_ID(node); if (sa != NULL) bcopy(sa, &resp_data->addr, sa->sa_len); NG_SEND_MSG_ID(error, node, resp, priv->response_addr, 0); out: if (sa != NULL) free(sa, M_SONAME); } /* * Parse out either an integer value or an alias. */ static int ng_ksocket_parse(const struct ng_ksocket_alias *aliases, const char *s, int family) { int k, val; char *eptr; /* Try aliases */ for (k = 0; aliases[k].name != NULL; k++) { if (strcmp(s, aliases[k].name) == 0 && aliases[k].family == family) return aliases[k].value; } /* Try parsing as a number */ val = (int)strtoul(s, &eptr, 10); if (val < 0 || *eptr != '\0') return (-1); return (val); } Index: head/sys/netgraph/ng_l2tp.h =================================================================== --- head/sys/netgraph/ng_l2tp.h (revision 298812) +++ head/sys/netgraph/ng_l2tp.h (revision 298813) @@ -1,196 +1,196 @@ /*- * Copyright (c) 2001-2002 Packet Design, LLC. * All rights reserved. * * Subject to the following obligations and disclaimer of warranty, * use and redistribution of this software, in source or object code * forms, with or without modifications are expressly permitted by * Packet Design; provided, however, that: * * (i) Any and all reproductions of the source or object code * must include the copyright notice above and the following * disclaimer of warranties; and * (ii) No rights are granted, in any manner or form, to use * Packet Design trademarks, including the mark "PACKET DESIGN" * on advertising, endorsements, or otherwise except as such * appears in the above copyright notice or in the software. * * THIS SOFTWARE IS BEING PROVIDED BY PACKET DESIGN "AS IS", AND * TO THE MAXIMUM EXTENT PERMITTED BY LAW, PACKET DESIGN MAKES NO * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING * THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, * OR NON-INFRINGEMENT. PACKET DESIGN DOES NOT WARRANT, GUARANTEE, * OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS * OF THE USE OF THIS SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, * RELIABILITY OR OTHERWISE. IN NO EVENT SHALL PACKET DESIGN BE * LIABLE FOR ANY DAMAGES RESULTING FROM OR ARISING OUT OF ANY USE * OF THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE, OR CONSEQUENTIAL * DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, LOSS OF * USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF * THE USE OF THIS SOFTWARE, EVEN IF PACKET DESIGN IS ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. * * Author: Archie Cobbs * * $FreeBSD$ */ #ifndef _NETGRAPH_NG_L2TP_H_ #define _NETGRAPH_NG_L2TP_H_ /* Node type name and magic cookie */ #define NG_L2TP_NODE_TYPE "l2tp" #define NGM_L2TP_COOKIE 1091515793 /* Hook names */ #define NG_L2TP_HOOK_CTRL "ctrl" /* control channel hook */ #define NG_L2TP_HOOK_LOWER "lower" /* hook to lower layers */ /* Session hooks: prefix plus hex session ID, e.g., "session_3e14" */ #define NG_L2TP_HOOK_SESSION_P "session_" /* session data hook (prefix) */ #define NG_L2TP_HOOK_SESSION_F "session_%04x" /* session data hook (format) */ -/* Set intial sequence numbers to not yet enabled node. */ +/* Set initial sequence numbers to not yet enabled node. */ struct ng_l2tp_seq_config { u_int16_t ns; /* sequence number to send next */ u_int16_t nr; /* sequence number to be recved next */ u_int16_t rack; /* last 'nr' received */ u_int16_t xack; /* last 'nr' sent */ }; /* Keep this in sync with the above structure definition. */ #define NG_L2TP_SEQ_CONFIG_TYPE_INFO { \ { "ns", &ng_parse_uint16_type }, \ { "nr", &ng_parse_uint16_type }, \ { NULL } \ } /* Configuration for a node */ struct ng_l2tp_config { u_char enabled; /* enables traffic flow */ u_char match_id; /* tunnel id must match 'tunnel_id' */ u_int16_t tunnel_id; /* local tunnel id */ u_int16_t peer_id; /* peer's tunnel id */ u_int16_t peer_win; /* peer's max recv window size */ u_int16_t rexmit_max; /* max retransmits before failure */ u_int16_t rexmit_max_to; /* max delay between retransmits */ }; /* Keep this in sync with the above structure definition */ #define NG_L2TP_CONFIG_TYPE_INFO { \ { "enabled", &ng_parse_uint8_type }, \ { "match_id", &ng_parse_uint8_type }, \ { "tunnel_id", &ng_parse_hint16_type }, \ { "peer_id", &ng_parse_hint16_type }, \ { "peer_win", &ng_parse_uint16_type }, \ { "rexmit_max", &ng_parse_uint16_type }, \ { "rexmit_max_to", &ng_parse_uint16_type }, \ { NULL } \ } /* Configuration for a session hook */ struct ng_l2tp_sess_config { u_int16_t session_id; /* local session id */ u_int16_t peer_id; /* peer's session id */ u_char control_dseq; /* whether we control data sequencing */ u_char enable_dseq; /* whether to enable data sequencing */ u_char include_length; /* whether to include length field */ }; /* Keep this in sync with the above structure definition */ #define NG_L2TP_SESS_CONFIG_TYPE_INFO { \ { "session_id", &ng_parse_hint16_type }, \ { "peer_id", &ng_parse_hint16_type }, \ { "control_dseq", &ng_parse_uint8_type }, \ { "enable_dseq", &ng_parse_uint8_type }, \ { "include_length", &ng_parse_uint8_type }, \ { NULL } \ } /* Statistics struct */ struct ng_l2tp_stats { u_int32_t xmitPackets; /* number of packets xmit */ u_int32_t xmitOctets; /* number of octets xmit */ u_int32_t xmitZLBs; /* ack-only packets transmitted */ u_int32_t xmitDrops; /* xmits dropped due to full window */ u_int32_t xmitTooBig; /* ctrl pkts dropped because too big */ u_int32_t xmitInvalid; /* ctrl packets with no session ID */ u_int32_t xmitDataTooBig; /* data pkts dropped because too big */ u_int32_t xmitRetransmits; /* retransmitted packets */ u_int32_t recvPackets; /* number of packets rec'd */ u_int32_t recvOctets; /* number of octets rec'd */ u_int32_t recvRunts; /* too short packets rec'd */ u_int32_t recvInvalid; /* invalid packets rec'd */ u_int32_t recvWrongTunnel; /* packets rec'd with wrong tunnel id */ u_int32_t recvUnknownSID; /* pkts rec'd with unknown session id */ u_int32_t recvBadAcks; /* ctrl pkts rec'd with invalid 'nr' */ u_int32_t recvOutOfOrder; /* out of order ctrl pkts rec'd */ u_int32_t recvDuplicates; /* duplicate ctrl pkts rec'd */ u_int32_t recvDataDrops; /* dup/out of order data pkts rec'd */ u_int32_t recvZLBs; /* ack-only packets rec'd */ u_int32_t memoryFailures; /* times we couldn't allocate memory */ }; /* Keep this in sync with the above structure definition */ #define NG_L2TP_STATS_TYPE_INFO { \ { "xmitPackets", &ng_parse_uint32_type }, \ { "xmitOctets", &ng_parse_uint32_type }, \ { "xmitZLBs", &ng_parse_uint32_type }, \ { "xmitDrops", &ng_parse_uint32_type }, \ { "xmitTooBig", &ng_parse_uint32_type }, \ { "xmitInvalid", &ng_parse_uint32_type }, \ { "xmitDataTooBig", &ng_parse_uint32_type }, \ { "xmitRetransmits", &ng_parse_uint32_type }, \ { "recvPackets", &ng_parse_uint32_type }, \ { "recvOctets", &ng_parse_uint32_type }, \ { "recvRunts", &ng_parse_uint32_type }, \ { "recvInvalid", &ng_parse_uint32_type }, \ { "recvWrongTunnel", &ng_parse_uint32_type }, \ { "recvUnknownSID", &ng_parse_uint32_type }, \ { "recvBadAcks", &ng_parse_uint32_type }, \ { "recvOutOfOrder", &ng_parse_uint32_type }, \ { "recvDuplicates", &ng_parse_uint32_type }, \ { "recvDataDrops", &ng_parse_uint32_type }, \ { "recvZLBs", &ng_parse_uint32_type }, \ { "memoryFailures", &ng_parse_uint32_type }, \ { NULL } \ } /* Session statistics struct. */ struct ng_l2tp_session_stats { u_int64_t xmitPackets; /* number of packets xmit */ u_int64_t xmitOctets; /* number of octets xmit */ u_int64_t recvPackets; /* number of packets received */ u_int64_t recvOctets; /* number of octets received */ }; /* Keep this in sync with the above structure definition. */ #define NG_L2TP_SESSION_STATS_TYPE_INFO { \ { "xmitPackets", &ng_parse_uint64_type }, \ { "xmitOctets", &ng_parse_uint64_type }, \ { "recvPackets", &ng_parse_uint64_type }, \ { "recvOctets", &ng_parse_uint64_type }, \ { NULL } \ } /* Netgraph commands */ enum { NGM_L2TP_SET_CONFIG = 1, /* supply a struct ng_l2tp_config */ NGM_L2TP_GET_CONFIG, /* returns a struct ng_l2tp_config */ NGM_L2TP_SET_SESS_CONFIG, /* supply struct ng_l2tp_sess_config */ NGM_L2TP_GET_SESS_CONFIG, /* supply a session id (u_int16_t) */ NGM_L2TP_GET_STATS, /* returns struct ng_l2tp_stats */ NGM_L2TP_CLR_STATS, /* clears stats */ NGM_L2TP_GETCLR_STATS, /* returns & clears stats */ NGM_L2TP_GET_SESSION_STATS, /* returns session stats */ NGM_L2TP_CLR_SESSION_STATS, /* clears session stats */ NGM_L2TP_GETCLR_SESSION_STATS, /* returns & clears session stats */ NGM_L2TP_ACK_FAILURE, /* sent *from* node after ack timeout */ NGM_L2TP_SET_SEQ /* supply a struct ng_l2tp_seq_config */ }; #endif /* _NETGRAPH_NG_L2TP_H_ */ Index: head/sys/netgraph/ng_lmi.c =================================================================== --- head/sys/netgraph/ng_lmi.c (revision 298812) +++ head/sys/netgraph/ng_lmi.c (revision 298813) @@ -1,1080 +1,1080 @@ /* * ng_lmi.c */ /*- * Copyright (c) 1996-1999 Whistle Communications, Inc. * All rights reserved. * * Subject to the following obligations and disclaimer of warranty, use and * redistribution of this software, in source or object code forms, with or * without modifications are expressly permitted by Whistle Communications; * provided, however, that: * 1. Any and all reproductions of the source or object code must include the * copyright notice above and the following disclaimer of warranties; and * 2. No rights are granted, in any manner or form, to use Whistle * Communications, Inc. trademarks, including the mark "WHISTLE * COMMUNICATIONS" on advertising, endorsements, or otherwise except as * such appears in the above copyright notice or in the software. * * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * Author: Julian Elischer * * $FreeBSD$ * $Whistle: ng_lmi.c,v 1.38 1999/11/01 09:24:52 julian Exp $ */ /* * This node performs the frame relay LMI protocol. It knows how * to do ITU Annex A, ANSI Annex D, and "Group-of-Four" variants * of the protocol. * * A specific protocol can be forced by connecting the corresponding * hook to DLCI 0 or 1023 (as appropriate) of a frame relay link. * * Alternately, this node can do auto-detection of the LMI protocol * by connecting hook "auto0" to DLCI 0 and "auto1023" to DLCI 1023. */ #include #include #include #include #include #include #include #include #include #include /* * Human readable names for LMI */ #define NAME_ANNEXA NG_LMI_HOOK_ANNEXA #define NAME_ANNEXD NG_LMI_HOOK_ANNEXD #define NAME_GROUP4 NG_LMI_HOOK_GROUPOF4 #define NAME_NONE "None" #define MAX_DLCIS 128 #define MAXDLCI 1023 /* * DLCI states */ #define DLCI_NULL 0 #define DLCI_UP 1 #define DLCI_DOWN 2 /* * Any received LMI frame should be at least this long */ #define LMI_MIN_LENGTH 8 /* XXX verify */ /* * Netgraph node methods and type descriptor */ static ng_constructor_t nglmi_constructor; static ng_rcvmsg_t nglmi_rcvmsg; static ng_shutdown_t nglmi_shutdown; static ng_newhook_t nglmi_newhook; static ng_rcvdata_t nglmi_rcvdata; static ng_disconnect_t nglmi_disconnect; static int nglmi_checkdata(hook_p hook, struct mbuf *m); static struct ng_type typestruct = { .version = NG_ABI_VERSION, .name = NG_LMI_NODE_TYPE, .constructor = nglmi_constructor, .rcvmsg = nglmi_rcvmsg, .shutdown = nglmi_shutdown, .newhook = nglmi_newhook, .rcvdata = nglmi_rcvdata, .disconnect = nglmi_disconnect, }; NETGRAPH_INIT(lmi, &typestruct); /* * Info and status per node */ struct nglmi_softc { node_p node; /* netgraph node */ int flags; /* state */ int poll_count; /* the count of times for autolmi */ int poll_state; /* state of auto detect machine */ u_char remote_seq; /* sequence number the remote sent */ u_char local_seq; /* last sequence number we sent */ u_char protoID; /* 9 for group of 4, 8 otherwise */ u_long seq_retries; /* sent this how many time so far */ struct callout handle; /* see timeout(9) */ int liv_per_full; int liv_rate; int livs; int need_full; hook_p lmi_channel; /* whatever we ended up using */ hook_p lmi_annexA; hook_p lmi_annexD; hook_p lmi_group4; hook_p lmi_channel0; /* auto-detect on DLCI 0 */ hook_p lmi_channel1023;/* auto-detect on DLCI 1023 */ char *protoname; /* cache protocol name */ u_char dlci_state[MAXDLCI + 1]; int invalidx; /* next dlci's to invalidate */ }; typedef struct nglmi_softc *sc_p; /* * Other internal functions */ static void LMI_ticker(node_p node, hook_p hook, void *arg1, int arg2); static void nglmi_startup_fixed(sc_p sc, hook_p hook); static void nglmi_startup_auto(sc_p sc); static void nglmi_startup(sc_p sc); static void nglmi_inquire(sc_p sc, int full); static void ngauto_state_machine(sc_p sc); /* * Values for 'flags' field * NB: the SCF_CONNECTED flag is set if and only if the timer is running. */ #define SCF_CONNECTED 0x01 /* connected to something */ #define SCF_AUTO 0x02 /* we are auto-detecting */ #define SCF_FIXED 0x04 /* we are fixed from the start */ #define SCF_LMITYPE 0x18 /* mask for determining Annex mode */ #define SCF_NOLMI 0x00 /* no LMI type selected yet */ #define SCF_ANNEX_A 0x08 /* running annex A mode */ #define SCF_ANNEX_D 0x10 /* running annex D mode */ #define SCF_GROUP4 0x18 /* running group of 4 */ #define SETLMITYPE(sc, annex) \ do { \ (sc)->flags &= ~SCF_LMITYPE; \ (sc)->flags |= (annex); \ } while (0) #define NOPROTO(sc) (((sc)->flags & SCF_LMITYPE) == SCF_NOLMI) #define ANNEXA(sc) (((sc)->flags & SCF_LMITYPE) == SCF_ANNEX_A) #define ANNEXD(sc) (((sc)->flags & SCF_LMITYPE) == SCF_ANNEX_D) #define GROUP4(sc) (((sc)->flags & SCF_LMITYPE) == SCF_GROUP4) #define LMIPOLLSIZE 3 #define LMI_PATIENCE 8 /* declare all DLCI DOWN after N LMI failures */ /* * Node constructor */ static int nglmi_constructor(node_p node) { sc_p sc; sc = malloc(sizeof(*sc), M_NETGRAPH, M_WAITOK | M_ZERO); NG_NODE_SET_PRIVATE(node, sc); sc->node = node; ng_callout_init(&sc->handle); sc->protoname = NAME_NONE; sc->liv_per_full = NG_LMI_SEQ_PER_FULL; /* make this dynamic */ sc->liv_rate = NG_LMI_KEEPALIVE_RATE; return (0); } /* * The LMI channel has a private pointer which is the same as the * node private pointer. The debug channel has a NULL private pointer. */ static int nglmi_newhook(node_p node, hook_p hook, const char *name) { sc_p sc = NG_NODE_PRIVATE(node); if (strcmp(name, NG_LMI_HOOK_DEBUG) == 0) { NG_HOOK_SET_PRIVATE(hook, NULL); return (0); } if (sc->flags & SCF_CONNECTED) { /* already connected, return an error */ return (EINVAL); } if (strcmp(name, NG_LMI_HOOK_ANNEXA) == 0) { sc->lmi_annexA = hook; NG_HOOK_SET_PRIVATE(hook, NG_NODE_PRIVATE(node)); sc->protoID = 8; SETLMITYPE(sc, SCF_ANNEX_A); sc->protoname = NAME_ANNEXA; nglmi_startup_fixed(sc, hook); } else if (strcmp(name, NG_LMI_HOOK_ANNEXD) == 0) { sc->lmi_annexD = hook; NG_HOOK_SET_PRIVATE(hook, NG_NODE_PRIVATE(node)); sc->protoID = 8; SETLMITYPE(sc, SCF_ANNEX_D); sc->protoname = NAME_ANNEXD; nglmi_startup_fixed(sc, hook); } else if (strcmp(name, NG_LMI_HOOK_GROUPOF4) == 0) { sc->lmi_group4 = hook; NG_HOOK_SET_PRIVATE(hook, NG_NODE_PRIVATE(node)); sc->protoID = 9; SETLMITYPE(sc, SCF_GROUP4); sc->protoname = NAME_GROUP4; nglmi_startup_fixed(sc, hook); } else if (strcmp(name, NG_LMI_HOOK_AUTO0) == 0) { /* Note this, and if B is already installed, we're complete */ sc->lmi_channel0 = hook; sc->protoname = NAME_NONE; NG_HOOK_SET_PRIVATE(hook, NG_NODE_PRIVATE(node)); if (sc->lmi_channel1023) nglmi_startup_auto(sc); } else if (strcmp(name, NG_LMI_HOOK_AUTO1023) == 0) { /* Note this, and if A is already installed, we're complete */ sc->lmi_channel1023 = hook; sc->protoname = NAME_NONE; NG_HOOK_SET_PRIVATE(hook, NG_NODE_PRIVATE(node)); if (sc->lmi_channel0) nglmi_startup_auto(sc); } else return (EINVAL); /* unknown hook */ return (0); } /* * We have just attached to a live (we hope) node. * Fire out a LMI inquiry, and then start up the timers. */ static void LMI_ticker(node_p node, hook_p hook, void *arg1, int arg2) { sc_p sc = NG_NODE_PRIVATE(node); if (sc->flags & SCF_AUTO) { ngauto_state_machine(sc); ng_callout(&sc->handle, node, NULL, NG_LMI_POLL_RATE * hz, LMI_ticker, NULL, 0); } else { if (sc->livs++ >= sc->liv_per_full) { nglmi_inquire(sc, 1); /* sc->livs = 0; *//* do this when we get the answer! */ } else { nglmi_inquire(sc, 0); } ng_callout(&sc->handle, node, NULL, sc->liv_rate * hz, LMI_ticker, NULL, 0); } } static void nglmi_startup_fixed(sc_p sc, hook_p hook) { sc->flags |= (SCF_FIXED | SCF_CONNECTED); sc->lmi_channel = hook; nglmi_startup(sc); } static void nglmi_startup_auto(sc_p sc) { sc->flags |= (SCF_AUTO | SCF_CONNECTED); sc->poll_state = 0; /* reset state machine */ sc->poll_count = 0; nglmi_startup(sc); } static void nglmi_startup(sc_p sc) { sc->remote_seq = 0; sc->local_seq = 1; sc->seq_retries = 0; sc->livs = sc->liv_per_full - 1; /* start off the ticker in 1 sec */ ng_callout(&sc->handle, sc->node, NULL, hz, LMI_ticker, NULL, 0); } static void nglmi_inquire(sc_p sc, int full) { struct mbuf *m; struct ng_tag_prio *ptag; char *cptr, *start; int error; if (sc->lmi_channel == NULL) return; MGETHDR(m, M_NOWAIT, MT_DATA); if (m == NULL) { log(LOG_ERR, "nglmi: unable to start up LMI processing\n"); return; } m->m_pkthdr.rcvif = NULL; /* Attach a tag to packet, marking it of link level state priority, so * that device driver would put it in the beginning of queue */ ptag = (struct ng_tag_prio *)m_tag_alloc(NGM_GENERIC_COOKIE, NG_TAG_PRIO, (sizeof(struct ng_tag_prio) - sizeof(struct m_tag)), M_NOWAIT); if (ptag != NULL) { /* if it failed, well, it was optional anyhow */ ptag->priority = NG_PRIO_LINKSTATE; ptag->discardability = -1; m_tag_prepend(m, &ptag->tag); } m->m_data += 4; /* leave some room for a header */ cptr = start = mtod(m, char *); /* add in the header for an LMI inquiry. */ *cptr++ = 0x03; /* UI frame */ if (GROUP4(sc)) *cptr++ = 0x09; /* proto discriminator */ else *cptr++ = 0x08; /* proto discriminator */ *cptr++ = 0x00; /* call reference */ *cptr++ = 0x75; /* inquiry */ /* If we are Annex-D, add locking shift to codeset 5. */ if (ANNEXD(sc)) *cptr++ = 0x95; /* locking shift */ /* Add a request type */ if (ANNEXA(sc)) *cptr++ = 0x51; /* report type */ else *cptr++ = 0x01; /* report type */ *cptr++ = 0x01; /* size = 1 */ if (full) *cptr++ = 0x00; /* full */ else *cptr++ = 0x01; /* partial */ /* Add a link verification IE */ if (ANNEXA(sc)) *cptr++ = 0x53; /* verification IE */ else *cptr++ = 0x03; /* verification IE */ *cptr++ = 0x02; /* 2 extra bytes */ *cptr++ = sc->local_seq; *cptr++ = sc->remote_seq; sc->seq_retries++; /* Send it */ m->m_len = m->m_pkthdr.len = cptr - start; NG_SEND_DATA_ONLY(error, sc->lmi_channel, m); /* If we've been sending requests for long enough, and there has * been no response, then mark as DOWN, any DLCIs that are UP. */ if (sc->seq_retries == LMI_PATIENCE) { int count; for (count = 0; count < MAXDLCI; count++) if (sc->dlci_state[count] == DLCI_UP) sc->dlci_state[count] = DLCI_DOWN; } } /* * State machine for LMI auto-detect. The transitions are ordered * to try the more likely possibilities first. */ static void ngauto_state_machine(sc_p sc) { if ((sc->poll_count <= 0) || (sc->poll_count > LMIPOLLSIZE)) { /* time to change states in the auto probe machine */ /* capture wild values of poll_count while we are at it */ sc->poll_count = LMIPOLLSIZE; sc->poll_state++; } switch (sc->poll_state) { case 7: log(LOG_WARNING, "nglmi: no response from exchange\n"); default: /* capture bad states */ sc->poll_state = 1; case 1: sc->lmi_channel = sc->lmi_channel0; SETLMITYPE(sc, SCF_ANNEX_D); break; case 2: sc->lmi_channel = sc->lmi_channel1023; SETLMITYPE(sc, SCF_ANNEX_D); break; case 3: sc->lmi_channel = sc->lmi_channel0; SETLMITYPE(sc, SCF_ANNEX_A); break; case 4: sc->lmi_channel = sc->lmi_channel1023; SETLMITYPE(sc, SCF_GROUP4); break; case 5: sc->lmi_channel = sc->lmi_channel1023; SETLMITYPE(sc, SCF_ANNEX_A); break; case 6: sc->lmi_channel = sc->lmi_channel0; SETLMITYPE(sc, SCF_GROUP4); break; } - /* send an inquirey encoded appropriatly */ + /* send an inquirey encoded appropriately */ nglmi_inquire(sc, 0); sc->poll_count--; } /* * Receive a netgraph control message. */ static int nglmi_rcvmsg(node_p node, item_p item, hook_p lasthook) { sc_p sc = NG_NODE_PRIVATE(node); struct ng_mesg *resp = NULL; int error = 0; struct ng_mesg *msg; NGI_GET_MSG(item, msg); switch (msg->header.typecookie) { case NGM_GENERIC_COOKIE: switch (msg->header.cmd) { case NGM_TEXT_STATUS: { char *arg; int pos, count; NG_MKRESPONSE(resp, msg, NG_TEXTRESPONSE, M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } arg = resp->data; pos = sprintf(arg, "protocol %s ", sc->protoname); if (sc->flags & SCF_FIXED) pos += sprintf(arg + pos, "fixed\n"); else if (sc->flags & SCF_AUTO) pos += sprintf(arg + pos, "auto-detecting\n"); else pos += sprintf(arg + pos, "auto on dlci %d\n", (sc->lmi_channel == sc->lmi_channel0) ? 0 : 1023); pos += sprintf(arg + pos, "keepalive period: %d seconds\n", sc->liv_rate); pos += sprintf(arg + pos, "unacknowledged keepalives: %ld\n", sc->seq_retries); for (count = 0; ((count <= MAXDLCI) && (pos < (NG_TEXTRESPONSE - 20))); count++) { if (sc->dlci_state[count]) { pos += sprintf(arg + pos, "dlci %d %s\n", count, (sc->dlci_state[count] == DLCI_UP) ? "up" : "down"); } } resp->header.arglen = pos + 1; break; } default: error = EINVAL; break; } break; case NGM_LMI_COOKIE: switch (msg->header.cmd) { case NGM_LMI_GET_STATUS: { struct nglmistat *stat; int k; NG_MKRESPONSE(resp, msg, sizeof(*stat), M_NOWAIT); if (!resp) { error = ENOMEM; break; } stat = (struct nglmistat *) resp->data; strncpy(stat->proto, sc->protoname, sizeof(stat->proto) - 1); strncpy(stat->hook, sc->protoname, sizeof(stat->hook) - 1); stat->autod = !!(sc->flags & SCF_AUTO); stat->fixed = !!(sc->flags & SCF_FIXED); for (k = 0; k <= MAXDLCI; k++) { switch (sc->dlci_state[k]) { case DLCI_UP: stat->up[k / 8] |= (1 << (k % 8)); /* fall through */ case DLCI_DOWN: stat->seen[k / 8] |= (1 << (k % 8)); break; } } break; } default: error = EINVAL; break; } break; default: error = EINVAL; break; } NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } #define STEPBY(stepsize) \ do { \ packetlen -= (stepsize); \ data += (stepsize); \ } while (0) /* * receive data, and use it to update our status. * Anything coming in on the debug port is discarded. */ static int nglmi_rcvdata(hook_p hook, item_p item) { sc_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); const u_char *data; unsigned short dlci; u_short packetlen; int resptype_seen = 0; struct mbuf *m; NGI_GET_M(item, m); NG_FREE_ITEM(item); if (NG_HOOK_PRIVATE(hook) == NULL) { goto drop; } packetlen = m->m_len; /* XXX what if it's more than 1 mbuf? */ if ((packetlen > MHLEN) && !(m->m_flags & M_EXT)) { log(LOG_WARNING, "nglmi: packetlen (%d) too big\n", packetlen); goto drop; } if (m->m_len < packetlen && (m = m_pullup(m, packetlen)) == NULL) { log(LOG_WARNING, "nglmi: m_pullup failed for %d bytes\n", packetlen); return (0); } if (nglmi_checkdata(hook, m) == 0) return (0); /* pass the first 4 bytes (already checked in the nglmi_checkdata()) */ data = mtod(m, const u_char *); STEPBY(4); /* Now check if there is a 'locking shift'. This is only seen in * Annex D frames. don't bother checking, we already did that. Don't - * increment immediatly as it might not be there. */ + * increment immediately as it might not be there. */ if (ANNEXD(sc)) STEPBY(1); /* If we get this far we should consider that it is a legitimate * frame and we know what it is. */ if (sc->flags & SCF_AUTO) { /* note the hook that this valid channel came from and drop * out of auto probe mode. */ if (ANNEXA(sc)) sc->protoname = NAME_ANNEXA; else if (ANNEXD(sc)) sc->protoname = NAME_ANNEXD; else if (GROUP4(sc)) sc->protoname = NAME_GROUP4; else { log(LOG_ERR, "nglmi: No known type\n"); goto drop; } sc->lmi_channel = hook; sc->flags &= ~SCF_AUTO; log(LOG_INFO, "nglmi: auto-detected %s LMI on DLCI %d\n", sc->protoname, hook == sc->lmi_channel0 ? 0 : 1023); } /* While there is more data in the status packet, keep processing * status items. First make sure there is enough data for the * segment descriptor's length field. */ while (packetlen >= 2) { u_int segtype = data[0]; u_int segsize = data[1]; /* Now that we know how long it claims to be, make sure * there is enough data for the next seg. */ if (packetlen < segsize + 2) break; switch (segtype) { case 0x01: case 0x51: if (resptype_seen) { log(LOG_WARNING, "nglmi: dup MSGTYPE\n"); goto nextIE; } resptype_seen++; /* The remote end tells us what kind of response * this is. Only expect a type 0 or 1. if we are a * full status, invalidate a few DLCIs just to see * that they are still ok. */ if (segsize != 1) goto nextIE; switch (data[2]) { case 1: /* partial status, do no extra processing */ break; case 0: { int count = 0; int idx = sc->invalidx; for (count = 0; count < 10; count++) { if (idx > MAXDLCI) idx = 0; if (sc->dlci_state[idx] == DLCI_UP) sc->dlci_state[idx] = DLCI_DOWN; idx++; } sc->invalidx = idx; /* we got and we wanted one. relax * now.. but don't reset to 0 if it * was unrequested. */ if (sc->livs > sc->liv_per_full) sc->livs = 0; break; } } break; case 0x03: case 0x53: /* The remote tells us what it thinks the sequence * numbers are. If it's not size 2, it must be a * duplicate to have gotten this far, skip it. */ if (segsize != 2) goto nextIE; sc->remote_seq = data[2]; if (sc->local_seq == data[3]) { sc->local_seq++; sc->seq_retries = 0; /* Note that all 3 Frame protocols seem to * not like 0 as a sequence number. */ if (sc->local_seq == 0) sc->local_seq = 1; } break; case 0x07: case 0x57: /* The remote tells us about a DLCI that it knows * about. There may be many of these in a single * status response */ switch (segsize) { case 6:/* only on 'group of 4' */ dlci = ((u_short) data[2] & 0xff) << 8; dlci |= (data[3] & 0xff); if ((dlci < 1024) && (dlci > 0)) { /* XXX */ } break; case 3: dlci = ((u_short) data[2] & 0x3f) << 4; dlci |= ((data[3] & 0x78) >> 3); if ((dlci < 1024) && (dlci > 0)) { /* set up the bottom half of the * support for that dlci if it's not * already been done */ /* store this information somewhere */ } break; default: goto nextIE; } if (sc->dlci_state[dlci] != DLCI_UP) { /* bring new DLCI to life */ /* may do more here some day */ if (sc->dlci_state[dlci] != DLCI_DOWN) log(LOG_INFO, "nglmi: DLCI %d became active\n", dlci); sc->dlci_state[dlci] = DLCI_UP; } break; } nextIE: STEPBY(segsize + 2); } NG_FREE_M(m); return (0); drop: NG_FREE_M(m); return (EINVAL); } /* * Check that a packet is entirely kosha. * return 1 of ok, and 0 if not. * All data is discarded if a 0 is returned. */ static int nglmi_checkdata(hook_p hook, struct mbuf *m) { sc_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); const u_char *data; u_short packetlen; unsigned short dlci; u_char type; u_char nextbyte; int seq_seen = 0; int resptype_seen = 0; /* 0 , 1 (partial) or 2 (full) */ int highest_dlci = 0; packetlen = m->m_len; data = mtod(m, const u_char *); if (*data != 0x03) { log(LOG_WARNING, "nglmi: unexpected value in LMI(%d)\n", 1); goto reject; } STEPBY(1); /* look at the protocol ID */ nextbyte = *data; if (sc->flags & SCF_AUTO) { SETLMITYPE(sc, SCF_NOLMI); /* start with a clean slate */ switch (nextbyte) { case 0x8: sc->protoID = 8; break; case 0x9: SETLMITYPE(sc, SCF_GROUP4); sc->protoID = 9; break; default: log(LOG_WARNING, "nglmi: bad Protocol ID(%d)\n", (int) nextbyte); goto reject; } } else { if (nextbyte != sc->protoID) { log(LOG_WARNING, "nglmi: unexpected Protocol ID(%d)\n", (int) nextbyte); goto reject; } } STEPBY(1); /* check call reference (always null in non ISDN frame relay) */ if (*data != 0x00) { log(LOG_WARNING, "nglmi: unexpected Call Reference (0x%x)\n", data[-1]); goto reject; } STEPBY(1); /* check message type */ switch ((type = *data)) { case 0x75: /* Status enquiry */ log(LOG_WARNING, "nglmi: unexpected message type(0x%x)\n", data[-1]); goto reject; case 0x7D: /* Status message */ break; default: log(LOG_WARNING, "nglmi: unexpected msg type(0x%x) \n", (int) type); goto reject; } STEPBY(1); /* Now check if there is a 'locking shift'. This is only seen in * Annex D frames. Don't increment immediately as it might not be * there. */ nextbyte = *data; if (sc->flags & SCF_AUTO) { if (!(GROUP4(sc))) { if (nextbyte == 0x95) { SETLMITYPE(sc, SCF_ANNEX_D); STEPBY(1); } else SETLMITYPE(sc, SCF_ANNEX_A); } else if (nextbyte == 0x95) { log(LOG_WARNING, "nglmi: locking shift seen in G4\n"); goto reject; } } else { if (ANNEXD(sc)) { if (*data == 0x95) STEPBY(1); else { log(LOG_WARNING, "nglmi: locking shift missing\n"); goto reject; } } else if (*data == 0x95) { log(LOG_WARNING, "nglmi: locking shift seen\n"); goto reject; } } /* While there is more data in the status packet, keep processing * status items. First make sure there is enough data for the * segment descriptor's length field. */ while (packetlen >= 2) { u_int segtype = data[0]; u_int segsize = data[1]; /* Now that we know how long it claims to be, make sure * there is enough data for the next seg. */ if (packetlen < (segsize + 2)) { log(LOG_WARNING, "nglmi: IE longer than packet\n"); break; } switch (segtype) { case 0x01: case 0x51: /* According to MCI's HP analyser, we should just * ignore if there is mor ethan one of these (?). */ if (resptype_seen) { log(LOG_WARNING, "nglmi: dup MSGTYPE\n"); goto nextIE; } if (segsize != 1) { log(LOG_WARNING, "nglmi: MSGTYPE wrong size\n"); goto reject; } /* The remote end tells us what kind of response * this is. Only expect a type 0 or 1. if it was a * full (type 0) check we just asked for a type * full. */ switch (data[2]) { case 1:/* partial */ if (sc->livs > sc->liv_per_full) { log(LOG_WARNING, "nglmi: LIV when FULL expected\n"); goto reject; /* need full */ } resptype_seen = 1; break; case 0:/* full */ /* Full response is always acceptable */ resptype_seen = 2; break; default: log(LOG_WARNING, "nglmi: Unknown report type %d\n", data[2]); goto reject; } break; case 0x03: case 0x53: /* The remote tells us what it thinks the sequence * numbers are. I would have thought that there * needs to be one and only one of these, but MCI * want us to just ignore extras. (?) */ if (resptype_seen == 0) { log(LOG_WARNING, "nglmi: no TYPE before SEQ\n"); goto reject; } if (seq_seen != 0) /* already seen seq numbers */ goto nextIE; if (segsize != 2) { log(LOG_WARNING, "nglmi: bad SEQ sts size\n"); goto reject; } if (sc->local_seq != data[3]) { log(LOG_WARNING, "nglmi: unexpected SEQ\n"); goto reject; } seq_seen = 1; break; case 0x07: case 0x57: /* The remote tells us about a DLCI that it knows * about. There may be many of these in a single * status response */ if (seq_seen != 1) { /* already seen seq numbers? */ log(LOG_WARNING, "nglmi: No sequence before DLCI\n"); goto reject; } if (resptype_seen != 2) { /* must be full */ log(LOG_WARNING, "nglmi: No resp type before DLCI\n"); goto reject; } if (GROUP4(sc)) { if (segsize != 6) { log(LOG_WARNING, "nglmi: wrong IE segsize\n"); goto reject; } dlci = ((u_short) data[2] & 0xff) << 8; dlci |= (data[3] & 0xff); } else { if (segsize != 3) { log(LOG_WARNING, "nglmi: DLCI headersize of %d" " not supported\n", segsize - 1); goto reject; } dlci = ((u_short) data[2] & 0x3f) << 4; dlci |= ((data[3] & 0x78) >> 3); } /* async can only have one of these */ #if 0 /* async not yet accepted */ if (async && highest_dlci) { log(LOG_WARNING, "nglmi: Async with > 1 DLCI\n"); goto reject; } #endif /* Annex D says these will always be Ascending, but * the HP test for G4 says we should accept * duplicates, so for now allow that. ( <= vs. < ) */ #if 0 /* MCI tests want us to accept out of order for AnxD */ if ((!GROUP4(sc)) && (dlci < highest_dlci)) { /* duplicate or mis-ordered dlci */ /* (spec says they will increase in number) */ log(LOG_WARNING, "nglmi: DLCI out of order\n"); goto reject; } #endif if (dlci > 1023) { log(LOG_WARNING, "nglmi: DLCI out of range\n"); goto reject; } highest_dlci = dlci; break; default: log(LOG_WARNING, "nglmi: unknown LMI segment type %d\n", segtype); } nextIE: STEPBY(segsize + 2); } if (packetlen != 0) { /* partial junk at end? */ log(LOG_WARNING, "nglmi: %d bytes extra at end of packet\n", packetlen); goto print; } if (resptype_seen == 0) { log(LOG_WARNING, "nglmi: No response type seen\n"); goto reject; /* had no response type */ } if (seq_seen == 0) { log(LOG_WARNING, "nglmi: No sequence numbers seen\n"); goto reject; /* had no sequence numbers */ } return (1); print: { int i, j, k, pos; char buf[100]; int loc; const u_char *bp = mtod(m, const u_char *); k = i = 0; loc = (m->m_len - packetlen); log(LOG_WARNING, "nglmi: error at location %d\n", loc); while (k < m->m_len) { pos = 0; j = 0; while ((j++ < 16) && k < m->m_len) { pos += sprintf(buf + pos, "%c%02x", ((loc == k) ? '>' : ' '), bp[k]); k++; } if (i == 0) log(LOG_WARNING, "nglmi: packet data:%s\n", buf); else log(LOG_WARNING, "%04d :%s\n", k, buf); i++; } } return (1); reject: { int i, j, k, pos; char buf[100]; int loc; const u_char *bp = mtod(m, const u_char *); k = i = 0; loc = (m->m_len - packetlen); log(LOG_WARNING, "nglmi: error at location %d\n", loc); while (k < m->m_len) { pos = 0; j = 0; while ((j++ < 16) && k < m->m_len) { pos += sprintf(buf + pos, "%c%02x", ((loc == k) ? '>' : ' '), bp[k]); k++; } if (i == 0) log(LOG_WARNING, "nglmi: packet data:%s\n", buf); else log(LOG_WARNING, "%04d :%s\n", k, buf); i++; } } NG_FREE_M(m); return (0); } /* * Do local shutdown processing.. * Cut any remaining links and free our local resources. */ static int nglmi_shutdown(node_p node) { const sc_p sc = NG_NODE_PRIVATE(node); NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(sc->node); free(sc, M_NETGRAPH); return (0); } /* * Hook disconnection * For this type, removal of any link except "debug" destroys the node. */ static int nglmi_disconnect(hook_p hook) { const sc_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); /* OK to remove debug hook(s) */ if (NG_HOOK_PRIVATE(hook) == NULL) return (0); /* Stop timer if it's currently active */ if (sc->flags & SCF_CONNECTED) ng_uncallout(&sc->handle, sc->node); /* Self-destruct */ if (NG_NODE_IS_VALID(NG_HOOK_NODE(hook))) ng_rmnode_self(NG_HOOK_NODE(hook)); return (0); } Index: head/sys/netgraph/ng_one2many.c =================================================================== --- head/sys/netgraph/ng_one2many.c (revision 298812) +++ head/sys/netgraph/ng_one2many.c (revision 298813) @@ -1,612 +1,612 @@ /* * ng_one2many.c */ /*- * Copyright (c) 2000 Whistle Communications, Inc. * All rights reserved. * * Subject to the following obligations and disclaimer of warranty, use and * redistribution of this software, in source or object code forms, with or * without modifications are expressly permitted by Whistle Communications; * provided, however, that: * 1. Any and all reproductions of the source or object code must include the * copyright notice above and the following disclaimer of warranties; and * 2. No rights are granted, in any manner or form, to use Whistle * Communications, Inc. trademarks, including the mark "WHISTLE * COMMUNICATIONS" on advertising, endorsements, or otherwise except as * such appears in the above copyright notice or in the software. * * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * Author: Archie Cobbs * * $FreeBSD$ */ /* * ng_one2many(4) netgraph node type * * Packets received on the "one" hook are sent out each of the - * "many" hooks accoring to an algorithm. Packets received on any + * "many" hooks according to an algorithm. Packets received on any * "many" hook are always delivered to the "one" hook. */ #include #include #include #include #include #include #include #include #include #include #include /* Per-link private data */ struct ng_one2many_link { hook_p hook; /* netgraph hook */ struct ng_one2many_link_stats stats; /* link stats */ }; /* Per-node private data */ struct ng_one2many_private { node_p node; /* link to node */ struct ng_one2many_config conf; /* node configuration */ struct ng_one2many_link one; /* "one" hook */ struct ng_one2many_link many[NG_ONE2MANY_MAX_LINKS]; u_int16_t nextMany; /* next round-robin */ u_int16_t numActiveMany; /* # active "many" */ u_int16_t activeMany[NG_ONE2MANY_MAX_LINKS]; }; typedef struct ng_one2many_private *priv_p; /* Netgraph node methods */ static ng_constructor_t ng_one2many_constructor; static ng_rcvmsg_t ng_one2many_rcvmsg; static ng_shutdown_t ng_one2many_shutdown; static ng_newhook_t ng_one2many_newhook; static ng_rcvdata_t ng_one2many_rcvdata; static ng_disconnect_t ng_one2many_disconnect; /* Other functions */ static void ng_one2many_update_many(priv_p priv); static void ng_one2many_notify(priv_p priv, uint32_t cmd); /****************************************************************** NETGRAPH PARSE TYPES ******************************************************************/ /* Parse type for struct ng_one2many_config */ static const struct ng_parse_fixedarray_info ng_one2many_enableLinks_array_type_info = { &ng_parse_uint8_type, NG_ONE2MANY_MAX_LINKS }; static const struct ng_parse_type ng_one2many_enableLinks_array_type = { &ng_parse_fixedarray_type, &ng_one2many_enableLinks_array_type_info, }; static const struct ng_parse_struct_field ng_one2many_config_type_fields[] = NG_ONE2MANY_CONFIG_TYPE_INFO(&ng_one2many_enableLinks_array_type); static const struct ng_parse_type ng_one2many_config_type = { &ng_parse_struct_type, &ng_one2many_config_type_fields }; /* Parse type for struct ng_one2many_link_stats */ static const struct ng_parse_struct_field ng_one2many_link_stats_type_fields[] = NG_ONE2MANY_LINK_STATS_TYPE_INFO; static const struct ng_parse_type ng_one2many_link_stats_type = { &ng_parse_struct_type, &ng_one2many_link_stats_type_fields }; /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_one2many_cmdlist[] = { { NGM_ONE2MANY_COOKIE, NGM_ONE2MANY_SET_CONFIG, "setconfig", &ng_one2many_config_type, NULL }, { NGM_ONE2MANY_COOKIE, NGM_ONE2MANY_GET_CONFIG, "getconfig", NULL, &ng_one2many_config_type }, { NGM_ONE2MANY_COOKIE, NGM_ONE2MANY_GET_STATS, "getstats", &ng_parse_int32_type, &ng_one2many_link_stats_type }, { NGM_ONE2MANY_COOKIE, NGM_ONE2MANY_CLR_STATS, "clrstats", &ng_parse_int32_type, NULL, }, { NGM_ONE2MANY_COOKIE, NGM_ONE2MANY_GETCLR_STATS, "getclrstats", &ng_parse_int32_type, &ng_one2many_link_stats_type }, { 0 } }; /* Node type descriptor */ static struct ng_type ng_one2many_typestruct = { .version = NG_ABI_VERSION, .name = NG_ONE2MANY_NODE_TYPE, .constructor = ng_one2many_constructor, .rcvmsg = ng_one2many_rcvmsg, .shutdown = ng_one2many_shutdown, .newhook = ng_one2many_newhook, .rcvdata = ng_one2many_rcvdata, .disconnect = ng_one2many_disconnect, .cmdlist = ng_one2many_cmdlist, }; NETGRAPH_INIT(one2many, &ng_one2many_typestruct); /****************************************************************** NETGRAPH NODE METHODS ******************************************************************/ /* * Node constructor */ static int ng_one2many_constructor(node_p node) { priv_p priv; /* Allocate and initialize private info */ priv = malloc(sizeof(*priv), M_NETGRAPH, M_WAITOK | M_ZERO); priv->conf.xmitAlg = NG_ONE2MANY_XMIT_ROUNDROBIN; priv->conf.failAlg = NG_ONE2MANY_FAIL_MANUAL; /* cross reference */ NG_NODE_SET_PRIVATE(node, priv); priv->node = node; /* Done */ return (0); } /* * Method for attaching a new hook */ static int ng_one2many_newhook(node_p node, hook_p hook, const char *name) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_one2many_link *link; int linkNum; u_long i; /* Which hook? */ if (strncmp(name, NG_ONE2MANY_HOOK_MANY_PREFIX, strlen(NG_ONE2MANY_HOOK_MANY_PREFIX)) == 0) { const char *cp; char *eptr; cp = name + strlen(NG_ONE2MANY_HOOK_MANY_PREFIX); if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0')) return (EINVAL); i = strtoul(cp, &eptr, 10); if (*eptr != '\0' || i >= NG_ONE2MANY_MAX_LINKS) return (EINVAL); linkNum = (int)i; link = &priv->many[linkNum]; } else if (strcmp(name, NG_ONE2MANY_HOOK_ONE) == 0) { linkNum = NG_ONE2MANY_ONE_LINKNUM; link = &priv->one; } else return (EINVAL); /* Is hook already connected? (should never happen) */ if (link->hook != NULL) return (EISCONN); /* Setup private info for this link */ NG_HOOK_SET_PRIVATE(hook, (void *)(intptr_t)linkNum); link->hook = hook; bzero(&link->stats, sizeof(link->stats)); if (linkNum != NG_ONE2MANY_ONE_LINKNUM) { priv->conf.enabledLinks[linkNum] = 1; /* auto-enable link */ ng_one2many_update_many(priv); } /* Done */ return (0); } /* * Receive a control message */ static int ng_one2many_rcvmsg(node_p node, item_p item, hook_p lasthook) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_mesg *resp = NULL; int error = 0; struct ng_mesg *msg; NGI_GET_MSG(item, msg); switch (msg->header.typecookie) { case NGM_ONE2MANY_COOKIE: switch (msg->header.cmd) { case NGM_ONE2MANY_SET_CONFIG: { struct ng_one2many_config *conf; int i; /* Check that new configuration is valid */ if (msg->header.arglen != sizeof(*conf)) { error = EINVAL; break; } conf = (struct ng_one2many_config *)msg->data; switch (conf->xmitAlg) { case NG_ONE2MANY_XMIT_ROUNDROBIN: case NG_ONE2MANY_XMIT_ALL: case NG_ONE2MANY_XMIT_FAILOVER: break; default: error = EINVAL; break; } switch (conf->failAlg) { case NG_ONE2MANY_FAIL_MANUAL: case NG_ONE2MANY_FAIL_NOTIFY: break; default: error = EINVAL; break; } if (error != 0) break; /* Normalized many link enabled bits */ for (i = 0; i < NG_ONE2MANY_MAX_LINKS; i++) conf->enabledLinks[i] = !!conf->enabledLinks[i]; /* Copy config and reset */ bcopy(conf, &priv->conf, sizeof(*conf)); ng_one2many_update_many(priv); break; } case NGM_ONE2MANY_GET_CONFIG: { struct ng_one2many_config *conf; NG_MKRESPONSE(resp, msg, sizeof(*conf), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } conf = (struct ng_one2many_config *)resp->data; bcopy(&priv->conf, conf, sizeof(priv->conf)); break; } case NGM_ONE2MANY_GET_STATS: case NGM_ONE2MANY_CLR_STATS: case NGM_ONE2MANY_GETCLR_STATS: { struct ng_one2many_link *link; int linkNum; /* Get link */ if (msg->header.arglen != sizeof(int32_t)) { error = EINVAL; break; } linkNum = *((int32_t *)msg->data); if (linkNum == NG_ONE2MANY_ONE_LINKNUM) link = &priv->one; else if (linkNum >= 0 && linkNum < NG_ONE2MANY_MAX_LINKS) { link = &priv->many[linkNum]; } else { error = EINVAL; break; } /* Get/clear stats */ if (msg->header.cmd != NGM_ONE2MANY_CLR_STATS) { NG_MKRESPONSE(resp, msg, sizeof(link->stats), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } bcopy(&link->stats, resp->data, sizeof(link->stats)); } if (msg->header.cmd != NGM_ONE2MANY_GET_STATS) bzero(&link->stats, sizeof(link->stats)); break; } default: error = EINVAL; break; } break; /* * One of our downstreams notifies us of link change. If we are * configured to listen to these message, then we remove/add * this hook from array of active hooks. */ case NGM_FLOW_COOKIE: { int linkNum; if (priv->conf.failAlg != NG_ONE2MANY_FAIL_NOTIFY) break; if (lasthook == NULL) break; linkNum = (intptr_t)NG_HOOK_PRIVATE(lasthook); if (linkNum == NG_ONE2MANY_ONE_LINKNUM) break; KASSERT((linkNum >= 0 && linkNum < NG_ONE2MANY_MAX_LINKS), ("%s: linkNum=%d", __func__, linkNum)); switch (msg->header.cmd) { case NGM_LINK_IS_UP: priv->conf.enabledLinks[linkNum] = 1; ng_one2many_update_many(priv); break; case NGM_LINK_IS_DOWN: priv->conf.enabledLinks[linkNum] = 0; ng_one2many_update_many(priv); break; default: break; } break; } default: error = EINVAL; break; } /* Done */ NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } /* * Receive data on a hook */ static int ng_one2many_rcvdata(hook_p hook, item_p item) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); struct ng_one2many_link *src; struct ng_one2many_link *dst = NULL; int error = 0; int linkNum; int i; struct mbuf *m; m = NGI_M(item); /* just peaking, mbuf still owned by item */ /* Get link number */ linkNum = (intptr_t)NG_HOOK_PRIVATE(hook); KASSERT(linkNum == NG_ONE2MANY_ONE_LINKNUM || (linkNum >= 0 && linkNum < NG_ONE2MANY_MAX_LINKS), ("%s: linkNum=%d", __func__, linkNum)); /* Figure out source link */ src = (linkNum == NG_ONE2MANY_ONE_LINKNUM) ? &priv->one : &priv->many[linkNum]; KASSERT(src->hook != NULL, ("%s: no src%d", __func__, linkNum)); /* Update receive stats */ src->stats.recvPackets++; src->stats.recvOctets += m->m_pkthdr.len; /* Figure out destination link */ if (linkNum == NG_ONE2MANY_ONE_LINKNUM) { if (priv->numActiveMany == 0) { NG_FREE_ITEM(item); return (ENOTCONN); } switch(priv->conf.xmitAlg) { case NG_ONE2MANY_XMIT_ROUNDROBIN: dst = &priv->many[priv->activeMany[priv->nextMany]]; priv->nextMany = (priv->nextMany + 1) % priv->numActiveMany; break; case NG_ONE2MANY_XMIT_ALL: /* no need to copy data for the 1st one */ dst = &priv->many[priv->activeMany[0]]; /* make copies of data and send for all links * except the first one, which we'll do last */ for (i = 1; i < priv->numActiveMany; i++) { struct mbuf *m2; struct ng_one2many_link *mdst; mdst = &priv->many[priv->activeMany[i]]; m2 = m_dup(m, M_NOWAIT); /* XXX m_copypacket() */ if (m2 == NULL) { mdst->stats.memoryFailures++; NG_FREE_ITEM(item); NG_FREE_M(m); return (ENOBUFS); } /* Update transmit stats */ mdst->stats.xmitPackets++; mdst->stats.xmitOctets += m->m_pkthdr.len; NG_SEND_DATA_ONLY(error, mdst->hook, m2); } break; case NG_ONE2MANY_XMIT_FAILOVER: dst = &priv->many[priv->activeMany[0]]; break; #ifdef INVARIANTS default: panic("%s: invalid xmitAlg", __func__); #endif } } else { dst = &priv->one; } /* Update transmit stats */ dst->stats.xmitPackets++; dst->stats.xmitOctets += m->m_pkthdr.len; /* Deliver packet */ NG_FWD_ITEM_HOOK(error, item, dst->hook); return (error); } /* * Shutdown node */ static int ng_one2many_shutdown(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); KASSERT(priv->numActiveMany == 0, ("%s: numActiveMany=%d", __func__, priv->numActiveMany)); free(priv, M_NETGRAPH); NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(node); return (0); } /* * Hook disconnection. */ static int ng_one2many_disconnect(hook_p hook) { const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); int linkNum; /* Get link number */ linkNum = (intptr_t)NG_HOOK_PRIVATE(hook); KASSERT(linkNum == NG_ONE2MANY_ONE_LINKNUM || (linkNum >= 0 && linkNum < NG_ONE2MANY_MAX_LINKS), ("%s: linkNum=%d", __func__, linkNum)); /* Nuke the link */ if (linkNum == NG_ONE2MANY_ONE_LINKNUM) priv->one.hook = NULL; else { priv->many[linkNum].hook = NULL; priv->conf.enabledLinks[linkNum] = 0; ng_one2many_update_many(priv); } /* If no hooks left, go away */ if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) ng_rmnode_self(NG_HOOK_NODE(hook)); return (0); } /****************************************************************** OTHER FUNCTIONS ******************************************************************/ /* * Update internal state after the addition or removal of a "many" link */ static void ng_one2many_update_many(priv_p priv) { uint16_t saveActive = priv->numActiveMany; int linkNum; /* Update list of which "many" links are up */ priv->numActiveMany = 0; for (linkNum = 0; linkNum < NG_ONE2MANY_MAX_LINKS; linkNum++) { switch (priv->conf.failAlg) { case NG_ONE2MANY_FAIL_MANUAL: case NG_ONE2MANY_FAIL_NOTIFY: if (priv->many[linkNum].hook != NULL && priv->conf.enabledLinks[linkNum]) { priv->activeMany[priv->numActiveMany] = linkNum; priv->numActiveMany++; } break; #ifdef INVARIANTS default: panic("%s: invalid failAlg", __func__); #endif } } if (priv->numActiveMany == 0 && saveActive > 0) ng_one2many_notify(priv, NGM_LINK_IS_DOWN); if (saveActive == 0 && priv->numActiveMany > 0) ng_one2many_notify(priv, NGM_LINK_IS_UP); /* Update transmit algorithm state */ switch (priv->conf.xmitAlg) { case NG_ONE2MANY_XMIT_ROUNDROBIN: if (priv->numActiveMany > 0) priv->nextMany %= priv->numActiveMany; break; case NG_ONE2MANY_XMIT_ALL: case NG_ONE2MANY_XMIT_FAILOVER: break; #ifdef INVARIANTS default: panic("%s: invalid xmitAlg", __func__); #endif } } /* * Notify upstream if we are out of links, or we have at least one link. */ static void ng_one2many_notify(priv_p priv, uint32_t cmd) { struct ng_mesg *msg; int dummy_error = 0; if (priv->one.hook == NULL) return; NG_MKMESSAGE(msg, NGM_FLOW_COOKIE, cmd, 0, M_NOWAIT); if (msg != NULL) NG_SEND_MSG_HOOK(dummy_error, priv->node, msg, priv->one.hook, 0); } Index: head/sys/netgraph/ng_ppp.c =================================================================== --- head/sys/netgraph/ng_ppp.c (revision 298812) +++ head/sys/netgraph/ng_ppp.c (revision 298813) @@ -1,2645 +1,2645 @@ /*- * Copyright (c) 1996-2000 Whistle Communications, Inc. * All rights reserved. * * Subject to the following obligations and disclaimer of warranty, use and * redistribution of this software, in source or object code forms, with or * without modifications are expressly permitted by Whistle Communications; * provided, however, that: * 1. Any and all reproductions of the source or object code must include the * copyright notice above and the following disclaimer of warranties; and * 2. No rights are granted, in any manner or form, to use Whistle * Communications, Inc. trademarks, including the mark "WHISTLE * COMMUNICATIONS" on advertising, endorsements, or otherwise except as * such appears in the above copyright notice or in the software. * * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * Copyright (c) 2007 Alexander Motin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice unmodified, this list of conditions, and the following * disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * Authors: Archie Cobbs , Alexander Motin * * $FreeBSD$ * $Whistle: ng_ppp.c,v 1.24 1999/11/01 09:24:52 julian Exp $ */ /* * PPP node type data-flow. * * hook xmit layer recv hook * ------------------------------------ * inet -> -> inet * ipv6 -> -> ipv6 * ipx -> proto -> ipx * atalk -> -> atalk * bypass -> -> bypass * -hcomp_xmit()----------proto_recv()- * vjc_ip <- <- vjc_ip * vjc_comp -> header compression -> vjc_comp * vjc_uncomp -> -> vjc_uncomp * vjc_vjip -> * -comp_xmit()-----------hcomp_recv()- * compress <- compression <- decompress * compress -> -> decompress * -crypt_xmit()-----------comp_recv()- * encrypt <- encryption <- decrypt * encrypt -> -> decrypt * -ml_xmit()-------------crypt_recv()- * multilink * -link_xmit()--------------ml_recv()- * linkX <- link <- linkX * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef NG_SEPARATE_MALLOC static MALLOC_DEFINE(M_NETGRAPH_PPP, "netgraph_ppp", "netgraph ppp node"); #else #define M_NETGRAPH_PPP M_NETGRAPH #endif #define PROT_VALID(p) (((p) & 0x0101) == 0x0001) #define PROT_COMPRESSABLE(p) (((p) & 0xff00) == 0x0000) /* Some PPP protocol numbers we're interested in */ #define PROT_ATALK 0x0029 #define PROT_COMPD 0x00fd #define PROT_CRYPTD 0x0053 #define PROT_IP 0x0021 #define PROT_IPV6 0x0057 #define PROT_IPX 0x002b #define PROT_LCP 0xc021 #define PROT_MP 0x003d #define PROT_VJCOMP 0x002d #define PROT_VJUNCOMP 0x002f /* Multilink PPP definitions */ #define MP_INITIAL_SEQ 0 /* per RFC 1990 */ #define MP_MIN_LINK_MRU 32 #define MP_SHORT_SEQ_MASK 0x00000fff /* short seq # mask */ #define MP_SHORT_SEQ_HIBIT 0x00000800 /* short seq # high bit */ #define MP_SHORT_FIRST_FLAG 0x00008000 /* first fragment in frame */ #define MP_SHORT_LAST_FLAG 0x00004000 /* last fragment in frame */ #define MP_LONG_SEQ_MASK 0x00ffffff /* long seq # mask */ #define MP_LONG_SEQ_HIBIT 0x00800000 /* long seq # high bit */ #define MP_LONG_FIRST_FLAG 0x80000000 /* first fragment in frame */ #define MP_LONG_LAST_FLAG 0x40000000 /* last fragment in frame */ #define MP_NOSEQ 0x7fffffff /* impossible sequence number */ /* Sign extension of MP sequence numbers */ #define MP_SHORT_EXTEND(s) (((s) & MP_SHORT_SEQ_HIBIT) ? \ ((s) | ~MP_SHORT_SEQ_MASK) \ : ((s) & MP_SHORT_SEQ_MASK)) #define MP_LONG_EXTEND(s) (((s) & MP_LONG_SEQ_HIBIT) ? \ ((s) | ~MP_LONG_SEQ_MASK) \ : ((s) & MP_LONG_SEQ_MASK)) -/* Comparision of MP sequence numbers. Note: all sequence numbers +/* Comparison of MP sequence numbers. Note: all sequence numbers except priv->xseq are stored with the sign bit extended. */ #define MP_SHORT_SEQ_DIFF(x,y) MP_SHORT_EXTEND((x) - (y)) #define MP_LONG_SEQ_DIFF(x,y) MP_LONG_EXTEND((x) - (y)) #define MP_RECV_SEQ_DIFF(priv,x,y) \ ((priv)->conf.recvShortSeq ? \ MP_SHORT_SEQ_DIFF((x), (y)) : \ MP_LONG_SEQ_DIFF((x), (y))) /* Increment receive sequence number */ #define MP_NEXT_RECV_SEQ(priv,seq) \ ((priv)->conf.recvShortSeq ? \ MP_SHORT_EXTEND((seq) + 1) : \ MP_LONG_EXTEND((seq) + 1)) /* Don't fragment transmitted packets to parts smaller than this */ #define MP_MIN_FRAG_LEN 32 /* Maximum fragment reasssembly queue length */ #define MP_MAX_QUEUE_LEN 128 /* Fragment queue scanner period */ #define MP_FRAGTIMER_INTERVAL (hz/2) /* Average link overhead. XXX: Should be given by user-level */ #define MP_AVERAGE_LINK_OVERHEAD 16 /* Keep this equal to ng_ppp_hook_names lower! */ #define HOOK_INDEX_MAX 13 /* We store incoming fragments this way */ struct ng_ppp_frag { int seq; /* fragment seq# */ uint8_t first; /* First in packet? */ uint8_t last; /* Last in packet? */ struct timeval timestamp; /* time of reception */ struct mbuf *data; /* Fragment data */ TAILQ_ENTRY(ng_ppp_frag) f_qent; /* Fragment queue */ }; /* Per-link private information */ struct ng_ppp_link { struct ng_ppp_link_conf conf; /* link configuration */ struct ng_ppp_link_stat64 stats; /* link stats */ hook_p hook; /* connection to link data */ int32_t seq; /* highest rec'd seq# - MSEQ */ uint32_t latency; /* calculated link latency */ struct timeval lastWrite; /* time of last write for MP */ int bytesInQueue; /* bytes in the output queue for MP */ }; /* Total per-node private information */ struct ng_ppp_private { struct ng_ppp_bund_conf conf; /* bundle config */ struct ng_ppp_link_stat64 bundleStats; /* bundle stats */ struct ng_ppp_link links[NG_PPP_MAX_LINKS];/* per-link info */ int32_t xseq; /* next out MP seq # */ int32_t mseq; /* min links[i].seq */ - uint16_t activeLinks[NG_PPP_MAX_LINKS]; /* indicies */ + uint16_t activeLinks[NG_PPP_MAX_LINKS]; /* indices */ uint16_t numActiveLinks; /* how many links up */ uint16_t lastLink; /* for round robin */ uint8_t vjCompHooked; /* VJ comp hooked up? */ uint8_t allLinksEqual; /* all xmit the same? */ hook_p hooks[HOOK_INDEX_MAX]; /* non-link hooks */ struct ng_ppp_frag fragsmem[MP_MAX_QUEUE_LEN]; /* fragments storage */ TAILQ_HEAD(ng_ppp_fraglist, ng_ppp_frag) /* fragment queue */ frags; TAILQ_HEAD(ng_ppp_fragfreelist, ng_ppp_frag) /* free fragment queue */ fragsfree; struct callout fragTimer; /* fraq queue check */ struct mtx rmtx; /* recv mutex */ struct mtx xmtx; /* xmit mutex */ }; typedef struct ng_ppp_private *priv_p; /* Netgraph node methods */ static ng_constructor_t ng_ppp_constructor; static ng_rcvmsg_t ng_ppp_rcvmsg; static ng_shutdown_t ng_ppp_shutdown; static ng_newhook_t ng_ppp_newhook; static ng_rcvdata_t ng_ppp_rcvdata; static ng_disconnect_t ng_ppp_disconnect; static ng_rcvdata_t ng_ppp_rcvdata_inet; static ng_rcvdata_t ng_ppp_rcvdata_inet_fast; static ng_rcvdata_t ng_ppp_rcvdata_ipv6; static ng_rcvdata_t ng_ppp_rcvdata_ipx; static ng_rcvdata_t ng_ppp_rcvdata_atalk; static ng_rcvdata_t ng_ppp_rcvdata_bypass; static ng_rcvdata_t ng_ppp_rcvdata_vjc_ip; static ng_rcvdata_t ng_ppp_rcvdata_vjc_comp; static ng_rcvdata_t ng_ppp_rcvdata_vjc_uncomp; static ng_rcvdata_t ng_ppp_rcvdata_vjc_vjip; static ng_rcvdata_t ng_ppp_rcvdata_compress; static ng_rcvdata_t ng_ppp_rcvdata_decompress; static ng_rcvdata_t ng_ppp_rcvdata_encrypt; static ng_rcvdata_t ng_ppp_rcvdata_decrypt; -/* We use integer indicies to refer to the non-link hooks. */ +/* We use integer indices to refer to the non-link hooks. */ static const struct { char *const name; ng_rcvdata_t *fn; } ng_ppp_hook_names[] = { #define HOOK_INDEX_ATALK 0 { NG_PPP_HOOK_ATALK, ng_ppp_rcvdata_atalk }, #define HOOK_INDEX_BYPASS 1 { NG_PPP_HOOK_BYPASS, ng_ppp_rcvdata_bypass }, #define HOOK_INDEX_COMPRESS 2 { NG_PPP_HOOK_COMPRESS, ng_ppp_rcvdata_compress }, #define HOOK_INDEX_ENCRYPT 3 { NG_PPP_HOOK_ENCRYPT, ng_ppp_rcvdata_encrypt }, #define HOOK_INDEX_DECOMPRESS 4 { NG_PPP_HOOK_DECOMPRESS, ng_ppp_rcvdata_decompress }, #define HOOK_INDEX_DECRYPT 5 { NG_PPP_HOOK_DECRYPT, ng_ppp_rcvdata_decrypt }, #define HOOK_INDEX_INET 6 { NG_PPP_HOOK_INET, ng_ppp_rcvdata_inet }, #define HOOK_INDEX_IPX 7 { NG_PPP_HOOK_IPX, ng_ppp_rcvdata_ipx }, #define HOOK_INDEX_VJC_COMP 8 { NG_PPP_HOOK_VJC_COMP, ng_ppp_rcvdata_vjc_comp }, #define HOOK_INDEX_VJC_IP 9 { NG_PPP_HOOK_VJC_IP, ng_ppp_rcvdata_vjc_ip }, #define HOOK_INDEX_VJC_UNCOMP 10 { NG_PPP_HOOK_VJC_UNCOMP, ng_ppp_rcvdata_vjc_uncomp }, #define HOOK_INDEX_VJC_VJIP 11 { NG_PPP_HOOK_VJC_VJIP, ng_ppp_rcvdata_vjc_vjip }, #define HOOK_INDEX_IPV6 12 { NG_PPP_HOOK_IPV6, ng_ppp_rcvdata_ipv6 }, { NULL, NULL } }; /* Helper functions */ static int ng_ppp_proto_recv(node_p node, item_p item, uint16_t proto, uint16_t linkNum); static int ng_ppp_hcomp_xmit(node_p node, item_p item, uint16_t proto); static int ng_ppp_hcomp_recv(node_p node, item_p item, uint16_t proto, uint16_t linkNum); static int ng_ppp_comp_xmit(node_p node, item_p item, uint16_t proto); static int ng_ppp_comp_recv(node_p node, item_p item, uint16_t proto, uint16_t linkNum); static int ng_ppp_crypt_xmit(node_p node, item_p item, uint16_t proto); static int ng_ppp_crypt_recv(node_p node, item_p item, uint16_t proto, uint16_t linkNum); static int ng_ppp_mp_xmit(node_p node, item_p item, uint16_t proto); static int ng_ppp_mp_recv(node_p node, item_p item, uint16_t proto, uint16_t linkNum); static int ng_ppp_link_xmit(node_p node, item_p item, uint16_t proto, uint16_t linkNum, int plen); static int ng_ppp_bypass(node_p node, item_p item, uint16_t proto, uint16_t linkNum); static void ng_ppp_bump_mseq(node_p node, int32_t new_mseq); static int ng_ppp_frag_drop(node_p node); static int ng_ppp_check_packet(node_p node); static void ng_ppp_get_packet(node_p node, struct mbuf **mp); static int ng_ppp_frag_process(node_p node, item_p oitem); static int ng_ppp_frag_trim(node_p node); static void ng_ppp_frag_timeout(node_p node, hook_p hook, void *arg1, int arg2); static void ng_ppp_frag_checkstale(node_p node); static void ng_ppp_frag_reset(node_p node); static void ng_ppp_mp_strategy(node_p node, int len, int *distrib); static int ng_ppp_intcmp(void *latency, const void *v1, const void *v2); static struct mbuf *ng_ppp_addproto(struct mbuf *m, uint16_t proto, int compOK); static struct mbuf *ng_ppp_cutproto(struct mbuf *m, uint16_t *proto); static struct mbuf *ng_ppp_prepend(struct mbuf *m, const void *buf, int len); static int ng_ppp_config_valid(node_p node, const struct ng_ppp_node_conf *newConf); static void ng_ppp_update(node_p node, int newConf); static void ng_ppp_start_frag_timer(node_p node); static void ng_ppp_stop_frag_timer(node_p node); /* Parse type for struct ng_ppp_mp_state_type */ static const struct ng_parse_fixedarray_info ng_ppp_rseq_array_info = { &ng_parse_hint32_type, NG_PPP_MAX_LINKS }; static const struct ng_parse_type ng_ppp_rseq_array_type = { &ng_parse_fixedarray_type, &ng_ppp_rseq_array_info, }; static const struct ng_parse_struct_field ng_ppp_mp_state_type_fields[] = NG_PPP_MP_STATE_TYPE_INFO(&ng_ppp_rseq_array_type); static const struct ng_parse_type ng_ppp_mp_state_type = { &ng_parse_struct_type, &ng_ppp_mp_state_type_fields }; /* Parse type for struct ng_ppp_link_conf */ static const struct ng_parse_struct_field ng_ppp_link_type_fields[] = NG_PPP_LINK_TYPE_INFO; static const struct ng_parse_type ng_ppp_link_type = { &ng_parse_struct_type, &ng_ppp_link_type_fields }; /* Parse type for struct ng_ppp_bund_conf */ static const struct ng_parse_struct_field ng_ppp_bund_type_fields[] = NG_PPP_BUND_TYPE_INFO; static const struct ng_parse_type ng_ppp_bund_type = { &ng_parse_struct_type, &ng_ppp_bund_type_fields }; /* Parse type for struct ng_ppp_node_conf */ static const struct ng_parse_fixedarray_info ng_ppp_array_info = { &ng_ppp_link_type, NG_PPP_MAX_LINKS }; static const struct ng_parse_type ng_ppp_link_array_type = { &ng_parse_fixedarray_type, &ng_ppp_array_info, }; static const struct ng_parse_struct_field ng_ppp_conf_type_fields[] = NG_PPP_CONFIG_TYPE_INFO(&ng_ppp_bund_type, &ng_ppp_link_array_type); static const struct ng_parse_type ng_ppp_conf_type = { &ng_parse_struct_type, &ng_ppp_conf_type_fields }; /* Parse type for struct ng_ppp_link_stat */ static const struct ng_parse_struct_field ng_ppp_stats_type_fields[] = NG_PPP_STATS_TYPE_INFO; static const struct ng_parse_type ng_ppp_stats_type = { &ng_parse_struct_type, &ng_ppp_stats_type_fields }; /* Parse type for struct ng_ppp_link_stat64 */ static const struct ng_parse_struct_field ng_ppp_stats64_type_fields[] = NG_PPP_STATS64_TYPE_INFO; static const struct ng_parse_type ng_ppp_stats64_type = { &ng_parse_struct_type, &ng_ppp_stats64_type_fields }; /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_ppp_cmds[] = { { NGM_PPP_COOKIE, NGM_PPP_SET_CONFIG, "setconfig", &ng_ppp_conf_type, NULL }, { NGM_PPP_COOKIE, NGM_PPP_GET_CONFIG, "getconfig", NULL, &ng_ppp_conf_type }, { NGM_PPP_COOKIE, NGM_PPP_GET_MP_STATE, "getmpstate", NULL, &ng_ppp_mp_state_type }, { NGM_PPP_COOKIE, NGM_PPP_GET_LINK_STATS, "getstats", &ng_parse_int16_type, &ng_ppp_stats_type }, { NGM_PPP_COOKIE, NGM_PPP_CLR_LINK_STATS, "clrstats", &ng_parse_int16_type, NULL }, { NGM_PPP_COOKIE, NGM_PPP_GETCLR_LINK_STATS, "getclrstats", &ng_parse_int16_type, &ng_ppp_stats_type }, { NGM_PPP_COOKIE, NGM_PPP_GET_LINK_STATS64, "getstats64", &ng_parse_int16_type, &ng_ppp_stats64_type }, { NGM_PPP_COOKIE, NGM_PPP_GETCLR_LINK_STATS64, "getclrstats64", &ng_parse_int16_type, &ng_ppp_stats64_type }, { 0 } }; /* Node type descriptor */ static struct ng_type ng_ppp_typestruct = { .version = NG_ABI_VERSION, .name = NG_PPP_NODE_TYPE, .constructor = ng_ppp_constructor, .rcvmsg = ng_ppp_rcvmsg, .shutdown = ng_ppp_shutdown, .newhook = ng_ppp_newhook, .rcvdata = ng_ppp_rcvdata, .disconnect = ng_ppp_disconnect, .cmdlist = ng_ppp_cmds, }; NETGRAPH_INIT(ppp, &ng_ppp_typestruct); /* Address and control field header */ static const uint8_t ng_ppp_acf[2] = { 0xff, 0x03 }; /* Maximum time we'll let a complete incoming packet sit in the queue */ static const struct timeval ng_ppp_max_staleness = { 2, 0 }; /* 2 seconds */ #define ERROUT(x) do { error = (x); goto done; } while (0) /************************************************************************ NETGRAPH NODE STUFF ************************************************************************/ /* * Node type constructor */ static int ng_ppp_constructor(node_p node) { priv_p priv; int i; /* Allocate private structure */ priv = malloc(sizeof(*priv), M_NETGRAPH_PPP, M_WAITOK | M_ZERO); NG_NODE_SET_PRIVATE(node, priv); /* Initialize state */ TAILQ_INIT(&priv->frags); TAILQ_INIT(&priv->fragsfree); for (i = 0; i < MP_MAX_QUEUE_LEN; i++) TAILQ_INSERT_TAIL(&priv->fragsfree, &priv->fragsmem[i], f_qent); for (i = 0; i < NG_PPP_MAX_LINKS; i++) priv->links[i].seq = MP_NOSEQ; ng_callout_init(&priv->fragTimer); mtx_init(&priv->rmtx, "ng_ppp_recv", NULL, MTX_DEF); mtx_init(&priv->xmtx, "ng_ppp_xmit", NULL, MTX_DEF); /* Done */ return (0); } /* * Give our OK for a hook to be added */ static int ng_ppp_newhook(node_p node, hook_p hook, const char *name) { const priv_p priv = NG_NODE_PRIVATE(node); hook_p *hookPtr = NULL; int linkNum = -1; int hookIndex = -1; /* Figure out which hook it is */ if (strncmp(name, NG_PPP_HOOK_LINK_PREFIX, /* a link hook? */ strlen(NG_PPP_HOOK_LINK_PREFIX)) == 0) { const char *cp; char *eptr; cp = name + strlen(NG_PPP_HOOK_LINK_PREFIX); if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0')) return (EINVAL); linkNum = (int)strtoul(cp, &eptr, 10); if (*eptr != '\0' || linkNum < 0 || linkNum >= NG_PPP_MAX_LINKS) return (EINVAL); hookPtr = &priv->links[linkNum].hook; hookIndex = ~linkNum; /* See if hook is already connected. */ if (*hookPtr != NULL) return (EISCONN); /* Disallow more than one link unless multilink is enabled. */ if (priv->links[linkNum].conf.enableLink && !priv->conf.enableMultilink && priv->numActiveLinks >= 1) return (ENODEV); } else { /* must be a non-link hook */ int i; for (i = 0; ng_ppp_hook_names[i].name != NULL; i++) { if (strcmp(name, ng_ppp_hook_names[i].name) == 0) { hookPtr = &priv->hooks[i]; hookIndex = i; break; } } if (ng_ppp_hook_names[i].name == NULL) return (EINVAL); /* no such hook */ /* See if hook is already connected */ if (*hookPtr != NULL) return (EISCONN); /* Every non-linkX hook have it's own function. */ NG_HOOK_SET_RCVDATA(hook, ng_ppp_hook_names[i].fn); } /* OK */ *hookPtr = hook; NG_HOOK_SET_PRIVATE(hook, (void *)(intptr_t)hookIndex); ng_ppp_update(node, 0); return (0); } /* * Receive a control message */ static int ng_ppp_rcvmsg(node_p node, item_p item, hook_p lasthook) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_mesg *resp = NULL; int error = 0; struct ng_mesg *msg; NGI_GET_MSG(item, msg); switch (msg->header.typecookie) { case NGM_PPP_COOKIE: switch (msg->header.cmd) { case NGM_PPP_SET_CONFIG: { struct ng_ppp_node_conf *const conf = (struct ng_ppp_node_conf *)msg->data; int i; /* Check for invalid or illegal config */ if (msg->header.arglen != sizeof(*conf)) ERROUT(EINVAL); if (!ng_ppp_config_valid(node, conf)) ERROUT(EINVAL); /* Copy config */ priv->conf = conf->bund; for (i = 0; i < NG_PPP_MAX_LINKS; i++) priv->links[i].conf = conf->links[i]; ng_ppp_update(node, 1); break; } case NGM_PPP_GET_CONFIG: { struct ng_ppp_node_conf *conf; int i; NG_MKRESPONSE(resp, msg, sizeof(*conf), M_NOWAIT); if (resp == NULL) ERROUT(ENOMEM); conf = (struct ng_ppp_node_conf *)resp->data; conf->bund = priv->conf; for (i = 0; i < NG_PPP_MAX_LINKS; i++) conf->links[i] = priv->links[i].conf; break; } case NGM_PPP_GET_MP_STATE: { struct ng_ppp_mp_state *info; int i; NG_MKRESPONSE(resp, msg, sizeof(*info), M_NOWAIT); if (resp == NULL) ERROUT(ENOMEM); info = (struct ng_ppp_mp_state *)resp->data; bzero(info, sizeof(*info)); for (i = 0; i < NG_PPP_MAX_LINKS; i++) { if (priv->links[i].seq != MP_NOSEQ) info->rseq[i] = priv->links[i].seq; } info->mseq = priv->mseq; info->xseq = priv->xseq; break; } case NGM_PPP_GET_LINK_STATS: case NGM_PPP_CLR_LINK_STATS: case NGM_PPP_GETCLR_LINK_STATS: case NGM_PPP_GET_LINK_STATS64: case NGM_PPP_GETCLR_LINK_STATS64: { struct ng_ppp_link_stat64 *stats; uint16_t linkNum; /* Process request. */ if (msg->header.arglen != sizeof(uint16_t)) ERROUT(EINVAL); linkNum = *((uint16_t *) msg->data); if (linkNum >= NG_PPP_MAX_LINKS && linkNum != NG_PPP_BUNDLE_LINKNUM) ERROUT(EINVAL); stats = (linkNum == NG_PPP_BUNDLE_LINKNUM) ? &priv->bundleStats : &priv->links[linkNum].stats; /* Make 64bit reply. */ if (msg->header.cmd == NGM_PPP_GET_LINK_STATS64 || msg->header.cmd == NGM_PPP_GETCLR_LINK_STATS64) { NG_MKRESPONSE(resp, msg, sizeof(struct ng_ppp_link_stat64), M_NOWAIT); if (resp == NULL) ERROUT(ENOMEM); bcopy(stats, resp->data, sizeof(*stats)); } else /* Make 32bit reply. */ if (msg->header.cmd == NGM_PPP_GET_LINK_STATS || msg->header.cmd == NGM_PPP_GETCLR_LINK_STATS) { struct ng_ppp_link_stat *rs; NG_MKRESPONSE(resp, msg, sizeof(struct ng_ppp_link_stat), M_NOWAIT); if (resp == NULL) ERROUT(ENOMEM); rs = (struct ng_ppp_link_stat *)resp->data; /* Truncate 64->32 bits. */ rs->xmitFrames = stats->xmitFrames; rs->xmitOctets = stats->xmitOctets; rs->recvFrames = stats->recvFrames; rs->recvOctets = stats->recvOctets; rs->badProtos = stats->badProtos; rs->runts = stats->runts; rs->dupFragments = stats->dupFragments; rs->dropFragments = stats->dropFragments; } /* Clear stats. */ if (msg->header.cmd != NGM_PPP_GET_LINK_STATS && msg->header.cmd != NGM_PPP_GET_LINK_STATS64) bzero(stats, sizeof(*stats)); break; } default: error = EINVAL; break; } break; case NGM_VJC_COOKIE: { /* * Forward it to the vjc node. leave the * old return address alone. * If we have no hook, let NG_RESPOND_MSG * clean up any remaining resources. * Because we have no resp, the item will be freed * along with anything it references. Don't * let msg be freed twice. */ NGI_MSG(item) = msg; /* put it back in the item */ msg = NULL; if ((lasthook = priv->hooks[HOOK_INDEX_VJC_IP])) { NG_FWD_ITEM_HOOK(error, item, lasthook); } return (error); } default: error = EINVAL; break; } done: NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } /* * Destroy node */ static int ng_ppp_shutdown(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); /* Stop fragment queue timer */ ng_ppp_stop_frag_timer(node); /* Take down netgraph node */ ng_ppp_frag_reset(node); mtx_destroy(&priv->rmtx); mtx_destroy(&priv->xmtx); bzero(priv, sizeof(*priv)); free(priv, M_NETGRAPH_PPP); NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(node); /* let the node escape */ return (0); } /* * Hook disconnection */ static int ng_ppp_disconnect(hook_p hook) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); const int index = (intptr_t)NG_HOOK_PRIVATE(hook); /* Zero out hook pointer */ if (index < 0) priv->links[~index].hook = NULL; else priv->hooks[index] = NULL; /* Update derived info (or go away if no hooks left). */ if (NG_NODE_NUMHOOKS(node) > 0) ng_ppp_update(node, 0); else if (NG_NODE_IS_VALID(node)) ng_rmnode_self(node); return (0); } /* * Proto layer */ /* * Receive data on a hook inet. */ static int ng_ppp_rcvdata_inet(hook_p hook, item_p item) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); if (!priv->conf.enableIP) { NG_FREE_ITEM(item); return (ENXIO); } return (ng_ppp_hcomp_xmit(NG_HOOK_NODE(hook), item, PROT_IP)); } /* * Receive data on a hook inet and pass it directly to first link. */ static int ng_ppp_rcvdata_inet_fast(hook_p hook, item_p item) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); return (ng_ppp_link_xmit(node, item, PROT_IP, priv->activeLinks[0], NGI_M(item)->m_pkthdr.len)); } /* * Receive data on a hook ipv6. */ static int ng_ppp_rcvdata_ipv6(hook_p hook, item_p item) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); if (!priv->conf.enableIPv6) { NG_FREE_ITEM(item); return (ENXIO); } return (ng_ppp_hcomp_xmit(NG_HOOK_NODE(hook), item, PROT_IPV6)); } /* * Receive data on a hook atalk. */ static int ng_ppp_rcvdata_atalk(hook_p hook, item_p item) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); if (!priv->conf.enableAtalk) { NG_FREE_ITEM(item); return (ENXIO); } return (ng_ppp_hcomp_xmit(NG_HOOK_NODE(hook), item, PROT_ATALK)); } /* * Receive data on a hook ipx */ static int ng_ppp_rcvdata_ipx(hook_p hook, item_p item) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); if (!priv->conf.enableIPX) { NG_FREE_ITEM(item); return (ENXIO); } return (ng_ppp_hcomp_xmit(NG_HOOK_NODE(hook), item, PROT_IPX)); } /* * Receive data on a hook bypass */ static int ng_ppp_rcvdata_bypass(hook_p hook, item_p item) { uint16_t linkNum; uint16_t proto; struct mbuf *m; NGI_GET_M(item, m); if (m->m_pkthdr.len < 4) { NG_FREE_ITEM(item); return (EINVAL); } if (m->m_len < 4 && (m = m_pullup(m, 4)) == NULL) { NG_FREE_ITEM(item); return (ENOBUFS); } linkNum = be16dec(mtod(m, uint8_t *)); proto = be16dec(mtod(m, uint8_t *) + 2); m_adj(m, 4); NGI_M(item) = m; if (linkNum == NG_PPP_BUNDLE_LINKNUM) return (ng_ppp_hcomp_xmit(NG_HOOK_NODE(hook), item, proto)); else return (ng_ppp_link_xmit(NG_HOOK_NODE(hook), item, proto, linkNum, 0)); } static int ng_ppp_bypass(node_p node, item_p item, uint16_t proto, uint16_t linkNum) { const priv_p priv = NG_NODE_PRIVATE(node); uint16_t hdr[2]; struct mbuf *m; int error; if (priv->hooks[HOOK_INDEX_BYPASS] == NULL) { NG_FREE_ITEM(item); return (ENXIO); } /* Add 4-byte bypass header. */ hdr[0] = htons(linkNum); hdr[1] = htons(proto); NGI_GET_M(item, m); if ((m = ng_ppp_prepend(m, &hdr, 4)) == NULL) { NG_FREE_ITEM(item); return (ENOBUFS); } NGI_M(item) = m; /* Send packet out hook. */ NG_FWD_ITEM_HOOK(error, item, priv->hooks[HOOK_INDEX_BYPASS]); return (error); } static int ng_ppp_proto_recv(node_p node, item_p item, uint16_t proto, uint16_t linkNum) { const priv_p priv = NG_NODE_PRIVATE(node); hook_p outHook = NULL; int error; #ifdef ALIGNED_POINTER struct mbuf *m, *n; NGI_GET_M(item, m); if (!ALIGNED_POINTER(mtod(m, caddr_t), uint32_t)) { n = m_defrag(m, M_NOWAIT); if (n == NULL) { m_freem(m); NG_FREE_ITEM(item); return (ENOBUFS); } m = n; } NGI_M(item) = m; #endif /* ALIGNED_POINTER */ switch (proto) { case PROT_IP: if (priv->conf.enableIP) outHook = priv->hooks[HOOK_INDEX_INET]; break; case PROT_IPV6: if (priv->conf.enableIPv6) outHook = priv->hooks[HOOK_INDEX_IPV6]; break; case PROT_ATALK: if (priv->conf.enableAtalk) outHook = priv->hooks[HOOK_INDEX_ATALK]; break; case PROT_IPX: if (priv->conf.enableIPX) outHook = priv->hooks[HOOK_INDEX_IPX]; break; } if (outHook == NULL) return (ng_ppp_bypass(node, item, proto, linkNum)); /* Send packet out hook. */ NG_FWD_ITEM_HOOK(error, item, outHook); return (error); } /* * Header compression layer */ static int ng_ppp_hcomp_xmit(node_p node, item_p item, uint16_t proto) { const priv_p priv = NG_NODE_PRIVATE(node); if (proto == PROT_IP && priv->conf.enableVJCompression && priv->vjCompHooked) { int error; /* Send packet out hook. */ NG_FWD_ITEM_HOOK(error, item, priv->hooks[HOOK_INDEX_VJC_IP]); return (error); } return (ng_ppp_comp_xmit(node, item, proto)); } /* * Receive data on a hook vjc_comp. */ static int ng_ppp_rcvdata_vjc_comp(hook_p hook, item_p item) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); if (!priv->conf.enableVJCompression) { NG_FREE_ITEM(item); return (ENXIO); } return (ng_ppp_comp_xmit(node, item, PROT_VJCOMP)); } /* * Receive data on a hook vjc_uncomp. */ static int ng_ppp_rcvdata_vjc_uncomp(hook_p hook, item_p item) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); if (!priv->conf.enableVJCompression) { NG_FREE_ITEM(item); return (ENXIO); } return (ng_ppp_comp_xmit(node, item, PROT_VJUNCOMP)); } /* * Receive data on a hook vjc_vjip. */ static int ng_ppp_rcvdata_vjc_vjip(hook_p hook, item_p item) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); if (!priv->conf.enableVJCompression) { NG_FREE_ITEM(item); return (ENXIO); } return (ng_ppp_comp_xmit(node, item, PROT_IP)); } static int ng_ppp_hcomp_recv(node_p node, item_p item, uint16_t proto, uint16_t linkNum) { const priv_p priv = NG_NODE_PRIVATE(node); if (priv->conf.enableVJDecompression && priv->vjCompHooked) { hook_p outHook = NULL; switch (proto) { case PROT_VJCOMP: outHook = priv->hooks[HOOK_INDEX_VJC_COMP]; break; case PROT_VJUNCOMP: outHook = priv->hooks[HOOK_INDEX_VJC_UNCOMP]; break; } if (outHook) { int error; /* Send packet out hook. */ NG_FWD_ITEM_HOOK(error, item, outHook); return (error); } } return (ng_ppp_proto_recv(node, item, proto, linkNum)); } /* * Receive data on a hook vjc_ip. */ static int ng_ppp_rcvdata_vjc_ip(hook_p hook, item_p item) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); if (!priv->conf.enableVJDecompression) { NG_FREE_ITEM(item); return (ENXIO); } return (ng_ppp_proto_recv(node, item, PROT_IP, NG_PPP_BUNDLE_LINKNUM)); } /* * Compression layer */ static int ng_ppp_comp_xmit(node_p node, item_p item, uint16_t proto) { const priv_p priv = NG_NODE_PRIVATE(node); if (priv->conf.enableCompression && proto < 0x4000 && proto != PROT_COMPD && proto != PROT_CRYPTD && priv->hooks[HOOK_INDEX_COMPRESS] != NULL) { struct mbuf *m; int error; NGI_GET_M(item, m); if ((m = ng_ppp_addproto(m, proto, 0)) == NULL) { NG_FREE_ITEM(item); return (ENOBUFS); } NGI_M(item) = m; /* Send packet out hook. */ NG_FWD_ITEM_HOOK(error, item, priv->hooks[HOOK_INDEX_COMPRESS]); return (error); } return (ng_ppp_crypt_xmit(node, item, proto)); } /* * Receive data on a hook compress. */ static int ng_ppp_rcvdata_compress(hook_p hook, item_p item) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); uint16_t proto; switch (priv->conf.enableCompression) { case NG_PPP_COMPRESS_NONE: NG_FREE_ITEM(item); return (ENXIO); case NG_PPP_COMPRESS_FULL: { struct mbuf *m; NGI_GET_M(item, m); if ((m = ng_ppp_cutproto(m, &proto)) == NULL) { NG_FREE_ITEM(item); return (EIO); } NGI_M(item) = m; if (!PROT_VALID(proto)) { NG_FREE_ITEM(item); return (EIO); } } break; default: proto = PROT_COMPD; break; } return (ng_ppp_crypt_xmit(node, item, proto)); } static int ng_ppp_comp_recv(node_p node, item_p item, uint16_t proto, uint16_t linkNum) { const priv_p priv = NG_NODE_PRIVATE(node); if (proto < 0x4000 && ((proto == PROT_COMPD && priv->conf.enableDecompression) || priv->conf.enableDecompression == NG_PPP_DECOMPRESS_FULL) && priv->hooks[HOOK_INDEX_DECOMPRESS] != NULL) { int error; if (priv->conf.enableDecompression == NG_PPP_DECOMPRESS_FULL) { struct mbuf *m; NGI_GET_M(item, m); if ((m = ng_ppp_addproto(m, proto, 0)) == NULL) { NG_FREE_ITEM(item); return (EIO); } NGI_M(item) = m; } /* Send packet out hook. */ NG_FWD_ITEM_HOOK(error, item, priv->hooks[HOOK_INDEX_DECOMPRESS]); return (error); } else if (proto == PROT_COMPD) { /* Disabled protos MUST be silently discarded, but * unsupported MUST not. Let user-level decide this. */ return (ng_ppp_bypass(node, item, proto, linkNum)); } return (ng_ppp_hcomp_recv(node, item, proto, linkNum)); } /* * Receive data on a hook decompress. */ static int ng_ppp_rcvdata_decompress(hook_p hook, item_p item) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); uint16_t proto; struct mbuf *m; if (!priv->conf.enableDecompression) { NG_FREE_ITEM(item); return (ENXIO); } NGI_GET_M(item, m); if ((m = ng_ppp_cutproto(m, &proto)) == NULL) { NG_FREE_ITEM(item); return (EIO); } NGI_M(item) = m; if (!PROT_VALID(proto)) { priv->bundleStats.badProtos++; NG_FREE_ITEM(item); return (EIO); } return (ng_ppp_hcomp_recv(node, item, proto, NG_PPP_BUNDLE_LINKNUM)); } /* * Encryption layer */ static int ng_ppp_crypt_xmit(node_p node, item_p item, uint16_t proto) { const priv_p priv = NG_NODE_PRIVATE(node); if (priv->conf.enableEncryption && proto < 0x4000 && proto != PROT_CRYPTD && priv->hooks[HOOK_INDEX_ENCRYPT] != NULL) { struct mbuf *m; int error; NGI_GET_M(item, m); if ((m = ng_ppp_addproto(m, proto, 0)) == NULL) { NG_FREE_ITEM(item); return (ENOBUFS); } NGI_M(item) = m; /* Send packet out hook. */ NG_FWD_ITEM_HOOK(error, item, priv->hooks[HOOK_INDEX_ENCRYPT]); return (error); } return (ng_ppp_mp_xmit(node, item, proto)); } /* * Receive data on a hook encrypt. */ static int ng_ppp_rcvdata_encrypt(hook_p hook, item_p item) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); if (!priv->conf.enableEncryption) { NG_FREE_ITEM(item); return (ENXIO); } return (ng_ppp_mp_xmit(node, item, PROT_CRYPTD)); } static int ng_ppp_crypt_recv(node_p node, item_p item, uint16_t proto, uint16_t linkNum) { const priv_p priv = NG_NODE_PRIVATE(node); if (proto == PROT_CRYPTD) { if (priv->conf.enableDecryption && priv->hooks[HOOK_INDEX_DECRYPT] != NULL) { int error; /* Send packet out hook. */ NG_FWD_ITEM_HOOK(error, item, priv->hooks[HOOK_INDEX_DECRYPT]); return (error); } else { /* Disabled protos MUST be silently discarded, but * unsupported MUST not. Let user-level decide this. */ return (ng_ppp_bypass(node, item, proto, linkNum)); } } return (ng_ppp_comp_recv(node, item, proto, linkNum)); } /* * Receive data on a hook decrypt. */ static int ng_ppp_rcvdata_decrypt(hook_p hook, item_p item) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); uint16_t proto; struct mbuf *m; if (!priv->conf.enableDecryption) { NG_FREE_ITEM(item); return (ENXIO); } NGI_GET_M(item, m); if ((m = ng_ppp_cutproto(m, &proto)) == NULL) { NG_FREE_ITEM(item); return (EIO); } NGI_M(item) = m; if (!PROT_VALID(proto)) { priv->bundleStats.badProtos++; NG_FREE_ITEM(item); return (EIO); } return (ng_ppp_comp_recv(node, item, proto, NG_PPP_BUNDLE_LINKNUM)); } /* * Link layer */ static int ng_ppp_link_xmit(node_p node, item_p item, uint16_t proto, uint16_t linkNum, int plen) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_ppp_link *link; int len, error; struct mbuf *m; uint16_t mru; /* Check if link correct. */ if (linkNum >= NG_PPP_MAX_LINKS) { ERROUT(ENETDOWN); } /* Get link pointer (optimization). */ link = &priv->links[linkNum]; /* Check link status (if real). */ if (link->hook == NULL) { ERROUT(ENETDOWN); } /* Extract mbuf. */ NGI_GET_M(item, m); /* Check peer's MRU for this link. */ mru = link->conf.mru; if (mru != 0 && m->m_pkthdr.len > mru) { NG_FREE_M(m); ERROUT(EMSGSIZE); } /* Prepend protocol number, possibly compressed. */ if ((m = ng_ppp_addproto(m, proto, link->conf.enableProtoComp)) == NULL) { ERROUT(ENOBUFS); } /* Prepend address and control field (unless compressed). */ if (proto == PROT_LCP || !link->conf.enableACFComp) { if ((m = ng_ppp_prepend(m, &ng_ppp_acf, 2)) == NULL) ERROUT(ENOBUFS); } /* Deliver frame. */ len = m->m_pkthdr.len; NG_FWD_NEW_DATA(error, item, link->hook, m); mtx_lock(&priv->xmtx); /* Update link stats. */ link->stats.xmitFrames++; link->stats.xmitOctets += len; /* Update bundle stats. */ if (plen > 0) { priv->bundleStats.xmitFrames++; priv->bundleStats.xmitOctets += plen; } /* Update 'bytes in queue' counter. */ if (error == 0) { /* bytesInQueue and lastWrite required only for mp_strategy. */ if (priv->conf.enableMultilink && !priv->allLinksEqual && !priv->conf.enableRoundRobin) { /* If queue was empty, then mark this time. */ if (link->bytesInQueue == 0) getmicrouptime(&link->lastWrite); link->bytesInQueue += len + MP_AVERAGE_LINK_OVERHEAD; /* Limit max queue length to 50 pkts. BW can be defined incorrectly and link may not signal overload. */ if (link->bytesInQueue > 50 * 1600) link->bytesInQueue = 50 * 1600; } } mtx_unlock(&priv->xmtx); return (error); done: NG_FREE_ITEM(item); return (error); } /* * Receive data on a hook linkX. */ static int ng_ppp_rcvdata(hook_p hook, item_p item) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); const int index = (intptr_t)NG_HOOK_PRIVATE(hook); const uint16_t linkNum = (uint16_t)~index; struct ng_ppp_link * const link = &priv->links[linkNum]; uint16_t proto; struct mbuf *m; int error = 0; KASSERT(linkNum < NG_PPP_MAX_LINKS, ("%s: bogus index 0x%x", __func__, index)); NGI_GET_M(item, m); mtx_lock(&priv->rmtx); /* Stats */ link->stats.recvFrames++; link->stats.recvOctets += m->m_pkthdr.len; /* Strip address and control fields, if present. */ if (m->m_len < 2 && (m = m_pullup(m, 2)) == NULL) ERROUT(ENOBUFS); if (mtod(m, uint8_t *)[0] == 0xff && mtod(m, uint8_t *)[1] == 0x03) m_adj(m, 2); /* Get protocol number */ if ((m = ng_ppp_cutproto(m, &proto)) == NULL) ERROUT(ENOBUFS); NGI_M(item) = m; /* Put changed m back into item. */ if (!PROT_VALID(proto)) { link->stats.badProtos++; ERROUT(EIO); } /* LCP packets must go directly to bypass. */ if (proto >= 0xB000) { mtx_unlock(&priv->rmtx); return (ng_ppp_bypass(node, item, proto, linkNum)); } /* Other packets are denied on a disabled link. */ if (!link->conf.enableLink) ERROUT(ENXIO); /* Proceed to multilink layer. Mutex will be unlocked inside. */ error = ng_ppp_mp_recv(node, item, proto, linkNum); mtx_assert(&priv->rmtx, MA_NOTOWNED); return (error); done: mtx_unlock(&priv->rmtx); NG_FREE_ITEM(item); return (error); } /* * Multilink layer */ /* * Handle an incoming multi-link fragment * * The fragment reassembly algorithm is somewhat complex. This is mainly * because we are required not to reorder the reconstructed packets, yet * fragments are only guaranteed to arrive in order on a per-link basis. * In other words, when we have a complete packet ready, but the previous * packet is still incomplete, we have to decide between delivering the * complete packet and throwing away the incomplete one, or waiting to * see if the remainder of the incomplete one arrives, at which time we * can deliver both packets, in order. * * This problem is exacerbated by "sequence number slew", which is when * the sequence numbers coming in from different links are far apart from * each other. In particular, certain unnamed equipment (*cough* Ascend) * has been seen to generate sequence number slew of up to 10 on an ISDN * 2B-channel MP link. There is nothing invalid about sequence number slew * but it makes the reasssembly process have to work harder. * * However, the peer is required to transmit fragments in order on each * link. That means if we define MSEQ as the minimum over all links of * the highest sequence number received on that link, then we can always * give up any hope of receiving a fragment with sequence number < MSEQ in * the future (all of this using 'wraparound' sequence number space). * Therefore we can always immediately throw away incomplete packets * missing fragments with sequence numbers < MSEQ. * * Here is an overview of our algorithm: * * o Received fragments are inserted into a queue, for which we * maintain these invariants between calls to this function: * * - Fragments are ordered in the queue by sequence number * - If a complete packet is at the head of the queue, then * the first fragment in the packet has seq# > MSEQ + 1 * (otherwise, we could deliver it immediately) * - If any fragments have seq# < MSEQ, then they are necessarily * part of a packet whose missing seq#'s are all > MSEQ (otherwise, * we can throw them away because they'll never be completed) * - The queue contains at most MP_MAX_QUEUE_LEN fragments * * o We have a periodic timer that checks the queue for the first * complete packet that has been sitting in the queue "too long". * When one is detected, all previous (incomplete) fragments are * discarded, their missing fragments are declared lost and MSEQ * is increased. * - * o If we recieve a fragment with seq# < MSEQ, we throw it away + * o If we receive a fragment with seq# < MSEQ, we throw it away * because we've already delcared it lost. * * This assumes linkNum != NG_PPP_BUNDLE_LINKNUM. */ static int ng_ppp_mp_recv(node_p node, item_p item, uint16_t proto, uint16_t linkNum) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_ppp_link *const link = &priv->links[linkNum]; struct ng_ppp_frag *frag; struct ng_ppp_frag *qent; int i, diff, inserted; struct mbuf *m; int error = 0; if ((!priv->conf.enableMultilink) || proto != PROT_MP) { /* Stats */ priv->bundleStats.recvFrames++; priv->bundleStats.recvOctets += NGI_M(item)->m_pkthdr.len; mtx_unlock(&priv->rmtx); return (ng_ppp_crypt_recv(node, item, proto, linkNum)); } NGI_GET_M(item, m); /* Get a new frag struct from the free queue */ if ((frag = TAILQ_FIRST(&priv->fragsfree)) == NULL) { printf("No free fragments headers in ng_ppp!\n"); NG_FREE_M(m); goto process; } /* Extract fragment information from MP header */ if (priv->conf.recvShortSeq) { uint16_t shdr; if (m->m_pkthdr.len < 2) { link->stats.runts++; NG_FREE_M(m); ERROUT(EINVAL); } if (m->m_len < 2 && (m = m_pullup(m, 2)) == NULL) ERROUT(ENOBUFS); shdr = be16dec(mtod(m, void *)); frag->seq = MP_SHORT_EXTEND(shdr); frag->first = (shdr & MP_SHORT_FIRST_FLAG) != 0; frag->last = (shdr & MP_SHORT_LAST_FLAG) != 0; diff = MP_SHORT_SEQ_DIFF(frag->seq, priv->mseq); m_adj(m, 2); } else { uint32_t lhdr; if (m->m_pkthdr.len < 4) { link->stats.runts++; NG_FREE_M(m); ERROUT(EINVAL); } if (m->m_len < 4 && (m = m_pullup(m, 4)) == NULL) ERROUT(ENOBUFS); lhdr = be32dec(mtod(m, void *)); frag->seq = MP_LONG_EXTEND(lhdr); frag->first = (lhdr & MP_LONG_FIRST_FLAG) != 0; frag->last = (lhdr & MP_LONG_LAST_FLAG) != 0; diff = MP_LONG_SEQ_DIFF(frag->seq, priv->mseq); m_adj(m, 4); } frag->data = m; getmicrouptime(&frag->timestamp); /* If sequence number is < MSEQ, we've already declared this fragment as lost, so we have no choice now but to drop it */ if (diff < 0) { link->stats.dropFragments++; NG_FREE_M(m); ERROUT(0); } /* Update highest received sequence number on this link and MSEQ */ priv->mseq = link->seq = frag->seq; for (i = 0; i < priv->numActiveLinks; i++) { struct ng_ppp_link *const alink = &priv->links[priv->activeLinks[i]]; if (MP_RECV_SEQ_DIFF(priv, alink->seq, priv->mseq) < 0) priv->mseq = alink->seq; } /* Remove frag struct from free queue. */ TAILQ_REMOVE(&priv->fragsfree, frag, f_qent); /* Add fragment to queue, which is sorted by sequence number */ inserted = 0; TAILQ_FOREACH_REVERSE(qent, &priv->frags, ng_ppp_fraglist, f_qent) { diff = MP_RECV_SEQ_DIFF(priv, frag->seq, qent->seq); if (diff > 0) { TAILQ_INSERT_AFTER(&priv->frags, qent, frag, f_qent); inserted = 1; break; } else if (diff == 0) { /* should never happen! */ link->stats.dupFragments++; NG_FREE_M(frag->data); TAILQ_INSERT_HEAD(&priv->fragsfree, frag, f_qent); ERROUT(EINVAL); } } if (!inserted) TAILQ_INSERT_HEAD(&priv->frags, frag, f_qent); process: /* Process the queue */ /* NOTE: rmtx will be unlocked for sending time! */ error = ng_ppp_frag_process(node, item); mtx_unlock(&priv->rmtx); return (error); done: mtx_unlock(&priv->rmtx); NG_FREE_ITEM(item); return (error); } /************************************************************************ HELPER STUFF ************************************************************************/ /* * If new mseq > current then set it and update all active links */ static void ng_ppp_bump_mseq(node_p node, int32_t new_mseq) { const priv_p priv = NG_NODE_PRIVATE(node); int i; if (MP_RECV_SEQ_DIFF(priv, priv->mseq, new_mseq) < 0) { priv->mseq = new_mseq; for (i = 0; i < priv->numActiveLinks; i++) { struct ng_ppp_link *const alink = &priv->links[priv->activeLinks[i]]; if (MP_RECV_SEQ_DIFF(priv, alink->seq, new_mseq) < 0) alink->seq = new_mseq; } } } /* * Examine our list of fragments, and determine if there is a * complete and deliverable packet at the head of the list. * Return 1 if so, zero otherwise. */ static int ng_ppp_check_packet(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_ppp_frag *qent, *qnext; /* Check for empty queue */ if (TAILQ_EMPTY(&priv->frags)) return (0); /* Check first fragment is the start of a deliverable packet */ qent = TAILQ_FIRST(&priv->frags); if (!qent->first || MP_RECV_SEQ_DIFF(priv, qent->seq, priv->mseq) > 1) return (0); /* Check that all the fragments are there */ while (!qent->last) { qnext = TAILQ_NEXT(qent, f_qent); if (qnext == NULL) /* end of queue */ return (0); if (qnext->seq != MP_NEXT_RECV_SEQ(priv, qent->seq)) return (0); qent = qnext; } /* Got one */ return (1); } /* * Pull a completed packet off the head of the incoming fragment queue. * This assumes there is a completed packet there to pull off. */ static void ng_ppp_get_packet(node_p node, struct mbuf **mp) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_ppp_frag *qent, *qnext; struct mbuf *m = NULL, *tail; qent = TAILQ_FIRST(&priv->frags); KASSERT(!TAILQ_EMPTY(&priv->frags) && qent->first, ("%s: no packet", __func__)); for (tail = NULL; qent != NULL; qent = qnext) { qnext = TAILQ_NEXT(qent, f_qent); KASSERT(!TAILQ_EMPTY(&priv->frags), ("%s: empty q", __func__)); TAILQ_REMOVE(&priv->frags, qent, f_qent); if (tail == NULL) tail = m = qent->data; else { m->m_pkthdr.len += qent->data->m_pkthdr.len; tail->m_next = qent->data; } while (tail->m_next != NULL) tail = tail->m_next; if (qent->last) { qnext = NULL; /* Bump MSEQ if necessary */ ng_ppp_bump_mseq(node, qent->seq); } TAILQ_INSERT_HEAD(&priv->fragsfree, qent, f_qent); } *mp = m; } /* * Trim fragments from the queue whose packets can never be completed. * This assumes a complete packet is NOT at the beginning of the queue. * Returns 1 if fragments were removed, zero otherwise. */ static int ng_ppp_frag_trim(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_ppp_frag *qent, *qnext = NULL; int removed = 0; /* Scan for "dead" fragments and remove them */ while (1) { int dead = 0; /* If queue is empty, we're done */ if (TAILQ_EMPTY(&priv->frags)) break; /* Determine whether first fragment can ever be completed */ TAILQ_FOREACH(qent, &priv->frags, f_qent) { if (MP_RECV_SEQ_DIFF(priv, qent->seq, priv->mseq) >= 0) break; qnext = TAILQ_NEXT(qent, f_qent); KASSERT(qnext != NULL, ("%s: last frag < MSEQ?", __func__)); if (qnext->seq != MP_NEXT_RECV_SEQ(priv, qent->seq) || qent->last || qnext->first) { dead = 1; break; } } if (!dead) break; /* Remove fragment and all others in the same packet */ while ((qent = TAILQ_FIRST(&priv->frags)) != qnext) { KASSERT(!TAILQ_EMPTY(&priv->frags), ("%s: empty q", __func__)); priv->bundleStats.dropFragments++; TAILQ_REMOVE(&priv->frags, qent, f_qent); NG_FREE_M(qent->data); TAILQ_INSERT_HEAD(&priv->fragsfree, qent, f_qent); removed = 1; } } return (removed); } /* * Drop fragments on queue overflow. * Returns 1 if fragments were removed, zero otherwise. */ static int ng_ppp_frag_drop(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); /* Check queue length */ if (TAILQ_EMPTY(&priv->fragsfree)) { struct ng_ppp_frag *qent; /* Get oldest fragment */ KASSERT(!TAILQ_EMPTY(&priv->frags), ("%s: empty q", __func__)); qent = TAILQ_FIRST(&priv->frags); /* Bump MSEQ if necessary */ ng_ppp_bump_mseq(node, qent->seq); /* Drop it */ priv->bundleStats.dropFragments++; TAILQ_REMOVE(&priv->frags, qent, f_qent); NG_FREE_M(qent->data); TAILQ_INSERT_HEAD(&priv->fragsfree, qent, f_qent); return (1); } return (0); } /* * Run the queue, restoring the queue invariants */ static int ng_ppp_frag_process(node_p node, item_p oitem) { const priv_p priv = NG_NODE_PRIVATE(node); struct mbuf *m; item_p item; uint16_t proto; do { /* Deliver any deliverable packets */ while (ng_ppp_check_packet(node)) { ng_ppp_get_packet(node, &m); if ((m = ng_ppp_cutproto(m, &proto)) == NULL) continue; if (!PROT_VALID(proto)) { priv->bundleStats.badProtos++; NG_FREE_M(m); continue; } if (oitem) { /* If original item present - reuse it. */ item = oitem; oitem = NULL; NGI_M(item) = m; } else { item = ng_package_data(m, NG_NOFLAGS); } if (item != NULL) { /* Stats */ priv->bundleStats.recvFrames++; priv->bundleStats.recvOctets += NGI_M(item)->m_pkthdr.len; /* Drop mutex for the sending time. * Priv may change, but we are ready! */ mtx_unlock(&priv->rmtx); ng_ppp_crypt_recv(node, item, proto, NG_PPP_BUNDLE_LINKNUM); mtx_lock(&priv->rmtx); } } /* Delete dead fragments and try again */ } while (ng_ppp_frag_trim(node) || ng_ppp_frag_drop(node)); /* If we haven't reused original item - free it. */ if (oitem) NG_FREE_ITEM(oitem); /* Done */ return (0); } /* * Check for 'stale' completed packets that need to be delivered * * If a link goes down or has a temporary failure, MSEQ can get * "stuck", because no new incoming fragments appear on that link. * This can cause completed packets to never get delivered if * their sequence numbers are all > MSEQ + 1. * * This routine checks how long all of the completed packets have * been sitting in the queue, and if too long, removes fragments * from the queue and increments MSEQ to allow them to be delivered. */ static void ng_ppp_frag_checkstale(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_ppp_frag *qent, *beg, *end; struct timeval now, age; struct mbuf *m; int seq; item_p item; int endseq; uint16_t proto; now.tv_sec = 0; /* uninitialized state */ while (1) { /* If queue is empty, we're done */ if (TAILQ_EMPTY(&priv->frags)) break; /* Find the first complete packet in the queue */ beg = end = NULL; seq = TAILQ_FIRST(&priv->frags)->seq; TAILQ_FOREACH(qent, &priv->frags, f_qent) { if (qent->first) beg = qent; else if (qent->seq != seq) beg = NULL; if (beg != NULL && qent->last) { end = qent; break; } seq = MP_NEXT_RECV_SEQ(priv, seq); } /* If none found, exit */ if (end == NULL) break; /* Get current time (we assume we've been up for >= 1 second) */ if (now.tv_sec == 0) getmicrouptime(&now); /* Check if packet has been queued too long */ age = now; timevalsub(&age, &beg->timestamp); if (timevalcmp(&age, &ng_ppp_max_staleness, < )) break; /* Throw away junk fragments in front of the completed packet */ while ((qent = TAILQ_FIRST(&priv->frags)) != beg) { KASSERT(!TAILQ_EMPTY(&priv->frags), ("%s: empty q", __func__)); priv->bundleStats.dropFragments++; TAILQ_REMOVE(&priv->frags, qent, f_qent); NG_FREE_M(qent->data); TAILQ_INSERT_HEAD(&priv->fragsfree, qent, f_qent); } /* Extract completed packet */ endseq = end->seq; ng_ppp_get_packet(node, &m); if ((m = ng_ppp_cutproto(m, &proto)) == NULL) continue; if (!PROT_VALID(proto)) { priv->bundleStats.badProtos++; NG_FREE_M(m); continue; } /* Deliver packet */ if ((item = ng_package_data(m, NG_NOFLAGS)) != NULL) { /* Stats */ priv->bundleStats.recvFrames++; priv->bundleStats.recvOctets += NGI_M(item)->m_pkthdr.len; ng_ppp_crypt_recv(node, item, proto, NG_PPP_BUNDLE_LINKNUM); } } } /* * Periodically call ng_ppp_frag_checkstale() */ static void ng_ppp_frag_timeout(node_p node, hook_p hook, void *arg1, int arg2) { /* XXX: is this needed? */ if (NG_NODE_NOT_VALID(node)) return; /* Scan the fragment queue */ ng_ppp_frag_checkstale(node); /* Start timer again */ ng_ppp_start_frag_timer(node); } /* * Deliver a frame out on the bundle, i.e., figure out how to fragment * the frame across the individual PPP links and do so. */ static int ng_ppp_mp_xmit(node_p node, item_p item, uint16_t proto) { const priv_p priv = NG_NODE_PRIVATE(node); const int hdr_len = priv->conf.xmitShortSeq ? 2 : 4; int distrib[NG_PPP_MAX_LINKS]; int firstFragment; int activeLinkNum; struct mbuf *m; int plen; int frags; int32_t seq; /* At least one link must be active */ if (priv->numActiveLinks == 0) { NG_FREE_ITEM(item); return (ENETDOWN); } /* Save length for later stats. */ plen = NGI_M(item)->m_pkthdr.len; if (!priv->conf.enableMultilink) { return (ng_ppp_link_xmit(node, item, proto, priv->activeLinks[0], plen)); } /* Check peer's MRRU for this bundle. */ if (plen > priv->conf.mrru) { NG_FREE_ITEM(item); return (EMSGSIZE); } /* Extract mbuf. */ NGI_GET_M(item, m); /* Prepend protocol number, possibly compressed. */ if ((m = ng_ppp_addproto(m, proto, 1)) == NULL) { NG_FREE_ITEM(item); return (ENOBUFS); } /* Clear distribution plan */ bzero(&distrib, priv->numActiveLinks * sizeof(distrib[0])); mtx_lock(&priv->xmtx); /* Round-robin strategy */ if (priv->conf.enableRoundRobin) { activeLinkNum = priv->lastLink++ % priv->numActiveLinks; distrib[activeLinkNum] = m->m_pkthdr.len; goto deliver; } /* Strategy when all links are equivalent (optimize the common case) */ if (priv->allLinksEqual) { int numFrags, fraction, remain; int i; /* Calculate optimal fragment count */ numFrags = priv->numActiveLinks; if (numFrags > m->m_pkthdr.len / MP_MIN_FRAG_LEN) numFrags = m->m_pkthdr.len / MP_MIN_FRAG_LEN; if (numFrags == 0) numFrags = 1; fraction = m->m_pkthdr.len / numFrags; remain = m->m_pkthdr.len - (fraction * numFrags); /* Assign distribution */ for (i = 0; i < numFrags; i++) { distrib[priv->lastLink++ % priv->numActiveLinks] = fraction + (((remain--) > 0)?1:0); } goto deliver; } /* Strategy when all links are not equivalent */ ng_ppp_mp_strategy(node, m->m_pkthdr.len, distrib); deliver: /* Estimate fragments count */ frags = 0; for (activeLinkNum = priv->numActiveLinks - 1; activeLinkNum >= 0; activeLinkNum--) { const uint16_t linkNum = priv->activeLinks[activeLinkNum]; struct ng_ppp_link *const link = &priv->links[linkNum]; frags += (distrib[activeLinkNum] + link->conf.mru - hdr_len - 1) / (link->conf.mru - hdr_len); } /* Get out initial sequence number */ seq = priv->xseq; /* Update next sequence number */ if (priv->conf.xmitShortSeq) { priv->xseq = (seq + frags) & MP_SHORT_SEQ_MASK; } else { priv->xseq = (seq + frags) & MP_LONG_SEQ_MASK; } mtx_unlock(&priv->xmtx); /* Send alloted portions of frame out on the link(s) */ for (firstFragment = 1, activeLinkNum = priv->numActiveLinks - 1; activeLinkNum >= 0; activeLinkNum--) { const uint16_t linkNum = priv->activeLinks[activeLinkNum]; struct ng_ppp_link *const link = &priv->links[linkNum]; /* Deliver fragment(s) out the next link */ for ( ; distrib[activeLinkNum] > 0; firstFragment = 0) { int len, lastFragment, error; struct mbuf *m2; /* Calculate fragment length; don't exceed link MTU */ len = distrib[activeLinkNum]; if (len > link->conf.mru - hdr_len) len = link->conf.mru - hdr_len; distrib[activeLinkNum] -= len; lastFragment = (len == m->m_pkthdr.len); /* Split off next fragment as "m2" */ m2 = m; if (!lastFragment) { struct mbuf *n = m_split(m, len, M_NOWAIT); if (n == NULL) { NG_FREE_M(m); if (firstFragment) NG_FREE_ITEM(item); return (ENOMEM); } m_tag_copy_chain(n, m, M_NOWAIT); m = n; } /* Prepend MP header */ if (priv->conf.xmitShortSeq) { uint16_t shdr; shdr = seq; seq = (seq + 1) & MP_SHORT_SEQ_MASK; if (firstFragment) shdr |= MP_SHORT_FIRST_FLAG; if (lastFragment) shdr |= MP_SHORT_LAST_FLAG; shdr = htons(shdr); m2 = ng_ppp_prepend(m2, &shdr, 2); } else { uint32_t lhdr; lhdr = seq; seq = (seq + 1) & MP_LONG_SEQ_MASK; if (firstFragment) lhdr |= MP_LONG_FIRST_FLAG; if (lastFragment) lhdr |= MP_LONG_LAST_FLAG; lhdr = htonl(lhdr); m2 = ng_ppp_prepend(m2, &lhdr, 4); } if (m2 == NULL) { if (!lastFragment) m_freem(m); if (firstFragment) NG_FREE_ITEM(item); return (ENOBUFS); } /* Send fragment */ if (firstFragment) { NGI_M(item) = m2; /* Reuse original item. */ } else { item = ng_package_data(m2, NG_NOFLAGS); } if (item != NULL) { error = ng_ppp_link_xmit(node, item, PROT_MP, linkNum, (firstFragment?plen:0)); if (error != 0) { if (!lastFragment) NG_FREE_M(m); return (error); } } } } /* Done */ return (0); } /* * Computing the optimal fragmentation * ----------------------------------- * * This routine tries to compute the optimal fragmentation pattern based * on each link's latency, bandwidth, and calculated additional latency. * The latter quantity is the additional latency caused by previously * written data that has not been transmitted yet. * * This algorithm is only useful when not all of the links have the * same latency and bandwidth values. * * The essential idea is to make the last bit of each fragment of the * frame arrive at the opposite end at the exact same time. This greedy * algorithm is optimal, in that no other scheduling could result in any * packet arriving any sooner unless packets are delivered out of order. * * Suppose link i has bandwidth b_i (in tens of bytes per milisecond) and * latency l_i (in miliseconds). Consider the function function f_i(t) * which is equal to the number of bytes that will have arrived at * the peer after t miliseconds if we start writing continuously at * time t = 0. Then f_i(t) = b_i * (t - l_i) = ((b_i * t) - (l_i * b_i). * That is, f_i(t) is a line with slope b_i and y-intersect -(l_i * b_i). * Note that the y-intersect is always <= zero because latency can't be * negative. Note also that really the function is f_i(t) except when * f_i(t) is negative, in which case the function is zero. To take * care of this, let Q_i(t) = { if (f_i(t) > 0) return 1; else return 0; }. * So the actual number of bytes that will have arrived at the peer after * t miliseconds is f_i(t) * Q_i(t). * * At any given time, each link has some additional latency a_i >= 0 * due to previously written fragment(s) which are still in the queue. * This value is easily computed from the time since last transmission, * the previous latency value, the number of bytes written, and the * link's bandwidth. * * Assume that l_i includes any a_i already, and that the links are * sorted by latency, so that l_i <= l_{i+1}. * * Let N be the total number of bytes in the current frame we are sending. * * Suppose we were to start writing bytes at time t = 0 on all links * simultaneously, which is the most we can possibly do. Then let * F(t) be equal to the total number of bytes received by the peer * after t miliseconds. Then F(t) = Sum_i (f_i(t) * Q_i(t)). * * Our goal is simply this: fragment the frame across the links such * that the peer is able to reconstruct the completed frame as soon as * possible, i.e., at the least possible value of t. Call this value t_0. * * Then it follows that F(t_0) = N. Our strategy is first to find the value * of t_0, and then deduce how many bytes to write to each link. * * Rewriting F(t_0): * * t_0 = ( N + Sum_i ( l_i * b_i * Q_i(t_0) ) ) / Sum_i ( b_i * Q_i(t_0) ) * * Now, we note that Q_i(t) is constant for l_i <= t <= l_{i+1}. t_0 will * lie in one of these ranges. To find it, we just need to find the i such * that F(l_i) <= N <= F(l_{i+1}). Then we compute all the constant values * for Q_i() in this range, plug in the remaining values, solving for t_0. * * Once t_0 is known, then the number of bytes to send on link i is * just f_i(t_0) * Q_i(t_0). * * In other words, we start allocating bytes to the links one at a time. * We keep adding links until the frame is completely sent. Some links * may not get any bytes because their latency is too high. * * Is all this work really worth the trouble? Depends on the situation. * The bigger the ratio of computer speed to link speed, and the more * important total bundle latency is (e.g., for interactive response time), * the more it's worth it. There is however the cost of calling this * function for every frame. The running time is O(n^2) where n is the * number of links that receive a non-zero number of bytes. * * Since latency is measured in miliseconds, the "resolution" of this * algorithm is one milisecond. * * To avoid this algorithm altogether, configure all links to have the * same latency and bandwidth. */ static void ng_ppp_mp_strategy(node_p node, int len, int *distrib) { const priv_p priv = NG_NODE_PRIVATE(node); int latency[NG_PPP_MAX_LINKS]; int sortByLatency[NG_PPP_MAX_LINKS]; int activeLinkNum; int t0, total, topSum, botSum; struct timeval now; int i, numFragments; /* If only one link, this gets real easy */ if (priv->numActiveLinks == 1) { distrib[0] = len; return; } /* Get current time */ getmicrouptime(&now); /* Compute latencies for each link at this point in time */ for (activeLinkNum = 0; activeLinkNum < priv->numActiveLinks; activeLinkNum++) { struct ng_ppp_link *alink; struct timeval diff; int xmitBytes; /* Start with base latency value */ alink = &priv->links[priv->activeLinks[activeLinkNum]]; latency[activeLinkNum] = alink->latency; sortByLatency[activeLinkNum] = activeLinkNum; /* see below */ /* Any additional latency? */ if (alink->bytesInQueue == 0) continue; /* Compute time delta since last write */ diff = now; timevalsub(&diff, &alink->lastWrite); /* alink->bytesInQueue will be changed, mark change time. */ alink->lastWrite = now; if (now.tv_sec < 0 || diff.tv_sec >= 10) { /* sanity */ alink->bytesInQueue = 0; continue; } /* How many bytes could have transmitted since last write? */ xmitBytes = (alink->conf.bandwidth * 10 * diff.tv_sec) + (alink->conf.bandwidth * (diff.tv_usec / 1000)) / 100; alink->bytesInQueue -= xmitBytes; if (alink->bytesInQueue < 0) alink->bytesInQueue = 0; else latency[activeLinkNum] += (100 * alink->bytesInQueue) / alink->conf.bandwidth; } /* Sort active links by latency */ qsort_r(sortByLatency, priv->numActiveLinks, sizeof(*sortByLatency), latency, ng_ppp_intcmp); /* Find the interval we need (add links in sortByLatency[] order) */ for (numFragments = 1; numFragments < priv->numActiveLinks; numFragments++) { for (total = i = 0; i < numFragments; i++) { int flowTime; flowTime = latency[sortByLatency[numFragments]] - latency[sortByLatency[i]]; total += ((flowTime * priv->links[ priv->activeLinks[sortByLatency[i]]].conf.bandwidth) + 99) / 100; } if (total >= len) break; } /* Solve for t_0 in that interval */ for (topSum = botSum = i = 0; i < numFragments; i++) { int bw = priv->links[ priv->activeLinks[sortByLatency[i]]].conf.bandwidth; topSum += latency[sortByLatency[i]] * bw; /* / 100 */ botSum += bw; /* / 100 */ } t0 = ((len * 100) + topSum + botSum / 2) / botSum; /* Compute f_i(t_0) all i */ for (total = i = 0; i < numFragments; i++) { int bw = priv->links[ priv->activeLinks[sortByLatency[i]]].conf.bandwidth; distrib[sortByLatency[i]] = (bw * (t0 - latency[sortByLatency[i]]) + 50) / 100; total += distrib[sortByLatency[i]]; } /* Deal with any rounding error */ if (total < len) { struct ng_ppp_link *fastLink = &priv->links[priv->activeLinks[sortByLatency[0]]]; int fast = 0; /* Find the fastest link */ for (i = 1; i < numFragments; i++) { struct ng_ppp_link *const link = &priv->links[priv->activeLinks[sortByLatency[i]]]; if (link->conf.bandwidth > fastLink->conf.bandwidth) { fast = i; fastLink = link; } } distrib[sortByLatency[fast]] += len - total; } else while (total > len) { struct ng_ppp_link *slowLink = &priv->links[priv->activeLinks[sortByLatency[0]]]; int delta, slow = 0; /* Find the slowest link that still has bytes to remove */ for (i = 1; i < numFragments; i++) { struct ng_ppp_link *const link = &priv->links[priv->activeLinks[sortByLatency[i]]]; if (distrib[sortByLatency[slow]] == 0 || (distrib[sortByLatency[i]] > 0 && link->conf.bandwidth < slowLink->conf.bandwidth)) { slow = i; slowLink = link; } } delta = total - len; if (delta > distrib[sortByLatency[slow]]) delta = distrib[sortByLatency[slow]]; distrib[sortByLatency[slow]] -= delta; total -= delta; } } /* * Compare two integers */ static int ng_ppp_intcmp(void *latency, const void *v1, const void *v2) { const int index1 = *((const int *) v1); const int index2 = *((const int *) v2); return ((int *)latency)[index1] - ((int *)latency)[index2]; } /* * Prepend a possibly compressed PPP protocol number in front of a frame */ static struct mbuf * ng_ppp_addproto(struct mbuf *m, uint16_t proto, int compOK) { if (compOK && PROT_COMPRESSABLE(proto)) { uint8_t pbyte = (uint8_t)proto; return ng_ppp_prepend(m, &pbyte, 1); } else { uint16_t pword = htons((uint16_t)proto); return ng_ppp_prepend(m, &pword, 2); } } /* * Cut a possibly compressed PPP protocol number from the front of a frame. */ static struct mbuf * ng_ppp_cutproto(struct mbuf *m, uint16_t *proto) { *proto = 0; if (m->m_len < 1 && (m = m_pullup(m, 1)) == NULL) return (NULL); *proto = *mtod(m, uint8_t *); m_adj(m, 1); if (!PROT_VALID(*proto)) { if (m->m_len < 1 && (m = m_pullup(m, 1)) == NULL) return (NULL); *proto = (*proto << 8) + *mtod(m, uint8_t *); m_adj(m, 1); } return (m); } /* * Prepend some bytes to an mbuf. */ static struct mbuf * ng_ppp_prepend(struct mbuf *m, const void *buf, int len) { M_PREPEND(m, len, M_NOWAIT); if (m == NULL || (m->m_len < len && (m = m_pullup(m, len)) == NULL)) return (NULL); bcopy(buf, mtod(m, uint8_t *), len); return (m); } /* * Update private information that is derived from other private information */ static void ng_ppp_update(node_p node, int newConf) { const priv_p priv = NG_NODE_PRIVATE(node); int i; /* Update active status for VJ Compression */ priv->vjCompHooked = priv->hooks[HOOK_INDEX_VJC_IP] != NULL && priv->hooks[HOOK_INDEX_VJC_COMP] != NULL && priv->hooks[HOOK_INDEX_VJC_UNCOMP] != NULL && priv->hooks[HOOK_INDEX_VJC_VJIP] != NULL; /* Increase latency for each link an amount equal to one MP header */ if (newConf) { for (i = 0; i < NG_PPP_MAX_LINKS; i++) { int hdrBytes; if (priv->links[i].conf.bandwidth == 0) continue; hdrBytes = MP_AVERAGE_LINK_OVERHEAD + (priv->links[i].conf.enableACFComp ? 0 : 2) + (priv->links[i].conf.enableProtoComp ? 1 : 2) + (priv->conf.xmitShortSeq ? 2 : 4); priv->links[i].latency = priv->links[i].conf.latency + (hdrBytes / priv->links[i].conf.bandwidth + 50) / 100; } } /* Update list of active links */ bzero(&priv->activeLinks, sizeof(priv->activeLinks)); priv->numActiveLinks = 0; priv->allLinksEqual = 1; for (i = 0; i < NG_PPP_MAX_LINKS; i++) { struct ng_ppp_link *const link = &priv->links[i]; /* Is link active? */ if (link->conf.enableLink && link->hook != NULL) { struct ng_ppp_link *link0; /* Add link to list of active links */ priv->activeLinks[priv->numActiveLinks++] = i; link0 = &priv->links[priv->activeLinks[0]]; /* Determine if all links are still equal */ if (link->latency != link0->latency || link->conf.bandwidth != link0->conf.bandwidth) priv->allLinksEqual = 0; /* Initialize rec'd sequence number */ if (link->seq == MP_NOSEQ) { link->seq = (link == link0) ? MP_INITIAL_SEQ : link0->seq; } } else link->seq = MP_NOSEQ; } /* Update MP state as multi-link is active or not */ if (priv->conf.enableMultilink && priv->numActiveLinks > 0) ng_ppp_start_frag_timer(node); else { ng_ppp_stop_frag_timer(node); ng_ppp_frag_reset(node); priv->xseq = MP_INITIAL_SEQ; priv->mseq = MP_INITIAL_SEQ; for (i = 0; i < NG_PPP_MAX_LINKS; i++) { struct ng_ppp_link *const link = &priv->links[i]; bzero(&link->lastWrite, sizeof(link->lastWrite)); link->bytesInQueue = 0; link->seq = MP_NOSEQ; } } if (priv->hooks[HOOK_INDEX_INET] != NULL) { if (priv->conf.enableIP == 1 && priv->numActiveLinks == 1 && priv->conf.enableMultilink == 0 && priv->conf.enableCompression == 0 && priv->conf.enableEncryption == 0 && priv->conf.enableVJCompression == 0) NG_HOOK_SET_RCVDATA(priv->hooks[HOOK_INDEX_INET], ng_ppp_rcvdata_inet_fast); else NG_HOOK_SET_RCVDATA(priv->hooks[HOOK_INDEX_INET], ng_ppp_rcvdata_inet); } } /* * Determine if a new configuration would represent a valid change * from the current configuration and link activity status. */ static int ng_ppp_config_valid(node_p node, const struct ng_ppp_node_conf *newConf) { const priv_p priv = NG_NODE_PRIVATE(node); int i, newNumLinksActive; /* Check per-link config and count how many links would be active */ for (newNumLinksActive = i = 0; i < NG_PPP_MAX_LINKS; i++) { if (newConf->links[i].enableLink && priv->links[i].hook != NULL) newNumLinksActive++; if (!newConf->links[i].enableLink) continue; if (newConf->links[i].mru < MP_MIN_LINK_MRU) return (0); if (newConf->links[i].bandwidth == 0) return (0); if (newConf->links[i].bandwidth > NG_PPP_MAX_BANDWIDTH) return (0); if (newConf->links[i].latency > NG_PPP_MAX_LATENCY) return (0); } /* Disallow changes to multi-link configuration while MP is active */ if (priv->numActiveLinks > 0 && newNumLinksActive > 0) { if (!priv->conf.enableMultilink != !newConf->bund.enableMultilink || !priv->conf.xmitShortSeq != !newConf->bund.xmitShortSeq || !priv->conf.recvShortSeq != !newConf->bund.recvShortSeq) return (0); } /* At most one link can be active unless multi-link is enabled */ if (!newConf->bund.enableMultilink && newNumLinksActive > 1) return (0); /* Configuration change would be valid */ return (1); } /* * Free all entries in the fragment queue */ static void ng_ppp_frag_reset(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_ppp_frag *qent, *qnext; for (qent = TAILQ_FIRST(&priv->frags); qent; qent = qnext) { qnext = TAILQ_NEXT(qent, f_qent); NG_FREE_M(qent->data); TAILQ_INSERT_HEAD(&priv->fragsfree, qent, f_qent); } TAILQ_INIT(&priv->frags); } /* * Start fragment queue timer */ static void ng_ppp_start_frag_timer(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); if (!(callout_pending(&priv->fragTimer))) ng_callout(&priv->fragTimer, node, NULL, MP_FRAGTIMER_INTERVAL, ng_ppp_frag_timeout, NULL, 0); } /* * Stop fragment queue timer */ static void ng_ppp_stop_frag_timer(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); if (callout_pending(&priv->fragTimer)) ng_uncallout(&priv->fragTimer, node); } Index: head/sys/netgraph/ng_pppoe.c =================================================================== --- head/sys/netgraph/ng_pppoe.c (revision 298812) +++ head/sys/netgraph/ng_pppoe.c (revision 298813) @@ -1,1960 +1,1960 @@ /* * ng_pppoe.c */ /*- * Copyright (c) 1996-1999 Whistle Communications, Inc. * All rights reserved. * * Subject to the following obligations and disclaimer of warranty, use and * redistribution of this software, in source or object code forms, with or * without modifications are expressly permitted by Whistle Communications; * provided, however, that: * 1. Any and all reproductions of the source or object code must include the * copyright notice above and the following disclaimer of warranties; and * 2. No rights are granted, in any manner or form, to use Whistle * Communications, Inc. trademarks, including the mark "WHISTLE * COMMUNICATIONS" on advertising, endorsements, or otherwise except as * such appears in the above copyright notice or in the software. * * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * Author: Julian Elischer * * $FreeBSD$ * $Whistle: ng_pppoe.c,v 1.10 1999/11/01 09:24:52 julian Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef NG_SEPARATE_MALLOC static MALLOC_DEFINE(M_NETGRAPH_PPPOE, "netgraph_pppoe", "netgraph pppoe node"); #else #define M_NETGRAPH_PPPOE M_NETGRAPH #endif #define SIGNOFF "session closed" /* * This section contains the netgraph method declarations for the * pppoe node. These methods define the netgraph pppoe 'type'. */ static ng_constructor_t ng_pppoe_constructor; static ng_rcvmsg_t ng_pppoe_rcvmsg; static ng_shutdown_t ng_pppoe_shutdown; static ng_newhook_t ng_pppoe_newhook; static ng_connect_t ng_pppoe_connect; static ng_rcvdata_t ng_pppoe_rcvdata; static ng_rcvdata_t ng_pppoe_rcvdata_ether; static ng_rcvdata_t ng_pppoe_rcvdata_debug; static ng_disconnect_t ng_pppoe_disconnect; /* Parse type for struct ngpppoe_init_data */ static const struct ng_parse_struct_field ngpppoe_init_data_type_fields[] = NG_PPPOE_INIT_DATA_TYPE_INFO; static const struct ng_parse_type ngpppoe_init_data_state_type = { &ng_parse_struct_type, &ngpppoe_init_data_type_fields }; /* Parse type for struct ngpppoe_sts */ static const struct ng_parse_struct_field ng_pppoe_sts_type_fields[] = NG_PPPOE_STS_TYPE_INFO; static const struct ng_parse_type ng_pppoe_sts_state_type = { &ng_parse_struct_type, &ng_pppoe_sts_type_fields }; /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_pppoe_cmds[] = { { NGM_PPPOE_COOKIE, NGM_PPPOE_CONNECT, "pppoe_connect", &ngpppoe_init_data_state_type, NULL }, { NGM_PPPOE_COOKIE, NGM_PPPOE_LISTEN, "pppoe_listen", &ngpppoe_init_data_state_type, NULL }, { NGM_PPPOE_COOKIE, NGM_PPPOE_OFFER, "pppoe_offer", &ngpppoe_init_data_state_type, NULL }, { NGM_PPPOE_COOKIE, NGM_PPPOE_SERVICE, "pppoe_service", &ngpppoe_init_data_state_type, NULL }, { NGM_PPPOE_COOKIE, NGM_PPPOE_SUCCESS, "pppoe_success", &ng_pppoe_sts_state_type, NULL }, { NGM_PPPOE_COOKIE, NGM_PPPOE_FAIL, "pppoe_fail", &ng_pppoe_sts_state_type, NULL }, { NGM_PPPOE_COOKIE, NGM_PPPOE_CLOSE, "pppoe_close", &ng_pppoe_sts_state_type, NULL }, { NGM_PPPOE_COOKIE, NGM_PPPOE_SETMODE, "pppoe_setmode", &ng_parse_string_type, NULL }, { NGM_PPPOE_COOKIE, NGM_PPPOE_GETMODE, "pppoe_getmode", NULL, &ng_parse_string_type }, { NGM_PPPOE_COOKIE, NGM_PPPOE_SETENADDR, "setenaddr", &ng_parse_enaddr_type, NULL }, { NGM_PPPOE_COOKIE, NGM_PPPOE_SETMAXP, "setmaxp", &ng_parse_uint16_type, NULL }, { 0 } }; /* Netgraph node type descriptor */ static struct ng_type typestruct = { .version = NG_ABI_VERSION, .name = NG_PPPOE_NODE_TYPE, .constructor = ng_pppoe_constructor, .rcvmsg = ng_pppoe_rcvmsg, .shutdown = ng_pppoe_shutdown, .newhook = ng_pppoe_newhook, .connect = ng_pppoe_connect, .rcvdata = ng_pppoe_rcvdata, .disconnect = ng_pppoe_disconnect, .cmdlist = ng_pppoe_cmds, }; NETGRAPH_INIT(pppoe, &typestruct); /* * States for the session state machine. * These have no meaning if there is no hook attached yet. */ enum state { PPPOE_SNONE=0, /* [both] Initial state */ PPPOE_LISTENING, /* [Daemon] Listening for discover initiation pkt */ PPPOE_SINIT, /* [Client] Sent discovery initiation */ PPPOE_PRIMED, /* [Server] Awaiting PADI from daemon */ PPPOE_SOFFER, /* [Server] Sent offer message (got PADI)*/ PPPOE_SREQ, /* [Client] Sent a Request */ PPPOE_NEWCONNECTED, /* [Server] Connection established, No data received */ PPPOE_CONNECTED, /* [Both] Connection established, Data received */ PPPOE_DEAD /* [Both] */ }; #define NUMTAGS 20 /* number of tags we are set up to work with */ /* * Information we store for each hook on each node for negotiating the * session. The mbuf and cluster are freed once negotiation has completed. * The whole negotiation block is then discarded. */ struct sess_neg { struct mbuf *m; /* holds cluster with last sent packet */ union packet *pkt; /* points within the above cluster */ struct callout handle; /* see timeout(9) */ u_int timeout; /* 0,1,2,4,8,16 etc. seconds */ u_int numtags; const struct pppoe_tag *tags[NUMTAGS]; u_int service_len; u_int ac_name_len; struct datatag service; struct datatag ac_name; }; typedef struct sess_neg *negp; /* * Session information that is needed after connection. */ struct sess_con { hook_p hook; uint16_t Session_ID; enum state state; ng_ID_t creator; /* who to notify */ struct pppoe_full_hdr pkt_hdr; /* used when connected */ negp neg; /* used when negotiating */ LIST_ENTRY(sess_con) sessions; }; typedef struct sess_con *sessp; #define SESSHASHSIZE 0x0100 #define SESSHASH(x) (((x) ^ ((x) >> 8)) & (SESSHASHSIZE - 1)) struct sess_hash_entry { struct mtx mtx; LIST_HEAD(hhead, sess_con) head; }; /* * Information we store for each node */ struct PPPoE { node_p node; /* back pointer to node */ hook_p ethernet_hook; hook_p debug_hook; u_int packets_in; /* packets in from ethernet */ u_int packets_out; /* packets out towards ethernet */ uint32_t flags; #define COMPAT_3COM 0x00000001 #define COMPAT_DLINK 0x00000002 struct ether_header eh; LIST_HEAD(, sess_con) listeners; struct sess_hash_entry sesshash[SESSHASHSIZE]; struct maxptag max_payload; /* PPP-Max-Payload (RFC4638) */ }; typedef struct PPPoE *priv_p; union uniq { char bytes[sizeof(void *)]; void *pointer; }; #define LEAVE(x) do { error = x; goto quit; } while(0) static void pppoe_start(sessp sp); static void pppoe_ticker(node_p node, hook_p hook, void *arg1, int arg2); static const struct pppoe_tag *scan_tags(sessp sp, const struct pppoe_hdr* ph); static int pppoe_send_event(sessp sp, enum cmd cmdid); /************************************************************************* * Some basic utilities from the Linux version with author's permission.* * Author: Michal Ostrowski * ************************************************************************/ /* * Return the location where the next tag can be put */ static __inline const struct pppoe_tag* next_tag(const struct pppoe_hdr* ph) { return (const struct pppoe_tag*)(((const char*)(ph + 1)) + ntohs(ph->length)); } /* * Look for a tag of a specific type. * Don't trust any length the other end says, * but assume we already sanity checked ph->length. */ static const struct pppoe_tag* get_tag(const struct pppoe_hdr* ph, uint16_t idx) { const char *const end = (const char *)next_tag(ph); const struct pppoe_tag *pt = (const void *)(ph + 1); const char *ptn; /* * Keep processing tags while a tag header will still fit. */ while((const char*)(pt + 1) <= end) { /* * If the tag data would go past the end of the packet, abort. */ ptn = (((const char *)(pt + 1)) + ntohs(pt->tag_len)); if (ptn > end) { CTR2(KTR_NET, "%20s: invalid length for tag %d", __func__, idx); return (NULL); } if (pt->tag_type == idx) { CTR2(KTR_NET, "%20s: found tag %d", __func__, idx); return (pt); } pt = (const struct pppoe_tag*)ptn; } CTR2(KTR_NET, "%20s: not found tag %d", __func__, idx); return (NULL); } /************************************************************************** * Inlines to initialise or add tags to a session's tag list. **************************************************************************/ /* * Initialise the session's tag list. */ static void init_tags(sessp sp) { KASSERT(sp->neg != NULL, ("%s: no neg", __func__)); sp->neg->numtags = 0; } static void insert_tag(sessp sp, const struct pppoe_tag *tp) { negp neg = sp->neg; int i; KASSERT(neg != NULL, ("%s: no neg", __func__)); if ((i = neg->numtags++) < NUMTAGS) { neg->tags[i] = tp; } else { log(LOG_NOTICE, "ng_pppoe: asked to add too many tags to " "packet\n"); neg->numtags--; } } /* * Make up a packet, using the tags filled out for the session. * * Assume that the actual pppoe header and ethernet header * are filled out externally to this routine. * Also assume that neg->wh points to the correct * location at the front of the buffer space. */ static void make_packet(sessp sp) { struct pppoe_full_hdr *wh = &sp->neg->pkt->pkt_header; const struct pppoe_tag **tag; char *dp; int count; int tlen; uint16_t length = 0; KASSERT((sp->neg != NULL) && (sp->neg->m != NULL), ("%s: called from wrong state", __func__)); CTR2(KTR_NET, "%20s: called %d", __func__, sp->Session_ID); dp = (char *)(&wh->ph + 1); for (count = 0, tag = sp->neg->tags; ((count < sp->neg->numtags) && (count < NUMTAGS)); tag++, count++) { tlen = ntohs((*tag)->tag_len) + sizeof(**tag); if ((length + tlen) > (ETHER_MAX_LEN - 4 - sizeof(*wh))) { log(LOG_NOTICE, "ng_pppoe: tags too long\n"); sp->neg->numtags = count; break; /* XXX chop off what's too long */ } bcopy(*tag, (char *)dp, tlen); length += tlen; dp += tlen; } wh->ph.length = htons(length); sp->neg->m->m_len = length + sizeof(*wh); sp->neg->m->m_pkthdr.len = length + sizeof(*wh); } /************************************************************************** * Routines to match a service. * **************************************************************************/ /* * Find a hook that has a service string that matches that * we are seeking. For now use a simple string. * In the future we may need something like regexp(). * * Null string is a wildcard (ANY service), according to RFC2516. * And historical FreeBSD wildcard is also "*". */ static hook_p pppoe_match_svc(node_p node, const struct pppoe_tag *tag) { const priv_p privp = NG_NODE_PRIVATE(node); sessp sp; LIST_FOREACH(sp, &privp->listeners, sessions) { negp neg = sp->neg; /* Empty Service-Name matches any service. */ if (neg->service_len == 0) break; /* Special case for a blank or "*" service name (wildcard). */ if (neg->service_len == 1 && neg->service.data[0] == '*') break; /* If the lengths don't match, that aint it. */ if (neg->service_len != ntohs(tag->tag_len)) continue; if (strncmp((const char *)(tag + 1), neg->service.data, ntohs(tag->tag_len)) == 0) break; } CTR3(KTR_NET, "%20s: matched %p for %s", __func__, sp?sp->hook:NULL, (const char *)(tag + 1)); return (sp?sp->hook:NULL); } /* * Broadcast the PADI packet in m0 to all listening hooks. * This routine is called when a PADI with empty Service-Name * tag is received. Client should receive PADOs with all * available services. */ static int pppoe_broadcast_padi(node_p node, struct mbuf *m0) { const priv_p privp = NG_NODE_PRIVATE(node); sessp sp; int error = 0; LIST_FOREACH(sp, &privp->listeners, sessions) { struct mbuf *m; m = m_dup(m0, M_NOWAIT); if (m == NULL) return (ENOMEM); NG_SEND_DATA_ONLY(error, sp->hook, m); if (error) return (error); } return (0); } /* * Find a hook, which name equals to given service. */ static hook_p pppoe_find_svc(node_p node, const char *svc_name, int svc_len) { const priv_p privp = NG_NODE_PRIVATE(node); sessp sp; LIST_FOREACH(sp, &privp->listeners, sessions) { negp neg = sp->neg; if (neg->service_len == svc_len && strncmp(svc_name, neg->service.data, svc_len) == 0) return (sp->hook); } return (NULL); } /************************************************************************** * Routines to find a particular session that matches an incoming packet. * **************************************************************************/ /* Find free session and add to hash. */ static uint16_t pppoe_getnewsession(sessp sp) { const priv_p privp = NG_NODE_PRIVATE(NG_HOOK_NODE(sp->hook)); static uint16_t pppoe_sid = 1; sessp tsp; uint16_t val, hash; restart: /* Atomicity is not needed here as value will be checked. */ val = pppoe_sid++; /* Spec says 0xFFFF is reserved, also don't use 0x0000. */ if (val == 0xffff || val == 0x0000) val = pppoe_sid = 1; /* Check it isn't already in use. */ hash = SESSHASH(val); mtx_lock(&privp->sesshash[hash].mtx); LIST_FOREACH(tsp, &privp->sesshash[hash].head, sessions) { if (tsp->Session_ID == val) break; } if (!tsp) { sp->Session_ID = val; LIST_INSERT_HEAD(&privp->sesshash[hash].head, sp, sessions); } mtx_unlock(&privp->sesshash[hash].mtx); if (tsp) goto restart; CTR2(KTR_NET, "%20s: new sid %d", __func__, val); return (val); } /* Add specified session to hash. */ static void pppoe_addsession(sessp sp) { const priv_p privp = NG_NODE_PRIVATE(NG_HOOK_NODE(sp->hook)); uint16_t hash = SESSHASH(sp->Session_ID); mtx_lock(&privp->sesshash[hash].mtx); LIST_INSERT_HEAD(&privp->sesshash[hash].head, sp, sessions); mtx_unlock(&privp->sesshash[hash].mtx); } /* Delete specified session from hash. */ static void pppoe_delsession(sessp sp) { const priv_p privp = NG_NODE_PRIVATE(NG_HOOK_NODE(sp->hook)); uint16_t hash = SESSHASH(sp->Session_ID); mtx_lock(&privp->sesshash[hash].mtx); LIST_REMOVE(sp, sessions); mtx_unlock(&privp->sesshash[hash].mtx); } /* Find matching peer/session combination. */ static sessp pppoe_findsession(priv_p privp, const struct pppoe_full_hdr *wh) { uint16_t session = ntohs(wh->ph.sid); uint16_t hash = SESSHASH(session); sessp sp = NULL; mtx_lock(&privp->sesshash[hash].mtx); LIST_FOREACH(sp, &privp->sesshash[hash].head, sessions) { if (sp->Session_ID == session && bcmp(sp->pkt_hdr.eh.ether_dhost, wh->eh.ether_shost, ETHER_ADDR_LEN) == 0) { break; } } mtx_unlock(&privp->sesshash[hash].mtx); CTR3(KTR_NET, "%20s: matched %p for %d", __func__, sp?sp->hook:NULL, session); return (sp); } static hook_p pppoe_finduniq(node_p node, const struct pppoe_tag *tag) { hook_p hook = NULL; union uniq uniq; bcopy(tag + 1, uniq.bytes, sizeof(void *)); /* Cycle through all known hooks. */ LIST_FOREACH(hook, &node->nd_hooks, hk_hooks) { /* Skip any nonsession hook. */ if (NG_HOOK_PRIVATE(hook) == NULL) continue; if (uniq.pointer == NG_HOOK_PRIVATE(hook)) break; } CTR3(KTR_NET, "%20s: matched %p for %p", __func__, hook, uniq.pointer); return (hook); } /************************************************************************** * Start of Netgraph entrypoints. * **************************************************************************/ /* * Allocate the private data structure and link it with node. */ static int ng_pppoe_constructor(node_p node) { priv_p privp; int i; /* Initialize private descriptor. */ privp = malloc(sizeof(*privp), M_NETGRAPH_PPPOE, M_WAITOK | M_ZERO); /* Link structs together; this counts as our one reference to *node. */ NG_NODE_SET_PRIVATE(node, privp); privp->node = node; /* Initialize to standard mode. */ memset(&privp->eh.ether_dhost, 0xff, ETHER_ADDR_LEN); privp->eh.ether_type = ETHERTYPE_PPPOE_DISC; LIST_INIT(&privp->listeners); for (i = 0; i < SESSHASHSIZE; i++) { mtx_init(&privp->sesshash[i].mtx, "PPPoE hash mutex", NULL, MTX_DEF); LIST_INIT(&privp->sesshash[i].head); } CTR3(KTR_NET, "%20s: created node [%x] (%p)", __func__, node->nd_ID, node); return (0); } /* * Give our ok for a hook to be added... * point the hook's private info to the hook structure. * * The following hook names are special: * "ethernet": the hook that should be connected to a NIC. * "debug": copies of data sent out here (when I write the code). * All other hook names need only be unique. (the framework checks this). */ static int ng_pppoe_newhook(node_p node, hook_p hook, const char *name) { const priv_p privp = NG_NODE_PRIVATE(node); sessp sp; if (strcmp(name, NG_PPPOE_HOOK_ETHERNET) == 0) { privp->ethernet_hook = hook; NG_HOOK_SET_RCVDATA(hook, ng_pppoe_rcvdata_ether); } else if (strcmp(name, NG_PPPOE_HOOK_DEBUG) == 0) { privp->debug_hook = hook; NG_HOOK_SET_RCVDATA(hook, ng_pppoe_rcvdata_debug); } else { /* * Any other unique name is OK. * The infrastructure has already checked that it's unique, * so just allocate it and hook it in. */ sp = malloc(sizeof(*sp), M_NETGRAPH_PPPOE, M_NOWAIT | M_ZERO); if (sp == NULL) return (ENOMEM); NG_HOOK_SET_PRIVATE(hook, sp); sp->hook = hook; } CTR5(KTR_NET, "%20s: node [%x] (%p) connected hook %s (%p)", __func__, node->nd_ID, node, name, hook); return(0); } /* * Hook has been added successfully. Request the MAC address of * the underlying Ethernet node. */ static int ng_pppoe_connect(hook_p hook) { const priv_p privp = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); struct ng_mesg *msg; int error; if (hook != privp->ethernet_hook) return (0); /* * If this is Ethernet hook, then request MAC address * from our downstream. */ NG_MKMESSAGE(msg, NGM_ETHER_COOKIE, NGM_ETHER_GET_ENADDR, 0, M_NOWAIT); if (msg == NULL) return (ENOBUFS); /* * Our hook and peer hook have HK_INVALID flag set, * so we can't use NG_SEND_MSG_HOOK() macro here. */ NG_SEND_MSG_ID(error, privp->node, msg, NG_NODE_ID(NG_PEER_NODE(privp->ethernet_hook)), NG_NODE_ID(privp->node)); return (error); } /* * Get a netgraph control message. * Check it is one we understand. If needed, send a response. * We sometimes save the address for an async action later. * Always free the message. */ static int ng_pppoe_rcvmsg(node_p node, item_p item, hook_p lasthook) { priv_p privp = NG_NODE_PRIVATE(node); struct ngpppoe_init_data *ourmsg = NULL; struct ng_mesg *resp = NULL; int error = 0; hook_p hook = NULL; sessp sp = NULL; negp neg = NULL; struct ng_mesg *msg; NGI_GET_MSG(item, msg); CTR5(KTR_NET, "%20s: node [%x] (%p) got message %d with cookie %d", __func__, node->nd_ID, node, msg->header.cmd, msg->header.typecookie); /* Deal with message according to cookie and command. */ switch (msg->header.typecookie) { case NGM_PPPOE_COOKIE: switch (msg->header.cmd) { case NGM_PPPOE_CONNECT: case NGM_PPPOE_LISTEN: case NGM_PPPOE_OFFER: case NGM_PPPOE_SERVICE: ourmsg = (struct ngpppoe_init_data *)msg->data; if (msg->header.arglen < sizeof(*ourmsg)) { log(LOG_ERR, "ng_pppoe[%x]: init data too " "small\n", node->nd_ID); LEAVE(EMSGSIZE); } if (msg->header.arglen - sizeof(*ourmsg) > PPPOE_SERVICE_NAME_SIZE) { log(LOG_ERR, "ng_pppoe[%x]: service name " "too big\n", node->nd_ID); LEAVE(EMSGSIZE); } if (msg->header.arglen - sizeof(*ourmsg) < ourmsg->data_len) { log(LOG_ERR, "ng_pppoe[%x]: init data has bad " "length, %d should be %zd\n", node->nd_ID, ourmsg->data_len, msg->header.arglen - sizeof (*ourmsg)); LEAVE(EMSGSIZE); } /* Make sure strcmp will terminate safely. */ ourmsg->hook[sizeof(ourmsg->hook) - 1] = '\0'; /* Find hook by name. */ hook = ng_findhook(node, ourmsg->hook); if (hook == NULL) LEAVE(ENOENT); sp = NG_HOOK_PRIVATE(hook); if (sp == NULL) LEAVE(EINVAL); if (msg->header.cmd == NGM_PPPOE_LISTEN) { /* * Ensure we aren't already listening for this * service. */ if (pppoe_find_svc(node, ourmsg->data, ourmsg->data_len) != NULL) LEAVE(EEXIST); } /* - * PPPOE_SERVICE advertisments are set up + * PPPOE_SERVICE advertisements are set up * on sessions that are in PRIMED state. */ if (msg->header.cmd == NGM_PPPOE_SERVICE) break; if (sp->state != PPPOE_SNONE) { log(LOG_NOTICE, "ng_pppoe[%x]: Session already " "active\n", node->nd_ID); LEAVE(EISCONN); } /* * Set up prototype header. */ neg = malloc(sizeof(*neg), M_NETGRAPH_PPPOE, M_NOWAIT | M_ZERO); if (neg == NULL) LEAVE(ENOMEM); neg->m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (neg->m == NULL) { free(neg, M_NETGRAPH_PPPOE); LEAVE(ENOBUFS); } neg->m->m_pkthdr.rcvif = NULL; sp->neg = neg; ng_callout_init(&neg->handle); neg->m->m_len = sizeof(struct pppoe_full_hdr); neg->pkt = mtod(neg->m, union packet*); memcpy((void *)&neg->pkt->pkt_header.eh, &privp->eh, sizeof(struct ether_header)); neg->pkt->pkt_header.ph.ver = 0x1; neg->pkt->pkt_header.ph.type = 0x1; neg->pkt->pkt_header.ph.sid = 0x0000; neg->timeout = 0; sp->creator = NGI_RETADDR(item); } switch (msg->header.cmd) { case NGM_PPPOE_GET_STATUS: { struct ngpppoestat *stats; NG_MKRESPONSE(resp, msg, sizeof(*stats), M_NOWAIT); if (!resp) LEAVE(ENOMEM); stats = (struct ngpppoestat *) resp->data; stats->packets_in = privp->packets_in; stats->packets_out = privp->packets_out; break; } case NGM_PPPOE_CONNECT: { /* * Check the hook exists and is Uninitialised. * Send a PADI request, and start the timeout logic. * Store the originator of this message so we can send * a success of fail message to them later. * Move the session to SINIT. * Set up the session to the correct state and * start it. */ int i, acnlen = 0, acnsep = 0, srvlen; for (i = 0; i < ourmsg->data_len; i++) { if (ourmsg->data[i] == '\\') { acnlen = i; acnsep = 1; break; } } srvlen = ourmsg->data_len - acnlen - acnsep; bcopy(ourmsg->data, neg->ac_name.data, acnlen); neg->ac_name_len = acnlen; neg->service.hdr.tag_type = PTT_SRV_NAME; neg->service.hdr.tag_len = htons((uint16_t)srvlen); bcopy(ourmsg->data + acnlen + acnsep, neg->service.data, srvlen); neg->service_len = srvlen; pppoe_start(sp); break; } case NGM_PPPOE_LISTEN: /* * Check the hook exists and is Uninitialised. * Install the service matching string. * Store the originator of this message so we can send * a success of fail message to them later. * Move the hook to 'LISTENING' */ neg->service.hdr.tag_type = PTT_SRV_NAME; neg->service.hdr.tag_len = htons((uint16_t)ourmsg->data_len); if (ourmsg->data_len) bcopy(ourmsg->data, neg->service.data, ourmsg->data_len); neg->service_len = ourmsg->data_len; neg->pkt->pkt_header.ph.code = PADT_CODE; /* * Wait for PADI packet coming from Ethernet. */ sp->state = PPPOE_LISTENING; LIST_INSERT_HEAD(&privp->listeners, sp, sessions); break; case NGM_PPPOE_OFFER: /* * Check the hook exists and is Uninitialised. * Store the originator of this message so we can send * a success of fail message to them later. * Store the AC-Name given and go to PRIMED. */ neg->ac_name.hdr.tag_type = PTT_AC_NAME; neg->ac_name.hdr.tag_len = htons((uint16_t)ourmsg->data_len); if (ourmsg->data_len) bcopy(ourmsg->data, neg->ac_name.data, ourmsg->data_len); neg->ac_name_len = ourmsg->data_len; neg->pkt->pkt_header.ph.code = PADO_CODE; /* * Wait for PADI packet coming from hook. */ sp->state = PPPOE_PRIMED; break; case NGM_PPPOE_SERVICE: /* * Check the session is primed. * for now just allow ONE service to be advertised. * If you do it twice you just overwrite. */ if (sp->state != PPPOE_PRIMED) { log(LOG_NOTICE, "ng_pppoe[%x]: session not " "primed\n", node->nd_ID); LEAVE(EISCONN); } neg = sp->neg; neg->service.hdr.tag_type = PTT_SRV_NAME; neg->service.hdr.tag_len = htons((uint16_t)ourmsg->data_len); if (ourmsg->data_len) bcopy(ourmsg->data, neg->service.data, ourmsg->data_len); neg->service_len = ourmsg->data_len; break; case NGM_PPPOE_SETMODE: { char *s; size_t len; if (msg->header.arglen == 0) LEAVE(EINVAL); s = (char *)msg->data; len = msg->header.arglen - 1; /* Search for matching mode string. */ if (len == strlen(NG_PPPOE_STANDARD) && (strncmp(NG_PPPOE_STANDARD, s, len) == 0)) { privp->flags = 0; privp->eh.ether_type = ETHERTYPE_PPPOE_DISC; break; } if (len == strlen(NG_PPPOE_3COM) && (strncmp(NG_PPPOE_3COM, s, len) == 0)) { privp->flags |= COMPAT_3COM; privp->eh.ether_type = ETHERTYPE_PPPOE_3COM_DISC; break; } if (len == strlen(NG_PPPOE_DLINK) && (strncmp(NG_PPPOE_DLINK, s, len) == 0)) { privp->flags |= COMPAT_DLINK; break; } error = EINVAL; break; } case NGM_PPPOE_GETMODE: { char *s; size_t len = 0; if (privp->flags == 0) len += strlen(NG_PPPOE_STANDARD) + 1; if (privp->flags & COMPAT_3COM) len += strlen(NG_PPPOE_3COM) + 1; if (privp->flags & COMPAT_DLINK) len += strlen(NG_PPPOE_DLINK) + 1; NG_MKRESPONSE(resp, msg, len, M_NOWAIT); if (resp == NULL) LEAVE(ENOMEM); s = (char *)resp->data; if (privp->flags == 0) { len = strlen(NG_PPPOE_STANDARD); strlcpy(s, NG_PPPOE_STANDARD, len + 1); break; } if (privp->flags & COMPAT_3COM) { len = strlen(NG_PPPOE_3COM); strlcpy(s, NG_PPPOE_3COM, len + 1); s += len; } if (privp->flags & COMPAT_DLINK) { if (s != resp->data) *s++ = '|'; len = strlen(NG_PPPOE_DLINK); strlcpy(s, NG_PPPOE_DLINK, len + 1); } break; } case NGM_PPPOE_SETENADDR: if (msg->header.arglen != ETHER_ADDR_LEN) LEAVE(EINVAL); bcopy(msg->data, &privp->eh.ether_shost, ETHER_ADDR_LEN); break; case NGM_PPPOE_SETMAXP: if (msg->header.arglen != sizeof(uint16_t)) LEAVE(EINVAL); privp->max_payload.hdr.tag_type = PTT_MAX_PAYL; privp->max_payload.hdr.tag_len = htons(sizeof(uint16_t)); privp->max_payload.data = htons(*((uint16_t *)msg->data)); break; default: LEAVE(EINVAL); } break; case NGM_ETHER_COOKIE: if (!(msg->header.flags & NGF_RESP)) LEAVE(EINVAL); switch (msg->header.cmd) { case NGM_ETHER_GET_ENADDR: if (msg->header.arglen != ETHER_ADDR_LEN) LEAVE(EINVAL); bcopy(msg->data, &privp->eh.ether_shost, ETHER_ADDR_LEN); break; default: LEAVE(EINVAL); } break; default: LEAVE(EINVAL); } /* Take care of synchronous response, if any. */ quit: CTR2(KTR_NET, "%20s: returning %d", __func__, error); NG_RESPOND_MSG(error, node, item, resp); /* Free the message and return. */ NG_FREE_MSG(msg); return(error); } /* * Start a client into the first state. A separate function because * it can be needed if the negotiation times out. */ static void pppoe_start(sessp sp) { hook_p hook = sp->hook; node_p node = NG_HOOK_NODE(hook); priv_p privp = NG_NODE_PRIVATE(node); negp neg = sp->neg; struct { struct pppoe_tag hdr; union uniq data; } __packed uniqtag; struct mbuf *m0; int error; /* * Kick the state machine into starting up. */ CTR2(KTR_NET, "%20s: called %d", __func__, sp->Session_ID); sp->state = PPPOE_SINIT; /* * Reset the packet header to broadcast. Since we are * in a client mode use configured ethertype. */ memcpy((void *)&neg->pkt->pkt_header.eh, &privp->eh, sizeof(struct ether_header)); neg->pkt->pkt_header.ph.code = PADI_CODE; uniqtag.hdr.tag_type = PTT_HOST_UNIQ; uniqtag.hdr.tag_len = htons((u_int16_t)sizeof(uniqtag.data)); uniqtag.data.pointer = sp; init_tags(sp); insert_tag(sp, &uniqtag.hdr); insert_tag(sp, &neg->service.hdr); if (privp->max_payload.data != 0) insert_tag(sp, &privp->max_payload.hdr); make_packet(sp); /* * Send packet and prepare to retransmit it after timeout. */ ng_callout(&neg->handle, node, hook, PPPOE_INITIAL_TIMEOUT * hz, pppoe_ticker, NULL, 0); neg->timeout = PPPOE_INITIAL_TIMEOUT * 2; m0 = m_copypacket(neg->m, M_NOWAIT); NG_SEND_DATA_ONLY(error, privp->ethernet_hook, m0); } static int send_acname(sessp sp, const struct pppoe_tag *tag) { int error, tlen; struct ng_mesg *msg; struct ngpppoe_sts *sts; CTR2(KTR_NET, "%20s: called %d", __func__, sp->Session_ID); NG_MKMESSAGE(msg, NGM_PPPOE_COOKIE, NGM_PPPOE_ACNAME, sizeof(struct ngpppoe_sts), M_NOWAIT); if (msg == NULL) return (ENOMEM); sts = (struct ngpppoe_sts *)msg->data; tlen = min(NG_HOOKSIZ - 1, ntohs(tag->tag_len)); strncpy(sts->hook, (const char *)(tag + 1), tlen); sts->hook[tlen] = '\0'; NG_SEND_MSG_ID(error, NG_HOOK_NODE(sp->hook), msg, sp->creator, 0); return (error); } static int send_sessionid(sessp sp) { int error; struct ng_mesg *msg; CTR2(KTR_NET, "%20s: called %d", __func__, sp->Session_ID); NG_MKMESSAGE(msg, NGM_PPPOE_COOKIE, NGM_PPPOE_SESSIONID, sizeof(uint16_t), M_NOWAIT); if (msg == NULL) return (ENOMEM); *(uint16_t *)msg->data = sp->Session_ID; NG_SEND_MSG_ID(error, NG_HOOK_NODE(sp->hook), msg, sp->creator, 0); return (error); } static int send_maxp(sessp sp, const struct pppoe_tag *tag) { int error; struct ng_mesg *msg; struct ngpppoe_maxp *maxp; CTR2(KTR_NET, "%20s: called %d", __func__, sp->Session_ID); NG_MKMESSAGE(msg, NGM_PPPOE_COOKIE, NGM_PPPOE_SETMAXP, sizeof(struct ngpppoe_maxp), M_NOWAIT); if (msg == NULL) return (ENOMEM); maxp = (struct ngpppoe_maxp *)msg->data; strncpy(maxp->hook, NG_HOOK_NAME(sp->hook), NG_HOOKSIZ); maxp->data = ntohs(((const struct maxptag *)tag)->data); NG_SEND_MSG_ID(error, NG_HOOK_NODE(sp->hook), msg, sp->creator, 0); return (error); } /* * Receive data from session hook and do something with it. */ static int ng_pppoe_rcvdata(hook_p hook, item_p item) { node_p node = NG_HOOK_NODE(hook); const priv_p privp = NG_NODE_PRIVATE(node); sessp sp = NG_HOOK_PRIVATE(hook); struct pppoe_full_hdr *wh; struct mbuf *m; int error; CTR6(KTR_NET, "%20s: node [%x] (%p) received %p on \"%s\" (%p)", __func__, node->nd_ID, node, item, hook->hk_name, hook); NGI_GET_M(item, m); switch (sp->state) { case PPPOE_NEWCONNECTED: case PPPOE_CONNECTED: { /* * Remove PPP address and control fields, if any. * For example, ng_ppp(4) always sends LCP packets * with address and control fields as required by * generic PPP. PPPoE is an exception to the rule. */ if (m->m_pkthdr.len >= 2) { if (m->m_len < 2 && !(m = m_pullup(m, 2))) LEAVE(ENOBUFS); if (mtod(m, u_char *)[0] == 0xff && mtod(m, u_char *)[1] == 0x03) m_adj(m, 2); } /* * Bang in a pre-made header, and set the length up * to be correct. Then send it to the ethernet driver. */ M_PREPEND(m, sizeof(*wh), M_NOWAIT); if (m == NULL) LEAVE(ENOBUFS); wh = mtod(m, struct pppoe_full_hdr *); bcopy(&sp->pkt_hdr, wh, sizeof(*wh)); wh->ph.length = htons(m->m_pkthdr.len - sizeof(*wh)); NG_FWD_NEW_DATA(error, item, privp->ethernet_hook, m); privp->packets_out++; break; } case PPPOE_PRIMED: { struct { struct pppoe_tag hdr; union uniq data; } __packed uniqtag; const struct pppoe_tag *tag; struct mbuf *m0; const struct pppoe_hdr *ph; negp neg = sp->neg; uint16_t session; uint16_t length; uint8_t code; /* * A PADI packet is being returned by the application * that has set up this hook. This indicates that it * wants us to offer service. */ if (m->m_len < sizeof(*wh)) { m = m_pullup(m, sizeof(*wh)); if (m == NULL) LEAVE(ENOBUFS); } wh = mtod(m, struct pppoe_full_hdr *); ph = &wh->ph; session = ntohs(wh->ph.sid); length = ntohs(wh->ph.length); code = wh->ph.code; /* Use peers mode in session. */ neg->pkt->pkt_header.eh.ether_type = wh->eh.ether_type; if (code != PADI_CODE) LEAVE(EINVAL); ng_uncallout(&neg->handle, node); /* * This is the first time we hear * from the client, so note it's * unicast address, replacing the * broadcast address. */ bcopy(wh->eh.ether_shost, neg->pkt->pkt_header.eh.ether_dhost, ETHER_ADDR_LEN); sp->state = PPPOE_SOFFER; neg->timeout = 0; neg->pkt->pkt_header.ph.code = PADO_CODE; /* * Start working out the tags to respond with. */ uniqtag.hdr.tag_type = PTT_AC_COOKIE; uniqtag.hdr.tag_len = htons((u_int16_t)sizeof(sp)); uniqtag.data.pointer = sp; init_tags(sp); insert_tag(sp, &neg->ac_name.hdr); /* AC_NAME */ if ((tag = get_tag(ph, PTT_SRV_NAME))) insert_tag(sp, tag); /* return service */ /* * If we have a NULL service request * and have an extra service defined in this hook, * then also add a tag for the extra service. * XXX this is a hack. eventually we should be able * to support advertising many services, not just one */ if (((tag == NULL) || (tag->tag_len == 0)) && (neg->service.hdr.tag_len != 0)) { insert_tag(sp, &neg->service.hdr); /* SERVICE */ } if ((tag = get_tag(ph, PTT_HOST_UNIQ))) insert_tag(sp, tag); /* returned hostunique */ insert_tag(sp, &uniqtag.hdr); scan_tags(sp, ph); make_packet(sp); /* * Send the offer but if they don't respond * in PPPOE_OFFER_TIMEOUT seconds, forget about it. */ ng_callout(&neg->handle, node, hook, PPPOE_OFFER_TIMEOUT * hz, pppoe_ticker, NULL, 0); m0 = m_copypacket(sp->neg->m, M_NOWAIT); NG_FWD_NEW_DATA(error, item, privp->ethernet_hook, m0); privp->packets_out++; break; } /* * Packets coming from the hook make no sense * to sessions in the rest of states. Throw them away. */ default: LEAVE(ENETUNREACH); } quit: if (item) NG_FREE_ITEM(item); NG_FREE_M(m); return (error); } /* * Receive data from ether and do something with it. */ static int ng_pppoe_rcvdata_ether(hook_p hook, item_p item) { node_p node = NG_HOOK_NODE(hook); const priv_p privp = NG_NODE_PRIVATE(node); sessp sp; const struct pppoe_tag *utag = NULL, *tag = NULL; const struct pppoe_full_hdr *wh; const struct pppoe_hdr *ph; negp neg = NULL; struct mbuf *m; hook_p sendhook; int error = 0; uint16_t session; uint16_t length; uint8_t code; struct mbuf *m0; CTR6(KTR_NET, "%20s: node [%x] (%p) received %p on \"%s\" (%p)", __func__, node->nd_ID, node, item, hook->hk_name, hook); NGI_GET_M(item, m); /* * Dig out various fields from the packet. * Use them to decide where to send it. */ privp->packets_in++; if( m->m_len < sizeof(*wh)) { m = m_pullup(m, sizeof(*wh)); /* Checks length */ if (m == NULL) { log(LOG_NOTICE, "ng_pppoe[%x]: couldn't " "m_pullup(wh)\n", node->nd_ID); LEAVE(ENOBUFS); } } wh = mtod(m, struct pppoe_full_hdr *); length = ntohs(wh->ph.length); switch(wh->eh.ether_type) { case ETHERTYPE_PPPOE_3COM_DISC: /* fall through */ case ETHERTYPE_PPPOE_DISC: /* * We need to try to make sure that the tag area * is contiguous, or we could wander off the end * of a buffer and make a mess. * (Linux wouldn't have this problem). */ if (m->m_pkthdr.len <= MHLEN) { if( m->m_len < m->m_pkthdr.len) { m = m_pullup(m, m->m_pkthdr.len); if (m == NULL) { log(LOG_NOTICE, "ng_pppoe[%x]: " "couldn't m_pullup(pkthdr)\n", node->nd_ID); LEAVE(ENOBUFS); } } } if (m->m_len != m->m_pkthdr.len) { /* * It's not all in one piece. * We need to do extra work. * Put it into a cluster. */ struct mbuf *n; n = m_dup(m, M_NOWAIT); m_freem(m); m = n; if (m) { /* just check we got a cluster */ if (m->m_len != m->m_pkthdr.len) { m_freem(m); m = NULL; } } if (m == NULL) { log(LOG_NOTICE, "ng_pppoe[%x]: packet " "fragmented\n", node->nd_ID); LEAVE(EMSGSIZE); } } wh = mtod(m, struct pppoe_full_hdr *); length = ntohs(wh->ph.length); ph = &wh->ph; session = ntohs(wh->ph.sid); code = wh->ph.code; switch(code) { case PADI_CODE: /* * We are a server: * Look for a hook with the required service and send * the ENTIRE packet up there. It should come back to * a new hook in PRIMED state. Look there for further * processing. */ tag = get_tag(ph, PTT_SRV_NAME); if (tag == NULL) { CTR1(KTR_NET, "%20s: PADI w/o Service-Name", __func__); LEAVE(ENETUNREACH); } /* * First, try to match Service-Name against our * listening hooks. If no success and we are in D-Link * compat mode and Service-Name is empty, then we * broadcast the PADI to all listening hooks. */ sendhook = pppoe_match_svc(node, tag); if (sendhook != NULL) NG_FWD_NEW_DATA(error, item, sendhook, m); else if (privp->flags & COMPAT_DLINK && ntohs(tag->tag_len) == 0) error = pppoe_broadcast_padi(node, m); else error = ENETUNREACH; break; case PADO_CODE: /* * We are a client: * Use the host_uniq tag to find the hook this is in * response to. Received #2, now send #3 * For now simply accept the first we receive. */ utag = get_tag(ph, PTT_HOST_UNIQ); if ((utag == NULL) || (ntohs(utag->tag_len) != sizeof(sp))) { log(LOG_NOTICE, "ng_pppoe[%x]: no host " "unique field\n", node->nd_ID); LEAVE(ENETUNREACH); } sendhook = pppoe_finduniq(node, utag); if (sendhook == NULL) { log(LOG_NOTICE, "ng_pppoe[%x]: no " "matching session\n", node->nd_ID); LEAVE(ENETUNREACH); } /* * Check the session is in the right state. * It needs to be in PPPOE_SINIT. */ sp = NG_HOOK_PRIVATE(sendhook); if (sp->state == PPPOE_SREQ || sp->state == PPPOE_CONNECTED) { break; /* Multiple PADO is OK. */ } if (sp->state != PPPOE_SINIT) { log(LOG_NOTICE, "ng_pppoe[%x]: session " "in wrong state\n", node->nd_ID); LEAVE(ENETUNREACH); } neg = sp->neg; /* If requested specific AC-name, check it. */ if (neg->ac_name_len) { tag = get_tag(ph, PTT_AC_NAME); if (!tag) { /* No PTT_AC_NAME in PADO */ break; } if (neg->ac_name_len != htons(tag->tag_len) || strncmp(neg->ac_name.data, (const char *)(tag + 1), neg->ac_name_len) != 0) { break; } } sp->state = PPPOE_SREQ; ng_uncallout(&neg->handle, node); /* * This is the first time we hear * from the server, so note it's * unicast address, replacing the * broadcast address . */ bcopy(wh->eh.ether_shost, neg->pkt->pkt_header.eh.ether_dhost, ETHER_ADDR_LEN); neg->timeout = 0; neg->pkt->pkt_header.ph.code = PADR_CODE; init_tags(sp); insert_tag(sp, utag); /* Host Unique */ if ((tag = get_tag(ph, PTT_AC_COOKIE))) insert_tag(sp, tag); /* return cookie */ if ((tag = get_tag(ph, PTT_AC_NAME))) { insert_tag(sp, tag); /* return it */ send_acname(sp, tag); } if ((tag = get_tag(ph, PTT_MAX_PAYL)) && (privp->max_payload.data != 0)) insert_tag(sp, tag); /* return it */ insert_tag(sp, &neg->service.hdr); /* Service */ scan_tags(sp, ph); make_packet(sp); sp->state = PPPOE_SREQ; ng_callout(&neg->handle, node, sp->hook, PPPOE_INITIAL_TIMEOUT * hz, pppoe_ticker, NULL, 0); neg->timeout = PPPOE_INITIAL_TIMEOUT * 2; m0 = m_copypacket(neg->m, M_NOWAIT); NG_FWD_NEW_DATA(error, item, privp->ethernet_hook, m0); break; case PADR_CODE: /* * We are a server: * Use the ac_cookie tag to find the * hook this is in response to. */ utag = get_tag(ph, PTT_AC_COOKIE); if ((utag == NULL) || (ntohs(utag->tag_len) != sizeof(sp))) { LEAVE(ENETUNREACH); } sendhook = pppoe_finduniq(node, utag); if (sendhook == NULL) LEAVE(ENETUNREACH); /* * Check the session is in the right state. * It needs to be in PPPOE_SOFFER or PPPOE_NEWCONNECTED. * If the latter, then this is a retry by the client, * so be nice, and resend. */ sp = NG_HOOK_PRIVATE(sendhook); if (sp->state == PPPOE_NEWCONNECTED) { /* * Whoa! drop back to resend that PADS packet. * We should still have a copy of it. */ sp->state = PPPOE_SOFFER; } else if (sp->state != PPPOE_SOFFER) LEAVE (ENETUNREACH); neg = sp->neg; ng_uncallout(&neg->handle, node); neg->pkt->pkt_header.ph.code = PADS_CODE; if (sp->Session_ID == 0) { neg->pkt->pkt_header.ph.sid = htons(pppoe_getnewsession(sp)); } send_sessionid(sp); neg->timeout = 0; /* * start working out the tags to respond with. */ init_tags(sp); insert_tag(sp, &neg->ac_name.hdr); /* AC_NAME */ if ((tag = get_tag(ph, PTT_SRV_NAME))) insert_tag(sp, tag);/* return service */ if ((tag = get_tag(ph, PTT_HOST_UNIQ))) insert_tag(sp, tag); /* return it */ insert_tag(sp, utag); /* ac_cookie */ scan_tags(sp, ph); make_packet(sp); sp->state = PPPOE_NEWCONNECTED; /* Send the PADS without a timeout - we're now connected. */ m0 = m_copypacket(sp->neg->m, M_NOWAIT); NG_FWD_NEW_DATA(error, item, privp->ethernet_hook, m0); /* * Having sent the last Negotiation header, * Set up the stored packet header to be correct for * the actual session. But keep the negotialtion stuff * around in case we need to resend this last packet. * We'll discard it when we move from NEWCONNECTED * to CONNECTED */ sp->pkt_hdr = neg->pkt->pkt_header; /* Configure ethertype depending on what * ethertype was used at discovery phase */ if (sp->pkt_hdr.eh.ether_type == ETHERTYPE_PPPOE_3COM_DISC) sp->pkt_hdr.eh.ether_type = ETHERTYPE_PPPOE_3COM_SESS; else sp->pkt_hdr.eh.ether_type = ETHERTYPE_PPPOE_SESS; sp->pkt_hdr.ph.code = 0; pppoe_send_event(sp, NGM_PPPOE_SUCCESS); break; case PADS_CODE: /* * We are a client: * Use the host_uniq tag to find the hook this is in * response to. Take the session ID and store it away. * Also make sure the pre-made header is correct and * set us into Session mode. */ utag = get_tag(ph, PTT_HOST_UNIQ); if ((utag == NULL) || (ntohs(utag->tag_len) != sizeof(sp))) { LEAVE (ENETUNREACH); } sendhook = pppoe_finduniq(node, utag); if (sendhook == NULL) LEAVE(ENETUNREACH); /* * Check the session is in the right state. * It needs to be in PPPOE_SREQ. */ sp = NG_HOOK_PRIVATE(sendhook); if (sp->state != PPPOE_SREQ) LEAVE(ENETUNREACH); neg = sp->neg; ng_uncallout(&neg->handle, node); neg->pkt->pkt_header.ph.sid = wh->ph.sid; sp->Session_ID = ntohs(wh->ph.sid); pppoe_addsession(sp); send_sessionid(sp); neg->timeout = 0; sp->state = PPPOE_CONNECTED; /* * Now we have gone to Connected mode, * Free all resources needed for negotiation. * Keep a copy of the header we will be using. */ sp->pkt_hdr = neg->pkt->pkt_header; if (privp->flags & COMPAT_3COM) sp->pkt_hdr.eh.ether_type = ETHERTYPE_PPPOE_3COM_SESS; else sp->pkt_hdr.eh.ether_type = ETHERTYPE_PPPOE_SESS; sp->pkt_hdr.ph.code = 0; m_freem(neg->m); free(sp->neg, M_NETGRAPH_PPPOE); sp->neg = NULL; if ((tag = get_tag(ph, PTT_MAX_PAYL)) && (privp->max_payload.data != 0)) send_maxp(sp, tag); pppoe_send_event(sp, NGM_PPPOE_SUCCESS); break; case PADT_CODE: /* * Find matching peer/session combination. */ sp = pppoe_findsession(privp, wh); if (sp == NULL) LEAVE(ENETUNREACH); /* Disconnect that hook. */ ng_rmhook_self(sp->hook); break; default: LEAVE(EPFNOSUPPORT); } break; case ETHERTYPE_PPPOE_3COM_SESS: case ETHERTYPE_PPPOE_SESS: /* * Find matching peer/session combination. */ sp = pppoe_findsession(privp, wh); if (sp == NULL) LEAVE (ENETUNREACH); m_adj(m, sizeof(*wh)); /* If packet too short, dump it. */ if (m->m_pkthdr.len < length) LEAVE(EMSGSIZE); /* Also need to trim excess at the end */ if (m->m_pkthdr.len > length) { m_adj(m, -((int)(m->m_pkthdr.len - length))); } if ( sp->state != PPPOE_CONNECTED) { if (sp->state == PPPOE_NEWCONNECTED) { sp->state = PPPOE_CONNECTED; /* * Now we have gone to Connected mode, * Free all resources needed for negotiation. * Be paranoid about whether there may be * a timeout. */ m_freem(sp->neg->m); ng_uncallout(&sp->neg->handle, node); free(sp->neg, M_NETGRAPH_PPPOE); sp->neg = NULL; } else { LEAVE (ENETUNREACH); } } NG_FWD_NEW_DATA(error, item, sp->hook, m); break; default: LEAVE(EPFNOSUPPORT); } quit: if (item) NG_FREE_ITEM(item); NG_FREE_M(m); return (error); } /* * Receive data from debug hook and bypass it to ether. */ static int ng_pppoe_rcvdata_debug(hook_p hook, item_p item) { node_p node = NG_HOOK_NODE(hook); const priv_p privp = NG_NODE_PRIVATE(node); int error; CTR6(KTR_NET, "%20s: node [%x] (%p) received %p on \"%s\" (%p)", __func__, node->nd_ID, node, item, hook->hk_name, hook); NG_FWD_ITEM_HOOK(error, item, privp->ethernet_hook); privp->packets_out++; return (error); } /* * Do local shutdown processing.. - * If we are a persistant device, we might refuse to go away, and + * If we are a persistent device, we might refuse to go away, and * we'd only remove our links and reset ourself. */ static int ng_pppoe_shutdown(node_p node) { const priv_p privp = NG_NODE_PRIVATE(node); int i; for (i = 0; i < SESSHASHSIZE; i++) mtx_destroy(&privp->sesshash[i].mtx); NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(privp->node); free(privp, M_NETGRAPH_PPPOE); return (0); } /* * Hook disconnection * * Clean up all dangling links and information about the session/hook. * For this type, removal of the last link destroys the node. */ static int ng_pppoe_disconnect(hook_p hook) { node_p node = NG_HOOK_NODE(hook); priv_p privp = NG_NODE_PRIVATE(node); sessp sp; if (hook == privp->debug_hook) { privp->debug_hook = NULL; } else if (hook == privp->ethernet_hook) { privp->ethernet_hook = NULL; if (NG_NODE_IS_VALID(node)) ng_rmnode_self(node); } else { sp = NG_HOOK_PRIVATE(hook); if (sp->state != PPPOE_SNONE ) { pppoe_send_event(sp, NGM_PPPOE_CLOSE); } /* * According to the spec, if we are connected, * we should send a DISC packet if we are shutting down * a session. */ if ((privp->ethernet_hook) && ((sp->state == PPPOE_CONNECTED) || (sp->state == PPPOE_NEWCONNECTED))) { struct mbuf *m; /* Generate a packet of that type. */ MGETHDR(m, M_NOWAIT, MT_DATA); if (m == NULL) log(LOG_NOTICE, "ng_pppoe[%x]: session out of " "mbufs\n", node->nd_ID); else { struct pppoe_full_hdr *wh; struct pppoe_tag *tag; int msglen = strlen(SIGNOFF); int error = 0; m->m_pkthdr.rcvif = NULL; m->m_pkthdr.len = m->m_len = sizeof(*wh); wh = mtod(m, struct pppoe_full_hdr *); bcopy(&sp->pkt_hdr, wh, sizeof(*wh)); /* Revert the stored header to DISC/PADT mode. */ wh->ph.code = PADT_CODE; /* * Configure ethertype depending on what * was used during sessions stage. */ if (wh->eh.ether_type == ETHERTYPE_PPPOE_3COM_SESS) wh->eh.ether_type = ETHERTYPE_PPPOE_3COM_DISC; else wh->eh.ether_type = ETHERTYPE_PPPOE_DISC; /* * Add a General error message and adjust * sizes. */ tag = (void *)(&wh->ph + 1); tag->tag_type = PTT_GEN_ERR; tag->tag_len = htons((u_int16_t)msglen); strncpy((char *)(tag + 1), SIGNOFF, msglen); m->m_pkthdr.len = (m->m_len += sizeof(*tag) + msglen); wh->ph.length = htons(sizeof(*tag) + msglen); NG_SEND_DATA_ONLY(error, privp->ethernet_hook, m); } } if (sp->state == PPPOE_LISTENING) LIST_REMOVE(sp, sessions); else if (sp->Session_ID) pppoe_delsession(sp); /* * As long as we have somewhere to store the timeout handle, * we may have a timeout pending.. get rid of it. */ if (sp->neg) { ng_uncallout(&sp->neg->handle, node); if (sp->neg->m) m_freem(sp->neg->m); free(sp->neg, M_NETGRAPH_PPPOE); } free(sp, M_NETGRAPH_PPPOE); NG_HOOK_SET_PRIVATE(hook, NULL); } if ((NG_NODE_NUMHOOKS(node) == 0) && (NG_NODE_IS_VALID(node))) ng_rmnode_self(node); return (0); } /* * Timeouts come here. */ static void pppoe_ticker(node_p node, hook_p hook, void *arg1, int arg2) { priv_p privp = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); sessp sp = NG_HOOK_PRIVATE(hook); negp neg = sp->neg; struct mbuf *m0 = NULL; int error = 0; CTR6(KTR_NET, "%20s: node [%x] (%p) hook \"%s\" (%p) session %d", __func__, node->nd_ID, node, hook->hk_name, hook, sp->Session_ID); switch(sp->state) { /* * Resend the last packet, using an exponential backoff. * After a period of time, stop growing the backoff, * And either leave it, or revert to the start. */ case PPPOE_SINIT: case PPPOE_SREQ: /* Timeouts on these produce resends. */ m0 = m_copypacket(sp->neg->m, M_NOWAIT); NG_SEND_DATA_ONLY( error, privp->ethernet_hook, m0); ng_callout(&neg->handle, node, hook, neg->timeout * hz, pppoe_ticker, NULL, 0); if ((neg->timeout <<= 1) > PPPOE_TIMEOUT_LIMIT) { if (sp->state == PPPOE_SREQ) { /* Revert to SINIT mode. */ pppoe_start(sp); } else { neg->timeout = PPPOE_TIMEOUT_LIMIT; } } break; case PPPOE_PRIMED: case PPPOE_SOFFER: /* A timeout on these says "give up" */ ng_rmhook_self(hook); break; default: /* Timeouts have no meaning in other states. */ log(LOG_NOTICE, "ng_pppoe[%x]: unexpected timeout\n", node->nd_ID); } } /* * Parse an incoming packet to see if any tags should be copied to the * output packet. Don't do any tags that have been handled in the main * state machine. */ static const struct pppoe_tag* scan_tags(sessp sp, const struct pppoe_hdr* ph) { const char *const end = (const char *)next_tag(ph); const char *ptn; const struct pppoe_tag *pt = (const void *)(ph + 1); /* * Keep processing tags while a tag header will still fit. */ CTR2(KTR_NET, "%20s: called %d", __func__, sp->Session_ID); while((const char*)(pt + 1) <= end) { /* * If the tag data would go past the end of the packet, abort. */ ptn = (((const char *)(pt + 1)) + ntohs(pt->tag_len)); if(ptn > end) return NULL; switch (pt->tag_type) { case PTT_RELAY_SID: insert_tag(sp, pt); break; case PTT_EOL: return NULL; case PTT_SRV_NAME: case PTT_AC_NAME: case PTT_HOST_UNIQ: case PTT_AC_COOKIE: case PTT_VENDOR: case PTT_SRV_ERR: case PTT_SYS_ERR: case PTT_GEN_ERR: case PTT_MAX_PAYL: break; } pt = (const struct pppoe_tag*)ptn; } return NULL; } static int pppoe_send_event(sessp sp, enum cmd cmdid) { int error; struct ng_mesg *msg; struct ngpppoe_sts *sts; CTR2(KTR_NET, "%20s: called %d", __func__, sp->Session_ID); NG_MKMESSAGE(msg, NGM_PPPOE_COOKIE, cmdid, sizeof(struct ngpppoe_sts), M_NOWAIT); if (msg == NULL) return (ENOMEM); sts = (struct ngpppoe_sts *)msg->data; strncpy(sts->hook, NG_HOOK_NAME(sp->hook), NG_HOOKSIZ); NG_SEND_MSG_ID(error, NG_HOOK_NODE(sp->hook), msg, sp->creator, 0); return (error); } Index: head/sys/netgraph/ng_pptpgre.c =================================================================== --- head/sys/netgraph/ng_pptpgre.c (revision 298812) +++ head/sys/netgraph/ng_pptpgre.c (revision 298813) @@ -1,983 +1,983 @@ /* * ng_pptpgre.c */ /*- * Copyright (c) 1996-1999 Whistle Communications, Inc. * All rights reserved. * * Subject to the following obligations and disclaimer of warranty, use and * redistribution of this software, in source or object code forms, with or * without modifications are expressly permitted by Whistle Communications; * provided, however, that: * 1. Any and all reproductions of the source or object code must include the * copyright notice above and the following disclaimer of warranties; and * 2. No rights are granted, in any manner or form, to use Whistle * Communications, Inc. trademarks, including the mark "WHISTLE * COMMUNICATIONS" on advertising, endorsements, or otherwise except as * such appears in the above copyright notice or in the software. * * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * Author: Archie Cobbs * * $FreeBSD$ * $Whistle: ng_pptpgre.c,v 1.7 1999/12/08 00:10:06 archie Exp $ */ /* * PPTP/GRE netgraph node type. * * This node type does the GRE encapsulation as specified for the PPTP * protocol (RFC 2637, section 4). This includes sequencing and * retransmission of frames, but not the actual packet delivery nor * any of the TCP control stream protocol. * * The "upper" hook of this node is suitable for attaching to a "ppp" * node link hook. The "lower" hook of this node is suitable for attaching * to a "ksocket" node on hook "inet/raw/gre". */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* GRE packet format, as used by PPTP */ struct greheader { #if BYTE_ORDER == LITTLE_ENDIAN u_char recursion:3; /* recursion control */ u_char ssr:1; /* strict source route */ u_char hasSeq:1; /* sequence number present */ u_char hasKey:1; /* key present */ u_char hasRoute:1; /* routing present */ u_char hasSum:1; /* checksum present */ u_char vers:3; /* version */ u_char flags:4; /* flags */ u_char hasAck:1; /* acknowlege number present */ #elif BYTE_ORDER == BIG_ENDIAN u_char hasSum:1; /* checksum present */ u_char hasRoute:1; /* routing present */ u_char hasKey:1; /* key present */ u_char hasSeq:1; /* sequence number present */ u_char ssr:1; /* strict source route */ u_char recursion:3; /* recursion control */ u_char hasAck:1; /* acknowlege number present */ u_char flags:4; /* flags */ u_char vers:3; /* version */ #else #error BYTE_ORDER is not defined properly #endif u_int16_t proto; /* protocol (ethertype) */ u_int16_t length; /* payload length */ u_int16_t cid; /* call id */ u_int32_t data[0]; /* opt. seq, ack, then data */ }; /* The PPTP protocol ID used in the GRE 'proto' field */ #define PPTP_GRE_PROTO 0x880b /* Bits that must be set a certain way in all PPTP/GRE packets */ #define PPTP_INIT_VALUE ((0x2001 << 16) | PPTP_GRE_PROTO) #define PPTP_INIT_MASK 0xef7fffff /* Min and max packet length */ #define PPTP_MAX_PAYLOAD (0xffff - sizeof(struct greheader) - 8) /* All times are scaled by this (PPTP_TIME_SCALE time units = 1 sec.) */ #define PPTP_TIME_SCALE 1024 /* milliseconds */ typedef u_int64_t pptptime_t; /* Acknowledgment timeout parameters and functions */ #define PPTP_XMIT_WIN 16 /* max xmit window */ #define PPTP_MIN_TIMEOUT (PPTP_TIME_SCALE / 83) /* 12 milliseconds */ #define PPTP_MAX_TIMEOUT (3 * PPTP_TIME_SCALE) /* 3 seconds */ -/* When we recieve a packet, we wait to see if there's an outgoing packet +/* When we receive a packet, we wait to see if there's an outgoing packet we can piggy-back the ACK off of. These parameters determine the mimimum and maxmimum length of time we're willing to wait in order to do that. These have no effect unless "enableDelayedAck" is turned on. */ #define PPTP_MIN_ACK_DELAY (PPTP_TIME_SCALE / 500) /* 2 milliseconds */ #define PPTP_MAX_ACK_DELAY (PPTP_TIME_SCALE / 2) /* 500 milliseconds */ /* See RFC 2637 section 4.4 */ #define PPTP_ACK_ALPHA(x) (((x) + 4) >> 3) /* alpha = 0.125 */ #define PPTP_ACK_BETA(x) (((x) + 2) >> 2) /* beta = 0.25 */ #define PPTP_ACK_CHI(x) ((x) << 2) /* chi = 4 */ #define PPTP_ACK_DELTA(x) ((x) << 1) /* delta = 2 */ #define PPTP_SEQ_DIFF(x,y) ((int32_t)(x) - (int32_t)(y)) #define SESSHASHSIZE 0x0020 #define SESSHASH(x) (((x) ^ ((x) >> 8)) & (SESSHASHSIZE - 1)) /* We keep packet retransmit and acknowlegement state in this struct */ struct ng_pptpgre_sess { node_p node; /* this node pointer */ hook_p hook; /* hook to upper layers */ struct ng_pptpgre_conf conf; /* configuration info */ struct mtx mtx; /* session mutex */ u_int32_t recvSeq; /* last seq # we rcv'd */ u_int32_t xmitSeq; /* last seq # we sent */ u_int32_t recvAck; /* last seq # peer ack'd */ u_int32_t xmitAck; /* last seq # we ack'd */ int32_t ato; /* adaptive time-out value */ int32_t rtt; /* round trip time estimate */ int32_t dev; /* deviation estimate */ u_int16_t xmitWin; /* size of xmit window */ struct callout sackTimer; /* send ack timer */ struct callout rackTimer; /* recv ack timer */ u_int32_t winAck; /* seq when xmitWin will grow */ pptptime_t timeSent[PPTP_XMIT_WIN]; LIST_ENTRY(ng_pptpgre_sess) sessions; }; typedef struct ng_pptpgre_sess *hpriv_p; /* Node private data */ struct ng_pptpgre_private { hook_p upper; /* hook to upper layers */ hook_p lower; /* hook to lower layers */ struct ng_pptpgre_sess uppersess; /* default session for compat */ LIST_HEAD(, ng_pptpgre_sess) sesshash[SESSHASHSIZE]; struct ng_pptpgre_stats stats; /* node statistics */ }; typedef struct ng_pptpgre_private *priv_p; /* Netgraph node methods */ static ng_constructor_t ng_pptpgre_constructor; static ng_rcvmsg_t ng_pptpgre_rcvmsg; static ng_shutdown_t ng_pptpgre_shutdown; static ng_newhook_t ng_pptpgre_newhook; static ng_rcvdata_t ng_pptpgre_rcvdata; static ng_rcvdata_t ng_pptpgre_rcvdata_lower; static ng_disconnect_t ng_pptpgre_disconnect; /* Helper functions */ static int ng_pptpgre_xmit(hpriv_p hpriv, item_p item); static void ng_pptpgre_start_send_ack_timer(hpriv_p hpriv); static void ng_pptpgre_start_recv_ack_timer(hpriv_p hpriv); static void ng_pptpgre_recv_ack_timeout(node_p node, hook_p hook, void *arg1, int arg2); static void ng_pptpgre_send_ack_timeout(node_p node, hook_p hook, void *arg1, int arg2); static hpriv_p ng_pptpgre_find_session(priv_p privp, u_int16_t cid); static void ng_pptpgre_reset(hpriv_p hpriv); static pptptime_t ng_pptpgre_time(void); /* Parse type for struct ng_pptpgre_conf */ static const struct ng_parse_struct_field ng_pptpgre_conf_type_fields[] = NG_PPTPGRE_CONF_TYPE_INFO; static const struct ng_parse_type ng_pptpgre_conf_type = { &ng_parse_struct_type, &ng_pptpgre_conf_type_fields, }; /* Parse type for struct ng_pptpgre_stats */ static const struct ng_parse_struct_field ng_pptpgre_stats_type_fields[] = NG_PPTPGRE_STATS_TYPE_INFO; static const struct ng_parse_type ng_pptp_stats_type = { &ng_parse_struct_type, &ng_pptpgre_stats_type_fields }; /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_pptpgre_cmdlist[] = { { NGM_PPTPGRE_COOKIE, NGM_PPTPGRE_SET_CONFIG, "setconfig", &ng_pptpgre_conf_type, NULL }, { NGM_PPTPGRE_COOKIE, NGM_PPTPGRE_GET_CONFIG, "getconfig", &ng_parse_hint16_type, &ng_pptpgre_conf_type }, { NGM_PPTPGRE_COOKIE, NGM_PPTPGRE_GET_STATS, "getstats", NULL, &ng_pptp_stats_type }, { NGM_PPTPGRE_COOKIE, NGM_PPTPGRE_CLR_STATS, "clrstats", NULL, NULL }, { NGM_PPTPGRE_COOKIE, NGM_PPTPGRE_GETCLR_STATS, "getclrstats", NULL, &ng_pptp_stats_type }, { 0 } }; /* Node type descriptor */ static struct ng_type ng_pptpgre_typestruct = { .version = NG_ABI_VERSION, .name = NG_PPTPGRE_NODE_TYPE, .constructor = ng_pptpgre_constructor, .rcvmsg = ng_pptpgre_rcvmsg, .shutdown = ng_pptpgre_shutdown, .newhook = ng_pptpgre_newhook, .rcvdata = ng_pptpgre_rcvdata, .disconnect = ng_pptpgre_disconnect, .cmdlist = ng_pptpgre_cmdlist, }; NETGRAPH_INIT(pptpgre, &ng_pptpgre_typestruct); #define ERROUT(x) do { error = (x); goto done; } while (0) /************************************************************************ NETGRAPH NODE STUFF ************************************************************************/ /* * Node type constructor */ static int ng_pptpgre_constructor(node_p node) { priv_p priv; int i; /* Allocate private structure */ priv = malloc(sizeof(*priv), M_NETGRAPH, M_WAITOK | M_ZERO); NG_NODE_SET_PRIVATE(node, priv); /* Initialize state */ mtx_init(&priv->uppersess.mtx, "ng_pptp", NULL, MTX_DEF); ng_callout_init(&priv->uppersess.sackTimer); ng_callout_init(&priv->uppersess.rackTimer); priv->uppersess.node = node; for (i = 0; i < SESSHASHSIZE; i++) LIST_INIT(&priv->sesshash[i]); LIST_INSERT_HEAD(&priv->sesshash[0], &priv->uppersess, sessions); /* Done */ return (0); } /* * Give our OK for a hook to be added. */ static int ng_pptpgre_newhook(node_p node, hook_p hook, const char *name) { const priv_p priv = NG_NODE_PRIVATE(node); /* Check hook name */ if (strcmp(name, NG_PPTPGRE_HOOK_UPPER) == 0) { priv->upper = hook; priv->uppersess.hook = hook; NG_HOOK_SET_PRIVATE(hook, &priv->uppersess); } else if (strcmp(name, NG_PPTPGRE_HOOK_LOWER) == 0) { priv->lower = hook; NG_HOOK_SET_RCVDATA(hook, ng_pptpgre_rcvdata_lower); } else { static const char hexdig[16] = "0123456789abcdef"; const char *hex; hpriv_p hpriv; int i, j; uint16_t cid, hash; /* Parse hook name to get session ID */ if (strncmp(name, NG_PPTPGRE_HOOK_SESSION_P, sizeof(NG_PPTPGRE_HOOK_SESSION_P) - 1) != 0) return (EINVAL); hex = name + sizeof(NG_PPTPGRE_HOOK_SESSION_P) - 1; for (cid = i = 0; i < 4; i++) { for (j = 0; j < 16 && hex[i] != hexdig[j]; j++); if (j == 16) return (EINVAL); cid = (cid << 4) | j; } if (hex[i] != '\0') return (EINVAL); hpriv = malloc(sizeof(*hpriv), M_NETGRAPH, M_NOWAIT | M_ZERO); if (hpriv == NULL) return (ENOMEM); /* Initialize state */ mtx_init(&hpriv->mtx, "ng_pptp", NULL, MTX_DEF); ng_callout_init(&hpriv->sackTimer); ng_callout_init(&hpriv->rackTimer); hpriv->conf.cid = cid; hpriv->node = node; hpriv->hook = hook; NG_HOOK_SET_PRIVATE(hook, hpriv); hash = SESSHASH(cid); LIST_INSERT_HEAD(&priv->sesshash[hash], hpriv, sessions); } return (0); } /* * Receive a control message. */ static int ng_pptpgre_rcvmsg(node_p node, item_p item, hook_p lasthook) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_mesg *resp = NULL; int error = 0; struct ng_mesg *msg; NGI_GET_MSG(item, msg); switch (msg->header.typecookie) { case NGM_PPTPGRE_COOKIE: switch (msg->header.cmd) { case NGM_PPTPGRE_SET_CONFIG: { struct ng_pptpgre_conf *const newConf = (struct ng_pptpgre_conf *) msg->data; hpriv_p hpriv; uint16_t hash; /* Check for invalid or illegal config */ if (msg->header.arglen != sizeof(*newConf)) ERROUT(EINVAL); /* Try to find session by cid. */ hpriv = ng_pptpgre_find_session(priv, newConf->cid); /* If not present - use upper. */ if (hpriv == NULL) { hpriv = &priv->uppersess; LIST_REMOVE(hpriv, sessions); hash = SESSHASH(newConf->cid); LIST_INSERT_HEAD(&priv->sesshash[hash], hpriv, sessions); } ng_pptpgre_reset(hpriv); /* reset on configure */ hpriv->conf = *newConf; break; } case NGM_PPTPGRE_GET_CONFIG: { hpriv_p hpriv; if (msg->header.arglen == 2) { /* Try to find session by cid. */ hpriv = ng_pptpgre_find_session(priv, *((uint16_t *)msg->data)); if (hpriv == NULL) ERROUT(EINVAL); } else if (msg->header.arglen == 0) { /* Use upper. */ hpriv = &priv->uppersess; } else ERROUT(EINVAL); NG_MKRESPONSE(resp, msg, sizeof(hpriv->conf), M_NOWAIT); if (resp == NULL) ERROUT(ENOMEM); bcopy(&hpriv->conf, resp->data, sizeof(hpriv->conf)); break; } case NGM_PPTPGRE_GET_STATS: case NGM_PPTPGRE_CLR_STATS: case NGM_PPTPGRE_GETCLR_STATS: { if (msg->header.cmd != NGM_PPTPGRE_CLR_STATS) { NG_MKRESPONSE(resp, msg, sizeof(priv->stats), M_NOWAIT); if (resp == NULL) ERROUT(ENOMEM); bcopy(&priv->stats, resp->data, sizeof(priv->stats)); } if (msg->header.cmd != NGM_PPTPGRE_GET_STATS) bzero(&priv->stats, sizeof(priv->stats)); break; } default: error = EINVAL; break; } break; default: error = EINVAL; break; } done: NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } /* * Receive incoming data on a hook. */ static int ng_pptpgre_rcvdata(hook_p hook, item_p item) { const hpriv_p hpriv = NG_HOOK_PRIVATE(hook); int rval; /* If not configured, reject */ if (!hpriv->conf.enabled) { NG_FREE_ITEM(item); return (ENXIO); } mtx_lock(&hpriv->mtx); rval = ng_pptpgre_xmit(hpriv, item); mtx_assert(&hpriv->mtx, MA_NOTOWNED); return (rval); } /* * Hook disconnection */ static int ng_pptpgre_disconnect(hook_p hook) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); const hpriv_p hpriv = NG_HOOK_PRIVATE(hook); /* Zero out hook pointer */ if (hook == priv->upper) { priv->upper = NULL; priv->uppersess.hook = NULL; } else if (hook == priv->lower) { priv->lower = NULL; } else { /* Reset node (stops timers) */ ng_pptpgre_reset(hpriv); LIST_REMOVE(hpriv, sessions); mtx_destroy(&hpriv->mtx); free(hpriv, M_NETGRAPH); } /* Go away if no longer connected to anything */ if ((NG_NODE_NUMHOOKS(node) == 0) && (NG_NODE_IS_VALID(node))) ng_rmnode_self(node); return (0); } /* * Destroy node */ static int ng_pptpgre_shutdown(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); /* Reset node (stops timers) */ ng_pptpgre_reset(&priv->uppersess); LIST_REMOVE(&priv->uppersess, sessions); mtx_destroy(&priv->uppersess.mtx); free(priv, M_NETGRAPH); /* Decrement ref count */ NG_NODE_UNREF(node); return (0); } /************************************************************************* TRANSMIT AND RECEIVE FUNCTIONS *************************************************************************/ /* * Transmit an outgoing frame, or just an ack if m is NULL. */ static int ng_pptpgre_xmit(hpriv_p hpriv, item_p item) { const priv_p priv = NG_NODE_PRIVATE(hpriv->node); u_char buf[sizeof(struct greheader) + 2 * sizeof(u_int32_t)]; struct greheader *const gre = (struct greheader *)buf; int grelen, error; struct mbuf *m; mtx_assert(&hpriv->mtx, MA_OWNED); if (item) { NGI_GET_M(item, m); } else { m = NULL; } /* Check if there's data */ if (m != NULL) { /* Check if windowing is enabled */ if (hpriv->conf.enableWindowing) { /* Is our transmit window full? */ if ((u_int32_t)PPTP_SEQ_DIFF(hpriv->xmitSeq, hpriv->recvAck) >= hpriv->xmitWin) { priv->stats.xmitDrops++; ERROUT(ENOBUFS); } } /* Sanity check frame length */ if (m->m_pkthdr.len > PPTP_MAX_PAYLOAD) { priv->stats.xmitTooBig++; ERROUT(EMSGSIZE); } } else { priv->stats.xmitLoneAcks++; } /* Build GRE header */ be32enc(gre, PPTP_INIT_VALUE); be16enc(&gre->length, (m != NULL) ? m->m_pkthdr.len : 0); be16enc(&gre->cid, hpriv->conf.peerCid); /* Include sequence number if packet contains any data */ if (m != NULL) { gre->hasSeq = 1; if (hpriv->conf.enableWindowing) { hpriv->timeSent[hpriv->xmitSeq - hpriv->recvAck] = ng_pptpgre_time(); } hpriv->xmitSeq++; be32enc(&gre->data[0], hpriv->xmitSeq); } /* Include acknowledgement (and stop send ack timer) if needed */ if (hpriv->conf.enableAlwaysAck || hpriv->xmitAck != hpriv->recvSeq) { gre->hasAck = 1; be32enc(&gre->data[gre->hasSeq], hpriv->recvSeq); hpriv->xmitAck = hpriv->recvSeq; if (hpriv->conf.enableDelayedAck) ng_uncallout(&hpriv->sackTimer, hpriv->node); } /* Prepend GRE header to outgoing frame */ grelen = sizeof(*gre) + sizeof(u_int32_t) * (gre->hasSeq + gre->hasAck); if (m == NULL) { MGETHDR(m, M_NOWAIT, MT_DATA); if (m == NULL) { priv->stats.memoryFailures++; ERROUT(ENOBUFS); } m->m_len = m->m_pkthdr.len = grelen; m->m_pkthdr.rcvif = NULL; } else { M_PREPEND(m, grelen, M_NOWAIT); if (m == NULL || (m->m_len < grelen && (m = m_pullup(m, grelen)) == NULL)) { priv->stats.memoryFailures++; ERROUT(ENOBUFS); } } bcopy(gre, mtod(m, u_char *), grelen); /* Update stats */ priv->stats.xmitPackets++; priv->stats.xmitOctets += m->m_pkthdr.len; /* * XXX: we should reset timer only after an item has been sent * successfully. */ if (hpriv->conf.enableWindowing && gre->hasSeq && hpriv->xmitSeq == hpriv->recvAck + 1) ng_pptpgre_start_recv_ack_timer(hpriv); mtx_unlock(&hpriv->mtx); /* Deliver packet */ if (item) { NG_FWD_NEW_DATA(error, item, priv->lower, m); } else { NG_SEND_DATA_ONLY(error, priv->lower, m); } return (error); done: mtx_unlock(&hpriv->mtx); NG_FREE_M(m); if (item) NG_FREE_ITEM(item); return (error); } /* * Handle an incoming packet. The packet includes the IP header. */ static int ng_pptpgre_rcvdata_lower(hook_p hook, item_p item) { hpriv_p hpriv; node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); int iphlen, grelen, extralen; const struct greheader *gre; const struct ip *ip; int error = 0; struct mbuf *m; NGI_GET_M(item, m); /* Update stats */ priv->stats.recvPackets++; priv->stats.recvOctets += m->m_pkthdr.len; /* Sanity check packet length */ if (m->m_pkthdr.len < sizeof(*ip) + sizeof(*gre)) { priv->stats.recvRunts++; ERROUT(EINVAL); } /* Safely pull up the complete IP+GRE headers */ if (m->m_len < sizeof(*ip) + sizeof(*gre) && (m = m_pullup(m, sizeof(*ip) + sizeof(*gre))) == NULL) { priv->stats.memoryFailures++; ERROUT(ENOBUFS); } ip = mtod(m, const struct ip *); iphlen = ip->ip_hl << 2; if (m->m_len < iphlen + sizeof(*gre)) { if ((m = m_pullup(m, iphlen + sizeof(*gre))) == NULL) { priv->stats.memoryFailures++; ERROUT(ENOBUFS); } ip = mtod(m, const struct ip *); } gre = (const struct greheader *)((const u_char *)ip + iphlen); grelen = sizeof(*gre) + sizeof(u_int32_t) * (gre->hasSeq + gre->hasAck); if (m->m_pkthdr.len < iphlen + grelen) { priv->stats.recvRunts++; ERROUT(EINVAL); } if (m->m_len < iphlen + grelen) { if ((m = m_pullup(m, iphlen + grelen)) == NULL) { priv->stats.memoryFailures++; ERROUT(ENOBUFS); } ip = mtod(m, const struct ip *); gre = (const struct greheader *)((const u_char *)ip + iphlen); } /* Sanity check packet length and GRE header bits */ extralen = m->m_pkthdr.len - (iphlen + grelen + gre->hasSeq * be16dec(&gre->length)); if (extralen < 0) { priv->stats.recvBadGRE++; ERROUT(EINVAL); } if ((be32dec(gre) & PPTP_INIT_MASK) != PPTP_INIT_VALUE) { priv->stats.recvBadGRE++; ERROUT(EINVAL); } hpriv = ng_pptpgre_find_session(priv, be16dec(&gre->cid)); if (hpriv == NULL || hpriv->hook == NULL || !hpriv->conf.enabled) { priv->stats.recvBadCID++; ERROUT(EINVAL); } mtx_lock(&hpriv->mtx); /* Look for peer ack */ if (gre->hasAck) { const u_int32_t ack = be32dec(&gre->data[gre->hasSeq]); const int index = ack - hpriv->recvAck - 1; long sample; long diff; /* Sanity check ack value */ if (PPTP_SEQ_DIFF(ack, hpriv->xmitSeq) > 0) { priv->stats.recvBadAcks++; goto badAck; /* we never sent it! */ } if (PPTP_SEQ_DIFF(ack, hpriv->recvAck) <= 0) goto badAck; /* ack already timed out */ hpriv->recvAck = ack; /* Update adaptive timeout stuff */ if (hpriv->conf.enableWindowing) { sample = ng_pptpgre_time() - hpriv->timeSent[index]; diff = sample - hpriv->rtt; hpriv->rtt += PPTP_ACK_ALPHA(diff); if (diff < 0) diff = -diff; hpriv->dev += PPTP_ACK_BETA(diff - hpriv->dev); /* +2 to compensate low precision of int math */ hpriv->ato = hpriv->rtt + PPTP_ACK_CHI(hpriv->dev + 2); if (hpriv->ato > PPTP_MAX_TIMEOUT) hpriv->ato = PPTP_MAX_TIMEOUT; else if (hpriv->ato < PPTP_MIN_TIMEOUT) hpriv->ato = PPTP_MIN_TIMEOUT; /* Shift packet transmit times in our transmit window */ bcopy(hpriv->timeSent + index + 1, hpriv->timeSent, sizeof(*hpriv->timeSent) * (PPTP_XMIT_WIN - (index + 1))); /* If we sent an entire window, increase window size */ if (PPTP_SEQ_DIFF(ack, hpriv->winAck) >= 0 && hpriv->xmitWin < PPTP_XMIT_WIN) { hpriv->xmitWin++; hpriv->winAck = ack + hpriv->xmitWin; } /* Stop/(re)start receive ACK timer as necessary */ ng_uncallout(&hpriv->rackTimer, hpriv->node); if (hpriv->recvAck != hpriv->xmitSeq) ng_pptpgre_start_recv_ack_timer(hpriv); } } badAck: /* See if frame contains any data */ if (gre->hasSeq) { const u_int32_t seq = be32dec(&gre->data[0]); /* Sanity check sequence number */ if (PPTP_SEQ_DIFF(seq, hpriv->recvSeq) <= 0) { if (seq == hpriv->recvSeq) priv->stats.recvDuplicates++; else priv->stats.recvOutOfOrder++; mtx_unlock(&hpriv->mtx); ERROUT(EINVAL); } hpriv->recvSeq = seq; /* We need to acknowledge this packet; do it soon... */ if (!(callout_pending(&hpriv->sackTimer))) { /* If delayed ACK is disabled, send it now */ if (!hpriv->conf.enableDelayedAck) { /* ack now */ ng_pptpgre_xmit(hpriv, NULL); /* ng_pptpgre_xmit() drops the mutex */ } else { /* ack later */ ng_pptpgre_start_send_ack_timer(hpriv); mtx_unlock(&hpriv->mtx); } } else mtx_unlock(&hpriv->mtx); /* Trim mbuf down to internal payload */ m_adj(m, iphlen + grelen); if (extralen > 0) m_adj(m, -extralen); mtx_assert(&hpriv->mtx, MA_NOTOWNED); /* Deliver frame to upper layers */ NG_FWD_NEW_DATA(error, item, hpriv->hook, m); } else { priv->stats.recvLoneAcks++; mtx_unlock(&hpriv->mtx); NG_FREE_ITEM(item); NG_FREE_M(m); /* no data to deliver */ } return (error); done: NG_FREE_ITEM(item); NG_FREE_M(m); return (error); } /************************************************************************* TIMER RELATED FUNCTIONS *************************************************************************/ /* * Start a timer for the peer's acknowledging our oldest unacknowledged * sequence number. If we get an ack for this sequence number before * the timer goes off, we cancel the timer. Resets currently running * recv ack timer, if any. */ static void ng_pptpgre_start_recv_ack_timer(hpriv_p hpriv) { int remain, ticks; /* Compute how long until oldest unack'd packet times out, and reset the timer to that time. */ remain = (hpriv->timeSent[0] + hpriv->ato) - ng_pptpgre_time(); if (remain < 0) remain = 0; /* Be conservative: timeout can happen up to 1 tick early */ ticks = howmany(remain * hz, PPTP_TIME_SCALE) + 1; ng_callout(&hpriv->rackTimer, hpriv->node, hpriv->hook, ticks, ng_pptpgre_recv_ack_timeout, hpriv, 0); } /* * The peer has failed to acknowledge the oldest unacknowledged sequence * number within the time allotted. Update our adaptive timeout parameters * and reset/restart the recv ack timer. */ static void ng_pptpgre_recv_ack_timeout(node_p node, hook_p hook, void *arg1, int arg2) { const priv_p priv = NG_NODE_PRIVATE(node); const hpriv_p hpriv = arg1; /* Update adaptive timeout stuff */ priv->stats.recvAckTimeouts++; hpriv->rtt = PPTP_ACK_DELTA(hpriv->rtt) + 1; /* +1 to avoid delta*0 case */ hpriv->ato = hpriv->rtt + PPTP_ACK_CHI(hpriv->dev); if (hpriv->ato > PPTP_MAX_TIMEOUT) hpriv->ato = PPTP_MAX_TIMEOUT; else if (hpriv->ato < PPTP_MIN_TIMEOUT) hpriv->ato = PPTP_MIN_TIMEOUT; /* Reset ack and sliding window */ hpriv->recvAck = hpriv->xmitSeq; /* pretend we got the ack */ hpriv->xmitWin = (hpriv->xmitWin + 1) / 2; /* shrink transmit window */ hpriv->winAck = hpriv->recvAck + hpriv->xmitWin; /* reset win expand time */ } /* * Start the send ack timer. This assumes the timer is not * already running. */ static void ng_pptpgre_start_send_ack_timer(hpriv_p hpriv) { int ackTimeout, ticks; /* Take 1/4 of the estimated round trip time */ ackTimeout = (hpriv->rtt >> 2); if (ackTimeout < PPTP_MIN_ACK_DELAY) ackTimeout = PPTP_MIN_ACK_DELAY; else if (ackTimeout > PPTP_MAX_ACK_DELAY) ackTimeout = PPTP_MAX_ACK_DELAY; /* Be conservative: timeout can happen up to 1 tick early */ ticks = howmany(ackTimeout * hz, PPTP_TIME_SCALE); ng_callout(&hpriv->sackTimer, hpriv->node, hpriv->hook, ticks, ng_pptpgre_send_ack_timeout, hpriv, 0); } /* * We've waited as long as we're willing to wait before sending an * acknowledgement to the peer for received frames. We had hoped to * be able to piggy back our acknowledgement on an outgoing data frame, * but apparently there haven't been any since. So send the ack now. */ static void ng_pptpgre_send_ack_timeout(node_p node, hook_p hook, void *arg1, int arg2) { const hpriv_p hpriv = arg1; mtx_lock(&hpriv->mtx); /* Send a frame with an ack but no payload */ ng_pptpgre_xmit(hpriv, NULL); mtx_assert(&hpriv->mtx, MA_NOTOWNED); } /************************************************************************* MISC FUNCTIONS *************************************************************************/ /* * Find the hook with a given session ID. */ static hpriv_p ng_pptpgre_find_session(priv_p privp, u_int16_t cid) { uint16_t hash = SESSHASH(cid); hpriv_p hpriv = NULL; LIST_FOREACH(hpriv, &privp->sesshash[hash], sessions) { if (hpriv->conf.cid == cid) break; } return (hpriv); } /* * Reset state (must be called with lock held or from writer) */ static void ng_pptpgre_reset(hpriv_p hpriv) { /* Reset adaptive timeout state */ hpriv->ato = PPTP_MAX_TIMEOUT; hpriv->rtt = PPTP_TIME_SCALE / 10; if (hpriv->conf.peerPpd > 1) /* ppd = 0 treat as = 1 */ hpriv->rtt *= hpriv->conf.peerPpd; hpriv->dev = 0; hpriv->xmitWin = (hpriv->conf.recvWin + 1) / 2; if (hpriv->xmitWin < 2) /* often the first packet is lost */ hpriv->xmitWin = 2; /* because the peer isn't ready */ else if (hpriv->xmitWin > PPTP_XMIT_WIN) hpriv->xmitWin = PPTP_XMIT_WIN; hpriv->winAck = hpriv->xmitWin; /* Reset sequence numbers */ hpriv->recvSeq = ~0; hpriv->recvAck = ~0; hpriv->xmitSeq = ~0; hpriv->xmitAck = ~0; /* Stop timers */ ng_uncallout(&hpriv->sackTimer, hpriv->node); ng_uncallout(&hpriv->rackTimer, hpriv->node); } /* * Return the current time scaled & translated to our internally used format. */ static pptptime_t ng_pptpgre_time(void) { struct timeval tv; pptptime_t t; microuptime(&tv); t = (pptptime_t)tv.tv_sec * PPTP_TIME_SCALE; t += tv.tv_usec / (1000000 / PPTP_TIME_SCALE); return(t); } Index: head/sys/netgraph/ng_sample.c =================================================================== --- head/sys/netgraph/ng_sample.c (revision 298812) +++ head/sys/netgraph/ng_sample.c (revision 298813) @@ -1,496 +1,496 @@ /* * ng_sample.c */ /*- * Copyright (c) 1996-1999 Whistle Communications, Inc. * All rights reserved. * * Subject to the following obligations and disclaimer of warranty, use and * redistribution of this software, in source or object code forms, with or * without modifications are expressly permitted by Whistle Communications; * provided, however, that: * 1. Any and all reproductions of the source or object code must include the * copyright notice above and the following disclaimer of warranties; and * 2. No rights are granted, in any manner or form, to use Whistle * Communications, Inc. trademarks, including the mark "WHISTLE * COMMUNICATIONS" on advertising, endorsements, or otherwise except as * such appears in the above copyright notice or in the software. * * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * Author: Julian Elischer * * $FreeBSD$ * $Whistle: ng_sample.c,v 1.13 1999/11/01 09:24:52 julian Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include /* If you do complicated mallocs you may want to do this */ /* and use it for your mallocs */ #ifdef NG_SEPARATE_MALLOC static MALLOC_DEFINE(M_NETGRAPH_XXX, "netgraph_xxx", "netgraph xxx node"); #else #define M_NETGRAPH_XXX M_NETGRAPH #endif /* * This section contains the netgraph method declarations for the * sample node. These methods define the netgraph 'type'. */ static ng_constructor_t ng_xxx_constructor; static ng_rcvmsg_t ng_xxx_rcvmsg; static ng_shutdown_t ng_xxx_shutdown; static ng_newhook_t ng_xxx_newhook; static ng_connect_t ng_xxx_connect; static ng_rcvdata_t ng_xxx_rcvdata; static ng_disconnect_t ng_xxx_disconnect; /* Parse type for struct ngxxxstat */ static const struct ng_parse_struct_field ng_xxx_stat_type_fields[] = NG_XXX_STATS_TYPE_INFO; static const struct ng_parse_type ng_xxx_stat_type = { &ng_parse_struct_type, &ng_xxx_stat_type_fields }; /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_xxx_cmdlist[] = { { NGM_XXX_COOKIE, NGM_XXX_GET_STATUS, "getstatus", NULL, &ng_xxx_stat_type, }, { NGM_XXX_COOKIE, NGM_XXX_SET_FLAG, "setflag", &ng_parse_int32_type, NULL }, { 0 } }; /* Netgraph node type descriptor */ static struct ng_type typestruct = { .version = NG_ABI_VERSION, .name = NG_XXX_NODE_TYPE, .constructor = ng_xxx_constructor, .rcvmsg = ng_xxx_rcvmsg, .shutdown = ng_xxx_shutdown, .newhook = ng_xxx_newhook, /* .findhook = ng_xxx_findhook, */ .connect = ng_xxx_connect, .rcvdata = ng_xxx_rcvdata, .disconnect = ng_xxx_disconnect, .cmdlist = ng_xxx_cmdlist, }; NETGRAPH_INIT(xxx, &typestruct); /* Information we store for each hook on each node */ struct XXX_hookinfo { int dlci; /* The DLCI it represents, -1 == downstream */ int channel; /* The channel representing this DLCI */ hook_p hook; }; /* Information we store for each node */ struct XXX { struct XXX_hookinfo channel[XXX_NUM_DLCIS]; struct XXX_hookinfo downstream_hook; node_p node; /* back pointer to node */ hook_p debughook; u_int packets_in; /* packets in from downstream */ u_int packets_out; /* packets out towards downstream */ u_int32_t flags; }; typedef struct XXX *xxx_p; /* * Allocate the private data structure. The generic node has already * been created. Link them together. We arrive with a reference to the node * i.e. the reference count is incremented for us already. * * If this were a device node than this work would be done in the attach() * routine and the constructor would return EINVAL as you should not be able * to creatednodes that depend on hardware (unless you can add the hardware :) */ static int ng_xxx_constructor(node_p node) { xxx_p privdata; int i; /* Initialize private descriptor */ privdata = malloc(sizeof(*privdata), M_NETGRAPH, M_WAITOK | M_ZERO); for (i = 0; i < XXX_NUM_DLCIS; i++) { privdata->channel[i].dlci = -2; privdata->channel[i].channel = i; } /* Link structs together; this counts as our one reference to *nodep */ NG_NODE_SET_PRIVATE(node, privdata); privdata->node = node; return (0); } /* * Give our ok for a hook to be added... * If we are not running this might kick a device into life. * Possibly decode information out of the hook name. * Add the hook's private info to the hook structure. * (if we had some). In this example, we assume that there is a * an array of structs, called 'channel' in the private info, * one for each active channel. The private * pointer of each hook points to the appropriate XXX_hookinfo struct * so that the source of an input packet is easily identified. * (a dlci is a frame relay channel) */ static int ng_xxx_newhook(node_p node, hook_p hook, const char *name) { const xxx_p xxxp = NG_NODE_PRIVATE(node); const char *cp; int dlci = 0; int chan; #if 0 /* Possibly start up the device if it's not already going */ if ((xxxp->flags & SCF_RUNNING) == 0) { ng_xxx_start_hardware(xxxp); } #endif /* Example of how one might use hooks with embedded numbers: All * hooks start with 'dlci' and have a decimal trailing channel * number up to 4 digits Use the leadin defined int he associated .h * file. */ if (strncmp(name, NG_XXX_HOOK_DLCI_LEADIN, strlen(NG_XXX_HOOK_DLCI_LEADIN)) == 0) { char *eptr; cp = name + strlen(NG_XXX_HOOK_DLCI_LEADIN); if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0')) return (EINVAL); dlci = (int)strtoul(cp, &eptr, 10); if (*eptr != '\0' || dlci < 0 || dlci > 1023) return (EINVAL); /* We have a dlci, now either find it, or allocate it */ for (chan = 0; chan < XXX_NUM_DLCIS; chan++) if (xxxp->channel[chan].dlci == dlci) break; if (chan == XXX_NUM_DLCIS) { for (chan = 0; chan < XXX_NUM_DLCIS; chan++) if (xxxp->channel[chan].dlci == -2) break; if (chan == XXX_NUM_DLCIS) return (ENOBUFS); xxxp->channel[chan].dlci = dlci; } if (xxxp->channel[chan].hook != NULL) return (EADDRINUSE); NG_HOOK_SET_PRIVATE(hook, xxxp->channel + chan); xxxp->channel[chan].hook = hook; return (0); } else if (strcmp(name, NG_XXX_HOOK_DOWNSTREAM) == 0) { /* Example of simple predefined hooks. */ /* do something specific to the downstream connection */ xxxp->downstream_hook.hook = hook; NG_HOOK_SET_PRIVATE(hook, &xxxp->downstream_hook); } else if (strcmp(name, NG_XXX_HOOK_DEBUG) == 0) { /* do something specific to a debug connection */ xxxp->debughook = hook; NG_HOOK_SET_PRIVATE(hook, NULL); } else return (EINVAL); /* not a hook we know about */ return(0); } /* * Get a netgraph control message. - * We actually recieve a queue item that has a pointer to the message. + * We actually receive a queue item that has a pointer to the message. * If we free the item, the message will be freed too, unless we remove * it from the item using NGI_GET_MSG(); * The return address is also stored in the item, as an ng_ID_t, * accessible as NGI_RETADDR(item); * Check it is one we understand. If needed, send a response. * We could save the address for an async action later, but don't here. * Always free the message. * The response should be in a malloc'd region that the caller can 'free'. * A response is not required. * Theoretically you could respond defferently to old message types if * the cookie in the header didn't match what we consider to be current * (so that old userland programs could continue to work). */ static int ng_xxx_rcvmsg(node_p node, item_p item, hook_p lasthook) { const xxx_p xxxp = NG_NODE_PRIVATE(node); struct ng_mesg *resp = NULL; int error = 0; struct ng_mesg *msg; NGI_GET_MSG(item, msg); /* Deal with message according to cookie and command */ switch (msg->header.typecookie) { case NGM_XXX_COOKIE: switch (msg->header.cmd) { case NGM_XXX_GET_STATUS: { struct ngxxxstat *stats; NG_MKRESPONSE(resp, msg, sizeof(*stats), M_NOWAIT); if (!resp) { error = ENOMEM; break; } stats = (struct ngxxxstat *) resp->data; stats->packets_in = xxxp->packets_in; stats->packets_out = xxxp->packets_out; break; } case NGM_XXX_SET_FLAG: if (msg->header.arglen != sizeof(u_int32_t)) { error = EINVAL; break; } xxxp->flags = *((u_int32_t *) msg->data); break; default: error = EINVAL; /* unknown command */ break; } break; default: error = EINVAL; /* unknown cookie type */ break; } /* Take care of synchronous response, if any */ NG_RESPOND_MSG(error, node, item, resp); /* Free the message and return */ NG_FREE_MSG(msg); return(error); } /* * Receive data, and do something with it. * Actually we receive a queue item which holds the data. * If we free the item it will also free the data unless we have * previously disassociated it using the NGI_GET_M() macro. * Possibly send it out on another link after processing. * Possibly do something different if it comes from different * hooks. The caller will never free m, so if we use up this data or * abort we must free it. * * If we want, we may decide to force this data to be queued and reprocessed * at the netgraph NETISR time. * We would do that by setting the HK_QUEUE flag on our hook. We would do that * in the connect() method. */ static int ng_xxx_rcvdata(hook_p hook, item_p item ) { const xxx_p xxxp = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); int chan = -2; int dlci = -2; int error; struct mbuf *m; NGI_GET_M(item, m); if (NG_HOOK_PRIVATE(hook)) { dlci = ((struct XXX_hookinfo *) NG_HOOK_PRIVATE(hook))->dlci; chan = ((struct XXX_hookinfo *) NG_HOOK_PRIVATE(hook))->channel; if (dlci != -1) { /* If received on a DLCI hook process for this * channel and pass it to the downstream module. * Normally one would add a multiplexing header at * the front here */ /* M_PREPEND(....) ; */ /* mtod(m, xxxxxx)->dlci = dlci; */ NG_FWD_NEW_DATA(error, item, xxxp->downstream_hook.hook, m); xxxp->packets_out++; } else { /* data came from the multiplexed link */ dlci = 1; /* get dlci from header */ /* madjust(....) *//* chop off header */ for (chan = 0; chan < XXX_NUM_DLCIS; chan++) if (xxxp->channel[chan].dlci == dlci) break; if (chan == XXX_NUM_DLCIS) { NG_FREE_ITEM(item); NG_FREE_M(m); return (ENETUNREACH); } /* If we were called at splnet, use the following: * NG_SEND_DATA_ONLY(error, otherhook, m); if this * node is running at some SPL other than SPLNET * then you should use instead: error = * ng_queueit(otherhook, m, NULL); m = NULL; * This queues the data using the standard NETISR * system and schedules the data to be picked * up again once the system has moved to SPLNET and * the processing of the data can continue. After * these are run 'm' should be considered * as invalid and NG_SEND_DATA actually zaps them. */ NG_FWD_NEW_DATA(error, item, xxxp->channel[chan].hook, m); xxxp->packets_in++; } } else { /* It's the debug hook, throw it away.. */ if (hook == xxxp->downstream_hook.hook) { NG_FREE_ITEM(item); NG_FREE_M(m); } } return 0; } #if 0 /* * If this were a device node, the data may have been received in response * to some interrupt. * in which case it would probably look as follows: */ devintr() { int error; /* get packet from device and send on */ m = MGET(blah blah) NG_SEND_DATA_ONLY(error, xxxp->upstream_hook.hook, m); /* see note above in xxx_rcvdata() */ /* and ng_xxx_connect() */ } #endif /* 0 */ /* * Do local shutdown processing.. * All our links and the name have already been removed. - * If we are a persistant device, we might refuse to go away. - * In the case of a persistant node we signal the framework that we + * If we are a persistent device, we might refuse to go away. + * In the case of a persistent node we signal the framework that we * are still in business by clearing the NGF_INVALID bit. However * If we find the NGF_REALLY_DIE bit set, this means that * we REALLY need to die (e.g. hardware removed). * This would have been set using the NG_NODE_REALLY_DIE(node) * macro in some device dependent function (not shown here) before * calling ng_rmnode_self(). */ static int ng_xxx_shutdown(node_p node) { const xxx_p privdata = NG_NODE_PRIVATE(node); #ifndef PERSISTANT_NODE NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(node); free(privdata, M_NETGRAPH); #else if (node->nd_flags & NGF_REALLY_DIE) { /* * WE came here because the widget card is being unloaded, - * so stop being persistant. + * so stop being persistent. * Actually undo all the things we did on creation. */ NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(privdata->node); free(privdata, M_NETGRAPH); return (0); } NG_NODE_REVIVE(node); /* tell ng_rmnode() we will persist */ #endif /* PERSISTANT_NODE */ return (0); } /* * This is called once we've already connected a new hook to the other node. * It gives us a chance to balk at the last minute. */ static int ng_xxx_connect(hook_p hook) { #if 0 /* * If we were a driver running at other than splnet then * we should set the QUEUE bit on the edge so that we * will deliver by queing. */ if /*it is the upstream hook */ NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook)); #endif #if 0 /* * If for some reason we want incoming date to be queued * by the NETISR system and delivered later we can set the same bit on * OUR hook. (maybe to allow unwinding of the stack) */ if (NG_HOOK_PRIVATE(hook)) { int dlci; /* * If it's dlci 1023, requeue it so that it's handled * at a lower priority. This is how a node decides to * defer a data message. */ dlci = ((struct XXX_hookinfo *) NG_HOOK_PRIVATE(hook))->dlci; if (dlci == 1023) { NG_HOOK_FORCE_QUEUE(hook); } #endif /* otherwise be really amiable and just say "YUP that's OK by me! " */ return (0); } /* * Hook disconnection * * For this type, removal of the last link destroys the node */ static int ng_xxx_disconnect(hook_p hook) { if (NG_HOOK_PRIVATE(hook)) ((struct XXX_hookinfo *) (NG_HOOK_PRIVATE(hook)))->hook = NULL; if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) /* already shutting down? */ ng_rmnode_self(NG_HOOK_NODE(hook)); return (0); } Index: head/sys/netgraph/ng_source.c =================================================================== --- head/sys/netgraph/ng_source.c (revision 298812) +++ head/sys/netgraph/ng_source.c (revision 298813) @@ -1,915 +1,915 @@ /* * ng_source.c */ /*- * Copyright (c) 2005 Gleb Smirnoff * Copyright 2002 Sandvine Inc. * All rights reserved. * * Subject to the following obligations and disclaimer of warranty, use and * redistribution of this software, in source or object code forms, with or * without modifications are expressly permitted by Sandvine Inc.; provided, * however, that: * 1. Any and all reproductions of the source or object code must include the * copyright notice above and the following disclaimer of warranties; and * 2. No rights are granted, in any manner or form, to use Sandvine Inc. * trademarks, including the mark "SANDVINE" on advertising, endorsements, * or otherwise except as such appears in the above copyright notice or in * the software. * * THIS SOFTWARE IS BEING PROVIDED BY SANDVINE "AS IS", AND TO THE MAXIMUM * EXTENT PERMITTED BY LAW, SANDVINE MAKES NO REPRESENTATIONS OR WARRANTIES, * EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, * ANY AND ALL IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR * PURPOSE, OR NON-INFRINGEMENT. SANDVINE DOES NOT WARRANT, GUARANTEE, OR * MAKE ANY REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE * USE OF THIS SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY * OR OTHERWISE. IN NO EVENT SHALL SANDVINE BE LIABLE FOR ANY DAMAGES * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF SANDVINE IS ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. * * Author: Dave Chapeskie */ #include __FBSDID("$FreeBSD$"); /* * This node is used for high speed packet geneneration. It queues - * all data recieved on its 'input' hook and when told to start via + * all data received on its 'input' hook and when told to start via * a control message it sends the packets out its 'output' hook. In * this way this node can be preloaded with a packet stream which it * can then send continuously as fast as possible. * * Currently it just copies the mbufs as required. It could do various * tricks to try and avoid this. Probably the best performance would * be achieved by modifying the appropriate drivers to be told to * self-re-enqueue packets (e.g. the if_bge driver could reuse the same * transmit descriptors) under control of this node; perhaps via some * flag in the mbuf or some such. The node could peek at an appropriate * ifnet flag to see if such support is available for the connected * interface. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define NG_SOURCE_INTR_TICKS 1 #define NG_SOURCE_DRIVER_IFQ_MAXLEN (4*1024) #define mtod_off(m,off,t) ((t)(mtod((m),caddr_t)+(off))) /* Per node info */ struct privdata { node_p node; hook_p input; hook_p output; struct ng_source_stats stats; struct ifqueue snd_queue; /* packets to send */ struct mbuf *last_packet; /* last pkt in queue */ struct ifnet *output_ifp; struct callout intr_ch; uint64_t packets; /* packets to send */ uint32_t queueOctets; struct ng_source_embed_info embed_timestamp; struct ng_source_embed_cnt_info embed_counter[NG_SOURCE_COUNTERS]; }; typedef struct privdata *sc_p; /* Node flags */ #define NG_SOURCE_ACTIVE (NGF_TYPE1) /* Netgraph methods */ static ng_constructor_t ng_source_constructor; static ng_rcvmsg_t ng_source_rcvmsg; static ng_shutdown_t ng_source_rmnode; static ng_newhook_t ng_source_newhook; static ng_connect_t ng_source_connect; static ng_rcvdata_t ng_source_rcvdata; static ng_disconnect_t ng_source_disconnect; /* Other functions */ static void ng_source_intr(node_p, hook_p, void *, int); static void ng_source_clr_data (sc_p); static int ng_source_start (sc_p, uint64_t); static void ng_source_stop (sc_p); static int ng_source_send (sc_p, int, int *); static int ng_source_store_output_ifp(sc_p, char *); static void ng_source_packet_mod(sc_p, struct mbuf *, int, int, caddr_t, int); static void ng_source_mod_counter(sc_p sc, struct ng_source_embed_cnt_info *cnt, struct mbuf *m, int increment); static int ng_source_dup_mod(sc_p, struct mbuf *, struct mbuf **); /* Parse type for timeval */ static const struct ng_parse_struct_field ng_source_timeval_type_fields[] = { { "tv_sec", &ng_parse_int32_type }, { "tv_usec", &ng_parse_int32_type }, { NULL } }; const struct ng_parse_type ng_source_timeval_type = { &ng_parse_struct_type, &ng_source_timeval_type_fields }; /* Parse type for struct ng_source_stats */ static const struct ng_parse_struct_field ng_source_stats_type_fields[] = NG_SOURCE_STATS_TYPE_INFO; static const struct ng_parse_type ng_source_stats_type = { &ng_parse_struct_type, &ng_source_stats_type_fields }; /* Parse type for struct ng_source_embed_info */ static const struct ng_parse_struct_field ng_source_embed_type_fields[] = NG_SOURCE_EMBED_TYPE_INFO; static const struct ng_parse_type ng_source_embed_type = { &ng_parse_struct_type, &ng_source_embed_type_fields }; /* Parse type for struct ng_source_embed_cnt_info */ static const struct ng_parse_struct_field ng_source_embed_cnt_type_fields[] = NG_SOURCE_EMBED_CNT_TYPE_INFO; static const struct ng_parse_type ng_source_embed_cnt_type = { &ng_parse_struct_type, &ng_source_embed_cnt_type_fields }; /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_source_cmds[] = { { NGM_SOURCE_COOKIE, NGM_SOURCE_GET_STATS, "getstats", NULL, &ng_source_stats_type }, { NGM_SOURCE_COOKIE, NGM_SOURCE_CLR_STATS, "clrstats", NULL, NULL }, { NGM_SOURCE_COOKIE, NGM_SOURCE_GETCLR_STATS, "getclrstats", NULL, &ng_source_stats_type }, { NGM_SOURCE_COOKIE, NGM_SOURCE_START, "start", &ng_parse_uint64_type, NULL }, { NGM_SOURCE_COOKIE, NGM_SOURCE_STOP, "stop", NULL, NULL }, { NGM_SOURCE_COOKIE, NGM_SOURCE_CLR_DATA, "clrdata", NULL, NULL }, { NGM_SOURCE_COOKIE, NGM_SOURCE_SETIFACE, "setiface", &ng_parse_string_type, NULL }, { NGM_SOURCE_COOKIE, NGM_SOURCE_SETPPS, "setpps", &ng_parse_uint32_type, NULL }, { NGM_SOURCE_COOKIE, NGM_SOURCE_SET_TIMESTAMP, "settimestamp", &ng_source_embed_type, NULL }, { NGM_SOURCE_COOKIE, NGM_SOURCE_GET_TIMESTAMP, "gettimestamp", NULL, &ng_source_embed_type }, { NGM_SOURCE_COOKIE, NGM_SOURCE_SET_COUNTER, "setcounter", &ng_source_embed_cnt_type, NULL }, { NGM_SOURCE_COOKIE, NGM_SOURCE_GET_COUNTER, "getcounter", &ng_parse_uint8_type, &ng_source_embed_cnt_type }, { 0 } }; /* Netgraph type descriptor */ static struct ng_type ng_source_typestruct = { .version = NG_ABI_VERSION, .name = NG_SOURCE_NODE_TYPE, .constructor = ng_source_constructor, .rcvmsg = ng_source_rcvmsg, .shutdown = ng_source_rmnode, .newhook = ng_source_newhook, .connect = ng_source_connect, .rcvdata = ng_source_rcvdata, .disconnect = ng_source_disconnect, .cmdlist = ng_source_cmds, }; NETGRAPH_INIT(source, &ng_source_typestruct); static int ng_source_set_autosrc(sc_p, uint32_t); /* * Node constructor */ static int ng_source_constructor(node_p node) { sc_p sc; sc = malloc(sizeof(*sc), M_NETGRAPH, M_WAITOK | M_ZERO); NG_NODE_SET_PRIVATE(node, sc); sc->node = node; sc->snd_queue.ifq_maxlen = 2048; /* XXX not checked */ ng_callout_init(&sc->intr_ch); return (0); } /* * Add a hook */ static int ng_source_newhook(node_p node, hook_p hook, const char *name) { sc_p sc = NG_NODE_PRIVATE(node); if (strcmp(name, NG_SOURCE_HOOK_INPUT) == 0) { sc->input = hook; } else if (strcmp(name, NG_SOURCE_HOOK_OUTPUT) == 0) { sc->output = hook; sc->output_ifp = NULL; bzero(&sc->stats, sizeof(sc->stats)); } else return (EINVAL); return (0); } /* * Hook has been added */ static int ng_source_connect(hook_p hook) { sc_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); struct ng_mesg *msg; int dummy_error = 0; /* * If this is "output" hook, then request information * from our downstream. */ if (hook == sc->output) { NG_MKMESSAGE(msg, NGM_ETHER_COOKIE, NGM_ETHER_GET_IFNAME, 0, M_NOWAIT); if (msg == NULL) return (ENOBUFS); /* * Our hook and peer hook have HK_INVALID flag set, * so we can't use NG_SEND_MSG_HOOK() macro here. */ NG_SEND_MSG_ID(dummy_error, sc->node, msg, NG_NODE_ID(NG_PEER_NODE(sc->output)), NG_NODE_ID(sc->node)); } return (0); } /* * Receive a control message */ static int ng_source_rcvmsg(node_p node, item_p item, hook_p lasthook) { sc_p sc = NG_NODE_PRIVATE(node); struct ng_mesg *msg, *resp = NULL; int error = 0; NGI_GET_MSG(item, msg); switch (msg->header.typecookie) { case NGM_SOURCE_COOKIE: if (msg->header.flags & NGF_RESP) { error = EINVAL; break; } switch (msg->header.cmd) { case NGM_SOURCE_GET_STATS: case NGM_SOURCE_CLR_STATS: case NGM_SOURCE_GETCLR_STATS: { struct ng_source_stats *stats; if (msg->header.cmd != NGM_SOURCE_CLR_STATS) { NG_MKRESPONSE(resp, msg, sizeof(*stats), M_NOWAIT); if (resp == NULL) { error = ENOMEM; goto done; } sc->stats.queueOctets = sc->queueOctets; sc->stats.queueFrames = sc->snd_queue.ifq_len; if ((sc->node->nd_flags & NG_SOURCE_ACTIVE) && !timevalisset(&sc->stats.endTime)) { getmicrotime(&sc->stats.elapsedTime); timevalsub(&sc->stats.elapsedTime, &sc->stats.startTime); } stats = (struct ng_source_stats *)resp->data; bcopy(&sc->stats, stats, sizeof(* stats)); } if (msg->header.cmd != NGM_SOURCE_GET_STATS) bzero(&sc->stats, sizeof(sc->stats)); } break; case NGM_SOURCE_START: { uint64_t packets; if (msg->header.arglen != sizeof(uint64_t)) { error = EINVAL; break; } packets = *(uint64_t *)msg->data; error = ng_source_start(sc, packets); break; } case NGM_SOURCE_STOP: ng_source_stop(sc); break; case NGM_SOURCE_CLR_DATA: ng_source_clr_data(sc); break; case NGM_SOURCE_SETIFACE: { char *ifname = (char *)msg->data; if (msg->header.arglen < 2) { error = EINVAL; break; } ng_source_store_output_ifp(sc, ifname); break; } case NGM_SOURCE_SETPPS: { uint32_t pps; if (msg->header.arglen != sizeof(uint32_t)) { error = EINVAL; break; } pps = *(uint32_t *)msg->data; sc->stats.maxPps = pps; break; } case NGM_SOURCE_SET_TIMESTAMP: { struct ng_source_embed_info *embed; if (msg->header.arglen != sizeof(*embed)) { error = EINVAL; goto done; } embed = (struct ng_source_embed_info *)msg->data; bcopy(embed, &sc->embed_timestamp, sizeof(*embed)); break; } case NGM_SOURCE_GET_TIMESTAMP: { struct ng_source_embed_info *embed; NG_MKRESPONSE(resp, msg, sizeof(*embed), M_NOWAIT); if (resp == NULL) { error = ENOMEM; goto done; } embed = (struct ng_source_embed_info *)resp->data; bcopy(&sc->embed_timestamp, embed, sizeof(*embed)); break; } case NGM_SOURCE_SET_COUNTER: { struct ng_source_embed_cnt_info *embed; if (msg->header.arglen != sizeof(*embed)) { error = EINVAL; goto done; } embed = (struct ng_source_embed_cnt_info *)msg->data; if (embed->index >= NG_SOURCE_COUNTERS || !(embed->width == 1 || embed->width == 2 || embed->width == 4)) { error = EINVAL; goto done; } bcopy(embed, &sc->embed_counter[embed->index], sizeof(*embed)); break; } case NGM_SOURCE_GET_COUNTER: { uint8_t index = *(uint8_t *)msg->data; struct ng_source_embed_cnt_info *embed; if (index >= NG_SOURCE_COUNTERS) { error = EINVAL; goto done; } NG_MKRESPONSE(resp, msg, sizeof(*embed), M_NOWAIT); if (resp == NULL) { error = ENOMEM; goto done; } embed = (struct ng_source_embed_cnt_info *)resp->data; bcopy(&sc->embed_counter[index], embed, sizeof(*embed)); break; } default: error = EINVAL; break; } break; case NGM_ETHER_COOKIE: if (!(msg->header.flags & NGF_RESP)) { error = EINVAL; break; } switch (msg->header.cmd) { case NGM_ETHER_GET_IFNAME: { char *ifname = (char *)msg->data; if (msg->header.arglen < 2) { error = EINVAL; break; } if (ng_source_store_output_ifp(sc, ifname) == 0) ng_source_set_autosrc(sc, 0); break; } default: error = EINVAL; } break; default: error = EINVAL; break; } done: /* Take care of synchronous response, if any. */ NG_RESPOND_MSG(error, node, item, resp); /* Free the message and return. */ NG_FREE_MSG(msg); return (error); } /* * Receive data on a hook * * If data comes in the input hook, enqueue it on the send queue. * If data comes in the output hook, discard it. */ static int ng_source_rcvdata(hook_p hook, item_p item) { sc_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); struct mbuf *m; int error = 0; NGI_GET_M(item, m); NG_FREE_ITEM(item); /* Which hook? */ if (hook == sc->output) { /* discard */ NG_FREE_M(m); return (error); } KASSERT(hook == sc->input, ("%s: no hook!", __func__)); /* Enqueue packet. */ /* XXX should we check IF_QFULL() ? */ _IF_ENQUEUE(&sc->snd_queue, m); sc->queueOctets += m->m_pkthdr.len; sc->last_packet = m; return (0); } /* * Shutdown processing */ static int ng_source_rmnode(node_p node) { sc_p sc = NG_NODE_PRIVATE(node); ng_source_stop(sc); ng_source_clr_data(sc); NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(node); free(sc, M_NETGRAPH); return (0); } /* * Hook disconnection */ static int ng_source_disconnect(hook_p hook) { sc_p sc; sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); KASSERT(sc != NULL, ("%s: null node private", __func__)); if (NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0 || hook == sc->output) ng_rmnode_self(NG_HOOK_NODE(hook)); return (0); } /* * Set sc->output_ifp to point to the struct ifnet of the interface * reached via our output hook. */ static int ng_source_store_output_ifp(sc_p sc, char *ifname) { struct ifnet *ifp; ifp = ifunit(ifname); if (ifp == NULL) { printf("%s: can't find interface %s\n", __func__, ifname); return (EINVAL); } sc->output_ifp = ifp; #if 1 /* XXX mucking with a drivers ifqueue size is ugly but we need it * to queue a lot of packets to get close to line rate on a gigabit * interface with small packets. * XXX we should restore the original value at stop or disconnect */ if (ifp->if_snd.ifq_maxlen < NG_SOURCE_DRIVER_IFQ_MAXLEN) { printf("ng_source: changing ifq_maxlen from %d to %d\n", ifp->if_snd.ifq_maxlen, NG_SOURCE_DRIVER_IFQ_MAXLEN); ifp->if_snd.ifq_maxlen = NG_SOURCE_DRIVER_IFQ_MAXLEN; } #endif return (0); } /* * Set the attached ethernet node's ethernet source address override flag. */ static int ng_source_set_autosrc(sc_p sc, uint32_t flag) { struct ng_mesg *msg; int error = 0; NG_MKMESSAGE(msg, NGM_ETHER_COOKIE, NGM_ETHER_SET_AUTOSRC, sizeof (uint32_t), M_NOWAIT); if (msg == NULL) return(ENOBUFS); *(uint32_t *)msg->data = flag; NG_SEND_MSG_HOOK(error, sc->node, msg, sc->output, 0); return (error); } /* * Clear out the data we've queued */ static void ng_source_clr_data (sc_p sc) { struct mbuf *m; for (;;) { _IF_DEQUEUE(&sc->snd_queue, m); if (m == NULL) break; NG_FREE_M(m); } sc->queueOctets = 0; sc->last_packet = NULL; } /* * Start sending queued data out the output hook */ static int ng_source_start(sc_p sc, uint64_t packets) { if (sc->output_ifp == NULL) { printf("ng_source: start without iface configured\n"); return (ENXIO); } if (sc->node->nd_flags & NG_SOURCE_ACTIVE) return (EBUSY); sc->node->nd_flags |= NG_SOURCE_ACTIVE; sc->packets = packets; timevalclear(&sc->stats.elapsedTime); timevalclear(&sc->stats.endTime); getmicrotime(&sc->stats.startTime); getmicrotime(&sc->stats.lastTime); ng_callout(&sc->intr_ch, sc->node, NULL, 0, ng_source_intr, sc, 0); return (0); } /* * Stop sending queued data out the output hook */ static void ng_source_stop(sc_p sc) { ng_uncallout(&sc->intr_ch, sc->node); sc->node->nd_flags &= ~NG_SOURCE_ACTIVE; getmicrotime(&sc->stats.endTime); sc->stats.elapsedTime = sc->stats.endTime; timevalsub(&sc->stats.elapsedTime, &sc->stats.startTime); } /* * While active called every NG_SOURCE_INTR_TICKS ticks. * Sends as many packets as the interface connected to our * output hook is able to enqueue. */ static void ng_source_intr(node_p node, hook_p hook, void *arg1, int arg2) { sc_p sc = (sc_p)arg1; struct ifqueue *ifq; int packets; KASSERT(sc != NULL, ("%s: null node private", __func__)); if (sc->packets == 0 || sc->output == NULL || (sc->node->nd_flags & NG_SOURCE_ACTIVE) == 0) { ng_source_stop(sc); return; } if (sc->output_ifp != NULL) { ifq = (struct ifqueue *)&sc->output_ifp->if_snd; packets = ifq->ifq_maxlen - ifq->ifq_len; } else packets = sc->snd_queue.ifq_len; if (sc->stats.maxPps != 0) { struct timeval now, elapsed; uint64_t usec; int maxpkt; getmicrotime(&now); elapsed = now; timevalsub(&elapsed, &sc->stats.lastTime); usec = elapsed.tv_sec * 1000000 + elapsed.tv_usec; maxpkt = (uint64_t)sc->stats.maxPps * usec / 1000000; sc->stats.lastTime = now; if (packets > maxpkt) packets = maxpkt; } ng_source_send(sc, packets, NULL); if (sc->packets == 0) ng_source_stop(sc); else ng_callout(&sc->intr_ch, node, NULL, NG_SOURCE_INTR_TICKS, ng_source_intr, sc, 0); } /* * Send packets out our output hook. */ static int ng_source_send(sc_p sc, int tosend, int *sent_p) { struct mbuf *m, *m2; int sent; int error = 0; KASSERT(tosend >= 0, ("%s: negative tosend param", __func__)); KASSERT(sc->node->nd_flags & NG_SOURCE_ACTIVE, ("%s: inactive node", __func__)); if ((uint64_t)tosend > sc->packets) tosend = sc->packets; /* Go through the queue sending packets one by one. */ for (sent = 0; error == 0 && sent < tosend; ++sent) { _IF_DEQUEUE(&sc->snd_queue, m); if (m == NULL) break; /* Duplicate and modify the packet. */ error = ng_source_dup_mod(sc, m, &m2); if (error) { if (error == ENOBUFS) _IF_PREPEND(&sc->snd_queue, m); else _IF_ENQUEUE(&sc->snd_queue, m); break; } /* Re-enqueue the original packet for us. */ _IF_ENQUEUE(&sc->snd_queue, m); sc->stats.outFrames++; sc->stats.outOctets += m2->m_pkthdr.len; NG_SEND_DATA_ONLY(error, sc->output, m2); if (error) break; } sc->packets -= sent; if (sent_p != NULL) *sent_p = sent; return (error); } /* * Modify packet in 'm' by changing 'len' bytes starting at 'offset' * to data in 'cp'. * * The packet data in 'm' must be in a contiguous buffer in a single mbuf. */ static void ng_source_packet_mod(sc_p sc, struct mbuf *m, int offset, int len, caddr_t cp, int flags) { if (len == 0) return; /* Can't modify beyond end of packet. */ /* TODO: Pad packet for this case. */ if (offset + len > m->m_len) return; bcopy(cp, mtod_off(m, offset, caddr_t), len); } static void ng_source_mod_counter(sc_p sc, struct ng_source_embed_cnt_info *cnt, struct mbuf *m, int increment) { caddr_t cp; uint32_t val; val = htonl(cnt->next_val); cp = (caddr_t)&val + sizeof(val) - cnt->width; ng_source_packet_mod(sc, m, cnt->offset, cnt->width, cp, cnt->flags); if (increment) { cnt->next_val += increment; if (increment > 0 && cnt->next_val > cnt->max_val) { cnt->next_val = cnt->min_val - 1 + (cnt->next_val - cnt->max_val); if (cnt->next_val > cnt->max_val) cnt->next_val = cnt->max_val; } else if (increment < 0 && cnt->next_val < cnt->min_val) { cnt->next_val = cnt->max_val + 1 + (cnt->next_val - cnt->min_val); if (cnt->next_val < cnt->min_val) cnt->next_val = cnt->max_val; } } } static int ng_source_dup_mod(sc_p sc, struct mbuf *m0, struct mbuf **m_ptr) { struct mbuf *m; struct ng_source_embed_cnt_info *cnt; struct ng_source_embed_info *ts; int modify; int error = 0; int i, increment; /* Are we going to modify packets? */ modify = sc->embed_timestamp.flags & NGM_SOURCE_EMBED_ENABLE; for (i = 0; !modify && i < NG_SOURCE_COUNTERS; ++i) modify = sc->embed_counter[i].flags & NGM_SOURCE_EMBED_ENABLE; /* Duplicate the packet. */ if (modify) m = m_dup(m0, M_NOWAIT); else m = m_copypacket(m0, M_NOWAIT); if (m == NULL) { error = ENOBUFS; goto done; } *m_ptr = m; if (!modify) goto done; /* Modify the copied packet for sending. */ KASSERT(M_WRITABLE(m), ("%s: packet not writable", __func__)); for (i = 0; i < NG_SOURCE_COUNTERS; ++i) { cnt = &sc->embed_counter[i]; if (cnt->flags & NGM_SOURCE_EMBED_ENABLE) { if ((cnt->flags & NGM_SOURCE_INC_CNT_PER_LIST) == 0 || sc->last_packet == m0) increment = cnt->increment; else increment = 0; ng_source_mod_counter(sc, cnt, m, increment); } } ts = &sc->embed_timestamp; if (ts->flags & NGM_SOURCE_EMBED_ENABLE) { struct timeval now; getmicrotime(&now); now.tv_sec = htonl(now.tv_sec); now.tv_usec = htonl(now.tv_usec); ng_source_packet_mod(sc, m, ts->offset, sizeof (now), (caddr_t)&now, ts->flags); } done: return(error); } Index: head/sys/netgraph/ng_split.h =================================================================== --- head/sys/netgraph/ng_split.h (revision 298812) +++ head/sys/netgraph/ng_split.h (revision 298813) @@ -1,45 +1,45 @@ /*- * * Copyright (c) 1999-2000, Vitaly V Belekhov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice unmodified, this list of conditions, and the following * disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ * */ #ifndef _NETGRAPH_NG_SPLIT_H_ #define _NETGRAPH_NG_SPLIT_H_ /* Node type name and magic cookie */ #define NG_SPLIT_NODE_TYPE "split" #define NGM_SPLIT_COOKIE 949409402 /* My hook names */ #define NG_SPLIT_HOOK_MIXED "mixed" /* Mixed stream (in/out) */ #define NG_SPLIT_HOOK_OUT "out" /* Output to outhook (sending out) */ -#define NG_SPLIT_HOOK_IN "in" /* Input from inhook (recieving) */ +#define NG_SPLIT_HOOK_IN "in" /* Input from inhook (receiving) */ #endif /* _NETGRAPH_NG_SPLIT_H_ */ Index: head/sys/netgraph/ng_tee.c =================================================================== --- head/sys/netgraph/ng_tee.c (revision 298812) +++ head/sys/netgraph/ng_tee.c (revision 298813) @@ -1,395 +1,395 @@ /* * ng_tee.c */ /*- * Copyright (c) 1996-1999 Whistle Communications, Inc. * All rights reserved. * * Subject to the following obligations and disclaimer of warranty, use and * redistribution of this software, in source or object code forms, with or * without modifications are expressly permitted by Whistle Communications; * provided, however, that: * 1. Any and all reproductions of the source or object code must include the * copyright notice above and the following disclaimer of warranties; and * 2. No rights are granted, in any manner or form, to use Whistle * Communications, Inc. trademarks, including the mark "WHISTLE * COMMUNICATIONS" on advertising, endorsements, or otherwise except as * such appears in the above copyright notice or in the software. * * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * Author: Julian Elischer * * $FreeBSD$ * $Whistle: ng_tee.c,v 1.18 1999/11/01 09:24:52 julian Exp $ */ /* * This node is like the tee(1) command and is useful for ``snooping.'' * It has 4 hooks: left, right, left2right, and right2left. Data * entering from the right is passed to the left and duplicated on * right2left, and data entering from the left is passed to the right * and duplicated on left2right. Data entering from left2right is * sent to left, and data from right2left to right. */ #include #include #include #include #include #include #include #include #include #include /* Per hook info */ struct hookinfo { hook_p hook; struct hookinfo *dest, *dup; struct ng_tee_hookstat stats; }; typedef struct hookinfo *hi_p; /* Per node info */ struct privdata { struct hookinfo left; struct hookinfo right; struct hookinfo left2right; struct hookinfo right2left; }; typedef struct privdata *sc_p; /* Netgraph methods */ static ng_constructor_t ng_tee_constructor; static ng_rcvmsg_t ng_tee_rcvmsg; static ng_close_t ng_tee_close; static ng_shutdown_t ng_tee_shutdown; static ng_newhook_t ng_tee_newhook; static ng_rcvdata_t ng_tee_rcvdata; static ng_disconnect_t ng_tee_disconnect; /* Parse type for struct ng_tee_hookstat */ static const struct ng_parse_struct_field ng_tee_hookstat_type_fields[] = NG_TEE_HOOKSTAT_INFO; static const struct ng_parse_type ng_tee_hookstat_type = { &ng_parse_struct_type, &ng_tee_hookstat_type_fields }; /* Parse type for struct ng_tee_stats */ static const struct ng_parse_struct_field ng_tee_stats_type_fields[] = NG_TEE_STATS_INFO(&ng_tee_hookstat_type); static const struct ng_parse_type ng_tee_stats_type = { &ng_parse_struct_type, &ng_tee_stats_type_fields }; /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_tee_cmds[] = { { NGM_TEE_COOKIE, NGM_TEE_GET_STATS, "getstats", NULL, &ng_tee_stats_type }, { NGM_TEE_COOKIE, NGM_TEE_CLR_STATS, "clrstats", NULL, NULL }, { NGM_TEE_COOKIE, NGM_TEE_GETCLR_STATS, "getclrstats", NULL, &ng_tee_stats_type }, { 0 } }; /* Netgraph type descriptor */ static struct ng_type ng_tee_typestruct = { .version = NG_ABI_VERSION, .name = NG_TEE_NODE_TYPE, .constructor = ng_tee_constructor, .rcvmsg = ng_tee_rcvmsg, .close = ng_tee_close, .shutdown = ng_tee_shutdown, .newhook = ng_tee_newhook, .rcvdata = ng_tee_rcvdata, .disconnect = ng_tee_disconnect, .cmdlist = ng_tee_cmds, }; NETGRAPH_INIT(tee, &ng_tee_typestruct); /* * Node constructor */ static int ng_tee_constructor(node_p node) { sc_p privdata; privdata = malloc(sizeof(*privdata), M_NETGRAPH, M_WAITOK | M_ZERO); NG_NODE_SET_PRIVATE(node, privdata); return (0); } /* * Add a hook */ static int ng_tee_newhook(node_p node, hook_p hook, const char *name) { sc_p privdata = NG_NODE_PRIVATE(node); hi_p hinfo; - /* Precalculate internal pathes. */ + /* Precalculate internal paths. */ if (strcmp(name, NG_TEE_HOOK_RIGHT) == 0) { hinfo = &privdata->right; if (privdata->left.dest) privdata->left.dup = privdata->left.dest; privdata->left.dest = hinfo; privdata->right2left.dest = hinfo; } else if (strcmp(name, NG_TEE_HOOK_LEFT) == 0) { hinfo = &privdata->left; if (privdata->right.dest) privdata->right.dup = privdata->right.dest; privdata->right.dest = hinfo; privdata->left2right.dest = hinfo; } else if (strcmp(name, NG_TEE_HOOK_RIGHT2LEFT) == 0) { hinfo = &privdata->right2left; if (privdata->right.dest) privdata->right.dup = hinfo; else privdata->right.dest = hinfo; } else if (strcmp(name, NG_TEE_HOOK_LEFT2RIGHT) == 0) { hinfo = &privdata->left2right; if (privdata->left.dest) privdata->left.dup = hinfo; else privdata->left.dest = hinfo; } else return (EINVAL); hinfo->hook = hook; bzero(&hinfo->stats, sizeof(hinfo->stats)); NG_HOOK_SET_PRIVATE(hook, hinfo); return (0); } /* * Receive a control message */ static int ng_tee_rcvmsg(node_p node, item_p item, hook_p lasthook) { const sc_p sc = NG_NODE_PRIVATE(node); struct ng_mesg *resp = NULL; int error = 0; struct ng_mesg *msg; NGI_GET_MSG(item, msg); switch (msg->header.typecookie) { case NGM_TEE_COOKIE: switch (msg->header.cmd) { case NGM_TEE_GET_STATS: case NGM_TEE_CLR_STATS: case NGM_TEE_GETCLR_STATS: { struct ng_tee_stats *stats; if (msg->header.cmd != NGM_TEE_CLR_STATS) { NG_MKRESPONSE(resp, msg, sizeof(*stats), M_NOWAIT); if (resp == NULL) { error = ENOMEM; goto done; } stats = (struct ng_tee_stats *)resp->data; bcopy(&sc->right.stats, &stats->right, sizeof(stats->right)); bcopy(&sc->left.stats, &stats->left, sizeof(stats->left)); bcopy(&sc->right2left.stats, &stats->right2left, sizeof(stats->right2left)); bcopy(&sc->left2right.stats, &stats->left2right, sizeof(stats->left2right)); } if (msg->header.cmd != NGM_TEE_GET_STATS) { bzero(&sc->right.stats, sizeof(sc->right.stats)); bzero(&sc->left.stats, sizeof(sc->left.stats)); bzero(&sc->right2left.stats, sizeof(sc->right2left.stats)); bzero(&sc->left2right.stats, sizeof(sc->left2right.stats)); } break; } default: error = EINVAL; break; } break; case NGM_FLOW_COOKIE: if (lasthook == sc->left.hook || lasthook == sc->right.hook) { hi_p const hinfo = NG_HOOK_PRIVATE(lasthook); if (hinfo && hinfo->dest) { NGI_MSG(item) = msg; NG_FWD_ITEM_HOOK(error, item, hinfo->dest->hook); return (error); } } break; default: error = EINVAL; break; } done: NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } /* * Receive data on a hook * * If data comes in the right link send a copy out right2left, and then * send the original onwards out through the left link. * Do the opposite for data coming in from the left link. * Data coming in right2left or left2right is forwarded * on through the appropriate destination hook as if it had come * from the other side. */ static int ng_tee_rcvdata(hook_p hook, item_p item) { const hi_p hinfo = NG_HOOK_PRIVATE(hook); hi_p h; int error = 0; struct mbuf *m; m = NGI_M(item); /* Update stats on incoming hook */ hinfo->stats.inOctets += m->m_pkthdr.len; hinfo->stats.inFrames++; /* Duplicate packet if requried */ if (hinfo->dup) { struct mbuf *m2; /* Copy packet (failure will not stop the original)*/ m2 = m_dup(m, M_NOWAIT); if (m2) { /* Deliver duplicate */ h = hinfo->dup; NG_SEND_DATA_ONLY(error, h->hook, m2); if (error == 0) { h->stats.outOctets += m->m_pkthdr.len; h->stats.outFrames++; } } } /* Deliver frame out destination hook */ if (hinfo->dest) { h = hinfo->dest; h->stats.outOctets += m->m_pkthdr.len; h->stats.outFrames++; NG_FWD_ITEM_HOOK(error, item, h->hook); } else NG_FREE_ITEM(item); return (error); } /* * We are going to be shut down soon * * If we have both a left and right hook, then we probably want to extricate * ourselves and leave the two peers still linked to each other. Otherwise we * should just shut down as a normal node would. */ static int ng_tee_close(node_p node) { const sc_p privdata = NG_NODE_PRIVATE(node); if (privdata->left.hook && privdata->right.hook) ng_bypass(privdata->left.hook, privdata->right.hook); return (0); } /* * Shutdown processing */ static int ng_tee_shutdown(node_p node) { const sc_p privdata = NG_NODE_PRIVATE(node); NG_NODE_SET_PRIVATE(node, NULL); free(privdata, M_NETGRAPH); NG_NODE_UNREF(node); return (0); } /* * Hook disconnection */ static int ng_tee_disconnect(hook_p hook) { sc_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); hi_p const hinfo = NG_HOOK_PRIVATE(hook); KASSERT(hinfo != NULL, ("%s: null info", __func__)); hinfo->hook = NULL; - /* Recalculate internal pathes. */ + /* Recalculate internal paths. */ if (sc->left.dest == hinfo) { sc->left.dest = sc->left.dup; sc->left.dup = NULL; } else if (sc->left.dup == hinfo) sc->left.dup = NULL; if (sc->right.dest == hinfo) { sc->right.dest = sc->right.dup; sc->right.dup = NULL; } else if (sc->right.dup == hinfo) sc->right.dup = NULL; if (sc->left2right.dest == hinfo) sc->left2right.dest = NULL; if (sc->right2left.dest == hinfo) sc->right2left.dest = NULL; /* Die when last hook disconnected. */ if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) && NG_NODE_IS_VALID(NG_HOOK_NODE(hook))) ng_rmnode_self(NG_HOOK_NODE(hook)); return (0); } Index: head/sys/netgraph/ng_tty.c =================================================================== --- head/sys/netgraph/ng_tty.c (revision 298812) +++ head/sys/netgraph/ng_tty.c (revision 298813) @@ -1,512 +1,512 @@ /* * ng_tty.c */ /*- * Copyright (c) 1996-1999 Whistle Communications, Inc. * All rights reserved. * * Subject to the following obligations and disclaimer of warranty, use and * redistribution of this software, in source or object code forms, with or * without modifications are expressly permitted by Whistle Communications; * provided, however, that: * 1. Any and all reproductions of the source or object code must include the * copyright notice above and the following disclaimer of warranties; and * 2. No rights are granted, in any manner or form, to use Whistle * Communications, Inc. trademarks, including the mark "WHISTLE * COMMUNICATIONS" on advertising, endorsements, or otherwise except as * such appears in the above copyright notice or in the software. * * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * Author: Archie Cobbs * * Updated by Andrew Thompson for MPSAFE TTY. * * $FreeBSD$ * $Whistle: ng_tty.c,v 1.21 1999/11/01 09:24:52 julian Exp $ */ /* * This file implements TTY hooks to link in to the netgraph system. The node * is created and then passed the callers opened TTY file descriptor number to * NGM_TTY_SET_TTY, this will hook the tty via ttyhook_register(). * * Incoming data is delivered directly to ng_tty via the TTY bypass hook as a * buffer pointer and length, this is converted to a mbuf and passed to the * peer. * * If the TTY device does not support bypass then incoming characters are * delivered to the hook one at a time, each in its own mbuf. You may * optionally define a ``hotchar,'' which causes incoming characters to be * buffered up until either the hotchar is seen or the mbuf is full (MHLEN * bytes). Then all buffered characters are immediately delivered. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Per-node private info */ struct ngt_softc { struct tty *tp; /* Terminal device */ node_p node; /* Netgraph node */ hook_p hook; /* Netgraph hook */ struct ifqueue outq; /* Queue of outgoing data */ size_t outqlen; /* Number of bytes in outq */ struct mbuf *m; /* Incoming non-bypass data buffer */ short hotchar; /* Hotchar, or -1 if none */ u_int flags; /* Flags */ }; typedef struct ngt_softc *sc_p; /* Flags */ #define FLG_DEBUG 0x0002 /* Netgraph methods */ static ng_constructor_t ngt_constructor; static ng_rcvmsg_t ngt_rcvmsg; static ng_shutdown_t ngt_shutdown; static ng_newhook_t ngt_newhook; static ng_connect_t ngt_connect; static ng_rcvdata_t ngt_rcvdata; static ng_disconnect_t ngt_disconnect; #define ERROUT(x) do { error = (x); goto done; } while (0) static th_getc_inject_t ngt_getc_inject; static th_getc_poll_t ngt_getc_poll; static th_rint_t ngt_rint; static th_rint_bypass_t ngt_rint_bypass; static th_rint_poll_t ngt_rint_poll; static struct ttyhook ngt_hook = { .th_getc_inject = ngt_getc_inject, .th_getc_poll = ngt_getc_poll, .th_rint = ngt_rint, .th_rint_bypass = ngt_rint_bypass, .th_rint_poll = ngt_rint_poll, }; /* Netgraph node type descriptor */ static struct ng_type typestruct = { .version = NG_ABI_VERSION, .name = NG_TTY_NODE_TYPE, .constructor = ngt_constructor, .rcvmsg = ngt_rcvmsg, .shutdown = ngt_shutdown, .newhook = ngt_newhook, .connect = ngt_connect, .rcvdata = ngt_rcvdata, .disconnect = ngt_disconnect, }; NETGRAPH_INIT(tty, &typestruct); #define NGTLOCK(sc) IF_LOCK(&sc->outq) #define NGTUNLOCK(sc) IF_UNLOCK(&sc->outq) /****************************************************************** NETGRAPH NODE METHODS ******************************************************************/ /* * Initialize a new node of this type. * * We only allow nodes to be created as a result of setting * the line discipline on a tty, so always return an error if not. */ static int ngt_constructor(node_p node) { sc_p sc; /* Allocate private structure */ sc = malloc(sizeof(*sc), M_NETGRAPH, M_WAITOK | M_ZERO); NG_NODE_SET_PRIVATE(node, sc); sc->node = node; mtx_init(&sc->outq.ifq_mtx, "ng_tty node+queue", NULL, MTX_DEF); IFQ_SET_MAXLEN(&sc->outq, ifqmaxlen); return (0); } /* * Add a new hook. There can only be one. */ static int ngt_newhook(node_p node, hook_p hook, const char *name) { const sc_p sc = NG_NODE_PRIVATE(node); if (strcmp(name, NG_TTY_HOOK)) return (EINVAL); if (sc->hook) return (EISCONN); NGTLOCK(sc); sc->hook = hook; NGTUNLOCK(sc); return (0); } /* * Set the hook into queueing mode (for outgoing packets), - * so that we wont deliver mbuf thru the whole graph holding + * so that we wont deliver mbuf through the whole graph holding * tty locks. */ static int ngt_connect(hook_p hook) { NG_HOOK_FORCE_QUEUE(hook); return (0); } /* * Disconnect the hook */ static int ngt_disconnect(hook_p hook) { const sc_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); if (hook != sc->hook) panic("%s", __func__); NGTLOCK(sc); sc->hook = NULL; NGTUNLOCK(sc); return (0); } /* * Remove this node. The does the netgraph portion of the shutdown. */ static int ngt_shutdown(node_p node) { const sc_p sc = NG_NODE_PRIVATE(node); struct tty *tp; tp = sc->tp; if (tp != NULL) { tty_lock(tp); ttyhook_unregister(tp); } /* Free resources */ IF_DRAIN(&sc->outq); mtx_destroy(&(sc)->outq.ifq_mtx); NG_NODE_UNREF(sc->node); free(sc, M_NETGRAPH); return (0); } /* * Receive control message */ static int ngt_rcvmsg(node_p node, item_p item, hook_p lasthook) { struct proc *p; const sc_p sc = NG_NODE_PRIVATE(node); struct ng_mesg *msg, *resp = NULL; int error = 0; NGI_GET_MSG(item, msg); switch (msg->header.typecookie) { case NGM_TTY_COOKIE: switch (msg->header.cmd) { case NGM_TTY_SET_TTY: if (sc->tp != NULL) return (EBUSY); p = pfind(((int *)msg->data)[0]); if (p == NULL || (p->p_flag & P_WEXIT)) return (ESRCH); _PHOLD(p); PROC_UNLOCK(p); error = ttyhook_register(&sc->tp, p, ((int *)msg->data)[1], &ngt_hook, sc); PRELE(p); if (error != 0) return (error); break; case NGM_TTY_SET_HOTCHAR: { int hotchar; if (msg->header.arglen != sizeof(int)) ERROUT(EINVAL); hotchar = *((int *) msg->data); if (hotchar != (u_char) hotchar && hotchar != -1) ERROUT(EINVAL); sc->hotchar = hotchar; /* race condition is OK */ break; } case NGM_TTY_GET_HOTCHAR: NG_MKRESPONSE(resp, msg, sizeof(int), M_NOWAIT); if (!resp) ERROUT(ENOMEM); /* Race condition here is OK */ *((int *) resp->data) = sc->hotchar; break; default: ERROUT(EINVAL); } break; default: ERROUT(EINVAL); } done: NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } /* * Receive incoming data from netgraph system. Put it on our * output queue and start output if necessary. */ static int ngt_rcvdata(hook_p hook, item_p item) { const sc_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); struct tty *tp = sc->tp; struct mbuf *m; if (hook != sc->hook) panic("%s", __func__); NGI_GET_M(item, m); NG_FREE_ITEM(item); if (tp == NULL) { NG_FREE_M(m); return (ENXIO); } IF_LOCK(&sc->outq); if (_IF_QFULL(&sc->outq)) { IF_UNLOCK(&sc->outq); NG_FREE_M(m); return (ENOBUFS); } _IF_ENQUEUE(&sc->outq, m); sc->outqlen += m->m_pkthdr.len; IF_UNLOCK(&sc->outq); /* notify the TTY that data is ready */ tty_lock(tp); if (!tty_gone(tp)) ttydevsw_outwakeup(tp); tty_unlock(tp); return (0); } static size_t ngt_getc_inject(struct tty *tp, void *buf, size_t len) { sc_p sc = ttyhook_softc(tp); size_t total = 0; int length; while (len) { struct mbuf *m; /* Remove first mbuf from queue */ IF_DEQUEUE(&sc->outq, m); if (m == NULL) break; /* Send as much of it as possible */ while (m != NULL) { length = min(m->m_len, len); memcpy((char *)buf + total, mtod(m, char *), length); m->m_data += length; m->m_len -= length; total += length; len -= length; if (m->m_len > 0) break; /* device can't take any more */ m = m_free(m); } /* Put remainder of mbuf chain (if any) back on queue */ if (m != NULL) { IF_PREPEND(&sc->outq, m); break; } } IF_LOCK(&sc->outq); sc->outqlen -= total; IF_UNLOCK(&sc->outq); MPASS(sc->outqlen >= 0); return (total); } static size_t ngt_getc_poll(struct tty *tp) { sc_p sc = ttyhook_softc(tp); return (sc->outqlen); } /* * Optimised TTY input. * * We get a buffer pointer to hopefully a complete data frame. Do not check for * the hotchar, just pass it on. */ static size_t ngt_rint_bypass(struct tty *tp, const void *buf, size_t len) { sc_p sc = ttyhook_softc(tp); node_p node = sc->node; struct mbuf *m, *mb; size_t total = 0; int error = 0, length; tty_lock_assert(tp, MA_OWNED); if (sc->hook == NULL) return (0); m = m_getm2(NULL, len, M_NOWAIT, MT_DATA, M_PKTHDR); if (m == NULL) { if (sc->flags & FLG_DEBUG) log(LOG_ERR, "%s: can't get mbuf\n", NG_NODE_NAME(node)); return (0); } m->m_pkthdr.rcvif = NULL; for (mb = m; mb != NULL; mb = mb->m_next) { length = min(M_TRAILINGSPACE(mb), len - total); memcpy(mtod(m, char *), (const char *)buf + total, length); mb->m_len = length; total += length; m->m_pkthdr.len += length; } if (sc->m != NULL) { /* * Odd, we have changed from non-bypass to bypass. It is * unlikely but not impossible, flush the data first. */ sc->m->m_data = sc->m->m_pktdat; NG_SEND_DATA_ONLY(error, sc->hook, sc->m); sc->m = NULL; } NG_SEND_DATA_ONLY(error, sc->hook, m); return (total); } /* * Receive data coming from the device one char at a time, when it is not in * bypass mode. */ static int ngt_rint(struct tty *tp, char c, int flags) { sc_p sc = ttyhook_softc(tp); node_p node = sc->node; struct mbuf *m; int error = 0; tty_lock_assert(tp, MA_OWNED); if (sc->hook == NULL) return (0); if (flags != 0) { /* framing error or overrun on this char */ if (sc->flags & FLG_DEBUG) log(LOG_DEBUG, "%s: line error %x\n", NG_NODE_NAME(node), flags); return (0); } /* Get a new header mbuf if we need one */ if (!(m = sc->m)) { MGETHDR(m, M_NOWAIT, MT_DATA); if (!m) { if (sc->flags & FLG_DEBUG) log(LOG_ERR, "%s: can't get mbuf\n", NG_NODE_NAME(node)); return (ENOBUFS); } m->m_len = m->m_pkthdr.len = 0; m->m_pkthdr.rcvif = NULL; sc->m = m; } /* Add char to mbuf */ *mtod(m, u_char *) = c; m->m_data++; m->m_len++; m->m_pkthdr.len++; /* Ship off mbuf if it's time */ if (sc->hotchar == -1 || c == sc->hotchar || m->m_len >= MHLEN) { m->m_data = m->m_pktdat; sc->m = NULL; NG_SEND_DATA_ONLY(error, sc->hook, m); /* Will queue */ } return (error); } static size_t ngt_rint_poll(struct tty *tp) { /* We can always accept input */ return (1); } Index: head/sys/netgraph/ng_vjc.c =================================================================== --- head/sys/netgraph/ng_vjc.c (revision 298812) +++ head/sys/netgraph/ng_vjc.c (revision 298813) @@ -1,614 +1,614 @@ /* * ng_vjc.c */ /*- * Copyright (c) 1996-1999 Whistle Communications, Inc. * All rights reserved. * * Subject to the following obligations and disclaimer of warranty, use and * redistribution of this software, in source or object code forms, with or * without modifications are expressly permitted by Whistle Communications; * provided, however, that: * 1. Any and all reproductions of the source or object code must include the * copyright notice above and the following disclaimer of warranties; and * 2. No rights are granted, in any manner or form, to use Whistle * Communications, Inc. trademarks, including the mark "WHISTLE * COMMUNICATIONS" on advertising, endorsements, or otherwise except as * such appears in the above copyright notice or in the software. * * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * Author: Archie Cobbs * * $FreeBSD$ * $Whistle: ng_vjc.c,v 1.17 1999/11/01 09:24:52 julian Exp $ */ /* * This node performs Van Jacobson IP header (de)compression. * You must have included net/slcompress.c in your kernel compilation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Check agreement with slcompress.c */ #if MAX_STATES != NG_VJC_MAX_CHANNELS #error NG_VJC_MAX_CHANNELS must be the same as MAX_STATES #endif /* Maximum length of a compressed TCP VJ header */ #define MAX_VJHEADER 19 /* Node private data */ struct ng_vjc_private { struct ngm_vjc_config conf; struct slcompress slc; hook_p ip; hook_p vjcomp; hook_p vjuncomp; hook_p vjip; }; typedef struct ng_vjc_private *priv_p; #define ERROUT(x) do { error = (x); goto done; } while (0) /* Netgraph node methods */ static ng_constructor_t ng_vjc_constructor; static ng_rcvmsg_t ng_vjc_rcvmsg; static ng_shutdown_t ng_vjc_shutdown; static ng_newhook_t ng_vjc_newhook; static ng_rcvdata_t ng_vjc_rcvdata; static ng_disconnect_t ng_vjc_disconnect; /* Helper stuff */ static struct mbuf *ng_vjc_pulluphdrs(struct mbuf *m, int knownTCP); /* Parse type for struct ngm_vjc_config */ static const struct ng_parse_struct_field ng_vjc_config_type_fields[] = NG_VJC_CONFIG_TYPE_INFO; static const struct ng_parse_type ng_vjc_config_type = { &ng_parse_struct_type, &ng_vjc_config_type_fields }; /* Parse type for the 'last_cs' and 'cs_next' fields in struct slcompress, which are pointers converted to integer indices, so parse them that way. */ #ifndef __LP64__ #define NG_VJC_TSTATE_PTR_TYPE &ng_parse_uint32_type #else #define NG_VJC_TSTATE_PTR_TYPE &ng_parse_uint64_type #endif /* Parse type for the 'cs_hdr' field in a struct cstate. Ideally we would like to use a 'struct ip' type instead of a simple array of bytes. */ static const struct ng_parse_fixedarray_info ng_vjc_cs_hdr_type_info = { &ng_parse_hint8_type, MAX_HDR }; static const struct ng_parse_type ng_vjc_cs_hdr_type = { &ng_parse_fixedarray_type, &ng_vjc_cs_hdr_type_info }; /* Parse type for a struct cstate */ static const struct ng_parse_struct_field ng_vjc_cstate_type_fields[] = { { "cs_next", NG_VJC_TSTATE_PTR_TYPE }, { "cs_hlen", &ng_parse_uint16_type }, { "cs_id", &ng_parse_uint8_type }, { "cs_filler", &ng_parse_uint8_type }, { "cs_hdr", &ng_vjc_cs_hdr_type }, { NULL } }; static const struct ng_parse_type ng_vjc_cstate_type = { &ng_parse_struct_type, &ng_vjc_cstate_type_fields }; /* Parse type for an array of MAX_STATES struct cstate's, ie, tstate & rstate */ static const struct ng_parse_fixedarray_info ng_vjc_cstatearray_type_info = { &ng_vjc_cstate_type, MAX_STATES }; static const struct ng_parse_type ng_vjc_cstatearray_type = { &ng_parse_fixedarray_type, &ng_vjc_cstatearray_type_info }; /* Parse type for struct slcompress. Keep this in sync with the definition of struct slcompress defined in */ static const struct ng_parse_struct_field ng_vjc_slcompress_type_fields[] = { { "last_cs", NG_VJC_TSTATE_PTR_TYPE }, { "last_recv", &ng_parse_uint8_type }, { "last_xmit", &ng_parse_uint8_type }, { "flags", &ng_parse_hint16_type }, #ifndef SL_NO_STATS { "sls_packets", &ng_parse_uint32_type }, { "sls_compressed", &ng_parse_uint32_type }, { "sls_searches", &ng_parse_uint32_type }, { "sls_misses", &ng_parse_uint32_type }, { "sls_uncompressedin", &ng_parse_uint32_type }, { "sls_compressedin", &ng_parse_uint32_type }, { "sls_errorin", &ng_parse_uint32_type }, { "sls_tossed", &ng_parse_uint32_type }, #endif { "tstate", &ng_vjc_cstatearray_type }, { "rstate", &ng_vjc_cstatearray_type }, { NULL } }; static const struct ng_parse_type ng_vjc_slcompress_type = { &ng_parse_struct_type, &ng_vjc_slcompress_type_fields }; /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_vjc_cmds[] = { { NGM_VJC_COOKIE, NGM_VJC_SET_CONFIG, "setconfig", &ng_vjc_config_type, NULL }, { NGM_VJC_COOKIE, NGM_VJC_GET_CONFIG, "getconfig", NULL, &ng_vjc_config_type, }, { NGM_VJC_COOKIE, NGM_VJC_GET_STATE, "getstate", NULL, &ng_vjc_slcompress_type, }, { NGM_VJC_COOKIE, NGM_VJC_CLR_STATS, "clrstats", NULL, NULL, }, { NGM_VJC_COOKIE, NGM_VJC_RECV_ERROR, "recverror", NULL, NULL, }, { 0 } }; /* Node type descriptor */ static struct ng_type ng_vjc_typestruct = { .version = NG_ABI_VERSION, .name = NG_VJC_NODE_TYPE, .constructor = ng_vjc_constructor, .rcvmsg = ng_vjc_rcvmsg, .shutdown = ng_vjc_shutdown, .newhook = ng_vjc_newhook, .rcvdata = ng_vjc_rcvdata, .disconnect = ng_vjc_disconnect, .cmdlist = ng_vjc_cmds, }; NETGRAPH_INIT(vjc, &ng_vjc_typestruct); /************************************************************************ NETGRAPH NODE METHODS ************************************************************************/ /* * Create a new node */ static int ng_vjc_constructor(node_p node) { priv_p priv; /* Allocate private structure */ priv = malloc(sizeof(*priv), M_NETGRAPH, M_WAITOK | M_ZERO); NG_NODE_SET_PRIVATE(node, priv); /* slcompress is not thread-safe. Protect it's state here. */ NG_NODE_FORCE_WRITER(node); /* Done */ return (0); } /* * Add a new hook */ static int ng_vjc_newhook(node_p node, hook_p hook, const char *name) { const priv_p priv = NG_NODE_PRIVATE(node); hook_p *hookp; /* Get hook */ if (strcmp(name, NG_VJC_HOOK_IP) == 0) hookp = &priv->ip; else if (strcmp(name, NG_VJC_HOOK_VJCOMP) == 0) hookp = &priv->vjcomp; else if (strcmp(name, NG_VJC_HOOK_VJUNCOMP) == 0) hookp = &priv->vjuncomp; else if (strcmp(name, NG_VJC_HOOK_VJIP) == 0) hookp = &priv->vjip; else return (EINVAL); /* See if already connected */ if (*hookp) return (EISCONN); /* OK */ *hookp = hook; return (0); } /* * Receive a control message */ static int ng_vjc_rcvmsg(node_p node, item_p item, hook_p lasthook) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_mesg *resp = NULL; int error = 0; struct ng_mesg *msg; NGI_GET_MSG(item, msg); /* Check type cookie */ switch (msg->header.typecookie) { case NGM_VJC_COOKIE: switch (msg->header.cmd) { case NGM_VJC_SET_CONFIG: { struct ngm_vjc_config *const c = (struct ngm_vjc_config *) msg->data; if (msg->header.arglen != sizeof(*c)) ERROUT(EINVAL); if ((priv->conf.enableComp || priv->conf.enableDecomp) && (c->enableComp || c->enableDecomp)) ERROUT(EALREADY); if (c->enableComp) { if (c->maxChannel > NG_VJC_MAX_CHANNELS - 1 || c->maxChannel < NG_VJC_MIN_CHANNELS - 1) ERROUT(EINVAL); } else c->maxChannel = NG_VJC_MAX_CHANNELS - 1; if (c->enableComp != 0 || c->enableDecomp != 0) { bzero(&priv->slc, sizeof(priv->slc)); sl_compress_init(&priv->slc, c->maxChannel); } priv->conf = *c; break; } case NGM_VJC_GET_CONFIG: { struct ngm_vjc_config *conf; NG_MKRESPONSE(resp, msg, sizeof(*conf), M_NOWAIT); if (resp == NULL) ERROUT(ENOMEM); conf = (struct ngm_vjc_config *)resp->data; *conf = priv->conf; break; } case NGM_VJC_GET_STATE: { const struct slcompress *const sl0 = &priv->slc; struct slcompress *sl; u_int16_t index; int i; /* Get response structure */ NG_MKRESPONSE(resp, msg, sizeof(*sl), M_NOWAIT); if (resp == NULL) ERROUT(ENOMEM); sl = (struct slcompress *)resp->data; *sl = *sl0; - /* Replace pointers with integer indicies */ + /* Replace pointers with integer indices */ if (sl->last_cs != NULL) { index = sl0->last_cs - sl0->tstate; bzero(&sl->last_cs, sizeof(sl->last_cs)); *((u_int16_t *)&sl->last_cs) = index; } for (i = 0; i < MAX_STATES; i++) { struct cstate *const cs = &sl->tstate[i]; index = sl0->tstate[i].cs_next - sl0->tstate; bzero(&cs->cs_next, sizeof(cs->cs_next)); *((u_int16_t *)&cs->cs_next) = index; } break; } case NGM_VJC_CLR_STATS: priv->slc.sls_packets = 0; priv->slc.sls_compressed = 0; priv->slc.sls_searches = 0; priv->slc.sls_misses = 0; priv->slc.sls_uncompressedin = 0; priv->slc.sls_compressedin = 0; priv->slc.sls_errorin = 0; priv->slc.sls_tossed = 0; break; case NGM_VJC_RECV_ERROR: sl_uncompress_tcp(NULL, 0, TYPE_ERROR, &priv->slc); break; default: error = EINVAL; break; } break; default: error = EINVAL; break; } done: NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } /* * Receive data */ static int ng_vjc_rcvdata(hook_p hook, item_p item) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); int error = 0; struct mbuf *m; NGI_GET_M(item, m); if (hook == priv->ip) { /* outgoing packet */ u_int type = TYPE_IP; /* Compress packet if enabled and proto is TCP */ if (priv->conf.enableComp) { struct ip *ip; if ((m = ng_vjc_pulluphdrs(m, 0)) == NULL) { NG_FREE_ITEM(item); return (ENOBUFS); } ip = mtod(m, struct ip *); if (ip->ip_p == IPPROTO_TCP) { const int origLen = m->m_len; type = sl_compress_tcp(m, ip, &priv->slc, priv->conf.compressCID); m->m_pkthdr.len += m->m_len - origLen; } } /* Dispatch to the appropriate outgoing hook */ switch (type) { case TYPE_IP: hook = priv->vjip; break; case TYPE_UNCOMPRESSED_TCP: hook = priv->vjuncomp; break; case TYPE_COMPRESSED_TCP: hook = priv->vjcomp; break; default: panic("%s: type=%d", __func__, type); } } else if (hook == priv->vjcomp) { /* incoming compressed packet */ int vjlen, need2pullup; struct mbuf *hm; u_char *hdr; u_int hlen; /* Are we decompressing? */ if (!priv->conf.enableDecomp) { NG_FREE_M(m); NG_FREE_ITEM(item); return (ENXIO); } /* Pull up the necessary amount from the mbuf */ need2pullup = MAX_VJHEADER; if (need2pullup > m->m_pkthdr.len) need2pullup = m->m_pkthdr.len; if (m->m_len < need2pullup && (m = m_pullup(m, need2pullup)) == NULL) { priv->slc.sls_errorin++; NG_FREE_ITEM(item); return (ENOBUFS); } /* Uncompress packet to reconstruct TCP/IP header */ vjlen = sl_uncompress_tcp_core(mtod(m, u_char *), m->m_len, m->m_pkthdr.len, TYPE_COMPRESSED_TCP, &priv->slc, &hdr, &hlen); if (vjlen <= 0) { NG_FREE_M(m); NG_FREE_ITEM(item); return (EINVAL); } m_adj(m, vjlen); /* Copy the reconstructed TCP/IP headers into a new mbuf */ MGETHDR(hm, M_NOWAIT, MT_DATA); if (hm == NULL) { priv->slc.sls_errorin++; NG_FREE_M(m); NG_FREE_ITEM(item); return (ENOBUFS); } hm->m_len = 0; hm->m_pkthdr.rcvif = NULL; if (hlen > MHLEN) { /* unlikely, but can happen */ if (!(MCLGET(hm, M_NOWAIT))) { m_freem(hm); priv->slc.sls_errorin++; NG_FREE_M(m); NG_FREE_ITEM(item); return (ENOBUFS); } } bcopy(hdr, mtod(hm, u_char *), hlen); hm->m_len = hlen; /* Glue TCP/IP headers and rest of packet together */ hm->m_next = m; hm->m_pkthdr.len = hlen + m->m_pkthdr.len; m = hm; hook = priv->ip; } else if (hook == priv->vjuncomp) { /* incoming uncompressed pkt */ u_char *hdr; u_int hlen; /* Are we decompressing? */ if (!priv->conf.enableDecomp) { NG_FREE_M(m); NG_FREE_ITEM(item); return (ENXIO); } /* Pull up IP+TCP headers */ if ((m = ng_vjc_pulluphdrs(m, 1)) == NULL) { NG_FREE_ITEM(item); return (ENOBUFS); } /* Run packet through uncompressor */ if (sl_uncompress_tcp_core(mtod(m, u_char *), m->m_len, m->m_pkthdr.len, TYPE_UNCOMPRESSED_TCP, &priv->slc, &hdr, &hlen) < 0) { NG_FREE_M(m); NG_FREE_ITEM(item); return (EINVAL); } hook = priv->ip; } else if (hook == priv->vjip) /* incoming regular packet (bypass) */ hook = priv->ip; else panic("%s: unknown hook", __func__); /* Send result back out */ NG_FWD_NEW_DATA(error, item, hook, m); return (error); } /* * Shutdown node */ static int ng_vjc_shutdown(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); bzero(priv, sizeof(*priv)); free(priv, M_NETGRAPH); NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(node); return (0); } /* * Hook disconnection */ static int ng_vjc_disconnect(hook_p hook) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); /* Zero out hook pointer */ if (hook == priv->ip) priv->ip = NULL; else if (hook == priv->vjcomp) priv->vjcomp = NULL; else if (hook == priv->vjuncomp) priv->vjuncomp = NULL; else if (hook == priv->vjip) priv->vjip = NULL; else panic("%s: unknown hook", __func__); /* Go away if no hooks left */ if ((NG_NODE_NUMHOOKS(node) == 0) && (NG_NODE_IS_VALID(node))) ng_rmnode_self(node); return (0); } /************************************************************************ HELPER STUFF ************************************************************************/ /* * Pull up the full IP and TCP headers of a packet. If packet is not * a TCP packet, just pull up the IP header. */ static struct mbuf * ng_vjc_pulluphdrs(struct mbuf *m, int knownTCP) { struct ip *ip; struct tcphdr *tcp; int ihlen, thlen; if (m->m_len < sizeof(*ip) && (m = m_pullup(m, sizeof(*ip))) == NULL) return (NULL); ip = mtod(m, struct ip *); if (!knownTCP && ip->ip_p != IPPROTO_TCP) return (m); ihlen = ip->ip_hl << 2; if (m->m_len < ihlen + sizeof(*tcp)) { if ((m = m_pullup(m, ihlen + sizeof(*tcp))) == NULL) return (NULL); ip = mtod(m, struct ip *); } tcp = (struct tcphdr *)((u_char *)ip + ihlen); thlen = tcp->th_off << 2; if (m->m_len < ihlen + thlen) m = m_pullup(m, ihlen + thlen); return (m); } Index: head/sys/netgraph/ng_vlan.c =================================================================== --- head/sys/netgraph/ng_vlan.c (revision 298812) +++ head/sys/netgraph/ng_vlan.c (revision 298813) @@ -1,710 +1,710 @@ /*- * Copyright (c) 2003 IPNET Internet Communication Company * Copyright (c) 2011 - 2012 Rozhuk Ivan * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * Author: Ruslan Ermilov * * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct ng_vlan_private { hook_p downstream_hook; hook_p nomatch_hook; uint32_t decap_enable; uint32_t encap_enable; uint16_t encap_proto; hook_p vlan_hook[(EVL_VLID_MASK + 1)]; }; typedef struct ng_vlan_private *priv_p; #define ETHER_VLAN_HDR_LEN (ETHER_HDR_LEN + ETHER_VLAN_ENCAP_LEN) #define VLAN_TAG_MASK 0xFFFF #define HOOK_VLAN_TAG_SET_MASK ((uintptr_t)((~0) & ~(VLAN_TAG_MASK))) #define IS_HOOK_VLAN_SET(hdata) \ ((((uintptr_t)hdata) & HOOK_VLAN_TAG_SET_MASK) == HOOK_VLAN_TAG_SET_MASK) static ng_constructor_t ng_vlan_constructor; static ng_rcvmsg_t ng_vlan_rcvmsg; static ng_shutdown_t ng_vlan_shutdown; static ng_newhook_t ng_vlan_newhook; static ng_rcvdata_t ng_vlan_rcvdata; static ng_disconnect_t ng_vlan_disconnect; /* Parse type for struct ng_vlan_filter. */ static const struct ng_parse_struct_field ng_vlan_filter_fields[] = NG_VLAN_FILTER_FIELDS; static const struct ng_parse_type ng_vlan_filter_type = { &ng_parse_struct_type, &ng_vlan_filter_fields }; static int ng_vlan_getTableLength(const struct ng_parse_type *type, const u_char *start, const u_char *buf) { const struct ng_vlan_table *const table = (const struct ng_vlan_table *)(buf - sizeof(u_int32_t)); return table->n; } /* Parse type for struct ng_vlan_table. */ static const struct ng_parse_array_info ng_vlan_table_array_info = { &ng_vlan_filter_type, ng_vlan_getTableLength }; static const struct ng_parse_type ng_vlan_table_array_type = { &ng_parse_array_type, &ng_vlan_table_array_info }; static const struct ng_parse_struct_field ng_vlan_table_fields[] = NG_VLAN_TABLE_FIELDS; static const struct ng_parse_type ng_vlan_table_type = { &ng_parse_struct_type, &ng_vlan_table_fields }; /* List of commands and how to convert arguments to/from ASCII. */ static const struct ng_cmdlist ng_vlan_cmdlist[] = { { NGM_VLAN_COOKIE, NGM_VLAN_ADD_FILTER, "addfilter", &ng_vlan_filter_type, NULL }, { NGM_VLAN_COOKIE, NGM_VLAN_DEL_FILTER, "delfilter", &ng_parse_hookbuf_type, NULL }, { NGM_VLAN_COOKIE, NGM_VLAN_GET_TABLE, "gettable", NULL, &ng_vlan_table_type }, { NGM_VLAN_COOKIE, NGM_VLAN_DEL_VID_FLT, "delvidflt", &ng_parse_uint16_type, NULL }, { NGM_VLAN_COOKIE, NGM_VLAN_GET_DECAP, "getdecap", NULL, &ng_parse_hint32_type }, { NGM_VLAN_COOKIE, NGM_VLAN_SET_DECAP, "setdecap", &ng_parse_hint32_type, NULL }, { NGM_VLAN_COOKIE, NGM_VLAN_GET_ENCAP, "getencap", NULL, &ng_parse_hint32_type }, { NGM_VLAN_COOKIE, NGM_VLAN_SET_ENCAP, "setencap", &ng_parse_hint32_type, NULL }, { NGM_VLAN_COOKIE, NGM_VLAN_GET_ENCAP_PROTO, "getencapproto", NULL, &ng_parse_hint16_type }, { NGM_VLAN_COOKIE, NGM_VLAN_SET_ENCAP_PROTO, "setencapproto", &ng_parse_hint16_type, NULL }, { 0 } }; static struct ng_type ng_vlan_typestruct = { .version = NG_ABI_VERSION, .name = NG_VLAN_NODE_TYPE, .constructor = ng_vlan_constructor, .rcvmsg = ng_vlan_rcvmsg, .shutdown = ng_vlan_shutdown, .newhook = ng_vlan_newhook, .rcvdata = ng_vlan_rcvdata, .disconnect = ng_vlan_disconnect, .cmdlist = ng_vlan_cmdlist, }; NETGRAPH_INIT(vlan, &ng_vlan_typestruct); /* * Helper functions. */ static __inline int m_chk(struct mbuf **mp, int len) { if ((*mp)->m_pkthdr.len < len) { m_freem((*mp)); (*mp) = NULL; return (EINVAL); } if ((*mp)->m_len < len && ((*mp) = m_pullup((*mp), len)) == NULL) return (ENOBUFS); return (0); } /* * Netgraph node functions. */ static int ng_vlan_constructor(node_p node) { priv_p priv; priv = malloc(sizeof(*priv), M_NETGRAPH, M_WAITOK | M_ZERO); priv->decap_enable = 0; priv->encap_enable = VLAN_ENCAP_FROM_FILTER; priv->encap_proto = htons(ETHERTYPE_VLAN); NG_NODE_SET_PRIVATE(node, priv); return (0); } static int ng_vlan_newhook(node_p node, hook_p hook, const char *name) { const priv_p priv = NG_NODE_PRIVATE(node); if (strcmp(name, NG_VLAN_HOOK_DOWNSTREAM) == 0) priv->downstream_hook = hook; else if (strcmp(name, NG_VLAN_HOOK_NOMATCH) == 0) priv->nomatch_hook = hook; else { /* * Any other hook name is valid and can * later be associated with a filter rule. */ } NG_HOOK_SET_PRIVATE(hook, NULL); return (0); } static int ng_vlan_rcvmsg(node_p node, item_p item, hook_p lasthook) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_mesg *msg, *resp = NULL; struct ng_vlan_filter *vf; hook_p hook; struct ng_vlan_table *t; uintptr_t hook_data; int i, vlan_count; uint16_t vid; int error = 0; NGI_GET_MSG(item, msg); /* Deal with message according to cookie and command. */ switch (msg->header.typecookie) { case NGM_VLAN_COOKIE: switch (msg->header.cmd) { case NGM_VLAN_ADD_FILTER: /* Check that message is long enough. */ if (msg->header.arglen != sizeof(*vf)) { error = EINVAL; break; } vf = (struct ng_vlan_filter *)msg->data; /* Sanity check the VLAN ID value. */ #ifdef NG_VLAN_USE_OLD_VLAN_NAME if (vf->vid == 0 && vf->vid != vf->vlan) { vf->vid = vf->vlan; } else if (vf->vid != 0 && vf->vlan != 0 && vf->vid != vf->vlan) { error = EINVAL; break; } #endif if (vf->vid & ~EVL_VLID_MASK || vf->pcp & ~7 || vf->cfi & ~1) { error = EINVAL; break; } /* Check that a referenced hook exists. */ hook = ng_findhook(node, vf->hook_name); if (hook == NULL) { error = ENOENT; break; } /* And is not one of the special hooks. */ if (hook == priv->downstream_hook || hook == priv->nomatch_hook) { error = EINVAL; break; } /* And is not already in service. */ if (IS_HOOK_VLAN_SET(NG_HOOK_PRIVATE(hook))) { error = EEXIST; break; } /* Check we don't already trap this VLAN. */ if (priv->vlan_hook[vf->vid] != NULL) { error = EEXIST; break; } /* Link vlan and hook together. */ NG_HOOK_SET_PRIVATE(hook, (void *)(HOOK_VLAN_TAG_SET_MASK | EVL_MAKETAG(vf->vid, vf->pcp, vf->cfi))); priv->vlan_hook[vf->vid] = hook; break; case NGM_VLAN_DEL_FILTER: /* Check that message is long enough. */ if (msg->header.arglen != NG_HOOKSIZ) { error = EINVAL; break; } /* Check that hook exists and is active. */ hook = ng_findhook(node, (char *)msg->data); if (hook == NULL) { error = ENOENT; break; } hook_data = (uintptr_t)NG_HOOK_PRIVATE(hook); if (IS_HOOK_VLAN_SET(hook_data) == 0) { error = ENOENT; break; } KASSERT(priv->vlan_hook[EVL_VLANOFTAG(hook_data)] == hook, ("%s: NGM_VLAN_DEL_FILTER: Invalid VID for Hook = %s\n", __func__, (char *)msg->data)); /* Purge a rule that refers to this hook. */ priv->vlan_hook[EVL_VLANOFTAG(hook_data)] = NULL; NG_HOOK_SET_PRIVATE(hook, NULL); break; case NGM_VLAN_DEL_VID_FLT: /* Check that message is long enough. */ if (msg->header.arglen != sizeof(uint16_t)) { error = EINVAL; break; } vid = (*((uint16_t *)msg->data)); /* Sanity check the VLAN ID value. */ if (vid & ~EVL_VLID_MASK) { error = EINVAL; break; } /* Check that hook exists and is active. */ hook = priv->vlan_hook[vid]; if (hook == NULL) { error = ENOENT; break; } hook_data = (uintptr_t)NG_HOOK_PRIVATE(hook); if (IS_HOOK_VLAN_SET(hook_data) == 0) { error = ENOENT; break; } KASSERT(EVL_VLANOFTAG(hook_data) == vid, ("%s: NGM_VLAN_DEL_VID_FLT:" " Invalid VID Hook = %us, must be: %us\n", __func__, (uint16_t )EVL_VLANOFTAG(hook_data), vid)); /* Purge a rule that refers to this hook. */ priv->vlan_hook[vid] = NULL; NG_HOOK_SET_PRIVATE(hook, NULL); break; case NGM_VLAN_GET_TABLE: /* Calculate vlans. */ vlan_count = 0; for (i = 0; i < (EVL_VLID_MASK + 1); i ++) { if (priv->vlan_hook[i] != NULL && NG_HOOK_IS_VALID(priv->vlan_hook[i])) vlan_count ++; } - /* Allocate memory for responce. */ + /* Allocate memory for response. */ NG_MKRESPONSE(resp, msg, sizeof(*t) + vlan_count * sizeof(*t->filter), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } - /* Pack data to responce. */ + /* Pack data to response. */ t = (struct ng_vlan_table *)resp->data; t->n = 0; vf = &t->filter[0]; for (i = 0; i < (EVL_VLID_MASK + 1); i ++) { hook = priv->vlan_hook[i]; if (hook == NULL || NG_HOOK_NOT_VALID(hook)) continue; hook_data = (uintptr_t)NG_HOOK_PRIVATE(hook); if (IS_HOOK_VLAN_SET(hook_data) == 0) continue; KASSERT(EVL_VLANOFTAG(hook_data) == i, ("%s: NGM_VLAN_GET_TABLE:" " hook %s VID = %us, must be: %i\n", __func__, NG_HOOK_NAME(hook), (uint16_t)EVL_VLANOFTAG(hook_data), i)); #ifdef NG_VLAN_USE_OLD_VLAN_NAME vf->vlan = i; #endif vf->vid = i; vf->pcp = EVL_PRIOFTAG(hook_data); vf->cfi = EVL_CFIOFTAG(hook_data); strncpy(vf->hook_name, NG_HOOK_NAME(hook), NG_HOOKSIZ); vf ++; t->n ++; } break; case NGM_VLAN_GET_DECAP: NG_MKRESPONSE(resp, msg, sizeof(uint32_t), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } (*((uint32_t *)resp->data)) = priv->decap_enable; break; case NGM_VLAN_SET_DECAP: if (msg->header.arglen != sizeof(uint32_t)) { error = EINVAL; break; } priv->decap_enable = (*((uint32_t *)msg->data)); break; case NGM_VLAN_GET_ENCAP: NG_MKRESPONSE(resp, msg, sizeof(uint32_t), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } (*((uint32_t *)resp->data)) = priv->encap_enable; break; case NGM_VLAN_SET_ENCAP: if (msg->header.arglen != sizeof(uint32_t)) { error = EINVAL; break; } priv->encap_enable = (*((uint32_t *)msg->data)); break; case NGM_VLAN_GET_ENCAP_PROTO: NG_MKRESPONSE(resp, msg, sizeof(uint16_t), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } (*((uint16_t *)resp->data)) = ntohs(priv->encap_proto); break; case NGM_VLAN_SET_ENCAP_PROTO: if (msg->header.arglen != sizeof(uint16_t)) { error = EINVAL; break; } priv->encap_proto = htons((*((uint16_t *)msg->data))); break; default: /* Unknown command. */ error = EINVAL; break; } break; case NGM_FLOW_COOKIE: { struct ng_mesg *copy; /* * Flow control messages should come only * from downstream. */ if (lasthook == NULL) break; if (lasthook != priv->downstream_hook) break; /* Broadcast the event to all uplinks. */ for (i = 0; i < (EVL_VLID_MASK + 1); i ++) { if (priv->vlan_hook[i] == NULL) continue; NG_COPYMESSAGE(copy, msg, M_NOWAIT); if (copy == NULL) continue; NG_SEND_MSG_HOOK(error, node, copy, priv->vlan_hook[i], 0); } break; } default: /* Unknown type cookie. */ error = EINVAL; break; } NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } static int ng_vlan_rcvdata(hook_p hook, item_p item) { const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); struct ether_header *eh; struct ether_vlan_header *evl; int error; uintptr_t hook_data; uint16_t vid, eth_vtag; struct mbuf *m; hook_p dst_hook; NGI_GET_M(item, m); /* Make sure we have an entire header. */ error = m_chk(&m, ETHER_HDR_LEN); if (error != 0) goto mchk_err; eh = mtod(m, struct ether_header *); if (hook == priv->downstream_hook) { /* * If from downstream, select between a match hook * or the nomatch hook. */ dst_hook = priv->nomatch_hook; /* Skip packets without tag. */ if ((m->m_flags & M_VLANTAG) == 0 && eh->ether_type != priv->encap_proto) { if (dst_hook == NULL) goto net_down; goto send_packet; } /* Process packets with tag. */ if (m->m_flags & M_VLANTAG) { /* * Packet is tagged, m contains a normal * Ethernet frame; tag is stored out-of-band. */ evl = NULL; vid = EVL_VLANOFTAG(m->m_pkthdr.ether_vtag); } else { /* eh->ether_type == priv->encap_proto */ error = m_chk(&m, ETHER_VLAN_HDR_LEN); if (error != 0) goto mchk_err; evl = mtod(m, struct ether_vlan_header *); vid = EVL_VLANOFTAG(ntohs(evl->evl_tag)); } if (priv->vlan_hook[vid] != NULL) { /* - * VLAN filter: allways remove vlan tags and + * VLAN filter: always remove vlan tags and * decapsulate packet. */ dst_hook = priv->vlan_hook[vid]; if (evl == NULL) { /* m->m_flags & M_VLANTAG */ m->m_pkthdr.ether_vtag = 0; m->m_flags &= ~M_VLANTAG; goto send_packet; } } else { /* nomatch_hook */ if (dst_hook == NULL) goto net_down; if (evl == NULL || priv->decap_enable == 0) goto send_packet; /* Save tag out-of-band. */ m->m_pkthdr.ether_vtag = ntohs(evl->evl_tag); m->m_flags |= M_VLANTAG; } /* * Decapsulate: * TPID = ether type encap * Move DstMAC and SrcMAC to ETHER_TYPE. * Before: * [dmac] [smac] [TPID] [PCP/CFI/VID] [ether_type] [payload] * |-----------| >>>>>>>>>>>>>>>>>>>> |--------------------| * After: * [free space ] [dmac] [smac] [ether_type] [payload] * |-----------| |--------------------| */ bcopy((char *)evl, ((char *)evl + ETHER_VLAN_ENCAP_LEN), (ETHER_ADDR_LEN * 2)); m_adj(m, ETHER_VLAN_ENCAP_LEN); } else { /* * It is heading towards the downstream. * If from nomatch, pass it unmodified. * Otherwise, do the VLAN encapsulation. */ dst_hook = priv->downstream_hook; if (dst_hook == NULL) goto net_down; if (hook != priv->nomatch_hook) {/* Filter hook. */ hook_data = (uintptr_t)NG_HOOK_PRIVATE(hook); if (IS_HOOK_VLAN_SET(hook_data) == 0) { /* * Packet from hook not in filter * call addfilter for this hook to fix. */ error = EOPNOTSUPP; goto drop; } eth_vtag = (hook_data & VLAN_TAG_MASK); if ((priv->encap_enable & VLAN_ENCAP_FROM_FILTER) == 0) { /* Just set packet header tag and send. */ m->m_flags |= M_VLANTAG; m->m_pkthdr.ether_vtag = eth_vtag; goto send_packet; } } else { /* nomatch_hook */ if ((priv->encap_enable & VLAN_ENCAP_FROM_NOMATCH) == 0 || (m->m_flags & M_VLANTAG) == 0) goto send_packet; /* Encapsulate tagged packet. */ eth_vtag = m->m_pkthdr.ether_vtag; m->m_pkthdr.ether_vtag = 0; m->m_flags &= ~M_VLANTAG; } /* * Transform the Ethernet header into an Ethernet header * with 802.1Q encapsulation. * Mod of: ether_vlanencap. * * TPID = ether type encap * Move DstMAC and SrcMAC from ETHER_TYPE. * Before: * [free space ] [dmac] [smac] [ether_type] [payload] * <<<<<<<<<<<<< |-----------| |--------------------| * After: * [dmac] [smac] [TPID] [PCP/CFI/VID] [ether_type] [payload] * |-----------| |-- inserted tag --| |--------------------| */ M_PREPEND(m, ETHER_VLAN_ENCAP_LEN, M_NOWAIT); if (m == NULL) error = ENOMEM; else error = m_chk(&m, ETHER_VLAN_HDR_LEN); if (error != 0) goto mchk_err; evl = mtod(m, struct ether_vlan_header *); bcopy(((char *)evl + ETHER_VLAN_ENCAP_LEN), (char *)evl, (ETHER_ADDR_LEN * 2)); evl->evl_encap_proto = priv->encap_proto; evl->evl_tag = htons(eth_vtag); } send_packet: NG_FWD_NEW_DATA(error, item, dst_hook, m); return (error); net_down: error = ENETDOWN; drop: m_freem(m); mchk_err: NG_FREE_ITEM(item); return (error); } static int ng_vlan_shutdown(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(node); free(priv, M_NETGRAPH); return (0); } static int ng_vlan_disconnect(hook_p hook) { const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); uintptr_t hook_data; if (hook == priv->downstream_hook) priv->downstream_hook = NULL; else if (hook == priv->nomatch_hook) priv->nomatch_hook = NULL; else { /* Purge a rule that refers to this hook. */ hook_data = (uintptr_t)NG_HOOK_PRIVATE(hook); if (IS_HOOK_VLAN_SET(hook_data)) priv->vlan_hook[EVL_VLANOFTAG(hook_data)] = NULL; } NG_HOOK_SET_PRIVATE(hook, NULL); if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) ng_rmnode_self(NG_HOOK_NODE(hook)); return (0); }