Source to bsd/net/ndrv.c
/*
* Copyright (c) 2000 Apple Computer, Inc. All rights reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* The contents of this file constitute Original Code as defined in and
* are subject to the Apple Public Source License Version 1.1 (the
* "License"). You may not use this file except in compliance with the
* License. Please obtain a copy of the License at
* http://www.apple.com/publicsource and read it before using this file.
*
* This Original Code and all software distributed under the License are
* distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the
* License for the specific language governing rights and limitations
* under the License.
*
* @APPLE_LICENSE_HEADER_END@
*/
/* Copyright (c) 1997, 1998 Apple Computer, Inc. All Rights Reserved */
/*
* @(#)ndrv.c 1.1 (MacOSX) 6/10/43
* Justin Walker, 970604
* AF_NDRV support
* 980130 - Cleanup, reorg, performance improvemements
*/
/*
* PF_NDRV allows raw access to a specified network device, directly
* with a socket. Expected use involves a socket option to request
* protocol packets. This lets ndrv_output() call dlil_output(), and
* lets DLIL find the proper recipient for incoming packets.
* The purpose here is for user-mode protocol implementation.
* Note that "pure raw access" will still be accomplished with BPF.
*
* In addition to the former use, when combined with socket NKEs,
* PF_NDRV permits a fairly flexible mechanism for implementing
* strange protocol support. One of the main ones will be the
* BlueBox/Classic Shared IP Address support.
*
* TODO: Add user access to protocol registration
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/protosw.h>
#include <sys/domain.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/ioctl.h>
#include <sys/errno.h>
#include <sys/syslog.h>
#include <sys/proc.h>
#include <kern/queue.h>
#include <net/if.h>
#include <net/netisr.h>
#include <net/route.h>
#include <net/if_llc.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include "if_blue.h" /* TEMP! Until BB gets in synch */
#include "ndrv.h"
#if INET
#include <netinet/in.h>
#include <netinet/in_var.h>
#endif
#include <netinet/if_ether.h>
#if NS
#include <netns/ns.h>
#include <netns/ns_if.h>
#endif
#if ISO
#include <netiso/argo_debug.h>
#include <netiso/iso.h>
#include <netiso/iso_var.h>
#include <netiso/iso_snpac.h>
#endif
#if LLC
#include <netccitt/dll.h>
#include <netccitt/llc_var.h>
#endif
#include <machine/spl.h>
int local_ndrv_attach(struct socket *, int);
int local_ndrv_detach(struct ndrv_cb *);
int local_ndrv_bind(struct socket *, struct sockaddr_ndrv *);
int local_ndrv_disconnect(struct ndrv_cb *);
extern int splitter_ctl(struct socket *, int, caddr_t, struct ifnet *);
void local_ndrv_dinit(void);
unsigned long ndrv_sendspace = NDRVSNDQ;
unsigned long ndrv_recvspace = NDRVRCVQ;
struct ndrv_cb ndrvl; /* Head of controlblock list */
#define BLUEQMAXLEN 50
int blueqmaxlen = BLUEQMAXLEN; /* Default */
/* Domain init function for AF_NDRV - noop */
void
local_ndrv_dinit(void)
{
}
/*
* Protocol init function for NDRV protocol
* Init the control block list.
*/
void
ndrv_init()
{ ndrvl.nd_next = ndrvl.nd_prev = &ndrvl;
blueq.ifq_maxlen = blueqmaxlen;
}
/*
* Protocol output - Called to output a raw network packet directly
* to the driver. If we're splitting, maybe loop it back.
*/
int
ndrv_output(register struct mbuf *m,
register struct socket *so)
{ register struct ndrv_cb *np = sotondrvcb(so);
register struct ifnet *ifp = np->nd_if;
int s, error;
extern struct ifnet_blue *blue_if;
extern void kprintf(const char *, ...);
extern int Filter_check(struct mbuf **);
#define senderr(e) { error = (e); goto bad;}
#if NDRV_DEBUG
kprintf("NDRV output: %x, %x, %x\n", m, so, np);
#endif
/*
* No header is a format error
*/
if ((m->m_flags&M_PKTHDR) == 0)
return(ENOBUFS); /* EINVAL??? */
/* If we're splitting, */
if (ifp->if_flags&IFF_SPLITTER) /* Splitter is turned on */
{ register struct mbuf *m0;
struct mbuf *m1;
register int rv;
extern struct mbuf *m_dup(struct mbuf *, int);
#if 0
kprintf("NDRV_OUTPUT: m0 = %x\n", m0);
#endif
m1 = m;
rv = Filter_check(&m1);
m = m1;
/*
* -1 => Not For MacOSX
* 0 => For Both
* 1 => For MacOSX
*/
if (rv >= 0)
{ register struct ether_header *eh;
if (rv == 0)
{ if ((m0 = m_dup(m, M_WAIT)) == NULL)
((struct ifnet_blue *)ifp->if_Y)->no_bufs2++;
} else
{ m0 = m;
m = NULL;
}
/* Hack alert! */
eh = mtod(m0, struct ether_header *);
m0->m_data += sizeof(struct ether_header);
m0->m_len -= sizeof (struct ether_header);
m0->m_pkthdr.len -= sizeof(struct ether_header);
m0->m_flags |= 0x80;
#if NDRV_DEBUG
kprintf("NDRV_OUTPUT: m0 = %x\n", m0);
#endif
new_ether_input(m0, eh, (struct ifnet *)blue_if, 0, 1);
if (!m)
return(0);
}
}
/*
* Output to real device.
*
* Can't do multicast accounting because we don't know
* (a) if our interface does multicast; and
* (b) what a multicast address looks like
*/
s = splimp();
/*
* Can't call DLIL to do the job - we don't have a tag
* and we aren't really a protocol
*/
(*ifp->if_output)(ifp, m);
splx(s);
ifp->if_obytes += m->m_len; /* MP alert! */
blue_if->pkts_out++;
return (0);
bad:
if (m)
m_freem(m);
return (error);
}
int ndrv_control(struct socket *so, u_long cmd, caddr_t data,
struct ifnet *ifp, struct proc *p)
{
return (splitter_ctl(so, (int)cmd, data,
ifp));
}
int ndrv_attach(struct socket *so, int proto,
struct proc *p)
{
return local_ndrv_attach(so, proto);
}
/*
* Destroy state just before socket deallocation.
* Flush data or not depending on the options.
*/
int ndrv_detach(struct socket *so)
{
register struct ndrv_cb *np = sotondrvcb(so);
if (np == 0)
return EINVAL;
return local_ndrv_detach(np);
}
/*
* If a socket isn't bound to a single address,
* the ndrv input routine will hand it anything
* within that protocol family (assuming there's
* nothing else around it should go to).
*
* Don't expect this to be used.
*/
int ndrv_connect(struct socket *so, struct sockaddr *nam,
struct proc *p)
{
register struct ndrv_cb *np = sotondrvcb(so);
if (np == 0)
return EINVAL;
if (np->nd_faddr)
return EISCONN;
bcopy((caddr_t) nam, (caddr_t) np->nd_faddr, sizeof(struct sockaddr_ndrv));
soisconnected(so);
return 0;
}
/*
* This is the "driver open" hook - we 'bind' to the
* named driver.
*/
int ndrv_bind(struct socket *so, struct sockaddr *nam,
struct proc *p)
{
register struct ndrv_cb *np = sotondrvcb(so);
if (np == 0)
return EINVAL;
if (np->nd_laddr)
return EINVAL; /* XXX */
return local_ndrv_bind(so, (struct sockaddr_ndrv *) nam);
}
int ndrv_disconnect(struct socket *so)
{
register struct ndrv_cb *np = sotondrvcb(so);
if (np == 0)
return EINVAL;
if (np->nd_faddr == 0)
return ENOTCONN;
local_ndrv_disconnect(np);
soisdisconnected(so);
return 0;
}
/*
* Mark the connection as being incapable of further input.
*/
int ndrv_shutdown(struct socket *so)
{
socantsendmore(so);
return 0;
}
/*
* Ship a packet out. The ndrv output will pass it
* to the appropriate driver. The really tricky part
* is the destination address...
*/
int ndrv_send(struct socket *so, int flags, struct mbuf *m,
struct sockaddr *addr, struct mbuf *control,
struct proc *p)
{
int error;
if (control)
return EOPNOTSUPP;
error = ndrv_output(m, so);
m = NULL;
return error;
}
int ndrv_abort(struct socket *so)
{
register struct ndrv_cb *np = sotondrvcb(so);
if (np == 0)
return EINVAL;
local_ndrv_disconnect(np);
sofree(so);
soisdisconnected(so);
return 0;
}
int ndrv_sense(struct socket *so, struct stat *sb)
{
/*
* stat: don't bother with a blocksize.
*/
return (0);
}
int ndrv_sockaddr(struct socket *so,
struct sockaddr **nam)
{
register struct ndrv_cb *np = sotondrvcb(so);
int len;
if (np == 0)
return EINVAL;
if (np->nd_laddr == 0)
return EINVAL;
len = np->nd_laddr->snd_len;
bcopy((caddr_t)np->nd_laddr, *nam,
(unsigned)len);
return 0;
}
int ndrv_peeraddr(struct socket *so,
struct sockaddr **nam)
{
register struct ndrv_cb *np = sotondrvcb(so);
int len;
if (np == 0)
return EINVAL;
if (np->nd_faddr == 0)
return ENOTCONN;
len = np->nd_faddr->snd_len;
bcopy((caddr_t)np->nd_faddr, *nam,
(unsigned)len);
return 0;
}
/* Control input */
void ndrv_ctlinput(int dummy1, struct sockaddr *dummy2, void *dummy3)
{
}
/* Control output */
int ndrv_ctloutput(struct socket *dummy1, struct sockopt *dummy2)
{
return(0);
}
/* Drain the queues */
void
ndrv_drain()
{
}
/* Sysctl hook for NDRV */
int
ndrv_sysctl()
{
return(0);
}
/*
* Allocate an ndrv control block and some buffer space for the socket
*/
int
local_ndrv_attach(register struct socket *so,
register int proto)
{ int error;
register struct ndrv_cb *np = sotondrvcb(so);
if ((so->so_state & SS_PRIV) == 0)
return(EPERM);
#if NDRV_DEBUG
kprintf("NDRV attach: %x, %x, %x\n", so, proto, np);
#endif
MALLOC(np, struct ndrv_cb *, sizeof(*np), M_PCB, M_WAITOK);
#if NDRV_DEBUG
kprintf("NDRV attach: %x, %x, %x\n", so, proto, np);
#endif
if ((so->so_pcb = (caddr_t)np))
bzero(np, sizeof(*np));
else
return(ENOBUFS);
if ((error = soreserve(so, ndrv_sendspace, ndrv_recvspace)))
return(error);
np->nd_signature = NDRV_SIGNATURE;
np->nd_socket = so;
np->nd_proto.sp_family = so->so_proto->pr_domain->dom_family;
np->nd_proto.sp_protocol = proto;
insque((queue_t)np, (queue_t)&ndrvl);
//##### eeccckkk added here to get the kext filter "attached"... TEMP
// sfilter_init(so);
return(0);
}
int
local_ndrv_detach(register struct ndrv_cb *np)
{ register struct socket *so = np->nd_socket;
extern struct ifnet_blue *blue_if;
extern void splitter_close(struct ndrv_cb *);
#if NDRV_DEBUG
kprintf("NDRV detach: %x, %x\n", so, np);
#endif
if (blue_if)
splitter_close(np); /* 'np' is freed within */
so->so_pcb = 0;
sofree(so);
return(0);
}
int
local_ndrv_disconnect(register struct ndrv_cb *np)
{
#if NDRV_DEBUG
kprintf("NDRV disconnect: %x\n", np);
#endif
if (np->nd_faddr)
{ m_freem(dtom(np->nd_faddr));
np->nd_faddr = 0;
}
if (np->nd_socket->so_state & SS_NOFDREF)
local_ndrv_detach(np);
return(0);
}
/*
* Here's where we latch onto the driver and make it ours.
*/
int
local_ndrv_bind(register struct socket *so,
register struct sockaddr_ndrv *sa)
{ register char *dname;
register struct ndrv_cb *np;
register struct ifnet *ifp;
extern int name_cmp(struct ifnet *, char *);
if TAILQ_EMPTY(&ifnet)
return(EADDRNOTAVAIL); /* Quick sanity check */
np = sotondrvcb(so);
/* I think we just latch onto a copy here; the caller frees */
np->nd_laddr = _MALLOC(sizeof(struct sockaddr_ndrv), M_IFADDR, M_WAITOK);
bcopy((caddr_t) sa, (caddr_t) np->nd_laddr, sizeof(struct sockaddr_ndrv));
dname = sa->snd_name;
if (dname == NULL)
return(EINVAL);
#if NDRV_DEBUG
kprintf("NDRV bind: %x, %x, %s\n", so, np, dname);
#endif
/* Track down the driver and its ifnet structure.
* There's no internal call for this so we have to dup the code
* in if.c/ifconf()
*/
TAILQ_FOREACH(ifp, &ifnet, if_link) {
if (name_cmp(ifp, dname) == 0)
break;
}
if (ifp == NULL)
return(EADDRNOTAVAIL);
/*
* Now, at this point, we should force open the driver and somehow
* register ourselves to receive packets (a la the bpf).
* However, we have this groaty hack in place that makes it
* not necessary for blue box purposes (the 'splitter' trick).
* If we want this to be a full-fledged AF, we have to force the
* open and implement a filter mechanism.
*/
np->nd_if = ifp;
return(0);
}
/*
* Try to compare a device name (q) with one of the funky ifnet
* device names (ifp).
*/
int name_cmp(register struct ifnet *ifp, register char *q)
{ register char *r;
register int len;
char buf[IFNAMSIZ];
static char *sprint_d();
r = buf;
len = strlen(ifp->if_name);
strncpy(r, ifp->if_name, IFNAMSIZ);
r += len;
(void)sprint_d(ifp->if_unit, r, r-buf);
#if NDRV_DEBUG
kprintf("Comparing %s, %s\n", buf, q);
#endif
return(strncmp(buf, q, IFNAMSIZ));
}
/* Hackery - return a string version of a decimal number */
static char *
sprint_d(n, buf, buflen)
u_int n;
char *buf;
int buflen;
{
register char *cp = buf + buflen - 1;
*cp = 0;
do {
cp--;
*cp = "0123456789"[n % 10];
n /= 10;
} while (n != 0);
return (cp);
}
/*
* When closing, dump any enqueued mbufs.
*/
void
ndrv_flushq(register struct ifqueue *q)
{ register struct mbuf *m;
register int s;
for (;;)
{ s = splimp();
IF_DEQUEUE(q, m);
if (m == NULL)
break;
IF_DROP(q);
splx(s);
if (m)
m_freem(m);
}
splx(s);
}
struct pr_usrreqs ndrv_usrreqs = {
ndrv_abort, pru_accept_notsupp, ndrv_attach, ndrv_bind,
ndrv_connect, pru_connect2_notsupp, ndrv_control, ndrv_detach,
ndrv_disconnect, pru_listen_notsupp, ndrv_peeraddr, pru_rcvd_notsupp,
pru_rcvoob_notsupp, ndrv_send, ndrv_sense, ndrv_shutdown,
ndrv_sockaddr, sosend, soreceive, sopoll
};
struct domain ndrvdomain;
struct protosw ndrvsw[] =
{ { SOCK_RAW, &ndrvdomain, 0, PR_ATOMIC|PR_ADDR,
0, ndrv_output, ndrv_ctlinput, ndrv_ctloutput,
0, ndrv_init, 0, 0,
ndrv_drain, ndrv_sysctl, &ndrv_usrreqs
}
};
struct domain ndrvdomain =
{ AF_NDRV, "NetDriver", NULL, NULL, NULL,
ndrvsw,
NULL, NULL, 0, 0, 0, 0
};