/* * Guillaume Cottenceau (gc@mandrakesoft.com) * * Copyright 2000 MandrakeSoft * * This software may be freely redistributed under the terms of the GNU * public license. * * 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. * */ /* * Portions from Erik Troan (ewt@redhat.com) * * Copyright 1996 Red Hat Software * */ /* * Portions from GRUB -- GRand Unified Bootloader * Copyright (C) 2000 Free Software Foundation, Inc. */ #include #include #include #include // #include #include #include #include #include #include #include #include #include #include #include #include #include #include "stage1.h" #include "log.h" #include "network.h" #include "frontend.h" #include "dhcp.h" typedef int bp_int32; typedef short bp_int16; #define BOOTP_OPTION_NETMASK 1 #define BOOTP_OPTION_GATEWAY 3 #define BOOTP_OPTION_DNS 6 #define BOOTP_OPTION_HOSTNAME 12 #define BOOTP_OPTION_DOMAIN 15 #define BOOTP_OPTION_ROOTPATH 17 #define BOOTP_OPTION_BROADCAST 28 #define DHCP_OPTION_REQADDR 50 #define DHCP_OPTION_LEASE 51 #define DHCP_OPTION_TYPE 53 #define DHCP_OPTION_SERVER 54 #define DHCP_OPTION_OPTIONREQ 55 #define DHCP_OPTION_MAXSIZE 57 #define BOOTP_CLIENT_PORT 68 #define BOOTP_SERVER_PORT 67 #define BOOTP_OPCODE_REQUEST 1 #define BOOTP_OPCODE_REPLY 2 #define DHCP_TYPE_DISCOVER 1 #define DHCP_TYPE_OFFER 2 #define DHCP_TYPE_REQUEST 3 #define DHCP_TYPE_ACK 5 #define DHCP_TYPE_RELEASE 7 #define BOOTP_VENDOR_LENGTH 64 #define DHCP_VENDOR_LENGTH 340 struct bootp_request { char opcode; char hw; char hwlength; char hopcount; bp_int32 id; bp_int16 secs; bp_int16 flags; bp_int32 ciaddr, yiaddr, server_ip, bootp_gw_ip; char hwaddr[16]; char servername[64]; char bootfile[128]; unsigned char vendor[DHCP_VENDOR_LENGTH]; } ; static const char vendor_cookie[] = { 99, 130, 83, 99, 255 }; static unsigned int verify_checksum(void * buf2, int length2) { unsigned int csum = 0; unsigned short * sp; for (sp = (unsigned short *) buf2; length2 > 0; (length2 -= 2), sp++) csum += *sp; while (csum >> 16) csum = (csum & 0xffff) + (csum >> 16); return (csum == 0xffff); } static int initial_setup_interface(char * device, int s) { struct sockaddr_in * addrp; struct ifreq req; struct rtentry route; int true = 1; addrp = (struct sockaddr_in *) &req.ifr_addr; strcpy(req.ifr_name, device); addrp->sin_family = AF_INET; addrp->sin_port = 0; memset(&addrp->sin_addr, 0, sizeof(addrp->sin_addr)); req.ifr_flags = 0; /* take it down */ if (ioctl(s, SIOCSIFFLAGS, &req)) { log_perror("SIOCSIFFLAGS (downing)"); return -1; } addrp->sin_family = AF_INET; addrp->sin_addr.s_addr = htonl(0); if (ioctl(s, SIOCSIFADDR, &req)) { log_perror("SIOCSIFADDR"); return -1; } req.ifr_flags = IFF_UP | IFF_BROADCAST | IFF_RUNNING; if (ioctl(s, SIOCSIFFLAGS, &req)) { log_perror("SIOCSIFFLAGS (upping)"); return -1; } memset(&route, 0, sizeof(route)); memcpy(&route.rt_gateway, addrp, sizeof(*addrp)); addrp->sin_family = AF_INET; addrp->sin_port = 0; addrp->sin_addr.s_addr = INADDR_ANY; memcpy(&route.rt_dst, addrp, sizeof(*addrp)); memcpy(&route.rt_genmask, addrp, sizeof(*addrp)); route.rt_dev = device; route.rt_flags = RTF_UP; route.rt_metric = 0; if (ioctl(s, SIOCADDRT, &route)) { if (errno != EEXIST) { close(s); log_perror("SIOCADDRT"); return -1; } } if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &true, sizeof(true))) { close(s); log_perror("setsockopt"); return -1; } /* I need to sleep a bit in order for kernel to finish init of the network device; this would allow to not send further multiple dhcp requests when only one is needed. */ wait_message("Bringing up networking..."); sleep(2); remove_wait_message(); return 0; } void set_missing_ip_info(struct interface_info * intf) { bp_int32 ipNum = *((bp_int32 *) &intf->ip); bp_int32 nmNum; if (intf->netmask.s_addr == 0) inet_aton(guess_netmask(inet_ntoa(intf->ip)), &intf->netmask); nmNum = *((bp_int32 *) &intf->netmask); if (intf->broadcast.s_addr == 0) *((bp_int32 *) &intf->broadcast) = (ipNum & nmNum) | ~(nmNum); if (intf->network.s_addr == 0) *((bp_int32 *) &intf->network) = ipNum & nmNum; } static void parse_reply(struct bootp_request * breq, struct interface_info * intf) { unsigned char * chptr; unsigned char option, length; memcpy(&intf->ip, &breq->yiaddr, 4); memcpy(&next_server, &breq->server_ip, 4); log_message("got ip: %s", inet_ntoa(intf->ip)); log_message("got next-server: %s", inet_ntoa(next_server)); chptr = breq->vendor; chptr += 4; while (*chptr != 0xFF && (void *) chptr < (void *) breq->vendor + DHCP_VENDOR_LENGTH) { char tmp_str[500]; option = *chptr++; if (!option) continue; length = *chptr++; switch (option) { case BOOTP_OPTION_DNS: memcpy(&dns_server, chptr, sizeof(dns_server)); log_message("got dns %s", inet_ntoa(dns_server)); if (length >= sizeof(dns_server)*2) { memcpy(&dns_server2, chptr+sizeof(dns_server), sizeof(dns_server2)); log_message("got dns2 %s", inet_ntoa(dns_server2)); } break; case BOOTP_OPTION_NETMASK: memcpy(&intf->netmask, chptr, sizeof(intf->netmask)); log_message("got netmask %s", inet_ntoa(intf->netmask)); break; case BOOTP_OPTION_DOMAIN: memcpy(tmp_str, chptr, length); tmp_str[length] = '\0'; domain = strdup(tmp_str); log_message("got domain %s", domain); break; case BOOTP_OPTION_BROADCAST: memcpy(&intf->broadcast, chptr, sizeof(intf->broadcast)); log_message("got broadcast %s", inet_ntoa(intf->broadcast)); break; case BOOTP_OPTION_GATEWAY: memcpy(&gateway, chptr, sizeof(gateway)); log_message("got gateway %s", inet_ntoa(gateway)); break; case BOOTP_OPTION_HOSTNAME: memcpy(tmp_str, chptr, length); tmp_str[length] = '\0'; hostname = strdup(tmp_str); log_message("got hostname %s", hostname); break; case BOOTP_OPTION_ROOTPATH: memcpy(tmp_str, chptr, length); tmp_str[length] = '\0'; rootpath = strdup(tmp_str); log_message("got rootpath: %s", rootpath); break; } chptr += length; } set_missing_ip_info(intf); } static void init_vendor_codes(struct bootp_request * breq) { memcpy(breq->vendor, vendor_cookie, sizeof(vendor_cookie)); } static char gen_hwaddr[16]; static int prepare_request(struct bootp_request * breq, int sock, char * device) { struct ifreq req; memset(breq, 0, sizeof(*breq)); breq->opcode = BOOTP_OPCODE_REQUEST; strcpy(req.ifr_name, device); if (ioctl(sock, SIOCGIFHWADDR, &req)) { log_perror("SIOCSIFHWADDR"); return -1; } breq->hw = 1; /* ethernet */ breq->hwlength = IFHWADDRLEN; memcpy(breq->hwaddr, req.ifr_hwaddr.sa_data, IFHWADDRLEN); memcpy(gen_hwaddr, req.ifr_hwaddr.sa_data, IFHWADDRLEN); breq->hopcount = 0; init_vendor_codes(breq); return 0; } static int get_vendor_code(struct bootp_request * bresp, unsigned char option, void * data) { unsigned char * chptr; unsigned int length, theOption; chptr = bresp->vendor + 4; while (*chptr != 0xFF && *chptr != option) { theOption = *chptr++; if (!theOption) continue; length = *chptr++; chptr += length; } if (*chptr++ == 0xff) return 1; length = *chptr++; memcpy(data, chptr, length); return 0; } static int currticks(void) { struct timeval tv; long csecs; int ticks_per_csec, ticks_per_usec; /* Note: 18.2 ticks/sec. */ gettimeofday (&tv, 0); csecs = tv.tv_sec / 10; ticks_per_csec = csecs * 182; ticks_per_usec = (((tv.tv_sec - csecs * 10) * 1000000 + tv.tv_usec) * 182 / 10000000); return ticks_per_csec + ticks_per_usec; } #define BACKOFF_LIMIT 7 #define TICKS_PER_SEC 18 #define MAX_ARP_RETRIES 4 static void rfc951_sleep(int exp) { static long seed = 0; long q; unsigned long tmo; union { char c[sizeof(gen_hwaddr)]; long l; short s[3]; } hw; if (exp > BACKOFF_LIMIT) exp = BACKOFF_LIMIT; if (!seed) { /* Initialize linear congruential generator. */ memcpy(&hw.c, &gen_hwaddr, sizeof(gen_hwaddr)); seed = currticks() + hw.l + hw.s[2]; } /* Simplified version of the LCG given in Bruce Scheier's "Applied Cryptography". */ q = seed / 53668; if ((seed = 40014 * (seed - 53668 * q) - 12211 * q) < 0) seed += 2147483563l; /* Compute mask. */ for (tmo = 63; tmo <= 60 * TICKS_PER_SEC && --exp > 0; tmo = 2 * tmo + 1) ; /* Sleep. */ log_message(""); for (tmo = (tmo & seed) + currticks (); currticks () < tmo;); } static int handle_transaction(int s, struct bootp_request * breq, struct bootp_request * bresp, struct sockaddr_in * server_addr, int dhcp_type) { struct pollfd polls; int i, j; int retry = 1; int sin; char eth_packet[ETH_FRAME_LEN]; struct iphdr * ip_hdr; struct udphdr * udp_hdr; unsigned char type; unsigned long starttime; int timeout = 1; breq->id = starttime = currticks(); breq->secs = 0; sin = socket(AF_PACKET, SOCK_DGRAM, ntohs(ETH_P_IP)); if (sin < 0) { log_perror("af_packet socket"); return -1; } while (retry <= MAX_ARP_RETRIES) { i = sizeof(*breq); if (sendto(s, breq, i, 0, (struct sockaddr *) server_addr, sizeof(*server_addr)) != i) { close(s); log_perror("sendto"); return -1; } polls.fd = sin; polls.events = POLLIN; while (poll(&polls, 1, timeout*1000) == 1) { if ((j = recv(sin, eth_packet, sizeof(eth_packet), 0)) == -1) { log_perror("recv"); continue; } /* We need to do some basic sanity checking of the header */ if (j < (sizeof(*ip_hdr) + sizeof(*udp_hdr))) continue; ip_hdr = (void *) eth_packet; if (!verify_checksum(ip_hdr, sizeof(*ip_hdr))) continue; if (ntohs(ip_hdr->tot_len) > j) continue; j = ntohs(ip_hdr->tot_len); if (ip_hdr->protocol != IPPROTO_UDP) continue; udp_hdr = (void *) (eth_packet + sizeof(*ip_hdr)); if (ntohs(udp_hdr->source) != BOOTP_SERVER_PORT) continue; if (ntohs(udp_hdr->dest) != BOOTP_CLIENT_PORT) continue; /* Go on with this packet; it looks sane */ /* Originally copied sizeof (*bresp) - this is a security problem due to a potential underflow of the source buffer. Also, it trusted that the packet was properly 0xFF terminated, which is not true in the case of the DHCP server on Cisco 800 series ISDN router. */ memset (bresp, 0xFF, sizeof (*bresp)); memcpy (bresp, (char *) udp_hdr + sizeof (*udp_hdr), j - sizeof (*ip_hdr) - sizeof (*udp_hdr)); /* sanity checks */ if (bresp->id != breq->id) continue; if (bresp->opcode != BOOTP_OPCODE_REPLY) continue; if (bresp->hwlength != breq->hwlength) continue; if (memcmp(bresp->hwaddr, breq->hwaddr, bresp->hwlength)) continue; if (get_vendor_code(bresp, DHCP_OPTION_TYPE, &type) || type != dhcp_type) continue; if (memcmp(bresp->vendor, vendor_cookie, 4)) continue; return 0; } rfc951_sleep(retry); breq->secs = htons ((currticks () - starttime) / 20); retry++; timeout *= 2; if (timeout > 5) timeout = 5; } return -1; } static void add_vendor_code(struct bootp_request * breq, unsigned char option, unsigned char length, void * data) { unsigned char * chptr; int theOption, theLength; chptr = breq->vendor; chptr += 4; while (*chptr != 0xFF && *chptr != option) { theOption = *chptr++; if (!theOption) continue; theLength = *chptr++; chptr += theLength; } *chptr++ = option; *chptr++ = length; memcpy(chptr, data, length); chptr[length] = 0xff; } enum return_type perform_dhcp(struct interface_info * intf) { int s, i; struct sockaddr_in server_addr; struct sockaddr_in client_addr; struct sockaddr_in broadcast_addr; struct bootp_request breq, bresp; unsigned char messageType; unsigned int lease; short aShort; int num_options; char requested_options[50]; /* Now used net_dev_black_list[], see get_net_devices() in probing.c if (strncmp(intf->device, "en", 2) & strncmp(intf->device, "eth", 3)) { stg1_info_message("DHCP available only for Ethernet networking."); return RETURN_ERROR; } */ s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) { log_perror("socket"); return RETURN_ERROR; } if (initial_setup_interface(intf->device, s) != 0) { close(s); return RETURN_ERROR; } if (prepare_request(&breq, s, intf->device) != 0) { close(s); return RETURN_ERROR; } messageType = DHCP_TYPE_DISCOVER; add_vendor_code(&breq, DHCP_OPTION_TYPE, 1, &messageType); memset(&client_addr.sin_addr, 0, sizeof(client_addr.sin_addr)); client_addr.sin_family = AF_INET; client_addr.sin_port = htons(BOOTP_CLIENT_PORT); /* bootp client */ if (bind(s, (struct sockaddr *) &client_addr, sizeof(client_addr))) { log_perror("bind"); return RETURN_ERROR; } broadcast_addr.sin_family = AF_INET; broadcast_addr.sin_port = htons(BOOTP_SERVER_PORT); /* bootp server */ memset(&broadcast_addr.sin_addr, 0xff, sizeof(broadcast_addr.sin_addr)); /* broadcast */ log_message("DHCP: sending DISCOVER"); wait_message("Sending DHCP request..."); i = handle_transaction(s, &breq, &bresp, &broadcast_addr, DHCP_TYPE_OFFER); remove_wait_message(); if (i != 0) { stg1_info_message("No DHCP reply received."); close(s); return RETURN_ERROR; } server_addr.sin_family = AF_INET; server_addr.sin_port = htons(BOOTP_SERVER_PORT); /* bootp server */ if (get_vendor_code(&bresp, DHCP_OPTION_SERVER, &server_addr.sin_addr)) { close(s); log_message("DHCPOFFER didn't include server address"); return RETURN_ERROR; } init_vendor_codes(&breq); messageType = DHCP_TYPE_REQUEST; add_vendor_code(&breq, DHCP_OPTION_TYPE, 1, &messageType); add_vendor_code(&breq, DHCP_OPTION_SERVER, 4, &server_addr.sin_addr); add_vendor_code(&breq, DHCP_OPTION_REQADDR, 4, &bresp.yiaddr); aShort = ntohs(sizeof(struct bootp_request)); add_vendor_code(&breq, DHCP_OPTION_MAXSIZE, 2, &aShort); num_options = 0; requested_options[num_options++] = BOOTP_OPTION_NETMASK; requested_options[num_options++] = BOOTP_OPTION_GATEWAY; requested_options[num_options++] = BOOTP_OPTION_DNS; requested_options[num_options++] = BOOTP_OPTION_DOMAIN; requested_options[num_options++] = BOOTP_OPTION_ROOTPATH; requested_options[num_options++] = BOOTP_OPTION_BROADCAST; add_vendor_code(&breq, DHCP_OPTION_OPTIONREQ, num_options, requested_options); /* request a lease of 1 hour */ i = htonl(60 * 60); add_vendor_code(&breq, DHCP_OPTION_LEASE, 4, &i); log_message("DHCP: sending REQUEST"); i = handle_transaction(s, &breq, &bresp, &broadcast_addr, DHCP_TYPE_ACK); if (i != 0) { close(s); return RETURN_ERROR; } if (get_vendor_code(&bresp, DHCP_OPTION_LEASE, &lease)) { log_message("failed to get lease time\n"); return RETURN_ERROR; } lease = ntohl(lease); close(s); intf->netmask.s_addr = 0; intf->broadcast.s_addr = 0; intf->network.s_addr = 0; parse_reply(&bresp, intf); return RETURN_OK; }