#include <sys/types.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/syslog.h>
#include <sys/uio.h>
#include <sys/mbuf.h>
#include <net/if.h>
#include <net/route.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <errno.h>
#include <event.h>
#include <imsg.h>
#include <pwd.h>
#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <vis.h>
#include "checksum.h"
#include "log.h"
#include "dhcpleased.h"
#include "engine.h"
#define START_EXP_BACKOFF 1
#define MAX_EXP_BACKOFF_SLOW 64
#define MAX_EXP_BACKOFF_FAST 2
#define MINIMUM(a, b) (((a) < (b)) ? (a) : (b))
#define MIN_V6ONLY_WAIT 300
enum if_state {
IF_DOWN,
IF_INIT,
IF_REQUESTING,
IF_BOUND,
IF_RENEWING,
IF_REBINDING,
IF_REBOOTING,
IF_IPV6_ONLY,
};
const char* if_state_name[] = {
"Down",
"Init",
"Requesting",
"Bound",
"Renewing",
"Rebinding",
"Rebooting",
"IPv6 only",
};
struct dhcpleased_iface {
LIST_ENTRY(dhcpleased_iface) entries;
enum if_state state;
struct event timer;
struct timeval timo;
uint32_t if_index;
char if_name[IF_NAMESIZE];
int rdomain;
int running;
struct ether_addr hw_address;
int link_state;
uint32_t cur_mtu;
uint32_t xid;
struct timespec request_time;
struct in_addr server_identifier;
struct in_addr dhcp_server;
struct in_addr requested_ip;
struct in_addr mask;
struct in_addr siaddr;
char file[4 * DHCP_FILE_LEN + 1];
char hostname[4 * 255 + 1];
char domainname[4 * 255 + 1];
struct dhcp_route prev_routes[MAX_DHCP_ROUTES];
int prev_routes_len;
struct dhcp_route routes[MAX_DHCP_ROUTES];
int routes_len;
struct in_addr nameservers[MAX_RDNS_COUNT];
uint32_t lease_time;
uint32_t renewal_time;
uint32_t rebinding_time;
uint32_t ipv6_only_time;
};
LIST_HEAD(, dhcpleased_iface) dhcpleased_interfaces;
__dead void engine_shutdown(void);
void engine_sig_handler(int sig, short, void *);
void engine_dispatch_frontend(int, short, void *);
void engine_dispatch_main(int, short, void *);
#ifndef SMALL
void send_interface_info(struct dhcpleased_iface *, pid_t);
void engine_showinfo_ctl(pid_t, uint32_t);
#endif
void engine_update_iface(struct imsg_ifinfo *);
struct dhcpleased_iface *get_dhcpleased_iface_by_id(uint32_t);
void remove_dhcpleased_iface(uint32_t);
void parse_dhcp(struct dhcpleased_iface *,
struct imsg_dhcp *);
void state_transition(struct dhcpleased_iface *, enum
if_state);
void iface_timeout(int, short, void *);
void request_dhcp_discover(struct dhcpleased_iface *);
void request_dhcp_request(struct dhcpleased_iface *);
void log_lease(struct dhcpleased_iface *, int);
void log_rdns(struct dhcpleased_iface *, int);
void send_configure_interface(struct dhcpleased_iface *);
void send_rdns_proposal(struct dhcpleased_iface *);
void send_deconfigure_interface(struct dhcpleased_iface *);
void send_rdns_withdraw(struct dhcpleased_iface *);
void send_routes_withdraw(struct dhcpleased_iface *);
void parse_lease(struct dhcpleased_iface *,
struct imsg_ifinfo *);
int engine_imsg_compose_main(int, pid_t, void *, uint16_t);
void log_dhcp_hdr(struct dhcp_hdr *);
const char *dhcp_message_type2str(uint8_t);
#ifndef SMALL
struct dhcpleased_conf *engine_conf;
#endif
static struct imsgev *iev_frontend;
static struct imsgev *iev_main;
int64_t proposal_id;
void
engine_sig_handler(int sig, short event, void *arg)
{
switch (sig) {
case SIGINT:
case SIGTERM:
engine_shutdown();
default:
fatalx("unexpected signal");
}
}
void
engine(int debug, int verbose)
{
struct event ev_sigint, ev_sigterm;
struct passwd *pw;
#ifndef SMALL
engine_conf = config_new_empty();
#endif
log_init(debug, LOG_DAEMON);
log_setverbose(verbose);
if ((pw = getpwnam(DHCPLEASED_USER)) == NULL)
fatal("getpwnam");
if (chdir("/") == -1)
fatal("chdir(\"/\")");
if (unveil("/", "") == -1)
fatal("unveil /");
if (unveil(NULL, NULL) == -1)
fatal("unveil");
setproctitle("%s", "engine");
log_procinit("engine");
if (setgroups(1, &pw->pw_gid) ||
setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
fatal("can't drop privileges");
if (pledge("stdio recvfd", NULL) == -1)
fatal("pledge");
event_init();
signal_set(&ev_sigint, SIGINT, engine_sig_handler, NULL);
signal_set(&ev_sigterm, SIGTERM, engine_sig_handler, NULL);
signal_add(&ev_sigint, NULL);
signal_add(&ev_sigterm, NULL);
signal(SIGPIPE, SIG_IGN);
signal(SIGHUP, SIG_IGN);
if ((iev_main = malloc(sizeof(struct imsgev))) == NULL)
fatal(NULL);
if (imsgbuf_init(&iev_main->ibuf, 3) == -1)
fatal(NULL);
imsgbuf_allow_fdpass(&iev_main->ibuf);
iev_main->handler = engine_dispatch_main;
iev_main->events = EV_READ;
event_set(&iev_main->ev, iev_main->ibuf.fd, iev_main->events,
iev_main->handler, iev_main);
event_add(&iev_main->ev, NULL);
LIST_INIT(&dhcpleased_interfaces);
event_dispatch();
engine_shutdown();
}
__dead void
engine_shutdown(void)
{
imsgbuf_clear(&iev_frontend->ibuf);
close(iev_frontend->ibuf.fd);
imsgbuf_clear(&iev_main->ibuf);
close(iev_main->ibuf.fd);
free(iev_frontend);
free(iev_main);
log_info("engine exiting");
exit(0);
}
int
engine_imsg_compose_frontend(int type, pid_t pid, void *data,
uint16_t datalen)
{
return (imsg_compose_event(iev_frontend, type, 0, pid, -1,
data, datalen));
}
int
engine_imsg_compose_main(int type, pid_t pid, void *data,
uint16_t datalen)
{
return (imsg_compose_event(iev_main, type, 0, pid, -1,
data, datalen));
}
void
engine_dispatch_frontend(int fd, short event, void *bula)
{
struct imsgev *iev = bula;
struct imsgbuf *ibuf = &iev->ibuf;
struct imsg imsg;
struct dhcpleased_iface *iface;
ssize_t n;
int shut = 0;
#ifndef SMALL
int verbose;
#endif
uint32_t if_index, type;
if (event & EV_READ) {
if ((n = imsgbuf_read(ibuf)) == -1)
fatal("imsgbuf_read error");
if (n == 0)
shut = 1;
}
if (event & EV_WRITE) {
if (imsgbuf_write(ibuf) == -1) {
if (errno == EPIPE)
shut = 1;
else
fatal("imsgbuf_write");
}
}
for (;;) {
if ((n = imsg_get(ibuf, &imsg)) == -1)
fatal("%s: imsg_get error", __func__);
if (n == 0)
break;
type = imsg_get_type(&imsg);
switch (type) {
#ifndef SMALL
case IMSG_CTL_LOG_VERBOSE:
if (imsg_get_data(&imsg, &verbose,
sizeof(verbose)) == -1)
fatalx("%s: invalid %s", __func__, i2s(type));
log_setverbose(verbose);
break;
case IMSG_CTL_SHOW_INTERFACE_INFO:
if (imsg_get_data(&imsg, &if_index,
sizeof(if_index)) == -1)
fatalx("%s: invalid %s", __func__, i2s(type));
engine_showinfo_ctl(imsg_get_pid(&imsg), if_index);
break;
case IMSG_REQUEST_REBOOT:
if (imsg_get_data(&imsg, &if_index,
sizeof(if_index)) == -1)
fatalx("%s: invalid %s", __func__, i2s(type));
iface = get_dhcpleased_iface_by_id(if_index);
if (iface != NULL) {
switch (iface->state) {
case IF_DOWN:
break;
case IF_INIT:
case IF_REQUESTING:
state_transition(iface, iface->state);
break;
case IF_RENEWING:
case IF_REBINDING:
case IF_REBOOTING:
case IF_BOUND:
case IF_IPV6_ONLY:
state_transition(iface, IF_REBOOTING);
break;
}
}
break;
#endif
case IMSG_REMOVE_IF:
if (imsg_get_data(&imsg, &if_index,
sizeof(if_index)) == -1)
fatalx("%s: invalid %s", __func__, i2s(type));
remove_dhcpleased_iface(if_index);
break;
case IMSG_DHCP: {
struct imsg_dhcp imsg_dhcp;
if (imsg_get_data(&imsg, &imsg_dhcp,
sizeof(imsg_dhcp)) == -1)
fatalx("%s: invalid %s", __func__, i2s(type));
iface = get_dhcpleased_iface_by_id(imsg_dhcp.if_index);
if (iface != NULL)
parse_dhcp(iface, &imsg_dhcp);
break;
}
case IMSG_REPROPOSE_RDNS:
LIST_FOREACH (iface, &dhcpleased_interfaces, entries)
send_rdns_proposal(iface);
break;
default:
log_debug("%s: unexpected imsg %d", __func__, type);
break;
}
imsg_free(&imsg);
}
if (!shut)
imsg_event_add(iev);
else {
event_del(&iev->ev);
event_loopexit(NULL);
}
}
void
engine_dispatch_main(int fd, short event, void *bula)
{
#ifndef SMALL
static struct dhcpleased_conf *nconf;
static struct iface_conf *iface_conf;
#endif
struct imsg imsg;
struct imsgev *iev = bula;
struct imsgbuf *ibuf = &iev->ibuf;
struct imsg_ifinfo imsg_ifinfo;
ssize_t n;
uint32_t type;
int shut = 0;
if (event & EV_READ) {
if ((n = imsgbuf_read(ibuf)) == -1)
fatal("imsgbuf_read error");
if (n == 0)
shut = 1;
}
if (event & EV_WRITE) {
if (imsgbuf_write(ibuf) == -1) {
if (errno == EPIPE)
shut = 1;
else
fatal("imsgbuf_write");
}
}
for (;;) {
if ((n = imsg_get(ibuf, &imsg)) == -1)
fatal("%s: imsg_get error", __func__);
if (n == 0)
break;
type = imsg_get_type(&imsg);
switch (type) {
case IMSG_SOCKET_IPC:
if (iev_frontend)
fatalx("%s: received unexpected imsg fd "
"to engine", __func__);
if ((fd = imsg_get_fd(&imsg)) == -1)
fatalx("%s: expected to receive imsg fd to "
"engine but didn't receive any", __func__);
iev_frontend = malloc(sizeof(struct imsgev));
if (iev_frontend == NULL)
fatal(NULL);
if (imsgbuf_init(&iev_frontend->ibuf, fd) == -1)
fatal(NULL);
iev_frontend->handler = engine_dispatch_frontend;
iev_frontend->events = EV_READ;
event_set(&iev_frontend->ev, iev_frontend->ibuf.fd,
iev_frontend->events, iev_frontend->handler,
iev_frontend);
event_add(&iev_frontend->ev, NULL);
if (pledge("stdio", NULL) == -1)
fatal("pledge");
break;
case IMSG_UPDATE_IF:
if (imsg_get_data(&imsg, &imsg_ifinfo,
sizeof(imsg_ifinfo)) == -1)
fatalx("%s: invalid %s", __func__, i2s(type));
if (imsg_ifinfo.lease[LEASE_SIZE - 1] != '\0')
fatalx("Invalid lease");
engine_update_iface(&imsg_ifinfo);
break;
#ifndef SMALL
case IMSG_RECONF_CONF:
if (nconf != NULL)
fatalx("%s: IMSG_RECONF_CONF already in "
"progress", __func__);
if ((nconf = malloc(sizeof(struct dhcpleased_conf))) ==
NULL)
fatal(NULL);
SIMPLEQ_INIT(&nconf->iface_list);
break;
case IMSG_RECONF_IFACE:
if ((iface_conf = malloc(sizeof(struct iface_conf)))
== NULL)
fatal(NULL);
if (imsg_get_data(&imsg, iface_conf,
sizeof(struct iface_conf)) == -1)
fatalx("%s: invalid %s", __func__, i2s(type));
iface_conf->vc_id = NULL;
iface_conf->vc_id_len = 0;
iface_conf->c_id = NULL;
iface_conf->c_id_len = 0;
iface_conf->h_name = NULL;
SIMPLEQ_INSERT_TAIL(&nconf->iface_list,
iface_conf, entry);
break;
case IMSG_RECONF_VC_ID:
if (iface_conf == NULL)
fatalx("%s: %s without IMSG_RECONF_IFACE",
__func__, i2s(type));
if (iface_conf->vc_id != NULL)
fatalx("%s: multiple %s for the same interface",
__func__, i2s(type));
if ((iface_conf->vc_id_len = imsg_get_len(&imsg))
> 255 + 2 || iface_conf->vc_id_len == 0)
fatalx("%s: invalid %s", __func__, i2s(type));
if ((iface_conf->vc_id = malloc(iface_conf->vc_id_len))
== NULL)
fatal(NULL);
if (imsg_get_data(&imsg, iface_conf->vc_id,
iface_conf->vc_id_len) == -1)
fatalx("%s: invalid %s", __func__, i2s(type));
break;
case IMSG_RECONF_C_ID:
if (iface_conf == NULL)
fatalx("%s: %s without IMSG_RECONF_IFACE",
__func__, i2s(type));
if (iface_conf->c_id != NULL)
fatalx("%s: multiple %s for the same interface",
__func__, i2s(type));
if ((iface_conf->c_id_len = imsg_get_len(&imsg))
> 255 + 2 || iface_conf->c_id_len == 0)
fatalx("%s: invalid %s", __func__, i2s(type));
if ((iface_conf->c_id = malloc(iface_conf->c_id_len))
== NULL)
fatal(NULL);
if (imsg_get_data(&imsg, iface_conf->c_id,
iface_conf->c_id_len) == -1)
fatalx("%s: invalid %s", __func__, i2s(type));
break;
case IMSG_RECONF_H_NAME: {
size_t len;
if (iface_conf == NULL)
fatalx("%s: %s without IMSG_RECONF_IFACE",
__func__, i2s(type));
if (iface_conf->h_name != NULL)
fatalx("%s: multiple %s for the same interface",
__func__, i2s(type));
if ((len = imsg_get_len(&imsg)) > 256 || len == 0)
fatalx("%s: invalid %s", __func__, i2s(type));
if ((iface_conf->h_name = malloc(len)) == NULL)
fatal(NULL);
if (imsg_get_data(&imsg, iface_conf->h_name, len) == -1)
fatalx("%s: invalid %s", __func__, i2s(type));
if (iface_conf->h_name[len - 1] != '\0')
fatalx("Invalid hostname");
break;
}
case IMSG_RECONF_END: {
struct dhcpleased_iface *iface;
int *ifaces;
int i, if_index;
if (nconf == NULL)
fatalx("%s: %s without IMSG_RECONF_CONF",
__func__, i2s(type));
ifaces = changed_ifaces(engine_conf, nconf);
merge_config(engine_conf, nconf);
nconf = NULL;
for (i = 0; ifaces[i] != 0; i++) {
if_index = ifaces[i];
iface = get_dhcpleased_iface_by_id(if_index);
if (iface == NULL)
continue;
iface_conf = find_iface_conf(
&engine_conf->iface_list, iface->if_name);
if (iface_conf == NULL)
continue;
if (iface_conf->ignore & IGN_DNS)
send_rdns_withdraw(iface);
if (iface_conf->ignore & IGN_ROUTES)
send_routes_withdraw(iface);
}
free(ifaces);
break;
}
#endif
default:
log_debug("%s: unexpected imsg %d", __func__, type);
break;
}
imsg_free(&imsg);
}
if (!shut)
imsg_event_add(iev);
else {
event_del(&iev->ev);
event_loopexit(NULL);
}
}
#ifndef SMALL
void
send_interface_info(struct dhcpleased_iface *iface, pid_t pid)
{
struct ctl_engine_info cei;
memset(&cei, 0, sizeof(cei));
cei.if_index = iface->if_index;
cei.running = iface->running;
cei.link_state = iface->link_state;
strlcpy(cei.state, if_state_name[iface->state], sizeof(cei.state));
memcpy(&cei.request_time, &iface->request_time,
sizeof(cei.request_time));
cei.server_identifier = iface->server_identifier;
cei.dhcp_server = iface->dhcp_server;
cei.requested_ip = iface->requested_ip;
cei.mask = iface->mask;
cei.routes_len = iface->routes_len;
memcpy(cei.routes, iface->routes, sizeof(cei.routes));
memcpy(cei.nameservers, iface->nameservers, sizeof(cei.nameservers));
cei.lease_time = iface->lease_time;
cei.renewal_time = iface->renewal_time;
cei.rebinding_time = iface->rebinding_time;
engine_imsg_compose_frontend(IMSG_CTL_SHOW_INTERFACE_INFO, pid, &cei,
sizeof(cei));
}
void
engine_showinfo_ctl(pid_t pid, uint32_t if_index)
{
struct dhcpleased_iface *iface;
if ((iface = get_dhcpleased_iface_by_id(if_index)) != NULL)
send_interface_info(iface, pid);
else
engine_imsg_compose_frontend(IMSG_CTL_END, pid, NULL, 0);
}
#endif
void
engine_update_iface(struct imsg_ifinfo *imsg_ifinfo)
{
struct dhcpleased_iface *iface;
int need_refresh = 0;
iface = get_dhcpleased_iface_by_id(imsg_ifinfo->if_index);
if (iface == NULL) {
if ((iface = calloc(1, sizeof(*iface))) == NULL)
fatal("calloc");
iface->state = IF_DOWN;
iface->xid = arc4random();
iface->timo.tv_usec = arc4random_uniform(1000000);
evtimer_set(&iface->timer, iface_timeout, iface);
iface->if_index = imsg_ifinfo->if_index;
iface->rdomain = imsg_ifinfo->rdomain;
iface->running = imsg_ifinfo->running;
iface->link_state = imsg_ifinfo->link_state;
iface->requested_ip.s_addr = INADDR_ANY;
memcpy(iface->if_name, imsg_ifinfo->if_name,
sizeof(iface->if_name));
iface->if_name[sizeof(iface->if_name) - 1] = '\0';
memcpy(&iface->hw_address, &imsg_ifinfo->hw_address,
sizeof(struct ether_addr));
LIST_INSERT_HEAD(&dhcpleased_interfaces, iface, entries);
need_refresh = 1;
} else {
if (memcmp(&iface->hw_address, &imsg_ifinfo->hw_address,
sizeof(struct ether_addr)) != 0) {
memcpy(&iface->hw_address, &imsg_ifinfo->hw_address,
sizeof(struct ether_addr));
need_refresh = 1;
}
if (imsg_ifinfo->rdomain != iface->rdomain) {
iface->rdomain = imsg_ifinfo->rdomain;
need_refresh = 1;
}
if (imsg_ifinfo->running != iface->running) {
iface->running = imsg_ifinfo->running;
need_refresh = 1;
}
if (imsg_ifinfo->link_state != iface->link_state) {
iface->link_state = imsg_ifinfo->link_state;
need_refresh = 1;
}
}
if (!need_refresh)
return;
if (iface->running && LINK_STATE_IS_UP(iface->link_state)) {
if (iface->requested_ip.s_addr == INADDR_ANY)
parse_lease(iface, imsg_ifinfo);
if (iface->requested_ip.s_addr == INADDR_ANY)
state_transition(iface, IF_INIT);
else
state_transition(iface, IF_REBOOTING);
} else
state_transition(iface, IF_DOWN);
}
struct dhcpleased_iface*
get_dhcpleased_iface_by_id(uint32_t if_index)
{
struct dhcpleased_iface *iface;
LIST_FOREACH (iface, &dhcpleased_interfaces, entries) {
if (iface->if_index == if_index)
return (iface);
}
return (NULL);
}
void
remove_dhcpleased_iface(uint32_t if_index)
{
struct dhcpleased_iface *iface;
iface = get_dhcpleased_iface_by_id(if_index);
if (iface == NULL)
return;
send_rdns_withdraw(iface);
send_deconfigure_interface(iface);
LIST_REMOVE(iface, entries);
evtimer_del(&iface->timer);
free(iface);
}
void
parse_dhcp(struct dhcpleased_iface *iface, struct imsg_dhcp *dhcp)
{
static uint8_t cookie[] = DHCP_COOKIE;
static struct ether_addr bcast_mac;
#ifndef SMALL
struct iface_conf *iface_conf;
#endif
struct ether_header *eh;
struct ether_addr ether_src, ether_dst;
struct ip *ip;
struct udphdr *udp;
struct dhcp_hdr *dhcp_hdr;
struct in_addr server_identifier, subnet_mask;
struct in_addr nameservers[MAX_RDNS_COUNT];
struct dhcp_route routes[MAX_DHCP_ROUTES];
size_t rem, i;
uint32_t lease_time = 0, renewal_time = 0;
uint32_t rebinding_time = 0;
uint32_t ipv6_only_time = 0;
uint8_t *p, dho = DHO_PAD, dho_len, slen;
uint8_t dhcp_message_type = 0;
int routes_len = 0, routers = 0, csr = 0;
char from[sizeof("xx:xx:xx:xx:xx:xx")];
char to[sizeof("xx:xx:xx:xx:xx:xx")];
char hbuf_src[INET_ADDRSTRLEN];
char hbuf_dst[INET_ADDRSTRLEN];
char hbuf[INET_ADDRSTRLEN];
char domainname[4 * 255 + 1];
char hostname[4 * 255 + 1];
if (bcast_mac.ether_addr_octet[0] == 0)
memset(bcast_mac.ether_addr_octet, 0xff, ETHER_ADDR_LEN);
#ifndef SMALL
iface_conf = find_iface_conf(&engine_conf->iface_list, iface->if_name);
#endif
memset(hbuf_src, 0, sizeof(hbuf_src));
memset(hbuf_dst, 0, sizeof(hbuf_dst));
p = dhcp->packet;
rem = dhcp->len;
if (rem < sizeof(*eh)) {
log_warnx("%s: message too short", __func__);
return;
}
eh = (struct ether_header *)p;
memcpy(ether_src.ether_addr_octet, eh->ether_shost,
sizeof(ether_src.ether_addr_octet));
strlcpy(from, ether_ntoa(ðer_src), sizeof(from));
memcpy(ether_dst.ether_addr_octet, eh->ether_dhost,
sizeof(ether_dst.ether_addr_octet));
strlcpy(to, ether_ntoa(ðer_dst), sizeof(to));
p += sizeof(*eh);
rem -= sizeof(*eh);
if (memcmp(ðer_dst, &iface->hw_address, sizeof(ether_dst)) != 0 &&
memcmp(ðer_dst, &bcast_mac, sizeof(ether_dst)) != 0)
return ;
if (rem < sizeof(*ip))
goto too_short;
if (log_getverbose() > 1)
log_debug("%s, from: %s, to: %s", __func__, from, to);
ip = (struct ip *)p;
if (rem < (size_t)ip->ip_hl << 2)
goto too_short;
if ((dhcp->csumflags & M_IPV4_CSUM_IN_OK) == 0 &&
wrapsum(checksum((uint8_t *)ip, ip->ip_hl << 2, 0)) != 0) {
log_warnx("%s: bad IP checksum", __func__);
return;
}
if (rem < ntohs(ip->ip_len))
goto too_short;
p += ip->ip_hl << 2;
rem -= ip->ip_hl << 2;
if (inet_ntop(AF_INET, &ip->ip_src, hbuf_src, sizeof(hbuf_src)) == NULL)
hbuf_src[0] = '\0';
if (inet_ntop(AF_INET, &ip->ip_dst, hbuf_dst, sizeof(hbuf_dst)) == NULL)
hbuf_dst[0] = '\0';
#ifndef SMALL
if (iface_conf != NULL) {
for (i = 0; (int)i < iface_conf->ignore_servers_len; i++) {
if (iface_conf->ignore_servers[i].s_addr ==
ip->ip_src.s_addr) {
log_debug("ignoring server %s", hbuf_src);
return;
}
}
}
#endif
if (rem < sizeof(*udp))
goto too_short;
udp = (struct udphdr *)p;
if (rem < ntohs(udp->uh_ulen))
goto too_short;
if (rem > ntohs(udp->uh_ulen)) {
if (log_getverbose() > 1) {
log_debug("%s: accepting packet with %lu bytes of data"
" after udp payload", __func__, rem -
ntohs(udp->uh_ulen));
}
rem = ntohs(udp->uh_ulen);
}
p += sizeof(*udp);
rem -= sizeof(*udp);
if ((dhcp->csumflags & M_UDP_CSUM_IN_OK) == 0 &&
udp->uh_sum != 0) {
udp->uh_sum = wrapsum(checksum((uint8_t *)udp, sizeof(*udp),
checksum(p, rem,
checksum((uint8_t *)&ip->ip_src, 2 * sizeof(ip->ip_src),
IPPROTO_UDP + ntohs(udp->uh_ulen)))));
if (udp->uh_sum != 0) {
log_warnx("%s: bad UDP checksum", __func__);
return;
}
}
if (log_getverbose() > 1) {
log_debug("%s: %s:%d -> %s:%d", __func__, hbuf_src,
ntohs(udp->uh_sport), hbuf_dst, ntohs(udp->uh_dport));
}
if (rem < sizeof(*dhcp_hdr))
goto too_short;
dhcp_hdr = (struct dhcp_hdr *)p;
p += sizeof(*dhcp_hdr);
rem -= sizeof(*dhcp_hdr);
dhcp_hdr->sname[DHCP_SNAME_LEN -1 ] = '\0';
dhcp_hdr->file[DHCP_FILE_LEN -1 ] = '\0';
if (log_getverbose() > 1)
log_dhcp_hdr(dhcp_hdr);
if (dhcp_hdr->op != DHCP_BOOTREPLY) {
log_warnx("%s: ignoring non-reply packet", __func__);
return;
}
if (ntohl(dhcp_hdr->xid) != iface->xid)
return;
if (rem < sizeof(cookie))
goto too_short;
if (memcmp(p, cookie, sizeof(cookie)) != 0) {
log_warnx("%s: no dhcp cookie in packet from %s", __func__,
from);
return;
}
p += sizeof(cookie);
rem -= sizeof(cookie);
memset(&server_identifier, 0, sizeof(server_identifier));
memset(&subnet_mask, 0, sizeof(subnet_mask));
memset(&routes, 0, sizeof(routes));
memset(&nameservers, 0, sizeof(nameservers));
memset(hostname, 0, sizeof(hostname));
memset(domainname, 0, sizeof(domainname));
while (rem > 0 && dho != DHO_END) {
dho = *p;
p += 1;
rem -= 1;
if (dho == DHO_PAD || dho == DHO_END)
dho_len = 0;
else {
if (rem == 0)
goto too_short;
dho_len = *p;
p += 1;
rem -= 1;
if (rem < dho_len)
goto too_short;
}
switch (dho) {
case DHO_PAD:
if (log_getverbose() > 1)
log_debug("DHO_PAD");
break;
case DHO_END:
if (log_getverbose() > 1)
log_debug("DHO_END");
break;
case DHO_DHCP_MESSAGE_TYPE:
if (dho_len != 1)
goto wrong_length;
dhcp_message_type = *p;
if (log_getverbose() > 1) {
log_debug("DHO_DHCP_MESSAGE_TYPE: %s",
dhcp_message_type2str(dhcp_message_type));
}
p += dho_len;
rem -= dho_len;
break;
case DHO_DHCP_SERVER_IDENTIFIER:
if (dho_len != sizeof(server_identifier))
goto wrong_length;
memcpy(&server_identifier, p,
sizeof(server_identifier));
if (log_getverbose() > 1) {
log_debug("DHO_DHCP_SERVER_IDENTIFIER: %s",
inet_ntop(AF_INET, &server_identifier,
hbuf, sizeof(hbuf)));
}
p += dho_len;
rem -= dho_len;
break;
case DHO_DHCP_LEASE_TIME:
if (dho_len != sizeof(lease_time))
goto wrong_length;
memcpy(&lease_time, p, sizeof(lease_time));
lease_time = ntohl(lease_time);
if (log_getverbose() > 1) {
log_debug("DHO_DHCP_LEASE_TIME %us",
lease_time);
}
p += dho_len;
rem -= dho_len;
break;
case DHO_SUBNET_MASK:
if (dho_len != sizeof(subnet_mask))
goto wrong_length;
memcpy(&subnet_mask, p, sizeof(subnet_mask));
if (log_getverbose() > 1) {
log_debug("DHO_SUBNET_MASK: %s",
inet_ntop(AF_INET, &subnet_mask, hbuf,
sizeof(hbuf)));
}
p += dho_len;
rem -= dho_len;
break;
case DHO_ROUTERS:
if (dho_len < sizeof(routes[routes_len].gw))
goto wrong_length;
if (dho_len % sizeof(routes[routes_len].gw) != 0)
goto wrong_length;
if (!csr) {
routers = 1;
while (routes_len < MAX_DHCP_ROUTES &&
dho_len > 0) {
memcpy(&routes[routes_len].gw, p,
sizeof(routes[routes_len].gw));
if (log_getverbose() > 1) {
log_debug("DHO_ROUTER: %s",
inet_ntop(AF_INET,
&routes[routes_len].gw,
hbuf, sizeof(hbuf)));
}
p += sizeof(routes[routes_len].gw);
rem -= sizeof(routes[routes_len].gw);
dho_len -=
sizeof(routes[routes_len].gw);
routes_len++;
}
}
if (dho_len != 0) {
p += dho_len;
rem -= dho_len;
}
break;
case DHO_DOMAIN_NAME_SERVERS:
if (dho_len < sizeof(nameservers[0]))
goto wrong_length;
if (dho_len % sizeof(nameservers[0]) != 0)
goto wrong_length;
memcpy(&nameservers, p, MINIMUM(sizeof(nameservers),
dho_len));
if (log_getverbose() > 1) {
for (i = 0; i < MINIMUM(sizeof(nameservers),
dho_len / sizeof(nameservers[0])); i++) {
log_debug("DHO_DOMAIN_NAME_SERVERS: %s "
"(%lu/%lu)", inet_ntop(AF_INET,
&nameservers[i], hbuf,
sizeof(hbuf)), i + 1,
dho_len / sizeof(nameservers[0]));
}
}
p += dho_len;
rem -= dho_len;
break;
case DHO_HOST_NAME:
if (dho_len < 1) {
break;
}
slen = dho_len;
while (slen > 0 && p[slen - 1] == '\0')
slen--;
strvisx(hostname, p, slen, VIS_SAFE);
if (log_getverbose() > 1)
log_debug("DHO_HOST_NAME: %s", hostname);
p += dho_len;
rem -= dho_len;
break;
case DHO_DOMAIN_NAME:
if (dho_len < 1) {
break;
}
slen = dho_len;
while (slen > 0 && p[slen - 1] == '\0')
slen--;
strvisx(domainname, p, slen, VIS_SAFE);
if (log_getverbose() > 1)
log_debug("DHO_DOMAIN_NAME: %s", domainname);
p += dho_len;
rem -= dho_len;
break;
case DHO_DHCP_RENEWAL_TIME:
if (dho_len != sizeof(renewal_time))
goto wrong_length;
memcpy(&renewal_time, p, sizeof(renewal_time));
renewal_time = ntohl(renewal_time);
if (log_getverbose() > 1) {
log_debug("DHO_DHCP_RENEWAL_TIME %us",
renewal_time);
}
p += dho_len;
rem -= dho_len;
break;
case DHO_DHCP_REBINDING_TIME:
if (dho_len != sizeof(rebinding_time))
goto wrong_length;
memcpy(&rebinding_time, p, sizeof(rebinding_time));
rebinding_time = ntohl(rebinding_time);
if (log_getverbose() > 1) {
log_debug("DHO_DHCP_REBINDING_TIME %us",
rebinding_time);
}
p += dho_len;
rem -= dho_len;
break;
case DHO_DHCP_CLIENT_IDENTIFIER:
#ifndef SMALL
if (iface_conf != NULL && iface_conf->c_id_len > 0) {
if (dho_len != iface_conf->c_id[1]) {
log_warnx("wrong "
"DHO_DHCP_CLIENT_IDENTIFIER");
} else if (memcmp(p, &iface_conf->c_id[2],
dho_len) != 0) {
log_warnx("wrong "
"DHO_DHCP_CLIENT_IDENTIFIER");
}
} else
#endif
{
if (dho_len != 1 + sizeof(iface->hw_address))
goto wrong_length;
if (*p != HTYPE_ETHER) {
log_warnx("DHO_DHCP_CLIENT_IDENTIFIER: "
"wrong type");
}
if (memcmp(p + 1, &iface->hw_address,
sizeof(iface->hw_address)) != 0) {
log_warnx("wrong "
"DHO_DHCP_CLIENT_IDENTIFIER");
}
}
p += dho_len;
rem -= dho_len;
break;
case DHO_CLASSLESS_STATIC_ROUTES: {
int prefixlen, compressed_prefixlen;
csr = 1;
if (routers) {
routers = 0;
routes_len = 0;
}
while (routes_len < MAX_DHCP_ROUTES && dho_len > 0) {
prefixlen = *p;
p += 1;
rem -= 1;
dho_len -= 1;
if (prefixlen < 0 || prefixlen > 32) {
log_warnx("%s: invalid prefixlen: %d",
__func__, prefixlen);
return;
}
if (prefixlen > 0)
routes[routes_len].mask.s_addr =
htonl(0xffffffff << (32 -
prefixlen));
else
routes[routes_len].mask.s_addr =
INADDR_ANY;
compressed_prefixlen = (prefixlen + 7) / 8;
if (dho_len < compressed_prefixlen)
goto wrong_length;
memcpy(&routes[routes_len].dst, p,
compressed_prefixlen);
p += compressed_prefixlen;
rem -= compressed_prefixlen;
dho_len -= compressed_prefixlen;
if (dho_len < sizeof(routes[routes_len].gw))
goto wrong_length;
memcpy(&routes[routes_len].gw, p,
sizeof(routes[routes_len].gw));
p += sizeof(routes[routes_len].gw);
rem -= sizeof(routes[routes_len].gw);
dho_len -= sizeof(routes[routes_len].gw);
routes_len++;
}
if (dho_len != 0) {
p += dho_len;
rem -= dho_len;
}
break;
}
case DHO_IPV6_ONLY_PREFERRED:
if (dho_len != sizeof(ipv6_only_time))
goto wrong_length;
memcpy(&ipv6_only_time, p, sizeof(ipv6_only_time));
ipv6_only_time = ntohl(ipv6_only_time);
if (log_getverbose() > 1) {
log_debug("DHO_IPV6_ONLY_PREFERRED %us",
ipv6_only_time);
}
if (ipv6_only_time < MIN_V6ONLY_WAIT) {
ipv6_only_time = MIN_V6ONLY_WAIT;
if (log_getverbose() > 1) {
log_debug("DHO_IPV6_ONLY_PREFERRED too "
"small, setting to %us",
ipv6_only_time);
}
}
p += dho_len;
rem -= dho_len;
break;
default:
if (log_getverbose() > 1)
log_debug("DHO_%u, len: %u", dho, dho_len);
p += dho_len;
rem -= dho_len;
}
}
while (rem != 0) {
if (*p != DHO_PAD)
break;
p++;
rem--;
}
if (rem != 0)
log_debug("%s: %lu bytes garbage data from %s", __func__, rem,
from);
log_debug("%s on %s from %s/%s to %s/%s",
dhcp_message_type2str(dhcp_message_type), iface->if_name, from,
hbuf_src, to, hbuf_dst);
switch (dhcp_message_type) {
case DHCPOFFER:
if (iface->state != IF_INIT) {
log_debug("ignoring unexpected DHCPOFFER");
return;
}
if (server_identifier.s_addr == INADDR_ANY &&
dhcp_hdr->yiaddr.s_addr == INADDR_ANY) {
log_warnx("%s: did not receive server identifier or "
"offered IP address", __func__);
return;
}
#ifndef SMALL
if (iface_conf != NULL && iface_conf->prefer_ipv6 &&
ipv6_only_time > 0) {
iface->ipv6_only_time = ipv6_only_time;
state_transition(iface, IF_IPV6_ONLY);
break;
}
#endif
iface->server_identifier = server_identifier;
iface->dhcp_server = server_identifier;
iface->requested_ip = dhcp_hdr->yiaddr;
state_transition(iface, IF_REQUESTING);
break;
case DHCPACK:
switch (iface->state) {
case IF_REQUESTING:
case IF_RENEWING:
case IF_REBINDING:
case IF_REBOOTING:
break;
default:
log_debug("ignoring unexpected DHCPACK");
return;
}
if (server_identifier.s_addr == INADDR_ANY &&
dhcp_hdr->yiaddr.s_addr == INADDR_ANY) {
log_warnx("%s: did not receive server identifier or "
"offered IP address", __func__);
return;
}
if (lease_time == 0) {
log_warnx("%s no lease time from %s", __func__, from);
return;
}
if (subnet_mask.s_addr == INADDR_ANY) {
log_warnx("%s: no subnetmask received from %s",
__func__, from);
return;
}
if (renewal_time == 0)
renewal_time = lease_time / 2;
if (rebinding_time == 0)
rebinding_time = lease_time - (lease_time / 8);
if (renewal_time >= rebinding_time) {
log_warnx("%s: renewal_time(%u) >= rebinding_time(%u) "
"from %s: using defaults",
__func__, renewal_time, rebinding_time, from);
renewal_time = rebinding_time = 0;
} else if (rebinding_time >= lease_time) {
log_warnx("%s: rebinding_time(%u) >= lease_time(%u) "
"from %s: using defaults",
__func__, rebinding_time, lease_time, from);
renewal_time = rebinding_time = 0;
}
if (renewal_time == 0)
renewal_time = lease_time / 2;
if (rebinding_time == 0)
rebinding_time = lease_time - (lease_time / 8);
clock_gettime(CLOCK_MONOTONIC, &iface->request_time);
iface->server_identifier = server_identifier;
iface->dhcp_server = server_identifier;
iface->requested_ip = dhcp_hdr->yiaddr;
iface->mask = subnet_mask;
#ifndef SMALL
if (iface_conf != NULL && iface_conf->ignore & IGN_ROUTES) {
iface->routes_len = 0;
memset(iface->routes, 0, sizeof(iface->routes));
} else
#endif
{
iface->prev_routes_len = iface->routes_len;
memcpy(iface->prev_routes, iface->routes,
sizeof(iface->prev_routes));
iface->routes_len = routes_len;
memcpy(iface->routes, routes, sizeof(iface->routes));
}
iface->lease_time = lease_time;
iface->renewal_time = renewal_time;
iface->rebinding_time = rebinding_time;
#ifndef SMALL
if (iface_conf != NULL && iface_conf->ignore & IGN_DNS) {
memset(iface->nameservers, 0,
sizeof(iface->nameservers));
} else
#endif
{
memcpy(iface->nameservers, nameservers,
sizeof(iface->nameservers));
}
iface->siaddr = dhcp_hdr->siaddr;
strnvis(iface->file, dhcp_hdr->file, sizeof(iface->file),
VIS_SAFE);
strlcpy(iface->domainname, domainname,
sizeof(iface->domainname));
strlcpy(iface->hostname, hostname, sizeof(iface->hostname));
#ifndef SMALL
if (iface_conf != NULL && iface_conf->prefer_ipv6 &&
ipv6_only_time > 0) {
iface->ipv6_only_time = ipv6_only_time;
state_transition(iface, IF_IPV6_ONLY);
break;
}
#endif
state_transition(iface, IF_BOUND);
break;
case DHCPNAK:
switch (iface->state) {
case IF_REQUESTING:
case IF_RENEWING:
case IF_REBINDING:
case IF_REBOOTING:
break;
default:
log_debug("ignoring unexpected DHCPNAK");
return;
}
state_transition(iface, IF_INIT);
break;
default:
log_warnx("%s: unimplemented message type %d", __func__,
dhcp_message_type);
break;
}
return;
too_short:
log_warnx("%s: message from %s too short", __func__, from);
return;
wrong_length:
log_warnx("%s: received option %d with wrong length: %d", __func__,
dho, dho_len);
return;
}
void
state_transition(struct dhcpleased_iface *iface, enum if_state new_state)
{
enum if_state old_state = iface->state;
struct timespec now, res;
iface->state = new_state;
switch (new_state) {
case IF_DOWN:
if (iface->requested_ip.s_addr == INADDR_ANY) {
iface->timo.tv_sec = -1;
break;
}
if (old_state == IF_DOWN) {
send_deconfigure_interface(iface);
iface->timo.tv_sec = -1;
} else {
send_rdns_withdraw(iface);
clock_gettime(CLOCK_MONOTONIC, &now);
timespecsub(&now, &iface->request_time, &res);
iface->timo.tv_sec = iface->lease_time - res.tv_sec;
if (iface->timo.tv_sec < 0)
iface->timo.tv_sec = 0;
}
break;
case IF_INIT:
switch (old_state) {
case IF_INIT:
if (iface->timo.tv_sec < MAX_EXP_BACKOFF_SLOW)
iface->timo.tv_sec *= 2;
break;
case IF_REQUESTING:
case IF_RENEWING:
case IF_REBINDING:
case IF_REBOOTING:
send_rdns_withdraw(iface);
send_deconfigure_interface(iface);
case IF_DOWN:
case IF_IPV6_ONLY:
iface->timo.tv_sec = START_EXP_BACKOFF;
iface->xid = arc4random();
break;
case IF_BOUND:
fatalx("invalid transition Bound -> Init");
break;
}
request_dhcp_discover(iface);
break;
case IF_REBOOTING:
if (old_state == IF_REBOOTING)
iface->timo.tv_sec *= 2;
else {
iface->timo.tv_sec = START_EXP_BACKOFF;
iface->xid = arc4random();
}
request_dhcp_request(iface);
break;
case IF_REQUESTING:
if (old_state == IF_REQUESTING)
iface->timo.tv_sec *= 2;
else
iface->timo.tv_sec = START_EXP_BACKOFF;
request_dhcp_request(iface);
break;
case IF_BOUND:
iface->timo.tv_sec = iface->renewal_time;
if (old_state == IF_REQUESTING || old_state == IF_REBOOTING) {
send_configure_interface(iface);
send_rdns_proposal(iface);
}
break;
case IF_RENEWING:
if (old_state == IF_BOUND) {
iface->timo.tv_sec = (iface->rebinding_time -
iface->renewal_time) / 2;
iface->xid = arc4random();
} else
iface->timo.tv_sec /= 2;
if (iface->timo.tv_sec < 60)
iface->timo.tv_sec = 60;
request_dhcp_request(iface);
break;
case IF_REBINDING:
if (old_state == IF_RENEWING) {
iface->timo.tv_sec = (iface->lease_time -
iface->rebinding_time) / 2;
} else
iface->timo.tv_sec /= 2;
request_dhcp_request(iface);
break;
case IF_IPV6_ONLY:
switch (old_state) {
case IF_REQUESTING:
case IF_RENEWING:
case IF_REBINDING:
case IF_REBOOTING:
send_rdns_withdraw(iface);
send_deconfigure_interface(iface);
case IF_INIT:
case IF_DOWN:
case IF_IPV6_ONLY:
iface->timo.tv_sec = iface->ipv6_only_time;
break;
case IF_BOUND:
fatalx("invalid transition Bound -> IPv6 only");
break;
}
}
log_debug("%s[%s] %s -> %s, timo: %lld", __func__, iface->if_name,
if_state_name[old_state], if_state_name[new_state],
iface->timo.tv_sec);
if (iface->timo.tv_sec == -1) {
if (evtimer_pending(&iface->timer, NULL))
evtimer_del(&iface->timer);
} else
evtimer_add(&iface->timer, &iface->timo);
}
void
iface_timeout(int fd, short events, void *arg)
{
struct dhcpleased_iface *iface = (struct dhcpleased_iface *)arg;
struct timespec now, res;
log_debug("%s[%d]: %s", __func__, iface->if_index,
if_state_name[iface->state]);
switch (iface->state) {
case IF_DOWN:
state_transition(iface, IF_DOWN);
break;
case IF_INIT:
state_transition(iface, IF_INIT);
break;
case IF_REBOOTING:
if (iface->timo.tv_sec >= MAX_EXP_BACKOFF_FAST)
state_transition(iface, IF_INIT);
else
state_transition(iface, IF_REBOOTING);
break;
case IF_REQUESTING:
if (iface->timo.tv_sec >= MAX_EXP_BACKOFF_SLOW)
state_transition(iface, IF_INIT);
else
state_transition(iface, IF_REQUESTING);
break;
case IF_BOUND:
state_transition(iface, IF_RENEWING);
break;
case IF_RENEWING:
clock_gettime(CLOCK_MONOTONIC, &now);
timespecsub(&now, &iface->request_time, &res);
log_debug("%s: res.tv_sec: %lld, rebinding_time: %u", __func__,
res.tv_sec, iface->rebinding_time);
if (res.tv_sec >= iface->rebinding_time)
state_transition(iface, IF_REBINDING);
else
state_transition(iface, IF_RENEWING);
break;
case IF_REBINDING:
clock_gettime(CLOCK_MONOTONIC, &now);
timespecsub(&now, &iface->request_time, &res);
log_debug("%s: res.tv_sec: %lld, lease_time: %u", __func__,
res.tv_sec, iface->lease_time);
if (res.tv_sec > iface->lease_time)
state_transition(iface, IF_INIT);
else
state_transition(iface, IF_REBINDING);
break;
case IF_IPV6_ONLY:
state_transition(iface, IF_REQUESTING);
break;
}
}
void
request_dhcp_discover(struct dhcpleased_iface *iface)
{
struct imsg_req_dhcp imsg;
memset(&imsg, 0, sizeof(imsg));
imsg.if_index = iface->if_index;
imsg.xid = iface->xid;
engine_imsg_compose_frontend(IMSG_SEND_DISCOVER, 0, &imsg, sizeof(imsg));
}
void
request_dhcp_request(struct dhcpleased_iface *iface)
{
struct imsg_req_dhcp imsg;
imsg.if_index = iface->if_index;
imsg.xid = iface->xid;
switch (iface->state) {
case IF_DOWN:
fatalx("invalid state IF_DOWN in %s", __func__);
break;
case IF_INIT:
fatalx("invalid state IF_INIT in %s", __func__);
break;
case IF_BOUND:
fatalx("invalid state IF_BOUND in %s", __func__);
break;
case IF_REBOOTING:
imsg.dhcp_server.s_addr = INADDR_ANY;
imsg.server_identifier.s_addr = INADDR_ANY;
imsg.requested_ip = iface->requested_ip;
imsg.ciaddr.s_addr = INADDR_ANY;
break;
case IF_REQUESTING:
imsg.dhcp_server.s_addr = INADDR_ANY;
imsg.server_identifier =
iface->server_identifier;
imsg.requested_ip = iface->requested_ip;
imsg.ciaddr.s_addr = INADDR_ANY;
break;
case IF_RENEWING:
imsg.dhcp_server = iface->dhcp_server;
imsg.server_identifier.s_addr = INADDR_ANY;
imsg.requested_ip.s_addr = INADDR_ANY;
imsg.ciaddr = iface->requested_ip;
break;
case IF_REBINDING:
imsg.dhcp_server.s_addr = INADDR_ANY;
imsg.server_identifier.s_addr = INADDR_ANY;
imsg.requested_ip.s_addr = INADDR_ANY;
imsg.ciaddr = iface->requested_ip;
break;
case IF_IPV6_ONLY:
fatalx("invalid state IF_IPV6_ONLY in %s", __func__);
break;
}
engine_imsg_compose_frontend(IMSG_SEND_REQUEST, 0, &imsg, sizeof(imsg));
}
void
log_lease(struct dhcpleased_iface *iface, int deconfigure)
{
char hbuf_lease[INET_ADDRSTRLEN], hbuf_server[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &iface->requested_ip, hbuf_lease,
sizeof(hbuf_lease));
inet_ntop(AF_INET, &iface->server_identifier, hbuf_server,
sizeof(hbuf_server));
if (deconfigure)
log_info("deleting %s from %s (lease from %s)", hbuf_lease,
iface->if_name, hbuf_server);
else
log_info("adding %s to %s (lease from %s)", hbuf_lease,
iface->if_name, hbuf_server);
}
void
send_configure_interface(struct dhcpleased_iface *iface)
{
struct imsg_configure_interface imsg;
int i, j, found;
log_lease(iface, 0);
memset(&imsg, 0, sizeof(imsg));
imsg.if_index = iface->if_index;
imsg.rdomain = iface->rdomain;
imsg.addr = iface->requested_ip;
imsg.mask = iface->mask;
imsg.siaddr = iface->siaddr;
strlcpy(imsg.file, iface->file, sizeof(imsg.file));
strlcpy(imsg.domainname, iface->domainname, sizeof(imsg.domainname));
strlcpy(imsg.hostname, iface->hostname, sizeof(imsg.hostname));
for (i = 0; i < iface->prev_routes_len; i++) {
found = 0;
for (j = 0; j < iface->routes_len; j++) {
if (memcmp(&iface->prev_routes[i], &iface->routes[j],
sizeof(struct dhcp_route)) == 0) {
found = 1;
break;
}
}
if (!found)
imsg.routes[imsg.routes_len++] = iface->prev_routes[i];
}
if (imsg.routes_len > 0)
engine_imsg_compose_main(IMSG_WITHDRAW_ROUTES, 0, &imsg,
sizeof(imsg));
imsg.routes_len = iface->routes_len;
memcpy(imsg.routes, iface->routes, sizeof(imsg.routes));
engine_imsg_compose_main(IMSG_CONFIGURE_INTERFACE, 0, &imsg,
sizeof(imsg));
}
void
send_deconfigure_interface(struct dhcpleased_iface *iface)
{
struct imsg_configure_interface imsg;
if (iface->requested_ip.s_addr == INADDR_ANY)
return;
log_lease(iface, 1);
memset(&imsg, 0, sizeof(imsg));
imsg.if_index = iface->if_index;
imsg.rdomain = iface->rdomain;
imsg.addr = iface->requested_ip;
imsg.mask = iface->mask;
imsg.siaddr = iface->siaddr;
strlcpy(imsg.file, iface->file, sizeof(imsg.file));
strlcpy(imsg.domainname, iface->domainname, sizeof(imsg.domainname));
strlcpy(imsg.hostname, iface->hostname, sizeof(imsg.hostname));
imsg.routes_len = iface->routes_len;
memcpy(imsg.routes, iface->routes, sizeof(imsg.routes));
engine_imsg_compose_main(IMSG_DECONFIGURE_INTERFACE, 0, &imsg,
sizeof(imsg));
iface->server_identifier.s_addr = INADDR_ANY;
iface->dhcp_server.s_addr = INADDR_ANY;
iface->requested_ip.s_addr = INADDR_ANY;
iface->mask.s_addr = INADDR_ANY;
iface->routes_len = 0;
memset(iface->routes, 0, sizeof(iface->routes));
}
void
send_routes_withdraw(struct dhcpleased_iface *iface)
{
struct imsg_configure_interface imsg;
if (iface->requested_ip.s_addr == INADDR_ANY || iface->routes_len == 0)
return;
imsg.if_index = iface->if_index;
imsg.rdomain = iface->rdomain;
imsg.addr = iface->requested_ip;
imsg.mask = iface->mask;
imsg.siaddr = iface->siaddr;
strlcpy(imsg.file, iface->file, sizeof(imsg.file));
strlcpy(imsg.domainname, iface->domainname, sizeof(imsg.domainname));
strlcpy(imsg.hostname, iface->hostname, sizeof(imsg.hostname));
imsg.routes_len = iface->routes_len;
memcpy(imsg.routes, iface->routes, sizeof(imsg.routes));
engine_imsg_compose_main(IMSG_WITHDRAW_ROUTES, 0, &imsg,
sizeof(imsg));
}
void
log_rdns(struct dhcpleased_iface *iface, int withdraw)
{
int i;
char hbuf_rdns[INET_ADDRSTRLEN], hbuf_server[INET_ADDRSTRLEN];
char *rdns_buf = NULL, *tmp_buf;
inet_ntop(AF_INET, &iface->server_identifier, hbuf_server,
sizeof(hbuf_server));
for (i = 0; i < MAX_RDNS_COUNT && iface->nameservers[i].s_addr !=
INADDR_ANY; i++) {
inet_ntop(AF_INET, &iface->nameservers[i], hbuf_rdns,
sizeof(hbuf_rdns));
tmp_buf = rdns_buf;
if (asprintf(&rdns_buf, "%s %s", tmp_buf ? tmp_buf : "",
hbuf_rdns) < 0) {
rdns_buf = NULL;
break;
}
free(tmp_buf);
}
if (rdns_buf != NULL) {
if (withdraw) {
log_info("deleting nameservers%s (lease from %s on %s)",
rdns_buf, hbuf_server, iface->if_name);
} else {
log_info("adding nameservers%s (lease from %s on %s)",
rdns_buf, hbuf_server, iface->if_name);
}
free(rdns_buf);
}
}
void
send_rdns_proposal(struct dhcpleased_iface *iface)
{
struct imsg_propose_rdns imsg;
log_rdns(iface, 0);
memset(&imsg, 0, sizeof(imsg));
imsg.if_index = iface->if_index;
imsg.rdomain = iface->rdomain;
for (imsg.rdns_count = 0; imsg.rdns_count < MAX_RDNS_COUNT &&
iface->nameservers[imsg.rdns_count].s_addr != INADDR_ANY;
imsg.rdns_count++)
;
memcpy(imsg.rdns, iface->nameservers, sizeof(imsg.rdns));
engine_imsg_compose_main(IMSG_PROPOSE_RDNS, 0, &imsg, sizeof(imsg));
}
void
send_rdns_withdraw(struct dhcpleased_iface *iface)
{
struct imsg_propose_rdns imsg;
log_rdns(iface, 1);
memset(&imsg, 0, sizeof(imsg));
imsg.if_index = iface->if_index;
imsg.rdomain = iface->rdomain;
engine_imsg_compose_main(IMSG_WITHDRAW_RDNS, 0, &imsg, sizeof(imsg));
memset(iface->nameservers, 0, sizeof(iface->nameservers));
}
void
parse_lease(struct dhcpleased_iface *iface, struct imsg_ifinfo *imsg_ifinfo)
{
char *p, *p1;
iface->requested_ip.s_addr = INADDR_ANY;
if ((p = strstr(imsg_ifinfo->lease, LEASE_IP_PREFIX)) == NULL)
return;
p += sizeof(LEASE_IP_PREFIX) - 1;
if ((p1 = strchr(p, '\n')) == NULL)
return;
*p1 = '\0';
if (inet_pton(AF_INET, p, &iface->requested_ip) != 1)
iface->requested_ip.s_addr = INADDR_ANY;
}
void
log_dhcp_hdr(struct dhcp_hdr *dhcp_hdr)
{
#ifndef SMALL
char hbuf[INET_ADDRSTRLEN];
log_debug("dhcp_hdr op: %s (%d)", dhcp_hdr->op == DHCP_BOOTREQUEST ?
"Boot Request" : dhcp_hdr->op == DHCP_BOOTREPLY ? "Boot Reply" :
"Unknown", dhcp_hdr->op);
log_debug("dhcp_hdr htype: %s (%d)", dhcp_hdr->htype == 1 ? "Ethernet":
"Unknown", dhcp_hdr->htype);
log_debug("dhcp_hdr hlen: %d", dhcp_hdr->hlen);
log_debug("dhcp_hdr hops: %d", dhcp_hdr->hops);
log_debug("dhcp_hdr xid: 0x%x", ntohl(dhcp_hdr->xid));
log_debug("dhcp_hdr secs: %u", dhcp_hdr->secs);
log_debug("dhcp_hdr flags: 0x%x", dhcp_hdr->flags);
log_debug("dhcp_hdr ciaddr: %s", inet_ntop(AF_INET, &dhcp_hdr->ciaddr,
hbuf, sizeof(hbuf)));
log_debug("dhcp_hdr yiaddr: %s", inet_ntop(AF_INET, &dhcp_hdr->yiaddr,
hbuf, sizeof(hbuf)));
log_debug("dhcp_hdr siaddr: %s", inet_ntop(AF_INET, &dhcp_hdr->siaddr,
hbuf, sizeof(hbuf)));
log_debug("dhcp_hdr giaddr: %s", inet_ntop(AF_INET, &dhcp_hdr->giaddr,
hbuf, sizeof(hbuf)));
log_debug("dhcp_hdr chaddr: %02x:%02x:%02x:%02x:%02x:%02x "
"(%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x)",
dhcp_hdr->chaddr[0], dhcp_hdr->chaddr[1], dhcp_hdr->chaddr[2],
dhcp_hdr->chaddr[3], dhcp_hdr->chaddr[4], dhcp_hdr->chaddr[5],
dhcp_hdr->chaddr[6], dhcp_hdr->chaddr[7], dhcp_hdr->chaddr[8],
dhcp_hdr->chaddr[9], dhcp_hdr->chaddr[10], dhcp_hdr->chaddr[11],
dhcp_hdr->chaddr[12], dhcp_hdr->chaddr[13], dhcp_hdr->chaddr[14],
dhcp_hdr->chaddr[15]);
#endif
}
const char *
dhcp_message_type2str(uint8_t dhcp_message_type)
{
switch (dhcp_message_type) {
case DHCPDISCOVER:
return "DHCPDISCOVER";
case DHCPOFFER:
return "DHCPOFFER";
case DHCPREQUEST:
return "DHCPREQUEST";
case DHCPDECLINE:
return "DHCPDECLINE";
case DHCPACK:
return "DHCPACK";
case DHCPNAK:
return "DHCPNAK";
case DHCPRELEASE:
return "DHCPRELEASE";
case DHCPINFORM:
return "DHCPINFORM";
default:
return "Unknown";
}
}