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