/************************************************************************** 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(16); 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; arpentryverhdrlen = 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); }