diff options
author | Antoine Ginies <aginies@mandriva.com> | 2011-01-19 10:44:49 +0000 |
---|---|---|
committer | Antoine Ginies <aginies@mandriva.com> | 2011-01-19 10:44:49 +0000 |
commit | 530a16ec071db0e24e6e949e265a96848864967c (patch) | |
tree | fe40cacd28d67b98186754c551b7fd339ebc7e17 /mdk-stage1/dhcp.c | |
download | drakx-backup-do-not-use-530a16ec071db0e24e6e949e265a96848864967c.tar drakx-backup-do-not-use-530a16ec071db0e24e6e949e265a96848864967c.tar.gz drakx-backup-do-not-use-530a16ec071db0e24e6e949e265a96848864967c.tar.bz2 drakx-backup-do-not-use-530a16ec071db0e24e6e949e265a96848864967c.tar.xz drakx-backup-do-not-use-530a16ec071db0e24e6e949e265a96848864967c.zip |
add mes5-2.6.33 branch
Diffstat (limited to 'mdk-stage1/dhcp.c')
-rw-r--r-- | mdk-stage1/dhcp.c | 678 |
1 files changed, 678 insertions, 0 deletions
diff --git a/mdk-stage1/dhcp.c b/mdk-stage1/dhcp.c new file mode 100644 index 000000000..fccb400e1 --- /dev/null +++ b/mdk-stage1/dhcp.c @@ -0,0 +1,678 @@ +/* + * 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 <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <net/if.h> +#include <arpa/inet.h> +#include <net/route.h> +#include <errno.h> +#include <net/ethernet.h> +#include <netinet/ip.h> +#include <netinet/udp.h> +#include <sys/time.h> +#include <time.h> +#include <fcntl.h> +#include <sys/poll.h> + +#include "stage1.h" +#include "log.h" +#include "tools.h" +#include "utils.h" +#include "network.h" +#include "frontend.h" +#include "automatic.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_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 DHCP_OPTION_CLIENT_IDENTIFIER 61 + +#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]; + 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; + + if (breq->bootfile && strlen(breq->bootfile) > 0) { + if (IS_NETAUTO) + add_to_env("KICKSTART", breq->bootfile); + else + log_message("warning: ignoring `bootfile' DHCP server parameter, since `netauto' boot parameter was not given; reboot with `linux netauto' (and anymore useful boot parameters) if you want `bootfile' to be used as a `auto_inst.cfg.pl' stage2 configuration file"); + } + + memcpy(&intf->ip, &breq->yiaddr, 4); + + chptr = (unsigned char *) 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; + + } + + 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 = req.ifr_hwaddr.sa_family; + 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 = (unsigned char*) 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 unsigned long currticks(void) +{ + struct timeval tv; + unsigned long csecs; + unsigned long 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 7 + +static void rfc951_sleep(int exp) +{ + static long seed = 0; + long q; + unsigned long tmo; + + if (exp > BACKOFF_LIMIT) + exp = BACKOFF_LIMIT; + + if (!seed) + /* Initialize linear congruential generator. */ + seed = (currticks () + *(long *) &gen_hwaddr + ((short *) gen_hwaddr)[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("<sleep>"); + + 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 < (signed)(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 = (unsigned char*) 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; +} + + +char * dhcp_hostname = NULL; +char * dhcp_domain = NULL; + +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]; + char * client_id_str, * client_id_hwaddr; + + s = socket(AF_INET, SOCK_DGRAM, 0); + if (s < 0) { + log_perror("socket"); + return RETURN_ERROR; + } + + { + enum return_type results; + char * questions[] = { "Host name", "Domain name", NULL }; + char * questions_auto[] = { "hostname", "domain" }; + static char ** answers = NULL; + char * boulet; + + client_id_str = client_id_hwaddr = NULL; + + results = ask_from_entries_auto("If the DHCP server needs to know you by name; please fill in this information. " + "Valid answers are for example: `mybox' for hostname and `mynetwork.com' for " + "domain name, for a machine called `mybox.mynetwork.com' on the Internet.", + questions, &answers, 32, questions_auto, NULL); + if (results == RETURN_OK) + { + dhcp_hostname = answers[0]; + if ((boulet = strchr(dhcp_hostname, '.')) != NULL) + boulet[0] = '\0'; + dhcp_domain = answers[1]; + + if (*dhcp_hostname && *dhcp_domain) { + /* if we have both, then create client id from them */ + client_id_str = malloc(1 + strlen(dhcp_hostname) + 1 + strlen(dhcp_domain) + 1); + client_id_str[0] = '\0'; + sprintf(client_id_str+1, "%s.%s", dhcp_hostname, dhcp_domain); + } + } + } + + 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); + + /* add pieces needed to have DDNS/DHCP IP selection based on requested name */ + if (dhcp_hostname && *dhcp_hostname) { /* pick client id form based on absence or presence of domain name */ + if (*dhcp_domain) /* alternate style <hostname>.<domainname> */ + add_vendor_code(&breq, DHCP_OPTION_CLIENT_IDENTIFIER, strlen(client_id_str+1)+1, client_id_str); + else { /* usual style (aka windows / dhcpcd) */ + /* but put MAC in form required for client identifier first */ + client_id_hwaddr = malloc(IFHWADDRLEN+2); + /* (from pump-0.8.22/dhcp.c) + * Microsoft uses a client identifier field of the 802.3 address with a + * pre-byte of a "1". In order to re-use the DHCP address that they set + * for this interface, we have to mimic their identifier. + */ + client_id_hwaddr[0] = 1; /* set flag for ethernet */ + memcpy(client_id_hwaddr+1, gen_hwaddr, IFHWADDRLEN); + add_vendor_code(&breq, DHCP_OPTION_CLIENT_IDENTIFIER, IFHWADDRLEN+1, client_id_hwaddr); + } + /* this is the one that the dhcp server really wants for DDNS updates */ + add_vendor_code(&breq, BOOTP_OPTION_HOSTNAME, strlen(dhcp_hostname), dhcp_hostname); + log_message("DHCP: telling server to use name = %s", dhcp_hostname); + } + + 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_error_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); + + /* if used the first time, then have to use it again */ + if (dhcp_hostname && *dhcp_hostname) { /* add pieces needed to have DDNS/DHCP IP selection based on requested name */ + if (dhcp_domain && *dhcp_domain) /* alternate style */ + add_vendor_code(&breq, DHCP_OPTION_CLIENT_IDENTIFIER, strlen(client_id_str+1)+1, client_id_str); + else /* usual style (aka windows / dhcpcd) */ + add_vendor_code(&breq, DHCP_OPTION_CLIENT_IDENTIFIER, IFHWADDRLEN+1, client_id_hwaddr); + /* this is the one that the dhcp server really wants for DDNS updates */ + add_vendor_code(&breq, BOOTP_OPTION_HOSTNAME, strlen(dhcp_hostname), dhcp_hostname); + } + + 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_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; +} |