1
0
mirror of https://github.com/PDP-10/klh10.git synced 2026-01-13 23:35:45 +00:00
PDP-10.klh10/src/dpimp.c
2015-12-22 21:03:27 +01:00

1855 lines
58 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* DPIMP.C - ARPANET IMP device emulator
*/
/* $Id: dpimp.c,v 2.5 2003/02/23 18:07:35 klh Exp $
*/
/* Copyright © 1992-1999, 2001 Kenneth L. Harrenstien
** All Rights Reserved
**
** This file is part of the KLH10 Distribution. Use, modification, and
** re-distribution is permitted subject to the terms in the file
** named "LICENSE", which contains the full text of the legal notices
** and should always accompany this Distribution.
**
** This software is provided "AS IS" with NO WARRANTY OF ANY KIND.
**
** This notice (including the copyright and warranty disclaimer)
** must be included in all copies or derivations of this software.
*/
/*
* $Log: dpimp.c,v $
* Revision 2.5 2003/02/23 18:07:35 klh
* Add NetBSD read loop fix for bpf.
*
* Revision 2.4 2001/11/19 10:31:57 klh
* Major revision of ARP code to do full ARP request/reply handling,
* needed for Linux; may come in handy for other ports.
*
* Revision 2.3 2001/11/10 21:28:59 klh
* Final 2.0 distribution checkin
*
*/
/*
This is a program intended to be run as a child of the KLH10
PDP-10 emulator, in order to provide an IP-only ethernet interface
that looks like a simplified IMP. It uses the host system's
packet-filtering mechanism to access the ethernet, and assumes that
only IP packets will be transferred in and out. This mechanism might
be one of:
PCAP - The portable packet capture library
TAP - Ethernet tunnel device
TUN - IP tunnel device (the simplest).
In what follows, "NET" is understood to be one of the above.
"Messages" to and from the IMP are sent over the standard DP
shared memory mechanism; these are composed of 8-bit bytes in exactly
the same order as if an IMP was sending or receiving them. Packets to
and from the NET are ethernet packets.
There are actually two processes active, one which pumps data
from the NET to the IMP-output buffer, and another that pumps in the
opposite direction from an input buffer to the NET. Generally they
are completely independent.
-----------------------------
The IP address used for the emulated host is whatever is
provided in the KLH10 configuration command for the LHDH device, which
interfaces to the IMP. This address must obviously be identical to
what the emulated host OS thinks it is!
Ensuring that inbound IP traffic is directed to the right
place is relatively easy. First, the IP address for the emulated host
must conform to the architecture of the local ethernet that the native
host is on, so that routers/gateways will direct packets to the
correct LAN. At this point, ARP takes over.
ARPs are handled by the native host OS. DPIMP arranges for
this by inserting an ARP entry into the kernel with the ATF_PUBL
(publish) flag set. This entry has the emulated host IP address paired with
the native host device ethernet address; the ATF_PUBL flag persuades
the native OS to act as a server in response to ARPs for that IP address.
As long as the emulated host IP address fits within the local network
structure, so that routers/gateways will direct packets to the right
LAN, this ARP entry ensures that the packets will arrive at the native
host's ethernet interface. Note: the code attempts to pick a reasonable
default for the local ethernet interface, unless a particular (and
possibly dedicated) interface is specified.
The NET packet-filtering mechanism is used to filter out
IP-type ethernet packets where the IP datagram's destination matches
that of the emulated host. While these IP datagrams are also
processed further within the OS when using a shared interface, it
appears that unless forwarding is explicitly set up or the kernel
compiled as a gateway, these datagrams will just be dropped on the
floor, which is precisely what we want.
Representing the source address in the IMP leader provided to
the emulated host (as a 24-bit host/imp field) is problematical, as is
the reverse mapping. Note that on receipt, only the 48-bit ethernet
address of the sender is known; the IP source address generally won't
be that of the last-hop sender. The ITS code doesn't really need to
pay attention to this field on input, but on output something has to be
picked.
Outbound routing in general is more difficult. IP addresses
can be turned into local network addresses by using AF_INET type
addresses in the putmsg() call for NET output; this causes the output
interface to look the IP address up in the ARP tables, and do an actual
ARP if necessary. However, this only works for the local network, and
is useless for finding a router or gateway; in order to send packets
elsewhere, the correct IP address must be known for a gateway/router on
the local net.
(NIT WARNING! This actually DOES NOT WORK for NIT because the
nit_if stream output code checks for and only accepts messages with
AF_UNSPEC addresses, so any NIT output must be accompanied by an
ethernet destination address. There's a slim possibility that some
sort of "raw" socket output can be done using the NIT (via protosw
dispatch: pr_output entry in nitsw[] table for SOCK_RAW, pointed to by
nitdomain (AF_NIT). Invoked by raw_usrreq in net/raw_usrreq.c. Must
investigate... raw socket output uses sendto() to specify address
along with data.)
One idea is to retain the use of ARPANET addresses for purposes
of talking between the emulated host and the DPIMP, such that only the
arpanet address of the host and a mythical prime gateway or two are known.
The main problem with this is that the ITS code would proceed to use its
ARPANET address in IP headers, which cannot be changed without revising
the IP checksum as well.
The other method is to pretend that the local network is an IMP
class A network, and map the low 3 octets of IP addresses directly into
the 24-bit host/imp field. However, it will be necessary to assemble
an ITS that knows its true IP address on the local net, and change the
prime-gateway routing table (at IPGWTG in INET >) to know about the
local net router/gateway. Any IMP code that assumes it lives on the
ARPANET must likewise be changed, although this is not too hard.
A bigger pain is that this code also must be modified to know
about the actual subnet mask in use, so it doesn't attempt to directly
send stuff that actually should go to the gateway. Either ITS must know
the address architecture plus a default gateway, or DPIMP must.
If the local net is a well-behaved class A, B, or C net then
things may work well at the ITS level. If not (ie subnetting is in effect)
then perhaps DPIMP should deal with it.
Compromise method: have ITS know its "real" IP address, so that
it will be inserted into all datagrams properly. However, disregard the
host/imp address in the IMP leader and always have DPIMP decide for itself
where to send the datagram based on the IP destination address. This
means DPIMP has to know the local subnetting arrangement and gateway routes
but this information can in principle be extracted from the kernel.
Under this scheme, it doesn't matter too much how the IMP leader
address is put together. However, just to have SOME consistent scheme,
we'll use:
IP octet 1 - Would go into NET field, but for now keep 0
IP octet 2 - Host #
IP octet 3 - IMP # high byte
IP octet 4 - IMP # low byte
AGH! Unfortunately it turns out that while the subnet mask can easily
be procured via an ioctl(), there is no way to get routing table
information other than by reading the kernel memory directly (which is
how netstat gets at the info). So it's very painful for DPIMP to do
the routing. Moreover, it will be inefficient for DPIMP to just use a
single default gateway if more than one is on the local network,
because redirects will not be recognized by DPIMP (unless even more
hair is added to intercept and understand them) and any attempt by ITS
to comply with the redirects it receives is useless as long as DPIMP
ignores the IMP leader addressing info.
For now, let's build the IP address by slapping the 1st IP byte
on from the native host's IP address, and taking the rest from the host/imp
fields per scheme above. DPIMP then checks to see if it's a local net
address, and if so uses that IP address. If not (ITS thinks it's local,
but it isn't on right subnet) then DPIMP substitutes a single default
gateway address. This resolves some but not all of the problem.
-------------------------------------------
Another ARP screw:
On at some systems (OSF/1 for example) the native host's IP
address may not be listed in the kernel's ARP tables! This means that
when the emulated host attempts to send an IP packet to the native
host, its attempt to determine the ethernet destination address will
fail because the arp_look() call can't find it.
This can be fixed in a couple of ways:
(1) Invoke the "arp" command as SU to insert the (perm, pub) entry
into the tables.
Pro: ensures set to right thing, DPIMP works without change.
Con: painful and subject to human error.
(2) DPIMP to look up the e/n address for the native host's default IP
interface, and store that mapping in its own table.
Pro: convenient, works invisibly.
Con: if it picks wrong interface you're hosed invisibly.
Algorithm to use:
- See if IMP ifc is same as default IP ifc.
- If yes - shared, not dedicated.
Get ether addr from pfdata.pf_FD as usual and copy into arptab cache.
- If no - dedicated, not shared.
See if native IP address can be found in OS ARP table.
If yes - save in cache, done.
If not - open another PF connection just to get EA for that
ifc.
-------------------------------------------
TUN to the rescue:
That being said, the new BSD "tun" (IP tunnel) device solves
most of these problems by moving all of the IP routing mechanisms into
the native OS itself. Once initialized, constant ARP hacking is not
required, nor any ethernet header hacking -- in fact it should work
for any physical network! KLH10_NET_TUN should eventually become the
default for every OS that implements /dev/tun.
*/
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <stdarg.h>
#include "klh10.h" /* Get config params */
/* This must precede any other OSD includes to ensure that DECOSF gets
the right flavor sockaddr (sigh)
*/
#define OSN_USE_IPONLY 1 /* Only need IP stuff */
#include "osdnet.h" /* OSD net defs, shared with DPNI20 */
#include <sys/resource.h> /* For setpriority() */
#include <sys/mman.h> /* For mlockall() */
#if CENV_SYS_NETBSD
#include <poll.h>
#endif
#include "dpimp.h" /* DPIMP specific defs, grabs DPSUP if needed */
#ifdef RCSID
RCSID(dpimp_c,"$Id: dpimp.c,v 2.5 2003/02/23 18:07:35 klh Exp $")
#endif
/* Structure of data shared between the two DPIMP forks; this is overlaid
on the "blob" within dpimp_s.
Currently only the ARP hackery uses this.
For concurrency fans, the access mechanism implemented here is a variant
of Peterson's algorithm, which in turn is akin to Dekker's algorithm.
*/
struct dpimpsh_s {
/* Locking mechanism to control access. */
int dpimpsh_lock[2]; /* Flag - trying to enter critical section */
int dpimpsh_lockid; /* Locker's ID */
/* ARP cache, may or may not be needed */
int dpimpsh_arpsiz; /* # of entries in ARP table */
int dpimpsh_arprefs; /* crude timeout counter */
struct arpent {
struct in_addr at_iaddr;
struct ether_addr at_eaddr;
int at_flags; /* ARPF_xxx flags, similar to ATF_xxx */
#define ARPF_INUSE 0x1
#define ARPF_COM 0x2
#define ARPF_PERM 0x4
int at_lastref; /* Value of arprefs at last ref */
} dpimpsh_arptab[1]; /* Actually N entries! */
};
#define DPIMPSH(dpimp) ((struct dpimpsh_s *)dpimp->dpimp_blob)
/* Globals */
struct dp_s dp; /* Device-Process struct for DP ops */
int cpupid; /* PID of superior CPU process */
int chpid; /* PID of child (R proc) */
int mylockid; /* Locker IDs: 1 for W, 0 for R */
int othlockid;
int swstatus = TRUE;
struct pfdata pfdata; /* Packet-Filter state */
struct in_addr ehost_ip; /* Emulated host IP addr, net order */
struct in_addr ihost_ip; /* IMP/Native host IP addr, net order */
struct in_addr ihost_nm; /* IMP/Native host subnet netmask, net order */
struct in_addr ihost_net; /* IMP/Native host net #, net order */
struct in_addr tun_ip; /* IP addr of host side of tunnel */
struct in_addr gwdef_ip; /* IP addr of default prime gateway */
struct ether_addr ehost_ea; /* Emulated host ethernet addr */
struct ether_addr ihost_ea; /* IMP/Native host ethernet addr */
int eaflags = 0;
#define EAF_IHOST 01 /* ihost_ea is set - IMP/Native host EA */
#define EAF_EHOST 02 /* ehost_ea is set - Emulated host EA */
/* Debug flag reference. Use DBGFLG within functions that have "dpimp";
* all others must use DP_DBGFLG. Both refer to the same location.
* Caching a local copy of the debug flag is a no-no because it lives
* in a shared memory location that may change at any time.
*/
#define DBGFLG (dpimp->dpimp_dpc.dpc_debug)
#define DP_DBGFLG (((struct dpimp_s *)dp.dp_adr)->dpimp_dpc.dpc_debug)
/* Local predeclarations */
void imptohost(struct dpimp_s *);
void hosttoimp(struct dpimp_s *);
void net_init(struct dpimp_s *);
#if !KLH10_NET_TUN
void arp_init(struct dpimp_s *);
struct arpent *arptab_look(struct in_addr);
struct arpent *arp_look(struct in_addr, struct ether_addr *);
struct arpent *arp_tnew(struct in_addr addr, struct ether_addr *, int);
int arp_refreset(void);
void arp_set(struct arpent *, struct in_addr, struct ether_addr *, int);
void arp_req(struct in_addr *ipa);
void arp_gotrep(unsigned char *buf, int cnt);
void arp_reply(unsigned char *eap, unsigned char *iap);
int hi_iproute(struct in_addr *ipa, unsigned char *lp, int cnt);
void ip_write(struct in_addr *, unsigned char *, int);
void ether_write(struct eth_header *, unsigned char *, int);
#endif /* !KLH10_NET_TUN */
void ihl_frag(int, unsigned char *);
void ihl_hhsend(struct dpimp_s *, int, unsigned char *);
void dumppkt(unsigned char *, int);
/* Error and diagnostic output */
static const char progname_i[] = "dpimp";
static const char progname_r[] = "dpimp-R";
static const char progname_w[] = "dpimp-W";
static const char *progname = progname_i;
static void efatal(int num, char *fmt, ...)
{
fprintf(stderr, "\n[%s: Fatal error: ", progname);
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
}
fputs("]\r\n", stderr);
/* DP automatically kills any child as well. */
dp_exit(&dp, num);
}
static void esfatal(int num, char *fmt, ...)
{
fprintf(stderr, "\r\n[%s: ", progname);
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
}
fprintf(stderr, " - %s]\r\n", dp_strerror(errno));
/* DP automatically kills any child as well. */
dp_exit(&dp, num);
}
static void dbprint(char *fmt, ...)
{
fprintf(stderr, "[%s: ", progname);
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
}
fputs("]", stderr);
}
static void dbprintln(char *fmt, ...)
{
fprintf(stderr, "[%s: ", progname);
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
}
fputs("]\r\n", stderr);
}
static void error(char *fmt, ...)
{
fprintf(stderr, "\n[%s: ", progname);
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
}
fputs("]\r\n", stderr);
}
static void syserr(int num, char *fmt, ...)
{
fprintf(stderr, "\n[%s: ", progname);
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
}
fprintf(stderr, " - %s]\r\n", dp_strerror(num));
}
int initdebug = 0;
int
main(int argc, char **argv)
{
struct dpimp_s *dpimp; /* Ptr to shared memory area */
/* Search for a "-debug" command-line argument so that we can start
debug output ASAP if necessary.
*/
if (argc > 1) {
int i;
for (i = 1; i < argc; ++i) {
if (strcmp(argv[i], "-debug") == 0) {
initdebug = TRUE;
break;
}
}
}
if (initdebug)
dbprint("Starting");
/* Right off the bat attempt to get the highest scheduling priority
** we can, since a slow response will cause the 10 monitor to declare
** the interface dead.
*/
#if CENV_SYS_SOLARIS || CENV_SYS_DECOSF || CENV_SYS_XBSD || CENV_SYS_LINUX
if (setpriority(PRIO_PROCESS, 0, -20) < 0)
syserr(errno, "Warning - cannot set high priority");
#elif CENV_SYS_UNIX /* Try old generic Unix call */
if (nice(-20) == -1)
syserr(errno, "Warning - cannot set high priority");
#else
error("Warning - cannot set high priority");
#endif
/* Next priority is to quickly close the vulnerability window;
disable TTY cruft to ensure that any TTY hacking done by superior
process doesn't inadvertently kill us off.
*/
signal(SIGINT, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
if (initdebug)
dbprint("Started");
/* General initialization */
if (geteuid() != 0)
efatal(1, "Must be superuser!");
if (!dp_main(&dp, argc, argv)) {
efatal(1, "DP init failed!");
}
dpimp = (struct dpimp_s *)dp.dp_adr; /* Make for easier refs */
/* Verify that the structure version is compatible */
if (dpimp->dpimp_ver != DPIMP_VERSION) {
efatal(1, "Wrong version of DPIMP: lhdh=%0lx dpimp=%0lx",
(long)dpimp->dpimp_ver, (long)DPIMP_VERSION);
}
/* Now can access DP args!
From here on we can use DBGFLG, which is actually a shared
memory reference that dpimp points to. Check here to accomodate the
case where it's not already set but "-debug" was given as a command
arg; leave it alone if already set since the exact bits have
significance.
*/
if (initdebug && !DBGFLG)
DBGFLG = 1;
if (DBGFLG)
dbprint("DP inited");
/* Always attempt to lock memory since the DP processes are fairly
** small, must respond quickly, and SU mode is more or less guaranteed.
** Skip it only if dp_main() already did it for us.
*/
#if CENV_SYS_DECOSF || CENV_SYS_SOLARIS || CENV_SYS_LINUX
if (!(dpimp->dpimp_dpc.dpc_flags & DPCF_MEMLOCK)) {
if (mlockall(MCL_CURRENT|MCL_FUTURE) != 0) {
dbprintln("Warning - cannot lock memory");
}
}
#endif
/* Now set up legacy variables based on parameters passed through
shared DP area.
*/
memcpy((void *)&ehost_ip, dpimp->dpimp_ip, 4); /* Host IP addr */
memcpy((void *)&tun_ip, dpimp->dpimp_tun, 4); /* Tunnel addr */
memcpy((void *)&gwdef_ip, dpimp->dpimp_gw, 4); /* Default GW addr */
memcpy((void *)&ehost_ea, dpimp->dpimp_eth, 6); /* Host Ether addr */
/* IMP must always have IP address specified! */
if (memcmp(dpimp->dpimp_ip, "\0\0\0\0", 4) == 0)
efatal(1, "no IP address specified");
/* Canonicalize ARP hackery flags */
if (dpimp->dpimp_doarp == TRUE) /* If simply set to "true", */
dpimp->dpimp_doarp = DPIMP_ARPF_PUBL | /* then do all */
DPIMP_ARPF_PERM | DPIMP_ARPF_OCHK;
/* Set up shared area for the DPIMP forks */
DPIMPSH(dpimp)->dpimpsh_lock[0] = 0;
DPIMPSH(dpimp)->dpimpsh_lock[1] = 0;
/* See if EA provided */
if (memcmp((void *)&ehost_ea, "\0\0\0\0\0\0", 6) != 0) {
eaflags |= EAF_EHOST;
}
/* Initialize various network info */
net_init(dpimp);
#if !KLH10_NET_TUN
/* TUN may not have an ethernet address associated with it;
not sure what to do if DPIMP turns out to need one.
*/
/* See if ether address needs to be set */
if (memcmp((void *)&ihost_ea, "\0\0\0\0\0\0", 6) != 0)
eaflags |= EAF_IHOST;
switch (eaflags & (EAF_IHOST|EAF_EHOST)) {
case 0:
efatal(1, "no ethernet address");
case EAF_IHOST:
break; /* OK, don't need anything special */
case EAF_EHOST:
error("couldn't get native ether addr, using specified");
ea_set(&ihost_ea, &ehost_ea);
break;
case EAF_IHOST|EAF_EHOST:
if (memcmp((void *)&ihost_ea, (void *)&ehost_ea, 6) == 0)
break; /* OK, addresses are same */
/* Ugh, specified an EA address different from one actually
** in use by interface! For now, don't allow clobberage.
*/
efatal(1, "changing ethernet addr is disallowed");
break;
}
#endif /* !KLH10_NET_TUN */
/* Make this a status (rather than debug) printout? */
if (swstatus) {
char ipbuf[OSN_IPSTRSIZ];
char eabuf[OSN_EASTRSIZ];
#if !KLH10_NET_TUN
dbprintln("ifc \"%s\" => ether %s",
dpimp->dpimp_ifnam,
eth_adrsprint(eabuf, (unsigned char *)&ihost_ea));
dbprintln(" inet %s",
ip_adrsprint(ipbuf, (unsigned char *)&ihost_ip));
dbprintln(" netmask %s",
ip_adrsprint(ipbuf, (unsigned char *)&ihost_nm));
dbprintln(" net %s",
ip_adrsprint(ipbuf, (unsigned char *)&ihost_net));
dbprintln(" gwdef %s",
ip_adrsprint(ipbuf, (unsigned char *)&gwdef_ip));
#else
dbprintln("ifc \"%s\"",
dpimp->dpimp_ifnam);
dbprintln(" tun %s",
ip_adrsprint(ipbuf, (unsigned char *)&tun_ip));
#endif
dbprintln(" HOST %s",
ip_adrsprint(ipbuf, (unsigned char *)&ehost_ip));
}
/* Init ARP stuff - ensure can talk to native host.
** Set up ARP entry so hardware host knows about our IP address and
** can respond to ARP requests for it.
*/
#if !KLH10_NET_TUN /* If TUN, already done by osn_pfinit */
arp_init(dpimp);
if (!osn_arp_stuff(dpimp->dpimp_ifnam,
(unsigned char *)&ehost_ip,
(unsigned char *)&ihost_ea, TRUE)) /* Set us up */
esfatal(1, "OSN_ARP_STUFF failed");
#endif
/* Now start up a child process to handle input */
if (DBGFLG)
dbprint("Forking R process");
if ((chpid = fork()) < 0)
esfatal(1, "fork failed");
if (chpid == 0) {
/* Child process.
** Child inherits signal handlers, which is what we want here.
*/
mylockid = 0;
othlockid = 1;
/* Fix up xfer mechanism so ACK of DP input goes to correct proc */
dp.dp_adr->dpc_frdp.dpx_donpid = getpid();
/* And ensure its memory is locked too, since the lockage isn't
** inherited over a fork(). Don't bother warning if it fails.
*/
#if CENV_SYS_DECOSF || CENV_SYS_SOLARIS || CENV_SYS_LINUX
(void) mlockall(MCL_CURRENT|MCL_FUTURE);
#endif
progname = progname_r; /* Reset progname to indicate identity */
imptohost(dpimp); /* Child process handles input */
}
mylockid = 1;
othlockid = 0;
progname = progname_w; /* Reset progname to indicate identity */
hosttoimp(dpimp); /* Parent process handles output */
return 1; /* Never returns, but placate compiler */
}
/* NET_INIT - Initialize net-related variables,
** given network interface we'll use.
*/
void
net_init(struct dpimp_s *dpimp)
{
struct ifreq ifr;
#if !KLH10_NET_TUN
#if 1 /* This code is identical to dpni20 - merge in osdnet? */
/* Ensure network device name, if specified, isn't too long */
if (dpimp->dpimp_ifnam[0] && (strlen(dpimp->dpimp_ifnam)
>= sizeof(ifr.ifr_name))) {
esfatal(0, "interface name \"%s\" too long - max %d",
dpimp->dpimp_ifnam, (int)sizeof(ifr.ifr_name));
}
/* Determine network device to use, if none was specified (this only
** works for shared devices, as dedicated ones will be "down" and
** cannot be found by iftab_init).
** Also grab native IP and ethernet addresses, if ARP might need them.
*/
if ((!dpimp->dpimp_ifnam[0] && !dpimp->dpimp_dedic)
|| (dpimp->dpimp_doarp & DPIMP_ARPF_OCHK)) {
if (osn_iftab_init() <= 0)
esfatal(0, "Couldn't find interface information");
/* Found at least one! Pick first one, if a default is needed. */
if (!dpimp->dpimp_ifnam[0]) {
struct ifent *ife = osn_ipdefault();
if (!ife)
esfatal(0, "Couldn't find default interface");
if (strlen(ife->ife_name) >= sizeof(dpimp->dpimp_ifnam))
esfatal(0, "Default interface name \"%s\" too long, max %d",
ife->ife_name, (int)sizeof(dpimp->dpimp_ifnam));
strcpy(dpimp->dpimp_ifnam, ife->ife_name);
if (swstatus)
dbprintln("Using default interface \"%s\"", dpimp->dpimp_ifnam);
}
}
#endif
/* Now set remaining stuff */
/* Find IMP host's IP address for this interface */
if (!osn_ifipget(-1, dpimp->dpimp_ifnam, (unsigned char *)&ihost_ip)) {
efatal(1,"osn_ifipget failed for \"%s\"", dpimp->dpimp_ifnam);
}
/* Ditto for its network mask */
if (!osn_ifnmget(-1, dpimp->dpimp_ifnam, (unsigned char *)&ihost_nm)) {
efatal(1,"osn_ifnmget failed for \"%s\"", dpimp->dpimp_ifnam);
}
/* Now set remaining stuff */
ihost_net.s_addr = ihost_nm.s_addr & ihost_ip.s_addr; /* Local net */
/* Either move this check up much earlier, or find a way to
** query OS for a default gateway.
*/
if (gwdef_ip.s_addr == -1 || gwdef_ip.s_addr == 0)
efatal(1, "No default prime gateway specified");
#endif /* !KLH10_NET_TUN */
/* Set up appropriate net fd and packet filter.
** Should also determine interface's ethernet addr, if possible,
** and set ihost_ea.
*/
{
struct osnpf npf;
npf.osnpf_ifnam = dpimp->dpimp_ifnam;
npf.osnpf_ifmeth = dpimp->dpimp_ifmeth;
npf.osnpf_dedic = FALSE; /* Force filtering always! */
npf.osnpf_rdtmo = dpimp->dpimp_rdtmo;
npf.osnpf_backlog = dpimp->dpimp_backlog;
npf.osnpf_ip.ia_addr = ehost_ip;
npf.osnpf_tun.ia_addr = tun_ip;
/* Ether addr is both a potential arg and a returned value;
the packetfilter open may use and/or change it.
*/
ea_set(&npf.osnpf_ea, dpimp->dpimp_eth); /* Set requested ea if any */
osn_pfinit(&pfdata, &npf, (void *)dpimp); /* Will abort if fails */
ea_set(&ihost_ea, &npf.osnpf_ea); /* Copy actual ea if one */
tun_ip = npf.osnpf_tun.ia_addr; /* Copy actual tun if any */
}
}
/* The DPIMP packet filter must implement the following test:
**
** if ((dest_IP_addr == ITS_IP_addr) && (ethertype == IP)) { success; }
**
** For efficiency, the strictest test is done first -- the low shortword
** of the IP address, which is the most likely to differ from that of the
** native host. We do this even before checking whether the packet contains
** IP data; if it's too short, it's rejected anyway.
*/
#if KLH10_NET_PCAP
/*
** BPF filter program stuff.
** Note that you can also obtain a specific filter program for a given
** expression by using tcpdump(1) with the -d option, for example:
** tcpdump -d -s 1514 ip dst host 1.2.3.4
** produces:
*/
#if 0
{ 0x20, 0, 0, 0x0000001e }, /* (000) ld [30] */
{ 0x15, 0, 3, 0x01020304 }, /* (001) jeq #0x1020304 jt 2 jf 5 */
{ 0x28, 0, 0, 0x0000000c }, /* (002) ldh [12] */
{ 0x15, 0, 1, 0x00000800 }, /* (003) jeq #0x800 jt 4 jf 5 */
{ 0x06, 0, 0, 0x000005ea }, /* (004) ret #1514 */
{ 0x06, 0, 0, 0x00000000 }, /* (005) ret #0 */
#endif
#define OSN_PFSTRUCT bpf_program
#define PF_FLEN bf_len
#define PF_FILT bf_insns
#define BPF_PFMAX 50 /* Max instructions in BPF filter */
struct bpf_insn bpf_pftab[BPF_PFMAX];
struct bpf_program bpf_pfilter = { 0,
bpf_pftab };
struct bpf_insn bpf_stmt(unsigned short code, bpf_u_int32 k)
{
struct bpf_insn ret;
ret.code = code;
ret.jt = 0;
ret.jf = 0;
ret.k = k;
return ret;
}
struct bpf_insn bpf_jump(unsigned short code, bpf_u_int32 k,
unsigned char jt, unsigned char jf)
{
struct bpf_insn ret;
ret.code = code;
ret.jt = jt;
ret.jf = jf;
ret.k = k;
return ret;
}
/* BPF simple Loads */
#define BPFI_LD(a) bpf_stmt(BPF_LD+BPF_W+BPF_ABS,(a)) /* Load word P[a:4] */
#define BPFI_LDH(a) bpf_stmt(BPF_LD+BPF_H+BPF_ABS,(a)) /* Load short P[a:2] */
#define BPFI_LDB(a) bpf_stmt(BPF_LD+BPF_B+BPF_ABS,(a)) /* Load byte P[a:1] */
/* BPF Jumps and skips */
#define BPFI_J(op,k,t,f) bpf_jump(BPF_JMP+(op)+BPF_K,(k),(t),(f))
#define BPFI_JEQ(k,n) BPFI_J(BPF_JEQ,(k),(n),0) /* Jump if A == K */
#define BPFI_JNE(k,n) BPFI_J(BPF_JEQ,(k),0,(n)) /* Jump if A != K */
#define BPFI_JGT(k,n) BPFI_J(BPF_JGT,(k),(n),0) /* Jump if A > K */
#define BPFI_JLE(k,n) BPFI_J(BPF_JGT,(k),0,(n)) /* Jump if A <= K */
#define BPFI_JGE(k,n) BPFI_J(BPF_JGE,(k),(n),0) /* Jump if A >= K */
#define BPFI_JLT(k,n) BPFI_J(BPF_JGE,(k),0,(n)) /* Jump if A < K */
#define BPFI_JDO(k,n) BPFI_J(BPF_JSET,(k),(n),0) /* Jump if A & K */
#define BPFI_JDZ(k,n) BPFI_J(BPF_JSET,(k),0,(n)) /* Jump if !(A & K) */
#define BPFI_CAME(k) BPFI_JEQ((k),1) /* Skip if A == K */
#define BPFI_CAMN(k) BPFI_JNE((k),1) /* Skip if A != K */
#define BPFI_CAMG(k) BPFI_JGT((k),1) /* Skip if A > K */
#define BPFI_CAMLE(k) BPFI_JLE((k),1) /* Skip if A <= K */
#define BPFI_CAMGE(k) BPFI_JGE((k),1) /* Skip if A >= K */
#define BPFI_CAML(k) BPFI_JLT((k),1) /* Skip if A < K */
#define BPFI_TDNN(k) BPFI_JDO((k),1) /* Skip if A & K */
#define BPFI_TDNE(k) BPFI_JDZ((k),1) /* Skip if !(A & K) */
/* BPF Returns */
#define BPFI_RET(n) bpf_stmt(BPF_RET+BPF_K, (n)) /* Return N bytes */
#define BPFI_RETFAIL() BPFI_RET(0) /* Failure return */
#define BPFI_RETWIN() BPFI_RET((u_int)-1) /* Success return */
static void pfshow(struct OSN_PFSTRUCT *);
struct OSN_PFSTRUCT *
pfbuild(void *arg, struct in_addr *ipa)
{
struct dpimp_s *dpimp = (struct dpimp_s *)arg;
unsigned char *ucp = (unsigned char *)ipa;
struct OSN_PFSTRUCT *pfp = &bpf_pfilter;
struct bpf_insn *p;
p = pfp->PF_FILT; /* Point to 1st instruction in BPF program */
/* We're interested in IP (and thus ARP as well) packets.
This is assumed to be the LAST part of the filter, thus
it must either leave the correct result on the stack, or
ensure it is empty (if accepting the packet).
*/
if (ipa->s_addr != 0) {
/* Want to pass ARP replies as well, so we can see responses to any
** ARPs we send out?
** ARP *requests* are also passed! The hope is that
** osn_arp_stuff() will have ensured that the host platform
** proxy-answers requests for our IP address, but that doesn't
** always work.
*/
*p++ = BPFI_LDH(PKBOFF_ETYPE); /* Load ethernet type field */
*p++ = BPFI_CAMN(ETHERTYPE_ARP); /* Skip unless ARP packet */
*p++ = BPFI_RETWIN(); /* If ARP, win now! */
/* If didn't pass, check for our IP address */
*p++ = BPFI_LD(PKBOFF_IPDEST); /* Get IP dest address */
*p++ = BPFI_CAME(ntohl(ehost_ip.s_addr)); /* Skip if matches */
*p++ = BPFI_RETFAIL(); /* Nope, fail */
/* Passed IP check, one last thing... */
*p++ = BPFI_LDH(PKBOFF_ETYPE); /* Load ethernet type field */
*p++ = BPFI_CAMN(ETHERTYPE_IP); /* Skip unless IP packet */
*p++ = BPFI_RETWIN(); /* If IP, win now! */
*p++ = BPFI_RETFAIL(); /* Nope, fail */
} else {
/* If not doing IP, fail at this point because the packet
doesn't match any of the desired types.
*/
*p++ = BPFI_RETFAIL(); /* Fail */
}
pfp->PF_FLEN = p - pfp->PF_FILT; /* Set # of items on list */
if (DBGFLG) /* If debugging, print out resulting filter */
pfshow(pfp);
return pfp;
}
/* Debug auxiliary to print out packetfilter we composed.
*/
static void
pfshow(struct OSN_PFSTRUCT *pf)
{
int i;
fprintf(stderr, "[dpimp: kernel packetfilter pri <>, len %d:\r\n",
/* pf->PF_PRIO, */ pf->PF_FLEN);
for (i = 0; i < pf->PF_FLEN; ++i)
fprintf(stderr, "%04X %2d %2d %0X\r\n",
pf->PF_FILT[i].code,
pf->PF_FILT[i].jt,
pf->PF_FILT[i].jf,
pf->PF_FILT[i].k);
fprintf(stderr, "]\r\n");
}
#endif /* KLH10_NET_PCAP */
/* Because until very recently LNX had no kernel packet filtering, must do it
manually. Ugh!
Call this even when using a dedicated interface, since only IP stuff
should be passed through the IMP.
Returns TRUE if packet OK, FALSE if it should be dropped.
Note that the code parallels that for pfbuild().
*/
int lnx_filter(struct dpimp_s *dpimp,
unsigned char *bp,
int cnt)
{
/* Code assumes buffer is at least shortword-aligned. */
unsigned short *sp = (unsigned short *)bp;
unsigned short etyp;
/* Get ethernet protocol type.
Could also test packet length, but for now assume higher level
will take care of those checks.
*/
etyp = ntohs(sp[PKSWOFF_ETYPE]);
switch (etyp) {
/* Must pass on ARP processing (Linux doesn't proxy ARP for us!!)
*/
case ETHERTYPE_ARP:
return TRUE;
case ETHERTYPE_IP:
/* For IP packet, return TRUE if IP destination matches ours */
return (memcmp(dpimp->dpimp_ip, bp + PKBOFF_IPDEST, 4) == 0);
}
/* No other types allowed through IMP */
return FALSE;
}
#if !KLH10_NET_TUN
/* ARP hacking code. Originally modelled after old BSD ARP stuff. */
/* Structure of DPIMP's ARP cache:
The ARP cache is a simple table that is entered by means of a hash
function, but which has no separate bucket chains -- everything is on
the same "chain". If a desired entry is not found at the first hash,
all succeeding entries are checked until either an empty entry is hit
or the entire table is checked. This works because entries are never
flushed once entered.
The rationale for this is that doing an external ARP lookup is
much more expensive than the time to do a full table scan, so we never
want to re-use an entry unless the table is full, and we want to use
all possible entries.
If an ITS is ever brought up that becomes consistently busy enough
to thrash on the ARP table then a more sophisticated algorithm can be
used.
*/
/* Set up by init for easier reference */
static struct dpimpsh_s *arpp;
static struct arpent *arptab_lim;
#define ARPTAB_HASH(max,a) \
((unsigned long)(a) % (max))
/* ARP_INIT
** Set up our own ARP cache, and ensure that native host is in
** it. Must be called after packetfilter already opened.
*/
void
arp_init(struct dpimp_s *dpimp)
{
struct dpimpsh_s *dsh = DPIMPSH(dpimp);
struct ether_addr ea;
struct arpent *at;
/* Init statics. Find # entries available in shared-area ARP table,
which is assumed to have been already cleared.
Note we add 1 because 1st entry is already in dpimpsh_s.
*/
arpp = dsh;
dsh->dpimpsh_arpsiz = 1 + (
(dpimp->dpimp_blobsiz <= sizeof(struct dpimpsh_s))
? 0
: ((dpimp->dpimp_blobsiz - sizeof(struct dpimpsh_s))
/ sizeof(struct arpent)));
arptab_lim = &dsh->dpimpsh_arptab[dsh->dpimpsh_arpsiz];
if (at = arp_look(ihost_ip, &ea)) {
/* It's now there, ensure it stays there */
at->at_flags |= ARPF_PERM;
return;
}
/* Not found in ARP cache or OS table */
/* For now, assume shared and stuff an entry in our cache! */
if (swstatus)
dbprintln("no native ARP entry, assuming shared ifc");
/* Store entry, say complete & permanent */
(void) arp_tnew(ihost_ip, &ihost_ea, ARPF_PERM|ARPF_COM);
}
/* ARP_REFRESET - reset all reference stamps to compensate for
* overflow of the main ref counter.
* This will be a rare event, so rather than trying to do anything
* clever, just clear everything so all entries start from the
* same place. This also obviates any need to lock since it doesn't
* matter if it's done twice.
*/
int
arp_refreset(void)
{
struct dpimpsh_s *dsh = arpp;
struct arpent *at = &dsh->dpimpsh_arptab[0];
int max = dsh->dpimpsh_arpsiz;
int i;
for (i = 0; i < max; i++, at++) {
at->at_lastref = 0;
}
return dsh->dpimpsh_arprefs = 1;
}
struct arpent *
arptab_look(struct in_addr addr)
{
struct dpimpsh_s *dsh = arpp;
int i;
int max = dsh->dpimpsh_arpsiz;
struct arpent *at =
&dsh->dpimpsh_arptab[ARPTAB_HASH(max, addr.s_addr)];
for (i = 0; i < max; i++, at++) {
if (at >= arptab_lim)
at = &dsh->dpimpsh_arptab[0];
if (at->at_flags == 0)
break;
if (at->at_iaddr.s_addr == addr.s_addr)
return at;
}
return NULL; /* Table full or hit empty entry */
}
/*
* Enter a new address in arptab, pushing out the oldest entry
* from the bucket if there is no room.
* This always succeeds since no bucket can be completely filled
* with permanent entries.
* If new entry matches an existing one, always replaces it; addr may
* have changed!
*/
struct arpent *
arp_tnew(struct in_addr addr,
struct ether_addr *eap,
int flags)
{
struct dpimpsh_s *dsh = arpp;
int i;
int max = dsh->dpimpsh_arpsiz;
struct arpent *at =
&dsh->dpimpsh_arptab[ARPTAB_HASH(max, addr.s_addr)];
int oldest = dsh->dpimpsh_arprefs;
struct arpent *ato = NULL;
for (i = 0; i < max; i++, at++) {
if (at >= arptab_lim)
at = &dsh->dpimpsh_arptab[0];
if (at->at_flags == 0)
break; /* Found an empty entry */
if (at->at_iaddr.s_addr == addr.s_addr)
break; /* Matches existing */
if (at->at_flags & ARPF_PERM)
continue; /* Never replace this */
if (at->at_lastref < oldest) {
oldest = at->at_lastref;
ato = at;
}
}
if (i >= max) { /* No empty entry found? */
if (ato == NULL) {
efatal(1, "ARP table choked?!");
}
at = ato; /* Re-use oldest entry */
}
arp_set(at, addr, eap,
(at->at_flags & ARPF_PERM) /* Preserve ATF_PERM if old entry */
| ARPF_INUSE | flags);
return at;
}
/* ARP_SET - Set an ARP cache entry. Done in one place to centralize
* the update access control, even though it's quite simple.
*/
void
arp_set(struct arpent *at,
struct in_addr addr,
struct ether_addr *eap,
int flags)
{
struct dpimpsh_s *dsh = arpp;
/* Get write lock */
dsh->dpimpsh_lock[mylockid] = TRUE;
dsh->dpimpsh_lockid = mylockid;
while (dsh->dpimpsh_lock[othlockid] /* Spin wait if other has it */
&& (dsh->dpimpsh_lockid != mylockid));
/* Start critical section */
at->at_flags = flags;
if (at->at_flags & ARPF_COM) /* Use EA only if now complete */
at->at_eaddr = *eap;
else
ea_clr(&at->at_eaddr);
at->at_iaddr = addr;
at->at_lastref = dsh->dpimpsh_arprefs;
/* End critical section */
dsh->dpimpsh_lock[mylockid] = FALSE;
}
/* ARP_LOOK - Look up Ethernet address given IP address.
** If not in our own cache, checks system.
** If not in system, fails.
*/
struct arpent *
arp_look(struct in_addr ip,
struct ether_addr *eap)
{
struct arpent *at;
at = arptab_look(ip); /* Look up IP addr */
if (at && (at->at_flags & ARPF_COM)) {
int i;
/* Exists and complete */
ea_set(eap, &(at->at_eaddr)); /* Return ether addr */
/* Note at_lastref is modified here without locking; this is OK */
if ((i = ++(arpp->dpimpsh_arprefs)) < 0)
i = arp_refreset();
at->at_lastref = i;
return at;
}
/* Not found in our cache or not yet resolved, try OS query. */
if (osn_iftab_arpget(ip, (unsigned char *)eap) /* Try table lookup */
|| osn_arp_look(&ip, (unsigned char *)eap)) { /* Attempt OS lookup */
at = arp_tnew(ip, eap, ARPF_COM); /* Won! Store in our cache */
if (swstatus) {
char ipbuf[OSN_IPSTRSIZ];
char eabuf[OSN_EASTRSIZ];
dbprintln("ARP cached %s = %s",
ip_adrsprint(ipbuf, (unsigned char *)&ip),
eth_adrsprint(eabuf, (unsigned char *)eap));
}
return at;
}
return NULL;
}
struct offset_ether_arp {
unsigned char offset[DPIMP_DATAOFFSET];
struct ether_arp arp;
};
/* ARP_REQ - Generates and sends ARP request.
Must remember the fact in our cache, so can process reply ourself
if any is received.
*/
void
arp_req(struct in_addr *ipa)
{
static int ethbuild = 0, arpbuild = 0;
static struct eth_header eh;
static struct offset_ether_arp arp;
struct arpent *at;
struct ether_addr ea;
/* Store request in cache */
memset((char *)&ea, 0, sizeof(ea));
at = arp_tnew(*ipa, &ea, 0); /* Say incomplete with 0 flag */
/* Build ethernet header if haven't already */
if (!ethbuild) {
memset(eh_dptr(&eh), 0xff, /* Set dest broadcast addr */
ETHER_ADRSIZ);
eh_sset(&eh, &ihost_ea); /* Set ether source addr */
eh_tset(&eh, ETHERTYPE_ARP);
ethbuild = TRUE;
}
/* Now put together the ARP packet */
if (!arpbuild) {
arp.arp.arp_hrd = htons(ARPHRD_ETHER); /* Set hdw addr format */
arp.arp.arp_pro = htons(ETHERTYPE_IP); /* Set ptcl addr fmt */
arp.arp.arp_hln = sizeof(arp.arp.arp_sha); /* Hdw address len */
arp.arp.arp_pln = sizeof(arp.arp.arp_spa); /* Ptcl address len */
arp.arp.arp_op = htons(ARPOP_REQUEST); /* Type REQUEST */
ea_set(arp.arp.arp_sha, &ihost_ea); /* Sender hdw addr */
memcpy((char *)arp.arp.arp_spa, /* Sender IP addr */
(char *)&ihost_ip, sizeof(arp.arp.arp_sha));
arpbuild = TRUE;
}
/* Now do only thing that varies -- set IP addr we're looking up. */
memcpy((char *)arp.arp.arp_tpa, /* Target IP addr */
(char *)ipa, sizeof(arp.arp.arp_tpa));
/* Now send it! */
if (swstatus) {
char ipbuf[OSN_IPSTRSIZ];
dbprintln("ARP req %s", ip_adrsprint(ipbuf, (unsigned char *)ipa));
}
ether_write(&eh, (unsigned char *)&arp.arp, sizeof(arp.arp));
}
/* ARP_GOTREP - Process an ARP Request or Reply.
If it matches a request we already sent out, remember its
information. Next time we try sending a packet to that IP address
we'll find the entry.
We should respond to ARP requests for our own IPv4 address.
*/
#define ARP_PKTSIZ (sizeof(struct ether_header) + sizeof(struct ether_arp))
void
arp_gotrep(unsigned char *buf, int cnt)
{
struct ether_arp *aa;
struct arpent *at;
struct arpent ent;
if (DP_DBGFLG) {
char eabuf[OSN_EASTRSIZ];
dbprintln("Got ARP from %s", eth_adrsprint(eabuf, eh_sptr(buf)));
}
/* Verify packet is an ether ARP reply */
if (cnt < ARP_PKTSIZ) {
if (DP_DBGFLG)
dbprintln("Dropped ARP, size %d < %d", cnt, (int)ARP_PKTSIZ);
return;
}
aa = (struct ether_arp *)(buf + ETHER_HDRSIZ);
if (aa->arp_hrd != htons(ARPHRD_ETHER)) { /* Check hdw addr format */
if (DP_DBGFLG)
dbprintln("Dropped ARP, hrd %0x != %0x",
aa->arp_hrd, ARPHRD_ETHER);
return;
}
if (aa->arp_pro != htons(ETHERTYPE_IP)) { /* Check ptcl addr fmt */
if (DP_DBGFLG)
dbprintln("Dropped ARP, pro %0x != %0x",
aa->arp_pro, ETHERTYPE_IP);
return;
}
if (aa->arp_hln != sizeof(aa->arp_sha)) { /* Check Hdw address len */
if (DP_DBGFLG)
dbprintln("Dropped ARP, hln %d != %d",
aa->arp_hln, sizeof(aa->arp_sha));
return;
}
if (aa->arp_pln != sizeof(aa->arp_spa)) { /* Check Ptcl address len */
if (DP_DBGFLG)
dbprintln("Dropped ARP, pln %d != %d",
aa->arp_pln, sizeof(aa->arp_spa));
return;
}
/* Passed so far! Determine nature of ARP packet */
if (aa->arp_op == htons(ARPOP_REQUEST)) {
/* See if request is targetted at our IP address */
memcpy((char *)&ent.at_iaddr, (char *)aa->arp_tpa, IP_ADRSIZ);
if (ent.at_iaddr.s_addr == ehost_ip.s_addr) {
/* Yep! Send back our reply! */
arp_reply((unsigned char *)aa->arp_sha,
(unsigned char *)aa->arp_spa);
return;
}
if (DP_DBGFLG) {
char ipbuf[OSN_IPSTRSIZ];
char eabuf[OSN_EASTRSIZ];
dbprintln("Dropped ARP req for %s from %s",
ip_adrsprint(ipbuf, (unsigned char *)&ent.at_iaddr),
eth_adrsprint(eabuf, (unsigned char *)&aa->arp_sha));
}
return;
}
if (aa->arp_op != htons(ARPOP_REPLY)) { /* Check ARP type REPLY */
if (DP_DBGFLG)
dbprintln("Dropped ARP, type %0x != (req | rep)", aa->arp_op);
return;
}
/* Passed! Now extract resolved IP and EA from sender fields */
memcpy((char *)&ent.at_iaddr, (char *)aa->arp_spa, IP_ADRSIZ);
memcpy((char *)&ent.at_eaddr, (char *)aa->arp_sha, ETHER_ADRSIZ);
/* Now look up and determine if it's for an outstanding request of ours */
at = arptab_look(ent.at_iaddr);
if (!at || (at->at_flags & ARPF_COM)) {
if (DP_DBGFLG) {
char ipbuf[OSN_IPSTRSIZ];
dbprintln("Dropped ARP reply, %s IP %s",
(at ? "no req for" : "already have"),
ip_adrsprint(ipbuf, (unsigned char *)&ent.at_iaddr));
}
return;
}
/* Success! */
arp_set(at,
at->at_iaddr,
&ent.at_eaddr, /* Remember new ether addr */
at->at_flags | ARPF_COM); /* Say entry now complete */
if (swstatus) {
char ipbuf[OSN_IPSTRSIZ];
char eabuf[OSN_EASTRSIZ];
dbprintln("ARP cached %s = %s",
ip_adrsprint( ipbuf, (unsigned char *)&ent.at_iaddr),
eth_adrsprint(eabuf, (unsigned char *)&ent.at_eaddr));
}
}
/* ARP_REPLY - Send out an ARP Reply for ourselves
*/
void
arp_reply(unsigned char *eap, /* Requestor ether addr */
unsigned char *iap) /* Requestor IP addr */
{
struct eth_header eh;
struct offset_ether_arp arp;
/* Build ethernet header */
eh_dset(&eh, eap); /* Set dest addr */
eh_sset(&eh, &ihost_ea); /* Set ether source addr */
eh_tset(&eh, ETHERTYPE_ARP);
/* Now put together the ARP packet */
arp.arp.arp_hrd = htons(ARPHRD_ETHER); /* Set hdw addr format */
arp.arp.arp_pro = htons(ETHERTYPE_IP); /* Set ptcl addr fmt */
arp.arp.arp_hln = sizeof(arp.arp.arp_sha); /* Hdw address len */
arp.arp.arp_pln = sizeof(arp.arp.arp_spa); /* Ptcl address len */
arp.arp.arp_op = htons(ARPOP_REPLY); /* Type REPLY */
memcpy((char *)arp.arp.arp_spa, /* Sender IP addr */
(char *)&ihost_ip, sizeof(arp.arp.arp_sha));
/* Sender hdw addr and IP addr (that's us - the resolved info) */
ea_set(arp.arp.arp_sha, &ihost_ea); /* Sender hdw addr */
memcpy((char *)arp.arp.arp_spa, (char *)&ehost_ip, IP_ADRSIZ);
/* Target hdw addr and IP addr (for politeness?) */
ea_set(arp.arp.arp_tha, eap); /* Target hdw addr */
memcpy((char *)arp.arp.arp_tpa, iap, IP_ADRSIZ);
/* Now send it! */
if (swstatus) {
char ipbuf[OSN_IPSTRSIZ];
char eabuf[OSN_EASTRSIZ];
char ipbuf2[OSN_IPSTRSIZ];
char eabuf2[OSN_EASTRSIZ];
dbprintln("ARP reply sent to %s %s (%s %s)",
ip_adrsprint(ipbuf, iap),
eth_adrsprint(eabuf2, (unsigned char *)&arp.arp.arp_tha),
ip_adrsprint(ipbuf2, (unsigned char *)&arp.arp.arp_spa),
eth_adrsprint(eabuf, (unsigned char *)&arp.arp.arp_sha));
}
ether_write(&eh, (unsigned char *)&arp.arp, sizeof(arp.arp));
}
#endif /* !KLH10_NET_TUN */
/* IMPTOHOST - Child-process main loop for pumping packets from IMP to HOST.
** Reads packets from net, fragments if necessary, and feeds
** IMP packets to DP superior process.
*/
# define MAXETHERLEN 1600 /* Actually 1519 but be generous */
#define NINBUFSIZ (DPIMP_DATAOFFSET+MAXETHERLEN)
void
imptohost(struct dpimp_s *dpimp)
{
struct dpx_s *dpx = dp_dpxfr(&dp);
int cnt;
unsigned char *inibuf;
unsigned char *buffp;
size_t max;
int stoploop = 50;
inibuf = dp_xsbuff(dpx, &max); /* Get initial buffer ptr */
/* Tell KLH10 we're initialized and ready by sending initial packet */
dp_xswait(dpx); /* Wait until buff free, in case */
dp_xsend(dpx, DPIMP_INIT, 0); /* Send INIT */
if (DBGFLG)
fprintf(stderr, "[dpimp-R: sent INIT]\r\n");
#if KLH10_NET_TUN
# define ETHER_HDR_OFFSET 0 /* No ether headers on TUN */
#else
# define ETHER_HDR_OFFSET ETHER_HDRSIZ
#endif
for (;;) {
/* Make sure that buffer is free before clobbering it */
dp_xswait(dpx); /* Wait until buff free */
if (DBGFLG)
fprintf(stderr, "[dpimp-R: InWait]\r\n");
/* Set up buffer and initialize offsets */
buffp = inibuf + DPIMP_DATAOFFSET - ETHER_HDR_OFFSET;
/* OK, now do a blocking read on packetfilter input! */
cnt = osn_pfread(&pfdata, buffp, MAXETHERLEN);
if (cnt <= ETHER_HDR_OFFSET) {
/* If call times out due to E/BIOCSRTIMEOUT, will return 0 */
if (cnt == 0 && dpimp->dpimp_rdtmo)
continue; /* Just try again */
if (DBGFLG)
dbprintln("ERead=%d, Err=%d", cnt, errno);
if (cnt >= 0) {
dbprintln("Eread = %d, %s", cnt,
(cnt > 0) ? "no ether data" : "no packet");
continue;
}
/* System call error of some kind */
if (errno == EINTR) /* Ignore spurious signals */
continue;
syserr(errno, "Eread = %d, errno %d", cnt, errno);
if (--stoploop <= 0)
efatal(1, "Too many retries, aborting");
continue; /* For now... */
}
if (DBGFLG) {
if (DBGFLG & 0x4) {
fprintf(stderr, "\r\n[dpimp-R: Read=%d\r\n", cnt);
dumppkt(buffp, cnt);
fprintf(stderr, "]");
}
else
dbprint("Read=%d", cnt);
}
/* Have packet, now dispatch it to host */
/* If there hasn't been any packet filtering yet, then must apply
* manual check to each and every packet read, even if dedicated.
*/
if (!pfdata.pf_can_filter) {
if (!lnx_filter(dpimp, buffp, cnt))
continue; /* Drop packet, continue reading */
}
#if !KLH10_NET_TUN
/* if (!pfdata.pf_ip4_only) */ {
/* Verify that pf filtering is doing its job */
switch (eh_tget((struct eth_header *)buffp)) {
case ETHERTYPE_IP:
break;
case ETHERTYPE_ARP: /* If ARP, */
arp_gotrep(buffp, cnt); /* attempt to process replies */
continue; /* and always drop packet */
default:
error("Non-IP ether packet: %0X",
eh_tget((struct eth_header *)buffp));
continue;
}
}
#endif
/* OK, it claims to be an IP packet, see if so long that we
** need to fragment it. Yech!
*/
cnt -= ETHER_HDR_OFFSET;
buffp += ETHER_HDR_OFFSET;
if (cnt > SI_MAXMSG) {
ihl_frag(cnt, buffp);
} else {
/* Small enough to constitute one IMP message, so pass it on! */
ihl_hhsend(dpimp, cnt, buffp);
}
}
}
void
ihl_frag(int cnt, unsigned char *pp)
{
/* For now, just drop it. */
error("Too-large packet (%d), can't fragment yet", cnt);
}
/* Send regular message from IMP to HOST.
** One problem here is what value to put in as the source host/imp.
** All we have is the ethernet source address (the IP header source addr
** is not meaningful as it is that of the ultimate source, not the
** last gateway/router).
** One possibility is to query the native host's ARP tables to look
** up the IP address for that ethernet address, and translate that.
** Another tactic is to do the inverse of hi_iproute() by
** extracting the IP source addr and seeing if it's on our local net.
** If so, put it in the IMP leader, otherwise substitute our default
** gateway.
** Fastest punt would be to just use the IP address of the native host,
** as if it were the final gateway -- and in a sense it is!
*/
/* Buffer for I->H leader, must initialize with source-host at startup */
unsigned char ihobuf[SIH_HSIZ+SI_LDRSIZ] = {
#if SIH_HSIZ
0, 0, 0, 0, /* SIH_HDR, SIH_TDATA, 0, 0 */
#endif
017, 0, 0, 0, /* IMP->Host normal message */
0, 0, 0, 0, /* IMP->Host source host */
SILNK_IP, 0, 0, 0 /* IMP->Host IP msg ID */
};
void
ihl_hhsend(struct dpimp_s *dpimp,
int cnt,
unsigned char *pp)
/* "pp" is packet data ptr, has room for header preceding */
{
int bits = cnt * 8; /* Why not... msg length in bits */
union ipaddr haddr;
/* Set up IMP leader */
ihobuf[SIH_HSIZ+SIL_LEN0] = bits & 0377; /* Lo 8 bits */
ihobuf[SIH_HSIZ+SIL_LEN1] = (bits>>8) & 0377; /* Hi 8 bits */
/* Hack to set host/imp value as properly as possible. */
memcpy((char *)&haddr.ia_octet[0], pp + IPBOFF_SRC, 4);
if ((haddr.ia_addr.s_addr & ihost_nm.s_addr) != ihost_net.s_addr) {
#if !KLH10_NET_TUN
haddr.ia_addr = gwdef_ip; /* Not local, use default GW */
#else
haddr.ia_addr = tun_ip; /* Not local, use tunnel end */
#endif
}
ihobuf[SIH_HSIZ+SIL_HST] = haddr.ia_octet[1];
ihobuf[SIH_HSIZ+SIL_IMP1] = haddr.ia_octet[2];
ihobuf[SIH_HSIZ+SIL_IMP0] = haddr.ia_octet[3];
cnt += SI_LDRSIZ; /* Compensate for IMP leader size */
#if SIH_HSIZ
ihobuf[2] = (cnt >> 8) & 0377; /* High byte of count */
ihobuf[3] = (cnt & 0377); /* Low byte of count */
#endif
pp -= sizeof(ihobuf); /* Back up to start of header+leader */
cnt += SIH_HSIZ;
memcpy(pp, ihobuf, sizeof(ihobuf)); /* Prepend onto data */
/* Send up to host! Assume we're already in shared buffer. */
{
struct dpx_s *dpx = dp_dpxfr(&dp);
unsigned char *buff;
size_t off, max;
buff = dp_xsbuff(dpx, &max); /* Set up buffer ptr & max count */
if ((off = pp - buff) >= max) {
efatal(1, "Bogus IH offset: %ld", (long)off);
}
dpimp->dpimp_inoff = off; /* Tell host what offset is */
dp_xsend(dpx, DPIMP_RPKT, cnt+off);
if (DBGFLG)
fprintf(stderr, "[dpimp-R: sent RPKT %d+%d]", (int)off, cnt);
}
}
/* HOSTTOIMP - Parent main loop for pumping packets from HOST to IMP.
** Reads IMP message from DP superior
** and interprets it. If a regular message, bundles it up and
** outputs to NET.
*/
void
hosttoimp(struct dpimp_s *dpimp)
{
struct dpx_s *dpx = dp_dpxto(&dp); /* Get ptr to "To-DP" dpx */
unsigned char *buff;
size_t max;
int rcnt;
unsigned char *inibuf;
#if !KLH10_NET_TUN
struct in_addr ipdest;
#endif
inibuf = dp_xrbuff(dpx, &max); /* Get initial buffer ptr */
if (DBGFLG)
fprintf(stderr, "[dpimp-W: Starting loop]\r\n");
for (;;) {
if (DBGFLG)
fprintf(stderr, "[dpimp-W: CmdWait]\r\n");
/* Wait until 10 has a command for us */
dp_xrwait(dpx); /* Wait until something there */
/* Process command from 10! */
switch (dp_xrcmd(dpx)) {
default:
fprintf(stderr, "[dpimp: Unknown cmd %d]\r\n", dp_xrcmd(dpx));
dp_xrdone(dpx);
continue;
case DPIMP_SPKT: /* Send regular packet */
rcnt = dp_xrcnt(dpx);
/* Adjust for offset */
rcnt -= dpimp->dpimp_outoff;
buff = inibuf + dpimp->dpimp_outoff;
if (DBGFLG) {
if (DBGFLG & 0x2) {
fprintf(stderr, "\r\n[dpimp-W: Sending %d\r\n", rcnt);
dumppkt(buff, rcnt);
fprintf(stderr, "]");
}
else
fprintf(stderr, "[dpimp-W: SPKT %d]", rcnt);
}
break;
}
/* Come here to handle output packet */
if (rcnt < (SIH_HSIZ+SI_LDRSIZ)) {
error("Host-Imp message too small: %d", rcnt);
continue;
}
#if 1
if (buff[SIH_HSIZ+SIL_FMT] != 017) {
error("Old-format leader received: %o", buff[SIH_HSIZ+SIL_FMT]);
continue;
}
#endif
/* Dispatch by message type. ITS actually only sends
** NOP and HH messages, nothing else.
*/
switch (buff[SIH_HSIZ+SIL_TYP]) {
case SIMT_HH: /* Regular Host-Host Message */
if (buff[SIH_HSIZ+SIL_LNK] != SILNK_IP) {
error("Non-IP Host-Imp msg received: %#o",
buff[SIH_HSIZ+SIL_LNK]);
continue;
}
#if KLH10_NET_TUN
if (DBGFLG)
dbprintln("net out = %d", rcnt - (SIH_HSIZ+SI_LDRSIZ));
if (osn_pfwrite(&pfdata, &buff[SIH_HSIZ+SI_LDRSIZ],
rcnt - (SIH_HSIZ+SI_LDRSIZ)) < 0)
syserr(errno, "tun write() failed");
else {
#else
if (hi_iproute(&ipdest, &buff[SIH_HSIZ], rcnt - SIH_HSIZ)) {
ip_write(&ipdest, &buff[SIH_HSIZ+SI_LDRSIZ],
rcnt - (SIH_HSIZ+SI_LDRSIZ));
#endif
#if !SICONF_SIMP
# error "Too hard to implement non-Simple IMP model!"
# if 0
{ int res;
/* IP packet sent out to net, now send RFNM to host */
buff[SIH_HSIZ+SIL_TYP] = SIMT_RFNM;
buff[2] = 0; /* High byte of count */
buff[3] = SI_LDRSIZ; /* Low byte of count */
res = write(ih_fd, buff, rcnt = SIH_HSIZ+SI_LDRSIZ);
if (res != rcnt) {
esfatal(2, "IH pipe write failed: %d != %d", res, rcnt);
} else if (swurgsig)
kill(cpupid, swurgsig); /* Wake host (cpu) up */
}
# endif
#endif
}
break;
case SIMT_LERR: /* Error in leader: err in previous IMP-to-Host ldr */
error("Received Host-to-IMP leader error msg");
break;
case SIMT_GDWN: /* Host Going Down */
error("Received Host-going-down msg");
break;
case SIMT_NOP: /* NOP */
/* A real IMP would examine this to see how much padding to
** add onto its leaders. However, ITS never wants any.
*/
break;
case SIMT_DERR: /* Error in Data (has msg-id) */
error("Received Host-to-IMP data error msg");
break;
default:
error("Unknown host-imp msg type: %#o", buff[SIH_HSIZ+SIL_TYP]);
}
/* Command done, tell 10 we're done with it */
if (DBGFLG)
fprintf(stderr, "[dpimp-W: CmdDone]");
dp_xrdone(dpx);
}
}
#if !KLH10_NET_TUN
/* HI_IPROUTE - Determine where to actually send Host-Host IP datagram.
** See discussion of routing in comments at start of file.
For now, let's build the IP address by slapping the 1st IP byte
on from the native host's IP address, and taking the rest from the host/imp
fields per scheme above. SIMP then checks to see if it's a local net
address, and if so uses that IP address. If not (ITS thinks it's local,
but it isn't on right subnet) then SIMP substitutes a single default
gateway address. This resolves some but not all of the problem.
*/
int
hi_iproute(struct in_addr *ipa, /* Dest IP addr to be put here */
unsigned char *lp, /* Ptr to start of IMP-Host leader */
int cnt) /* Cnt of data including leader */
{
union ipaddr haddr;
if (cnt < (SI_LDRSIZ+IPBOFF_DEST+4)) {
error("Host-Imp IP datagram too short: %d", cnt);
return FALSE;
}
/* Derive destination IP address from IMP leader */
haddr.ia_addr = ihost_ip; /* Init with native IP addr */
haddr.ia_octet[1] = lp[SIL_HST]; /* Set up host byte */
haddr.ia_octet[2] = lp[SIL_IMP1]; /* High imp byte (old "logical host")*/
haddr.ia_octet[3] = lp[SIL_IMP0]; /* Low byte of imp */
/* Now see if address is local, or if gateway routing needed. */
if ((haddr.ia_addr.s_addr & ihost_nm.s_addr) == ihost_net.s_addr) {
*ipa = haddr.ia_addr; /* Local, win! */
return TRUE;
}
/* Yucko, need to substitute gateway address. Lotsa luck. */
*ipa = gwdef_ip;
return TRUE;
}
/* IP_WRITE - Send IP packet out onto ethernet via packetfilter.
*/
void
ip_write(struct in_addr *ipa, unsigned char *buf, int len)
{
struct eth_header eh;
/* Set up ethernet header */
if (!arp_look(*ipa, (struct ether_addr *)eh_dptr(&eh))) {
/* Failed, so generate an ARP request and send that instead.
** Perhaps later add code to hang on to the packet; problem is figuring
** out when it's time to try sending it again.
** Could spin off thread to do timeout & re-send?
*/
arp_req(ipa); /* Send ARP request for this packet */
sleep(1); /* Gross crock */
if (!arp_look(*ipa, (struct ether_addr *)eh_dptr(&eh))) {
/* Try again */
if (swstatus) {
char ipbuf[OSN_IPSTRSIZ];
dbprintln("No ARP, dropped pkt to %s",
ip_adrsprint(ipbuf, (unsigned char *)ipa));
}
return; /* Failed, just ignore for now */
}
}
eh_sset(&eh, &ihost_ea); /* Set source addr */
eh_tset(&eh, ETHERTYPE_IP);
ether_write(&eh, buf, len);
}
/*
* Write an ethernet frame, consisting of the header and the
* data. They are supplied separately but sent together.
*
* It is assumed there is space before the payload data to put
* a copy of the header!
*/
void
ether_write(struct eth_header *hp,
unsigned char *pp,
int cnt)
{
#if KLH10_NET_PCAP || KLH10_NET_TAP_BRIDGE
char *buf = (char *)(pp - ETHER_HDRSIZ);
if (DP_DBGFLG)
dbprintln("net out = %d", cnt);
memcpy(buf, (char *)hp, ETHER_HDRSIZ);
if (osn_pfwrite(&pfdata, buf, (size_t)(cnt + ETHER_HDRSIZ)) < 0) {
/* What to do here? For debugging, complain but return. */
error("write failed - %s", dp_strerror(errno));
}
#elif KLH10_NET_TUN
/* No code needed here -- routine never used */
#else
# error "No implementation for ether_write()"
#endif
}
#endif /* !KLH10_NET_TUN */
void
dumppkt(unsigned char *ucp, int cnt)
{
int i;
while (cnt > 0) {
for (i = 8; --i >= 0 && cnt > 0;) {
if (--cnt >= 0)
fprintf(stderr, " %02x", *ucp++);
if (--cnt >= 0)
fprintf(stderr, "%02x", *ucp++);
}
fprintf(stderr, "\r\n");
}
}
/* Add OSDNET shared code here */
#include "osdnet.c"