view dhcp.c @ 0:49aca2b065f8 draft

Add dhcpcd-3 re-write
author Roy Marples <roy@marples.name>
date Mon, 27 Nov 2006 20:23:22 +0000
parents
children 1405eec5277b
line wrap: on
line source

/*
 * dhcpcd - DHCP client daemon -
 * Copyright (C) 2005 - 2006 Roy Marples <uberlord@gentoo.org>
 * 
 * dhcpcd is an RFC2131 compliant DHCP client daemon.
 *
 * This 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <sys/socket.h>
#include <sys/types.h>

#include <netinet/in.h>
#include <net/if_arp.h>

#include <limits.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>

#include "common.h"
#include "dhcp.h"
#include "interface.h"
#include "logger.h"
#include "socket.h"

static char *dhcp_message[] = {
  [DHCP_DISCOVER] 	= "DHCP_DISCOVER",
  [DHCP_OFFER]		= "DHCP_OFFER",
  [DHCP_REQUEST]	= "DHCP_REQUEST",
  [DHCP_DECLINE]	= "DHCP_DECLINE",
  [DHCP_ACK]		= "DHCP_ACK",
  [DHCP_NAK]		= "DHCP_NAK",
  [DHCP_RELEASE]	= "DHCP_RELEASE",
  [DHCP_INFORM]		= "DHCP_INFORM",
  [DHCP_INFORM + 1]	= NULL
};

size_t send_message (interface_t *iface, dhcp_t *dhcp,
		     unsigned long xid, char type, options_t *options)
{
  dhcpmessage_t message;
  unsigned char *p = (unsigned char *) &message.options;
  unsigned char *n_params = NULL;
  unsigned long l;

  if (!iface || !options || !dhcp)
    return -1;

  memset (&message, 0, sizeof (dhcpmessage_t));

  message.op = DHCP_BOOTREQUEST;
  message.hwtype = ARPHRD_ETHER;
  message.hwlen = ETHER_ADDR_LEN;
  message.secs = htons (10);
  message.xid = xid;
  memcpy (&message.hwaddr, &iface->ethernet_address, ETHER_ADDR_LEN);
  message.cookie = htonl(MAGIC_COOKIE);

  if (iface->previous_address.s_addr != 0 &&
      iface->previous_address.s_addr == dhcp->address.s_addr)
    message.ciaddr = iface->previous_address.s_addr; 

  *p++ = DHCP_MESSAGETYPE; 
  *p++ = 1;
  *p++ = type;

  if (type == DHCP_REQUEST)
    {
      *p++ = DHCP_MAXMESSAGESIZE;
      *p++ = 2;
      uint16_t sz = htons (sizeof (struct udp_dhcp_packet));
      memcpy (p, &sz, 2);
      p += 2;
    }

  if (dhcp->address.s_addr != 0 && iface->previous_address.s_addr == 0)
    {
      *p++ = DHCP_ADDRESS;
      *p++ = 4;
      memcpy (p, &dhcp->address.s_addr, 4);
      p += 4;
    }

  if (dhcp->serveraddress.s_addr != 0 && dhcp->address.s_addr !=0 &&
      iface->previous_address.s_addr == 0)
    {
      *p++ = DHCP_SERVERIDENTIFIER;
      *p++ = 4;
      memcpy (p, &dhcp->serveraddress.s_addr, 4);
      p += 4;

      /* Blank out the server address so we broadcast */
      if (type == DHCP_REQUEST) dhcp->serveraddress.s_addr = 0;
    }

  if (type == DHCP_REQUEST || type == DHCP_DISCOVER)
    {
      if (dhcp->leasetime > 0)
	{
	  *p++ = DHCP_LEASETIME;
	  *p++ = 4;
	  uint32_t ul = htonl (dhcp->leasetime);
	  memcpy (p, &ul, 4);
	  p += 4;
	}
    }

  *p++ = DHCP_PARAMETERREQUESTLIST;
  n_params = p;
  *p++ = 0;

  if (type == DHCP_REQUEST)
    {
      *p++ = DHCP_RENEWALTIME;
      *p++ = DHCP_REBINDTIME;
      *p++ = DHCP_NETMASK;
      *p++ = DHCP_BROADCAST;
      *p++ = DHCP_CSR;
      /* RFC 3442 states classless static routes should be before routers
       * and static routes as classless static routes override them both */
      *p++ = DHCP_ROUTERS;
      *p++ = DHCP_STATICROUTE;
      *p++ = DHCP_HOSTNAME;
      *p++ = DHCP_DNSSEARCH;
      *p++ = DHCP_DNSDOMAIN;
      *p++ = DHCP_DNSSERVER;
      *p++ = DHCP_NISDOMAIN;
      *p++ = DHCP_NISSERVER;
      *p++ = DHCP_NTPSERVER;
      /* These parameters were requested by dhcpcd-2.0 and earlier
	 but we never did anything with them */
      /*    *p++ = DHCP_DEFAULTIPTTL;
       *p++ = DHCP_MASKDISCOVERY;
       *p++ = DHCP_ROUTERDISCOVERY; */
    }
  else
    /* Always request one parameter so we don't get the server default
       when we don't actally need any at this time */
    *p++ = DHCP_DNSSERVER;

  *n_params = p - n_params - 1;

  if (type == DHCP_REQUEST)
    {
      if (options->hostname) 
	{
	  if (options->fqdn == FQDN_DISABLE)
	    {
	      *p++ = DHCP_HOSTNAME;
	      *p++ = l = strlen (options->hostname);
	      memcpy (p, options->hostname, l);
	      p += l;
	    }
	  else
	    {
	      /* Draft IETF DHC-FQDN option (81) */
	      *p++ = DHCP_FQDN;
	      *p++ = (l = strlen (options->hostname)) + 3;
	      /* Flags: 0000NEOS
	       * S: 1 => Client requests Server to update A RR in DNS as well as PTR
	       * O: 1 => Server indicates to client that DNS has been updated
	       * E: 1 => Name data is DNS format
	       * N: 1 => Client requests Server to not update DNS
	       */
	      *p++ = options->fqdn & 0x9;
	      *p++ = 0; /* rcode1, response from DNS server for PTR RR */
	      *p++ = 0; /* rcode2, response from DNS server for A RR if S=1 */
	      memcpy (p, options->hostname, l);
	      p += l;
	    }
	}
    }

  if (options->userclass)
    {
      *p++ = DHCP_USERCLASS;
      *p++ = l = strlen (options->userclass);
      memcpy (p, options->userclass, l);
      p += l;
    }

  *p++ = DHCP_CLASSID;
  *p++ = l = strlen (options->classid);
  memcpy (p, options->classid, l);
  p += l;

  *p++ = DHCP_CLIENTID;
  if (options->clientid[0])
    {
      l = strlen (options->clientid);
      *p++ = l + 1;
      *p++ = 0; /* string */
      memcpy (p, options, l);
      p += l;
    }
  else
    {
      *p++ = ETHER_ADDR_LEN + 1;
      *p++ = ARPHRD_ETHER;
      memcpy (p, &iface->ethernet_address, ETHER_ADDR_LEN);
      p += ETHER_ADDR_LEN;
    }

  *p = DHCP_END;

  struct udp_dhcp_packet packet;
  memset (&packet, 0, sizeof (struct udp_dhcp_packet));
  make_dhcp_packet (&packet, (unsigned char *) &message,
		    dhcp->address, dhcp->serveraddress);

  logger (LOG_DEBUG, "Sending %s with xid %d", dhcp_message[(int) type], xid);
  return send_packet (iface, ETHERTYPE_IP, (unsigned char *) &packet,
		      sizeof (struct udp_dhcp_packet));
}

static unsigned long getnetmask (unsigned long ip_in)
{
  unsigned long t, p = ntohl (ip_in);

  if (IN_CLASSA (p))
    t = ~IN_CLASSA_NET;
  else
    {
      if (IN_CLASSB (p))
	t = ~IN_CLASSB_NET;
      else
	{
	  if (IN_CLASSC (p))
	    t = ~IN_CLASSC_NET;
	  else
	    t = 0;
	}
    }
  while (t & p) t >>= 1;
  return htonl (~t);
}

/* Decode an RFC3397 DNS search order option into a space
   seperated string. Returns length of string (including 
   terminating zero) or zero on error. out may be NULL
   to just determine output length. */
static unsigned int decode_search (u_char *p, int len, char *out)
{
  u_char *r, *q = p;
  unsigned int count = 0, l, hops;

  while (q - p < len)
    {
      r = NULL;
      hops = 0;
      while ((l = *q++))
	{
	  unsigned int label_type = l & 0xc0;
	  if (label_type == 0x80 || label_type == 0x40)
	    return 0;
	  else if (label_type == 0xc0) /* pointer */
	    { 
	      l = (l & 0x3f) << 8;
	      l |= *q++;

	      /* save source of first jump. */
	      if (!r)
		r = q;

	      hops++;
	      if (hops > 255)
		return 0;

	      q = p + l;
	      if (q - p >= len)
		return 0;
	    }
	  else 
	    {
	      /* straightforward name segment, add with '.' */
	      count += l + 1;
	      if (out)
		{
		  memcpy (out, q, l);
		  out += l;
		  *out++ = '.';
		}
	      q += l;
	    }
	}

      /* change last dot to space */
      if (out)
	*(out - 1) = ' ';

      if (r)
	q = r;
    }

  /* change last space to zero terminator */
  if (out)
    *(out - 1) = 0;

  return count;  
}

/* Add our classless static routes to the routes variable
 * and return the last route set */
static route_t *decodeCSR(unsigned char *p, int len)
{
  /* Minimum is 5 -first is CIDR and a router length of 4 */
  if (len < 5)
    return NULL;

  unsigned char *q = p;
  int cidr;
  int ocets;
  route_t *first = xmalloc (sizeof (route_t));
  route_t *route = first;

  while (q - p < len)
    {
      memset (route, 0, sizeof (route_t));

      cidr = (int) *q++;
      if (cidr == 0)
	ocets = 0;
      else if (cidr < 9)
	ocets = 1;
      else if (cidr < 17)
	ocets = 2;
      else if (cidr < 25)
	ocets = 3;
      else
	ocets = 4;

      if (ocets > 0)
	{
	  memcpy (&route->destination.s_addr, q, ocets);
	  q += ocets;
	}

      /* Now enter the netmask */
      if (ocets > 0)
	{
	  memset (&route->netmask.s_addr, 255, ocets - 1);
	  memset ((unsigned char *) &route->netmask.s_addr + (ocets - 1),
		  (256 - (1 << (32 - cidr) % 8)), 1);
	}

      /* Finally, snag the router */
      memcpy (&route->gateway.s_addr, q, 4);
      q += 4;

      /* We have another route */
      if (q - p < len)
	{
	  route->next = xmalloc (sizeof (route_t));
	  route = route->next;
	}
    }

  return first;
}

void free_dhcp (dhcp_t *dhcp)
{
  if (!dhcp)
    return;

  if (dhcp->routes)
    free_route (dhcp->routes);

  if (dhcp->hostname)
    free (dhcp->hostname);

  if (dhcp->dnsservers)
    free_address (dhcp->dnsservers);
  if (dhcp->dnsdomain)
    free (dhcp->dnsdomain);
  if (dhcp->dnssearch)
    free (dhcp->dnssearch);

  if (dhcp->ntpservers)
    free_address (dhcp->ntpservers);

  if (dhcp->nisdomain)
    free (dhcp->nisdomain);
  if (dhcp->nisservers)
    free_address (dhcp->nisservers);

  if (dhcp->rootpath)
    free (dhcp->rootpath);

  if (dhcp->fqdn)
    {
      if (dhcp->fqdn->name)
	free (dhcp->fqdn->name);
      free (dhcp->fqdn);
    }
}

static void dhcp_add_address(address_t *address, unsigned char *data, int length)
{
  int i;
  address_t *p = address;

  for (i = 0; i < length; i += 4)
    {
      memset (p, 0, sizeof (address_t));
      memcpy (&p->address.s_addr, data + i, 4);
      if (length - i > 4)
	{
	  p->next = xmalloc (sizeof (address_t));
	  p = p->next;
	}
    }
}

int parse_dhcpmessage (dhcp_t *dhcp, dhcpmessage_t *message)
{
  unsigned char *p = message->options;
  unsigned char option;
  unsigned char length;
  unsigned char *end = message->options + sizeof (message->options);
  unsigned int len = 0;
  int i;
  int retval = -1;
  route_t *first_route = xmalloc (sizeof (route_t));
  route_t *route = first_route;
  route_t *last_route = NULL;
  route_t *csr = NULL;
  char classid[CLASS_ID_MAX_LEN];
  char clientid[CLIENT_ID_MAX_LEN];

  memset (first_route, 0, sizeof (route_t));

  /* The message back never has the class or client id's so we save them */
  strcpy (classid, dhcp->classid);
  strcpy (clientid, dhcp->clientid);

  free_dhcp (dhcp);
  memset (dhcp, 0, sizeof (dhcp_t));

  dhcp->address.s_addr = message->yiaddr;
  strcpy (dhcp->servername, message->servername);

  while (p < end)
    {
      option = *p++;
      if (!option)
	continue;

      length = *p++;

      if (p + length >= end)
	{
	  retval = -1;
	  goto eexit;
	}

      switch (option)
	{
	case DHCP_END:
	  goto eexit;

	case DHCP_MESSAGETYPE:
	  retval = (int) *p;
	  break;

	case DHCP_ADDRESS:
	  memcpy (&dhcp->address.s_addr, p, 4);
	  break;
	case DHCP_NETMASK:
	  memcpy (&dhcp->netmask.s_addr, p, 4);
	  break;
	case DHCP_BROADCAST:
	  memcpy (&dhcp->broadcast.s_addr, p, 4);
	  break;
	case DHCP_SERVERIDENTIFIER:
	  memcpy (&dhcp->serveraddress.s_addr, p, 4);
	  break;

	case DHCP_LEASETIME:
	  dhcp->leasetime = ntohl (* (uint32_t *) p);
	  break;
	case DHCP_RENEWALTIME:
	  dhcp->renewaltime = ntohl (* (uint32_t *) p);
	  break;
	case DHCP_REBINDTIME:
	  dhcp->rebindtime = ntohl (* (uint32_t *) p);
	  break;
	case DHCP_MTU:
	  dhcp->mtu = ntohs (* (uint16_t *) p);
	  /* Minimum legal mtu is 68 */
	  if (dhcp->mtu > 0 && dhcp->mtu < 68)
	    dhcp->mtu = 68;
	  break;

	case DHCP_HOSTNAME:
	  if (dhcp->hostname)
	    free (dhcp->hostname);
	  dhcp->hostname = xmalloc (length + 1);
	  memcpy (dhcp->hostname, p, length);
	  dhcp->hostname[length] = '\0';
	  break;

	case DHCP_DNSDOMAIN:
	  if (dhcp->dnsdomain)
	    free (dhcp->dnsdomain);
	  dhcp->dnsdomain = xmalloc (length + 1);
	  memcpy (dhcp->dnsdomain, p, length);
	  dhcp->dnsdomain[length] = '\0';
	  break;

	case DHCP_MESSAGE:
	  if (dhcp->message)
	    free (dhcp->message);
	  dhcp->message = xmalloc (length + 1);
	  memcpy (dhcp->message, p, length);
	  dhcp->message[length] = '\0';
	  break;

	case DHCP_ROOTPATH:
	  if (dhcp->rootpath)
	    free (dhcp->rootpath);
	  dhcp->rootpath = xmalloc (length + 1);
	  memcpy (dhcp->rootpath, p, length);
	  dhcp->rootpath[length] = '\0';
	  break;

	case DHCP_NISDOMAIN:
	  if (dhcp->nisdomain)
	    free (dhcp->nisdomain);
	  dhcp->nisdomain = xmalloc (length + 1);
	  memcpy (dhcp->nisdomain, p, length);
	  dhcp->nisdomain[length] = '\0';
	  break;

	case DHCP_DNSSERVER:
	  if (dhcp->dnsservers)
	    free_address (dhcp->dnsservers);
	  dhcp->dnsservers = xmalloc (sizeof (address_t));
	  dhcp_add_address (dhcp->dnsservers, p, length);
	  break;
	case DHCP_NTPSERVER:
	  if (dhcp->ntpservers)
	    free_address (dhcp->ntpservers);
	  dhcp->ntpservers = xmalloc (sizeof (address_t));
	  dhcp_add_address (dhcp->ntpservers, p, length);
	  break;
	case DHCP_NISSERVER:
	  if (dhcp->nisservers)
	    free_address (dhcp->nisservers);
	  dhcp->nisservers = xmalloc (sizeof (address_t));
	  dhcp_add_address (dhcp->nisservers, p, length);
	  break;

	case DHCP_DNSSEARCH:
	  if (dhcp->dnssearch)
	    free (dhcp->dnssearch);
	  if ((len = decode_search (p, length, NULL)))
	    {
	      dhcp->dnssearch = xmalloc (len);
	      decode_search (p, length, dhcp->dnssearch);
	    }
	  break;

	case DHCP_CSR:
	  csr = decodeCSR (p, length);
	  break;

	case DHCP_STATICROUTE:
	  for (i = 0; i < length; i += 8)
	    {
	      memcpy (&route->destination.s_addr, p + i, 4);
	      memcpy (&route->gateway.s_addr, p + i + 4, 4);
	      route->netmask.s_addr = getnetmask (route->destination.s_addr); 
	      last_route = route;
	      route->next = xmalloc (sizeof (route_t));
	      route = route->next;
	      memset (route, 0, sizeof (route_t));
	    }
	  break;

	case DHCP_ROUTERS:
	  for (i = 0; i < length; i += 4)
	    {
	      memcpy (&route->gateway.s_addr, p + i, 4);
	      last_route = route;
	      route->next = xmalloc (sizeof (route_t));
	      route = route->next;
	      memset (route, 0, sizeof (route_t));
	    }
	  break;

	default:
	  logger (LOG_DEBUG, "no facility to parse DHCP code %u", option);
	  break;
	}

      p += length;
    }

eexit:
  /* Fill in any missing fields */
  if (!dhcp->netmask.s_addr)
    dhcp->netmask.s_addr = getnetmask (dhcp->address.s_addr);
  if (!dhcp->broadcast.s_addr)
    dhcp->broadcast.s_addr = dhcp->address.s_addr | ~dhcp->netmask.s_addr;

  /* If we have classess static routes then we discard
     static routes and routers according to RFC 3442 */
  if (csr)
    {
      dhcp->routes = csr;
      free_route (first_route); 
    }
  else
    {
      dhcp->routes = first_route;
      if (last_route)
	{
	  free (last_route->next);
	  last_route->next = NULL;
	}
      else
	{
	  free_route (dhcp->routes);
	  dhcp->routes = NULL;
	}
    }

  /* The message back never has the class or client id's so we restore them */
  strcpy (dhcp->classid, classid);
  strcpy (dhcp->clientid, clientid);

  return retval;
}