| /* |
| * BOOTP packet protocol handling. |
| */ |
| #include <sys/types.h> |
| #include <sys/uio.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <time.h> |
| #include <unistd.h> |
| #include <netinet/in.h> |
| |
| #include "ipconfig.h" |
| #include "netdev.h" |
| #include "bootp_packet.h" |
| #include "bootp_proto.h" |
| #include "packet.h" |
| |
| static uint8_t bootp_options[312] = { |
| [ 0] = 99, 130, 83, 99,/* RFC1048 magic cookie */ |
| [ 4] = 1, 4, /* 4- 9 subnet mask */ |
| [ 10] = 3, 4, /* 10- 15 default gateway */ |
| [ 16] = 5, 8, /* 16- 25 nameserver */ |
| [ 26] = 12, 32, /* 26- 59 host name */ |
| [ 60] = 40, 32, /* 60- 95 nis domain name */ |
| [ 96] = 17, 40, /* 96-137 boot path */ |
| [138] = 57, 2, 1, 150, /* 138-141 extension buffer */ |
| [142] = 255, /* end of list */ |
| }; |
| |
| /* |
| * Send a plain bootp request packet with options |
| */ |
| int bootp_send_request(struct netdev *dev) |
| { |
| struct bootp_hdr bootp; |
| struct iovec iov[] = { |
| /* [0] = ip + udp headers */ |
| [1] = {&bootp, sizeof(bootp)}, |
| [2] = {bootp_options, 312} |
| }; |
| |
| memset(&bootp, 0, sizeof(struct bootp_hdr)); |
| |
| bootp.op = BOOTP_REQUEST, bootp.htype = dev->hwtype; |
| bootp.hlen = dev->hwlen; |
| bootp.xid = dev->bootp.xid; |
| bootp.ciaddr = dev->ip_addr; |
| bootp.secs = htons(time(NULL) - dev->open_time); |
| memcpy(bootp.chaddr, dev->hwaddr, 16); |
| |
| dprintf("-> bootp xid 0x%08x secs 0x%08x ", |
| bootp.xid, ntohs(bootp.secs)); |
| |
| return packet_send(dev, iov, 2); |
| } |
| |
| /* |
| * DESCRIPTION |
| * bootp_ext119_decode() decodes Domain Search Option data. |
| * The decoded string is separated with ' '. |
| * For example, it is either "foo.bar.baz. bar.baz.", "foo.bar.", or "foo.". |
| * |
| * ARGUMENTS |
| * const uint8_t *ext |
| * *ext is a pointer to a DHCP Domain Search Option data. *ext does not |
| * include a tag(code) octet and a length octet in DHCP options. |
| * For example, if *ext is {3, 'f', 'o', 'o', 0}, this function returns |
| * a pointer to a "foo." string. |
| * |
| * int16_t ext_size |
| * ext_size is the memory size of *ext. For example, |
| * if *ext is {3, 'f', 'o', 'o', 0}, ext_size must be 5. |
| * |
| * uint8_t *tmp |
| * *tmp is a pointer to a temporary memory space for decoding. |
| * The memory size must be equal to or more than ext_size. |
| * 'memset(tmp, 0, sizeof(tmp));' is not required, but values in *tmp |
| * are changed in decoding process. |
| * |
| * RETURN VALUE |
| * if OK, a pointer to a decoded string malloc-ed |
| * else , NULL |
| * |
| * SEE ALSO RFC3397 |
| */ |
| static char *bootp_ext119_decode(const void *ext, int16_t ext_size, void *tmp) |
| { |
| uint8_t *u8ext; |
| int_fast32_t i; |
| int_fast32_t decoded_size; |
| int_fast8_t currentdomain_is_singledot; |
| |
| /* only for validating *ext */ |
| uint8_t *is_pointee; |
| int_fast32_t is_pointee_size; |
| |
| /* only for structing a decoded string */ |
| char *decoded_str; |
| int_fast32_t dst_i; |
| |
| if (ext == NULL || ext_size <= 0 || tmp == NULL) |
| return NULL; |
| |
| u8ext = (uint8_t *)ext; |
| is_pointee = tmp; |
| memset(is_pointee, 0, (size_t)ext_size); |
| is_pointee_size = 0; |
| |
| /* |
| * validate the format of *ext and |
| * calculate the memory size for a decoded string |
| */ |
| i = 0; |
| decoded_size = 0; |
| currentdomain_is_singledot = 1; |
| while (1) { |
| if (i >= ext_size) |
| return NULL; |
| |
| if (u8ext[i] == 0) { |
| /* Zero-ending */ |
| if (currentdomain_is_singledot) |
| decoded_size++; /* for '.' */ |
| decoded_size++; /* for ' ' or '\0' */ |
| currentdomain_is_singledot = 1; |
| i++; |
| if (i == ext_size) |
| break; |
| is_pointee_size = i; |
| } else if (u8ext[i] < 0x40) { |
| /* Label(sub-domain string) */ |
| int j; |
| |
| /* loosely validate characters for domain names */ |
| if (i + u8ext[i] >= ext_size) |
| return NULL; |
| for (j = i + 1; j <= i + u8ext[i]; j++) |
| if (!(u8ext[j] == '-' || |
| ('0' <= u8ext[j] && u8ext[j] <= '9') || |
| ('A' <= u8ext[j] && u8ext[j] <= 'Z') || |
| ('a' <= u8ext[j] && u8ext[j] <= 'z'))) |
| return NULL; |
| |
| is_pointee[i] = 1; |
| decoded_size += u8ext[i] + 1; /* for Label + '.' */ |
| currentdomain_is_singledot = 0; |
| i += u8ext[i] + 1; |
| } else if (u8ext[i] < 0xc0) |
| return NULL; |
| |
| else { |
| /* Compression-pointer (to a prior Label) */ |
| int_fast32_t p; |
| |
| if (i + 1 >= ext_size) |
| return NULL; |
| |
| p = ((0x3f & u8ext[i]) << 8) + u8ext[i + 1]; |
| if (!(p < is_pointee_size && is_pointee[p])) |
| return NULL; |
| |
| while (1) { |
| /* u8ext[p] was validated */ |
| if (u8ext[p] == 0) { |
| /* Zero-ending */ |
| decoded_size++; |
| break; |
| } else if (u8ext[p] < 0x40) { |
| /* Label(sub-domain string) */ |
| decoded_size += u8ext[p] + 1; |
| p += u8ext[p] + 1; |
| } else { |
| /* Compression-pointer */ |
| p = ((0x3f & u8ext[p]) << 8) |
| + u8ext[p + 1]; |
| } |
| } |
| |
| currentdomain_is_singledot = 1; |
| i += 2; |
| if (i == ext_size) |
| break; |
| is_pointee_size = i; |
| } |
| } |
| |
| |
| /* |
| * construct a decoded string |
| */ |
| decoded_str = malloc(decoded_size); |
| if (decoded_str == NULL) |
| return NULL; |
| |
| i = 0; |
| dst_i = 0; |
| currentdomain_is_singledot = 1; |
| while (1) { |
| if (u8ext[i] == 0) { |
| /* Zero-ending */ |
| if (currentdomain_is_singledot) { |
| if (dst_i != 0) |
| dst_i++; |
| decoded_str[dst_i] = '.'; |
| } |
| dst_i++; |
| decoded_str[dst_i] = ' '; |
| |
| currentdomain_is_singledot = 1; |
| i++; |
| if (i == ext_size) |
| break; |
| } else if (u8ext[i] < 0x40) { |
| /* Label(sub-domain string) */ |
| if (dst_i != 0) |
| dst_i++; |
| memcpy(&decoded_str[dst_i], &u8ext[i + 1], |
| (size_t)u8ext[i]); |
| dst_i += u8ext[i]; |
| decoded_str[dst_i] = '.'; |
| |
| currentdomain_is_singledot = 0; |
| i += u8ext[i] + 1; |
| } else { |
| /* Compression-pointer (to a prior Label) */ |
| int_fast32_t p; |
| |
| p = ((0x3f & u8ext[i]) << 8) + u8ext[i + 1]; |
| while (1) { |
| if (u8ext[p] == 0) { |
| /* Zero-ending */ |
| decoded_str[dst_i++] = '.'; |
| decoded_str[dst_i] = ' '; |
| break; |
| } else if (u8ext[p] < 0x40) { |
| /* Label(sub-domain string) */ |
| dst_i++; |
| memcpy(&decoded_str[dst_i], |
| &u8ext[p + 1], |
| (size_t)u8ext[p]); |
| dst_i += u8ext[p]; |
| decoded_str[dst_i] = '.'; |
| |
| p += u8ext[p] + 1; |
| } else { |
| /* Compression-pointer */ |
| p = ((0x3f & u8ext[p]) << 8) |
| + u8ext[p + 1]; |
| } |
| } |
| |
| currentdomain_is_singledot = 1; |
| i += 2; |
| if (i == ext_size) |
| break; |
| } |
| } |
| decoded_str[dst_i] = '\0'; |
| #ifdef DEBUG |
| if (dst_i + 1 != decoded_size) { |
| dprintf("bug:%s():bottom: malloc(%ld), write(%ld)\n", |
| __func__, (long)decoded_size, (long)(dst_i + 1)); |
| exit(1); |
| } |
| #endif |
| return decoded_str; |
| } |
| |
| /* |
| * Parse a bootp reply packet |
| */ |
| int bootp_parse(struct netdev *dev, struct bootp_hdr *hdr, |
| uint8_t *exts, int extlen) |
| { |
| uint8_t ext119_buf[BOOTP_EXTS_SIZE]; |
| int16_t ext119_len = 0; |
| |
| dev->bootp.gateway = hdr->giaddr; |
| dev->ip_addr = hdr->yiaddr; |
| dev->ip_server = hdr->siaddr; |
| dev->ip_netmask = INADDR_ANY; |
| dev->ip_broadcast = INADDR_ANY; |
| dev->ip_gateway = hdr->giaddr; |
| dev->ip_nameserver[0] = INADDR_ANY; |
| dev->ip_nameserver[1] = INADDR_ANY; |
| dev->hostname[0] = '\0'; |
| dev->nisdomainname[0] = '\0'; |
| dev->bootpath[0] = '\0'; |
| memcpy(&dev->filename, &hdr->boot_file, FNLEN); |
| |
| if (extlen >= 4 && exts[0] == 99 && exts[1] == 130 && |
| exts[2] == 83 && exts[3] == 99) { |
| uint8_t *ext; |
| |
| for (ext = exts + 4; ext - exts < extlen;) { |
| int len; |
| uint8_t opt = *ext++; |
| |
| if (opt == 0) |
| continue; |
| else if (opt == 255) |
| break; |
| |
| if (ext - exts >= extlen) |
| break; |
| len = *ext++; |
| |
| if (ext - exts + len > extlen) |
| break; |
| switch (opt) { |
| case 1: /* subnet mask */ |
| if (len == 4) |
| memcpy(&dev->ip_netmask, ext, 4); |
| break; |
| case 3: /* default gateway */ |
| if (len >= 4) |
| memcpy(&dev->ip_gateway, ext, 4); |
| break; |
| case 6: /* DNS server */ |
| if (len >= 4) |
| memcpy(&dev->ip_nameserver, ext, |
| len >= 8 ? 8 : 4); |
| break; |
| case 12: /* host name */ |
| if (len > sizeof(dev->hostname) - 1) |
| len = sizeof(dev->hostname) - 1; |
| memcpy(&dev->hostname, ext, len); |
| dev->hostname[len] = '\0'; |
| break; |
| case 15: /* domain name */ |
| if (len > sizeof(dev->dnsdomainname) - 1) |
| len = sizeof(dev->dnsdomainname) - 1; |
| memcpy(&dev->dnsdomainname, ext, len); |
| dev->dnsdomainname[len] = '\0'; |
| break; |
| case 17: /* root path */ |
| if (len > sizeof(dev->bootpath) - 1) |
| len = sizeof(dev->bootpath) - 1; |
| memcpy(&dev->bootpath, ext, len); |
| dev->bootpath[len] = '\0'; |
| break; |
| case 26: /* interface MTU */ |
| if (len == 2) |
| dev->mtu = (ext[0] << 8) + ext[1]; |
| break; |
| case 28: /* broadcast addr */ |
| if (len == 4) |
| memcpy(&dev->ip_broadcast, ext, 4); |
| break; |
| case 40: /* NIS domain name */ |
| if (len > sizeof(dev->nisdomainname) - 1) |
| len = sizeof(dev->nisdomainname) - 1; |
| memcpy(&dev->nisdomainname, ext, len); |
| dev->nisdomainname[len] = '\0'; |
| break; |
| case 54: /* server identifier */ |
| if (len == 4 && !dev->ip_server) |
| memcpy(&dev->ip_server, ext, 4); |
| break; |
| case 119: /* Domain Search Option */ |
| if (ext119_len >= 0 && |
| ext119_len + len <= sizeof(ext119_buf)) { |
| memcpy(ext119_buf + ext119_len, |
| ext, len); |
| ext119_len += len; |
| } else |
| ext119_len = -1; |
| |
| break; |
| } |
| |
| ext += len; |
| } |
| } |
| if (ext119_len > 0) { |
| char *ret; |
| uint8_t ext119_tmp[BOOTP_EXTS_SIZE]; |
| |
| ret = bootp_ext119_decode(ext119_buf, ext119_len, ext119_tmp); |
| if (ret != NULL) { |
| if (dev->domainsearch != NULL) |
| free(dev->domainsearch); |
| dev->domainsearch = ret; |
| } |
| } |
| |
| /* |
| * Got packet. |
| */ |
| return 1; |
| } |
| |
| /* |
| * Receive a bootp reply and parse packet |
| * Returns: |
| *-1 = Error in packet_recv, try again later |
| * 0 = Unexpected packet, discarded |
| * 1 = Correctly received and parsed packet |
| */ |
| int bootp_recv_reply(struct netdev *dev) |
| { |
| struct bootp_hdr bootp; |
| uint8_t bootp_options[BOOTP_EXTS_SIZE]; |
| struct iovec iov[] = { |
| /* [0] = ip + udp headers */ |
| [1] = {&bootp, sizeof(struct bootp_hdr)}, |
| [2] = {bootp_options, sizeof(bootp_options)} |
| }; |
| int ret; |
| |
| ret = packet_recv(dev, iov, 3); |
| if (ret <= 0) |
| return ret; |
| |
| if (ret < sizeof(struct bootp_hdr) || |
| bootp.op != BOOTP_REPLY || /* RFC951 7.5 */ |
| bootp.xid != dev->bootp.xid || |
| memcmp(bootp.chaddr, dev->hwaddr, 16)) |
| return 0; |
| |
| ret -= sizeof(struct bootp_hdr); |
| |
| return bootp_parse(dev, &bootp, bootp_options, ret); |
| } |
| |
| /* |
| * Initialise interface for bootp. |
| */ |
| int bootp_init_if(struct netdev *dev) |
| { |
| short flags; |
| |
| /* |
| * Get the device flags |
| */ |
| if (netdev_getflags(dev, &flags)) |
| return -1; |
| |
| /* |
| * We can't do DHCP nor BOOTP if this device |
| * doesn't support broadcast. |
| */ |
| if (dev->mtu < 364 || (flags & IFF_BROADCAST) == 0) { |
| dev->caps &= ~(CAP_BOOTP | CAP_DHCP); |
| return 0; |
| } |
| |
| /* |
| * Get a random XID |
| */ |
| dev->bootp.xid = (uint32_t) lrand48(); |
| dev->open_time = time(NULL); |
| |
| return 0; |
| } |