freebsd-dev/sys/netgraph/atm/atmpif/ng_atmpif.c
2005-01-07 01:45:51 +00:00

707 lines
16 KiB
C

/*-
* 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/mbuf.h>
#include <sys/malloc.h>
#include <sys/errno.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/syslog.h>
#include <vm/uma.h>
#include <net/if.h>
#include <netatm/port.h>
#include <netatm/queue.h>
#include <netatm/atm.h>
#include <netatm/atm_sys.h>
#include <netatm/atm_cm.h>
#include <netatm/atm_if.h>
#include <netatm/atm_sap.h>
#include <netatm/atm_pcb.h>
#include <netatm/atm_stack.h>
#include <netatm/atm_var.h>
#include <netgraph/ng_message.h>
#include <netgraph/netgraph.h>
#include <netgraph/ng_parse.h>
#include <netgraph/atm/ng_atmpif.h>
#include <netgraph/atm/atmpif/ng_atmpif_var.h>
#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);
}