Move source from ndhc/ to src/ since ifchd is no longer a separate program.

This commit is contained in:
Nicholas J. Kain
2014-04-06 16:57:06 -04:00
parent b511d45c2f
commit b761889025
37 changed files with 8 additions and 8 deletions

22
src/CMakeLists.txt Normal file
View File

@@ -0,0 +1,22 @@
project (ndhc)
cmake_minimum_required (VERSION 2.6)
include_directories("${PROJECT_SOURCE_DIR}")
set(RAGEL_IFCHD_PARSE ${CMAKE_CURRENT_BINARY_DIR}/ifchd-parse.c)
find_program(RAGEL ragel)
add_custom_command(
OUTPUT ${RAGEL_IFCHD_PARSE}
COMMAND ${RAGEL} -G2 -o ${RAGEL_IFCHD_PARSE} ifchd-parse.rl
DEPENDS ifchd-parse.rl
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Compiling Ragel state machine: ifchd-parse.rl"
VERBATIM
)
file(GLOB NDHC_SRCS "*.c")
add_executable(ndhc ${RAGEL_IFCHD_PARSE} ${NDHC_SRCS})
target_link_libraries(ndhc ncmlib)

766
src/arp.c Normal file
View File

@@ -0,0 +1,766 @@
/* arp.c - arp ping checking
*
* Copyright (c) 2010-2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - 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.
*/
#include <stdbool.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <netinet/if_ether.h>
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <linux/filter.h>
#include <errno.h>
#include "nk/log.h"
#include "nk/io.h"
#include "arp.h"
#include "state.h"
#include "dhcp.h"
#include "sys.h"
#include "ifchange.h"
#include "options.h"
#include "leasefile.h"
#include "sockd.h"
#define ARP_MSG_SIZE 0x2a
#define ARP_RETRANS_DELAY 5000 // ms
// From RFC5227
int arp_probe_wait = 1000; // initial random delay (ms)
int arp_probe_num = 3; // number of probe packets
int arp_probe_min = 1000; // minimum delay until repeated probe (ms)
int arp_probe_max = 2000; // maximum delay until repeated probe (ms)
#define ANNOUNCE_WAIT 2000 // delay before announcing
#define ANNOUNCE_NUM 2 // number of Announcement packets
#define ANNOUNCE_INTERVAL 2000 // time between Announcement packets
#define MAX_CONFLICTS 10 // max conflicts before rate-limiting
#define RATE_LIMIT_INTERVAL 60000 // delay between successive attempts
#define DEFEND_INTERVAL 10000 // minimum interval between defensive ARPs
typedef enum {
AS_NONE = 0, // Nothing to react to wrt ARP
AS_COLLISION_CHECK, // Checking to see if another host has our IP before
// accepting a new lease.
AS_GW_CHECK, // Seeing if the default GW still exists on the local
// segment after the hardware link was lost.
AS_GW_QUERY, // Finding the default GW MAC address.
AS_DEFENSE, // Defending our IP address (RFC5227)
AS_MAX,
} arp_state_t;
static long long arp_wake_ts[AS_MAX] = { -1, -1, -1, -1, -1 };
typedef enum {
ASEND_COLLISION_CHECK,
ASEND_GW_PING,
ASEND_ANNOUNCE,
ASEND_MAX,
} arp_send_t;
static arp_state_t arpState;
struct arp_stats {
long long ts;
int count;
};
static struct arp_stats arp_send_stats[ASEND_MAX];
static int using_arp_bpf; // Is a BPF installed on the ARP socket?
int arp_relentless_def; // Don't give up defense no matter what.
static long long last_conflict_ts; // TS of the last conflicting ARP seen.
static int gw_check_init_pingcount; // Initial count of ASEND_GW_PING when
// AS_GW_CHECK was entered.
static uint16_t probe_wait_time; // Time to wait for a COLLISION_CHECK reply.
static long long arp_check_start_ts; // TS of when we started the
// AS_COLLISION_CHECK state.
static unsigned int total_conflicts; // Total number of address conflicts on
// the interface. Never decreases.
static struct dhcpmsg arp_dhcp_packet; // Used only for AS_COLLISION_CHECK
static char arp_router_has_replied;
static char arp_server_has_replied;
static struct arpMsg arpreply;
static size_t arpreply_offset;
static void arpreply_clear(void)
{
memset(&arpreply, 0, sizeof arpreply);
arpreply_offset = 0;
}
void arp_reset_send_stats(void)
{
for (int i = 0; i < ASEND_MAX; ++i) {
arp_send_stats[i].ts = 0;
arp_send_stats[i].count = 0;
}
}
static int get_arp_basic_socket(void)
{
char resp;
int fd = request_sockd_fd("a", 1, &resp);
switch (resp) {
case 'A': using_arp_bpf = 1; break;
case 'a': using_arp_bpf = 0; break;
default: suicide("%s: (%s) expected a or A sockd reply but got %c",
client_config.interface, __func__, resp);
}
return fd;
}
static int get_arp_defense_socket(struct client_state_t *cs)
{
char buf[32];
size_t buflen = 0;
buf[0] = 'd';
buflen += 1;
memcpy(buf + buflen, &cs->clientAddr, sizeof cs->clientAddr);
buflen += sizeof cs->clientAddr;
memcpy(buf + buflen, client_config.arp, 6);
buflen += 6;
char resp;
int fd = request_sockd_fd(buf, buflen, &resp);
switch (resp) {
case 'D': using_arp_bpf = 1; break;
case 'd': using_arp_bpf = 0; break;
default: suicide("%s: (%s) expected d or D sockd reply but got %c",
client_config.interface, __func__, resp);
}
return fd;
}
static int arp_open_fd(struct client_state_t *cs, arp_state_t state)
{
if (cs->arpFd >= 0) {
log_warning("%s: (%s) called but fd already exists",
client_config.interface, __func__);
return 0;
}
switch (state) {
default:
log_warning("%s: (%s) called for 'default' state",
client_config.interface, __func__);
return 0;
case AS_COLLISION_CHECK:
case AS_GW_QUERY:
case AS_GW_CHECK: cs->arpFd = get_arp_basic_socket(); break;
case AS_DEFENSE: cs->arpFd = get_arp_defense_socket(cs); break;
}
if (cs->arpFd < 0) {
log_error("arp: Failed to create socket: %s", strerror(errno));
return -1;
}
epoll_add(cs->epollFd, cs->arpFd);
arpreply_clear();
return 0;
}
static void arp_min_close_fd(struct client_state_t *cs)
{
if (cs->arpFd < 0)
return;
epoll_del(cs->epollFd, cs->arpFd);
close(cs->arpFd);
cs->arpFd = -1;
arpState = AS_NONE;
}
static void arp_switch_state(struct client_state_t *cs, arp_state_t state)
{
if (arpState == state || arpState >= AS_MAX)
return;
if (state == AS_NONE) {
arp_close_fd(cs);
return;
}
bool force_reopen = state == AS_DEFENSE || arpState == AS_DEFENSE;
if (force_reopen)
arp_min_close_fd(cs);
if (cs->arpFd < 0 || force_reopen) {
if (arp_open_fd(cs, state) < 0)
suicide("arp: Failed to open arpFd when changing state %u -> %u",
arpState, state);
}
arpState = state;
}
void arp_close_fd(struct client_state_t *cs)
{
arp_min_close_fd(cs);
for (int i = 0; i < AS_MAX; ++i)
arp_wake_ts[i] = -1;
}
static void arp_reopen_fd(struct client_state_t *cs)
{
arp_state_t prev_state = arpState;
arp_min_close_fd(cs);
arp_switch_state(cs, prev_state);
}
static int arp_send(struct client_state_t *cs, struct arpMsg *arp)
{
struct sockaddr_ll addr = {
.sll_family = AF_PACKET,
.sll_ifindex = client_config.ifindex,
.sll_halen = 6,
};
memcpy(addr.sll_addr, client_config.arp, 6);
if (cs->arpFd < 0) {
log_warning("arp: Send attempted when no ARP fd is open.");
return -1;
}
if (safe_sendto(cs->arpFd, (const char *)arp, sizeof *arp,
0, (struct sockaddr *)&addr, sizeof addr) < 0) {
log_error("arp: sendto failed: %s", strerror(errno));
arp_reopen_fd(cs);
return -1;
}
return 0;
}
#define BASE_ARPMSG() struct arpMsg arp = { \
.h_proto = htons(ETH_P_ARP), \
.htype = htons(ARPHRD_ETHER), \
.ptype = htons(ETH_P_IP), \
.hlen = 6, .plen = 4, \
.operation = htons(ARPOP_REQUEST), \
.smac = {0}, \
}; \
memcpy(arp.h_source, client_config.arp, 6); \
memset(arp.h_dest, 0xff, 6); \
memcpy(arp.smac, client_config.arp, 6)
// Returns 0 on success, -1 on failure.
static int arp_ping(struct client_state_t *cs, uint32_t test_ip)
{
BASE_ARPMSG();
memcpy(arp.sip4, &cs->clientAddr, sizeof cs->clientAddr);
memcpy(arp.dip4, &test_ip, sizeof test_ip);
if (arp_send(cs, &arp) < 0)
return -1;
arp_send_stats[ASEND_GW_PING].count++;
arp_send_stats[ASEND_GW_PING].ts = curms();
return 0;
}
// Returns 0 on success, -1 on failure.
static int arp_ip_anon_ping(struct client_state_t *cs, uint32_t test_ip)
{
BASE_ARPMSG();
memcpy(arp.dip4, &test_ip, sizeof test_ip);
log_line("arp: Probing for hosts that may conflict with our lease...");
if (arp_send(cs, &arp) < 0)
return -1;
arp_send_stats[ASEND_COLLISION_CHECK].count++;
arp_send_stats[ASEND_COLLISION_CHECK].ts = curms();
return 0;
}
static int arp_announcement(struct client_state_t *cs)
{
BASE_ARPMSG();
memcpy(arp.sip4, &cs->clientAddr, 4);
memcpy(arp.dip4, &cs->clientAddr, 4);
if (arp_send(cs, &arp) < 0)
return -1;
arp_send_stats[ASEND_ANNOUNCE].count++;
arp_send_stats[ASEND_ANNOUNCE].ts = curms();
return 0;
}
#undef BASE_ARPMSG
// Callable from DS_REQUESTING, DS_RENEWING, or DS_REBINDING via an_packet()
int arp_check(struct client_state_t *cs, struct dhcpmsg *packet)
{
memcpy(&arp_dhcp_packet, packet, sizeof (struct dhcpmsg));
arp_switch_state(cs, AS_COLLISION_CHECK);
if (arp_ip_anon_ping(cs, arp_dhcp_packet.yiaddr) < 0)
return -1;
cs->arpPrevState = cs->dhcpState;
cs->dhcpState = DS_COLLISION_CHECK;
arp_check_start_ts = arp_send_stats[ASEND_COLLISION_CHECK].ts;
probe_wait_time = arp_probe_wait;
arp_wake_ts[AS_COLLISION_CHECK] = arp_check_start_ts + probe_wait_time;
return 0;
}
// Callable only from DS_BOUND via state.c:ifup_action().
int arp_gw_check(struct client_state_t *cs)
{
if (arpState == AS_GW_CHECK) // Guard against state bounce.
return 0;
gw_check_init_pingcount = arp_send_stats[ASEND_GW_PING].count;
arp_server_has_replied = 0;
if (arp_ping(cs, cs->serverAddr) < 0)
return -1;
if (cs->routerAddr) {
arp_router_has_replied = 0;
if (arp_ping(cs, cs->routerAddr) < 0)
return -1;
} else
arp_router_has_replied = 1;
arp_switch_state(cs, AS_GW_CHECK);
cs->arpPrevState = cs->dhcpState;
cs->dhcpState = DS_BOUND_GW_CHECK;
arp_wake_ts[AS_GW_CHECK] =
arp_send_stats[ASEND_GW_PING].ts + ARP_RETRANS_DELAY + 250;
return 0;
}
// Should only be called from DS_BOUND state.
static int arp_get_gw_hwaddr(struct client_state_t *cs)
{
if (cs->dhcpState != DS_BOUND)
log_error("arp_get_gw_hwaddr: called when state != DS_BOUND");
arp_switch_state(cs, AS_GW_QUERY);
if (cs->routerAddr)
log_line("arp: Searching for dhcp server and gw addresses...");
else
log_line("arp: Searching for dhcp server address...");
cs->got_server_arp = 0;
if (arp_ping(cs, cs->serverAddr) < 0)
return -1;
if (cs->routerAddr) {
cs->got_router_arp = 0;
if (arp_ping(cs, cs->routerAddr) < 0)
return -1;
} else
cs->got_router_arp = 1;
arp_wake_ts[AS_GW_QUERY] =
arp_send_stats[ASEND_GW_PING].ts + ARP_RETRANS_DELAY + 250;
return 0;
}
static void arp_failed(struct client_state_t *cs)
{
log_line("arp: Offered address is in use. Declining.");
send_decline(cs, arp_dhcp_packet.yiaddr);
arp_wake_ts[AS_COLLISION_CHECK] = -1;
reinit_selecting(cs, total_conflicts < MAX_CONFLICTS ?
0 : RATE_LIMIT_INTERVAL);
}
static void arp_gw_failed(struct client_state_t *cs)
{
arp_wake_ts[AS_GW_CHECK] = -1;
reinit_selecting(cs, 0);
}
static int act_if_arp_gw_failed(struct client_state_t *cs)
{
if (arp_send_stats[ASEND_GW_PING].count >= gw_check_init_pingcount + 6) {
if (arp_router_has_replied && !arp_server_has_replied)
log_line("arp: DHCP server didn't reply. Getting new lease.");
else if (!arp_router_has_replied && arp_server_has_replied)
log_line("arp: Gateway didn't reply. Getting new lease.");
else
log_line("arp: DHCP server and gateway didn't reply. Getting new lease.");
arp_gw_failed(cs);
return 1;
}
return 0;
}
void arp_set_defense_mode(struct client_state_t *cs)
{
arp_switch_state(cs, AS_DEFENSE);
}
void arp_success(struct client_state_t *cs)
{
char clibuf[INET_ADDRSTRLEN];
struct in_addr temp_addr = {.s_addr = arp_dhcp_packet.yiaddr};
inet_ntop(AF_INET, &temp_addr, clibuf, sizeof clibuf);
log_line("Lease of %s obtained. Lease time is %ld seconds.",
clibuf, cs->lease);
cs->clientAddr = arp_dhcp_packet.yiaddr;
cs->dhcpState = DS_BOUND;
cs->init = 0;
last_conflict_ts = 0;
arp_wake_ts[AS_COLLISION_CHECK] = -1;
ifchange_bind(cs, &arp_dhcp_packet);
if (cs->arpPrevState == DS_RENEWING || cs->arpPrevState == DS_REBINDING) {
arp_switch_state(cs, AS_DEFENSE);
} else {
cs->routerAddr = get_option_router(&arp_dhcp_packet);
arp_get_gw_hwaddr(cs);
}
set_listen_none(cs);
write_leasefile(temp_addr);
arp_announcement(cs);
if (client_config.quit_after_lease)
exit(EXIT_SUCCESS);
if (!client_config.foreground)
background();
}
static void arp_gw_success(struct client_state_t *cs)
{
log_line("arp: Network seems unchanged. Resuming normal operation.");
arp_switch_state(cs, AS_DEFENSE);
arp_announcement(cs);
arp_wake_ts[AS_GW_CHECK] = -1;
cs->dhcpState = cs->arpPrevState;
}
// ARP validation functions that will be performed by the BPF if it is
// installed.
static int arp_validate_bpf(struct arpMsg *am)
{
if (am->h_proto != htons(ETH_P_ARP)) {
log_warning("arp: IP header does not indicate ARP protocol");
return 0;
}
if (am->htype != htons(ARPHRD_ETHER)) {
log_warning("arp: ARP hardware type field invalid");
return 0;
}
if (am->ptype != htons(ETH_P_IP)) {
log_warning("arp: ARP protocol type field invalid");
return 0;
}
if (am->hlen != 6) {
log_warning("arp: ARP hardware address length invalid");
return 0;
}
if (am->plen != 4) {
log_warning("arp: ARP protocol address length invalid");
return 0;
}
return 1;
}
// ARP validation functions that will be performed by the BPF if it is
// installed.
static int arp_validate_bpf_defense(struct client_state_t *cs,
struct arpMsg *am)
{
if (memcmp(am->sip4, &cs->clientAddr, 4))
return 0;
if (!memcmp(am->smac, client_config.arp, 6))
return 0;
return 1;
}
static int arp_is_query_reply(struct arpMsg *am)
{
if (am->operation != htons(ARPOP_REPLY))
return 0;
if (memcmp(am->h_dest, client_config.arp, 6))
return 0;
if (memcmp(am->dmac, client_config.arp, 6))
return 0;
return 1;
}
static int arp_gen_probe_wait(struct client_state_t *cs)
{
// This is not a uniform distribution but it doesn't matter here.
return arp_probe_min + (nk_random_u32(&cs->rnd32_state) & 0x7fffffffu)
% (arp_probe_max - arp_probe_min);
}
static void arp_defense_timeout(struct client_state_t *cs, long long nowts)
{
(void)nowts; // Suppress warning; parameter necessary but unused.
if (arp_wake_ts[AS_DEFENSE] != -1) {
log_line("arp: Defending our lease IP.");
arp_announcement(cs);
arp_wake_ts[AS_DEFENSE] = -1;
}
}
static void arp_gw_check_timeout(struct client_state_t *cs, long long nowts)
{
arp_defense_timeout(cs, nowts);
if (act_if_arp_gw_failed(cs))
return;
long long rtts = arp_send_stats[ASEND_GW_PING].ts + ARP_RETRANS_DELAY;
if (nowts < rtts) {
arp_wake_ts[AS_GW_CHECK] = rtts;
return;
}
if (!arp_router_has_replied) {
log_line("arp: Still waiting for gateway to reply to arp ping...");
if (arp_ping(cs, cs->routerAddr) < 0)
log_warning("arp: Failed to send ARP ping in retransmission.");
}
if (!arp_server_has_replied) {
log_line("arp: Still waiting for DHCP server to reply to arp ping...");
if (arp_ping(cs, cs->serverAddr) < 0)
log_warning("arp: Failed to send ARP ping in retransmission.");
}
arp_wake_ts[AS_GW_CHECK] =
arp_send_stats[ASEND_GW_PING].ts + ARP_RETRANS_DELAY;
}
static void arp_gw_query_timeout(struct client_state_t *cs, long long nowts)
{
arp_defense_timeout(cs, nowts);
long long rtts = arp_send_stats[ASEND_GW_PING].ts + ARP_RETRANS_DELAY;
if (nowts < rtts) {
arp_wake_ts[AS_GW_QUERY] = rtts;
return;
}
if (!cs->got_router_arp) {
log_line("arp: Still looking for gateway hardware address...");
if (arp_ping(cs, cs->routerAddr) < 0)
log_warning("arp: Failed to send ARP ping in retransmission.");
}
if (!cs->got_server_arp) {
log_line("arp: Still looking for DHCP server hardware address...");
if (arp_ping(cs, cs->serverAddr) < 0)
log_warning("arp: Failed to send ARP ping in retransmission.");
}
arp_wake_ts[AS_GW_QUERY] =
arp_send_stats[ASEND_GW_PING].ts + ARP_RETRANS_DELAY;
}
static void arp_collision_timeout(struct client_state_t *cs, long long nowts)
{
arp_defense_timeout(cs, nowts);
if (nowts >= arp_check_start_ts + ANNOUNCE_WAIT ||
arp_send_stats[ASEND_COLLISION_CHECK].count >= arp_probe_num) {
arp_success(cs);
return;
}
long long rtts = arp_send_stats[ASEND_COLLISION_CHECK].ts +
probe_wait_time;
if (nowts < rtts) {
arp_wake_ts[AS_COLLISION_CHECK] = rtts;
return;
}
if (arp_ip_anon_ping(cs, arp_dhcp_packet.yiaddr) < 0)
log_warning("arp: Failed to send ARP ping in retransmission.");
probe_wait_time = arp_gen_probe_wait(cs);
arp_wake_ts[AS_COLLISION_CHECK] =
arp_send_stats[ASEND_COLLISION_CHECK].ts + probe_wait_time;
}
static void arp_do_defense(struct client_state_t *cs)
{
// Even though the BPF will usually catch this case, sometimes there are
// packets still in the socket buffer that arrived before the defense
// BPF was installed, so it's necessary to check here.
if (!arp_validate_bpf_defense(cs, &arpreply))
return;
log_line("arp: Detected a peer attempting to use our IP!");
long long nowts = curms();
arp_wake_ts[AS_DEFENSE] = -1;
if (!last_conflict_ts ||
nowts - last_conflict_ts < DEFEND_INTERVAL) {
log_line("arp: Defending our lease IP.");
arp_announcement(cs);
} else if (!arp_relentless_def) {
log_line("arp: Conflicting peer is persistent. Requesting new lease.");
send_release(cs);
reinit_selecting(cs, 0);
} else {
arp_wake_ts[AS_DEFENSE] =
arp_send_stats[ASEND_ANNOUNCE].ts + DEFEND_INTERVAL;
}
total_conflicts++;
last_conflict_ts = nowts;
}
static void arp_do_gw_query_done(struct client_state_t *cs)
{
arp_wake_ts[AS_GW_QUERY] = -1;
arp_switch_state(cs, AS_DEFENSE);
arp_announcement(cs); // Do a second announcement.
}
static void arp_do_gw_query(struct client_state_t *cs)
{
if (!arp_is_query_reply(&arpreply)) {
arp_do_defense(cs);
return;
}
if (!memcmp(arpreply.sip4, &cs->routerAddr, 4)) {
memcpy(cs->routerArp, arpreply.smac, 6);
log_line("arp: Gateway hardware address %02x:%02x:%02x:%02x:%02x:%02x",
cs->routerArp[0], cs->routerArp[1],
cs->routerArp[2], cs->routerArp[3],
cs->routerArp[4], cs->routerArp[5]);
cs->got_router_arp = 1;
if (cs->routerAddr == cs->serverAddr)
goto server_is_router;
if (cs->got_server_arp)
arp_do_gw_query_done(cs);
return;
}
if (!memcmp(arpreply.sip4, &cs->serverAddr, 4)) {
server_is_router:
memcpy(cs->serverArp, arpreply.smac, 6);
log_line("arp: DHCP Server hardware address %02x:%02x:%02x:%02x:%02x:%02x",
cs->serverArp[0], cs->serverArp[1],
cs->serverArp[2], cs->serverArp[3],
cs->serverArp[4], cs->serverArp[5]);
cs->got_server_arp = 1;
if (cs->got_router_arp)
arp_do_gw_query_done(cs);
return;
}
arp_do_defense(cs);
}
static void arp_do_collision_check(struct client_state_t *cs)
{
if (!arp_is_query_reply(&arpreply))
return;
// If this packet was sent from our lease IP, and does not have a
// MAC address matching our own (the latter check guards against stupid
// hubs or repeaters), then it's a conflict and thus a failure.
if (!memcmp(arpreply.sip4, &arp_dhcp_packet.yiaddr, 4) &&
!memcmp(client_config.arp, arpreply.smac, 6)) {
total_conflicts++;
arp_failed(cs);
}
}
static void arp_do_gw_check(struct client_state_t *cs)
{
if (!arp_is_query_reply(&arpreply))
return;
if (!memcmp(arpreply.sip4, &cs->routerAddr, 4)) {
// Success only if the router/gw MAC matches stored value
if (!memcmp(cs->routerArp, arpreply.smac, 6)) {
arp_router_has_replied = 1;
if (cs->routerAddr == cs->serverAddr)
goto server_is_router;
if (arp_server_has_replied)
arp_gw_success(cs);
} else {
log_line("arp: Gateway is different. Getting a new lease.");
arp_gw_failed(cs);
}
return;
}
if (!memcmp(arpreply.sip4, &cs->serverAddr, 4)) {
server_is_router:
// Success only if the server MAC matches stored value
if (!memcmp(cs->serverArp, arpreply.smac, 6)) {
arp_server_has_replied = 1;
if (arp_router_has_replied)
arp_gw_success(cs);
} else {
log_line("arp: DHCP server is different. Getting a new lease.");
arp_gw_failed(cs);
}
}
}
static void arp_do_invalid(struct client_state_t *cs)
{
log_error("handle_arp_response: called in invalid state %u", arpState);
arp_close_fd(cs);
}
typedef struct {
void (*packet_fn)(struct client_state_t *cs);
void (*timeout_fn)(struct client_state_t *cs, long long nowts);
} arp_state_fn_t;
static const arp_state_fn_t arp_states[] = {
{ arp_do_invalid, 0 }, // AS_NONE
{ arp_do_collision_check, arp_collision_timeout }, // AS_COLLISION_CHECK
{ arp_do_gw_check, arp_gw_check_timeout }, // AS_GW_CHECK
{ arp_do_gw_query, arp_gw_query_timeout }, // AS_GW_QUERY
{ arp_do_defense, arp_defense_timeout }, // AS_DEFENSE
{ arp_do_invalid, 0 }, // AS_MAX
};
void handle_arp_response(struct client_state_t *cs)
{
ssize_t r = 0;
if (arpreply_offset < sizeof arpreply) {
r = safe_read(cs->arpFd, (char *)&arpreply + arpreply_offset,
sizeof arpreply - arpreply_offset);
if (r < 0) {
log_error("arp: ARP response read failed: %s", strerror(errno));
switch (arpState) {
case AS_COLLISION_CHECK: arp_failed(cs); break;
case AS_GW_CHECK: arp_gw_failed(cs); break;
default: arp_reopen_fd(cs); break;
}
} else
arpreply_offset += (size_t)r;
}
if (r <= 0) {
handle_arp_timeout(cs, curms());
return;
}
if (arpreply_offset < ARP_MSG_SIZE)
return;
// Emulate the BPF filters if they are not in use.
if (!using_arp_bpf && (!arp_validate_bpf(&arpreply) ||
(arpState == AS_DEFENSE &&
!arp_validate_bpf_defense(cs, &arpreply)))) {
arpreply_clear();
return;
}
if (arp_states[arpState].packet_fn)
arp_states[arpState].packet_fn(cs);
arpreply_clear();
}
// Perform retransmission if necessary.
void handle_arp_timeout(struct client_state_t *cs, long long nowts)
{
if (arp_states[arpState].timeout_fn)
arp_states[arpState].timeout_fn(cs, nowts);
}
long long arp_get_wake_ts(void)
{
long long mt = -1;
for (int i = 0; i < AS_MAX; ++i) {
if (arp_wake_ts[i] < 0)
continue;
if (mt < 0 || mt > arp_wake_ts[i])
mt = arp_wake_ts[i];
}
return mt;
}

71
src/arp.h Normal file
View File

@@ -0,0 +1,71 @@
/* arp.h - functions to call the interface change daemon
*
* Copyright (c) 2010-2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - 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.
*/
#ifndef ARP_H_
#define ARP_H_
#include <stdint.h>
#include <net/if_arp.h>
#include "ndhc.h"
#include "dhcp.h"
struct arpMsg {
// Ethernet header
uint8_t h_dest[6]; // 00 destination ether addr
uint8_t h_source[6]; // 06 source ether addr
uint16_t h_proto; // 0c packet type ID field
// ARP packet
uint16_t htype; // 0e hardware type (must be ARPHRD_ETHER)
uint16_t ptype; // 10 protocol type (must be ETH_P_IP)
uint8_t hlen; // 12 hardware address length (must be 6)
uint8_t plen; // 13 protocol address length (must be 4)
uint16_t operation; // 14 ARP opcode
uint8_t smac[6]; // 16 sender's hardware address
uint8_t sip4[4]; // 1c sender's IP address
uint8_t dmac[6]; // 20 target's hardware address
uint8_t dip4[4]; // 26 target's IP address
uint8_t pad[18]; // 2a pad for min. ethernet payload (60 bytes)
};
extern int arp_probe_wait;
extern int arp_probe_num;
extern int arp_probe_min;
extern int arp_probe_max;
extern int arp_relentless_def;
void arp_reset_send_stats(void);
void arp_close_fd(struct client_state_t *cs);
int arp_check(struct client_state_t *cs, struct dhcpmsg *packet);
int arp_gw_check(struct client_state_t *cs);
void arp_set_defense_mode(struct client_state_t *cs);
void arp_success(struct client_state_t *cs);
void handle_arp_response(struct client_state_t *cs);
void handle_arp_timeout(struct client_state_t *cs, long long nowts);
long long arp_get_wake_ts(void);
#endif /* ARP_H_ */

544
src/dhcp.c Normal file
View File

@@ -0,0 +1,544 @@
/* dhcp.c - general DHCP protocol handling
*
* Copyright (c) 2004-2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - 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.
*/
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <netpacket/packet.h>
#include <net/ethernet.h>
#include <errno.h>
#include "nk/log.h"
#include "nk/io.h"
#include "nk/random.h"
#include "dhcp.h"
#include "state.h"
#include "arp.h"
#include "ifchange.h"
#include "sys.h"
#include "options.h"
#include "sockd.h"
typedef enum {
LM_NONE = 0,
LM_COOKED,
LM_RAW
} listen_mode_t;
static int get_udp_unicast_socket(void)
{
return request_sockd_fd("u", 1, NULL);
}
static int get_raw_broadcast_socket(void)
{
return request_sockd_fd("s", 1, NULL);
}
static int get_udp_listen_socket(struct client_state_t *cs)
{
char buf[32];
buf[0] = 'u';
memcpy(buf + 1, &cs->clientAddr, sizeof cs->clientAddr);
return request_sockd_fd(buf, 1 + sizeof cs->clientAddr, NULL);
}
static int get_raw_listen_socket(struct client_state_t *cs)
{
char resp;
int fd = request_sockd_fd("L", 1, &resp);
switch (resp) {
case 'L': cs->using_dhcp_bpf = 1; break;
case 'l': cs->using_dhcp_bpf = 0; break;
default: suicide("%s: (%s) expected l or L sockd reply but got %c",
client_config.interface, __func__, resp);
}
return fd;
}
// Broadcast a DHCP message using a UDP socket.
static ssize_t send_dhcp_cooked(struct client_state_t *cs,
struct dhcpmsg *payload)
{
ssize_t ret = -1;
int fd = get_udp_unicast_socket();
if (fd < 0)
goto out;
struct sockaddr_in raddr = {
.sin_family = AF_INET,
.sin_port = htons(DHCP_SERVER_PORT),
.sin_addr.s_addr = cs->serverAddr,
};
if (connect(fd, (struct sockaddr *)&raddr, sizeof(struct sockaddr)) < 0) {
log_error("%s: (%s) connect failed: %s", client_config.interface,
__func__, strerror(errno));
goto out_fd;
}
// Send packets that are as short as possible.
ssize_t endloc = get_end_option_idx(payload);
if (endloc < 0) {
log_error("%s: (%s) No end marker. Not sending.",
client_config.interface, __func__);
goto out_fd;
}
size_t payload_len =
sizeof *payload - (sizeof payload->options - 1 - endloc);
ret = safe_write(fd, (const char *)payload, payload_len);
if (ret < 0 || (size_t)ret != payload_len)
log_error("%s: (%s) write failed: %d", client_config.interface,
__func__, ret);
out_fd:
close(fd);
out:
return ret;
}
// Read a packet from a cooked socket. Returns -1 on fatal error, -2 on
// transient error.
static ssize_t get_cooked_packet(struct dhcpmsg *packet, int fd)
{
memset(packet, 0, sizeof *packet);
ssize_t bytes = safe_read(fd, (char *)packet, sizeof *packet);
if (bytes < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK)
return -2;
log_line("%s: Read on listen socket failed: %s",
client_config.interface, strerror(errno));
return -1;
}
return bytes;
}
// 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;
}
// This function is not suitable for summing buffers that are greater than
// 128k bytes in length: failure case will be incorrect checksums via
// 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.
static uint16_t net_checksum(void *buf, size_t size)
{
uint32_t sum = 0;
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)));
}
if (odd) {
uint16_t hi = b[i*2];
uint16_t lo = 0;
sum += ntohs((lo + (hi << 8)));
}
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;
}
static int get_raw_packet_validate_bpf(struct ip_udp_dhcp_packet *packet)
{
if (packet->ip.version != IPVERSION) {
log_warning("%s: IP version is not IPv4.", client_config.interface);
return 0;
}
if (packet->ip.ihl != sizeof packet->ip >> 2) {
log_warning("%s: IP header length incorrect.",
client_config.interface);
return 0;
}
if (packet->ip.protocol != IPPROTO_UDP) {
log_warning("%s: IP header is not UDP: %d",
client_config.interface, packet->ip.protocol);
return 0;
}
if (ntohs(packet->udp.dest) != DHCP_CLIENT_PORT) {
log_warning("%s: UDP destination port incorrect: %d",
client_config.interface, ntohs(packet->udp.dest));
return 0;
}
if (ntohs(packet->udp.len) !=
ntohs(packet->ip.tot_len) - sizeof packet->ip) {
log_warning("%s: UDP header length incorrect.",
client_config.interface);
return 0;
}
return 1;
}
// Read a packet from a raw socket. Returns -1 on fatal error, -2 on
// transient error.
static ssize_t get_raw_packet(struct client_state_t *cs,
struct dhcpmsg *payload)
{
struct ip_udp_dhcp_packet packet;
memset(&packet, 0, sizeof packet);
ssize_t inc = safe_read(cs->listenFd, (char *)&packet, sizeof packet);
if (inc < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK)
return -2;
log_warning("%s: (%s) read error %s", client_config.interface,
__func__, strerror(errno));
return -1;
}
if (inc != ntohs(packet.ip.tot_len)) {
log_warning("%s: UDP length does not match header length fields.",
client_config.interface);
return -2;
}
if (!cs->using_dhcp_bpf && !get_raw_packet_validate_bpf(&packet))
return -2;
if (!ip_checksum(&packet)) {
log_warning("%s: IP header checksum incorrect.",
client_config.interface);
return -2;
}
if (packet.udp.check && !udp_checksum(&packet)) {
log_error("%s: Packet with bad UDP checksum received. Ignoring.",
client_config.interface);
return -2;
}
size_t l = ntohs(packet.ip.tot_len) - sizeof packet.ip - sizeof packet.udp;
memcpy(payload, &packet.data, l);
return l;
}
// Broadcast a DHCP message using a raw socket.
static ssize_t send_dhcp_raw(struct dhcpmsg *payload)
{
ssize_t ret = -1;
int fd = get_raw_broadcast_socket();
if (fd < 0)
return ret;
// Send packets that are as short as possible.
ssize_t endloc = get_end_option_idx(payload);
if (endloc < 0) {
log_error("%s: (%s) No end marker. Not sending.",
client_config.interface, __func__);
close(fd);
return ret;
}
size_t padding = sizeof payload->options - 1 - endloc;
size_t iud_len = sizeof(struct ip_udp_dhcp_packet) - padding;
size_t ud_len = sizeof(struct udp_dhcp_packet) - padding;
struct iphdr ph = {
.saddr = INADDR_ANY,
.daddr = INADDR_BROADCAST,
.protocol = IPPROTO_UDP,
.tot_len = htons(ud_len),
};
struct ip_udp_dhcp_packet iudmsg = {
.ip = {
.saddr = INADDR_ANY,
.daddr = INADDR_BROADCAST,
.protocol = IPPROTO_UDP,
.tot_len = htons(iud_len),
.ihl = sizeof iudmsg.ip >> 2,
.version = IPVERSION,
.ttl = IPDEFTTL,
},
.data = *payload,
};
iudmsg.udp.source = htons(DHCP_CLIENT_PORT);
iudmsg.udp.dest = htons(DHCP_SERVER_PORT);
iudmsg.udp.len = htons(ud_len);
iudmsg.udp.check = 0;
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);
iudmsg.ip.check = net_checksum(&iudmsg.ip, sizeof iudmsg.ip);
struct sockaddr_ll da = {
.sll_family = AF_PACKET,
.sll_protocol = htons(ETH_P_IP),
.sll_pkttype = PACKET_BROADCAST,
.sll_ifindex = client_config.ifindex,
.sll_halen = 6,
};
memcpy(da.sll_addr, "\xff\xff\xff\xff\xff\xff", 6);
ret = safe_sendto(fd, (const char *)&iudmsg, iud_len, 0,
(struct sockaddr *)&da, sizeof da);
if (ret < 0)
log_error("%s: (%s) sendto failed: %s", client_config.interface,
__func__, strerror(errno));
close(fd);
return ret;
}
// Switch listen socket between raw (if-bound), kernel (ip-bound), and none
static void change_listen_mode(struct client_state_t *cs, int new_mode)
{
cs->listenMode = new_mode;
if (cs->listenFd >= 0) {
epoll_del(cs->epollFd, cs->listenFd);
close(cs->listenFd);
cs->listenFd = -1;
}
switch (cs->listenMode) {
default: return;
case LM_RAW: cs->listenFd = get_raw_listen_socket(cs); break;
case LM_COOKED: cs->listenFd = get_udp_listen_socket(cs); break;
}
if (cs->listenFd < 0)
suicide("%s: FATAL: Couldn't listen on socket: %s",
client_config.interface, strerror(errno));
epoll_add(cs->epollFd, cs->listenFd);
}
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);
}
static int validate_dhcp_packet(struct client_state_t *cs, size_t len,
struct dhcpmsg *packet, uint8_t *msgtype)
{
if (len < offsetof(struct dhcpmsg, options)) {
log_warning("%s: Packet is too short to contain magic cookie. Ignoring.",
client_config.interface);
return 0;
}
if (ntohl(packet->cookie) != DHCP_MAGIC) {
log_warning("%s: Packet with bad magic number. Ignoring.",
client_config.interface);
return 0;
}
if (packet->xid != cs->xid) {
log_warning("%s: Packet XID %lx does not equal our XID %lx. Ignoring.",
client_config.interface, packet->xid, cs->xid);
return 0;
}
if (memcmp(packet->chaddr, client_config.arp, sizeof client_config.arp)) {
log_warning("%s: Packet client MAC %2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x does not equal our MAC %2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x. Ignoring it.",
client_config.interface,
packet->chaddr[0], packet->chaddr[1], packet->chaddr[2],
packet->chaddr[3], packet->chaddr[4], packet->chaddr[5],
client_config.arp[0], client_config.arp[1],
client_config.arp[2], client_config.arp[3],
client_config.arp[4], client_config.arp[5]);
return 0;
}
*msgtype = get_option_msgtype(packet);
if (!*msgtype) {
log_warning("%s: Packet does not specify a DHCP message type. Ignoring.",
client_config.interface);
return 0;
}
char clientid[MAX_DOPT_SIZE];
size_t cidlen = get_option_clientid(packet, clientid, MAX_DOPT_SIZE);
if (cidlen == 0)
return 1;
if (memcmp(client_config.clientid, clientid,
min_size_t(cidlen, client_config.clientid_len))) {
log_warning("%s: Packet clientid does not match our clientid. Ignoring.",
client_config.interface);
return 0;
}
return 1;
}
void handle_packet(struct client_state_t *cs)
{
uint8_t msgtype;
struct dhcpmsg packet;
if (cs->listenMode == LM_NONE)
return;
ssize_t r = cs->listenMode == LM_RAW ?
get_raw_packet(cs, &packet) : get_cooked_packet(&packet, cs->listenFd);
if (r < 0) {
// Transient issue handled by packet collection functions.
if (r == -2 || (r == -1 && errno == EINTR))
return;
log_error("%s: Error reading from listening socket: %s. Reopening.",
client_config.interface, strerror(errno));
change_listen_mode(cs, cs->listenMode);
return;
}
size_t len = (size_t)r;
if (!validate_dhcp_packet(cs, len, &packet, &msgtype))
return;
packet_action(cs, &packet, msgtype);
}
// Initialize a DHCP client packet that will be sent to a server
static struct dhcpmsg init_packet(char type, uint32_t xid)
{
struct dhcpmsg packet = {
.op = 1, // BOOTREQUEST (client)
.htype = 1, // ETH_10MB
.hlen = 6, // ETH_10MB_LEN
.cookie = htonl(DHCP_MAGIC),
.options[0] = DCODE_END,
.xid = xid,
};
add_option_msgtype(&packet, type);
memcpy(packet.chaddr, client_config.arp, 6);
add_option_clientid(&packet, client_config.clientid,
client_config.clientid_len);
return packet;
}
ssize_t send_discover(struct client_state_t *cs)
{
struct dhcpmsg packet = init_packet(DHCPDISCOVER, cs->xid);
if (cs->clientAddr)
add_option_reqip(&packet, cs->clientAddr);
add_option_maxsize(&packet);
add_option_request_list(&packet);
add_option_vendor(&packet);
add_option_hostname(&packet);
log_line("%s: Discovering DHCP servers...", client_config.interface);
return send_dhcp_raw(&packet);
}
ssize_t send_selecting(struct client_state_t *cs)
{
char clibuf[INET_ADDRSTRLEN];
struct dhcpmsg packet = init_packet(DHCPREQUEST, cs->xid);
add_option_reqip(&packet, cs->clientAddr);
add_option_serverid(&packet, cs->serverAddr);
add_option_maxsize(&packet);
add_option_request_list(&packet);
add_option_vendor(&packet);
add_option_hostname(&packet);
inet_ntop(AF_INET, &(struct in_addr){.s_addr = cs->clientAddr},
clibuf, sizeof clibuf);
log_line("%s: Sending a selection request for %s...",
client_config.interface, clibuf);
return send_dhcp_raw(&packet);
}
ssize_t send_renew(struct client_state_t *cs)
{
struct dhcpmsg packet = init_packet(DHCPREQUEST, cs->xid);
packet.ciaddr = cs->clientAddr;
add_option_maxsize(&packet);
add_option_request_list(&packet);
add_option_vendor(&packet);
add_option_hostname(&packet);
log_line("%s: Sending a renew request...", client_config.interface);
return send_dhcp_cooked(cs, &packet);
}
ssize_t send_rebind(struct client_state_t *cs)
{
struct dhcpmsg packet = init_packet(DHCPREQUEST, cs->xid);
packet.ciaddr = cs->clientAddr;
add_option_reqip(&packet, cs->clientAddr);
add_option_maxsize(&packet);
add_option_request_list(&packet);
add_option_vendor(&packet);
add_option_hostname(&packet);
log_line("%s: Sending a rebind request...", client_config.interface);
return send_dhcp_raw(&packet);
}
ssize_t send_decline(struct client_state_t *cs, uint32_t server)
{
struct dhcpmsg packet = init_packet(DHCPDECLINE, cs->xid);
add_option_reqip(&packet, cs->clientAddr);
add_option_serverid(&packet, server);
log_line("%s: Sending a decline message...", client_config.interface);
return send_dhcp_raw(&packet);
}
ssize_t send_release(struct client_state_t *cs)
{
struct dhcpmsg packet = init_packet(DHCPRELEASE,
nk_random_u32(&cs->rnd32_state));
packet.ciaddr = cs->clientAddr;
add_option_reqip(&packet, cs->clientAddr);
add_option_serverid(&packet, cs->serverAddr);
log_line("%s: Sending a release message...", client_config.interface);
return send_dhcp_cooked(cs, &packet);
}

95
src/dhcp.h Normal file
View File

@@ -0,0 +1,95 @@
/* dhcp.h - general DHCP protocol handling
*
* Copyright (c) 2004-2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - 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.
*/
#ifndef NDHC_DHCP_H_
#define NDHC_DHCP_H_
#include <stdint.h>
#include <netinet/udp.h>
#include <netinet/ip.h>
#include "ndhc.h"
#define DHCP_SERVER_PORT 67
#define DHCP_CLIENT_PORT 68
#define DHCP_MAGIC 0x63825363
enum {
DHCPDISCOVER = 1,
DHCPOFFER = 2,
DHCPREQUEST = 3,
DHCPDECLINE = 4,
DHCPACK = 5,
DHCPNAK = 6,
DHCPRELEASE = 7,
DHCPINFORM = 8
};
struct dhcpmsg {
uint8_t op; // Message type: 1 = BOOTREQUEST for clients.
uint8_t htype; // ARP HW address type: always '1' for ethernet.
uint8_t hlen; // Hardware address length: always '6' for ethernet.
uint8_t hops; // Client sets to zero.
uint32_t xid; // Transaction ID: random number identifying session
uint16_t secs; // Filled by client: seconds since client began address
// aquisition or renewal process.
uint16_t flags; // DHCP flags
uint32_t ciaddr; // Client IP: only filled in if client is in BOUND, RENEW,
// or REBINDING and can reply to ARP requests
uint32_t yiaddr; // 'your' (client) IP address
uint32_t siaddr; // Always zero -- unused.
uint32_t giaddr; // Always zero -- unused.
uint8_t chaddr[16]; // Client MAC address
uint8_t sname[64]; // More DHCP options (#3)
uint8_t file[128]; // More DHCP options (#2)
uint32_t cookie; // Magic number cookie that starts DHCP options
uint8_t options[308]; // DHCP options field (#1)
};
struct ip_udp_dhcp_packet {
struct iphdr ip;
struct udphdr udp;
struct dhcpmsg data;
};
struct udp_dhcp_packet {
struct udphdr udp;
struct dhcpmsg data;
};
void set_listen_raw(struct client_state_t *cs);
void set_listen_cooked(struct client_state_t *cs);
void set_listen_none(struct client_state_t *cs);
void handle_packet(struct client_state_t *cs);
ssize_t send_discover(struct client_state_t *cs);
ssize_t send_selecting(struct client_state_t *cs);
ssize_t send_renew(struct client_state_t *cs);
ssize_t send_rebind(struct client_state_t *cs);
ssize_t send_decline(struct client_state_t *cs, uint32_t server);
ssize_t send_release(struct client_state_t *cs);
#endif

217
src/duiaid.c Normal file
View File

@@ -0,0 +1,217 @@
/* duiaid.c - DUID/IAID storage and generation for clientids
*
* Copyright (c) 2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - 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.
*/
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <limits.h>
#include <errno.h>
#include "nk/log.h"
#include "nk/random.h"
#include "nk/io.h"
#include "duiaid.h"
#include "ndhc.h"
static void get_duid_path(char *duidfile, size_t dlen)
{
int splen = snprintf(duidfile, dlen, "%s/DUID", state_dir);
if (splen < 0)
suicide("%s: snprintf failed; return=%d", __func__, splen);
if ((size_t)splen >= dlen)
suicide("%s: snprintf dest buffer too small %d >= %u",
__func__, splen, sizeof dlen);
}
static void get_iaid_path(char *iaidfile, size_t ilen, uint8_t *hwaddr,
size_t hwaddrlen)
{
if (hwaddrlen != 6)
suicide("%s: Hardware address length=%u != 6 bytes",
__func__, hwaddrlen);
int splen = snprintf
(iaidfile, ilen,
"%s/IAID-%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x",
state_dir, hwaddr[0], hwaddr[1], hwaddr[2],
hwaddr[3], hwaddr[4], hwaddr[5]);
if (splen < 0)
suicide("%s: snprintf failed; return=%d", __func__, splen);
if ((size_t)splen >= ilen)
suicide("%s: snprintf dest buffer too small %d >= %u",
__func__, splen, sizeof ilen);
}
static int open_duidfile_read(void)
{
char duidfile[PATH_MAX];
get_duid_path(duidfile, sizeof duidfile);
int fd = open(duidfile, O_RDONLY, 0);
if (fd < 0) {
log_line("Failed to open duidfile '%s' for reading: %s",
duidfile, strerror(errno));
}
return fd;
}
static int open_duidfile_write(void)
{
char duidfile[PATH_MAX];
get_duid_path(duidfile, sizeof duidfile);
int fd = open(duidfile, O_WRONLY|O_TRUNC|O_CREAT, 0644);
if (fd < 0)
suicide("Failed to open duidfile '%s' for writing: %s",
duidfile, strerror(errno));
return fd;
}
static int open_iaidfile_read(uint8_t *hwaddr, size_t hwaddrlen)
{
char iaidfile[PATH_MAX];
get_iaid_path(iaidfile, sizeof iaidfile, hwaddr, hwaddrlen);
int fd = open(iaidfile, O_RDONLY, 0);
if (fd < 0) {
log_line("Failed to open iaidfile '%s' for reading: %s",
iaidfile, strerror(errno));
}
return fd;
}
static int open_iaidfile_write(uint8_t *hwaddr, size_t hwaddrlen)
{
char iaidfile[PATH_MAX];
get_iaid_path(iaidfile, sizeof iaidfile, hwaddr, hwaddrlen);
int fd = open(iaidfile, O_WRONLY|O_TRUNC|O_CREAT, 0644);
if (fd < 0)
suicide("Failed to open iaidfile '%s' for writing: %s",
iaidfile, strerror(errno));
return fd;
}
// We use DUID-UUID (RFC6355)
// It is a 16-bit type=4 in network byte order followed by a 128-byte UUID.
// RFC6355 specifies a RFC4122 UUID, but I simply use a 128-byte random
// value, as the complexity of RFC4122 UUID generation is completely
// unwarranted for DHCPv4.
static size_t generate_duid(struct nk_random_state_u32 *s, char *dest,
size_t dlen)
{
const size_t tlen = sizeof(uint16_t) + 4 * sizeof(uint32_t);
if (dlen < tlen)
suicide("%s: dlen < %u", __func__, tlen);
size_t off = 0;
uint16_t typefield = htons(4);
memcpy(dest+off, &typefield, sizeof typefield);
off += sizeof typefield;
for (size_t i = 0; i < 4; ++i) {
uint32_t r32 = nk_random_u32(s);
memcpy(dest+off, &r32, sizeof r32);
off += sizeof r32;
}
return off;
}
// RFC6355 specifies the IAID as a 32-bit value that uniquely identifies
// a hardware link for a given host.
static size_t generate_iaid(struct nk_random_state_u32 *s, char *dest,
size_t dlen)
{
if (dlen < sizeof(uint32_t))
suicide("%s: dlen < %u", __func__, sizeof(uint32_t));
size_t off = 0;
uint32_t r32 = nk_random_u32(s);
memcpy(dest+off, &r32, sizeof r32);
off += sizeof r32;
return off;
}
// Failures are all fatal.
void get_clientid(struct client_state_t *cs, struct client_config_t *cc)
{
if (cc->clientid_len > 0)
return;
char iaid[sizeof cc->clientid];
char duid[sizeof cc->clientid];
size_t iaid_len;
size_t duid_len;
int fd = open_iaidfile_read(cc->arp, sizeof cc->arp);
if (fd < 0) {
iaid_len = generate_iaid(&cs->rnd32_state, iaid, sizeof iaid);
fd = open_iaidfile_write(cc->arp, sizeof cc->arp);
ssize_t r = safe_write(fd, iaid, iaid_len);
if (r < 0 || (size_t)r != iaid_len)
suicide("%s: (%s) failed to write generated IAID.",
cc->interface, __func__);
} else {
ssize_t r = safe_read(fd, iaid, sizeof iaid);
if (r < 0)
suicide("%s: (%s) failed to read IAID from file",
cc->interface, __func__);
iaid_len = (size_t)r;
}
close(fd);
fd = open_duidfile_read();
if (fd < 0) {
duid_len = generate_duid(&cs->rnd32_state, duid, sizeof duid);
fd = open_duidfile_write();
ssize_t r = safe_write(fd, duid, duid_len);
if (r < 0 || (size_t)r != duid_len)
suicide("%s: (%s) failed to write generated DUID.",
cc->interface, __func__);
} else {
ssize_t r = safe_read(fd, duid, sizeof duid);
if (r < 0)
suicide("%s: (%s) failed to read DUID from file",
cc->interface, __func__);
duid_len = (size_t)r;
}
close(fd);
const uint8_t cid_type = 255;
size_t cdl = sizeof cid_type + iaid_len + duid_len;
if (cdl > sizeof cc->clientid)
suicide("%s: (%s) clientid length %u > %u",
cc->interface, __func__, cdl, sizeof cc->clientid);
uint8_t cid_len = 0;
memcpy(cc->clientid + cid_len, &cid_type, sizeof cid_type);
cid_len += sizeof cid_type;
memcpy(cc->clientid + cid_len, iaid, iaid_len);
cid_len += iaid_len;
memcpy(cc->clientid + cid_len, duid, duid_len);
cid_len += duid_len;
cc->clientid_len = cid_len;
}

35
src/duiaid.h Normal file
View File

@@ -0,0 +1,35 @@
/* duiaid.h - DUID/IAID storage and generation for clientids
*
* Copyright (c) 2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - 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.
*/
#ifndef NJK_NDHC_DUIAID_H_
#define NJK_NDHC_DUIAID_H_
#include "ndhc.h"
void get_clientid(struct client_state_t *cs, struct client_config_t *cc);
#endif /* NJK_NDHC_DUIAID_H_ */

314
src/ifchange.c Normal file
View File

@@ -0,0 +1,314 @@
/* ifchange.c - functions to call the interface change daemon
*
* Copyright (c) 2004-2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - 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.
*/
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <errno.h>
#include <limits.h>
#include "nk/log.h"
#include "nk/io.h"
#include "options.h"
#include "ndhc.h"
#include "dhcp.h"
#include "options.h"
#include "arp.h"
#include "ifchange.h"
static struct dhcpmsg cfg_packet; // Copy of the current configuration packet.
static int ifcmd_raw(char *buf, size_t buflen, char *optname,
char *optdata, ssize_t optlen)
{
if (!optdata) {
log_warning("%s: (%s) '%s' option has no data",
client_config.interface, __func__, optname);
return -1;
}
if (optlen > INT_MAX || optlen < 0) {
log_warning("%s: (%s) '%s' option optlen out of bounds",
client_config.interface, __func__, optname);
return -1;
}
if (buflen < strlen(optname) + optlen + 3) {
log_warning("%s: (%s) '%s' option buf too short",
client_config.interface, __func__, optname);
return -1;
}
int ioptlen = (int)optlen;
ssize_t olen = snprintf(buf, buflen, "%s:%.*s;",
optname, ioptlen, optdata);
if (olen < 0 || (size_t)olen >= buflen) {
log_warning("%s: (%s) '%s' option would truncate, so it was dropped.",
client_config.interface, __func__, optname);
memset(buf, 0, buflen);
return -1;
}
return olen;
}
static int ifcmd_bytes(char *buf, size_t buflen, char *optname,
uint8_t *optdata, ssize_t optlen)
{
return ifcmd_raw(buf, buflen, optname, (char *)optdata, optlen);
}
static int ifcmd_u8(char *buf, size_t buflen, char *optname,
uint8_t *optdata, ssize_t optlen)
{
if (!optdata || optlen < 1)
return -1;
char numbuf[16];
uint8_t c = optdata[0];
ssize_t olen = snprintf(numbuf, sizeof numbuf, "%c", c);
if (olen < 0 || (size_t)olen >= sizeof numbuf)
return -1;
return ifcmd_raw(buf, buflen, optname, numbuf, strlen(numbuf));
}
static int ifcmd_u16(char *buf, size_t buflen, char *optname,
uint8_t *optdata, ssize_t optlen)
{
if (!optdata || optlen < 2)
return -1;
char numbuf[16];
uint16_t v;
memcpy(&v, optdata, 2);
v = ntohs(v);
ssize_t olen = snprintf(numbuf, sizeof numbuf, "%hu", v);
if (olen < 0 || (size_t)olen >= sizeof numbuf)
return -1;
return ifcmd_raw(buf, buflen, optname, numbuf, strlen(numbuf));
}
static int ifcmd_s32(char *buf, size_t buflen, char *optname,
uint8_t *optdata, ssize_t optlen)
{
if (!optdata || optlen < 4)
return -1;
char numbuf[16];
int32_t v;
memcpy(&v, optdata, 4);
v = ntohl(v);
ssize_t olen = snprintf(numbuf, sizeof numbuf, "%d", v);
if (olen < 0 || (size_t)olen >= sizeof numbuf)
return -1;
return ifcmd_raw(buf, buflen, optname, numbuf, strlen(numbuf));
}
static int ifcmd_ip(char *buf, size_t buflen, char *optname,
uint8_t *optdata, ssize_t optlen)
{
if (!optdata || optlen < 4)
return -1;
char ipbuf[INET_ADDRSTRLEN];
inet_ntop(AF_INET, optdata, ipbuf, sizeof ipbuf);
return ifcmd_raw(buf, buflen, optname, ipbuf, strlen(ipbuf));
}
static int ifcmd_iplist(char *out, size_t outlen, char *optname,
uint8_t *optdata, ssize_t optlen)
{
char buf[2048];
char ipbuf[INET_ADDRSTRLEN];
size_t bufoff = 0;
size_t optoff = 0;
if (!optdata || optlen < 4)
return -1;
inet_ntop(AF_INET, optdata + optoff, ipbuf, sizeof ipbuf);
ssize_t wc = snprintf(buf + bufoff, sizeof buf, "%s", ipbuf);
if (wc < 0 || (size_t)wc >= sizeof buf)
return -1;
optoff += 4;
bufoff += wc;
while (optlen - optoff >= 4) {
inet_ntop(AF_INET, optdata + optoff, ipbuf, sizeof ipbuf);
wc = snprintf(buf + bufoff, sizeof buf, ",%s", ipbuf);
if (wc < 0 || (size_t)wc >= sizeof buf)
return -1;
optoff += 4;
bufoff += wc;
}
return ifcmd_raw(out, outlen, optname, buf, strlen(buf));
}
static int ifchd_cmd(char *b, size_t bl, uint8_t *od, ssize_t ol, uint8_t code)
{
switch (code) {
case DCODE_ROUTER: return ifcmd_ip(b, bl, "routr", od, ol);
case DCODE_DNS: return ifcmd_iplist(b, bl, "dns", od, ol);
case DCODE_LPRSVR: return ifcmd_iplist(b, bl, "lpr", od, ol);
case DCODE_NTPSVR: return ifcmd_iplist(b, bl, "ntp", od, ol);
case DCODE_WINS: return ifcmd_iplist(b, bl, "wins", od, ol);
case DCODE_HOSTNAME: return ifcmd_bytes(b, bl, "host", od, ol);
case DCODE_DOMAIN: return ifcmd_bytes(b, bl, "dom", od, ol);
case DCODE_TIMEZONE: return ifcmd_s32(b, bl, "tzone", od, ol);
case DCODE_MTU: return ifcmd_u16(b, bl, "mtu", od, ol);
case DCODE_IPTTL: return ifcmd_u8(b, bl, "ipttl", od, ol);
default: break;
}
log_warning("%s: Invalid option code (%c) for ifchd cmd.",
client_config.interface, code);
return -1;
}
static void pipewrite(struct client_state_t *cs, const char *buf, size_t count)
{
cs->ifchWorking = 1;
ssize_t r = safe_write(pToIfchW, buf, count);
if (r < 0 || (size_t)r != count) {
log_error("%s: (%s) write failed: %d", client_config.interface);
return;
}
log_line("%s: Sent to ifchd: '%s'", client_config.interface, buf);
}
void ifchange_deconfig(struct client_state_t *cs)
{
char buf[256];
if (cs->ifDeconfig)
return;
cs->ifDeconfig = 1;
snprintf(buf, sizeof buf, "ip4:0.0.0.0,255.255.255.255;");
log_line("%s: Resetting IP configuration.", client_config.interface);
pipewrite(cs, buf, strlen(buf));
memset(&cfg_packet, 0, sizeof cfg_packet);
}
static size_t send_client_ip(char *out, size_t olen, struct dhcpmsg *packet)
{
uint8_t optdata[MAX_DOPT_SIZE], olddata[MAX_DOPT_SIZE];
char ip[INET_ADDRSTRLEN], sn[INET_ADDRSTRLEN], bc[INET_ADDRSTRLEN];
ssize_t optlen, oldlen;
bool change_ipaddr = false;
bool have_subnet = false;
bool change_subnet = false;
bool have_bcast = false;
bool change_bcast = false;
if (memcmp(&packet->yiaddr, &cfg_packet.yiaddr, sizeof packet->yiaddr))
change_ipaddr = true;
inet_ntop(AF_INET, &packet->yiaddr, ip, sizeof ip);
optlen = get_dhcp_opt(packet, DCODE_SUBNET, optdata, sizeof optdata);
if (optlen >= 4) {
have_subnet = true;
inet_ntop(AF_INET, optdata, sn, sizeof sn);
oldlen = get_dhcp_opt(&cfg_packet, DCODE_SUBNET, olddata,
sizeof olddata);
if (oldlen != optlen || memcmp(optdata, olddata, optlen))
change_subnet = true;
}
optlen = get_dhcp_opt(packet, DCODE_BROADCAST, optdata, sizeof optdata);
if (optlen >= 4) {
have_bcast = true;
inet_ntop(AF_INET, optdata, bc, sizeof bc);
oldlen = get_dhcp_opt(&cfg_packet, DCODE_BROADCAST, olddata,
sizeof olddata);
if (oldlen != optlen || memcmp(optdata, olddata, optlen))
change_bcast = true;
}
// Nothing to change.
if (!change_ipaddr && !change_subnet && !change_bcast)
return 0;
if (!have_subnet) {
static char snClassC[] = "255.255.255.0";
log_line("%s: Server did not send a subnet mask. Assuming 255.255.255.0.",
client_config.interface);
memcpy(sn, snClassC, sizeof snClassC);
}
int snlen;
if (have_bcast) {
snlen = snprintf(out, olen, "ip4:%s,%s,%s;", ip, sn, bc);
} else {
snlen = snprintf(out, olen, "ip4:%s,%s;", ip, sn);
}
if (snlen < 0 || (size_t)snlen >= olen) {
log_warning("%s: (%s) ip4 command would truncate so it was dropped.",
client_config.interface, __func__);
memset(out, 0, olen);
return 0;
}
return snlen;
}
static size_t send_cmd(char *out, size_t olen, struct dhcpmsg *packet,
uint8_t code)
{
uint8_t optdata[MAX_DOPT_SIZE], olddata[MAX_DOPT_SIZE];
ssize_t optlen, oldlen;
if (!packet)
return 0;
optlen = get_dhcp_opt(packet, code, optdata, sizeof optdata);
if (!optlen)
return 0;
oldlen = get_dhcp_opt(&cfg_packet, code, olddata, sizeof olddata);
if (oldlen == optlen && !memcmp(optdata, olddata, optlen))
return 0;
int r = ifchd_cmd(out, olen, optdata, optlen, code);
return r > 0 ? r : 0;
}
void ifchange_bind(struct client_state_t *cs, struct dhcpmsg *packet)
{
char buf[2048];
size_t bo;
if (!packet)
return;
memset(buf, 0, sizeof buf);
bo = send_client_ip(buf, sizeof buf, packet);
bo += send_cmd(buf + bo, sizeof buf - bo, packet, DCODE_ROUTER);
bo += send_cmd(buf + bo, sizeof buf - bo, packet, DCODE_DNS);
bo += send_cmd(buf + bo, sizeof buf - bo, packet, DCODE_HOSTNAME);
bo += send_cmd(buf + bo, sizeof buf - bo, packet, DCODE_DOMAIN);
bo += send_cmd(buf + bo, sizeof buf - bo, packet, DCODE_MTU);
bo += send_cmd(buf + bo, sizeof buf - bo, packet, DCODE_WINS);
if (bo)
pipewrite(cs, buf, bo);
cs->ifDeconfig = 0;
memcpy(&cfg_packet, packet, sizeof cfg_packet);
}

35
src/ifchange.h Normal file
View File

@@ -0,0 +1,35 @@
/* ifchange.h - functions to call the interface change daemon
*
* Copyright (c) 2004-2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - 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.
*/
#ifndef IFCHANGE_H_
#define IFCHANGE_H_
void ifchange_bind(struct client_state_t *cs, struct dhcpmsg *packet);
void ifchange_deconfig(struct client_state_t *cs);
#endif

34
src/ifchd-parse.h Normal file
View File

@@ -0,0 +1,34 @@
/* ifchd-parse.h - interface change daemon parser
*
* Copyright (c) 2004-2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - 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.
*/
#ifndef _NJK_NDHC_IFCHD_PARSE_H_
#define _NJK_NDHC_IFCHD_PARSE_H_
int execute_buffer(char *newbuf);
#endif /* _NJK_NDHC_IFCHD_PARSE_H_ */

235
src/ifchd-parse.rl Normal file
View File

@@ -0,0 +1,235 @@
/* ifchd-parse.rl - interface change daemon parser
*
* Copyright (c) 2004-2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - 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.
*/
#include <stddef.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include "nk/log.h"
#include "ifchd-parse.h"
#include "ifchd.h"
#include "ifset.h"
#include "ndhc.h"
%%{
machine ipv4set_parser;
action XSt { arg_start = p; }
action IpEn {
arg_len = p - arg_start;
if (arg_len < sizeof ip4_addr) {
have_ip = true;
memcpy(ip4_addr, arg_start, arg_len);
}
ip4_addr[arg_len] = 0;
}
action SnEn {
arg_len = p - arg_start;
if (arg_len < sizeof ip4_subnet) {
have_subnet = true;
memcpy(ip4_subnet, arg_start, arg_len);
}
ip4_subnet[arg_len] = 0;
}
action BcEn {
arg_len = p - arg_start;
if (arg_len < sizeof ip4_bcast) {
have_ip = true;
memcpy(ip4_bcast, arg_start, arg_len);
}
ip4_bcast[arg_len] = 0;
}
v4addr = digit{1,3} '.' digit{1,3} '.' digit{1,3} '.' digit{1,3};
ip4_nobc = (v4addr > XSt % IpEn) ',' (v4addr > XSt % SnEn);
ip4_bc = (v4addr > XSt % IpEn) ',' (v4addr > XSt % SnEn) ','
(v4addr > XSt % BcEn);
main := (ip4_bc|ip4_nobc);
}%%
%% write data;
static void perform_ip4set(const char *buf, size_t len)
{
char ip4_addr[INET_ADDRSTRLEN];
char ip4_subnet[INET_ADDRSTRLEN];
char ip4_bcast[INET_ADDRSTRLEN];
const char *p = buf;
const char *pe = p + len;
const char *eof = pe;
const char *arg_start;
size_t arg_len;
int cs = 0;
bool have_ip = false;
bool have_subnet = false;
bool have_bcast = false;
%% write init;
%% write exec;
if (cs < ipv4set_parser_first_final) {
log_line("%s: received invalid arguments", __func__);
return;
}
// These should never trigger because of the above check, but be safe...
if (!have_ip) {
log_line("%s: No IPv4 address specified.", __func__);
return;
}
if (!have_subnet) {
log_line("%s: No IPv4 subnet specified.", __func__);
return;
}
perform_ip_subnet_bcast(ip4_addr, ip4_subnet,
have_bcast ? ip4_bcast : NULL);
}
%%{
machine ifchd_parser;
action Reset { cl.state = STATE_NOTHING; }
action ArgSt { arg_start = p; }
action ArgEn {
arg_len = p - arg_start;
if (arg_len > sizeof tb - 1) {
log_line("command argument would overflow");
return -1;
}
memcpy(tb, arg_start, arg_len);
tb[arg_len] = 0;
}
action Dispatch {
switch (cl.state) {
case STATE_IP4SET: perform_ip4set(tb, arg_len); break;
case STATE_TIMEZONE: perform_timezone( tb, arg_len); break;
case STATE_ROUTER: perform_router(tb, arg_len); break;
case STATE_DNS: perform_dns(tb, arg_len); break;
case STATE_LPRSVR: perform_lprsvr(tb, arg_len); break;
case STATE_HOSTNAME: perform_hostname(tb, arg_len); break;
case STATE_DOMAIN: perform_domain(tb, arg_len); break;
case STATE_IPTTL: perform_ipttl(tb, arg_len); break;
case STATE_MTU: perform_mtu(tb, arg_len); break;
case STATE_NTPSVR: perform_ntpsrv(tb, arg_len); break;
case STATE_WINS: perform_wins(tb, arg_len); break;
default:
log_line("error: invalid state in dispatch_work");
return -1;
}
}
terminator = ';' > Dispatch;
v4addr = digit{1,3} '.' digit{1,3} '.' digit{1,3} '.' digit{1,3};
ip_arg = (v4addr > ArgSt % ArgEn) terminator;
ip4set_arg = (((v4addr ','){1,2} v4addr) > ArgSt % ArgEn) terminator;
iplist_arg = (((v4addr ',')* v4addr) > ArgSt % ArgEn) terminator;
str_arg = ([^;\0]+ > ArgSt % ArgEn) terminator;
s32_arg = (extend{4} > ArgSt % ArgEn) terminator;
u16_arg = (extend{2} > ArgSt % ArgEn) terminator;
u8_arg = (extend{1} > ArgSt % ArgEn) terminator;
cmd_ip = ('routr:' % { cl.state = STATE_ROUTER; }) ip_arg;
cmd_ip4set = ('ip4:' % { cl.state = STATE_IP4SET; }) ip4set_arg;
cmd_iplist = ('dns:' % { cl.state = STATE_DNS; }
|'lpr:' % { cl.state = STATE_LPRSVR; }
|'ntp:' % { cl.state = STATE_NTPSVR; }
|'wins:' % { cl.state = STATE_WINS; }
) iplist_arg;
cmd_str = ('host:' % { cl.state = STATE_HOSTNAME; }
|'dom:' % { cl.state = STATE_DOMAIN; }
) str_arg;
cmd_s32 = ('tzone:' % { cl.state = STATE_TIMEZONE; }) s32_arg;
cmd_u16 = ('mtu:' % { cl.state = STATE_MTU; }) u16_arg;
cmd_u8 = ('ipttl:' % { cl.state = STATE_IPTTL; }) u8_arg;
command = (cmd_ip|cmd_ip4set|cmd_iplist|cmd_str|cmd_s32|cmd_u16|cmd_u8);
main := (command > Reset)+;
}%%
%% write data;
/*
* Returns -1 on fatal error; that leads to peer connection being closed.
*/
int execute_buffer(char *newbuf)
{
char buf[MAX_BUF * 2];
char tb[MAX_BUF];
ssize_t buflen = snprintf(buf, sizeof buf, "%s%s", cl.ibuf, newbuf);
if (buflen < 0) {
log_error("%s: (%s) snprintf1 failed; your system is broken?",
client_config.interface, __func__);
return -1;
}
if ((size_t)buflen >= sizeof buf) {
log_error("%s: (%s) input is too long for buffer",
client_config.interface, __func__);
return -1;
}
size_t init_siz = strlen(buf);
const char *p = buf;
const char *pe = p + init_siz;
const char *arg_start;
size_t arg_len;
int cs = 0;
%% write init;
%% write exec;
size_t bytes_left = pe - p;
if (bytes_left > 0) {
size_t taken = init_siz - bytes_left;
ssize_t ilen = snprintf(cl.ibuf, sizeof cl.ibuf, "%s", buf + taken);
if (ilen < 0) {
log_error("%s: (%s) snprintf2 failed; your system is broken?",
client_config.interface, __func__);
return -1;
}
if ((size_t)ilen >= sizeof buf) {
log_error("%s: (%s) unconsumed input too long for buffer",
client_config.interface, __func__);
return -1;
}
}
if (cs < ifchd_parser_first_final) {
log_error("%s: ifch received invalid commands",
client_config.interface);
return -1;
}
log_line("%s: Commands received and successfully executed.",
client_config.interface);
return 0;
}

406
src/ifchd.c Normal file
View File

@@ -0,0 +1,406 @@
/* ifchd.c - interface change daemon
*
* Copyright (c) 2004-2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - 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.
*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <sys/signalfd.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <getopt.h>
#include "nk/log.h"
#include "nk/privilege.h"
#include "nk/signals.h"
#include "nk/io.h"
#include "seccomp.h"
#include "ifchd.h"
#include "ndhc.h"
#include "ifchd-parse.h"
#include "sys.h"
#include "ifset.h"
struct ifchd_client cl;
static int epollfd, signalFd;
/* Slots are for signalFd and the ndhc -> ifchd pipe. */
static struct epoll_event events[2];
static int resolv_conf_fd = -1;
/* int ntp_conf_fd = -1; */
/* If true, allow HOSTNAME changes from dhcp server. */
int allow_hostname = 0;
uid_t ifch_uid = 0;
gid_t ifch_gid = 0;
static void writeordie(int fd, const char *buf, size_t len)
{
ssize_t r = safe_write(fd, buf, len);
if (r < 0 || (size_t)r != len)
suicide("%s: (%s) write failed: %d", client_config.interface,
__func__, r);
}
/* Writes a new resolv.conf based on the information we have received. */
static void write_resolve_conf(void)
{
static const char ns_str[] = "nameserver ";
static const char dom_str[] = "domain ";
static const char srch_str[] = "search ";
int r;
off_t off;
char buf[MAX_BUF];
if (resolv_conf_fd < 0)
return;
if (strlen(cl.namesvrs) == 0)
return;
if (lseek(resolv_conf_fd, 0, SEEK_SET) < 0)
return;
char *p = cl.namesvrs;
while (p && (*p != '\0')) {
char *q = strchr(p, ',');
if (!q)
q = strchr(p, '\0');
else
*q++ = '\0';
ssize_t sl = snprintf(buf, sizeof buf, "%s", p);
if (sl < 0 || (size_t)sl >= sizeof buf) {
log_warning("%s: (%s) snprintf failed appending nameservers",
client_config.interface, __func__);
}
writeordie(resolv_conf_fd, ns_str, strlen(ns_str));
writeordie(resolv_conf_fd, buf, strlen(buf));
writeordie(resolv_conf_fd, "\n", 1);
p = q;
}
p = cl.domains;
int numdoms = 0;
while (p && (*p != '\0')) {
char *q = strchr(p, ',');
if (!q)
q = strchr(p, '\0');
else
*q++ = '\0';
ssize_t sl = snprintf(buf, sizeof buf, "%s", p);
if (sl < 0 || (size_t)sl >= sizeof buf) {
log_warning("%s: (%s) snprintf failed appending domains",
client_config.interface, __func__);
}
if (numdoms == 0) {
writeordie(resolv_conf_fd, dom_str, strlen(dom_str));
writeordie(resolv_conf_fd, buf, strlen(buf));
} else {
if (numdoms == 1) {
writeordie(resolv_conf_fd, "\n", 1);
writeordie(resolv_conf_fd, srch_str, strlen(srch_str));
writeordie(resolv_conf_fd, buf, strlen(buf));
} else {
writeordie(resolv_conf_fd, " ", 1);
writeordie(resolv_conf_fd, buf, strlen(buf));
}
}
++numdoms;
p = q;
if (numdoms > 6)
break;
}
writeordie(resolv_conf_fd, "\n", 1);
off = lseek(resolv_conf_fd, 0, SEEK_CUR);
if (off < 0) {
log_line("write_resolve_conf: lseek returned error: %s",
strerror(errno));
return;
}
retry:
r = ftruncate(resolv_conf_fd, off);
if (r < 0) {
if (errno == EINTR)
goto retry;
log_line("write_resolve_conf: ftruncate returned error: %s",
strerror(errno));
return;
}
r = fsync(resolv_conf_fd);
if (r < 0) {
log_line("write_resolve_conf: fsync returned error: %s",
strerror(errno));
return;
}
}
/* XXX: addme */
void perform_timezone(const char *str, size_t len)
{
(void)len;
log_line("Timezone setting NYI: '%s'", str);
}
/* Add a dns server to the /etc/resolv.conf -- we already have a fd. */
void perform_dns(const char *str, size_t len)
{
if (!str || resolv_conf_fd < 0)
return;
if (len > sizeof cl.namesvrs) {
log_line("DNS server list is too long: %zu > %zu", len, cl.namesvrs);
return;
}
ssize_t sl = snprintf(cl.namesvrs, sizeof cl.namesvrs, "%s", str);
if (sl < 0 || (size_t)sl >= sizeof cl.namesvrs) {
log_warning("%s: (%s) snprintf failed",
client_config.interface, __func__);
}
write_resolve_conf();
log_line("Added DNS server: '%s'", str);
}
/* Updates for print daemons are too non-standard to be useful. */
void perform_lprsvr(const char *str, size_t len)
{
(void)len;
log_line("Line printer server setting NYI: '%s'", str);
}
/* Sets machine hostname. */
void perform_hostname(const char *str, size_t len)
{
if (!allow_hostname || !str)
return;
if (sethostname(str, len) < 0)
log_line("sethostname returned %s", strerror(errno));
else
log_line("Set hostname: '%s'", str);
}
/* update "domain" and "search" in /etc/resolv.conf */
void perform_domain(const char *str, size_t len)
{
if (!str || resolv_conf_fd < 0)
return;
if (len > sizeof cl.domains) {
log_line("DNS domain list is too long: %zu > %zu", len, cl.namesvrs);
return;
}
ssize_t sl = snprintf(cl.domains, sizeof cl.domains, "%s", str);
if (sl < 0 || (size_t)sl >= sizeof cl.domains) {
log_warning("%s: (%s) snprintf failed",
client_config.interface, __func__);
}
write_resolve_conf();
log_line("Added DNS domain: '%s'", str);
}
/* I don't think this can be done without a netfilter extension
* that isn't in the mainline kernels. */
void perform_ipttl(const char *str, size_t len)
{
(void)len;
log_line("TTL setting NYI: '%s'", str);
}
/* XXX: addme */
void perform_ntpsrv(const char *str, size_t len)
{
(void)len;
log_line("NTP server setting NYI: '%s'", str);
}
/* Maybe Samba cares about this feature? I don't know. */
void perform_wins(const char *str, size_t len)
{
(void)str;
(void)len;
}
static void setup_signals_ifch(void)
{
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGPIPE);
sigaddset(&mask, SIGUSR1);
sigaddset(&mask, SIGUSR2);
sigaddset(&mask, SIGTSTP);
sigaddset(&mask, SIGTTIN);
sigaddset(&mask, SIGCHLD);
sigaddset(&mask, SIGHUP);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGTERM);
if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0)
suicide("sigprocmask failed");
signalFd = signalfd(-1, &mask, SFD_NONBLOCK);
if (signalFd < 0)
suicide("signalfd failed");
}
static void signal_dispatch(void)
{
int t;
size_t off = 0;
struct signalfd_siginfo si = {0};
again:
t = read(signalFd, (char *)&si + off, sizeof si - off);
if (t < 0) {
if (t == EAGAIN || t == EWOULDBLOCK || t == EINTR)
goto again;
else
suicide("signalfd read error");
}
if (off + (unsigned)t < sizeof si)
off += t;
switch (si.ssi_signo) {
case SIGINT:
case SIGTERM:
exit(EXIT_SUCCESS);
break;
case SIGPIPE:
log_line("ndhc-ifch: IPC pipe closed. Exiting.");
exit(EXIT_SUCCESS);
break;
default:
break;
}
}
static void inform_execute(char c)
{
ssize_t r = safe_write(pToNdhcW, &c, sizeof c);
if (r == 0) {
// Remote end hung up.
exit(EXIT_SUCCESS);
} else if (r < 0)
suicide("%s: (%s) error writing to ifch -> ndhc pipe: %s",
client_config.interface, __func__, strerror(errno));
}
static void process_client_pipe(void)
{
char buf[MAX_BUF];
memset(buf, '\0', sizeof buf);
ssize_t r = safe_read(pToIfchR, buf, sizeof buf - 1);
if (r == 0) {
// Remote end hung up.
exit(EXIT_SUCCESS);
} else if (r < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK)
return;
suicide("%s: (%s) error reading from ndhc -> ifch pipe: %s",
client_config.interface, __func__, strerror(errno));
}
if (execute_buffer(buf) < 0) {
inform_execute('-');
suicide("%s: (%s) received invalid commands: '%s'",
client_config.interface, __func__, buf);
} else
inform_execute('+');
}
static void do_ifch_work(void)
{
epollfd = epoll_create1(0);
if (epollfd < 0)
suicide("epoll_create1 failed");
if (enforce_seccomp_ifch())
log_line("ifch seccomp filter cannot be installed");
cl.state = STATE_NOTHING;
memset(cl.ibuf, 0, sizeof cl.ibuf);
memset(cl.namesvrs, 0, sizeof cl.namesvrs);
memset(cl.domains, 0, sizeof cl.domains);
epoll_add(epollfd, pToIfchR);
epoll_add(epollfd, signalFd);
for (;;) {
int r = epoll_wait(epollfd, events, 2, -1);
if (r < 0) {
if (errno == EINTR)
continue;
else
suicide("epoll_wait failed");
}
for (int i = 0; i < r; ++i) {
int fd = events[i].data.fd;
if (fd == pToIfchR)
process_client_pipe();
else if (fd == signalFd)
signal_dispatch();
else
suicide("ifch: unexpected fd while performing epoll");
}
}
}
void ifch_main(void)
{
prctl(PR_SET_NAME, "ndhc: ifch");
umask(077);
setup_signals_ifch();
// If we are requested to update resolv.conf, preopen the fd before
// we drop root privileges, making sure that if we create
// resolv.conf, it will be world-readable.
if (strncmp(resolv_conf_d, "", sizeof resolv_conf_d)) {
umask(022);
resolv_conf_fd = open(resolv_conf_d, O_RDWR | O_CREAT, 644);
umask(077);
if (resolv_conf_fd < 0) {
suicide("FATAL - unable to open resolv.conf");
}
}
memset(resolv_conf_d, '\0', sizeof resolv_conf_d);
nk_set_chroot(chroot_dir);
memset(chroot_dir, '\0', sizeof chroot_dir);
nk_set_uidgid(ifch_uid, ifch_gid, "cap_net_admin=ep");
do_ifch_work();
}

51
src/ifchd.h Normal file
View File

@@ -0,0 +1,51 @@
#ifndef NJK_IFCHD_H_
#define NJK_IFCHD_H_
#include <limits.h>
#include "ndhc-defines.h"
enum ifchd_states {
STATE_NOTHING,
STATE_IP4SET,
STATE_TIMEZONE,
STATE_ROUTER,
STATE_DNS,
STATE_LPRSVR,
STATE_HOSTNAME,
STATE_DOMAIN,
STATE_IPTTL,
STATE_MTU,
STATE_NTPSVR,
STATE_WINS
};
#include <net/if.h>
struct ifchd_client {
/* Socket fd, current state, and idle time for connection. */
int state;
/* Per-connection buffer. */
char ibuf[MAX_BUF];
/* ' '-delimited buffers of nameservers and domains */
char namesvrs[MAX_BUF];
char domains[MAX_BUF];
};
extern struct ifchd_client cl;
extern int allow_hostname;
extern uid_t ifch_uid;
extern gid_t ifch_gid;
void perform_timezone(const char *str, size_t len);
void perform_dns(const char *str, size_t len);
void perform_lprsvr(const char *str, size_t len);
void perform_hostname(const char *str, size_t len);
void perform_domain(const char *str, size_t len);
void perform_ipttl(const char *str, size_t len);
void perform_ntpsrv(const char *str, size_t len);
void perform_wins(const char *str, size_t len);
void ifch_main(void);
#endif /* NJK_IFCHD_H_ */

651
src/ifset.c Normal file
View File

@@ -0,0 +1,651 @@
/* ifset.c - Linux-specific net interface settings include
*
* Copyright (c) 2004-2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - 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.
*/
#include <unistd.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/route.h>
#include <net/if.h>
#include <netpacket/packet.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <pwd.h>
#include <grp.h>
#include <errno.h>
#include "nk/log.h"
#include "ifset.h"
#include "ifchd.h"
#include "ndhc.h"
#include "nl.h"
static uint32_t ifset_nl_seq = 1;
// 32-bit position values are relatively prime to 37, so the residue mod37
// gives a unique mapping for each value. Gives correct result for v=0.
static int trailz(uint32_t v)
{
static const int bpm37[] = {
32, 0, 1, 26, 2, 23, 27, 0, 3, 16, 24, 30, 28, 11, 0, 13, 4, 7, 17,
0, 25, 22, 31, 15, 29, 10, 12, 6, 0, 21, 14, 9, 5, 20, 8, 19, 18
};
return bpm37[(-v & v) % 37];
}
// sn must be in network order
static inline int subnet4_to_prefixlen(uint32_t sn)
{
return 32 - trailz(ntohl(sn));
}
struct ipbcpfx {
int fd;
uint32_t ipaddr;
uint32_t bcast;
uint8_t prefixlen;
bool already_ok;
};
static ssize_t rtnl_do_send(int fd, uint8_t *sbuf, size_t slen,
const char *fnname)
{
uint8_t response[NLMSG_ALIGN(sizeof(struct nlmsghdr)) + 64];
struct sockaddr_nl nl_addr;
ssize_t r;
memset(&nl_addr, 0, sizeof nl_addr);
nl_addr.nl_family = AF_NETLINK;
retry_sendto:
r = sendto(fd, sbuf, slen, 0, (struct sockaddr *)&nl_addr,
sizeof nl_addr);
if (r < 0) {
if (errno == EINTR)
goto retry_sendto;
else {
log_line("%s: (%s) netlink sendto failed: %s",
client_config.interface, fnname, strerror(errno));
return -1;
}
}
struct iovec iov = {
.iov_base = response,
.iov_len = sizeof response,
};
struct msghdr msg = {
.msg_name = &nl_addr,
.msg_namelen = sizeof nl_addr,
.msg_iov = &iov,
.msg_iovlen = 1,
};
retry_recv:
r = recvmsg(fd, &msg, 0);
if (r < 0) {
if (errno == EINTR)
goto retry_recv;
else {
log_line("%s: (%s) netlink recvmsg failed: %s",
client_config.interface, fnname, strerror(errno));
return -1;
}
}
if (msg.msg_flags & MSG_TRUNC) {
log_error("%s: (%s) Buffer not long enough for message.",
client_config.interface, fnname);
return -1;
}
if ((size_t)r < sizeof(struct nlmsghdr)) {
log_line("%s: (%s) netlink recvmsg returned a headerless response",
client_config.interface, fnname);
return -1;
}
const struct nlmsghdr *nlh = (const struct nlmsghdr *)response;
if (nlh->nlmsg_type == NLMSG_ERROR) {
if (nlmsg_get_error(nlh) == 0)
return 0;
else {
log_line("%s: (%s) netlink sendto returned NLMSG_ERROR: %s",
client_config.interface, fnname,
strerror(nlmsg_get_error(nlh)));
return -1;
}
}
if (nlh->nlmsg_type == NLMSG_DONE)
return -2;
log_line("%s: (%s) netlink sendto returned an error.",
client_config.interface, __func__);
return -1;
}
static ssize_t rtnl_if_flags_send(int fd, int type, int ifi_flags)
{
uint8_t request[NLMSG_ALIGN(sizeof(struct nlmsghdr)) +
NLMSG_ALIGN(sizeof(struct ifinfomsg))];
struct nlmsghdr *header;
struct ifinfomsg *ifinfomsg;
memset(&request, 0, sizeof request);
header = (struct nlmsghdr *)request;
header->nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
header->nlmsg_type = type;
header->nlmsg_flags = NLM_F_ACK | NLM_F_REQUEST;
header->nlmsg_seq = ifset_nl_seq++;
ifinfomsg = NLMSG_DATA(header);
ifinfomsg->ifi_flags = ifi_flags;
ifinfomsg->ifi_index = client_config.ifindex;
ifinfomsg->ifi_change = 0xffffffff;
return rtnl_do_send(fd, request, header->nlmsg_len, __func__);
}
static ssize_t rtnl_addr_broadcast_send(int fd, int type, int ifa_flags,
int ifa_scope, uint32_t *ipaddr,
uint32_t *bcast, uint8_t prefixlen)
{
uint8_t request[NLMSG_ALIGN(sizeof(struct nlmsghdr)) +
NLMSG_ALIGN(sizeof(struct ifaddrmsg)) +
2 * RTA_LENGTH(sizeof(struct in6_addr))];
struct nlmsghdr *header;
struct ifaddrmsg *ifaddrmsg;
if (!ipaddr && !bcast) {
log_warning("%s: (%s) no ipaddr or bcast!",
client_config.interface, __func__);
return -1;
}
memset(&request, 0, sizeof request);
header = (struct nlmsghdr *)request;
header->nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg));
header->nlmsg_type = type;
header->nlmsg_flags = NLM_F_REPLACE | NLM_F_ACK | NLM_F_REQUEST;
header->nlmsg_seq = ifset_nl_seq++;
ifaddrmsg = NLMSG_DATA(header);
ifaddrmsg->ifa_family = AF_INET;
ifaddrmsg->ifa_prefixlen = prefixlen;
ifaddrmsg->ifa_flags = ifa_flags;
ifaddrmsg->ifa_scope = ifa_scope;
ifaddrmsg->ifa_index = client_config.ifindex;
if (ipaddr) {
if (nl_add_rtattr(header, sizeof request, IFA_LOCAL,
ipaddr, sizeof *ipaddr) < 0) {
log_line("%s: (%s) couldn't add IFA_LOCAL to nlmsg",
client_config.interface, __func__);
return -1;
}
}
if (bcast) {
if (nl_add_rtattr(header, sizeof request, IFA_BROADCAST,
bcast, sizeof *bcast) < 0) {
log_line("%s: (%s) couldn't add IFA_BROADCAST to nlmsg",
client_config.interface, __func__);
return -1;
}
}
return rtnl_do_send(fd, request, header->nlmsg_len, __func__);
}
static ssize_t rtnl_set_default_gw_v4(int fd, uint32_t gw4, int metric)
{
uint8_t request[NLMSG_ALIGN(sizeof(struct nlmsghdr)) +
NLMSG_ALIGN(sizeof(struct rtmsg)) +
3 * RTA_LENGTH(sizeof(struct in6_addr)) +
RTA_LENGTH(sizeof(int))];
struct nlmsghdr *header;
struct rtmsg *rtmsg;
memset(&request, 0, sizeof request);
header = (struct nlmsghdr *)request;
header->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
header->nlmsg_type = RTM_NEWROUTE;
header->nlmsg_flags = NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK
| NLM_F_REQUEST;
header->nlmsg_seq = ifset_nl_seq++;
rtmsg = NLMSG_DATA(header);
rtmsg->rtm_family = AF_INET;
rtmsg->rtm_protocol = RTPROT_DHCP;
rtmsg->rtm_scope = RT_SCOPE_UNIVERSE;
rtmsg->rtm_type = RTN_UNICAST;
uint32_t dstaddr4 = 0;
if (nl_add_rtattr(header, sizeof request, RTA_DST,
&dstaddr4, sizeof dstaddr4) < 0) {
log_line("%s: (%s) couldn't add RTA_DST to nlmsg",
client_config.interface, __func__);
return -1;
}
if (nl_add_rtattr(header, sizeof request, RTA_OIF,
&client_config.ifindex,
sizeof client_config.ifindex) < 0) {
log_line("%s: (%s) couldn't add RTA_OIF to nlmsg",
client_config.interface, __func__);
return -1;
}
if (nl_add_rtattr(header, sizeof request, RTA_GATEWAY,
&gw4, sizeof gw4) < 0) {
log_line("%s: (%s) couldn't add RTA_GATEWAY to nlmsg",
client_config.interface, __func__);
return -1;
}
if (metric > 0) {
if (nl_add_rtattr(header, sizeof request, RTA_PRIORITY,
&metric, sizeof metric) < 0) {
log_line("%s: (%s) couldn't add RTA_PRIORITY to nlmsg",
client_config.interface, __func__);
return -1;
}
}
return rtnl_do_send(fd, request, header->nlmsg_len, __func__);
}
struct link_flag_data {
int fd;
uint32_t flags;
bool got_flags;
};
static void link_flags_get_do(const struct nlmsghdr *nlh, void *data)
{
struct ifinfomsg *ifm = NLMSG_DATA(nlh);
struct link_flag_data *ifd = data;
switch(nlh->nlmsg_type) {
case RTM_NEWLINK:
if (ifm->ifi_index != client_config.ifindex)
break;
ifd->flags = ifm->ifi_flags;
ifd->got_flags = true;
break;
case RTM_DELLINK:
log_line("%s: got RTM_DELLINK", __func__);
break;
default:
log_line("%s: got %u", __func__, nlh->nlmsg_type);
break;
}
}
static int link_flags_get(int fd, uint32_t *flags)
{
char nlbuf[8192];
struct link_flag_data ipx = { .fd = fd, .flags = 0, .got_flags = false };
ssize_t ret;
uint32_t seq = ifset_nl_seq++;
if (nl_sendgetlink(fd, seq, client_config.ifindex) < 0)
return -1;
do {
ret = nl_recv_buf(fd, nlbuf, sizeof nlbuf);
if (ret < 0)
return -2;
if (nl_foreach_nlmsg(nlbuf, ret, seq, 0, link_flags_get_do,
&ipx) < 0)
return -3;
} while (ret > 0);
if (ipx.got_flags) {
*flags = ipx.flags;
return 0;
}
return -4;
}
static int link_set_flags(int fd, uint32_t flags)
{
uint32_t oldflags;
int r = link_flags_get(fd, &oldflags);
if (r < 0) {
log_line("%s: (%s) failed to get old link flags: %u",
client_config.interface, __func__, r);
return -1;
}
if ((oldflags & flags) == flags)
return 1;
return (int)rtnl_if_flags_send(fd, RTM_SETLINK, flags | oldflags);
}
#if 0
static int link_unset_flags(int fd, uint32_t flags)
{
uint32_t oldflags;
int r = link_flags_get(fd, &oldflags);
if (r < 0) {
log_line("%s: (%s) failed to get old link flags: %u",
client_config.interface, __func__, r);
return -1;
}
if ((oldflags & flags) == 0)
return 1;
return (int)rtnl_if_flags_send(fd, RTM_SETLINK, oldflags & ~flags);
}
#endif
static void ipbcpfx_clear_others_do(const struct nlmsghdr *nlh, void *data)
{
struct rtattr *tb[IFA_MAX] = {0};
struct ifaddrmsg *ifm = NLMSG_DATA(nlh);
struct ipbcpfx *ipx = data;
int r;
nl_rtattr_parse(nlh, sizeof *ifm, rtattr_assign, tb);
switch(nlh->nlmsg_type) {
case RTM_NEWADDR:
if (ifm->ifa_index != (unsigned)client_config.ifindex)
return;
if (ifm->ifa_family != AF_INET)
return;
if (!(ifm->ifa_flags & IFA_F_PERMANENT))
goto erase;
if (ifm->ifa_scope != RT_SCOPE_UNIVERSE)
goto erase;
if (ifm->ifa_prefixlen != ipx->prefixlen)
goto erase;
if (!tb[IFA_ADDRESS])
goto erase;
if (memcmp(RTA_DATA(tb[IFA_ADDRESS]), &ipx->ipaddr,
sizeof ipx->ipaddr))
goto erase;
if (!tb[IFA_BROADCAST])
goto erase;
if (memcmp(RTA_DATA(tb[IFA_BROADCAST]), &ipx->bcast,
sizeof ipx->bcast))
goto erase;
break;
default:
return;
}
// We already have the proper IP+broadcast+prefix.
ipx->already_ok = true;
return;
erase:
r = rtnl_addr_broadcast_send(ipx->fd, RTM_DELADDR, ifm->ifa_flags,
ifm->ifa_scope,
tb[IFA_ADDRESS] ? RTA_DATA(tb[IFA_ADDRESS]) : NULL,
tb[IFA_BROADCAST] ? RTA_DATA(tb[IFA_BROADCAST]) : NULL,
ifm->ifa_prefixlen);
if (r < 0 && r != -2) {
log_warning("%s: (%s) Failed to delete IP and broadcast addresses.",
client_config.interface, __func__);
}
return;
}
static int ipbcpfx_clear_others(int fd, uint32_t ipaddr, uint32_t bcast,
uint8_t prefixlen)
{
char nlbuf[8192];
struct ipbcpfx ipx = { .fd = fd, .ipaddr = ipaddr, .bcast = bcast,
.prefixlen = prefixlen, .already_ok = false };
ssize_t ret;
uint32_t seq = ifset_nl_seq++;
if (nl_sendgetaddr4(fd, seq, client_config.ifindex) < 0)
return -1;
do {
ret = nl_recv_buf(fd, nlbuf, sizeof nlbuf);
if (ret < 0)
return -2;
if (nl_foreach_nlmsg(nlbuf, ret, seq, 0,
ipbcpfx_clear_others_do, &ipx) < 0)
return -3;
} while (ret > 0);
return ipx.already_ok ? 1 : 0;
}
static ssize_t rtnl_if_mtu_set(int fd, unsigned int mtu)
{
uint8_t request[NLMSG_ALIGN(sizeof(struct nlmsghdr)) +
NLMSG_ALIGN(sizeof(struct ifinfomsg)) +
RTA_LENGTH(sizeof(unsigned int))];
struct nlmsghdr *header;
struct ifinfomsg *ifinfomsg;
uint32_t oldflags;
int r = link_flags_get(fd, &oldflags);
if (r < 0) {
log_line("%s: (%s) failed to get old link flags: %u",
client_config.interface, __func__, r);
return -1;
}
memset(&request, 0, sizeof request);
header = (struct nlmsghdr *)request;
header->nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
header->nlmsg_type = RTM_SETLINK;
header->nlmsg_flags = NLM_F_ACK | NLM_F_REQUEST;
header->nlmsg_seq = ifset_nl_seq++;
ifinfomsg = NLMSG_DATA(header);
ifinfomsg->ifi_flags = oldflags;
ifinfomsg->ifi_index = client_config.ifindex;
ifinfomsg->ifi_change = 0xffffffff;
if (nl_add_rtattr(header, sizeof request, IFLA_MTU,
&mtu, sizeof mtu) < 0) {
log_line("%s: (%s) couldn't add IFLA_MTU to nlmsg",
client_config.interface, __func__);
return -1;
}
return rtnl_do_send(fd, request, header->nlmsg_len, __func__);
}
int perform_ifup(void)
{
int fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
if (fd < 0) {
log_line("%s: (%s) netlink socket open failed: %s",
client_config.interface, __func__, strerror(errno));
return fd;
}
int r = link_set_flags(fd, IFF_UP);
if (r < 0)
log_line("%s: (%s) Failed to set link to be up.",
client_config.interface, __func__);
close(fd);
return r;
}
// str_bcast is optional.
void perform_ip_subnet_bcast(const char *str_ipaddr,
const char *str_subnet, const char *str_bcast)
{
struct in_addr ipaddr, subnet, bcast;
int fd, r;
uint8_t prefixlen;
if (!str_ipaddr) {
log_line("%s: (%s) interface ip address is NULL",
client_config.interface, __func__);
return;
}
if (!str_subnet) {
log_line("%s: (%s) interface subnet address is NULL",
client_config.interface, __func__);
return;
}
if (inet_pton(AF_INET, str_ipaddr, &ipaddr) <= 0) {
log_line("%s: (%s) bad interface ip address: '%s'",
client_config.interface, __func__, str_ipaddr);
return;
}
if (inet_pton(AF_INET, str_subnet, &subnet) <= 0) {
log_line("%s: (%s) bad interface subnet address: '%s'",
client_config.interface, __func__, str_subnet);
return;
}
prefixlen = subnet4_to_prefixlen(subnet.s_addr);
if (str_bcast) {
if (inet_pton(AF_INET, str_bcast, &bcast) <= 0) {
log_line("%s: (%s) bad interface broadcast address: '%s'",
client_config.interface, __func__, str_bcast);
return;
}
} else {
// Generate the standard broadcast address if unspecified.
bcast.s_addr = ipaddr.s_addr | htonl(0xfffffffflu >> prefixlen);
}
fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
if (fd < 0) {
log_line("%s: (%s) netlink socket open failed: %s",
client_config.interface, __func__, strerror(errno));
return;
}
r = ipbcpfx_clear_others(fd, ipaddr.s_addr, bcast.s_addr, prefixlen);
if (r < 0 && r > -3) {
if (r == -1)
log_line("%s: (%s) error requesting link ip address list",
client_config.interface, __func__);
else if (r == -2)
log_line("%s: (%s) error receiving link ip address list",
client_config.interface, __func__);
close(fd);
return;
}
if (r < 1) {
r = rtnl_addr_broadcast_send(fd, RTM_NEWADDR, IFA_F_PERMANENT,
RT_SCOPE_UNIVERSE, &ipaddr.s_addr, &bcast.s_addr,
prefixlen);
if (r < 0) {
close(fd);
return;
}
log_line("Interface IP set to: '%s'", str_ipaddr);
log_line("Interface subnet set to: '%s'", str_subnet);
if (str_bcast)
log_line("Broadcast address set to: '%s'", str_bcast);
} else
log_line("Interface IP, subnet, and broadcast were already OK.");
if (link_set_flags(fd, IFF_UP | IFF_RUNNING) < 0)
log_line("%s: (%s) Failed to set link to be up and running.",
client_config.interface, __func__);
close(fd);
}
void perform_router(const char *str_router, size_t len)
{
if (!str_router)
return;
if (len < 7)
return;
struct in_addr router;
if (inet_pton(AF_INET, str_router, &router) <= 0) {
log_line("%s: (%s) bad router ip address: '%s'",
client_config.interface, __func__, str_router);
return;
}
int fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
if (fd < 0) {
log_line("%s: (%s) netlink socket open failed: %s",
client_config.interface, __func__, strerror(errno));
return;
}
if (rtnl_set_default_gw_v4(fd, router.s_addr,
client_config.metric) < 0)
log_line("%s: (%s) failed to set route: %s",
client_config.interface, __func__, strerror(errno));
else
log_line("Gateway router set to: '%s'", str_router);
close(fd);
}
void perform_mtu(const char *str, size_t len)
{
if (!str)
return;
if (len < 2)
return;
char *estr;
long tmtu = strtol(str, &estr, 10);
if (estr == str) {
log_line("%s: (%s) provided mtu arg isn't a valid number",
client_config.interface, __func__);
return;
}
if ((tmtu == LONG_MAX || tmtu == LONG_MIN) && errno == ERANGE) {
log_line("%s: (%s) provided mtu arg would overflow a long",
client_config.interface, __func__);
return;
}
if (tmtu > INT_MAX) {
log_line("%s: (%s) provided mtu arg would overflow int",
client_config.interface, __func__);
return;
}
// 68 bytes for IPv4. 1280 bytes for IPv6.
if (tmtu < 68) {
log_line("%s: (%s) provided mtu arg (%ul) less than minimum MTU (68)",
client_config.interface, __func__, tmtu);
return;
}
unsigned int mtu = (unsigned int)tmtu;
int fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
if (fd < 0) {
log_line("%s: (%s) netlink socket open failed: %s",
client_config.interface, __func__, strerror(errno));
return;
}
if (rtnl_if_mtu_set(fd, mtu) < 0)
log_line("%s: (%s) failed to set MTU [%d]",
client_config.interface, __func__, mtu);
else
log_line("MTU set to: '%s'", str);
close(fd);
}

38
src/ifset.h Normal file
View File

@@ -0,0 +1,38 @@
/* ifset.h - Linux-specific net interface settings include
*
* Copyright (c) 2004-2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - 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.
*/
#ifndef NJK_IFSET_H_
#define NJK_IFSET_H_
int perform_ifup(void);
void perform_ip_subnet_bcast(const char *str_ipaddr,
const char *str_subnet,
const char *str_bcast);
void perform_router(const char *str, size_t len);
void perform_mtu(const char *str, size_t len);
#endif

106
src/leasefile.c Normal file
View File

@@ -0,0 +1,106 @@
/* leasefile.c - functions for writing the lease file
*
* Copyright (c) 2011-2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - 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.
*/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <errno.h>
#include <limits.h>
#include "nk/log.h"
#include "nk/io.h"
#include "leasefile.h"
#include "ndhc.h"
static int leasefilefd = -1;
static void get_leasefile_path(char *leasefile, size_t dlen, char *ifname)
{
int splen = snprintf(leasefile, dlen, "%s/LEASE-%s",
state_dir, ifname);
if (splen < 0)
suicide("%s: (%s) snprintf failed; return=%d",
client_config.interface, __func__, splen);
if ((size_t)splen >= dlen)
suicide("%s: (%s) snprintf dest buffer too small %d >= %u",
client_config.interface, __func__, splen, sizeof dlen);
}
void open_leasefile(void)
{
char leasefile[PATH_MAX];
get_leasefile_path(leasefile, sizeof leasefile, client_config.interface);
leasefilefd = open(leasefile, O_WRONLY|O_TRUNC|O_CREAT, 0644);
if (leasefilefd < 0)
suicide("%s: Failed to create lease file '%s': %s",
client_config.interface, leasefile, strerror(errno));
}
void write_leasefile(struct in_addr ipnum)
{
char ip[INET_ADDRSTRLEN];
char out[INET_ADDRSTRLEN*2];
ssize_t ret;
if (leasefilefd < 0) {
log_error("%s: (%s) leasefile fd < 0; no leasefile will be written",
client_config.interface, __func__);
return;
}
inet_ntop(AF_INET, &ipnum, ip, sizeof ip);
ssize_t olen = snprintf(out, sizeof out, "%s\n", ip);
if (olen < 0 || (size_t)olen >= sizeof ip) {
log_error("%s: (%s) snprintf failed; return=%d",
client_config.interface, __func__, olen);
return;
}
retry_trunc:
ret = ftruncate(leasefilefd, 0);
switch (ret) {
default: break;
case -1:
if (errno == EINTR)
goto retry_trunc;
log_warning("%s: Failed to truncate lease file: %s",
client_config.interface, strerror(errno));
return;
}
lseek(leasefilefd, 0, SEEK_SET);
size_t outlen = strlen(out);
ret = safe_write(leasefilefd, out, outlen);
if (ret < 0 || (size_t)ret != outlen)
log_warning("%s: Failed to write ip to lease file.",
client_config.interface);
else
fsync(leasefilefd);
}

36
src/leasefile.h Normal file
View File

@@ -0,0 +1,36 @@
/* leasefile.h - functions for writing the lease file
*
* Copyright (c) 2011-2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - 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.
*/
#ifndef NJK_NDHC_LEASEFILE_H_
#define NJK_NDHC_LEASEFILE_H_
void open_leasefile(void);
void write_leasefile(struct in_addr ipnum);
#endif /* NJK_NDHC_LEASEFILE_H_ */

10
src/ndhc-defines.h Normal file
View File

@@ -0,0 +1,10 @@
#ifndef NDHC_DEFINES_H_
#define NDHC_DEFINES_H_
#define PID_FILE_DEFAULT "/var/run/ndhc.pid"
#define PID_FILE_IFCH_DEFAULT "/var/run/ifchd.pid"
#define NDHC_VERSION "1.5"
#define MAX_BUF 1024
#endif /* NDHC_DEFINES_H_ */

155
src/ndhc.8 Normal file
View File

@@ -0,0 +1,155 @@
.TH NDHC 8 2012-07-20 Linux "Linux Administrator's Manual"
.SH NAME
ndhc \- secure DHCP client
.SH SYNOPSIS
.B ndhc
.RI [ OPTION ]...
.SH DESCRIPTION
The ndhc client negotiates a lease with the DHCP server and informs ifchd of
the change when it is obtained or lost. It also defends the assigned IP
address against hostile imposters and requests a new lease if it detects that
the interface has been connected to a new network. It requires a cooperating
ifchd server to properly perform its duties.
.SH OPTIONS
.TP
.BI \-c\ CLIENTID ,\ \-\-clientid= CLIENTID
Specifies the client identifier that will be sent to the remote server. This
can be any (reasonably sized, <64byte or so) text string, or an ethernet
MAC address in a form similar to 'aa:bb:cc:dd:ee:ff'. ndhc is smart enough
to recognize MAC addresses. ISP DHCP servers commonly check the value of this
field before providing a lease. The default value is the MAC address of
the network interface to which ndhc is bound.
.TP
.BI \-h\ HOSTNAME ,\ \-\-hostname= HOSTNAME
Send the specified client hostname to the remote DHCP server. This option
should not be necessary in most instances, but may perhaps be useful for odd
DHCP servers that perform some kind of authentication against the hostname
option field. The default is to send no hostname option at all.
.TP
.BI \-v\ VENDORID ,\ \-\-vendorid= VENDORID
Send the specified vendor identification string to the remote DHCP server.
This option should not be necessary in most instances, but may perhaps be
useful for odd DHCP servers that perform some kind of authentication against
the vendor id option field. The default is to send the string 'ndhc'.
.TP
.BR \-b ,\ \-\-background
Immediately fork into the background, even before obtaining a lease.
.TP
.BI \-p\ PIDFILE ,\ \-\-pidfile= PIDFILE
Write the process id number of the ndhc process into the specified file name.
The default is to not write the process id number into any file at all.
.TP
.BI \-l\ LEASEFILE ,\ \-\-leasefile= LEASEFILE
Write the IP address of the currently held DHCP lease into the specified file
name. The default is to not write the lease IP address into any file at all.
This file can be quite useful for reacting to changes in IP address -- one
can listen for changes to it using fanotify() or inotify() on Linux.
.TP
.BI \-i\ INTERFACE ,\ \-\-interface= INTERFACE
Act as a DHCP client for the specified interface. A single ndhc daemon can
only act as a DHCP client for a single interface. Specify the interface it
should use by name. The default is to listen on 'eth0'.
.TP
.BR \-n ,\ \-\-now
Exit with failure if a lease cannot be obtained. Useful for some init scripts.
.TP
.BR \-q ,\ \-\-quit
Exit after obtaining a lease. Useful for some init scripts.
.TP
.BI \-r\ IP ,\ \-\-request= IP
Request the specified IP address from the remote DHCP server. The DHCP server
has no obligation to provide us with this IP, but it may acquiesce to the
request if it would not conflict with another host.
.TP
.BI \-u\ USER ,\ \-\-user= USER
This option specifies the user name or user id that ndhc will change to after
startup. ndhc will also change its group to match the default group of this
user.
.TP
.BI \-U\ USER ,\ \-\-ifch\-user= USER
This option specifies the user name or user id that ndhc-ifch will change to
after startup. ndhc-ifch will also change its group to match the default group
of this user.
.TP
.BI \-C\ CHROOTDIR ,\ \-\-chroot= CHROOTDIR
This option specifies the directory to which ndhc should confine itself via
chroot() after startup. This directory should have access to dev/urandom and
dev/null. For logging to work, a dev/log socket or device should also exist.
.TP
.BR \-d ,\ \-\-relentless\-defense
If specified, ndhc will never back down in defending the IP address that it
has been assigned by the remote DHCP server. This behavior should not be
specified for average machines, but is useful for servers or routers where
the IP address of the machine must remain fixed for proper operation.
.TP
.TP
.BI \-R\ RESOLVCONF ,\ \-\-resolv\-conf= RESOLVCONF
Specifies the path to the system resolv.conf. This file will typically be in
/etc/resolv.conf. If this option is specified, ndhc will update the contents
of this file to match the DNS servers specified by the remote DHCP server. If
this option is not specified, ifchd will never change the system DNS resolution
configuration.
.TP
.BI \-H ,\ \-\-dhcp\-set\-hostname
If specified, ndhc will update the system host name in response to any
hostname option field provided by a remote DHCP server on the request of
a ndhc client. If this option is not specified, ndhc will never change
the system hostname.
.BR \-S ,\ \-\-seccomp\-enforce
Enforces seccomp-based syscall whitelisting. System calls that ndhc and
ndhc-ifch are not expected to need are prohibited from being called if this
flag is set. The lists of allowed syscalls are hardcoded, and attempts
to call a non-listed syscall will result in the ndhc process being
terminated. As systems vary, it cannot be guaranteed that these system
call lists are accurate for your system, and thus seccomp filtering will
not be used unless this flag is set.
.TP
.BR \-w\ TIMEMS ,\ \-\-arp\-probe\-wait= TIMEMS
Adjusts the time that we wait for an ARP response when checking to see if
our lease assignment is already taken by an existing host. Default is
1000ms.
.TP
.BR \-W\ NUMPROBES ,\ \-\-arp\-probe\-num= NUMPROBES
Adjusts the number of ARP packets that we send when probing for collisions
with an existing host that is using our assigned IP. Once we have sent
the specified number of probe packets with no response, ndhc is willing
to believe that there is no colliding host. Default number is 3 probes.
.TP
.BR \-m\ TIMEMS ,\ \-\-arp\-probe\-min= TIMEMS
Adjusts the minimum time that we wait between sending probe packets. The
default is 1000ms. The precise inter-probe wait time is randomized.
.TP
.BR \-M\ TIMEMS ,\ \-\-arp\-probe\-max= TIMEMS
Adjusts the maximum time that we wait between sending probe packets. The
default is 2000ms. The precise inter-probe wait time is randomized.
.TP
.BR \-v ,\ \-\-version
Display the ndhc version number.
.SH SIGNALS
It is not necessary to sleep between sending signals, as signals received are
processed sequentially in the order they are received.
.B ndhc
responds to the following signals:
.TP
.B SIGUSR1
This signal causes
.B ndhc
to renew the current lease or, if it does not have one, obtain a
new lease.
.TP
.B SIGUSR2
This signal causes
.B ndhc
to release the current lease and go to sleep until it receives a SIGUSR1.
.SH NOTES
ndhc will seed its random number generator (used for generating xids)
by reading /dev/urandom. If you have a lot of embedded systems on the same
network, with no entropy, you can either seed /dev/urandom by a method of
your own, or doing the following on startup:
ifconfig eth0 > /dev/urandom
in order to seed /dev/urandom with some data (mac address) unique to your
system. If reading /dev/urandom fails, ndhc will fall back to seeding with
time(0).

649
src/ndhc.c Normal file
View File

@@ -0,0 +1,649 @@
/* ndhc.c - DHCP client
*
* Copyright (c) 2004-2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - 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.
*/
#include <stdio.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/file.h>
#include <unistd.h>
#include <getopt.h>
#include <stdlib.h>
#include <signal.h>
#include <time.h>
#include <string.h>
#include <ctype.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <sys/signalfd.h>
#include <sys/prctl.h>
#include <net/if.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>
#include <limits.h>
#include "nk/log.h"
#include "nk/privilege.h"
#include "nk/pidfile.h"
#include "nk/io.h"
#include "nk/copy_cmdarg.h"
#include "ndhc.h"
#include "ndhc-defines.h"
#include "seccomp.h"
#include "state.h"
#include "options.h"
#include "dhcp.h"
#include "sys.h"
#include "ifchange.h"
#include "arp.h"
#include "nl.h"
#include "netlink.h"
#include "leasefile.h"
#include "ifset.h"
#include "ifchd.h"
#include "duiaid.h"
#include "sockd.h"
struct client_state_t cs = {
.ifchWorking = 0,
.ifDeconfig = 0,
.init = 1,
.epollFd = -1,
.signalFd = -1,
.listenFd = -1,
.arpFd = -1,
.nlFd = -1,
.nlPortId = -1,
.routerArp = "\0\0\0\0\0\0",
.serverArp = "\0\0\0\0\0\0",
};
struct client_config_t client_config = {
.interface = "eth0",
.arp = "\0\0\0\0\0\0",
.clientid_len = 0,
.metric = 0,
.foreground = 1,
};
static void show_usage(void)
{
printf(
"ndhc " NDHC_VERSION ", dhcp client. Licensed under 2-clause BSD.\n"
"Copyright (C) 2004-2014 Nicholas J. Kain\n"
"Usage: ndhc [OPTIONS]\n\n"
" -c, --clientid=CLIENTID Client identifier\n"
" -h, --hostname=HOSTNAME Client hostname\n"
" -V, --vendorid=VENDORID Client vendor identification string\n"
" -b, --background Fork to background if lease cannot be\n"
" immediately negotiated.\n"
" -p, --pidfile=FILE File where the ndhc pid will be written\n"
" -i, --interface=INTERFACE Interface to use (default: eth0)\n"
" -n, --now Exit with failure if lease cannot be\n"
" immediately negotiated.\n"
" -q, --quit Quit after obtaining lease\n"
" -r, --request=IP IP address to request (default: none)\n"
" -u, --user=USER Change ndhc privileges to this user\n"
" -U, --ifch-user=USER Change ndhc-ifch privileges to this user\n"
" -D, --sockd-user=USER Change ndhc-sockd privileges to this user\n"
" -C, --chroot=DIR Chroot to this directory\n"
" -s, --state-dir=DIR State storage dir (default: /etc/ndhc)\n"
#ifdef ENABLE_SECCOMP_FILTER
" -S, --seccomp-enforce Enforce seccomp syscall restrictions\n"
#endif
" -d, --relentless-defense Never back off in defending IP against\n"
" conflicting hosts (servers only)\n"
" -w, --arp-probe-wait Time to delay before first ARP probe\n"
" -W, --arp-probe-num Number of ARP probes before lease is ok\n"
" -m, --arp-probe-min Min ms to wait for ARP response\n"
" -M, --arp-probe-max Max ms to wait for ARP response\n"
" -t, --gw-metric Route metric for default gw (default: 0)\n"
" -R, --resolve-conf=FILE Path to resolv.conf or equivalent\n"
" -H, --dhcp-hostname Allow DHCP to set machine hostname\n"
" -v, --version Display version\n"
);
exit(EXIT_SUCCESS);
}
static void setup_signals_ndhc(void)
{
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGUSR1);
sigaddset(&mask, SIGUSR2);
sigaddset(&mask, SIGCHLD);
sigaddset(&mask, SIGPIPE);
sigaddset(&mask, SIGTERM);
if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0)
suicide("sigprocmask failed");
if (cs.signalFd >= 0) {
epoll_del(cs.epollFd, cs.signalFd);
close(cs.signalFd);
}
cs.signalFd = signalfd(-1, &mask, SFD_NONBLOCK);
if (cs.signalFd < 0)
suicide("signalfd failed");
epoll_add(cs.epollFd, cs.signalFd);
}
static void signal_dispatch(void)
{
int t;
size_t off = 0;
struct signalfd_siginfo si = {0};
again:
t = read(cs.signalFd, (char *)&si + off, sizeof si - off);
if (t < 0) {
if (t == EAGAIN || t == EWOULDBLOCK || t == EINTR)
goto again;
else
suicide("signalfd read error");
}
if (off + (unsigned)t < sizeof si)
off += t;
switch (si.ssi_signo) {
case SIGUSR1:
force_renew_action(&cs);
break;
case SIGUSR2:
force_release_action(&cs);
break;
case SIGPIPE:
log_line("ndhc-master: IPC pipe closed. Exiting.");
exit(EXIT_SUCCESS);
break;
case SIGCHLD:
suicide("ndhc-master: Subprocess terminated unexpectedly. Exiting.");
break;
case SIGTERM:
log_line("Received SIGTERM. Exiting gracefully.");
exit(EXIT_SUCCESS);
break;
default:
break;
}
}
static int is_string_hwaddr(char *str, size_t slen)
{
if (slen == 17 && str[2] == ':' && str[5] == ':' && str[8] == ':' &&
str[11] == ':' && str[14] == ':' &&
isxdigit(str[0]) && isxdigit(str[1]) && isxdigit(str[3]) &&
isxdigit(str[4]) && isxdigit(str[6]) && isxdigit(str[7]) &&
isxdigit(str[9]) && isxdigit(str[10]) && isxdigit(str[12]) &&
isxdigit(str[13]) && isxdigit(str[15]) && isxdigit(str[16])
)
return 1;
return 0;
}
static int get_clientid_string(char *str, size_t slen)
{
if (!slen)
return -1;
if (!is_string_hwaddr(str, slen)) {
client_config.clientid[0] = 0;
memcpy(&client_config.clientid + 1, str,
min_size_t(slen, sizeof client_config.clientid - 1));
client_config.clientid_len = slen + 1;
return 0;
}
uint8_t mac[6];
for (size_t i = 0; i < sizeof mac; ++i)
mac[i] = strtol(str+i*3, NULL, 16);
client_config.clientid[0] = 1; // Ethernet MAC type
memcpy(&client_config.clientid + 1, mac, sizeof mac);
client_config.clientid_len = 7;
return 1;
}
static void fail_if_state_dir_dne(void)
{
if (strlen(state_dir) == 0)
suicide("state_dir path is empty; it must be specified");
struct stat st;
if (stat(state_dir, &st) < 0)
suicide("failed to stat state_dir path '%s': %s",
state_dir, strerror(errno));
if (!S_ISDIR(st.st_mode))
suicide("state_dir path '%s' does not specify a directory", state_dir);
}
static void handle_ifch_message(void)
{
char c;
ssize_t r = safe_read(pToNdhcR, &c, sizeof c);
if (r == 0) {
// Remote end hung up.
exit(EXIT_SUCCESS);
} else if (r < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK)
return;
suicide("%s: (%s) error reading from ifch -> ndhc pipe: %s",
client_config.interface, __func__, strerror(errno));
}
if (c == '+')
cs.ifchWorking = 0;
}
#define NDHC_NUM_EP_FDS 4
static void do_ndhc_work(void)
{
struct epoll_event events[NDHC_NUM_EP_FDS];
long long nowts;
int timeout;
cs.epollFd = epoll_create1(0);
if (cs.epollFd < 0)
suicide("epoll_create1 failed");
if (enforce_seccomp_ndhc())
log_line("ndhc seccomp filter cannot be installed");
setup_signals_ndhc();
epoll_add(cs.epollFd, cs.nlFd);
epoll_add(cs.epollFd, pToNdhcR);
set_listen_raw(&cs);
nowts = curms();
goto jumpstart;
for (;;) {
int r = epoll_wait(cs.epollFd, events, NDHC_NUM_EP_FDS, timeout);
if (r < 0) {
if (errno == EINTR)
continue;
else
suicide("epoll_wait failed");
}
for (int i = 0; i < r; ++i) {
int fd = events[i].data.fd;
if (fd == cs.signalFd)
signal_dispatch();
else if (fd == cs.listenFd)
handle_packet(&cs);
else if (fd == cs.arpFd)
handle_arp_response(&cs);
else if (fd == cs.nlFd)
handle_nl_message(&cs);
else if (fd == pToNdhcR)
handle_ifch_message();
else
suicide("epoll_wait: unknown fd");
}
for (;;) {
nowts = curms();
long long arp_wake_ts = arp_get_wake_ts();
long long dhcp_wake_ts = dhcp_get_wake_ts();
if (arp_wake_ts < 0) {
if (dhcp_wake_ts != -1) {
timeout = dhcp_wake_ts - nowts;
if (timeout < 0)
timeout = 0;
} else
timeout = -1;
} else {
// If dhcp_wake_ts is -1 then we want to sleep anyway.
timeout = (arp_wake_ts < dhcp_wake_ts ?
arp_wake_ts : dhcp_wake_ts) - nowts;
if (timeout < 0)
timeout = 0;
}
if (!timeout) {
jumpstart:
timeout_action(&cs, nowts);
} else
break;
}
}
}
char state_dir[PATH_MAX] = "/etc/ndhc";
char chroot_dir[PATH_MAX] = "";
char resolv_conf_d[PATH_MAX] = "";
static char pidfile[PATH_MAX] = PID_FILE_DEFAULT;
static uid_t ndhc_uid = 0;
static gid_t ndhc_gid = 0;
int pToNdhcR;
int pToNdhcW;
int pToIfchR;
int pToIfchW;
int sockdSock[2];
int sockdPipe[2];
static void create_ifch_ipc_pipes(void) {
int niPipe[2];
int inPipe[2];
if (pipe2(niPipe, 0))
suicide("FATAL - can't create ndhc -> ndhc-ifch pipe: %s",
strerror(errno));
if (fcntl(niPipe[0], F_SETFL, fcntl(niPipe[0], F_GETFL) | O_NONBLOCK) < 0)
suicide("FATAL - failed to set ndhc -> ndhc-ifch read-side nonblocking: %s",
strerror(errno));
pToNdhcR = niPipe[0];
pToNdhcW = niPipe[1];
if (pipe2(inPipe, 0))
suicide("FATAL - can't create ndhc-ifch -> ndhc pipe: %s",
strerror(errno));
if (fcntl(inPipe[0], F_SETFL, fcntl(inPipe[0], F_GETFL) | O_NONBLOCK) < 0)
suicide("FATAL - failed to set ndhc-ifch -> ndhc read-side nonblocking: %s",
strerror(errno));
pToIfchR = inPipe[0];
pToIfchW = inPipe[1];
}
static void create_sockd_ipc_pipes(void) {
if (pipe2(sockdPipe, 0))
suicide("FATAL - can't create ndhc/sockd pipe: %s", strerror(errno));
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sockdSock) < 0)
suicide("FATAL - can't create ndhc/sockd socket: %s", strerror(errno));
}
static void spawn_ifch(void)
{
create_ifch_ipc_pipes();
pid_t ifch_pid = fork();
if (ifch_pid == 0) {
close(pToNdhcR);
close(pToIfchW);
// Don't share the RNG state with the master process.
nk_random_u32_init(&cs.rnd32_state);
ifch_main();
} else if (ifch_pid > 0) {
close(pToIfchR);
close(pToNdhcW);
} else
suicide("failed to fork ndhc-ifch: %s", strerror(errno));
}
static void spawn_sockd(void)
{
create_sockd_ipc_pipes();
pid_t sockd_pid = fork();
if (sockd_pid == 0) {
close(sockdPipe[0]);
close(sockdSock[0]);
// Don't share the RNG state with the master process.
nk_random_u32_init(&cs.rnd32_state);
sockd_main();
} else if (sockd_pid > 0) {
close(sockdSock[1]);
close(sockdPipe[1]);
} else
suicide("failed to fork ndhc-sockd: %s", strerror(errno));
}
static void ndhc_main(void) {
prctl(PR_SET_NAME, "ndhc: master");
log_line("ndhc client " NDHC_VERSION " started on interface [%s].",
client_config.interface);
if ((cs.nlFd = nl_open(NETLINK_ROUTE, RTMGRP_LINK, &cs.nlPortId)) < 0)
suicide("%s: failed to open netlink socket", __func__);
if (client_config.foreground && !client_config.background_if_no_lease) {
if (file_exists(pidfile, "w") < 0)
suicide("%s: can't open pidfile '%s' for write!",
__func__, pidfile);
write_pid(pidfile);
}
open_leasefile();
nk_set_chroot(chroot_dir);
memset(chroot_dir, '\0', sizeof chroot_dir);
nk_set_uidgid(ndhc_uid, ndhc_gid, NULL);
if (cs.ifsPrevState != IFS_UP)
ifchange_deconfig(&cs);
do_ndhc_work();
}
void background(void)
{
static char called;
if (!called) {
called = 1; // Do not fork again.
if (daemon(0, 0) < 0) {
perror("fork");
exit(EXIT_SUCCESS);
}
}
if (file_exists(pidfile, "w") < 0) {
log_warning("Cannot open pidfile for write!");
} else
write_pid(pidfile);
}
static void parse_program_options(int argc, char *argv[])
{
static const struct option arg_options[] = {
{"clientid", required_argument, 0, 'c'},
{"background", no_argument, 0, 'b'},
{"pidfile", required_argument, 0, 'p'},
{"hostname", required_argument, 0, 'h'},
{"interface", required_argument, 0, 'i'},
{"now", no_argument, 0, 'n'},
{"quit", no_argument, 0, 'q'},
{"request", required_argument, 0, 'r'},
{"vendorid", required_argument, 0, 'V'},
{"user", required_argument, 0, 'u'},
{"ifch-user", required_argument, 0, 'U'},
{"sockd-user", required_argument, 0, 'D'},
{"chroot", required_argument, 0, 'C'},
{"state-dir", required_argument, 0, 's'},
{"seccomp-enforce", no_argument, 0, 'S'},
{"relentless-defense", no_argument, 0, 'd'},
{"arp-probe-wait", required_argument, 0, 'w'},
{"arp-probe-num", required_argument, 0, 'W'},
{"arp-probe-min", required_argument, 0, 'm'},
{"arp-probe-max", required_argument, 0, 'M'},
{"gw-metric", required_argument, 0, 't'},
{"resolv-conf", required_argument, 0, 'R'},
{"dhcp-set-hostname", no_argument, 0, 'H'},
{"version", no_argument, 0, 'v'},
{"help", no_argument, 0, '?'},
{0, 0, 0, 0}
};
while (1) {
int c;
c = getopt_long(argc, argv, "c:bp:P:h:i:nqr:V:u:U:D:C:s:Sdw:W:m:M:t:R:Hv?",
arg_options, NULL);
if (c < 0) break;
switch (c) {
case 'c':
get_clientid_string(optarg, strlen(optarg));
break;
case 'b':
client_config.background_if_no_lease = 1;
gflags_detach = 1;
break;
case 'p':
copy_cmdarg(pidfile, optarg, sizeof pidfile, "pidfile");
break;
case 'h':
copy_cmdarg(client_config.hostname, optarg,
sizeof client_config.hostname, "hostname");
break;
case 'i':
copy_cmdarg(client_config.interface, optarg,
sizeof client_config.interface, "interface");
break;
case 'n':
client_config.abort_if_no_lease = 1;
break;
case 'q':
client_config.quit_after_lease = 1;
break;
case 'r':
cs.clientAddr = inet_addr(optarg);
break;
case 'u':
if (nk_uidgidbyname(optarg, &ndhc_uid, &ndhc_gid))
suicide("invalid ndhc user '%s' specified", optarg);
break;
case 'U':
if (nk_uidgidbyname(optarg, &ifch_uid, &ifch_gid))
suicide("invalid ifch user '%s' specified", optarg);
break;
case 'D':
if (nk_uidgidbyname(optarg, &sockd_uid, &sockd_gid))
suicide("invalid sockd user '%s' specified", optarg);
break;
case 'C':
copy_cmdarg(chroot_dir, optarg, sizeof chroot_dir, "chroot");
break;
case 's':
copy_cmdarg(state_dir, optarg, sizeof state_dir, "state-dir");
break;
case 'S':
seccomp_enforce = true;
break;
case 'd':
arp_relentless_def = 1;
break;
case 'w':
case 'W': {
int t = atoi(optarg);
if (t < 0)
break;
if (c == 'w')
arp_probe_wait = t;
else
arp_probe_num = t;
break;
}
case 'm':
case 'M': {
int t = atoi(optarg);
if (c == 'm')
arp_probe_min = t;
else
arp_probe_max = t;
if (arp_probe_min > arp_probe_max) {
t = arp_probe_max;
arp_probe_max = arp_probe_min;
arp_probe_min = t;
}
break;
}
case 'v':
printf("ndhc %s, dhcp client.\n", NDHC_VERSION);
printf("Copyright (c) 2004-2014 Nicholas J. Kain\n"
"All rights reserved.\n\n"
"Redistribution and use in source and binary forms, with or without\n"
"modification, are permitted provided that the following conditions are met:\n\n"
"- Redistributions of source code must retain the above copyright notice,\n"
" this list of conditions and the following disclaimer.\n"
"- Redistributions in binary form must reproduce the above copyright notice,\n"
" this list of conditions and the following disclaimer in the documentation\n"
" and/or other materials provided with the distribution.\n\n"
"THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n"
"AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n"
"IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n"
"ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n"
"LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n"
"CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n"
"SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n"
"INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n"
"CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n"
"ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n"
"POSSIBILITY OF SUCH DAMAGE.\n");
exit(EXIT_SUCCESS);
break;
case 'V':
copy_cmdarg(client_config.vendor, optarg,
sizeof client_config.vendor, "vendorid");
break;
case 't': {
char *p;
long mt = strtol(optarg, &p, 10);
if (p == optarg)
suicide("gw-metric arg '%s' isn't a valid number", optarg);
if (mt > INT_MAX)
suicide("gw-metric arg '%s' is too large", optarg);
if (mt < 0)
mt = 0;
client_config.metric = (int)mt;
break;
}
case 'R':
copy_cmdarg(resolv_conf_d, optarg, sizeof resolv_conf_d,
"resolv-conf");
break;
case 'H':
allow_hostname = 1;
break;
default:
show_usage();
}
}
}
int main(int argc, char *argv[])
{
parse_program_options(argc, argv);
nk_random_u32_init(&cs.rnd32_state);
if (getuid())
suicide("I need to be started as root.");
if (!strncmp(chroot_dir, "", sizeof chroot_dir))
suicide("No chroot path is specified. Refusing to run.");
fail_if_state_dir_dne();
if (nl_getifdata() < 0)
suicide("failed to get interface MAC or index");
get_clientid(&cs, &client_config);
switch (perform_ifup()) {
case 1: cs.ifsPrevState = IFS_UP;
case 0: break;
default: suicide("failed to set the interface to up state");
}
spawn_ifch();
spawn_sockd();
ndhc_main();
exit(EXIT_SUCCESS);
}

82
src/ndhc.h Normal file
View File

@@ -0,0 +1,82 @@
/* ndhc.h - DHCP client
*
* Copyright (c) 2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - 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.
*/
#ifndef NJK_NDHC_NDHC_H_
#define NJK_NDHC_NDHC_H_
#include <stdint.h>
#include <limits.h>
#include <net/if.h>
#include "nk/random.h"
struct client_state_t {
unsigned long long leaseStartTime;
int dhcpState;
int arpPrevState;
int ifsPrevState;
int ifchWorking; // ifch is performing interface changes.
int ifDeconfig; // Set if the interface has already been deconfigured.
int listenMode;
int epollFd, signalFd, listenFd, arpFd, nlFd;
int nlPortId;
uint32_t clientAddr, serverAddr, routerAddr;
uint32_t lease, renewTime, rebindTime, xid;
struct nk_random_state_u32 rnd32_state;
uint8_t routerArp[6], serverArp[6];
uint8_t using_dhcp_bpf, init, got_router_arp, got_server_arp;
};
struct client_config_t {
char foreground; // Do not fork
char quit_after_lease; // Quit after obtaining lease
char abort_if_no_lease; // Abort if no lease
char background_if_no_lease; // Fork to background if no lease
char interface[IFNAMSIZ]; // The name of the interface to use
char clientid[64]; // Optional client id to use
uint8_t clientid_len; // Length of the clientid
char hostname[64]; // Optional hostname to use
char vendor[64]; // Vendor identification that will be sent
int metric; // Metric for the default route
int ifindex; // Index number of the interface to use
uint8_t arp[6]; // Our arp address
};
extern struct client_config_t client_config;
extern int pToIfchR;
extern int pToIfchW;
extern int pToNdhcR;
extern int pToNdhcW;
extern int sockdSock[2];
extern int sockdPipe[2];
extern char state_dir[PATH_MAX];
extern char chroot_dir[PATH_MAX];
extern char resolv_conf_d[PATH_MAX];
void background(void);
#endif /* NJK_NDHC_NDHC_H_ */

194
src/netlink.c Normal file
View File

@@ -0,0 +1,194 @@
/* netlink.c - netlink physical link notification handling and info retrieval
*
* Copyright (c) 2011-2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - 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.
*/
#include <unistd.h>
#include <arpa/inet.h>
#include <assert.h>
#include <asm/types.h>
#include <sys/socket.h>
#include <net/if.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <poll.h>
#include "nk/log.h"
#include "netlink.h"
#include "nl.h"
#include "state.h"
static void nl_process_msgs(const struct nlmsghdr *nlh, void *data)
{
struct ifinfomsg *ifm = NLMSG_DATA(nlh);
struct client_state_t *cs = data;
switch(nlh->nlmsg_type) {
case RTM_NEWLINK:
if (ifm->ifi_index != client_config.ifindex)
break;
// IFF_UP corresponds to ifconfig down or ifconfig up.
if (ifm->ifi_flags & IFF_UP) {
// IFF_RUNNING is the hardware carrier.
if (ifm->ifi_flags & IFF_RUNNING) {
if (cs->ifsPrevState != IFS_UP) {
cs->ifsPrevState = IFS_UP;
ifup_action(cs);
}
} else if (cs->ifsPrevState != IFS_DOWN) {
// Interface configured, but no hardware carrier.
cs->ifsPrevState = IFS_DOWN;
ifnocarrier_action(cs);
}
} else if (cs->ifsPrevState != IFS_SHUT) {
// User shut down the interface.
cs->ifsPrevState = IFS_SHUT;
ifdown_action(cs);
}
break;
case RTM_DELLINK:
if (ifm->ifi_index != client_config.ifindex)
break;
if (cs->ifsPrevState != IFS_REMOVED) {
cs->ifsPrevState = IFS_REMOVED;
log_line("Interface removed. Exiting.");
exit(EXIT_SUCCESS);
}
break;
default:
break;
}
}
void handle_nl_message(struct client_state_t *cs)
{
char nlbuf[8192];
ssize_t ret;
assert(cs->nlFd != -1);
do {
ret = nl_recv_buf(cs->nlFd, nlbuf, sizeof nlbuf);
if (ret < 0)
break;
if (nl_foreach_nlmsg(nlbuf, ret, 0, cs->nlPortId, nl_process_msgs, cs)
< 0)
break;
} while (ret > 0);
}
static int get_if_index_and_mac(const struct nlmsghdr *nlh,
struct ifinfomsg *ifm)
{
struct rtattr *tb[IFLA_MAX] = {0};
nl_rtattr_parse(nlh, sizeof *ifm, rtattr_assign, tb);
if (tb[IFLA_IFNAME] && !strncmp(client_config.interface,
RTA_DATA(tb[IFLA_IFNAME]),
sizeof client_config.interface)) {
client_config.ifindex = ifm->ifi_index;
if (!tb[IFLA_ADDRESS])
suicide("FATAL: Adapter %s lacks a hardware address.");
int maclen = tb[IFLA_ADDRESS]->rta_len - 4;
if (maclen != 6)
suicide("FATAL: Adapter hardware address length should be 6, but is %u.",
maclen);
const unsigned char *mac = RTA_DATA(tb[IFLA_ADDRESS]);
log_line("%s hardware address %x:%x:%x:%x:%x:%x",
client_config.interface,
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
memcpy(client_config.arp, mac, 6);
return 1;
}
return 0;
}
static void do_handle_getifdata(const struct nlmsghdr *nlh, void *data)
{
int *got_ifdata = (int *)data;
struct ifinfomsg *ifm = NLMSG_DATA(nlh);
switch(nlh->nlmsg_type) {
case RTM_NEWLINK:
*got_ifdata |= get_if_index_and_mac(nlh, ifm);
break;
default:
break;
}
}
static int handle_getifdata(int fd, uint32_t seq)
{
char nlbuf[8192];
ssize_t ret;
int got_ifdata = 0;
do {
ret = nl_recv_buf(fd, nlbuf, sizeof nlbuf);
if (ret < 0)
return -1;
if (nl_foreach_nlmsg(nlbuf, ret, seq, 0,
do_handle_getifdata, &got_ifdata) < 0)
return -1;
} while (ret > 0);
return got_ifdata ? 0 : -1;
}
int nl_getifdata(void)
{
int ret = -1;
int fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
if (fd < 0) {
log_line("%s: (%s) netlink socket open failed: %s",
client_config.interface, __func__, strerror(errno));
goto fail;
}
struct timespec ts;
if (clock_gettime(CLOCK_REALTIME, &ts) < 0) {
log_line("%s: (%s) clock_gettime failed",
client_config.interface, __func__);
goto fail_fd;
}
uint32_t seq = ts.tv_nsec;
if (nl_sendgetlinks(fd, seq)) {
log_line("%s: (%s) nl_sendgetlinks failed",
client_config.interface, __func__);
goto fail_fd;
}
for (int pr = 0; !pr;) {
pr = poll(&((struct pollfd){.fd=fd,.events=POLLIN}), 1, -1);
if (pr == 1)
ret = handle_getifdata(fd, seq);
else if (pr < 0 && errno != EINTR)
goto fail_fd;
}
fail_fd:
close(fd);
fail:
return ret;
}

46
src/netlink.h Normal file
View File

@@ -0,0 +1,46 @@
/* netlink.h - netlink physical link notification handling and info retrieval
*
* Copyright (c) 2011-2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - 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.
*/
#ifndef NK_NETLINK_H_
#define NK_NETLINK_H_
#include <linux/rtnetlink.h>
#include "state.h"
enum {
IFS_NONE = 0,
IFS_UP,
IFS_DOWN,
IFS_SHUT,
IFS_REMOVED
};
void handle_nl_message(struct client_state_t *cs);
int nl_getifdata(void);
#endif /* NK_NETLINK_H_ */

314
src/nl.c Normal file
View File

@@ -0,0 +1,314 @@
/* nl.c - low level netlink protocol functions
*
* Copyright (c) 2011-2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - 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.
*/
#include <assert.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <linux/rtnetlink.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include "nk/log.h"
#include "nl.h"
int rtattr_assign(struct rtattr *attr, int type, void *data)
{
struct rtattr **tb = data;
if (type >= IFA_MAX)
return 0;
tb[type] = attr;
return 0;
}
#define NLMSG_TAIL(nmsg) \
((struct rtattr *) (((uint8_t*) (nmsg)) + \
NLMSG_ALIGN((nmsg)->nlmsg_len)))
int nl_add_rtattr(struct nlmsghdr *n, size_t max_length, int type,
const void *data, size_t data_length)
{
size_t length = RTA_LENGTH(data_length);
if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(length) > max_length)
return -E2BIG;
struct rtattr *rta = NLMSG_TAIL(n);
rta->rta_type = type;
rta->rta_len = length;
memcpy(RTA_DATA(rta), data, data_length);
n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(length);
return 0;
}
void nl_rtattr_parse(const struct nlmsghdr *nlh, size_t offset,
nl_rtattr_parse_fn workfn, void *data)
{
struct rtattr *attr =
(struct rtattr *)((char *)NLMSG_DATA(nlh) + NLMSG_ALIGN(offset));
size_t rtlen = nlh->nlmsg_len - NLMSG_HDRLEN - NLMSG_ALIGN(offset);
for (; RTA_OK(attr, rtlen); attr = RTA_NEXT(attr, rtlen)) {
if (workfn(attr, attr->rta_type, data) < 0)
break;
}
}
ssize_t nl_recv_buf(int fd, char *buf, size_t blen)
{
struct sockaddr_nl addr;
struct iovec iov = {
.iov_base = buf,
.iov_len = blen,
};
struct msghdr msg = {
.msg_name = &addr,
.msg_namelen = sizeof addr,
.msg_iov = &iov,
.msg_iovlen = 1,
};
ssize_t ret;
retry:
ret = recvmsg(fd, &msg, MSG_DONTWAIT);
if (ret < 0) {
if (errno == EINTR)
goto retry;
if (errno != EAGAIN && errno != EWOULDBLOCK) {
log_error("%s: recvmsg failed: %s", __func__, strerror(errno));
return -1;
}
return 0;
}
if (msg.msg_flags & MSG_TRUNC) {
log_error("%s: Buffer not long enough for message.", __func__);
return -1;
}
if (msg.msg_namelen != sizeof addr) {
log_error("%s: Response was not of the same address family.",
__func__);
return -1;
}
return ret;
}
int nl_foreach_nlmsg(char *buf, size_t blen, uint32_t seq, uint32_t portid,
nlmsg_foreach_fn pfn, void *fnarg)
{
const struct nlmsghdr *nlh = (const struct nlmsghdr *)buf;
assert(pfn);
for (;NLMSG_OK(nlh, blen); nlh = NLMSG_NEXT(nlh, blen)) {
// PortID should be zero for messages from the kernel.
if (nlh->nlmsg_pid && portid && nlh->nlmsg_pid != portid)
continue;
if (seq && nlh->nlmsg_seq != seq)
continue;
if (nlh->nlmsg_type >= NLMSG_MIN_TYPE) {
pfn(nlh, fnarg);
} else {
switch (nlh->nlmsg_type) {
case NLMSG_ERROR:
log_line("%s: Received a NLMSG_ERROR: %s",
__func__, strerror(nlmsg_get_error(nlh)));
return -1;
case NLMSG_DONE:
return 0;
case NLMSG_OVERRUN:
log_line("%s: Received a NLMSG_OVERRUN.", __func__);
case NLMSG_NOOP:
default:
break;
}
}
}
return 0;
}
static int nl_sendgetlink_do(int fd, int seq, int ifindex, int by_ifindex)
{
char nlbuf[512];
struct nlmsghdr *nlh = (struct nlmsghdr *)nlbuf;
struct ifinfomsg *ifinfomsg;
ssize_t r;
memset(nlbuf, 0, sizeof nlbuf);
nlh->nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
nlh->nlmsg_type = RTM_GETLINK;
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT;
nlh->nlmsg_seq = seq;
if (by_ifindex) {
ifinfomsg = NLMSG_DATA(nlh);
ifinfomsg->ifi_index = ifindex;
}
struct sockaddr_nl addr = {
.nl_family = AF_NETLINK,
};
retry_sendto:
r = sendto(fd, nlbuf, nlh->nlmsg_len, 0,
(struct sockaddr *)&addr, sizeof addr);
if (r < 0) {
if (errno == EINTR)
goto retry_sendto;
else {
log_warning("%s: netlink sendto socket failed: %s",
__func__, strerror(errno));
return -1;
}
}
return 0;
}
int nl_sendgetlinks(int fd, int seq)
{
return nl_sendgetlink_do(fd, seq, 0, 0);
}
int nl_sendgetlink(int fd, int seq, int ifindex)
{
return nl_sendgetlink_do(fd, seq, ifindex, 1);
}
static int nl_sendgetaddr_do(int fd, int seq, int ifindex, int by_ifindex,
int afamily, int by_afamily)
{
char nlbuf[512];
struct nlmsghdr *nlh = (struct nlmsghdr *)nlbuf;
struct ifaddrmsg *ifaddrmsg;
ssize_t r;
memset(nlbuf, 0, sizeof nlbuf);
nlh->nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg));
nlh->nlmsg_type = RTM_GETADDR;
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT;
nlh->nlmsg_seq = seq;
ifaddrmsg = NLMSG_DATA(nlh);
if (by_afamily)
ifaddrmsg->ifa_family = afamily;
if (by_ifindex)
ifaddrmsg->ifa_index = ifindex;
struct sockaddr_nl addr = {
.nl_family = AF_NETLINK,
};
retry_sendto:
r = sendto(fd, nlbuf, nlh->nlmsg_len, 0,
(struct sockaddr *)&addr, sizeof addr);
if (r < 0) {
if (errno == EINTR)
goto retry_sendto;
else {
log_warning("%s: netlink sendto socket failed: %s",
__func__, strerror(errno));
return -1;
}
}
return 0;
}
int nl_sendgetaddrs(int fd, int seq)
{
return nl_sendgetaddr_do(fd, seq, 0, 0, 0, 0);
}
int nl_sendgetaddrs4(int fd, int seq)
{
return nl_sendgetaddr_do(fd, seq, 0, 0, AF_INET, 1);
}
int nl_sendgetaddrs6(int fd, int seq)
{
return nl_sendgetaddr_do(fd, seq, 0, 0, AF_INET6, 1);
}
int nl_sendgetaddr4(int fd, int seq, int ifindex)
{
return nl_sendgetaddr_do(fd, seq, ifindex, 1, AF_INET, 1);
}
int nl_sendgetaddr6(int fd, int seq, int ifindex)
{
return nl_sendgetaddr_do(fd, seq, ifindex, 1, AF_INET6, 1);
}
int nl_open(int nltype, int nlgroup, int *nlportid)
{
int fd;
fd = socket(AF_NETLINK, SOCK_RAW, nltype);
if (fd < 0) {
log_error("%s: socket failed: %s", __func__, strerror(errno));
return -1;
}
if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK) < 0) {
log_error("%s: Set non-blocking failed: %s",
__func__, strerror(errno));
goto err_close;
}
if (fcntl(fd, F_SETFD, FD_CLOEXEC)) {
log_error("%s: Set close-on-exec failed: %s",
__func__, strerror(errno));
goto err_close;
}
socklen_t al;
struct sockaddr_nl nlsock = {
.nl_family = AF_NETLINK,
.nl_groups = nlgroup,
};
if (bind(fd, (struct sockaddr *)&nlsock, sizeof nlsock) < 0) {
log_error("%s: bind to group failed: %s",
__func__, strerror(errno));
goto err_close;
}
al = sizeof nlsock;
if (getsockname(fd, (struct sockaddr *)&nlsock, &al) < 0) {
log_error("%s: getsockname failed: %s",
__func__, strerror(errno));
goto err_close;
}
if (al != sizeof nlsock) {
log_error("%s: Bound socket doesn't have right family size.",
__func__);
goto err_close;
}
if (nlsock.nl_family != AF_NETLINK) {
log_error("%s: Bound socket isn't AF_NETLINK.",
__func__);
goto err_close;
}
if (nlportid)
*nlportid = nlsock.nl_pid;
return fd;
err_close:
close(fd);
return -1;
}

67
src/nl.h Normal file
View File

@@ -0,0 +1,67 @@
/* nl.h - low level netlink protocol functions
*
* Copyright (c) 2011-2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - 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.
*/
#ifndef NK_NL_H_
#define NK_NL_H_
// Limited netlink code. The horrors...
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
static inline int nlmsg_get_error(const struct nlmsghdr *nlh)
{
const struct nlmsgerr *err = (const struct nlmsgerr *)NLMSG_DATA(nlh);
if (nlh->nlmsg_len < sizeof(struct nlmsgerr) + NLMSG_HDRLEN)
return EBADMSG;
return err->error & 0x7fffffff;
}
int rtattr_assign(struct rtattr *attr, int type, void *data);
int nl_add_rtattr(struct nlmsghdr *n, size_t max_length, int type,
const void *data, size_t data_length);
typedef int (*nl_rtattr_parse_fn)(struct rtattr *attr, int type, void *data);
void nl_rtattr_parse(const struct nlmsghdr *nlh, size_t offset,
nl_rtattr_parse_fn workfn, void *data);
ssize_t nl_recv_buf(int fd, char *buf, size_t blen);
typedef void (*nlmsg_foreach_fn)(const struct nlmsghdr *, void *);
int nl_foreach_nlmsg(char *buf, size_t blen, uint32_t seq,
uint32_t portid,
nlmsg_foreach_fn pfn, void *fnarg);
int nl_sendgetlinks(int fd, int seq);
int nl_sendgetlink(int fd, int seq, int ifindex);
int nl_sendgetaddr4(int fd, int seq, int ifindex);
int nl_sendgetaddr6(int fd, int seq, int ifindex);
int nl_sendgetaddrs(int fd, int seq);
int nl_sendgetaddrs4(int fd, int seq);
int nl_sendgetaddrs6(int fd, int seq);
int nl_open(int nltype, int nlgroup, int *nlportid);
#endif /* NK_NL_H_ */

365
src/options.c Normal file
View File

@@ -0,0 +1,365 @@
/* options.c - DHCP options handling
*
* Copyright (c) 2004-2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - 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.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include "nk/log.h"
#include "options.h"
static int do_overload_value(const uint8_t *buf, ssize_t blen, int overload)
{
ssize_t i = 0;
while (i < blen) {
if (buf[i] == DCODE_PADDING) {
++i;
continue;
}
if (buf[i] == DCODE_END)
break;
if (i >= blen - 2)
break;
if (buf[i] == DCODE_OVERLOAD) {
if (buf[i+1] == 1) {
overload |= buf[i+2];
i += 3;
continue;
}
}
i += buf[i+1] + 2;
}
return overload;
}
static int overload_value(const struct dhcpmsg *packet)
{
int ol = do_overload_value(packet->options, sizeof packet->options, 0);
if (ol & 1 && ol & 2)
return ol;
if (ol & 1) {
ol |= do_overload_value(packet->file, sizeof packet->file, ol);
return ol;
}
if (ol & 2) {
ol |= do_overload_value(packet->sname, sizeof packet->sname, ol);
return ol;
}
return ol; // ol == 0
}
static ssize_t do_get_dhcp_opt(const uint8_t *sbuf, ssize_t slen, uint8_t code,
uint8_t *dbuf, ssize_t dlen, ssize_t didx)
{
ssize_t i = 0;
while (i < slen) {
if (sbuf[i] == DCODE_PADDING) {
++i;
continue;
}
if (sbuf[i] == DCODE_END)
break;
if (i >= slen - 2)
break;
ssize_t soptsiz = sbuf[i+1];
if (sbuf[i] == code) {
if (dlen - didx < soptsiz)
return didx;
if (slen - i - 2 < soptsiz)
return didx;
memcpy(dbuf+didx, sbuf+i+2, soptsiz);
didx += soptsiz;
}
i += soptsiz + 2;
}
return didx;
}
ssize_t get_dhcp_opt(const struct dhcpmsg *packet, uint8_t code, uint8_t *dbuf,
ssize_t dlen)
{
int ol = overload_value(packet);
ssize_t didx = do_get_dhcp_opt(packet->options, sizeof packet->options,
code, dbuf, dlen, 0);
if (ol & 1)
didx += do_get_dhcp_opt(packet->file, sizeof packet->file, code,
dbuf, dlen, didx);
if (ol & 2)
didx += do_get_dhcp_opt(packet->sname, sizeof packet->sname, code,
dbuf, dlen, didx);
return didx;
}
// return the position of the 'end' option
ssize_t get_end_option_idx(const struct dhcpmsg *packet)
{
for (size_t i = 0; i < sizeof packet->options; ++i) {
if (packet->options[i] == DCODE_END)
return i;
if (packet->options[i] == DCODE_PADDING)
continue;
if (i + 1 >= sizeof packet->options)
break;
i += packet->options[i+1] + 1;
}
log_warning("get_end_option_idx: Did not find DCODE_END marker.");
return -1;
}
static inline size_t sizeof_option_str(uint8_t code, size_t datalen)
{
if (code == DCODE_PADDING || code == DCODE_END)
return 1;
return 2 + datalen;
}
// Add a raw data string to the options. It will take a binary string suitable
// for use with eg memcpy() and will append it to the options[] field of
// a dhcp packet with the requested option code and proper length specifier.
size_t add_option_string(struct dhcpmsg *packet, uint8_t code, const char *str,
size_t slen)
{
size_t len = sizeof_option_str(code, slen);
if (slen > 255 || len != slen + 2) {
log_warning("add_option_string: Length checks failed.");
return 0;
}
ssize_t end = get_end_option_idx(packet);
if (end < 0) {
log_warning("add_option_string: Buffer has no DCODE_END marker.");
return 0;
}
if (end + len >= sizeof packet->options) {
log_warning("add_option_string: No space for option 0x%02x.", code);
return 0;
}
packet->options[end] = code;
packet->options[end+1] = slen;
memcpy(packet->options + end + 2, str, slen);
packet->options[end+len] = DCODE_END;
return len;
}
static ssize_t add_option_check(struct dhcpmsg *packet, uint8_t code,
uint8_t rlen)
{
ssize_t end = get_end_option_idx(packet);
if (end < 0) {
log_warning("add_u%01u_option: Buffer has no DCODE_END marker.", rlen*8);
return -1;
}
if ((size_t)end + 2 + rlen >= sizeof packet->options) {
log_warning("add_u%01u_option: No space for option 0x%02x.",
rlen*8, code);
return -1;
}
return end;
}
static size_t add_u8_option(struct dhcpmsg *packet, uint8_t code, uint8_t data)
{
ssize_t end = add_option_check(packet, code, 1);
if (end < 0)
return 0;
packet->options[end] = code;
packet->options[end+1] = 1;
packet->options[end+2] = data;
packet->options[end+3] = DCODE_END;
return 3;
}
#ifndef NDHS_BUILD
// Data should be in network byte order.
static size_t add_u16_option(struct dhcpmsg *packet, uint8_t code,
uint16_t data)
{
ssize_t end = add_option_check(packet, code, 2);
if (end < 0)
return 0;
uint8_t *dp = (uint8_t *)&data;
packet->options[end] = code;
packet->options[end+1] = 2;
packet->options[end+2] = dp[0];
packet->options[end+3] = dp[1];
packet->options[end+4] = DCODE_END;
return 4;
}
#endif
// Data should be in network byte order.
size_t add_u32_option(struct dhcpmsg *packet, uint8_t code, uint32_t data)
{
ssize_t end = add_option_check(packet, code, 4);
if (end < 0)
return 0;
uint8_t *dp = (uint8_t *)&data;
packet->options[end] = code;
packet->options[end+1] = 4;
packet->options[end+2] = dp[0];
packet->options[end+3] = dp[1];
packet->options[end+4] = dp[2];
packet->options[end+5] = dp[3];
packet->options[end+6] = DCODE_END;
return 6;
}
// Add a parameter request list for stubborn DHCP servers
size_t add_option_request_list(struct dhcpmsg *packet)
{
static const uint8_t reqdata[] = {
DCODE_SUBNET, DCODE_ROUTER, DCODE_DNS, DCODE_HOSTNAME, DCODE_DOMAIN,
DCODE_BROADCAST,
};
return add_option_string(packet, DCODE_PARAM_REQ,
(char *)reqdata, sizeof reqdata);
}
#ifdef NDHS_BUILD
size_t add_option_domain_name(struct dhcpmsg *packet, const char *dom,
size_t domlen)
{
return add_option_string(packet, DCODE_DOMAIN, dom, domlen);
}
void add_option_subnet_mask(struct dhcpmsg *packet, uint32_t subnet)
{
add_u32_option(packet, DCODE_SUBNET, subnet);
}
void add_option_broadcast(struct dhcpmsg *packet, uint32_t bc)
{
add_u32_option(packet, DCODE_BROADCAST, bc);
}
#endif
void add_option_msgtype(struct dhcpmsg *packet, uint8_t type)
{
add_u8_option(packet, DCODE_MSGTYPE, type);
}
void add_option_reqip(struct dhcpmsg *packet, uint32_t ip)
{
add_u32_option(packet, DCODE_REQIP, ip);
}
void add_option_serverid(struct dhcpmsg *packet, uint32_t sid)
{
add_u32_option(packet, DCODE_SERVER_ID, sid);
}
void add_option_clientid(struct dhcpmsg *packet, const char *clientid,
size_t clen)
{
add_option_string(packet, DCODE_CLIENT_ID, clientid, clen);
}
#ifndef NDHS_BUILD
void add_option_maxsize(struct dhcpmsg *packet)
{
add_u16_option(packet, DCODE_MAX_SIZE,
htons(sizeof(struct ip_udp_dhcp_packet)));
}
void add_option_vendor(struct dhcpmsg *packet)
{
size_t len = strlen(client_config.vendor);
if (len)
add_option_string(packet, DCODE_VENDOR, client_config.vendor, len);
else
add_option_string(packet, DCODE_VENDOR, "ndhc", sizeof "ndhc" - 1);
}
void add_option_hostname(struct dhcpmsg *packet)
{
size_t len = strlen(client_config.hostname);
if (len)
add_option_string(packet, DCODE_HOSTNAME, client_config.hostname, len);
}
#endif
uint32_t get_option_router(const struct dhcpmsg *packet)
{
ssize_t ol;
uint32_t ret = 0;
uint8_t buf[MAX_DOPT_SIZE];
ol = get_dhcp_opt(packet, DCODE_ROUTER, buf, sizeof buf);
if (ol == sizeof ret)
memcpy(&ret, buf, sizeof ret);
return ret;
}
uint8_t get_option_msgtype(const struct dhcpmsg *packet)
{
ssize_t ol;
uint8_t ret = 0;
uint8_t buf[MAX_DOPT_SIZE];
ol = get_dhcp_opt(packet, DCODE_MSGTYPE, buf, sizeof buf);
if (ol == sizeof ret)
ret = buf[0];
return ret;
}
uint32_t get_option_serverid(const struct dhcpmsg *packet, int *found)
{
ssize_t ol;
uint32_t ret = 0;
uint8_t buf[MAX_DOPT_SIZE];
*found = 0;
ol = get_dhcp_opt(packet, DCODE_SERVER_ID, buf, sizeof buf);
if (ol == sizeof ret) {
*found = 1;
memcpy(&ret, buf, sizeof ret);
}
return ret;
}
uint32_t get_option_leasetime(const struct dhcpmsg *packet)
{
ssize_t ol;
uint32_t ret = 0;
uint8_t buf[MAX_DOPT_SIZE];
ol = get_dhcp_opt(packet, DCODE_LEASET, buf, sizeof buf);
if (ol == sizeof ret) {
memcpy(&ret, buf, sizeof ret);
ret = ntohl(ret);
}
return ret;
}
// Returned buffer is not nul-terminated.
size_t get_option_clientid(const struct dhcpmsg *packet, const char *cbuf,
size_t clen)
{
if (clen < 1)
return 0;
ssize_t ol = get_dhcp_opt(packet, DCODE_CLIENT_ID,
(uint8_t *)cbuf, clen);
return ol > 0 ? ol : 0;
}

91
src/options.h Normal file
View File

@@ -0,0 +1,91 @@
/* options.h - DHCP options handling
*
* Copyright (c) 2004-2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - 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.
*/
#ifndef OPTIONS_H_
#define OPTIONS_H_
#include "dhcp.h"
#define DCODE_PADDING 0x00
#define DCODE_SUBNET 0x01
#define DCODE_TIMEZONE 0x02
#define DCODE_ROUTER 0x03
#define DCODE_DNS 0x06
#define DCODE_LPRSVR 0x09
#define DCODE_HOSTNAME 0x0c
#define DCODE_DOMAIN 0x0f
#define DCODE_IPTTL 0x17
#define DCODE_MTU 0x1a
#define DCODE_BROADCAST 0x1c
#define DCODE_NTPSVR 0x2a
#define DCODE_WINS 0x2c
#define DCODE_REQIP 0x32
#define DCODE_LEASET 0x33
#define DCODE_OVERLOAD 0x34
#define DCODE_MSGTYPE 0x35
#define DCODE_SERVER_ID 0x36
#define DCODE_PARAM_REQ 0x37
#define DCODE_MAX_SIZE 0x39
#define DCODE_VENDOR 0x3c
#define DCODE_CLIENT_ID 0x3d
#define DCODE_END 0xff
#define MAX_DOPT_SIZE 500
ssize_t get_dhcp_opt(const struct dhcpmsg *packet, uint8_t code, uint8_t *dbuf,
ssize_t dlen);
ssize_t get_end_option_idx(const struct dhcpmsg *packet);
size_t add_option_string(struct dhcpmsg *packet, uint8_t code, const char *str,
size_t slen);
size_t add_u32_option(struct dhcpmsg *packet, uint8_t code, uint32_t data);
size_t add_option_request_list(struct dhcpmsg *packet);
#ifdef NDHS_BUILD
size_t add_option_domain_name(struct dhcpmsg *packet, const char *dom,
size_t domlen);
void add_option_subnet_mask(struct dhcpmsg *packet, uint32_t subnet);
void add_option_broadcast(struct dhcpmsg *packet, uint32_t bc);
#endif
void add_option_msgtype(struct dhcpmsg *packet, uint8_t type);
void add_option_reqip(struct dhcpmsg *packet, uint32_t ip);
void add_option_serverid(struct dhcpmsg *packet, uint32_t sid);
void add_option_clientid(struct dhcpmsg *packet, const char *clientid,
size_t clen);
#ifndef NDHS_BUILD
void add_option_maxsize(struct dhcpmsg *packet);
void add_option_vendor(struct dhcpmsg *packet);
void add_option_hostname(struct dhcpmsg *packet);
#endif
uint32_t get_option_router(const struct dhcpmsg *packet);
uint8_t get_option_msgtype(const struct dhcpmsg *packet);
uint32_t get_option_serverid(const struct dhcpmsg *packet, int *found);
uint32_t get_option_leasetime(const struct dhcpmsg *packet);
size_t get_option_clientid(const struct dhcpmsg *packet, const char *cbuf,
size_t clen);
#endif

221
src/seccomp.c Normal file
View File

@@ -0,0 +1,221 @@
/* seccomp.h - seccomp syscall filters for ndhc
*
* Copyright (c) 2012-2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - 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.
*/
#include <stdbool.h>
#include "seccomp.h"
#include "nk/log.h"
#include "nk/seccomp-bpf.h"
bool seccomp_enforce = false;
int enforce_seccomp_ndhc(void)
{
#ifdef ENABLE_SECCOMP_FILTER
if (!seccomp_enforce)
return 0;
struct sock_filter filter[] = {
VALIDATE_ARCHITECTURE,
EXAMINE_SYSCALL,
ALLOW_SYSCALL(epoll_wait),
ALLOW_SYSCALL(epoll_ctl),
ALLOW_SYSCALL(read),
ALLOW_SYSCALL(write),
ALLOW_SYSCALL(close),
#if defined(__x86_64__) || (defined(__arm__) && defined(__ARM_EABI__))
ALLOW_SYSCALL(sendto), // used for glibc syslog routines
ALLOW_SYSCALL(recvmsg),
ALLOW_SYSCALL(connect),
ALLOW_SYSCALL(socketpair),
#elif defined(__i386__)
ALLOW_SYSCALL(socketcall),
#else
#error Target platform does not support seccomp-filter.
#endif
ALLOW_SYSCALL(open),
// Allowed by vDSO
ALLOW_SYSCALL(getcpu),
ALLOW_SYSCALL(time),
ALLOW_SYSCALL(gettimeofday),
ALLOW_SYSCALL(clock_gettime),
// These are for 'write_leasefile()'
ALLOW_SYSCALL(ftruncate),
ALLOW_SYSCALL(lseek),
ALLOW_SYSCALL(fsync),
// These are for 'background()'
ALLOW_SYSCALL(clone),
ALLOW_SYSCALL(set_robust_list),
ALLOW_SYSCALL(setsid),
ALLOW_SYSCALL(chdir),
ALLOW_SYSCALL(fstat),
ALLOW_SYSCALL(dup2),
ALLOW_SYSCALL(rt_sigprocmask),
ALLOW_SYSCALL(signalfd4),
ALLOW_SYSCALL(mmap),
ALLOW_SYSCALL(munmap),
ALLOW_SYSCALL(rt_sigreturn),
#ifdef __NR_sigreturn
ALLOW_SYSCALL(sigreturn),
#endif
ALLOW_SYSCALL(exit_group),
ALLOW_SYSCALL(exit),
KILL_PROCESS,
};
struct sock_fprog prog = {
.len = (unsigned short)(sizeof filter / sizeof filter[0]),
.filter = filter,
};
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0))
return -1;
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog))
return -1;
log_line("ndhc seccomp filter installed. Please disable seccomp if you encounter problems.");
#endif
return 0;
}
int enforce_seccomp_ifch(void)
{
#ifdef ENABLE_SECCOMP_FILTER
if (!seccomp_enforce)
return 0;
struct sock_filter filter[] = {
VALIDATE_ARCHITECTURE,
EXAMINE_SYSCALL,
ALLOW_SYSCALL(read),
ALLOW_SYSCALL(write),
ALLOW_SYSCALL(epoll_wait),
ALLOW_SYSCALL(epoll_ctl),
ALLOW_SYSCALL(close),
#if defined(__x86_64__) || (defined(__arm__) && defined(__ARM_EABI__))
ALLOW_SYSCALL(sendto), // used for glibc syslog routines
ALLOW_SYSCALL(recvmsg),
ALLOW_SYSCALL(socket),
ALLOW_SYSCALL(socketpair),
#elif defined(__i386__)
ALLOW_SYSCALL(socketcall),
#else
#error Target platform does not support seccomp-filter.
#endif
ALLOW_SYSCALL(open),
ALLOW_SYSCALL(fstat),
ALLOW_SYSCALL(fsync),
ALLOW_SYSCALL(lseek),
ALLOW_SYSCALL(truncate),
ALLOW_SYSCALL(rt_sigreturn),
#ifdef __NR_sigreturn
ALLOW_SYSCALL(sigreturn),
#endif
// Allowed by vDSO
ALLOW_SYSCALL(getcpu),
ALLOW_SYSCALL(time),
ALLOW_SYSCALL(gettimeofday),
ALLOW_SYSCALL(clock_gettime),
ALLOW_SYSCALL(exit_group),
ALLOW_SYSCALL(exit),
KILL_PROCESS,
};
struct sock_fprog prog = {
.len = (unsigned short)(sizeof filter / sizeof filter[0]),
.filter = filter,
};
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0))
return -1;
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog))
return -1;
log_line("ndhc-ifch seccomp filter installed. Please disable seccomp if you encounter problems.");
#endif
return 0;
}
int enforce_seccomp_sockd(void)
{
#ifdef ENABLE_SECCOMP_FILTER
if (!seccomp_enforce)
return 0;
struct sock_filter filter[] = {
VALIDATE_ARCHITECTURE,
EXAMINE_SYSCALL,
ALLOW_SYSCALL(epoll_wait),
ALLOW_SYSCALL(epoll_ctl),
ALLOW_SYSCALL(read),
ALLOW_SYSCALL(write),
ALLOW_SYSCALL(close),
#if defined(__x86_64__) || (defined(__arm__) && defined(__ARM_EABI__))
ALLOW_SYSCALL(sendto), // used for glibc syslog routines
ALLOW_SYSCALL(recvmsg),
ALLOW_SYSCALL(socket),
ALLOW_SYSCALL(setsockopt),
ALLOW_SYSCALL(bind),
ALLOW_SYSCALL(socketpair),
#elif defined(__i386__)
ALLOW_SYSCALL(socketcall),
ALLOW_SYSCALL(fcntl64),
#else
#error Target platform does not support seccomp-filter.
#endif
ALLOW_SYSCALL(fcntl),
ALLOW_SYSCALL(open),
// Allowed by vDSO
ALLOW_SYSCALL(getcpu),
ALLOW_SYSCALL(time),
ALLOW_SYSCALL(gettimeofday),
ALLOW_SYSCALL(clock_gettime),
ALLOW_SYSCALL(rt_sigreturn),
#ifdef __NR_sigreturn
ALLOW_SYSCALL(sigreturn),
#endif
ALLOW_SYSCALL(exit_group),
ALLOW_SYSCALL(exit),
KILL_PROCESS,
};
struct sock_fprog prog = {
.len = (unsigned short)(sizeof filter / sizeof filter[0]),
.filter = filter,
};
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0))
return -1;
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog))
return -1;
log_line("ndhc-sockd seccomp filter installed. Please disable seccomp if you encounter problems.");
#endif
return 0;
}

39
src/seccomp.h Normal file
View File

@@ -0,0 +1,39 @@
/* seccomp.h - seccomp syscall filters for ndhc
*
* Copyright (c) 2012-2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - 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.
*/
#ifndef NJK_NDHC_SECCOMP_H_
#define NJK_NDHC_SECCOMP_H_
#include <stdbool.h>
extern bool seccomp_enforce;
int enforce_seccomp_ndhc(void);
int enforce_seccomp_ifch(void);
int enforce_seccomp_sockd(void);
#endif /* NJK_NDHC_SECCOMP_H_ */

633
src/sockd.c Normal file
View File

@@ -0,0 +1,633 @@
/* sockd.c - privsep socket creation daemon
*
* Copyright (c) 2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - 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.
*/
#include <stdbool.h>
#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <assert.h>
#include <sys/socket.h>
#include <sys/signalfd.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/prctl.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <netpacket/packet.h>
#include <net/ethernet.h>
#include <netinet/if_ether.h>
#include <linux/filter.h>
#include <pwd.h>
#include <grp.h>
#include "nk/log.h"
#include "nk/io.h"
#include "nk/privilege.h"
#include "sockd.h"
#include "ndhc-defines.h"
#include "ndhc.h"
#include "dhcp.h"
#include "sys.h"
#include "seccomp.h"
static int epollfd, signalFd;
/* Slots are for signalFd and the ndhc -> ifchd pipe. */
static struct epoll_event events[2];
uid_t sockd_uid = 0;
gid_t sockd_gid = 0;
// Interface to make requests of sockd. Called from ndhc process.
int request_sockd_fd(char *buf, size_t buflen, char *response)
{
if (!buflen)
return -1;
ssize_t r = safe_write(sockdSock[0], buf, buflen);
if (r < 0 || (size_t)r != buflen)
suicide("%s: (%s) write failed: %d", client_config.interface,
__func__, r);
char data[MAX_BUF], control[MAX_BUF];
struct iovec iov = {
.iov_base = data,
.iov_len = sizeof data - 1,
};
struct msghdr msg = {
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = control,
.msg_controllen = sizeof control
};
retry:
r = recvmsg(sockdSock[0], &msg, 0);
if (r == 0) {
suicide("%s: (%s) recvmsg received EOF", client_config.interface,
__func__);
} else if (r < 0) {
if (errno == EINTR)
goto retry;
suicide("%s: (%s) recvmsg failed: %s", client_config.interface,
__func__, strerror(errno));
}
data[iov.iov_len] = '\0';
char repc = data[0];
for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); cmsg;
cmsg = CMSG_NXTHDR(&msg, cmsg)) {
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
if (response)
*response = repc;
else if (repc != buf[0])
suicide("%s: (%s) expected %c sockd reply but got %c",
client_config.interface, __func__, buf[0], repc);
int *fd = (int *)CMSG_DATA(cmsg);
return *fd;
}
}
suicide("%s: (%s) sockd reply did not include a fd",
client_config.interface, __func__);
}
static int create_arp_socket(void)
{
int fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ARP));
if (fd < 0) {
log_error("%s: (%s) socket failed: %s", client_config.interface,
__func__, strerror(errno));
goto out;
}
int opt = 1;
if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof opt) < 0) {
log_error("%s: (%s) setsockopt failed: %s", client_config.interface,
__func__, strerror(errno));
goto out_fd;
}
if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK) < 0) {
log_error("%s: (%s) fcntl failed: %s", client_config.interface,
__func__, strerror(errno));
goto out_fd;
}
struct sockaddr_ll saddr = {
.sll_family = AF_PACKET,
.sll_protocol = htons(ETH_P_ARP),
.sll_ifindex = client_config.ifindex,
};
if (bind(fd, (struct sockaddr *)&saddr, sizeof(struct sockaddr_ll)) < 0) {
log_error("%s: (%s) bind failed: %s", client_config.interface,
__func__, strerror(errno));
goto out_fd;
}
return fd;
out_fd:
close(fd);
out:
return -1;
}
// 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)
{
int fd;
if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
log_error("%s: (%s) socket failed: %s",
client_config.interface, __func__, strerror(errno));
goto out;
}
int opt = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt) < 0) {
log_error("%s: (%s) Set reuse addr failed: %s",
client_config.interface, __func__, strerror(errno));
goto out_fd;
}
if (setsockopt(fd, SOL_SOCKET, SO_DONTROUTE, &opt, sizeof opt) < 0) {
log_error("%s: (%s) Set don't route failed: %s",
client_config.interface, __func__, strerror(errno));
goto out_fd;
}
struct ifreq ifr;
memset(&ifr, 0, sizeof ifr);
ssize_t sl = snprintf(ifr.ifr_name, sizeof ifr.ifr_name, "%s", iface);
if (sl < 0 || (size_t)sl >= sizeof ifr.ifr_name) {
log_error("%s: (%s) Set interface name failed.",
client_config.interface, __func__);
goto out_fd;
}
if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof ifr) < 0) {
log_error("%s: (%s) Set bind to device failed: %s",
client_config.interface, __func__, strerror(errno));
goto out_fd;
}
if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK) < 0) {
log_error("%s: (%s) Set non-blocking failed: %s",
client_config.interface, __func__, strerror(errno));
goto out_fd;
}
struct sockaddr_in sa = {
.sin_family = AF_INET,
.sin_port = htons(port),
.sin_addr.s_addr = ip,
};
if (bind(fd, (struct sockaddr *)&sa, sizeof sa) < 0)
goto out_fd;
return fd;
out_fd:
close(fd);
out:
return -1;
}
static int create_raw_socket(struct sockaddr_ll *sa, bool *using_bpf,
const struct sock_fprog *filter_prog)
{
int fd;
if ((fd = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_IP))) < 0) {
log_error("create_raw_socket: socket failed: %s", strerror(errno));
goto out;
}
if (filter_prog) {
int r = setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, filter_prog,
sizeof *filter_prog);
if (using_bpf)
*using_bpf = !r;
}
int opt = 1;
if (setsockopt(fd, SOL_SOCKET, SO_DONTROUTE, &opt, sizeof opt) < 0) {
log_error("create_raw_socket: Failed to set don't route: %s",
strerror(errno));
goto out_fd;
}
if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK) < 0) {
log_error("create_raw_socket: Set non-blocking failed: %s",
strerror(errno));
goto out_fd;
}
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;
}
// Returns fd of new listen socket bound to 0.0.0.0:@68 on interface @inf
// on success, or -1 on failure.
static int create_udp_listen_socket(void)
{
int fd = create_udp_socket(INADDR_ANY, DHCP_CLIENT_PORT,
client_config.interface);
if (fd < 0)
return -1;
int opt = 1;
if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof opt) < 0) {
log_error("%s: (%s) Set broadcast failed: %s",
client_config.interface, __func__, strerror(errno));
close(fd);
return -1;
}
return fd;
}
static int create_udp_send_socket(uint32_t client_addr)
{
return create_udp_socket(client_addr, DHCP_CLIENT_PORT,
client_config.interface);
}
static int create_raw_listen_socket(bool *using_bpf)
{
static const struct sock_filter sf_dhcp[] = {
// Verify that the packet has a valid IPv4 version nibble and
// that no IP options are defined.
BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 0),
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x45, 1, 0),
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),
// Make certain that the packet is not a fragment. All bits in
// the flag and fragment offset field must be set to zero except
// for the Evil and DF bits (0,1).
BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 6),
BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x3fff, 0, 1),
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),
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K,
(DHCP_SERVER_PORT << 16) + DHCP_CLIENT_PORT, 1, 0),
BPF_STMT(BPF_RET + BPF_K, 0),
// 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].
BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 2),
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),
BPF_STMT(BPF_RET + BPF_A, 0),
};
static const struct sock_fprog sfp_dhcp = {
.len = sizeof sf_dhcp / sizeof sf_dhcp[0],
.filter = (struct sock_filter *)sf_dhcp,
};
struct sockaddr_ll sa = {
.sll_family = AF_PACKET,
.sll_protocol = htons(ETH_P_IP),
.sll_ifindex = client_config.ifindex,
};
return create_raw_socket(&sa, using_bpf, &sfp_dhcp);
}
static int create_raw_broadcast_socket(void)
{
struct sockaddr_ll da = {
.sll_family = AF_PACKET,
.sll_protocol = htons(ETH_P_IP),
.sll_pkttype = PACKET_BROADCAST,
.sll_ifindex = client_config.ifindex,
.sll_halen = 6,
};
memcpy(da.sll_addr, "\xff\xff\xff\xff\xff\xff", 6);
return create_raw_socket(&da, NULL, NULL);
}
static bool arp_set_bpf_basic(int fd)
{
static struct sock_filter sf_arp[] = {
// Verify that the frame has ethernet protocol type of ARP
// and that the ARP hardware type field indicates Ethernet.
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 12),
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (ETH_P_ARP << 16) | ARPHRD_ETHER,
1, 0),
BPF_STMT(BPF_RET + BPF_K, 0),
// Verify that the ARP protocol type field indicates IP, the ARP
// hardware address length field is 6, and the ARP protocol address
// length field is 4.
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 16),
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (ETH_P_IP << 16) | 0x0604, 1, 0),
BPF_STMT(BPF_RET + BPF_K, 0),
// Sanity tests passed, so send all possible data.
BPF_STMT(BPF_RET + BPF_K, 0x7fffffff),
};
static const struct sock_fprog sfp_arp = {
.len = sizeof sf_arp / sizeof sf_arp[0],
.filter = sf_arp,
};
int ret = setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &sfp_arp,
sizeof sfp_arp) != -1;
if (ret < 0)
log_warning("%s: Failed to set BPF for basic ARP socket: %s",
client_config.interface, strerror(errno));
return ret == 0;
}
static bool arp_set_bpf_defense(int fd, uint32_t client_addr,
uint8_t client_mac[6])
{
uint32_t mac4b;
uint16_t mac2b;
memcpy(&mac4b, client_mac, 4);
memcpy(&mac2b, client_mac + 4, 2);
struct sock_filter sf_arp[] = {
// Verify that the frame has ethernet protocol type of ARP
// and that the ARP hardware type field indicates Ethernet.
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 12),
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (ETH_P_ARP << 16) | ARPHRD_ETHER,
1, 0),
BPF_STMT(BPF_RET + BPF_K, 0),
// Verify that the ARP protocol type field indicates IP, the ARP
// hardware address length field is 6, and the ARP protocol address
// length field is 4.
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 16),
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (ETH_P_IP << 16) | 0x0604, 1, 0),
BPF_STMT(BPF_RET + BPF_K, 0),
// If the ARP packet source IP does not match our IP address, then
// it can be ignored.
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 28),
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, client_addr, 1, 0),
BPF_STMT(BPF_RET + BPF_K, 0),
// If the first four bytes of the ARP packet source hardware address
// does not equal our hardware address, then it's a conflict and should
// be passed along.
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 22),
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, mac4b, 1, 0),
BPF_STMT(BPF_RET + BPF_K, 0x7fffffff),
// If the last two bytes of the ARP packet source hardware address
// do not equal our hardware address, then it's a conflict and should
// be passed along.
BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 26),
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, mac2b, 1, 0),
BPF_STMT(BPF_RET + BPF_K, 0x7fffffff),
// Packet announces our IP address and hardware address, so it requires
// no action.
BPF_STMT(BPF_RET + BPF_K, 0),
};
struct sock_fprog sfp_arp = {
.len = sizeof sf_arp / sizeof sf_arp[0],
.filter = (struct sock_filter *)sf_arp,
};
int ret = setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &sfp_arp,
sizeof sfp_arp) != -1;
if (ret < 0)
log_warning("%s: Failed to set BPF for defense ARP socket: %s",
client_config.interface, strerror(errno));
return ret == 0;
}
static int create_arp_defense_socket(uint32_t client_addr,
uint8_t client_mac[6], bool *using_bpf)
{
assert(using_bpf);
int fd = create_arp_socket();
*using_bpf = arp_set_bpf_defense(fd, client_addr, client_mac);
return fd;
}
static int create_arp_basic_socket(bool *using_bpf)
{
assert(using_bpf);
int fd = create_arp_socket();
*using_bpf = arp_set_bpf_basic(fd);
return fd;
}
// XXX: Can share with ifch
static void setup_signals_sockd(void)
{
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGPIPE);
sigaddset(&mask, SIGUSR1);
sigaddset(&mask, SIGUSR2);
sigaddset(&mask, SIGTSTP);
sigaddset(&mask, SIGTTIN);
sigaddset(&mask, SIGCHLD);
sigaddset(&mask, SIGHUP);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGTERM);
if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0)
suicide("sigprocmask failed");
signalFd = signalfd(-1, &mask, SFD_NONBLOCK);
if (signalFd < 0)
suicide("signalfd failed");
}
// XXX: Can share with ifch
static void signal_dispatch(void)
{
int t;
size_t off = 0;
struct signalfd_siginfo si = {0};
again:
t = read(signalFd, (char *)&si + off, sizeof si - off);
if (t < 0) {
if (t == EAGAIN || t == EWOULDBLOCK || t == EINTR)
goto again;
else
suicide("signalfd read error");
}
if (off + (unsigned)t < sizeof si)
off += t;
switch (si.ssi_signo) {
case SIGINT:
case SIGTERM:
exit(EXIT_SUCCESS);
break;
case SIGPIPE:
log_line("ndhc-sockd: IPC pipe closed. Exiting.");
exit(EXIT_SUCCESS);
break;
default:
break;
}
}
static void xfer_fd(int fd, char cmd)
{
char control[sizeof(struct cmsghdr) + 10];
struct iovec iov = {
.iov_base = &cmd,
.iov_len = 1,
};
struct msghdr msg = {
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = control,
.msg_controllen = sizeof control,
};
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof fd);
int *cmsg_fd = (int *)CMSG_DATA(cmsg);
*cmsg_fd = fd;
msg.msg_controllen = cmsg->cmsg_len;
retry:
if (sendmsg(sockdSock[1], &msg, 0) < 0) {
if (errno == EINTR)
goto retry;
suicide("%s: (%s) sendmsg failed: %s", client_config.interface,
__func__, strerror(errno));
}
}
static size_t execute_sockd(char *buf, size_t buflen)
{
if (!buflen)
return 0;
char c = buf[0];
switch (c) {
case 'L': {
bool using_bpf;
int fd = create_raw_listen_socket(&using_bpf);
xfer_fd(fd, using_bpf ? 'L' : 'l');
return 1;
}
case 'U': xfer_fd(create_udp_listen_socket(), 'U'); return 1;
case 'a': {
bool using_bpf;
int fd = create_arp_basic_socket(&using_bpf);
xfer_fd(fd, using_bpf ? 'A' : 'a'); return 1;
}
case 'd': {
uint32_t client_addr;
uint8_t client_mac[6];
bool using_bpf;
if (buflen < 1 + sizeof client_addr + 6)
return 0;
memcpy(&client_addr, buf + 1, sizeof client_addr);
memcpy(client_mac, buf + 1 + sizeof client_addr, 6);
int fd = create_arp_defense_socket(client_addr, client_mac,
&using_bpf);
xfer_fd(fd, using_bpf ? 'D' : 'd');
return 11;
}
case 's': xfer_fd(create_raw_broadcast_socket(), 's'); return 1;
case 'u': {
uint32_t client_addr;
if (buflen < 1 + sizeof client_addr)
return 0;
memcpy(&client_addr, buf + 1, sizeof client_addr);
xfer_fd(create_udp_send_socket(client_addr), 'u');
return 5;
}
default: suicide("%s: (%s) received invalid commands: '%c'",
client_config.interface, __func__, c);
}
}
static void process_client_pipe(void)
{
static char buf[MAX_BUF];
static size_t buflen;
if (buflen == MAX_BUF)
suicide("%s: (%s) receive buffer exhausted", client_config.interface,
__func__);
int r = safe_recv(sockdSock[1], buf + buflen, sizeof buf - buflen,
MSG_DONTWAIT);
if (r == 0) {
// Remote end hung up.
exit(EXIT_SUCCESS);
} else if (r < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK)
return;
suicide("%s: (%s) error reading from ndhc -> sockd pipe: %s",
client_config.interface, __func__, strerror(errno));
}
buflen += (size_t)r;
buflen -= execute_sockd(buf, buflen);
}
static void do_sockd_work(void)
{
epollfd = epoll_create1(0);
if (epollfd < 0)
suicide("epoll_create1 failed");
if (enforce_seccomp_sockd())
log_line("sockd seccomp filter cannot be installed");
epoll_add(epollfd, sockdSock[1]);
epoll_add(epollfd, signalFd);
for (;;) {
int r = epoll_wait(epollfd, events, 2, -1);
if (r < 0) {
if (errno == EINTR)
continue;
else
suicide("epoll_wait failed");
}
for (int i = 0; i < r; ++i) {
int fd = events[i].data.fd;
if (fd == sockdSock[1])
process_client_pipe();
else if (fd == signalFd)
signal_dispatch();
else
suicide("sockd: unexpected fd while performing epoll");
}
}
}
void sockd_main(void)
{
prctl(PR_SET_NAME, "ndhc: sockd");
umask(077);
setup_signals_sockd();
nk_set_chroot(chroot_dir);
memset(chroot_dir, 0, sizeof chroot_dir);
nk_set_uidgid(sockd_uid, sockd_gid,
"cap_net_bind_service,cap_net_broadcast,cap_net_raw=ep");
do_sockd_work();
}

9
src/sockd.h Normal file
View File

@@ -0,0 +1,9 @@
#ifndef NDHC_SOCKD_H_
#define NDHC_SOCKD_H_
extern uid_t sockd_uid;
extern gid_t sockd_gid;
int request_sockd_fd(char *buf, size_t buflen, char *response);
void sockd_main(void);
#endif /* NDHC_SOCKD_H_ */

409
src/state.c Normal file
View File

@@ -0,0 +1,409 @@
/* state.c - high level DHCP state machine
*
* Copyright (c) 2011-2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - 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.
*/
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "nk/log.h"
#include "nk/random.h"
#include "state.h"
#include "ifchange.h"
#include "arp.h"
#include "options.h"
#include "ndhc.h"
#include "sys.h"
static void selecting_packet(struct client_state_t *cs, struct dhcpmsg *packet,
uint8_t msgtype);
static void an_packet(struct client_state_t *cs, struct dhcpmsg *packet,
uint8_t msgtype);
static void selecting_timeout(struct client_state_t *cs, long long nowts);
static void requesting_timeout(struct client_state_t *cs, long long nowts);
static void bound_timeout(struct client_state_t *cs, long long nowts);
static void renewing_timeout(struct client_state_t *cs, long long nowts);
static void rebinding_timeout(struct client_state_t *cs, long long nowts);
static void released_timeout(struct client_state_t *cs, long long nowts);
static void xmit_release(struct client_state_t *cs);
static void print_release(struct client_state_t *cs);
static void frenew(struct client_state_t *cs);
typedef struct {
void (*packet_fn)(struct client_state_t *cs, struct dhcpmsg *packet,
uint8_t msgtype);
void (*timeout_fn)(struct client_state_t *cs, long long nowts);
void (*force_renew_fn)(struct client_state_t *cs);
void (*force_release_fn)(struct client_state_t *cs);
} dhcp_state_t;
static const dhcp_state_t dhcp_states[] = {
{ selecting_packet, selecting_timeout, 0, print_release}, // SELECTING
{ an_packet, requesting_timeout, 0, print_release}, // REQUESTING
{ 0, bound_timeout, frenew, xmit_release}, // BOUND
{ an_packet, renewing_timeout, 0, xmit_release}, // RENEWING
{ an_packet, rebinding_timeout, 0, xmit_release}, // REBINDING
{ 0, 0, 0, xmit_release}, // BOUND_GW_CHECK
{ 0, 0, 0, xmit_release}, // COLLISION_CHECK
{ 0, released_timeout, frenew, 0}, // RELEASED
{ 0, 0, 0, 0}, // NUM_STATES
};
static unsigned int num_dhcp_requests;
static long long dhcp_wake_ts = -1;
static int delay_timeout(struct client_state_t *cs, size_t numpackets)
{
int to = 64;
char tot[] = { 4, 8, 16, 32, 64 };
if (numpackets < sizeof tot)
to = tot[numpackets];
// Distribution is a bit biased but it doesn't really matter.
return to * 1000 + (nk_random_u32(&cs->rnd32_state) & 0x7fffffffu) % 1000;
}
static void reinit_shared_deconfig(struct client_state_t *cs)
{
ifchange_deconfig(cs);
arp_close_fd(cs);
cs->clientAddr = 0;
num_dhcp_requests = 0;
cs->got_router_arp = 0;
cs->got_server_arp = 0;
memset(&cs->routerArp, 0, sizeof cs->routerArp);
memset(&cs->serverArp, 0, sizeof cs->serverArp);
arp_reset_send_stats();
}
void reinit_selecting(struct client_state_t *cs, int timeout)
{
reinit_shared_deconfig(cs);
cs->dhcpState = DS_SELECTING;
dhcp_wake_ts = curms() + timeout;
set_listen_raw(cs);
}
static void set_released(struct client_state_t *cs)
{
reinit_shared_deconfig(cs);
cs->dhcpState = DS_RELEASED;
dhcp_wake_ts = -1;
set_listen_none(cs);
}
// Triggered after a DHCP lease request packet has been sent and no reply has
// been received within the response wait time. If we've not exceeded the
// maximum number of request retransmits, then send another packet and wait
// again. Otherwise, return to the DHCP initialization state.
static void requesting_timeout(struct client_state_t *cs, long long nowts)
{
if (num_dhcp_requests < 5) {
send_selecting(cs);
dhcp_wake_ts = nowts + delay_timeout(cs, num_dhcp_requests);
num_dhcp_requests++;
} else
reinit_selecting(cs, 0);
}
// Triggered when the lease has been held for a significant fraction of its
// total time, and it is time to renew the lease so that it is not lost.
static void bound_timeout(struct client_state_t *cs, long long nowts)
{
long long rnt = cs->leaseStartTime + cs->renewTime * 1000;
if (nowts < rnt) {
dhcp_wake_ts = rnt;
return;
}
cs->dhcpState = DS_RENEWING;
set_listen_cooked(cs);
renewing_timeout(cs, nowts);
}
// Triggered when a DHCP renew request has been sent and no reply has been
// received within the response wait time. This function is also directly
// called by bound_timeout() when it is time to renew a lease before it
// expires. Check to see if the lease is still valid, and if it is, send
// a unicast DHCP renew packet. If it is not, then change to the REBINDING
// state to send broadcast queries.
static void renewing_timeout(struct client_state_t *cs, long long nowts)
{
long long rbt = cs->leaseStartTime + cs->rebindTime * 1000;
if (nowts < rbt) {
if (rbt - nowts < 30000) {
dhcp_wake_ts = rbt;
return;
}
send_renew(cs);
dhcp_wake_ts = nowts + ((rbt - nowts) / 2);
} else {
cs->dhcpState = DS_REBINDING;
rebinding_timeout(cs, nowts);
}
}
// Triggered when a DHCP rebind request has been sent and no reply has been
// received within the response wait time. Check to see if the lease is still
// valid, and if it is, send a broadcast DHCP renew packet. If it is not, then
// change to the SELECTING state to get a new lease.
static void rebinding_timeout(struct client_state_t *cs, long long nowts)
{
long long elt = cs->leaseStartTime + cs->lease * 1000;
if (nowts < elt) {
if (elt - nowts < 30000) {
dhcp_wake_ts = elt;
return;
}
send_rebind(cs);
dhcp_wake_ts = nowts + ((elt - nowts) / 2);
} else {
log_line("Lease expired. Searching for a new lease...");
reinit_selecting(cs, 0);
}
}
static void released_timeout(struct client_state_t *cs, long long nowts)
{
(void)cs;
(void)nowts;
dhcp_wake_ts = -1;
}
static int validate_serverid(struct client_state_t *cs, struct dhcpmsg *packet,
char *typemsg)
{
int found;
uint32_t sid = get_option_serverid(packet, &found);
if (!found) {
log_line("Received %s with no server id. Ignoring it.");
return 0;
}
if (cs->serverAddr != sid) {
char svrbuf[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &(struct in_addr){.s_addr=sid},
svrbuf, sizeof svrbuf);
log_line("Received %s with an unexpected server id: %s. Ignoring it.",
typemsg, svrbuf);
return 0;
}
return 1;
}
// Can transition to DS_BOUND or DS_SELECTING.
static void an_packet(struct client_state_t *cs, struct dhcpmsg *packet,
uint8_t msgtype)
{
if (msgtype == DHCPACK) {
if (!validate_serverid(cs, packet, "a DHCP ACK"))
return;
cs->lease = get_option_leasetime(packet);
cs->leaseStartTime = curms();
if (!cs->lease) {
log_line("No lease time received, assuming 1h.");
cs->lease = 60 * 60;
} else {
if (cs->lease < 60) {
log_warning("Server sent lease of <1m. Forcing lease to 1m.");
cs->lease = 60;
}
}
// Always use RFC2131 'default' values. It's not worth validating
// the remote server values, if they even exist, for sanity.
cs->renewTime = cs->lease >> 1;
cs->rebindTime = (cs->lease >> 3) * 0x7; // * 0.875
dhcp_wake_ts = cs->leaseStartTime + cs->renewTime * 1000;
// Only check if we are either in the REQUESTING state, or if we
// have received a lease with a different IP than what we had before.
if (cs->dhcpState == DS_REQUESTING ||
memcmp(&packet->yiaddr, &cs->clientAddr, 4)) {
char clibuf[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &(struct in_addr){.s_addr=cs->clientAddr},
clibuf, sizeof clibuf);
log_line("Accepted a firm offer for %s. Validating...", clibuf);
if (arp_check(cs, packet) < 0) {
log_warning("Failed to make arp socket. Searching for new lease...");
reinit_selecting(cs, 3000);
}
} else {
log_line("Lease refreshed to %u seconds.", cs->lease);
cs->dhcpState = DS_BOUND;
arp_set_defense_mode(cs);
set_listen_none(cs);
}
} else if (msgtype == DHCPNAK) {
if (!validate_serverid(cs, packet, "a DHCP NAK"))
return;
log_line("Our request was rejected. Searching for a new lease...");
reinit_selecting(cs, 3000);
}
}
static void selecting_packet(struct client_state_t *cs, struct dhcpmsg *packet,
uint8_t msgtype)
{
if (msgtype == DHCPOFFER) {
int found;
uint32_t sid = get_option_serverid(packet, &found);
if (found) {
char clibuf[INET_ADDRSTRLEN];
char svrbuf[INET_ADDRSTRLEN];
cs->serverAddr = sid;
cs->xid = packet->xid;
cs->clientAddr = packet->yiaddr;
cs->dhcpState = DS_REQUESTING;
dhcp_wake_ts = curms();
num_dhcp_requests = 0;
inet_ntop(AF_INET, &(struct in_addr){.s_addr=cs->clientAddr},
clibuf, sizeof clibuf);
inet_ntop(AF_INET, &(struct in_addr){.s_addr=cs->serverAddr},
svrbuf, sizeof svrbuf);
log_line("Received an offer of %s from server %s.",
clibuf, svrbuf);
} else {
log_line("Invalid offer received: it didn't have a server id.");
}
}
}
// Triggered after a DHCP discover packet has been sent and no reply has
// been received within the response wait time. If we've not exceeded the
// maximum number of discover retransmits, then send another packet and wait
// again. Otherwise, background or fail.
static void selecting_timeout(struct client_state_t *cs, long long nowts)
{
if (cs->init && num_dhcp_requests >= 2) {
if (client_config.background_if_no_lease) {
log_line("No lease, going to background.");
cs->init = 0;
background();
} else if (client_config.abort_if_no_lease)
suicide("No lease, failing.");
}
if (num_dhcp_requests == 0)
cs->xid = nk_random_u32(&cs->rnd32_state);
send_discover(cs);
dhcp_wake_ts = nowts + delay_timeout(cs, num_dhcp_requests);
num_dhcp_requests++;
}
static void xmit_release(struct client_state_t *cs)
{
char clibuf[INET_ADDRSTRLEN];
char svrbuf[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &(struct in_addr){.s_addr=cs->clientAddr},
clibuf, sizeof clibuf);
inet_ntop(AF_INET, &(struct in_addr){.s_addr=cs->serverAddr},
svrbuf, sizeof svrbuf);
log_line("Unicasting a release of %s to %s.", clibuf, svrbuf);
send_release(cs);
print_release(cs);
}
static void print_release(struct client_state_t *cs)
{
log_line("ndhc going to sleep. Wake it by sending a SIGUSR1.");
set_released(cs);
}
static void frenew(struct client_state_t *cs)
{
if (cs->dhcpState == DS_BOUND) {
log_line("Forcing a DHCP renew...");
cs->dhcpState = DS_RENEWING;
set_listen_cooked(cs);
send_renew(cs);
} else if (cs->dhcpState == DS_RELEASED)
reinit_selecting(cs, 0);
}
void ifup_action(struct client_state_t *cs)
{
// If we have a lease, check to see if our gateway is still valid via ARP.
// If it fails, state -> SELECTING.
if (cs->routerAddr && (cs->dhcpState == DS_BOUND ||
cs->dhcpState == DS_RENEWING ||
cs->dhcpState == DS_REBINDING)) {
if (arp_gw_check(cs) != -1) {
log_line("nl: %s is back. Revalidating lease...",
client_config.interface);
return;
} else
log_warning("nl: arp_gw_check could not make arp socket.");
}
if (cs->dhcpState == DS_SELECTING)
return;
log_line("nl: %s is back. Searching for new lease...",
client_config.interface);
reinit_selecting(cs, 0);
}
void ifdown_action(struct client_state_t *cs)
{
log_line("nl: %s shut down. Going to sleep.", client_config.interface);
set_released(cs);
}
void ifnocarrier_action(struct client_state_t *cs)
{
(void)cs;
log_line("nl: %s carrier down.", client_config.interface);
}
void packet_action(struct client_state_t *cs, struct dhcpmsg *packet,
uint8_t msgtype)
{
if (dhcp_states[cs->dhcpState].packet_fn)
dhcp_states[cs->dhcpState].packet_fn(cs, packet, msgtype);
}
void timeout_action(struct client_state_t *cs, long long nowts)
{
handle_arp_timeout(cs, nowts);
if (dhcp_states[cs->dhcpState].timeout_fn)
dhcp_states[cs->dhcpState].timeout_fn(cs, nowts);
}
void force_renew_action(struct client_state_t *cs)
{
if (dhcp_states[cs->dhcpState].force_renew_fn)
dhcp_states[cs->dhcpState].force_renew_fn(cs);
}
void force_release_action(struct client_state_t *cs)
{
if (dhcp_states[cs->dhcpState].force_release_fn)
dhcp_states[cs->dhcpState].force_release_fn(cs);
}
long long dhcp_get_wake_ts(void)
{
return dhcp_wake_ts;
}

60
src/state.h Normal file
View File

@@ -0,0 +1,60 @@
/* state.h - high level DHCP state machine
*
* Copyright (c) 2011-2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - 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.
*/
#ifndef NDHC_STATE_H_
#define NDHC_STATE_H_
#include "ndhc.h"
#include "dhcp.h"
typedef enum {
DS_SELECTING = 0,
DS_REQUESTING,
DS_BOUND,
DS_RENEWING,
DS_REBINDING,
DS_BOUND_GW_CHECK,
DS_COLLISION_CHECK,
DS_RELEASED,
DS_NUM_STATES,
} dhcp_states_t;
void reinit_selecting(struct client_state_t *cs, int timeout);
void packet_action(struct client_state_t *cs, struct dhcpmsg *packet,
uint8_t msgtype);
void timeout_action(struct client_state_t *cs, long long nowts);
void force_renew_action(struct client_state_t *cs);
void force_release_action(struct client_state_t *cs);
void ifup_action(struct client_state_t *cs);
void ifnocarrier_action(struct client_state_t *cs);
void ifdown_action(struct client_state_t *cs);
long long dhcp_get_wake_ts(void);
#endif

57
src/sys.c Normal file
View File

@@ -0,0 +1,57 @@
/* sys.c - linux-specific signal and epoll functions
*
* Copyright (c) 2010-2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - 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.
*/
#include <string.h>
#include <errno.h>
#include <sys/epoll.h>
#include <sys/signalfd.h>
#include "nk/log.h"
#include "sys.h"
void epoll_add(int epfd, int fd)
{
struct epoll_event ev;
int r;
ev.events = EPOLLIN | EPOLLRDHUP | EPOLLERR | EPOLLHUP;
ev.data.fd = fd;
r = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
if (r < 0)
suicide("epoll_add failed %s", strerror(errno));
}
void epoll_del(int epfd, int fd)
{
struct epoll_event ev;
int r;
ev.events = EPOLLIN | EPOLLRDHUP | EPOLLERR | EPOLLHUP;
ev.data.fd = fd;
r = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &ev);
if (r < 0)
suicide("epoll_del failed %s", strerror(errno));
}

49
src/sys.h Normal file
View File

@@ -0,0 +1,49 @@
/* sys.h - linux-specific signal and epoll functions
*
* Copyright (c) 2010-2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - 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.
*/
#ifndef SYS_H_
#define SYS_H_
#include <time.h>
#include "ndhc-defines.h"
static inline unsigned long long curms()
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec * 1000ULL + ts.tv_nsec / 1000000ULL;
}
static inline size_t min_size_t(size_t a, size_t b)
{
return a < b ? a : b;
}
void epoll_add(int epfd, int fd);
void epoll_del(int epfd, int fd);
#endif /* SYS_H_ */