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
smbsrv_pcap_close(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
smbsrv_pcap_open(char * outfile)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
pcap_file_header(char * outfile,int fd)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
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)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
smb_req_pcap_m(uintptr_t mbuf_addr,const void * data,void * arg)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