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 = 0x1000; /* abitrary */
 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 }