OpenSolaris_b135/grub/grub-0.97/netboot/nic.c

Compare this file to the similar file:
Show the results in this format:

/**************************************************************************
Etherboot -  Network Bootstrap Program

Literature dealing with the network protocols:
	ARP - RFC826
	RARP - RFC903
        IP - RFC791
	UDP - RFC768
	BOOTP - RFC951, RFC2132 (vendor extensions)
	DHCP - RFC2131, RFC2132 (options)
	TFTP - RFC1350, RFC2347 (options), RFC2348 (blocksize), RFC2349 (tsize)
	RPC - RFC1831, RFC1832 (XDR), RFC1833 (rpcbind/portmapper)
	NFS - RFC1094, RFC1813 (v3, useful for clarifications, not implemented)
	IGMP - RFC1112, RFC2113, RFC2365, RFC2236, RFC3171

**************************************************************************/
#include "etherboot.h"
#include "grub.h"
#include "nic.h"
#include "elf.h" /* FOR EM_CURRENT */
#include "bootp.h"
#include "if_arp.h"
#include "tftp.h"
#include "timer.h"
#include "ip.h"
#include "udp.h"

/* Currently no other module uses rom, but it is available */
struct rom_info		rom;
struct arptable_t	arptable[MAX_ARP];
#ifdef MULTICAST_LEVEL2
unsigned long last_igmpv1 = 0;
struct igmptable_t	igmptable[MAX_IGMP];
#endif
static unsigned long	netmask;
/* Used by nfs.c */
char *hostname = "";
int hostnamelen = 0;
/* Used by fsys_tftp.c */
int use_bios_pxe = 0;
static uint32_t xid;
static unsigned char *end_of_rfc1533 = NULL;
static const unsigned char broadcast[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
static const in_addr zeroIP = { 0L };
static char rfc1533_venddata[MAX_RFC1533_VENDLEN];
static unsigned char rfc1533_cookie[4] = { RFC1533_COOKIE };
static unsigned char rfc1533_cookie_bootp[5] = { RFC1533_COOKIE, RFC1533_END };
static unsigned char rfc1533_cookie_dhcp[] = { RFC1533_COOKIE };
static int dhcp_reply;
static in_addr dhcp_server = { 0L };
static in_addr dhcp_addr = { 0L };

static const unsigned char dhcpdiscover[] = {
	RFC2132_MSG_TYPE, 1, DHCPDISCOVER,
	RFC2132_MAX_SIZE, 2,	/* request as much as we can */
	ETH_MAX_MTU / 256, ETH_MAX_MTU % 256,
	/* Vendor class identifier */
#ifdef SOLARIS_NETBOOT
	RFC2132_VENDOR_CLASS_ID,32,'P','X','E','C','l','i','e','n','t',':',
	'A','r','c','h',':','0','0','0','0','0',':','U','N','D','I',':',
	'0','0','2','0','0','1',
#else
	RFC2132_VENDOR_CLASS_ID, 10, 'G', 'R', 'U', 'B', 'C', 'l', 'i', 'e', 'n', 't',
#endif
	RFC2132_PARAM_LIST, 4, RFC1533_NETMASK, RFC1533_GATEWAY,
	RFC1533_HOSTNAME, RFC1533_EXTENSIONPATH, RFC1533_END
};
static const unsigned char dhcprequest [] = {
	RFC2132_MSG_TYPE,1,DHCPREQUEST,
	RFC2132_SRV_ID,4,0,0,0,0,
	RFC2132_REQ_ADDR,4,0,0,0,0,
	RFC2132_MAX_SIZE,2,	/* request as much as we can */
	ETH_MAX_MTU / 256, ETH_MAX_MTU % 256,
	/* Vendor class identifier */
#ifdef SOLARIS_NETBOOT
	RFC2132_VENDOR_CLASS_ID,32,'P','X','E','C','l','i','e','n','t',':',
	'A','r','c','h',':','0','0','0','0','0',':','U','N','D','I',':',
	'0','0','2','0','0','1',
#else
	RFC2132_VENDOR_CLASS_ID, 10, 'G', 'R', 'U', 'B', 'C', 'l', 'i', 'e', 'n', 't',
#endif
	RFC2132_PARAM_LIST,
	/* 4 standard + 2 vendortags */
	4 + 2,
	/* Standard parameters */
	RFC1533_NETMASK, RFC1533_GATEWAY,
	RFC1533_HOSTNAME, RFC1533_EXTENSIONPATH,
	/* Etherboot vendortags */
	RFC1533_VENDOR_MAGIC,
	RFC1533_VENDOR_CONFIGFILE,
	RFC1533_END
};

/* See nic.h */
int user_abort = 0;
int network_ready = 0;

#ifdef	REQUIRE_VCI_ETHERBOOT
int	vci_etherboot;
#endif

char *bootfile = NULL;
configfile_origin_t configfile_origin = CFG_HARDCODED;
char *vendor_configfile = NULL;
char vendor_configfile_len;

static void update_network_configuration(void);

static int dummy(void *unused __unused)
{
	return (0);
}

/* Careful.  We need an aligned buffer to avoid problems on machines
 * that care about alignment.  To trivally align the ethernet data
 * (the ip hdr and arp requests) we offset the packet by 2 bytes.
 * leaving the ethernet data 16 byte aligned.  Beyond this
 * we use memmove but this makes the common cast simple and fast.
 */
static char	packet[ETH_FRAME_LEN + ETH_DATA_ALIGN] __aligned;

struct nic	nic =
{
	{
		0,				/* dev.disable */
		{
			0,
			0,
			PCI_BUS_TYPE,
		},				/* dev.devid */
		0,				/* index */
		0,				/* type */
		PROBE_FIRST,			/* how_pobe */
		PROBE_NONE,			/* to_probe */
		0,				/* failsafe */
		0,				/* type_index */
		{},				/* state */
	},
	(int (*)(struct nic *, int))dummy,      /* poll */
	(void (*)(struct nic *, const char *,
		unsigned int, unsigned int,
		const char *))dummy,		/* transmit */
	(void (*)(struct nic *, irq_action_t))dummy, /* irq */
	0,					/* flags */
	&rom,					/* rom_info */
	arptable[ARP_CLIENT].node,		/* node_addr */
	packet + ETH_DATA_ALIGN,		/* packet */
	0,					/* packetlen */
	0,			/* ioaddr */
	0,			/* irqno */
	NULL,					/* priv_data */
};



int grub_eth_probe(void)
{
	static int probed = 0;
	struct dev *dev;

	EnterFunction("grub_eth_probe");

	if (probed)
		return 1;

	network_ready = 0;
	grub_memset((char *)arptable, 0, MAX_ARP * sizeof(struct arptable_t));
	dev = &nic.dev;
	dev->how_probe = -1;
	dev->type = NIC_DRIVER;
	dev->failsafe = 1;
	rom = *((struct rom_info *)ROM_INFO_LOCATION);

	probed = (eth_probe(dev) == PROBE_WORKED);

	LeaveFunction("grub_eth_probe");
	return probed;
}

int eth_probe(struct dev *dev)
{
	return probe(dev);
}

int eth_poll(int retrieve)
{
	return ((*nic.poll)(&nic, retrieve));
}

void eth_transmit(const char *d, unsigned int t, unsigned int s, const void *p)
{
	(*nic.transmit)(&nic, d, t, s, p);
	if (t == IP) twiddle();
}

void eth_disable(void)
{
#ifdef MULTICAST_LEVEL2
	int i;
	for(i = 0; i < MAX_IGMP; i++) {
		leave_group(i);
	}
#endif
	disable(&nic.dev);
}

void eth_irq (irq_action_t action)
{
	(*nic.irq)(&nic,action);
}

/**************************************************************************
IPCHKSUM - Checksum IP Header
**************************************************************************/
uint16_t ipchksum(const void *data, unsigned long length)
{
	unsigned long sum;
	unsigned long i;
	const uint8_t *ptr;

	/* In the most straight forward way possible,
	 * compute an ip style checksum.
	 */
	sum = 0;
	ptr = data;
	for(i = 0; i < length; i++) {
		unsigned long value;
		value = ptr[i];
		if (i & 1) {
			value <<= 8;
		}
		/* Add the new value */
		sum += value;
		/* Wrap around the carry */
		if (sum > 0xFFFF) {
			sum = (sum + (sum >> 16)) & 0xFFFF;
		}
	}
	return (~cpu_to_le16(sum)) & 0xFFFF;
}

uint16_t add_ipchksums(unsigned long offset, uint16_t sum, uint16_t new)
{
	unsigned long checksum;
	sum = ~sum & 0xFFFF;
	new = ~new & 0xFFFF;
	if (offset & 1) {
		/* byte swap the sum if it came from an odd offset 
		 * since the computation is endian independant this
		 * works.
		 */
		new = bswap_16(new);
	}
	checksum = sum + new;
	if (checksum > 0xFFFF) {
		checksum -= 0xFFFF;
	}
	return (~checksum) & 0xFFFF;
}

/**************************************************************************
DEFAULT_NETMASK - Return default netmask for IP address
**************************************************************************/
static inline unsigned long default_netmask(void)
{
	int net = ntohl(arptable[ARP_CLIENT].ipaddr.s_addr) >> 24;
	if (net <= 127)
		return(htonl(0xff000000));
	else if (net < 192)
		return(htonl(0xffff0000));
	else
		return(htonl(0xffffff00));
}

/**************************************************************************
IP_TRANSMIT - Send an IP datagram
**************************************************************************/
static int await_arp(int ival, void *ptr,
	unsigned short ptype, struct iphdr *ip __unused, struct udphdr *udp __unused)
{
	struct	arprequest *arpreply;
	if (ptype != ARP)
		return 0;
	if (nic.packetlen < ETH_HLEN + sizeof(struct arprequest))
		return 0;
	arpreply = (struct arprequest *)&nic.packet[ETH_HLEN];

	if (arpreply->opcode != htons(ARP_REPLY)) 
		return 0;
	if (memcmp(arpreply->sipaddr, ptr, sizeof(in_addr)) != 0)
		return 0;
	memcpy(arptable[ival].node, arpreply->shwaddr, ETH_ALEN);
	return 1;
}

int ip_transmit(int len, const void *buf)
{
	unsigned long destip;
	struct iphdr *ip;
	struct arprequest arpreq;
	int arpentry, i;
	int retry;

	ip = (struct iphdr *)buf;
	destip = ip->dest.s_addr;
	if (destip == IP_BROADCAST) {
		eth_transmit(broadcast, IP, len, buf);
#ifdef MULTICAST_LEVEL1 
	} else if ((destip & htonl(MULTICAST_MASK)) == htonl(MULTICAST_NETWORK)) {
		unsigned char multicast[6];
		unsigned long hdestip;
		hdestip = ntohl(destip);
		multicast[0] = 0x01;
		multicast[1] = 0x00;
		multicast[2] = 0x5e;
		multicast[3] = (hdestip >> 16) & 0x7;
		multicast[4] = (hdestip >> 8) & 0xff;
		multicast[5] = hdestip & 0xff;
		eth_transmit(multicast, IP, len, buf);
#endif
	} else {
		if (((destip & netmask) !=
		     (arptable[ARP_CLIENT].ipaddr.s_addr & netmask)) &&
		    arptable[ARP_GATEWAY].ipaddr.s_addr)
			destip = arptable[ARP_GATEWAY].ipaddr.s_addr;
		for(arpentry = 0; arpentry<MAX_ARP; arpentry++)
			if (arptable[arpentry].ipaddr.s_addr == destip) break;
		if (arpentry == MAX_ARP) {
			printf("%@ is not in my arp table!\n", destip);
			return(0);
		}
		for (i = 0; i < ETH_ALEN; i++)
			if (arptable[arpentry].node[i])
				break;
		if (i == ETH_ALEN) {	/* Need to do arp request */
			arpreq.hwtype = htons(1);
			arpreq.protocol = htons(IP);
			arpreq.hwlen = ETH_ALEN;
			arpreq.protolen = 4;
			arpreq.opcode = htons(ARP_REQUEST);
			memcpy(arpreq.shwaddr, arptable[ARP_CLIENT].node, ETH_ALEN);
			memcpy(arpreq.sipaddr, &arptable[ARP_CLIENT].ipaddr, sizeof(in_addr));
			memset(arpreq.thwaddr, 0, ETH_ALEN);
			memcpy(arpreq.tipaddr, &destip, sizeof(in_addr));
			for (retry = 1; retry <= MAX_ARP_RETRIES; retry++) {
				long timeout;
				eth_transmit(broadcast, ARP, sizeof(arpreq),
					&arpreq);
				timeout = rfc2131_sleep_interval(TIMEOUT, retry);
				if (await_reply(await_arp, arpentry,
					arpreq.tipaddr, timeout)) goto xmit;
			}
			return(0);
		}
xmit:
		eth_transmit(arptable[arpentry].node, IP, len, buf);
	}
	return 1;
}

void build_ip_hdr(unsigned long destip, int ttl, int protocol, int option_len,
	int len, const void *buf)
{
	struct iphdr *ip;
	ip = (struct iphdr *)buf;
	ip->verhdrlen = 0x45;
	ip->verhdrlen += (option_len/4);
	ip->service = 0;
	ip->len = htons(len);
	ip->ident = 0;
	ip->frags = 0; /* Should we set don't fragment? */
	ip->ttl = ttl;
	ip->protocol = protocol;
	ip->chksum = 0;
	ip->src.s_addr = arptable[ARP_CLIENT].ipaddr.s_addr;
	ip->dest.s_addr = destip;
	ip->chksum = ipchksum(buf, sizeof(struct iphdr) + option_len);
}

static uint16_t udpchksum(struct iphdr *ip, struct udphdr *udp)
{
	struct udp_pseudo_hdr pseudo;
	uint16_t checksum;

	/* Compute the pseudo header */
	pseudo.src.s_addr  = ip->src.s_addr;
	pseudo.dest.s_addr = ip->dest.s_addr;
	pseudo.unused      = 0;
	pseudo.protocol    = IP_UDP;
	pseudo.len         = udp->len;

	/* Sum the pseudo header */
	checksum = ipchksum(&pseudo, 12);

	/* Sum the rest of the udp packet */
	checksum = add_ipchksums(12, checksum, ipchksum(udp, ntohs(udp->len)));
	return checksum;
}


void build_udp_hdr(unsigned long destip, 
	unsigned int srcsock, unsigned int destsock, int ttl,
	int len, const void *buf)
{
	struct iphdr *ip;
	struct udphdr *udp;
	ip = (struct iphdr *)buf;
	build_ip_hdr(destip, ttl, IP_UDP, 0, len, buf);
	udp = (struct udphdr *)((char *)buf + sizeof(struct iphdr));
	udp->src = htons(srcsock);
	udp->dest = htons(destsock);
	udp->len = htons(len - sizeof(struct iphdr));
	udp->chksum = 0;
	if ((udp->chksum = udpchksum(ip, udp)) == 0)
		udp->chksum = 0xffff;
}


/**************************************************************************
UDP_TRANSMIT - Send an UDP datagram
**************************************************************************/
int udp_transmit(unsigned long destip, unsigned int srcsock,
	unsigned int destsock, int len, const void *buf)
{
	build_udp_hdr(destip, srcsock, destsock, 60, len, buf);
	return ip_transmit(len, buf);
}

/**************************************************************************
QDRAIN - clear the nic's receive queue
**************************************************************************/
static int await_qdrain(int ival __unused, void *ptr __unused,
	unsigned short ptype __unused, 
	struct iphdr *ip __unused, struct udphdr *udp __unused)
{
	return 0;
}

void rx_qdrain(void)
{
	/* Clear out the Rx queue first.  It contains nothing of interest,
	 * except possibly ARP requests from the DHCP/TFTP server.  We use
	 * polling throughout Etherboot, so some time may have passed since we
	 * last polled the receive queue, which may now be filled with
	 * broadcast packets.  This will cause the reply to the packets we are
	 * about to send to be lost immediately.  Not very clever.  */
	await_reply(await_qdrain, 0, NULL, 0);
}

/**
 * rarp
 *
 * Get IP address by rarp. Just copy from etherboot
 **/
static int await_rarp(int ival, void *ptr, unsigned short ptype, 
		      struct iphdr *ip, struct udphdr *udp)
{
	struct arprequest *arpreply;
	if (ptype != RARP)
		return 0;
	if (nic.packetlen < ETH_HLEN + sizeof(struct arprequest))
		return 0;
	arpreply = (struct arprequest *)&nic.packet[ETH_HLEN];
	if (arpreply->opcode != htons(RARP_REPLY))
		return 0;
	if (memcmp(arpreply->thwaddr, ptr, ETH_ALEN) == 0){
		memcpy(arptable[ARP_SERVER].node, arpreply->shwaddr, ETH_ALEN);
		memcpy(&arptable[ARP_SERVER].ipaddr, arpreply->sipaddr, sizeof(in_addr));
		memcpy(&arptable[ARP_CLIENT].ipaddr, arpreply->tipaddr, sizeof(in_addr));
		memset(&arptable[ARP_GATEWAY].ipaddr, 0, sizeof(in_addr));
		return 1;
	}
	return 0;
}

int rarp(void)
{
	int retry;

	/* arp and rarp requests share the same packet structure. */
	struct arprequest rarpreq;

	if(!grub_eth_probe())
		return 0;
	network_ready = 0;

	memset(&rarpreq, 0, sizeof(rarpreq));

	rarpreq.hwtype = htons(1);
	rarpreq.protocol = htons(IP);
	rarpreq.hwlen = ETH_ALEN;
	rarpreq.protolen = 4;
	rarpreq.opcode = htons(RARP_REQUEST);
	memcpy(&rarpreq.shwaddr, arptable[ARP_CLIENT].node, ETH_ALEN);
	/* sipaddr is already zeroed out */
	memcpy(&rarpreq.thwaddr, arptable[ARP_CLIENT].node, ETH_ALEN);
	/* tipaddr is already zeroed out */

	for (retry = 0; retry < MAX_ARP_RETRIES; ++retry) {
		long timeout;
		eth_transmit(broadcast, RARP, sizeof(rarpreq), &rarpreq);

		timeout = rfc2131_sleep_interval(TIMEOUT, retry);
		if (await_reply(await_rarp, 0, rarpreq.shwaddr, timeout))
			break;
		if (user_abort)
			return 0;
	}

	if (retry == MAX_ARP_RETRIES) {
		return (0);
	}

	network_ready = 1;
  	update_network_configuration();
	return (1);
}

/**
 * bootp
 *
 * Get IP address by bootp, segregate from bootp in etherboot.
 **/
static int await_bootp(int ival __unused, void *ptr __unused,
	unsigned short ptype __unused, struct iphdr *ip __unused, 
	struct udphdr *udp)
{
	struct	bootp_t *bootpreply;
	int len;		/* Length of vendor */

	if (!udp) {
		return 0;
	}
	bootpreply = (struct bootp_t *)
		&nic.packet[ETH_HLEN + sizeof(struct iphdr) + sizeof(struct udphdr)];
	len = nic.packetlen - (ETH_HLEN + sizeof(struct iphdr) + 
		sizeof(struct udphdr) + sizeof(struct bootp_t) - BOOTP_VENDOR_LEN);
	if (len < 0) {
		return 0;
	}
	if (udp->dest != htons(BOOTP_CLIENT))
		return 0;
	if (bootpreply->bp_op != BOOTP_REPLY)
		return 0;
	if (bootpreply->bp_xid != xid)
		return 0;
	if (memcmp((char *)&bootpreply->bp_siaddr, (char *)&zeroIP, sizeof(in_addr)) == 0)
		return 0;
	if ((memcmp(broadcast, bootpreply->bp_hwaddr, ETH_ALEN) != 0) &&
	    (memcmp(arptable[ARP_CLIENT].node, bootpreply->bp_hwaddr, ETH_ALEN) != 0)) {
		return 0;
	}

#ifdef SOLARIS_NETBOOT
	/* fill in netinfo */
	dhcpack_length = len + sizeof (struct bootp_t) - BOOTP_VENDOR_LEN;
	memcpy((char *)dhcpack_buf, (char *)bootpreply, dhcpack_length);
#endif

	arptable[ARP_CLIENT].ipaddr.s_addr = bootpreply->bp_yiaddr.s_addr;
	netmask = default_netmask();
	arptable[ARP_SERVER].ipaddr.s_addr = bootpreply->bp_siaddr.s_addr;
	memset(arptable[ARP_SERVER].node, 0, ETH_ALEN);  /* Kill arp */
	arptable[ARP_GATEWAY].ipaddr.s_addr = bootpreply->bp_giaddr.s_addr;
	memset(arptable[ARP_GATEWAY].node, 0, ETH_ALEN);  /* Kill arp */
	bootfile = bootpreply->bp_file;
	memcpy((char *)rfc1533_venddata, (char *)(bootpreply->bp_vend), len);
	decode_rfc1533(rfc1533_venddata, 0, len, 1);
	return(1);
}

int bootp(void)
{
	int retry;
	struct bootpip_t ip;
	unsigned long  starttime;
	
	EnterFunction("bootp");

	if(!grub_eth_probe())
		return 0;
	network_ready = 0;

	memset(&ip, 0, sizeof(struct bootpip_t));
	ip.bp.bp_op = BOOTP_REQUEST;
	ip.bp.bp_htype = 1;
	ip.bp.bp_hlen = ETH_ALEN;
	starttime = currticks();
	/* Use lower 32 bits of node address, more likely to be
	   distinct than the time since booting */
	memcpy(&xid, &arptable[ARP_CLIENT].node[2], sizeof(xid));
	ip.bp.bp_xid = xid += htonl(starttime);
	/* bp_secs defaults to zero */
	memcpy(ip.bp.bp_hwaddr, arptable[ARP_CLIENT].node, ETH_ALEN);
	memcpy(ip.bp.bp_vend, rfc1533_cookie_bootp, sizeof(rfc1533_cookie_bootp)); /* request RFC-style options */

	for (retry = 0; retry < MAX_BOOTP_RETRIES; ) {
		long timeout;

		rx_qdrain();

		udp_transmit(IP_BROADCAST, BOOTP_CLIENT, BOOTP_SERVER,
			sizeof(struct bootpip_t), &ip);
		timeout = rfc2131_sleep_interval(TIMEOUT, retry++);
		if (await_reply(await_bootp, 0, NULL, timeout)){
			network_ready = 1;
			return(1);
		}
		if (user_abort)
			return 0;
		ip.bp.bp_secs = htons((currticks()-starttime)/TICKS_PER_SEC);
	}
	return(0);
}

/**
 * dhcp
 *
 * Get IP address by dhcp, segregate from bootp in etherboot.
 **/
static int await_dhcp(int ival __unused, void *ptr __unused,
	unsigned short ptype __unused, struct iphdr *ip __unused, 
	struct udphdr *udp)
{
	struct	dhcp_t *dhcpreply;
	int len;

	if (!udp) {
		return 0;
	}
	dhcpreply = (struct dhcp_t *)
		&nic.packet[ETH_HLEN + sizeof(struct iphdr) + sizeof(struct udphdr)];
	len = nic.packetlen - (ETH_HLEN + sizeof(struct iphdr) + 
		sizeof(struct udphdr) + sizeof(struct dhcp_t) - DHCP_OPT_LEN);
	if (len < 0){
		return 0;
	}
	if (udp->dest != htons(BOOTP_CLIENT))
		return 0;
	if (dhcpreply->bp_op != BOOTP_REPLY)
		return 0;
	if (dhcpreply->bp_xid != xid)
		return 0;
	if (memcmp((char *)&dhcpreply->bp_siaddr, (char *)&zeroIP, sizeof(in_addr)) == 0)
		return 0;
	if ((memcmp(broadcast, dhcpreply->bp_hwaddr, ETH_ALEN) != 0) &&
	    (memcmp(arptable[ARP_CLIENT].node, dhcpreply->bp_hwaddr, ETH_ALEN) != 0)) {
		return 0;
	}

#ifdef SOLARIS_NETBOOT
	/* fill in netinfo */
	dhcpack_length = len + sizeof (struct dhcp_t) - DHCP_OPT_LEN;
	memcpy((char *)dhcpack_buf, (char *)dhcpreply, dhcpack_length);
#endif
	arptable[ARP_CLIENT].ipaddr.s_addr = dhcpreply->bp_yiaddr.s_addr;
	dhcp_addr.s_addr = dhcpreply->bp_yiaddr.s_addr;
	netmask = default_netmask();
	arptable[ARP_SERVER].ipaddr.s_addr = dhcpreply->bp_siaddr.s_addr;
	memset(arptable[ARP_SERVER].node, 0, ETH_ALEN);  /* Kill arp */
	arptable[ARP_GATEWAY].ipaddr.s_addr = dhcpreply->bp_giaddr.s_addr;
	memset(arptable[ARP_GATEWAY].node, 0, ETH_ALEN);  /* Kill arp */
	bootfile = dhcpreply->bp_file;
	memcpy((char *)rfc1533_venddata, (char *)(dhcpreply->bp_vend), len);
	decode_rfc1533(rfc1533_venddata, 0, len, 1);
	return(1);
}

int dhcp(void)
{
	int retry;
	int reqretry;
	struct dhcpip_t ip;
	unsigned long  starttime;

	/* try bios pxe stack first */
	if (dhcp_undi())
		return 1;

	if(!grub_eth_probe())
		return 0;

	network_ready = 0;

	memset(&ip, 0, sizeof(ip));
	ip.bp.bp_op = BOOTP_REQUEST;
	ip.bp.bp_htype = 1;
	ip.bp.bp_hlen = ETH_ALEN;
	starttime = currticks();
	/* Use lower 32 bits of node address, more likely to be
	   distinct than the time since booting */
	memcpy(&xid, &arptable[ARP_CLIENT].node[2], sizeof(xid));
	ip.bp.bp_xid = xid += htonl(starttime);
	memcpy(ip.bp.bp_hwaddr, arptable[ARP_CLIENT].node, ETH_ALEN);
	memcpy(ip.bp.bp_vend, rfc1533_cookie_dhcp, sizeof rfc1533_cookie_dhcp); /* request RFC-style options */
	memcpy(ip.bp.bp_vend + sizeof rfc1533_cookie_dhcp, dhcpdiscover, sizeof dhcpdiscover);

	for (retry = 0; retry < MAX_BOOTP_RETRIES; ) {
		long timeout;

		rx_qdrain();

		udp_transmit(IP_BROADCAST, BOOTP_CLIENT, BOOTP_SERVER,
			     sizeof(ip), &ip);
		timeout = rfc2131_sleep_interval(TIMEOUT, retry++);
		if (await_reply(await_dhcp, 0, NULL, timeout)) {
			/* If not a DHCPOFFER then must be just a
			   BOOTP reply, be backward compatible with
			   BOOTP then. Jscott report a bug here, but I
			   don't know how it happened */
			if (dhcp_reply != DHCPOFFER){
				network_ready = 1;
				return(1);
			}
			dhcp_reply = 0;
			memcpy(ip.bp.bp_vend, rfc1533_cookie_dhcp, sizeof rfc1533_cookie_dhcp);
			memcpy(ip.bp.bp_vend + sizeof rfc1533_cookie_dhcp, dhcprequest, sizeof dhcprequest);
			/* Beware: the magic numbers 9 and 15 depend on
			   the layout of dhcprequest */
			memcpy(&ip.bp.bp_vend[9], &dhcp_server, sizeof(in_addr));
			memcpy(&ip.bp.bp_vend[15], &dhcp_addr, sizeof(in_addr));
			for (reqretry = 0; reqretry < MAX_BOOTP_RETRIES; ) {
				udp_transmit(IP_BROADCAST, BOOTP_CLIENT, BOOTP_SERVER,
					     sizeof(ip), &ip);
				dhcp_reply=0;
				timeout = rfc2131_sleep_interval(TIMEOUT, reqretry++);
				if (await_reply(await_dhcp, 0, NULL, timeout))
					if (dhcp_reply == DHCPACK){
						network_ready = 1;
						return(1);
					}
				if (user_abort)
					return 0;
			}
		}
		if (user_abort)
			return 0;
		ip.bp.bp_secs = htons((currticks()-starttime)/TICKS_PER_SEC);
	}
	return(0);
}

#ifdef MULTICAST_LEVEL2
static void send_igmp_reports(unsigned long now)
{
	int i;
	for(i = 0; i < MAX_IGMP; i++) {
		if (igmptable[i].time && (now >= igmptable[i].time)) {
			struct igmp_ip_t igmp;
			igmp.router_alert[0] = 0x94;
			igmp.router_alert[1] = 0x04;
			igmp.router_alert[2] = 0;
			igmp.router_alert[3] = 0;
			build_ip_hdr(igmptable[i].group.s_addr, 
				1, IP_IGMP, sizeof(igmp.router_alert), sizeof(igmp), &igmp);
			igmp.igmp.type = IGMPv2_REPORT;
			if (last_igmpv1 && 
				(now < last_igmpv1 + IGMPv1_ROUTER_PRESENT_TIMEOUT)) {
				igmp.igmp.type = IGMPv1_REPORT;
			}
			igmp.igmp.response_time = 0;
			igmp.igmp.chksum = 0;
			igmp.igmp.group.s_addr = igmptable[i].group.s_addr;
			igmp.igmp.chksum = ipchksum(&igmp.igmp, sizeof(igmp.igmp));
			ip_transmit(sizeof(igmp), &igmp);
#ifdef	MDEBUG
			printf("Sent IGMP report to: %@\n", igmp.igmp.group.s_addr);
#endif
			/* Don't send another igmp report until asked */
			igmptable[i].time = 0;
		}
	}
}

static void process_igmp(struct iphdr *ip, unsigned long now)
{
	struct igmp *igmp;
	int i;
	unsigned iplen = 0;
	if (!ip || (ip->protocol == IP_IGMP) ||
		(nic.packetlen < sizeof(struct iphdr) + sizeof(struct igmp))) {
		return;
	}
	iplen = (ip->verhdrlen & 0xf)*4;
	igmp = (struct igmp *)&nic.packet[sizeof(struct iphdr)];
	if (ipchksum(igmp, ntohs(ip->len) - iplen) != 0)
		return;
	if ((igmp->type == IGMP_QUERY) && 
		(ip->dest.s_addr == htonl(GROUP_ALL_HOSTS))) {
		unsigned long interval = IGMP_INTERVAL;
		if (igmp->response_time == 0) {
			last_igmpv1 = now;
		} else {
			interval = (igmp->response_time * TICKS_PER_SEC)/10;
		}
		
#ifdef	MDEBUG
		printf("Received IGMP query for: %@\n", igmp->group.s_addr);
#endif			       
		for(i = 0; i < MAX_IGMP; i++) {
			uint32_t group = igmptable[i].group.s_addr;
			if ((group == 0) || (group == igmp->group.s_addr)) {
				unsigned long time;
				time = currticks() + rfc1112_sleep_interval(interval, 0);
				if (time < igmptable[i].time) {
					igmptable[i].time = time;
				}
			}
		}
	}
	if (((igmp->type == IGMPv1_REPORT) || (igmp->type == IGMPv2_REPORT)) &&
		(ip->dest.s_addr == igmp->group.s_addr)) {
#ifdef	MDEBUG
		printf("Received IGMP report for: %@\n", igmp->group.s_addr);
#endif			       
		for(i = 0; i < MAX_IGMP; i++) {
			if ((igmptable[i].group.s_addr == igmp->group.s_addr) &&
				igmptable[i].time != 0) {
				igmptable[i].time = 0;
			}
		}
	}
}

void leave_group(int slot)
{
	/* Be very stupid and always send a leave group message if 
	 * I have subscribed.  Imperfect but it is standards
	 * compliant, easy and reliable to implement.
	 *
	 * The optimal group leave method is to only send leave when,
	 * we were the last host to respond to a query on this group,
	 * and igmpv1 compatibility is not enabled.
	 */
	if (igmptable[slot].group.s_addr) {
		struct igmp_ip_t igmp;
		igmp.router_alert[0] = 0x94;
		igmp.router_alert[1] = 0x04;
		igmp.router_alert[2] = 0;
		igmp.router_alert[3] = 0;
		build_ip_hdr(htonl(GROUP_ALL_HOSTS),
			1, IP_IGMP, sizeof(igmp.router_alert), sizeof(igmp), &igmp);
		igmp.igmp.type = IGMP_LEAVE;
		igmp.igmp.response_time = 0;
		igmp.igmp.chksum = 0;
		igmp.igmp.group.s_addr = igmptable[slot].group.s_addr;
		igmp.igmp.chksum = ipchksum(&igmp.igmp, sizeof(igmp));
		ip_transmit(sizeof(igmp), &igmp);
#ifdef	MDEBUG
		printf("Sent IGMP leave for: %@\n", igmp.igmp.group.s_addr);
#endif	
	}
	memset(&igmptable[slot], 0, sizeof(igmptable[0]));
}

void join_group(int slot, unsigned long group)
{
	/* I have already joined */
	if (igmptable[slot].group.s_addr == group)
		return;
	if (igmptable[slot].group.s_addr) {
		leave_group(slot);
	}
	/* Only join a group if we are given a multicast ip, this way
	 * code can be given a non-multicast (broadcast or unicast ip)
	 * and still work... 
	 */
	if ((group & htonl(MULTICAST_MASK)) == htonl(MULTICAST_NETWORK)) {
		igmptable[slot].group.s_addr = group;
		igmptable[slot].time = currticks();
	}
}
#else
#define send_igmp_reports(now);
#define process_igmp(ip, now)
#endif

/**************************************************************************
AWAIT_REPLY - Wait until we get a response for our request
************f**************************************************************/
int await_reply(reply_t reply, int ival, void *ptr, long timeout)
{
	unsigned long time, now;
	struct	iphdr *ip;
	unsigned iplen = 0;
	struct	udphdr *udp;
	unsigned short ptype;
	int result;

	user_abort = 0;

	time = timeout + currticks();
	/* The timeout check is done below.  The timeout is only checked if
	 * there is no packet in the Rx queue.  This assumes that eth_poll()
	 * needs a negligible amount of time.  
	 */
	for (;;) {
		now = currticks();
		send_igmp_reports(now);
		result = eth_poll(1);
		if (result == 0) {
			/* We don't have anything */
		
			/* Check for abort key only if the Rx queue is empty -
			 * as long as we have something to process, don't
			 * assume that something failed.  It is unlikely that
			 * we have no processing time left between packets.  */
			poll_interruptions();
			/* Do the timeout after at least a full queue walk.  */
			if ((timeout == 0) || (currticks() > time) || user_abort == 1) {
				break;
			}
			continue;
		}

		/* We have something! */
	
		/* Find the Ethernet packet type */
		if (nic.packetlen >= ETH_HLEN) {
			ptype = ((unsigned short) nic.packet[12]) << 8
				| ((unsigned short) nic.packet[13]);
		} else continue; /* what else could we do with it? */
		/* Verify an IP header */
		ip = 0;
		if ((ptype == IP) && (nic.packetlen >= ETH_HLEN + sizeof(struct iphdr))) {
			unsigned ipoptlen;
			ip = (struct iphdr *)&nic.packet[ETH_HLEN];
			if ((ip->verhdrlen < 0x45) || (ip->verhdrlen > 0x4F)) 
				continue;
			iplen = (ip->verhdrlen & 0xf) * 4;
			if (ipchksum(ip, iplen) != 0)
				continue;
			if (ip->frags & htons(0x3FFF)) {
				static int warned_fragmentation = 0;
				if (!warned_fragmentation) {
					printf("ALERT: got a fragmented packet - reconfigure your server\n");
					warned_fragmentation = 1;
				}
				continue;
			}
			if (ntohs(ip->len) > ETH_MAX_MTU)
				continue;

			ipoptlen = iplen - sizeof(struct iphdr);
			if (ipoptlen) {
				/* Delete the ip options, to guarantee
				 * good alignment, and make etherboot simpler.
				 */
				memmove(&nic.packet[ETH_HLEN + sizeof(struct iphdr)], 
					&nic.packet[ETH_HLEN + iplen],
					nic.packetlen - ipoptlen);
				nic.packetlen -= ipoptlen;
			}
		}
		udp = 0;
		if (ip && (ip->protocol == IP_UDP) && 
		    (nic.packetlen >= ETH_HLEN + sizeof(struct iphdr) + sizeof(struct udphdr))) {
			udp = (struct udphdr *)&nic.packet[ETH_HLEN + sizeof(struct iphdr)];
			
			/* Make certain we have a reasonable packet length */
			if (ntohs(udp->len) > (ntohs(ip->len) - iplen))
				continue;

			if (udp->chksum && udpchksum(ip, udp)) {
				printf("UDP checksum error\n");
				continue;
			}
		}
		result = reply(ival, ptr, ptype, ip, udp);
		if (result > 0) {
			return result;
		}
		
		/* If it isn't a packet the upper layer wants see if there is a default
		 * action.  This allows us reply to arp and igmp queryies.
		 */
		if ((ptype == ARP) &&
		    (nic.packetlen >= ETH_HLEN + sizeof(struct arprequest))) {
			struct	arprequest *arpreply;
			unsigned long tmp;
			
			arpreply = (struct arprequest *)&nic.packet[ETH_HLEN];
			memcpy(&tmp, arpreply->tipaddr, sizeof(in_addr));
			if ((arpreply->opcode == htons(ARP_REQUEST)) &&
			    (tmp == arptable[ARP_CLIENT].ipaddr.s_addr)) {
				arpreply->opcode = htons(ARP_REPLY);
				memcpy(arpreply->tipaddr, arpreply->sipaddr, sizeof(in_addr));
				memcpy(arpreply->thwaddr, arpreply->shwaddr, ETH_ALEN);
				memcpy(arpreply->sipaddr, &arptable[ARP_CLIENT].ipaddr, sizeof(in_addr));
				memcpy(arpreply->shwaddr, arptable[ARP_CLIENT].node, ETH_ALEN);
				eth_transmit(arpreply->thwaddr, ARP,
					     sizeof(struct  arprequest),
					     arpreply);
#ifdef	MDEBUG
				memcpy(&tmp, arpreply->tipaddr, sizeof(in_addr));
				printf("Sent ARP reply to: %@\n",tmp);
#endif	/* MDEBUG */
			}
		}
		process_igmp(ip, now);
	}
	return(0);
}

#ifdef	REQUIRE_VCI_ETHERBOOT
/**************************************************************************
FIND_VCI_ETHERBOOT - Looks for "Etherboot" in Vendor Encapsulated Identifiers
On entry p points to byte count of VCI options
**************************************************************************/
static int find_vci_etherboot(unsigned char *p)
{
	unsigned char	*end = p + 1 + *p;

	for (p++; p < end; ) {
		if (*p == RFC2132_VENDOR_CLASS_ID) {
			if (strncmp("Etherboot", p + 2, sizeof("Etherboot") - 1) == 0)
				return (1);
		} else if (*p == RFC1533_END)
			return (0);
		p += TAG_LEN(p) + 2;
	}
	return (0);
}
#endif	/* REQUIRE_VCI_ETHERBOOT */

/**
 * decode_rfc1533
 *
 * Decodes RFC1533 header
 **/
int decode_rfc1533(unsigned char *p, unsigned int block, unsigned int len, int eof)
{
	static unsigned char *extdata = NULL, *extend = NULL;
	unsigned char        *extpath = NULL;
	unsigned char        *endp;

	if (block == 0) {
		end_of_rfc1533 = NULL;
		if (memcmp(p, rfc1533_cookie, sizeof(rfc1533_cookie)))
			return(0); /* no RFC 1533 header found */
		p += 4;
		endp = p + len;
	} else {
		if (block == 1) {
			if (memcmp(p, rfc1533_cookie, sizeof(rfc1533_cookie)))
				return(0); /* no RFC 1533 header found */
			p += 4;
			len -= 4; }
		if (extend + len <= (unsigned char *)
		    rfc1533_venddata + sizeof(rfc1533_venddata)) {
			memcpy(extend, p, len);
			extend += len;
		} else {
			printf("Overflow in vendor data buffer! Aborting...\n");
			*extdata = RFC1533_END;
			return(0);
		}
		p = extdata; endp = extend;
	}
	if (!eof)
		return 1;
	while (p < endp) {
		unsigned char c = *p;
		if (c == RFC1533_PAD) {
			p++;
			continue;
		}
		else if (c == RFC1533_END) {
			end_of_rfc1533 = endp = p;
			continue;
		}
		else if (c == RFC1533_NETMASK)
			memcpy(&netmask, p+2, sizeof(in_addr));
		else if (c == RFC1533_GATEWAY) {
			/* This is a little simplistic, but it will
			   usually be sufficient.
			   Take only the first entry */
			if (TAG_LEN(p) >= sizeof(in_addr))
				memcpy(&arptable[ARP_GATEWAY].ipaddr, p+2, sizeof(in_addr));
		}
		else if (c == RFC1533_EXTENSIONPATH)
			extpath = p;
		else if (c == RFC2132_MSG_TYPE)
			dhcp_reply=*(p+2);
		else if (c == RFC2132_SRV_ID)
			memcpy(&dhcp_server, p+2, sizeof(in_addr));
		else if (c == RFC1533_HOSTNAME) {
			hostname = p + 2;
			hostnamelen = *(p + 1);
		}
		else if (c == RFC1533_VENDOR_CONFIGFILE){
			int l = TAG_LEN (p);
	  
			/* Eliminate the trailing NULs according to RFC 2132.  */
			while (*(p + 2 + l - 1) == '\000' && l > 0)
				l--;
	  
			/* XXX: Should check if LEN is less than the maximum length
			   of CONFIG_FILE. This kind of robustness will be a goal
			   in GRUB 1.0.  */
			memcpy (config_file, p + 2, l);
			config_file[l] = 0;
			vendor_configfile = p + 2;
			vendor_configfile_len = l;
			configfile_origin = CFG_150;
		}
		else {
			;
		}
		p += TAG_LEN(p) + 2;
	}
	extdata = extend = endp;
	if (block <= 0 && extpath != NULL) {
		char fname[64];
		if (TAG_LEN(extpath) >= sizeof(fname)){
			printf("Overflow in vendor data buffer! Aborting...\n");
			*extdata = RFC1533_END;
			return(0);
		}
		memcpy(fname, extpath+2, TAG_LEN(extpath));
		fname[(int)TAG_LEN(extpath)] = '\0';
		printf("Loading BOOTP-extension file: %s\n",fname);
		tftp_file_read(fname, decode_rfc1533);
	}
	return 1;	/* proceed with next block */
}


/* FIXME double check TWO_SECOND_DIVISOR */
#define TWO_SECOND_DIVISOR (RAND_MAX/TICKS_PER_SEC)
/**************************************************************************
RFC2131_SLEEP_INTERVAL - sleep for expotentially longer times (base << exp) +- 1 sec)
**************************************************************************/
long rfc2131_sleep_interval(long base, int exp)
{
	unsigned long tmo;
#ifdef BACKOFF_LIMIT
	if (exp > BACKOFF_LIMIT)
		exp = BACKOFF_LIMIT;
#endif
	tmo = (base << exp) + (TICKS_PER_SEC - (random()/TWO_SECOND_DIVISOR));
	return tmo;
}

#ifdef MULTICAST_LEVEL2
/**************************************************************************
RFC1112_SLEEP_INTERVAL - sleep for expotentially longer times, up to (base << exp)
**************************************************************************/
long rfc1112_sleep_interval(long base, int exp)
{
	unsigned long divisor, tmo;
#ifdef BACKOFF_LIMIT
	if (exp > BACKOFF_LIMIT)
		exp = BACKOFF_LIMIT;
#endif
	divisor = RAND_MAX/(base << exp);
	tmo = random()/divisor;
	return tmo;
}
#endif /* MULTICAST_LEVEL_2 */
/* ifconfig - configure network interface.  */
int
ifconfig (char *ip, char *sm, char *gw, char *svr)
{
  in_addr tmp;
  
  if (sm) 
    {
      if (! inet_aton (sm, &tmp))
	return 0;
      
      netmask = tmp.s_addr;
    }
  
  if (ip) 
    {
      if (! inet_aton (ip, &arptable[ARP_CLIENT].ipaddr)) 
	return 0;
      
      if (! netmask && ! sm) 
	netmask = default_netmask ();
    }
  
  if (gw && ! inet_aton (gw, &arptable[ARP_GATEWAY].ipaddr)) 
    return 0;

  /* Clear out the ARP entry.  */
  grub_memset (arptable[ARP_GATEWAY].node, 0, ETH_ALEN);
  
  if (svr && ! inet_aton (svr, &arptable[ARP_SERVER].ipaddr)) 
    return 0;

  /* Likewise.  */
  grub_memset (arptable[ARP_SERVER].node, 0, ETH_ALEN);
  
  if (ip || sm)
    {
      if (IP_BROADCAST == (netmask | arptable[ARP_CLIENT].ipaddr.s_addr)
	  || netmask == (netmask | arptable[ARP_CLIENT].ipaddr.s_addr)
	  || ! netmask)
	network_ready = 0;
      else
	network_ready = 1;
    }
  
  update_network_configuration();
  return 1;
}

/*
 * print_network_configuration
 *
 * Output the network configuration. It may broke the graphic console now.:-(
 */
void print_network_configuration (void)
{
	EnterFunction("print_network_configuration");
	if (! network_ready)
		grub_printf ("Network interface not initialized yet.\n");
	else {
		if (hostnamelen == 0)
			etherboot_printf ("Hostname: not set\n");
		else
			etherboot_printf ("Hostname: %s\n", hostname);

		etherboot_printf ("Address: %@\n", arptable[ARP_CLIENT].ipaddr.s_addr);
		etherboot_printf ("Netmask: %@\n", netmask);
		etherboot_printf ("Gateway: %@\n", arptable[ARP_GATEWAY].ipaddr.s_addr);
		etherboot_printf ("Server: %@\n", arptable[ARP_SERVER].ipaddr.s_addr);
		if (vendor_configfile == NULL) {
			etherboot_printf ("Site Option 150: not set\n");
		} else {
			/*
			 * vendor_configfile points into the packet and
			 * is not NULL terminated, so it needs to be
			 * patched up before printing it out
			 */
			char c = vendor_configfile[vendor_configfile_len];
			vendor_configfile[vendor_configfile_len] = '\0';
			etherboot_printf ("Site Option 150: %s\n",
			    vendor_configfile);
			vendor_configfile[vendor_configfile_len] = c;
		}

		if (bootfile == NULL)
			etherboot_printf ("BootFile: not set\n");
		else
			etherboot_printf ("BootFile: %s\n", bootfile);

		etherboot_printf ("GRUB menu file: %s", config_file);
		switch (configfile_origin) {
		case CFG_HARDCODED:
			etherboot_printf (" from hardcoded default\n");
			break;
		case CFG_150:
			etherboot_printf (" from Site Option 150\n");
			break;
		case CFG_MAC:
			etherboot_printf (" inferred from system MAC\n");
			break;
		case CFG_BOOTFILE:
			etherboot_printf (" inferred from BootFile\n");
			break;
		default:
			etherboot_printf ("\n");
		}
	}
	LeaveFunction("print_network_configuration");
}

/*
 * update_network_configuration
 *
 * Update network configuration for diskless clients (Solaris only)
 */
static void update_network_configuration (void)
{
#ifdef SOLARIS_NETBOOT
  	struct sol_netinfo {
	  	uint8_t sn_infotype;
		uint8_t sn_mactype;
		uint8_t sn_maclen;
	  	uint8_t sn_padding;
		unsigned long sn_ciaddr;
		unsigned long sn_siaddr;
		unsigned long sn_giaddr;
		unsigned long sn_netmask;
		uint8_t sn_macaddr[1];
	} *sip;

	if (! network_ready)
	  	return;

	sip = (struct sol_netinfo *)dhcpack_buf;
	sip->sn_infotype = 0xf0;	/* something not BOOTP_REPLY */
	sip->sn_mactype = 4;		/* DL_ETHER */
	sip->sn_maclen = ETH_ALEN;
	sip->sn_ciaddr = arptable[ARP_CLIENT].ipaddr.s_addr;
	sip->sn_siaddr = arptable[ARP_SERVER].ipaddr.s_addr;
	sip->sn_giaddr = arptable[ARP_GATEWAY].ipaddr.s_addr;
	sip->sn_netmask = netmask;
	memcpy(sip->sn_macaddr, arptable[ARP_CLIENT].node, ETH_ALEN);
	dhcpack_length = sizeof (*sip) + sip->sn_maclen - 1;
#endif /* SOLARIS_NETBOOT */
}

/**
 * cleanup_net
 *
 * Mark network unusable, and disable NICs
 */
void cleanup_net (void)
{
	if (network_ready){
		/* Stop receiving packets.  */
		if (use_bios_pxe)
			undi_pxe_disable();
		else
			eth_disable ();
		network_ready = 0;
	}
}

/*******************************************************************
 * dhcp implementation reusing the BIOS pxe stack
 */
static void
dhcp_copy(struct dhcp_t *dhcpreply)
{
	unsigned long time;
	int ret, len = DHCP_OPT_LEN;

	/* fill in netinfo */
	dhcpack_length = sizeof (struct dhcp_t);
	memcpy((char *)dhcpack_buf, (char *)dhcpreply, dhcpack_length);

	memcpy(arptable[ARP_CLIENT].node, dhcpreply->bp_hwaddr, ETH_ALEN);
	arptable[ARP_CLIENT].ipaddr.s_addr = dhcpreply->bp_yiaddr.s_addr;
	dhcp_addr.s_addr = dhcpreply->bp_yiaddr.s_addr;
	netmask = default_netmask();
	arptable[ARP_SERVER].ipaddr.s_addr = dhcpreply->bp_siaddr.s_addr;
	memset(arptable[ARP_SERVER].node, 0, ETH_ALEN);  /* Kill arp */
	arptable[ARP_GATEWAY].ipaddr.s_addr = dhcpreply->bp_giaddr.s_addr;
	memset(arptable[ARP_GATEWAY].node, 0, ETH_ALEN);  /* Kill arp */
	bootfile = dhcpreply->bp_file;
	memcpy((char *)rfc1533_venddata, (char *)(dhcpreply->bp_vend), len);
	decode_rfc1533(rfc1533_venddata, 0, len, 1);
}

int dhcp_undi(void)
{
	struct dhcp_t *dhcpreply;

	if (!undi_bios_pxe((void **)&dhcpreply))
		return 0;

	dhcp_copy(dhcpreply);
	network_ready = 1;
	use_bios_pxe = 1;
	return (1);
}