Index: user/markj/netdump/sbin/dumpon/dumpon.c =================================================================== --- user/markj/netdump/sbin/dumpon/dumpon.c (revision 330550) +++ user/markj/netdump/sbin/dumpon/dumpon.c (revision 330551) @@ -1,481 +1,494 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1980, 1993 * The Regents of the University of California. 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 University 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 REGENTS 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 REGENTS 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. */ #if 0 #ifndef lint static const char copyright[] = "@(#) Copyright (c) 1980, 1993\n\ The Regents of the University of California. All rights reserved.\n"; #endif /* not lint */ #ifndef lint static char sccsid[] = "From: @(#)swapon.c 8.1 (Berkeley) 6/5/93"; #endif /* not lint */ #endif #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_CRYPTO #include #include #include #endif static int verbose; static void usage(void) { fprintf(stderr, "usage: dumpon [-v] [-k ] [-Zz] \n" " dumpon [-v] [-k ] [-Zz]\n" " [-g |default] -s -c \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) { int name[] = { CTL_HW, HW_PHYSMEM }; size_t namelen = nitems(name); unsigned long physmem; size_t len; off_t mediasize; int minidump; len = sizeof(minidump); if (sysctlbyname("debug.minidump", &minidump, &len, NULL, 0) == 0 && minidump == 1) return; len = sizeof(physmem); if (sysctl(name, namelen, &physmem, &len, NULL, 0) != 0) err(EX_OSERR, "can't get memory size"); if (ioctl(fd, DIOCGMEDIASIZE, &mediasize) != 0) err(EX_OSERR, "%s: can't get size", fn); if ((uintmax_t)mediasize < (uintmax_t)physmem) { if (verbose) printf("%s is smaller than physical memory\n", fn); exit(EX_IOERR); } } #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; fp = fopen(pubkeyfile, "r"); if (fp == NULL) err(1, "Unable to open %s", pubkeyfile); if (cap_enter() < 0 && errno != ENOSYS) err(1, "Unable to enter capability mode"); pubkey = RSA_new(); if (pubkey == NULL) { errx(1, "Unable to allocate an RSA structure: %s", ERR_error_string(ERR_get_error(), NULL)); } pubkey = PEM_read_RSA_PUBKEY(fp, &pubkey, NULL, NULL); fclose(fp); fp = NULL; 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); } #endif 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) { if (errno == ENOMEM) { err(EX_OSERR, "Kernel returned too large of a buffer for '%s'\n", sysctlname); } else { err(EX_OSERR, "Sysctl get '%s'\n", sysctlname); } } if (strlen(dumpdev) == 0) (void)strlcpy(dumpdev, _PATH_DEVNULL, sizeof(dumpdev)); if (verbose) - printf("kernel dumps on %s\n", dumpdev); + printf("kernel dumps on "); + printf("%s\n", dumpdev); /* If netdump is enabled, print the configuration parameters. */ - 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)"); + 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); - 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[]) { + char dumpdev[PATH_MAX]; + struct diocskerneldump_arg _kda, *kdap; struct netdump_conf ndconf; - struct diocskerneldump_arg kda; struct addrinfo hints, *res; const char *dev, *pubkeyfile, *server, *client, *gateway; - int ch, do_listdumpdev = 0, error, fd, i; - bool enable, gzip, zstd; + 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, "c:g:k:ls:vZz")) != -1) - switch((char)ch) { + 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; break; case 'Z': zstd = true; break; case 'z': gzip = true; break; default: usage(); } if (gzip && zstd) errx(EX_USAGE, "The -z and -Z options are mutually exclusive."); argc -= optind; argv += optind; - if (do_listdumpdev) { + if (list) { listdumpdev(); exit(EX_OK); } if (argc != 1) usage(); - if (server != NULL && client != NULL) +#ifndef HAVE_CRYPTO + if (pubkeyfile != NULL) + errx("Unable to use the public key. Recompile dumpon with OpenSSL support."); +#endif + + if (server != NULL && client != NULL) { + enable = true; dev = _PATH_NETDUMP; - else if (server == NULL && client == NULL && argc > 0) { - dev = argv[0]; - enable = strcmp(dev, "off") != 0; + 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(); -#ifndef HAVE_CRYPTO - if (pubkeyfile != NULL) { - enable = false; - warnx("Unable to use the public key. Recompile dumpon with OpenSSL support."); - } -#endif + fd = opendumpdev(dev, dumpdev); + if (!netdump && !gzip) + check_size(fd, dumpdev); - if (server != NULL) { + 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); - fd = open(dev, O_RDONLY); - if (fd < 0) - err(EX_OSFILE, "%s", dev); - if (ioctl(fd, NETDUMPSCONF, &ndconf) != 0) - err(EX_OSERR, "ioctl(NETDUMPSCONF)"); - } else if (enable) { - char tmp[PATH_MAX]; - char *dumpdev; - - 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) - check_size(fd, dumpdev); - - bzero(&kda, sizeof(kda)); - kda.kda_enable = 0; - i = ioctl(fd, DIOCSKERNELDUMP, &kda); - explicit_bzero(&kda, sizeof(kda)); - #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); - if (i < 0) - err(EX_OSERR, "ioctl(DIOCSKERNELDUMP)"); + 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"); - if (i < 0) - err(EX_OSERR, "ioctl(DIOCSKERNELDUMP)"); +#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 (verbose) + printf("kernel dumps on %s\n", dumpdev); - exit (0); + exit(EX_OK); } Index: user/markj/netdump/sys/netinet/netdump/netdump.h =================================================================== --- user/markj/netdump/sys/netinet/netdump/netdump.h (revision 330550) +++ user/markj/netdump/sys/netinet/netdump/netdump.h (revision 330551) @@ -1,122 +1,124 @@ /*- * Copyright (c) 2005-2014 Sandvine Incorporated * Copyright (c) 2000 Darrell Anderson * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. * * $FreeBSD$ */ #ifndef _NETINET_NETDUMP_H_ #define _NETINET_NETDUMP_H_ #include +#include #include #include #include #define NETDUMP_PORT 20023 /* Server udp port number for data. */ #define NETDUMP_ACKPORT 20024 /* Client udp port number for acks. */ #define NETDUMP_HERALD 1 /* Broadcast before starting a dump. */ #define NETDUMP_FINISHED 2 /* Send after finishing a dump. */ #define NETDUMP_VMCORE 3 /* Contains dump data. */ #define NETDUMP_KDH 4 /* Contains kernel dump header. */ #define NETDUMP_DATASIZE 4096 /* Arbitrary packet size limit. */ struct netdump_msg_hdr { uint32_t mh_type; /* Netdump message type. */ uint32_t mh_seqno; /* Match acks with msgs. */ uint64_t mh_offset; /* vmcore offset (bytes). */ uint32_t mh_len; /* Attached data (bytes). */ uint32_t mh__pad; } __packed; 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; }; #define _PATH_NETDUMP "/dev/netdump" #define NETDUMPGCONF _IOR('n', 1, struct netdump_conf) #define NETDUMPSCONF _IOW('n', 2, struct netdump_conf) #ifdef _KERNEL #ifdef NETDUMP #define NETDUMP_MAX_IN_FLIGHT 64 enum netdump_ev { NETDUMP_START, NETDUMP_END, }; struct ifnet; struct mbuf; typedef void netdump_init_t(struct ifnet *, int *nmbufp, int *nclustp); typedef void netdump_event_t(struct ifnet *, enum netdump_ev); typedef int netdump_transmit_t(struct ifnet *, struct mbuf *); typedef int netdump_poll_t(struct ifnet *, int); struct netdump_methods { netdump_init_t *nd_init; netdump_event_t *nd_event; netdump_transmit_t *nd_transmit; netdump_poll_t *nd_poll; }; #define NETDUMP_DEFINE(driver) \ static netdump_init_t driver##_netdump_init; \ static netdump_event_t driver##_netdump_event; \ static netdump_transmit_t driver##_netdump_transmit; \ static netdump_poll_t driver##_netdump_poll; \ \ static struct netdump_methods driver##_netdump_methods = { \ .nd_init = driver##_netdump_init, \ .nd_event = driver##_netdump_event, \ .nd_transmit = driver##_netdump_transmit, \ .nd_poll = driver##_netdump_poll, \ } #define NETDUMP_SET(ifp, driver) \ (ifp)->if_netdump_methods = &driver##_netdump_methods #else /* !NETDUMP */ #define NETDUMP_DEFINE(driver) #define NETDUMP_SET(ifp, driver) #endif /* NETDUMP */ #endif /* _KERNEL */ #endif /* _NETINET_NETDUMP_H_ */ Index: user/markj/netdump/sys/netinet/netdump/netdump_client.c =================================================================== --- user/markj/netdump/sys/netinet/netdump/netdump_client.c (revision 330550) +++ user/markj/netdump/sys/netinet/netdump/netdump_client.c (revision 330551) @@ -1,1241 +1,1251 @@ /*- * Copyright (c) 2005-2014 Sandvine Incorporated. All rights reserved. * Copyright (c) 2000 Darrell Anderson * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * netdump_client.c * FreeBSD subsystem supporting netdump network dumps. * A dedicated server must be running to accept client dumps. */ #include __FBSDID("$FreeBSD$"); #include "opt_netdump.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef NETDUMP_DEBUG #define NETDDEBUG(f, ...) \ printf(("%s: " f), __func__, ## __VA_ARGS__) #define NETDDEBUG_IF(i, f, ...) \ if_printf((i), ("%s: " f), __func__, ## __VA_ARGS__) #if NETDUMP_DEBUG > 1 #define NETDDEBUGV(f, ...) \ printf(("%s: " f), __func__, ## __VA_ARGS__) #define NETDDEBUGV_IF(i, f, ...) \ if_printf((i), ("%s: " f), __func__, ## __VA_ARGS__) #else #define NETDDEBUGV(f, ...) #define NETDDEBUGV_IF(i, f, ...) #endif #else #define NETDDEBUG(f, ...) #define NETDDEBUG_IF(i, f, ...) #define NETDDEBUGV(f, ...) #define NETDDEBUGV_IF(i, f, ...) #endif /* Defined in kern_mbuf.c. */ void netdump_mbuf_init(int nmbuf, int nclust); void netdump_mbuf_drain(void); void netdump_mbuf_dump(void); static int netdump_arp_gw(void); static void netdump_cleanup(void); static int netdump_configure(struct netdump_conf *); 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, struct ether_addr dst, u_short etype); static void netdump_handle_arp(struct mbuf **mb); static void netdump_handle_ip(struct mbuf **mb); static int netdump_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t addr, int flags __unused, struct thread *td); static int netdump_modevent(module_t mod, int type, void *priv); static void netdump_network_poll(void); static void netdump_pkt_in(struct ifnet *ifp, struct mbuf *m); static int netdump_send(uint32_t type, off_t offset, unsigned char *data, uint32_t datalen); static int netdump_send_arp(in_addr_t dst); static int netdump_start(struct dumperinfo *di); static int netdump_udp_output(struct mbuf *m); /* Must be at least as big as the chunks dumpsys() gives us. */ static unsigned char nd_buf[MAXDUMPPGS * PAGE_SIZE]; static uint32_t nd_seqno; static int dump_failed, have_gw_mac; static void (*drv_if_input)(struct ifnet *, struct mbuf *); static int restore_gw_addr; static uint64_t rcvd_acks; CTASSERT(sizeof(rcvd_acks) * NBBY == NETDUMP_MAX_IN_FLIGHT); /* * Times to poll the NIC (0.5ms each poll) before assuming packetloss * occurred (default to 1s). */ static int nd_polls = 2000; /* Times to retransmit lost packets. */ static int nd_retries = 10; /* Number of ARP retries. */ static int nd_arp_retries = 3; /* 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 /* General dynamic settings. */ static struct ether_addr nd_gw_mac; static struct ifnet *nd_ifp; static uint16_t nd_server_port = NETDUMP_PORT; FEATURE(netdump, "Netdump client support"); static SYSCTL_NODE(_net, OID_AUTO, netdump, CTLFLAG_RD, NULL, "netdump parameters"); static int nd_enabled; SYSCTL_INT(_net_netdump, OID_AUTO, enabled, CTLFLAG_RD, &nd_enabled, 0, "netdump configuration status"); static char nd_path[MAXPATHLEN]; SYSCTL_STRING(_net_netdump, OID_AUTO, path, CTLFLAG_RW, nd_path, sizeof(nd_path), "Server path for output files"); /* * Checks for netdump support on a network interface * * Parameters: * ifp The network interface that is being tested for support * * Returns: * int 1 if the interface is supported, 0 if not */ static bool netdump_supported_nic(struct ifnet *ifp) { return (ifp->if_netdump_methods != NULL); } /*- * Network specific primitives. * Following down the code they are divided ordered as: * - Packet buffer primitives * - Output primitives * - Input primitives * - Polling primitives */ /* * Handles creation of the ethernet header, then places outgoing packets into * the tx buffer for the NIC * * Parameters: * m The mbuf containing the packet to be sent (will be freed by * this function or the NIC driver) * ifp The interface to send on * dst The destination ethernet address (source address will be looked * up using ifp) * etype The ETHERTYPE_* value for the protocol that is being sent * * Returns: * int see errno.h, 0 for success */ static int netdump_ether_output(struct mbuf *m, struct ifnet *ifp, struct ether_addr dst, u_short etype) { struct ether_header *eh; if (((ifp->if_flags & (IFF_MONITOR | IFF_UP)) != IFF_UP) || (ifp->if_drv_flags & IFF_DRV_RUNNING) != IFF_DRV_RUNNING) { if_printf(ifp, "netdump_ether_output: interface isn't up\n"); m_freem(m); return (ENETDOWN); } /* Fill in the ethernet header. */ M_PREPEND(m, ETHER_HDR_LEN, M_NOWAIT); if (m == NULL) { printf("%s: out of mbufs\n", __func__); return (ENOBUFS); } eh = mtod(m, struct ether_header *); memcpy(eh->ether_shost, IF_LLADDR(ifp), ETHER_ADDR_LEN); memcpy(eh->ether_dhost, dst.octet, ETHER_ADDR_LEN); eh->ether_type = htons(etype); return ((ifp->if_netdump_methods->nd_transmit)(ifp, m)); } /* * Unreliable transmission of an mbuf chain to the netdump server * Note: can't handle fragmentation; fails if the packet is larger than * nd_ifp->if_mtu after adding the UDP/IP headers * * Parameters: * m mbuf chain * * Returns: * int see errno.h, 0 for success */ static int netdump_udp_output(struct mbuf *m) { struct udpiphdr *ui; struct ip *ip; MPASS(nd_ifp != NULL); M_PREPEND(m, sizeof(struct udpiphdr), M_NOWAIT); if (m == NULL) { printf("%s: out of mbufs\n", __func__); return (ENOBUFS); } if (m->m_pkthdr.len > nd_ifp->if_mtu) { printf("netdump_udp_output: Packet is too big: %d > MTU %u\n", m->m_pkthdr.len, nd_ifp->if_mtu); m_freem(m); return (ENOBUFS); } ui = mtod(m, struct udpiphdr *); bzero(ui->ui_x1, sizeof(ui->ui_x1)); ui->ui_pr = IPPROTO_UDP; ui->ui_len = htons(m->m_pkthdr.len - sizeof(struct ip)); ui->ui_ulen = ui->ui_len; ui->ui_src = nd_client; ui->ui_dst = nd_server; /* Use this src port so that the server can connect() the socket */ ui->ui_sport = htons(NETDUMP_ACKPORT); ui->ui_dport = htons(nd_server_port); ui->ui_sum = 0; if ((ui->ui_sum = in_cksum(m, m->m_pkthdr.len)) == 0) ui->ui_sum = 0xffff; ip = mtod(m, struct ip *); ip->ip_v = IPVERSION; ip->ip_hl = sizeof(struct ip) >> 2; ip->ip_tos = 0; ip->ip_len = htons(m->m_pkthdr.len); ip->ip_id = 0; ip->ip_off = htons(IP_DF); ip->ip_ttl = 255; ip->ip_sum = 0; ip->ip_sum = in_cksum(m, sizeof(struct ip)); return (netdump_ether_output(m, nd_ifp, nd_gw_mac, ETHERTYPE_IP)); } /* * Builds and sends a single ARP request to locate the server * * Return value: * 0 on success * errno on error */ static int netdump_send_arp(in_addr_t dst) { struct ether_addr bcast; struct mbuf *m; struct arphdr *ah; int pktlen; MPASS(nd_ifp != NULL); /* Fill-up a broadcast address. */ memset(&bcast, 0xFF, ETHER_ADDR_LEN); m = m_gethdr(M_NOWAIT, MT_DATA); if (m == NULL) { printf("netdump_send_arp: Out of mbufs\n"); return (ENOBUFS); } pktlen = arphdr_len2(ETHER_ADDR_LEN, sizeof(struct in_addr)); m->m_len = pktlen; m->m_pkthdr.len = pktlen; MH_ALIGN(m, pktlen); ah = mtod(m, struct arphdr *); ah->ar_hrd = htons(ARPHRD_ETHER); ah->ar_pro = htons(ETHERTYPE_IP); ah->ar_hln = ETHER_ADDR_LEN; ah->ar_pln = sizeof(struct in_addr); ah->ar_op = htons(ARPOP_REQUEST); memcpy(ar_sha(ah), IF_LLADDR(nd_ifp), ETHER_ADDR_LEN); ((struct in_addr *)ar_spa(ah))->s_addr = nd_client.s_addr; bzero(ar_tha(ah), ETHER_ADDR_LEN); ((struct in_addr *)ar_tpa(ah))->s_addr = dst; return (netdump_ether_output(m, nd_ifp, bcast, ETHERTYPE_ARP)); } /* * Sends ARP requests to locate the server and waits for a response. * We first try to ARP the server itself, and fall back to the provided * gateway if the server appears to be off-link. * * Return value: * 0 on success * errno on error */ static int netdump_arp_gw(void) { in_addr_t dst; int error, polls, retries; dst = nd_server.s_addr; restart: for (retries = 0; retries < nd_arp_retries && have_gw_mac == 0; retries++) { error = netdump_send_arp(dst); if (error != 0) return (error); for (polls = 0; polls < nd_polls && have_gw_mac == 0; polls++) { netdump_network_poll(); DELAY(500); } if (have_gw_mac == 0) printf("(ARP retry)"); } if (have_gw_mac != 0) return (0); if (dst == nd_server.s_addr && nd_server.s_addr != nd_gateway.s_addr) { printf("Failed to ARP server, trying to reach gateway...\n"); dst = nd_gateway.s_addr; goto restart; } printf("\nARP timed out.\n"); return (ETIMEDOUT); } /* * Dummy free function for EXT_NETDUMP clusters. */ static void netdump_mbuf_free(struct mbuf *m __unused) { } /* * Construct and reliably send a netdump packet. May fail from a resource * shortage or extreme number of unacknowledged retransmissions. Wait for * an acknowledgement before returning. Splits packets into chunks small * enough to be sent without fragmentation (looks up the interface MTU) * * Parameters: * type netdump packet type (HERALD, FINISHED, or VMCORE) * offset vmcore data offset (bytes) * data vmcore data * datalen vmcore data size (bytes) * * Returns: * int see errno.h, 0 for success */ static int netdump_send(uint32_t type, off_t offset, unsigned char *data, uint32_t datalen) { struct netdump_msg_hdr *nd_msg_hdr; struct mbuf *m, *m2; uint64_t want_acks; uint32_t i, pktlen, sent_so_far; int retries, polls, error; want_acks = 0; rcvd_acks = 0; retries = 0; MPASS(nd_ifp != NULL); retransmit: /* Chunks can be too big to fit in packets. */ for (i = sent_so_far = 0; sent_so_far < datalen || (i == 0 && datalen == 0); i++) { pktlen = datalen - sent_so_far; /* First bound: the packet structure. */ pktlen = min(pktlen, NETDUMP_DATASIZE); /* Second bound: the interface MTU (assume no IP options). */ pktlen = min(pktlen, nd_ifp->if_mtu - sizeof(struct udpiphdr) - sizeof(struct netdump_msg_hdr)); /* * Check if it is retransmitting and this has been ACKed * already. */ if ((rcvd_acks & (1 << i)) != 0) { sent_so_far += pktlen; continue; } /* * Get and fill a header mbuf, then chain data as an extended * mbuf. */ m = m_gethdr(M_NOWAIT, MT_DATA); if (m == NULL) { printf("netdump_send: Out of mbufs\n"); return (ENOBUFS); } m->m_len = sizeof(struct netdump_msg_hdr); m->m_pkthdr.len = sizeof(struct netdump_msg_hdr); MH_ALIGN(m, sizeof(struct netdump_msg_hdr)); nd_msg_hdr = mtod(m, struct netdump_msg_hdr *); nd_msg_hdr->mh_seqno = htonl(nd_seqno + i); nd_msg_hdr->mh_type = htonl(type); nd_msg_hdr->mh_offset = htobe64(offset + sent_so_far); nd_msg_hdr->mh_len = htonl(pktlen); nd_msg_hdr->mh__pad = 0; if (pktlen != 0) { m2 = m_get(M_NOWAIT, MT_DATA); if (m2 == NULL) { m_freem(m); printf("netdump_send: Out of mbufs\n"); return (ENOBUFS); } MEXTADD(m2, data + sent_so_far, pktlen, netdump_mbuf_free, NULL, NULL, 0, EXT_NETDUMP); m2->m_len = pktlen; m_cat(m, m2); m->m_pkthdr.len += pktlen; } error = netdump_udp_output(m); if (error != 0) return (error); /* Note that we're waiting for this packet in the bitfield. */ want_acks |= (1 << i); sent_so_far += pktlen; } if (i >= NETDUMP_MAX_IN_FLIGHT) printf("Warning: Sent more than %d packets (%d). " "Acknowledgements will fail unless the size of " "rcvd_acks/want_acks is increased.\n", NETDUMP_MAX_IN_FLIGHT, i); /* * Wait for acks. A *real* window would speed things up considerably. */ polls = 0; while (rcvd_acks != want_acks) { if (polls++ > nd_polls) { if (retries++ > nd_retries) return (ETIMEDOUT); printf(". "); goto retransmit; } netdump_network_poll(); DELAY(500); } nd_seqno += i; return (0); } /* * Handler for IP packets: checks their sanity and then processes any netdump * ACK packets it finds. * * It needs to replicate partially the behaviour of ip_input() and * udp_input(). * * Parameters: * mb a pointer to an mbuf * containing the packet received * Updates *mb if m_pullup et al change the pointer * Assumes the calling function will take care of freeing the mbuf */ static void netdump_handle_ip(struct mbuf **mb) { struct ip *ip; struct udpiphdr *udp; struct netdump_ack *nd_ack; struct mbuf *m; int rcv_ackno; unsigned short hlen; /* IP processing. */ m = *mb; if (m->m_pkthdr.len < sizeof(struct ip)) { NETDDEBUG("dropping packet too small for IP header\n"); return; } if (m->m_len < sizeof(struct ip)) { m = m_pullup(m, sizeof(struct ip)); *mb = m; if (m == NULL) { NETDDEBUG("m_pullup failed\n"); return; } } ip = mtod(m, struct ip *); /* IP version. */ if (ip->ip_v != IPVERSION) { NETDDEBUG("bad IP version %d\n", ip->ip_v); return; } /* Header length. */ hlen = ip->ip_hl << 2; if (hlen < sizeof(struct ip)) { NETDDEBUG("bad IP header length (%hu)\n", hlen); return; } if (hlen > m->m_len) { m = m_pullup(m, hlen); *mb = m; if (m == NULL) { NETDDEBUG("m_pullup failed\n"); return; } ip = mtod(m, struct ip *); } /* Ignore packets with IP options. */ if (hlen > sizeof(struct ip)) { NETDDEBUG("drop packet with IP options\n"); return; } #ifdef INVARIANTS if (((ntohl(ip->ip_dst.s_addr) >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET || (ntohl(ip->ip_src.s_addr) >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET) && (m->m_pkthdr.rcvif->if_flags & IFF_LOOPBACK) == 0) { NETDDEBUG("Bad IP header (RFC1122)\n"); return; } #endif /* Checksum. */ if ((m->m_pkthdr.csum_flags & CSUM_IP_CHECKED) != 0) { if ((m->m_pkthdr.csum_flags & CSUM_IP_VALID) == 0) { NETDDEBUG("bad IP checksum\n"); return; } } else { /* XXX */ ; } /* Convert fields to host byte order. */ ip->ip_len = ntohs(ip->ip_len); if (ip->ip_len < hlen) { NETDDEBUG("IP packet smaller (%hu) than header (%hu)\n", ip->ip_len, hlen); return; } if (m->m_pkthdr.len < ip->ip_len) { NETDDEBUG("IP packet bigger (%hu) than ethernet packet (%d)\n", ip->ip_len, m->m_pkthdr.len); return; } if (m->m_pkthdr.len > ip->ip_len) { /* Truncate the packet to the IP length. */ if (m->m_len == m->m_pkthdr.len) { m->m_len = ip->ip_len; m->m_pkthdr.len = ip->ip_len; } else m_adj(m, ip->ip_len - m->m_pkthdr.len); } ip->ip_off = ntohs(ip->ip_off); /* Check that the source is the server's IP. */ if (ip->ip_src.s_addr != nd_server.s_addr) { NETDDEBUG("drop packet not from server (from 0x%x)\n", ip->ip_src.s_addr); return; } /* Check if the destination IP is ours. */ if (ip->ip_dst.s_addr != nd_client.s_addr) { NETDDEBUGV("drop packet not to our IP\n"); return; } if (ip->ip_p != IPPROTO_UDP) { NETDDEBUG("drop non-UDP packet\n"); return; } /* Do not deal with fragments. */ if ((ip->ip_off & (IP_MF | IP_OFFMASK)) != 0) { NETDDEBUG("drop fragmented packet\n"); return; } /* UDP custom is to have packet length not include IP header. */ ip->ip_len -= hlen; /* UDP processing. */ /* Get IP and UDP headers together, along with the netdump packet. */ if (m->m_pkthdr.len < sizeof(struct udpiphdr) + sizeof(struct netdump_ack)) { NETDDEBUG("ignoring small packet\n"); return; } if (m->m_len < sizeof(struct udpiphdr) + sizeof(struct netdump_ack)) { m = m_pullup(m, sizeof(struct udpiphdr) + sizeof(struct netdump_ack)); *mb = m; if (m == NULL) { NETDDEBUG("m_pullup failed\n"); return; } } udp = mtod(m, struct udpiphdr *); if (ntohs(udp->ui_u.uh_dport) != NETDUMP_ACKPORT) { NETDDEBUG("not on the netdump port.\n"); return; } /* Netdump processing. */ /* * Packet is meant for us. Extract the ack sequence number and the * port number if necessary. */ nd_ack = (struct netdump_ack *)(mtod(m, caddr_t) + sizeof(struct udpiphdr)); rcv_ackno = ntohl(nd_ack->na_seqno); if (nd_server_port == NETDUMP_PORT) nd_server_port = ntohs(udp->ui_u.uh_sport); if (rcv_ackno >= nd_seqno + NETDUMP_MAX_IN_FLIGHT) printf("%s: ACK %d too far in future!\n", __func__, rcv_ackno); else if (rcv_ackno >= nd_seqno) { /* We're interested in this ack. Record it. */ rcvd_acks |= 1 << (rcv_ackno - nd_seqno); } } /* * Handler for ARP packets: checks their sanity and then * 1. If the ARP is a request for our IP, respond with our MAC address * 2. If the ARP is a response from our server, record its MAC address * * It needs to replicate partially the behaviour of arpintr() and * in_arpinput(). * * Parameters: * mb a pointer to an mbuf * containing the packet received * Updates *mb if m_pullup et al change the pointer * Assumes the calling function will take care of freeing the mbuf */ static void netdump_handle_arp(struct mbuf **mb) { char buf[INET_ADDRSTRLEN]; struct in_addr isaddr, itaddr, myaddr; struct ether_addr dst; struct mbuf *m; struct arphdr *ah; struct ifnet *ifp; uint8_t *enaddr; int req_len, op; m = *mb; ifp = m->m_pkthdr.rcvif; if (m->m_len < sizeof(struct arphdr)) { m = m_pullup(m, sizeof(struct arphdr)); *mb = m; if (m == NULL) { NETDDEBUG("runt packet: m_pullup failed\n"); return; } } ah = mtod(m, struct arphdr *); if (ntohs(ah->ar_hrd) != ARPHRD_ETHER) { NETDDEBUG("unknown hardware address 0x%2D)\n", (unsigned char *)&ah->ar_hrd, ""); return; } if (ntohs(ah->ar_pro) != ETHERTYPE_IP) { NETDDEBUG("drop ARP for unknown protocol %d\n", ntohs(ah->ar_pro)); return; } req_len = arphdr_len2(ifp->if_addrlen, sizeof(struct in_addr)); if (m->m_len < req_len) { m = m_pullup(m, req_len); *mb = m; if (m == NULL) { NETDDEBUG("runt packet: m_pullup failed\n"); return; } } ah = mtod(m, struct arphdr *); op = ntohs(ah->ar_op); memcpy(&isaddr, ar_spa(ah), sizeof(isaddr)); memcpy(&itaddr, ar_tpa(ah), sizeof(itaddr)); enaddr = (uint8_t *)IF_LLADDR(ifp); myaddr = nd_client; if (memcmp(ar_sha(ah), enaddr, ifp->if_addrlen) == 0) { NETDDEBUG("ignoring ARP from myself\n"); return; } if (isaddr.s_addr == nd_client.s_addr) { printf("%s: %*D is using my IP address %s!\n", __func__, ifp->if_addrlen, (u_char *)ar_sha(ah), ":", inet_ntoa_r(isaddr, buf)); return; } if (memcmp(ar_sha(ah), ifp->if_broadcastaddr, ifp->if_addrlen) == 0) { NETDDEBUG("ignoring ARP from broadcast address\n"); return; } if (op == ARPOP_REPLY) { if (isaddr.s_addr != nd_gateway.s_addr && isaddr.s_addr != nd_server.s_addr) { inet_ntoa_r(isaddr, buf); NETDDEBUG( "ignoring ARP reply from %s (not netdump server)\n", buf); return; } memcpy(nd_gw_mac.octet, ar_sha(ah), min(ah->ar_hln, ETHER_ADDR_LEN)); have_gw_mac = 1; NETDDEBUG("got server MAC address %6D\n", nd_gw_mac.octet, ":"); return; } if (op != ARPOP_REQUEST) { NETDDEBUG("ignoring ARP non-request/reply\n"); return; } if (itaddr.s_addr != nd_client.s_addr) { NETDDEBUG("ignoring ARP not to our IP\n"); return; } memcpy(ar_tha(ah), ar_sha(ah), ah->ar_hln); memcpy(ar_sha(ah), enaddr, ah->ar_hln); memcpy(ar_tpa(ah), ar_spa(ah), ah->ar_pln); memcpy(ar_spa(ah), &itaddr, ah->ar_pln); ah->ar_op = htons(ARPOP_REPLY); ah->ar_pro = htons(ETHERTYPE_IP); m->m_flags &= ~(M_BCAST|M_MCAST); m->m_len = arphdr_len(ah); m->m_pkthdr.len = m->m_len; memcpy(dst.octet, ar_tha(ah), ETHER_ADDR_LEN); netdump_ether_output(m, ifp, dst, ETHERTYPE_ARP); *mb = NULL; } /* * Handler for incoming packets directly from the network adapter * Identifies the packet type (IP or ARP) and passes it along to one of the * helper functions netdump_handle_ip or netdump_handle_arp. * * It needs to replicate partially the behaviour of ether_input() and * ether_demux(). * * Parameters: * ifp the interface the packet came from (should be nd_ifp) * m an mbuf containing the packet received */ static void netdump_pkt_in(struct ifnet *ifp, struct mbuf *m) { struct ether_header *eh; u_short etype; /* Ethernet processing. */ if ((m->m_flags & M_PKTHDR) == 0) { NETDDEBUG_IF(ifp, "discard frame without packet header\n"); goto done; } if (m->m_len < ETHER_HDR_LEN) { NETDDEBUG_IF(ifp, "discard frame without leading eth header (len %u pktlen %u)\n", m->m_len, m->m_pkthdr.len); goto done; } if ((m->m_flags & M_HASFCS) != 0) { m_adj(m, -ETHER_CRC_LEN); m->m_flags &= ~M_HASFCS; } eh = mtod(m, struct ether_header *); etype = ntohs(eh->ether_type); if ((m->m_flags & M_VLANTAG) != 0 || etype == ETHERTYPE_VLAN) { NETDDEBUG_IF(ifp, "ignoring vlan packets\n"); goto done; } /* XXX: Probably must also check if we're the recipient MAC address. */ /* Done ethernet processing. Strip off the ethernet header. */ m_adj(m, ETHER_HDR_LEN); switch (etype) { case ETHERTYPE_ARP: netdump_handle_arp(&m); break; case ETHERTYPE_IP: netdump_handle_ip(&m); break; default: NETDDEBUG_IF(ifp, "dropping unknown ethertype %hu\n", etype); break; } done: if (m != NULL) m_freem(m); } /* * After trapping, instead of assuming that most of the network stack is sane, * we just poll the driver directly for packets. */ static void netdump_network_poll(void) { MPASS(nd_ifp != NULL); nd_ifp->if_netdump_methods->nd_poll(nd_ifp, 1000); } /*- * Dumping specific primitives. */ /* * Callback from dumpsys() to dump a chunk of memory. * Copies it out to our static buffer then sends it across the network. * Detects the initial KDH and makes sure it is given a special packet type. * * Parameters: * priv Unused. Optional private pointer. * virtual Virtual address (where to read the data from) * physical Unused. Physical memory address. * offset Offset from start of core file * length Data length * * Return value: * 0 on success * errno on error */ static int netdump_dumper(void *priv __unused, void *virtual, vm_offset_t physical __unused, off_t offset, size_t length) { int error; NETDDEBUGV("netdump_dumper(NULL, %p, NULL, %ju, %zu)\n", virtual, (uintmax_t)offset, length); if (virtual == NULL) { if (dump_failed != 0) printf("failed to dump the kernel core\n"); else if (netdump_send(NETDUMP_FINISHED, 0, NULL, 0) != 0) printf("failed to close the transaction\n"); else printf("\nnetdump finished.\n"); netdump_cleanup(); return (0); } if (length > sizeof(nd_buf)) return (ENOSPC); memmove(nd_buf, virtual, length); error = netdump_send(NETDUMP_VMCORE, offset, nd_buf, length); if (error != 0) { dump_failed = 1; return (error); } return (0); } /* * Perform any initalization needed prior to transmitting the kernel core. */ static int netdump_start(struct dumperinfo *di) { char *path; char buf[INET_ADDRSTRLEN]; uint32_t len; int error; error = 0; /* Check if the dumping is allowed to continue. */ if (nd_enabled == 0) return (EINVAL); MPASS(nd_ifp != NULL); if (nd_server.s_addr == INADDR_ANY) { printf("netdump_start: can't netdump; no server IP given\n"); return (EINVAL); } if (nd_client.s_addr == INADDR_ANY) { printf("netdump_start: can't netdump; no client IP given\n"); return (EINVAL); } /* We start dumping at offset 0. */ di->dumpoff = 0; nd_seqno = 1; /* * nd_server_port could have switched after the first ack the * first time it gets called. Adjust it accordingly. */ nd_server_port = NETDUMP_PORT; /* Switch to the netdump mbuf zones. */ netdump_mbuf_dump(); nd_ifp->if_netdump_methods->nd_event(nd_ifp, NETDUMP_START); /* Make the card use *our* receive callback. */ drv_if_input = nd_ifp->if_input; nd_ifp->if_input = netdump_pkt_in; if (nd_gateway.s_addr == INADDR_ANY) { restore_gw_addr = 1; nd_gateway.s_addr = nd_server.s_addr; } printf("netdump in progress. searching for server...\n"); if (netdump_arp_gw()) { printf("failed to locate server MAC address\n"); error = EINVAL; goto trig_abort; } if (nd_path[0] != '\0') { path = nd_path; len = strlen(path) + 1; } else { path = NULL; len = 0; } if (netdump_send(NETDUMP_HERALD, 0, path, len) != 0) { printf("failed to contact netdump server\n"); error = EINVAL; goto trig_abort; } printf("netdumping to %s (%6D)\n", inet_ntoa_r(nd_server, buf), nd_gw_mac.octet, ":"); return (0); trig_abort: netdump_cleanup(); return (error); } static int netdump_write_headers(struct dumperinfo *di, struct kerneldumpheader *kdh, void *key, uint32_t keysize) { if (sizeof(*kdh) + keysize > sizeof(nd_buf)) return (EINVAL); memcpy(nd_buf, kdh, sizeof(*kdh)); if (key != NULL) memcpy(nd_buf + sizeof(*kdh), key, keysize); return (netdump_send(NETDUMP_KDH, 0, nd_buf, sizeof(*kdh) + keysize)); } /* * Cleanup routine for a possibly failed netdump. */ static void netdump_cleanup(void) { if (restore_gw_addr != 0) { nd_gateway.s_addr = INADDR_ANY; restore_gw_addr = 0; } if (drv_if_input != NULL) { nd_ifp->if_input = drv_if_input; drv_if_input = NULL; } nd_ifp->if_netdump_methods->nd_event(nd_ifp, NETDUMP_END); } /*- * KLD specific code. */ static struct cdevsw netdump_cdevsw = { .d_version = D_VERSION, .d_ioctl = netdump_ioctl, .d_name = "netdump", }; static struct cdev *netdump_cdev; static int netdump_configure(struct netdump_conf *conf) { struct ifnet *ifp; int nmbuf, nclust; IFNET_RLOCK_NOSLEEP(); TAILQ_FOREACH(ifp, &V_ifnet, if_link) { if (strcmp(ifp->if_xname, conf->ndc_iface) == 0) break; } /* XXX ref */ IFNET_RUNLOCK_NOSLEEP(); if (ifp == NULL) { printf("netdump: unknown interface '%s'\n", conf->ndc_iface); return (1); } else if (!netdump_supported_nic(ifp) || ifp->if_type != IFT_ETHER) { printf("netdump: unsupported interface '%s'\n", conf->ndc_iface); return (1); } /* * We need two headers per message. Multiply by four to give us some * breathing room. */ nmbuf = NETDUMP_MAX_IN_FLIGHT * 4; nclust = 0; ifp->if_netdump_methods->nd_init(ifp, &nmbuf, &nclust); netdump_mbuf_init(nmbuf, nclust); nd_ifp = ifp; memcpy(&nd_conf, conf, sizeof(nd_conf)); nd_enabled = 1; return (0); } /* * ioctl(2) handler for the netdump device. This is currently only used to * register netdump as a dump device. * * Parameters: * dev, Unused. * cmd, The ioctl to be handled. * addr, The parameter for the ioctl. * flags, Unused. * td, The thread invoking this ioctl. * * Returns: * 0 on success, and an errno value on failure. */ static int netdump_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t addr, int flags __unused, struct thread *td) { struct dumperinfo dumper; struct netdump_conf *conf; int error; u_int u; error = 0; switch (cmd) { case DIOCSKERNELDUMP: u = *(u_int *)addr; if (u != 0) { error = ENXIO; break; } if (nd_enabled) { nd_enabled = 0; netdump_mbuf_drain(); } break; case NETDUMPGCONF: conf = (struct netdump_conf *)addr; if (!nd_enabled) { error = ENXIO; 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)); break; case NETDUMPSCONF: error = priv_check(td, PRIV_SETDUMPER); if (error != 0) break; conf = (struct netdump_conf *)addr; + if (conf->ndc_kda.kda_enable == 0) { + if (nd_enabled) { + nd_enabled = 0; + netdump_mbuf_drain(); + } + break; + } + if (netdump_configure(conf) != 0) { error = EINVAL; break; } dumper.dumper_start = netdump_start; dumper.dumper_hdr = netdump_write_headers; dumper.dumper = netdump_dumper; dumper.priv = NULL; dumper.blocksize = NETDUMP_DATASIZE; dumper.maxiosize = MAXDUMPPGS * PAGE_SIZE; dumper.mediaoffset = 0; dumper.mediasize = 0; - error = set_dumper(&dumper, conf->ndc_iface, td, 0, 0, NULL, 0, - NULL); + error = set_dumper(&dumper, conf->ndc_iface, td, + conf->ndc_kda.kda_compression, conf->ndc_kda.kda_encryption, + conf->ndc_kda.kda_key, conf->ndc_kda.kda_encryptedkeysize, + conf->ndc_kda.kda_encryptedkey); if (error != 0) { nd_enabled = 0; netdump_mbuf_drain(); } break; default: error = EINVAL; break; } return (error); } /* * Called upon system init or kld load. Initializes the netdump parameters to * sane defaults (locates the first available NIC and uses the first IPv4 IP on * that card as the client IP). Leaves the server IP unconfigured. * * Parameters: * mod, Unused. * what, The module event type. * priv, Unused. * * Returns: * int, An errno value if an error occured, 0 otherwise. */ static int netdump_modevent(module_t mod __unused, int what, void *priv __unused) { struct netdump_conf conf; char *arg; int error; error = 0; switch (what) { case MOD_LOAD: error = make_dev_p(MAKEDEV_WAITOK, &netdump_cdev, &netdump_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "netdump"); if (error != 0) return (error); if ((arg = kern_getenv("net.dump.iface")) != NULL) { strlcpy(conf.ndc_iface, arg, sizeof(conf.ndc_iface)); freeenv(arg); if ((arg = kern_getenv("net.dump.server")) != NULL) { inet_aton(arg, &conf.ndc_server); freeenv(arg); } if ((arg = kern_getenv("net.dump.client")) != NULL) { inet_aton(arg, &conf.ndc_server); freeenv(arg); } if ((arg = kern_getenv("net.dump.gateway")) != NULL) { inet_aton(arg, &conf.ndc_server); freeenv(arg); } /* Ignore errors; we print a message to the console. */ (void)netdump_configure(&conf); } break; case MOD_UNLOAD: destroy_dev(netdump_cdev); if (nd_enabled) { (void)set_dumper(NULL, NULL, curthread, 0, 0, NULL, 0, NULL); netdump_mbuf_drain(); } break; default: error = EOPNOTSUPP; break; } return (error); } static moduledata_t netdump_mod = { "netdump", netdump_modevent, NULL, }; MODULE_VERSION(netdump, 1); DECLARE_MODULE(netdump, netdump_mod, SI_SUB_PSEUDO, SI_ORDER_ANY);