#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/ip.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 "log.h"
#include "dhcp6leased.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))
enum if_state {
IF_DOWN,
IF_INIT,
IF_REQUESTING,
IF_BOUND,
IF_RENEWING,
IF_REBINDING,
IF_REBOOTING,
};
const char* if_state_name[] = {
"Down",
"Init",
"Requesting",
"Bound",
"Renewing",
"Rebinding",
"Rebooting",
"IPv6 only",
};
enum reconfigure_action {
CONFIGURE,
DECONFIGURE,
};
const char* reconfigure_action_name[] = {
"configure",
"deconfigure",
};
struct dhcp6leased_iface {
LIST_ENTRY(dhcp6leased_iface) entries;
enum if_state state;
struct event timer;
struct timeval timo;
uint32_t if_index;
int rdomain;
int running;
int link_state;
uint8_t xid[XID_SIZE];
int serverid_len;
uint8_t serverid[SERVERID_SIZE];
struct prefix pds[MAX_IA];
struct prefix new_pds[MAX_IA];
struct timespec request_time;
struct timespec elapsed_time_start;
uint32_t lease_time;
uint32_t t1;
uint32_t t2;
};
LIST_HEAD(, dhcp6leased_iface) dhcp6leased_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 *);
void send_interface_info(struct dhcp6leased_iface *, pid_t);
void engine_showinfo_ctl(struct imsg *, uint32_t);
void engine_update_iface(struct imsg_ifinfo *);
struct dhcp6leased_iface *get_dhcp6leased_iface_by_id(uint32_t);
void remove_dhcp6leased_iface(uint32_t);
void parse_dhcp(struct dhcp6leased_iface *,
struct imsg_dhcp *);
int parse_ia_pd_options(uint8_t *, size_t, struct prefix *);
void state_transition(struct dhcp6leased_iface *, enum
if_state);
void iface_timeout(int, short, void *);
void request_dhcp_discover(struct dhcp6leased_iface *);
void request_dhcp_request(struct dhcp6leased_iface *);
void configure_interfaces(struct dhcp6leased_iface *);
void deconfigure_interfaces(struct dhcp6leased_iface *);
void deprecate_interfaces(struct dhcp6leased_iface *);
int prefixcmp(struct prefix *, struct prefix *, int);
void send_reconfigure_interface(struct iface_pd_conf *,
struct prefix *, enum reconfigure_action);
void send_reconfigure_reject_route(
struct dhcp6leased_iface *, struct in6_addr *,
uint8_t, enum reconfigure_action);
int engine_imsg_compose_main(int, pid_t, void *, uint16_t);
const char *dhcp_option_type2str(int);
const char *dhcp_duid2str(int, uint8_t *);
const char *dhcp_status2str(int);
void in6_prefixlen2mask(struct in6_addr *, int len);
struct dhcp6leased_conf *engine_conf;
static struct imsgev *iev_frontend;
static struct imsgev *iev_main;
int64_t proposal_id;
static struct dhcp_duid duid;
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;
engine_conf = config_new_empty();
log_init(debug, LOG_DAEMON);
log_setverbose(verbose);
if ((pw = getpwnam(DHCP6LEASED_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(&dhcp6leased_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 dhcp6leased_iface *iface;
ssize_t n;
int shut = 0;
int verbose;
uint32_t if_index;
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;
switch (imsg.hdr.type) {
case IMSG_CTL_LOG_VERBOSE:
if (IMSG_DATA_SIZE(imsg) != sizeof(verbose))
fatalx("%s: IMSG_CTL_LOG_VERBOSE wrong length: "
"%lu", __func__, IMSG_DATA_SIZE(imsg));
memcpy(&verbose, imsg.data, sizeof(verbose));
log_setverbose(verbose);
break;
case IMSG_CTL_SHOW_INTERFACE_INFO:
if (IMSG_DATA_SIZE(imsg) != sizeof(if_index))
fatalx("%s: IMSG_CTL_SHOW_INTERFACE_INFO wrong "
"length: %lu", __func__,
IMSG_DATA_SIZE(imsg));
memcpy(&if_index, imsg.data, sizeof(if_index));
engine_showinfo_ctl(&imsg, if_index);
break;
case IMSG_REQUEST_REBOOT:
if (IMSG_DATA_SIZE(imsg) != sizeof(if_index))
fatalx("%s: IMSG_CTL_SEND_DISCOVER wrong "
"length: %lu", __func__,
IMSG_DATA_SIZE(imsg));
memcpy(&if_index, imsg.data, sizeof(if_index));
iface = get_dhcp6leased_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:
state_transition(iface, IF_REBOOTING);
break;
}
}
break;
case IMSG_REMOVE_IF:
if (IMSG_DATA_SIZE(imsg) != sizeof(if_index))
fatalx("%s: IMSG_REMOVE_IF wrong length: %lu",
__func__, IMSG_DATA_SIZE(imsg));
memcpy(&if_index, imsg.data, sizeof(if_index));
remove_dhcp6leased_iface(if_index);
break;
case IMSG_DHCP: {
struct imsg_dhcp imsg_dhcp;
if (IMSG_DATA_SIZE(imsg) != sizeof(imsg_dhcp))
fatalx("%s: IMSG_DHCP wrong length: %lu",
__func__, IMSG_DATA_SIZE(imsg));
memcpy(&imsg_dhcp, imsg.data, sizeof(imsg_dhcp));
iface = get_dhcp6leased_iface_by_id(imsg_dhcp.if_index);
if (iface != NULL)
parse_dhcp(iface, &imsg_dhcp);
break;
}
default:
log_debug("%s: unexpected imsg %d", __func__,
imsg.hdr.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)
{
static struct dhcp6leased_conf *nconf;
static struct iface_conf *iface_conf;
static struct iface_ia_conf *iface_ia_conf;
struct iface_pd_conf *iface_pd_conf;
struct imsg imsg;
struct imsgev *iev = bula;
struct imsgbuf *ibuf = &iev->ibuf;
struct imsg_ifinfo imsg_ifinfo;
ssize_t n;
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;
switch (imsg.hdr.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_UUID:
if (IMSG_DATA_SIZE(imsg) != sizeof(duid.uuid))
fatalx("%s: IMSG_UUID wrong length: "
"%lu", __func__, IMSG_DATA_SIZE(imsg));
duid.type = htons(DUID_UUID_TYPE);
memcpy(duid.uuid, imsg.data, sizeof(duid.uuid));
break;
case IMSG_UPDATE_IF:
if (IMSG_DATA_SIZE(imsg) != sizeof(imsg_ifinfo))
fatalx("%s: IMSG_UPDATE_IF wrong length: %lu",
__func__, IMSG_DATA_SIZE(imsg));
memcpy(&imsg_ifinfo, imsg.data, sizeof(imsg_ifinfo));
engine_update_iface(&imsg_ifinfo);
break;
case IMSG_RECONF_CONF:
if (nconf != NULL)
fatalx("%s: IMSG_RECONF_CONF already in "
"progress", __func__);
if (IMSG_DATA_SIZE(imsg) !=
sizeof(struct dhcp6leased_conf))
fatalx("%s: IMSG_RECONF_CONF wrong length: %lu",
__func__, IMSG_DATA_SIZE(imsg));
if ((nconf = malloc(sizeof(struct dhcp6leased_conf))) ==
NULL)
fatal(NULL);
memcpy(nconf, imsg.data,
sizeof(struct dhcp6leased_conf));
SIMPLEQ_INIT(&nconf->iface_list);
break;
case IMSG_RECONF_IFACE:
if (IMSG_DATA_SIZE(imsg) != sizeof(struct
iface_conf))
fatalx("%s: IMSG_RECONF_IFACE wrong length: "
"%lu", __func__, IMSG_DATA_SIZE(imsg));
if ((iface_conf = malloc(sizeof(struct iface_conf)))
== NULL)
fatal(NULL);
memcpy(iface_conf, imsg.data, sizeof(struct
iface_conf));
if (iface_conf->name[sizeof(iface_conf->name) - 1]
!= '\0')
fatalx("%s: IMSG_RECONF_IFACE invalid name",
__func__);
SIMPLEQ_INIT(&iface_conf->iface_ia_list);
SIMPLEQ_INSERT_TAIL(&nconf->iface_list,
iface_conf, entry);
iface_conf->ia_count = 0;
break;
case IMSG_RECONF_IFACE_IA:
if (IMSG_DATA_SIZE(imsg) != sizeof(struct
iface_ia_conf))
fatalx("%s: IMSG_RECONF_IFACE_IA wrong "
"length: %lu", __func__,
IMSG_DATA_SIZE(imsg));
if ((iface_ia_conf =
malloc(sizeof(struct iface_ia_conf))) == NULL)
fatal(NULL);
memcpy(iface_ia_conf, imsg.data, sizeof(struct
iface_ia_conf));
SIMPLEQ_INIT(&iface_ia_conf->iface_pd_list);
SIMPLEQ_INSERT_TAIL(&iface_conf->iface_ia_list,
iface_ia_conf, entry);
iface_ia_conf->id = iface_conf->ia_count++;
if (iface_conf->ia_count > MAX_IA)
fatalx("Too many prefix delegation requests.");
break;
case IMSG_RECONF_IFACE_PD:
if (IMSG_DATA_SIZE(imsg) != sizeof(struct
iface_pd_conf))
fatalx("%s: IMSG_RECONF_IFACE_PD wrong length: "
"%lu", __func__, IMSG_DATA_SIZE(imsg));
if ((iface_pd_conf =
malloc(sizeof(struct iface_pd_conf))) == NULL)
fatal(NULL);
memcpy(iface_pd_conf, imsg.data, sizeof(struct
iface_pd_conf));
if (iface_pd_conf->name[sizeof(iface_pd_conf->name) - 1]
!= '\0')
fatalx("%s: IMSG_RECONF_IFACE_PD invalid name",
__func__);
SIMPLEQ_INSERT_TAIL(&iface_ia_conf->iface_pd_list,
iface_pd_conf, entry);
break;
case IMSG_RECONF_IFACE_IA_END:
iface_ia_conf = NULL;
break;
case IMSG_RECONF_IFACE_END:
iface_conf = NULL;
break;
case IMSG_RECONF_END: {
struct dhcp6leased_iface *iface;
int *ifaces;
int i, if_index;
char *if_name;
char ifnamebuf[IF_NAMESIZE];
if (nconf == NULL)
fatalx("%s: IMSG_RECONF_END without "
"IMSG_RECONF_CONF", __func__);
ifaces = changed_ifaces(engine_conf, nconf);
merge_config(engine_conf, nconf);
nconf = NULL;
for (i = 0; ifaces[i] != 0; i++) {
if_index = ifaces[i];
if_name = if_indextoname(if_index, ifnamebuf);
iface = get_dhcp6leased_iface_by_id(if_index);
if (if_name == NULL || iface == NULL)
continue;
iface_conf = find_iface_conf(
&engine_conf->iface_list, if_name);
if (iface_conf == NULL)
continue;
}
free(ifaces);
break;
}
default:
log_debug("%s: unexpected imsg %d", __func__,
imsg.hdr.type);
break;
}
imsg_free(&imsg);
}
if (!shut)
imsg_event_add(iev);
else {
event_del(&iev->ev);
event_loopexit(NULL);
}
}
void
send_interface_info(struct dhcp6leased_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.lease_time = iface->lease_time;
cei.t1 = iface->t1;
cei.t2 = iface->t2;
memcpy(&cei.pds, &iface->pds, sizeof(cei.pds));
engine_imsg_compose_frontend(IMSG_CTL_SHOW_INTERFACE_INFO, pid, &cei,
sizeof(cei));
}
void
engine_showinfo_ctl(struct imsg *imsg, uint32_t if_index)
{
struct dhcp6leased_iface *iface;
switch (imsg->hdr.type) {
case IMSG_CTL_SHOW_INTERFACE_INFO:
if ((iface = get_dhcp6leased_iface_by_id(if_index)) != NULL)
send_interface_info(iface, imsg->hdr.pid);
else
engine_imsg_compose_frontend(IMSG_CTL_END,
imsg->hdr.pid, NULL, 0);
break;
default:
log_debug("%s: error handling imsg", __func__);
break;
}
}
void
engine_update_iface(struct imsg_ifinfo *imsg_ifinfo)
{
struct dhcp6leased_iface *iface;
struct iface_conf *iface_conf;
int need_refresh = 0;
char ifnamebuf[IF_NAMESIZE], *if_name;
iface = get_dhcp6leased_iface_by_id(imsg_ifinfo->if_index);
if (iface == NULL) {
if ((iface = calloc(1, sizeof(*iface))) == NULL)
fatal("calloc");
iface->state = IF_DOWN;
arc4random_buf(iface->xid, sizeof(iface->xid));
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;
LIST_INSERT_HEAD(&dhcp6leased_interfaces, iface, entries);
need_refresh = 1;
} else {
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 ((if_name = if_indextoname(iface->if_index, ifnamebuf)) == NULL) {
log_debug("%s: unknown interface %d", __func__,
iface->if_index);
return;
}
if ((iface_conf = find_iface_conf(&engine_conf->iface_list, if_name))
== NULL) {
log_debug("%s: no interface configuration for %d", __func__,
iface->if_index);
return;
}
if (iface->running && LINK_STATE_IS_UP(iface->link_state)) {
uint32_t i;
int got_lease;
if (iface->pds[0].prefix_len == 0)
memcpy(iface->pds, imsg_ifinfo->pds,
sizeof(iface->pds));
got_lease = 0;
for (i = 0; i < iface_conf->ia_count; i++) {
if (iface->pds[i].prefix_len > 0) {
got_lease = 1;
break;
}
}
if (got_lease)
state_transition(iface, IF_REBOOTING);
else
state_transition(iface, IF_INIT);
} else
state_transition(iface, IF_DOWN);
}
struct dhcp6leased_iface*
get_dhcp6leased_iface_by_id(uint32_t if_index)
{
struct dhcp6leased_iface *iface;
LIST_FOREACH (iface, &dhcp6leased_interfaces, entries) {
if (iface->if_index == if_index)
return (iface);
}
return (NULL);
}
void
remove_dhcp6leased_iface(uint32_t if_index)
{
struct dhcp6leased_iface *iface;
iface = get_dhcp6leased_iface_by_id(if_index);
if (iface == NULL)
return;
deconfigure_interfaces(iface);
LIST_REMOVE(iface, entries);
evtimer_del(&iface->timer);
free(iface);
}
void
parse_dhcp(struct dhcp6leased_iface *iface, struct imsg_dhcp *dhcp)
{
struct iface_conf *iface_conf;
struct iface_ia_conf *ia_conf;
struct dhcp_hdr hdr;
struct dhcp_option_hdr opt_hdr;
struct dhcp_iapd iapd;
size_t rem;
uint32_t t1, t2, lease_time;
int serverid_len, rapid_commit = 0;
uint8_t serverid[SERVERID_SIZE];
uint8_t *p;
char ifnamebuf[IF_NAMESIZE], *if_name;
char ntopbuf[INET6_ADDRSTRLEN];
if ((if_name = if_indextoname(iface->if_index, ifnamebuf)) == NULL) {
log_debug("%s: unknown interface %d", __func__,
iface->if_index);
goto out;
}
if ((iface_conf = find_iface_conf(&engine_conf->iface_list, if_name))
== NULL) {
log_debug("%s: no interface configuration for %d", __func__,
iface->if_index);
goto out;
}
log_debug("%s: %s ia_count: %d", __func__, if_name,
iface_conf->ia_count);
serverid_len = t1 = t2 = lease_time = 0;
memset(iface->new_pds, 0, sizeof(iface->new_pds));
p = dhcp->packet;
rem = dhcp->len;
if (rem < sizeof(struct dhcp_hdr)) {
log_warnx("%s: message too short", __func__);
goto out;
}
memcpy(&hdr, p, sizeof(struct dhcp_hdr));
p += sizeof(struct dhcp_hdr);
rem -= sizeof(struct dhcp_hdr);
if (log_getverbose() > 1)
log_debug("%s: %s, xid: 0x%02x%02x%02x", __func__,
dhcp_message_type2str(hdr.msg_type), hdr.xid[0], hdr.xid[1],
hdr.xid[2]);
while (rem >= sizeof(struct dhcp_option_hdr)) {
memcpy(&opt_hdr, p, sizeof(struct dhcp_option_hdr));
opt_hdr.code = ntohs(opt_hdr.code);
opt_hdr.len = ntohs(opt_hdr.len);
p += sizeof(struct dhcp_option_hdr);
rem -= sizeof(struct dhcp_option_hdr);
if (log_getverbose() > 1)
log_debug("%s: %s, len: %u", __func__,
dhcp_option_type2str(opt_hdr.code), opt_hdr.len);
if (rem < opt_hdr.len) {
log_warnx("%s: malformed packet, ignoring", __func__);
goto out;
}
switch (opt_hdr.code) {
case DHO_CLIENTID:
if (opt_hdr.len != sizeof(struct dhcp_duid) ||
memcmp(&duid, p, sizeof(struct dhcp_duid)) != 0) {
log_debug("%s: message not for us", __func__);
goto out;
}
break;
case DHO_SERVERID:
if (opt_hdr.len < 2 + 1) {
log_warnx("%s: SERVERID too short", __func__);
goto out;
}
if (opt_hdr.len > SERVERID_SIZE) {
log_warnx("%s: SERVERID too long", __func__);
goto out;
}
log_debug("%s: SERVERID: %s", __func__,
dhcp_duid2str(opt_hdr.len, p));
if (serverid_len != 0) {
log_warnx("%s: duplicate SERVERID option",
__func__);
goto out;
}
serverid_len = opt_hdr.len;
memcpy(serverid, p, serverid_len);
break;
case DHO_IA_PD:
if (opt_hdr.len < sizeof(struct dhcp_iapd)) {
log_warnx("%s: IA_PD too short", __func__);
goto out;
}
memcpy(&iapd, p, sizeof(struct dhcp_iapd));
if (t1 == 0 || t1 > ntohl(iapd.t1))
t1 = ntohl(iapd.t1);
if (t2 == 0 || t2 > ntohl(iapd.t2))
t2 = ntohl(iapd.t2);
log_debug("%s: IA_PD, IAID: %08x, T1: %u, T2: %u",
__func__, ntohl(iapd.iaid), ntohl(iapd.t1),
ntohl(iapd.t2));
if (ntohl(iapd.iaid) < iface_conf->ia_count) {
int status_code;
status_code = parse_ia_pd_options(p +
sizeof(struct dhcp_iapd), opt_hdr.len -
sizeof(struct dhcp_iapd),
&iface->new_pds[ntohl(iapd.iaid)]);
if (status_code != DHCP_STATUS_SUCCESS &&
iface->state == IF_RENEWING) {
state_transition(iface, IF_REBINDING);
goto out;
}
}
break;
case DHO_RAPID_COMMIT:
if (opt_hdr.len != 0) {
log_warnx("%s: invalid rapid commit option",
__func__);
goto out;
}
rapid_commit = 1;
break;
default:
log_debug("unhandled option: %u", opt_hdr.code);
break;
}
p += opt_hdr.len;
rem -= opt_hdr.len;
}
if (serverid_len == 0) {
log_warnx("%s: Did not receive server identifier", __func__);
goto out;
}
SIMPLEQ_FOREACH(ia_conf, &iface_conf->iface_ia_list, entry) {
struct prefix *pd = &iface->new_pds[ia_conf->id];
if (pd->prefix_len == 0) {
log_warnx("%s: no IA for IAID %d found", __func__,
ia_conf->id);
goto out;
}
if (pd->prefix_len > ia_conf->prefix_len) {
log_warnx("%s: prefix for IAID %d too small: %d > %d",
__func__, ia_conf->id, pd->prefix_len,
ia_conf->prefix_len);
goto out;
}
if (lease_time == 0 || lease_time > pd->vltime)
lease_time = pd->vltime;
log_debug("%s: pltime: %u, vltime: %u, prefix: %s/%u",
__func__, pd->pltime, pd->vltime, inet_ntop(AF_INET6,
&pd->prefix, ntopbuf, INET6_ADDRSTRLEN), pd->prefix_len);
}
switch (hdr.msg_type) {
case DHCPSOLICIT:
case DHCPREQUEST:
case DHCPCONFIRM:
case DHCPRENEW:
case DHCPREBIND:
case DHCPRELEASE:
case DHCPDECLINE:
case DHCPINFORMATIONREQUEST:
log_warnx("%s: Ignoring client-only message (%s) from server",
__func__, dhcp_message_type2str(hdr.msg_type));
goto out;
case DHCPRELAYFORW:
case DHCPRELAYREPL:
log_warnx("%s: Ignoring relay-agent-only message (%s) from "
"server", __func__, dhcp_message_type2str(hdr.msg_type));
goto out;
case DHCPADVERTISE:
if (iface->state != IF_INIT) {
log_debug("%s: ignoring unexpected %s", __func__,
dhcp_message_type2str(hdr.msg_type));
goto out;
}
iface->serverid_len = serverid_len;
memcpy(iface->serverid, serverid, SERVERID_SIZE);
memcpy(iface->pds, iface->new_pds, sizeof(iface->pds));
state_transition(iface, IF_REQUESTING);
break;
case DHCPREPLY:
switch (iface->state) {
case IF_REQUESTING:
case IF_RENEWING:
case IF_REBINDING:
case IF_REBOOTING:
break;
case IF_INIT:
if (rapid_commit && engine_conf->rapid_commit)
break;
default:
log_debug("%s: ignoring unexpected %s", __func__,
dhcp_message_type2str(hdr.msg_type));
goto out;
}
iface->serverid_len = serverid_len;
memcpy(iface->serverid, serverid, SERVERID_SIZE);
if (t1 == 0)
iface->t1 = lease_time / 2;
else
iface->t1 = t1;
if (t2 == 0)
iface->t2 = lease_time - (lease_time / 8);
else
iface->t2 = t2;
iface->lease_time = lease_time;
clock_gettime(CLOCK_MONOTONIC, &iface->request_time);
state_transition(iface, IF_BOUND);
break;
case DHCPRECONFIGURE:
log_warnx("%s: Ignoring %s from server",
__func__, dhcp_message_type2str(hdr.msg_type));
goto out;
default:
fatalx("%s: %s unhandled",
__func__, dhcp_message_type2str(hdr.msg_type));
break;
}
out:
return;
}
int
parse_ia_pd_options(uint8_t *p, size_t len, struct prefix *prefix)
{
struct dhcp_option_hdr opt_hdr;
struct dhcp_iaprefix iaprefix;
struct in6_addr mask;
int i;
uint16_t status_code = DHCP_STATUS_SUCCESS;
char ntopbuf[INET6_ADDRSTRLEN], *visbuf;
while (len >= sizeof(struct dhcp_option_hdr)) {
memcpy(&opt_hdr, p, sizeof(struct dhcp_option_hdr));
opt_hdr.code = ntohs(opt_hdr.code);
opt_hdr.len = ntohs(opt_hdr.len);
p += sizeof(struct dhcp_option_hdr);
len -= sizeof(struct dhcp_option_hdr);
if (log_getverbose() > 1)
log_debug("%s: %s, len: %u", __func__,
dhcp_option_type2str(opt_hdr.code), opt_hdr.len);
if (len < opt_hdr.len) {
log_warnx("%s: malformed packet, ignoring", __func__);
return DHCP_STATUS_UNSPECFAIL;
}
switch (opt_hdr.code) {
case DHO_IA_PREFIX:
if (len < sizeof(struct dhcp_iaprefix)) {
log_warnx("%s: malformed packet, ignoring",
__func__);
return DHCP_STATUS_UNSPECFAIL;
}
memcpy(&iaprefix, p, sizeof(struct dhcp_iaprefix));
log_debug("%s: pltime: %u, vltime: %u, prefix: %s/%u",
__func__, ntohl(iaprefix.pltime),
ntohl(iaprefix.vltime), inet_ntop(AF_INET6,
&iaprefix.prefix, ntopbuf, INET6_ADDRSTRLEN),
iaprefix.prefix_len);
if (ntohl(iaprefix.vltime) < ntohl(iaprefix.pltime)) {
log_warnx("%s: vltime < pltime, ignoring IA_PD",
__func__);
break;
}
if (ntohl(iaprefix.vltime) == 0) {
log_debug("%s: vltime == 0, ignoring IA_PD",
__func__);
break;
}
prefix->prefix = iaprefix.prefix;
prefix->prefix_len = iaprefix.prefix_len;
prefix->vltime = ntohl(iaprefix.vltime);
prefix->pltime = ntohl(iaprefix.pltime);
memset(&mask, 0, sizeof(mask));
in6_prefixlen2mask(&mask, prefix->prefix_len);
for (i = 0; i < 16; i++)
prefix->prefix.s6_addr[i] &= mask.s6_addr[i];
break;
case DHO_STATUS_CODE:
if (len < 2) {
log_warnx("%s: malformed packet, ignoring",
__func__);
return DHCP_STATUS_UNSPECFAIL;
}
memcpy(&status_code, p, sizeof(uint16_t));
status_code = ntohs(status_code);
visbuf = calloc(4, opt_hdr.len - 2 + 1);
if (visbuf == NULL) {
log_warn("%s", __func__);
break;
}
strvisx(visbuf, p + 2, opt_hdr.len - 2, VIS_SAFE);
log_debug("%s: %s - %s", __func__,
dhcp_status2str(status_code), visbuf);
break;
default:
log_debug("unhandled option: %u", opt_hdr.code);
}
p += opt_hdr.len;
len -= opt_hdr.len;
}
return status_code;
}
void
state_transition(struct dhcp6leased_iface *iface, enum if_state new_state)
{
enum if_state old_state = iface->state;
char ifnamebuf[IF_NAMESIZE], *if_name;
iface->state = new_state;
switch (new_state) {
case IF_DOWN:
switch (old_state) {
case IF_RENEWING:
case IF_REBINDING:
case IF_REBOOTING:
case IF_BOUND:
deprecate_interfaces(iface);
break;
default:
break;
}
iface->timo.tv_sec = -1;
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:
deconfigure_interfaces(iface);
case IF_DOWN:
iface->timo.tv_sec = START_EXP_BACKOFF;
clock_gettime(CLOCK_MONOTONIC,
&iface->elapsed_time_start);
break;
case IF_BOUND:
fatal("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;
arc4random_buf(iface->xid, sizeof(iface->xid));
}
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;
clock_gettime(CLOCK_MONOTONIC,
&iface->elapsed_time_start);
}
request_dhcp_request(iface);
break;
case IF_BOUND:
iface->timo.tv_sec = iface->t1;
switch (old_state) {
case IF_REQUESTING:
case IF_RENEWING:
case IF_REBINDING:
case IF_REBOOTING:
configure_interfaces(iface);
break;
case IF_INIT:
if (engine_conf->rapid_commit)
configure_interfaces(iface);
else
fatal("invalid transition Init -> Bound");
break;
default:
break;
}
break;
case IF_RENEWING:
if (old_state == IF_BOUND) {
iface->timo.tv_sec = (iface->t2 -
iface->t1) / 2;
arc4random_buf(iface->xid, sizeof(iface->xid));
} 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->t2) / 2;
} else
iface->timo.tv_sec /= 2;
request_dhcp_request(iface);
break;
}
if_name = if_indextoname(iface->if_index, ifnamebuf);
log_debug("%s[%s] %s -> %s, timo: %lld", __func__, if_name == NULL ?
"?" : 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 dhcp6leased_iface *iface = (struct dhcp6leased_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, t2: %u", __func__,
res.tv_sec, iface->t2);
if (res.tv_sec >= iface->t2)
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;
}
}
void
request_dhcp_discover(struct dhcp6leased_iface *iface)
{
struct imsg_req_dhcp imsg;
struct timespec now, res;
memset(&imsg, 0, sizeof(imsg));
imsg.if_index = iface->if_index;
memcpy(imsg.xid, iface->xid, sizeof(imsg.xid));
clock_gettime(CLOCK_MONOTONIC, &now);
timespecsub(&now, &iface->elapsed_time_start, &res);
if (res.tv_sec * 100 > 0xffff)
imsg.elapsed_time = 0xffff;
else
imsg.elapsed_time = res.tv_sec * 100;
engine_imsg_compose_frontend(IMSG_SEND_SOLICIT, 0, &imsg, sizeof(imsg));
}
void
request_dhcp_request(struct dhcp6leased_iface *iface)
{
struct imsg_req_dhcp imsg;
struct timespec now, res;
memset(&imsg, 0, sizeof(imsg));
imsg.if_index = iface->if_index;
memcpy(imsg.xid, iface->xid, sizeof(imsg.xid));
clock_gettime(CLOCK_MONOTONIC, &now);
timespecsub(&now, &iface->elapsed_time_start, &res);
if (res.tv_sec * 100 > 0xffff)
imsg.elapsed_time = 0xffff;
else
imsg.elapsed_time = res.tv_sec * 100;
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:
case IF_REQUESTING:
case IF_RENEWING:
case IF_REBINDING:
imsg.serverid_len = iface->serverid_len;
memcpy(imsg.serverid, iface->serverid, SERVERID_SIZE);
memcpy(imsg.pds, iface->pds, sizeof(iface->pds));
break;
}
switch (iface->state) {
case IF_REQUESTING:
engine_imsg_compose_frontend(IMSG_SEND_REQUEST, 0, &imsg,
sizeof(imsg));
break;
case IF_RENEWING:
engine_imsg_compose_frontend(IMSG_SEND_RENEW, 0, &imsg,
sizeof(imsg));
break;
case IF_REBOOTING:
case IF_REBINDING:
engine_imsg_compose_frontend(IMSG_SEND_REBIND, 0, &imsg,
sizeof(imsg));
break;
default:
fatalx("%s: wrong state", __func__);
}
}
void
configure_interfaces(struct dhcp6leased_iface *iface)
{
struct iface_conf *iface_conf;
struct iface_ia_conf *ia_conf;
struct iface_pd_conf *pd_conf;
struct imsg_lease_info imsg_lease_info;
uint32_t i;
char ntopbuf[INET6_ADDRSTRLEN];
char ifnamebuf[IF_NAMESIZE], *if_name;
if ((if_name = if_indextoname(iface->if_index, ifnamebuf)) == NULL) {
log_debug("%s: unknown interface %d", __func__,
iface->if_index);
return;
}
if ((iface_conf = find_iface_conf(&engine_conf->iface_list, if_name))
== NULL) {
log_debug("%s: no interface configuration for %d", __func__,
iface->if_index);
return;
}
for (i = 0; i < iface_conf->ia_count; i++) {
struct prefix *pd = &iface->new_pds[i];
log_info("prefix delegation #%d %s/%d received on %s from "
"server %s", i, inet_ntop(AF_INET6, &pd->prefix, ntopbuf,
INET6_ADDRSTRLEN), pd->prefix_len, if_name,
dhcp_duid2str(iface->serverid_len, iface->serverid));
send_reconfigure_reject_route(iface, &pd->prefix,
pd->prefix_len, CONFIGURE);
}
SIMPLEQ_FOREACH(ia_conf, &iface_conf->iface_ia_list, entry) {
struct prefix *pd = &iface->new_pds[ia_conf->id];
SIMPLEQ_FOREACH(pd_conf, &ia_conf->iface_pd_list, entry) {
send_reconfigure_interface(pd_conf, pd, CONFIGURE);
}
}
if (prefixcmp(iface->pds, iface->new_pds, iface_conf->ia_count) != 0) {
log_info("Prefix delegations on %s from server %s changed",
if_name, dhcp_duid2str(iface->serverid_len,
iface->serverid));
for (i = 0; i < iface_conf->ia_count; i++) {
log_debug("%s: iface->pds [%d]: %s/%d", __func__, i,
inet_ntop(AF_INET6, &iface->pds[i].prefix, ntopbuf,
INET6_ADDRSTRLEN), iface->pds[i].prefix_len);
log_debug("%s: pds [%d]: %s/%d", __func__, i,
inet_ntop(AF_INET6, &iface->new_pds[i].prefix,
ntopbuf, INET6_ADDRSTRLEN),
iface->new_pds[i].prefix_len);
}
deconfigure_interfaces(iface);
}
memcpy(iface->pds, iface->new_pds, sizeof(iface->pds));
memset(iface->new_pds, 0, sizeof(iface->new_pds));
memset(&imsg_lease_info, 0, sizeof(imsg_lease_info));
imsg_lease_info.if_index = iface->if_index;
memcpy(imsg_lease_info.pds, iface->pds, sizeof(iface->pds));
engine_imsg_compose_main(IMSG_WRITE_LEASE, 0, &imsg_lease_info,
sizeof(imsg_lease_info));
}
void
deconfigure_interfaces(struct dhcp6leased_iface *iface)
{
struct iface_conf *iface_conf;
struct iface_ia_conf *ia_conf;
struct iface_pd_conf *pd_conf;
uint32_t i;
char ntopbuf[INET6_ADDRSTRLEN];
char ifnamebuf[IF_NAMESIZE], *if_name;
if ((if_name = if_indextoname(iface->if_index, ifnamebuf)) == NULL) {
log_debug("%s: unknown interface %d", __func__,
iface->if_index);
return;
}
if ((iface_conf = find_iface_conf(&engine_conf->iface_list, if_name))
== NULL) {
log_debug("%s: no interface configuration for %d", __func__,
iface->if_index);
return;
}
for (i = 0; i < iface_conf->ia_count; i++) {
struct prefix *pd = &iface->pds[i];
log_info("Prefix delegation #%d %s/%d expired on %s from "
"server %s", i, inet_ntop(AF_INET6, &pd->prefix, ntopbuf,
INET6_ADDRSTRLEN), pd->prefix_len, if_name,
dhcp_duid2str(iface->serverid_len, iface->serverid));
send_reconfigure_reject_route(iface, &pd->prefix,
pd->prefix_len, DECONFIGURE);
}
SIMPLEQ_FOREACH(ia_conf, &iface_conf->iface_ia_list, entry) {
struct prefix *pd = &iface->pds[ia_conf->id];
SIMPLEQ_FOREACH(pd_conf, &ia_conf->iface_pd_list, entry) {
send_reconfigure_interface(pd_conf, pd, DECONFIGURE);
}
}
memset(iface->pds, 0, sizeof(iface->pds));
}
void
deprecate_interfaces(struct dhcp6leased_iface *iface)
{
struct iface_conf *iface_conf;
struct iface_ia_conf *ia_conf;
struct iface_pd_conf *pd_conf;
struct timespec now, diff;
uint32_t i;
char ntopbuf[INET6_ADDRSTRLEN];
char ifnamebuf[IF_NAMESIZE], *if_name;
if ((if_name = if_indextoname(iface->if_index, ifnamebuf)) == NULL) {
log_debug("%s: unknown interface %d", __func__,
iface->if_index);
return;
}
if ((iface_conf = find_iface_conf(&engine_conf->iface_list, if_name))
== NULL) {
log_debug("%s: no interface configuration for %d", __func__,
iface->if_index);
return;
}
for (i = 0; i < iface_conf->ia_count; i++) {
struct prefix *pd = &iface->pds[i];
log_info("%s went down, deprecating prefix delegation #%d %s/%d"
" from server %s", if_name, i, inet_ntop(AF_INET6,
&pd->prefix, ntopbuf, INET6_ADDRSTRLEN), pd->prefix_len,
dhcp_duid2str(iface->serverid_len, iface->serverid));
}
clock_gettime(CLOCK_MONOTONIC, &now);
timespecsub(&now, &iface->request_time, &diff);
SIMPLEQ_FOREACH(ia_conf, &iface_conf->iface_ia_list, entry) {
struct prefix *pd = &iface->pds[ia_conf->id];
if (pd->vltime > diff.tv_sec)
pd->vltime -= diff.tv_sec;
else
pd->vltime = 0;
pd->pltime = 0;
SIMPLEQ_FOREACH(pd_conf, &ia_conf->iface_pd_list, entry) {
send_reconfigure_interface(pd_conf, pd, CONFIGURE);
}
}
}
int
prefixcmp(struct prefix *a, struct prefix *b, int count)
{
int i;
for (i = 0; i < count; i++) {
if (a[i].prefix_len != b[i].prefix_len)
return 1;
if (memcmp(&a[i].prefix, &b[i].prefix,
sizeof(struct in6_addr)) != 0)
return 1;
}
return 0;
}
void
send_reconfigure_interface(struct iface_pd_conf *pd_conf, struct prefix *pd,
enum reconfigure_action action)
{
struct imsg_configure_address address;
uint32_t if_index;
int i;
char ntopbuf[INET6_ADDRSTRLEN];
if (pd->prefix_len == 0)
return;
if (strcmp(pd_conf->name, "reserve") == 0)
return;
if ((if_index = if_nametoindex(pd_conf->name)) == 0)
return;
memset(&address, 0, sizeof(address));
address.if_index = if_index;
address.addr.sin6_family = AF_INET6;
address.addr.sin6_len = sizeof(address.addr);
address.addr.sin6_addr = pd->prefix;
for (i = 0; i < 16; i++)
address.addr.sin6_addr.s6_addr[i] |=
pd_conf->prefix_mask.s6_addr[i];
address.addr.sin6_addr.s6_addr[15] |= 1;
in6_prefixlen2mask(&address.mask, pd_conf->prefix_len);
log_debug("%s: %s %s: %s/%d", __func__, pd_conf->name,
reconfigure_action_name[action], inet_ntop(AF_INET6,
&address.addr.sin6_addr, ntopbuf, INET6_ADDRSTRLEN),
pd_conf->prefix_len);
address.vltime = pd->vltime;
address.pltime = pd->pltime;
if (action == CONFIGURE)
engine_imsg_compose_main(IMSG_CONFIGURE_ADDRESS, 0, &address,
sizeof(address));
else
engine_imsg_compose_main(IMSG_DECONFIGURE_ADDRESS, 0, &address,
sizeof(address));
}
void
send_reconfigure_reject_route(struct dhcp6leased_iface *iface,
struct in6_addr *prefix, uint8_t prefix_len, enum reconfigure_action action)
{
struct imsg_configure_reject_route imsg;
memset(&imsg, 0, sizeof(imsg));
imsg.if_index = iface->if_index;
imsg.rdomain = iface->rdomain;
memcpy(&imsg.prefix, prefix, sizeof(imsg.prefix));
in6_prefixlen2mask(&imsg.mask, prefix_len);
if (action == CONFIGURE)
engine_imsg_compose_main(IMSG_CONFIGURE_REJECT_ROUTE, 0, &imsg,
sizeof(imsg));
else
engine_imsg_compose_main(IMSG_DECONFIGURE_REJECT_ROUTE, 0,
&imsg, sizeof(imsg));
}
const char *
dhcp_message_type2str(int type)
{
static char buf[sizeof("Unknown [255]")];
switch (type) {
case DHCPSOLICIT:
return "DHCPSOLICIT";
case DHCPADVERTISE:
return "DHCPADVERTISE";
case DHCPREQUEST:
return "DHCPREQUEST";
case DHCPCONFIRM:
return "DHCPCONFIRM";
case DHCPRENEW:
return "DHCPRENEW";
case DHCPREBIND:
return "DHCPREBIND";
case DHCPREPLY:
return "DHCPREPLY";
case DHCPRELEASE:
return "DHCPRELEASE";
case DHCPDECLINE:
return "DHCPDECLINE";
case DHCPRECONFIGURE:
return "DHCPRECONFIGURE";
case DHCPINFORMATIONREQUEST:
return "DHCPINFORMATIONREQUEST";
case DHCPRELAYFORW:
return "DHCPRELAYFORW";
case DHCPRELAYREPL:
return "DHCPRELAYREPL";
default:
snprintf(buf, sizeof(buf), "Unknown [%u]", type & 0xff);
return buf;
}
}
const char *
dhcp_option_type2str(int code)
{
static char buf[sizeof("Unknown [65535]")];
switch (code) {
case DHO_CLIENTID:
return "DHO_CLIENTID";
case DHO_SERVERID:
return "DHO_SERVERID";
case DHO_ORO:
return "DHO_ORO";
case DHO_ELAPSED_TIME:
return "DHO_ELAPSED_TIME";
case DHO_STATUS_CODE:
return "DHO_STATUS_CODE";
case DHO_RAPID_COMMIT:
return "DHO_RAPID_COMMIT";
case DHO_VENDOR_CLASS:
return "DHO_VENDOR_CLASS";
case DHO_IA_PD:
return "DHO_IA_PD";
case DHO_IA_PREFIX:
return "DHO_IA_PREFIX";
case DHO_SOL_MAX_RT:
return "DHO_SOL_MAX_RT";
case DHO_INF_MAX_RT:
return "DHO_INF_MAX_RT";
default:
snprintf(buf, sizeof(buf), "Unknown [%u]", code &0xffff);
return buf;
}
}
const char*
dhcp_duid2str(int len, uint8_t *p)
{
static char buf[2 * 130];
int i, rem;
char *pbuf;
if (len > 130)
return "invalid";
pbuf = buf;
rem = sizeof(buf);
for (i = 0; i < len && rem > 0; i++, pbuf += 2, rem -=2)
snprintf(pbuf, rem, "%02x", p[i]);
return buf;
}
const char*
dhcp_status2str(int status)
{
static char buf[sizeof("Unknown [255]")];
switch (status) {
case DHCP_STATUS_SUCCESS:
return "Success";
case DHCP_STATUS_UNSPECFAIL:
return "UnspecFail";
case DHCP_STATUS_NOADDRSAVAIL:
return "NoAddrsAvail";
case DHCP_STATUS_NOBINDING:
return "NoBinding";
case DHCP_STATUS_NOTONLINK:
return "NotOnLink";
case DHCP_STATUS_USEMULTICAST:
return "UseMulticast";
case DHCP_STATUS_NOPREFIXAVAIL:
return "NoPrefixAvail";
default:
snprintf(buf, sizeof(buf), "Unknown [%u]", status & 0xff);
return buf;
}
}
void
in6_prefixlen2mask(struct in6_addr *maskp, int len)
{
u_char maskarray[8] = {0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff};
int bytelen, bitlen, i;
if (0 > len || len > 128)
fatalx("%s: invalid prefix length(%d)\n", __func__, len);
bzero(maskp, sizeof(*maskp));
bytelen = len / 8;
bitlen = len % 8;
for (i = 0; i < bytelen; i++)
maskp->s6_addr[i] = 0xff;
if (bitlen)
maskp->s6_addr[bytelen] = maskarray[bitlen - 1];
}