Add netdump support to dumpon(8).

A new usage is added so that parameters for netdump may be specified.
Specifically, one configures an interface for netdump with:

# dumpon -c <client IP> -s <server IP> [-g <gateway IP>] <iface name>

Reviewed by:	bdrewery, cem (earlier versions), sbruno
MFC after:	1 month
Sponsored by:	Dell EMC Isilon
Differential Revision:	https://reviews.freebsd.org/D15254
This commit is contained in:
Mark Johnston 2018-05-06 00:42:30 +00:00
parent e505460228
commit 0ff40d3d29
2 changed files with 328 additions and 100 deletions

View File

@ -28,7 +28,7 @@
.\" From: @(#)swapon.8 8.1 (Berkeley) 6/5/93
.\" $FreeBSD$
.\"
.Dd February 13, 2018
.Dd March 6, 2018
.Dt DUMPON 8
.Os
.Sh NAME
@ -37,10 +37,19 @@
.Sh SYNOPSIS
.Nm
.Op Fl v
.Op Fl k Ar public_key_file
.Op Fl z
.Op Fl k Ar pubkey
.Op Fl Z
.Ar special_file
.Op Fl z
.Ar device
.Nm
.Op Fl v
.Op Fl k Ar pubkey
.Op Fl Z
.Op Fl z
.Op Fl g Ar gateway | Li default
.Fl s Ar server
.Fl c Ar client
.Ar iface
.Nm
.Op Fl v
.Cm off
@ -60,7 +69,7 @@ normally occur from the system multi-user initialization file
controlled by the
.Dq dumpdev
and
.Dq dumppubkey
.Dq dumpon_flags
variables in the boot time configuration file
.Pa /etc/rc.conf .
.Pp
@ -72,8 +81,7 @@ Alternatively, full memory dumps can be enabled by setting the
variable to 0.
.Pp
For systems using full memory dumps, the size of the specified dump
device must be at
least the size of physical memory.
device must be at least the size of physical memory.
Even though an additional 64 kB header is added to the dump, the BIOS for a
platform typically holds back some memory, so it is not usually
necessary to size the dump device larger than the actual amount of RAM
@ -86,8 +94,37 @@ total amount of physical memory as reported by the
.Xr sysctl 8
variable.
.Pp
.Nm
is used to configure a local storage device as the dump device.
With additional parameters, the kernel can instead be configured to
transmit a dump to a remote server using
.Xr netdump 4 .
This eliminates the need to reserve space for saving crash dumps and
is especially useful in diskless environments.
The
.Op Fl k Ar public_key_file
.Xr netdump 4
server address is specified with
.Fl s Ar server ,
and the local address is specified with
.Fl c Ar client .
The
.Fl g Ar gateway
parameter may be used to specify a first-hop router to the server,
or to specify that the currently configured default gateway is to
be used.
Note that the
.Xr netdump 4
configuration is not automatically updated if any network configuration
(e.g., the default route) changes after the
.Nm
invocation.
The name of the interface to be used must be specified as
.Ar iface .
The interface must be up in order to configure
.Xr netdump 4 .
.Pp
The
.Fl k Ar pubkey
flag causes
.Nm
to generate a one-time key for kernel crash dump encryption.
@ -95,16 +132,16 @@ The key will be replaced by a new one when the
.Nm
utility is run again.
The key is encrypted using
.Ar public_key_file .
.Ar pubkey .
This process is sandboxed using
.Xr capsicum 4 .
Both plain and encrypted keys are sent to the kernel using
.Dv DIOCSKERNELDUMP
.Xr ioctl 2 .
A user can specify the
.Ar public_key_file
.Ar pubkey
in the
.Dq dumppubkey
.Dq dumpon_flags
variable defined in
.Pa /etc/rc.conf
for use with the
@ -172,13 +209,13 @@ should be used as the dump device.
The
.Nm
utility operates by opening
.Ar special_file
.Ar device
and making a
.Dv DIOCSKERNELDUMP
.Xr ioctl 2
request on it to save kernel crash dumps.
If
.Ar special_file
.Ar device
is the text string:
.Dq Li off ,
.Nm

View File

@ -46,12 +46,15 @@ __FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/capsicum.h>
#include <sys/disk.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <ifaddrs.h>
#include <netdb.h>
#include <paths.h>
#include <stdbool.h>
#include <stdint.h>
@ -61,6 +64,15 @@ __FBSDID("$FreeBSD$");
#include <sysexits.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/route.h>
#include <netinet/in.h>
#include <netinet/netdump/netdump.h>
#ifdef HAVE_CRYPTO
#include <openssl/err.h>
#include <openssl/pem.h>
@ -69,16 +81,109 @@ __FBSDID("$FreeBSD$");
static int verbose;
static void
static void _Noreturn
usage(void)
{
fprintf(stderr, "%s\n%s\n%s\n",
"usage: dumpon [-v] [-k public_key_file] [-Zz] special_file",
" dumpon [-v] off",
" dumpon [-v] -l");
fprintf(stderr,
"usage: dumpon [-v] [-k <pubkey>] [-Zz] <device>\n"
" dumpon [-v] [-k <pubkey>] [-Zz]\n"
" [-g <gateway>|default] -s <server> -c <client> <iface>\n"
" dumpon [-v] off\n"
" dumpon [-v] -l\n");
exit(EX_USAGE);
}
/*
* Look for a default route on the specified interface.
*/
static char *
find_gateway(const char *ifname)
{
struct ifaddrs *ifa, *ifap;
struct rt_msghdr *rtm;
struct sockaddr *sa;
struct sockaddr_dl *sdl;
struct sockaddr_in *dst, *mask, *gw;
char *buf, *next, *ret;
size_t sz;
int error, i, ifindex, mib[7];
ret = NULL;
/* First look up the interface index. */
if (getifaddrs(&ifap) != 0)
err(EX_OSERR, "getifaddrs");
for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
if (ifa->ifa_addr->sa_family != AF_LINK)
continue;
if (strcmp(ifa->ifa_name, ifname) == 0) {
sdl = (struct sockaddr_dl *)(void *)ifa->ifa_addr;
ifindex = sdl->sdl_index;
break;
}
}
if (ifa == NULL)
errx(1, "couldn't find interface index for '%s'", ifname);
freeifaddrs(ifap);
/* Now get the IPv4 routing table. */
mib[0] = CTL_NET;
mib[1] = PF_ROUTE;
mib[2] = 0;
mib[3] = AF_INET;
mib[4] = NET_RT_DUMP;
mib[5] = 0;
mib[6] = -1; /* FIB */
for (;;) {
if (sysctl(mib, nitems(mib), NULL, &sz, NULL, 0) != 0)
err(EX_OSERR, "sysctl(NET_RT_DUMP)");
buf = malloc(sz);
error = sysctl(mib, nitems(mib), buf, &sz, NULL, 0);
if (error == 0)
break;
if (errno != ENOMEM)
err(EX_OSERR, "sysctl(NET_RT_DUMP)");
free(buf);
}
for (next = buf; next < buf + sz; next += rtm->rtm_msglen) {
rtm = (struct rt_msghdr *)(void *)next;
if (rtm->rtm_version != RTM_VERSION)
continue;
if ((rtm->rtm_flags & RTF_GATEWAY) == 0 ||
rtm->rtm_index != ifindex)
continue;
dst = gw = mask = NULL;
sa = (struct sockaddr *)(rtm + 1);
for (i = 0; i < RTAX_MAX; i++) {
if ((rtm->rtm_addrs & (1 << i)) != 0) {
switch (i) {
case RTAX_DST:
dst = (void *)sa;
break;
case RTAX_GATEWAY:
gw = (void *)sa;
break;
case RTAX_NETMASK:
mask = (void *)sa;
break;
}
}
sa = (struct sockaddr *)((char *)sa + SA_SIZE(sa));
}
if (dst->sin_addr.s_addr == INADDR_ANY &&
mask->sin_addr.s_addr == 0) {
ret = inet_ntoa(gw->sin_addr);
break;
}
}
free(buf);
return (ret);
}
static void
check_size(int fd, const char *fn)
{
@ -107,13 +212,13 @@ check_size(int fd, const char *fn)
#ifdef HAVE_CRYPTO
static void
genkey(const char *pubkeyfile, struct diocskerneldump_arg *kda)
genkey(const char *pubkeyfile, struct diocskerneldump_arg *kdap)
{
FILE *fp;
RSA *pubkey;
assert(pubkeyfile != NULL);
assert(kda != NULL);
assert(kdap != NULL);
fp = NULL;
pubkey = NULL;
@ -137,21 +242,21 @@ genkey(const char *pubkeyfile, struct diocskerneldump_arg *kda)
if (pubkey == NULL)
errx(1, "Unable to read data from %s.", pubkeyfile);
kda->kda_encryptedkeysize = RSA_size(pubkey);
if (kda->kda_encryptedkeysize > KERNELDUMP_ENCKEY_MAX_SIZE) {
kdap->kda_encryptedkeysize = RSA_size(pubkey);
if (kdap->kda_encryptedkeysize > KERNELDUMP_ENCKEY_MAX_SIZE) {
errx(1, "Public key has to be at most %db long.",
8 * KERNELDUMP_ENCKEY_MAX_SIZE);
}
kda->kda_encryptedkey = calloc(1, kda->kda_encryptedkeysize);
if (kda->kda_encryptedkey == NULL)
kdap->kda_encryptedkey = calloc(1, kdap->kda_encryptedkeysize);
if (kdap->kda_encryptedkey == NULL)
err(1, "Unable to allocate encrypted key");
kda->kda_encryption = KERNELDUMP_ENC_AES_256_CBC;
arc4random_buf(kda->kda_key, sizeof(kda->kda_key));
if (RSA_public_encrypt(sizeof(kda->kda_key), kda->kda_key,
kda->kda_encryptedkey, pubkey,
RSA_PKCS1_PADDING) != (int)kda->kda_encryptedkeysize) {
kdap->kda_encryption = KERNELDUMP_ENC_AES_256_CBC;
arc4random_buf(kdap->kda_key, sizeof(kdap->kda_key));
if (RSA_public_encrypt(sizeof(kdap->kda_key), kdap->kda_key,
kdap->kda_encryptedkey, pubkey,
RSA_PKCS1_PADDING) != (int)kdap->kda_encryptedkeysize) {
errx(1, "Unable to encrypt the one-time key.");
}
RSA_free(pubkey);
@ -162,8 +267,10 @@ static void
listdumpdev(void)
{
char dumpdev[PATH_MAX];
struct netdump_conf ndconf;
size_t len;
const char *sysctlname = "kern.shutdown.dumpdevname";
int fd;
len = sizeof(dumpdev);
if (sysctlbyname(sysctlname, &dumpdev, &len, NULL, 0) != 0) {
@ -174,36 +281,88 @@ listdumpdev(void)
err(EX_OSERR, "Sysctl get '%s'\n", sysctlname);
}
}
if (verbose) {
if (strlen(dumpdev) == 0)
(void)strlcpy(dumpdev, _PATH_DEVNULL, sizeof(dumpdev));
if (verbose)
printf("kernel dumps on ");
}
if (strlen(dumpdev) == 0) {
printf("%s\n", _PATH_DEVNULL);
} else {
printf("%s\n", dumpdev);
/* If netdump is enabled, print the configuration parameters. */
if (verbose) {
fd = open(_PATH_NETDUMP, O_RDONLY);
if (fd < 0) {
if (errno != ENOENT)
err(EX_OSERR, "opening %s", _PATH_NETDUMP);
return;
}
if (ioctl(fd, NETDUMPGCONF, &ndconf) != 0) {
if (errno != ENXIO)
err(EX_OSERR, "ioctl(NETDUMPGCONF)");
(void)close(fd);
return;
}
printf("server address: %s\n", inet_ntoa(ndconf.ndc_server));
printf("client address: %s\n", inet_ntoa(ndconf.ndc_client));
printf("gateway address: %s\n", inet_ntoa(ndconf.ndc_gateway));
(void)close(fd);
}
}
static int
opendumpdev(const char *arg, char *dumpdev)
{
int fd, i;
if (strncmp(arg, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
strlcpy(dumpdev, arg, PATH_MAX);
else {
i = snprintf(dumpdev, PATH_MAX, "%s%s", _PATH_DEV, arg);
if (i < 0)
err(EX_OSERR, "%s", arg);
if (i >= PATH_MAX)
errc(EX_DATAERR, EINVAL, "%s", arg);
}
fd = open(dumpdev, O_RDONLY);
if (fd < 0)
err(EX_OSFILE, "%s", dumpdev);
return (fd);
}
int
main(int argc, char *argv[])
{
struct diocskerneldump_arg kda;
const char *pubkeyfile;
int ch;
int i, fd;
int do_listdumpdev = 0;
bool enable, gzip, zstd;
char dumpdev[PATH_MAX];
struct diocskerneldump_arg _kda, *kdap;
struct netdump_conf ndconf;
struct addrinfo hints, *res;
const char *dev, *pubkeyfile, *server, *client, *gateway;
int ch, error, fd;
bool enable, gzip, list, netdump, zstd;
gzip = zstd = false;
gzip = list = netdump = zstd = false;
kdap = NULL;
pubkeyfile = NULL;
server = client = gateway = NULL;
while ((ch = getopt(argc, argv, "k:lvZz")) != -1)
while ((ch = getopt(argc, argv, "c:g:k:ls:vZz")) != -1)
switch ((char)ch) {
case 'c':
client = optarg;
break;
case 'g':
gateway = optarg;
break;
case 'k':
pubkeyfile = optarg;
break;
case 'l':
do_listdumpdev = 1;
list = true;
break;
case 's':
server = optarg;
break;
case 'v':
verbose = 1;
@ -224,7 +383,7 @@ main(int argc, char *argv[])
argc -= optind;
argv += optind;
if (do_listdumpdev) {
if (list) {
listdumpdev();
exit(EX_OK);
}
@ -232,72 +391,104 @@ main(int argc, char *argv[])
if (argc != 1)
usage();
enable = (strcmp(argv[0], "off") != 0);
#ifndef HAVE_CRYPTO
if (pubkeyfile != NULL) {
enable = false;
warnx("Unable to use the public key. Recompile dumpon with OpenSSL support.");
}
if (pubkeyfile != NULL)
errx("Unable to use the public key. Recompile dumpon with OpenSSL support.");
#endif
if (enable) {
char tmp[PATH_MAX];
char *dumpdev;
if (server != NULL && client != NULL) {
enable = true;
dev = _PATH_NETDUMP;
netdump = true;
kdap = &ndconf.ndc_kda;
} else if (server == NULL && client == NULL && argc > 0) {
enable = strcmp(argv[0], "off") != 0;
dev = enable ? argv[0] : _PATH_DEVNULL;
netdump = false;
kdap = &_kda;
} else
usage();
if (strncmp(argv[0], _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0) {
dumpdev = argv[0];
} else {
i = snprintf(tmp, PATH_MAX, "%s%s", _PATH_DEV, argv[0]);
if (i < 0) {
err(EX_OSERR, "%s", argv[0]);
} else if (i >= PATH_MAX) {
errno = EINVAL;
err(EX_DATAERR, "%s", argv[0]);
}
dumpdev = tmp;
}
fd = open(dumpdev, O_RDONLY);
if (fd < 0)
err(EX_OSFILE, "%s", dumpdev);
if (!gzip && !zstd)
fd = opendumpdev(dev, dumpdev);
if (!netdump && !gzip)
check_size(fd, dumpdev);
bzero(&kda, sizeof(kda));
kda.kda_enable = 0;
i = ioctl(fd, DIOCSKERNELDUMP, &kda);
explicit_bzero(&kda, sizeof(kda));
bzero(kdap, sizeof(*kdap));
kdap->kda_enable = 0;
if (ioctl(fd, DIOCSKERNELDUMP, kdap) != 0)
err(EX_OSERR, "ioctl(DIOCSKERNELDUMP)");
if (!enable)
exit(EX_OK);
explicit_bzero(kdap, sizeof(*kdap));
kdap->kda_enable = 1;
kdap->kda_compression = KERNELDUMP_COMP_NONE;
if (zstd)
kdap->kda_compression = KERNELDUMP_COMP_ZSTD;
else if (gzip)
kdap->kda_compression = KERNELDUMP_COMP_GZIP;
if (netdump) {
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_protocol = IPPROTO_UDP;
res = NULL;
error = getaddrinfo(server, NULL, &hints, &res);
if (error != 0)
err(1, "%s", gai_strerror(error));
if (res == NULL)
errx(1, "failed to resolve '%s'", server);
server = inet_ntoa(
((struct sockaddr_in *)(void *)res->ai_addr)->sin_addr);
freeaddrinfo(res);
if (strlcpy(ndconf.ndc_iface, argv[0],
sizeof(ndconf.ndc_iface)) >= sizeof(ndconf.ndc_iface))
errx(EX_USAGE, "invalid interface name '%s'", argv[0]);
if (inet_aton(server, &ndconf.ndc_server) == 0)
errx(EX_USAGE, "invalid server address '%s'", server);
if (inet_aton(client, &ndconf.ndc_client) == 0)
errx(EX_USAGE, "invalid client address '%s'", client);
if (gateway == NULL)
gateway = server;
else if (strcmp(gateway, "default") == 0 &&
(gateway = find_gateway(argv[0])) == NULL)
errx(EX_NOHOST,
"failed to look up next-hop router for %s", server);
if (inet_aton(gateway, &ndconf.ndc_gateway) == 0)
errx(EX_USAGE, "invalid gateway address '%s'", gateway);
#ifdef HAVE_CRYPTO
if (pubkeyfile != NULL)
genkey(pubkeyfile, &kda);
genkey(pubkeyfile, kdap);
#endif
kda.kda_enable = 1;
kda.kda_compression = KERNELDUMP_COMP_NONE;
if (zstd)
kda.kda_compression = KERNELDUMP_COMP_ZSTD;
else if (gzip)
kda.kda_compression = KERNELDUMP_COMP_GZIP;
i = ioctl(fd, DIOCSKERNELDUMP, &kda);
explicit_bzero(kda.kda_encryptedkey, kda.kda_encryptedkeysize);
free(kda.kda_encryptedkey);
explicit_bzero(&kda, sizeof(kda));
if (i == 0 && verbose)
printf("kernel dumps on %s\n", dumpdev);
error = ioctl(fd, NETDUMPSCONF, &ndconf);
if (error != 0)
error = errno;
explicit_bzero(kdap->kda_encryptedkey,
kdap->kda_encryptedkeysize);
free(kdap->kda_encryptedkey);
explicit_bzero(kdap, sizeof(*kdap));
if (error != 0)
errc(EX_OSERR, error, "ioctl(NETDUMPSCONF)");
} else {
fd = open(_PATH_DEVNULL, O_RDONLY);
if (fd < 0)
err(EX_OSFILE, "%s", _PATH_DEVNULL);
kda.kda_enable = 0;
i = ioctl(fd, DIOCSKERNELDUMP, &kda);
explicit_bzero(&kda, sizeof(kda));
if (i == 0 && verbose)
printf("kernel dumps disabled\n");
#ifdef HAVE_CRYPTO
if (pubkeyfile != NULL)
genkey(pubkeyfile, kdap);
#endif
error = ioctl(fd, DIOCSKERNELDUMP, kdap);
if (error != 0)
error = errno;
explicit_bzero(kdap->kda_encryptedkey,
kdap->kda_encryptedkeysize);
free(kdap->kda_encryptedkey);
explicit_bzero(kdap, sizeof(*kdap));
if (error != 0)
errc(EX_OSERR, error, "ioctl(DIOCSKERNELDUMP)");
}
if (i < 0)
err(EX_OSERR, "ioctl(DIOCSKERNELDUMP)");
if (verbose)
printf("kernel dumps on %s\n", dumpdev);
exit (0);
exit(EX_OK);
}