diff options
Diffstat (limited to 'main/common/dhcpboot.c')
-rw-r--r-- | main/common/dhcpboot.c | 1328 |
1 files changed, 1328 insertions, 0 deletions
diff --git a/main/common/dhcpboot.c b/main/common/dhcpboot.c new file mode 100644 index 0000000..e0ded6a --- /dev/null +++ b/main/common/dhcpboot.c @@ -0,0 +1,1328 @@ +/************************************************************************** + * + * Copyright (c) 2013 Alcatel-Lucent + * + * Alcatel Lucent licenses this file to You under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. A copy of the License is contained the + * file LICENSE at the top level of this repository. + * You may also obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************** + * + * dhcpboot.c: + * + * This code implements a subset of the DHCP client protocol. + * Based on RFC2131 spec, the "automatic allocation" mode, in which DHCP + * assigns a permanent IP address to a client, is the only mode supported. + * + * The idea is that the monitor boots up, and if IPADD is set to DHCP, then + * DHCP is used to populate shell variables with a server-supplied IP + * address, NetMask and Gateway IP address. Then, when the application + * is launched (probably via TFS), it can retrieve the content of those + * shell variables for use by the application. + * + * Sequence of events for this limited implementation of DHCP... + * Client issues a DHCP_DISCOVER, server responds with a DHCP_OFFER, + * client issues a DHCP_REQUEST and server responds with a DHCP_ACK. + * DISCOVER: request by the client to broadcast the fact that it is looking + * for a DHCP server. + * OFFER: reply from the server when it receives a DISCOVER request from + * a client. The offer may contain all the information that the DHCP + * client needs to bootup, but this is dependent on the configuration of + * the server. + * REQUEST: request by the client for the server (now known because an OFFER + * was received) to send it the information it needs. + * ACK: reply from the server with the information requested. + * + * NOTE: this file contains a generic DHCP client supporting "automatic + * allocation mode" (infinite lease time). There are several different + * application-specific enhancements that can be added and hopefully + * they have been isolated through the use of the dhcp_00.c file. + * I've attempted to isolate as much of the non-generic code to + * the file dhcp_XX.c (where dhcp_00.c is the default code). If non-default + * code is necessary, then limit the changes to a new dhcp_XX.c file. This + * will allow the code in this file to stay generic; hence, the user of this + * code will be able to accept monitor upgrades without the need to touch + * this file. The makefile must link in some additional dhcp_XX.c file + * (default is dhcp_00.c). Bottom line... there should be no need to modify + * this file for application-specific stuff; if there is, please let me know. + * + * NOTE1: the shell variable IPADD can also be set to DHCPV or DHCPv to + * enable different levels of verbosity during DHCP transactions... 'V' + * is full DHCP verbosity and 'v' only prints the DhcpSetEnv() calls. + * + * NOTE2: this file supports DHCP and BOOTP. Most of the function names + * refer to DHCP even though their functionality is shared by both DHCP + * and BOOTP. This is because I wrote this originally for DHCP, then added + * the hooks for BOOTP... Bottom line: don't let the names confuse you! + * + * + * Original author: Ed Sutter (ed.sutter@alcatel-lucent.com) + * + */ + +#include "config.h" +#include "endian.h" +#include "cpuio.h" +#include "ether.h" +#include "tfs.h" +#include "tfsprivate.h" +#include "genlib.h" +#include "stddefs.h" +#include "cli.h" +#include "timer.h" + +int DHCPStartup(short), BOOTPStartup(short); +int DhcpSetEnv(char *,char *); +int SendDHCPDiscover(int,short); +void dhcpDumpVsa(void), printDhcpOptions(uchar *); + +unsigned short DHCPState; + +#if INCLUDE_DHCPBOOT + +static struct elapsed_tmr dhcpTmr; +static int DHCPCommandIssued; +static ulong DHCPTransactionId; + +/* Variables used for DHCP Class ID specification: */ +static char *DHCPClassId; +static int DHCPClassIdSize; + +/* Variables used for DHCP Client ID specification: */ +static char DHCPClientId[32]; +static int DHCPClientIdSize, DHCPClientIdType; + +/* Variables used for setting up a DHCP Parameter Request List: */ +static uchar DHCPRequestList[32]; +static int DHCPRqstListSize; + +/* Variable to keep track of elapsed seconds since DHCP started: */ +static short DHCPElapsedSecs; + +char *DhcpHelp[] = { + "Issue a DHCP discover", + "-[brvV] [vsa]", +#if INCLUDE_VERBOSEHELP + "Options...", + " -b use bootp", + " -r retry", + " -v|V verbosity", +#endif + 0, +}; + +int +Dhcp(int argc,char *argv[]) +{ + int opt, bootp; + + bootp = 0; + DHCPCommandIssued = 1; + while ((opt=getopt(argc,argv,"brvV")) != -1) { + switch(opt) { + case 'b': + bootp = 1; + break; + case 'r': + DHCPCommandIssued = 0; + break; +#if INCLUDE_ETHERVERBOSE + case 'v': + EtherVerbose = SHOW_DHCP; + break; + case 'V': + EtherVerbose = DHCP_VERBOSE; + break; +#endif + default: + return(CMD_PARAM_ERROR); + } + } + + if (argc == optind+1) { + if (!strcmp(argv[optind],"vsa")) { + dhcpDumpVsa(); + return(CMD_SUCCESS); + } + else + return(CMD_PARAM_ERROR); + } + else if (argc != optind) + return(CMD_PARAM_ERROR); + + startElapsedTimer(&dhcpTmr,RetransmitDelay(DELAY_INIT_DHCP)*1000); + + if (bootp) { + DHCPState = BOOTPSTATE_INITIALIZE; + if (DHCPCommandIssued) + BOOTPStartup(0); + } + else { + DHCPState = DHCPSTATE_INITIALIZE; + if (DHCPCommandIssued) + DHCPStartup(0); + } + return(CMD_SUCCESS); +} + +/* dhcpDumpVsa(): + * Simply dump the content of the VSA shell variable in DHCP format. + * The variable content is stored in ascii and must be converted to binary + * prior to calling printDhcpOptions(). + */ +void +dhcpDumpVsa(void) +{ + int i; + char tmp[3], *vsa_b, *vsa_a, len; + + vsa_a = getenv("DHCPVSA"); + if ((!vsa_a) || (!strcmp(vsa_a,"TRUE"))) + return; + len = strlen(vsa_a); + vsa_b = malloc(len); + if (!vsa_b) + return; + + len >>= 1; + tmp[2] = 0; + for(i=0;i<len;i++) { + tmp[0] = *vsa_a++; + tmp[1] = *vsa_a++; + vsa_b[i] = (char)strtol(tmp,0,16); + } + /* First 4 bytes of DHCPVSA is the cookie, so skip over that. */ + printDhcpOptions((unsigned char *)vsa_b+4); + free(vsa_b); +} + +void +dhcpDisable() +{ + DHCPState = DHCPSTATE_NOTUSED; +} + +/* DHCPStartup(): + * This function is called at the point in which the ethernet interface is + * started if, and only if, the IPADD shell variable is set to DHCP. + * In older version of DHCP, the default was to use "LUCENT.PPA.1.1" as + * the default vcid. Now it is only used if specified in the shell variable + * DHCPCLASSID. The same strategy applies to DHCPCLIENTID. +*/ +int +DHCPStartup(short seconds) +{ + char *id, *colon, *rlist; + +#if !INCLUDE_TFTP + printf("WARNING: DHCP can't load bootfile, TFTP not built into monitor.\n"); +#endif + + /* The format of DHCPCLASSID is simply a string of characters. */ + id = getenv("DHCPCLASSID"); + if (id) + DHCPClassId = id; + else + DHCPClassId = ""; + DHCPClassIdSize = strlen(DHCPClassId); + + /* The format of DHCPCLIENTID is "TYPE:ClientID" where 'TYPE is a + * decimal number ranging from 1-255 used as the "type" portion of + * the option, and ClientID is a string of ascii-coded hex pairs + * that are converted to binary and used as the client identifier. + */ + id = getenv("DHCPCLIENTID"); + if (id) { + colon = strchr(id,':'); + if ((colon) && (!(strlen(colon+1) & 1))) { + DHCPClientIdType = atoi(id); + colon++; + for(DHCPClientIdSize=0;*colon;DHCPClientIdSize++) { + uchar tmp; + + tmp = colon[2]; + colon[2] = 0; + DHCPClientId[DHCPClientIdSize] = (uchar)strtol(colon,0,16); + colon[2] = tmp; + colon+=2; + } + } + } + else + DHCPClientIdSize = 0; + + /* The format of DHCPRQSTLIST is #:#:#:#:# where each '#' is a decimal + * number representing a parameter to be requested via the Parameter + * Request List option... + */ + rlist = getenv("DHCPRQSTLIST"); + if (rlist) { + DHCPRqstListSize = 0; + colon = rlist; + while(*colon) { + if (*colon++ == ':') + DHCPRqstListSize++; + } + if (DHCPRqstListSize > sizeof(DHCPRequestList)) { + printf("DHCPRQSTLIST too big.\n"); + DHCPRqstListSize = 0; + } + else { + char *rqst; + + DHCPRqstListSize = 0; + rqst = rlist; + while(1) { + DHCPRequestList[DHCPRqstListSize++] = strtol(rqst,&colon,0); + if (*colon != ':') + break; + rqst = colon+1; + } + DHCPRequestList[DHCPRqstListSize] = 0; + } + } + else + DHCPRqstListSize = 0; + + return(SendDHCPDiscover(0,seconds)); +} + +int +BOOTPStartup(short seconds) +{ + return(SendDHCPDiscover(1,seconds)); +} + +uchar * +dhcpLoadShellVarOpts(uchar *options) +{ + if (DHCPClassIdSize) { + *options++ = DHCPOPT_CLASSID; + *options++ = DHCPClassIdSize; + memcpy((char *)options, (char *)DHCPClassId, DHCPClassIdSize); + options += DHCPClassIdSize; + } + if (DHCPClientIdSize) { + *options++ = DHCPOPT_CLIENTID; + *options++ = DHCPClientIdSize+1; + *options++ = DHCPClientIdType; + memcpy((char *)options, (char *)DHCPClientId, DHCPClientIdSize); + options += DHCPClientIdSize; + } + if (DHCPRqstListSize) { + *options++ = DHCPOPT_PARMRQSTLIST; + *options++ = DHCPRqstListSize; + memcpy((char *)options, (char *)DHCPRequestList, DHCPRqstListSize); + options += DHCPRqstListSize; + } + return(options); +} + +/* SendDHCPDiscover() + * The DHCPDISCOVER is issued as an ethernet broadcast. IF the bootp + * flag is non-zero then just do a bootp request (a subset of the + * DHCPDISCOVER stuff). + */ +int +SendDHCPDiscover(int bootp,short seconds) +{ + struct dhcphdr *dhcpdata; + struct bootphdr *bootpdata; + struct ether_header *te; + struct ip *ti; + struct Udphdr *tu; + ushort uh_ulen; + int optlen; + char *dhcpflags; + ulong cookie; + uchar *dhcpOptions, *dhcpOptionsBase; + + /* Retrieve an ethernet buffer from the driver and populate the + * ethernet level of packet: + */ + te = (struct ether_header *) getXmitBuffer(); + memcpy((char *)&te->ether_shost, (char *)BinEnetAddr,6); + memcpy((char *)&te->ether_dhost, (char *)BroadcastAddr,6); + te->ether_type = ecs(ETHERTYPE_IP); + + /* Move to the IP portion of the packet and populate it appropriately: */ + ti = (struct ip *) (te + 1); + ti->ip_vhl = IP_HDR_VER_LEN; + ti->ip_tos = 0; + ti->ip_id = 0; + ti->ip_off = ecs(0x4000); /* No fragmentation allowed */ + ti->ip_ttl = UDP_TTL; + ti->ip_p = IP_UDP; + memset((char *)&ti->ip_src.s_addr,0,4); + memset((char *)&ti->ip_dst.s_addr,0xff,4); + + /* Now udp... */ + tu = (struct Udphdr *) (ti + 1); + tu->uh_sport = ecs(DhcpClientPort); + tu->uh_dport = ecs(DhcpServerPort); + + /* First the stuff that is the same for BOOTP or DHCP... */ + bootpdata = (struct bootphdr *)(tu+1); + dhcpdata = (struct dhcphdr *)(tu+1); + dhcpdata->op = DHCPBOOTP_REQUEST; + dhcpdata->htype = 1; + dhcpdata->hlen = 6; + dhcpdata->hops = 0; + dhcpdata->seconds = ecs(seconds); + memset((char *)dhcpdata->bootfile,0,sizeof(dhcpdata->bootfile)); + memset((char *)dhcpdata->server_hostname,0,sizeof(dhcpdata->server_hostname)); + + /* For the first DHCPDISCOVER issued, establish a transaction id based + * on a crc32 of the mac address. For each DHCPDISCOVER after that, + * just increment. + */ + if (!DHCPTransactionId) + DHCPTransactionId = crc32(BinEnetAddr,6); + else + DHCPTransactionId++; + + memcpy((char *)&dhcpdata->transaction_id,(char *)&DHCPTransactionId,4); + memset((char *)&dhcpdata->client_ip,0,4); + memset((char *)&dhcpdata->your_ip,0,4); + memset((char *)&dhcpdata->server_ip,0,4); + memset((char *)&dhcpdata->router_ip,0,4); + memcpy((char *)dhcpdata->client_macaddr, (char *)BinEnetAddr,6); + dhcpflags = getenv("DHCPFLAGS"); + if (dhcpflags) /* 0x8000 is the only bit used currently. */ + dhcpdata->flags = (ushort)strtoul(dhcpflags,0,0); + else + dhcpdata->flags = 0; + + self_ecs(dhcpdata->flags); + + /* Finally, the DHCP or BOOTP specific stuff... + * Based on RFC1534 (Interoperation Between DHCP and BOOTP), any message + * received by a DHCP server that contains a 'DHCP_MESSAGETYPE' option + * is assumed to have been sent by a DHCP client. A message without the + * DHCP_MESSAGETYPE option is assumed to have been sent by a BOOTP + * client. + */ + uh_ulen = optlen = 0; + if (bootp) { + memset((char *)bootpdata->vsa,0,sizeof(bootpdata->vsa)); + uh_ulen = sizeof(struct Udphdr) + sizeof(struct bootphdr); + tu->uh_ulen = ecs(uh_ulen); + } + else { + if (!buildDhcpHdr(dhcpdata)) { + /* The cookie should only be loaded at the start of the + * vendor specific area if vendor-specific options are present. + */ + cookie = ecl(STANDARD_MAGIC_COOKIE); + memcpy((char *)&dhcpdata->magic_cookie,(char *)&cookie,4); + dhcpOptionsBase = (uchar *)(dhcpdata+1); + dhcpOptions = dhcpOptionsBase; + *dhcpOptions++ = DHCPOPT_MESSAGETYPE; + *dhcpOptions++ = 1; + *dhcpOptions++ = DHCPDISCOVER; + dhcpOptions = dhcpLoadShellVarOpts(dhcpOptions); + *dhcpOptions++ = 0xff; + + /* Calculate ip and udp lengths after all DHCP options are loaded + * so that the size is easily computed based on the value of the + * dhcpOptions pointer. Apparently, the minimum size of the + * options space is 64 bytes, we determined this simply because + * the DHCP server we are using complains if the size is smaller. + * Also, NULL out the space that is added to get a minimum option + * size of 64 bytes, + */ + optlen = dhcpOptions - dhcpOptionsBase; + if (optlen < 64) { + memset((char *)dhcpOptions,0,64-optlen); + optlen = 64; + } + uh_ulen = sizeof(struct Udphdr)+sizeof(struct dhcphdr)+optlen; + tu->uh_ulen = ecs(uh_ulen); + } + } + ti->ip_len = ecs((sizeof(struct ip) + uh_ulen)); + + ipChksum(ti); /* Compute checksum of ip hdr */ + udpChksum(ti); /* Compute UDP checksum */ + + if (bootp) { + DHCPState = BOOTPSTATE_REQUEST; + sendBuffer(BOOTPSIZE); + } + else { + DHCPState = DHCPSTATE_SELECT; + sendBuffer(DHCPSIZE+optlen); + } +#if INCLUDE_ETHERVERBOSE + if (EtherVerbose & SHOW_DHCP) + printf(" %s startup (%d elapsed secs)\n", + bootp ? "BOOTP" : "DHCP",seconds); +#endif + return(0); +} + +/* SendDHCPRequest() + * The DHCP request is broadcast back with the "server identifier" option + * set to indicate which server has been selected (in case more than one + * has offered). + */ +int +SendDHCPRequest(struct dhcphdr *dhdr) +{ + uchar *op; + struct dhcphdr *dhcpdata; + struct ether_header *te; + struct ip *ti; + struct Udphdr *tu; + int optlen; + uchar *dhcpOptions, *dhcpOptionsBase; + ushort uh_ulen; + ulong cookie; + + te = (struct ether_header *) getXmitBuffer(); + memcpy((char *)&te->ether_shost, (char *)BinEnetAddr,6); + memcpy((char *)&te->ether_dhost, (char *)BroadcastAddr,6); + te->ether_type = ecs(ETHERTYPE_IP); + + ti = (struct ip *) (te + 1); + ti->ip_vhl = IP_HDR_VER_LEN; + ti->ip_tos = 0; + ti->ip_id = 0; + ti->ip_off = ecs(IP_DONTFRAG); /* No fragmentation allowed */ + ti->ip_ttl = UDP_TTL; + ti->ip_p = IP_UDP; + memset((char *)&ti->ip_src.s_addr,0,4); + memset((char *)&ti->ip_dst.s_addr,0xff,4); + + tu = (struct Udphdr *) (ti + 1); + tu->uh_sport = ecs(DhcpClientPort); + tu->uh_dport = ecs(DhcpServerPort); + + dhcpdata = (struct dhcphdr *)(tu+1); + dhcpdata->op = DHCPBOOTP_REQUEST; + dhcpdata->htype = 1; + dhcpdata->hlen = 6; + dhcpdata->hops = 0; + dhcpdata->seconds = ecs(DHCPElapsedSecs); + /* Use the same xid for the request as was used for the discover... + * (rfc2131 section 4.4.1) + */ + memcpy((char *)&dhcpdata->transaction_id,(char *)&dhdr->transaction_id,4); + + dhcpdata->flags = dhdr->flags; + memset((char *)&dhcpdata->client_ip,0,4); + memcpy((char *)&dhcpdata->your_ip,(char *)&dhdr->your_ip,4); + memset((char *)&dhcpdata->server_ip,0,4); + memset((char *)&dhcpdata->router_ip,0,4); + cookie = ecl(STANDARD_MAGIC_COOKIE); + memcpy((char *)&dhcpdata->magic_cookie,(char *)&cookie,4); + memcpy((char *)dhcpdata->client_macaddr, (char *)BinEnetAddr,6); + + dhcpOptionsBase = (uchar *)(dhcpdata+1); + dhcpOptions = dhcpOptionsBase; + *dhcpOptions++ = DHCPOPT_MESSAGETYPE; + *dhcpOptions++ = 1; + *dhcpOptions++ = DHCPREQUEST; + + *dhcpOptions++ = DHCPOPT_SERVERID; /* Server id ID */ + *dhcpOptions++ = 4; + op = DhcpGetOption(DHCPOPT_SERVERID,(uchar *)(dhdr+1)); + if (op) + memcpy((char *)dhcpOptions, (char *)op+2,4); + else + memset((char *)dhcpOptions, (char)0,4); + dhcpOptions+=4; + + *dhcpOptions++ = DHCPOPT_REQUESTEDIP; /* Requested IP */ + *dhcpOptions++ = 4; + memcpy((char *)dhcpOptions,(char *)&dhdr->your_ip,4); + dhcpOptions += 4; + dhcpOptions = dhcpLoadShellVarOpts(dhcpOptions); + *dhcpOptions++ = 0xff; + + /* See note in SendDHCPDiscover() regarding the computation of the + * ip and udp lengths. + */ + optlen = dhcpOptions - dhcpOptionsBase; + if (optlen < 64) + optlen = 64; + uh_ulen = sizeof(struct Udphdr) + sizeof(struct dhcphdr) + optlen; + tu->uh_ulen = ecs(uh_ulen); + ti->ip_len = ecs((sizeof(struct ip) + uh_ulen)); + + ipChksum(ti); /* Compute checksum of ip hdr */ + udpChksum(ti); /* Compute UDP checksum */ + + DHCPState = DHCPSTATE_REQUEST; + sendBuffer(DHCPSIZE+optlen); + return(0); +} + +/* randomDhcpStartupDelay(): + * Randomize the startup for DHCP/BOOTP (see RFC2131 Sec 4.4.1)... + * Return a value between 1 and 10 based on the last 4 bits of the + * board's MAC address. + * The presence of the DHCPSTARTUPDELAY shell variable overrides + * this random value. + */ +int +randomDhcpStartupDelay(void) +{ + char *env; + int randomsec; + + env = getenv("DHCPSTARTUPDELAY"); + if (env) { + randomsec = (int)strtol(env,0,0); + } + else { + randomsec = (BinEnetAddr[5] & 0xf); + if (randomsec > 10) + randomsec -= 7; + else if (randomsec == 0) + randomsec = 10; + } + return(randomsec); +} + +/* dhcpStateCheck(): + * Called by pollethernet() to monitor the progress of DHCPState. + * The retry rate is "almost" what is specified in the RFC... + * Refer to the RetransmitDelay() function for details. + * + * Regarding timing... + * The DHCP startup may be running without an accurate measure of elapsed + * time. The value of LoopsPerMillisecond is used as an approximation of + * the number of times this function must be called for one second to + * pass (dependent on network traffic, etc...). RetransmitDelay() is + * called to retrieve the number of seconds that must elapse prior to + * retransmitting the last DHCP message. The static variables in this + * function are used to keep track of that timeout. + */ +void +dhcpStateCheck(void) +{ + int delaysecs; + + /* If the DHCP command has been issued, it is assumed that the script + * is handling retries... + */ + if (DHCPCommandIssued) + return; + + /* Return, restart or fall through; depending on DHCPState... */ + switch(DHCPState) { + case DHCPSTATE_NOTUSED: + case BOOTPSTATE_COMPLETE: + case DHCPSTATE_BOUND: + return; + case DHCPSTATE_RESTART: + DHCPStartup(0); + return; + case BOOTPSTATE_RESTART: + BOOTPStartup(0); + return; + case DHCPSTATE_INITIALIZE: + case BOOTPSTATE_INITIALIZE: + delaysecs = randomDhcpStartupDelay(); + startElapsedTimer(&dhcpTmr,delaysecs * 1000); +#if INCLUDE_ETHERVERBOSE + if (EtherVerbose & SHOW_DHCP) + printf("\nDHCP/BOOTP %d sec startup delay...\n",delaysecs); +#endif + if (DHCPState & BOOTP_MODE) + DHCPState = BOOTPSTATE_INITDELAY; + else + DHCPState = DHCPSTATE_INITDELAY; + return; + case DHCPSTATE_INITDELAY: + case BOOTPSTATE_INITDELAY: + if (msecElapsed(&dhcpTmr) || (gotachar())) { + DHCPElapsedSecs = 0; + startElapsedTimer(&dhcpTmr, + RetransmitDelay(DELAY_INIT_DHCP)*1000); + if (DHCPState & BOOTP_MODE) + BOOTPStartup(0); + else + DHCPStartup(0); + } + return; + default: + break; + } + + if (msecElapsed(&dhcpTmr)) { + int lastdelay; + + lastdelay = RetransmitDelay(DELAY_RETURN); + delaysecs = RetransmitDelay(DELAY_INCREMENT); + + if (delaysecs != RETRANSMISSION_TIMEOUT) { + DHCPElapsedSecs += delaysecs; + startElapsedTimer(&dhcpTmr,delaysecs*1000); + + if (DHCPState & BOOTP_MODE) + BOOTPStartup(DHCPElapsedSecs); + else + DHCPStartup(DHCPElapsedSecs); +#if INCLUDE_ETHERVERBOSE + if (EtherVerbose & SHOW_DHCP) + printf(" DHCP/BOOTP retry (%d secs)\n",lastdelay); +#endif + } + else { +#if INCLUDE_ETHERVERBOSE + if (EtherVerbose & SHOW_DHCP) + printf(" DHCP/BOOTP giving up\n"); +#endif + } + } +} + +/* xidCheck(): + * Common function used for DHCP and BOOTP to verify incoming transaction + * id... + */ +int +xidCheck(char *id,int bootp) +{ + if (memcmp(id,(char *)&DHCPTransactionId,4)) { +#if INCLUDE_ETHERVERBOSE + if (EtherVerbose & SHOW_DHCP) { + printf("%s ignored: unexpected transaction id.\n", + bootp ? "BOOTP":"DHCP"); + } +#endif + return(-1); + } + return(0); +} + +int +loadBootFile(int bootp) +{ +#if INCLUDE_TFTP + ulong addr; + char bfile[TFSNAMESIZE+TFSINFOSIZE+32]; + char *tfsfile, *bootfile, *tftpsrvr, *flags, *info; + + /* If the DHCPDONTBOOT variable is present, then don't put + * the file in TFS and don't run it. Just complete the TFTP + * transfer, and allow the user to deal with the downloaded + * data using APPRAMBASE and TFTPGET shell variables (probably + * through some boot script). + */ + if (getenv("DHCPDONTBOOT")) + tfsfile = (char *)0; + else + tfsfile = bfile; + + /* If both bootfile and server-ip are specified, then boot it. + * The name of the file must contain information that tells the monitor + * what type of file it is, so the first 'comma' extension is used as + * the flag field (if it is a valid flag set) and the second 'comma' + * extension is used as the info field. + */ + bootfile = getenv("BOOTFILE"); + tftpsrvr = getenv("BOOTSRVR"); + + if (bootfile && tftpsrvr) { + int tftpworked; + + addr = getAppRamStart(); + info = ""; + flags = "e"; + strncpy(bfile,bootfile,sizeof(bfile)); + + if (tfsfile) { + char *icomma, *fcomma; + + fcomma = strchr(bfile,','); + if (fcomma) { + icomma = strchr(fcomma+1,','); + if (icomma) { + *icomma = 0; + info = icomma+1; + } + *fcomma = 0; + if (tfsctrl(TFS_FATOB,(long)(fcomma+1),0) != 0) + flags = fcomma+1; + } + } + + /* Since we are about to transition to TFTP, match TFTP's + * verbosity to the verbosity currently set for DHCP... + */ +#if INCLUDE_ETHERVERBOSE + if (EtherVerbose & SHOW_DHCP) + EtherVerbose |= SHOW_TFTP_STATE; +#endif + + /* If the TFTP transfer succeeds, attempt to run the boot file; + * if the TFTP transfer fails, then re-initialize the tftp state + * and set the DHCP state such that dhcpStateCheck() will + * cause the handshake to start over again... + */ + tftpworked = tftpGet(addr,tftpsrvr,"octet",bfile,tfsfile,flags,info); + if (tftpworked) { +#if INCLUDE_ETHERVERBOSE + EtherVerbose = 0; +#endif + if (tfsfile) { + int err; + char *argv[2]; + + argv[0] = bfile; + argv[1] = 0; + err = tfsrun(argv,0); + if (err != TFS_OKAY) + printf("DHCP-invoked tfsrun(%s) failed: %s\n", + bfile,tfserrmsg(err)); + } + } + else { + tftpInit(); + RetransmitDelay(DELAY_INIT_TFTP); +#if INCLUDE_ETHERVERBOSE + EtherVerbose &= ~SHOW_TFTP_STATE; +#endif + if (bootp) + DHCPState = BOOTPSTATE_RESTART; + else + DHCPState = DHCPSTATE_RESTART; + } + } +#if INCLUDE_ETHERVERBOSE + else + EtherVerbose &= ~(SHOW_DHCP|DHCP_VERBOSE); +#endif +#endif + return(0); +} + +/* processBOOTP(): + * A subset of processDHCP(). + * We get here from processDHCP, because it detects that the current + * value of DHCPState is BOOTPSTATE_REQUEST. + */ +int +processBOOTP(struct ether_header *ehdr,ushort size) +{ + struct ip *ihdr; + struct Udphdr *uhdr; + struct bootphdr *bhdr; + ulong ip, temp_ip, cookie; + uchar buf[16], *op; + +#if INCLUDE_ETHERVERBOSE + if (EtherVerbose & SHOW_HEX) + printMem((uchar *)ehdr,size,EtherVerbose & SHOW_ASCII); +#endif + + ihdr = (struct ip *)(ehdr + 1); + uhdr = (struct Udphdr *)((char *)ihdr + IP_HLEN(ihdr)); + bhdr = (struct bootphdr *)(uhdr+1); + + /* Verify incoming transaction id matches the previous outgoing value: */ + if (xidCheck((char *)&bhdr->transaction_id,1) < 0) + return(-1); + + /* If bootfile is nonzero, store it into BOOTFILE shell var: */ + if (bhdr->bootfile[0]) + DhcpSetEnv("BOOTFILE", (char *)bhdr->bootfile); + + /* Assign IP "server_ip" to the BOOTSRVR shell var (if non-zero): */ + memcpy((char *)&temp_ip,(char *)&bhdr->server_ip,4); + if (temp_ip) + DhcpSetEnv("BOOTSRVR",(char *)IpToString(temp_ip,(char *)buf)); + + /* Assign IP "router_ip" to the RLYAGNT shell var (if non-zero): */ + memcpy((char *)&temp_ip,(char *)&bhdr->router_ip,4); + if (temp_ip) + DhcpSetEnv("RLYAGNT",(char *)IpToString(temp_ip,(char *)buf)); + + /* Assign IP address loaded in "your_ip" to the IPADD shell var: */ + memcpy((char *)BinIpAddr,(char *)&bhdr->your_ip,4); + memcpy((char *)&temp_ip,(char *)&bhdr->your_ip,4); + DhcpSetEnv("IPADD",(char *)IpToString(temp_ip,(char *)buf)); + + /* If STANDARD_MAGIC_COOKIE exists, then process options... */ + memcpy((char *)&cookie,(char *)bhdr->vsa,4); + if (cookie == ecl(STANDARD_MAGIC_COOKIE)) { + /* Assign subnet mask option to NETMASK shell var (if found): */ + op = DhcpGetOption(DHCPOPT_SUBNETMASK,&bhdr->vsa[4]); + if (op) { + memcpy((char *)&ip,(char *)op+2,4); + DhcpSetEnv("NETMASK",(char *)IpToString(ip,(char *)buf)); + } + /* Assign first router option to GIPADD shell var (if found): */ + /* (the router option can have multiple entries, and they are */ + /* supposed to be in order of preference, so use the first one) */ + op = DhcpGetOption(DHCPOPT_ROUTER,&bhdr->vsa[4]); + if (op) { + memcpy((char *)&ip, (char *)op+2,4); + DhcpSetEnv("GIPADD",(char *)IpToString(ip,(char *)buf)); + } + } + + DhcpBootpDone(1,(struct dhcphdr *)bhdr, + size - ((int)((int)&bhdr->vsa - (int)ehdr))); + + DHCPState = BOOTPSTATE_COMPLETE; + + /* Call loadBootFile() which will then kick off a tftp client + * transfer if both BOOTFILE and BOOTSRVR shell variables are + * loaded; otherwise, we are done. + */ + loadBootFile(1); + + return(0); +} + +int +processDHCP(struct ether_header *ehdr,ushort size) +{ + struct ip *ihdr; + struct Udphdr *uhdr; + struct dhcphdr *dhdr; + uchar buf[16], *op, msgtype; + ulong ip, temp_ip, leasetime; + + if (DHCPState == BOOTPSTATE_REQUEST) + return(processBOOTP(ehdr,size)); + +#if INCLUDE_ETHERVERBOSE + if (EtherVerbose & SHOW_HEX) + printMem((uchar *)ehdr,size,EtherVerbose & SHOW_ASCII); +#endif + + ihdr = (struct ip *)(ehdr + 1); + uhdr = (struct Udphdr *)((char *)ihdr + IP_HLEN(ihdr)); + dhdr = (struct dhcphdr *)(uhdr+1); + + /* Verify incoming transaction id matches the previous outgoing value: */ + if (xidCheck((char *)&dhdr->transaction_id,0) < 0) + return(-1); + + op = DhcpGetOption(DHCPOPT_MESSAGETYPE,(uchar *)(dhdr+1)); + if (op) + msgtype = *(op+2); + else + msgtype = DHCPUNKNOWN; + + if ((DHCPState == DHCPSTATE_SELECT) && (msgtype == DHCPOFFER)) { + /* Target issued the DISCOVER, the incoming packet is the server's + * OFFER reply. The function "ValidDHCPOffer() will return + * non-zero if the request is to be sent. + */ + if (ValidDHCPOffer(dhdr)) + SendDHCPRequest(dhdr); +#if INCLUDE_ETHERVERBOSE + else if (EtherVerbose & SHOW_DHCP) { + char ip[4]; + memcpy(ip,(char *)&ihdr->ip_src,4); + printf(" DHCP offer from %d.%d.%d.%d ignored\n", + ip[0],ip[1],ip[2],ip[3]); + } +#endif + } + else if ((DHCPState == DHCPSTATE_REQUEST) && (msgtype == DHCPACK)) { + ulong cookie; + uchar ipsrc[4]; + + /* Target issued the REQUEST, the incoming packet is the server's + * ACK reply. We're done so load the environment now. + */ + memcpy((char *)ipsrc,(char *)&ihdr->ip_src,4); + shell_sprintf("DHCPOFFERFROM","%d.%d.%d.%d", + ipsrc[0],ipsrc[1],ipsrc[2],ipsrc[3]); + + /* If bootfile is nonzero, store it into BOOTFILE shell var: */ + if (dhdr->bootfile[0]) + DhcpSetEnv("BOOTFILE",(char *)dhdr->bootfile); + + /* Assign IP "server_ip" to the BOOTSRVR shell var (if non-zero): */ + memcpy((char *)&temp_ip,(char *)&dhdr->server_ip,4); + if (temp_ip) + DhcpSetEnv("BOOTSRVR",(char *)IpToString(temp_ip,(char *)buf)); + + /* Assign IP "router_ip" to the RLYAGNT shell var (if non-zero): */ + memcpy((char *)&temp_ip,(char *)&dhdr->router_ip,4); + if (temp_ip) + DhcpSetEnv("RLYAGNT",(char *)IpToString(temp_ip,(char *)buf)); + + /* Assign IP address loaded in "your_ip" to the IPADD shell var: */ + memcpy((char *)BinIpAddr,(char *)&dhdr->your_ip,4); + memcpy((char *)&temp_ip,(char *)&dhdr->your_ip,4); + DhcpSetEnv("IPADD",IpToString(temp_ip,(char *)buf)); + + op = DhcpGetOption(DHCPOPT_ROOTPATH,(uchar *)(dhdr+1)); + if (op) + DhcpSetEnv("ROOTPATH",(char *)op+2); + + /* If STANDARD_MAGIC_COOKIE exists, process options... */ + memcpy((char *)&cookie,(char *)&dhdr->magic_cookie,4); + if (cookie == ecl(STANDARD_MAGIC_COOKIE)) { + /* Assign subnet mask to NETMASK shell var (if found): */ + op = DhcpGetOption(DHCPOPT_SUBNETMASK,(uchar *)(dhdr+1)); + if (op) { + memcpy((char *)&ip,(char *)op+2,4); + DhcpSetEnv("NETMASK",(char *)IpToString(ip,(char *)buf)); + } + + /* Assign Hostname to HOSTNAME shell var (if found): */ + op = DhcpGetOption(DHCPOPT_HOSTNAME,(uchar *)(dhdr+1)); + if (op) { + char tmpnam[64]; + int optlen = *(op+1); + + if (optlen < sizeof(tmpnam)-1) { + memset(tmpnam,0,sizeof(tmpnam)); + memcpy(tmpnam,(char *)(op+2),optlen); + DhcpSetEnv("HOSTNAME",tmpnam); + } + } + + /* Assign gateway IP to GIPADD shell var (if found): + * (the router option can have multiple entries, and they are + * supposed to be in order of preference, so use the first one). + */ + op = DhcpGetOption(DHCPOPT_ROUTER,(uchar *)(dhdr+1)); + if (op) { + memcpy((char *)&ip,(char *)op+2,4); + DhcpSetEnv("GIPADD",(char *)IpToString(ip,(char *)buf)); + } + /* Process DHCPOPT_LEASETIME option as follows... + * If not set, assume infinite and clear DHCPLEASETIME shellvar. + * If set, then look for the presence of the DHCPLEASETIME shell + * variable and use it as a minimum. If the incoming value is + * >= what is in the shell variable, accept it and load the shell + * variable with this value. If incoming lease time is less than + * what is stored in DHCPLEASETIME, ignore the request. + * If DHCPLEASETIME is not set, then just load the incoming lease + * into the DHCPLEASETIME shell variable and accept the offer. + */ + op = DhcpGetOption(DHCPOPT_LEASETIME,(uchar *)(dhdr+1)); + if (op) { + memcpy((char *)&leasetime,(char *)op+2,4); + leasetime = ecl(leasetime); + if (getenv("DHCPLEASETIME")) { + ulong minleasetime; + minleasetime = strtol(getenv("DHCPLEASETIME"),0,0); + if (leasetime < minleasetime) { + printf("DHCP: incoming lease time 0x%lx too small.\n", + leasetime); + return(-1); + } + } + sprintf((char *)buf,"0x%lx",leasetime); + setenv("DHCPLEASETIME",(char *)buf); + } + else + setenv("DHCPLEASETIME",(char *)0); + } + + /* Check for vendor specific stuff... */ + DhcpVendorSpecific(dhdr); + + DhcpBootpDone(0,dhdr, + size - ((int)((int)&dhdr->magic_cookie - (int)ehdr))); + + DHCPState = DHCPSTATE_BOUND; + + /* Call loadBootFile() which will then kick off a tftp client + * transfer if both BOOTFILE and BOOTSRVR shell variables are + * loaded; otherwise, we are done. + */ + loadBootFile(0); + } + return(0); +} + +char * +DHCPop(int op) +{ + switch(op) { + case DHCPBOOTP_REQUEST: + return("REQUEST"); + case DHCPBOOTP_REPLY: + return("REPLY"); + default: + return("???"); + } +} + +char * +DHCPmsgtype(int msg) +{ + char *type; + + switch(msg) { + case DHCPDISCOVER: + type = "DISCOVER"; + break; + case DHCPOFFER: + type = "OFFER"; + break; + case DHCPREQUEST: + type = "REQUEST"; + break; + case DHCPDECLINE: + type = "DECLINE"; + break; + case DHCPACK: + type = "ACK"; + break; + case DHCPNACK: + type = "NACK"; + break; + case DHCPRELEASE: + type = "RELEASE"; + break; + case DHCPINFORM: + type = "INFORM"; + break; + case DHCPFORCERENEW: + type = "FORCERENEW"; + break; + case DHCPLEASEQUERY: + type = "LEASEQUERY"; + break; + case DHCPLEASEUNASSIGNED: + type = "LEASEUNASSIGNED"; + break; + case DHCPLEASEUNKNOWN: + type = "LEASEUNKNOWN"; + break; + case DHCPLEASEACTIVE: + type = "LEASEACTIVE"; + break; + default: + type = "???"; + break; + } + return(type); +} + +/* printDhcpOptions(): + * Verbosely display the DHCP options pointed to by the incoming + * options pointer. + */ +void +printDhcpOptions(uchar *options) +{ + int i, safety, opt, optlen; + + safety = 0; + while(*options != 0xff) { + if (safety++ > 10000) { + printf("Aborting, overflow likely\n"); + break; + } + opt = (int)*options++; + if (opt == 0) /* padding */ + continue; + printf(" option %3d: ",opt); + optlen = (int)*options++; + if (opt==DHCPOPT_MESSAGETYPE) { + printf("DHCP_%s",DHCPmsgtype(*options++)); + } + else if ((opt < DHCPOPT_HOSTNAME) || + (opt == DHCPOPT_BROADCASTADDRESS) || + (opt == DHCPOPT_REQUESTEDIP) || + (opt == DHCPOPT_SERVERID) || + (opt == DHCPOPT_NISSERVER)) { + for(i=0;i<optlen;i++) + printf("%d ",*options++); + } + else if ((opt == DHCPOPT_NISDOMAINNAME) || (opt == DHCPOPT_CLASSID)) { + for(i=0;i<optlen;i++) + printf("%c",*options++); + } + else if (opt == DHCPOPT_CLIENTID) { + printf("%d 0x",(int)*options++); + for(i=1;i<optlen;i++) + printf("%02x",*options++); + } + else { + printf("0x"); + for(i=0;i<optlen;i++) + printf("%02x",*options++); + } + printf("\n"); + } +} + +/* printDhcp(): + * Try to format the DHCP stuff... + */ +void +printDhcp(struct Udphdr *p) +{ + struct dhcphdr *d; + uchar *client_ip, *your_ip, *server_ip, *router_ip; + ulong cookie, xid; + + d = (struct dhcphdr *)(p+1); + + client_ip = (uchar *)&(d->client_ip); + your_ip = (uchar *)&(d->your_ip); + server_ip = (uchar *)&(d->server_ip); + router_ip = (uchar *)&(d->router_ip); + memcpy((char *)&xid,(char *)&d->transaction_id,4); + /* xid = ecl(xid) */ + + printf(" DHCP: sport dport ulen sum\n"); + printf(" %4d %4d %4d %4d\n", + ecs(p->uh_sport), ecs(p->uh_dport), ecs(p->uh_ulen),ecs(p->uh_sum)); + printf(" op = %s, htype = %d, hlen = %d, hops = %d\n", + DHCPop(d->op),d->htype,d->hlen,d->hops); + printf(" seconds = %d, flags = 0x%x, xid= 0x%lx\n", + ecs(d->seconds),ecs(d->flags),xid); + printf(" client_macaddr = %02x:%02x:%02x:%02x:%02x:%02x\n", + d->client_macaddr[0], d->client_macaddr[1], + d->client_macaddr[2], d->client_macaddr[3], + d->client_macaddr[4], d->client_macaddr[5]); + printf(" client_ip = %d.%d.%d.%d\n", + client_ip[0],client_ip[1],client_ip[2],client_ip[3]); + printf(" your_ip = %d.%d.%d.%d\n", + your_ip[0],your_ip[1],your_ip[2],your_ip[3]); + printf(" server_ip = %d.%d.%d.%d\n", + server_ip[0],server_ip[1],server_ip[2],server_ip[3]); + printf(" router_ip = %d.%d.%d.%d\n", + router_ip[0],router_ip[1],router_ip[2],router_ip[3]); + if (d->bootfile[0]) + printf(" bootfile: %s\n", d->bootfile); + if (d->server_hostname[0]) + printf(" server_hostname: %s\n", d->server_hostname); + + /* If STANDARD_MAGIC_COOKIE doesn't exist, then don't process options... */ + memcpy((char *)&cookie,(char *)&d->magic_cookie,4); + if (cookie != ecl(STANDARD_MAGIC_COOKIE)) + return; + + printDhcpOptions((uchar *)(d+1)); +} + +/* DhcpGetOption(): + * Based on the incoming option pointer and a specified option value, + * search through the options list for the value and return a pointer + * to that option. + */ +uchar * +DhcpGetOption(unsigned char optval,unsigned char *options) +{ + int safety; + + safety = 0; + while(*options != 0xff) { + if (safety++ > 1000) + break; + if (*options == 0) { /* Skip over padding. */ + options++; + continue; + } + if (*options == optval) + return(options); + options += ((*(options+1)) + 2); + } + return((uchar *)0); +} + +int +DhcpSetEnv(char *name,char *value) +{ +#if INCLUDE_ETHERVERBOSE + if (EtherVerbose & SHOW_DHCP) + printf(" Dhcp/Bootp SetEnv: %s = %s\n",name,value); +#endif + return(setenv(name,value)); +} + +int +DhcpIPCheck(char *ipadd) +{ + char verbose; + + if (!memcmp(ipadd,"DHCP",4)) { + verbose = ipadd[4]; + DHCPState = DHCPSTATE_INITIALIZE; + } + else if (!memcmp(ipadd,"BOOTP",5)) { + verbose = ipadd[5]; + DHCPState = BOOTPSTATE_INITIALIZE; + } + else { + if (IpToBin(ipadd,BinIpAddr) < 0) { + verbose = 0; + DHCPState = BOOTPSTATE_INITIALIZE; + } + else { + DHCPState = DHCPSTATE_NOTUSED; + return(0); + } + } + + BinIpAddr[0] = 0; + BinIpAddr[1] = 0; + BinIpAddr[2] = 0; + BinIpAddr[3] = 0; +#if INCLUDE_ETHERVERBOSE + if (verbose == 'V') + EtherVerbose = DHCP_VERBOSE; + else if (verbose == 'v') + EtherVerbose = SHOW_DHCP; +#endif + return(0); +} + +char * +dhcpStringState(int state) +{ + switch(state) { + case DHCPSTATE_INITIALIZE: + return("DHCP_INITIALIZE"); + case DHCPSTATE_SELECT: + return("DHCP_SELECT"); + case DHCPSTATE_REQUEST: + return("DHCP_REQUEST"); + case DHCPSTATE_BOUND: + return("DHCP_BOUND"); + case DHCPSTATE_RENEW: + return("DHCP_RENEW"); + case DHCPSTATE_REBIND: + return("DHCP_REBIND"); + case DHCPSTATE_NOTUSED: + return("DHCP_NOTUSED"); + case DHCPSTATE_RESTART: + return("DHCP_RESTART"); + case BOOTPSTATE_INITIALIZE: + return("BOOTP_INITIALIZE"); + case BOOTPSTATE_REQUEST: + return("BOOTP_REQUEST"); + case BOOTPSTATE_RESTART: + return("BOOTP_RESTART"); + case BOOTPSTATE_COMPLETE: + return("BOOTP_COMPLETE"); + default: + return("???"); + } +} + +void +ShowDhcpStats() +{ + printf("Current DHCP State: %s\n",dhcpStringState(DHCPState)); +} + +#endif |