xref: /illumos-gate/usr/src/cmd/mdb/common/modules/smbsrv/smbsrv_pcap.c (revision 9b9d39d2a32ff806d2431dbcc50968ef1e6d46b2)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
14  */
15 
16 /*
17  * Support functions for dumping SMB request and response data from a
18  * crash dump as a pcap file.  This allows using tools like wireshark
19  * to examine the request we were working on when we crashed.
20  *
21  * This feature is only available in mdb (not in kmdb).
22  */
23 
24 #ifdef _KMDB
25 #error "Makefile should have excluded this file."
26 #endif
27 
28 #include <mdb/mdb_modapi.h>
29 #include <mdb/mdb_ks.h>
30 #include <sys/thread.h>
31 #include <sys/taskq.h>
32 #include <smbsrv/smb_vops.h>
33 #include <smbsrv/smb.h>
34 #include <smbsrv/smb_ktypes.h>
35 
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <netinet/in.h>
39 #include <netinet/ip.h>
40 #include <inet/tcp.h>
41 
42 #include <fcntl.h>
43 #include <unistd.h>
44 
45 #include "smbsrv_pcap.h"
46 
47 /* Not sure why this isn't declared... */
48 extern int fstat(int, struct stat *);
49 
50 /*
51  * In the capture file, packets are truncated at 64k.
52  * The SMB len is shorter so that after we add the
53  * (faked up) headers we're still below PCAP_SNAPLEN.
54  */
55 #define	PCAP_SNAPLEN	(1<<16)
56 #define	MAX_SMB_LEN	(PCAP_SNAPLEN - 0x40)
57 
58 /*
59  * pcap file format stuff, mostly from:
60  * wiki.wireshark.org/Development/LibpcapFileFormat
61  */
62 
63 #define	PCAP_MAGIC	0xa1b2c3d4
64 #define	PCAP_VMAJOR	2
65 #define	PCAP_VMINOR	4
66 #define	PCAP_DLT_RAW	0xc
67 
68 struct pcap_file_hdr {
69 	uint32_t magic_number;
70 	uint16_t version_major;
71 	uint16_t version_minor;
72 	uint32_t thiszone;	/* TZ correction */
73 	uint32_t sigflags;	/* accuracy of timestamps */
74 	uint32_t snaplen;	/* max legnth of captured packets */
75 	uint32_t network;	/* data link type */
76 };
77 
78 struct pcap_frame_hdr {
79 	uint32_t ts_sec;	/* timestamp seconds */
80 	uint32_t ts_usec;	/* timestamp microseconds */
81 	uint32_t incl_len;	/* number of octets of packet saved in file */
82 	uint32_t orig_len;	/* actual length of packet */
83 };
84 
85 struct my_ip6_hdr {
86 	uint8_t  ip6_vers;	/* 6 */
87 	uint8_t  ip6_class;
88 	uint16_t ip6_xflow;
89 	uint16_t ip6_paylen;
90 	uint8_t  ip6_nexthdr;
91 	uint8_t  ip6_hoplim;
92 	in6_addr_t ip6_src;
93 	in6_addr_t ip6_dst;
94 };
95 
96 static int pcap_fd = -1;
97 
98 /* For faking TCP sequence numbers. */
99 static uint32_t call_seqno;
100 static uint32_t reply_seqno;
101 
102 static int pcap_file_header(char *, int);
103 static int smb_req_pcap_m(uintptr_t, const void *, void *);
104 
105 void
106 smbsrv_pcap_close(void)
107 {
108 	if (pcap_fd != -1) {
109 		close(pcap_fd);
110 		pcap_fd = -1;
111 	}
112 }
113 
114 int
115 smbsrv_pcap_open(char *outfile)
116 {
117 	int fd;
118 
119 	fd = open(outfile, O_RDWR | O_CREAT | O_NOFOLLOW, 0644);
120 	if (fd < 0) {
121 		mdb_warn("Can't open pcap output file: %s\n", outfile);
122 		return (DCMD_ERR);
123 	}
124 	if (pcap_file_header(outfile, fd) < 0) {
125 		close(fd);
126 		return (DCMD_ERR);
127 	}
128 	pcap_fd = fd;
129 	call_seqno = 1;
130 	reply_seqno = 1;
131 
132 	return (DCMD_OK);
133 }
134 
135 /*
136  * Check or create a pcap file header
137  */
138 static int
139 pcap_file_header(char *outfile, int fd)
140 {
141 	struct stat st;
142 	struct pcap_file_hdr hdr;
143 	int n;
144 
145 	if (fstat(fd, &st) < 0) {
146 		mdb_warn("Can't stat pcap output file: %s\n", outfile);
147 		return (-1);
148 	}
149 	if (st.st_size < sizeof (hdr))
150 		goto create;
151 
152 	n = read(fd, &hdr, sizeof (hdr));
153 	if (n != sizeof (hdr))
154 		goto create;
155 
156 	/*
157 	 * This only supports appending to files we created,
158 	 * so the file headers should all be native endian
159 	 * and have the values we write when creating.
160 	 */
161 	if (hdr.magic_number != PCAP_MAGIC ||
162 	    hdr.version_major != PCAP_VMAJOR ||
163 	    hdr.version_minor != PCAP_VMINOR ||
164 	    hdr.snaplen != PCAP_SNAPLEN ||
165 	    hdr.network != PCAP_DLT_RAW) {
166 		mdb_warn("Existing file not pcap: %s\n", outfile);
167 		return (-1);
168 	}
169 
170 	/* We will append to this file. */
171 	(void) lseek(fd, st.st_size, SEEK_SET);
172 	return (0);
173 
174 create:
175 	hdr.magic_number = PCAP_MAGIC;
176 	hdr.version_major = PCAP_VMAJOR;
177 	hdr.version_minor = PCAP_VMINOR;
178 	hdr.thiszone = 0;
179 	hdr.sigflags = 0;
180 	hdr.snaplen = PCAP_SNAPLEN;
181 	hdr.network = PCAP_DLT_RAW;
182 
183 	(void) lseek(fd, (off_t)0, SEEK_SET);
184 	n = write(fd, &hdr, sizeof (hdr));
185 	if (n != sizeof (hdr)) {
186 		mdb_warn("Can't write output file: %s\n", outfile);
187 		return (-1);
188 	}
189 	(void) ftruncate(fd, (off_t)sizeof (hdr));
190 	return (0);
191 }
192 
193 struct req_dump_state {
194 	int32_t rem_len;
195 	int tbuf_size;
196 	char *tbuf;
197 };
198 
199 /*
200  * Simlar to smb_req_dump, but write a pcap frame.
201  * The headers are faked up, intended only to be
202  * good enough so wireshark will display this.
203  * These NEVER go over any network.
204  */
205 int
206 smbsrv_pcap_dump(struct mbuf_chain *mbc, int32_t smb_len,
207     smb_inaddr_t *src_ip, uint16_t src_port,
208     smb_inaddr_t *dst_ip, uint16_t dst_port,
209     hrtime_t rqtime, boolean_t is_reply)
210 {
211 	struct req_dump_state dump_state;
212 	struct pcap_frame_hdr phdr;
213 	struct my_ip6_hdr ip6_hdr;
214 	struct ipha_s ip_hdr;
215 	tcpha_t tcp_hdr;
216 	uint32_t nb_hdr;
217 	uint32_t *seqno;
218 	uint32_t *ackno;
219 	void *ip_hdr_p;
220 	int ip_hdr_len;
221 	int len_w_hdrs;
222 	int truncated;
223 	int n, rc;
224 	off_t pkt_off;
225 
226 	if (smb_len < sizeof (nb_hdr))
227 		return (DCMD_OK);
228 	if (mbc->chain == NULL)
229 		return (DCMD_ERR);
230 
231 	/*
232 	 * This code is not making fragments (for now), so just
233 	 * limit SMB frames to 64k - header(s) size.
234 	 */
235 	if (smb_len > MAX_SMB_LEN) {
236 		truncated = smb_len - MAX_SMB_LEN;
237 		smb_len = MAX_SMB_LEN;
238 	} else {
239 		truncated = 0;
240 	}
241 
242 	switch (src_ip->a_family) {
243 	case AF_INET:
244 		ip_hdr_len = sizeof (ip_hdr);
245 		break;
246 	case AF_INET6:
247 		ip_hdr_len = sizeof (ip6_hdr);
248 		break;
249 	default:
250 		mdb_warn("unknown network addr family\n");
251 		return (DCMD_ERR);
252 	}
253 
254 	/* Which is seq/ack? */
255 	if (is_reply) {
256 		/* it's a reply */
257 		seqno = &reply_seqno;
258 		ackno = &call_seqno;
259 	} else {
260 		/* it's a call */
261 		seqno = &call_seqno;
262 		ackno = &reply_seqno;
263 	}
264 
265 	/*
266 	 * Build & dump the (faked up) frame headers:
267 	 *	pcap packet header
268 	 *	IP header (v4 or v6)
269 	 *	TCP header
270 	 *	NetBIOS header
271 	 *
272 	 * Build back to front, computing lengths,
273 	 * then write them all out.
274 	 */
275 
276 	/* NetBIOS (just a 32-bit payload len) */
277 	nb_hdr = htonl(smb_len);
278 	len_w_hdrs = smb_len + sizeof (nb_hdr);
279 
280 	/* TCP (w/ faked seq. numbers) */
281 	tcp_hdr.tha_lport = htons(src_port);
282 	tcp_hdr.tha_fport = htons(dst_port);
283 	tcp_hdr.tha_seq = htonl(*seqno);
284 	tcp_hdr.tha_ack = htonl(*ackno);
285 	tcp_hdr.tha_offset_and_reserved = 0x50;
286 	tcp_hdr.tha_flags = 0x10; /* ACK */
287 	tcp_hdr.tha_win = htons(0xFF00);
288 	tcp_hdr.tha_sum = 0;
289 	tcp_hdr.tha_urp = 0;
290 	(*seqno) += len_w_hdrs;
291 	len_w_hdrs += sizeof (tcp_hdr);
292 
293 	/* IP header */
294 	switch (src_ip->a_family) {
295 	case AF_INET:
296 		ip_hdr_p = &ip_hdr;
297 		ip_hdr_len = sizeof (ip_hdr);
298 		/* IPv4 len includes the IP4 header */
299 		len_w_hdrs += ip_hdr_len;
300 		ip_hdr.ipha_version_and_hdr_length = 0x45;
301 		ip_hdr.ipha_type_of_service = 0;
302 		if (len_w_hdrs > 0xFFFF)
303 			ip_hdr.ipha_length = 0xFFFF;
304 		else
305 			ip_hdr.ipha_length = htons(len_w_hdrs);
306 		ip_hdr.ipha_ident = 0;
307 		ip_hdr.ipha_fragment_offset_and_flags = 0;
308 		ip_hdr.ipha_ttl = 60;
309 		ip_hdr.ipha_protocol = 6; /* TCP */
310 		ip_hdr.ipha_hdr_checksum = 0;
311 		ip_hdr.ipha_src = src_ip->a_ipv4;
312 		ip_hdr.ipha_dst = dst_ip->a_ipv4;
313 		break;
314 
315 	case AF_INET6:
316 		ip_hdr_p = &ip_hdr;
317 		ip_hdr_len = sizeof (ip6_hdr);
318 		ip6_hdr.ip6_vers = 6;
319 		ip6_hdr.ip6_class = 0;
320 		ip6_hdr.ip6_xflow = 0;
321 		if (len_w_hdrs > 0xFFFF)
322 			ip6_hdr.ip6_paylen = 0xFFFF;
323 		else
324 			ip6_hdr.ip6_paylen = htons(len_w_hdrs);
325 		ip6_hdr.ip6_nexthdr = 6; /* TCP */
326 		ip6_hdr.ip6_hoplim = 64;
327 		bcopy(&src_ip->a_ipv6, &ip6_hdr.ip6_src,
328 		    sizeof (ip6_hdr.ip6_src));
329 		bcopy(&dst_ip->a_ipv6, &ip6_hdr.ip6_dst,
330 		    sizeof (ip6_hdr.ip6_dst));
331 		len_w_hdrs += ip_hdr_len;
332 		break;
333 	default:
334 		ip_hdr_p = NULL;
335 		ip_hdr_len = 0;
336 		break;
337 	}
338 
339 	/* pcap header */
340 	phdr.ts_sec = rqtime / NANOSEC;
341 	phdr.ts_usec = (rqtime / 1000) % MICROSEC;
342 	phdr.incl_len = len_w_hdrs; /* not incl. pcap header */
343 	phdr.orig_len = len_w_hdrs + truncated;
344 	len_w_hdrs += sizeof (phdr);
345 
346 	/*
347 	 * Write out all the headers:
348 	 * pcap, IP, TCP, NetBIOS
349 	 *
350 	 * To avoid any possibility of scrambling the
351 	 * pcap file, save the offset here and seek to
352 	 * where we should be when done writing.
353 	 */
354 	pkt_off = lseek(pcap_fd, (off_t)0, SEEK_CUR);
355 	n = write(pcap_fd, &phdr, sizeof (phdr));
356 	if (n != sizeof (phdr)) {
357 		mdb_warn("failed to write pcap hdr\n");
358 		goto errout;
359 	}
360 	n = write(pcap_fd, ip_hdr_p, ip_hdr_len);
361 	if (n != ip_hdr_len) {
362 		mdb_warn("failed to write IP hdr\n");
363 		goto errout;
364 	}
365 	n = write(pcap_fd, &tcp_hdr, sizeof (tcp_hdr));
366 	if (n != sizeof (tcp_hdr)) {
367 		mdb_warn("failed to write TCP hdr\n");
368 		goto errout;
369 	}
370 	n = write(pcap_fd, &nb_hdr, sizeof (nb_hdr));
371 	if (n != sizeof (nb_hdr)) {
372 		mdb_warn("failed to write NBT hdr\n");
373 		goto errout;
374 	}
375 
376 	/*
377 	 * Finally, walk the mbuf chain writing SMB data
378 	 * to the pcap file, for exactly smb_len bytes.
379 	 */
380 	dump_state.rem_len = smb_len;
381 	dump_state.tbuf_size = MCLBYTES;
382 	dump_state.tbuf = mdb_alloc(dump_state.tbuf_size, UM_SLEEP);
383 	rc = mdb_pwalk("smb_mbuf_walker", smb_req_pcap_m,
384 	    &dump_state, (uintptr_t)mbc->chain);
385 	mdb_free(dump_state.tbuf, dump_state.tbuf_size);
386 	if (rc < 0) {
387 		mdb_warn("cannot walk smb_req mbuf_chain");
388 		goto errout;
389 	}
390 
391 	pkt_off += len_w_hdrs;
392 	(void) lseek(pcap_fd, pkt_off, SEEK_SET);
393 	return (DCMD_OK);
394 
395 errout:
396 	(void) lseek(pcap_fd, pkt_off, SEEK_SET);
397 	(void) ftruncate(pcap_fd, pkt_off);
398 	return (DCMD_ERR);
399 }
400 
401 /*
402  * Call-back function, called for each mbuf_t in a chain.
403  * Copy data from this mbuf to the pcap file.
404  */
405 static int
406 smb_req_pcap_m(uintptr_t mbuf_addr, const void *data, void *arg)
407 {
408 	struct req_dump_state *st = arg;
409 	const struct mbuf *m = data;
410 	uintptr_t addr;
411 	int cnt, mlen, n, x;
412 
413 	addr = (uintptr_t)m->m_data;
414 	mlen = m->m_len;
415 	if (mlen > st->rem_len)
416 		mlen = st->rem_len;
417 	if (mlen <= 0)
418 		return (WALK_DONE);
419 
420 	cnt = mlen;
421 	while (cnt > 0) {
422 		x = MIN(cnt, st->tbuf_size);
423 		n = mdb_vread(st->tbuf, x, addr);
424 		if (n != x) {
425 			mdb_warn("failed copying mbuf %p\n", mbuf_addr);
426 			return (WALK_ERR);
427 		}
428 		n = write(pcap_fd, st->tbuf, x);
429 		if (n != x) {
430 			mdb_warn("failed writing pcap data\n");
431 			return (WALK_ERR);
432 		}
433 		addr += x;
434 		cnt -= x;
435 	}
436 
437 	st->rem_len -= mlen;
438 	return (WALK_NEXT);
439 }
440