nic.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/**************************************************************************
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;
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
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));
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 */
/* We don't care bootpreply->bp_file, it must be 'pxegrub':-) */
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 */
/* We don't care bootpreply->bp_file. It must be 'pxegrub' */
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;
if(!grub_eth_probe())
return 0;
network_ready = 0;
memset(&ip, 0, sizeof(struct dhcpip_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);
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(struct bootpip_t), &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(struct bootpip_t), &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;
}
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 (! grub_eth_probe ())
grub_printf ("No ethernet card found.\n");
else if (! network_ready)
grub_printf ("Not initialized yet.\n");
else {
etherboot_printf ("Address: %@\n", arptable[ARP_CLIENT].ipaddr.s_addr);
etherboot_printf ("Netmask: %@\n", netmask);
etherboot_printf ("Server: %@\n", arptable[ARP_SERVER].ipaddr.s_addr);
etherboot_printf ("Gateway: %@\n", arptable[ARP_GATEWAY].ipaddr.s_addr);
}
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. */
eth_disable ();
network_ready = 0;
}
}