Move source from ndhc/ to src/ since ifchd is no longer a separate program.
This commit is contained in:
22
src/CMakeLists.txt
Normal file
22
src/CMakeLists.txt
Normal 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
766
src/arp.c
Normal 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
71
src/arp.h
Normal 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
544
src/dhcp.c
Normal 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
95
src/dhcp.h
Normal 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
217
src/duiaid.c
Normal 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
35
src/duiaid.h
Normal 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
314
src/ifchange.c
Normal 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
35
src/ifchange.h
Normal 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
34
src/ifchd-parse.h
Normal 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
235
src/ifchd-parse.rl
Normal 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
406
src/ifchd.c
Normal 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
51
src/ifchd.h
Normal 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
651
src/ifset.c
Normal 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
38
src/ifset.h
Normal 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
106
src/leasefile.c
Normal 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
36
src/leasefile.h
Normal 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
10
src/ndhc-defines.h
Normal 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
155
src/ndhc.8
Normal 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
649
src/ndhc.c
Normal 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
82
src/ndhc.h
Normal 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
194
src/netlink.c
Normal 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
46
src/netlink.h
Normal 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
314
src/nl.c
Normal 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
67
src/nl.h
Normal 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
365
src/options.c
Normal 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
91
src/options.h
Normal 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
221
src/seccomp.c
Normal 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
39
src/seccomp.h
Normal 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
633
src/sockd.c
Normal 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
9
src/sockd.h
Normal 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
409
src/state.c
Normal 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
60
src/state.h
Normal 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
57
src/sys.c
Normal 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
49
src/sys.h
Normal 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_ */
|
Reference in New Issue
Block a user