/*- * Copyright 2003 Harti Brandt * Copyright 2003 Vincent Jardin * 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. */ /* * ATM Virtal Adapter Support * -------------------------- * * Loadable kernel module and netgraph support * */ #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 #include #include #include #ifdef NG_SEPARATE_MALLOC MALLOC_DEFINE(M_NETGRAPH_ATMPIF, "netgraph_vatmpif", "netgraph HARP virtual Physical Interface"); #else #define M_NETGRAPH_ATMPIF M_NETGRAPH #endif /* * Local definitions */ /* * Protocol header */ struct vatmpif_header { /* The cell header (minus the HEC) is contained in the least-significant * 32-bits of a word. */ uint32_t cellhdr; /* Stored in network order */ /* Let's use cellhdr = htonl(ATM_HDR_SET(vpi, vci, pt, clp)) * and vpi = ATM_HDR_GET_VPI(ntohl(cellhdr)) * vci = ATM_HDR_GET_VCI(ntohl(cellhdr)) * pt = ATM_HDR_GET_PT (ntohl(cellhdr)) * clp = ATM_HDR_GET_CLP(ntohl(cellhdr)) */ int32_t seq; /* sequence number in network byte order */ uint64_t cookie; /* optional field */ uint8_t aal; /* AAL */ uint8_t __pad[3]; }; /* * Local functions */ /* Parse type for a MAC address */ static ng_parse_t ng_macaddr_parse; static ng_unparse_t ng_macaddr_unparse; const struct ng_parse_type ng_mac_addr_type = { parse: ng_macaddr_parse, unparse: ng_macaddr_unparse, }; /* Parse type for struct ng_atmpif_config */ static const struct ng_parse_struct_field ng_atmpif_config_type_fields[] = NG_ATMPIF_CONFIG_TYPE_INFO; static const struct ng_parse_type ng_atmpif_config_type = { &ng_parse_struct_type, &ng_atmpif_config_type_fields, }; /* Parse type for struct ng_atmpif_link_status */ static const struct ng_parse_struct_field ng_atmpif_link_status_type_fields[] = NG_ATMPIF_LINK_STATUS_TYPE_INFO; static const struct ng_parse_type ng_atmpif_link_status_type = { &ng_parse_struct_type, &ng_atmpif_link_status_type_fields, }; /* Parse type for struct ng_atmpif_stats */ static const struct ng_parse_struct_field ng_atmpif_stats_type_fields[] = NG_ATMPIF_STATS_TYPE_INFO; static const struct ng_parse_type ng_atmpif_stats_type = { &ng_parse_struct_type, &ng_atmpif_stats_type_fields, }; static const struct ng_cmdlist ng_atmpif_cmdlist[] = { { NGM_ATMPIF_COOKIE, NGM_ATMPIF_SET_CONFIG, "setconfig", mesgType: &ng_atmpif_config_type, respType: NULL }, { NGM_ATMPIF_COOKIE, NGM_ATMPIF_GET_CONFIG, "getconfig", mesgType: NULL, respType: &ng_atmpif_config_type }, { NGM_ATMPIF_COOKIE, NGM_ATMPIF_GET_LINK_STATUS, "getlinkstatus", mesgType: NULL, respType: &ng_atmpif_link_status_type }, { NGM_ATMPIF_COOKIE, NGM_ATMPIF_GET_STATS, "getstats", mesgType: NULL, respType: &ng_atmpif_stats_type }, { NGM_ATMPIF_COOKIE, NGM_ATMPIF_CLR_STATS, "clrstats", mesgType: NULL, respType: NULL }, { NGM_ATMPIF_COOKIE, NGM_ATMPIF_GETCLR_STATS, "getclrstats", mesgType: NULL, respType: &ng_atmpif_stats_type }, { 0 } }; uma_zone_t vatmpif_nif_zone; uma_zone_t vatmpif_vcc_zone; /* * Netgraph node methods */ static ng_constructor_t ng_atmpif_constructor; static ng_rcvmsg_t ng_atmpif_rcvmsg; static ng_shutdown_t ng_atmpif_rmnode; static ng_newhook_t ng_atmpif_newhook; static ng_rcvdata_t ng_atmpif_rcvdata; static ng_disconnect_t ng_atmpif_disconnect; static int ng_atmpif_mod_event(module_t, int, void *); /* * Node type descriptor */ static struct ng_type ng_atmpif_typestruct = { .version = NG_ABI_VERSION, .name = NG_ATMPIF_NODE_TYPE, .mod_event = ng_atmpif_mod_event, .constructor = ng_atmpif_constructor, .rcvmsg = ng_atmpif_rcvmsg, .shutdown = ng_atmpif_rmnode, .newhook = ng_atmpif_newhook, .rcvdata = ng_atmpif_rcvdata, .disconnect = ng_atmpif_disconnect, .cmdlist = ng_atmpif_cmdlist, }; NETGRAPH_INIT(atmpif, &ng_atmpif_typestruct); /****************************************************************** NETGRAPH NODE METHODS ******************************************************************/ /* * Node constructor * * Called at splnet() */ static int ng_atmpif_constructor(node_p nodep) { priv_p priv; /* * Allocate and initialize private info */ priv = malloc(sizeof(*priv), M_NETGRAPH_ATMPIF, M_NOWAIT | M_ZERO); if (priv == NULL) return (ENOMEM); priv->conf.debug = 0x00; priv->conf.pcr = ATM_PCR_OC3C; priv->conf.macaddr.ma_data[0] = 0x02; /* XXX : non unique bit */ priv->conf.macaddr.ma_data[1] = 0x09; /* XXX */ priv->conf.macaddr.ma_data[2] = 0xc0; /* XXX */ priv->conf.macaddr.ma_data[3] = (u_char)((random() & 0xff0000) >> 16); priv->conf.macaddr.ma_data[4] = (u_char)((random() & 0x00ff00) >> 8); priv->conf.macaddr.ma_data[5] = (u_char)((random() & 0x0000ff) >> 0); NG_NODE_SET_PRIVATE(nodep, priv); priv->node = nodep; /* Done */ return (0); } /* * Method for attaching a new hook * A hook is a virtual ATM link. */ static int ng_atmpif_newhook(node_p node, hook_p hook, const char *name) { const priv_p priv = NG_NODE_PRIVATE(node); /* * Check for a link hook */ if (strcmp(name, NG_ATMPIF_HOOK_LINK) == 0) { int error; /* * Do not create twice a link hook */ if (priv->link != NULL) return (EEXIST); priv->link = malloc(sizeof(*priv->link), M_NETGRAPH_ATMPIF, M_NOWAIT | M_ZERO); if (priv->link == NULL) return (ENOMEM); /* * Register as an HARP device */ if ((error = vatmpif_harp_attach(node))) { free(priv->link, M_NETGRAPH_ATMPIF); priv->link = NULL; return (error); } priv->link->hook = hook; return (0); } /* Unknown hook name */ return (EINVAL); } /* * Receive a control message from ngctl or the netgraph's API */ static int ng_atmpif_rcvmsg(node_p node, item_p item, hook_p lasthook) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_mesg *msg; struct ng_mesg *resp = NULL; int error = 0; NGI_GET_MSG(item, msg); switch (msg->header.typecookie) { case NGM_ATMPIF_COOKIE: switch (msg->header.cmd) { case NGM_ATMPIF_GET_CONFIG: { struct ng_vatmpif_config *conf; NG_MKRESPONSE(resp, msg, sizeof(struct ng_vatmpif_config), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } conf = (struct ng_vatmpif_config *)resp->data; *conf = priv->conf; /* no sanity checking needed */ break; } case NGM_ATMPIF_SET_CONFIG: { struct ng_vatmpif_config *conf; if (msg->header.arglen != sizeof(*conf)) { error = EINVAL; break; } conf = (struct ng_vatmpif_config *)msg->data; priv->conf = *conf; break; } case NGM_ATMPIF_GET_LINK_STATUS: { struct ng_vatmpif_hook *link; struct ng_atmpif_link_status *status; if ((link = priv->link) == NULL) { error = ENOTCONN; break; } NG_MKRESPONSE(resp, msg, sizeof(*status), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } status = (struct ng_atmpif_link_status *)resp->data; status->InSeq = link->InSeq; status->OutSeq = link->OutSeq; status->cur_pcr = link->cur_pcr; break; } case NGM_ATMPIF_GET_STATS: case NGM_ATMPIF_CLR_STATS: case NGM_ATMPIF_GETCLR_STATS: { struct ng_vatmpif_hook *link; if ((link = priv->link) == NULL) { error = ENOTCONN; break; } /* Get/clear stats */ if (msg->header.cmd != NGM_ATMPIF_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_ATMPIF_GET_STATS) bzero(&link->stats, sizeof(link->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); } /* * Hook disconnection. * It shutdown the virtual ATM link however the node is kept. */ static int ng_atmpif_disconnect(hook_p hook) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); /* * Deregister from the HARP stack */ vatmpif_harp_detach(node); /* * Free associated link information */ KASSERT(priv->link != NULL, ("%s: no link", __func__)); FREE(priv->link, M_NETGRAPH_ATMPIF); priv->link = NULL; /* Shutdown the physical interface */ priv->vu_pif.pif_flags &= ~PIF_UP; /* No more hooks, however I prefer to keep the node * instead of going away * However, if we are interested in removing it, let's * call ng_rmnode(hook->node); here. */ return (0); } /* * Shutdown node * * Free the private data. */ static int ng_atmpif_rmnode(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); /* Free private data */ FREE(priv, M_NETGRAPH_ATMPIF); NG_NODE_SET_PRIVATE(node, NULL); /* Unref node */ NG_NODE_UNREF(node); return (0); } /* * Receive data * * Then vatmpif_harp_recv_drain will schedule a call into the kernel * to process the atm_intrq. * It means that it should be processing at splimp() if * the node was a regular hw driver. */ static int ng_atmpif_rcvdata(hook_p hook, item_p item) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); struct vatmpif_header *h; struct vatmpif_header hdrbuf; int error = 0; struct mbuf *m; NGI_GET_M(item, m); NG_FREE_ITEM(item); /* Is the Physical Interface UP ? */ if (!(priv->vu_pif.pif_flags & PIF_UP)) { log(LOG_ERR, "%s%d: down while %s", priv->vu_pif.pif_name, priv->vu_pif.pif_unit, __func__); error = ENETDOWN; goto drop; } /* Sanity check header length */ if (m->m_pkthdr.len < sizeof(*h)) { priv->link->stats.hva_st_ng.ng_badpdu++; error = EINVAL; goto drop; } /* Get the Virtual ATM Physical Interface header */ if (m->m_len >= sizeof(*h)) { /* the common case */ h = mtod(m, struct vatmpif_header *); } else { m_copydata(m, 0, sizeof(*h), (caddr_t)&hdrbuf); h = &hdrbuf; /* allocated on the stack */ } /* * Consume the vatmpif header */ m_adj(m, sizeof(*h)); /* * Parse the header h */ /* * duplication and out of order test. * * . let's SEQ_MAX be the highest sequence number * . let's assume that h->seq = SEQ_MAX, (1) */ if (ntohl(h->seq) < priv->link->InSeq) { /* . is false due to (1) */ /* duplicate or out of order */ priv->link->stats.hva_st_ng.ng_errseq++; error = EINVAL; goto drop; } /* . then the mbuf is not dropped */ /* PDUs have been lost ?? */ if (priv->link->InSeq < ntohl(h->seq)) { /* . it is true only if a PDU has been lost, * . else due to (1) priv->link->InSeq is * . already equal to SEQ_MAX. */ priv->link->stats.hva_st_ng.ng_lostpdu++; priv->link->InSeq = ntohl(h->seq); } /* Save the sequence number */ priv->link->InSeq = ntohl(h->seq) + 1; /* . it leads to InSeq = SEQ_MAX + 1 = SEQ_MIN */ /* . it means that InSeq is always the next intended * . sequence number if none is lost, doesn't it ? */ /* * Send the packet to the stack. */ priv->link->stats.hva_st_ng.ng_rx_pdu++; error = vatmpif_harp_recv_drain(priv, m, ATM_HDR_GET_VPI(ntohl(h->cellhdr)), ATM_HDR_GET_VCI(ntohl(h->cellhdr)), ATM_HDR_GET_PT (ntohl(h->cellhdr)), ATM_HDR_GET_CLP(ntohl(h->cellhdr)), h->aal); return (error); drop: m_freem(m); return (error); } /* * Transmit data. Called by the HARP's outpout function. You should * notice that the return value is not returned upward by the HARP * stack. It is only used in order to update the stats. */ int ng_atmpif_transmit(const priv_p priv, struct mbuf *m, uint8_t vpi, uint16_t vci, uint8_t pt, uint8_t clp, Vatmpif_aal aal) { struct vatmpif_header *h; int error = 0; /* Is the Physical Interface UP ? */ if (!(priv->vu_pif.pif_flags & PIF_UP)) { log(LOG_ERR, "%s%d: down while %s", priv->vu_pif.pif_name, priv->vu_pif.pif_unit, __func__); error = ENETDOWN; goto drop; } /* If the hook is not connected, free the mbuf */ if (priv->link == NULL) { log(LOG_ERR, "%s%d: no hook while %s", priv->vu_pif.pif_name, priv->vu_pif.pif_unit, __func__); error = ENETDOWN; goto drop; } M_PREPEND(m, sizeof(*h), M_DONTWAIT); if (m == NULL) { error = ENOBUFS; goto drop; } m = m_pullup(m, sizeof(*h)); if (m == NULL) { error = ENOBUFS; goto drop; } h = mtod(m, struct vatmpif_header *); /* htonl is linear */ h->cellhdr = htonl(ATM_HDR_SET_VPI(vpi)); h->cellhdr += htonl(ATM_HDR_SET_VCI(vci)); h->cellhdr += htonl(ATM_HDR_SET_PT (pt)); h->cellhdr += htonl(ATM_HDR_SET_CLP(clp)); h->aal = aal; priv->link->OutSeq++; h->seq = htonl(priv->link->OutSeq); h->cookie = 0; if (IS_VATMPIF_DEBUG_PACKET(priv)) atm_pdu_print(m, __func__); /* Send it out to the "link" hook */ priv->link->stats.hva_st_ng.ng_tx_pdu++; NG_SEND_DATA_ONLY(error, priv->link->hook, m); return (error); drop: if (m != NULL) m_freem(m); return (error); } /****************************************************************** MAC Address parser *****************************************************************/ static int ng_macaddr_parse(const struct ng_parse_type *type, const char *s, int *const off, const u_char *const start, u_char *const buf, int *const buflen) { char *eptr; u_long val; int i; if (*buflen < 6) return (ERANGE); for (i = 0; i < 6; i++) { val = strtoul(s + *off, &eptr, 16); if (val > 0xff || eptr == s + *off) return (EINVAL); buf[i] = (u_char)val; *off = (eptr - s); if (i < 6 - 1) { if (*eptr != ':') return (EINVAL); (*off)++; } } *buflen = 6; return (0); } static int ng_macaddr_unparse(const struct ng_parse_type *type, const u_char *data, int *off, char *cbuf, int cbuflen) { int len; len = snprintf(cbuf, cbuflen, "%02x:%02x:%02x:%02x:%02x:%02x", data[*off], data[*off + 1], data[*off + 2], data[*off + 3], data[*off + 4], data[*off + 5]); if (len >= cbuflen) return (ERANGE); *off += 6; return (0); } /* * this holds all the stuff that should be done at load time */ static int ng_atmpif_mod_event(module_t mod, int event, void *data) { int error = 0; switch (event) { case MOD_LOAD: vatmpif_nif_zone = uma_zcreate("vatmpif nif", sizeof(struct atm_nif), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0); if (vatmpif_nif_zone == NULL) { error = ENOMEM; break; } vatmpif_vcc_zone = uma_zcreate("vatmpif vcc", sizeof(Vatmpif_vcc), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0); if (vatmpif_vcc_zone == NULL) { uma_zdestroy(vatmpif_nif_zone); error = ENOMEM; break; } break; case MOD_UNLOAD: uma_zdestroy(vatmpif_nif_zone); uma_zdestroy(vatmpif_vcc_zone); break; default: error = EOPNOTSUPP; break; } return (error); }