dhcpcd/socket.c

562 lines
14 KiB
C
Raw Normal View History

2006-11-28 04:23:22 +08:00
/*
* dhcpcd - DHCP client daemon
2008-01-08 17:51:23 +08:00
* Copyright 2006-2008 Roy Marples <roy@marples.name>
2006-11-28 04:23:22 +08:00
*
* Distributed under the terms of the GNU General Public License v2
2006-11-28 04:23:22 +08:00
*/
#ifdef __linux__
2008-01-09 17:38:00 +08:00
# define _BSD_SOURCE
#endif
#include <sys/types.h>
2006-11-28 04:23:22 +08:00
#include <sys/ioctl.h>
2007-11-28 06:03:42 +08:00
#include <sys/param.h>
#include <sys/select.h>
2006-11-28 04:23:22 +08:00
#include <sys/socket.h>
#include <sys/uio.h>
#include <net/if.h>
2006-11-28 04:23:22 +08:00
#include <netinet/in_systm.h>
2007-04-05 23:01:50 +08:00
#include <netinet/in.h>
#include <netinet/udp.h>
2007-04-05 23:01:50 +08:00
#include <arpa/inet.h>
2006-11-28 04:23:22 +08:00
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
2006-11-28 04:23:22 +08:00
#include <string.h>
#include <unistd.h>
#include "dhcp.h"
#include "interface.h"
#include "logger.h"
#include "socket.h"
2006-11-28 04:23:22 +08:00
/* A suitably large buffer for all transactions.
BPF buffer size is set by the kernel, so no define. */
#ifdef __linux__
2008-01-09 17:38:00 +08:00
# define BUFFER_LENGTH 4096
2006-11-28 04:23:22 +08:00
#endif
static uint16_t checksum (unsigned char *addr, uint16_t len)
{
2007-04-11 21:18:33 +08:00
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;
2006-11-28 04:23:22 +08:00
}
void make_dhcp_packet(struct udp_dhcp_packet *packet,
2007-04-11 21:18:33 +08:00
const unsigned char *data, int length,
struct in_addr source, struct in_addr dest)
2006-11-28 04:23:22 +08:00
{
2007-04-11 21:18:33 +08:00
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 (struct udphdr) + length);
ip->ip_len = udp->uh_ulen;
udp->uh_sum = checksum ((unsigned char *) packet,
sizeof (struct udp_dhcp_packet));
ip->ip_v = IPVERSION;
ip->ip_hl = 5;
ip->ip_id = 0;
ip->ip_tos = IPTOS_LOWDELAY;
ip->ip_len = htons (sizeof (struct ip) + sizeof (struct udphdr) +
length);
ip->ip_id = 0;
ip->ip_off = 0;
ip->ip_ttl = IPDEFTTL;
ip->ip_sum = checksum ((unsigned char *) ip, sizeof (struct ip));
2006-11-28 04:23:22 +08:00
}
static int valid_dhcp_packet (unsigned char *data)
2006-11-28 04:23:22 +08:00
{
2007-04-11 21:18:33 +08:00
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 (struct ip)))
{
logger (LOG_DEBUG, "bad IP header checksum, ignoring");
retval = -1;
goto eexit;
}
memcpy (&source, &d.packet->ip.ip_src, sizeof (struct in_addr));
memcpy (&dest, &d.packet->ip.ip_dst, sizeof (struct in_addr));
memset (&d.packet->ip, 0, sizeof (struct ip));
d.packet->udp.uh_sum = 0;
d.packet->ip.ip_p = IPPROTO_UDP;
memcpy (&d.packet->ip.ip_src, &source, sizeof (struct in_addr));
memcpy (&d.packet->ip.ip_dst, &dest, sizeof (struct in_addr));
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;
}
2006-11-28 04:23:22 +08:00
eexit:
2007-04-11 21:18:33 +08:00
d.packet->ip.ip_sum = ipsum;
d.packet->ip.ip_len = iplen;
d.packet->udp.uh_sum = udpsum;
2006-11-28 04:23:22 +08:00
2007-04-11 21:18:33 +08:00
return retval;
2006-11-28 04:23:22 +08:00
}
2007-11-28 06:03:42 +08:00
#ifdef BSD
2006-11-28 04:23:22 +08:00
/* Credit where credit is due :)
The below BPF filter is taken from ISC DHCP */
# include <net/bpf.h>
static struct bpf_insn dhcp_bpf_filter [] = {
2007-04-11 21:18:33 +08:00
/* 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),
2006-11-28 04:23:22 +08:00
2007-04-11 21:18:33 +08:00
/* 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),
2006-11-28 04:23:22 +08:00
2007-04-11 21:18:33 +08:00
/* 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),
2006-11-28 04:23:22 +08:00
2007-04-11 21:18:33 +08:00
/* Get the IP header length... */
BPF_STMT (BPF_LDX + BPF_B + BPF_MSH, 14),
2006-11-28 04:23:22 +08:00
2007-04-11 21:18:33 +08:00
/* 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),
2006-11-28 04:23:22 +08:00
2007-04-11 21:18:33 +08:00
/* If we passed all the tests, ask for the whole packet. */
BPF_STMT (BPF_RET+BPF_K, (u_int) - 1),
2006-11-28 04:23:22 +08:00
2007-04-11 21:18:33 +08:00
/* Otherwise, drop it. */
BPF_STMT (BPF_RET+BPF_K, 0),
2006-11-28 04:23:22 +08:00
};
static struct bpf_insn arp_bpf_filter [] = {
2007-04-11 21:18:33 +08:00
/* 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),
2006-11-28 04:23:22 +08:00
2007-04-11 21:18:33 +08:00
/* 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),
2006-11-28 04:23:22 +08:00
2007-04-11 21:18:33 +08:00
/* If we passed all the tests, ask for the whole packet. */
BPF_STMT (BPF_RET+BPF_K, (u_int) - 1),
2006-11-28 04:23:22 +08:00
2007-04-11 21:18:33 +08:00
/* Otherwise, drop it. */
BPF_STMT (BPF_RET+BPF_K, 0),
2006-11-28 04:23:22 +08:00
};
int open_socket (interface_t *iface, bool arp)
{
2007-04-11 21:18:33 +08:00
int n = 0;
int fd = -1;
char *device;
2007-04-11 21:18:33 +08:00
int flags;
struct ifreq ifr;
int buf = 0;
struct bpf_program p;
device = xmalloc (sizeof (char) * PATH_MAX);
2007-04-11 21:18:33 +08:00
do {
snprintf (device, PATH_MAX, "/dev/bpf%d", n++);
fd = open (device, O_RDWR);
2007-07-18 19:35:50 +08:00
} while (fd == -1 && errno == EBUSY);
free (device);
2007-04-11 21:18:33 +08:00
2007-07-18 19:35:50 +08:00
if (fd == -1) {
2007-04-11 21:18:33 +08:00
logger (LOG_ERR, "unable to open a BPF device");
return -1;
}
2007-07-18 19:35:50 +08:00
if ((flags = fcntl (fd, F_GETFD, 0)) == -1
|| fcntl (fd, F_SETFD, flags | FD_CLOEXEC) == -1)
2007-04-11 21:18:33 +08:00
{
logger (LOG_ERR, "fcntl: %s", strerror (errno));
close (fd);
return -1;
}
memset (&ifr, 0, sizeof (struct ifreq));
strlcpy (ifr.ifr_name, iface->name, sizeof (ifr.ifr_name));
2007-07-18 19:35:50 +08:00
if (ioctl (fd, BIOCSETIF, &ifr) == -1) {
2007-04-11 21:18:33 +08:00
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. */
2007-07-18 19:35:50 +08:00
if (ioctl (fd, BIOCGBLEN, &buf) == -1) {
2007-04-11 21:18:33 +08:00
logger (LOG_ERR, "ioctl BIOCGBLEN: %s", strerror (errno));
close (fd);
return -1;
}
iface->buffer_length = buf;
flags = 1;
2007-07-18 19:35:50 +08:00
if (ioctl (fd, BIOCIMMEDIATE, &flags) == -1) {
2007-04-11 21:18:33 +08:00
logger (LOG_ERR, "ioctl BIOCIMMEDIATE: %s", strerror (errno));
close (fd);
return -1;
}
/* Install the DHCP filter */
if (arp) {
p.bf_insns = arp_bpf_filter;
p.bf_len = sizeof (arp_bpf_filter) / sizeof (struct bpf_insn);
} else {
p.bf_insns = dhcp_bpf_filter;
p.bf_len = sizeof (dhcp_bpf_filter) / sizeof (struct bpf_insn);
}
2007-07-18 19:35:50 +08:00
if (ioctl (fd, BIOCSETF, &p) == -1) {
2007-04-11 21:18:33 +08:00
logger (LOG_ERR, "ioctl BIOCSETF: %s", strerror (errno));
close (fd);
return -1;
}
if (iface->fd > -1)
close (iface->fd);
iface->fd = fd;
return fd;
2006-11-28 04:23:22 +08:00
}
2006-12-15 08:00:18 +08:00
int send_packet (const interface_t *iface, int type,
2007-04-11 21:18:33 +08:00
const unsigned char *data, int len)
2006-11-28 04:23:22 +08:00
{
2007-04-11 21:18:33 +08:00
int retval = -1;
struct iovec iov[2];
if (iface->family == ARPHRD_ETHER) {
struct ether_header hw;
memset (&hw, 0, sizeof (struct ether_header));
memset (&hw.ether_dhost, 0xff, ETHER_ADDR_LEN);
hw.ether_type = htons (type);
iov[0].iov_base = &hw;
iov[0].iov_len = sizeof (struct ether_header);
} 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;
2006-11-28 04:23:22 +08:00
}
/* BPF requires that we read the entire buffer.
So we pass the buffer in the API so we can loop on >1 dhcp packet. */
2006-12-15 08:00:18 +08:00
int get_packet (const interface_t *iface, unsigned char *data,
2007-04-11 21:18:33 +08:00
unsigned char *buffer, int *buffer_len, int *buffer_pos)
2006-11-28 04:23:22 +08:00
{
2007-04-11 21:18:33 +08:00
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 timeval tv;
logger (LOG_ERR, "read: %s", strerror (errno));
tv.tv_sec = 3;
tv.tv_usec = 0;
select (0, NULL, NULL, NULL, &tv);
return -1;
}
} else
bpf.buffer += *buffer_pos;
while (bpf.packet) {
int len = -1;
union
{
unsigned char *buffer;
struct ether_header *hw;
} hdr;
unsigned char *payload;
/* 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 (struct ether_header);
/* 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 (struct ether_header);
memcpy (data, payload, len);
} 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 (struct ip) -
sizeof (struct udphdr);
memcpy (data, &pay.packet->dhcp, len);
}
}
/* Update the buffer_pos pointer */
bpf.buffer +=
BPF_WORDALIGN (bpf.packet->bh_hdrlen + bpf.packet->bh_caplen);
if (bpf.buffer - buffer < *buffer_len)
*buffer_pos = bpf.buffer - buffer;
else
*buffer_pos = 0;
if (len != -1)
return len;
if (*buffer_pos == 0)
break;
}
/* No valid packets left, so return */
*buffer_pos = 0;
return -1;
2006-11-28 04:23:22 +08:00
}
#elif __linux__
#include <netpacket/packet.h>
int open_socket (interface_t *iface, bool arp)
{
2007-04-11 21:18:33 +08:00
int fd;
int flags;
union sockunion {
struct sockaddr sa;
struct sockaddr_ll sll;
struct sockaddr_storage ss;
} su;
2007-04-11 21:18:33 +08:00
2007-07-18 19:35:50 +08:00
if ((fd = socket (PF_PACKET, SOCK_DGRAM, htons (ETH_P_IP))) == -1) {
2007-04-11 21:18:33 +08:00
logger (LOG_ERR, "socket: %s", strerror (errno));
return -1;
}
2007-07-18 19:35:50 +08:00
if ((flags = fcntl (fd, F_GETFD, 0)) == -1
|| fcntl (fd, F_SETFD, flags | FD_CLOEXEC) == -1)
2007-04-11 21:18:33 +08:00
{
logger (LOG_ERR, "fcntl: %s", strerror (errno));
close (fd);
return -1;
}
memset (&su, 0, sizeof (struct sockaddr_storage));
su.sll.sll_family = AF_PACKET;
2007-04-11 21:18:33 +08:00
if (arp)
su.sll.sll_protocol = htons (ETH_P_ARP);
2007-04-11 21:18:33 +08:00
else
su.sll.sll_protocol = htons (ETH_P_IP);
if (! (su.sll.sll_ifindex = if_nametoindex (iface->name))) {
2007-04-11 21:18:33 +08:00
logger (LOG_ERR, "if_nametoindex: Couldn't find index for interface `%s'",
iface->name);
close (fd);
return -1;
}
if (bind (fd, &su.sa, sizeof (struct sockaddr_storage)) == -1)
2007-04-11 21:18:33 +08:00
{
logger (LOG_ERR, "bind: %s", strerror (errno));
close (fd);
return -1;
}
if (iface->fd > -1)
close (iface->fd);
iface->fd = fd;
iface->socket_protocol = ntohs (su.sll.sll_protocol);
2007-04-11 21:18:33 +08:00
iface->buffer_length = BUFFER_LENGTH;
return fd;
2006-11-28 04:23:22 +08:00
}
2006-12-15 07:47:56 +08:00
int send_packet (const interface_t *iface, const int type,
2007-04-11 21:18:33 +08:00
const unsigned char *data, const int len)
2006-11-28 04:23:22 +08:00
{
union sockunion {
struct sockaddr sa;
struct sockaddr_ll sll;
struct sockaddr_storage ss;
} su;
2007-04-11 21:18:33 +08:00
int retval;
if (! iface)
return -1;
memset (&su, 0, sizeof (struct sockaddr_storage));
su.sll.sll_family = AF_PACKET;
su.sll.sll_protocol = htons (type);
if (! (su.sll.sll_ifindex = if_nametoindex (iface->name))) {
2007-04-11 21:18:33 +08:00
logger (LOG_ERR, "if_nametoindex: Couldn't find index for interface `%s'",
iface->name);
return -1;
}
su.sll.sll_halen = iface->hwlen;
memset(&su.sll.sll_addr, 0xff, iface->hwlen);
2007-04-11 21:18:33 +08:00
if ((retval = sendto (iface->fd, data, len, 0, &su.sa,
sizeof (struct sockaddr_storage))) == -1)
2007-04-11 21:18:33 +08:00
logger (LOG_ERR, "sendto: %s", strerror (errno));
return retval;
2006-11-28 04:23:22 +08:00
}
/* 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. */
2006-12-15 07:47:56 +08:00
int get_packet (const interface_t *iface, unsigned char *data,
2007-04-11 21:18:33 +08:00
unsigned char *buffer, int *buffer_len, int *buffer_pos)
2006-11-28 04:23:22 +08:00
{
2007-04-11 21:18:33 +08:00
long 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);
2007-07-18 19:35:50 +08:00
if (bytes == -1) {
2007-04-11 21:18:33 +08:00
struct timeval tv;
logger (LOG_ERR, "read: %s", strerror (errno));
tv.tv_sec = 3;
tv.tv_usec = 0;
select (0, NULL, NULL, NULL, &tv);
return -1;
}
*buffer_len = bytes;
/* If it's an ARP reply, then just send it back */
if (iface->socket_protocol == ETH_P_ARP) {
memcpy (data, buffer, bytes);
return bytes;
}
if ((unsigned) bytes < (sizeof (struct ip) + sizeof (struct udphdr))) {
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;
}
bytes = ntohs (pay.packet->ip.ip_len);
/* This is like our BPF filter above */
if (pay.packet->ip.ip_p != IPPROTO_UDP || pay.packet->ip.ip_v != IPVERSION ||
pay.packet->ip.ip_hl != sizeof (pay.packet->ip) >> 2 ||
pay.packet->udp.uh_dport != htons (DHCP_CLIENT_PORT) ||
bytes > (int) sizeof (struct udp_dhcp_packet) ||
ntohs (pay.packet->udp.uh_ulen)
!= (uint16_t) (bytes - sizeof (pay.packet->ip)))
{
return -1;
}
2007-07-18 19:35:50 +08:00
if (valid_dhcp_packet (buffer) == -1)
2007-04-11 21:18:33 +08:00
return -1;
memcpy(data, &pay.packet->dhcp,
bytes - (sizeof (pay.packet->ip) + sizeof (pay.packet->udp)));
return bytes - (sizeof (pay.packet->ip) + sizeof (pay.packet->udp));
2006-11-28 04:23:22 +08:00
}
#else
2008-01-09 17:38:00 +08:00
# 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."
2006-11-28 04:23:22 +08:00
#endif