/* vim: set filetype=c:
 *
 **************************************************************************
 *
 * Boot-ROM-Code to load an operating system across a TCP/IP network.
 *
 * Module:  arp.c86
 * Purpose: ARP handling
 * Entries: ip_resolve, arp_recv
 *
 **************************************************************************
 *
 * Copyright (C) 1995-2007 Gero Kuhlmann <gero@gkminix.han.de>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: arp.c86,v 1.7 2007/01/06 18:30:47 gkminix Exp $
 */


#include <common.h86>
#include <romconfig.h86>
#include <i386/string.h86>
#include <i386/misc.h86>
#include <i386/time.h86>
#include <i386/conio.h86>
#include <net/ethernet.h86>
#include <pxe/general.h86>
#include <pxe/undi.h86>
#include "headers/net.h86"
#include "arp.h86"
#include "undi.h86"



/*
 **************************************************************************
 * 
 * Global variables
 */
static struct arp_cache arp_list[ARPNUM];	/* ARP address cache	*/
static short next_arp;				/* next table entry	*/

static __u8 bcasthw[ETH_ALEN] = {		/* broadcast address	*/
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};



/*
 **************************************************************************
 * 
 * Send buffers: these get shared among various modules to save memory
 */
struct arphdr arpbuf asm("_linkbuf") __attribute__((common));
size_t sizes[1] asm("_sendsizes") __attribute__((common));
__fptr bufs[1] asm("_sendbufs") __attribute__((common));



/*
 **************************************************************************
 * 
 * Find an IP address in the ARP cache. Return -1 if not found.
 */
static short findip(t_ipaddr ip)
{
  register short i;

  for (i = 0; i < ARPNUM; i++)
	if (arp_list[i].ipaddr == ip)
		break;
  return(i);
}



/*
 **************************************************************************
 * 
 * Add a new entry to the ARP cache.
 */
static void addcache(const __u8 *ha, t_ipaddr ip)
{
  register short i;

  /*
   * If the IP address already exists in the ARP table then just replace
   * it's hardware address with the new one. Otherwise create a new ARP
   * table entry.
   */
  if ((i = findip(ip)) >= ARPNUM) {
	i = next_arp++;
	if (next_arp >= ARPNUM)
		next_arp = 0;
	arp_list[i].ipaddr = ip;
  }
  memcpy(arp_list[i].hwaddr, ha, ETH_ALEN);
}



/*
 **************************************************************************
 * 
 * ARP receiver routine
 */
void arp_recv(size_t bufsize)
{
#define arpreply ((struct arphdr *)(readbuf.buf))

#ifdef ARPDEBUG
  register short ret;
#endif

  /*
   * Do some checks on the received packet. It must be an ARP packet,
   * with an ethernet hardware address, and for the IP protocol. Other
   * packets just get discarded. Then save the sender's address.
   */
  if (bufsize < (sizeof(struct arphdr) + sizeof(readbuf.protocol)) ||
      readbuf.protocol != UNDI_PROT_ARP ||
      arpreply->ar_op  != htons(ARPOP_REQUEST) ||
      arpreply->ar_tip != myipaddr ||
      arpreply->ar_hrd != htons(mytype) ||
      arpreply->ar_hln != ETH_ALEN ||
      arpreply->ar_pro != htons(ARPPROT_IP) ||
      arpreply->ar_pln != IP_ALEN)
	goto recv_ret;

  arpbuf.ar_hrd = htons(mytype);
  arpbuf.ar_pro = htons(ARPPROT_IP);
  arpbuf.ar_hln = ETH_ALEN;
  arpbuf.ar_pln = IP_ALEN;
  arpbuf.ar_op  = htons(ARPOP_REPLY);
  arpbuf.ar_tip = arpreply->ar_sip;
  arpbuf.ar_sip = myipaddr;
  memcpy(arpbuf.ar_tha, arpreply->ar_sha, ETH_ALEN);
  memcpy(arpbuf.ar_sha, myhwaddr, ETH_ALEN);
  sizes[0] = sizeof(arpbuf);
  bufs[0] = MK_FP(getds(), &arpbuf);
#ifdef ARPDEBUG
  ret = undi_send(arpreply->ar_sha, UNDI_PROT_ARP, 1, sizes, bufs);
  printf("ARP: reply sent, result = %x\n", ret);
#else
  undi_send(arpreply->ar_sha, UNDI_PROT_ARP, 1, sizes, bufs);
#endif

recv_ret:
  return;

#undef arpreply
}



/*
 **************************************************************************
 * 
 * Resolve IP address and return pointer to hardware address.
 */
short ip_resolve(t_ipaddr myip, t_ipaddr ip, t_ipaddr gw, __u8 *hwaddr)
{
#define arpreply ((struct arphdr *)(readbuf.buf))

  register short ret;
  register short counts;
  unsigned short tout;
  size_t i;

#ifdef ARPDEBUG
  printf("ARP: Resolving IP address %lx\n", ip);
#endif

  /* Check for default addresses */
  if (myip == htonl(IP_ANY))
	myip = myipaddr;
  if (gw == htonl(IP_ANY))
	gw = mygateway;

  /* Check that the gateway address is correct */
  if (gw != htonl(IP_ANY) &&
      (gw == htonl(IP_BROADCAST) || gw == myip || ((gw ^ myip) & mynetmask)))
	gw = htonl(IP_ANY);

  /* Check for invalid IP addresses */
  ret = PXENV_STATUS_FAILURE;
  if (ip == htonl(IP_ANY) || ip == myip || ip == htonl(IP_LOCALHOST))
	goto res_ret;

  /* Check for broadcast address */
  ret = PXENV_STATUS_SUCCESS;
  if (ip == htonl(IP_BROADCAST) ||
      !((ip ^ htonl(IP_BROADCAST)) & ~mynetmask)) {
	memcpy(hwaddr, bcasthw, ETH_ALEN);
	goto res_ret;
  }

  /* Check for multicast address */
  if (IN_MULTICAST(ntohl(ip))) {
	undi_get_mcast(ip, hwaddr);
	goto res_ret;
  }

  /*
   * If the destination is not on subnet, send the packets through gateway.
   * Also check for a local broadcast address.
   */
  ret = PXENV_STATUS_FAILURE;
  if ((ip ^ myip) & mynetmask) {
	if (gw != htonl(IP_ANY))
		ip = gw;
	else
		goto res_ret;
  }

  /* Check if we got the address in the cache already */
  ret = PXENV_STATUS_SUCCESS;
  if ((i = findip(ip)) < ARPNUM) {
	memcpy(hwaddr, arp_list[i].hwaddr, ETH_ALEN);
	goto res_ret;
  }

  /* No, we have to broadcast a request */
  arpbuf.ar_hrd = htons(mytype);
  arpbuf.ar_pro = htons(ARPPROT_IP);
  arpbuf.ar_hln = ETH_ALEN;
  arpbuf.ar_pln = IP_ALEN;
  arpbuf.ar_op  = htons(ARPOP_REQUEST);
  arpbuf.ar_sip = myip;
  arpbuf.ar_tip = ip;
  memcpy(arpbuf.ar_sha, myhwaddr, ETH_ALEN);
  memset(arpbuf.ar_tha, 0, ETH_ALEN);
  sizes[0] = sizeof(arpbuf);
  bufs[0] = MK_FP(getds(), &arpbuf);

  /*
   * Send the ARP request and wait for an answer. This is a double loop
   * which retries when no answer has been received in a timeout time.
   */
  tout = ARP_TIMEOUT;
  for (counts = 0; counts < ARP_COUNTS; counts++) {
	/* Send the request */
#ifdef ARPDEBUG
	printf("ARP: Sending request\n");
#endif
	if ((ret = undi_send(NULL, UNDI_PROT_ARP, 1, sizes, bufs))
						 != PXENV_STATUS_SUCCESS)
		goto res_ret;

	/* Wait for someone to answer the broadcast request */
	set_timeout(tout);
	do {
		/*
		 * Read next packet from UNDI and check that it's a
		 * correct ARP reply packet.
		 */
		i = undi_recv();
#ifdef ARPDEBUG
		if (i > 0)
			printf("ARP: Received %d bytes\n", i);
#endif
		if (i >= (sizeof(struct arphdr) + sizeof(readbuf.protocol)) &&
		    readbuf.protocol == UNDI_PROT_ARP &&
		    arpreply->ar_op  == htons(ARPOP_REPLY) &&
		    arpreply->ar_hrd == htons(mytype) &&
		    arpreply->ar_hln == ETH_ALEN &&
		    arpreply->ar_pro == htons(ARPPROT_IP) &&
		    arpreply->ar_pln == IP_ALEN &&
		    arpreply->ar_tip == myip) {
			/*
			 * Add new IP address to cache and set return value
			 */
			if (arpreply->ar_sip == ip) {
				addcache(arpreply->ar_sha, arpreply->ar_sip);
				memcpy(hwaddr, arpreply->ar_sha, ETH_ALEN);
				goto res_ret;
			}
		}
	} while (!chk_timeout());

	/* Double timeout value for next round */
	tout <<= 1;
  }
  ret = PXENV_STATUS_ARP_TIMEOUT;

res_ret:
#ifdef ARPDEBUG
  printf("ARP: result = %x\n", ret);
#endif
  return(ret);

#undef arpreply
}

