1073 lines
27 KiB
C
1073 lines
27 KiB
C
/* $OpenBSD: kroute.c,v 1.197 2021/03/28 17:25:21 krw Exp $ */
|
|
|
|
/*
|
|
* Copyright 2012 Kenneth R Westerback <krw@openbsd.org>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include <sys/ioctl.h>
|
|
#include <sys/queue.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/sysctl.h>
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <net/if.h>
|
|
#include <net/if_types.h>
|
|
#include <net/route.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include <netinet/if_ether.h>
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <ifaddrs.h>
|
|
#include <imsg.h>
|
|
#include <limits.h>
|
|
#include <poll.h>
|
|
#include <resolv.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "dhcp.h"
|
|
#include "dhcpd.h"
|
|
#include "log.h"
|
|
#include "privsep.h"
|
|
|
|
#define ROUNDUP(a) \
|
|
((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
|
|
|
|
#define CIDR_MAX_BITS 32
|
|
|
|
int delete_addresses(char *, int, struct in_addr, struct in_addr);
|
|
void set_address(char *, int, struct in_addr, struct in_addr);
|
|
void delete_address(char *, int, struct in_addr);
|
|
|
|
char *get_routes(int, size_t *);
|
|
void get_rtaddrs(int, struct sockaddr *, struct sockaddr **);
|
|
unsigned int route_pos(struct rt_msghdr *, uint8_t *, unsigned int,
|
|
struct in_addr);
|
|
void flush_routes(int, int, int, uint8_t *, unsigned int,
|
|
struct in_addr);
|
|
void discard_route(uint8_t *, unsigned int);
|
|
void add_route(char *, int, int, struct in_addr, struct in_addr,
|
|
struct in_addr, struct in_addr, int);
|
|
void set_routes(char *, int, int, int, struct in_addr,
|
|
struct in_addr, uint8_t *, unsigned int);
|
|
|
|
int default_route_index(int, int);
|
|
char *resolv_conf_tail(void);
|
|
char *set_resolv_conf(char *, char *, struct unwind_info *);
|
|
|
|
void set_mtu(char *, int, uint16_t);
|
|
|
|
/*
|
|
* delete_addresses() removes all inet addresses on the named interface, except
|
|
* for newaddr/newnetmask.
|
|
*
|
|
* If newaddr/newmask is already present, return 1, else 0.
|
|
*/
|
|
int
|
|
delete_addresses(char *name, int ioctlfd, struct in_addr newaddr,
|
|
struct in_addr newnetmask)
|
|
{
|
|
struct in_addr addr, netmask;
|
|
struct ifaddrs *ifap, *ifa;
|
|
int found;
|
|
|
|
if (getifaddrs(&ifap) == -1)
|
|
fatal("getifaddrs");
|
|
|
|
found = 0;
|
|
for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
|
|
if ((ifa->ifa_flags & IFF_LOOPBACK) != 0 ||
|
|
(ifa->ifa_flags & IFF_POINTOPOINT) != 0 ||
|
|
((ifa->ifa_flags & IFF_UP) == 0) ||
|
|
(ifa->ifa_addr->sa_family != AF_INET) ||
|
|
(strcmp(name, ifa->ifa_name) != 0))
|
|
continue;
|
|
|
|
memcpy(&addr,
|
|
&((struct sockaddr_in *)ifa->ifa_addr)->sin_addr,
|
|
sizeof(addr));
|
|
memcpy(&netmask,
|
|
&((struct sockaddr_in *)ifa->ifa_netmask)->sin_addr,
|
|
sizeof(netmask));
|
|
|
|
if (addr.s_addr == newaddr.s_addr &&
|
|
netmask.s_addr == newnetmask.s_addr) {
|
|
found = 1;
|
|
} else {
|
|
delete_address(name, ioctlfd, addr);
|
|
}
|
|
}
|
|
|
|
freeifaddrs(ifap);
|
|
return found;
|
|
}
|
|
|
|
/*
|
|
* set_address() is the equivalent of
|
|
*
|
|
* ifconfig <if> inet <addr> netmask <mask> broadcast <addr>
|
|
*/
|
|
void
|
|
set_address(char *name, int ioctlfd, struct in_addr addr,
|
|
struct in_addr netmask)
|
|
{
|
|
struct ifaliasreq ifaliasreq;
|
|
struct sockaddr_in *in;
|
|
|
|
if (delete_addresses(name, ioctlfd, addr, netmask) == 1)
|
|
return;
|
|
|
|
memset(&ifaliasreq, 0, sizeof(ifaliasreq));
|
|
strncpy(ifaliasreq.ifra_name, name, sizeof(ifaliasreq.ifra_name));
|
|
|
|
/* The actual address in ifra_addr. */
|
|
in = (struct sockaddr_in *)&ifaliasreq.ifra_addr;
|
|
in->sin_family = AF_INET;
|
|
in->sin_len = sizeof(ifaliasreq.ifra_addr);
|
|
in->sin_addr.s_addr = addr.s_addr;
|
|
|
|
/* And the netmask in ifra_mask. */
|
|
in = (struct sockaddr_in *)&ifaliasreq.ifra_mask;
|
|
in->sin_family = AF_INET;
|
|
in->sin_len = sizeof(ifaliasreq.ifra_mask);
|
|
in->sin_addr.s_addr = netmask.s_addr;
|
|
|
|
/* No need to set broadcast address. Kernel can figure it out. */
|
|
|
|
if (ioctl(ioctlfd, SIOCAIFADDR, &ifaliasreq) == -1)
|
|
log_warn("%s: SIOCAIFADDR %s", log_procname,
|
|
inet_ntoa(addr));
|
|
}
|
|
|
|
void
|
|
delete_address(char *name, int ioctlfd, struct in_addr addr)
|
|
{
|
|
struct ifaliasreq ifaliasreq;
|
|
struct sockaddr_in *in;
|
|
|
|
/*
|
|
* Delete specified address on specified interface.
|
|
*
|
|
* Deleting the address also clears out arp entries.
|
|
*/
|
|
|
|
memset(&ifaliasreq, 0, sizeof(ifaliasreq));
|
|
strncpy(ifaliasreq.ifra_name, name, sizeof(ifaliasreq.ifra_name));
|
|
|
|
in = (struct sockaddr_in *)&ifaliasreq.ifra_addr;
|
|
in->sin_family = AF_INET;
|
|
in->sin_len = sizeof(ifaliasreq.ifra_addr);
|
|
in->sin_addr.s_addr = addr.s_addr;
|
|
|
|
/* SIOCDIFADDR will result in a RTM_DELADDR message we must catch! */
|
|
if (ioctl(ioctlfd, SIOCDIFADDR, &ifaliasreq) == -1) {
|
|
if (errno != EADDRNOTAVAIL)
|
|
log_warn("%s: SIOCDIFADDR %s", log_procname,
|
|
inet_ntoa(addr));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* get_routes() returns all relevant routes currently configured, and the
|
|
* length of the buffer being returned.
|
|
*/
|
|
char *
|
|
get_routes(int rdomain, size_t *len)
|
|
{
|
|
int mib[7];
|
|
char *buf, *bufp, *errmsg = NULL;
|
|
size_t needed;
|
|
|
|
mib[0] = CTL_NET;
|
|
mib[1] = PF_ROUTE; /* PF_ROUTE (not AF_ROUTE) for sysctl(2)! */
|
|
mib[2] = 0;
|
|
mib[3] = AF_INET;
|
|
mib[4] = NET_RT_FLAGS;
|
|
mib[5] = RTF_STATIC;
|
|
mib[6] = rdomain;
|
|
|
|
buf = NULL;
|
|
errmsg = NULL;
|
|
for (;;) {
|
|
if (sysctl(mib, 7, NULL, &needed, NULL, 0) == -1) {
|
|
errmsg = "sysctl size of routes:";
|
|
break;
|
|
}
|
|
if (needed == 0) {
|
|
free(buf);
|
|
return NULL;
|
|
}
|
|
if ((bufp = realloc(buf, needed)) == NULL) {
|
|
errmsg = "routes buf realloc:";
|
|
break;
|
|
}
|
|
buf = bufp;
|
|
if (sysctl(mib, 7, buf, &needed, NULL, 0) == -1) {
|
|
if (errno == ENOMEM)
|
|
continue;
|
|
errmsg = "sysctl retrieval of routes:";
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (errmsg != NULL) {
|
|
log_warn("%s: get_routes - %s (msize=%zu)", log_procname,
|
|
errmsg, needed);
|
|
free(buf);
|
|
buf = NULL;
|
|
}
|
|
|
|
*len = needed;
|
|
return buf;
|
|
}
|
|
|
|
/*
|
|
* get_rtaddrs() populates rti_info with pointers to the
|
|
* sockaddr's contained in a rtm message.
|
|
*/
|
|
void
|
|
get_rtaddrs(int addrs, struct sockaddr *sa, struct sockaddr **rti_info)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < RTAX_MAX; i++) {
|
|
if (addrs & (1 << i)) {
|
|
rti_info[i] = sa;
|
|
sa = (struct sockaddr *)((char *)(sa) +
|
|
ROUNDUP(sa->sa_len));
|
|
} else
|
|
rti_info[i] = NULL;
|
|
}
|
|
}
|
|
/*
|
|
* route_pos() finds the position of the *rtm route within
|
|
* routes.
|
|
*
|
|
* If the *rtm route is not in routes, return routes_len.
|
|
*/
|
|
unsigned int
|
|
route_pos(struct rt_msghdr *rtm, uint8_t *routes, unsigned int routes_len,
|
|
struct in_addr address)
|
|
{
|
|
struct sockaddr *rti_info[RTAX_MAX];
|
|
struct sockaddr *dst, *netmask, *gateway;
|
|
in_addr_t dstaddr, netmaskaddr, gatewayaddr;
|
|
in_addr_t routesdstaddr, routesnetmaskaddr;
|
|
in_addr_t routesgatewayaddr;
|
|
unsigned int i, len;
|
|
|
|
get_rtaddrs(rtm->rtm_addrs,
|
|
(struct sockaddr *)((char *)(rtm) + rtm->rtm_hdrlen),
|
|
rti_info);
|
|
|
|
dst = rti_info[RTAX_DST];
|
|
netmask = rti_info[RTAX_NETMASK];
|
|
gateway = rti_info[RTAX_GATEWAY];
|
|
|
|
if (dst == NULL || netmask == NULL || gateway == NULL)
|
|
return routes_len;
|
|
|
|
if (dst->sa_family != AF_INET || netmask->sa_family != AF_INET ||
|
|
gateway->sa_family != AF_INET)
|
|
return routes_len;
|
|
|
|
dstaddr = ((struct sockaddr_in *)dst)->sin_addr.s_addr;
|
|
netmaskaddr = ((struct sockaddr_in *)netmask)->sin_addr.s_addr;
|
|
gatewayaddr = ((struct sockaddr_in *)gateway)->sin_addr.s_addr;
|
|
|
|
dstaddr &= netmaskaddr;
|
|
i = 0;
|
|
while (i < routes_len) {
|
|
len = extract_route(&routes[i], routes_len - i, &routesdstaddr,
|
|
&routesnetmaskaddr, &routesgatewayaddr);
|
|
if (len == 0)
|
|
break;
|
|
|
|
/* Direct route in routes:
|
|
*
|
|
* dst=1.2.3.4 netmask=255.255.255.255 gateway=0.0.0.0
|
|
*
|
|
* direct route in rtm:
|
|
*
|
|
* dst=1.2.3.4 netmask=255.255.255.255 gateway = address
|
|
*
|
|
* So replace 0.0.0.0 with address for comparison.
|
|
*/
|
|
if (routesgatewayaddr == INADDR_ANY)
|
|
routesgatewayaddr = address.s_addr;
|
|
routesdstaddr &= routesnetmaskaddr;
|
|
|
|
if (dstaddr == routesdstaddr &&
|
|
netmaskaddr == routesnetmaskaddr &&
|
|
gatewayaddr == routesgatewayaddr)
|
|
return i;
|
|
|
|
i += len;
|
|
}
|
|
|
|
return routes_len;
|
|
}
|
|
|
|
void
|
|
flush_routes(int index, int routefd, int rdomain, uint8_t *routes,
|
|
unsigned int routes_len, struct in_addr address)
|
|
{
|
|
static int seqno;
|
|
char *lim, *buf, *next;
|
|
struct rt_msghdr *rtm;
|
|
size_t len;
|
|
ssize_t rlen;
|
|
unsigned int pos;
|
|
|
|
buf = get_routes(rdomain, &len);
|
|
if (buf == NULL)
|
|
return;
|
|
|
|
lim = buf + len;
|
|
for (next = buf; next < lim; next += rtm->rtm_msglen) {
|
|
rtm = (struct rt_msghdr *)next;
|
|
if (rtm->rtm_version != RTM_VERSION)
|
|
continue;
|
|
if (rtm->rtm_index != index)
|
|
continue;
|
|
if (rtm->rtm_tableid != rdomain)
|
|
continue;
|
|
if ((rtm->rtm_flags & RTF_STATIC) == 0)
|
|
continue;
|
|
if ((rtm->rtm_flags & (RTF_LOCAL|RTF_BROADCAST)) != 0)
|
|
continue;
|
|
|
|
pos = route_pos(rtm, routes, routes_len, address);
|
|
if (pos < routes_len) {
|
|
discard_route(routes + pos, routes_len - pos);
|
|
continue;
|
|
}
|
|
|
|
rtm->rtm_type = RTM_DELETE;
|
|
rtm->rtm_seq = seqno++;
|
|
|
|
rlen = write(routefd, (char *)rtm, rtm->rtm_msglen);
|
|
if (rlen == -1) {
|
|
if (errno != ESRCH)
|
|
log_warn("%s: write(RTM_DELETE)", log_procname);
|
|
} else if (rlen < (int)rtm->rtm_msglen)
|
|
log_warnx("%s: write(RTM_DELETE): %zd of %u bytes",
|
|
log_procname, rlen, rtm->rtm_msglen);
|
|
}
|
|
|
|
free(buf);
|
|
}
|
|
|
|
void
|
|
discard_route(uint8_t *routes, unsigned int routes_len)
|
|
{
|
|
unsigned int len;
|
|
|
|
len = 1 + sizeof(struct in_addr) + (routes[0] + 7) / 8;
|
|
memmove(routes, routes + len, routes_len - len);
|
|
routes[routes_len - len] = CIDR_MAX_BITS + 1;
|
|
}
|
|
|
|
/*
|
|
* add_route() adds a single route to the routing table.
|
|
*/
|
|
void
|
|
add_route(char *name, int rdomain, int routefd, struct in_addr dest,
|
|
struct in_addr netmask, struct in_addr gateway, struct in_addr address,
|
|
int flags)
|
|
{
|
|
char destbuf[INET_ADDRSTRLEN];
|
|
char maskbuf[INET_ADDRSTRLEN];
|
|
struct iovec iov[5];
|
|
struct sockaddr_in sockaddr_in[4];
|
|
struct rt_msghdr rtm;
|
|
int i, iovcnt = 0;
|
|
|
|
memset(&rtm, 0, sizeof(rtm));
|
|
rtm.rtm_index = if_nametoindex(name);
|
|
if (rtm.rtm_index == 0)
|
|
return;
|
|
|
|
rtm.rtm_version = RTM_VERSION;
|
|
rtm.rtm_type = RTM_ADD;
|
|
rtm.rtm_tableid = rdomain;
|
|
rtm.rtm_priority = RTP_NONE;
|
|
rtm.rtm_flags = flags;
|
|
|
|
iov[0].iov_base = &rtm;
|
|
iov[0].iov_len = sizeof(rtm);
|
|
|
|
memset(sockaddr_in, 0, sizeof(sockaddr_in));
|
|
for (i = 0; i < 4; i++) {
|
|
sockaddr_in[i].sin_len = sizeof(sockaddr_in[i]);
|
|
sockaddr_in[i].sin_family = AF_INET;
|
|
iov[i+1].iov_base = &sockaddr_in[i];
|
|
iov[i+1].iov_len = sizeof(sockaddr_in[i]);
|
|
}
|
|
|
|
/* Order of sockaddr_in's is mandatory! */
|
|
sockaddr_in[0].sin_addr = dest;
|
|
sockaddr_in[1].sin_addr = gateway;
|
|
sockaddr_in[2].sin_addr = netmask;
|
|
sockaddr_in[3].sin_addr = address;
|
|
if (address.s_addr == INADDR_ANY) {
|
|
rtm.rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK;
|
|
iovcnt = 4;
|
|
} else {
|
|
rtm.rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK | RTA_IFA;
|
|
iovcnt = 5;
|
|
}
|
|
|
|
for (i = 0; i < iovcnt; i++)
|
|
rtm.rtm_msglen += iov[i].iov_len;
|
|
|
|
if (writev(routefd, iov, iovcnt) == -1) {
|
|
if (errno != EEXIST || log_getverbose() != 0) {
|
|
strlcpy(destbuf, inet_ntoa(dest), sizeof(destbuf));
|
|
strlcpy(maskbuf, inet_ntoa(netmask),sizeof(maskbuf));
|
|
log_warn("%s: add route %s/%s via %s", log_procname,
|
|
destbuf, maskbuf, inet_ntoa(gateway));
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* set_routes() adds the routes contained in 'routes' to the routing table.
|
|
*/
|
|
void
|
|
set_routes(char *name, int index, int rdomain, int routefd, struct in_addr addr,
|
|
struct in_addr addrmask, uint8_t *routes, unsigned int routes_len)
|
|
{
|
|
const struct in_addr any = { INADDR_ANY };
|
|
const struct in_addr broadcast = { INADDR_BROADCAST };
|
|
struct in_addr dest, gateway, netmask;
|
|
in_addr_t addrnet, gatewaynet;
|
|
unsigned int i, len;
|
|
|
|
flush_routes(index, routefd, rdomain, routes, routes_len, addr);
|
|
|
|
addrnet = addr.s_addr & addrmask.s_addr;
|
|
|
|
/* Add classless static routes. */
|
|
i = 0;
|
|
while (i < routes_len) {
|
|
len = extract_route(&routes[i], routes_len - i,
|
|
&dest.s_addr, &netmask.s_addr, &gateway.s_addr);
|
|
if (len == 0)
|
|
return;
|
|
i += len;
|
|
|
|
if (gateway.s_addr == INADDR_ANY) {
|
|
/*
|
|
* DIRECT ROUTE
|
|
*
|
|
* route add -net $dest -netmask $netmask -cloning
|
|
* -iface $addr
|
|
*/
|
|
add_route(name, rdomain, routefd, dest, netmask,
|
|
addr, any, RTF_STATIC | RTF_CLONING);
|
|
} else if (netmask.s_addr == INADDR_ANY) {
|
|
/*
|
|
* DEFAULT ROUTE
|
|
*/
|
|
gatewaynet = gateway.s_addr & addrmask.s_addr;
|
|
if (gatewaynet != addrnet) {
|
|
/*
|
|
* DIRECT ROUTE TO DEFAULT GATEWAY
|
|
*
|
|
* route add -net $gateway
|
|
* -netmask 255.255.255.255
|
|
* -cloning -iface $addr
|
|
*
|
|
* If the default route gateway is not reachable
|
|
* via the IP assignment then add a cloning
|
|
* direct route for the gateway. Deals with
|
|
* weird configs seen in the wild.
|
|
*
|
|
* e.g. add the route if we were given a /32 IP
|
|
* assignment. a.k.a. "make Google Cloud DHCP
|
|
* work".
|
|
*
|
|
*/
|
|
add_route(name, rdomain, routefd, gateway,
|
|
broadcast, addr, any,
|
|
RTF_STATIC | RTF_CLONING);
|
|
}
|
|
|
|
if (memcmp(&gateway, &addr, sizeof(addr)) == 0) {
|
|
/*
|
|
* DEFAULT ROUTE IS A DIRECT ROUTE
|
|
*
|
|
* route add default -iface $addr
|
|
*/
|
|
add_route(name, rdomain, routefd, any, any,
|
|
gateway, any, RTF_STATIC);
|
|
} else {
|
|
/*
|
|
* DEFAULT ROUTE IS VIA GATEWAY
|
|
*
|
|
* route add default $gateway -ifa $addr
|
|
*
|
|
*/
|
|
add_route(name, rdomain, routefd, any, any,
|
|
gateway, addr, RTF_STATIC | RTF_GATEWAY);
|
|
}
|
|
} else {
|
|
/*
|
|
* NON-DIRECT, NON-DEFAULT ROUTE
|
|
*
|
|
* route add -net $dest -netmask $netmask $gateway
|
|
*/
|
|
add_route(name, rdomain, routefd, dest, netmask,
|
|
gateway, any, RTF_STATIC | RTF_GATEWAY);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* default_route_index() returns the interface index of the current
|
|
* default route (a.k.a. 0.0.0.0/0).
|
|
*/
|
|
int
|
|
default_route_index(int rdomain, int routefd)
|
|
{
|
|
struct pollfd fds[1];
|
|
struct timespec now, stop, timeout;
|
|
int nfds;
|
|
struct iovec iov[3];
|
|
struct sockaddr_in sin;
|
|
struct {
|
|
struct rt_msghdr m_rtm;
|
|
char m_space[512];
|
|
} m_rtmsg;
|
|
pid_t pid;
|
|
ssize_t len;
|
|
int seq;
|
|
|
|
memset(&m_rtmsg, 0, sizeof(m_rtmsg));
|
|
m_rtmsg.m_rtm.rtm_version = RTM_VERSION;
|
|
m_rtmsg.m_rtm.rtm_type = RTM_GET;
|
|
m_rtmsg.m_rtm.rtm_tableid = rdomain;
|
|
m_rtmsg.m_rtm.rtm_seq = seq = arc4random();
|
|
m_rtmsg.m_rtm.rtm_addrs = RTA_DST | RTA_NETMASK;
|
|
m_rtmsg.m_rtm.rtm_msglen = sizeof(struct rt_msghdr) +
|
|
2 * sizeof(struct sockaddr_in);
|
|
|
|
memset(&sin, 0, sizeof(sin));
|
|
sin.sin_len = sizeof(sin);
|
|
sin.sin_family = AF_INET;
|
|
|
|
iov[0].iov_base = &m_rtmsg.m_rtm;
|
|
iov[0].iov_len = sizeof(m_rtmsg.m_rtm);
|
|
iov[1].iov_base = &sin;
|
|
iov[1].iov_len = sizeof(sin);
|
|
iov[2].iov_base = &sin;
|
|
iov[2].iov_len = sizeof(sin);
|
|
|
|
pid = getpid();
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
timespecclear(&timeout);
|
|
timeout.tv_sec = 3;
|
|
timespecadd(&now, &timeout, &stop);
|
|
|
|
if (writev(routefd, iov, 3) == -1) {
|
|
if (errno == ESRCH)
|
|
log_debug("%s: writev(RTM_GET) - no default route",
|
|
log_procname);
|
|
else
|
|
log_warn("%s: writev(RTM_GET)", log_procname);
|
|
return 0;
|
|
}
|
|
|
|
for (;;) {
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
if (timespeccmp(&stop, &now, <=))
|
|
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;
|
|
log_warn("%s: ppoll(routefd)", log_procname);
|
|
break;
|
|
}
|
|
if ((fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) != 0) {
|
|
log_warnx("%s: routefd: ERR|HUP|NVAL", log_procname);
|
|
break;
|
|
}
|
|
if (nfds == 0 || (fds[0].revents & POLLIN) == 0)
|
|
continue;
|
|
|
|
len = read(routefd, &m_rtmsg, sizeof(m_rtmsg));
|
|
if (len == -1) {
|
|
log_warn("%s: read(RTM_GET)", log_procname);
|
|
break;
|
|
} else if (len == 0) {
|
|
log_warnx("%s: read(RTM_GET): 0 bytes", log_procname);
|
|
break;
|
|
}
|
|
|
|
if (m_rtmsg.m_rtm.rtm_version == RTM_VERSION &&
|
|
m_rtmsg.m_rtm.rtm_type == RTM_GET &&
|
|
m_rtmsg.m_rtm.rtm_pid == pid &&
|
|
m_rtmsg.m_rtm.rtm_seq == seq &&
|
|
(m_rtmsg.m_rtm.rtm_flags & RTF_UP) == RTF_UP) {
|
|
if (m_rtmsg.m_rtm.rtm_errno != 0) {
|
|
log_warnx("%s: read(RTM_GET): %s", log_procname,
|
|
strerror(m_rtmsg.m_rtm.rtm_errno));
|
|
break;
|
|
}
|
|
return m_rtmsg.m_rtm.rtm_index;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* resolv_conf_tail() returns the contents of /etc/resolv.conf.tail, if
|
|
* any. NULL is returned if there is no such file, the file is emtpy
|
|
* or any errors are encounted in reading the file.
|
|
*/
|
|
char *
|
|
resolv_conf_tail(void)
|
|
{
|
|
struct stat sb;
|
|
const char *tail_path = "/etc/resolv.conf.tail";
|
|
char *tailcontents = NULL;
|
|
ssize_t tailn;
|
|
int tailfd;
|
|
|
|
tailfd = open(tail_path, O_RDONLY);
|
|
if (tailfd == -1) {
|
|
if (errno != ENOENT)
|
|
log_warn("%s: open(%s)", log_procname, tail_path);
|
|
} else if (fstat(tailfd, &sb) == -1) {
|
|
log_warn("%s: fstat(%s)", log_procname, tail_path);
|
|
} else if (sb.st_size > 0 && sb.st_size < LLONG_MAX) {
|
|
tailcontents = calloc(1, sb.st_size + 1);
|
|
if (tailcontents == NULL)
|
|
fatal("%s contents", tail_path);
|
|
tailn = read(tailfd, tailcontents, sb.st_size);
|
|
if (tailn == -1)
|
|
log_warn("%s: read(%s)", log_procname,
|
|
tail_path);
|
|
else if (tailn == 0)
|
|
log_warnx("%s: got no data from %s",
|
|
log_procname,tail_path);
|
|
else if (tailn != sb.st_size)
|
|
log_warnx("%s: short read of %s",
|
|
log_procname, tail_path);
|
|
else {
|
|
close(tailfd);
|
|
return tailcontents;
|
|
}
|
|
|
|
close(tailfd);
|
|
free(tailcontents);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* set_resolv_conf() creates a string that are the resolv.conf contents
|
|
* that should be used when IMSG_WRITE_RESOLV_CONF messages are received.
|
|
*/
|
|
char *
|
|
set_resolv_conf(char *name, char *search, struct unwind_info *ns_info)
|
|
{
|
|
char *ns, *p, *tail;
|
|
struct in_addr addr;
|
|
unsigned int i;
|
|
int rslt;
|
|
|
|
ns = NULL;
|
|
for (i = 0; i < ns_info->count; i++) {
|
|
addr.s_addr = ns_info->ns[i];
|
|
rslt = asprintf(&p, "%snameserver %s\n",
|
|
(ns == NULL) ? "" : ns, inet_ntoa(addr));
|
|
if (rslt == -1)
|
|
fatal("nameserver");
|
|
free(ns);
|
|
ns = p;
|
|
}
|
|
|
|
if (search == NULL && ns == NULL)
|
|
return NULL;
|
|
|
|
tail = resolv_conf_tail();
|
|
|
|
rslt = asprintf(&p, "# Generated by %s dhclient\n%s%s%s", name,
|
|
(search == NULL) ? "" : search,
|
|
(ns == NULL) ? "" : ns,
|
|
(tail == NULL) ? "" : tail);
|
|
if (rslt == -1)
|
|
fatal("resolv.conf");
|
|
|
|
free(tail);
|
|
free(ns);
|
|
|
|
return p;
|
|
}
|
|
|
|
/*
|
|
* set_mtu() is the equivalent of
|
|
*
|
|
* ifconfig <if> mtu <mtu>
|
|
*/
|
|
void
|
|
set_mtu(char *name, int ioctlfd, uint16_t mtu)
|
|
{
|
|
struct ifreq ifr;
|
|
|
|
memset(&ifr, 0, sizeof(ifr));
|
|
strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
|
|
|
|
if (ioctl(ioctlfd, SIOCGIFMTU, &ifr) == -1) {
|
|
log_warn("%s: SIOCGIFMTU", log_procname);
|
|
return;
|
|
}
|
|
if (ifr.ifr_mtu == mtu)
|
|
return; /* Avoid unnecessary RTM_IFINFO! */
|
|
|
|
ifr.ifr_mtu = mtu;
|
|
if (ioctl(ioctlfd, SIOCSIFMTU, &ifr) == -1)
|
|
log_warn("%s: SIOCSIFMTU %u", log_procname, mtu);
|
|
}
|
|
|
|
/*
|
|
* extract_route() decodes the route pointed to by routes into its
|
|
* {destination, netmask, gateway} and returns the number of bytes consumed
|
|
* from routes.
|
|
*/
|
|
unsigned int
|
|
extract_route(uint8_t *routes, unsigned int routes_len, in_addr_t *dest,
|
|
in_addr_t *netmask, in_addr_t *gateway)
|
|
{
|
|
unsigned int bits, bytes, len;
|
|
|
|
if (routes[0] > CIDR_MAX_BITS)
|
|
return 0;
|
|
|
|
bits = routes[0];
|
|
bytes = (bits + 7) / 8;
|
|
len = 1 + bytes + sizeof(*gateway);
|
|
if (len > routes_len)
|
|
return 0;
|
|
|
|
if (dest != NULL)
|
|
memcpy(dest, &routes[1], bytes);
|
|
|
|
if (netmask != NULL) {
|
|
if (bits == 0)
|
|
*netmask = INADDR_ANY;
|
|
else
|
|
*netmask = htonl(0xffffffff << (CIDR_MAX_BITS - bits));
|
|
if (dest != NULL)
|
|
*dest &= *netmask;
|
|
}
|
|
|
|
if (gateway != NULL)
|
|
memcpy(gateway, &routes[1 + bytes], sizeof(*gateway));
|
|
|
|
return len;
|
|
}
|
|
|
|
/*
|
|
* [priv_]write_resolv_conf write out a new resolv.conf.
|
|
*/
|
|
void
|
|
write_resolv_conf(void)
|
|
{
|
|
int rslt;
|
|
|
|
rslt = imsg_compose(unpriv_ibuf, IMSG_WRITE_RESOLV_CONF,
|
|
0, 0, -1, NULL, 0);
|
|
if (rslt == -1)
|
|
log_warn("%s: imsg_compose(IMSG_WRITE_RESOLV_CONF)",
|
|
log_procname);
|
|
}
|
|
|
|
void
|
|
priv_write_resolv_conf(int index, int routefd, int rdomain, char *contents,
|
|
int *lastidx)
|
|
{
|
|
char ifname[IF_NAMESIZE];
|
|
const char *path = "/etc/resolv.conf";
|
|
ssize_t n;
|
|
size_t sz;
|
|
int fd, retries, newidx;
|
|
|
|
if (contents == NULL)
|
|
return;
|
|
|
|
retries = 0;
|
|
do {
|
|
newidx = default_route_index(rdomain, routefd);
|
|
retries++;
|
|
} while (newidx == 0 && retries < 3);
|
|
|
|
if (newidx == 0) {
|
|
log_debug("%s: %s not updated, no default route is UP",
|
|
log_procname, path);
|
|
return;
|
|
} else if (newidx != index) {
|
|
*lastidx = newidx;
|
|
if (if_indextoname(newidx, ifname) == NULL) {
|
|
memset(ifname, 0, sizeof(ifname));
|
|
strlcat(ifname, "<unknown>", sizeof(ifname));
|
|
}
|
|
log_debug("%s: %s not updated, default route on %s",
|
|
log_procname, path, ifname);
|
|
return;
|
|
} else if (newidx == *lastidx) {
|
|
log_debug("%s: %s not updated, same as last write",
|
|
log_procname, path);
|
|
return;
|
|
}
|
|
|
|
*lastidx = newidx;
|
|
log_debug("%s: %s updated", log_procname, path);
|
|
|
|
fd = open(path, O_WRONLY | O_CREAT | O_TRUNC,
|
|
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
|
|
if (fd == -1) {
|
|
log_warn("%s: open(%s)", log_procname, path);
|
|
return;
|
|
}
|
|
|
|
sz = strlen(contents);
|
|
n = write(fd, contents, sz);
|
|
if (n == -1)
|
|
log_warn("%s: write(%s)", log_procname, path);
|
|
else if ((size_t)n < sz)
|
|
log_warnx("%s: write(%s): %zd of %zu bytes", log_procname,
|
|
path, n, sz);
|
|
|
|
close(fd);
|
|
}
|
|
|
|
/*
|
|
* [priv_]propose implements a proposal.
|
|
*/
|
|
void
|
|
propose(struct proposal *proposal)
|
|
{
|
|
struct option_data opt;
|
|
int rslt;
|
|
|
|
log_debug("%s: proposing address %s netmask 0x%08x", log_procname,
|
|
inet_ntoa(proposal->address), ntohl(proposal->netmask.s_addr));
|
|
|
|
opt.data = (u_int8_t *)proposal + sizeof(struct proposal);
|
|
opt.len = proposal->routes_len;
|
|
if (opt.len > 0)
|
|
log_debug("%s: proposing static route(s) %s", log_procname,
|
|
pretty_print_option(DHO_CLASSLESS_STATIC_ROUTES, &opt, 0));
|
|
|
|
opt.data += opt.len;
|
|
opt.len = proposal->domains_len;
|
|
if (opt.len > 0)
|
|
log_debug("%s: proposing search domain(s) %s", log_procname,
|
|
pretty_print_option(DHO_DOMAIN_SEARCH, &opt, 0));
|
|
|
|
opt.data += opt.len;
|
|
opt.len = proposal->ns_len;
|
|
if (opt.len > 0)
|
|
log_debug("%s: proposing DNS server(s) %s", log_procname,
|
|
pretty_print_option(DHO_DOMAIN_NAME_SERVERS, &opt, 0));
|
|
|
|
if (proposal->mtu != 0)
|
|
log_debug("%s: proposing mtu %u", log_procname, proposal->mtu);
|
|
|
|
rslt = imsg_compose(unpriv_ibuf, IMSG_PROPOSE, 0, 0, -1, proposal,
|
|
sizeof(*proposal) + proposal->routes_len +
|
|
proposal->domains_len + proposal->ns_len);
|
|
if (rslt == -1)
|
|
log_warn("%s: imsg_compose(IMSG_PROPOSE)", log_procname);
|
|
}
|
|
|
|
void
|
|
priv_propose(char *name, int ioctlfd, struct proposal *proposal,
|
|
size_t sz, char **resolv_conf, int routefd, int rdomain, int index,
|
|
int *lastidx)
|
|
{
|
|
struct unwind_info unwind_info;
|
|
uint8_t *dns, *domains, *routes;
|
|
char *search = NULL;
|
|
int rslt;
|
|
|
|
if (sz != proposal->routes_len + proposal->domains_len +
|
|
proposal->ns_len) {
|
|
log_warnx("%s: bad IMSG_PROPOSE data", log_procname);
|
|
return;
|
|
}
|
|
|
|
routes = (uint8_t *)proposal + sizeof(struct proposal);
|
|
domains = routes + proposal->routes_len;
|
|
dns = domains + proposal->domains_len;
|
|
|
|
memset(&unwind_info, 0, sizeof(unwind_info));
|
|
if (proposal->ns_len >= sizeof(in_addr_t)) {
|
|
if (proposal->ns_len > sizeof(unwind_info.ns)) {
|
|
memcpy(unwind_info.ns, dns, sizeof(unwind_info.ns));
|
|
unwind_info.count = sizeof(unwind_info.ns) /
|
|
sizeof(in_addr_t);
|
|
} else {
|
|
memcpy(unwind_info.ns, dns, proposal->ns_len);
|
|
unwind_info.count = proposal->ns_len /
|
|
sizeof(in_addr_t);
|
|
}
|
|
}
|
|
|
|
if (proposal->domains_len > 0) {
|
|
rslt = asprintf(&search, "search %.*s\n",
|
|
proposal->domains_len, domains);
|
|
if (rslt == -1)
|
|
search = NULL;
|
|
}
|
|
|
|
free(*resolv_conf);
|
|
*resolv_conf = set_resolv_conf(name, search, &unwind_info);
|
|
free(search);
|
|
|
|
if (proposal->mtu != 0) {
|
|
if (proposal->mtu < 68)
|
|
log_warnx("%s: mtu size %d < 68: ignored", log_procname,
|
|
proposal->mtu);
|
|
else
|
|
set_mtu(name, ioctlfd, proposal->mtu);
|
|
}
|
|
|
|
set_address(name, ioctlfd, proposal->address, proposal->netmask);
|
|
|
|
set_routes(name, index, rdomain, routefd, proposal->address,
|
|
proposal->netmask, routes, proposal->routes_len);
|
|
|
|
*lastidx = 0;
|
|
priv_write_resolv_conf(index, routefd, rdomain, *resolv_conf, lastidx);
|
|
}
|
|
|
|
/*
|
|
* [priv_]revoke_proposal de-configures a proposal.
|
|
*/
|
|
void
|
|
revoke_proposal(struct proposal *proposal)
|
|
{
|
|
int rslt;
|
|
|
|
if (proposal == NULL)
|
|
return;
|
|
|
|
rslt = imsg_compose(unpriv_ibuf, IMSG_REVOKE, 0, 0, -1, proposal,
|
|
sizeof(*proposal));
|
|
if (rslt == -1)
|
|
log_warn("%s: imsg_compose(IMSG_REVOKE)", log_procname);
|
|
}
|
|
|
|
void
|
|
priv_revoke_proposal(char *name, int ioctlfd, struct proposal *proposal,
|
|
char **resolv_conf)
|
|
{
|
|
free(*resolv_conf);
|
|
*resolv_conf = NULL;
|
|
|
|
delete_address(name, ioctlfd, proposal->address);
|
|
}
|
|
|
|
/*
|
|
* [priv_]tell_unwind sends out inforation unwind may be intereted in.
|
|
*/
|
|
void
|
|
tell_unwind(struct unwind_info *unwind_info, int ifi_flags)
|
|
{
|
|
struct unwind_info noinfo;
|
|
int rslt;
|
|
|
|
if ((ifi_flags & IFI_IN_CHARGE) == 0)
|
|
return;
|
|
|
|
if (unwind_info != NULL)
|
|
rslt = imsg_compose(unpriv_ibuf, IMSG_TELL_UNWIND, 0, 0, -1,
|
|
unwind_info, sizeof(*unwind_info));
|
|
else {
|
|
memset(&noinfo, 0, sizeof(noinfo));
|
|
rslt = imsg_compose(unpriv_ibuf, IMSG_TELL_UNWIND, 0, 0, -1,
|
|
&noinfo, sizeof(noinfo));
|
|
}
|
|
|
|
if (rslt == -1)
|
|
log_warn("%s: imsg_compose(IMSG_TELL_UNWIND)", log_procname);
|
|
}
|
|
|
|
void
|
|
priv_tell_unwind(int index, int routefd, int rdomain,
|
|
struct unwind_info *unwind_info)
|
|
{
|
|
struct rt_msghdr rtm;
|
|
struct sockaddr_rtdns rtdns;
|
|
struct iovec iov[3];
|
|
long pad = 0;
|
|
int iovcnt = 0, padlen;
|
|
|
|
memset(&rtm, 0, sizeof(rtm));
|
|
|
|
rtm.rtm_version = RTM_VERSION;
|
|
rtm.rtm_type = RTM_PROPOSAL;
|
|
rtm.rtm_msglen = sizeof(rtm);
|
|
rtm.rtm_tableid = rdomain;
|
|
rtm.rtm_index = index;
|
|
rtm.rtm_seq = arc4random();
|
|
rtm.rtm_priority = RTP_PROPOSAL_DHCLIENT;
|
|
rtm.rtm_addrs = RTA_DNS;
|
|
rtm.rtm_flags = RTF_UP;
|
|
|
|
iov[iovcnt].iov_base = &rtm;
|
|
iov[iovcnt++].iov_len = sizeof(rtm);
|
|
|
|
memset(&rtdns, 0, sizeof(rtdns));
|
|
rtdns.sr_family = AF_INET;
|
|
|
|
rtdns.sr_len = 2 + unwind_info->count * sizeof(in_addr_t);
|
|
memcpy(rtdns.sr_dns, unwind_info->ns,
|
|
unwind_info->count * sizeof(in_addr_t));
|
|
|
|
iov[iovcnt].iov_base = &rtdns;
|
|
iov[iovcnt++].iov_len = sizeof(rtdns);
|
|
rtm.rtm_msglen += sizeof(rtdns);
|
|
padlen = ROUNDUP(sizeof(rtdns)) - sizeof(rtdns);
|
|
if (padlen > 0) {
|
|
iov[iovcnt].iov_base = &pad;
|
|
iov[iovcnt++].iov_len = padlen;
|
|
rtm.rtm_msglen += padlen;
|
|
}
|
|
|
|
if (writev(routefd, iov, iovcnt) == -1)
|
|
log_warn("failed to tell unwind");
|
|
}
|