2928 lines
74 KiB
C
2928 lines
74 KiB
C
/* $OpenBSD: dhclient.c,v 1.728 2024/04/28 16:43:42 florian Exp $ */
|
|
|
|
/*
|
|
* Copyright 2004 Henning Brauer <henning@openbsd.org>
|
|
* Copyright (c) 1995, 1996, 1997, 1998, 1999
|
|
* The Internet Software Consortium. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. 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.
|
|
* 3. Neither the name of The Internet Software Consortium nor the names
|
|
* of its contributors may be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM 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.
|
|
*
|
|
* This software has been written for the Internet Software Consortium
|
|
* by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
|
|
* Enterprises. To learn more about the Internet Software Consortium,
|
|
* see ``http://www.vix.com/isc''. To learn more about Vixie
|
|
* Enterprises, see ``http://www.vix.com''.
|
|
*
|
|
* This client was substantially modified and enhanced by Elliot Poger
|
|
* for use on Linux while he was working on the MosquitoNet project at
|
|
* Stanford.
|
|
*
|
|
* The current version owes much to Elliot's Linux enhancements, but
|
|
* was substantially reorganized and partially rewritten by Ted Lemon
|
|
* so as to use the same networking framework that the Internet Software
|
|
* Consortium DHCP server uses. Much system-specific configuration code
|
|
* was moved into a shell script so that as support for more operating
|
|
* systems is added, it will not be necessary to port and maintain
|
|
* system-specific configuration code to these operating systems - instead,
|
|
* the shell script can invoke the native tools to accomplish the same
|
|
* purpose.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/uio.h>
|
|
#include <sys/queue.h>
|
|
|
|
#include <net/if.h>
|
|
#include <net/if_types.h>
|
|
#include <net/if_dl.h>
|
|
#include <net/route.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include <netinet/if_ether.h>
|
|
|
|
#include <net80211/ieee80211.h>
|
|
#include <net80211/ieee80211_ioctl.h>
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <ifaddrs.h>
|
|
#include <imsg.h>
|
|
#include <limits.h>
|
|
#include <paths.h>
|
|
#include <poll.h>
|
|
#include <pwd.h>
|
|
#include <resolv.h>
|
|
#include <signal.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <syslog.h>
|
|
#include <unistd.h>
|
|
|
|
#include "dhcp.h"
|
|
#include "dhcpd.h"
|
|
#include "log.h"
|
|
#include "privsep.h"
|
|
|
|
char *path_dhclient_conf;
|
|
char *path_lease_db;
|
|
char *log_procname;
|
|
|
|
int nullfd = -1;
|
|
int cmd_opts;
|
|
int quit;
|
|
|
|
const struct in_addr inaddr_any = { INADDR_ANY };
|
|
const struct in_addr inaddr_broadcast = { INADDR_BROADCAST };
|
|
|
|
struct client_config *config;
|
|
struct imsgbuf *unpriv_ibuf;
|
|
|
|
void usage(void);
|
|
int res_hnok_list(const char *);
|
|
int addressinuse(char *, struct in_addr, char *);
|
|
|
|
void fork_privchld(struct interface_info *, int, int);
|
|
void get_name(struct interface_info *, int, char *);
|
|
void get_ssid(struct interface_info *, int);
|
|
void get_sockets(struct interface_info *);
|
|
int get_routefd(int);
|
|
void set_iff_up(struct interface_info *, int);
|
|
void set_user(char *);
|
|
int get_ifa_family(char *, int);
|
|
struct ifaddrs *get_link_ifa(const char *, struct ifaddrs *);
|
|
void interface_state(struct interface_info *);
|
|
struct interface_info *initialize_interface(char *, int);
|
|
void tick_msg(const char *, int);
|
|
void rtm_dispatch(struct interface_info *, struct rt_msghdr *);
|
|
|
|
struct client_lease *apply_defaults(struct client_lease *);
|
|
struct client_lease *clone_lease(struct client_lease *);
|
|
|
|
void state_reboot(struct interface_info *);
|
|
void state_init(struct interface_info *);
|
|
void state_selecting(struct interface_info *);
|
|
void state_bound(struct interface_info *);
|
|
void state_panic(struct interface_info *);
|
|
|
|
void set_interval(struct interface_info *, struct timespec *);
|
|
void set_resend_timeout(struct interface_info *, struct timespec *,
|
|
void (*where)(struct interface_info *));
|
|
void set_secs(struct interface_info *, struct timespec *);
|
|
|
|
void send_discover(struct interface_info *);
|
|
void send_request(struct interface_info *);
|
|
void send_decline(struct interface_info *);
|
|
void send_release(struct interface_info *);
|
|
|
|
void process_offer(struct interface_info *, struct option_data *,
|
|
const char *);
|
|
void bind_lease(struct interface_info *);
|
|
|
|
void make_discover(struct interface_info *, struct client_lease *);
|
|
void make_request(struct interface_info *, struct client_lease *);
|
|
void make_decline(struct interface_info *, struct client_lease *);
|
|
void make_release(struct interface_info *, struct client_lease *);
|
|
|
|
void release_lease(struct interface_info *);
|
|
void propose_release(struct interface_info *);
|
|
|
|
void write_lease_db(struct interface_info *);
|
|
char *lease_as_string(char *, struct client_lease *);
|
|
struct proposal *lease_as_proposal(struct client_lease *);
|
|
struct unwind_info *lease_as_unwind_info(struct client_lease *);
|
|
void append_statement(char *, size_t, char *, char *);
|
|
time_t lease_expiry(struct client_lease *);
|
|
time_t lease_renewal(struct client_lease *);
|
|
time_t lease_rebind(struct client_lease *);
|
|
void get_lease_timeouts(struct interface_info *, struct client_lease *);
|
|
|
|
struct client_lease *packet_to_lease(struct interface_info *,
|
|
struct option_data *);
|
|
void go_daemon(void);
|
|
int rdaemon(int);
|
|
int take_charge(struct interface_info *, int, char *);
|
|
int autoconf(struct interface_info *);
|
|
struct client_lease *get_recorded_lease(struct interface_info *);
|
|
|
|
#define ROUNDUP(a) \
|
|
((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
|
|
#define ADVANCE(x, n) (x += ROUNDUP((n)->sa_len))
|
|
|
|
#define TICK_WAIT 0
|
|
#define TICK_SUCCESS 1
|
|
#define TICK_DAEMON 2
|
|
|
|
static FILE *leaseFile;
|
|
|
|
int
|
|
get_ifa_family(char *cp, int n)
|
|
{
|
|
struct sockaddr *sa;
|
|
unsigned int i;
|
|
|
|
for (i = 1; i; i <<= 1) {
|
|
if ((i & n) != 0) {
|
|
sa = (struct sockaddr *)cp;
|
|
if (i == RTA_IFA)
|
|
return sa->sa_family;
|
|
ADVANCE(cp, sa);
|
|
}
|
|
}
|
|
|
|
return AF_UNSPEC;
|
|
}
|
|
|
|
struct ifaddrs *
|
|
get_link_ifa(const char *name, struct ifaddrs *ifap)
|
|
{
|
|
struct ifaddrs *ifa;
|
|
struct sockaddr_dl *sdl;
|
|
|
|
for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
|
|
if (strcmp(name, ifa->ifa_name) == 0 &&
|
|
(ifa->ifa_flags & IFF_LOOPBACK) == 0 &&
|
|
(ifa->ifa_flags & IFF_POINTOPOINT) == 0 &&
|
|
ifa->ifa_data != NULL && /* NULL shouldn't be possible. */
|
|
ifa->ifa_addr != NULL &&
|
|
ifa->ifa_addr->sa_family == AF_LINK) {
|
|
sdl = (struct sockaddr_dl *)ifa->ifa_addr;
|
|
if (sdl->sdl_alen == ETHER_ADDR_LEN &&
|
|
(sdl->sdl_type == IFT_ETHER ||
|
|
sdl->sdl_type == IFT_CARP))
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ifa == NULL)
|
|
fatal("get_link_ifa()");
|
|
|
|
return ifa;
|
|
}
|
|
|
|
void
|
|
interface_state(struct interface_info *ifi)
|
|
{
|
|
struct ifaddrs *ifap, *ifa;
|
|
struct if_data *ifd;
|
|
struct sockaddr_dl *sdl;
|
|
char *oldlladdr;
|
|
int newlinkup, oldlinkup;
|
|
|
|
oldlinkup = LINK_STATE_IS_UP(ifi->link_state);
|
|
|
|
if (getifaddrs(&ifap) == -1)
|
|
fatal("getifaddrs");
|
|
|
|
ifa = get_link_ifa(ifi->name, ifap);
|
|
sdl = (struct sockaddr_dl *)ifa->ifa_addr;
|
|
ifd = (struct if_data *)ifa->ifa_data;
|
|
|
|
if ((ifa->ifa_flags & IFF_UP) == 0 ||
|
|
(ifa->ifa_flags & IFF_RUNNING) == 0) {
|
|
ifi->link_state = LINK_STATE_DOWN;
|
|
} else {
|
|
ifi->link_state = ifd->ifi_link_state;
|
|
ifi->mtu = ifd->ifi_mtu;
|
|
}
|
|
|
|
newlinkup = LINK_STATE_IS_UP(ifi->link_state);
|
|
if (newlinkup != oldlinkup) {
|
|
log_debug("%s: link %s -> %s", log_procname,
|
|
(oldlinkup != 0) ? "up" : "down",
|
|
(newlinkup != 0) ? "up" : "down");
|
|
tick_msg("link", newlinkup ? TICK_SUCCESS : TICK_WAIT);
|
|
}
|
|
|
|
if (memcmp(&ifi->hw_address, LLADDR(sdl), ETHER_ADDR_LEN) != 0) {
|
|
if (log_getverbose()) {
|
|
oldlladdr = strdup(ether_ntoa(&ifi->hw_address));
|
|
if (oldlladdr == NULL)
|
|
fatal("oldlladdr");
|
|
log_debug("%s: LLADDR %s -> %s", log_procname,
|
|
oldlladdr,
|
|
ether_ntoa((struct ether_addr *)LLADDR(sdl)));
|
|
free(oldlladdr);
|
|
}
|
|
memcpy(&ifi->hw_address, LLADDR(sdl), ETHER_ADDR_LEN);
|
|
quit = RESTART; /* Even if MTU has changed. */
|
|
}
|
|
|
|
freeifaddrs(ifap);
|
|
}
|
|
|
|
struct interface_info *
|
|
initialize_interface(char *name, int noaction)
|
|
{
|
|
struct interface_info *ifi;
|
|
struct ifaddrs *ifap, *ifa;
|
|
struct if_data *ifd;
|
|
struct sockaddr_dl *sdl;
|
|
int ioctlfd;
|
|
|
|
ifi = calloc(1, sizeof(*ifi));
|
|
if (ifi == NULL)
|
|
fatal("ifi");
|
|
|
|
ifi->rbuf_max = RT_BUF_SIZE;
|
|
ifi->rbuf = malloc(ifi->rbuf_max);
|
|
if (ifi->rbuf == NULL)
|
|
fatal("rbuf");
|
|
|
|
if ((ioctlfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
|
|
fatal("socket(AF_INET, SOCK_DGRAM)");
|
|
|
|
get_name(ifi, ioctlfd, name);
|
|
ifi->index = if_nametoindex(ifi->name);
|
|
if (ifi->index == 0)
|
|
fatalx("if_nametoindex(%s) == 0", ifi->name);
|
|
|
|
if (getifaddrs(&ifap) == -1)
|
|
fatal("getifaddrs()");
|
|
|
|
ifa = get_link_ifa(ifi->name, ifap);
|
|
sdl = (struct sockaddr_dl *)ifa->ifa_addr;
|
|
ifd = (struct if_data *)ifa->ifa_data;
|
|
|
|
if ((ifa->ifa_flags & IFF_UP) == 0 ||
|
|
(ifa->ifa_flags & IFF_RUNNING) == 0)
|
|
ifi->link_state = LINK_STATE_DOWN;
|
|
else
|
|
ifi->link_state = ifd->ifi_link_state;
|
|
|
|
memcpy(ifi->hw_address.ether_addr_octet, LLADDR(sdl), ETHER_ADDR_LEN);
|
|
ifi->rdomain = ifd->ifi_rdomain;
|
|
|
|
get_sockets(ifi);
|
|
get_ssid(ifi, ioctlfd);
|
|
|
|
if (noaction == 0 && !LINK_STATE_IS_UP(ifi->link_state))
|
|
set_iff_up(ifi, ioctlfd);
|
|
|
|
close(ioctlfd);
|
|
freeifaddrs(ifap);
|
|
|
|
return ifi;
|
|
}
|
|
|
|
void
|
|
get_name(struct interface_info *ifi, int ioctlfd, char *arg)
|
|
{
|
|
struct ifgroupreq ifgr;
|
|
size_t len;
|
|
|
|
if (strcmp(arg, "egress") == 0) {
|
|
memset(&ifgr, 0, sizeof(ifgr));
|
|
strlcpy(ifgr.ifgr_name, "egress", sizeof(ifgr.ifgr_name));
|
|
if (ioctl(ioctlfd, SIOCGIFGMEMB, (caddr_t)&ifgr) == -1)
|
|
fatal("SIOCGIFGMEMB");
|
|
if (ifgr.ifgr_len > sizeof(struct ifg_req))
|
|
fatalx("too many interfaces in group egress");
|
|
if ((ifgr.ifgr_groups = calloc(1, ifgr.ifgr_len)) == NULL)
|
|
fatalx("ifgr_groups");
|
|
if (ioctl(ioctlfd, SIOCGIFGMEMB, (caddr_t)&ifgr) == -1)
|
|
fatal("SIOCGIFGMEMB");
|
|
len = strlcpy(ifi->name, ifgr.ifgr_groups->ifgrq_member,
|
|
IFNAMSIZ);
|
|
free(ifgr.ifgr_groups);
|
|
} else
|
|
len = strlcpy(ifi->name, arg, IFNAMSIZ);
|
|
|
|
if (len >= IFNAMSIZ)
|
|
fatalx("interface name too long");
|
|
}
|
|
|
|
void
|
|
get_ssid(struct interface_info *ifi, int ioctlfd)
|
|
{
|
|
struct ieee80211_nwid nwid;
|
|
struct ifreq ifr;
|
|
|
|
memset(&ifr, 0, sizeof(ifr));
|
|
memset(&nwid, 0, sizeof(nwid));
|
|
|
|
ifr.ifr_data = (caddr_t)&nwid;
|
|
strlcpy(ifr.ifr_name, ifi->name, sizeof(ifr.ifr_name));
|
|
|
|
if (ioctl(ioctlfd, SIOCG80211NWID, (caddr_t)&ifr) == 0) {
|
|
memset(ifi->ssid, 0, sizeof(ifi->ssid));
|
|
memcpy(ifi->ssid, nwid.i_nwid, nwid.i_len);
|
|
ifi->ssid_len = nwid.i_len;
|
|
}
|
|
}
|
|
|
|
void
|
|
set_iff_up(struct interface_info *ifi, int ioctlfd)
|
|
{
|
|
struct ifreq ifr;
|
|
|
|
memset(&ifr, 0, sizeof(ifr));
|
|
strlcpy(ifr.ifr_name, ifi->name, sizeof(ifr.ifr_name));
|
|
|
|
if (ioctl(ioctlfd, SIOCGIFFLAGS, (caddr_t)&ifr) == -1)
|
|
fatal("%s: SIOCGIFFLAGS", ifi->name);
|
|
|
|
if ((ifr.ifr_flags & IFF_UP) == 0) {
|
|
ifi->link_state = LINK_STATE_DOWN;
|
|
ifr.ifr_flags |= IFF_UP;
|
|
if (ioctl(ioctlfd, SIOCSIFFLAGS, (caddr_t)&ifr) == -1)
|
|
fatal("%s: SIOCSIFFLAGS", ifi->name);
|
|
}
|
|
}
|
|
|
|
void
|
|
set_user(char *user)
|
|
{
|
|
struct passwd *pw;
|
|
|
|
pw = getpwnam(user);
|
|
if (pw == NULL)
|
|
fatalx("no such user: %s", user);
|
|
|
|
if (chroot(pw->pw_dir) == -1)
|
|
fatal("chroot(%s)", pw->pw_dir);
|
|
if (chdir("/") == -1)
|
|
fatal("chdir(\"/\")");
|
|
if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1)
|
|
fatal("setresgid");
|
|
if (setgroups(1, &pw->pw_gid) == -1)
|
|
fatal("setgroups");
|
|
if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1)
|
|
fatal("setresuid");
|
|
|
|
endpwent();
|
|
}
|
|
|
|
void
|
|
get_sockets(struct interface_info *ifi)
|
|
{
|
|
unsigned char *newp;
|
|
size_t newsize;
|
|
|
|
ifi->udpfd = get_udp_sock(ifi->rdomain);
|
|
ifi->bpffd = get_bpf_sock(ifi->name);
|
|
|
|
newsize = configure_bpf_sock(ifi->bpffd);
|
|
if (newsize > ifi->rbuf_max) {
|
|
if ((newp = realloc(ifi->rbuf, newsize)) == NULL)
|
|
fatal("rbuf");
|
|
ifi->rbuf = newp;
|
|
ifi->rbuf_max = newsize;
|
|
}
|
|
}
|
|
|
|
int
|
|
get_routefd(int rdomain)
|
|
{
|
|
int routefd, rtfilter;
|
|
|
|
if ((routefd = socket(AF_ROUTE, SOCK_RAW, AF_INET)) == -1)
|
|
fatal("socket(AF_ROUTE, SOCK_RAW)");
|
|
|
|
rtfilter = ROUTE_FILTER(RTM_PROPOSAL) | ROUTE_FILTER(RTM_IFINFO) |
|
|
ROUTE_FILTER(RTM_NEWADDR) | ROUTE_FILTER(RTM_DELADDR) |
|
|
ROUTE_FILTER(RTM_IFANNOUNCE) | ROUTE_FILTER(RTM_80211INFO);
|
|
|
|
if (setsockopt(routefd, AF_ROUTE, ROUTE_MSGFILTER,
|
|
&rtfilter, sizeof(rtfilter)) == -1)
|
|
fatal("setsockopt(ROUTE_MSGFILTER)");
|
|
if (setsockopt(routefd, AF_ROUTE, ROUTE_TABLEFILTER, &rdomain,
|
|
sizeof(rdomain)) == -1)
|
|
fatal("setsockopt(ROUTE_TABLEFILTER)");
|
|
|
|
return routefd;
|
|
}
|
|
|
|
void
|
|
routefd_handler(struct interface_info *ifi, int routefd)
|
|
{
|
|
struct rt_msghdr *rtm;
|
|
unsigned char *buf = ifi->rbuf;
|
|
unsigned char *lim, *next;
|
|
ssize_t n;
|
|
|
|
do {
|
|
n = read(routefd, buf, RT_BUF_SIZE);
|
|
} while (n == -1 && errno == EINTR);
|
|
if (n == -1) {
|
|
log_warn("%s: routing socket", log_procname);
|
|
return;
|
|
}
|
|
if (n == 0)
|
|
fatalx("%s: routing socket closed", log_procname);
|
|
|
|
lim = buf + n;
|
|
for (next = buf; next < lim && quit == 0; next += rtm->rtm_msglen) {
|
|
rtm = (struct rt_msghdr *)next;
|
|
if (lim < next + sizeof(rtm->rtm_msglen) ||
|
|
lim < next + rtm->rtm_msglen)
|
|
fatalx("%s: partial rtm in buffer", log_procname);
|
|
|
|
if (rtm->rtm_version != RTM_VERSION)
|
|
continue;
|
|
|
|
rtm_dispatch(ifi, rtm);
|
|
}
|
|
}
|
|
|
|
void
|
|
rtm_dispatch(struct interface_info *ifi, struct rt_msghdr *rtm)
|
|
{
|
|
struct if_msghdr *ifm;
|
|
struct if_announcemsghdr *ifan;
|
|
struct ifa_msghdr *ifam;
|
|
struct if_ieee80211_data *ifie;
|
|
char *oldssid;
|
|
uint32_t oldmtu;
|
|
|
|
switch (rtm->rtm_type) {
|
|
case RTM_PROPOSAL:
|
|
if (rtm->rtm_priority == RTP_PROPOSAL_SOLICIT) {
|
|
if (quit == 0 && ifi->active != NULL)
|
|
tell_unwind(ifi->unwind_info, ifi->flags);
|
|
return;
|
|
}
|
|
if (rtm->rtm_index != ifi->index ||
|
|
rtm->rtm_priority != RTP_PROPOSAL_DHCLIENT)
|
|
return;
|
|
if ((rtm->rtm_flags & RTF_PROTO3) != 0) {
|
|
if (rtm->rtm_seq == (int32_t)ifi->xid) {
|
|
ifi->flags |= IFI_IN_CHARGE;
|
|
return;
|
|
} else if ((ifi->flags & IFI_IN_CHARGE) != 0) {
|
|
log_debug("%s: yielding responsibility",
|
|
log_procname);
|
|
quit = TERMINATE;
|
|
}
|
|
} else if ((rtm->rtm_flags & RTF_PROTO2) != 0) {
|
|
release_lease(ifi); /* OK even if we sent it. */
|
|
quit = TERMINATE;
|
|
} else
|
|
return; /* Ignore tell_unwind() proposals. */
|
|
break;
|
|
|
|
case RTM_DESYNC:
|
|
log_warnx("%s: RTM_DESYNC", log_procname);
|
|
break;
|
|
|
|
case RTM_IFINFO:
|
|
ifm = (struct if_msghdr *)rtm;
|
|
if (ifm->ifm_index != ifi->index)
|
|
break;
|
|
if ((rtm->rtm_flags & RTF_UP) == 0)
|
|
fatalx("down");
|
|
|
|
oldmtu = ifi->mtu;
|
|
interface_state(ifi);
|
|
if (oldmtu == ifi->mtu)
|
|
quit = RESTART;
|
|
else
|
|
log_debug("%s: MTU %u -> %u",
|
|
log_procname, oldmtu, ifi->mtu);
|
|
break;
|
|
|
|
case RTM_80211INFO:
|
|
if (rtm->rtm_index != ifi->index)
|
|
break;
|
|
ifie = &((struct if_ieee80211_msghdr *)rtm)->ifim_ifie;
|
|
if (ifi->ssid_len != ifie->ifie_nwid_len || memcmp(ifi->ssid,
|
|
ifie->ifie_nwid, ifie->ifie_nwid_len) != 0) {
|
|
if (log_getverbose()) {
|
|
oldssid = strdup(pretty_print_string(ifi->ssid,
|
|
ifi->ssid_len, 1));
|
|
if (oldssid == NULL)
|
|
fatal("oldssid");
|
|
log_debug("%s: SSID %s -> %s", log_procname,
|
|
oldssid, pretty_print_string(ifie->ifie_nwid,
|
|
ifie->ifie_nwid_len, 1));
|
|
free(oldssid);
|
|
}
|
|
quit = RESTART;
|
|
}
|
|
break;
|
|
|
|
case RTM_IFANNOUNCE:
|
|
ifan = (struct if_announcemsghdr *)rtm;
|
|
if (ifan->ifan_what == IFAN_DEPARTURE && ifan->ifan_index ==
|
|
ifi->index)
|
|
fatalx("departed");
|
|
break;
|
|
|
|
case RTM_NEWADDR:
|
|
case RTM_DELADDR:
|
|
/* Need to check if it is time to write resolv.conf. */
|
|
ifam = (struct ifa_msghdr *)rtm;
|
|
if (get_ifa_family((char *)ifam + ifam->ifam_hdrlen,
|
|
ifam->ifam_addrs) != AF_INET)
|
|
return;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Responsibility for resolv.conf may have changed hands.
|
|
*/
|
|
if (quit == 0 && ifi->active != NULL &&
|
|
(ifi->flags & IFI_IN_CHARGE) != 0 &&
|
|
ifi->state == S_BOUND)
|
|
write_resolv_conf();
|
|
}
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
uint8_t actions[DHO_END];
|
|
struct stat sb;
|
|
struct interface_info *ifi;
|
|
char *ignore_list, *p;
|
|
int fd, socket_fd[2];
|
|
int routefd;
|
|
int ch, i;
|
|
|
|
if (isatty(STDERR_FILENO) != 0)
|
|
log_init(1, LOG_DEBUG); /* log to stderr until daemonized */
|
|
else
|
|
log_init(0, LOG_DEBUG); /* can't log to stderr */
|
|
|
|
log_setverbose(0); /* Don't show log_debug() messages. */
|
|
|
|
if (lstat(_PATH_DHCLIENT_CONF, &sb) == 0)
|
|
path_dhclient_conf = _PATH_DHCLIENT_CONF;
|
|
memset(actions, ACTION_USELEASE, sizeof(actions));
|
|
|
|
while ((ch = getopt(argc, argv, "c:di:nrv")) != -1) {
|
|
syslog(LOG_ALERT | LOG_CONS,
|
|
"dhclient will go away, so -%c option will not exist", ch);
|
|
switch (ch) {
|
|
case 'c':
|
|
if (strlen(optarg) == 0)
|
|
path_dhclient_conf = NULL;
|
|
else if (lstat(optarg, &sb) == 0)
|
|
path_dhclient_conf = optarg;
|
|
else
|
|
fatal("lstat(%s)", optarg);
|
|
break;
|
|
case 'd':
|
|
cmd_opts |= OPT_FOREGROUND;
|
|
break;
|
|
case 'i':
|
|
syslog(LOG_ALERT | LOG_CONS,
|
|
"dhclient will go away, for -i learn dhcpleased.conf");
|
|
if (strlen(optarg) == 0)
|
|
break;
|
|
ignore_list = strdup(optarg);
|
|
if (ignore_list == NULL)
|
|
fatal("ignore_list");
|
|
for (p = strsep(&ignore_list, ", "); p != NULL;
|
|
p = strsep(&ignore_list, ", ")) {
|
|
if (*p == '\0')
|
|
continue;
|
|
i = name_to_code(p);
|
|
if (i == DHO_END)
|
|
fatalx("invalid option name: '%s'", p);
|
|
actions[i] = ACTION_IGNORE;
|
|
}
|
|
free(ignore_list);
|
|
break;
|
|
case 'n':
|
|
cmd_opts |= OPT_NOACTION;
|
|
break;
|
|
case 'r':
|
|
cmd_opts |= OPT_RELEASE;
|
|
break;
|
|
case 'v':
|
|
cmd_opts |= OPT_VERBOSE;
|
|
break;
|
|
default:
|
|
usage();
|
|
}
|
|
}
|
|
|
|
argc -= optind;
|
|
argv += optind;
|
|
|
|
if (argc != 1)
|
|
usage();
|
|
|
|
syslog(LOG_ALERT | LOG_CONS,
|
|
"dhclient will go away, stop using it");
|
|
|
|
execl("/sbin/ifconfig", "ifconfig", argv[0], "inet", "autoconf", NULL);
|
|
|
|
if ((cmd_opts & (OPT_FOREGROUND | OPT_NOACTION)) != 0)
|
|
cmd_opts |= OPT_VERBOSE;
|
|
|
|
if ((cmd_opts & OPT_VERBOSE) != 0)
|
|
log_setverbose(1); /* Show log_debug() messages. */
|
|
|
|
ifi = initialize_interface(argv[0], cmd_opts & OPT_NOACTION);
|
|
|
|
log_procname = strdup(ifi->name);
|
|
if (log_procname == NULL)
|
|
fatal("log_procname");
|
|
setproctitle("%s", log_procname);
|
|
log_procinit(log_procname);
|
|
|
|
tzset();
|
|
|
|
if (setrtable(ifi->rdomain) == -1)
|
|
fatal("setrtable(%u)", ifi->rdomain);
|
|
|
|
if ((cmd_opts & OPT_RELEASE) != 0) {
|
|
if ((cmd_opts & OPT_NOACTION) == 0)
|
|
propose_release(ifi);
|
|
exit(0);
|
|
}
|
|
|
|
signal(SIGPIPE, SIG_IGN); /* Don't wait for go_daemon()! */
|
|
|
|
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0,
|
|
socket_fd) == -1)
|
|
fatal("socketpair");
|
|
|
|
if ((nullfd = open(_PATH_DEVNULL, O_RDWR)) == -1)
|
|
fatal("open(%s)", _PATH_DEVNULL);
|
|
|
|
fork_privchld(ifi, socket_fd[0], socket_fd[1]);
|
|
|
|
close(socket_fd[0]);
|
|
if ((unpriv_ibuf = malloc(sizeof(*unpriv_ibuf))) == NULL)
|
|
fatal("unpriv_ibuf");
|
|
imsg_init(unpriv_ibuf, socket_fd[1]);
|
|
|
|
read_conf(ifi->name, actions, &ifi->hw_address);
|
|
if ((cmd_opts & OPT_NOACTION) != 0)
|
|
return 0;
|
|
|
|
if (asprintf(&path_lease_db, "%s.%s", _PATH_LEASE_DB, ifi->name) == -1)
|
|
fatal("path_lease_db");
|
|
|
|
routefd = get_routefd(ifi->rdomain);
|
|
fd = take_charge(ifi, routefd, path_lease_db); /* Kill other dhclients. */
|
|
if (autoconf(ifi)) {
|
|
/* dhcpleased has been notified to request a new lease. */
|
|
return 0;
|
|
}
|
|
if (fd != -1)
|
|
read_lease_db(&ifi->lease_db);
|
|
|
|
if ((leaseFile = fopen(path_lease_db, "w")) == NULL)
|
|
log_warn("%s: fopen(%s)", log_procname, path_lease_db);
|
|
write_lease_db(ifi);
|
|
|
|
set_user("_dhcp");
|
|
|
|
if ((cmd_opts & OPT_FOREGROUND) == 0) {
|
|
if (pledge("stdio inet dns route proc", NULL) == -1)
|
|
fatal("pledge");
|
|
} else {
|
|
if (pledge("stdio inet dns route", NULL) == -1)
|
|
fatal("pledge");
|
|
}
|
|
|
|
tick_msg("link", LINK_STATE_IS_UP(ifi->link_state) ? TICK_SUCCESS :
|
|
TICK_WAIT);
|
|
quit = RESTART;
|
|
dispatch(ifi, routefd);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
usage(void)
|
|
{
|
|
extern char *__progname;
|
|
|
|
fprintf(stderr,
|
|
"usage: %s [-dnrv] [-c file] [-i options] "
|
|
"interface\n", __progname);
|
|
exit(1);
|
|
}
|
|
|
|
void
|
|
state_preboot(struct interface_info *ifi)
|
|
{
|
|
interface_state(ifi);
|
|
if (quit != 0)
|
|
return;
|
|
|
|
if (LINK_STATE_IS_UP(ifi->link_state)) {
|
|
tick_msg("link", TICK_SUCCESS);
|
|
ifi->state = S_REBOOTING;
|
|
state_reboot(ifi);
|
|
} else {
|
|
tick_msg("link", TICK_WAIT);
|
|
set_timeout(ifi, 1, state_preboot);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Called when the interface link becomes active.
|
|
*/
|
|
void
|
|
state_reboot(struct interface_info *ifi)
|
|
{
|
|
const struct timespec reboot_intvl = {config->reboot_interval, 0};
|
|
struct client_lease *lease;
|
|
|
|
cancel_timeout(ifi);
|
|
|
|
/*
|
|
* If there is no recorded lease or the lease is BOOTP then
|
|
* go straight to INIT and try to DISCOVER a new lease.
|
|
*/
|
|
ifi->active = get_recorded_lease(ifi);
|
|
if (ifi->active == NULL || BOOTP_LEASE(ifi->active)) {
|
|
ifi->state = S_INIT;
|
|
state_init(ifi);
|
|
return;
|
|
}
|
|
lease = apply_defaults(ifi->active);
|
|
get_lease_timeouts(ifi, lease);
|
|
free_client_lease(lease);
|
|
|
|
ifi->xid = arc4random();
|
|
make_request(ifi, ifi->active);
|
|
|
|
ifi->destination.s_addr = INADDR_BROADCAST;
|
|
clock_gettime(CLOCK_MONOTONIC, &ifi->first_sending);
|
|
timespecadd(&ifi->first_sending, &reboot_intvl, &ifi->reboot_timeout);
|
|
ifi->interval = 0;
|
|
|
|
send_request(ifi);
|
|
}
|
|
|
|
/*
|
|
* Called when a lease has completely expired and we've been unable to
|
|
* renew it.
|
|
*/
|
|
void
|
|
state_init(struct interface_info *ifi)
|
|
{
|
|
const struct timespec offer_intvl = {config->offer_interval, 0};
|
|
|
|
ifi->xid = arc4random();
|
|
make_discover(ifi, ifi->active);
|
|
|
|
ifi->destination.s_addr = INADDR_BROADCAST;
|
|
ifi->state = S_SELECTING;
|
|
clock_gettime(CLOCK_MONOTONIC, &ifi->first_sending);
|
|
timespecadd(&ifi->first_sending, &offer_intvl, &ifi->offer_timeout);
|
|
ifi->select_timeout = ifi->offer_timeout;
|
|
ifi->interval = 0;
|
|
|
|
send_discover(ifi);
|
|
}
|
|
|
|
/*
|
|
* Called when one or more DHCPOFFER packets have been received and a
|
|
* configurable period of time has passed.
|
|
*/
|
|
void
|
|
state_selecting(struct interface_info *ifi)
|
|
{
|
|
cancel_timeout(ifi);
|
|
|
|
if (ifi->offer == NULL) {
|
|
state_panic(ifi);
|
|
return;
|
|
}
|
|
|
|
ifi->state = S_REQUESTING;
|
|
|
|
/* If it was a BOOTREPLY, we can just take the lease right now. */
|
|
if (BOOTP_LEASE(ifi->offer)) {
|
|
bind_lease(ifi);
|
|
return;
|
|
}
|
|
|
|
ifi->destination.s_addr = INADDR_BROADCAST;
|
|
clock_gettime(CLOCK_MONOTONIC, &ifi->first_sending);
|
|
ifi->interval = 0;
|
|
|
|
/*
|
|
* Make a DHCPREQUEST packet from the lease we picked. Keep
|
|
* the current xid, as all offers should have had the same
|
|
* one.
|
|
*/
|
|
make_request(ifi, ifi->offer);
|
|
|
|
/* Toss the lease we picked - we'll get it back in a DHCPACK. */
|
|
free_client_lease(ifi->offer);
|
|
ifi->offer = NULL;
|
|
free(ifi->offer_src);
|
|
ifi->offer_src = NULL;
|
|
|
|
send_request(ifi);
|
|
}
|
|
|
|
void
|
|
dhcpoffer(struct interface_info *ifi, struct option_data *options,
|
|
const char *src)
|
|
{
|
|
if (ifi->state != S_SELECTING) {
|
|
log_debug("%s: unexpected DHCPOFFER from %s - state #%d",
|
|
log_procname, src, ifi->state);
|
|
return;
|
|
}
|
|
|
|
log_debug("%s: DHCPOFFER from %s", log_procname, src);
|
|
process_offer(ifi, options, src);
|
|
}
|
|
|
|
void
|
|
bootreply(struct interface_info *ifi, struct option_data *options,
|
|
const char *src)
|
|
{
|
|
if (ifi->state != S_SELECTING) {
|
|
log_debug("%s: unexpected BOOTREPLY from %s - state #%d",
|
|
log_procname, src, ifi->state);
|
|
return;
|
|
}
|
|
|
|
log_debug("%s: BOOTREPLY from %s", log_procname, src);
|
|
process_offer(ifi, options, src);
|
|
}
|
|
|
|
void
|
|
process_offer(struct interface_info *ifi, struct option_data *options,
|
|
const char *src)
|
|
{
|
|
const struct timespec select_intvl = {config->select_interval, 0};
|
|
struct timespec now;
|
|
struct client_lease *lease;
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
|
|
lease = packet_to_lease(ifi, options);
|
|
if (lease != NULL) {
|
|
if (ifi->offer == NULL) {
|
|
ifi->offer = lease;
|
|
free(ifi->offer_src);
|
|
ifi->offer_src = strdup(src); /* NULL is OK */
|
|
timespecadd(&now, &select_intvl, &ifi->select_timeout);
|
|
if (timespeccmp(&ifi->select_timeout,
|
|
&ifi->offer_timeout, >))
|
|
ifi->select_timeout = ifi->offer_timeout;
|
|
} else if (lease->address.s_addr ==
|
|
ifi->offer->address.s_addr) {
|
|
/* Decline duplicate offers. */
|
|
} else if (lease->address.s_addr ==
|
|
ifi->requested_address.s_addr) {
|
|
free_client_lease(ifi->offer);
|
|
ifi->offer = lease;
|
|
free(ifi->offer_src);
|
|
ifi->offer_src = strdup(src); /* NULL is OK */
|
|
}
|
|
|
|
if (ifi->offer != lease) {
|
|
make_decline(ifi, lease);
|
|
send_decline(ifi);
|
|
free_client_lease(lease);
|
|
} else if (ifi->offer->address.s_addr ==
|
|
ifi->requested_address.s_addr) {
|
|
ifi->select_timeout = now;
|
|
}
|
|
}
|
|
|
|
if (timespeccmp(&now, &ifi->select_timeout, >=))
|
|
state_selecting(ifi);
|
|
else {
|
|
ifi->timeout = ifi->select_timeout;
|
|
ifi->timeout_func = state_selecting;
|
|
}
|
|
}
|
|
|
|
void
|
|
dhcpack(struct interface_info *ifi, struct option_data *options,
|
|
const char *src)
|
|
{
|
|
struct client_lease *lease;
|
|
|
|
if (ifi->state != S_REBOOTING &&
|
|
ifi->state != S_REQUESTING &&
|
|
ifi->state != S_RENEWING) {
|
|
log_debug("%s: unexpected DHCPACK from %s - state #%d",
|
|
log_procname, src, ifi->state);
|
|
return;
|
|
}
|
|
|
|
log_debug("%s: DHCPACK", log_procname);
|
|
|
|
lease = packet_to_lease(ifi, options);
|
|
if (lease == NULL) {
|
|
ifi->state = S_INIT;
|
|
state_init(ifi);
|
|
return;
|
|
}
|
|
|
|
ifi->offer = lease;
|
|
ifi->offer_src = strdup(src); /* NULL is OK */
|
|
memcpy(ifi->offer->ssid, ifi->ssid, sizeof(ifi->offer->ssid));
|
|
ifi->offer->ssid_len = ifi->ssid_len;
|
|
|
|
/* Stop resending DHCPREQUEST. */
|
|
cancel_timeout(ifi);
|
|
|
|
bind_lease(ifi);
|
|
}
|
|
|
|
void
|
|
dhcpnak(struct interface_info *ifi, const char *src)
|
|
{
|
|
struct client_lease *ll, *pl;
|
|
|
|
if (ifi->state != S_REBOOTING &&
|
|
ifi->state != S_REQUESTING &&
|
|
ifi->state != S_RENEWING) {
|
|
log_debug("%s: unexpected DHCPNAK from %s - state #%d",
|
|
log_procname, src, ifi->state);
|
|
return;
|
|
}
|
|
|
|
log_debug("%s: DHCPNAK", log_procname);
|
|
|
|
/* Remove the NAK'd address from the database. */
|
|
TAILQ_FOREACH_SAFE(ll, &ifi->lease_db, next, pl) {
|
|
if (ifi->ssid_len == ll->ssid_len &&
|
|
memcmp(ifi->ssid, ll->ssid, ll->ssid_len) == 0 &&
|
|
ll->address.s_addr == ifi->requested_address.s_addr) {
|
|
if (ll == ifi->active) {
|
|
tell_unwind(NULL, ifi->flags);
|
|
free(ifi->unwind_info);
|
|
ifi->unwind_info = NULL;
|
|
revoke_proposal(ifi->configured);
|
|
free(ifi->configured);
|
|
ifi->configured = NULL;
|
|
ifi->active = NULL;
|
|
}
|
|
TAILQ_REMOVE(&ifi->lease_db, ll, next);
|
|
free_client_lease(ll);
|
|
write_lease_db(ifi);
|
|
}
|
|
}
|
|
|
|
/* Stop sending DHCPREQUEST packets. */
|
|
cancel_timeout(ifi);
|
|
|
|
ifi->state = S_INIT;
|
|
state_init(ifi);
|
|
}
|
|
|
|
void
|
|
bind_lease(struct interface_info *ifi)
|
|
{
|
|
struct timespec now;
|
|
struct client_lease *lease, *pl, *ll;
|
|
struct proposal *effective_proposal = NULL;
|
|
struct unwind_info *unwind_info;
|
|
char *msg = NULL;
|
|
int rslt, seen;
|
|
|
|
tick_msg("lease", TICK_SUCCESS);
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
|
|
lease = apply_defaults(ifi->offer);
|
|
get_lease_timeouts(ifi, lease);
|
|
|
|
/* Replace the old active lease with the accepted offer. */
|
|
ifi->active = ifi->offer;
|
|
ifi->offer = NULL;
|
|
|
|
/*
|
|
* Supply unwind with updated info.
|
|
*/
|
|
unwind_info = lease_as_unwind_info(ifi->active);
|
|
if (ifi->unwind_info == NULL && unwind_info != NULL) {
|
|
ifi->unwind_info = unwind_info;
|
|
tell_unwind(ifi->unwind_info, ifi->flags);
|
|
} else if (ifi->unwind_info != NULL && unwind_info == NULL) {
|
|
tell_unwind(NULL, ifi->flags);
|
|
free(ifi->unwind_info);
|
|
ifi->unwind_info = NULL;
|
|
} else if (ifi->unwind_info != NULL && unwind_info != NULL) {
|
|
if (memcmp(ifi->unwind_info, unwind_info,
|
|
sizeof(*ifi->unwind_info)) != 0) {
|
|
tell_unwind(NULL, ifi->flags);
|
|
free(ifi->unwind_info);
|
|
ifi->unwind_info = unwind_info;
|
|
tell_unwind(ifi->unwind_info, ifi->flags);
|
|
}
|
|
}
|
|
|
|
effective_proposal = lease_as_proposal(lease);
|
|
if (ifi->configured != NULL) {
|
|
if (memcmp(ifi->configured, effective_proposal,
|
|
sizeof(*ifi->configured)) == 0)
|
|
goto newlease;
|
|
}
|
|
free(ifi->configured);
|
|
ifi->configured = effective_proposal;
|
|
effective_proposal = NULL;
|
|
|
|
propose(ifi->configured);
|
|
rslt = asprintf(&msg, "%s lease accepted from %s",
|
|
inet_ntoa(ifi->active->address),
|
|
(ifi->offer_src == NULL) ? "<unknown>" : ifi->offer_src);
|
|
if (rslt == -1)
|
|
fatal("bind msg");
|
|
|
|
newlease:
|
|
seen = 0;
|
|
TAILQ_FOREACH_SAFE(ll, &ifi->lease_db, next, pl) {
|
|
if (ifi->ssid_len != ll->ssid_len ||
|
|
memcmp(ifi->ssid, ll->ssid, ll->ssid_len) != 0)
|
|
continue;
|
|
if (ifi->active == ll)
|
|
seen = 1;
|
|
else if (ll->address.s_addr == ifi->active->address.s_addr) {
|
|
TAILQ_REMOVE(&ifi->lease_db, ll, next);
|
|
free_client_lease(ll);
|
|
}
|
|
}
|
|
if (seen == 0) {
|
|
if (ifi->active->epoch == 0)
|
|
time(&ifi->active->epoch);
|
|
TAILQ_INSERT_HEAD(&ifi->lease_db, ifi->active, next);
|
|
}
|
|
|
|
/*
|
|
* Write out updated information before going daemon.
|
|
*
|
|
* Some scripts (e.g. the installer in autoinstall mode) assume that
|
|
* the bind process is complete and all related information is in
|
|
* place when dhclient(8) goes daemon.
|
|
*/
|
|
write_lease_db(ifi);
|
|
|
|
free_client_lease(lease);
|
|
free(effective_proposal);
|
|
free(ifi->offer_src);
|
|
ifi->offer_src = NULL;
|
|
|
|
if (msg != NULL) {
|
|
if ((cmd_opts & OPT_FOREGROUND) != 0) {
|
|
/* log msg on console only. */
|
|
;
|
|
} else if (isatty(STDERR_FILENO) != 0) {
|
|
/*
|
|
* log msg to console and then go_daemon() so it is
|
|
* logged again, this time to /var/log/daemon.
|
|
*/
|
|
log_info("%s: %s", log_procname, msg);
|
|
go_daemon();
|
|
}
|
|
log_info("%s: %s", log_procname, msg);
|
|
free(msg);
|
|
}
|
|
|
|
ifi->state = S_BOUND;
|
|
go_daemon();
|
|
|
|
/*
|
|
* Set timeout to start the renewal process.
|
|
*
|
|
* If the renewal time is in the past, the lease is from the
|
|
* leaseDB. Rather than immediately trying to contact a server,
|
|
* pause the configured time between attempts.
|
|
*/
|
|
if (timespeccmp(&now, &ifi->renew, >=))
|
|
set_timeout(ifi, config->retry_interval, state_bound);
|
|
else {
|
|
ifi->timeout = ifi->renew;
|
|
ifi->timeout_func = state_bound;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Called when we've successfully bound to a particular lease, but the renewal
|
|
* time on that lease has expired. We are expected to unicast a DHCPREQUEST to
|
|
* the server that gave us our original lease.
|
|
*/
|
|
void
|
|
state_bound(struct interface_info *ifi)
|
|
{
|
|
struct option_data *opt;
|
|
struct in_addr *dest;
|
|
|
|
ifi->xid = arc4random();
|
|
make_request(ifi, ifi->active);
|
|
|
|
dest = &ifi->destination;
|
|
opt = &ifi->active->options[DHO_DHCP_SERVER_IDENTIFIER];
|
|
|
|
if (opt->len == sizeof(*dest))
|
|
dest->s_addr = ((struct in_addr *)opt->data)->s_addr;
|
|
else
|
|
dest->s_addr = INADDR_BROADCAST;
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &ifi->first_sending);
|
|
ifi->interval = 0;
|
|
ifi->state = S_RENEWING;
|
|
|
|
send_request(ifi);
|
|
}
|
|
|
|
int
|
|
addressinuse(char *name, struct in_addr address, char *ifname)
|
|
{
|
|
struct ifaddrs *ifap, *ifa;
|
|
struct sockaddr_in *sin;
|
|
int used = 0;
|
|
|
|
if (getifaddrs(&ifap) != 0) {
|
|
log_warn("%s: getifaddrs", log_procname);
|
|
return 0;
|
|
}
|
|
|
|
for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
|
|
if (ifa->ifa_addr == NULL ||
|
|
ifa->ifa_addr->sa_family != AF_INET)
|
|
continue;
|
|
|
|
sin = (struct sockaddr_in *)ifa->ifa_addr;
|
|
if (memcmp(&address, &sin->sin_addr, sizeof(address)) == 0) {
|
|
strlcpy(ifname, ifa->ifa_name, IF_NAMESIZE);
|
|
used = 1;
|
|
if (strncmp(ifname, name, IF_NAMESIZE) != 0)
|
|
break;
|
|
}
|
|
}
|
|
|
|
freeifaddrs(ifap);
|
|
return used;
|
|
}
|
|
|
|
/*
|
|
* Allocate a client_lease structure and initialize it from the
|
|
* parameters in the received packet.
|
|
*
|
|
* Return NULL and decline the lease if a valid lease cannot be
|
|
* constructed.
|
|
*/
|
|
struct client_lease *
|
|
packet_to_lease(struct interface_info *ifi, struct option_data *options)
|
|
{
|
|
char ifname[IF_NAMESIZE];
|
|
struct dhcp_packet *packet = &ifi->recv_packet;
|
|
struct client_lease *lease;
|
|
char *pretty, *name;
|
|
int i;
|
|
|
|
lease = calloc(1, sizeof(*lease));
|
|
if (lease == NULL) {
|
|
log_warn("%s: lease", log_procname);
|
|
return NULL; /* Can't even DECLINE. */
|
|
}
|
|
|
|
/* Copy the lease addresses. */
|
|
lease->address.s_addr = packet->yiaddr.s_addr;
|
|
lease->next_server.s_addr = packet->siaddr.s_addr;
|
|
|
|
/* Copy the lease options. */
|
|
for (i = 0; i < DHO_COUNT; i++) {
|
|
if (options[i].len == 0)
|
|
continue;
|
|
name = code_to_name(i);
|
|
if (i == DHO_DOMAIN_SEARCH) {
|
|
/* Replace RFC 1035 data with a string. */
|
|
pretty = rfc1035_as_string(options[i].data,
|
|
options[i].len);
|
|
free(options[i].data);
|
|
options[i].data = strdup(pretty);
|
|
if (options[i].data == NULL)
|
|
fatal("RFC1035 string");
|
|
options[i].len = strlen(options[i].data);
|
|
} else
|
|
pretty = pretty_print_option(i, &options[i], 0);
|
|
if (strlen(pretty) == 0)
|
|
continue;
|
|
switch (i) {
|
|
case DHO_DOMAIN_SEARCH:
|
|
case DHO_DOMAIN_NAME:
|
|
/*
|
|
* Allow deviant but historically blessed
|
|
* practice of supplying multiple domain names
|
|
* with DHO_DOMAIN_NAME. Thus allowing multiple
|
|
* entries in the resolv.conf 'search' statement.
|
|
*/
|
|
if (res_hnok_list(pretty) == 0) {
|
|
log_debug("%s: invalid host name in %s",
|
|
log_procname, name);
|
|
continue;
|
|
}
|
|
break;
|
|
case DHO_HOST_NAME:
|
|
case DHO_NIS_DOMAIN:
|
|
if (res_hnok(pretty) == 0) {
|
|
log_debug("%s: invalid host name in %s",
|
|
log_procname, name);
|
|
continue;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
lease->options[i] = options[i];
|
|
options[i].data = NULL;
|
|
options[i].len = 0;
|
|
}
|
|
|
|
/*
|
|
* If this lease doesn't supply a required parameter, decline it.
|
|
*/
|
|
for (i = 0; i < config->required_option_count; i++) {
|
|
if (lease->options[config->required_options[i]].len == 0) {
|
|
name = code_to_name(config->required_options[i]);
|
|
log_warnx("%s: %s required but missing", log_procname,
|
|
name);
|
|
goto decline;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If this lease is trying to sell us an address we are already
|
|
* using, decline it.
|
|
*/
|
|
memset(ifname, 0, sizeof(ifname));
|
|
if (addressinuse(ifi->name, lease->address, ifname) != 0 &&
|
|
strncmp(ifname, ifi->name, IF_NAMESIZE) != 0) {
|
|
log_warnx("%s: %s already configured on %s", log_procname,
|
|
inet_ntoa(lease->address), ifname);
|
|
goto decline;
|
|
}
|
|
|
|
/* If the server name was filled out, copy it. */
|
|
if ((lease->options[DHO_DHCP_OPTION_OVERLOAD].len == 0 ||
|
|
(lease->options[DHO_DHCP_OPTION_OVERLOAD].data[0] & 2) == 0) &&
|
|
packet->sname[0]) {
|
|
lease->server_name = calloc(1, DHCP_SNAME_LEN + 1);
|
|
if (lease->server_name == NULL) {
|
|
log_warn("%s: SNAME", log_procname);
|
|
goto decline;
|
|
}
|
|
memcpy(lease->server_name, packet->sname, DHCP_SNAME_LEN);
|
|
if (res_hnok(lease->server_name) == 0) {
|
|
log_debug("%s: invalid host name in SNAME ignored",
|
|
log_procname);
|
|
free(lease->server_name);
|
|
lease->server_name = NULL;
|
|
}
|
|
}
|
|
|
|
/* If the file name was filled out, copy it. */
|
|
if ((lease->options[DHO_DHCP_OPTION_OVERLOAD].len == 0 ||
|
|
(lease->options[DHO_DHCP_OPTION_OVERLOAD].data[0] & 1) == 0) &&
|
|
packet->file[0]) {
|
|
/* Don't count on the NUL terminator. */
|
|
lease->filename = malloc(DHCP_FILE_LEN + 1);
|
|
if (lease->filename == NULL) {
|
|
log_warn("%s: filename", log_procname);
|
|
goto decline;
|
|
}
|
|
memcpy(lease->filename, packet->file, DHCP_FILE_LEN);
|
|
lease->filename[DHCP_FILE_LEN] = '\0';
|
|
}
|
|
|
|
/*
|
|
* Record the client identifier used to obtain the lease. We already
|
|
* checked that the packet client identifier is absent (RFC 2131) or
|
|
* matches what we sent (RFC 6842),
|
|
*/
|
|
i = DHO_DHCP_CLIENT_IDENTIFIER;
|
|
if (lease->options[i].len == 0 && config->send_options[i].len != 0) {
|
|
lease->options[i].len = config->send_options[i].len;
|
|
lease->options[i].data = malloc(lease->options[i].len);
|
|
if (lease->options[i].data == NULL)
|
|
fatal("lease client-identifier");
|
|
memcpy(lease->options[i].data, config->send_options[i].data,
|
|
lease->options[i].len);
|
|
}
|
|
|
|
time(&lease->epoch);
|
|
return lease;
|
|
|
|
decline:
|
|
make_decline(ifi, lease);
|
|
send_decline(ifi);
|
|
free_client_lease(lease);
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
set_interval(struct interface_info *ifi, struct timespec *now)
|
|
{
|
|
struct timespec interval;
|
|
|
|
if (timespeccmp(now, &ifi->timeout, >))
|
|
ifi->interval = 1;
|
|
else {
|
|
timespecsub(&ifi->timeout, now, &interval);
|
|
if (interval.tv_sec == 0 || interval.tv_nsec > 500000000LL)
|
|
interval.tv_sec++;
|
|
ifi->interval = interval.tv_sec;
|
|
}
|
|
}
|
|
|
|
void
|
|
set_resend_timeout(struct interface_info *ifi, struct timespec *now,
|
|
void (*where)(struct interface_info *))
|
|
{
|
|
const struct timespec reboot_intvl = {config->reboot_interval, 0};
|
|
const struct timespec initial_intvl = {config->initial_interval, 0};
|
|
const struct timespec cutoff_intvl = {config->backoff_cutoff, 0};
|
|
const struct timespec onesecond = {1, 0};
|
|
struct timespec interval, when;
|
|
|
|
if (timespeccmp(now, &ifi->link_timeout, <))
|
|
interval = onesecond;
|
|
else if (ifi->interval == 0) {
|
|
if (ifi->state == S_REBOOTING)
|
|
interval = reboot_intvl;
|
|
else
|
|
interval = initial_intvl;
|
|
} else {
|
|
timespecclear(&interval);
|
|
interval.tv_sec = ifi->interval + arc4random_uniform(2 *
|
|
ifi->interval);
|
|
}
|
|
if (timespeccmp(&interval, &onesecond, <))
|
|
interval = onesecond;
|
|
else if (timespeccmp(&interval, &cutoff_intvl, >))
|
|
interval = cutoff_intvl;
|
|
|
|
timespecadd(now, &interval, &when);
|
|
switch (ifi->state) {
|
|
case S_REBOOTING:
|
|
case S_RENEWING:
|
|
if (timespeccmp(&when, &ifi->expiry, >))
|
|
when = ifi->expiry;
|
|
break;
|
|
case S_SELECTING:
|
|
if (timespeccmp(&when, &ifi->select_timeout, >))
|
|
when = ifi->select_timeout;
|
|
break;
|
|
case S_REQUESTING:
|
|
if (timespeccmp(&when, &ifi->offer_timeout, >))
|
|
when = ifi->offer_timeout;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ifi->timeout = when;
|
|
ifi->timeout_func = where;
|
|
}
|
|
|
|
void
|
|
set_secs(struct interface_info *ifi, struct timespec *now)
|
|
{
|
|
struct timespec interval;
|
|
|
|
if (ifi->state != S_REQUESTING) {
|
|
/* Update the number of seconds since we started sending. */
|
|
timespecsub(now, &ifi->first_sending, &interval);
|
|
if (interval.tv_nsec > 500000000LL)
|
|
interval.tv_sec++;
|
|
if (interval.tv_sec > UINT16_MAX)
|
|
ifi->secs = UINT16_MAX;
|
|
else
|
|
ifi->secs = interval.tv_sec;
|
|
}
|
|
|
|
ifi->sent_packet.secs = htons(ifi->secs);
|
|
}
|
|
|
|
/*
|
|
* Send out a DHCPDISCOVER packet, and set a timeout to send out another
|
|
* one after the right interval has expired. If we don't get an offer by
|
|
* the time we reach the panic interval, call the panic function.
|
|
*/
|
|
void
|
|
send_discover(struct interface_info *ifi)
|
|
{
|
|
struct timespec now;
|
|
ssize_t rslt;
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
if (timespeccmp(&now, &ifi->offer_timeout, >=)) {
|
|
state_panic(ifi);
|
|
return;
|
|
}
|
|
|
|
set_resend_timeout(ifi, &now, send_discover);
|
|
set_interval(ifi, &now);
|
|
set_secs(ifi, &now);
|
|
|
|
rslt = send_packet(ifi, inaddr_any, inaddr_broadcast, "DHCPDISCOVER");
|
|
if (rslt != -1)
|
|
log_debug("%s: DHCPDISCOVER %s", log_procname,
|
|
(ifi->requested_address.s_addr == INADDR_ANY) ? "" :
|
|
inet_ntoa(ifi->requested_address));
|
|
|
|
tick_msg("lease", TICK_WAIT);
|
|
}
|
|
|
|
/*
|
|
* Called if we haven't received any offers in a preset amount of time. When
|
|
* this happens, we try to use existing leases that haven't yet expired.
|
|
*
|
|
* If LINK_STATE_UNKNOWN, do NOT use recorded leases.
|
|
*/
|
|
void
|
|
state_panic(struct interface_info *ifi)
|
|
{
|
|
log_debug("%s: no acceptable DHCPOFFERS received", log_procname);
|
|
|
|
if (ifi->link_state >= LINK_STATE_UP) {
|
|
ifi->offer = get_recorded_lease(ifi);
|
|
if (ifi->offer != NULL) {
|
|
ifi->state = S_REQUESTING;
|
|
ifi->offer_src = strdup(path_lease_db); /* NULL is OK. */
|
|
bind_lease(ifi);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* No leases were available, or what was available didn't work
|
|
*/
|
|
log_debug("%s: no working leases in persistent database - sleeping",
|
|
log_procname);
|
|
ifi->state = S_INIT;
|
|
set_timeout(ifi, config->retry_interval, state_init);
|
|
tick_msg("lease", TICK_DAEMON);
|
|
}
|
|
|
|
void
|
|
send_request(struct interface_info *ifi)
|
|
{
|
|
struct sockaddr_in destination;
|
|
struct in_addr from;
|
|
struct timespec now;
|
|
ssize_t rslt;
|
|
char *addr;
|
|
|
|
cancel_timeout(ifi);
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
|
|
switch (ifi->state) {
|
|
case S_REBOOTING:
|
|
if (timespeccmp(&now, &ifi->reboot_timeout, >=))
|
|
ifi->state = S_INIT;
|
|
else {
|
|
destination.sin_addr.s_addr = INADDR_BROADCAST;
|
|
if (ifi->active == NULL)
|
|
from.s_addr = INADDR_ANY;
|
|
else
|
|
from.s_addr = ifi->active->address.s_addr;
|
|
}
|
|
break;
|
|
case S_RENEWING:
|
|
if (timespeccmp(&now, &ifi->expiry, >=))
|
|
ifi->state = S_INIT;
|
|
else {
|
|
if (timespeccmp(&now, &ifi->rebind, >=))
|
|
destination.sin_addr.s_addr = INADDR_BROADCAST;
|
|
else
|
|
destination.sin_addr.s_addr = ifi->destination.s_addr;
|
|
if (ifi->active == NULL)
|
|
from.s_addr = INADDR_ANY;
|
|
else
|
|
from.s_addr = ifi->active->address.s_addr;
|
|
}
|
|
break;
|
|
case S_REQUESTING:
|
|
if (timespeccmp(&now, &ifi->offer_timeout, >=))
|
|
ifi->state = S_INIT;
|
|
else {
|
|
destination.sin_addr.s_addr = INADDR_BROADCAST;
|
|
from.s_addr = INADDR_ANY;
|
|
}
|
|
break;
|
|
default:
|
|
ifi->state = S_INIT;
|
|
break;
|
|
}
|
|
|
|
if (ifi->state == S_INIT) {
|
|
/* Something has gone wrong. Start over. */
|
|
state_init(ifi);
|
|
return;
|
|
}
|
|
|
|
set_resend_timeout(ifi, &now, send_request);
|
|
set_interval(ifi, &now);
|
|
set_secs(ifi, &now);
|
|
|
|
rslt = send_packet(ifi, from, destination.sin_addr, "DHCPREQUEST");
|
|
if (rslt != -1 && log_getverbose()) {
|
|
addr = strdup(inet_ntoa(ifi->requested_address));
|
|
if (addr == NULL)
|
|
fatal("strdup(ifi->requested_address)");
|
|
if (destination.sin_addr.s_addr == INADDR_BROADCAST)
|
|
log_debug("%s: DHCPREQUEST %s", log_procname, addr);
|
|
else
|
|
log_debug("%s: DHCPREQUEST %s from %s", log_procname,
|
|
addr, inet_ntoa(destination.sin_addr));
|
|
free(addr);
|
|
}
|
|
|
|
tick_msg("lease", TICK_WAIT);
|
|
}
|
|
|
|
void
|
|
send_decline(struct interface_info *ifi)
|
|
{
|
|
ssize_t rslt;
|
|
|
|
rslt = send_packet(ifi, inaddr_any, inaddr_broadcast, "DHCPDECLINE");
|
|
if (rslt != -1)
|
|
log_debug("%s: DHCPDECLINE", log_procname);
|
|
}
|
|
|
|
void
|
|
send_release(struct interface_info *ifi)
|
|
{
|
|
ssize_t rslt;
|
|
|
|
rslt = send_packet(ifi, ifi->configured->address, ifi->destination,
|
|
"DHCPRELEASE");
|
|
if (rslt != -1)
|
|
log_debug("%s: DHCPRELEASE", log_procname);
|
|
}
|
|
|
|
void
|
|
make_discover(struct interface_info *ifi, struct client_lease *lease)
|
|
{
|
|
struct option_data options[DHO_COUNT];
|
|
struct dhcp_packet *packet = &ifi->sent_packet;
|
|
unsigned char discover = DHCPDISCOVER;
|
|
int i;
|
|
|
|
memset(options, 0, sizeof(options));
|
|
memset(packet, 0, sizeof(*packet));
|
|
|
|
/* Set DHCP_MESSAGE_TYPE to DHCPDISCOVER */
|
|
i = DHO_DHCP_MESSAGE_TYPE;
|
|
options[i].data = &discover;
|
|
options[i].len = sizeof(discover);
|
|
|
|
/* Request the options we want */
|
|
i = DHO_DHCP_PARAMETER_REQUEST_LIST;
|
|
options[i].data = config->requested_options;
|
|
options[i].len = config->requested_option_count;
|
|
|
|
/* If we had an address, try to get it again. */
|
|
if (lease != NULL) {
|
|
ifi->requested_address = lease->address;
|
|
i = DHO_DHCP_REQUESTED_ADDRESS;
|
|
options[i].data = (char *)&lease->address;
|
|
options[i].len = sizeof(lease->address);
|
|
} else
|
|
ifi->requested_address.s_addr = INADDR_ANY;
|
|
|
|
/* Send any options requested in the config file. */
|
|
for (i = 0; i < DHO_COUNT; i++)
|
|
if (options[i].data == NULL &&
|
|
config->send_options[i].data != NULL) {
|
|
options[i].data = config->send_options[i].data;
|
|
options[i].len = config->send_options[i].len;
|
|
}
|
|
|
|
/*
|
|
* Set up the option buffer to fit in a 576-byte UDP packet, which
|
|
* RFC 791 says is the largest packet that *MUST* be accepted
|
|
* by any host.
|
|
*/
|
|
i = pack_options(ifi->sent_packet.options, 576 - DHCP_FIXED_LEN,
|
|
options);
|
|
if (i == -1 || packet->options[i] != DHO_END)
|
|
fatalx("options do not fit in DHCPDISCOVER packet");
|
|
ifi->sent_packet_length = DHCP_FIXED_NON_UDP+i+1;
|
|
if (ifi->sent_packet_length < BOOTP_MIN_LEN)
|
|
ifi->sent_packet_length = BOOTP_MIN_LEN;
|
|
|
|
packet->op = BOOTREQUEST;
|
|
packet->htype = HTYPE_ETHER;
|
|
packet->hlen = ETHER_ADDR_LEN;
|
|
packet->hops = 0;
|
|
packet->xid = ifi->xid;
|
|
packet->secs = 0; /* filled in by send_discover. */
|
|
packet->flags = 0;
|
|
|
|
packet->ciaddr.s_addr = INADDR_ANY;
|
|
packet->yiaddr.s_addr = INADDR_ANY;
|
|
packet->siaddr.s_addr = INADDR_ANY;
|
|
packet->giaddr.s_addr = INADDR_ANY;
|
|
|
|
memcpy(&packet->chaddr, ifi->hw_address.ether_addr_octet,
|
|
ETHER_ADDR_LEN);
|
|
}
|
|
|
|
void
|
|
make_request(struct interface_info *ifi, struct client_lease *lease)
|
|
{
|
|
struct option_data options[DHO_COUNT];
|
|
struct dhcp_packet *packet = &ifi->sent_packet;
|
|
unsigned char request = DHCPREQUEST;
|
|
int i;
|
|
|
|
memset(options, 0, sizeof(options));
|
|
memset(packet, 0, sizeof(*packet));
|
|
|
|
/* Set DHCP_MESSAGE_TYPE to DHCPREQUEST */
|
|
i = DHO_DHCP_MESSAGE_TYPE;
|
|
options[i].data = &request;
|
|
options[i].len = sizeof(request);
|
|
|
|
/* Request the options we want */
|
|
i = DHO_DHCP_PARAMETER_REQUEST_LIST;
|
|
options[i].data = config->requested_options;
|
|
options[i].len = config->requested_option_count;
|
|
|
|
/*
|
|
* If we are requesting an address that hasn't yet been assigned
|
|
* to us, use the DHCP Requested Address option.
|
|
*/
|
|
if (ifi->state == S_REQUESTING) {
|
|
/* Send back the server identifier. */
|
|
i = DHO_DHCP_SERVER_IDENTIFIER;
|
|
options[i].data = lease->options[i].data;
|
|
options[i].len = lease->options[i].len;
|
|
}
|
|
if (ifi->state == S_REQUESTING ||
|
|
ifi->state == S_REBOOTING) {
|
|
i = DHO_DHCP_REQUESTED_ADDRESS;
|
|
options[i].data = (char *)&lease->address.s_addr;
|
|
options[i].len = sizeof(lease->address.s_addr);
|
|
}
|
|
|
|
/* Send any options requested in the config file. */
|
|
for (i = 0; i < DHO_COUNT; i++)
|
|
if (options[i].data == NULL &&
|
|
config->send_options[i].data != NULL) {
|
|
options[i].data = config->send_options[i].data;
|
|
options[i].len = config->send_options[i].len;
|
|
}
|
|
|
|
/*
|
|
* Set up the option buffer to fit in a 576-byte UDP packet, which
|
|
* RFC 791 says is the largest packet that *MUST* be accepted
|
|
* by any host.
|
|
*/
|
|
i = pack_options(ifi->sent_packet.options, 576 - DHCP_FIXED_LEN,
|
|
options);
|
|
if (i == -1 || packet->options[i] != DHO_END)
|
|
fatalx("options do not fit in DHCPREQUEST packet");
|
|
ifi->sent_packet_length = DHCP_FIXED_NON_UDP+i+1;
|
|
if (ifi->sent_packet_length < BOOTP_MIN_LEN)
|
|
ifi->sent_packet_length = BOOTP_MIN_LEN;
|
|
|
|
packet->op = BOOTREQUEST;
|
|
packet->htype = HTYPE_ETHER;
|
|
packet->hlen = ETHER_ADDR_LEN;
|
|
packet->hops = 0;
|
|
packet->xid = ifi->xid;
|
|
packet->secs = 0; /* Filled in by send_request. */
|
|
packet->flags = 0;
|
|
|
|
/*
|
|
* If we own the address we're requesting, put it in ciaddr. Otherwise
|
|
* set ciaddr to zero.
|
|
*/
|
|
ifi->requested_address = lease->address;
|
|
if (ifi->state == S_BOUND ||
|
|
ifi->state == S_RENEWING)
|
|
packet->ciaddr.s_addr = lease->address.s_addr;
|
|
else
|
|
packet->ciaddr.s_addr = INADDR_ANY;
|
|
|
|
packet->yiaddr.s_addr = INADDR_ANY;
|
|
packet->siaddr.s_addr = INADDR_ANY;
|
|
packet->giaddr.s_addr = INADDR_ANY;
|
|
|
|
memcpy(&packet->chaddr, ifi->hw_address.ether_addr_octet,
|
|
ETHER_ADDR_LEN);
|
|
}
|
|
|
|
void
|
|
make_decline(struct interface_info *ifi, struct client_lease *lease)
|
|
{
|
|
struct option_data options[DHO_COUNT];
|
|
struct dhcp_packet *packet = &ifi->sent_packet;
|
|
unsigned char decline = DHCPDECLINE;
|
|
int i;
|
|
|
|
memset(options, 0, sizeof(options));
|
|
memset(packet, 0, sizeof(*packet));
|
|
|
|
/* Set DHCP_MESSAGE_TYPE to DHCPDECLINE */
|
|
i = DHO_DHCP_MESSAGE_TYPE;
|
|
options[i].data = &decline;
|
|
options[i].len = sizeof(decline);
|
|
|
|
/* Send back the server identifier. */
|
|
i = DHO_DHCP_SERVER_IDENTIFIER;
|
|
options[i].data = lease->options[i].data;
|
|
options[i].len = lease->options[i].len;
|
|
|
|
/* Send back the address we're declining. */
|
|
i = DHO_DHCP_REQUESTED_ADDRESS;
|
|
options[i].data = (char *)&lease->address.s_addr;
|
|
options[i].len = sizeof(lease->address.s_addr);
|
|
|
|
/* Send the uid if the user supplied one. */
|
|
i = DHO_DHCP_CLIENT_IDENTIFIER;
|
|
if (config->send_options[i].len != 0) {
|
|
options[i].data = config->send_options[i].data;
|
|
options[i].len = config->send_options[i].len;
|
|
}
|
|
|
|
/*
|
|
* Set up the option buffer to fit in a 576-byte UDP packet, which
|
|
* RFC 791 says is the largest packet that *MUST* be accepted
|
|
* by any host.
|
|
*/
|
|
i = pack_options(ifi->sent_packet.options, 576 - DHCP_FIXED_LEN,
|
|
options);
|
|
if (i == -1 || packet->options[i] != DHO_END)
|
|
fatalx("options do not fit in DHCPDECLINE packet");
|
|
ifi->sent_packet_length = DHCP_FIXED_NON_UDP+i+1;
|
|
if (ifi->sent_packet_length < BOOTP_MIN_LEN)
|
|
ifi->sent_packet_length = BOOTP_MIN_LEN;
|
|
|
|
packet->op = BOOTREQUEST;
|
|
packet->htype = HTYPE_ETHER;
|
|
packet->hlen = ETHER_ADDR_LEN;
|
|
packet->hops = 0;
|
|
packet->xid = ifi->xid;
|
|
packet->secs = 0;
|
|
packet->flags = 0;
|
|
|
|
/* ciaddr must always be zero. */
|
|
packet->ciaddr.s_addr = INADDR_ANY;
|
|
packet->yiaddr.s_addr = INADDR_ANY;
|
|
packet->siaddr.s_addr = INADDR_ANY;
|
|
packet->giaddr.s_addr = INADDR_ANY;
|
|
|
|
memcpy(&packet->chaddr, ifi->hw_address.ether_addr_octet,
|
|
ETHER_ADDR_LEN);
|
|
}
|
|
|
|
void
|
|
make_release(struct interface_info *ifi, struct client_lease *lease)
|
|
{
|
|
struct option_data options[DHO_COUNT];
|
|
struct dhcp_packet *packet = &ifi->sent_packet;
|
|
unsigned char release = DHCPRELEASE;
|
|
int i;
|
|
|
|
memset(options, 0, sizeof(options));
|
|
memset(packet, 0, sizeof(*packet));
|
|
|
|
/* Set DHCP_MESSAGE_TYPE to DHCPRELEASE */
|
|
i = DHO_DHCP_MESSAGE_TYPE;
|
|
options[i].data = &release;
|
|
options[i].len = sizeof(release);
|
|
|
|
/* Send back the server identifier. */
|
|
i = DHO_DHCP_SERVER_IDENTIFIER;
|
|
options[i].data = lease->options[i].data;
|
|
options[i].len = lease->options[i].len;
|
|
|
|
i = pack_options(ifi->sent_packet.options, 576 - DHCP_FIXED_LEN,
|
|
options);
|
|
if (i == -1 || packet->options[i] != DHO_END)
|
|
fatalx("options do not fit in DHCPRELEASE packet");
|
|
ifi->sent_packet_length = DHCP_FIXED_NON_UDP+i+1;
|
|
if (ifi->sent_packet_length < BOOTP_MIN_LEN)
|
|
ifi->sent_packet_length = BOOTP_MIN_LEN;
|
|
|
|
packet->op = BOOTREQUEST;
|
|
packet->htype = HTYPE_ETHER;
|
|
packet->hlen = ETHER_ADDR_LEN;
|
|
packet->hops = 0;
|
|
packet->xid = ifi->xid;
|
|
packet->secs = 0;
|
|
packet->flags = 0;
|
|
|
|
/*
|
|
* Note we return the *offered* address. NOT the configured address
|
|
* which could have been changed via dhclient.conf. But the packet
|
|
* is sent from the *configured* address.
|
|
*
|
|
* This might easily confuse a server, but if you play with fire
|
|
* by modifying the address you are on your own!
|
|
*/
|
|
packet->ciaddr.s_addr = ifi->active->address.s_addr;
|
|
packet->yiaddr.s_addr = INADDR_ANY;
|
|
packet->siaddr.s_addr = INADDR_ANY;
|
|
packet->giaddr.s_addr = INADDR_ANY;
|
|
|
|
memcpy(&packet->chaddr, ifi->hw_address.ether_addr_octet,
|
|
ETHER_ADDR_LEN);
|
|
}
|
|
|
|
void
|
|
free_client_lease(struct client_lease *lease)
|
|
{
|
|
int i;
|
|
|
|
if (lease == NULL)
|
|
return;
|
|
|
|
free(lease->server_name);
|
|
free(lease->filename);
|
|
for (i = 0; i < DHO_COUNT; i++)
|
|
free(lease->options[i].data);
|
|
|
|
free(lease);
|
|
}
|
|
|
|
void
|
|
write_lease_db(struct interface_info *ifi)
|
|
{
|
|
struct client_lease_tq *lease_db = &ifi->lease_db;
|
|
struct client_lease *lp, *pl;
|
|
char *leasestr;
|
|
|
|
TAILQ_FOREACH_SAFE(lp, lease_db, next, pl) {
|
|
if (lp != ifi->active && lease_expiry(lp) == 0) {
|
|
TAILQ_REMOVE(lease_db, lp, next);
|
|
free_client_lease(lp);
|
|
}
|
|
}
|
|
|
|
if (leaseFile == NULL)
|
|
return;
|
|
|
|
rewind(leaseFile);
|
|
|
|
/*
|
|
* The leases file is kept in chronological order, with the
|
|
* most recently bound lease last. When the file was read
|
|
* leases that were not expired were added to the head of the
|
|
* TAILQ ifi->leases as they were read. Therefore write out
|
|
* the leases in ifi->leases in reverse order to recreate
|
|
* the chonological order required.
|
|
*/
|
|
TAILQ_FOREACH_REVERSE(lp, lease_db, client_lease_tq, next) {
|
|
leasestr = lease_as_string("lease", lp);
|
|
if (leasestr != NULL)
|
|
fprintf(leaseFile, "%s", leasestr);
|
|
else
|
|
log_warnx("%s: cannot make lease into string",
|
|
log_procname);
|
|
}
|
|
|
|
fflush(leaseFile);
|
|
ftruncate(fileno(leaseFile), ftello(leaseFile));
|
|
fsync(fileno(leaseFile));
|
|
}
|
|
|
|
void
|
|
append_statement(char *string, size_t sz, char *s1, char *s2)
|
|
{
|
|
strlcat(string, s1, sz);
|
|
strlcat(string, s2, sz);
|
|
strlcat(string, ";\n", sz);
|
|
}
|
|
|
|
struct unwind_info *
|
|
lease_as_unwind_info(struct client_lease *lease)
|
|
{
|
|
struct unwind_info *unwind_info;
|
|
struct option_data *opt;
|
|
unsigned int servers;
|
|
|
|
unwind_info = calloc(1, sizeof(*unwind_info));
|
|
if (unwind_info == NULL)
|
|
fatal("unwind_info");
|
|
|
|
opt = &lease->options[DHO_DOMAIN_NAME_SERVERS];
|
|
if (opt->len != 0) {
|
|
servers = opt->len / sizeof(in_addr_t);
|
|
if (servers > MAXNS)
|
|
servers = MAXNS;
|
|
if (servers > 0) {
|
|
unwind_info->count = servers;
|
|
memcpy(unwind_info->ns, opt->data, servers *
|
|
sizeof(in_addr_t));
|
|
}
|
|
}
|
|
|
|
if (unwind_info->count == 0) {
|
|
free(unwind_info);
|
|
unwind_info = NULL;
|
|
}
|
|
|
|
return unwind_info;
|
|
}
|
|
|
|
struct proposal *
|
|
lease_as_proposal(struct client_lease *lease)
|
|
{
|
|
uint8_t defroute[5]; /* 1 + sizeof(in_addr_t) */
|
|
struct option_data fake;
|
|
struct option_data *opt;
|
|
struct proposal *proposal;
|
|
uint8_t *ns, *p, *routes, *domains;
|
|
unsigned int routes_len = 0, domains_len = 0, ns_len = 0;
|
|
uint16_t mtu;
|
|
|
|
/* Determine sizes of variable length data. */
|
|
opt = NULL;
|
|
if (lease->options[DHO_CLASSLESS_STATIC_ROUTES].len != 0) {
|
|
opt = &lease->options[DHO_CLASSLESS_STATIC_ROUTES];
|
|
} else if (lease->options[DHO_CLASSLESS_MS_STATIC_ROUTES].len != 0) {
|
|
opt = &lease->options[DHO_CLASSLESS_MS_STATIC_ROUTES];
|
|
} else if (lease->options[DHO_ROUTERS].len != 0) {
|
|
/* Fake a classless static default route. */
|
|
opt = &lease->options[DHO_ROUTERS];
|
|
fake.len = sizeof(defroute);
|
|
fake.data = defroute;
|
|
fake.data[0] = 0;
|
|
memcpy(&fake.data[1], opt->data, sizeof(defroute) - 1);
|
|
opt = &fake;
|
|
}
|
|
if (opt != NULL) {
|
|
routes_len = opt->len;
|
|
routes = opt->data;
|
|
}
|
|
|
|
opt = NULL;
|
|
if (lease->options[DHO_DOMAIN_SEARCH].len != 0)
|
|
opt = &lease->options[DHO_DOMAIN_SEARCH];
|
|
else if (lease->options[DHO_DOMAIN_NAME].len != 0)
|
|
opt = &lease->options[DHO_DOMAIN_NAME];
|
|
if (opt != NULL) {
|
|
domains_len = opt->len;
|
|
domains = opt->data;
|
|
}
|
|
|
|
if (lease->options[DHO_DOMAIN_NAME_SERVERS].len != 0) {
|
|
opt = &lease->options[DHO_DOMAIN_NAME_SERVERS];
|
|
ns_len = opt->len;
|
|
ns = opt->data;
|
|
}
|
|
|
|
/* Allocate proposal. */
|
|
proposal = calloc(1, sizeof(*proposal) + routes_len + domains_len +
|
|
ns_len);
|
|
if (proposal == NULL)
|
|
fatal("proposal");
|
|
|
|
/* Fill in proposal. */
|
|
proposal->address = lease->address;
|
|
|
|
opt = &lease->options[DHO_INTERFACE_MTU];
|
|
if (opt->len == sizeof(mtu)) {
|
|
memcpy(&mtu, opt->data, sizeof(mtu));
|
|
proposal->mtu = ntohs(mtu);
|
|
}
|
|
|
|
opt = &lease->options[DHO_SUBNET_MASK];
|
|
if (opt->len == sizeof(proposal->netmask))
|
|
memcpy(&proposal->netmask, opt->data, opt->len);
|
|
|
|
/* Append variable length uint8_t data. */
|
|
p = (uint8_t *)proposal + sizeof(struct proposal);
|
|
memcpy(p, routes, routes_len);
|
|
p += routes_len;
|
|
proposal->routes_len = routes_len;
|
|
memcpy(p, domains, domains_len);
|
|
p += domains_len;
|
|
proposal->domains_len = domains_len;
|
|
memcpy(p, ns, ns_len);
|
|
proposal->ns_len = ns_len;
|
|
|
|
return proposal;
|
|
}
|
|
|
|
char *
|
|
lease_as_string(char *type, struct client_lease *lease)
|
|
{
|
|
static char string[8192];
|
|
char timebuf[27]; /* 6 2017/04/08 05:47:50 UTC; */
|
|
struct option_data *opt;
|
|
struct tm *tm;
|
|
char *buf, *name;
|
|
time_t t;
|
|
size_t rslt;
|
|
int i;
|
|
|
|
memset(string, 0, sizeof(string));
|
|
|
|
strlcat(string, type, sizeof(string));
|
|
strlcat(string, " {\n", sizeof(string));
|
|
strlcat(string, BOOTP_LEASE(lease) ? " bootp;\n" : "", sizeof(string));
|
|
|
|
append_statement(string, sizeof(string), " fixed-address ",
|
|
inet_ntoa(lease->address));
|
|
append_statement(string, sizeof(string), " next-server ",
|
|
inet_ntoa(lease->next_server));
|
|
|
|
if (lease->filename != NULL) {
|
|
buf = pretty_print_string(lease->filename,
|
|
strlen(lease->filename), 1);
|
|
if (buf == NULL)
|
|
return NULL;
|
|
append_statement(string, sizeof(string), " filename ", buf);
|
|
}
|
|
if (lease->server_name != NULL) {
|
|
buf = pretty_print_string(lease->server_name,
|
|
strlen(lease->server_name), 1);
|
|
if (buf == NULL)
|
|
return NULL;
|
|
append_statement(string, sizeof(string), " server-name ",
|
|
buf);
|
|
}
|
|
if (lease->ssid_len != 0) {
|
|
buf = pretty_print_string(lease->ssid, lease->ssid_len, 1);
|
|
if (buf == NULL)
|
|
return NULL;
|
|
append_statement(string, sizeof(string), " ssid ", buf);
|
|
}
|
|
|
|
for (i = 0; i < DHO_COUNT; i++) {
|
|
opt = &lease->options[i];
|
|
if (opt->len == 0)
|
|
continue;
|
|
name = code_to_name(i);
|
|
|
|
buf = pretty_print_option(i, opt, 1);
|
|
if (strlen(buf) == 0)
|
|
continue;
|
|
strlcat(string, " option ", sizeof(string));
|
|
strlcat(string, name, sizeof(string));
|
|
append_statement(string, sizeof(string), " ", buf);
|
|
}
|
|
|
|
i = asprintf(&buf, "%lld", (long long)lease->epoch);
|
|
if (i == -1)
|
|
return NULL;
|
|
append_statement(string, sizeof(string), " epoch ", buf);
|
|
free(buf);
|
|
|
|
t = lease->epoch + lease_renewal(lease);
|
|
if ((tm = gmtime(&t)) == NULL)
|
|
return NULL;
|
|
rslt = strftime(timebuf, sizeof(timebuf), DB_TIMEFMT, tm);
|
|
if (rslt == 0)
|
|
return NULL;
|
|
append_statement(string, sizeof(string), " renew ", timebuf);
|
|
|
|
t = lease->epoch + lease_rebind(lease);
|
|
if ((tm = gmtime(&t)) == NULL)
|
|
return NULL;
|
|
rslt = strftime(timebuf, sizeof(timebuf), DB_TIMEFMT, tm);
|
|
if (rslt == 0)
|
|
return NULL;
|
|
append_statement(string, sizeof(string), " rebind ", timebuf);
|
|
|
|
t = lease->epoch + lease_expiry(lease);
|
|
if ((tm = gmtime(&t)) == NULL)
|
|
return NULL;
|
|
rslt = strftime(timebuf, sizeof(timebuf), DB_TIMEFMT, tm);
|
|
if (rslt == 0)
|
|
return NULL;
|
|
append_statement(string, sizeof(string), " expire ", timebuf);
|
|
|
|
rslt = strlcat(string, "}\n", sizeof(string));
|
|
if (rslt >= sizeof(string))
|
|
return NULL;
|
|
|
|
return string;
|
|
}
|
|
|
|
void
|
|
go_daemon(void)
|
|
{
|
|
static int daemonized = 0;
|
|
|
|
if ((cmd_opts & OPT_FOREGROUND) != 0 || daemonized != 0)
|
|
return;
|
|
|
|
daemonized = 1;
|
|
|
|
if (rdaemon(nullfd) == -1)
|
|
fatal("daemonize");
|
|
|
|
/* Stop logging to stderr. */
|
|
log_init(0, LOG_DAEMON);
|
|
if ((cmd_opts & OPT_VERBOSE) != 0)
|
|
log_setverbose(1); /* Show log_debug() messages. */
|
|
log_procinit(log_procname);
|
|
|
|
setproctitle("%s", log_procname);
|
|
signal(SIGHUP, SIG_IGN);
|
|
signal(SIGPIPE, SIG_IGN);
|
|
}
|
|
|
|
int
|
|
rdaemon(int devnull)
|
|
{
|
|
if (devnull == -1) {
|
|
errno = EBADF;
|
|
return -1;
|
|
}
|
|
if (fcntl(devnull, F_GETFL) == -1)
|
|
return -1;
|
|
|
|
switch (fork()) {
|
|
case -1:
|
|
return -1;
|
|
case 0:
|
|
break;
|
|
default:
|
|
_exit(0);
|
|
}
|
|
|
|
if (setsid() == -1)
|
|
return -1;
|
|
|
|
(void)dup2(devnull, STDIN_FILENO);
|
|
(void)dup2(devnull, STDOUT_FILENO);
|
|
(void)dup2(devnull, STDERR_FILENO);
|
|
if (devnull > 2)
|
|
(void)close(devnull);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* resolv_conf(5) says a max of DHCP_DOMAIN_SEARCH_CNT domains and total
|
|
* length of DHCP_DOMAIN_SEARCH_LEN bytes are acceptable for the 'search'
|
|
* statement.
|
|
*/
|
|
int
|
|
res_hnok_list(const char *names)
|
|
{
|
|
char *dupnames, *hn, *inputstring;
|
|
int count;
|
|
|
|
if (strlen(names) >= DHCP_DOMAIN_SEARCH_LEN)
|
|
return 0;
|
|
|
|
dupnames = inputstring = strdup(names);
|
|
if (inputstring == NULL)
|
|
fatal("domain name list");
|
|
|
|
count = 0;
|
|
while ((hn = strsep(&inputstring, " \t")) != NULL) {
|
|
if (strlen(hn) == 0)
|
|
continue;
|
|
if (res_hnok(hn) == 0)
|
|
break;
|
|
count++;
|
|
if (count > DHCP_DOMAIN_SEARCH_CNT)
|
|
break;
|
|
}
|
|
|
|
free(dupnames);
|
|
|
|
return count > 0 && count < 7 && hn == NULL;
|
|
}
|
|
|
|
/*
|
|
* Decode a byte string encoding a list of domain names as specified in RFC1035
|
|
* section 4.1.4.
|
|
*
|
|
* The result is a string consisting of a blank separated list of domain names.
|
|
*
|
|
* e.g.
|
|
*
|
|
* 3:65:6e:67:5:61:70:70:6c:65:3:63:6f:6d:0:9:6d:61:72:6b:65:74:69:6e:67:c0:04
|
|
*
|
|
* which represents
|
|
*
|
|
* 3 |'e'|'n'|'g'| 5 |'a'|'p'|'p'|'l'|
|
|
* 'e'| 3 |'c'|'o'|'m'| 0 | 9 |'m'|'a'|
|
|
* 'r'|'k'|'e'|'t'|'i'|'n'|'g'|xC0|x04|
|
|
*
|
|
* will be translated to
|
|
*
|
|
* "eng.apple.com. marketing.apple.com."
|
|
*/
|
|
char *
|
|
rfc1035_as_string(unsigned char *src, size_t srclen)
|
|
{
|
|
static char search[DHCP_DOMAIN_SEARCH_LEN];
|
|
unsigned char name[DHCP_DOMAIN_SEARCH_LEN];
|
|
unsigned char *endsrc, *cp;
|
|
int len, domains;
|
|
|
|
memset(search, 0, sizeof(search));
|
|
|
|
/* Compute expanded length. */
|
|
domains = 0;
|
|
cp = src;
|
|
endsrc = src + srclen;
|
|
|
|
while (cp < endsrc && domains < DHCP_DOMAIN_SEARCH_CNT) {
|
|
len = dn_expand(src, endsrc, cp, name, sizeof(name));
|
|
if (len == -1)
|
|
goto bad;
|
|
cp += len;
|
|
if (domains > 0)
|
|
strlcat(search, " ", sizeof(search));
|
|
strlcat(search, name, sizeof(search));
|
|
if (strlcat(search, ".", sizeof(search)) >= sizeof(search))
|
|
goto bad;
|
|
domains++;
|
|
}
|
|
|
|
return search;
|
|
|
|
bad:
|
|
memset(search, 0, sizeof(search));
|
|
return search;
|
|
}
|
|
|
|
void
|
|
fork_privchld(struct interface_info *ifi, int fd, int fd2)
|
|
{
|
|
struct pollfd pfd[1];
|
|
struct imsgbuf *priv_ibuf;
|
|
ssize_t n;
|
|
int ioctlfd, routefd, nfds, rslt;
|
|
|
|
switch (fork()) {
|
|
case -1:
|
|
fatal("fork");
|
|
break;
|
|
case 0:
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
if (chdir("/") == -1)
|
|
fatal("chdir(\"/\")");
|
|
|
|
go_daemon();
|
|
|
|
free(log_procname);
|
|
rslt = asprintf(&log_procname, "%s [priv]", ifi->name);
|
|
if (rslt == -1)
|
|
fatal("log_procname");
|
|
setproctitle("%s", log_procname);
|
|
log_procinit(log_procname);
|
|
|
|
close(fd2);
|
|
|
|
if ((priv_ibuf = malloc(sizeof(*priv_ibuf))) == NULL)
|
|
fatal("priv_ibuf");
|
|
|
|
imsg_init(priv_ibuf, fd);
|
|
|
|
if ((ioctlfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
|
|
fatal("socket(AF_INET, SOCK_DGRAM)");
|
|
if ((routefd = socket(AF_ROUTE, SOCK_RAW, 0)) == -1)
|
|
fatal("socket(AF_ROUTE, SOCK_RAW)");
|
|
|
|
if (unveil(_PATH_RESCONF, "wc") == -1)
|
|
fatal("unveil %s", _PATH_RESCONF);
|
|
if (unveil("/etc/resolv.conf.tail", "r") == -1)
|
|
fatal("unveil /etc/resolve.conf.tail");
|
|
if (unveil(NULL, NULL) == -1)
|
|
fatal("unveil");
|
|
|
|
while (quit == 0) {
|
|
pfd[0].fd = priv_ibuf->fd;
|
|
pfd[0].events = POLLIN;
|
|
|
|
nfds = ppoll(pfd, 1, NULL, NULL);
|
|
if (nfds == -1) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
log_warn("%s: ppoll(priv_ibuf)", log_procname);
|
|
break;
|
|
}
|
|
if ((pfd[0].revents & (POLLERR | POLLHUP | POLLNVAL)) != 0)
|
|
break;
|
|
if (nfds == 0 || (pfd[0].revents & POLLIN) == 0)
|
|
continue;
|
|
|
|
if ((n = imsg_read(priv_ibuf)) == -1 && errno != EAGAIN) {
|
|
log_warn("%s: imsg_read(priv_ibuf)", log_procname);
|
|
break;
|
|
}
|
|
if (n == 0) {
|
|
/* Connection closed - other end should log message. */
|
|
break;
|
|
}
|
|
|
|
dispatch_imsg(ifi->name, ifi->rdomain, ioctlfd, routefd,
|
|
priv_ibuf);
|
|
}
|
|
close(routefd);
|
|
close(ioctlfd);
|
|
|
|
imsg_clear(priv_ibuf);
|
|
close(fd);
|
|
|
|
exit(1);
|
|
}
|
|
|
|
struct client_lease *
|
|
apply_defaults(struct client_lease *lease)
|
|
{
|
|
struct option_data emptyopt = {0, NULL};
|
|
struct client_lease *newlease;
|
|
char *fmt;
|
|
int i;
|
|
|
|
newlease = clone_lease(lease);
|
|
if (newlease == NULL)
|
|
fatalx("unable to clone lease");
|
|
|
|
if (config->filename != NULL) {
|
|
free(newlease->filename);
|
|
newlease->filename = strdup(config->filename);
|
|
if (newlease->filename == NULL)
|
|
fatal("strdup(config->filename)");
|
|
}
|
|
if (config->server_name != NULL) {
|
|
free(newlease->server_name);
|
|
newlease->server_name = strdup(config->server_name);
|
|
if (newlease->server_name == NULL)
|
|
fatal("strdup(config->server_name)");
|
|
}
|
|
if (config->address.s_addr != INADDR_ANY)
|
|
newlease->address.s_addr = config->address.s_addr;
|
|
if (config->next_server.s_addr != INADDR_ANY)
|
|
newlease->next_server.s_addr = config->next_server.s_addr;
|
|
|
|
for (i = 0; i < DHO_COUNT; i++) {
|
|
fmt = code_to_format(i);
|
|
switch (config->default_actions[i]) {
|
|
case ACTION_IGNORE:
|
|
merge_option_data(fmt, &emptyopt, &emptyopt,
|
|
&newlease->options[i]);
|
|
break;
|
|
|
|
case ACTION_SUPERSEDE:
|
|
merge_option_data(fmt, &config->defaults[i], &emptyopt,
|
|
&newlease->options[i]);
|
|
break;
|
|
|
|
case ACTION_PREPEND:
|
|
merge_option_data(fmt, &config->defaults[i],
|
|
&lease->options[i], &newlease->options[i]);
|
|
break;
|
|
|
|
case ACTION_APPEND:
|
|
merge_option_data(fmt, &lease->options[i],
|
|
&config->defaults[i], &newlease->options[i]);
|
|
break;
|
|
|
|
case ACTION_DEFAULT:
|
|
if (newlease->options[i].len == 0)
|
|
merge_option_data(fmt, &config->defaults[i],
|
|
&emptyopt, &newlease->options[i]);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (newlease->options[DHO_STATIC_ROUTES].len != 0) {
|
|
log_debug("%s: DHO_STATIC_ROUTES (option 33) not supported",
|
|
log_procname);
|
|
free(newlease->options[DHO_STATIC_ROUTES].data);
|
|
newlease->options[DHO_STATIC_ROUTES].data = NULL;
|
|
newlease->options[DHO_STATIC_ROUTES].len = 0;
|
|
}
|
|
|
|
/*
|
|
* RFC 3442 says client *MUST* ignore DHO_ROUTERS
|
|
* when DHO_CLASSLESS_[MS_]_ROUTES present.
|
|
*/
|
|
if ((newlease->options[DHO_CLASSLESS_MS_STATIC_ROUTES].len != 0) ||
|
|
(newlease->options[DHO_CLASSLESS_STATIC_ROUTES].len != 0)) {
|
|
free(newlease->options[DHO_ROUTERS].data);
|
|
newlease->options[DHO_ROUTERS].data = NULL;
|
|
newlease->options[DHO_ROUTERS].len = 0;
|
|
}
|
|
|
|
return newlease;
|
|
}
|
|
|
|
struct client_lease *
|
|
clone_lease(struct client_lease *oldlease)
|
|
{
|
|
struct client_lease *newlease;
|
|
int i;
|
|
|
|
newlease = calloc(1, sizeof(*newlease));
|
|
if (newlease == NULL)
|
|
goto cleanup;
|
|
|
|
newlease->epoch = oldlease->epoch;
|
|
newlease->address = oldlease->address;
|
|
newlease->next_server = oldlease->next_server;
|
|
memcpy(newlease->ssid, oldlease->ssid, sizeof(newlease->ssid));
|
|
newlease->ssid_len = oldlease->ssid_len;
|
|
|
|
if (oldlease->server_name != NULL) {
|
|
newlease->server_name = strdup(oldlease->server_name);
|
|
if (newlease->server_name == NULL)
|
|
goto cleanup;
|
|
}
|
|
if (oldlease->filename != NULL) {
|
|
newlease->filename = strdup(oldlease->filename);
|
|
if (newlease->filename == NULL)
|
|
goto cleanup;
|
|
}
|
|
|
|
for (i = 0; i < DHO_COUNT; i++) {
|
|
if (oldlease->options[i].len == 0)
|
|
continue;
|
|
newlease->options[i].len = oldlease->options[i].len;
|
|
newlease->options[i].data = calloc(1,
|
|
newlease->options[i].len);
|
|
if (newlease->options[i].data == NULL)
|
|
goto cleanup;
|
|
memcpy(newlease->options[i].data, oldlease->options[i].data,
|
|
newlease->options[i].len);
|
|
}
|
|
|
|
return newlease;
|
|
|
|
cleanup:
|
|
free_client_lease(newlease);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int
|
|
autoconf(struct interface_info *ifi)
|
|
{
|
|
struct ifreq ifr;
|
|
int ioctlfd;
|
|
|
|
if ((ioctlfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
|
|
fatal("socket(AF_INET, SOCK_DGRAM)");
|
|
|
|
memset(&ifr, 0, sizeof(ifr));
|
|
strlcpy(ifr.ifr_name, ifi->name, sizeof(ifr.ifr_name));
|
|
|
|
if (ioctl(ioctlfd, SIOCGIFXFLAGS, (caddr_t)&ifr) < 0)
|
|
fatal("SIOGIFXFLAGS");
|
|
|
|
close(ioctlfd);
|
|
|
|
return ifr.ifr_flags & IFXF_AUTOCONF4;
|
|
}
|
|
|
|
int
|
|
take_charge(struct interface_info *ifi, int routefd, char *leasespath)
|
|
{
|
|
const struct timespec max_timeout = { 9, 0 };
|
|
const struct timespec resend_intvl = { 3, 0 };
|
|
const struct timespec leasefile_intvl = { 0, 3000000 };
|
|
struct timespec now, resend, stop, timeout;
|
|
struct pollfd fds[1];
|
|
struct rt_msghdr rtm;
|
|
int fd, nfds;
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
resend = now;
|
|
timespecadd(&now, &max_timeout, &stop);
|
|
|
|
/*
|
|
* Send RTM_PROPOSAL with RTF_PROTO3 set.
|
|
*
|
|
* When it comes back, we're in charge and other dhclients are
|
|
* dead processes walking.
|
|
*/
|
|
memset(&rtm, 0, sizeof(rtm));
|
|
|
|
rtm.rtm_version = RTM_VERSION;
|
|
rtm.rtm_type = RTM_PROPOSAL;
|
|
rtm.rtm_msglen = sizeof(rtm);
|
|
rtm.rtm_tableid = ifi->rdomain;
|
|
rtm.rtm_index = ifi->index;
|
|
rtm.rtm_priority = RTP_PROPOSAL_DHCLIENT;
|
|
rtm.rtm_addrs = 0;
|
|
rtm.rtm_flags = RTF_UP | RTF_PROTO3;
|
|
|
|
for (fd = -1; fd == -1 && quit != TERMINATE;) {
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
if (timespeccmp(&now, &stop, >=))
|
|
fatalx("failed to take charge");
|
|
|
|
if ((ifi->flags & IFI_IN_CHARGE) == 0) {
|
|
if (timespeccmp(&now, &resend, >=)) {
|
|
timespecadd(&resend, &resend_intvl, &resend);
|
|
rtm.rtm_seq = ifi->xid = arc4random();
|
|
if (write(routefd, &rtm, sizeof(rtm)) == -1)
|
|
fatal("write(routefd)");
|
|
}
|
|
timespecsub(&resend, &now, &timeout);
|
|
} else {
|
|
/*
|
|
* Keep trying to open leasefile in 3ms intervals
|
|
* while continuing to process any RTM_* messages
|
|
* that come in.
|
|
*/
|
|
timeout = leasefile_intvl;
|
|
}
|
|
|
|
fds[0].fd = routefd;
|
|
fds[0].events = POLLIN;
|
|
nfds = ppoll(fds, 1, &timeout, NULL);
|
|
if (nfds == -1) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
fatal("ppoll(routefd)");
|
|
}
|
|
|
|
if ((fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) != 0)
|
|
fatalx("routefd: ERR|HUP|NVAL");
|
|
if (nfds == 1 && (fds[0].revents & POLLIN) == POLLIN)
|
|
routefd_handler(ifi, routefd);
|
|
|
|
if (quit != TERMINATE && (ifi->flags & IFI_IN_CHARGE) == IFI_IN_CHARGE) {
|
|
fd = open(leasespath, O_NONBLOCK |
|
|
O_RDONLY|O_EXLOCK|O_CREAT|O_NOFOLLOW, 0640);
|
|
if (fd == -1 && errno != EWOULDBLOCK)
|
|
break;
|
|
}
|
|
}
|
|
|
|
return fd;
|
|
}
|
|
|
|
struct client_lease *
|
|
get_recorded_lease(struct interface_info *ifi)
|
|
{
|
|
char ifname[IF_NAMESIZE];
|
|
struct client_lease *lp;
|
|
int i;
|
|
|
|
/* Update on-disk db, which clears out expired leases. */
|
|
ifi->active = NULL;
|
|
write_lease_db(ifi);
|
|
|
|
/* Run through the list of leases and see if one can be used. */
|
|
i = DHO_DHCP_CLIENT_IDENTIFIER;
|
|
TAILQ_FOREACH(lp, &ifi->lease_db, next) {
|
|
if (lp->ssid_len != ifi->ssid_len)
|
|
continue;
|
|
if (memcmp(lp->ssid, ifi->ssid, lp->ssid_len) != 0)
|
|
continue;
|
|
if ((lp->options[i].len != 0) && ((lp->options[i].len !=
|
|
config->send_options[i].len) ||
|
|
memcmp(lp->options[i].data, config->send_options[i].data,
|
|
lp->options[i].len) != 0))
|
|
continue;
|
|
if (addressinuse(ifi->name, lp->address, ifname) != 0 &&
|
|
strncmp(ifname, ifi->name, IF_NAMESIZE) != 0)
|
|
continue;
|
|
break;
|
|
}
|
|
|
|
return lp;
|
|
}
|
|
|
|
time_t
|
|
lease_expiry(struct client_lease *lease)
|
|
{
|
|
time_t cur_time;
|
|
uint32_t expiry;
|
|
|
|
time(&cur_time);
|
|
expiry = 0;
|
|
if (lease->options[DHO_DHCP_LEASE_TIME].len == sizeof(expiry)) {
|
|
memcpy(&expiry, lease->options[DHO_DHCP_LEASE_TIME].data,
|
|
sizeof(expiry));
|
|
expiry = ntohl(expiry);
|
|
if (expiry < 60)
|
|
expiry = 60;
|
|
}
|
|
expiry = lease->epoch + expiry - cur_time;
|
|
|
|
return (expiry > 0) ? expiry : 0;
|
|
}
|
|
|
|
time_t
|
|
lease_renewal(struct client_lease *lease)
|
|
{
|
|
time_t cur_time, expiry;
|
|
uint32_t renewal;
|
|
|
|
time(&cur_time);
|
|
expiry = lease_expiry(lease);
|
|
|
|
renewal = expiry / 2;
|
|
if (lease->options[DHO_DHCP_RENEWAL_TIME].len == sizeof(renewal)) {
|
|
memcpy(&renewal, lease->options[DHO_DHCP_RENEWAL_TIME].data,
|
|
sizeof(renewal));
|
|
renewal = ntohl(renewal);
|
|
}
|
|
renewal = lease->epoch + renewal - cur_time;
|
|
|
|
return (renewal > 0) ? renewal : 0;
|
|
}
|
|
|
|
time_t
|
|
lease_rebind(struct client_lease *lease)
|
|
{
|
|
time_t cur_time, expiry;
|
|
uint32_t rebind;
|
|
|
|
time(&cur_time);
|
|
expiry = lease_expiry(lease);
|
|
|
|
rebind = (expiry / 8) * 7;
|
|
if (lease->options[DHO_DHCP_REBINDING_TIME].len == sizeof(rebind)) {
|
|
memcpy(&rebind, lease->options[DHO_DHCP_REBINDING_TIME].data,
|
|
sizeof(rebind));
|
|
rebind = ntohl(rebind);
|
|
}
|
|
rebind = lease->epoch + rebind - cur_time;
|
|
|
|
return (rebind > 0) ? rebind : 0;
|
|
}
|
|
|
|
void
|
|
get_lease_timeouts(struct interface_info *ifi, struct client_lease *lease)
|
|
{
|
|
struct timespec now, interval;
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
timespecclear(&interval);
|
|
|
|
interval.tv_sec = lease_expiry(lease);
|
|
timespecadd(&now, &interval, &ifi->expiry);
|
|
|
|
interval.tv_sec = lease_rebind(lease);
|
|
timespecadd(&now, &interval, &ifi->rebind);
|
|
|
|
interval.tv_sec = lease_renewal(lease);
|
|
timespecadd(&now, &interval, &ifi->renew);
|
|
|
|
if (timespeccmp(&ifi->rebind, &ifi->expiry, >))
|
|
ifi->rebind = ifi->expiry;
|
|
if (timespeccmp(&ifi->renew, &ifi->rebind, >))
|
|
ifi->renew = ifi->rebind;
|
|
}
|
|
|
|
void
|
|
tick_msg(const char *preamble, int action)
|
|
{
|
|
const struct timespec grace_intvl = {3, 0};
|
|
const struct timespec link_intvl = {config->link_interval, 0};
|
|
static struct timespec grace, stop;
|
|
struct timespec now;
|
|
static int linkup, preamble_sent, sleeping;
|
|
int printmsg;
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
|
|
if (!timespecisset(&stop)) {
|
|
preamble_sent = 0;
|
|
timespecadd(&now, &link_intvl, &stop);
|
|
timespecadd(&now, &grace_intvl, &grace);
|
|
return;
|
|
}
|
|
|
|
if (isatty(STDERR_FILENO) == 0 || sleeping == 1)
|
|
printmsg = 0; /* Already in the background. */
|
|
else if (timespeccmp(&now, &grace, <))
|
|
printmsg = 0; /* Wait a bit before speaking. */
|
|
else if (linkup && strcmp("link", preamble) == 0)
|
|
printmsg = 0; /* One 'got link' is enough for anyone. */
|
|
else if (log_getverbose())
|
|
printmsg = 0; /* Verbose has sufficent verbiage. */
|
|
else
|
|
printmsg = 1;
|
|
|
|
if (timespeccmp(&now, &stop, >=)) {
|
|
if (action == TICK_WAIT)
|
|
action = TICK_DAEMON;
|
|
if (linkup == 0) {
|
|
log_debug("%s: link timeout (%lld seconds) expired",
|
|
log_procname, (long long)link_intvl.tv_sec);
|
|
linkup = 1;
|
|
}
|
|
}
|
|
|
|
if (printmsg && preamble_sent == 0) {
|
|
fprintf(stderr, "%s: no %s...", log_procname, preamble);
|
|
preamble_sent = 1;
|
|
}
|
|
|
|
switch (action) {
|
|
case TICK_SUCCESS:
|
|
if (printmsg)
|
|
fprintf(stderr, "got %s\n", preamble);
|
|
preamble_sent = 0;
|
|
if (strcmp("link", preamble) == 0) {
|
|
linkup = 1;
|
|
/* New silent period for "no lease ... got lease". */
|
|
timespecadd(&now, &grace_intvl, &grace);
|
|
}
|
|
break;
|
|
case TICK_WAIT:
|
|
if (printmsg)
|
|
fprintf(stderr, ".");
|
|
break;
|
|
case TICK_DAEMON:
|
|
if (printmsg)
|
|
fprintf(stderr, "sleeping\n");
|
|
go_daemon();
|
|
sleeping = 1; /* OPT_FOREGROUND means isatty() == 1! */
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (printmsg)
|
|
fflush(stderr);
|
|
}
|
|
|
|
/*
|
|
* Release the lease used to configure the interface.
|
|
*
|
|
* 1) Send DHCPRELEASE.
|
|
* 2) Unconfigure address/routes/etc.
|
|
* 3) Remove lease from database & write updated DB.
|
|
*/
|
|
void
|
|
release_lease(struct interface_info *ifi)
|
|
{
|
|
char buf[INET_ADDRSTRLEN];
|
|
struct option_data *opt;
|
|
|
|
if (ifi->configured == NULL || ifi->active == NULL)
|
|
return; /* Nothing to release. */
|
|
strlcpy(buf, inet_ntoa(ifi->configured->address), sizeof(buf));
|
|
|
|
opt = &ifi->active->options[DHO_DHCP_SERVER_IDENTIFIER];
|
|
if (opt->len == sizeof(in_addr_t))
|
|
ifi->destination.s_addr = *(in_addr_t *)opt->data;
|
|
else
|
|
ifi->destination.s_addr = INADDR_BROADCAST;
|
|
|
|
ifi->xid = arc4random();
|
|
make_release(ifi, ifi->active);
|
|
send_release(ifi);
|
|
|
|
tell_unwind(NULL, ifi->flags);
|
|
|
|
revoke_proposal(ifi->configured);
|
|
imsg_flush(unpriv_ibuf);
|
|
|
|
TAILQ_REMOVE(&ifi->lease_db, ifi->active, next);
|
|
free_client_lease(ifi->active);
|
|
ifi->active = NULL;
|
|
write_lease_db(ifi);
|
|
|
|
free(ifi->configured);
|
|
ifi->configured = NULL;
|
|
free(ifi->unwind_info);
|
|
ifi->unwind_info = NULL;
|
|
|
|
log_warnx("%s: %s RELEASED to %s", log_procname, buf,
|
|
inet_ntoa(ifi->destination));
|
|
}
|
|
|
|
void
|
|
propose_release(struct interface_info *ifi)
|
|
{
|
|
const struct timespec max_timeout = { 3, 0 };
|
|
struct timespec now, stop, timeout;
|
|
struct pollfd fds[1];
|
|
struct rt_msghdr rtm;
|
|
int nfds, routefd, rtfilter;
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
timespecadd(&now, &max_timeout, &stop);
|
|
|
|
if ((routefd = socket(AF_ROUTE, SOCK_RAW, AF_INET)) == -1)
|
|
fatal("socket(AF_ROUTE, SOCK_RAW)");
|
|
|
|
rtfilter = ROUTE_FILTER(RTM_PROPOSAL);
|
|
|
|
if (setsockopt(routefd, AF_ROUTE, ROUTE_MSGFILTER,
|
|
&rtfilter, sizeof(rtfilter)) == -1)
|
|
fatal("setsockopt(ROUTE_MSGFILTER)");
|
|
if (setsockopt(routefd, AF_ROUTE, ROUTE_TABLEFILTER, &ifi->rdomain,
|
|
sizeof(ifi->rdomain)) == -1)
|
|
fatal("setsockopt(ROUTE_TABLEFILTER)");
|
|
|
|
memset(&rtm, 0, sizeof(rtm));
|
|
rtm.rtm_version = RTM_VERSION;
|
|
rtm.rtm_type = RTM_PROPOSAL;
|
|
rtm.rtm_msglen = sizeof(rtm);
|
|
rtm.rtm_tableid = ifi->rdomain;
|
|
rtm.rtm_index = ifi->index;
|
|
rtm.rtm_priority = RTP_PROPOSAL_DHCLIENT;
|
|
rtm.rtm_addrs = 0;
|
|
rtm.rtm_flags = RTF_UP;
|
|
rtm.rtm_flags |= RTF_PROTO2;
|
|
rtm.rtm_seq = ifi->xid = arc4random();
|
|
|
|
if (write(routefd, &rtm, sizeof(rtm)) == -1)
|
|
fatal("write(routefd)");
|
|
log_debug("%s: sent RTM_PROPOSAL to release lease", log_procname);
|
|
|
|
while (quit == 0) {
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
if (timespeccmp(&now, &stop, >=))
|
|
break;
|
|
timespecsub(&stop, &now, &timeout);
|
|
fds[0].fd = routefd;
|
|
fds[0].events = POLLIN;
|
|
nfds = ppoll(fds, 1, &timeout, NULL);
|
|
if (nfds == -1) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
fatal("ppoll(routefd)");
|
|
}
|
|
if ((fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) != 0)
|
|
fatalx("routefd: ERR|HUP|NVAL");
|
|
if (nfds == 0 || (fds[0].revents & POLLIN) == 0)
|
|
continue;
|
|
routefd_handler(ifi, routefd);
|
|
}
|
|
}
|