Source to arch/i386/netboot/arp.c


Enter a symbol's name here to quickly find it.

/* netboot
 *
 * arp.c,v
 * Revision 1.2  1993/07/09  15:24:10  brezak
 * Cleanup warnings and add netbsd kernel name suffix.
 *
 * Revision 1.1  1993/07/08  16:03:47  brezak
 * Diskless boot prom code from Jim McKim ([email protected])
 *
 * Revision 1.2  1993/06/30  20:14:10  mckim
 * Added BOOTP support.
 *
 * Revision 1.1.1.1  1993/05/28  11:41:06  mckim
 * Initial version.
 *
 *
 * source in this file came from
 * the Mach ethernet boot written by Leendert van Doorn.
 *
 * Ethernet (Reverse) Address Resolution Protocol (see RFC 903, and 826).
 * No doubt this code is overkill, but I had it lying around.
 *
 * Copyright (c) 1992 by Leendert van Doorn
 */

#include "proto.h"
#include "assert.h"
#include "param.h"
#include "packet.h"
#include "ether.h"
#include "inet.h"
#include "arp.h"
#include "bootp.h"
#include "tftp.h"

static u_char bcastaddr[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
static arptab_t arptab[ARPTAB_SIZE];

extern u_char vendor_area[64];
ipaddr_t ip_myaddr = IP_ANYADDR;
ipaddr_t ip_gateway = IP_ANYADDR;

#ifdef USE_RARP
/*
 * Broadcast a RARP request (i.e. who knows who I am)
 */
static void
RarpWhoAmI(void) {
  arphdr_t *ap;
  packet_t *pkt;
  pkt = PktAlloc(sizeof(ethhdr_t));
  pkt->pkt_len = sizeof(arphdr_t);
  ap = (arphdr_t *) pkt->pkt_offset;
  ap->arp_hrd = htons(ARPHRD_ETHER);
  ap->arp_pro = htons(ETHTYPE_IP);
  ap->arp_hln = ETH_ADDRSIZE;
  ap->arp_pln = sizeof(ipaddr_t);
  ap->arp_op = htons(REVARP_REQUEST);
  bcopy((char *)eth_myaddr, (char *)ap->arp_sha, ETH_ADDRSIZE);
  bcopy((char *)eth_myaddr, (char *)ap->arp_tha, ETH_ADDRSIZE);
  EtherSend(pkt, ETHTYPE_RARP, bcastaddr);
  PktRelease(pkt);
}
#endif


#ifdef USE_BOOTP
static int saved_bootp_xid; /* from last bootp req */
extern int time_zero;
/*
 * Broadcast a BOOTP request (i.e. who knows who I am)
 */
static void
BootpWhoAmI(void) {
  struct bootp *bp;
  packet_t *pkt;
  udphdr_t *up;
  pkt = PktAlloc(sizeof(ethhdr_t)+sizeof(iphdr_t));
  pkt->pkt_len = sizeof(ethhdr_t) + sizeof(iphdr_t) +
    sizeof(udphdr_t) +sizeof(struct bootp);
  up = (udphdr_t *) pkt->pkt_offset;
  bp = (struct bootp *) ((char *)up + sizeof(udphdr_t));
  up->uh_dport = htons(IPPORT_BOOTPS);
  up->uh_len = htons(sizeof(udphdr_t) + sizeof(struct bootp));
  bp->bp_op = BOOTREQUEST;
  bp->bp_htype = 1;
  bp->bp_hlen = ETH_ADDRSIZE;
  bp->bp_xid = saved_bootp_xid = rand();
  bp->bp_secs = htons(timer() - time_zero);
  bcopy((char *)eth_myaddr, (char *)bp->bp_chaddr, ETH_ADDRSIZE);
  IpSend(pkt, IP_BCASTADDR, IP_ANYADDR);
  PktInit();
}
#endif

extern ipaddr_t tftp_gateway;
extern ipaddr_t tftp_server;

#ifdef USE_RARP
/*
 * Called when packet containing RARP is received
 */
static inline ipaddr_t
RarpInput(packet_t *pkt, ipaddr_t *server) {
  ipaddr_t ipaddr;
  ethhdr_t *ep;
  ep = (ethhdr_t *)pkt->pkt_offset;

  /* is rarp? */
  if (pkt->pkt_len >= sizeof(arphdr_t) &&
      ntohs(ep->eth_proto) == ETHTYPE_RARP) {
    ipaddr_t ipa;
    arphdr_t *ap;
    ap = (arphdr_t *) (pkt->pkt_offset + sizeof(ethhdr_t));
    if (ntohs(ap->arp_op) != REVARP_REPLY ||
	ntohs(ap->arp_pro) != ETHTYPE_IP)
      return 0;
    if (bcmp(ap->arp_tha, eth_myaddr, ETH_ADDRSIZE))
      return 0;

    bcopy((char *)ap->arp_tpa, (char *)&ipaddr, sizeof(ipaddr_t));
    printf("From RARP server ");
    bcopy((char *)ap->arp_spa, (char *)&ipa, sizeof(ipaddr_t));
    IpPrintAddr(ipa);
    printf(": using IP address ");
    IpPrintAddr(ipaddr);

    if (server) {
      bcopy((char *)ap->arp_spa, (char *)server, sizeof(ipaddr_t));
      printf(",\n tftp server ");
      IpPrintAddr(*server);
    }

    printf("\n");
    return ipaddr;
  }
  return 0;
}
#endif

#ifdef USE_BOOTP
static inline ipaddr_t
BootpInput(packet_t *pkt, ipaddr_t *server, ipaddr_t *gateway, char *filename) {
  ipaddr_t ipaddr;
  ethhdr_t *ep;
  ep = (ethhdr_t *)pkt->pkt_offset;


  if (pkt->pkt_len < sizeof(iphdr_t)+sizeof(udphdr_t)+sizeof(struct bootp))
    return 0;
  if (ntohs(ep->eth_proto) == ETHTYPE_IP) {
    iphdr_t *ip;
    udphdr_t *up;
    struct bootp *bp;
    ip = (iphdr_t *) ((char *)ep + sizeof(ethhdr_t));
    up = (udphdr_t *) ((char *)ip + sizeof(iphdr_t));
    bp = (struct bootp *) ((char *)up + sizeof(udphdr_t));

#if 0
DUMP_STRUCT("eboot", ep, 100);
printf("pktlen %d of %d\n\n", pkt->pkt_len, sizeof(iphdr_t)+sizeof(udphdr_t)+sizeof(struct bootp));
#endif

    if (ip->ip_p != IP_PROTO_UDP) {
      return 0;
    }

    if (up->uh_dport != htons(IPPORT_BOOTPC)) {
      return 0;
    }

    if (bp->bp_xid != saved_bootp_xid) {
      return 0;
    }

    /* passed all checks - is the packet we expected */
    ipaddr = bp->bp_yiaddr;
    printf("From BOOTP server ");
    IpPrintAddr(ip->ip_src);
    printf(": using IP address ");
    IpPrintAddr(bp->bp_yiaddr);

    if (server) {
      *server = bp->bp_siaddr;
      printf(",\n tftp server ");
      IpPrintAddr(bp->bp_siaddr);
    }

    if (bp->bp_giaddr) {
      *gateway = bp->bp_giaddr;
      printf(",\n gateway ");
      IpPrintAddr(bp->bp_giaddr);
    }

    if (*bp->bp_file) {
      bcopy((char *)bp->bp_file, filename, MAX_FILE_NAME_LEN-1);
      printf(",\n file '%s'", bp->bp_file);
    }

    bcopy((char *)bp->bp_vend, (char *)vendor_area, sizeof(vendor_area));

    printf("\n");

    PktInit();
    return ipaddr;
  }
  return 0;
}
#endif

/*
 * Using the BOOTP and/or RARP request/reply exchange we try to obtain our
 * internet address (see RFC 903).
 */
ipaddr_t
GetIpAddress(ipaddr_t *serv_addr, ipaddr_t *myaddr, ipaddr_t *gateway, char *filename) {
  u_long time, current, timeout;
  int retry;
  packet_t *pkt;
  int spin = 0;

#if TRACE > 0
  printe("GetIpAddress: Requesting IP address for ");
  EtherPrintAddr(eth_myaddr);
  printe("\n");
#endif

  timeout = 4; /* four seconds */
  for (retry = 0; retry < NRETRIES; retry++) {
#ifdef USE_RARP
    RarpWhoAmI();
#endif
#ifdef USE_BOOTP
    BootpWhoAmI();
#endif
    printf("%c\b", "-\\|/"[spin++ % 4]);

    time = timer() + timeout;
    do {
      pkt = EtherReceive();
      if (pkt) {
	*myaddr = 0;
#ifdef USE_RARP
	*myaddr = RarpInput(pkt, serv_addr);
#endif
#ifdef USE_BOOTP
	if (!*myaddr)
	  *myaddr = BootpInput(pkt, serv_addr, gateway, filename);
#endif
	PktRelease(pkt);
	if (*myaddr) {
	  return 1;
	}
      }
      HandleKbdAttn();
      current = timer();
    } while (current < time);
    EtherReset();
    timeout <<= 1;
  }
  printf("No response for "
#ifdef USE_BOOTP
	 "BOOTP "
#endif
#ifdef USE_RARP
	 "RARP "
#endif
	 "request\n");
  return IP_ANYADDR;
}

/*
 * Broadcast an ARP packet (i.e. ask who has address "addr")
 */
static void
ArpWhoHas(ipaddr_t addr) {
  arphdr_t *ap;
  packet_t *pkt;

  pkt = PktAlloc(sizeof(ethhdr_t));
  pkt->pkt_len = sizeof(arphdr_t);
  ap = (arphdr_t *) pkt->pkt_offset;
  ap->arp_hrd = htons(ARPHRD_ETHER);
  ap->arp_pro = htons(ETHTYPE_IP);
  ap->arp_hln = ETH_ADDRSIZE;
  ap->arp_pln = sizeof(ipaddr_t);
  ap->arp_op = htons(ARPOP_REQUEST);
  bcopy((char *)eth_myaddr, (char *)ap->arp_sha, ETH_ADDRSIZE);
  bcopy((char *)&ip_myaddr, (char *)ap->arp_spa, sizeof(ipaddr_t));
  bcopy((char *)&addr, (char *)ap->arp_tpa, sizeof(ipaddr_t));
#if TRACE > 0
printe("ArpWhoHas:\n");	
DUMP_STRUCT("arphdr_t", ap, sizeof(arphdr_t));
#endif
  EtherSend(pkt, ETHTYPE_ARP, bcastaddr);
  PktRelease(pkt);
}

/*
 * Free an arptab entry
 */
static void
ArpTfree(arptab_t *at) {
  if (at->at_hold)
    PktRelease(at->at_hold);
  at->at_hold = (packet_t *)0;
  at->at_timer = at->at_flags = 0;
  at->at_ipaddr = 0;
}

/*
 * Enter a new address in arptab, pushing out the oldest entry 
 * from the bucket if there is no room.
 */
static arptab_t *
ArpTnew(ipaddr_t addr) {
  u_short n;
  u_long oldest;
  arptab_t *at, *ato;

  oldest = ~0;
  ato = at = &arptab[ARPTAB_HASH(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_timer < oldest) {
      oldest = at->at_timer;
      ato = at;
    }
  }
  at = ato;
  ArpTfree(at);
 out:
  at->at_ipaddr = addr;
  at->at_flags = ATF_INUSE;
  return at;
}

/*
 * Resolve an IP address into a hardware address.  If success, 
 * destha 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 packet and 
 * resend it once the address is finally resolved.
 */
int
ArpResolve(packet_t *pkt, ipaddr_t destip, u_char *destha) {
  arptab_t *at;
  u_long lna = ntohl(destip) & 0xFF;

  if (lna == 0xFF || lna == 0x0) { /* broadcast address */
    bcopy((char *)bcastaddr, (char *)destha, ETH_ADDRSIZE);
    return 1;
  }

  ARPTAB_LOOK(at, destip);
  if (at == 0) {
    at = ArpTnew(destip);
    at->at_hold = pkt;
    ArpWhoHas(destip);
    return 0;
  }

  at->at_timer = timer(); /* restart the timer */
  if (at->at_flags & ATF_COM) { /* entry is complete */
    bcopy((char *)at->at_eaddr, (char *)destha, ETH_ADDRSIZE);
    return 1;
  }

  /*
   * There is an arptab entry, but no hardware address
   * response yet.  Replace the held packet with this
   * latest one.
   */
  if (at->at_hold)
    PktRelease(at->at_hold);
  at->at_hold = pkt;
  ArpWhoHas(destip);
  return 0;
}


/*
 * Called when packet containing ARP is received.
 * Algorithm is that given in RFC 826.
 */
void
ArpInput(packet_t *pkt) {
  arphdr_t *ap;
  arptab_t *at;
  packet_t *phold;
  ipaddr_t isaddr, itaddr;

#if 0
T(ArpInput);
#endif
  if (pkt->pkt_len < sizeof(arphdr_t)) {
#if 0
    printf("ArpInput: bad packet size %d\n", pkt->pkt_len);
#endif
    return;
  }

  ap = (arphdr_t *) (pkt->pkt_offset + sizeof(ethhdr_t));
#if 0
DUMP_STRUCT("arphdr_t", ap, sizeof(arphdr_t));
#endif
  if (ntohs(ap->arp_pro) != ETHTYPE_IP) {
#if 0
    printf("ArpInput: incorrect proto addr %x\n", ap->arp_pro);
#endif
    return;
  }

  bcopy((char *)ap->arp_spa, (char *)&isaddr, sizeof(ipaddr_t));
  bcopy((char *)ap->arp_tpa, (char *)&itaddr, sizeof(ipaddr_t));
  if (!bcmp(ap->arp_sha, eth_myaddr, ETH_ADDRSIZE)) {
#if 0
    printf("ArpInput: incorrect sender h/w addr ");
    EtherPrintAddr(ap->arp_sha);
    printf("/n");
#endif
    return;
  }

  at = (arptab_t *)0;
  ARPTAB_LOOK(at, isaddr);
  if (at) {
    bcopy((char *)ap->arp_sha, (char *)at->at_eaddr, ETH_ADDRSIZE);
    at->at_flags |= ATF_COM;
    if (at->at_hold) {
      phold = at->at_hold;
      at->at_hold = (packet_t *)0;
#if 0
      printf("ArpInput: found addr, releasing packet\n");
#endif
      EtherSend(phold, ETHTYPE_IP, at->at_eaddr);
      PktRelease(phold);
    }
  }

  /*
   * Only answer ARP request which are for me
   */
  if (itaddr != ip_myaddr) {
#if 0
    printf("ArpInput: it addr ");
    IpPrintAddr(itaddr);
    printf(" somebody else\n");
#endif
    return;
  }

  if (at == 0) {		/* ensure we have a table entry */
    at = ArpTnew(isaddr);
    bcopy((char *)ap->arp_sha, (char *)at->at_eaddr, ETH_ADDRSIZE);
    at->at_flags |= ATF_COM;
  }
  if (ntohs(ap->arp_op) != ARPOP_REQUEST) {
    printf("ArpInput: incorrect operation: 0x%x\n", ntohs(ap->arp_op));
    return;
  }
  bcopy((char *)ap->arp_sha, (char *)ap->arp_tha, ETH_ADDRSIZE);
  bcopy((char *)ap->arp_spa, (char *)ap->arp_tpa, sizeof(ipaddr_t));
  bcopy((char *)eth_myaddr, (char *)ap->arp_sha, ETH_ADDRSIZE);
  bcopy((char *)&itaddr, (char *)ap->arp_spa, sizeof(ipaddr_t));
  ap->arp_op = htons(ARPOP_REPLY);
#if 0
printf("ArpInput: valid request rec'd, replying\n");
#endif
  EtherSend(pkt, ETHTYPE_ARP, ap->arp_tha);
}