List-ify kernel dump device configuration

Allow users to specify multiple dump configurations in a prioritized list.
This enables fallback to secondary device(s) if primary dump fails.  E.g.,
one might configure a preference for netdump, but fallback to disk dump as a
second choice if netdump is unavailable.

This change does not list-ify netdump configuration, which is tracked
separately from ordinary disk dumps internally; only one netdump
configuration can be made at a time, for now.  It also does not implement
IPv6 netdump.

savecore(8) is already capable of scanning and iterating multiple devices
from /etc/fstab or passed on the command line.

This change doesn't update the rc or loader variables 'dumpdev' in any way;
it can still be set to configure a single dump device, and rc.d/savecore
still uses it as a single device.  Only dumpon(8) is updated to be able to
configure the more complicated configurations for now.

As part of revving the ABI, unify netdump and disk dump configuration ioctl
/ structure, and leave room for ipv6 netdump as a future possibility.
Backwards-compatibility ioctls are added to smooth ABI transition,
especially for developers who may not keep kernel and userspace perfectly
synced.

Reviewed by:	markj, scottl (earlier version)
Relnotes:	maybe
Sponsored by:	Dell EMC Isilon
Differential Revision:	https://reviews.freebsd.org/D19996
This commit is contained in:
Conrad Meyer 2019-05-06 18:24:07 +00:00
parent 46068e86c3
commit 6b6e2954dd
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=347192
11 changed files with 606 additions and 208 deletions

View file

@ -28,7 +28,7 @@
.\" From: @(#)swapon.8 8.1 (Berkeley) 6/5/93
.\" $FreeBSD$
.\"
.Dd November 17, 2018
.Dd May 6, 2019
.Dt DUMPON 8
.Os
.Sh NAME
@ -36,12 +36,16 @@
.Nd "specify a device for crash dumps"
.Sh SYNOPSIS
.Nm
.Op Fl i Ar index
.Op Fl r
.Op Fl v
.Op Fl k Ar pubkey
.Op Fl Z
.Op Fl z
.Ar device
.Nm
.Op Fl i Ar index
.Op Fl r
.Op Fl v
.Op Fl k Ar pubkey
.Op Fl Z
@ -72,8 +76,38 @@ and
.Va dumpon_flags .
For more information on this usage, see
.Xr rc.conf 5 .
.Pp
Starting in
.Fx 13.0 ,
.Nm
can configure a series of fallback dump devices.
For example, an administrator may prefer
.Xr netdump 4
by default, but if the
.Xr netdump 4
service cannot be reached or some other failure occurs, they might choose a
local disk dump as a second choice option.
.Ss General options
.Bl -tag -width _k_pubkey
.It Fl i Ar index
Insert the specified dump configuration into the prioritized fallback dump
device list at the specified index, starting at zero.
.Pp
If
.Fl i
is not specified, the configured dump device is appended to the prioritized
list.
.It Fl r
Remove the specified dump device configuration or configurations from the
fallback dump device list rather than inserting or appending it.
In contrast,
.Do
.Nm
off
.Dc
removes all configured devices.
Conflicts with
.Fl i .
.It Fl k Ar pubkey
Configure encrypted kernel dumps.
.Pp
@ -96,7 +130,7 @@ The
.Va pubkey
file should be a PEM-formatted RSA key of at least 1024 bits.
.It Fl l
List the currently configured dump device, or /dev/null if no device is
List the currently configured dump device(s), or /dev/null if no devices are
configured.
.It Fl v
Enable verbose mode.

View file

@ -86,8 +86,8 @@ static void _Noreturn
usage(void)
{
fprintf(stderr,
"usage: dumpon [-v] [-k <pubkey>] [-Zz] <device>\n"
" dumpon [-v] [-k <pubkey>] [-Zz]\n"
"usage: dumpon [-i index] [-r] [-v] [-k <pubkey>] [-Zz] <device>\n"
" dumpon [-i index] [-r] [-v] [-k <pubkey>] [-Zz]\n"
" [-g <gateway>] -s <server> -c <client> <iface>\n"
" dumpon [-v] off\n"
" dumpon [-v] -l\n");
@ -290,8 +290,10 @@ genkey(const char *pubkeyfile, struct diocskerneldump_arg *kdap)
static void
listdumpdev(void)
{
static char ip[200];
char dumpdev[PATH_MAX];
struct netdump_conf ndconf;
struct diocskerneldump_arg ndconf;
size_t len;
const char *sysctlname = "kern.shutdown.dumpdevname";
int fd;
@ -308,9 +310,17 @@ listdumpdev(void)
if (strlen(dumpdev) == 0)
(void)strlcpy(dumpdev, _PATH_DEVNULL, sizeof(dumpdev));
if (verbose)
printf("kernel dumps on ");
printf("%s\n", dumpdev);
if (verbose) {
char *ctx, *dd;
unsigned idx;
printf("kernel dumps on priority: device\n");
idx = 0;
ctx = dumpdev;
while ((dd = strsep(&ctx, ",")) != NULL)
printf("%u: %s\n", idx++, dd);
} else
printf("%s\n", dumpdev);
/* If netdump is enabled, print the configuration parameters. */
if (verbose) {
@ -320,16 +330,22 @@ listdumpdev(void)
err(EX_OSERR, "opening %s", _PATH_NETDUMP);
return;
}
if (ioctl(fd, NETDUMPGCONF, &ndconf) != 0) {
if (ioctl(fd, DIOCGKERNELDUMP, &ndconf) != 0) {
if (errno != ENXIO)
err(EX_OSERR, "ioctl(NETDUMPGCONF)");
err(EX_OSERR, "ioctl(DIOCGKERNELDUMP)");
(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));
printf("server address: %s\n",
inet_ntop(ndconf.kda_af, &ndconf.kda_server, ip,
sizeof(ip)));
printf("client address: %s\n",
inet_ntop(ndconf.kda_af, &ndconf.kda_client, ip,
sizeof(ip)));
printf("gateway address: %s\n",
inet_ntop(ndconf.kda_af, &ndconf.kda_gateway, ip,
sizeof(ip)));
(void)close(fd);
}
}
@ -359,19 +375,20 @@ int
main(int argc, char *argv[])
{
char dumpdev[PATH_MAX];
struct diocskerneldump_arg _kda, *kdap;
struct netdump_conf ndconf;
struct diocskerneldump_arg ndconf, *kdap;
struct addrinfo hints, *res;
const char *dev, *pubkeyfile, *server, *client, *gateway;
int ch, error, fd;
bool enable, gzip, list, netdump, zstd;
bool gzip, list, netdump, zstd, insert, rflag;
uint8_t ins_idx;
gzip = list = netdump = zstd = false;
gzip = list = netdump = zstd = insert = rflag = false;
kdap = NULL;
pubkeyfile = NULL;
server = client = gateway = NULL;
ins_idx = KDA_APPEND;
while ((ch = getopt(argc, argv, "c:g:k:ls:vZz")) != -1)
while ((ch = getopt(argc, argv, "c:g:i:k:lrs:vZz")) != -1)
switch ((char)ch) {
case 'c':
client = optarg;
@ -379,12 +396,28 @@ main(int argc, char *argv[])
case 'g':
gateway = optarg;
break;
case 'i':
{
int i;
i = atoi(optarg);
if (i < 0 || i >= KDA_APPEND - 1)
errx(EX_USAGE,
"-i index must be between zero and %d.",
(int)KDA_APPEND - 2);
insert = true;
ins_idx = i;
}
break;
case 'k':
pubkeyfile = optarg;
break;
case 'l':
list = true;
break;
case 'r':
rflag = true;
break;
case 's':
server = optarg;
break;
@ -404,6 +437,9 @@ main(int argc, char *argv[])
if (gzip && zstd)
errx(EX_USAGE, "The -z and -Z options are mutually exclusive.");
if (insert && rflag)
errx(EX_USAGE, "The -i and -r options are mutually exclusive.");
argc -= optind;
argv += optind;
@ -422,31 +458,30 @@ main(int argc, char *argv[])
#endif
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;
if (strcmp(argv[0], "off") == 0) {
rflag = true;
dev = _PATH_DEVNULL;
} else
dev = argv[0];
netdump = false;
kdap = &_kda;
} else
usage();
fd = opendumpdev(dev, dumpdev);
if (!netdump && !gzip)
if (!netdump && !gzip && !rflag)
check_size(fd, dumpdev);
kdap = &ndconf;
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;
if (rflag)
kdap->kda_index = KDA_REMOVE;
else
kdap->kda_index = ins_idx;
kdap->kda_compression = KERNELDUMP_COMP_NONE;
if (zstd)
kdap->kda_compression = KERNELDUMP_COMP_ZSTD;
@ -467,12 +502,12 @@ main(int argc, char *argv[])
((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))
if (strlcpy(ndconf.kda_iface, argv[0],
sizeof(ndconf.kda_iface)) >= sizeof(ndconf.kda_iface))
errx(EX_USAGE, "invalid interface name '%s'", argv[0]);
if (inet_aton(server, &ndconf.ndc_server) == 0)
if (inet_aton(server, &ndconf.kda_server.in4) == 0)
errx(EX_USAGE, "invalid server address '%s'", server);
if (inet_aton(client, &ndconf.ndc_client) == 0)
if (inet_aton(client, &ndconf.kda_client.in4) == 0)
errx(EX_USAGE, "invalid client address '%s'", client);
if (gateway == NULL) {
@ -485,39 +520,41 @@ main(int argc, char *argv[])
gateway = server;
}
}
if (inet_aton(gateway, &ndconf.ndc_gateway) == 0)
if (inet_aton(gateway, &ndconf.kda_gateway.in4) == 0)
errx(EX_USAGE, "invalid gateway address '%s'", gateway);
ndconf.kda_af = AF_INET;
}
#ifdef HAVE_CRYPTO
if (pubkeyfile != NULL)
genkey(pubkeyfile, kdap);
if (pubkeyfile != NULL)
genkey(pubkeyfile, kdap);
#endif
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 {
#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)");
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) {
if (netdump) {
/*
* Be slightly less user-hostile for some common
* errors, especially as users don't have any great
* discoverability into which NICs support netdump.
*/
if (error == ENXIO)
errx(EX_OSERR, "Unable to configure netdump "
"because the interface's link is down.");
else if (error == ENODEV)
errx(EX_OSERR, "Unable to configure netdump "
"because the interface driver does not yet "
"support netdump.");
}
errc(EX_OSERR, error, "ioctl(DIOCSKERNELDUMP)");
}
if (verbose)
printf("kernel dumps on %s\n", dumpdev);
listdumpdev();
exit(EX_OK);
}

View file

@ -106,15 +106,26 @@ static int
null_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t data __unused,
int flags __unused, struct thread *td)
{
struct diocskerneldump_arg kda;
int error;
error = 0;
switch (cmd) {
#ifdef COMPAT_FREEBSD11
case DIOCSKERNELDUMP_FREEBSD11:
gone_in(13, "FreeBSD 11.x ABI compat");
/* FALLTHROUGH */
#endif
#ifdef COMPAT_FREEBSD12
case DIOCSKERNELDUMP_FREEBSD12:
if (cmd == DIOCSKERNELDUMP_FREEBSD12)
gone_in(14, "FreeBSD 12.x ABI compat");
/* FALLTHROUGH */
#endif
case DIOCSKERNELDUMP:
error = clear_dumper(td);
bzero(&kda, sizeof(kda));
kda.kda_index = KDA_REMOVE_ALL;
error = dumper_remove(NULL, &kda);
break;
case FIONBIO:
break;

View file

@ -135,15 +135,14 @@ g_dev_fini(struct g_class *mp)
}
static int
g_dev_setdumpdev(struct cdev *dev, struct diocskerneldump_arg *kda,
struct thread *td)
g_dev_setdumpdev(struct cdev *dev, struct diocskerneldump_arg *kda)
{
struct g_kerneldump kd;
struct g_consumer *cp;
int error, len;
if (dev == NULL || kda == NULL)
return (clear_dumper(td));
MPASS(dev != NULL && kda != NULL);
MPASS(kda->kda_index != KDA_REMOVE);
cp = dev->si_drv2;
len = sizeof(kd);
@ -154,9 +153,7 @@ g_dev_setdumpdev(struct cdev *dev, struct diocskerneldump_arg *kda,
if (error != 0)
return (error);
error = set_dumper(&kd.di, devtoname(dev), td, kda->kda_compression,
kda->kda_encryption, kda->kda_key, kda->kda_encryptedkeysize,
kda->kda_encryptedkey);
error = dumper_insert(&kd.di, devtoname(dev), kda);
if (error == 0)
dev->si_flags |= SI_DUMPDEV;
@ -173,7 +170,7 @@ init_dumpdev(struct cdev *dev)
size_t len;
bzero(&kda, sizeof(kda));
kda.kda_enable = 1;
kda.kda_index = KDA_APPEND;
if (dumpdev == NULL)
return (0);
@ -190,7 +187,7 @@ init_dumpdev(struct cdev *dev)
if (error != 0)
return (error);
error = g_dev_setdumpdev(dev, &kda, curthread);
error = g_dev_setdumpdev(dev, &kda);
if (error == 0) {
freeenv(dumpdev);
dumpdev = NULL;
@ -509,6 +506,9 @@ g_dev_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread
struct g_provider *pp;
off_t offset, length, chunk, odd;
int i, error;
#ifdef COMPAT_FREEBSD12
struct diocskerneldump_arg kda_copy;
#endif
cp = dev->si_drv2;
pp = cp->provider;
@ -547,15 +547,35 @@ g_dev_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread
{
struct diocskerneldump_arg kda;
gone_in(13, "FreeBSD 11.x ABI compat");
bzero(&kda, sizeof(kda));
kda.kda_encryption = KERNELDUMP_ENC_NONE;
kda.kda_enable = (uint8_t)*(u_int *)data;
if (kda.kda_enable == 0)
error = g_dev_setdumpdev(NULL, NULL, td);
kda.kda_index = (*(u_int *)data ? 0 : KDA_REMOVE_ALL);
if (kda.kda_index == KDA_REMOVE_ALL)
error = dumper_remove(devtoname(dev), &kda);
else
error = g_dev_setdumpdev(dev, &kda, td);
error = g_dev_setdumpdev(dev, &kda);
break;
}
#endif
#ifdef COMPAT_FREEBSD12
case DIOCSKERNELDUMP_FREEBSD12:
{
struct diocskerneldump_arg_freebsd12 *kda12;
gone_in(14, "FreeBSD 12.x ABI compat");
kda12 = (void *)data;
memcpy(&kda_copy, kda12, sizeof(kda_copy));
kda_copy.kda_index = (kda12->kda12_enable ?
0 : KDA_REMOVE_ALL);
explicit_bzero(kda12, sizeof(*kda12));
/* Kludge to pass kda_copy to kda in fallthrough. */
data = (void *)&kda_copy;
}
/* FALLTHROUGH */
#endif
case DIOCSKERNELDUMP:
{
@ -563,15 +583,19 @@ g_dev_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread
uint8_t *encryptedkey;
kda = (struct diocskerneldump_arg *)data;
if (kda->kda_enable == 0) {
error = g_dev_setdumpdev(NULL, NULL, td);
if (kda->kda_index == KDA_REMOVE_ALL ||
kda->kda_index == KDA_REMOVE_DEV ||
kda->kda_index == KDA_REMOVE) {
error = dumper_remove(devtoname(dev), kda);
explicit_bzero(kda, sizeof(*kda));
break;
}
if (kda->kda_encryption != KERNELDUMP_ENC_NONE) {
if (kda->kda_encryptedkeysize <= 0 ||
if (kda->kda_encryptedkeysize == 0 ||
kda->kda_encryptedkeysize >
KERNELDUMP_ENCKEY_MAX_SIZE) {
explicit_bzero(kda, sizeof(*kda));
return (EINVAL);
}
encryptedkey = malloc(kda->kda_encryptedkeysize, M_TEMP,
@ -583,7 +607,7 @@ g_dev_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread
}
if (error == 0) {
kda->kda_encryptedkey = encryptedkey;
error = g_dev_setdumpdev(dev, kda, td);
error = g_dev_setdumpdev(dev, kda);
}
if (encryptedkey != NULL) {
explicit_bzero(encryptedkey, kda->kda_encryptedkeysize);
@ -859,8 +883,13 @@ g_dev_orphan(struct g_consumer *cp)
g_trace(G_T_TOPOLOGY, "g_dev_orphan(%p(%s))", cp, cp->geom->name);
/* Reset any dump-area set on this device */
if (dev->si_flags & SI_DUMPDEV)
(void)clear_dumper(curthread);
if (dev->si_flags & SI_DUMPDEV) {
struct diocskerneldump_arg kda;
bzero(&kda, sizeof(kda));
kda.kda_index = KDA_REMOVE_DEV;
(void)dumper_remove(devtoname(dev), &kda);
}
/* Destroy the struct cdev *so we get no more requests */
delist_dev(dev);

View file

@ -155,7 +155,6 @@ struct g_raid_disk {
struct g_raid_softc *d_softc; /* Back-pointer to softc. */
struct g_consumer *d_consumer; /* GEOM disk consumer. */
void *d_md_data; /* Disk's metadata storage. */
struct g_kerneldump d_kd; /* Kernel dumping method/args. */
int d_candelete; /* BIO_DELETE supported. */
uint64_t d_flags; /* Additional flags. */
u_int d_state; /* Disk state. */
@ -164,6 +163,7 @@ struct g_raid_disk {
int d_read_errs; /* Count of the read errors */
TAILQ_HEAD(, g_raid_subdisk) d_subdisks; /* List of subdisks. */
TAILQ_ENTRY(g_raid_disk) d_next; /* Next disk in the node. */
struct g_kerneldump d_kd; /* Kernel dumping method/args. */
};
#define G_RAID_SUBDISK_S_NONE 0x00 /* Absent. */

View file

@ -43,6 +43,7 @@ __FBSDID("$FreeBSD$");
#include "opt_ekcd.h"
#include "opt_kdb.h"
#include "opt_panic.h"
#include "opt_printf.h"
#include "opt_sched.h"
#include "opt_watchdog.h"
@ -53,6 +54,7 @@ __FBSDID("$FreeBSD$");
#include <sys/conf.h>
#include <sys/compressor.h>
#include <sys/cons.h>
#include <sys/disk.h>
#include <sys/eventhandler.h>
#include <sys/filedesc.h>
#include <sys/jail.h>
@ -69,6 +71,7 @@ __FBSDID("$FreeBSD$");
#include <sys/reboot.h>
#include <sys/resourcevar.h>
#include <sys/rwlock.h>
#include <sys/sbuf.h>
#include <sys/sched.h>
#include <sys/smp.h>
#include <sys/sysctl.h>
@ -209,7 +212,16 @@ const char *panicstr;
int dumping; /* system is dumping */
int rebooting; /* system is rebooting */
static struct dumperinfo dumper; /* our selected dumper */
/*
* Used to serialize between sysctl kern.shutdown.dumpdevname and list
* modifications via ioctl.
*/
static struct mtx dumpconf_list_lk;
MTX_SYSINIT(dumper_configs, &dumpconf_list_lk, "dumper config list", MTX_DEF);
/* Our selected dumper(s). */
static TAILQ_HEAD(dumpconflist, dumperinfo) dumper_configs =
TAILQ_HEAD_INITIALIZER(dumper_configs);
/* Context information for dump-debuggers. */
static struct pcb dumppcb; /* Registers. */
@ -364,7 +376,7 @@ doadump(boolean_t textdump)
error = 0;
if (dumping)
return (EBUSY);
if (dumper.dumper == NULL)
if (TAILQ_EMPTY(&dumper_configs))
return (ENXIO);
savectx(&dumppcb);
@ -375,11 +387,18 @@ doadump(boolean_t textdump)
#ifdef DDB
if (textdump && textdump_pending) {
coredump = FALSE;
textdump_dumpsys(&dumper);
textdump_dumpsys(TAILQ_FIRST(&dumper_configs));
}
#endif
if (coredump)
error = dumpsys(&dumper);
if (coredump) {
struct dumperinfo *di;
TAILQ_FOREACH(di, &dumper_configs, di_next) {
error = dumpsys(di);
if (error == 0)
break;
}
}
dumping--;
return (error);
@ -952,9 +971,35 @@ kthread_shutdown(void *arg, int howto)
printf("done\n");
}
static char dumpdevname[sizeof(((struct cdev*)NULL)->si_name)];
SYSCTL_STRING(_kern_shutdown, OID_AUTO, dumpdevname, CTLFLAG_RD,
dumpdevname, 0, "Device for kernel dumps");
static int
dumpdevname_sysctl_handler(SYSCTL_HANDLER_ARGS)
{
char buf[256];
struct dumperinfo *di;
struct sbuf sb;
int error;
error = sysctl_wire_old_buffer(req, 0);
if (error != 0)
return (error);
sbuf_new_for_sysctl(&sb, buf, sizeof(buf), req);
mtx_lock(&dumpconf_list_lk);
TAILQ_FOREACH(di, &dumper_configs, di_next) {
if (di != TAILQ_FIRST(&dumper_configs))
sbuf_putc(&sb, ',');
sbuf_cat(&sb, di->di_devname);
}
mtx_unlock(&dumpconf_list_lk);
error = sbuf_finish(&sb);
sbuf_delete(&sb);
return (error);
}
SYSCTL_PROC(_kern_shutdown, OID_AUTO, dumpdevname, CTLTYPE_STRING | CTLFLAG_RD,
&dumper_configs, 0, dumpdevname_sysctl_handler, "A",
"Device(s) for kernel dumps");
static int _dump_append(struct dumperinfo *di, void *virtual,
vm_offset_t physical, size_t length);
@ -1092,31 +1137,67 @@ kerneldumpcomp_destroy(struct dumperinfo *di)
free(kdcomp, M_DUMPER);
}
/*
* Must not be present on global list.
*/
static void
free_single_dumper(struct dumperinfo *di)
{
if (di == NULL)
return;
if (di->blockbuf != NULL) {
explicit_bzero(di->blockbuf, di->blocksize);
free(di->blockbuf, M_DUMPER);
}
kerneldumpcomp_destroy(di);
#ifdef EKCD
if (di->kdcrypto != NULL) {
explicit_bzero(di->kdcrypto, sizeof(*di->kdcrypto) +
di->kdcrypto->kdc_dumpkeysize);
free(di->kdcrypto, M_EKCD);
}
#endif
explicit_bzero(di, sizeof(*di));
free(di, M_DUMPER);
}
/* Registration of dumpers */
int
set_dumper(struct dumperinfo *di, const char *devname, struct thread *td,
uint8_t compression, uint8_t encryption, const uint8_t *key,
uint32_t encryptedkeysize, const uint8_t *encryptedkey)
dumper_insert(const struct dumperinfo *di_template, const char *devname,
const struct diocskerneldump_arg *kda)
{
size_t wantcopy;
struct dumperinfo *newdi, *listdi;
bool inserted;
uint8_t index;
int error;
error = priv_check(td, PRIV_SETDUMPER);
index = kda->kda_index;
MPASS(index != KDA_REMOVE && index != KDA_REMOVE_DEV &&
index != KDA_REMOVE_ALL);
error = priv_check(curthread, PRIV_SETDUMPER);
if (error != 0)
return (error);
if (dumper.dumper != NULL)
return (EBUSY);
dumper = *di;
dumper.blockbuf = NULL;
dumper.kdcrypto = NULL;
dumper.kdcomp = NULL;
newdi = malloc(sizeof(*newdi) + strlen(devname) + 1, M_DUMPER, M_WAITOK
| M_ZERO);
memcpy(newdi, di_template, sizeof(*newdi));
newdi->blockbuf = NULL;
newdi->kdcrypto = NULL;
newdi->kdcomp = NULL;
strcpy(newdi->di_devname, devname);
if (encryption != KERNELDUMP_ENC_NONE) {
if (kda->kda_encryption != KERNELDUMP_ENC_NONE) {
#ifdef EKCD
dumper.kdcrypto = kerneldumpcrypto_create(di->blocksize,
encryption, key, encryptedkeysize, encryptedkey);
if (dumper.kdcrypto == NULL) {
newdi->kdcrypto = kerneldumpcrypto_create(di_template->blocksize,
kda->kda_encryption, kda->kda_key,
kda->kda_encryptedkeysize, kda->kda_encryptedkey);
if (newdi->kdcrypto == NULL) {
error = EINVAL;
goto cleanup;
}
@ -1125,66 +1206,117 @@ set_dumper(struct dumperinfo *di, const char *devname, struct thread *td,
goto cleanup;
#endif
}
wantcopy = strlcpy(dumpdevname, devname, sizeof(dumpdevname));
if (wantcopy >= sizeof(dumpdevname)) {
printf("set_dumper: device name truncated from '%s' -> '%s'\n",
devname, dumpdevname);
}
if (compression != KERNELDUMP_COMP_NONE) {
if (kda->kda_compression != KERNELDUMP_COMP_NONE) {
/*
* We currently can't support simultaneous encryption and
* compression.
* compression because our only encryption mode is an unpadded
* block cipher, go figure. This is low hanging fruit to fix.
*/
if (encryption != KERNELDUMP_ENC_NONE) {
if (kda->kda_encryption != KERNELDUMP_ENC_NONE) {
error = EOPNOTSUPP;
goto cleanup;
}
dumper.kdcomp = kerneldumpcomp_create(&dumper, compression);
if (dumper.kdcomp == NULL) {
newdi->kdcomp = kerneldumpcomp_create(newdi,
kda->kda_compression);
if (newdi->kdcomp == NULL) {
error = EINVAL;
goto cleanup;
}
}
dumper.blockbuf = malloc(di->blocksize, M_DUMPER, M_WAITOK | M_ZERO);
newdi->blockbuf = malloc(newdi->blocksize, M_DUMPER, M_WAITOK | M_ZERO);
/* Add the new configuration to the queue */
mtx_lock(&dumpconf_list_lk);
inserted = false;
TAILQ_FOREACH(listdi, &dumper_configs, di_next) {
if (index == 0) {
TAILQ_INSERT_BEFORE(listdi, newdi, di_next);
inserted = true;
break;
}
index--;
}
if (!inserted)
TAILQ_INSERT_TAIL(&dumper_configs, newdi, di_next);
mtx_unlock(&dumpconf_list_lk);
return (0);
cleanup:
(void)clear_dumper(td);
free_single_dumper(newdi);
return (error);
}
int
clear_dumper(struct thread *td)
static bool
dumper_config_match(const struct dumperinfo *di, const char *devname,
const struct diocskerneldump_arg *kda)
{
if (kda->kda_index == KDA_REMOVE_ALL)
return (true);
if (strcmp(di->di_devname, devname) != 0)
return (false);
/*
* Allow wildcard removal of configs matching a device on g_dev_orphan.
*/
if (kda->kda_index == KDA_REMOVE_DEV)
return (true);
if (di->kdcomp != NULL) {
if (di->kdcomp->kdc_format != kda->kda_compression)
return (false);
} else if (kda->kda_compression != KERNELDUMP_COMP_NONE)
return (false);
#ifdef EKCD
if (di->kdcrypto != NULL) {
if (di->kdcrypto->kdc_encryption != kda->kda_encryption)
return (false);
/*
* Do we care to verify keys match to delete? It seems weird
* to expect multiple fallback dump configurations on the same
* device that only differ in crypto key.
*/
} else
#endif
if (kda->kda_encryption != KERNELDUMP_ENC_NONE)
return (false);
return (true);
}
int
dumper_remove(const char *devname, const struct diocskerneldump_arg *kda)
{
struct dumperinfo *di, *sdi;
bool found;
int error;
error = priv_check(td, PRIV_SETDUMPER);
error = priv_check(curthread, PRIV_SETDUMPER);
if (error != 0)
return (error);
#ifdef NETDUMP
netdump_mbuf_drain();
#endif
#ifdef EKCD
if (dumper.kdcrypto != NULL) {
explicit_bzero(dumper.kdcrypto, sizeof(*dumper.kdcrypto) +
dumper.kdcrypto->kdc_dumpkeysize);
free(dumper.kdcrypto, M_EKCD);
/*
* Try to find a matching configuration, and kill it.
*
* NULL 'kda' indicates remove any configuration matching 'devname',
* which may remove multiple configurations in atypical configurations.
*/
found = false;
mtx_lock(&dumpconf_list_lk);
TAILQ_FOREACH_SAFE(di, &dumper_configs, di_next, sdi) {
if (dumper_config_match(di, devname, kda)) {
found = true;
TAILQ_REMOVE(&dumper_configs, di, di_next);
free_single_dumper(di);
}
}
#endif
mtx_unlock(&dumpconf_list_lk);
kerneldumpcomp_destroy(&dumper);
if (dumper.blockbuf != NULL) {
explicit_bzero(dumper.blockbuf, dumper.blocksize);
free(dumper.blockbuf, M_DUMPER);
}
explicit_bzero(&dumper, sizeof(dumper));
dumpdevname[0] = '\0';
/* Only produce ENOENT if a more targeted match didn't match. */
if (!found && kda->kda_index == KDA_REMOVE)
return (ENOENT);
return (0);
}

View file

@ -60,18 +60,18 @@ struct netdump_ack {
uint32_t na_seqno; /* Match acks with msgs. */
} __packed;
struct netdump_conf {
struct diocskerneldump_arg ndc_kda;
char ndc_iface[IFNAMSIZ];
struct in_addr ndc_server;
struct in_addr ndc_client;
struct in_addr ndc_gateway;
struct netdump_conf_freebsd12 {
struct diocskerneldump_arg_freebsd12 ndc12_kda;
char ndc12_iface[IFNAMSIZ];
struct in_addr ndc12_server;
struct in_addr ndc12_client;
struct in_addr ndc12_gateway;
};
#define _PATH_NETDUMP "/dev/netdump"
#define NETDUMPGCONF_FREEBSD12 _IOR('n', 1, struct netdump_conf_freebsd12)
#define NETDUMPSCONF_FREEBSD12 _IOW('n', 2, struct netdump_conf_freebsd12)
#define NETDUMPGCONF _IOR('n', 1, struct netdump_conf)
#define NETDUMPSCONF _IOW('n', 2, struct netdump_conf)
#define _PATH_NETDUMP "/dev/netdump"
#ifdef _KERNEL
#ifdef NETDUMP

View file

@ -89,7 +89,8 @@ __FBSDID("$FreeBSD$");
static int netdump_arp_gw(void);
static void netdump_cleanup(void);
static int netdump_configure(struct netdump_conf *, struct thread *);
static int netdump_configure(struct diocskerneldump_arg *,
struct thread *);
static int netdump_dumper(void *priv __unused, void *virtual,
vm_offset_t physical __unused, off_t offset, size_t length);
static int netdump_ether_output(struct mbuf *m, struct ifnet *ifp,
@ -118,10 +119,10 @@ static uint64_t rcvd_acks;
CTASSERT(sizeof(rcvd_acks) * NBBY == NETDUMP_MAX_IN_FLIGHT);
/* Configuration parameters. */
static struct netdump_conf nd_conf;
#define nd_server nd_conf.ndc_server
#define nd_client nd_conf.ndc_client
#define nd_gateway nd_conf.ndc_gateway
static struct diocskerneldump_arg nd_conf;
#define nd_server nd_conf.kda_server.in4
#define nd_client nd_conf.kda_client.in4
#define nd_gateway nd_conf.kda_gateway.in4
/* General dynamic settings. */
static struct ether_addr nd_gw_mac;
@ -1059,7 +1060,7 @@ static struct cdevsw netdump_cdevsw = {
static struct cdev *netdump_cdev;
static int
netdump_configure(struct netdump_conf *conf, struct thread *td)
netdump_configure(struct diocskerneldump_arg *conf, struct thread *td)
{
struct epoch_tracker et;
struct ifnet *ifp;
@ -1071,7 +1072,7 @@ netdump_configure(struct netdump_conf *conf, struct thread *td)
}
NET_EPOCH_ENTER(et);
CK_STAILQ_FOREACH(ifp, &V_ifnet, if_link) {
if (strcmp(ifp->if_xname, conf->ndc_iface) == 0)
if (strcmp(ifp->if_xname, conf->kda_iface) == 0)
break;
}
/* XXX ref */
@ -1083,7 +1084,7 @@ netdump_configure(struct netdump_conf *conf, struct thread *td)
if ((if_getflags(ifp) & IFF_UP) == 0)
return (ENXIO);
if (!netdump_supported_nic(ifp) || ifp->if_type != IFT_ETHER)
return (EINVAL);
return (ENODEV);
nd_ifp = ifp;
netdump_reinit(ifp);
@ -1135,19 +1136,24 @@ static int
netdump_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t addr,
int flags __unused, struct thread *td)
{
struct diocskerneldump_arg *kda;
struct diocskerneldump_arg kda_copy, *conf;
struct dumperinfo dumper;
struct netdump_conf *conf;
uint8_t *encryptedkey;
int error;
#ifdef COMPAT_FREEBSD11
u_int u;
#endif
#ifdef COMPAT_FREEBSD12
struct diocskerneldump_arg_freebsd12 *kda12;
struct netdump_conf_freebsd12 *conf12;
#endif
conf = NULL;
error = 0;
switch (cmd) {
#ifdef COMPAT_FREEBSD11
case DIOCSKERNELDUMP_FREEBSD11:
gone_in(13, "11.x ABI compatibility");
u = *(u_int *)addr;
if (u != 0) {
error = ENXIO;
@ -1159,9 +1165,17 @@ netdump_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t addr,
}
break;
#endif
case DIOCSKERNELDUMP:
kda = (void *)addr;
if (kda->kda_enable != 0) {
#ifdef COMPAT_FREEBSD12
/*
* Used by dumpon(8) in 12.x for clearing previous
* configuration -- then NETDUMPSCONF_FREEBSD12 is used to
* actually configure netdump.
*/
case DIOCSKERNELDUMP_FREEBSD12:
gone_in(14, "12.x ABI compatibility");
kda12 = (void *)addr;
if (kda12->kda12_enable) {
error = ENXIO;
break;
}
@ -1170,28 +1184,99 @@ netdump_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t addr,
netdump_mbuf_drain();
}
break;
case NETDUMPGCONF:
conf = (struct netdump_conf *)addr;
case NETDUMPGCONF_FREEBSD12:
gone_in(14, "FreeBSD 12.x ABI compat");
conf12 = (void *)addr;
if (!nd_enabled) {
error = ENXIO;
break;
}
if (nd_conf.kda_af != AF_INET) {
error = EOPNOTSUPP;
break;
}
strlcpy(conf->ndc_iface, nd_ifp->if_xname,
sizeof(conf->ndc_iface));
memcpy(&conf->ndc_server, &nd_server, sizeof(nd_server));
memcpy(&conf->ndc_client, &nd_client, sizeof(nd_client));
memcpy(&conf->ndc_gateway, &nd_gateway, sizeof(nd_gateway));
strlcpy(conf12->ndc12_iface, nd_ifp->if_xname,
sizeof(conf12->ndc12_iface));
memcpy(&conf12->ndc12_server, &nd_server,
sizeof(conf12->ndc12_server));
memcpy(&conf12->ndc12_client, &nd_client,
sizeof(conf12->ndc12_client));
memcpy(&conf12->ndc12_gateway, &nd_gateway,
sizeof(conf12->ndc12_gateway));
break;
case NETDUMPSCONF:
conf = (struct netdump_conf *)addr;
encryptedkey = NULL;
kda = &conf->ndc_kda;
#endif
case DIOCGKERNELDUMP:
conf = (void *)addr;
/*
* For now, index is ignored; netdump doesn't support multiple
* configurations (yet).
*/
if (!nd_enabled) {
error = ENXIO;
conf = NULL;
break;
}
conf->ndc_iface[sizeof(conf->ndc_iface) - 1] = '\0';
if (kda->kda_enable == 0) {
if (nd_enabled) {
error = clear_dumper(td);
strlcpy(conf->kda_iface, nd_ifp->if_xname,
sizeof(conf->kda_iface));
memcpy(&conf->kda_server, &nd_server, sizeof(nd_server));
memcpy(&conf->kda_client, &nd_client, sizeof(nd_client));
memcpy(&conf->kda_gateway, &nd_gateway, sizeof(nd_gateway));
conf->kda_af = nd_conf.kda_af;
conf = NULL;
break;
#ifdef COMPAT_FREEBSD12
case NETDUMPSCONF_FREEBSD12:
gone_in(14, "FreeBSD 12.x ABI compat");
conf12 = (struct netdump_conf_freebsd12 *)addr;
_Static_assert(offsetof(struct diocskerneldump_arg, kda_server)
== offsetof(struct netdump_conf_freebsd12, ndc12_server),
"simplifying assumption");
memset(&kda_copy, 0, sizeof(kda_copy));
memcpy(&kda_copy, conf12,
offsetof(struct diocskerneldump_arg, kda_server));
/* 12.x ABI could only configure IPv4 (INET) netdump. */
kda_copy.kda_af = AF_INET;
memcpy(&kda_copy.kda_server.in4, &conf12->ndc12_server,
sizeof(struct in_addr));
memcpy(&kda_copy.kda_client.in4, &conf12->ndc12_client,
sizeof(struct in_addr));
memcpy(&kda_copy.kda_gateway.in4, &conf12->ndc12_gateway,
sizeof(struct in_addr));
kda_copy.kda_index =
(conf12->ndc12_kda.kda12_enable ? 0 : KDA_REMOVE_ALL);
conf = &kda_copy;
explicit_bzero(conf12, sizeof(*conf12));
/* FALLTHROUGH */
#endif
case DIOCSKERNELDUMP:
encryptedkey = NULL;
if (cmd == DIOCSKERNELDUMP) {
conf = (void *)addr;
memcpy(&kda_copy, conf, sizeof(kda_copy));
}
/* Netdump only supports IP4 at this time. */
if (conf->kda_af != AF_INET) {
error = EPROTONOSUPPORT;
break;
}
conf->kda_iface[sizeof(conf->kda_iface) - 1] = '\0';
if (conf->kda_index == KDA_REMOVE ||
conf->kda_index == KDA_REMOVE_DEV ||
conf->kda_index == KDA_REMOVE_ALL) {
if (nd_enabled || conf->kda_index == KDA_REMOVE_ALL) {
error = dumper_remove(conf->kda_iface, conf);
if (error == 0) {
nd_enabled = 0;
netdump_mbuf_drain();
@ -1204,19 +1289,23 @@ netdump_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t addr,
if (error != 0)
break;
if (kda->kda_encryption != KERNELDUMP_ENC_NONE) {
if (kda->kda_encryptedkeysize <= 0 ||
kda->kda_encryptedkeysize >
KERNELDUMP_ENCKEY_MAX_SIZE)
return (EINVAL);
encryptedkey = malloc(kda->kda_encryptedkeysize, M_TEMP,
M_WAITOK);
error = copyin(kda->kda_encryptedkey, encryptedkey,
kda->kda_encryptedkeysize);
if (conf->kda_encryption != KERNELDUMP_ENC_NONE) {
if (conf->kda_encryptedkeysize <= 0 ||
conf->kda_encryptedkeysize >
KERNELDUMP_ENCKEY_MAX_SIZE) {
error = EINVAL;
break;
}
encryptedkey = malloc(conf->kda_encryptedkeysize,
M_TEMP, M_WAITOK);
error = copyin(conf->kda_encryptedkey, encryptedkey,
conf->kda_encryptedkeysize);
if (error != 0) {
free(encryptedkey, M_TEMP);
return (error);
break;
}
conf->kda_encryptedkey = encryptedkey;
}
memset(&dumper, 0, sizeof(dumper));
@ -1229,12 +1318,10 @@ netdump_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t addr,
dumper.mediaoffset = 0;
dumper.mediasize = 0;
error = set_dumper(&dumper, conf->ndc_iface, td,
kda->kda_compression, kda->kda_encryption,
kda->kda_key, kda->kda_encryptedkeysize,
encryptedkey);
error = dumper_insert(&dumper, conf->kda_iface, conf);
if (encryptedkey != NULL) {
explicit_bzero(encryptedkey, kda->kda_encryptedkeysize);
explicit_bzero(encryptedkey,
conf->kda_encryptedkeysize);
free(encryptedkey, M_TEMP);
}
if (error != 0) {
@ -1243,9 +1330,12 @@ netdump_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t addr,
}
break;
default:
error = EINVAL;
error = ENOTTY;
break;
}
explicit_bzero(&kda_copy, sizeof(kda_copy));
if (conf != NULL)
explicit_bzero(conf, sizeof(*conf));
return (error);
}
@ -1265,7 +1355,7 @@ netdump_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t addr,
static int
netdump_modevent(module_t mod __unused, int what, void *priv __unused)
{
struct netdump_conf conf;
struct diocskerneldump_arg conf;
char *arg;
int error;
@ -1278,33 +1368,41 @@ netdump_modevent(module_t mod __unused, int what, void *priv __unused)
return (error);
if ((arg = kern_getenv("net.dump.iface")) != NULL) {
strlcpy(conf.ndc_iface, arg, sizeof(conf.ndc_iface));
strlcpy(conf.kda_iface, arg, sizeof(conf.kda_iface));
freeenv(arg);
if ((arg = kern_getenv("net.dump.server")) != NULL) {
inet_aton(arg, &conf.ndc_server);
inet_aton(arg, &conf.kda_server.in4);
freeenv(arg);
}
if ((arg = kern_getenv("net.dump.client")) != NULL) {
inet_aton(arg, &conf.ndc_server);
inet_aton(arg, &conf.kda_server.in4);
freeenv(arg);
}
if ((arg = kern_getenv("net.dump.gateway")) != NULL) {
inet_aton(arg, &conf.ndc_server);
inet_aton(arg, &conf.kda_server.in4);
freeenv(arg);
}
conf.kda_af = AF_INET;
/* Ignore errors; we print a message to the console. */
(void)netdump_configure(&conf, curthread);
}
break;
case MOD_UNLOAD:
destroy_dev(netdump_cdev);
if (nd_enabled) {
struct diocskerneldump_arg kda;
printf("netdump: disabling dump device for unload\n");
(void)clear_dumper(curthread);
bzero(&kda, sizeof(kda));
kda.kda_index = KDA_REMOVE_DEV;
(void)dumper_remove(nd_conf.kda_iface, &kda);
netdump_mbuf_drain();
nd_enabled = 0;
}
destroy_dev(netdump_cdev);
break;
default:
error = EOPNOTSUPP;

View file

@ -352,15 +352,19 @@ struct dumperinfo {
off_t origdumpoff; /* Starting dump offset. */
struct kerneldumpcrypto *kdcrypto; /* Kernel dump crypto. */
struct kerneldumpcomp *kdcomp; /* Kernel dump compression. */
TAILQ_ENTRY(dumperinfo) di_next;
char di_devname[];
};
extern int dumping; /* system is dumping */
int doadump(boolean_t);
int set_dumper(struct dumperinfo *di, const char *devname, struct thread *td,
uint8_t compression, uint8_t encryption, const uint8_t *key,
uint32_t encryptedkeysize, const uint8_t *encryptedkey);
int clear_dumper(struct thread *td);
struct diocskerneldump_arg;
int dumper_insert(const struct dumperinfo *di_template, const char *devname,
const struct diocskerneldump_arg *kda);
int dumper_remove(const char *devname, const struct diocskerneldump_arg *kda);
int dump_start(struct dumperinfo *di, struct kerneldumpheader *kdh);
int dump_append(struct dumperinfo *, void *, vm_offset_t, size_t);

View file

@ -19,6 +19,10 @@
#include <sys/kerneldump.h>
#include <sys/types.h>
#include <sys/disk_zone.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#ifdef _KERNEL
@ -143,17 +147,66 @@ struct diocgattr_arg {
#define DIOCZONECMD _IOWR('d', 143, struct disk_zone_args)
struct diocskerneldump_arg_freebsd12 {
uint8_t kda12_enable;
uint8_t kda12_compression;
uint8_t kda12_encryption;
uint8_t kda12_key[KERNELDUMP_KEY_MAX_SIZE];
uint32_t kda12_encryptedkeysize;
uint8_t *kda12_encryptedkey;
};
#define DIOCSKERNELDUMP_FREEBSD12 \
_IOW('d', 144, struct diocskerneldump_arg_freebsd12)
union kd_ip {
struct in_addr in4;
struct in6_addr in6;
};
/*
* Sentinel values for kda_index.
*
* If kda_index is KDA_REMOVE_ALL, all dump configurations are cleared.
*
* If kda_index is KDA_REMOVE_DEV, all dump configurations for the specified
* device are cleared.
*
* If kda_index is KDA_REMOVE, only the specified dump configuration for the
* given device is removed from the list of fallback dump configurations.
*
* If kda_index is KDA_APPEND, the dump configuration is added after all
* existing dump configurations.
*
* Otherwise, the new configuration is inserted into the fallback dump list at
* index 'kda_index'.
*/
#define KDA_REMOVE UINT8_MAX
#define KDA_REMOVE_ALL (UINT8_MAX - 1)
#define KDA_REMOVE_DEV (UINT8_MAX - 2)
#define KDA_APPEND (UINT8_MAX - 3)
struct diocskerneldump_arg {
uint8_t kda_enable;
uint8_t kda_index;
uint8_t kda_compression;
uint8_t kda_encryption;
uint8_t kda_key[KERNELDUMP_KEY_MAX_SIZE];
uint32_t kda_encryptedkeysize;
uint8_t *kda_encryptedkey;
char kda_iface[IFNAMSIZ];
union kd_ip kda_server;
union kd_ip kda_client;
union kd_ip kda_gateway;
uint8_t kda_af;
};
#define DIOCSKERNELDUMP _IOW('d', 144, struct diocskerneldump_arg)
_Static_assert(__offsetof(struct diocskerneldump_arg, kda_iface) ==
sizeof(struct diocskerneldump_arg_freebsd12), "simplifying assumption");
#define DIOCSKERNELDUMP _IOW('d', 145, struct diocskerneldump_arg)
/*
* Enable/Disable the device for kernel core dumps.
*/
#define DIOCGKERNELDUMP _IOWR('d', 146, struct diocskerneldump_arg)
/*
* Get current kernel netdump configuration details for a given index.
*/
#endif /* _SYS_DISK_H_ */

View file

@ -60,7 +60,7 @@
* in the range 5 to 9.
*/
#undef __FreeBSD_version
#define __FreeBSD_version 1300022 /* Master, propagated to newvers */
#define __FreeBSD_version 1300023 /* Master, propagated to newvers */
/*
* __FreeBSD_kernel__ indicates that this system uses the kernel of FreeBSD,