| /* |
| * Copyright 2013 Google Inc. |
| * |
| * See file CREDITS for list of people who contributed to this |
| * project. |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License as |
| * published by the Free Software Foundation; either version 2 of |
| * the License, or (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but without any warranty; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * 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., 59 Temple Place, Suite 330, Boston, |
| * MA 02111-1307 USA |
| */ |
| |
| #include <assert.h> |
| #include <endian.h> |
| #include <stdint.h> |
| |
| #include "base/time.h" |
| #include "base/xalloc.h" |
| #include "drivers/net/net.h" |
| #include "net/connection.h" |
| #include "net/ipv4/ipv4.h" |
| #include "net/ipv4/uip/arp.h" |
| #include "net/ipv4/uip/uip.h" |
| #include "net/netboot/dhcp.h" |
| |
| typedef enum DhcpOpcode |
| { |
| DhcpRequestOp = 1, |
| DhcpReplyOp = 2 |
| } DhcpOpcode; |
| |
| typedef enum DhcpHardwareType |
| { |
| DhcpEthernet = 1 |
| } DhcpHardwareType; |
| |
| static const uint16_t DhcpServerPort = 67; |
| static const uint16_t DhcpClientPort = 68; |
| |
| // Wait for a response for 3 seconds before resending a request. |
| static const uint64_t DhcpRespTimeoutUs = 3 * 1000 * 1000; |
| |
| typedef struct __attribute__((packed)) DhcpPacket |
| { |
| uint8_t opcode; |
| uint8_t hw_type; |
| uint8_t hw_length; |
| uint8_t hops; |
| uint32_t transaction_id; |
| uint16_t seconds; |
| uint16_t flags; |
| uint32_t client_ip; |
| uint32_t your_ip; |
| uint32_t server_ip; |
| uint32_t gateway_ip; |
| uint8_t client_hw_addr[16]; |
| uint8_t server_name[64]; |
| uint8_t bootfile_name[128]; |
| uint32_t cookie; |
| uint8_t options[308]; |
| } DhcpPacket; |
| |
| static const unsigned int DhcpMinPacketSize = 576; |
| static const unsigned int DhcpMaxPacketSize = |
| sizeof(DhcpPacket) + UIP_IPH_LEN + UIP_UDPH_LEN; |
| |
| static const uint8_t DhcpCookie[] = { 99, 130, 83, 99 }; |
| |
| typedef enum DhcpTags { |
| // "Vendor extensions". |
| DhcpTagPadding = 0, |
| DhcpTagSubnetMask = 1, |
| DhcpTagTimeOffset = 2, |
| DhcpTagDefaultRouter = 3, |
| DhcpTagTimeServer = 4, |
| DhcpTagDnsServer = 6, |
| DhcpTagLogServer = 7, |
| DhcpTagCookieServer = 8, |
| DhcpTagPrintServer = 9, |
| DhcpTagImpressServer = 10, |
| DhcpTagResourceLocationServer = 11, |
| DhcpTagHostName = 12, |
| DhcpTagBootFileSize = 13, |
| DhcpTagMeritDumpFile = 14, |
| DhcpTagDomainName = 15, |
| DhcpTagSwapServer = 16, |
| DhcpTagRootPath = 17, |
| DhcpTagExtensionsPath = 18, |
| |
| // IP layer paramters per host. |
| DhcpTagIpForwarding = 19, |
| DhcpTagNonLocalSourceRouting = 20, |
| DhcpTagPolicyFilter = 21, |
| DhcpTagMaximumDatagramSize = 22, |
| DhcpTagDefaultTimeToLive = 23, |
| DhcpTagPathMtuAgingTimeout = 24, |
| DhcpTagPathMtuPlateauTable = 25, |
| |
| // IP later parameters per interface. |
| DhcpTagInterfaceMtu = 26, |
| DhcpTagAllSubnetsAreLocal = 27, |
| DhcpTagBroadcastAddress = 28, |
| DhcpTagPerformMaskDiscovery = 29, |
| DhcpTagMaskSupplier = 30, |
| DhcpTagPerformRouterDiscovery = 31, |
| DhcpTagRouterSolicitationAddress = 32, |
| DhcpTagStaticRouteOption = 33, |
| |
| // Link layer paramters per interface. |
| DhcpTagTrailerEncapsulation = 34, |
| DhcpTagArpCacheTimeout = 35, |
| DhcpTagEthernetEncapsulation = 36, |
| |
| // Tcp parameters. |
| DhcpTagTcpDefaultTtl = 37, |
| DhcpTagTcpKeepaliveInterval = 38, |
| DhcpTagTcpKeepaliveGarbage = 39, |
| |
| // Application and service parameters. |
| DhcpTagNetworkInformationServiceDomain = 40, |
| DhcpTagNetworkInformationServers = 41, |
| DhcpTagNetworkTimeProtocolServers = 42, |
| DhcpTagVendorSpecificInformation = 43, |
| DhcpTagNetbiosOverTcpIpNameServer = 44, |
| DhcpTagNetbiosOverTcpIpDatagramDistributionServer = 45, |
| DhcpTagNetbiosOverTcpIpNodeType = 46, |
| DhcpTagNetbiosOverTcpIpScope = 47, |
| DhcpTagXWindowSystemFontServer = 48, |
| DhcpTagXWindowSystemDisplayManager = 49, |
| |
| // Dhcp extensions. |
| DhcpTagRequestedIpAddress = 50, |
| DhcpTagIpAddressLeaseTime = 51, |
| DhcpTagOptionOverload = 52, |
| DhcpTagMessageType = 53, |
| DhcpTagServerIdentifier = 54, |
| DhcpTagParameterRequestList = 55, |
| DhcpTagMessage = 56, |
| DhcpTagMaximumDhcpMessageSize = 57, |
| DhcpTagRenewalTimeValue = 58, |
| DhcpTagRebindTimeValue = 59, |
| DhcpTagClassIdentifier = 60, |
| DhcpTagClientIdentifier = 61, |
| |
| DhcpTagEndOfList = 255 |
| } DhcpTags; |
| |
| typedef enum DhcpState |
| { |
| DhcpInit, |
| DhcpRequesting, |
| DhcpBound |
| } DhcpState; |
| |
| typedef enum DhcpMessageType |
| { |
| DhcpNoMessageType = 0, |
| DhcpDiscover = 1, |
| DhcpOffer = 2, |
| DhcpRequest = 3, |
| DhcpDecline = 4, |
| DhcpAck = 5, |
| DhcpNak = 6, |
| DhcpRelease = 7 |
| } DhcpMessageType; |
| |
| enum { |
| OptionOverloadNone = 0, |
| OptionOverloadFile = 1, |
| OptionOverloadSname = 2, |
| OptionOverloadBoth = 3 |
| }; |
| |
| static void uip_ipaddr_from_int(uip_ipaddr_t *ip, uint32_t num) |
| { |
| uip_ipaddr(ip, num >> 0, num >> 8, num >> 16, num >> 24); |
| } |
| |
| static void uip_ipaddr_from_array(uip_ipaddr_t *ip, uint8_t *array) |
| { |
| uip_ipaddr(ip, array[0], array[1], array[2], array[3]); |
| } |
| |
| static uint32_t uip_ipaddr_to_int(uip_ipaddr_t ip) |
| { |
| return (uip_ipaddr1(&ip) << 0) | (uip_ipaddr2(&ip) << 8) | |
| (uip_ipaddr2(&ip) << 16) | (uip_ipaddr3(&ip) << 24); |
| } |
| |
| typedef int (*DhcpOptionFunc)(uint8_t tag, uint8_t length, uint8_t *value, |
| void *data); |
| |
| static int dhcp_process_options(DhcpPacket *packet, int overload, |
| DhcpOptionFunc func, void *data) |
| { |
| uint8_t *options; |
| int size; |
| switch (overload) { |
| case OptionOverloadNone: |
| options = packet->options; |
| size = sizeof(packet->options); |
| break; |
| case OptionOverloadFile: |
| options = packet->bootfile_name; |
| size = sizeof(packet->bootfile_name); |
| break; |
| case OptionOverloadSname: |
| options = packet->server_name; |
| size = sizeof(packet->server_name); |
| break; |
| case OptionOverloadBoth: |
| options = packet->server_name; |
| size = sizeof(packet->server_name) + |
| sizeof(packet->bootfile_name); |
| break; |
| default: |
| return 1; |
| } |
| |
| int pos = 0; |
| |
| int new_overload = 0; |
| |
| while (pos < size) { |
| uint8_t tag = options[pos++]; |
| if (pos >= size) |
| return 1; |
| uint8_t length = options[pos++]; |
| if (pos >= size) |
| return 1; |
| uint8_t *value = &options[pos]; |
| pos += length; |
| if (pos >= size) |
| return 1; |
| |
| // Handle "end of list" and "option overload" options. |
| if (tag == DhcpTagEndOfList) |
| break; |
| if (tag == DhcpTagOptionOverload) { |
| if (length < 1) |
| return 1; |
| new_overload = *value; |
| continue; |
| } |
| |
| // Otherwise, use the provided callback. |
| assert(func); |
| if (func(tag, length, value, data)) |
| return 1; |
| } |
| |
| if (!overload && new_overload) |
| return dhcp_process_options(packet, new_overload, func, data); |
| |
| return 0; |
| } |
| |
| static int dhcp_get_type(uint8_t tag, uint8_t length, uint8_t *value, |
| void *data) |
| { |
| if (tag != DhcpTagMessageType) |
| return 0; |
| if (length < 1) |
| return 1; |
| |
| DhcpMessageType *type = (DhcpMessageType *)data; |
| *type = (DhcpMessageType)*value; |
| |
| return 0; |
| } |
| |
| static int dhcp_get_server(uint8_t tag, uint8_t length, uint8_t *value, |
| void *data) |
| { |
| if (tag != DhcpTagServerIdentifier) |
| return 0; |
| if (length < sizeof(uint32_t)) |
| return 1; |
| |
| memcpy(data, value, sizeof(uint32_t)); |
| |
| return 0; |
| } |
| |
| static int dhcp_apply_options(uint8_t tag, uint8_t length, uint8_t *value, |
| void *data) |
| { |
| switch (tag) { |
| case DhcpTagSubnetMask: |
| if (length < 4) { |
| printf("Truncated subnet mask.\n"); |
| return 1; |
| } else { |
| uip_ipaddr_t netmask; |
| uip_ipaddr_from_array(&netmask, value); |
| uip_setnetmask(&netmask); |
| return 0; |
| } |
| case DhcpTagDefaultRouter: |
| if (length < 4) { |
| printf("Truncated routers.\n"); |
| return 1; |
| } else { |
| uip_ipaddr_t router; |
| uip_ipaddr_from_array(&router, value); |
| uip_setdraddr(&router); |
| return 0; |
| } |
| } |
| return 0; |
| } |
| |
| int dhcp_check_for_reply(NetConOps *con, DhcpState state, |
| DhcpPacket *in, DhcpPacket *out) |
| { |
| size_t incoming; |
| if (netcon_incoming(con, &incoming) || !incoming) |
| return 0; |
| |
| // If the packet is too big, ignore it. |
| if (incoming > sizeof(*in)) { |
| netcon_receive(con, NULL, &incoming, incoming); |
| return 0; |
| } |
| |
| // Receive the packet. |
| memset(in, 0, sizeof(*in)); |
| if (netcon_receive(con, in, &incoming, sizeof(*in))) |
| return 0; |
| |
| // Check for problems in the reply. |
| if (in->opcode != DhcpReplyOp || |
| in->hw_length != out->hw_length || |
| memcmp(in->client_hw_addr, |
| out->client_hw_addr, |
| out->hw_length) || |
| (in->transaction_id != out->transaction_id)) { |
| return 0; |
| } |
| |
| if (memcmp(&in->cookie, DhcpCookie, sizeof(DhcpCookie))) |
| return 0; |
| |
| DhcpMessageType type = DhcpNoMessageType; |
| if (dhcp_process_options(in, OptionOverloadNone, &dhcp_get_type, &type)) |
| return 0; |
| |
| switch (state) { |
| case DhcpInit: |
| if (type != DhcpOffer) |
| return 0; |
| break; |
| case DhcpRequesting: |
| if (type != DhcpAck && type != DhcpNak) |
| return 0; |
| break; |
| case DhcpBound: |
| // We shouldn't get any more packets once we're bound. |
| break; |
| } |
| |
| // Everything checks out. We have a valid reply. |
| return 1; |
| } |
| |
| static int dhcp_send_packet(NetConOps *con, DhcpState state, const char *name, |
| DhcpPacket *out, DhcpPacket *in) |
| { |
| // Send the outbound packet. |
| printf("Sending %s... ", name); |
| if (netcon_send(con, out, sizeof(*out))) |
| return 1; |
| printf("done.\n"); |
| |
| // Prepare for the reply. |
| printf("Waiting for reply... "); |
| uint64_t start = time_us(0); |
| while (!dhcp_check_for_reply(con, state, in, out)) { |
| if (time_us(start) > DhcpRespTimeoutUs) { |
| if (netcon_send(con, out, sizeof(*out))) |
| return 1; |
| start = time_us(0); |
| } |
| } |
| printf("done.\n"); |
| |
| return 0; |
| } |
| |
| static void dhcp_prep_packet(DhcpPacket *packet, uint32_t transaction_id) |
| { |
| memset(packet, 0, sizeof(*packet)); |
| packet->opcode = DhcpRequestOp; |
| packet->hw_type = DhcpEthernet; |
| packet->hw_length = sizeof(uip_ethaddr); |
| packet->hops = 0; |
| packet->transaction_id = transaction_id; |
| packet->seconds = htonw(100); |
| assert(sizeof(uip_ethaddr) < sizeof(packet->client_hw_addr)); |
| memcpy(packet->client_hw_addr, &uip_ethaddr, sizeof(uip_ethaddr)); |
| memcpy(&packet->cookie, DhcpCookie, sizeof(DhcpCookie)); |
| } |
| |
| static void dhcp_add_option(uint8_t **options, uint8_t tag, void *value, |
| uint8_t length, int *remaining) |
| { |
| assert(*remaining >= length + 2); |
| (*options)[0] = tag; |
| (*options)[1] = length; |
| if (length) { |
| assert(value); |
| memcpy(*options + 2, value, length); |
| } |
| *remaining -= length + 2; |
| *options += length + 2; |
| } |
| |
| static int dhcp_request_work(NetConOps *con, uip_ipaddr_t *next_ip, |
| uip_ipaddr_t *server_ip, const char **bootfile) |
| { |
| DhcpPacket out, in; |
| uint8_t byte; |
| uint8_t *options; |
| int remaining; |
| uint8_t requested[] = { DhcpTagSubnetMask, DhcpTagDefaultRouter }; |
| assert(DhcpMaxPacketSize >= DhcpMinPacketSize); |
| uint16_t max_size = htonw(DhcpMaxPacketSize); |
| uint8_t client_id[1 + sizeof(uip_ethaddr)]; |
| client_id[0] = DhcpEthernet; |
| memcpy(client_id + 1, &uip_ethaddr, sizeof(uip_ethaddr)); |
| |
| // Send a DHCP discover packet. |
| dhcp_prep_packet(&out, rand()); |
| options = out.options; |
| remaining = sizeof(out.options); |
| byte = DhcpDiscover; |
| dhcp_add_option(&options, DhcpTagMessageType, &byte, sizeof(byte), |
| &remaining); |
| dhcp_add_option(&options, DhcpTagClientIdentifier, client_id, |
| sizeof(client_id), &remaining); |
| dhcp_add_option(&options, DhcpTagParameterRequestList, requested, |
| sizeof(requested), &remaining); |
| dhcp_add_option(&options, DhcpTagMaximumDhcpMessageSize, |
| &max_size, sizeof(max_size), &remaining); |
| dhcp_add_option(&options, DhcpTagEndOfList, NULL, 0, &remaining); |
| if (dhcp_send_packet(con, DhcpInit, "DHCP discover", &out, &in)) |
| return 1; |
| |
| // Extract the DHCP server id. |
| uint32_t server_id; |
| if (dhcp_process_options(&in, OptionOverloadNone, &dhcp_get_server, |
| &server_id)) { |
| printf("Failed to extract server id.\n"); |
| return 1; |
| } |
| |
| // We got an offer. Request it. |
| dhcp_prep_packet(&out, rand()); |
| options = out.options; |
| remaining = sizeof(out.options); |
| byte = DhcpRequest; |
| dhcp_add_option(&options, DhcpTagMessageType, &byte, sizeof(byte), |
| &remaining); |
| dhcp_add_option(&options, DhcpTagClientIdentifier, client_id, |
| sizeof(client_id), &remaining); |
| dhcp_add_option(&options, DhcpTagRequestedIpAddress, &in.your_ip, |
| sizeof(in.your_ip), &remaining); |
| dhcp_add_option(&options, DhcpTagParameterRequestList, requested, |
| sizeof(requested), &remaining); |
| dhcp_add_option(&options, DhcpTagMaximumDhcpMessageSize, |
| &max_size, sizeof(max_size), &remaining); |
| dhcp_add_option(&options, DhcpTagServerIdentifier, |
| &server_id, sizeof(server_id), &remaining); |
| dhcp_add_option(&options, DhcpTagEndOfList, NULL, 0, &remaining); |
| if (dhcp_send_packet(con, DhcpRequesting, "DHCP request", &out, &in)) |
| return 1; |
| |
| DhcpMessageType type; |
| if (dhcp_process_options(&in, OptionOverloadNone, &dhcp_get_type, |
| &type)) { |
| printf("Failed to extract message type.\n"); |
| return 1; |
| } |
| if (type == DhcpNak) { |
| printf("DHCP request nak-ed by the server.\n"); |
| return 1; |
| } |
| |
| // The server acked, completing the transaction. |
| |
| // Apply the settings. |
| if (dhcp_process_options(&in, OptionOverloadNone, |
| &dhcp_apply_options, NULL)) { |
| return 1; |
| } |
| |
| int bootfile_size = sizeof(in.bootfile_name) + 1; |
| char *file = xmalloc(bootfile_size); |
| file[bootfile_size - 1] = 0; |
| memcpy(file, in.bootfile_name, sizeof(in.bootfile_name)); |
| *bootfile = file; |
| uip_ipaddr_from_int(next_ip, in.server_ip); |
| uip_ipaddr_from_int(server_ip, server_id); |
| |
| uip_ipaddr_t my_ip; |
| uip_ipaddr_from_int(&my_ip, in.your_ip); |
| uip_sethostaddr(&my_ip); |
| |
| return 0; |
| } |
| |
| int dhcp_request(NetDevice *dev, uip_ipaddr_t *next_ip, |
| uip_ipaddr_t *server_ip, const char **bootfile) |
| { |
| // Set up the UDP connection. |
| uip_ipaddr_t addr; |
| uip_ipaddr(&addr, 255,255,255,255); |
| Ipv4UdpCon *con = new_ipv4_udp_con(dev, addr, DhcpServerPort); |
| int ret = netcon_bind(&con->ops, DhcpClientPort); |
| // We shouldn't attempt to DHCP request if we failed to bind. |
| ret = ret || dhcp_request_work(&con->ops, next_ip, server_ip, bootfile); |
| // Failure should not prevent closing the connection. |
| ret = netcon_close(&con->ops) || ret; |
| free(con); |
| return ret; |
| } |
| |
| static int dhcp_release_work(NetConOps *con) |
| { |
| DhcpPacket release; |
| |
| // Prepare the DHCP release packet. |
| dhcp_prep_packet(&release, rand()); |
| uip_ipaddr_t my_ip; |
| uip_gethostaddr(&my_ip); |
| release.client_ip = uip_ipaddr_to_int(my_ip); |
| |
| uint8_t *options = release.options; |
| int remaining = sizeof(release.options); |
| uint8_t byte = DhcpRelease; |
| dhcp_add_option(&options, DhcpTagMessageType, &byte, sizeof(byte), |
| &remaining); |
| dhcp_add_option(&options, DhcpTagEndOfList, NULL, 0, &remaining); |
| |
| return netcon_send(con, &release, sizeof(release)); |
| } |
| |
| int dhcp_release(NetDevice *dev, uip_ipaddr_t server_ip) |
| { |
| // Set up the UDP connection. |
| Ipv4UdpCon *con = new_ipv4_udp_con(dev, server_ip, DhcpServerPort); |
| int ret = netcon_bind(&con->ops, DhcpClientPort); |
| // We shouldn't attempt to DHCP release if we failed to bind. |
| ret = ret || dhcp_release_work(&con->ops); |
| // Failure should not prevent closing the connection. |
| ret = netcon_close(&con->ops) || ret; |
| free(con); |
| return ret; |
| } |