9c969b771a
the MAC label referenced from 'struct socket' in the IPv4 and IPv6-based protocols. This permits MAC labels to be checked during network delivery operations without dereferencing inp->inp_socket to get to so->so_label, which will eventually avoid our having to grab the socket lock during delivery at the network layer. This change introduces 'struct inpcb' as a labeled object to the MAC Framework, along with the normal circus of entry points: initialization, creation from socket, destruction, as well as a delivery access control check. For most policies, the inpcb label will simply be a cache of the socket label, so a new protocol switch method is introduced, pr_sosetlabel() to notify protocols that the socket layer label has been updated so that the cache can be updated while holding appropriate locks. Most protocols implement this using pru_sosetlabel_null(), but IPv4/IPv6 protocols using inpcbs use the the worker function in_pcbsosetlabel(), which calls into the MAC Framework to perform a cache update. Biba, LOMAC, and MLS implement these entry points, as do the stub policy, and test policy. Reviewed by: sam, bms Obtained from: TrustedBSD Project Sponsored by: DARPA, Network Associates Laboratories
734 lines
14 KiB
C
734 lines
14 KiB
C
/*
|
|
* ===================================
|
|
* HARP | Host ATM Research Platform
|
|
* ===================================
|
|
*
|
|
*
|
|
* This Host ATM Research Platform ("HARP") file (the "Software") is
|
|
* made available by Network Computing Services, Inc. ("NetworkCS")
|
|
* "AS IS". NetworkCS does not provide maintenance, improvements or
|
|
* support of any kind.
|
|
*
|
|
* NETWORKCS MAKES NO WARRANTIES OR REPRESENTATIONS, EXPRESS OR IMPLIED,
|
|
* INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
* AND FITNESS FOR A PARTICULAR PURPOSE, AS TO ANY ELEMENT OF THE
|
|
* SOFTWARE OR ANY SUPPORT PROVIDED IN CONNECTION WITH THIS SOFTWARE.
|
|
* In no event shall NetworkCS be responsible for any damages, including
|
|
* but not limited to consequential damages, arising from or relating to
|
|
* any use of the Software or related support.
|
|
*
|
|
* Copyright 1994-1998 Network Computing Services, Inc.
|
|
*
|
|
* Copies of this Software may be made, however, the above copyright
|
|
* notice must be reproduced on all copies.
|
|
*/
|
|
|
|
/*
|
|
* Core ATM Services
|
|
* -----------------
|
|
*
|
|
* ATM DGRAM socket protocol processing
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/sockio.h>
|
|
#include <sys/protosw.h>
|
|
#include <sys/socket.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_sap.h>
|
|
#include <netatm/atm_cm.h>
|
|
#include <netatm/atm_if.h>
|
|
#include <netatm/atm_ioctl.h>
|
|
#include <netatm/atm_sigmgr.h>
|
|
#include <netatm/atm_stack.h>
|
|
#include <netatm/atm_pcb.h>
|
|
#include <netatm/atm_var.h>
|
|
|
|
|
|
/*
|
|
* Local functions
|
|
*/
|
|
static int atm_dgram_attach(struct socket *, int, struct thread *);
|
|
static int atm_dgram_control(struct socket *, u_long, caddr_t,
|
|
struct ifnet *, struct thread *);
|
|
static int atm_dgram_info(caddr_t);
|
|
|
|
|
|
/*
|
|
* New-style socket request routines
|
|
*/
|
|
struct pr_usrreqs atm_dgram_usrreqs = {
|
|
atm_proto_notsupp1, /* pru_abort */
|
|
pru_accept_notsupp, /* pru_accept */
|
|
atm_dgram_attach, /* pru_attach */
|
|
atm_proto_notsupp2, /* pru_bind */
|
|
pru_connect_notsupp, /* pru_connect */
|
|
pru_connect2_notsupp, /* pru_connect2 */
|
|
atm_dgram_control, /* pru_control */
|
|
atm_proto_notsupp1, /* pru_detach */
|
|
atm_proto_notsupp1, /* pru_disconnect */
|
|
pru_listen_notsupp, /* pru_listen */
|
|
atm_proto_notsupp3, /* pru_peeraddr */
|
|
pru_rcvd_notsupp, /* pru_rcvd */
|
|
pru_rcvoob_notsupp, /* pru_rcvoob */
|
|
atm_proto_notsupp4, /* pru_send */
|
|
pru_sense_null, /* pru_sense */
|
|
atm_proto_notsupp1, /* pru_shutdown */
|
|
atm_proto_notsupp3, /* pru_sockaddr */
|
|
NULL, /* pru_sosend */
|
|
NULL, /* pru_soreceive */
|
|
NULL, /* pru_sooll */
|
|
pru_sosetlabel_null /* pru_sosetlabel */
|
|
};
|
|
|
|
|
|
/*
|
|
* Handy common code macros
|
|
*/
|
|
#ifdef DIAGNOSTIC
|
|
#define ATM_INTRO() \
|
|
int s, err = 0; \
|
|
s = splnet(); \
|
|
/* \
|
|
* Stack queue should have been drained \
|
|
*/ \
|
|
if (atm_stackq_head != NULL) \
|
|
panic("atm_usrreq: stack queue not empty"); \
|
|
;
|
|
#else
|
|
#define ATM_INTRO() \
|
|
int s, err = 0; \
|
|
s = splnet(); \
|
|
;
|
|
#endif
|
|
|
|
#define ATM_OUTRO() \
|
|
/* \
|
|
* Drain any deferred calls \
|
|
*/ \
|
|
STACK_DRAIN(); \
|
|
(void) splx(s); \
|
|
return (err); \
|
|
;
|
|
|
|
#define ATM_RETERR(errno) { \
|
|
err = errno; \
|
|
goto out; \
|
|
}
|
|
|
|
|
|
/*
|
|
* Attach protocol to socket
|
|
*
|
|
* Arguments:
|
|
* so pointer to socket
|
|
* proto protocol identifier
|
|
* p pointer to process
|
|
*
|
|
* Returns:
|
|
* 0 request processed
|
|
* errno error processing request - reason indicated
|
|
*
|
|
*/
|
|
static int
|
|
atm_dgram_attach(so, proto, td)
|
|
struct socket *so;
|
|
int proto;
|
|
struct thread *td;
|
|
{
|
|
ATM_INTRO();
|
|
|
|
/*
|
|
* Nothing to do here for ioctl()-only sockets
|
|
*/
|
|
ATM_OUTRO();
|
|
}
|
|
|
|
|
|
/*
|
|
* Process ioctl system calls
|
|
*
|
|
* Arguments:
|
|
* so pointer to socket
|
|
* cmd ioctl code
|
|
* data pointer to code specific parameter data area
|
|
* ifp pointer to ifnet structure if it's an interface ioctl
|
|
* p pointer to process
|
|
*
|
|
* Returns:
|
|
* 0 request processed
|
|
* errno error processing request - reason indicated
|
|
*
|
|
*/
|
|
static int
|
|
atm_dgram_control(so, cmd, data, ifp, td)
|
|
struct socket *so;
|
|
u_long cmd;
|
|
caddr_t data;
|
|
struct ifnet *ifp;
|
|
struct thread *td;
|
|
{
|
|
ATM_INTRO();
|
|
|
|
/*
|
|
* First, figure out which ioctl we're dealing with and
|
|
* then process it based on the sub-op code
|
|
*/
|
|
switch (cmd) {
|
|
|
|
case AIOCCFG: {
|
|
struct atmcfgreq *acp = (struct atmcfgreq *)data;
|
|
struct atm_pif *pip;
|
|
|
|
if (td && (suser(td) != 0))
|
|
ATM_RETERR(EPERM);
|
|
|
|
switch (acp->acr_opcode) {
|
|
|
|
case AIOCS_CFG_ATT:
|
|
/*
|
|
* Attach signalling manager
|
|
*/
|
|
if ((pip = atm_pifname(acp->acr_att_intf)) == NULL)
|
|
ATM_RETERR(ENXIO);
|
|
err = atm_sigmgr_attach(pip, acp->acr_att_proto);
|
|
break;
|
|
|
|
case AIOCS_CFG_DET:
|
|
/*
|
|
* Detach signalling manager
|
|
*/
|
|
if ((pip = atm_pifname(acp->acr_det_intf)) == NULL)
|
|
ATM_RETERR(ENXIO);
|
|
err = atm_sigmgr_detach(pip);
|
|
break;
|
|
|
|
default:
|
|
err = EOPNOTSUPP;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case AIOCADD: {
|
|
struct atmaddreq *aap = (struct atmaddreq *)data;
|
|
Atm_endpoint *epp;
|
|
|
|
if (td && (suser(td) != 0))
|
|
ATM_RETERR(EPERM);
|
|
|
|
switch (aap->aar_opcode) {
|
|
|
|
case AIOCS_ADD_PVC:
|
|
/*
|
|
* Add a PVC definition
|
|
*/
|
|
|
|
/*
|
|
* Locate requested endpoint service
|
|
*/
|
|
epp = aap->aar_pvc_sap > ENDPT_MAX ? NULL :
|
|
atm_endpoints[aap->aar_pvc_sap];
|
|
if (epp == NULL)
|
|
ATM_RETERR(ENOPROTOOPT);
|
|
|
|
/*
|
|
* Let endpoint service handle it from here
|
|
*/
|
|
err = (*epp->ep_ioctl)(AIOCS_ADD_PVC, data, NULL);
|
|
break;
|
|
|
|
case AIOCS_ADD_ARP:
|
|
/*
|
|
* Add an ARP mapping
|
|
*/
|
|
epp = atm_endpoints[ENDPT_IP];
|
|
if (epp == NULL)
|
|
ATM_RETERR(ENOPROTOOPT);
|
|
|
|
/*
|
|
* Let IP/ATM endpoint handle this
|
|
*/
|
|
err = (*epp->ep_ioctl) (AIOCS_ADD_ARP, data, NULL);
|
|
break;
|
|
|
|
default:
|
|
err = EOPNOTSUPP;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case AIOCDEL: {
|
|
struct atmdelreq *adp = (struct atmdelreq *)data;
|
|
struct atm_pif *pip;
|
|
struct sigmgr *smp;
|
|
Atm_endpoint *epp;
|
|
|
|
if (td && (suser(td) != 0))
|
|
ATM_RETERR(EPERM);
|
|
|
|
switch (adp->adr_opcode) {
|
|
|
|
case AIOCS_DEL_PVC:
|
|
case AIOCS_DEL_SVC:
|
|
/*
|
|
* Delete a PVC or SVC
|
|
*/
|
|
|
|
/*
|
|
* Locate appropriate sigmgr
|
|
*/
|
|
if ((pip = atm_pifname(adp->adr_pvc_intf)) == NULL)
|
|
ATM_RETERR(ENXIO);
|
|
if ((smp = pip->pif_sigmgr) == NULL)
|
|
ATM_RETERR(ENOENT);
|
|
|
|
/*
|
|
* Let sigmgr handle it from here
|
|
*/
|
|
err = (*smp->sm_ioctl)(adp->adr_opcode, data,
|
|
(caddr_t)pip->pif_siginst);
|
|
break;
|
|
|
|
case AIOCS_DEL_ARP:
|
|
/*
|
|
* Delete an ARP mapping
|
|
*/
|
|
epp = atm_endpoints[ENDPT_IP];
|
|
if (epp == NULL)
|
|
ATM_RETERR(ENOPROTOOPT);
|
|
|
|
/*
|
|
* Let IP/ATM endpoint handle this
|
|
*/
|
|
err = (*epp->ep_ioctl) (AIOCS_DEL_ARP, data, NULL);
|
|
break;
|
|
|
|
default:
|
|
err = EOPNOTSUPP;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case AIOCSET: {
|
|
struct atmsetreq *asp = (struct atmsetreq *)data;
|
|
struct atm_pif *pip;
|
|
struct atm_nif *nip;
|
|
struct sigmgr *smp;
|
|
struct ifnet *ifp2;
|
|
|
|
if (td && (suser(td) != 0))
|
|
ATM_RETERR(EPERM);
|
|
|
|
switch (asp->asr_opcode) {
|
|
|
|
case AIOCS_SET_ASV:
|
|
/*
|
|
* Set an ARP server address
|
|
*/
|
|
|
|
/*
|
|
* Locate appropriate sigmgr
|
|
*/
|
|
if ((nip = atm_nifname(asp->asr_arp_intf)) == NULL)
|
|
ATM_RETERR(ENXIO);
|
|
pip = nip->nif_pif;
|
|
if ((smp = pip->pif_sigmgr) == NULL)
|
|
ATM_RETERR(ENOENT);
|
|
|
|
/*
|
|
* Let sigmgr handle it from here
|
|
*/
|
|
err = (*smp->sm_ioctl)(AIOCS_SET_ASV, data,
|
|
(caddr_t)nip);
|
|
break;
|
|
|
|
case AIOCS_SET_MAC:
|
|
/*
|
|
* Set physical interface MAC/ESI address
|
|
*/
|
|
|
|
/*
|
|
* Locate physical interface
|
|
*/
|
|
if ((pip = atm_pifname(asp->asr_mac_intf)) == NULL)
|
|
ATM_RETERR(ENXIO);
|
|
|
|
/*
|
|
* Interface must be detached
|
|
*/
|
|
if (pip->pif_sigmgr != NULL)
|
|
ATM_RETERR(EADDRINUSE);
|
|
|
|
/*
|
|
* Just plunk the address into the pif
|
|
*/
|
|
bcopy((caddr_t)&asp->asr_mac_addr,
|
|
(caddr_t)&pip->pif_macaddr,
|
|
sizeof(struct mac_addr));
|
|
break;
|
|
|
|
case AIOCS_SET_NIF:
|
|
/*
|
|
* Define network interfaces
|
|
*/
|
|
if ((pip = atm_pifname(asp->asr_nif_intf)) == NULL)
|
|
ATM_RETERR(ENXIO);
|
|
|
|
/*
|
|
* Validate interface count - logical interfaces
|
|
* are differentiated by the atm address selector.
|
|
*/
|
|
if (asp->asr_nif_cnt == 0 || asp->asr_nif_cnt > 256)
|
|
ATM_RETERR(EINVAL);
|
|
|
|
/*
|
|
* Make sure prefix name is unique
|
|
*/
|
|
IFNET_RLOCK();
|
|
TAILQ_FOREACH(ifp2, &ifnet, if_link) {
|
|
if (!strcmp(ifp2->if_dname, asp->asr_nif_pref)) {
|
|
/*
|
|
* If this is for the interface we're
|
|
* (re-)defining, let it through
|
|
*/
|
|
for (nip = pip->pif_nif; nip;
|
|
nip = nip->nif_pnext) {
|
|
if (&nip->nif_if == ifp2)
|
|
break;
|
|
}
|
|
if (nip)
|
|
continue;
|
|
IFNET_RUNLOCK();
|
|
ATM_RETERR(EEXIST);
|
|
}
|
|
}
|
|
IFNET_RUNLOCK();
|
|
|
|
/*
|
|
* Let interface handle it from here
|
|
*/
|
|
err = (*pip->pif_ioctl)(AIOCS_SET_NIF, data,
|
|
(caddr_t)pip);
|
|
break;
|
|
|
|
case AIOCS_SET_PRF:
|
|
/*
|
|
* Set interface NSAP Prefix
|
|
*/
|
|
|
|
/*
|
|
* Locate appropriate sigmgr
|
|
*/
|
|
if ((pip = atm_pifname(asp->asr_prf_intf)) == NULL)
|
|
ATM_RETERR(ENXIO);
|
|
if ((smp = pip->pif_sigmgr) == NULL)
|
|
ATM_RETERR(ENOENT);
|
|
|
|
/*
|
|
* Let sigmgr handle it from here
|
|
*/
|
|
err = (*smp->sm_ioctl)(AIOCS_SET_PRF, data,
|
|
(caddr_t)pip->pif_siginst);
|
|
break;
|
|
|
|
default:
|
|
err = EOPNOTSUPP;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case AIOCINFO:
|
|
err = atm_dgram_info(data);
|
|
break;
|
|
|
|
default:
|
|
err = EOPNOTSUPP;
|
|
}
|
|
|
|
out:
|
|
ATM_OUTRO();
|
|
}
|
|
|
|
|
|
/*
|
|
* Process AIOCINFO ioctl system calls
|
|
*
|
|
* Called at splnet.
|
|
*
|
|
* Arguments:
|
|
* data pointer to AIOCINFO parameter structure
|
|
*
|
|
* Returns:
|
|
* 0 request processed
|
|
* errno error processing request - reason indicated
|
|
*
|
|
*/
|
|
static int
|
|
atm_dgram_info(data)
|
|
caddr_t data;
|
|
{
|
|
struct atminfreq *aip = (struct atminfreq *)data;
|
|
struct atm_pif *pip;
|
|
struct atm_nif *nip;
|
|
struct sigmgr *smp;
|
|
Atm_endpoint *epp;
|
|
int len = aip->air_buf_len;
|
|
int err = 0;
|
|
|
|
switch (aip->air_opcode) {
|
|
|
|
case AIOCS_INF_VST:
|
|
case AIOCS_INF_CFG:
|
|
/*
|
|
* Get vendor interface information
|
|
*/
|
|
if (aip->air_vinfo_intf[0] != '\0') {
|
|
/*
|
|
* Interface specified
|
|
*/
|
|
if ((pip = atm_pifname(aip->air_vinfo_intf))) {
|
|
err = (*pip->pif_ioctl)(aip->air_opcode, data,
|
|
(caddr_t)pip);
|
|
} else {
|
|
err = ENXIO;
|
|
}
|
|
} else {
|
|
/*
|
|
* Want info for every interface
|
|
*/
|
|
for (pip = atm_interface_head; pip;
|
|
pip = pip->pif_next) {
|
|
err = (*pip->pif_ioctl)(aip->air_opcode, data,
|
|
(caddr_t)pip);
|
|
if (err)
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case AIOCS_INF_IPM:
|
|
/*
|
|
* Get IP Map information
|
|
*/
|
|
epp = atm_endpoints[ENDPT_IP];
|
|
if (epp) {
|
|
err = (*epp->ep_ioctl) (AIOCS_INF_IPM, data, NULL);
|
|
} else {
|
|
err = ENOPROTOOPT;
|
|
}
|
|
break;
|
|
|
|
case AIOCS_INF_ARP:
|
|
/*
|
|
* Get ARP table information
|
|
*/
|
|
for (pip = atm_interface_head; pip; pip = pip->pif_next) {
|
|
if ((smp = pip->pif_sigmgr) != NULL) {
|
|
err = (*smp->sm_ioctl)(AIOCS_INF_ARP,
|
|
data, (caddr_t)pip->pif_siginst);
|
|
}
|
|
if (err)
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case AIOCS_INF_ASV:
|
|
/*
|
|
* Get ARP server information
|
|
*/
|
|
if (aip->air_asrv_intf[0] != '\0') {
|
|
/*
|
|
* Interface specified
|
|
*/
|
|
if ((nip = atm_nifname(aip->air_asrv_intf))) {
|
|
if ((smp = nip->nif_pif->pif_sigmgr) != NULL) {
|
|
err = (*smp->sm_ioctl)(AIOCS_INF_ASV,
|
|
data, (caddr_t)nip);
|
|
}
|
|
} else {
|
|
err = ENXIO;
|
|
}
|
|
} else {
|
|
/*
|
|
* Want info for all arp servers
|
|
*/
|
|
for (pip = atm_interface_head; pip;
|
|
pip = pip->pif_next) {
|
|
if ((smp = pip->pif_sigmgr) != NULL) {
|
|
for (nip = pip->pif_nif; nip;
|
|
nip = nip->nif_pnext) {
|
|
err = (*smp->sm_ioctl)
|
|
(AIOCS_INF_ASV, data,
|
|
(caddr_t)nip);
|
|
if (err)
|
|
break;
|
|
}
|
|
if (err)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case AIOCS_INF_INT:
|
|
/*
|
|
* Get physical interface info
|
|
*/
|
|
if (aip->air_int_intf[0] != '\0') {
|
|
/*
|
|
* Interface specified
|
|
*/
|
|
if ((pip = atm_pifname(aip->air_int_intf))) {
|
|
err = (*pip->pif_ioctl)(AIOCS_INF_INT,
|
|
data, (caddr_t)pip);
|
|
} else {
|
|
err = ENXIO;
|
|
}
|
|
} else {
|
|
/*
|
|
* Want info for every physical interface
|
|
*/
|
|
for (pip = atm_interface_head; pip;
|
|
pip = pip->pif_next) {
|
|
err = (*pip->pif_ioctl)(AIOCS_INF_INT,
|
|
data, (caddr_t)pip);
|
|
if (err)
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case AIOCS_INF_VCC:
|
|
/*
|
|
* Get VCC information
|
|
*/
|
|
if (aip->air_vcc_intf[0] != '\0') {
|
|
/*
|
|
* Interface specified
|
|
*/
|
|
if ((pip = atm_pifname(aip->air_vcc_intf))) {
|
|
if ((smp = pip->pif_sigmgr) != NULL) {
|
|
err = (*smp->sm_ioctl)(AIOCS_INF_VCC,
|
|
data,
|
|
(caddr_t)pip->pif_siginst);
|
|
}
|
|
} else {
|
|
err = ENXIO;
|
|
}
|
|
} else {
|
|
/*
|
|
* Want info for every interface
|
|
*/
|
|
for (pip = atm_interface_head; pip;
|
|
pip = pip->pif_next) {
|
|
if ((smp = pip->pif_sigmgr) != NULL) {
|
|
err = (*smp->sm_ioctl)(AIOCS_INF_VCC,
|
|
data,
|
|
(caddr_t)pip->pif_siginst);
|
|
}
|
|
if (err)
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case AIOCS_INF_NIF:
|
|
/*
|
|
* Get network interface info
|
|
*/
|
|
if (aip->air_int_intf[0] != '\0') {
|
|
/*
|
|
* Interface specified
|
|
*/
|
|
if ((nip = atm_nifname(aip->air_int_intf))) {
|
|
pip = nip->nif_pif;
|
|
err = (*pip->pif_ioctl)(AIOCS_INF_NIF,
|
|
data, (caddr_t)nip);
|
|
} else {
|
|
err = ENXIO;
|
|
}
|
|
} else {
|
|
/*
|
|
* Want info for every network interface
|
|
*/
|
|
for (pip = atm_interface_head; pip;
|
|
pip = pip->pif_next) {
|
|
for (nip = pip->pif_nif; nip;
|
|
nip = nip->nif_pnext) {
|
|
err = (*pip->pif_ioctl)(AIOCS_INF_NIF,
|
|
data, (caddr_t)nip);
|
|
if (err)
|
|
break;
|
|
}
|
|
if (err)
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case AIOCS_INF_PIS:
|
|
/*
|
|
* Get physical interface statistics
|
|
*/
|
|
if (aip->air_physt_intf[0] != '\0') {
|
|
/*
|
|
* Interface specified
|
|
*/
|
|
if ((pip = atm_pifname(aip->air_physt_intf))) {
|
|
err = (*pip->pif_ioctl)(AIOCS_INF_PIS,
|
|
data, (caddr_t)pip);
|
|
} else {
|
|
err = ENXIO;
|
|
}
|
|
} else {
|
|
/*
|
|
* Want statistics for every physical interface
|
|
*/
|
|
for (pip = atm_interface_head; pip;
|
|
pip = pip->pif_next) {
|
|
err = (*pip->pif_ioctl)(AIOCS_INF_PIS,
|
|
data, (caddr_t)pip);
|
|
if (err)
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case AIOCS_INF_VER:
|
|
/*
|
|
* Get ATM software version
|
|
*/
|
|
if (len < sizeof(atm_version)) {
|
|
err = ENOSPC;
|
|
break;
|
|
}
|
|
if ((err = copyout((caddr_t)&atm_version,
|
|
aip->air_buf_addr,
|
|
sizeof(atm_version))) != 0) {
|
|
break;
|
|
}
|
|
aip->air_buf_addr += sizeof(atm_version);
|
|
aip->air_buf_len -= sizeof(atm_version);
|
|
break;
|
|
|
|
default:
|
|
err = EOPNOTSUPP;
|
|
}
|
|
|
|
/*
|
|
* Calculate returned buffer length
|
|
*/
|
|
aip->air_buf_len = len - aip->air_buf_len;
|
|
|
|
return (err);
|
|
}
|
|
|