Source to bsd/net/if_blue.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 */
/*
* @(#)if_blue.c 1.1 (MacOSX) 6/10/43
* Justin Walker, 9970520
* First wave - splitter and notification support for the Blue Box
* 980130 - Second wave - Performance improvements, reorg and cleanup
*/
#include <sys/kdebug.h>
#if KDEBUG
#define DBG_SPLT_BFCHK DRVDBG_CODE(DBG_DRVSPLT, 0)
#define DBG_SPLT_APPND DRVDBG_CODE(DBG_DRVSPLT, 1)
#define DBG_SPLT_MBUF DRVDBG_CODE(DBG_DRVSPLT, 2)
#define DBG_SPLT_DUP DRVDBG_CODE(DBG_DRVSPLT, 3)
#define DBG_SPLT_PAD DRVDBG_CODE(DBG_DRVSPLT, 4)
#endif
#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/socket.h>
#include <sys/socketvar.h>
#include <sys/ioctl.h>
#include <sys/errno.h>
#include <sys/syslog.h>
#include <sys/proc.h>
#include <sys/vm.h>
#include <kern/cpu_number.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"
#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 <sys/systm.h>
#include <machine/spl.h>
#include <kern/thread.h>
#include <kern/queue.h>
/* Dummy IFs to differentiate source of looped packets */
struct ifnet rhap_if_s;
struct ifnet *rhap_if = &rhap_if_s;
struct ifnet_blue *blue_if;
struct sockaddr_dl ndrvsrc = {sizeof (struct sockaddr_dl), AF_NDRV};
struct ifqueue blueq;
extern int if_register(register struct BlueFilter *f
#ifdef BF_if
,
register struct ifnet *ifp
#endif
);
/*
* Blue Box support:
* 1st cut: the Y splitter
* A process turns on the splitter by opening the "raw" device
* (socket() for AF_NDRV) and issuing an SIOCSSPLITTER ioctl.
* Incoming packets are routed into MacOSX as well as to the requesting
* interface.
* Outbound packets are sent, and are examined to see if they should go
* back up (loopback, sort of). Packets that are looped back include:
* broadcast
* multicast
*/
int
new_splitter(register struct socket *so)
{ register struct ifnet_blue *ifb;
register struct ndrv_cb *np;
register struct ifnet *ifp;
struct BlueFilter filter;
int retval;
if ((ifb = _MALLOC(sizeof (struct ifnet_blue), M_PCB, M_WAITOK))
== NULL)
{
#if BLUE_DEBUG
kprintf("Can't create new splitter\n");
#endif
return(ENOBUFS);
}
bzero(ifb, sizeof(struct ifnet_blue));
np = (struct ndrv_cb *)so->so_pcb;
#if BLUE_DEBUG
kprintf("NEW SPLT: %x, %x\n", so, np);
if (np)
printf("SIG: %x, ifp: %x\n", np->nd_signature, np->nd_if);
#endif
if (np == NULL)
return(EINVAL); /* XXX */
if (np->nd_signature != NDRV_SIGNATURE)
return(EINVAL); /* XXX */
if ((ifp = np->nd_if) == NULL)
return(EINVAL); /* XXX */
if (ifp->if_flags & IFF_SPLITTER)
return(EBUSY);
if ((ifp->if_flags&IFF_UP) == 0)
return(ENXIO);
/*
* Bump the receive sockbuf size - need a big buffer
* to offset the scheduling latencies of the system
* Try to get something if our grandiose design fails.
*/
if (sbreserve(&so->so_rcv, 131072) == 0)
{ if (sbreserve(&so->so_rcv, 65536) == 0 &&
sbreserve(&so->so_rcv, 32768) == 0 &&
sbreserve(&so->so_rcv, 16384) == 0)
return(ENOBUFS);
}
ifp->if_flags |= IFF_SPLITTER;
/*
* Register each IP address associated with this ifnet
* This takes care of addresses registered prior to startup
* of the BlueBox.
* TODO: Appletalk sockaddrs
*/
#define IFA2IN(ifa) \
((struct in_addr) \
((struct sockaddr_in *)(ifa->ifa_addr))->sin_addr).s_addr
{ struct ifaddr *ifa;
TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link)
{ if (ifa->ifa_addr->sa_family == AF_INET)
{ filter.BF_flags = (BF_ALLOC|BF_IP);
filter.BF_address = IFA2IN(ifa);
#if BLUE_DEBUG
kprintf("[1] IP registering [%x] %x\n",
filter.BF_flags,
(unsigned int)filter.BF_address);
#endif
retval = if_register(&filter);
#if BLUE_DEBUG
if (retval)
kprintf("if_register(IP) returns %d\n",
retval);
#endif
}
}
}
blue_if = (struct ifnet_blue *)ifb;
ifb->blue_pid = ((struct proc *)current_proc())->p_pid;
ifb->ifb_so = so;
ifp->if_Y = (void *)ifb;
return(0);
}
/*
* Determine if destined for BlueBox or not. Called from ether_output()
* and ether_input().
* Returns NULL if we ate the packet, otherwise, the mbuf to continue with.
*/
struct mbuf *
splitter_input(register struct mbuf *m, register struct ifnet *ifp)
{ register struct ifnet_blue *ifb;
#if 0
register int s, flags;
#else
register int flags;
#endif
int rv;
register struct mbuf *m0 = NULL;
struct mbuf *m1;
extern struct mbuf *m_dup(struct mbuf *, int);
extern int BlueFilter_check(struct mbuf **, struct ifnet_blue *);
extern void blue_notify(struct mbuf *);
extern int blue_notify1(struct mbuf *);
if ((ifb = (struct ifnet_blue *)ifp->if_Y) == NULL)
{ ifp->if_flags &= ~IFF_SPLITTER;
return(m);
}
flags = m->m_flags;
m1 = m;
/* Check filters */
if ((rv = BlueFilter_check(&m1, ifb)) == -1)
return(m1); /* Not for BB, MacOSX will want to see it. */
m = m1;
if (rv == 0) /* It's for both - dup the packet */
{ m0 = m_dup(m, M_DONTWAIT);
if (m0 == NULL)
{ blue_if->no_bufs1++;
return(m); /* Give it to MacOSX */
}
} else
{ /* Oy, veh! The depths to which we stoop! */
/* We'll just assume M_PKTHDR is set */
if (m->m_next == 0 && (m->m_flags & M_EXT)
&& m->m_pkthdr.len <= MHLEN)
{ m0 = m_dup(m, M_DONTWAIT);
if (m0)
{ m_freem(m);
m = NULL;
} else
m0 = m;
} else
m0 = m;
}
if (flags & 0x10)
blue_if->pkts_looped_r2b++;
#if 0
schednetisr(NETISR_BLUE);
s = splimp();
if (IF_QFULL(&blueq)) {
IF_DROP(&blueq);
m_freem(m0);
} else
IF_ENQUEUE(&blueq, m0);
splx(s);
#else
blue_notify1(m0);
sorwakeup(blue_if->ifb_so);
blue_if->sig_sent++;
#endif
/* If we eat the packet (rv==1) return NULL */
return(rv == 0 ? m : NULL);
}
void
blue_notify()
{ register int do_notify = 0;
register int s;
register struct mbuf *m;
extern int blue_notify1(struct mbuf *);
/*
* Move the packets from the blue queue to the indicated socket
* If we haven't told anyone yet, send a signal.
*/
for (;;)
{ s = splimp();
IF_DEQUEUE(&blueq, m);
splx(s);
if (m == 0)
break;
do_notify = blue_notify1(m);
}
if (do_notify)
sorwakeup(blue_if->ifb_so); /* Start by using SIGIO */
}
int
blue_notify1(register struct mbuf *m)
{ register int rv;
/* move packet from if queue to socket */
/* !!!Fix this to work generically!!! */
ndrvsrc.sdl_type = IFT_ETHER;
ndrvsrc.sdl_nlen = 0;
ndrvsrc.sdl_alen = 6;
ndrvsrc.sdl_slen = 0;
bcopy(m->m_data+6, &ndrvsrc.sdl_data, 6);
if (sbappendaddr(&(blue_if->ifb_so->so_rcv),
(struct sockaddr *)&ndrvsrc, m,
(struct mbuf *)0) == 0)
{ register struct mbuf *n;
KERNEL_DEBUG(DBG_SPLT_APPND | DBG_FUNC_NONE,
blue_if->ifb_so->so_rcv.sb_cc,
blue_if->ifb_so->so_rcv.sb_hiwat,
blue_if->ifb_so->so_rcv.sb_mbcnt,
blue_if->ifb_so->so_rcv.sb_mbmax,
blue_if->ifb_so->so_rcv.sb_lowat );
if (m->m_flags & M_PKTHDR)
KERNEL_DEBUG(DBG_SPLT_MBUF, 0, m->m_pkthdr.len,
m->m_flags, 0, 0);
for (n = m; n; n = n->m_next)
KERNEL_DEBUG(DBG_SPLT_MBUF, 1,
(int)n, (int)n->m_next, n->m_len,
n->m_flags);
m_freem(m);
blue_if->full_sockbuf++;
rv = 1;
} else
{ register struct mbuf *n;
KERNEL_DEBUG(DBG_SPLT_APPND | DBG_FUNC_NONE,
blue_if->ifb_so->so_rcv.sb_cc,
blue_if->ifb_so->so_rcv.sb_hiwat,
blue_if->ifb_so->so_rcv.sb_mbcnt,
blue_if->ifb_so->so_rcv.sb_mbmax,
blue_if->ifb_so->so_rcv.sb_lowat );
if (m->m_flags & M_PKTHDR)
KERNEL_DEBUG(DBG_SPLT_MBUF, 2, m->m_pkthdr.len,
m->m_flags, 0, 0);
for (n = m; n; n = n->m_next)
KERNEL_DEBUG(DBG_SPLT_MBUF, 3,
(int)n, (int)n->m_next, n->m_len,
n->m_flags);
blue_if->pkts_up++;
rv = 0;
}
return(rv);
}
/*
* Check the incoming packet against the registered filters
* Rules (the rules are subtly different for input to the
* y-adapter customer and the "real" stacks):
* For BB: return 1
* For Both: return 0
* Not For BB: return -1
* Multicast/Broadcast => For Both
* Hack:
* if no registered filters, For Both
* Atalk filter registered
* filter matches => For BB else Not For BB
* IP filter registered
* filter matches => For BB else Not For BB
* Not For BB
* WARNING: this is a big-endian routine.
* WARNING 2: m_pullup can give you a new mbuf!
*/
int
BlueFilter_check(struct mbuf **m0, register struct ifnet_blue *ifb)
{ register struct BlueFilter *bf;
register unsigned char *p;
register unsigned short *s;
register unsigned long *l;
int total, flags;
register struct mbuf *m;
extern struct mbuf *m_pullup(struct mbuf *, int);
#define FILTER_LEN 32
KERNEL_DEBUG(DBG_SPLT_BFCHK | DBG_FUNC_START, 0, 0, 0, 0, 0 );
m = *m0;
if (FILTER_LEN > m->m_pkthdr.len)
{ KERNEL_DEBUG(DBG_SPLT_BFCHK | DBG_FUNC_END, 0, 0, 0, 0, 0 );
return(-1);
}
flags = m->m_flags;
while ((FILTER_LEN > m->m_len) && m->m_next) {
total = m->m_len + (m->m_next)->m_len;
if ((m = m_pullup(m, min(FILTER_LEN, total))) == 0)
{ KERNEL_DEBUG(DBG_SPLT_BFCHK | DBG_FUNC_END, 1, flags, total, 0, 0);
return(-1);
}
}
*m0 = m; /* Update, just in case */
p = mtod(m, unsigned char *); /* Point to destination media addr */
if (p[0] & 0x01) /* Multicast/broadcast */
{ KERNEL_DEBUG(DBG_SPLT_BFCHK | DBG_FUNC_END, 2, 0, 0, 0, 0 );
return(0);
}
s = (unsigned short *)p;
bf = &ifb->filter[BFS_ATALK];
if (!bf->BF_flags && !bf[1].BF_flags) /* Hack for Developer Release Blue Box */
{ KERNEL_DEBUG(DBG_SPLT_BFCHK | DBG_FUNC_END, 3, 0, 0, 0, 0 );
return(0);
}
#if BLUE_DEBUG
kprintf("PKT: %x, %x, %x\n", s[6], s[7], s[8]);
#endif
if (bf->BF_flags) /* Filtering Appletalk */
{ l = (unsigned long *)&s[8];
#if BLUE_DEBUG
kprintf("AT: %x, %x, %x, %x, %x, %x\n", s[6], s[7],
*l, s[10], s[13], p[30]);
#endif
if (s[6] <= ETHERMTU)
{ if (s[7] == 0xaaaa) /* Could be Atalk */
{ /* Verify SNAP header */
if (*l == 0x03080007 && s[10] == 0x809b)
{ if ((bf->BF_flags&BF_VALID) == 0 ||
(s[13] == bf->BF_address &&
p[30] == bf->BF_node))
{ KERNEL_DEBUG(DBG_SPLT_BFCHK | DBG_FUNC_END, 4,
s[13], p[30], 0, 0 );
return(1);
}
} else if (*l == 0x03000000 && s[10] == 0x80f3)
/* AARP pkts aren't net-addressed */
{ KERNEL_DEBUG(DBG_SPLT_BFCHK | DBG_FUNC_END, 5, 0, 0, 0, 0 );
return(0);
}
/* Not for us */
KERNEL_DEBUG(DBG_SPLT_BFCHK | DBG_FUNC_END, 6, s[13], p[30], 0, 0 );
return(-1);
} else /* Not for us? */
{ KERNEL_DEBUG(DBG_SPLT_BFCHK | DBG_FUNC_END, 7, s[7], 0, 0, 0 );
return(-1);
}
} /* Fall through */
} /* Fall through */
bf++; /* Look for IP next */
if (bf->BF_flags) /* Filtering IP */
{
l = (unsigned long *)&s[15];
#if BLUE_DEBUG
kprintf("IP: %x, %x\n", s[6], *l);
#endif
if (s[6] > ETHERMTU)
{ if (s[6] == 0x800) /* Is IP */
{ /* Verify IP address */
if ((bf->BF_flags&BF_VALID) == 0 ||
*l == bf->BF_address)
{ KERNEL_DEBUG(DBG_SPLT_BFCHK | DBG_FUNC_END, 8, *l, 0, 0, 0 );
return(1);
} else /* Not for us */
{ KERNEL_DEBUG(DBG_SPLT_BFCHK | DBG_FUNC_END, 9, *l, 0, 0, 0 );
return(-1);
}
} else if (s[6] == 0x806)
{ /* ARP pkts aren't net-addressed */
KERNEL_DEBUG(DBG_SPLT_BFCHK | DBG_FUNC_END, 10, 0, 0, 0, 0 );
return(0);
}
}
}
KERNEL_DEBUG(DBG_SPLT_BFCHK | DBG_FUNC_END, 11, s[6], 0, 0, 0 );
return(-1);
}
int
splitter_ctl(register struct socket *so, register int cmd,
register caddr_t data, register struct ifnet *ifp)
{ register struct ndrv_cb *np = sotondrvcb(so);
register struct ifnet_blue *ifb;
register struct BlueFilter *bf = (struct BlueFilter *)data, *bf1;
u_long at_dl_tag;
int error=0;
if ((ifb = np->nd_if->if_Y) == NULL)
return(ENXIO);
if (cmd == SIOCSSPLTFILT)
{
#if BLUE_DEBUG
kprintf("Filter: %s, %x, %x, %x\n", bf->ifr_name, bf->BF_flags, bf->BF_address,
bf->BF_node);
#endif
if (bf->BF_flags & BF_ATALK)
bf1 = &ifb->filter[BFS_ATALK];
else if (bf->BF_flags & BF_IP)
bf1 = &ifb->filter[BFS_IP];
else
return(EINVAL);
if (bf->BF_flags&BF_ALLOC)
{ if ((bf1->BF_flags&(BF_ALLOC|BF_VALID)) ==
(BF_ALLOC|BF_VALID))
return(EBUSY);
*bf1 = *bf;
bf1->BF_flags |= BF_VALID;
} else if (bf->BF_flags&BF_DEALLOC)
{ if (bf1->BF_flags&BF_ALLOC)
bf1->BF_flags = 0;
else
return(EINVAL);
}
/* Register AppleTalk Tags if not registered */
/* Check if a tag was already registered for AppleTalk */
error = dlil_find_dltag(ifp->if_family, ifp->if_unit,
PF_APPLETALK, &at_dl_tag);
#if BLUE_DEBUG
kprintf("splitter_ctl: ifp=%s%u find_dltag returns=%d dl_tag=%d\n",
ifp->if_name, ifp->if_unit, error, at_dl_tag);
#endif
if (error == EPROTONOSUPPORT) {
ether_attach_at(ifp, &at_dl_tag,
&at_dl_tag);
error = 0;
}
} else if (cmd == SIOCZSPLTSTAT)
{ ifb->pkts_up = 0;
ifb->pkts_out = 0;
ifb->pkts_looped_r2b = 0;
ifb->pkts_looped_b2r = 0;
ifb->no_bufs1 = 0;
ifb->no_bufs2 = 0;
ifb->full_sockbuf = 0;
} else if (cmd == SIOCGSPLTSTAT)
{ register struct Ystats *ys = (struct Ystats *)data;
ys->YS_blue_pid = ifb->blue_pid;
ys->YS_filter[BFS_ATALK] = ifb->filter[BFS_ATALK];
ys->YS_filter[BFS_IP] = ifb->filter[BFS_IP];
ys->YS_pkts_up = ifb->pkts_up;
ys->YS_pkts_out = ifb->pkts_out;
ys->YS_pkts_looped_b2r = ifb->pkts_looped_b2r;
ys->YS_pkts_looped_r2b = ifb->pkts_looped_r2b;
ys->YS_no_bufs1 = ifb->no_bufs1;
ys->YS_no_bufs2 = ifb->no_bufs2;
ys->YS_full_sockbuf = ifb->full_sockbuf;
} else
return(EINVAL);
return(0);
}
void
splitter_close(register struct ndrv_cb *np)
{ extern struct ifnet_blue *blue_if;
extern void ndrv_flushq(struct ifqueue *);
if (blue_if)
{ /* If we're the guy holding the Y-adapter, clean it up */
if (blue_if->blue_pid ==
((struct proc *)current_proc())->p_pid)
{ if (np->nd_if)
{ np->nd_if->if_flags &= ~IFF_SPLITTER;
np->nd_if->if_Y = 0;
}
BFIx = 0;
/* Clean out the filter supply */
bzero(RhapFilter,
sizeof(struct BlueFilter) * BFCount);
blue_if->ifb_so = 0;
blue_if->filter[0].BF_flags = 0;
blue_if->filter[1].BF_flags = 0;
ndrv_flushq(&blueq);
if (np->nd_laddr)
{ FREE((caddr_t) np->nd_laddr, M_IFADDR);
np->nd_laddr = 0;
}
}
}
remque((queue_t)np);
FREE((caddr_t)np, M_PCB);
}
/*
* Dup the mbuf chain passed in. The whole thing. No cute additional cruft.
* And really copy the thing. That way, we don't "precompute" checksums
* for unsuspecting consumers.
* Assumption: m->m_nextpkt == 0.
* Trick: for small packets, don't dup into a cluster. That way received
* packets don't take up too much room in the sockbuf (cf. sbspace()).
*/
int MDFail;
struct mbuf *
m_dup(register struct mbuf *m, int how)
{ register struct mbuf *n, **np;
struct mbuf *top;
int copyhdr = 0;
KERNEL_DEBUG(DBG_SPLT_DUP | DBG_FUNC_START, m->m_flags, m->m_len,
m->m_pkthdr.len, 0, 0 );
np = ⊤
top = 0;
if (m->m_flags & M_PKTHDR)
copyhdr = 1;
/*
* Quick check: if we have one mbuf and its data fits in an
* mbuf with packet header, just copy and go.
*/
if (m->m_next == NULL)
{ /* Then just move the data into an mbuf and be done... */
if (copyhdr)
{ if (m->m_pkthdr.len <= MHLEN)
{ if ((n = m_gethdr(how, m->m_type)) == NULL)
return(NULL);
bcopy(m->m_data, n->m_data, m->m_pkthdr.len);
n->m_pkthdr.len = m->m_pkthdr.len;
n->m_len = m->m_len;
KERNEL_DEBUG(DBG_SPLT_DUP | DBG_FUNC_END, 2,
m->m_pkthdr.len, m->m_flags,
n->m_flags, 0 );
return(n);
}
} else if (m->m_len <= MLEN)
{ if ((n = m_get(how, m->m_type)) == NULL)
return(NULL);
bcopy(m->m_data, n->m_data, m->m_len);
n->m_len = m->m_len;
KERNEL_DEBUG(DBG_SPLT_DUP | DBG_FUNC_END, 3, m->m_len,
m->m_flags, n->m_flags, 0 );
return(n);
}
}
while (m)
{
#if BLUE_DEBUG
kprintf("<%x: %x, %x, %x\n", m, m->m_flags, m->m_len,
m->m_data);
#endif
if (copyhdr)
n = m_gethdr(how, m->m_type);
else
n = m_get(how, m->m_type);
if (n == 0)
goto nospace;
if (m->m_flags & M_EXT)
{ MCLGET(n, how);
if ((n->m_flags & M_EXT) == 0)
goto nospace;
}
*np = n;
if (copyhdr)
{ /* Don't use M_COPY_PKTHDR: preserve m_data */
n->m_pkthdr = m->m_pkthdr;
n->m_flags |= (m->m_flags & M_COPYFLAGS);
copyhdr = 0;
if ((n->m_flags & M_EXT) == 0)
n->m_data = n->m_pktdat;
}
n->m_len = m->m_len;
/*
* Get the dup on the same bdry as the original
* Assume that the two mbufs have the same offset to data area
* (up to word bdries)
*/
bcopy(mtod(m, caddr_t), mtod(n, caddr_t), (unsigned)n->m_len);
m = m->m_next;
np = &n->m_next;
#if BLUE_DEBUG
kprintf(">%x: %x, %x, %x\n", n, n->m_flags, n->m_len,
n->m_data);
#endif
}
if (top == 0)
MDFail++;
KERNEL_DEBUG(DBG_SPLT_DUP | DBG_FUNC_END, 0, (int)top, 0, 0, 0 );
return (top);
nospace:
m_freem(top);
MDFail++;
KERNEL_DEBUG(DBG_SPLT_DUP | DBG_FUNC_END, 1, 0, 0, 0, 0 );
return (0);
}