/* This probably doesn't work on anything other then Ethernet! It may work on PLIP as well, but ARCnet and Token Ring are unlikely at best. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bootpc.h" #include "net.h" #define NUM_RETRIES 3 #ifdef STANDALONE #define _(foo) ((foo)) #include #else #include "log.h" #include "intl.h" #endif #ifdef STANDALONE void logMessage(char * mess, ...) { va_list args; va_start(args, mess); vprintf(mess, args); va_end(args); puts(""); } #endif 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_BOOTFILE 13 #define BOOTP_OPTION_DOMAIN 15 #define BOOTP_OPTION_BROADCAST 28 #define DHCP_OPTION_REQADDR 50 #define DHCP_OPTION_LEASE 51 #define DHCP_OPTION_OVERLOAD 52 #define DHCP_OPTION_TYPE 53 #define DHCP_OPTION_SERVER 54 #define DHCP_OPTION_T1 58 #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_DECLINE 4 #define DHCP_TYPE_ACK 5 #define DHCP_TYPE_NAK 6 #define DHCP_TYPE_RELEASE 7 #define DHCP_TYPE_INFORM 8 #define BOOTP_VENDOR_LENGTH 64 #define DHCP_VENDOR_LENGTH 340 struct bootpRequest { 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 vendCookie[] = { 99, 130, 83, 99, 255 }; static char * perrorstr(char * msg); static char * setupInterface(char * device, int s); static void parseReply(struct bootpRequest * breq, struct intfInfo * intf, struct netInfo * net); static char * prepareRequest(struct bootpRequest * breq, int sock, char * device, time_t startTime); static void initVendorCodes(struct bootpRequest * breq); static char * handleTransaction(int s, int timeout, struct bootpRequest * breq, struct bootpRequest * bresp, struct sockaddr_in * serverAddr, struct sockaddr_in * respondant, int dhcpType); static int dhcpMessageType(struct bootpRequest * response); static char * setupInterface(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)); memset(&route, 0, sizeof(route)); memcpy(&route.rt_dst, addrp, sizeof(route.rt_dst)); if (ioctl(s, SIOCSIFADDR, &req)) return perrorstr("SIOCSIFADDR"); if (ioctl(s, SIOCSIFNETMASK, &req)) return perrorstr("SIOCSIFNETMASK"); /* the broadcast address is 255.255.255.255 */ memset(&addrp->sin_addr,255 , sizeof(addrp->sin_addr)); if (ioctl(s, SIOCSIFBRDADDR, &req)) return perrorstr("SIOCSIFBRDADDR"); req.ifr_flags = IFF_UP | IFF_BROADCAST | IFF_RUNNING; if (ioctl(s, SIOCSIFFLAGS, &req)) return perrorstr("SIOCSIFFLAGS"); route.rt_dev = device; route.rt_flags = RTF_UP; if (ioctl(s, SIOCADDRT, &route)) return perrorstr("SIOCSADDRT"); if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &true, 1)) { close(s); return perrorstr("setsockopt"); } return NULL; } static int dhcpMessageType(struct bootpRequest * response) { unsigned char * chptr; unsigned char option, length; chptr = response->vendor; chptr += 4; while (*chptr != 0xFF) { option = *chptr++; if (!option) continue; length = *chptr++; if (option == DHCP_OPTION_TYPE) return *chptr; chptr += length; } return -1; } void setMissingIpInfo(struct intfInfo * intf) { bp_int32 ipNum = *((bp_int32 *) &intf->ip); bp_int32 nmNum = *((bp_int32 *) &intf->netmask); bp_int32 ipRealNum = ntohl(ipNum); if (!(intf->set & INTFINFO_HAS_NETMASK)) { if (((ipRealNum & 0xFF000000) >> 24) <= 127) nmNum = 0xFF000000; else if (((ipRealNum & 0xFF000000) >> 24) <= 191) nmNum = 0xFFFF0000; else nmNum = 0xFFFFFF00; *((bp_int32 *) &intf->netmask) = nmNum = htonl(nmNum); } if (!(intf->set & INTFINFO_HAS_BROADCAST)) *((bp_int32 *) &intf->broadcast) = (ipNum & nmNum) | ~(nmNum); if (!(intf->set & INTFINFO_HAS_NETWORK)) *((bp_int32 *) &intf->network) = ipNum & nmNum; intf->set |= INTFINFO_HAS_BROADCAST | INTFINFO_HAS_NETWORK | INTFINFO_HAS_NETMASK; } static void parseReply(struct bootpRequest * breq, struct intfInfo * intf, struct netInfo * net) { unsigned int i; unsigned char * chptr; unsigned char option, length; i = ~(INTFINFO_HAS_IP | INTFINFO_HAS_NETMASK | INTFINFO_HAS_NETWORK | INTFINFO_HAS_BROADCAST); intf->set &= i; intf->manuallySet &= i; if (strlen(breq->bootfile)) { intf->bootFile = strdup(breq->bootfile); intf->set |= INTFINFO_HAS_BOOTFILE; } else { intf->set &= ~INTFINFO_HAS_BOOTFILE; } if (breq->server_ip) { intf->set |= INTFINFO_HAS_BOOTSERVER; memcpy(&intf->bootServer, &breq->server_ip, sizeof(breq->server_ip)); } memcpy(&intf->ip, &breq->yiaddr, 4); intf->set |= INTFINFO_HAS_IP; chptr = breq->vendor; chptr += 4; while (*chptr != 0xFF) { option = *chptr++; if (!option) continue; length = *chptr++; switch (option) { case BOOTP_OPTION_DNS: net->numDns = 0; for (i = 0; i < length; i += 4) memcpy(&net->dnsServers[net->numDns++], chptr + i, 4); net->set |= NETINFO_HAS_DNS; break; case BOOTP_OPTION_NETMASK: memcpy(&intf->netmask, chptr, 4); intf->set |= INTFINFO_HAS_NETMASK; break; case BOOTP_OPTION_DOMAIN: net->domain = malloc(length + 1); memcpy(net->domain, chptr, length); net->domain[length] = '\0'; net->set |= NETINFO_HAS_DOMAIN; break; case BOOTP_OPTION_BROADCAST: memcpy(&intf->broadcast, chptr, 4); intf->set |= INTFINFO_HAS_BROADCAST; break; case BOOTP_OPTION_GATEWAY: memcpy(&net->gateway, chptr, 4); net->set |= NETINFO_HAS_GATEWAY; break; case BOOTP_OPTION_HOSTNAME: net->hostname = malloc(length + 1); memcpy(net->hostname, chptr, length); net->hostname[length] = '\0'; net->set |= NETINFO_HAS_HOSTNAME; break; case BOOTP_OPTION_BOOTFILE: /* we ignore this right now */ break; case DHCP_OPTION_OVERLOAD: /* FIXME: we should pay attention to this */ logMessage("dhcp overload option is currently ignored!"); break; } chptr += length; } setMissingIpInfo(intf); } static char * perrorstr(char * msg) { static char * err = NULL; static int errsize = 0; static int newsize; newsize = strlen(msg) + strlen(strerror(errno)) + 3; if (!errsize) { errsize = newsize; err = malloc(errsize); } else if (errsize < newsize) { free(err); errsize = newsize; err = malloc(errsize); } sprintf(err, "%s: %s", msg, strerror(errno)); return err; } static void initVendorCodes(struct bootpRequest * breq) { memcpy(breq->vendor, vendCookie, sizeof(vendCookie)); } static char * prepareRequest(struct bootpRequest * breq, int sock, char * device, time_t startTime) { struct ifreq req; int i; memset(breq, 0, sizeof(*breq)); breq->opcode = BOOTP_OPCODE_REQUEST; strcpy(req.ifr_name, device); if (ioctl(sock, SIOCGIFHWADDR, &req)) return perrorstr("SIOCSIFHWADDR"); breq->hw = 1; /* ethernet */ breq->hwlength = IFHWADDRLEN; memcpy(breq->hwaddr, req.ifr_hwaddr.sa_data, IFHWADDRLEN); /* we should use something random here, but I don't want to start using stuff from the math library */ breq->id = time(NULL); for (i = 0; i < IFHWADDRLEN; i++) breq->id ^= breq->hwaddr[i] << (8 * (i % 4)); breq->hopcount = 0; breq->secs = time(NULL) - startTime; initVendorCodes(breq); return NULL; } static char * handleTransaction(int s, int timeout, struct bootpRequest * breq, struct bootpRequest * bresp, struct sockaddr_in * serverAddr, struct sockaddr_in * respondant, int dhcpType) { struct timeval tv; fd_set readfs; int i; struct sockaddr_in tmpAddress; int gotit = 0; int tries = NUM_RETRIES; int nextTimeout = 4; while (!gotit && tries--) { i = sizeof(*breq); if (dhcpType == -1) i -= (DHCP_VENDOR_LENGTH - BOOTP_VENDOR_LENGTH); if (sendto(s, breq, i, 0, (struct sockaddr *) serverAddr, sizeof(*serverAddr)) != i) { close(s); return perrorstr("sendto"); } tv.tv_usec = 0; tv.tv_sec = nextTimeout; if (tv.tv_sec > timeout) tv.tv_sec = timeout; timeout -= tv.tv_sec; nextTimeout *= 2; switch (time(NULL) & 4) { case 0: nextTimeout--; break; case 1: nextTimeout++; break; } FD_ZERO(&readfs); FD_SET(s, &readfs); switch ((select(s + 1, &readfs, NULL, NULL, &tv))) { case 0: break; case 1: i = sizeof(tmpAddress); if (recvfrom(s, bresp, sizeof(*bresp), 0, (struct sockaddr *) &tmpAddress, &i) < 0) return perrorstr("recvfrom"); /* 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 (dhcpMessageType(bresp) != dhcpType) continue; if (memcmp(bresp->vendor, vendCookie, 4)) continue; if (respondant) *respondant = tmpAddress; gotit = 1; break; default: return perrorstr("select"); } } if (!gotit) if (dhcpType == -1) return _("No BOOTP reply received"); else return _("No DHCP reply received"); return NULL; } static void addVendorCode(struct bootpRequest * 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; } static int getVendorCode(struct bootpRequest * bresp, unsigned char option, void * data) { unsigned char * chptr; unsigned int length, theOption; chptr = bresp->vendor; chptr += 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 void hareyCarey(int signo) { logMessage("DHCP FAILED TO RENEW LEASE -- KILLING INSTALL"); kill(getppid(), SIGFPE); } /* This is somewhat broken. We try only to renew the lease. If we fail, we don't try to completely rebind. This doesn't follow the DHCP spec, but for the install it should be a reasonable compromise. */ static void dhcpDaemon(int s, struct bootpRequest * origResult, char * device) { unsigned int t1, lease; /* offsets from "now" */ time_t now; struct bootpRequest breq, bresp; unsigned char messageType; struct sockaddr_in serverAddr; char * chptr; int renewLease = 1; while (renewLease) { now = time(NULL); if (getVendorCode(origResult, DHCP_OPTION_LEASE, &lease)) renewLease = 0; if (getVendorCode(origResult, DHCP_OPTION_T1, &t1)) { /* this is equiv to t1 = 0.875 * lease, which is mandated by RFC */ t1 = (7 * lease) >> 3; } t1 = 120; /* if this goes off, we have a problem! */ signal(SIGALRM, hareyCarey); alarm((now + lease) - time(NULL)); /* go to sleep until we're needed again */ sleep((now + t1) - time(NULL)); if ((chptr = prepareRequest(&breq, s, device, time(NULL)))) { close(s); while (1); /* problem */ } messageType = DHCP_TYPE_REQUEST; addVendorCode(&breq, DHCP_OPTION_TYPE, 1, &messageType); breq.ciaddr = origResult->yiaddr; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(BOOTP_SERVER_PORT); /* bootp server */ getVendorCode(origResult, DHCP_OPTION_SERVER, &serverAddr.sin_addr); if ((chptr = handleTransaction(s, 60, &breq, &bresp, &serverAddr, NULL, DHCP_TYPE_ACK))) { renewLease = 0; } else { *origResult = bresp; } } #ifdef STANDALONE printf("dhcp renewel failed\n"); #else logMessage("DHCP FAILED TO RENEW LEASE -- KILLING INSTALL"); kill(getppid(), SIGFPE); #endif } char * doDhcp(int timeout, struct intfInfo * intf, struct netInfo * net, int shouldDaemonize) { int s, i; struct sockaddr_in serverAddr; struct sockaddr_in clientAddr; struct sockaddr_in broadcastAddr; struct bootpRequest breq, bresp; unsigned char * chptr; unsigned char messageType; unsigned int lease; time_t startTime = time(NULL); s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) { return perrorstr("socket"); } if ((chptr = setupInterface(intf->device, s))) { close(s); return chptr; } if ((chptr = prepareRequest(&breq, s, intf->device, startTime))) { close(s); return chptr; } messageType = DHCP_TYPE_DISCOVER; addVendorCode(&breq, DHCP_OPTION_TYPE, 1, &messageType); memset(&clientAddr.sin_addr, 0, sizeof(&clientAddr.sin_addr)); clientAddr.sin_family = AF_INET; clientAddr.sin_port = htons(BOOTP_CLIENT_PORT); /* bootp client */ if (bind(s, (struct sockaddr *) &clientAddr, sizeof(clientAddr))) { return perrorstr("bind"); } broadcastAddr.sin_family = AF_INET; broadcastAddr.sin_port = htons(BOOTP_SERVER_PORT); /* bootp server */ memset(&broadcastAddr.sin_addr, 0xff, sizeof(broadcastAddr.sin_addr)); /* broadcast */ if ((chptr = handleTransaction(s, timeout, &breq, &bresp, &broadcastAddr, NULL, DHCP_TYPE_OFFER))) { close(s); return chptr; } serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(BOOTP_SERVER_PORT); /* bootp server */ if (getVendorCode(&bresp, DHCP_OPTION_SERVER, &serverAddr.sin_addr)) { close(s); return "DHCPOFFER didn't include server address"; } initVendorCodes(&breq); messageType = DHCP_TYPE_REQUEST; addVendorCode(&breq, DHCP_OPTION_TYPE, 1, &messageType); addVendorCode(&breq, DHCP_OPTION_SERVER, 4, &serverAddr.sin_addr); addVendorCode(&breq, DHCP_OPTION_REQADDR, 4, &bresp.yiaddr); /* request a lease of 1 hour */ i = htonl(60 * 60); addVendorCode(&breq, DHCP_OPTION_LEASE, 4, &i); if ((chptr = handleTransaction(s, timeout, &breq, &bresp, &broadcastAddr, NULL, DHCP_TYPE_ACK))) { close(s); return chptr; } if (getVendorCode(&bresp, DHCP_OPTION_LEASE, &lease)) return "failed to get lease time\n"; lease = ntohl(lease); if (lease && lease != 0xffffffff && shouldDaemonize) { #ifndef STANDALONE if (!fork()) #endif dhcpDaemon(s, &bresp, intf->device); } close(s); parseReply(&bresp, intf, net); if (!(intf->set & INTFINFO_HAS_BOOTSERVER)) { intf->bootServer = serverAddr.sin_addr; intf->set |= INTFINFO_HAS_BOOTSERVER; } return NULL; } char * doBootp(int timeout, struct intfInfo * intf, struct netInfo * net) { int s; struct sockaddr_in clientAddr; struct sockaddr_in broadcastAddr; struct sockaddr_in serverAddr; struct bootpRequest breq, bresp; unsigned char * chptr; time_t startTime = time(NULL); s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) { return perrorstr("socket"); } if ((chptr = setupInterface(intf->device, s))) { close(s); return chptr; } if ((chptr = prepareRequest(&breq, s, intf->device, startTime))) { close(s); return chptr; } memset(&clientAddr.sin_addr, 0, sizeof(&clientAddr.sin_addr)); clientAddr.sin_family = AF_INET; clientAddr.sin_port = htons(BOOTP_CLIENT_PORT); /* bootp client */ if (bind(s, (struct sockaddr *) &clientAddr, sizeof(clientAddr))) { return perrorstr("bind"); } broadcastAddr.sin_family = AF_INET; broadcastAddr.sin_port = htons(BOOTP_SERVER_PORT); /* bootp server */ memset(&broadcastAddr.sin_addr, 0xff, sizeof(broadcastAddr.sin_addr)); /* broadcast */ if ((chptr = handleTransaction(s, timeout, &breq, &bresp, &broadcastAddr, &serverAddr, -1))) { close(s); return chptr; } close(s); parseReply(&bresp, intf, net); if (!(intf->set & INTFINFO_HAS_BOOTSERVER)) { intf->bootServer = serverAddr.sin_addr; intf->set |= INTFINFO_HAS_BOOTSERVER; } return NULL; } #ifdef STANDALONE void main (void) { char * msg; struct intfInfo intf; struct netInfo net; memset(&intf, 0, sizeof(intf)); memset(&net, 0, sizeof(net)); strcpy(intf.device, "eth0"); #ifndef USEBOOTP msg = doDhcp(30, &intf, &net); #else msg = doBootp(30, &intf, &net); #endif if (msg) { puts(msg); exit(1); } printf("got address %s\n", inet_ntoa(intf.ip)); if (intf.set & INTFINFO_HAS_NETMASK) printf("got netmask %s\n", inet_ntoa(intf.netmask)); if (net.set & NETINFO_HAS_DOMAIN) printf("got domain %s\n", net.domain); } #endif