blob: 7f1cd7200a13625a47bd2449867de4c35648c5fc [file] [log] [blame]
/*
* 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;
}