/**************************************************************************
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 */
#ifdef MULTICAST_LEVEL2
unsigned long last_igmpv1 = 0;
#endif
static unsigned long netmask;
/* Used by nfs.c */
int hostnamelen = 0;
/* Used by fsys_tftp.c */
int use_bios_pxe = 0;
static int dhcp_reply;
static const unsigned char dhcpdiscover[] = {
/* Vendor class identifier */
#ifdef SOLARIS_NETBOOT
'A','r','c','h',':','0','0','0','0','0',':','U','N','D','I',':',
'0','0','2','0','0','1',
#else
#endif
};
static const unsigned char dhcprequest [] = {
RFC2132_SRV_ID,4,0,0,0,0,
RFC2132_REQ_ADDR,4,0,0,0,0,
/* Vendor class identifier */
#ifdef SOLARIS_NETBOOT
'A','r','c','h',':','0','0','0','0','0',':','U','N','D','I',':',
'0','0','2','0','0','1',
#else
#endif
/* 4 standard + 2 vendortags */
4 + 2,
/* Standard parameters */
/* Etherboot vendortags */
};
/* See nic.h */
int user_abort = 0;
int network_ready = 0;
#ifdef REQUIRE_VCI_ETHERBOOT
int vci_etherboot;
#endif
char vendor_configfile_len;
static void update_network_configuration(void);
{
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.
*/
{
{
0, /* dev.disable */
{
0,
0,
}, /* dev.devid */
0, /* index */
0, /* type */
PROBE_FIRST, /* how_pobe */
PROBE_NONE, /* to_probe */
0, /* failsafe */
0, /* type_index */
{}, /* state */
},
(void (*)(struct nic *, const char *,
unsigned int, unsigned int,
const char *))dummy, /* transmit */
0, /* flags */
&rom, /* rom_info */
0, /* packetlen */
0, /* ioaddr */
0, /* irqno */
NULL, /* priv_data */
};
int grub_eth_probe(void)
{
static int probed = 0;
EnterFunction("grub_eth_probe");
if (probed)
return 1;
network_ready = 0;
LeaveFunction("grub_eth_probe");
return probed;
}
{
}
{
}
void eth_transmit(const char *d, unsigned int t, unsigned int s, const void *p)
{
}
void eth_disable(void)
{
#ifdef MULTICAST_LEVEL2
int i;
for(i = 0; i < MAX_IGMP; i++) {
leave_group(i);
}
#endif
}
{
}
/**************************************************************************
IPCHKSUM - Checksum IP Header
**************************************************************************/
{
unsigned long sum;
unsigned long i;
/* In the most straight forward way possible,
* compute an ip style checksum.
*/
sum = 0;
for(i = 0; i < length; i++) {
unsigned long value;
if (i & 1) {
value <<= 8;
}
/* Add the new value */
/* Wrap around the carry */
if (sum > 0xFFFF) {
}
}
}
{
unsigned long checksum;
if (offset & 1) {
/* byte swap the sum if it came from an odd offset
* since the computation is endian independant this
* works.
*/
}
if (checksum > 0xFFFF) {
checksum -= 0xFFFF;
}
return (~checksum) & 0xFFFF;
}
/**************************************************************************
DEFAULT_NETMASK - Return default netmask for IP address
**************************************************************************/
static inline unsigned long default_netmask(void)
{
if (net <= 127)
return(htonl(0xff000000));
else if (net < 192)
return(htonl(0xffff0000));
else
return(htonl(0xffffff00));
}
/**************************************************************************
IP_TRANSMIT - Send an IP datagram
**************************************************************************/
{
return 0;
return 0;
return 0;
return 0;
return 1;
}
{
unsigned long destip;
int arpentry, i;
int retry;
if (destip == IP_BROADCAST) {
#ifdef MULTICAST_LEVEL1
unsigned long hdestip;
multicast[0] = 0x01;
#endif
} else {
return(0);
}
for (i = 0; i < ETH_ALEN; i++)
break;
if (i == ETH_ALEN) { /* Need to do arp request */
long timeout;
&arpreq);
}
return(0);
}
xmit:
}
return 1;
}
{
ip->ident = 0;
}
{
/* Compute the pseudo header */
/* Sum the pseudo header */
/* Sum the rest of the udp packet */
return checksum;
}
{
}
/**************************************************************************
UDP_TRANSMIT - Send an UDP datagram
**************************************************************************/
{
}
/**************************************************************************
QDRAIN - clear the nic's receive queue
**************************************************************************/
{
return 0;
}
void rx_qdrain(void)
{
/* Clear out the Rx queue first. It contains nothing of interest,
* 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. */
}
/**
* rarp
*
* Get IP address by rarp. Just copy from etherboot
**/
{
return 0;
return 0;
return 0;
return 1;
}
return 0;
}
int rarp(void)
{
int retry;
/* arp and rarp requests share the same packet structure. */
if(!grub_eth_probe())
return 0;
network_ready = 0;
/* sipaddr is already zeroed out */
/* tipaddr is already zeroed out */
long timeout;
break;
if (user_abort)
return 0;
}
if (retry == MAX_ARP_RETRIES) {
return (0);
}
network_ready = 1;
return (1);
}
/**
* bootp
*
* Get IP address by bootp, segregate from bootp in etherboot.
**/
{
if (!udp) {
return 0;
}
bootpreply = (struct bootp_t *)
if (len < 0) {
return 0;
}
return 0;
return 0;
return 0;
return 0;
return 0;
}
#ifdef SOLARIS_NETBOOT
/* fill in netinfo */
#endif
netmask = default_netmask();
return(1);
}
int bootp(void)
{
int retry;
unsigned long starttime;
EnterFunction("bootp");
if(!grub_eth_probe())
return 0;
network_ready = 0;
/* Use lower 32 bits of node address, more likely to be
distinct than the time since booting */
/* bp_secs defaults to zero */
memcpy(ip.bp.bp_vend, rfc1533_cookie_bootp, sizeof(rfc1533_cookie_bootp)); /* request RFC-style options */
long timeout;
rx_qdrain();
network_ready = 1;
return(1);
}
if (user_abort)
return 0;
}
return(0);
}
/**
* dhcp
*
* Get IP address by dhcp, segregate from bootp in etherboot.
**/
{
int len;
if (!udp) {
return 0;
}
if (len < 0){
return 0;
}
return 0;
return 0;
return 0;
return 0;
return 0;
}
#ifdef SOLARIS_NETBOOT
/* fill in netinfo */
#endif
netmask = default_netmask();
return(1);
}
int dhcp(void)
{
int retry;
int reqretry;
unsigned long starttime;
/* try bios pxe stack first */
if (dhcp_undi())
return 1;
if(!grub_eth_probe())
return 0;
network_ready = 0;
/* Use lower 32 bits of node address, more likely to be
distinct than the time since booting */
memcpy(ip.bp.bp_vend, rfc1533_cookie_dhcp, sizeof rfc1533_cookie_dhcp); /* request RFC-style options */
long timeout;
rx_qdrain();
/* 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;
/* Beware: the magic numbers 9 and 15 depend on
the layout of dhcprequest */
dhcp_reply=0;
if (dhcp_reply == DHCPACK){
network_ready = 1;
return(1);
}
if (user_abort)
return 0;
}
}
if (user_abort)
return 0;
}
return(0);
}
#ifdef MULTICAST_LEVEL2
{
int i;
for(i = 0; i < MAX_IGMP; i++) {
if (last_igmpv1 &&
}
#ifdef MDEBUG
#endif
/* Don't send another igmp report until asked */
}
}
}
{
int i;
unsigned iplen = 0;
return;
}
return;
if (igmp->response_time == 0) {
last_igmpv1 = now;
} else {
}
#ifdef MDEBUG
#endif
for(i = 0; i < MAX_IGMP; i++) {
unsigned long time;
}
}
}
}
#ifdef MDEBUG
#endif
for(i = 0; i < MAX_IGMP; i++) {
}
}
}
}
{
/* 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.
*/
#ifdef MDEBUG
#endif
}
}
{
/* I have already joined */
return;
}
/* 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...
*/
}
}
#else
#endif
/**************************************************************************
AWAIT_REPLY - Wait until we get a response for our request
************f**************************************************************/
{
unsigned iplen = 0;
unsigned short ptype;
int result;
user_abort = 0;
/* 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 (;;) {
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. */
/* Do the timeout after at least a full queue walk. */
break;
}
continue;
}
/* We have something! */
/* Find the Ethernet packet type */
} else continue; /* what else could we do with it? */
/* Verify an IP header */
ip = 0;
unsigned ipoptlen;
continue;
continue;
static int warned_fragmentation = 0;
if (!warned_fragmentation) {
printf("ALERT: got a fragmented packet - reconfigure your server\n");
warned_fragmentation = 1;
}
continue;
}
continue;
if (ipoptlen) {
/* Delete the ip options, to guarantee
* good alignment, and make etherboot simpler.
*/
}
}
udp = 0;
/* Make certain we have a reasonable packet length */
continue;
printf("UDP checksum error\n");
continue;
}
}
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.
*/
unsigned long tmp;
sizeof(struct arprequest),
arpreply);
#ifdef MDEBUG
#endif /* MDEBUG */
}
}
}
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)
{
for (p++; p < end; ) {
if (*p == RFC2132_VENDOR_CLASS_ID) {
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
**/
{
unsigned char *endp;
if (block == 0) {
return(0); /* no RFC 1533 header found */
p += 4;
} else {
if (block == 1) {
return(0); /* no RFC 1533 header found */
p += 4;
len -= 4; }
rfc1533_venddata + sizeof(rfc1533_venddata)) {
} else {
printf("Overflow in vendor data buffer! Aborting...\n");
*extdata = RFC1533_END;
return(0);
}
}
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)
else if (c == RFC1533_GATEWAY) {
/* This is a little simplistic, but it will
usually be sufficient.
Take only the first entry */
}
else if (c == RFC1533_EXTENSIONPATH)
extpath = p;
else if (c == RFC2132_MSG_TYPE)
dhcp_reply=*(p+2);
else if (c == RFC2132_SRV_ID)
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. */
config_file[l] = 0;
vendor_configfile = p + 2;
}
else {
;
}
p += TAG_LEN(p) + 2;
}
printf("Overflow in vendor data buffer! Aborting...\n");
*extdata = RFC1533_END;
return(0);
}
}
return 1; /* proceed with next block */
}
/* FIXME double check TWO_SECOND_DIVISOR */
/**************************************************************************
RFC2131_SLEEP_INTERVAL - sleep for expotentially longer times (base << exp) +- 1 sec)
**************************************************************************/
{
unsigned long tmo;
#ifdef BACKOFF_LIMIT
if (exp > BACKOFF_LIMIT)
exp = BACKOFF_LIMIT;
#endif
return tmo;
}
#ifdef MULTICAST_LEVEL2
/**************************************************************************
RFC1112_SLEEP_INTERVAL - sleep for expotentially longer times, up to (base << exp)
**************************************************************************/
{
#ifdef BACKOFF_LIMIT
if (exp > BACKOFF_LIMIT)
exp = BACKOFF_LIMIT;
#endif
return tmo;
}
#endif /* MULTICAST_LEVEL_2 */
/* ifconfig - configure network interface. */
int
{
if (sm)
{
return 0;
}
if (ip)
{
return 0;
netmask = default_netmask ();
}
return 0;
/* Clear out the ARP entry. */
return 0;
/* Likewise. */
{
|| ! netmask)
network_ready = 0;
else
network_ready = 1;
}
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
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];
etherboot_printf ("Site Option 150: %s\n",
}
etherboot_printf ("BootFile: not set\n");
else
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 {
unsigned long sn_ciaddr;
unsigned long sn_siaddr;
unsigned long sn_giaddr;
unsigned long sn_netmask;
} *sip;
if (! network_ready)
return;
#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)
else
eth_disable ();
network_ready = 0;
}
}
/*******************************************************************
* dhcp implementation reusing the BIOS pxe stack
*/
static void
{
unsigned long time;
/* fill in netinfo */
dhcpack_length = sizeof (struct dhcp_t);
netmask = default_netmask();
}
int dhcp_undi(void)
{
if (!undi_bios_pxe((void **)&dhcpreply))
return 0;
network_ready = 1;
use_bios_pxe = 1;
return (1);
}