changeset 21327:a99734b8cddf

10962 Want a way to extract SMB packets from a crash dump Reviewed by: Dan Fields <dan.fields@nexenta.com> Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com> Approved by: Joshua M. Clulow <josh@sysmgr.org>
author Gordon Ross <gwr@nexenta.com>
date Wed, 27 May 2015 17:25:56 -0400
parents db8ec8447779
children 603acf6e5b38
files usr/src/cmd/mdb/common/modules/smbsrv/smbsrv.c usr/src/cmd/mdb/common/modules/smbsrv/smbsrv_pcap.c usr/src/cmd/mdb/common/modules/smbsrv/smbsrv_pcap.h usr/src/cmd/mdb/intel/amd64/libfksmbsrv/Makefile usr/src/cmd/mdb/intel/amd64/smbsrv/Makefile usr/src/cmd/mdb/intel/ia32/libfksmbsrv/Makefile usr/src/cmd/mdb/sparc/v9/smbsrv/Makefile
diffstat 7 files changed, 823 insertions(+), 30 deletions(-) [+]
line wrap: on
line diff
--- a/usr/src/cmd/mdb/common/modules/smbsrv/smbsrv.c	Mon Apr 20 23:02:00 2015 -0400
+++ b/usr/src/cmd/mdb/common/modules/smbsrv/smbsrv.c	Wed May 27 17:25:56 2015 -0400
@@ -32,6 +32,10 @@
 #include <smbsrv/smb.h>
 #include <smbsrv/smb_ktypes.h>
 
+#ifndef _KMDB
+#include "smbsrv_pcap.h"
+#endif
+
 #ifdef _KERNEL
 #define	SMBSRV_OBJNAME	"smbsrv"
 #else
@@ -392,6 +396,15 @@
 static int smb_obj_expand(uintptr_t, uint_t, const smb_exp_t *, ulong_t);
 static int smb_obj_list(const char *, uint_t, uint_t);
 static int smb_worker_findstack(uintptr_t);
+static void smb_inaddr_ntop(smb_inaddr_t *, char *, size_t);
+
+typedef int (*dump_func_t)(struct mbuf_chain *, int32_t,
+    smb_inaddr_t *, uint16_t, smb_inaddr_t *, uint16_t,
+    hrtime_t, boolean_t);
+static int smb_req_dump(struct mbuf_chain *, int32_t,
+    smb_inaddr_t *, uint16_t, smb_inaddr_t *, uint16_t,
+    hrtime_t, boolean_t);
+static int smb_req_dump_m(uintptr_t, const void *, void *);
 
 /*
  * *****************************************************************************
@@ -622,7 +635,7 @@
 	    !(opts & SMB_OPT_WALK)) {
 		char	cipaddr[INET6_ADDRSTRLEN];
 		char	lipaddr[INET6_ADDRSTRLEN];
-		int	ipaddrstrlen;
+		int	ipaddrstrlen = INET6_ADDRSTRLEN;
 		smb_session_t	*se;
 		const char	*state;
 
@@ -638,28 +651,10 @@
 		else
 			state = smb_session_state[se->s_state];
 
-		switch (se->ipaddr.a_family) {
-		case AF_INET:
+		if (se->ipaddr.a_family == AF_INET)
 			ipaddrstrlen = INET_ADDRSTRLEN;
-			(void) mdb_snprintf(cipaddr, sizeof (cipaddr),
-			    "%I", se->ipaddr.a_ipv4);
-			(void) mdb_snprintf(lipaddr, sizeof (lipaddr),
-			    "%I", se->local_ipaddr.a_ipv4);
-			break;
-		case AF_INET6:
-			ipaddrstrlen = INET6_ADDRSTRLEN;
-			(void) mdb_snprintf(cipaddr, sizeof (cipaddr),
-			    "%N", &(se->ipaddr.a_ipv6));
-			(void) mdb_snprintf(lipaddr, sizeof (lipaddr),
-			    "%N", &(se->local_ipaddr.a_ipv6));
-			break;
-		default:
-			ipaddrstrlen = INET_ADDRSTRLEN;
-			(void) mdb_snprintf(cipaddr, sizeof (cipaddr),
-			    "unknown");
-			(void) mdb_snprintf(lipaddr, sizeof (lipaddr),
-			    "unknown");
-		}
+		smb_inaddr_ntop(&se->ipaddr, cipaddr, ipaddrstrlen);
+		smb_inaddr_ntop(&se->local_ipaddr, lipaddr, ipaddrstrlen);
 
 		if (opts & SMB_OPT_VERBOSE) {
 			mdb_printf("%<b>%<u>SMB session information "
@@ -876,6 +871,209 @@
 	return (DCMD_OK);
 }
 
+static void
+smbreq_dump_help(void)
+{
+	mdb_printf(
+	    "Dump the network data for an smb_request_t, either"
+	    " command, reply, or (by default) both.  Optionally"
+	    " append data to a pcap file (mdb only, not kmdb).\n\n");
+	(void) mdb_dec_indent(2);
+	mdb_printf("%<b>OPTIONS%</b>\n");
+	(void) mdb_inc_indent(2);
+	mdb_printf(
+	    "-c\tDump only the SMB command message\n"
+	    "-r\tDump only the SMB reply message (if present)\n"
+	    "-o FILE\tOutput to FILE (append) in pcap format\n");
+}
+
+#define	SMB_RDOPT_COMMAND	1
+#define	SMB_RDOPT_REPLY		2
+#define	SMB_RDOPT_OUTFILE	4
+
+/*
+ * Like "smbreq" but just dump the command/reply messages.
+ * With the output file option, append to a pcap file.
+ */
+static int
+smbreq_dump_dcmd(uintptr_t rqaddr, uint_t flags, int argc,
+    const mdb_arg_t *argv)
+{
+	smb_session_t	*ssn;
+	smb_request_t	*sr;
+	char		*outfile = NULL;
+	dump_func_t	dump_func;
+	uint64_t	msgid;
+	uintptr_t	ssnaddr;
+	uint_t		opts = 0;
+	int		rc = DCMD_OK;
+
+	if (!(flags & DCMD_ADDRSPEC))
+		return (DCMD_USAGE);
+
+	if (mdb_getopts(argc, argv,
+	    'c', MDB_OPT_SETBITS, SMB_RDOPT_COMMAND, &opts,
+	    'r', MDB_OPT_SETBITS, SMB_RDOPT_REPLY, &opts,
+	    'o', MDB_OPT_STR, &outfile,
+	    NULL) != argc)
+		return (DCMD_USAGE);
+#ifdef	_KMDB
+	if (outfile != NULL) {
+		mdb_warn("smbreq_dump -o option not supported in kmdb\n");
+		return (DCMD_ERR);
+	}
+#endif	/* _KMDB */
+
+	/*
+	 * Default without -c or -r is to dump both.
+	 */
+	if ((opts & (SMB_RDOPT_COMMAND | SMB_RDOPT_REPLY)) == 0)
+		opts |= SMB_RDOPT_COMMAND | SMB_RDOPT_REPLY;
+
+	/*
+	 * Get the smb_request_t, for the cmd/reply messages.
+	 */
+	sr = mdb_alloc(sizeof (*sr), UM_SLEEP | UM_GC);
+	if (mdb_vread(sr, sizeof (*sr), rqaddr) == -1) {
+		mdb_warn("failed to read smb_request at %p", rqaddr);
+		return (DCMD_ERR);
+	}
+	if (sr->sr_magic != SMB_REQ_MAGIC) {
+		mdb_warn("not an smb_request_t (%p)>", rqaddr);
+		return (DCMD_ERR);
+	}
+
+	/*
+	 * Get the session too, for the IP addresses & ports.
+	 */
+	ssnaddr = (uintptr_t)sr->session;
+	ssn = mdb_alloc(sizeof (*ssn), UM_SLEEP | UM_GC);
+	if (mdb_vread(ssn, sizeof (*ssn), ssnaddr) == -1) {
+		mdb_warn("failed to read smb_request at %p", ssnaddr);
+		return (DCMD_ERR);
+	}
+	if (ssn->s_magic != SMB_SESSION_MAGIC) {
+		mdb_warn("not an smb_session_t (%p)>", ssnaddr);
+		return (DCMD_ERR);
+	}
+
+#ifndef	_KMDB
+	if (outfile != NULL) {
+		rc = smbsrv_pcap_open(outfile);
+		if (rc != DCMD_OK)
+			return (rc);
+		dump_func = smbsrv_pcap_dump;
+	} else
+#endif	/* _KMDB */
+	{
+		dump_func = smb_req_dump;
+	}
+
+	if (sr->smb2_messageid != 0)
+		msgid = sr->smb2_messageid;
+	else
+		msgid = sr->smb_mid;
+	mdb_printf("Dumping request %-?p, Msg_ID 0x%llx\n",
+	    rqaddr, msgid);
+
+	if (opts & SMB_RDOPT_COMMAND) {
+		/*
+		 * Dump the command, length=max_bytes
+		 * src=remote, dst=local
+		 */
+		rc = dump_func(&sr->command, sr->command.max_bytes,
+		    &ssn->ipaddr, ssn->s_remote_port,
+		    &ssn->local_ipaddr, ssn->s_local_port,
+		    sr->sr_time_submitted, B_FALSE);
+	}
+
+	if ((opts & SMB_RDOPT_REPLY) != 0 &&
+	    rc == DCMD_OK) {
+		/*
+		 * Dump the reply, length=chain_offset
+		 * src=local, dst=remote
+		 */
+		rc = dump_func(&sr->reply, sr->reply.chain_offset,
+		    &ssn->local_ipaddr, ssn->s_local_port,
+		    &ssn->ipaddr, ssn->s_remote_port,
+		    sr->sr_time_start, B_TRUE);
+	}
+
+#ifndef	_KMDB
+	if (outfile != NULL) {
+		smbsrv_pcap_close();
+	}
+#endif
+
+	return (DCMD_OK);
+}
+
+struct req_dump_state {
+	int32_t rem_len;
+};
+
+static int
+smb_req_dump(struct mbuf_chain *mbc, int32_t smb_len,
+    smb_inaddr_t *src_ip, uint16_t src_port,
+    smb_inaddr_t *dst_ip, uint16_t dst_port,
+    hrtime_t rqtime, boolean_t is_reply)
+{
+	char	src_buf[INET6_ADDRSTRLEN];
+	char	dst_buf[INET6_ADDRSTRLEN];
+	struct req_dump_state dump_state;
+	_NOTE(ARGUNUSED(rqtime));
+
+	if (smb_len < 4)
+		return (DCMD_OK);
+	if (mbc->chain == NULL)
+		return (DCMD_ERR);
+
+	smb_inaddr_ntop(src_ip, src_buf, sizeof (src_buf));
+	smb_inaddr_ntop(dst_ip, dst_buf, sizeof (dst_buf));
+
+	mdb_printf("%-8s SRC: %s/%u  DST: %s/%u  LEN: %u\n",
+	    (is_reply) ? "Reply:" : "Call:",
+	    src_buf, src_port, dst_buf, dst_port, smb_len);
+
+	/*
+	 * Calling "smb_mbuf_dump" with a wrapper function
+	 * so we can set its length arg, and decrement
+	 * req_dump_state.rem_len as it goes.
+	 */
+	dump_state.rem_len = smb_len;
+	if (mdb_pwalk("smb_mbuf_walker", smb_req_dump_m,
+	    &dump_state, (uintptr_t)mbc->chain) == -1) {
+		mdb_warn("cannot walk smb_req mbuf_chain");
+		return (DCMD_ERR);
+	}
+	return (DCMD_OK);
+}
+
+static int
+smb_req_dump_m(uintptr_t m_addr, const void *data, void *arg)
+{
+	struct req_dump_state *st = arg;
+	const struct mbuf *m = data;
+	mdb_arg_t	argv;
+	int cnt;
+
+	cnt = st->rem_len;
+	if (cnt > m->m_len)
+		cnt = m->m_len;
+	if (cnt <= 0)
+		return (WALK_DONE);
+
+	argv.a_type = MDB_TYPE_IMMEDIATE;
+	argv.a_un.a_val = cnt;
+	if (mdb_call_dcmd("smb_mbuf_dump", m_addr, 0, 1, &argv) < 0) {
+		mdb_warn("%p::smb_mbuf_dump failed\n", m_addr);
+		return (WALK_ERR);
+	}
+
+	st->rem_len -= cnt;
+	return (WALK_NEXT);
+}
+
 /*
  * *****************************************************************************
  * ****************************** smb_user_t ***********************************
@@ -1922,6 +2120,92 @@
 }
 
 /*
+ * *******************************************************************
+ * (smb) mbuf_t
+ *
+ * ::smb_mbuf_dump [max_len]
+ * dcmd to dump the data portion of an mbuf_t
+ * stop at max_len
+ */
+static int
+smb_mbuf_dump_dcmd(uintptr_t addr, uint_t flags, int argc,
+    const mdb_arg_t *argv)
+{
+	struct m_hdr mh;
+	uintptr_t mdata;
+	int len, max_len;
+	int dumpptr_flags;
+
+	if (mdb_vread(&mh, sizeof (mh), addr) < 0) {
+		mdb_warn("failed to read mbuf at %p", addr);
+		return (DCMD_ERR);
+	}
+	len = mh.mh_len;
+	mdata = (uintptr_t)mh.mh_data;
+
+	if (argc > 0) {
+		if (argv[0].a_type == MDB_TYPE_IMMEDIATE)
+			max_len = argv[0].a_un.a_val;
+		else
+			max_len = mdb_strtoull(argv[0].a_un.a_str);
+		if (len > max_len)
+			len = max_len;
+	}
+	if (len <= 0)
+		return (DCMD_OK);
+
+	if (DCMD_HDRSPEC(flags)) {
+		mdb_printf("%<u>%-16s %-16s %-12s%</u>\n",
+		    "mbuf_t", "m_data", "m_len");
+	}
+	mdb_printf("%-16p %-16p %-12u\n",
+	    addr, mdata, mh.mh_len);
+
+	dumpptr_flags = MDB_DUMP_RELATIVE | MDB_DUMP_ASCII | MDB_DUMP_HEADER;
+	if (mdb_dumpptr(mdata, len, dumpptr_flags,
+	    (mdb_dumpptr_cb_t)mdb_vread, NULL) < 0)
+		return (DCMD_ERR);
+
+	return (DCMD_OK);
+}
+
+static int
+smb_mbuf_walk_init(mdb_walk_state_t *wsp)
+{
+	mbuf_t *m;
+
+	if (wsp->walk_addr == 0) {
+		mdb_printf("require address of an mbuf_t\n");
+		return (WALK_ERR);
+	}
+	m = mdb_alloc(sizeof (*m), UM_SLEEP | UM_GC);
+	wsp->walk_data = m;
+	return (WALK_NEXT);
+}
+
+static int
+smb_mbuf_walk_step(mdb_walk_state_t *wsp)
+{
+	uintptr_t addr = wsp->walk_addr;
+	mbuf_t *m = wsp->walk_data;
+	int rc;
+
+	if (wsp->walk_addr == 0)
+		return (WALK_DONE);
+
+	if (mdb_vread(m, sizeof (*m), addr) == -1) {
+		mdb_warn("failed to read mbuf_t at %p", addr);
+		return (WALK_ERR);
+	}
+
+	rc = wsp->walk_callback(addr, m, wsp->walk_cbdata);
+	wsp->walk_addr = (uintptr_t)m->m_next;
+
+	return (rc);
+}
+
+/*
+ * *******************************************************************
  * ::smbstat
  *
  * Prints SMB requests statistics.
@@ -2466,6 +2750,23 @@
 	return (DCMD_OK);
 }
 
+static void
+smb_inaddr_ntop(smb_inaddr_t *ina, char *buf, size_t sz)
+{
+
+	switch (ina->a_family) {
+	case AF_INET:
+		(void) mdb_snprintf(buf, sz, "%I", ina->a_ipv4);
+		break;
+	case AF_INET6:
+		(void) mdb_snprintf(buf, sz, "%N", ina->a_ipv6);
+		break;
+	default:
+		(void) mdb_snprintf(buf, sz, "(?)");
+		break;
+	}
+}
+
 /*
  * MDB module linkage information:
  *
@@ -2505,6 +2806,12 @@
 	    ":[-v]",
 	    "print smb_request_t information",
 	    smbreq_dcmd },
+	{   "smbreq_dump",
+	    ":[-cr] [-o outfile]",
+	    "dump smb_request_t packets (cmd/reply)",
+	    smbreq_dump_dcmd,
+	    smbreq_dump_help,
+	},
 	{   "smblock", ":[-v]",
 	    "print smb_lock_t information",
 	    smblock_dcmd },
@@ -2550,6 +2857,9 @@
 	{   "smbfssd", "[-v]",
 	    "print smb_fssd_t information",
 	    smbfssd_dcmd },
+	{   "smb_mbuf_dump", ":[max_len]",
+	    "print mbuf_t data",
+	    smb_mbuf_dump_dcmd },
 	{ NULL }
 };
 
@@ -2578,6 +2888,12 @@
 	    smb_ace_walk_step,
 	    NULL,
 	    NULL },
+	{   "smb_mbuf_walker",
+	    "walk list of mbuf_t structures",
+	    smb_mbuf_walk_init,
+	    smb_mbuf_walk_step,
+	    NULL,
+	    NULL },
 	{ NULL }
 };
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/mdb/common/modules/smbsrv/smbsrv_pcap.c	Wed May 27 17:25:56 2015 -0400
@@ -0,0 +1,439 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
+ */
+
+/*
+ * Support functions for dumping SMB request and response data from a
+ * crash dump as a pcap file.  This allows using tools like wireshark
+ * to examine the request we were working on when we crashed.
+ *
+ * This feature is only available in mdb (not in kmdb).
+ */
+
+#ifdef _KMDB
+#error "Makefile should have excluded this file."
+#endif
+
+#include <mdb/mdb_modapi.h>
+#include <mdb/mdb_ks.h>
+#include <sys/thread.h>
+#include <sys/taskq.h>
+#include <smbsrv/smb_vops.h>
+#include <smbsrv/smb.h>
+#include <smbsrv/smb_ktypes.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <inet/tcp.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "smbsrv_pcap.h"
+
+/* Not sure why this isn't declared... */
+extern int fstat(int, struct stat *);
+
+/*
+ * In the capture file, packets are truncated at 64k.
+ * The SMB len is shorter so that after we add the
+ * (faked up) headers we're still below PCAP_SNAPLEN.
+ */
+#define	PCAP_SNAPLEN	(1<<16)
+#define	MAX_SMB_LEN	(PCAP_SNAPLEN - 0x40)
+
+/*
+ * pcap file format stuff, mostly from:
+ * wiki.wireshark.org/Development/LibpcapFileFormat
+ */
+
+#define	PCAP_MAGIC	0xa1b2c3d4
+#define	PCAP_VMAJOR	2
+#define	PCAP_VMINOR	4
+#define	PCAP_DLT_RAW	0xc
+
+struct pcap_file_hdr {
+	uint32_t magic_number;
+	uint16_t version_major;
+	uint16_t version_minor;
+	uint32_t thiszone;	/* TZ correction */
+	uint32_t sigflags;	/* accuracy of timestamps */
+	uint32_t snaplen;	/* max legnth of captured packets */
+	uint32_t network;	/* data link type */
+};
+
+struct pcap_frame_hdr {
+	uint32_t ts_sec;	/* timestamp seconds */
+	uint32_t ts_usec;	/* timestamp microseconds */
+	uint32_t incl_len;	/* number of octets of packet saved in file */
+	uint32_t orig_len;	/* actual length of packet */
+};
+
+struct my_ip6_hdr {
+	uint8_t  ip6_vers;	/* 6 */
+	uint8_t  ip6_class;
+	uint16_t ip6_xflow;
+	uint16_t ip6_paylen;
+	uint8_t  ip6_nexthdr;
+	uint8_t  ip6_hoplim;
+	in6_addr_t ip6_src;
+	in6_addr_t ip6_dst;
+};
+
+static int pcap_fd = -1;
+
+/* For faking TCP sequence numbers. */
+static uint32_t call_seqno;
+static uint32_t reply_seqno;
+
+static int pcap_file_header(char *, int);
+static int smb_req_pcap_m(uintptr_t, const void *, void *);
+
+void
+smbsrv_pcap_close(void)
+{
+	if (pcap_fd != -1) {
+		close(pcap_fd);
+		pcap_fd = -1;
+	}
+}
+
+int
+smbsrv_pcap_open(char *outfile)
+{
+	int fd;
+
+	fd = open(outfile, O_RDWR | O_CREAT | O_NOFOLLOW, 0644);
+	if (fd < 0) {
+		mdb_warn("Can't open pcap output file: %s\n", outfile);
+		return (DCMD_ERR);
+	}
+	if (pcap_file_header(outfile, fd) < 0) {
+		close(fd);
+		return (DCMD_ERR);
+	}
+	pcap_fd = fd;
+	call_seqno = 1;
+	reply_seqno = 1;
+
+	return (DCMD_OK);
+}
+
+/*
+ * Check or create a pcap file header
+ */
+static int
+pcap_file_header(char *outfile, int fd)
+{
+	struct stat st;
+	struct pcap_file_hdr hdr;
+	int n;
+
+	if (fstat(fd, &st) < 0) {
+		mdb_warn("Can't stat pcap output file: %s\n", outfile);
+		return (-1);
+	}
+	if (st.st_size < sizeof (hdr))
+		goto create;
+
+	n = read(fd, &hdr, sizeof (hdr));
+	if (n != sizeof (hdr))
+		goto create;
+
+	/*
+	 * This only supports appending to files we created,
+	 * so the file headers should all be native endian
+	 * and have the values we write when creating.
+	 */
+	if (hdr.magic_number != PCAP_MAGIC ||
+	    hdr.version_major != PCAP_VMAJOR ||
+	    hdr.version_minor != PCAP_VMINOR ||
+	    hdr.snaplen != PCAP_SNAPLEN ||
+	    hdr.network != PCAP_DLT_RAW) {
+		mdb_warn("Existing file not pcap: %s\n", outfile);
+		return (-1);
+	}
+
+	/* We will append to this file. */
+	(void) lseek(fd, st.st_size, SEEK_SET);
+	return (0);
+
+create:
+	hdr.magic_number = PCAP_MAGIC;
+	hdr.version_major = PCAP_VMAJOR;
+	hdr.version_minor = PCAP_VMINOR;
+	hdr.thiszone = 0;
+	hdr.sigflags = 0;
+	hdr.snaplen = PCAP_SNAPLEN;
+	hdr.network = PCAP_DLT_RAW;
+
+	(void) lseek(fd, (off_t)0, SEEK_SET);
+	n = write(fd, &hdr, sizeof (hdr));
+	if (n != sizeof (hdr)) {
+		mdb_warn("Can't write output file: %s\n", outfile);
+		return (-1);
+	}
+	(void) ftruncate(fd, (off_t)sizeof (hdr));
+	return (0);
+}
+
+struct req_dump_state {
+	int32_t rem_len;
+	int tbuf_size;
+	char *tbuf;
+};
+
+/*
+ * Simlar to smb_req_dump, but write a pcap frame.
+ * The headers are faked up, intended only to be
+ * good enough so wireshark will display this.
+ * These NEVER go over any network.
+ */
+int
+smbsrv_pcap_dump(struct mbuf_chain *mbc, int32_t smb_len,
+    smb_inaddr_t *src_ip, uint16_t src_port,
+    smb_inaddr_t *dst_ip, uint16_t dst_port,
+    hrtime_t rqtime, boolean_t is_reply)
+{
+	struct req_dump_state dump_state;
+	struct pcap_frame_hdr phdr;
+	struct my_ip6_hdr ip6_hdr;
+	struct ipha_s ip_hdr;
+	tcpha_t tcp_hdr;
+	uint32_t nb_hdr;
+	uint32_t *seqno;
+	uint32_t *ackno;
+	void *ip_hdr_p;
+	int ip_hdr_len;
+	int len_w_hdrs;
+	int truncated;
+	int n, rc;
+	off_t pkt_off;
+
+	if (smb_len < sizeof (nb_hdr))
+		return (DCMD_OK);
+	if (mbc->chain == NULL)
+		return (DCMD_ERR);
+
+	/*
+	 * This code is not making fragments (for now), so just
+	 * limit SMB frames to 64k - header(s) size.
+	 */
+	if (smb_len > MAX_SMB_LEN) {
+		truncated = smb_len - MAX_SMB_LEN;
+		smb_len = MAX_SMB_LEN;
+	} else {
+		truncated = 0;
+	}
+
+	switch (src_ip->a_family) {
+	case AF_INET:
+		ip_hdr_len = sizeof (ip_hdr);
+		break;
+	case AF_INET6:
+		ip_hdr_len = sizeof (ip6_hdr);
+		break;
+	default:
+		mdb_warn("unknown network addr family\n");
+		return (DCMD_ERR);
+	}
+
+	/* Which is seq/ack? */
+	if (is_reply) {
+		/* it's a reply */
+		seqno = &reply_seqno;
+		ackno = &call_seqno;
+	} else {
+		/* it's a call */
+		seqno = &call_seqno;
+		ackno = &reply_seqno;
+	}
+
+	/*
+	 * Build & dump the (faked up) frame headers:
+	 *	pcap packet header
+	 *	IP header (v4 or v6)
+	 *	TCP header
+	 *	NetBIOS header
+	 *
+	 * Build back to front, computing lengths,
+	 * then write them all out.
+	 */
+
+	/* NetBIOS (just a 32-bit payload len) */
+	nb_hdr = htonl(smb_len);
+	len_w_hdrs = smb_len + sizeof (nb_hdr);
+
+	/* TCP (w/ faked seq. numbers) */
+	tcp_hdr.tha_lport = htons(src_port);
+	tcp_hdr.tha_fport = htons(dst_port);
+	tcp_hdr.tha_seq = htonl(*seqno);
+	tcp_hdr.tha_ack = htonl(*ackno);
+	tcp_hdr.tha_offset_and_reserved = 0x50;
+	tcp_hdr.tha_flags = 0x10; /* ACK */
+	tcp_hdr.tha_win = htons(0xFF00);
+	tcp_hdr.tha_sum = 0;
+	tcp_hdr.tha_urp = 0;
+	(*seqno) += len_w_hdrs;
+	len_w_hdrs += sizeof (tcp_hdr);
+
+	/* IP header */
+	switch (src_ip->a_family) {
+	case AF_INET:
+		ip_hdr_p = &ip_hdr;
+		ip_hdr_len = sizeof (ip_hdr);
+		/* IPv4 len includes the IP4 header */
+		len_w_hdrs += ip_hdr_len;
+		ip_hdr.ipha_version_and_hdr_length = 0x45;
+		ip_hdr.ipha_type_of_service = 0;
+		if (len_w_hdrs > 0xFFFF)
+			ip_hdr.ipha_length = 0xFFFF;
+		else
+			ip_hdr.ipha_length = htons(len_w_hdrs);
+		ip_hdr.ipha_ident = 0;
+		ip_hdr.ipha_fragment_offset_and_flags = 0;
+		ip_hdr.ipha_ttl = 60;
+		ip_hdr.ipha_protocol = 6; /* TCP */
+		ip_hdr.ipha_hdr_checksum = 0;
+		ip_hdr.ipha_src = src_ip->a_ipv4;
+		ip_hdr.ipha_dst = dst_ip->a_ipv4;
+		break;
+
+	case AF_INET6:
+		ip_hdr_p = &ip_hdr;
+		ip_hdr_len = sizeof (ip6_hdr);
+		ip6_hdr.ip6_vers = 6;
+		ip6_hdr.ip6_class = 0;
+		ip6_hdr.ip6_xflow = 0;
+		if (len_w_hdrs > 0xFFFF)
+			ip6_hdr.ip6_paylen = 0xFFFF;
+		else
+			ip6_hdr.ip6_paylen = htons(len_w_hdrs);
+		ip6_hdr.ip6_nexthdr = 6; /* TCP */
+		ip6_hdr.ip6_hoplim = 64;
+		bcopy(&src_ip->a_ipv6, &ip6_hdr.ip6_src,
+		    sizeof (ip6_hdr.ip6_src));
+		bcopy(&dst_ip->a_ipv6, &ip6_hdr.ip6_dst,
+		    sizeof (ip6_hdr.ip6_dst));
+		len_w_hdrs += ip_hdr_len;
+		break;
+	default:
+		ip_hdr_p = NULL;
+		ip_hdr_len = 0;
+		break;
+	}
+
+	/* pcap header */
+	phdr.ts_sec = rqtime / NANOSEC;
+	phdr.ts_usec = (rqtime / 1000) % MICROSEC;
+	phdr.incl_len = len_w_hdrs; /* not incl. pcap header */
+	phdr.orig_len = len_w_hdrs + truncated;
+	len_w_hdrs += sizeof (phdr);
+
+	/*
+	 * Write out all the headers:
+	 * pcap, IP, TCP, NetBIOS
+	 *
+	 * To avoid any possibility of scrambling the
+	 * pcap file, save the offset here and seek to
+	 * where we should be when done writing.
+	 */
+	pkt_off = lseek(pcap_fd, (off_t)0, SEEK_CUR);
+	n = write(pcap_fd, &phdr, sizeof (phdr));
+	if (n != sizeof (phdr)) {
+		mdb_warn("failed to write pcap hdr\n");
+		goto errout;
+	}
+	n = write(pcap_fd, ip_hdr_p, ip_hdr_len);
+	if (n != ip_hdr_len) {
+		mdb_warn("failed to write IP hdr\n");
+		goto errout;
+	}
+	n = write(pcap_fd, &tcp_hdr, sizeof (tcp_hdr));
+	if (n != sizeof (tcp_hdr)) {
+		mdb_warn("failed to write TCP hdr\n");
+		goto errout;
+	}
+	n = write(pcap_fd, &nb_hdr, sizeof (nb_hdr));
+	if (n != sizeof (nb_hdr)) {
+		mdb_warn("failed to write NBT hdr\n");
+		goto errout;
+	}
+
+	/*
+	 * Finally, walk the mbuf chain writing SMB data
+	 * to the pcap file, for exactly smb_len bytes.
+	 */
+	dump_state.rem_len = smb_len;
+	dump_state.tbuf_size = MCLBYTES;
+	dump_state.tbuf = mdb_alloc(dump_state.tbuf_size, UM_SLEEP);
+	rc = mdb_pwalk("smb_mbuf_walker", smb_req_pcap_m,
+	    &dump_state, (uintptr_t)mbc->chain);
+	mdb_free(dump_state.tbuf, dump_state.tbuf_size);
+	if (rc < 0) {
+		mdb_warn("cannot walk smb_req mbuf_chain");
+		goto errout;
+	}
+
+	pkt_off += len_w_hdrs;
+	(void) lseek(pcap_fd, pkt_off, SEEK_SET);
+	return (DCMD_OK);
+
+errout:
+	(void) lseek(pcap_fd, pkt_off, SEEK_SET);
+	(void) ftruncate(pcap_fd, pkt_off);
+	return (DCMD_ERR);
+}
+
+/*
+ * Call-back function, called for each mbuf_t in a chain.
+ * Copy data from this mbuf to the pcap file.
+ */
+static int
+smb_req_pcap_m(uintptr_t mbuf_addr, const void *data, void *arg)
+{
+	struct req_dump_state *st = arg;
+	const struct mbuf *m = data;
+	uintptr_t addr;
+	int cnt, mlen, n, x;
+
+	addr = (uintptr_t)m->m_data;
+	mlen = m->m_len;
+	if (mlen > st->rem_len)
+		mlen = st->rem_len;
+	if (mlen <= 0)
+		return (WALK_DONE);
+
+	cnt = mlen;
+	while (cnt > 0) {
+		x = MIN(cnt, st->tbuf_size);
+		n = mdb_vread(st->tbuf, x, addr);
+		if (n != x) {
+			mdb_warn("failed copying mbuf %p\n", mbuf_addr);
+			return (WALK_ERR);
+		}
+		n = write(pcap_fd, st->tbuf, x);
+		if (n != x) {
+			mdb_warn("failed writing pcap data\n");
+			return (WALK_ERR);
+		}
+		addr += x;
+		cnt -= x;
+	}
+
+	st->rem_len -= mlen;
+	return (WALK_NEXT);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/mdb/common/modules/smbsrv/smbsrv_pcap.h	Wed May 27 17:25:56 2015 -0400
@@ -0,0 +1,26 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
+ */
+
+#ifndef _SMBSRV_PCAP_H
+#define	_SMBSRV_PCAP_H
+
+extern void smbsrv_pcap_close();
+extern int smbsrv_pcap_open(char *);
+
+extern int smbsrv_pcap_dump(struct mbuf_chain *, int32_t,
+    smb_inaddr_t *, uint16_t, smb_inaddr_t *, uint16_t,
+    hrtime_t, boolean_t);
+
+#endif /* _SMBSRV_PCAP_H */
--- a/usr/src/cmd/mdb/intel/amd64/libfksmbsrv/Makefile	Mon Apr 20 23:02:00 2015 -0400
+++ b/usr/src/cmd/mdb/intel/amd64/libfksmbsrv/Makefile	Wed May 27 17:25:56 2015 -0400
@@ -23,13 +23,13 @@
 # Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 #
-# Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
+# Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
 #
 
 MODULE = libfksmbsrv.so
 MDBTGT = proc
 
-MODSRCS = smbsrv.c list.c
+MODSRCS = smbsrv.c smbsrv_pcap.c list.c
 
 include ../../../../Makefile.cmd
 include ../../../../Makefile.cmd.64
--- a/usr/src/cmd/mdb/intel/amd64/smbsrv/Makefile	Mon Apr 20 23:02:00 2015 -0400
+++ b/usr/src/cmd/mdb/intel/amd64/smbsrv/Makefile	Wed May 27 17:25:56 2015 -0400
@@ -22,13 +22,19 @@
 # Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 #
-# Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
+# Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
 #
 
 MODULE = smbsrv.so
 MDBTGT = kvm
 
-MODSRCS = smbsrv.c
+#
+# This signals that $(KMODSRCS) != $(MODSRCS).  Dump to a pcap file
+# is not available in kmdb, so don't bother compiling that code.
+KMOD_SOURCES_DIFFERENT=$(POUND_SIGN)
+
+KMODSRCS = smbsrv.c
+MODSRCS = smbsrv.c smbsrv_pcap.c
 
 include ../../../../Makefile.cmd
 include ../../../../Makefile.cmd.64
--- a/usr/src/cmd/mdb/intel/ia32/libfksmbsrv/Makefile	Mon Apr 20 23:02:00 2015 -0400
+++ b/usr/src/cmd/mdb/intel/ia32/libfksmbsrv/Makefile	Wed May 27 17:25:56 2015 -0400
@@ -23,13 +23,13 @@
 # Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 #
-# Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
+# Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
 #
 
 MODULE = libfksmbsrv.so
 MDBTGT = proc
 
-MODSRCS = smbsrv.c list.c
+MODSRCS = smbsrv.c smbsrv_pcap.c list.c
 
 include ../../../../Makefile.cmd
 include ../../Makefile.ia32
--- a/usr/src/cmd/mdb/sparc/v9/smbsrv/Makefile	Mon Apr 20 23:02:00 2015 -0400
+++ b/usr/src/cmd/mdb/sparc/v9/smbsrv/Makefile	Wed May 27 17:25:56 2015 -0400
@@ -22,13 +22,19 @@
 # Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 #
-# Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
+# Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
 #
 
 MODULE = smbsrv.so
 MDBTGT = kvm
 
-MODSRCS = smbsrv.c
+#
+# This signals that $(KMODSRCS) != $(MODSRCS).  Dump to a pcap file
+# is not available in kmdb, so don't bother compiling that code.
+KMOD_SOURCES_DIFFERENT=$(POUND_SIGN)
+
+KMODSRCS = smbsrv.c
+MODSRCS = smbsrv.c smbsrv_pcap.c
 
 include ../../../../Makefile.cmd
 include ../../../../Makefile.cmd.64