/* * GRUB -- GRand Unified Bootloader * Copyright (C) 2000,2001,2002 Free Software Foundation, Inc. * * 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 * (at your option) 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. */ /* Based on "src/main.c" in etherboot-5.0.5. */ /************************************************************************** ETHERBOOT - BOOTP/TFTP Bootstrap Program Author: Martin Renters Date: Dec/93 Literature dealing with the network protocols: ARP - RFC826 RARP - RFC903 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) **************************************************************************/ #define GRUB 1 #include #include /* #define DEBUG 1 */ struct arptable_t arptable[MAX_ARP]; /* Set if the user pushes Control-C. */ int ip_abort = 0; /* Set if an ethernet card is probed and IP addresses are set. */ int network_ready = 0; struct rom_info rom; static int vendorext_isvalid; static unsigned long netmask; static struct bootpd_t bootp_data; static unsigned long xid; static unsigned char *end_of_rfc1533 = NULL; #ifndef NO_DHCP_SUPPORT #endif /* NO_DHCP_SUPPORT */ /* äEth */ static unsigned char vendorext_magic[] = {0xE4, 0x45, 0x74, 0x68}; static const unsigned char broadcast[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; #ifdef NO_DHCP_SUPPORT static unsigned char rfc1533_cookie[5] = {RFC1533_COOKIE, RFC1533_END}; #else /* ! NO_DHCP_SUPPORT */ static int dhcp_reply; static in_addr dhcp_server = {0L}; static in_addr dhcp_addr = {0L}; static unsigned char rfc1533_cookie[] = {RFC1533_COOKIE}; static unsigned char rfc1533_end[] = {RFC1533_END}; 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, RFC2132_PARAM_LIST, 4, RFC1533_NETMASK, RFC1533_GATEWAY, RFC1533_HOSTNAME, RFC1533_EXTENSIONPATH }; 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, /* request parameters */ 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, }; #endif /* ! NO_DHCP_SUPPORT */ static unsigned short ipchksum (unsigned short *ip, int len); static unsigned short udpchksum (struct iphdr *packet); void print_network_configuration (void) { if (! 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); } } /************************************************************************** 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)); } /* 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; } return 1; } /************************************************************************** UDP_TRANSMIT - Send a UDP datagram **************************************************************************/ int udp_transmit (unsigned long destip, unsigned int srcsock, unsigned int destsock, int len, const void *buf) { struct iphdr *ip; struct udphdr *udp; struct arprequest arpreq; int arpentry, i; int retry; ip = (struct iphdr *) buf; udp = (struct udphdr *) ((unsigned long) buf + sizeof (struct iphdr)); ip->verhdrlen = 0x45; ip->service = 0; ip->len = htons (len); ip->ident = 0; ip->frags = 0; ip->ttl = 60; ip->protocol = IP_UDP; ip->chksum = 0; ip->src.s_addr = arptable[ARP_CLIENT].ipaddr.s_addr; ip->dest.s_addr = destip; ip->chksum = ipchksum ((unsigned short *) buf, sizeof (struct iphdr)); udp->src = htons (srcsock); udp->dest = htons (destsock); udp->len = htons (len - sizeof (struct iphdr)); udp->chksum = 0; udp->chksum = htons (udpchksum (ip)); if (udp->chksum == 0) udp->chksum = 0xffff; if (destip == IP_BROADCAST) { eth_transmit (broadcast, IP, len, buf); } 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) { etherboot_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. */ #ifdef DEBUG grub_printf ("arp request.\n"); #endif arpreq.hwtype = htons (1); arpreq.protocol = htons (IP); arpreq.hwlen = ETH_ALEN; arpreq.protolen = 4; arpreq.opcode = htons (ARP_REQUEST); grub_memmove (arpreq.shwaddr, arptable[ARP_CLIENT].node, ETH_ALEN); grub_memmove (arpreq.sipaddr, (char *) &arptable[ARP_CLIENT].ipaddr, sizeof (in_addr)); grub_memset (arpreq.thwaddr, 0, ETH_ALEN); grub_memmove (arpreq.tipaddr, (char *) &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; if (ip_abort) return 0; } return 0; } xmit: eth_transmit (arptable[arpentry].node, IP, len, buf); } return 1; } /************************************************************************** TFTP - Download extended BOOTP data, or kernel image **************************************************************************/ static int tftp (const char *name, int (*fnc) (unsigned char *, int, int, int)) { int retry = 0; static unsigned short iport = 2000; unsigned short oport = 0; unsigned short len, block = 0, prevblock = 0; int bcounter = 0; struct tftp_t *tr; struct tftpreq_t tp; int rc; int packetsize = TFTP_DEFAULTSIZE_PACKET; /* 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); tp.opcode = htons (TFTP_RRQ); len = (grub_sprintf ((char *) tp.u.rrq, "%s%coctet%cblksize%c%d", name, 0, 0, 0, TFTP_MAX_PACKET) + sizeof (tp.ip) + sizeof (tp.udp) + sizeof (tp.opcode) + 1); if (! udp_transmit (arptable[ARP_SERVER].ipaddr.s_addr, ++iport, TFTP_PORT, len, &tp)) return 0; for (;;) { long timeout; #ifdef CONGESTED timeout = rfc2131_sleep_interval (block ? TFTP_REXMT : TIMEOUT, retry); #else timeout = rfc2131_sleep_interval (TIMEOUT, retry); #endif if (! await_reply (AWAIT_TFTP, iport, NULL, timeout)) { if (! block && retry++ < MAX_TFTP_RETRIES) { /* Maybe initial request was lost. */ if (! udp_transmit (arptable[ARP_SERVER].ipaddr.s_addr, ++iport, TFTP_PORT, len, &tp)) return 0; continue; } #ifdef CONGESTED if (block && ((retry += TFTP_REXMT) < TFTP_TIMEOUT)) { /* We resend our last ack. */ #ifdef MDEBUG grub_printf ("\n"); #endif udp_transmit (arptable[ARP_SERVER].ipaddr.s_addr, iport, oport, TFTP_MIN_PACKET, &tp); continue; } #endif /* Timeout. */ break; } tr = (struct tftp_t *) &nic.packet[ETH_HLEN]; if (tr->opcode == ntohs (TFTP_ERROR)) { grub_printf ("TFTP error %d (%s)\n", ntohs (tr->u.err.errcode), tr->u.err.errmsg); break; } if (tr->opcode == ntohs (TFTP_OACK)) { char *p = tr->u.oack.data, *e; /* Shouldn't happen. */ if (prevblock) /* Ignore it. */ continue; len = ntohs (tr->udp.len) - sizeof (struct udphdr) - 2; if (len > TFTP_MAX_PACKET) goto noak; e = p + len; while (*p != '\000' && p < e) { if (! grub_strcmp ("blksize", p)) { p += 8; if ((packetsize = getdec (&p)) < TFTP_DEFAULTSIZE_PACKET) goto noak; while (p < e && *p) p++; if (p < e) p++; } else { noak: tp.opcode = htons (TFTP_ERROR); tp.u.err.errcode = 8; len = (grub_sprintf ((char *) tp.u.err.errmsg, "RFC1782 error") + sizeof (tp.ip) + sizeof (tp.udp) + sizeof (tp.opcode) + sizeof (tp.u.err.errcode) + 1); udp_transmit (arptable[ARP_SERVER].ipaddr.s_addr, iport, ntohs (tr->udp.src), len, &tp); return 0; } } if (p > e) goto noak; /* This ensures that the packet does not get processed as data! */ block = tp.u.ack.block = 0; } else if (tr->opcode == ntohs (TFTP_DATA)) { len = ntohs (tr->udp.len) - sizeof (struct udphdr) - 4; /* Shouldn't happen. */ if (len > packetsize) /* Ignore it. */ continue; block = ntohs (tp.u.ack.block = tr->u.data.block); } else /* Neither TFTP_OACK nor TFTP_DATA. */ break; if ((block || bcounter) && (block != prevblock + 1)) /* Block order should be continuous */ tp.u.ack.block = htons (block = prevblock); /* Should be continuous. */ tp.opcode = htons (TFTP_ACK); oport = ntohs (tr->udp.src); /* Ack. */ udp_transmit (arptable[ARP_SERVER].ipaddr.s_addr, iport, oport, TFTP_MIN_PACKET, &tp); if ((unsigned short) (block - prevblock) != 1) /* Retransmission or OACK, don't process via callback * and don't change the value of prevblock. */ continue; prevblock = block; /* Is it the right place to zero the timer? */ retry = 0; if ((rc = fnc (tr->u.data.download, ++bcounter, len, len < packetsize)) >= 0) return rc; /* End of data. */ if (len < packetsize) return 1; } return 0; } /************************************************************************** RARP - Get my IP address and load information **************************************************************************/ int rarp (void) { int retry; /* arp and rarp requests share the same packet structure. */ struct arprequest rarpreq; /* Make sure that an ethernet is probed. */ if (! eth_probe ()) return 0; /* Clear the ready flag. */ network_ready = 0; grub_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); grub_memmove ((char *) &rarpreq.shwaddr, arptable[ARP_CLIENT].node, ETH_ALEN); /* sipaddr is already zeroed out */ grub_memmove ((char *) &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 (ip_abort) return 0; } if (retry < MAX_ARP_RETRIES) { network_ready = 1; return 1; } return 0; } /************************************************************************** BOOTP - Get my IP address and load information **************************************************************************/ int bootp (void) { int retry; #ifndef NO_DHCP_SUPPORT int reqretry; #endif /* ! NO_DHCP_SUPPORT */ struct bootpip_t ip; unsigned long starttime; /* Make sure that an ethernet is probed. */ if (! eth_probe ()) return 0; /* Clear the ready flag. */ network_ready = 0; #ifdef DEBUG grub_printf ("network is ready.\n"); #endif grub_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 */ grub_memmove (&xid, &arptable[ARP_CLIENT].node[2], sizeof(xid)); ip.bp.bp_xid = xid += htonl (starttime); grub_memmove (ip.bp.bp_hwaddr, arptable[ARP_CLIENT].node, ETH_ALEN); #ifdef DEBUG etherboot_printf ("bp_op = %d\n", ip.bp.bp_op); etherboot_printf ("bp_htype = %d\n", ip.bp.bp_htype); etherboot_printf ("bp_hlen = %d\n", ip.bp.bp_hlen); etherboot_printf ("bp_xid = %d\n", ip.bp.bp_xid); etherboot_printf ("bp_hwaddr = %!\n", ip.bp.bp_hwaddr); etherboot_printf ("bp_hops = %d\n", (int) ip.bp.bp_hops); etherboot_printf ("bp_secs = %d\n", (int) ip.bp.bp_hwaddr); #endif #ifdef NO_DHCP_SUPPORT /* Request RFC-style options. */ grub_memmove (ip.bp.bp_vend, rfc1533_cookie, 5); #else /* Request RFC-style options. */ grub_memmove (ip.bp.bp_vend, rfc1533_cookie, sizeof rfc1533_cookie); grub_memmove (ip.bp.bp_vend + sizeof rfc1533_cookie, dhcpdiscover, sizeof dhcpdiscover); grub_memmove (ip.bp.bp_vend + sizeof rfc1533_cookie + sizeof dhcpdiscover, rfc1533_end, sizeof rfc1533_end); #endif /* ! NO_DHCP_SUPPORT */ for (retry = 0; retry < MAX_BOOTP_RETRIES;) { long timeout; #ifdef DEBUG grub_printf ("retry = %d\n", retry); #endif /* 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); udp_transmit (IP_BROADCAST, BOOTP_CLIENT, BOOTP_SERVER, sizeof (struct bootpip_t), &ip); timeout = rfc2131_sleep_interval (TIMEOUT, retry++); #ifdef NO_DHCP_SUPPORT if (await_reply (AWAIT_BOOTP, 0, NULL, timeout)) { network_ready = 1; return 1; } #else /* ! NO_DHCP_SUPPORT */ if (await_reply (AWAIT_BOOTP, 0, NULL, timeout)) { if (dhcp_reply != DHCPOFFER) { network_ready = 1; return 1; } dhcp_reply = 0; #ifdef DEBUG etherboot_printf ("bp_op = %d\n", (int) ip.bp.bp_op); etherboot_printf ("bp_htype = %d\n", (int) ip.bp.bp_htype); etherboot_printf ("bp_hlen = %d\n", (int) ip.bp.bp_hlen); etherboot_printf ("bp_xid = %d\n", (int) ip.bp.bp_xid); etherboot_printf ("bp_hwaddr = %!\n", ip.bp.bp_hwaddr); etherboot_printf ("bp_hops = %d\n", (int) ip.bp.bp_hops); etherboot_printf ("bp_secs = %d\n", (int) ip.bp.bp_hwaddr); #endif grub_memmove (ip.bp.bp_vend, rfc1533_cookie, sizeof rfc1533_cookie); grub_memmove (ip.bp.bp_vend + sizeof rfc1533_cookie, dhcprequest, sizeof dhcprequest); grub_memmove (ip.bp.bp_vend + sizeof rfc1533_cookie + sizeof dhcprequest, rfc1533_end, sizeof rfc1533_end); grub_memmove (ip.bp.bp_vend + 9, (char *) &dhcp_server, sizeof (in_addr)); grub_memmove (ip.bp.bp_vend + 15, (char *) &dhcp_addr, sizeof (in_addr)); #ifdef DEBUG grub_printf ("errnum = %d\n", errnum); #endif for (reqretry = 0; reqretry < MAX_BOOTP_RETRIES;) { int ret; #ifdef DEBUG grub_printf ("reqretry = %d\n", reqretry); #endif ret = udp_transmit (IP_BROADCAST, BOOTP_CLIENT, BOOTP_SERVER, sizeof (struct bootpip_t), &ip); if (! ret) grub_printf ("udp_transmit failed.\n"); dhcp_reply = 0; timeout = rfc2131_sleep_interval (TIMEOUT, reqretry++); if (await_reply (AWAIT_BOOTP, 0, NULL, timeout)) if (dhcp_reply == DHCPACK) { network_ready = 1; return 1; } #ifdef DEBUG grub_printf ("dhcp_reply = %d\n", dhcp_reply); #endif if (ip_abort) return 0; } } #endif /* ! NO_DHCP_SUPPORT */ if (ip_abort) return 0; ip.bp.bp_secs = htons ((currticks () - starttime) / TICKS_PER_SEC); } /* Timeout. */ return 0; } /************************************************************************** UDPCHKSUM - Checksum UDP Packet (one of the rare cases when assembly is actually simpler...) RETURNS: checksum, 0 on checksum error. This allows for using the same routine for RX and TX summing: RX if (packet->udp.chksum && udpchksum(packet)) error("checksum error"); TX packet->udp.chksum=0; if (0==(packet->udp.chksum=udpchksum(packet))) packet->upd.chksum=0xffff; **************************************************************************/ static inline void dosum (unsigned short *start, unsigned int len, unsigned short *sum) { __asm__ __volatile__ ("clc\n" "1:\tlodsw\n\t" "xchg %%al,%%ah\n\t" /* convert to host byte order */ "adcw %%ax,%0\n\t" /* add carry of previous iteration */ "loop 1b\n\t" "adcw $0,%0" /* add carry of last iteration */ : "=b" (*sum), "=S"(start), "=c"(len) : "0"(*sum), "1"(start), "2"(len) : "ax", "cc" ); } /* UDP sum: * proto, src_ip, dst_ip, udp_dport, udp_sport, 2*udp_len, payload */ static unsigned short udpchksum (struct iphdr *packet) { int len = ntohs (packet->len); unsigned short rval; /* add udplength + protocol number */ rval = (len - sizeof (struct iphdr)) + IP_UDP; /* pad to an even number of bytes */ if (len % 2) { ((char *) packet)[len++] = 0; } /* sum over src/dst ipaddr + udp packet */ len -= (char *) &packet->src - (char *) packet; dosum ((unsigned short *) &packet->src, len >> 1, &rval); /* take one's complement */ return ~rval; } /************************************************************************** AWAIT_REPLY - Wait until we get a response for our request **************************************************************************/ int await_reply (int type, int ival, void *ptr, int timeout) { unsigned long time; struct iphdr *ip; struct udphdr *udp; struct arprequest *arpreply; struct bootp_t *bootpreply; unsigned short ptype; unsigned int protohdrlen = (ETH_HLEN + sizeof (struct iphdr) + sizeof (struct udphdr)); /* Clear the abort flag. */ ip_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 (;;) { if (eth_poll ()) { /* We have something! */ /* Check for ARP - No IP hdr. */ if (nic.packetlen >= ETH_HLEN) { ptype = (((unsigned short) nic.packet[12]) << 8 | ((unsigned short) nic.packet[13])); } else /* What else could we do with it? */ continue; if (nic.packetlen >= ETH_HLEN + sizeof (struct arprequest) && ptype == ARP) { unsigned long tmp; arpreply = (struct arprequest *) &nic.packet[ETH_HLEN]; if (arpreply->opcode == htons (ARP_REPLY) && ! grub_memcmp (arpreply->sipaddr, ptr, sizeof (in_addr)) && type == AWAIT_ARP) { grub_memmove ((char *) arptable[ival].node, arpreply->shwaddr, ETH_ALEN); return 1; } grub_memmove ((char *) &tmp, arpreply->tipaddr, sizeof (in_addr)); if (arpreply->opcode == htons (ARP_REQUEST) && tmp == arptable[ARP_CLIENT].ipaddr.s_addr) { arpreply->opcode = htons (ARP_REPLY); grub_memmove (arpreply->tipaddr, arpreply->sipaddr, sizeof (in_addr)); grub_memmove (arpreply->thwaddr, (char *) arpreply->shwaddr, ETH_ALEN); grub_memmove (arpreply->sipaddr, (char *) &arptable[ARP_CLIENT].ipaddr, sizeof (in_addr)); grub_memmove (arpreply->shwaddr, arptable[ARP_CLIENT].node, ETH_ALEN); eth_transmit (arpreply->thwaddr, ARP, sizeof (struct arprequest), arpreply); #ifdef MDEBUG grub_memmove (&tmp, arpreply->tipaddr, sizeof (in_addr)); etherboot_printf ("Sent ARP reply to: %@\n", tmp); #endif /* MDEBUG */ } continue; } if (type == AWAIT_QDRAIN) continue; /* Check for RARP - No IP hdr. */ if (type == AWAIT_RARP && nic.packetlen >= ETH_HLEN + sizeof (struct arprequest) && ptype == RARP) { arpreply = (struct arprequest *) &nic.packet[ETH_HLEN]; if (arpreply->opcode == htons (RARP_REPLY) && ! grub_memcmp (arpreply->thwaddr, ptr, ETH_ALEN)) { grub_memmove ((char *) arptable[ARP_SERVER].node, arpreply->shwaddr, ETH_ALEN); grub_memmove ((char *) &arptable[ARP_SERVER].ipaddr, arpreply->sipaddr, sizeof (in_addr)); grub_memmove ((char *) &arptable[ARP_CLIENT].ipaddr, arpreply->tipaddr, sizeof (in_addr)); return 1; } continue; } /* Anything else has IP header. */ if (nic.packetlen < protohdrlen || ptype != IP) continue; ip = (struct iphdr *) &nic.packet[ETH_HLEN]; if (ip->verhdrlen != 0x45 || ipchksum ((unsigned short *) ip, sizeof (struct iphdr)) || ip->protocol != IP_UDP) continue; /* - Till Straumann added udp checksum (safer on a wireless link) added fragmentation check: I had a corrupted image in memory due to fragmented TFTP packets - took me 3 days to find the cause for this :-( */ /* If More Fragments bit and Fragment Offset field are non-zero then packet is fragmented */ if (ip->frags & htons(0x3FFF)) { grub_printf ("ALERT: got a fragmented packet - reconfigure your server\n"); continue; } udp = (struct udphdr *) &nic.packet[(ETH_HLEN + sizeof (struct iphdr))]; if (udp->chksum && udpchksum (ip)) { grub_printf ("UDP checksum error\n"); continue; } /* BOOTP ? */ bootpreply = (struct bootp_t *) &nic.packet[(ETH_HLEN + sizeof (struct iphdr) + sizeof (struct udphdr))]; if (type == AWAIT_BOOTP #ifdef NO_DHCP_SUPPORT && (nic.packetlen >= (ETH_HLEN + sizeof (struct bootp_t) - BOOTP_VENDOR_LEN)) #else && (nic.packetlen >= (ETH_HLEN + sizeof (struct bootp_t) - DHCP_OPT_LEN)) #endif /* ! NO_DHCP_SUPPORT */ && udp->dest == htons (BOOTP_CLIENT) && bootpreply->bp_op == BOOTP_REPLY && bootpreply->bp_xid == xid && (! grub_memcmp (broadcast, bootpreply->bp_hwaddr, ETH_ALEN) || ! grub_memcmp (arptable[ARP_CLIENT].node, bootpreply->bp_hwaddr, ETH_ALEN))) { #ifdef DEBUG grub_printf ("BOOTP packet was received.\n"); #endif arptable[ARP_CLIENT].ipaddr.s_addr = bootpreply->bp_yiaddr.s_addr; #ifndef NO_DHCP_SUPPORT dhcp_addr.s_addr = bootpreply->bp_yiaddr.s_addr; #ifdef DEBUG etherboot_printf ("dhcp_addr = %@\n", dhcp_addr.s_addr); #endif #endif /* ! NO_DHCP_SUPPORT */ netmask = default_netmask (); arptable[ARP_SERVER].ipaddr.s_addr = bootpreply->bp_siaddr.s_addr; /* Kill arp. */ grub_memset (arptable[ARP_SERVER].node, 0, ETH_ALEN); arptable[ARP_GATEWAY].ipaddr.s_addr = bootpreply->bp_giaddr.s_addr; /* Kill arp. */ grub_memset (arptable[ARP_GATEWAY].node, 0, ETH_ALEN); grub_memmove ((char *) BOOTP_DATA_ADDR, (char *) bootpreply, sizeof (struct bootpd_t)); #ifdef NO_DHCP_SUPPORT decode_rfc1533 (BOOTP_DATA_ADDR->bootp_reply.bp_vend, 0, BOOTP_VENDOR_LEN + MAX_BOOTP_EXTLEN, 1); #else decode_rfc1533 (BOOTP_DATA_ADDR->bootp_reply.bp_vend, 0, DHCP_OPT_LEN + MAX_BOOTP_EXTLEN, 1); #endif /* ! NO_DHCP_SUPPORT */ return 1; } /* TFTP ? */ if (type == AWAIT_TFTP && ntohs (udp->dest) == ival) return 1; } else { /* 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. */ if (checkkey () != -1 && ASCII_CHAR (getkey ()) == CTRL_C) { ip_abort = 1; return 0; } /* Do the timeout after at least a full queue walk. */ if ((timeout == 0) || (currticks() > time)) { break; } } } return 0; } /************************************************************************** DECODE_RFC1533 - Decodes RFC1533 header **************************************************************************/ int decode_rfc1533 (unsigned char *p, int block, 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; vendorext_isvalid = 0; if (grub_memcmp (p, rfc1533_cookie, 4)) /* no RFC 1533 header found */ return 0; p += 4; endp = p + len; } else { if (block == 1) { if (grub_memcmp (p, rfc1533_cookie, 4)) /* no RFC 1533 header found */ return 0; p += 4; len -= 4; } if (extend + len <= ((unsigned char *) &(BOOTP_DATA_ADDR->bootp_extension[MAX_BOOTP_EXTLEN]))) { grub_memmove (extend, p, len); extend += len; } else { grub_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) { grub_memmove ((char *) &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)) grub_memmove ((char *) &arptable[ARP_GATEWAY].ipaddr, p + 2, sizeof (in_addr)); } else if (c == RFC1533_EXTENSIONPATH) extpath = p; #ifndef NO_DHCP_SUPPORT else if (c == RFC2132_MSG_TYPE) { dhcp_reply = *(p + 2); } else if (c == RFC2132_SRV_ID) { grub_memmove ((char *) &dhcp_server, p + 2, sizeof (in_addr)); #ifdef DEBUG etherboot_printf ("dhcp_server = %@\n", dhcp_server.s_addr); #endif } #endif /* ! NO_DHCP_SUPPORT */ else if (c == RFC1533_VENDOR_MAGIC && TAG_LEN(p) >= 6 && ! grub_memcmp (p + 2, vendorext_magic, 4) && p[6] == RFC1533_VENDOR_MAJOR) vendorext_isvalid++; /* GRUB now handles its own tag. Get the name of a configuration file from the network. Cool... */ 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. */ grub_memmove (config_file, p + 2, l); config_file[l] = 0; } p += TAG_LEN (p) + 2; } extdata = extend = endp; /* Perhaps we can eliminate this because we doesn't require so much information, but I leave this alone. */ if (block == 0 && extpath != NULL) { char fname[64]; int fnamelen = TAG_LEN (extpath); while (*(extpath + 2 + fnamelen - 1) == '\000' && fnamelen > 0) fnamelen--; if (fnamelen + 1 > sizeof (fname)) { grub_printf ("Too long file name for Extensions Path\n"); return 0; } else if (! fnamelen) { grub_printf ("Empty file name for Extensions Path\n"); return 0; } grub_memmove (fname, extpath + 2, fnamelen); fname[fnamelen] = '\000'; grub_printf ("Loading BOOTP-extension file: %s\n", fname); tftp (fname, decode_rfc1533); } /* Proceed with next block. */ return -1; } /************************************************************************** IPCHKSUM - Checksum IP Header **************************************************************************/ static unsigned short ipchksum (unsigned short *ip, int len) { unsigned long sum = 0; len >>= 1; while (len--) { sum += *(ip++); if (sum > 0xFFFF) sum -= 0xFFFF; } return (~sum) & 0x0000FFFF; } #define TWO_SECOND_DIVISOR (2147483647l/TICKS_PER_SEC) /************************************************************************** RFC2131_SLEEP_INTERVAL - sleep for expotentially longer times **************************************************************************/ long rfc2131_sleep_interval (int base, int exp) { static long seed = 0; long q; unsigned long tmo; #ifdef BACKOFF_LIMIT if (exp > BACKOFF_LIMIT) exp = BACKOFF_LIMIT; #endif if (!seed) /* Initialize linear congruential generator */ seed = (currticks () + *((long *) &arptable[ARP_CLIENT].node) + ((short *) arptable[ARP_CLIENT].node)[2]); /* simplified version of the LCG given in Bruce Schneier's "Applied Cryptography" */ q = seed / 53668; if ((seed = 40014 * (seed - 53668 * q) - 12211 *q ) < 0) seed += 2147483563L; tmo = (base << exp) + (TICKS_PER_SEC - (seed / TWO_SECOND_DIVISOR)); return tmo; } /************************************************************************** CLEANUP - shut down networking **************************************************************************/ void cleanup_net (void) { if (network_ready) { /* Stop receiving packets. */ eth_disable (); network_ready = 0; } }