Source to kern/mach_net.c
/*
* Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* "Portions Copyright (c) 1999 Apple Computer, Inc. All Rights
* Reserved. This file contains Original Code and/or Modifications of
* Original Code as defined in and that are subject to the Apple Public
* Source License Version 1.0 (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.
*
* The 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@
*/
/*
* Mach Operating System
* Copyright (c) 1989 Carnegie-Mellon University
* Copyright (c) 1988 Carnegie-Mellon University
* Copyright (c) 1987 Carnegie-Mellon University
* All rights reserved. The CMU software License Agreement specifies
* the terms and conditions for use and redistribution.
*/
/*
* HISTORY
* Revision 1.1.1.1 1997/09/30 02:44:31 wsanchez
* Import of kernel from umeshv/kernel
*
* 1-Mar-90 Gregg Kellogg (gk) at NeXT
* NeXT: Don't make the mach_net_kmsg_zone quite so big.
* Turn off optimization around netipc_listen().
*
* Revision 2.11 89/02/25 18:05:43 gm0w
* Changes for cleanup.
*
* Revision 2.10 89/01/15 16:24:27 rpd
* Use decl_simple_lock_data.
* [89/01/15 15:02:07 rpd]
*
* Revision 2.8 88/10/11 10:19:58 rpd
* Removed SEND_KERNEL from the msg_queue operation.
* Removed redundant log messages.
* [88/10/09 08:46:23 rpd]
*
* Changed "struct KMsg" to "struct kern_msg".
* Removed redundant "#if MACH_NET/#endif" wrapper.
* [88/10/04 07:07:58 rpd]
*
* Revision 2.7 88/10/01 21:57:33 rpd
* Changed msg_remote_port to msg_local_port, because that is
* the destination port of a copied-in message now.
* [88/10/01 21:30:34 rpd]
*
* Revision 2.6 88/08/24 01:51:51 mwyoung
* Corrected include file references.
* [88/08/22 22:06:23 mwyoung]
*
* 22-Aug-88 Michael Young (mwyoung) at Carnegie-Mellon University
* Removed ancient history.
*
* 15-Aug-88 Avadis Tevanian (avie) at NeXT
* Changed number of allocated messages for mach_net from 8 to 4.
*
* Revision 2.5 88/08/06 18:22:03 rpd
* Eliminated use of kern/mach_ipc_defs.h.
*
* Revision 2.4 88/07/20 16:38:58 rpd
* When queueing a message, bump current and ip_data_grams
* ipc_statistics counters.
*
* 18-May-88 Daniel Julin (dpj) at Carnegie-Mellon University
* Added another access check for mach_net_server. Only privileged
* users should be allowed to send packets this way, because there
* is little or no check against false source addresses.
*
* 6-May-88 Daniel Julin (dpj) at Carnegie-Mellon University
* Added an access check for netipc_listen and
* (from code by rds).
*
* 3-Apr-88 Michael Young (mwyoung) at Carnegie-Mellon University
* De-linted.
*
* 1-Feb-88 David Golub (dbg) at Carnegie-Mellon University
* Receive_ip_datagram is called at splnet. It must drop priority
* to spl0 to call the IPC routines, and raise back to splnet when
* it exits. NOTE that this works ONLY if this is always called
* from the netisr thread - it will garble interrupt handling
* horribly.
*
* 8-Dec-87 David Black (dlb) at Carnegie-Mellon University
* Removed spl check code; interrupt disabling is no longer needed
* because all network code runs in a thread context. Removed TT
* conditionals.
*
* 9-Nov-87 David Golub (dbg) at Carnegie-Mellon University
* Since network routines now run in a thread context, use
* msg_queue to send message.
*
*/
#import <mach_net.h>
#import <sys/param.h>
#import <sys/systm.h>
#import <sys/mbuf.h>
#import <sys/socket.h>
#import <net/if.h>
#import <net/route.h>
#import <netinet/in.h>
#import <netinet/in_systm.h>
#import <netinet/ip.h>
#import <netinet/in_pcb.h>
#import <netinet/ip.h>
#import <netinet/ip_var.h>
#import <mach/mach_types.h>
#import <mach/mig_errors.h>
#import <kern/zalloc.h>
#import <sys/param.h> /* NBPG, CLBYTES, etc. */
#import <vm/vm_kern.h> /* kernel_map */
#import <kern/parallel.h>
#import <machine/spl.h>
#if MACH_NET
#define IP_MSG_ID 1959
static
void
mach_net_output(
ipc_kmsg_t kmsg
)
{
struct ip_msg {
mach_msg_header_t header;
struct ip ip;
} *ip_msg = (struct ip_msg *) &kmsg->ikm_header;
struct mbuf *top;
register struct mbuf **mp;
register int size;
register caddr_t cp;
static struct route cached_route = { 0 };
if (!(current_proc()->p_ucred->cr_uid == 0))
return;
kmsg->ikm_header.msgh_size += kmsg->ikm_delta;
/*
* Only accept simple messages large enough
* to contain an IP header.
*/
if ((ip_msg->header.msgh_bits & MACH_MSGH_BITS_COMPLEX) ||
(ip_msg->header.msgh_size < sizeof (struct ip_msg)))
return;
/*
* Check that user has supplied a reasonable IP datagram length.
*/
size = ip_msg->ip.ip_len;
if ((size <= sizeof(struct ip)) || (size > (ip_msg->header.msgh_size - sizeof(mach_msg_header_t))))
return;
/*
* Remainder of this procedure must execute on master.
*/
unix_master();
/*
* Copy data to mbuf chain.
* This loop is similar to those in sosend() (in sys/uipc_socket.c)
* and if_ubaget() (in vaxif/if_uba.c).
*/
top = 0;
mp = ⊤
cp = (caddr_t) &ip_msg->ip;
while (size > 0) {
register struct mbuf *m;
register unsigned int len;
MGET(m, M_WAIT, MT_DATA);
if (m == 0) {
m_freem(top);
unix_release();
return;
}
if (size >= NBPG/2) { /* heuristic */
MCLGET(m);
if (m->m_len != MCLBYTES)
m->m_len = len = MIN(MLEN, size);
else
m->m_len = len = MIN(MCLBYTES, size);
} else
m->m_len = len = MIN(MLEN, size);
bcopy(cp, mtod(m, caddr_t), len);
cp += len;
size -= len;
*mp = m;
mp = &m->m_next;
}
/*
* Use cached route if the destination address is the same.
* Otherwise, zero the route so that ip_output() will find a new one.
* The use of the cached route decreases the total time to send a datagram
* by as much as 40%.
*/
if (((struct sockaddr_in *) &cached_route.ro_dst)->sin_addr.s_addr
!= ip_msg->ip.ip_dst.s_addr) {
if (cached_route.ro_rt)
RTFREE(cached_route.ro_rt);
cached_route.ro_rt = 0;
}
/*
* Send the mbuf chain.
* We use the IP_FORWARDING option because
* the user supplies a complete IP header.
* Deallocation of the mbuf chain is handled by ip_output().
*/
if (ip_output(top, (struct mbuf *)0,
&cached_route, (IP_FORWARDING|IP_ALLOWBROADCAST)) != 0) {
unix_release();
return;
}
unix_release();
return;
}
boolean_t
netipc_msg_send(
ipc_kmsg_t kmsg
)
{
if (kmsg->ikm_header.msgh_id != IP_MSG_ID)
return (FALSE);
mach_net_output(kmsg);
return (TRUE);
}
/*
* Data structures and associated routines
* to register IPC ports for incoming datagrams.
*
* Listeners are organized into buckets by the (IP) protocol number
* for which they are listening. Within a bucket, the listeners
* are linked in a list that is maintained by a move-to-front strategy.
*
* A zero entry in any of the source or destination
* fields indicates a wildcard that matches the corresponding field
* in any incoming datagram.
*
* The lock field is used for mutual exclusion when adding listeners.
* Lookups do not require synchronization because the ipc_port field
* (used to tell whether a listener is present) is always updated last,
* in a single operation.
*
* The routine to add and remove listeners are called by the user
* via a Matchmaker interface. Remove_listener() is also called by
* port_deallocate().
*/
struct lbucket {
decl_simple_lock_data(, lock)
struct listener *head;
};
#define LBHSZ 16 /* kept small -- not many protocols */
#define LBHASH(protocol) ((protocol) % LBHSZ)
struct lbucket listeners[LBHSZ]; /* indexed by hashed protocol */
struct listener {
struct listener *next;
long src_addr;
long dst_addr;
unsigned short src_port;
unsigned short dst_port;
port_t ipc_port;
};
#define LISTENER_MAX 100
#define LISTENER_CHUNK 1
zone_t listener_zone;
#define FOR_EACH_BUCKET(b) for((b)=listeners; (b) < &listeners[LBHSZ]; (b)++)
#define FOR_EACH_LISTENER(lp,b) for((lp)=(b)->head; (lp) != 0; (lp)=(lp)->next)
#define LOCK_BUCKET(b, ipl) { ipl = splnet(); simple_lock(&(b)->lock); }
#define UNLOCK_BUCKET(b, ipl) { simple_unlock(&(b)->lock); splx(ipl); }
#endif
/*
* Register a listener.
* No checking is done to prevent conflict with existing listeners.
*/
/*ARGSUSED*/
#if m68k
/*
* Don't know why this pragme is here. My guess is an old M68K
* compiler bug which has probably been fixed. -PFK
*/
#pragma CC_OPT_OFF
#endif m68k
kern_return_t netipc_listen(server_port, _src_addr, _dst_addr,
_src_port, _dst_port, _protocol, ipc_port)
port_t server_port; /* unused */
int _src_addr;
int _dst_addr;
int _src_port;
int _dst_port;
int _protocol;
port_t ipc_port;
{
#if MACH_NET
register struct listener *lp;
register struct lbucket *b;
int ipl;
long src_addr = _src_addr;
long dst_addr = _dst_addr;
unsigned short src_port = _src_port;
unsigned short dst_port = _dst_port;
u_char protocol = _protocol;
if (!(current_proc()->p_ucred->cr_uid == 0))
return(KERN_NO_ACCESS);
if (ipc_port == PORT_NULL)
return(KERN_INVALID_ARGUMENT);
lp = (struct listener *) zalloc(listener_zone);
lp->src_addr = src_addr;
lp->src_port = src_port;
lp->dst_addr = dst_addr;
lp->dst_port = dst_port;
ipc_port_reference((ipc_port_t)lp->ipc_port = ipc_port);
b = &listeners[LBHASH(protocol)];
LOCK_BUCKET(b, ipl);
lp->next = b->head;
b->head = lp;
UNLOCK_BUCKET(b, ipl);
ipc_kobject_set((ipc_port_t)ipc_port, 0, IKOT_NETIPC);
return(KERN_SUCCESS);
#else
return (KERN_FAILURE);
#endif
}
#if m68k
/*
* See comment at beginning of routine.
*/
#pragma CC_OPT_RESTORE
#endif m68k
/*
* Remove all listeners with the specified port.
*/
/*ARGSUSED*/
kern_return_t netipc_ignore(server_port, ipc_port)
port_t server_port; /* unused */
register port_t ipc_port;
{
#if MACH_NET
register struct lbucket *b;
register struct listener *prev, *lp;
kern_return_t result = KERN_FAILURE;
int ipl;
if (ipc_port == PORT_NULL)
return KERN_INVALID_ARGUMENT;
FOR_EACH_BUCKET (b) {
LOCK_BUCKET(b, ipl);
prev = b->head;
FOR_EACH_LISTENER (lp, b) {
if (lp->ipc_port == ipc_port) {
result = KERN_SUCCESS;
if (lp == b->head) {
b->head = lp->next;
zfree(listener_zone, (vm_offset_t) lp);
if ((prev = lp = b->head) == 0)
break;
} else {
prev->next = lp->next;
zfree(listener_zone, (vm_offset_t) lp);
lp = prev;
}
ipc_port_release((ipc_port_t)ipc_port);
}
else {
prev = lp;
}
}
UNLOCK_BUCKET(b, ipl);
}
return(result);
#else
return (KERN_FAILURE);
#endif
}
#if MACH_NET
/*
* Find a listener for the given source, destination, and protocol.
*/
port_t find_listener(src_addr, src_port, dst_addr, dst_port, protocol)
u_long src_addr;
u_short src_port;
u_long dst_addr;
u_short dst_port;
u_char protocol;
{
register struct lbucket *b;
register struct listener *prev, *lp;
#define ADDR_MATCH(lp, addr) ((lp)->addr==INADDR_ANY || (lp)->addr==(addr))
#define PORT_MATCH(lp, port) ((lp)->port==0 || (lp)->port ==(port))
b = &listeners[LBHASH(protocol)];
prev = b->head;
FOR_EACH_LISTENER(lp, b) {
/*
* The following tests should be ordered
* in increasing likelihood of success.
*/
if (PORT_MATCH(lp, dst_port) &&
PORT_MATCH(lp, src_port) &&
ADDR_MATCH(lp, src_addr) &&
ADDR_MATCH(lp, dst_addr)) {
if (lp != b->head) {
/* move to front */
prev->next = lp->next;
lp->next = b->head;
b->head = lp;
}
return(lp->ipc_port);
} else
prev = lp;
}
return(PORT_NULL);
}
/*
* IP header plus source and destination ports.
* Common to both UDP datagrams and TCP segments.
*/
struct ip_plus_ports {
struct ip ip_header;
u_short src_port;
u_short dst_port;
};
/*
* Skeletal IPC message header for fast initialization.
*/
static mach_msg_header_t ip_msg_template;
#define MACH_NET_MSG_SIZE_MAX 2048
zone_t mach_net_kmsg_zone;
void mach_net_init()
{
register struct lbucket *b;
vm_offset_t zone_data;
listener_zone = zinit(
sizeof(struct listener),
LISTENER_MAX * sizeof(struct listener),
LISTENER_CHUNK * sizeof(struct listener),
FALSE,
"net listener zone"
);
ip_msg_template.msgh_bits =
MACH_MSGH_BITS(MACH_MSG_TYPE_PORT_SEND, 0) |
MACH_MSGH_BITS_OLD_FORMAT;
ip_msg_template.msgh_size = MACH_NET_MSG_SIZE_MAX - IKM_OVERHEAD;
ip_msg_template.msgh_remote_port = MACH_PORT_NULL;
ip_msg_template.msgh_local_port = MACH_PORT_NULL;
ip_msg_template.msgh_id = IP_MSG_ID;
FOR_EACH_BUCKET (b) {
simple_lock_init(&b->lock);
}
/*
* Create and allocate some space for our private kmsg zone.
*/
mach_net_kmsg_zone = zinit(MACH_NET_MSG_SIZE_MAX, 4 * MACH_NET_MSG_SIZE_MAX,
MACH_NET_MSG_SIZE_MAX, FALSE, "mach_net messages");
zchange(mach_net_kmsg_zone, FALSE, FALSE, FALSE, FALSE);
(void) kmem_alloc_wired(kernel_map,
&zone_data, 4 * MACH_NET_MSG_SIZE_MAX);
zcram(mach_net_kmsg_zone, zone_data,
4 * MACH_NET_MSG_SIZE_MAX);
}
void
netipc_msg_release(
ipc_kmsg_t kmsg
)
{
zfree(mach_net_kmsg_zone, kmsg);
}
/*
* This routine is called from ipintr() (in netinet/ip_input.c)
* to filter incoming datagrams.
* Returns TRUE if a listener was found (or the packet was dropped),
* FALSE if the datagram should be passed on to socket code.
*
* This routine is always called at splnet, and must return that way.
* However, it must drop interrupt level to spl0 to use any of the MACH_IPC
* routines (which do not block interrupts).
*/
boolean_t receive_ip_datagram(mp)
struct mbuf **mp;
{
register struct mbuf *m = *mp;
register struct ip_plus_ports *ipp;
port_t port;
ipc_kmsg_t kmsg;
register caddr_t cp;
int space;
/*
* Get IP header and ports (for UDP or TCP) together in first mbuf.
* Note: IP leaves IP header in first mbuf.
*/
ipp = mtod(m, struct ip_plus_ports *);
if (ipp->ip_header.ip_hl > (sizeof (struct ip) >> 2))
ip_stripoptions(&ipp->ip_header, (struct mbuf *)0);
if (m->m_off > MMAXOFF || m->m_len < sizeof(struct ip_plus_ports)) {
if ((*mp = m = m_pullup(m, sizeof(struct ip_plus_ports))) == 0)
return(TRUE);
ipp = mtod(m, struct ip_plus_ports *);
}
port = find_listener(
ipp->ip_header.ip_src.s_addr, ipp->src_port, /* source addr and port */
ipp->ip_header.ip_dst.s_addr, ipp->dst_port, /* dest addr and port */
ipp->ip_header.ip_p); /* protocol */
if (port == PORT_NULL) {
/*
* No one is listening for this datagram.
* Pass it on to the socket code.
*/
return(FALSE);
}
/*
* Drop interrupt level to use MACH_IPC.
*/
(void) spl0();
if ((kmsg = (ipc_kmsg_t) zget(mach_net_kmsg_zone)) == IKM_NULL) {
/*
* Could not get a buffer - drop the datagram on the floor.
printf("receive_ip_datagram: kern_msg_allocate returns null.\n");
*/
m_freem(m);
/*
* Raise interrupt level to return.
*/
(void) splnet();
return(TRUE);
}
ikm_init_special(kmsg, IKM_SIZE_NETIPC);
/*
* Undo changes to IP header made by ipintr()
* before we were called. Unfortunately, the
* type of service field is gone for good.
*/
ipp->ip_header.ip_len += (ipp->ip_header.ip_hl << 2);
ipp->ip_header.ip_off >>= 3;
/*
* Copy mbuf chain to kmsg buffer.
*/
space = MACH_NET_MSG_SIZE_MAX - sizeof (struct ipc_kmsg);
cp = (caddr_t)(&kmsg->ikm_header + 1);
while (m && space > 0) {
register unsigned int len;
len = MIN(m->m_len, space);
bcopy(mtod(m, caddr_t), cp, len);
cp += len;
space -= len;
m = m_free(m);
}
kmsg->ikm_delta = space;
space &= ~3;
kmsg->ikm_delta = space - kmsg->ikm_delta;
/*
* Fill in message header and deliver it.
*/
kmsg->ikm_header = ip_msg_template;
kmsg->ikm_header.msgh_size -= space;
kmsg->ikm_header.msgh_remote_port = port;
ipc_port_reference((ipc_port_t)port);
ipc_mqueue_send_always(kmsg);
/*
* Raise interrupt level to return.
*/
(void) splnet();
return(TRUE);
}
#endif