summaryrefslogblamecommitdiffstats
path: root/src/customdhcpcd/socket.c
blob: 9cb1227648a96718b646012dd9b48de748669b1f (plain) (tree)


































































                                                                             




                                                 



                                                 




                                                           
 


                                                          
 


                                                      
 

                                           
 


                                                               
 

                                                             
 

                                

  




                                                            
 


                                                          
 

                                                             
 

                                




                                








                                                                




                                                            

























                                     


                                                     

                                                                 
 




































                                                                       



                                                  










































                                                                       

      


                                
 
                




                                                  













































































                                                                    


                                                        
                                                           
 
























                                                                      




                                                                        

                                                           
 
























































































                                                                       





                                                  











































































































                                                                      


                                                        
                                                           
 


































                                                                     




                                                                      

                                                           
 



















































                                                                         


     



                                                                              
      
/*
 * dhcpcd - DHCP client daemon
 * Copyright 2006-2008 Roy Marples <roy@marples.name>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <net/if.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#define __FAVOR_BSD /* Nasty hack so we can use BSD semantics for UDP */
#include <netinet/udp.h>
#undef __FAVOR_BSD
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#if defined(BSD) || defined(__FreeBSD_kernel__)
# include <net/bpf.h>
#elif __linux__
# include <linux/filter.h>
# include <netpacket/packet.h>
# define bpf_insn sock_filter
#endif

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

/* A suitably large buffer for all transactions.
 * BPF buffer size is set by the kernel, so no define. */
#ifdef __linux__
# define BUFFER_LENGTH 4096
#endif

/* Broadcast address for IPoIB */
static const uint8_t ipv4_bcast_addr[] =
{
  0x00, 0xff, 0xff, 0xff,
  0xff, 0x12, 0x40, 0x1b, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff
};

/* Credit where credit is due :)
 * The below BPF filter is taken from ISC DHCP */
static struct bpf_insn dhcp_bpf_filter [] =
{
  /* Make sure this is an IP packet... */
  BPF_STMT (BPF_LD + BPF_H + BPF_ABS, 12),
  BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 8),

  /* Make sure it's a UDP packet... */
  BPF_STMT (BPF_LD + BPF_B + BPF_ABS, 23),
  BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 6),

  /* Make sure this isn't a fragment... */
  BPF_STMT (BPF_LD + BPF_H + BPF_ABS, 20),
  BPF_JUMP (BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 4, 0),

  /* Get the IP header length... */
  BPF_STMT (BPF_LDX + BPF_B + BPF_MSH, 14),

  /* Make sure it's to the right port... */
  BPF_STMT (BPF_LD + BPF_H + BPF_IND, 16),
  BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, DHCP_CLIENT_PORT, 0, 1),

  /* If we passed all the tests, ask for the whole packet. */
  BPF_STMT (BPF_RET + BPF_K, ~0U),

  /* Otherwise, drop it. */
  BPF_STMT (BPF_RET + BPF_K, 0),
};

static struct bpf_insn arp_bpf_filter [] =
{
  /* Make sure this is an ARP packet... */
  BPF_STMT (BPF_LD + BPF_H + BPF_ABS, 12),
  BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_ARP, 0, 3),

  /* Make sure this is an ARP REPLY... */
  BPF_STMT (BPF_LD + BPF_H + BPF_ABS, 20),
  BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REPLY, 0, 1),

  /* If we passed all the tests, ask for the whole packet. */
  BPF_STMT (BPF_RET + BPF_K, ~0U),

  /* Otherwise, drop it. */
  BPF_STMT (BPF_RET + BPF_K, 0),
};

void setup_packet_filters (void)
{
#ifdef __linux__
  /* We need to massage the filters for Linux cooked packets */
  dhcp_bpf_filter[1].jf = 0; /* skip the IP packet type check */
  dhcp_bpf_filter[2].k -= ETH_HLEN;
  dhcp_bpf_filter[4].k -= ETH_HLEN;
  dhcp_bpf_filter[6].k -= ETH_HLEN;
  dhcp_bpf_filter[7].k -= ETH_HLEN;

  arp_bpf_filter[1].jf = 0; /* skip the IP packet type check */
  arp_bpf_filter[2].k -= ETH_HLEN;
#endif
}

static uint16_t checksum (unsigned char *addr, uint16_t len)
{
  uint32_t sum = 0;
  union
  {
    unsigned char *addr;
    uint16_t *i;
  } p;
  uint16_t nleft = len;

  p.addr = addr;
  while (nleft > 1)
    {
      sum += *p.i++;
      nleft -= 2;
    }

  if (nleft == 1)
    {
      uint8_t a = 0;
      memcpy (&a, p.i, 1);
      sum += ntohs (a) << 8;
    }

  sum = (sum >> 16) + (sum & 0xffff);
  sum += (sum >> 16);

  return ~sum;
}

void make_dhcp_packet(struct udp_dhcp_packet *packet,
                      const unsigned char *data, size_t length,
                      struct in_addr source, struct in_addr dest)
{
  struct ip *ip = &packet->ip;
  struct udphdr *udp = &packet->udp;

  /* OK, this is important :)
   * We copy the data to our packet and then create a small part of the
   * ip structure and an invalid ip_len (basically udp length).
   * We then fill the udp structure and put the checksum
   * of the whole packet into the udp checksum.
   * Finally we complete the ip structure and ip checksum.
   * If we don't do the ordering like so then the udp checksum will be
   * broken, so find another way of doing it! */

  memcpy (&packet->dhcp, data, length);

  ip->ip_p = IPPROTO_UDP;
  ip->ip_src.s_addr = source.s_addr;
  if (dest.s_addr == 0)
    ip->ip_dst.s_addr = INADDR_BROADCAST;
  else
    ip->ip_dst.s_addr = dest.s_addr;

  udp->uh_sport = htons (DHCP_CLIENT_PORT);
  udp->uh_dport = htons (DHCP_SERVER_PORT);
  udp->uh_ulen = htons (sizeof (*udp) + length);
  ip->ip_len = udp->uh_ulen;
  udp->uh_sum = checksum ((unsigned char *) packet, sizeof (*packet));

  ip->ip_v = IPVERSION;
  ip->ip_hl = 5;
  ip->ip_id = 0;
  ip->ip_tos = IPTOS_LOWDELAY;
  ip->ip_len = htons (sizeof (*ip) + sizeof (*udp) + length);
  ip->ip_id = 0;
  ip->ip_off = htons (IP_DF); /* Don't fragment */
  ip->ip_ttl = IPDEFTTL;

  ip->ip_sum = checksum ((unsigned char *) ip, sizeof (*ip));
}

static int valid_dhcp_packet (unsigned char *data)
{
  union
  {
    unsigned char *data;
    struct udp_dhcp_packet *packet;
  } d;
  uint16_t bytes;
  uint16_t ipsum;
  uint16_t iplen;
  uint16_t udpsum;
  struct in_addr source;
  struct in_addr dest;
  int retval = 0;

  d.data = data;
  bytes = ntohs (d.packet->ip.ip_len);
  ipsum = d.packet->ip.ip_sum;
  iplen = d.packet->ip.ip_len;
  udpsum = d.packet->udp.uh_sum;

  d.data = data;
  d.packet->ip.ip_sum = 0;
  if (ipsum != checksum ((unsigned char *) &d.packet->ip,
                         sizeof (d.packet->ip)))
    {
      logger (LOG_DEBUG, "bad IP header checksum, ignoring");
      retval = -1;
      goto eexit;
    }

  memcpy (&source, &d.packet->ip.ip_src, sizeof (d.packet->ip.ip_src));
  memcpy (&dest, &d.packet->ip.ip_dst, sizeof (d.packet->ip.ip_dst));
  memset (&d.packet->ip, 0, sizeof (d.packet->ip));
  d.packet->udp.uh_sum = 0;

  d.packet->ip.ip_p = IPPROTO_UDP;
  memcpy (&d.packet->ip.ip_src, &source, sizeof (d.packet->ip.ip_src));
  memcpy (&d.packet->ip.ip_dst, &dest, sizeof (d.packet->ip.ip_dst));
  d.packet->ip.ip_len = d.packet->udp.uh_ulen;
  if (udpsum && udpsum != checksum (d.data, bytes))
    {
      logger (LOG_ERR, "bad UDP checksum, ignoring");
      retval = -1;
    }

eexit:
  d.packet->ip.ip_sum = ipsum;
  d.packet->ip.ip_len = iplen;
  d.packet->udp.uh_sum = udpsum;

  return retval;
}

#if defined(BSD) || defined(__FreeBSD_kernel__)
int open_socket (interface_t *iface, int protocol)
{
  int n = 0;
  int fd = -1;
  char *device;
  int flags;
  struct ifreq ifr;
  int buf = 0;
  struct bpf_program pf;

  device = xmalloc (sizeof (char) * PATH_MAX);
  do
    {
      snprintf (device, PATH_MAX, "/dev/bpf%d",  n++);
      fd = open (device, O_RDWR);
    }
  while (fd == -1 && errno == EBUSY);
  free (device);

  if (fd == -1)
    {
      logger (LOG_ERR, "unable to open a BPF device");
      return -1;
    }

  close_on_exec (fd);

  memset (&ifr, 0, sizeof (ifr));
  strlcpy (ifr.ifr_name, iface->name, sizeof (ifr.ifr_name));
  if (ioctl (fd, BIOCSETIF, &ifr) == -1)
    {
      logger (LOG_ERR,
              "cannot attach interface `%s' to bpf device `%s': %s",
              iface->name, device, strerror (errno));
      close (fd);
      return -1;
    }

  /* Get the required BPF buffer length from the kernel. */
  if (ioctl (fd, BIOCGBLEN, &buf) == -1)
    {
      logger (LOG_ERR, "ioctl BIOCGBLEN: %s", strerror (errno));
      close (fd);
      return -1;
    }
  iface->buffer_length = buf;

  flags = 1;
  if (ioctl (fd, BIOCIMMEDIATE, &flags) == -1)
    {
      logger (LOG_ERR, "ioctl BIOCIMMEDIATE: %s", strerror (errno));
      close (fd);
      return -1;
    }

  /* Install the DHCP filter */
  if (protocol == ETHERTYPE_ARP)
    {
      pf.bf_insns = arp_bpf_filter;
      pf.bf_len = sizeof (arp_bpf_filter)
                  / sizeof (arp_bpf_filter[0]);
    }
  else
    {
      pf.bf_insns = dhcp_bpf_filter;
      pf.bf_len = sizeof (dhcp_bpf_filter)
                  / sizeof (dhcp_bpf_filter[0]);
    }
  if (ioctl (fd, BIOCSETF, &pf) == -1)
    {
      logger (LOG_ERR, "ioctl BIOCSETF: %s", strerror (errno));
      close (fd);
      return -1;
    }

  if (iface->fd > -1)
    close (iface->fd);
  iface->fd = fd;

  return fd;
}

ssize_t send_packet (const interface_t *iface, int type,
                     const unsigned char *data, size_t len)
{
  ssize_t retval = -1;
  struct iovec iov[2];

  if (iface->family == ARPHRD_ETHER)
    {
      struct ether_header hw;
      memset (&hw, 0, sizeof (hw));
      memset (&hw.ether_dhost, 0xff, ETHER_ADDR_LEN);
      hw.ether_type = htons (type);

      iov[0].iov_base = &hw;
      iov[0].iov_len = sizeof (hw);
    }
  else
    {
      logger (LOG_ERR, "unsupported interace type %d", iface->family);
      return -1;
    }
  iov[1].iov_base = (unsigned char *) data;
  iov[1].iov_len = len;

  if ((retval = writev(iface->fd, iov, 2)) == -1)
    logger (LOG_ERR, "writev: %s", strerror (errno));

  return retval;
}

/* BPF requires that we read the entire buffer.
 * So we pass the buffer in the API so we can loop on >1 dhcp packet. */
ssize_t get_packet (const interface_t *iface, unsigned char *data,
                    unsigned char *buffer,
                    size_t *buffer_len, size_t *buffer_pos)
{
  union
  {
    unsigned char *buffer;
    struct bpf_hdr *packet;
  } bpf;

  bpf.buffer = buffer;

  if (*buffer_pos < 1)
    {
      memset (bpf.buffer, 0, iface->buffer_length);
      *buffer_len = read (iface->fd, bpf.buffer, iface->buffer_length);
      *buffer_pos = 0;
      if (*buffer_len < 1)
        {
          struct timespec ts;
          logger (LOG_ERR, "read: %s", strerror (errno));
          ts.tv_sec = 3;
          ts.tv_nsec = 0;
          nanosleep (&ts, NULL);
          return (-1);
        }
    }
  else
    bpf.buffer += *buffer_pos;

  while (bpf.packet)
    {
      size_t len = 0;
      union
      {
        unsigned char *buffer;
        struct ether_header *hw;
      } hdr;
      unsigned char *payload;
      bool have_data = false;

      /* Ensure that the entire packet is in our buffer */
      if (*buffer_pos + bpf.packet->bh_hdrlen + bpf.packet->bh_caplen
          > (unsigned) *buffer_len)
        break;

      hdr.buffer = bpf.buffer + bpf.packet->bh_hdrlen;
      payload = hdr.buffer + sizeof (*hdr.hw);

      /* If it's an ARP reply, then just send it back */
      if (hdr.hw->ether_type == htons (ETHERTYPE_ARP))
        {
          len = bpf.packet->bh_caplen -
                sizeof (*hdr.hw);
          memcpy (data, payload, len);
          have_data = true;
        }
      else
        {
          if (valid_dhcp_packet (payload) >= 0)
            {
              union
              {
                unsigned char *buffer;
                struct udp_dhcp_packet *packet;
              } pay;
              pay.buffer = payload;
              len = ntohs (pay.packet->ip.ip_len) -
                    sizeof (pay.packet->ip) -
                    sizeof (pay.packet->udp);
              memcpy (data, &pay.packet->dhcp, len);
              have_data = true;
            }
        }

      /* Update the buffer_pos pointer */
      bpf.buffer += BPF_WORDALIGN (bpf.packet->bh_hdrlen +
                                   bpf.packet->bh_caplen);
      if ((unsigned) (bpf.buffer - buffer) < *buffer_len)
        *buffer_pos = bpf.buffer - buffer;
      else
        *buffer_pos = 0;

      if (have_data)
        return len;

      if (*buffer_pos == 0)
        break;
    }

  /* No valid packets left, so return */
  *buffer_pos = 0;
  return -1;
}

#elif __linux__

int open_socket (interface_t *iface, int protocol)
{
  int fd;
  union sockunion
  {
    struct sockaddr sa;
    struct sockaddr_in sin;
    struct sockaddr_ll sll;
    struct sockaddr_storage ss;
  } su;
  struct sock_fprog pf;
  struct ifreq ifr;
  int n = 1;

  /* We need to bind to a port, otherwise Linux generate ICMP messages
   * that cannot contect the port when we have an address.
   * We don't actually use this fd at all, instead using our packet
   * filter socket. */
  if (iface->listen_fd == -1 && protocol == ETHERTYPE_IP)
    {
      if ((fd = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
        {
          logger (LOG_ERR, "socket: %s", strerror (errno));
        }
      else
        {
          memset (&su, 0, sizeof (su));
          su.sin.sin_family = AF_INET;
          su.sin.sin_port = htons (DHCP_CLIENT_PORT);
          if (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR,
                          &n, sizeof (n)) == -1)
            logger (LOG_ERR, "SO_REUSEADDR: %s",
                    strerror (errno));
          if (setsockopt (fd, SOL_SOCKET, SO_RCVBUF,
                          &n, sizeof (n)) == -1)
            logger (LOG_ERR, "SO_RCVBUF: %s",
                    strerror (errno));
          memset (&ifr, 0, sizeof (ifr));
          strncpy (ifr.ifr_name, iface->name,
                   sizeof (ifr.ifr_name));
          if (setsockopt (fd, SOL_SOCKET, SO_BINDTODEVICE,
                          &ifr, sizeof (ifr)) == -1)
            logger (LOG_ERR, "SO_SOBINDTODEVICE: %s",
                    strerror (errno));
          if (bind (fd, &su.sa, sizeof (su)) == -1)
            {
              logger (LOG_ERR, "bind: %s", strerror (errno));
              close (fd);
            }
          else
            {
              iface->listen_fd = fd;
              close_on_exec (fd);
            }
        }
    }

  if ((fd = socket (PF_PACKET, SOCK_DGRAM, htons (protocol))) == -1)
    {
      logger (LOG_ERR, "socket: %s", strerror (errno));
      return (-1);
    }
  close_on_exec (fd);

  memset (&su, 0, sizeof (su));
  su.sll.sll_family = PF_PACKET;
  su.sll.sll_protocol = htons (protocol);
  if (! (su.sll.sll_ifindex = if_nametoindex (iface->name)))
    {
      logger (LOG_ERR,
              "if_nametoindex: no index for interface `%s'",
              iface->name);
      close (fd);
      return (-1);
    }

  /* Install the DHCP filter */
  memset (&pf, 0, sizeof (pf));
  if (protocol == ETHERTYPE_ARP)
    {
      pf.filter = arp_bpf_filter;
      pf.len = sizeof (arp_bpf_filter) / sizeof (arp_bpf_filter[0]);
    }
  else
    {
      pf.filter = dhcp_bpf_filter;
      pf.len = sizeof (dhcp_bpf_filter) / sizeof (dhcp_bpf_filter[0]);
    }
  if (setsockopt (fd, SOL_SOCKET, SO_ATTACH_FILTER,
                  &pf, sizeof (pf)) != 0)
    {
      logger (LOG_ERR, "SO_ATTACH_FILTER: %s", strerror (errno));
      close (fd);
      return (-1);
    }

  if (bind (fd, &su.sa, sizeof (su)) == -1)
    {
      logger (LOG_ERR, "bind: %s", strerror (errno));
      close (fd);
      return (-1);
    }

  if (iface->fd > -1)
    close (iface->fd);
  iface->fd = fd;
  iface->socket_protocol = protocol;
  iface->buffer_length = BUFFER_LENGTH;

  return (fd);
}

ssize_t send_packet (const interface_t *iface, int type,
                     const unsigned char *data, size_t len)
{
  union sockunion
  {
    struct sockaddr sa;
    struct sockaddr_ll sll;
    struct sockaddr_storage ss;
  } su;
  ssize_t retval;

  if (! iface)
    return (-1);

  memset (&su, 0, sizeof (su));
  su.sll.sll_family = AF_PACKET;
  su.sll.sll_protocol = htons (type);

  if (! (su.sll.sll_ifindex = if_nametoindex (iface->name)))
    {
      logger (LOG_ERR, "if_nametoindex: no index for interface `%s'",
              iface->name);
      return (-1);
    }

  su.sll.sll_hatype = htons (iface->family);
  su.sll.sll_halen = iface->hwlen;
  if (iface->family == ARPHRD_INFINIBAND)
    memcpy (&su.sll.sll_addr,
            &ipv4_bcast_addr, sizeof (ipv4_bcast_addr));
  else
    memset (&su.sll.sll_addr, 0xff, iface->hwlen);

  if ((retval = sendto (iface->fd, data, len, 0, &su.sa,
                        sizeof (su))) == -1)

    logger (LOG_ERR, "sendto: %s", strerror (errno));
  return (retval);
}

/* Linux has no need for the buffer as we can read as much as we want.
 * We only have the buffer listed to keep the same API. */
ssize_t get_packet (const interface_t *iface, unsigned char *data,
                    unsigned char *buffer,
                    size_t *buffer_len, size_t *buffer_pos)
{
  ssize_t bytes;
  union
  {
    unsigned char *buffer;
    struct udp_dhcp_packet *packet;
  } pay;

  /* We don't use the given buffer, but we need to rewind the position */
  *buffer_pos = 0;

  memset (buffer, 0, iface->buffer_length);
  bytes = read (iface->fd, buffer, iface->buffer_length);

  if (bytes == -1)
    {
      struct timespec ts;
      logger (LOG_ERR, "read: %s", strerror (errno));
      ts.tv_sec = 3;
      ts.tv_nsec = 0;
      nanosleep (&ts, NULL);
      return (-1);
    }

  *buffer_len = bytes;
  /* If it's an ARP reply, then just send it back */
  if (iface->socket_protocol == ETHERTYPE_ARP)
    {
      memcpy (data, buffer, bytes);
      return (bytes);
    }

  if ((unsigned) bytes < (sizeof (pay.packet->ip) +
                          sizeof (pay.packet->udp)))
    {
      logger (LOG_DEBUG, "message too short, ignoring");
      return (-1);
    }

  pay.buffer = buffer;
  if (bytes < ntohs (pay.packet->ip.ip_len))
    {
      logger (LOG_DEBUG, "truncated packet, ignoring");
      return (-1);
    }

  if (valid_dhcp_packet (buffer) == -1)
    return (-1);

  bytes = ntohs (pay.packet->ip.ip_len) -
          (sizeof (pay.packet->ip) + sizeof (pay.packet->udp));
  memcpy (data, &pay.packet->dhcp, bytes);
  return (bytes);
}

#else
#error "Platform not supported!"
#error "We currently support BPF and Linux sockets."
#error "Other platforms may work using BPF. If yours does, please let me know"
#error "so I can add it to our list."
#endif