2011-07-25 12:00:57 +05:30
|
|
|
/* dhcp.c - general DHCP protocol handling
|
2011-03-31 09:29:22 +05:30
|
|
|
*
|
2011-07-25 12:00:57 +05:30
|
|
|
* Copyright (c) 2004-2011 Nicholas J. Kain <njkain at gmail dot com>
|
|
|
|
* All rights reserved.
|
2011-03-31 09:29:22 +05:30
|
|
|
*
|
2011-07-25 12:00:57 +05:30
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions are met:
|
2011-03-31 09:29:22 +05:30
|
|
|
*
|
2011-07-25 12:00:57 +05:30
|
|
|
* - Redistributions of source code must retain the above copyright notice,
|
|
|
|
* this list of conditions and the following disclaimer.
|
2011-03-31 09:29:22 +05:30
|
|
|
*
|
2011-07-25 12:00:57 +05:30
|
|
|
* - 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 COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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.
|
2011-03-31 09:29:22 +05:30
|
|
|
*/
|
|
|
|
|
2010-12-24 20:11:52 +05:30
|
|
|
#include <stdlib.h>
|
2010-11-12 14:32:18 +05:30
|
|
|
#include <unistd.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <netinet/in.h>
|
2011-06-11 20:49:05 +05:30
|
|
|
#include <arpa/inet.h>
|
2011-06-25 20:10:41 +05:30
|
|
|
#include <net/if.h>
|
2010-11-12 14:32:18 +05:30
|
|
|
#include <sys/types.h>
|
2011-06-11 20:49:05 +05:30
|
|
|
#include <sys/stat.h>
|
2010-11-12 14:32:18 +05:30
|
|
|
#include <sys/socket.h>
|
2011-06-11 20:49:05 +05:30
|
|
|
#include <fcntl.h>
|
2010-11-12 14:32:18 +05:30
|
|
|
#include <features.h>
|
|
|
|
#include <netpacket/packet.h>
|
|
|
|
#include <net/ethernet.h>
|
2011-06-25 20:10:41 +05:30
|
|
|
#include <linux/filter.h>
|
2011-06-11 20:49:05 +05:30
|
|
|
#include <time.h>
|
2010-11-12 14:32:18 +05:30
|
|
|
#include <errno.h>
|
|
|
|
|
2011-07-02 13:21:44 +05:30
|
|
|
#include "dhcp.h"
|
2011-06-30 09:17:31 +05:30
|
|
|
#include "state.h"
|
2010-12-24 21:10:46 +05:30
|
|
|
#include "arp.h"
|
2010-12-24 20:42:41 +05:30
|
|
|
#include "ifchange.h"
|
2010-12-24 20:11:52 +05:30
|
|
|
#include "sys.h"
|
2010-11-12 14:32:18 +05:30
|
|
|
#include "log.h"
|
2010-11-13 18:48:23 +05:30
|
|
|
#include "io.h"
|
2010-11-12 14:32:18 +05:30
|
|
|
#include "options.h"
|
2011-06-25 20:10:41 +05:30
|
|
|
#include "strl.h"
|
2011-06-28 00:37:00 +05:30
|
|
|
#include "random.h"
|
2010-11-12 14:32:18 +05:30
|
|
|
|
2011-07-01 07:03:38 +05:30
|
|
|
typedef enum {
|
|
|
|
LM_NONE = 0,
|
|
|
|
LM_COOKED,
|
|
|
|
LM_RAW
|
|
|
|
} listen_mode_t;
|
|
|
|
|
2011-06-29 09:26:12 +05:30
|
|
|
// Returns fd of new udp socket bound on success, or -1 on failure.
|
|
|
|
static int create_udp_socket(uint32_t ip, uint16_t port, char *iface)
|
2011-06-25 20:10:41 +05:30
|
|
|
{
|
2011-06-26 02:01:21 +05:30
|
|
|
int fd;
|
2011-06-25 20:41:48 +05:30
|
|
|
if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
|
2011-06-29 09:26:12 +05:30
|
|
|
log_error("create_udp_socket: socket failed: %s", strerror(errno));
|
2011-06-25 20:10:41 +05:30
|
|
|
goto out;
|
|
|
|
}
|
2011-06-26 02:01:21 +05:30
|
|
|
int opt = 1;
|
|
|
|
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt) == -1) {
|
2011-07-12 02:03:57 +05:30
|
|
|
log_error("create_udp_socket: Set reuse addr failed: %s",
|
2011-06-26 02:01:21 +05:30
|
|
|
strerror(errno));
|
2011-06-25 20:10:41 +05:30
|
|
|
goto out_fd;
|
2011-06-26 02:01:21 +05:30
|
|
|
}
|
|
|
|
if (setsockopt(fd, SOL_SOCKET, SO_DONTROUTE, &opt, sizeof opt) == -1) {
|
2011-07-12 02:03:57 +05:30
|
|
|
log_error("create_udp_socket: Set don't route failed: %s",
|
2011-06-26 02:01:21 +05:30
|
|
|
strerror(errno));
|
2011-06-25 20:10:41 +05:30
|
|
|
goto out_fd;
|
2011-06-26 02:01:21 +05:30
|
|
|
}
|
|
|
|
struct ifreq ifr;
|
|
|
|
memset(&ifr, 0, sizeof (struct ifreq));
|
2011-06-29 09:26:12 +05:30
|
|
|
strlcpy(ifr.ifr_name, iface, IFNAMSIZ);
|
2011-06-26 02:01:21 +05:30
|
|
|
if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof ifr) < 0) {
|
2011-07-12 02:03:57 +05:30
|
|
|
log_error("create_udp_socket: Set bind to device failed: %s",
|
2011-06-26 02:01:21 +05:30
|
|
|
strerror(errno));
|
|
|
|
goto out_fd;
|
|
|
|
}
|
2011-06-25 20:16:24 +05:30
|
|
|
if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK) == -1) {
|
2011-07-12 02:03:57 +05:30
|
|
|
log_error("create_udp_socket: Set non-blocking failed: %s",
|
2011-06-25 20:16:24 +05:30
|
|
|
strerror(errno));
|
|
|
|
goto out_fd;
|
|
|
|
}
|
2011-06-25 20:10:41 +05:30
|
|
|
|
2011-06-29 09:26:12 +05:30
|
|
|
struct sockaddr_in sa = {
|
2011-06-26 02:01:21 +05:30
|
|
|
.sin_family = AF_INET,
|
|
|
|
.sin_port = htons(port),
|
|
|
|
.sin_addr.s_addr = ip,
|
|
|
|
};
|
2011-06-29 09:26:12 +05:30
|
|
|
if (bind(fd, (struct sockaddr *)&sa, sizeof sa) == -1)
|
2011-06-25 20:10:41 +05:30
|
|
|
goto out_fd;
|
|
|
|
|
|
|
|
return fd;
|
|
|
|
out_fd:
|
|
|
|
close(fd);
|
|
|
|
out:
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2011-07-01 15:05:38 +05:30
|
|
|
// Returns fd of new listen socket bound to 0.0.0.0:@68 on interface @inf
|
2011-06-29 09:26:12 +05:30
|
|
|
// on success, or -1 on failure.
|
2011-07-01 15:05:38 +05:30
|
|
|
static int create_udp_listen_socket(struct client_state_t *cs, char *inf)
|
2011-06-25 20:10:41 +05:30
|
|
|
{
|
2011-07-01 15:05:38 +05:30
|
|
|
int fd = create_udp_socket(INADDR_ANY, DHCP_CLIENT_PORT, inf);
|
2011-06-29 09:26:12 +05:30
|
|
|
if (fd == -1)
|
|
|
|
return -1;
|
|
|
|
int opt = 1;
|
|
|
|
if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof opt) == -1) {
|
2011-07-12 02:03:57 +05:30
|
|
|
log_error("create_udp_listen_socket: Set broadcast failed: %s",
|
2011-06-26 02:01:21 +05:30
|
|
|
strerror(errno));
|
2011-06-29 09:26:12 +05:30
|
|
|
close(fd);
|
|
|
|
return -1;
|
2011-06-25 20:10:41 +05:30
|
|
|
}
|
2011-06-29 09:26:12 +05:30
|
|
|
return fd;
|
|
|
|
}
|
2011-06-25 20:10:41 +05:30
|
|
|
|
2011-06-29 09:26:12 +05:30
|
|
|
// Broadcast a DHCP message using a UDP socket.
|
2011-07-04 04:26:57 +05:30
|
|
|
static int send_dhcp_cooked(struct client_state_t *cs, struct dhcpmsg *payload)
|
2011-06-29 09:26:12 +05:30
|
|
|
{
|
|
|
|
int ret = -1;
|
2011-07-04 04:26:57 +05:30
|
|
|
int fd = create_udp_socket(cs->clientAddr, DHCP_CLIENT_PORT,
|
2011-06-29 09:26:12 +05:30
|
|
|
client_config.interface);
|
|
|
|
if (fd == -1)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
struct sockaddr_in raddr = {
|
|
|
|
.sin_family = AF_INET,
|
|
|
|
.sin_port = htons(DHCP_SERVER_PORT),
|
2011-07-04 04:26:57 +05:30
|
|
|
.sin_addr.s_addr = cs->serverAddr,
|
2011-06-29 09:26:12 +05:30
|
|
|
};
|
|
|
|
if (connect(fd, (struct sockaddr *)&raddr, sizeof(struct sockaddr)) == -1) {
|
|
|
|
log_error("send_dhcp_cooked: connect failed: %s", strerror(errno));
|
2011-06-26 02:01:21 +05:30
|
|
|
goto out_fd;
|
2011-06-25 20:16:24 +05:30
|
|
|
}
|
2011-06-29 09:26:12 +05:30
|
|
|
|
2011-07-27 17:19:30 +05:30
|
|
|
// Send packets that are as short as possible.
|
2011-06-29 09:26:12 +05:30
|
|
|
ssize_t endloc = get_end_option_idx(payload);
|
|
|
|
if (endloc < 0) {
|
2011-07-26 09:18:35 +05:30
|
|
|
log_error("send_dhcp_cooked: No end marker. Not sending.");
|
2011-06-26 02:01:21 +05:30
|
|
|
goto out_fd;
|
2011-06-25 20:10:41 +05:30
|
|
|
}
|
2011-06-29 09:26:12 +05:30
|
|
|
size_t payload_len =
|
|
|
|
sizeof *payload - (sizeof payload->options - 1 - endloc);
|
|
|
|
ret = safe_write(fd, (const char *)payload, payload_len);
|
|
|
|
if (ret == -1)
|
|
|
|
log_error("send_dhcp_cooked: write failed: %s", strerror(errno));
|
|
|
|
out_fd:
|
2011-06-26 02:01:21 +05:30
|
|
|
close(fd);
|
2011-06-29 09:26:12 +05:30
|
|
|
out:
|
|
|
|
return ret;
|
2011-06-25 20:10:41 +05:30
|
|
|
}
|
2011-06-27 03:07:57 +05:30
|
|
|
|
2011-06-11 20:35:53 +05:30
|
|
|
// Read a packet from a cooked socket. Returns -1 on fatal error, -2 on
|
|
|
|
// transient error.
|
2011-06-29 09:26:12 +05:30
|
|
|
static int get_cooked_packet(struct dhcpmsg *packet, int fd)
|
2010-11-12 14:32:18 +05:30
|
|
|
{
|
2011-06-27 02:55:00 +05:30
|
|
|
memset(packet, 0, sizeof *packet);
|
2011-06-29 09:26:12 +05:30
|
|
|
int bytes = safe_read(fd, (char *)packet, sizeof *packet);
|
2010-11-13 19:07:33 +05:30
|
|
|
if (bytes == -1) {
|
2011-05-30 20:24:05 +05:30
|
|
|
if (errno == EAGAIN || errno == EWOULDBLOCK)
|
|
|
|
return -2;
|
2011-03-31 09:29:22 +05:30
|
|
|
log_line("Read on listen socket failed: %s", strerror(errno));
|
|
|
|
return -1;
|
2010-11-13 01:03:17 +05:30
|
|
|
}
|
|
|
|
return bytes;
|
2010-11-12 14:32:18 +05:30
|
|
|
}
|
|
|
|
|
2011-06-27 02:03:07 +05:30
|
|
|
// When summing ones-complement 16-bit values using a 32-bit unsigned
|
|
|
|
// representation, fold the carry bits that have spilled into the upper
|
|
|
|
// 16-bits of the 32-bit unsigned value back into the 16-bit ones-complement
|
|
|
|
// binary value.
|
|
|
|
static inline uint16_t foldcarry(uint32_t v)
|
|
|
|
{
|
|
|
|
v = (v >> 16) + (v & 0xffff);
|
|
|
|
v += v >> 16;
|
|
|
|
return v;
|
|
|
|
}
|
|
|
|
|
2011-06-26 19:51:02 +05:30
|
|
|
// This function is not suitable for summing buffers that are greater than
|
2011-06-27 03:07:57 +05:30
|
|
|
// 128k bytes in length: failure case will be incorrect checksums via
|
2011-06-26 19:51:02 +05:30
|
|
|
// unsigned overflow, which is a defined operation and is safe. This limit
|
|
|
|
// should not be an issue for IPv4 or IPv6 packet, which are limited to
|
|
|
|
// at most 64k bytes.
|
2011-06-27 02:03:07 +05:30
|
|
|
static uint16_t net_checksum(void *buf, size_t size)
|
2011-06-26 02:25:00 +05:30
|
|
|
{
|
2011-06-27 02:03:07 +05:30
|
|
|
uint32_t sum = 0;
|
2011-06-26 19:51:02 +05:30
|
|
|
int odd = size & 0x01;
|
|
|
|
size_t i;
|
|
|
|
size &= ~((size_t)0x01);
|
|
|
|
size >>= 1;
|
|
|
|
uint8_t *b = buf;
|
|
|
|
for (i = 0; i < size; ++i) {
|
|
|
|
uint16_t hi = b[i*2];
|
|
|
|
uint16_t lo = b[i*2+1];
|
|
|
|
sum += ntohs((lo + (hi << 8)));
|
2011-06-26 02:25:00 +05:30
|
|
|
}
|
2011-06-26 19:51:02 +05:30
|
|
|
if (odd) {
|
|
|
|
uint16_t hi = b[i*2];
|
|
|
|
uint16_t lo = 0;
|
|
|
|
sum += ntohs((lo + (hi << 8)));
|
2011-06-26 02:25:00 +05:30
|
|
|
}
|
2011-06-27 02:03:07 +05:30
|
|
|
return ~foldcarry(sum);
|
|
|
|
}
|
|
|
|
|
|
|
|
// For two sequences of bytes A and B that return checksums CS(A) and CS(B),
|
|
|
|
// this function will calculate the checksum CS(AB) of the concatenated value
|
|
|
|
// AB given the checksums of the individual parts CS(A) and CS(B).
|
|
|
|
static inline uint16_t net_checksum_add(uint16_t a, uint16_t b)
|
|
|
|
{
|
|
|
|
return ~foldcarry((~a & 0xffff) + (~b & 0xffff));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns 1 if IP checksum is correct, otherwise 0.
|
|
|
|
static int ip_checksum(struct ip_udp_dhcp_packet *packet)
|
|
|
|
{
|
|
|
|
return net_checksum(&packet->ip, sizeof packet->ip) == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns 1 if UDP checksum is correct, otherwise 0.
|
|
|
|
static int udp_checksum(struct ip_udp_dhcp_packet *packet)
|
|
|
|
{
|
|
|
|
struct iphdr ph = {
|
|
|
|
.saddr = packet->ip.saddr,
|
|
|
|
.daddr = packet->ip.daddr,
|
|
|
|
.protocol = packet->ip.protocol,
|
|
|
|
.tot_len = packet->udp.len,
|
|
|
|
};
|
|
|
|
uint16_t udpcs = net_checksum(&packet->udp, ntohs(packet->udp.len));
|
|
|
|
uint16_t hdrcs = net_checksum(&ph, sizeof ph);
|
|
|
|
uint16_t cs = net_checksum_add(udpcs, hdrcs);
|
|
|
|
return cs == 0;
|
2011-06-26 02:25:00 +05:30
|
|
|
}
|
|
|
|
|
2011-07-01 13:44:10 +05:30
|
|
|
static int get_raw_packet_validate_bpf(struct ip_udp_dhcp_packet *packet)
|
|
|
|
{
|
|
|
|
if (packet->ip.version != IPVERSION) {
|
2011-07-12 02:03:57 +05:30
|
|
|
log_line("IP version is not IPv4.");
|
2011-07-01 13:44:10 +05:30
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (packet->ip.ihl != sizeof packet->ip >> 2) {
|
2011-07-12 02:03:57 +05:30
|
|
|
log_line("IP header length incorrect.");
|
2011-07-01 13:44:10 +05:30
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (packet->ip.protocol != IPPROTO_UDP) {
|
|
|
|
log_line("IP header is not UDP: %d", packet->ip.protocol);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (ntohs(packet->udp.dest) != DHCP_CLIENT_PORT) {
|
|
|
|
log_line("UDP destination port incorrect: %d", ntohs(packet->udp.dest));
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (ntohs(packet->udp.len) !=
|
|
|
|
ntohs(packet->ip.tot_len) - sizeof packet->ip) {
|
2011-07-12 02:03:57 +05:30
|
|
|
log_line("UDP header length incorrect.");
|
2011-07-01 13:44:10 +05:30
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2011-06-11 20:35:53 +05:30
|
|
|
// Read a packet from a raw socket. Returns -1 on fatal error, -2 on
|
|
|
|
// transient error.
|
2011-07-01 14:08:38 +05:30
|
|
|
static int get_raw_packet(struct client_state_t *cs, struct dhcpmsg *payload)
|
2011-06-11 20:35:53 +05:30
|
|
|
{
|
|
|
|
struct ip_udp_dhcp_packet packet;
|
2011-06-27 02:55:00 +05:30
|
|
|
memset(&packet, 0, sizeof packet);
|
2011-06-27 02:03:07 +05:30
|
|
|
|
2011-07-01 14:08:38 +05:30
|
|
|
ssize_t inc = safe_read(cs->listenFd, (char *)&packet, sizeof packet);
|
2011-06-27 02:55:00 +05:30
|
|
|
if (inc == -1) {
|
2011-06-11 20:35:53 +05:30
|
|
|
if (errno == EAGAIN || errno == EWOULDBLOCK)
|
|
|
|
return -2;
|
|
|
|
log_line("get_raw_packet: read error %s", strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2011-06-27 02:55:00 +05:30
|
|
|
if (inc > ntohs(packet.ip.tot_len))
|
|
|
|
log_line("Discarded extra bytes after reading a single UDP datagram.");
|
2011-06-11 20:35:53 +05:30
|
|
|
|
2011-07-01 13:44:10 +05:30
|
|
|
if (!cs->using_dhcp_bpf && !get_raw_packet_validate_bpf(&packet))
|
2011-06-11 20:35:53 +05:30
|
|
|
return -2;
|
2011-07-01 13:44:10 +05:30
|
|
|
|
2011-06-27 02:03:07 +05:30
|
|
|
if (!ip_checksum(&packet)) {
|
2011-07-12 02:03:57 +05:30
|
|
|
log_line("IP header checksum incorrect.");
|
2011-06-11 20:35:53 +05:30
|
|
|
return -2;
|
|
|
|
}
|
2011-06-27 02:03:07 +05:30
|
|
|
if (packet.udp.check && !udp_checksum(&packet)) {
|
2011-07-12 02:03:57 +05:30
|
|
|
log_error("Packet with bad UDP checksum received. Ignoring.");
|
2011-06-11 20:35:53 +05:30
|
|
|
return -2;
|
|
|
|
}
|
|
|
|
|
2011-06-27 02:55:00 +05:30
|
|
|
size_t l = ntohs(packet.ip.tot_len) - sizeof packet.ip - sizeof packet.udp;
|
|
|
|
memcpy(payload, &packet.data, l);
|
|
|
|
return l;
|
2011-06-11 20:35:53 +05:30
|
|
|
}
|
|
|
|
|
2011-07-01 13:44:10 +05:30
|
|
|
static int create_raw_socket(struct client_state_t *cs, struct sockaddr_ll *sa,
|
2011-06-29 09:26:12 +05:30
|
|
|
const struct sock_fprog *filter_prog)
|
2010-11-12 14:32:18 +05:30
|
|
|
{
|
2011-06-29 09:26:12 +05:30
|
|
|
int fd;
|
2011-06-25 19:48:14 +05:30
|
|
|
if ((fd = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_IP))) < 0) {
|
2011-06-29 09:26:12 +05:30
|
|
|
log_error("create_raw_socket: socket failed: %s", strerror(errno));
|
2011-03-31 09:29:22 +05:30
|
|
|
goto out;
|
2010-11-13 01:03:17 +05:30
|
|
|
}
|
2011-06-29 09:26:12 +05:30
|
|
|
|
2011-07-01 13:44:10 +05:30
|
|
|
if (cs) {
|
|
|
|
if (filter_prog && (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER,
|
2011-07-01 15:19:39 +05:30
|
|
|
filter_prog, sizeof *filter_prog) != -1))
|
2011-07-01 13:44:10 +05:30
|
|
|
cs->using_dhcp_bpf = 1;
|
2011-07-01 15:19:39 +05:30
|
|
|
else
|
2011-07-01 13:44:10 +05:30
|
|
|
cs->using_dhcp_bpf = 0;
|
|
|
|
}
|
2011-06-29 09:26:12 +05:30
|
|
|
|
2011-06-26 02:01:21 +05:30
|
|
|
int opt = 1;
|
|
|
|
if (setsockopt(fd, SOL_SOCKET, SO_DONTROUTE, &opt, sizeof opt) == -1) {
|
2011-07-12 02:03:57 +05:30
|
|
|
log_error("create_raw_socket: Failed to set don't route: %s",
|
2011-06-26 02:01:21 +05:30
|
|
|
strerror(errno));
|
|
|
|
goto out_fd;
|
|
|
|
}
|
2011-06-25 20:16:24 +05:30
|
|
|
if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK) == -1) {
|
2011-07-12 02:03:57 +05:30
|
|
|
log_error("create_raw_socket: Set non-blocking failed: %s",
|
2011-06-26 02:25:00 +05:30
|
|
|
strerror(errno));
|
2011-06-25 20:16:24 +05:30
|
|
|
goto out_fd;
|
|
|
|
}
|
2011-06-29 09:26:12 +05:30
|
|
|
if (bind(fd, (struct sockaddr *)sa, sizeof *sa) < 0) {
|
|
|
|
log_error("create_raw_socket: bind failed: %s", strerror(errno));
|
|
|
|
goto out_fd;
|
|
|
|
}
|
|
|
|
return fd;
|
|
|
|
out_fd:
|
|
|
|
close(fd);
|
|
|
|
out:
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2011-07-01 13:44:10 +05:30
|
|
|
static int create_raw_listen_socket(struct client_state_t *cs, int ifindex)
|
2011-06-29 09:26:12 +05:30
|
|
|
{
|
2011-07-01 09:42:03 +05:30
|
|
|
static const struct sock_filter sf_dhcp[] = {
|
2011-07-01 13:44:10 +05:30
|
|
|
// Verify that the packet has a valid IPv4 version nibble and
|
|
|
|
// that no IP options are defined.
|
2011-07-01 09:42:03 +05:30
|
|
|
BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 0),
|
2011-07-01 13:44:10 +05:30
|
|
|
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x45, 1, 0),
|
2011-07-01 09:42:03 +05:30
|
|
|
BPF_STMT(BPF_RET + BPF_K, 0),
|
|
|
|
// Verify that the IP header has a protocol number indicating UDP.
|
|
|
|
BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 9),
|
|
|
|
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 1, 0),
|
|
|
|
BPF_STMT(BPF_RET + BPF_K, 0),
|
|
|
|
// Packet is UDP. Advance X past the IP header.
|
|
|
|
BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 0),
|
|
|
|
// Verify that the UDP client and server ports match that of the
|
|
|
|
// IANA-assigned DHCP ports.
|
|
|
|
BPF_STMT(BPF_LD + BPF_W + BPF_IND, 0),
|
2011-07-01 21:07:13 +05:30
|
|
|
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K,
|
|
|
|
(DHCP_SERVER_PORT << 16) + DHCP_CLIENT_PORT, 1, 0),
|
2011-07-01 09:42:03 +05:30
|
|
|
BPF_STMT(BPF_RET + BPF_K, 0),
|
2011-07-01 13:44:10 +05:30
|
|
|
// Get the UDP length field and store it in X.
|
|
|
|
BPF_STMT(BPF_LD + BPF_H + BPF_IND, 4),
|
|
|
|
BPF_STMT(BPF_MISC + BPF_TAX, 0),
|
|
|
|
// Get the IPv4 length field and store it in A and M[0].
|
2011-07-01 09:42:03 +05:30
|
|
|
BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 2),
|
2011-07-01 13:44:10 +05:30
|
|
|
BPF_STMT(BPF_ST, 0),
|
|
|
|
// Verify that UDP length = IP length - IP header size
|
|
|
|
BPF_STMT(BPF_ALU + BPF_SUB + BPF_K, 20),
|
|
|
|
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_X, 0, 1, 0),
|
|
|
|
BPF_STMT(BPF_RET + BPF_K, 0),
|
|
|
|
// Pass the number of octets that are specified in the IPv4 header.
|
|
|
|
BPF_STMT(BPF_LD + BPF_MEM, 0),
|
2011-07-01 09:42:03 +05:30
|
|
|
BPF_STMT(BPF_RET + BPF_A, 0),
|
2011-06-29 09:26:12 +05:30
|
|
|
};
|
2011-07-01 09:42:03 +05:30
|
|
|
static const struct sock_fprog sfp_dhcp = {
|
|
|
|
.len = sizeof sf_dhcp / sizeof sf_dhcp[0],
|
|
|
|
.filter = (struct sock_filter *)sf_dhcp,
|
2011-06-29 09:26:12 +05:30
|
|
|
};
|
|
|
|
struct sockaddr_ll sa = {
|
|
|
|
.sll_family = AF_PACKET,
|
|
|
|
.sll_protocol = htons(ETH_P_IP),
|
|
|
|
.sll_ifindex = ifindex,
|
|
|
|
};
|
2011-07-01 13:44:10 +05:30
|
|
|
return create_raw_socket(cs, &sa, &sfp_dhcp);
|
2011-06-29 09:26:12 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
// Broadcast a DHCP message using a raw socket.
|
|
|
|
static int send_dhcp_raw(struct dhcpmsg *payload)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
struct sockaddr_ll da = {
|
2011-06-26 02:01:21 +05:30
|
|
|
.sll_family = AF_PACKET,
|
|
|
|
.sll_protocol = htons(ETH_P_IP),
|
2011-07-04 04:26:57 +05:30
|
|
|
.sll_pkttype = PACKET_BROADCAST,
|
2011-06-26 02:25:00 +05:30
|
|
|
.sll_ifindex = client_config.ifindex,
|
2011-06-26 02:01:21 +05:30
|
|
|
.sll_halen = 6,
|
|
|
|
};
|
2011-06-29 09:26:12 +05:30
|
|
|
memcpy(da.sll_addr, "\xff\xff\xff\xff\xff\xff", 6);
|
2011-07-01 13:44:10 +05:30
|
|
|
int fd = create_raw_socket(NULL, &da, NULL);
|
2011-06-29 09:26:12 +05:30
|
|
|
if (fd == -1)
|
|
|
|
return ret;
|
2010-11-13 01:03:17 +05:30
|
|
|
|
2011-07-27 17:19:30 +05:30
|
|
|
// Send packets that are as short as possible.
|
2011-06-27 22:23:35 +05:30
|
|
|
ssize_t endloc = get_end_option_idx(payload);
|
2011-06-27 02:55:00 +05:30
|
|
|
if (endloc < 0) {
|
2011-07-26 09:18:35 +05:30
|
|
|
log_error("send_dhcp_raw: No end marker. Not sending.");
|
2011-06-29 09:26:12 +05:30
|
|
|
close(fd);
|
|
|
|
return ret;
|
2011-03-31 01:27:01 +05:30
|
|
|
}
|
2011-06-27 22:23:35 +05:30
|
|
|
size_t padding = sizeof payload->options - 1 - endloc;
|
2011-06-27 02:55:00 +05:30
|
|
|
size_t iud_len = sizeof(struct ip_udp_dhcp_packet) - padding;
|
|
|
|
size_t ud_len = sizeof(struct udp_dhcp_packet) - padding;
|
2011-06-27 03:20:35 +05:30
|
|
|
|
|
|
|
struct iphdr ph = {
|
|
|
|
.saddr = INADDR_ANY,
|
|
|
|
.daddr = INADDR_BROADCAST,
|
|
|
|
.protocol = IPPROTO_UDP,
|
|
|
|
.tot_len = htons(ud_len),
|
|
|
|
};
|
2011-06-27 02:55:00 +05:30
|
|
|
struct ip_udp_dhcp_packet iudmsg = {
|
2011-06-26 19:51:02 +05:30
|
|
|
.ip = {
|
|
|
|
.saddr = INADDR_ANY,
|
|
|
|
.daddr = INADDR_BROADCAST,
|
2011-06-27 03:20:35 +05:30
|
|
|
.protocol = IPPROTO_UDP,
|
|
|
|
.tot_len = htons(iud_len),
|
|
|
|
.ihl = sizeof iudmsg.ip >> 2,
|
|
|
|
.version = IPVERSION,
|
|
|
|
.ttl = IPDEFTTL,
|
2011-06-26 19:51:02 +05:30
|
|
|
},
|
|
|
|
.udp = {
|
|
|
|
.source = htons(DHCP_CLIENT_PORT),
|
|
|
|
.dest = htons(DHCP_SERVER_PORT),
|
2011-06-27 02:55:00 +05:30
|
|
|
.len = htons(ud_len),
|
2011-06-26 19:51:02 +05:30
|
|
|
},
|
|
|
|
.data = *payload,
|
|
|
|
};
|
2011-06-27 03:20:35 +05:30
|
|
|
uint16_t udpcs = net_checksum(&iudmsg.udp, ud_len);
|
|
|
|
uint16_t phcs = net_checksum(&ph, sizeof ph);
|
|
|
|
iudmsg.udp.check = net_checksum_add(udpcs, phcs);
|
2011-06-27 02:55:00 +05:30
|
|
|
iudmsg.ip.check = net_checksum(&iudmsg.ip, sizeof iudmsg.ip);
|
|
|
|
|
2011-06-29 09:26:12 +05:30
|
|
|
ret = safe_sendto(fd, (const char *)&iudmsg, iud_len, 0,
|
|
|
|
(struct sockaddr *)&da, sizeof da);
|
|
|
|
if (ret == -1)
|
2011-06-26 02:25:00 +05:30
|
|
|
log_error("send_dhcp_raw: sendto failed: %s", strerror(errno));
|
2010-11-13 01:03:17 +05:30
|
|
|
close(fd);
|
2011-06-29 09:26:12 +05:30
|
|
|
return ret;
|
2010-11-13 01:03:17 +05:30
|
|
|
}
|
2010-12-24 19:25:59 +05:30
|
|
|
|
2011-06-11 14:33:33 +05:30
|
|
|
// Switch listen socket between raw (if-bound), kernel (ip-bound), and none
|
2011-07-01 07:03:38 +05:30
|
|
|
static void change_listen_mode(struct client_state_t *cs, int new_mode)
|
2010-12-24 20:11:52 +05:30
|
|
|
{
|
|
|
|
cs->listenMode = new_mode;
|
|
|
|
if (cs->listenFd >= 0) {
|
|
|
|
epoll_del(cs, cs->listenFd);
|
|
|
|
close(cs->listenFd);
|
|
|
|
cs->listenFd = -1;
|
|
|
|
}
|
2011-07-12 02:03:57 +05:30
|
|
|
if (new_mode == LM_NONE)
|
2011-07-01 06:18:09 +05:30
|
|
|
return;
|
2011-07-01 07:32:28 +05:30
|
|
|
cs->listenFd = new_mode == LM_RAW ?
|
2011-07-01 13:44:10 +05:30
|
|
|
create_raw_listen_socket(cs, client_config.ifindex) :
|
2011-07-01 15:05:38 +05:30
|
|
|
create_udp_listen_socket(cs, client_config.interface);
|
2010-12-24 20:11:52 +05:30
|
|
|
if (cs->listenFd < 0) {
|
2011-07-12 02:03:57 +05:30
|
|
|
log_error("FATAL: Couldn't listen on socket: %s.", strerror(errno));
|
2010-12-24 20:11:52 +05:30
|
|
|
exit(EXIT_FAILURE);
|
|
|
|
}
|
2011-06-11 14:33:33 +05:30
|
|
|
epoll_add(cs, cs->listenFd);
|
2010-12-24 20:11:52 +05:30
|
|
|
}
|
|
|
|
|
2011-07-01 07:03:38 +05:30
|
|
|
void set_listen_raw(struct client_state_t *cs)
|
|
|
|
{
|
|
|
|
change_listen_mode(cs, LM_RAW);
|
|
|
|
}
|
|
|
|
|
|
|
|
void set_listen_cooked(struct client_state_t *cs)
|
|
|
|
{
|
|
|
|
change_listen_mode(cs, LM_COOKED);
|
|
|
|
}
|
|
|
|
|
|
|
|
void set_listen_none(struct client_state_t *cs)
|
|
|
|
{
|
|
|
|
change_listen_mode(cs, LM_NONE);
|
|
|
|
}
|
|
|
|
|
2011-07-01 15:03:12 +05:30
|
|
|
static int validate_dhcp_packet(struct client_state_t *cs, int len,
|
2011-07-18 20:17:35 +05:30
|
|
|
struct dhcpmsg *packet, uint8_t *msgtype)
|
2011-07-01 15:03:12 +05:30
|
|
|
{
|
|
|
|
if (len < sizeof *packet - sizeof packet->options) {
|
2011-07-01 21:07:13 +05:30
|
|
|
log_line("Packet is too short to contain magic cookie. Ignoring.");
|
2011-07-01 15:03:12 +05:30
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (ntohl(packet->cookie) != DHCP_MAGIC) {
|
|
|
|
log_line("Packet with bad magic number. Ignoring.");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (packet->xid != cs->xid) {
|
|
|
|
log_line("Packet XID %lx does not equal our XID %lx. Ignoring.",
|
|
|
|
packet->xid, cs->xid);
|
|
|
|
return 0;
|
|
|
|
}
|
2011-07-26 10:34:59 +05:30
|
|
|
*msgtype = get_option_msgtype(packet);
|
|
|
|
if (!*msgtype) {
|
2011-07-01 21:07:13 +05:30
|
|
|
log_line("Packet does not specify a DHCP message type. Ignoring.");
|
2011-07-01 15:03:12 +05:30
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2010-12-24 19:25:59 +05:30
|
|
|
void handle_packet(struct client_state_t *cs)
|
|
|
|
{
|
2011-07-18 20:17:35 +05:30
|
|
|
uint8_t msgtype;
|
2010-12-24 19:25:59 +05:30
|
|
|
int len;
|
2011-06-27 02:55:00 +05:30
|
|
|
struct dhcpmsg packet;
|
2010-12-24 19:25:59 +05:30
|
|
|
|
2011-07-01 14:08:38 +05:30
|
|
|
if (cs->listenMode == LM_NONE)
|
2010-12-24 19:25:59 +05:30
|
|
|
return;
|
2011-07-01 14:08:38 +05:30
|
|
|
len = cs->listenMode == LM_RAW ?
|
|
|
|
get_raw_packet(cs, &packet) : get_cooked_packet(&packet, cs->listenFd);
|
2010-12-24 19:25:59 +05:30
|
|
|
|
2011-06-11 13:57:56 +05:30
|
|
|
if (len < 0) {
|
|
|
|
// Transient issue handled by packet collection functions.
|
|
|
|
if (len == -2 || (len == -1 && errno == EINTR))
|
|
|
|
return;
|
2011-07-01 15:03:12 +05:30
|
|
|
log_error("Error reading from listening socket: %s. Reopening.",
|
2011-06-11 13:57:56 +05:30
|
|
|
strerror(errno));
|
|
|
|
change_listen_mode(cs, cs->listenMode);
|
2010-12-24 19:25:59 +05:30
|
|
|
}
|
|
|
|
|
2011-07-18 20:17:35 +05:30
|
|
|
if (!validate_dhcp_packet(cs, len, &packet, &msgtype))
|
2010-12-24 19:25:59 +05:30
|
|
|
return;
|
2011-07-18 20:17:35 +05:30
|
|
|
packet_action(cs, &packet, msgtype);
|
2010-12-24 19:25:59 +05:30
|
|
|
}
|
2011-06-11 20:49:05 +05:30
|
|
|
|
2011-06-27 22:31:39 +05:30
|
|
|
// Initialize a DHCP client packet that will be sent to a server
|
2011-06-27 21:50:22 +05:30
|
|
|
static struct dhcpmsg init_packet(char type, uint32_t xid)
|
2011-06-11 20:49:05 +05:30
|
|
|
{
|
2011-06-27 21:50:22 +05:30
|
|
|
struct dhcpmsg packet = {
|
|
|
|
.op = 1, // BOOTREQUEST (client)
|
|
|
|
.htype = 1, // ETH_10MB
|
|
|
|
.hlen = 6, // ETH_10MB_LEN
|
|
|
|
.cookie = htonl(DHCP_MAGIC),
|
2011-07-25 12:41:47 +05:30
|
|
|
.options[0] = DCODE_END,
|
2011-06-27 21:50:22 +05:30
|
|
|
.xid = xid,
|
|
|
|
};
|
2011-07-26 09:04:32 +05:30
|
|
|
add_option_msgtype(&packet, type);
|
2011-06-27 21:50:22 +05:30
|
|
|
memcpy(packet.chaddr, client_config.arp, 6);
|
2011-07-02 13:18:08 +05:30
|
|
|
add_option_clientid(&packet);
|
2011-06-27 21:50:22 +05:30
|
|
|
return packet;
|
2011-06-11 20:49:05 +05:30
|
|
|
}
|
|
|
|
|
2011-07-01 21:07:13 +05:30
|
|
|
int send_discover(struct client_state_t *cs)
|
2011-06-11 20:49:05 +05:30
|
|
|
{
|
2011-07-01 21:07:13 +05:30
|
|
|
struct dhcpmsg packet = init_packet(DHCPDISCOVER, cs->xid);
|
|
|
|
if (cs->clientAddr)
|
2011-07-26 09:04:32 +05:30
|
|
|
add_option_reqip(&packet, cs->clientAddr);
|
|
|
|
add_option_maxsize(&packet);
|
2011-06-27 22:23:35 +05:30
|
|
|
add_option_request_list(&packet);
|
2011-07-02 13:18:08 +05:30
|
|
|
add_option_vendor(&packet);
|
2011-07-03 15:33:54 +05:30
|
|
|
add_option_hostname(&packet);
|
2011-07-12 09:15:37 +05:30
|
|
|
log_line("Discovering DHCP servers...");
|
2011-06-26 02:25:00 +05:30
|
|
|
return send_dhcp_raw(&packet);
|
2011-06-11 20:49:05 +05:30
|
|
|
}
|
|
|
|
|
2011-07-01 21:07:13 +05:30
|
|
|
int send_selecting(struct client_state_t *cs)
|
2011-06-11 20:49:05 +05:30
|
|
|
{
|
2011-07-12 02:32:32 +05:30
|
|
|
char clibuf[INET_ADDRSTRLEN];
|
2011-07-01 21:07:13 +05:30
|
|
|
struct dhcpmsg packet = init_packet(DHCPREQUEST, cs->xid);
|
2011-07-26 09:04:32 +05:30
|
|
|
add_option_reqip(&packet, cs->clientAddr);
|
|
|
|
add_option_serverid(&packet, cs->serverAddr);
|
|
|
|
add_option_maxsize(&packet);
|
2011-06-27 22:23:35 +05:30
|
|
|
add_option_request_list(&packet);
|
2011-07-02 13:18:08 +05:30
|
|
|
add_option_vendor(&packet);
|
2011-07-03 15:33:54 +05:30
|
|
|
add_option_hostname(&packet);
|
2011-07-12 02:32:32 +05:30
|
|
|
inet_ntop(AF_INET, &(struct in_addr){.s_addr = cs->clientAddr},
|
|
|
|
clibuf, sizeof clibuf);
|
2011-07-12 09:15:37 +05:30
|
|
|
log_line("Sending a selection request for %s...", clibuf);
|
2011-06-26 02:25:00 +05:30
|
|
|
return send_dhcp_raw(&packet);
|
2011-06-11 20:49:05 +05:30
|
|
|
}
|
|
|
|
|
2011-07-01 21:07:13 +05:30
|
|
|
int send_renew(struct client_state_t *cs)
|
2011-06-11 20:49:05 +05:30
|
|
|
{
|
2011-07-01 21:07:13 +05:30
|
|
|
struct dhcpmsg packet = init_packet(DHCPREQUEST, cs->xid);
|
|
|
|
packet.ciaddr = cs->clientAddr;
|
2011-07-26 09:04:32 +05:30
|
|
|
add_option_maxsize(&packet);
|
2011-06-27 22:23:35 +05:30
|
|
|
add_option_request_list(&packet);
|
2011-07-02 13:18:08 +05:30
|
|
|
add_option_vendor(&packet);
|
2011-07-03 15:33:54 +05:30
|
|
|
add_option_hostname(&packet);
|
2011-07-12 09:15:37 +05:30
|
|
|
log_line("Sending a renew request...");
|
2011-07-04 04:26:57 +05:30
|
|
|
return send_dhcp_cooked(cs, &packet);
|
2011-07-01 21:07:13 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
int send_rebind(struct client_state_t *cs)
|
|
|
|
{
|
|
|
|
struct dhcpmsg packet = init_packet(DHCPREQUEST, cs->xid);
|
|
|
|
packet.ciaddr = cs->clientAddr;
|
2011-07-26 09:04:32 +05:30
|
|
|
add_option_reqip(&packet, cs->clientAddr);
|
|
|
|
add_option_maxsize(&packet);
|
2011-07-01 21:07:13 +05:30
|
|
|
add_option_request_list(&packet);
|
2011-07-02 13:18:08 +05:30
|
|
|
add_option_vendor(&packet);
|
2011-07-03 15:33:54 +05:30
|
|
|
add_option_hostname(&packet);
|
2011-07-12 09:15:37 +05:30
|
|
|
log_line("Sending a rebind request...");
|
2011-07-01 21:07:13 +05:30
|
|
|
return send_dhcp_raw(&packet);
|
2011-06-11 20:49:05 +05:30
|
|
|
}
|
|
|
|
|
2011-07-01 21:07:13 +05:30
|
|
|
int send_decline(struct client_state_t *cs, uint32_t server)
|
2011-06-11 20:49:05 +05:30
|
|
|
{
|
2011-07-01 21:07:13 +05:30
|
|
|
struct dhcpmsg packet = init_packet(DHCPDECLINE, cs->xid);
|
2011-07-26 09:04:32 +05:30
|
|
|
add_option_reqip(&packet, cs->clientAddr);
|
|
|
|
add_option_serverid(&packet, server);
|
2011-07-12 09:15:37 +05:30
|
|
|
log_line("Sending a decline message...");
|
2011-06-26 02:25:00 +05:30
|
|
|
return send_dhcp_raw(&packet);
|
2011-06-11 20:49:05 +05:30
|
|
|
}
|
|
|
|
|
2011-07-01 21:07:13 +05:30
|
|
|
int send_release(struct client_state_t *cs)
|
2011-06-11 20:49:05 +05:30
|
|
|
{
|
2011-06-28 00:37:00 +05:30
|
|
|
struct dhcpmsg packet = init_packet(DHCPRELEASE, libc_random_u32());
|
2011-07-01 21:07:13 +05:30
|
|
|
packet.ciaddr = cs->clientAddr;
|
2011-07-26 09:04:32 +05:30
|
|
|
add_option_reqip(&packet, cs->clientAddr);
|
|
|
|
add_option_serverid(&packet, cs->serverAddr);
|
2011-07-12 09:15:37 +05:30
|
|
|
log_line("Sending a release message...");
|
2011-07-04 04:26:57 +05:30
|
|
|
return send_dhcp_cooked(cs, &packet);
|
2011-06-11 20:49:05 +05:30
|
|
|
}
|
2011-06-26 02:25:00 +05:30
|
|
|
|