From 2a8812d0baeb5f2258f865457be86ad9d7d2d116 Mon Sep 17 00:00:00 2001 From: Mark Johnston Date: Wed, 21 Sep 2016 11:43:02 -0700 Subject: [PATCH 2/7] Add the core netdump client code This is the code that runs after a panic. It's basically a little UDP stack that sets up external mbuf clusters containing kernel dump data and passes them directly to the driver for the chosen interface. It overrides a couple of ifnet methods in order to transmit and receive packets. It also needs the driver to register some netdump methods to be called after a panic. --- etc/mtree/BSD.include.dist | 2 + include/Makefile | 1 + sys/amd64/conf/IQ.amd64 | 3 + sys/conf/NOTES | 5 + sys/conf/files | 4 + sys/conf/options | 5 + sys/net/if_var.h | 7 + sys/netinet/netdump/netdump.h | 128 ++++ sys/netinet/netdump/netdump_client.c | 1176 ++++++++++++++++++++++++++++++++++ sys/netinet/netdump/netdump_mbuf.c | 103 +++ 10 files changed, 1434 insertions(+) create mode 100644 sys/netinet/netdump/netdump.h create mode 100644 sys/netinet/netdump/netdump_client.c create mode 100644 sys/netinet/netdump/netdump_mbuf.c diff --git a/etc/mtree/BSD.include.dist b/etc/mtree/BSD.include.dist index e22e353..49d74b9 100644 --- a/etc/mtree/BSD.include.dist +++ b/etc/mtree/BSD.include.dist @@ -272,6 +272,8 @@ .. .. netinet + netdump + .. .. netinet6 .. diff --git a/include/Makefile b/include/Makefile index c61a9d5..4cb7ce2c 100644 --- a/include/Makefile +++ b/include/Makefile @@ -53,6 +53,7 @@ LSUBDIRS= cam/ata cam/scsi \ geom/mirror geom/mountver geom/multipath geom/nop \ geom/raid geom/raid3 geom/shsec geom/stripe geom/virstor \ netgraph/atm netgraph/netflow \ + netinet/netdump \ security/audit \ security/mac_biba security/mac_bsdextended security/mac_lomac \ security/mac_mls security/mac_partition \ diff --git a/sys/amd64/conf/IQ.amd64 b/sys/amd64/conf/IQ.amd64 index 2eb0d90..ac10607 100644 --- a/sys/amd64/conf/IQ.amd64 +++ b/sys/amd64/conf/IQ.amd64 @@ -135,6 +135,9 @@ options IPOIB # Enable shared channels support options VM_SHARED_CHANS +options NETDUMP +#options NETDUMP_DEBUG + # Uncomment to enable MNVI seatbelt (note bug 173058 though) #options MNVI_SEATBELT diff --git a/sys/conf/NOTES b/sys/conf/NOTES index b537797..56de2bd 100644 --- a/sys/conf/NOTES +++ b/sys/conf/NOTES @@ -995,6 +995,11 @@ options TCP_SIGNATURE #include support for RFC 2385 # a smooth scheduling of the traffic. options DUMMYNET +# Begin Isilon +options NETDUMP +options NETDUMP_DEBUG +# End Isilon + ##################################################################### # FILESYSTEM OPTIONS diff --git a/sys/conf/files b/sys/conf/files index ac6367b..3436b9a 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -3420,6 +3420,10 @@ netinet/libalias/alias_mod.c optional libalias | netgraph_nat netinet/libalias/alias_proxy.c optional libalias inet | netgraph_nat inet netinet/libalias/alias_util.c optional libalias inet | netgraph_nat inet netinet/libalias/alias_sctp.c optional libalias inet | netgraph_nat inet +# Begin Isilon +netinet/netdump/netdump_client.c optional inet netdump +netinet/netdump/netdump_mbuf.c optional inet netdump +# End Isilon netinet6/dest6.c optional inet6 netinet6/frag6.c optional inet6 netinet6/icmp6.c optional inet6 diff --git a/sys/conf/options b/sys/conf/options index d65bd12..611102b 100644 --- a/sys/conf/options +++ b/sys/conf/options @@ -300,6 +300,11 @@ NFS_ROOT opt_nfsroot.h # SMB/CIFS requester NETSMB opt_netsmb.h +# Begin Isilon +NETDUMP opt_global.h +NETDUMP_DEBUG opt_netdump.h +# End Isilon + # Options used only in subr_param.c. HZ opt_param.h MAXFILES opt_param.h diff --git a/sys/net/if_var.h b/sys/net/if_var.h index 49dad2b..764ab11 100644 --- a/sys/net/if_var.h +++ b/sys/net/if_var.h @@ -69,6 +69,9 @@ struct mbuf; /* End Isilon */ struct route; /* if_output */ struct vnet; +/* Begin Isilon */ +struct netdump_methods; +/* End Isilon */ #ifdef _KERNEL /* Begin Isilon */ @@ -218,6 +221,10 @@ struct ifnet { */ u_int if_hw_tsomaxsegcount; /* TSO maximum segment count */ u_int if_hw_tsomaxsegsize; /* TSO maximum segment size in bytes */ + + /* Begin Isilon */ + struct netdump_methods *if_netdump_methods; + /* End Isilon */ void *if_unused[2]; void *if_pspare[8]; /* 1 netmap, 7 TDB */ }; diff --git a/sys/netinet/netdump/netdump.h b/sys/netinet/netdump/netdump.h new file mode 100644 index 0000000..936b1314 --- /dev/null +++ b/sys/netinet/netdump/netdump.h @@ -0,0 +1,128 @@ +/*- + * 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 + +#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 8192 /* Packets payload. */ + +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 { + 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; +struct mbufq; + +struct mbuf *netdump_m_get(int, short, int); +void netdump_m_free(struct mbuf *); +void netdump_mq_init(struct ifnet *); +void netdump_mq_teardown(void); + +typedef void netdump_init_t(struct ifnet *, struct mbufq *, struct mbufq *); +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_ */ diff --git a/sys/netinet/netdump/netdump_client.c b/sys/netinet/netdump/netdump_client.c new file mode 100644 index 0000000..dc8d964 --- /dev/null +++ b/sys/netinet/netdump/netdump_client.c @@ -0,0 +1,1176 @@ +/*- + * 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 + +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_fini(void *priv __unused); +static void netdump_handle_arp(struct mbuf **mb); +static void netdump_handle_ip(struct mbuf **mb); +static int netdump_init(void *priv __unused); +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(void); +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; + +/* 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; +struct ifnet *nd_ifp; +int nd_enabled; +static uint16_t nd_server_port = NETDUMP_PORT; + +/* + * 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 %lu\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(void) +{ + 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"); + 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 = nd_gateway.s_addr; + return (netdump_ether_output(m, nd_ifp, bcast, ETHERTYPE_ARP)); +} + +/* + * Sends ARP requests to locate the server and waits for a response + * + * Return value: + * 0 on success + * errno on error + */ +static int +netdump_arp_gw(void) +{ + int err, polls, retries; + + for (retries = 0; retries < nd_retries && have_gw_mac == 0; retries++) { + err = netdump_send_arp(); + if (err != 0) + return (err); + 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); + + printf("\nARP timed out.\n"); + return (ETIMEDOUT); +} + +/* + * 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_gethdr(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, NULL, + NULL, NULL, 0, EXT_NETDUMP); + m2->m_len = pktlen; + + m_cat(m, m2); + /* XXX use m_catpkt */ + m->m_pkthdr.len += m2->m_len; + } + 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 %zd 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; + } + } + + /* 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) { + 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 err, msgtype; + + NETDDEBUGV("netdump_dumper(NULL, %p, NULL, %ju, %zu)\n", + virtual, (uintmax_t)offset, length); + + if (length > sizeof(nd_buf)) + return (ENOSPC); + + /* + * The first write (at offset 0) is the kernel dump header. Flag it + * for the server to treat specially. + * XXX: This doesn't strip out the footer KDH, although it + * should not hurt anything. + */ + msgtype = NETDUMP_VMCORE; + if (offset == 0 && length > 0) + msgtype = NETDUMP_KDH; + else if (offset > 0) + offset -= NETDUMP_DATASIZE; /* di->blocksize */ + memmove(nd_buf, virtual, length); + err = netdump_send(msgtype, offset, nd_buf, length); + if (err != 0) { + dump_failed = 1; + return (err); + } + return (0); +} + +/* + * Perform any initalization needed prior to transmitting the kernel core. + */ +int +netdump_init(void *priv __unused) +{ + char buf[INET_ADDRSTRLEN]; + 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_init: can't netdump; no server IP given\n"); + return (EINVAL); + } + if (nd_client.s_addr == INADDR_ANY) { + printf("netdump_init: can't netdump; no client IP given\n"); + return (EINVAL); + } + + 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; + + 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 (netdump_send(NETDUMP_HERALD, 0, NULL, 0) != 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); +} + +/* + * Completion routine for a netdump. + */ +static void +netdump_fini(void *priv __unused) +{ + + 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(); +} + +/* + * 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; + + 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); + } + + netdump_mq_init(ifp); + + 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_mq_teardown(); + } + 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 (netdump_configure(conf) != 0) { + error = EINVAL; + break; + } + + dumper.dumper_init = netdump_init; + dumper.dumper = netdump_dumper; + dumper.dumper_fini = netdump_fini; + 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); + if (error != 0) { + nd_enabled = 0; + netdump_mq_teardown(); + } + break; + default: + error = EINVAL; + break; + } + return (error); +} + +/* + * Called upon system init or kld load. Initializes the sysctl variables 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 = getenv("net.dump.iface")) != NULL) { + strlcpy(conf.ndc_iface, arg, sizeof(conf.ndc_iface)); + freeenv(arg); + + if ((arg = getenv("net.dump.server")) != NULL) { + inet_aton(arg, &conf.ndc_server); + freeenv(arg); + } + if ((arg = getenv("net.dump.client")) != NULL) { + inet_aton(arg, &conf.ndc_server); + freeenv(arg); + } + if ((arg = 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); + netdump_mq_teardown(); + } + 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); diff --git a/sys/netinet/netdump/netdump_mbuf.c b/sys/netinet/netdump/netdump_mbuf.c new file mode 100644 index 0000000..03f214f --- /dev/null +++ b/sys/netinet/netdump/netdump_mbuf.c @@ -0,0 +1,103 @@ +/*- + * Copyright (c) 2015 Mark Johnston + * + * 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +static struct mbufq hdrs; +static struct mbufq bufs; + +struct mbuf * +netdump_m_get(int size, short type, int flags) +{ + struct mbufq *mq; + struct mbuf *m; + + if (type != MT_DATA) + return (NULL); + + if (size <= MSIZE) + mq = &hdrs; + else { + mq = &bufs; + flags |= M_EXT; /* m_init overwrites mbuf flags */ + } + + if ((m = mbufq_dequeue(mq)) == NULL) + return (NULL); + m_init(m, NULL, size, M_NOWAIT, type, flags); + if (m->m_flags & M_EXT) + m->m_data = m->m_ext.ext_buf; + return (m); +} + +void +netdump_m_free(struct mbuf *m) +{ + + m->m_next = NULL; + if ((m->m_flags & M_EXT) && m->m_ext.ext_type != EXT_NETDUMP) + mbufq_enqueue(&bufs, m); + else { + m->m_flags &= ~M_EXT; + mbufq_enqueue(&hdrs, m); + } +} + +void +netdump_mq_init(struct ifnet *ifp) +{ + struct mbuf *m; + int i; + + mbufq_init(&hdrs, INT_MAX); + mbufq_init(&bufs, INT_MAX); + + for (i = 0; i < NETDUMP_MAX_IN_FLIGHT * 2; i++) { + m = m_gethdr(M_WAITOK, MT_DATA); + mbufq_enqueue(&hdrs, m); + } + ifp->if_netdump_methods->nd_init(ifp, &hdrs, &bufs); +} + +void +netdump_mq_teardown(void) +{ + + mbufq_drain(&hdrs); + mbufq_drain(&bufs); +} -- 2.10.1