last line botched (fix from Mike Laman, NCR Torrey Pines)
[unix-history] / usr / src / sys / netinet / if_ether.c
/* if_ether.c 6.5 84/07/08 */
/*
* Ethernet address resolution protocol.
*/
#include "../h/param.h"
#include "../h/systm.h"
#include "../h/mbuf.h"
#include "../h/socket.h"
#include "../h/time.h"
#include "../h/kernel.h"
#include "../h/errno.h"
#include "../h/ioctl.h"
#include "../net/if.h"
#include "../netinet/in.h"
#include "../netinet/in_systm.h"
#include "../netinet/ip.h"
#include "../netinet/if_ether.h"
#define ARPTAB_BSIZ 5 /* bucket size */
#define ARPTAB_NB 19 /* number of buckets */
#define ARPTAB_SIZE (ARPTAB_BSIZ * ARPTAB_NB)
struct arptab arptab[ARPTAB_SIZE];
int arptab_size = ARPTAB_SIZE; /* for arp command */
#define ARPTAB_HASH(a) \
((short)((((a) >> 16) ^ (a)) & 0x7fff) % ARPTAB_NB)
#define ARPTAB_LOOK(at,addr) { \
register n; \
at = &arptab[ARPTAB_HASH(addr) * ARPTAB_BSIZ]; \
for (n = 0 ; n < ARPTAB_BSIZ ; n++,at++) \
if (at->at_iaddr.s_addr == addr) \
break; \
if (n >= ARPTAB_BSIZ) \
at = 0; }
int arpt_age; /* aging timer */
/* timer values */
#define ARPT_AGE (60*1) /* aging timer, 1 min. */
#define ARPT_KILLC 20 /* kill completed entry in 20 mins. */
#define ARPT_KILLI 3 /* kill incomplete entry in 3 minutes */
struct ether_addr etherbroadcastaddr = {{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }};
extern struct ifnet loif;
/*
* Local addresses in the range oldmap to infinity are
* mapped according to the old mapping scheme. That is,
* mapping of Internet to Ethernet addresses is performed
* by taking the high three bytes of the network interface's
* address and the low three bytes of the local address part.
* This only allows boards from the same manufacturer to
* communicate unless the on-board address is overridden
* (not possible in many manufacture's hardware).
*
* NB: setting oldmap to zero completely disables ARP
* (i.e. identical to setting IFF_NOARP with an ioctl).
*/
int oldmap = 1024;
/*
* Timeout routine. Age arp_tab entries once a minute.
*/
arptimer()
{
register struct arptab *at;
register i;
timeout(arptimer, (caddr_t)0, hz);
if (++arpt_age > ARPT_AGE) {
arpt_age = 0;
at = &arptab[0];
for (i = 0; i < ARPTAB_SIZE; i++, at++) {
if (at->at_flags == 0 || (at->at_flags & ATF_PERM))
continue;
if (++at->at_timer < ((at->at_flags&ATF_COM) ?
ARPT_KILLC : ARPT_KILLI))
continue;
/* timer has expired, clear entry */
arptfree(at);
}
}
}
/*
* Broadcast an ARP packet, asking who has addr on interface ac.
*/
arpwhohas(ac, addr)
register struct arpcom *ac;
struct in_addr *addr;
{
register struct mbuf *m;
register struct ether_header *eh;
register struct ether_arp *ea;
struct sockaddr sa;
if ((m = m_get(M_DONTWAIT, MT_DATA)) == NULL)
return (1);
m->m_len = sizeof *ea;
m->m_off = MMAXOFF - m->m_len;
ea = mtod(m, struct ether_arp *);
eh = (struct ether_header *)sa.sa_data;
bzero((caddr_t)ea, sizeof (*ea));
eh->ether_dhost = etherbroadcastaddr;
eh->ether_type = ETHERPUP_ARPTYPE; /* if_output will swap */
ea->arp_hrd = htons(ARPHRD_ETHER);
ea->arp_pro = htons(ETHERPUP_IPTYPE);
ea->arp_hln = sizeof arp_sha(ea); /* hardware address length */
ea->arp_pln = sizeof arp_spa(ea); /* protocol address length */
ea->arp_op = htons(ARPOP_REQUEST);
arp_sha(ea) = ac->ac_enaddr;
arp_spa(ea) = ((struct sockaddr_in *)&ac->ac_if.if_addr)->sin_addr;
arp_tpa(ea) = *addr;
sa.sa_family = AF_UNSPEC;
return ((*ac->ac_if.if_output)(&ac->ac_if, m, &sa));
}
/*
* Resolve an IP address into an ethernet address. If success,
* desten is filled in and 1 is returned. If there is no entry
* in arptab, set one up and broadcast a request
* for the IP address; return 0. Hold onto this mbuf and
* resend it once the address is finally resolved.
*
* We do some (conservative) locking here at splimp, since
* arptab is also altered from input interrupt service (ecintr/ilintr
* calls arpinput when ETHERPUP_ARPTYPE packets come in).
*/
arpresolve(ac, m, destip, desten)
register struct arpcom *ac;
struct mbuf *m;
register struct in_addr *destip;
register struct ether_addr *desten;
{
register struct arptab *at;
register struct ifnet *ifp;
register int i;
struct sockaddr_in sin;
int s, lna;
lna = in_lnaof(*destip);
if (lna == INADDR_ANY) { /* broadcast address */
*desten = etherbroadcastaddr;
return (1);
}
ifp = &ac->ac_if;
/* if for us, then use software loopback driver */
if (destip->s_addr ==
((struct sockaddr_in *)&ifp->if_addr)-> sin_addr.s_addr &&
(loif.if_flags & IFF_UP)) {
sin.sin_family = AF_INET;
sin.sin_addr = *destip;
(void) looutput(&loif, m, (struct sockaddr *)&sin);
/*
* We really don't want to indicate failure,
* but the packet has already been sent and freed.
*/
return (0);
}
s = splimp();
ARPTAB_LOOK(at, destip->s_addr);
if (at == 0) { /* not found */
if ((ifp->if_flags & IFF_NOARP) || lna >= oldmap) {
*desten = ac->ac_enaddr;
desten->ether_addr_octet[3] = (lna >> 16) & 0x7f;
desten->ether_addr_octet[4] = (lna >> 8) & 0xff;
desten->ether_addr_octet[5] = lna & 0xff;
splx(s);
return (1);
} else {
at = arptnew(destip);
at->at_hold = m;
arpwhohas(ac, destip);
splx(s);
return (0);
}
}
at->at_timer = 0; /* restart the timer */
if (at->at_flags & ATF_COM) { /* entry IS complete */
*desten = at->at_enaddr;
splx(s);
return (1);
}
/*
* There is an arptab entry, but no ethernet address
* response yet. Replace the held mbuf with this
* latest one.
*/
if (at->at_hold)
m_freem(at->at_hold);
at->at_hold = m;
arpwhohas(ac, destip); /* ask again */
splx(s);
return (0);
}
/*
* Called from ecintr/ilintr when ether packet type ETHERPUP_ARP
* is received. Algorithm is that given in RFC 826.
* In addition, a sanity check is performed on the sender
* protocol address, to catch impersonators.
*/
arpinput(ac, m)
register struct arpcom *ac;
struct mbuf *m;
{
register struct ether_arp *ea;
struct ether_header *eh;
register struct arptab *at = 0; /* same as "merge" flag */
struct sockaddr_in sin;
struct sockaddr sa;
struct mbuf *mhold;
struct in_addr isaddr,itaddr,myaddr;
if (m->m_len < sizeof *ea)
goto out;
if (ac->ac_if.if_flags & IFF_NOARP)
goto out;
myaddr = ((struct sockaddr_in *)&ac->ac_if.if_addr)->sin_addr;
ea = mtod(m, struct ether_arp *);
if (ntohs(ea->arp_pro) != ETHERPUP_IPTYPE)
goto out;
isaddr = arp_spa(ea);
itaddr = arp_tpa(ea);
if (!bcmp((caddr_t)&arp_sha(ea), (caddr_t)&ac->ac_enaddr,
sizeof (ac->ac_enaddr)))
goto out; /* it's from me, ignore it. */
if (isaddr.s_addr == myaddr.s_addr) {
printf("duplicate IP address!! sent from ethernet address: ");
printf("%x %x %x %x %x %x\n", ea->arp_xsha[0], ea->arp_xsha[1],
ea->arp_xsha[2], ea->arp_xsha[3],
ea->arp_xsha[4], ea->arp_xsha[5]);
itaddr = myaddr;
if (ntohs(ea->arp_op) == ARPOP_REQUEST)
goto reply;
goto out;
}
ARPTAB_LOOK(at, isaddr.s_addr);
if (at) { /* XXX ? - can overwrite ATF_PERM */
at->at_enaddr = arp_sha(ea);
at->at_flags |= ATF_COM;
if (at->at_hold) {
mhold = at->at_hold;
at->at_hold = 0;
sin.sin_family = AF_INET;
sin.sin_addr = isaddr;
(*ac->ac_if.if_output)(&ac->ac_if,
mhold, (struct sockaddr *)&sin);
}
} else if (itaddr.s_addr == myaddr.s_addr) {
/* ensure we have a table entry */
at = arptnew(&isaddr);
at->at_enaddr = arp_sha(ea);
at->at_flags |= ATF_COM;
}
if (ntohs(ea->arp_op) != ARPOP_REQUEST)
goto out;
ARPTAB_LOOK(at, itaddr.s_addr);
if (at == NULL) {
if (itaddr.s_addr != myaddr.s_addr)
goto out; /* if I am not the target */
at = arptnew(&myaddr);
at->at_enaddr = ac->ac_enaddr;
at->at_flags |= ATF_COM;
}
if (itaddr.s_addr != myaddr.s_addr && (at->at_flags & ATF_PUBL) == 0)
goto out;
reply:
arp_tha(ea) = arp_sha(ea);
arp_tpa(ea) = arp_spa(ea);
arp_sha(ea) = at->at_enaddr;
arp_spa(ea) = itaddr;
ea->arp_op = htons(ARPOP_REPLY);
eh = (struct ether_header *)sa.sa_data;
eh->ether_dhost = arp_tha(ea);
eh->ether_type = ETHERPUP_ARPTYPE;
sa.sa_family = AF_UNSPEC;
(*ac->ac_if.if_output)(&ac->ac_if, m, &sa);
return;
out:
m_freem(m);
return;
}
/*
* Free an arptab entry.
*/
arptfree(at)
register struct arptab *at;
{
int s = splimp();
if (at->at_hold)
m_freem(at->at_hold);
at->at_hold = 0;
at->at_timer = at->at_flags = 0;
at->at_iaddr.s_addr = 0;
splx(s);
}
/*
* 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 (except from arpioctl when testing whether
* another permanent entry).
*/
struct arptab *
arptnew(addr)
struct in_addr *addr;
{
register n;
int oldest = 0;
register struct arptab *at, *ato = NULL;
static int first = 1;
if (first) {
first = 0;
timeout(arptimer, (caddr_t)0, hz);
}
at = &arptab[ARPTAB_HASH(addr->s_addr) * ARPTAB_BSIZ];
for (n = 0 ; n < ARPTAB_BSIZ ; n++,at++) {
if (at->at_flags == 0)
goto out; /* found an empty entry */
if (at->at_flags & ATF_PERM)
continue;
if (at->at_timer > oldest) {
oldest = at->at_timer;
ato = at;
}
}
if (ato == NULL)
return(NULL);
at = ato;
arptfree(at);
out:
at->at_iaddr = *addr;
at->at_flags = ATF_INUSE;
return (at);
}
arpioctl(cmd, data)
int cmd;
caddr_t data;
{
register struct arpreq *ar = (struct arpreq *)data;
register struct arptab *at;
register struct sockaddr_in *sin;
int s;
if (ar->arp_pa.sa_family != AF_INET ||
ar->arp_ha.sa_family != AF_UNSPEC)
return (EAFNOSUPPORT);
sin = (struct sockaddr_in *)&ar->arp_pa;
s = splimp();
ARPTAB_LOOK(at, sin->sin_addr.s_addr);
if (at == NULL) { /* not found */
if (cmd != SIOCSARP) {
splx(s);
return (ENXIO);
}
if (if_ifwithnet(&ar->arp_pa) == NULL) {
splx(s);
return (ENETUNREACH);
}
}
switch (cmd) {
case SIOCSARP: /* set entry */
if (at == NULL) {
at = arptnew(&sin->sin_addr);
if (ar->arp_flags & ATF_PERM) {
/* never make all entries in a bucket permanent */
register struct arptab *tat;
/* try to re-allocate */
tat = arptnew(&sin->sin_addr);
if (tat == NULL) {
arptfree(at);
splx(s);
return (EADDRNOTAVAIL);
}
arptfree(tat);
}
}
at->at_enaddr = *(struct ether_addr *)ar->arp_ha.sa_data;
at->at_flags = ATF_COM | ATF_INUSE |
(ar->arp_flags & (ATF_PERM|ATF_PUBL));
at->at_timer = 0;
break;
case SIOCDARP: /* delete entry */
arptfree(at);
break;
case SIOCGARP: /* get entry */
*(struct ether_addr *)ar->arp_ha.sa_data = at->at_enaddr;
ar->arp_flags = at->at_flags;
break;
}
splx(s);
return (0);
}