/*
 **************************************************************************
 *
 * Boot-ROM-Code to load an operating system across a TCP/IP network.
 *
 * Module:  bootp.c
 * Purpose: Get information for client with BOOTP protocol
 * Entries: bootp
 *
 **************************************************************************
 *
 * Copyright (C) 1995-2003 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: bootp.c,v 1.6 2003/03/09 00:24:27 gkminix Exp $
 *
 **************************************************************************
 *
 * We can print error messages within this module as it gets never called
 * by a PXE function.
 */


#include <general.h>
#include <kernel/net.h>
#include <kernel/arpa.h>
#include <kernel/romlib.h>
#include <pxe/common.h>
#include "bootp.h"



/*
 **************************************************************************
 * 
 * Global variables:
 */
static unsigned long boot_xid;			/* bootp transaction ID	*/
static unsigned long boot_start_time;		/* when started to boot	*/



/*
 **************************************************************************
 * 
 * Send a BOOTP request via UDP.
 *
 */
static int bootp_send()
{
  register struct bootp *bp;
  int i;

  /* Create a new BOOTP output buffer */
  if (!new_bootp_buf(BOOTP_DISCOVER))
	return(PXENV_STATUS_FAILURE);
  bp = bootp_bufs[BOOTP_DISCOVER];

  /* Assemble the BOOTP request */
  memset(bp, 0, bootp_sizes[BOOTP_DISCOVER]);
  memcpy(bp->bp_chaddr, myhwaddr, ETH_ALEN);
  bp->bp_op = BOOTP_OP_REQUEST;
  bp->bp_hwtype = mytype;
  bp->bp_hlen = ETH_ALEN;
  bp->bp_xid = boot_xid;
  bp->bp_secs = htons(((unsigned short)(get_ticks() - boot_start_time)) / 18);
  bp->bp_ciaddr = n_IP_ANY;
  bp->bp_siaddr = n_IP_BROADCAST;
  *((unsigned long *)(bp->bp_vend)) = VM_RFC1048;
  bp->bp_vend[VM_SIZE] = VEND_END;

  /* Set the size of the BOOTP record */
  set_bootp_size();

  /* Send the bootp record as a broadcast message to all servers */
  return(udp_write((unsigned char *)bp, getds(), bootp_sizes[BOOTP_DISCOVER]));
}



/*
 **************************************************************************
 * 
 * Validate received BOOTP reply
 */
static int validate()
{
  register struct bootp *bp = bootp_bufs[cur_bootp_buf];
  int retval = FALSE;

  if (
      /* Only accept BOOTP replies */
      bp->bp_op == BOOTP_OP_REPLY &&

      /* Only accept packets that match my transaction ID */
      bp->bp_xid == boot_xid &&

      /* Only accept packets that match my hardware address */
      !memcmp(bp->bp_chaddr, myhwaddr, ETH_ALEN) &&

      /* Only accept packets with no vendor field or correct magic number */
      (bp->bp_vend[0] == '\0' ||
       (*((unsigned long *)(bp->bp_vend)) == VM_RFC1048 &&
        get_vend(VEND_END) != NULL)) &&

      /* Do not accept loopback addresses */
      bp->bp_yiaddr != n_IP_LOCALHOST)
	retval = TRUE;

  return(retval);
}



/*
 **************************************************************************
 * 
 * Get a BOOTP record from the server.
 *
 */
static int bootp_get()
{
  register struct bootp *bp;
  unsigned int timeout;
  unsigned int bufsize;
  int retval, retries;
  char errch;

  /*
   * Setup the IP interface information. Our own address has to be
   * IP_ANY, so that the IP layer will not discard the incoming packets.
   * Then open a UDP socket.
   */
  myipaddr = n_IP_ANY;
  mynetmask = n_IP_CLASS_A;
  printf("\n\nBOOTP: ");
  if ((retval = udp_open(n_IP_BROADCAST, n_IP_ANY,
				htons(BOOTP_C_PORT),
				htons(BOOTP_S_PORT))) != PXENV_STATUS_SUCCESS)
	return(retval);

  /*
   * Now loop until receiving a reply from a server. The retry time is
   * computed as suggested in RFC951.
   */
  retries = 0;
  timeout = BOOTP_TIMEOUT + random(0x001f);
  printf("Sending request (press ESC to abort): ");
  while (TRUE) {
	/* Send new BOOTP request */
	boot_xid = get_ticks() + random(0x7fff);
	if ((retval = bootp_send()) != PXENV_STATUS_SUCCESS)
		break;

	/* Allocate buffer for BOOTP reply */
	if (!new_bootp_buf(BOOTP_REPLY)) {
		retval = PXENV_STATUS_FAILURE;
		break;
	}
	bp = bootp_bufs[BOOTP_REPLY];

	/* Wait for reply packet */
	bufsize = bootp_sizes[BOOTP_REPLY];
	retval = udp_read((unsigned char *)bp, getds(), &bufsize,
							timeout, CHR_ESC);

	/* Check for error and that we received a valid packet */
	errch = '?';
	if (retval != PXENV_STATUS_SUCCESS) {
		/* Check if user pressed ESC key */
		if (retval == PXENV_STATUS_ARP_CANCELED) {
			retval = PXENV_STATUS_BOOTP_CANCELED;
			break;
		}

		/* We have a timeout */
		errch = '.';
		if (retries++ > BOOTP_RETRIES) {
			retval = PXENV_STATUS_BOOTP_TIMEOUT;
			break;
		}
	} else if (bufsize >= BOOTP_MIN_SIZE && validate()) {
		/* We received a valid BOOTP reply packet */
		printf("ok\n");
		retval = PXENV_STATUS_SUCCESS;
		break;
	}

	/* Increase timeout time and print error character */
	timeout = (timeout << 1) + random(0x001f);
	while (timeout > 60)		/* dont allow more than 60 seconds */
		timeout -= 60;
	printf("%c", errch);
  }

  /* Close UDP connection and return */
  udp_close();
  return(retval);
}



/*
 **************************************************************************
 * 
 * Handle BOOTP protocol.
 */
int bootp()
{
  register unsigned char *cp;
  int retval;

  /* Set boot start time - required for gateway booting */
  boot_start_time = get_ticks();

  /* Get BOOTP record from server */
  if ((retval = bootp_get()) != PXENV_STATUS_SUCCESS)
	goto bootp_ret;

  /* Setup the network interface with the values received from BOOTP server */
  cur_bootp_buf = BOOTP_REPLY;
  myipaddr = bootp_bufs[BOOTP_REPLY]->bp_yiaddr;
  if ((cp = get_vend(VEND_SUBNET)) != NULL)
	mynetmask = *((t_ipaddr *)(cp + 1));
  else if (n_IN_CLASS_A(myipaddr))
	mynetmask = n_IP_CLASS_A;
  else if (n_IN_CLASS_B(myipaddr))
	mynetmask = n_IP_CLASS_B;
  else if (n_IN_CLASS_C(myipaddr))
	mynetmask = n_IP_CLASS_C;
  if ((cp = get_vend(VEND_ROUTER)) != NULL)
	mygateway = *((t_ipaddr *)(cp + 1));
  else
	mygateway = n_IP_ANY;

#ifndef NOBPEXT
  /* Next try to get the extensions file if there is any specified */
  if ((cp = get_vend(VEND_EXTFILE)) != NULL)
	get_ext(cp);
#endif

  /*
   * Finally set the size of the reply buffer. Also we have to set the
   * pointer to the acknowledge packet (which doesn't exist with BOOTP).
   */
  set_bootp_size();
  bootp_bufs[BOOTP_ACK] = bootp_bufs[BOOTP_REPLY];
  bootp_sizes[BOOTP_ACK] = bootp_sizes[BOOTP_REPLY];
  retval = PXENV_STATUS_SUCCESS;

bootp_ret:
  return(retval);
}



syntax highlighted by Code2HTML, v. 0.9.1