szaydel
8/2/2017 - 9:14 PM

[Auditing Dtrace Snippets] Pieces of dtrace useful for building audit logs and general operation auditing #tags: smb, nfs, audit, history, c

[Auditing Dtrace Snippets] Pieces of dtrace useful for building audit logs and general operation auditing #tags: smb, nfs, audit, history, commands log, io audit

#!/usr/sbin/dtrace -qCs
#pragma D option aggsortkey
#pragma D option aggsortkeypos=0

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or https://www.illumos.org/license/.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 * Copyright (c) 2017-2018 RackTop Systems.
 *
 * Author: Sam Zaydel
 */

/*
 * Operations we deemed frequent, like read/write are aggregated into 1ms slices,
 * which in theory means we can still end-up with 1000 log entries each second.
 * We may have to adjust that, depending on what we observe in the lab and testing
 * more broadly.
 *
 * *** Grouping of Operations ***
 * Grouped IOs are bucketed with timestamps which are computed as follows: 
 * walltimestamp - walltimestamp % GRANULARITY. The modulo method allows us
 * to zero out as many zeros from the least significant to most significant
 * number as we please, and therefore allow us to create aggregates with 1ms
 * or 10ms or 100ms, or 1sec, etc. resolution. We likely will need to reduce
 * granularity down to 100ms or 1sec, so as to protect us from ourselves.
 *
 * *** About Sorting ***
 * Aggregations are sorted based on the order of keys, with 0th being the key used.
 * The 0th position is currently timestamp, so if we want to continue to sort in 
 * this way, we need to make sure it either remains in the 0th position, or if we
 * have to change its place, we need to adjust `aggsortkeypos`.
 *
 * *** Fields ***
 * timestamp (hrtime_t) | timestamp, nanoseconds since epoch;
 * opname (string)      | type of operation, i.e. READ, WRITE, etc.
 * proto+vers (int)     | protocol and major+minor version enum, see [protovers]
 * clientIP (string)    | client-side IP address
 * file/dir (string)    | filesystem path of resource being accessed
 * uid (unit32_t)       | posix or pseudo (ephemeral) uid of authenticated user
 * gid (unit32_t)       | posix or pseudo (ephemeral) gid of authenticated user
 * return code(int)     | return code with cmd status 0 == OK | !0 == Error
 * sum bytes (int)      | number of bytes a command processed if not meta operation
 *
 */

 /*
  * Notes:
  * This script was changed post following git commit to support the changes
  * in this commit. It will therefore not work correctly with system running
  * code earlier than this changeset.
  * Tue Sep 12 14:49:00 2017 +0300 eae5de5d4a (origin/vgusev/bsr_nfs41)
  * NFS41-36 dtrace: resolve arguments for new stubs
  * op-*-start and op-*-done  [Vitaliy Gusev]
  */

vnode_t *nfsren_from[kthread_t *];  /* vnode pointer used with NFS rename op */
vnode_t *nfsren_to[kthread_t *];    /* vnode pointer used with NFS rename op */
smb_session_t *smbsess[kthread_t *];
string opname[string], createtyp[int];


inline const int METAOP = 0xffffffff; /* not a read or a write op */
inline const int GRANULARITY = 100000000; /* 1/10th of a second */

/*
 * These are elements we use in each of the SMB clauses to capture
 * relevant information about each operation. 
 *
 * path         args[2]->vp->v_path;
 * uid          args[1]->cr_uid;
 * gid          args[1]->cr_gid;
 * io bytes     args[3]->uio_resid;
 * ipaddress    args[0]->session->ip_addr_str;
 * remoteport   args[0]->session->s_remote_port;
*/

enum protovers {
    NFS2    = 0, /* NFSv2 not used here, we don't track it */
    NFS3    = 1, /* NFSv3 */ 
    NFS4    = 2, /* NFSv4 */
    NFS41   = 3, /* NFSv41 */
    /* skipping for future expansion */
    SMB11   = 7, /* SMBv1.1 */
    SMB21   = 8, /* SMBv2.1 */
    SMB3    = 9  /* Not yet implemented */
};

enum protovers smbvers[int]; /* Map smb version from dialect to enum value */

/* NFS file types, see usr/src/uts/common/nfs/nfs4_kprot.h */
enum nfs_ftype4 {
        NF4REG = 1,
        NF4DIR = 2,
        NF4BLK = 3,
        NF4CHR = 4,
        NF4LNK = 5,
        NF4SOCK = 6,
        NF4FIFO = 7,
        NF4ATTRDIR = 8,
        NF4NAMEDATTR = 9
};

enum opentype4 {
        OPEN4_NOCREATE = 0,
        OPEN4_CREATE = 1
};

BEGIN {
    smbvers[0xb]                = SMB11;
    smbvers[0x210]              = SMB21;

    createtyp[NF4DIR]           = "MKDIR";
    createtyp[NF4LNK]           = "LINK";
    createtyp[NF4REG]           = "CREATE";

    /* Lookup table used by both SMB and NFS clauses below */
    opname["op-create-done"]    = "CREATE";
    opname["op-read-done"]      = "READ";
    opname["op-write-done"]     = "WRITE";
    opname["op-remove-done"]    = "DELETE";
    opname["op-rename-done"]    = "RENAME";
    opname["op-mkdir-start"]    = "MKDIR";
    opname["op-mkdir-done"]     = "MKDIR";
    opname["op-rmdir-done"]     = "RMDIR";
    opname["smb_fsop_read"]     = "READ";
    opname["smb_fsop_write"]    = "WRITE";
    opname["smb_fsop_remove"]   = "DELETE";
    opname["smb_fsop_create"]   = "CREATE";
    opname["smb_fsop_rmdir"]    = "RMDIR";
    opname["smb_fsop_mkdir"]    = "MKDIR";
    opname["smb_fsop_rename"]   = "RENAME";
}

/* NFSv3 WRITE */
::rfs3_write:op-write-start
/substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->realts    = walltimestamp;
    this->ts        = this->realts - this->realts % GRANULARITY;
    this->b         = args[2]->data.data_len;
}

::rfs3_write:op-write-done
/substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->res = args[2]->status;

    @nfslog[this->ts, opname[probename], NFS3, args[0]->ci_remote, 
        args[1]->noi_curpath,
        args[1]->noi_cred->cr_uid,
        args[1]->noi_cred->cr_gid, this->res] = sum(this->b);
}

/* NFSv4 WRITE */
::rfs4_op_write:op-write-start
/substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->realts    = walltimestamp;
    this->ts        = this->realts - this->realts % GRANULARITY;
    this->b         = args[2]->data_len;
}

::rfs4_op_write:op-write-done
/substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->res = args[2]->status;

    @nfslog[this->ts, opname[probename], NFS4, args[0]->ci_remote, 
        args[1]->noi_curpath, args[1]->noi_cred->cr_uid,
        args[1]->noi_cred->cr_gid, this->res] = sum(this->b);
}

/* NFSv3/NFSv4 READ */
::rfs3_read:op-read-start,
::rfs4_op_read:op-read-start
/substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->realts    = walltimestamp;
    this->ts        = this->realts - this->realts % GRANULARITY;
    this->vers      = probefunc == "rfs4_op_read" ? NFS4 : NFS3;
    this->b         = args[2]->count;
}

::rfs3_read:op-read-done,
::rfs4_op_read:op-read-done
/substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->res = args[2]->status;

    @nfslog[this->ts, opname[probename], this->vers, args[0]->ci_remote, 
        args[1]->noi_curpath, args[1]->noi_cred->cr_uid,
        args[1]->noi_cred->cr_gid, this->res] = sum(this->b);
}

/* NFSv3 REMOVE */
::rfs3_remove:op-remove-start
/substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->ts        = walltimestamp;
    this->p         = strjoin(args[1]->noi_curpath, "/");
    this->p         = strjoin(this->p, args[2]->object.name);
}

::rfs3_remove:op-remove-done
/substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->res = args[2]->status;
    
    @nfslog[this->ts, opname[probename], NFS3, args[0]->ci_remote, 
        this->p, args[1]->noi_cred->cr_uid,
        args[1]->noi_cred->cr_gid, this->res] = sum(0);
}

/* NFSv4 REMOVE */
::rfs4_op_remove:op-remove-start
/substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->ts        = walltimestamp;
    this->p         = strjoin(args[1]->noi_curpath, "/");
    /* 
     * Make sure that only actual length of the string in 
     * args[2]->target.utf8string_val is strjoin(ed) with the
     * remainder of the path. Otherwise we will pick-up crap, because
     * utf8string_val is not properly terminated.
     */
    this->p = strjoin(this->p,
                        substr(args[2]->target.utf8string_val,
                                0, args[2]->target.utf8string_len)
                    );
}
::rfs4_op_remove:op-remove-done
/substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->res = args[2]->status;

    @nfslog[this->ts, opname[probename], NFS4, args[0]->ci_remote, 
        this->p, args[1]->noi_cred->cr_uid,
        args[1]->noi_cred->cr_gid, this->res] = sum(0);
}

/* NFSv3 CREATE */
::rfs3_create:op-create-start
/substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->ts        = walltimestamp;
    this->p         = strjoin(args[1]->noi_curpath, "/");
    this->p         = strjoin(this->p, args[2]->where.name);
}

::rfs3_create:op-create-done
/substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->res = args[2]->status;

    @nfslog[this->ts, opname[probename], NFS3, args[0]->ci_remote, 
        this->p, args[1]->noi_cred->cr_uid,
        args[1]->noi_cred->cr_gid, this->res] = sum(0);
}

/* NFSv3 MKDIR */
::rfs3_mkdir:op-mkdir-start
/substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->ts        = walltimestamp;
    this->p         = strjoin(args[1]->noi_curpath, "/");
    this->p         = strjoin(this->p, args[2]->where.name);
}

::rfs3_mkdir:op-mkdir-done
/substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->res = args[2]->status;

    @nfslog[this->ts, opname[probename], NFS3, args[0]->ci_remote, 
        this->p, args[1]->noi_cred->cr_uid,
        args[1]->noi_cred->cr_gid, this->res] = sum(0);
}

/* NFSv3 RMDIR */
::rfs3_rmdir:op-rmdir-start
/substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->ts        = walltimestamp;
    this->p         = strjoin(args[1]->noi_curpath, "/");
    this->p         = strjoin(this->p, args[2]->object.name);
}

::rfs3_rmdir:op-rmdir-done
/substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->res = args[2]->status;

    @nfslog[this->ts, opname[probename], NFS3, args[0]->ci_remote, 
        this->p, args[1]->noi_cred->cr_uid,
        args[1]->noi_cred->cr_gid, this->res] = sum(0);
}

/* NFSv4 CREATE */
::rfs4_op_create:op-create-start
/substr(args[1]->noi_curpath, 0, 1) == "/"/{
    this->t         = args[2]->type;
    this->ts        = walltimestamp;
    this->p         = strjoin(args[1]->noi_curpath, "/");
    /* 
     * Make sure that only actual length of the string in 
     * args[2]->objname.utf8string_val is strjoin(ed) with the
     * remainder of the path. Otherwise we will pick-up crap, because
     * utf8string_val is not properly terminated.
     */
    this->p = strjoin(this->p,
                        substr(args[2]->objname.utf8string_val,
                                0, args[2]->objname.utf8string_len)
                );
}

::rfs4_op_create:op-create-done
/
substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->res = args[2]->status;

    @nfslog[this->ts, createtyp[this->t], NFS4, args[0]->ci_remote, 
        this->p, args[1]->noi_cred->cr_uid,
        args[1]->noi_cred->cr_gid, this->res] = sum(0);
}

/* NFSv4 CREATE (regular files) */
::rfs4_op_open:op-open-start
/args[2]->opentype == OPEN4_CREATE &&
substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->iscreate  = OPEN4_CREATE;
    this->openargs  = args[2];
    this->ts        = walltimestamp;
    this->val       = args[2]->claim.open_claim4_u.file.utf8string_val;
    this->len       = args[2]->claim.open_claim4_u.file.utf8string_len;
    this->p         = strjoin(args[1]->noi_curpath, "/");
    /* 
     * Make sure that only actual length of the string in
     * args[2]->claim.open_claim4_u.file.utf8string_val is strjoin(ed) with
     * the remainder of the path. Otherwise we will pick-up crap, because
     * utf8string_val is not properly terminated.
     */
    this->p = strjoin(this->p, substr(this->val, 0, this->len));
}

::rfs4_op_open:op-open-done
/ this->iscreate == OPEN4_CREATE &&
substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->res = args[2]->status;
    this->iscreate = 0;

    @nfslog[this->ts, createtyp[NF4REG], NFS4, args[0]->ci_remote, 
        this->p, args[1]->noi_cred->cr_uid,
        args[1]->noi_cred->cr_gid, this->res] = sum(0);
}

/* NFSv3 RENAME (start) */
::rfs3_rename:op-rename-start {
    in_rename[curthread] = 1; /* set flag triggering entry into zfs_rename */
    this->ts        = walltimestamp;
    this->newn      = args[2]->to.name;
    this->oldn      = args[2]->from.name;
}

/* NFSv4 RENAME (start) */
::rfs4_op_rename:op-rename-start {
    in_rename[curthread] = 1; /* set flag triggering entry into zfs_rename */
    this->ts        = walltimestamp;
    /* Store pointer and length of old and new value(s) of name */
    this->newval    = args[2]->newname.utf8string_val;
    this->newlen    = args[2]->newname.utf8string_len;
    this->oldval    = args[2]->oldname.utf8string_val;
    this->oldlen    = args[2]->oldname.utf8string_len;

    /* Trim off anything > utf8string_val, to avoid junk in name */
    this->newn      = substr(this->newval, 0, this->newlen);
    this->oldn      = substr(this->oldval, 0, this->oldlen);
}

/* 
 * This probe is triggered when NFS asks the filesystem to rename,
 * and it gives us access to pointers to vnodes, both from and to.
 */
::zfs_rename:entry /in_rename[curthread]/ {
    /* 
     * Set these to 0 later, otherwise we will have issues with
     * dropped dynamic variables after running for some period of time.
     */
    nfsren_from[curthread] = args[0];
    nfsren_to[curthread] = args[2];
}

/* NFSv3 RENAME (done) */
::rfs3_rename:op-rename-done /in_rename[curthread]/ {
    /* 
     * We are expecting that v_path(s) will be valid here, but we experienced
     * instances where the values were not valid, and to protect from that we
     * add an override here in the cases where values are NULL or "".
     */
    this->from      = nfsren_from[curthread]->v_path == NULL ? "<NONE>" : 
                        nfsren_from[curthread]->v_path == "" ? "<NONE>" :
                        nfsren_from[curthread]->v_path;
    this->to        = nfsren_to[curthread]->v_path == NULL ? "<NONE>" :
                        nfsren_to[curthread]->v_path == "" ? "<NONE>" :
                        nfsren_to[curthread]->v_path;
    this->fp_old    = strjoin(this->from, "/");
    this->fp_old    = strjoin(this->fp_old, this->oldn);
    this->fp_new    = strjoin(this->to, "/");
    this->fp_new    = strjoin(this->fp_new, this->newn);
    this->oldnew    = strjoin(this->fp_old, "|");
    this->oldnew    = strjoin(this->oldnew, this->fp_new);

    in_rename[curthread]    = 0;
    nfsren_from[curthread]  = 0;
    nfsren_to[curthread]    = 0;

    this->res = args[2]->status;

    @nfslog[this->ts, opname[probename], NFS3, args[0]->ci_remote, 
    this->oldnew, args[1]->noi_cred->cr_uid,
    args[1]->noi_cred->cr_gid, this->res] = sum(0);
}

/* NFSv4 RENAME (done) */
::rfs4_op_rename:op-rename-done /in_rename[curthread]/ {
    /* 
     * We are expecting that v_path(s) will be valid here, but we experienced
     * instances where the values were not valid, and to protect from that we
     * add an override here in the cases where values are NULL or "".
     */
    this->from      = nfsren_from[curthread]->v_path == NULL ? "<NONE>" : 
                        nfsren_from[curthread]->v_path == "" ? "<NONE>" :
                        nfsren_from[curthread]->v_path;
    this->to        = nfsren_to[curthread]->v_path == NULL ? "<NONE>" :
                        nfsren_to[curthread]->v_path == "" ? "<NONE>" :
                        nfsren_to[curthread]->v_path;
    this->fp_old    = strjoin(this->from, "/");
    this->fp_old    = strjoin(this->fp_old, this->oldn);
    this->fp_new    = strjoin(this->to, "/");
    this->fp_new    = strjoin(this->fp_new, this->newn);
    this->oldnew    = strjoin(this->fp_old, "|");
    this->oldnew    = strjoin(this->oldnew, this->fp_new);

    in_rename[curthread]    = 0;
    nfsren_from[curthread]  = 0;
    nfsren_to[curthread]    = 0;

    this->res = args[2]->status;

    @nfslog[this->ts, opname[probename], NFS4, args[0]->ci_remote, 
        this->oldnew, args[1]->noi_cred->cr_uid,
        args[1]->noi_cred->cr_gid, this->res] = sum(0);
}

/* SMB Read and Write */
::smb_fsop_write:entry,::smb_fsop_read:entry
/substr(args[2]->vp->v_path, 0, 1) == "/"/ {
    this->p         = args[2]->vp->v_path;
    this->realts    = walltimestamp;
    this->ts        = this->realts - this->realts % GRANULARITY;
    this->v         = args[0]->session->dialect;
    this->sess      = args[0]->session;
    this->cr_gid    = args[1]->cr_gid;
    this->cr_uid    = args[1]->cr_uid;
    this->b         = args[3]->uio_resid;
}

::smb_fsop_write:return,::smb_fsop_read:return {
    @smblog[this->ts, opname[probefunc], smbvers[this->v],
        this->sess->ip_addr_str, stringof(this->p), this->cr_uid, this->cr_gid, 
        args[1]] = sum(this->b);
}

/* SMB Create and Remove */
::smb_fsop_create:entry,
::smb_fsop_mkdir:entry
/substr(args[2]->vp->v_path, 0, 1) == "/"/ {
    this->p         = strjoin(args[2]->vp->v_path, "/"); 
    this->p         = strjoin(this->p, args[3]);
    this->ts        = walltimestamp;
    this->v         = args[0]->session->dialect;
    this->sess      = args[0]->session;
    this->cr_gid    = args[1]->cr_gid;
    this->cr_uid    = args[1]->cr_uid; 
}

/*
 * Because smb_fsop_remove and smb_fsop_rmdir have a 0
 * in place of ptr to smb_request_t, we have to brab a handle
 * here and then make sure to zero this out after this function
 * returns. We do not check upon return for existence of value
 * self->reqfree, because it does not really matter, since we
 * are setting to 0 at that point.
 */
::smb_request_free:entry {
    self->reqfree = args[0];
}

::smb_fsop_remove:entry,
::smb_fsop_rmdir:entry
/substr(args[2]->vp->v_path, 0, 1) == "/"/ {
    this->p         = strjoin(args[2]->vp->v_path, "/"); 
    this->p         = strjoin(this->p, args[3]);
    this->ts        = walltimestamp;

    this->v         = self->reqfree->session->dialect;
    this->cr_gid    = args[1]->cr_gid;
    this->cr_uid    = args[1]->cr_uid; 
}

::smb_fsop_remove:return,
::smb_fsop_rmdir:return {
    this->retcode = args[1];
    @smblog[this->ts, opname[probefunc], smbvers[this->v],
        self->reqfree->session->ip_addr_str, stringof(this->p), 
        this->cr_uid, this->cr_gid, this->retcode] = sum(0);
}

::smb_request_free:return {
    self->reqfree = 0;
}

::smb_fsop_mkdir:return,
::smb_fsop_create:return {
    this->retcode = args[1];
    @smblog[this->ts, opname[probefunc], smbvers[this->v], this->sess->ip_addr_str,
        stringof(this->p), this->cr_uid, this->cr_gid, this->retcode] = sum(0);
}

/* SMB Rename */
::smb_fsop_rename:entry
/substr(args[2]->vp->v_path, 0, 1) == "/"/ {
    self->in        = 1;
    this->p         = strjoin(args[2]->vp->v_path, "/"); 
    this->p         = strjoin(this->p, args[3]);
    this->new       = strjoin(args[4]->vp->v_path, "/");
    this->new       = strjoin(this->new, args[5]);
    this->oldnew    = strjoin(this->p, "|");
    this->oldnew    = strjoin(this->oldnew, this->new);
    this->ts        = walltimestamp;
    this->v         = args[0]->session->dialect;
    this->sess      = args[0]->session;
    this->cr_gid    = args[1]->cr_gid;
    this->cr_uid    = args[1]->cr_uid;
}

::smb_fsop_rename:return /self->in/ {
    self->in = 0;
    this->retcode = args[1];

    @smblog[this->ts, opname[probefunc], smbvers[this->v], this->sess->ip_addr_str,
        stringof(this->oldnew), this->cr_uid, this->cr_gid, this->retcode] = sum(0);
}

tick-1sec {
    printa("%d,%s,%d,%s,%s,%d,%d,%d,0x%@x\n", @nfslog);
    printa("%d,%s,%d,%s,%s,%d,%d,%d,0x%@x\n", @smblog);
    trunc(@nfslog); trunc(@smblog);
}
#!/usr/sbin/dtrace -qCs
#pragma D option aggsortkey
#pragma D option aggsortkeypos=0

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or https://www.illumos.org/license/.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 * Copyright (c) 2017 RackTop Systems.
 *
 * Author: Sam Zaydel
 */

/*
 * Operations we deemed frequent, like read/write are aggregated into 1ms slices,
 * which in theory means we can still end-up with 1000 log entries each second.
 * We may have to adjust that, depending on what we observe in the lab and testing
 * more broadly.
 *
 * *** Grouping of Operations ***
 * Grouped IOs are bucketed with timestamps which are computed as follows: 
 * walltimestamp - walltimestamp % GRANULARITY. The modulo method allows us
 * to zero out as many zeros from the least significant to most significant
 * number as we please, and therefore allow us to create aggregates with 1ms
 * or 10ms or 100ms, or 1sec, etc. resolution. We likely will need to reduce
 * granularity down to 100ms or 1sec, so as to protect us from ourselves.
 *
 * *** About Sorting ***
 * Aggregations are sorted based on the order of keys, with 0th being the key used.
 * The 0th position is currently timestamp, so if we want to continue to sort in 
 * this way, we need to make sure it either remains in the 0th position, or if we
 * have to change its place, we need to adjust `aggsortkeypos`.
 *
 * *** Fields ***
 * timestamp (hrtime_t) | timestamp, nanoseconds since epoch;
 * opname (string)      | type of operation, i.e. READ, WRITE, etc.
 * proto+vers (int)     | protocol and major+minor version enum, see [protovers]
 * clientIP (string)    | client-side IP address
 * file/dir (string)    | filesystem path of resource being accessed
 * uid (unit32_t)       | posix or pseudo (ephemeral) uid of authenticated user
 * gid (unit32_t)       | posix or pseudo (ephemeral) gid of authenticated user
 * return code(int)     | return code with cmd status 0 == OK | !0 == Error
 * sum bytes (int)      | number of bytes a command processed if not meta operation
 *
 */

vnode_t *nfsren_from[kthread_t *];  /* vnode pointer used with NFS rename op */
vnode_t *nfsren_to[kthread_t *];    /* vnode pointer used with NFS rename op */
smb_session_t *smbsess[kthread_t *];
string opname[string], createtyp[int];


inline const int METAOP = 0xffffffff; /* not a read or a write op */
inline const int GRANULARITY = 100000000; /* 1/10th of a second */

/*
 * These are elements we use in each of the SMB clauses to capture
 * relevant information about each operation. 
 *
 * path         args[2]->vp->v_path;
 * uid          args[1]->cr_uid;
 * gid          args[1]->cr_gid;
 * io bytes     args[3]->uio_resid;
 * ipaddress    args[0]->session->ip_addr_str;
 * remoteport   args[0]->session->s_remote_port;
*/

enum protovers {
    NFS2    = 0, /* NFSv2 not used here, we don't track it */
    NFS3    = 1, /* NFSv3 */ 
    NFS4    = 2, /* NFSv4 */
    NFS41   = 3, /* NFSv41 */
    /* skipping for future expansion */
    SMB11   = 7, /* SMBv1.1 */
    SMB21   = 8, /* SMBv2.1 */
    SMB3    = 9  /* Not yet implemented */
};

enum protovers smbvers[int]; /* Map smb version from dialect to enum value */

/* NFS file types, see usr/src/uts/common/nfs/nfs4_kprot.h */
enum nfs_ftype4 {
        NF4REG = 1,
        NF4DIR = 2,
        NF4BLK = 3,
        NF4CHR = 4,
        NF4LNK = 5,
        NF4SOCK = 6,
        NF4FIFO = 7,
        NF4ATTRDIR = 8,
        NF4NAMEDATTR = 9
};

enum opentype4 {
        OPEN4_NOCREATE = 0,
        OPEN4_CREATE = 1
};

BEGIN {
    smbvers[0xb]                = SMB11;
    smbvers[0x210]              = SMB21;

    createtyp[NF4DIR]           = "MKDIR";
    createtyp[NF4LNK]           = "LINK";
    createtyp[NF4REG]           = "CREATE";

    /* Lookup table used by both SMB and NFS clauses below */
    opname["op-create-done"]    = "CREATE";
    opname["op-read-done"]      = "READ";
    opname["op-write-done"]     = "WRITE";
    opname["op-remove-done"]    = "DELETE";
    opname["op-rename-done"]    = "RENAME";
    opname["op-mkdir-start"]    = "MKDIR";
    opname["op-mkdir-done"]     = "MKDIR";
    opname["op-rmdir-done"]     = "RMDIR";
    opname["smb_fsop_read"]     = "READ";
    opname["smb_fsop_write"]    = "WRITE";
    opname["smb_fsop_remove"]   = "DELETE";
    opname["smb_fsop_create"]   = "CREATE";
    opname["smb_fsop_rmdir"]    = "RMDIR";
    opname["smb_fsop_mkdir"]    = "MKDIR";
    opname["smb_fsop_rename"]   = "RENAME";
}

/* NFSv3 WRITE */
::rfs3_write:op-write-start
/substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->realts    = walltimestamp;
    this->ts        = this->realts - this->realts % GRANULARITY;
    this->b         = args[2]->data.data_len;
}

::rfs3_write:op-write-done
/substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->res = args[2]->status;

    @nfslog[this->ts, opname[probename], NFS3, args[0]->ci_remote, 
        args[1]->noi_curpath,
        args[1]->noi_cred->cr_uid,
        args[1]->noi_cred->cr_gid, this->res] = sum(this->b);
}

/* NFSv4 WRITE */
::rfs4_op_write:op-write-start
/substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->realts    = walltimestamp;
    this->ts        = this->realts - this->realts % GRANULARITY;
    this->b         = args[2]->data_len;
}

::rfs4_op_write:op-write-done
/substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->res = args[2]->status;

    @nfslog[this->ts, opname[probename], NFS4, args[0]->ci_remote, 
        args[1]->noi_curpath, args[1]->noi_cred->cr_uid,
        args[1]->noi_cred->cr_gid, this->res] = sum(this->b);
}

/* NFSv3/NFSv4 READ */
::rfs3_read:op-read-start,
::rfs4_op_read:op-read-start
/substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->realts    = walltimestamp;
    this->ts        = this->realts - this->realts % GRANULARITY;
    this->vers      = probefunc == "rfs4_op_read" ? NFS4 : NFS3;
    this->b         = args[2]->count;
}

::rfs3_read:op-read-done,
::rfs4_op_read:op-read-done
/substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->res = args[2]->status;

    @nfslog[this->ts, opname[probename], this->vers, args[0]->ci_remote, 
        args[1]->noi_curpath, args[1]->noi_cred->cr_uid,
        args[1]->noi_cred->cr_gid, this->res] = sum(this->b);
}

/* NFSv3 REMOVE */
::rfs3_remove:op-remove-start
/substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->ts        = walltimestamp;
    this->p         = strjoin(args[1]->noi_curpath, "/");
    this->p         = strjoin(this->p, args[2]->object.name);
}

::rfs3_remove:op-remove-done
/substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->res = args[2]->status;
    
    @nfslog[this->ts, opname[probename], NFS3, args[0]->ci_remote, 
        this->p, args[1]->noi_cred->cr_uid,
        args[1]->noi_cred->cr_gid, this->res] = sum(0);
}

/* NFSv4 REMOVE */
::rfs4_op_remove:op-remove-start
/substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->ts        = walltimestamp;
    this->p         = strjoin(args[1]->noi_curpath, "/");
    /* 
     * Make sure that only actual length of the string in 
     * args[2]->target.utf8string_val is strjoin(ed) with the
     * remainder of the path. Otherwise we will pick-up crap, because
     * utf8string_val is not properly terminated.
     */
    this->p = strjoin(this->p,
                        substr(args[2]->target.utf8string_val,
                                0, args[2]->target.utf8string_len)
                    );
}
::rfs4_op_remove:op-remove-done
/substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->res = args[2]->status;

    @nfslog[this->ts, opname[probename], NFS4, args[0]->ci_remote, 
        this->p, args[1]->noi_cred->cr_uid,
        args[1]->noi_cred->cr_gid, this->res] = sum(0);
}

/* NFSv3 CREATE */
::rfs3_create:op-create-start
/substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->ts        = walltimestamp;
    this->p         = strjoin(args[1]->noi_curpath, "/");
    this->p         = strjoin(this->p, args[2]->where.name);
}

::rfs3_create:op-create-done
/substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->res = args[2]->status;

    @nfslog[this->ts, opname[probename], NFS3, args[0]->ci_remote, 
        this->p, args[1]->noi_cred->cr_uid,
        args[1]->noi_cred->cr_gid, this->res] = sum(0);
}

/* NFSv3 MKDIR */
::rfs3_mkdir:op-mkdir-start
/substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->ts        = walltimestamp;
    this->p         = strjoin(args[1]->noi_curpath, "/");
    this->p         = strjoin(this->p, args[2]->where.name);
}

::rfs3_mkdir:op-mkdir-done
/substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->res = args[2]->status;

    @nfslog[this->ts, opname[probename], NFS3, args[0]->ci_remote, 
        this->p, args[1]->noi_cred->cr_uid,
        args[1]->noi_cred->cr_gid, this->res] = sum(0);
}

/* NFSv3 RMDIR */
::rfs3_rmdir:op-rmdir-start
/substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->ts        = walltimestamp;
    this->p         = strjoin(args[1]->noi_curpath, "/");
    this->p         = strjoin(this->p, args[2]->object.name);
}

::rfs3_rmdir:op-rmdir-done
/substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->res = args[2]->status;

    @nfslog[this->ts, opname[probename], NFS3, args[0]->ci_remote, 
        this->p, args[1]->noi_cred->cr_uid,
        args[1]->noi_cred->cr_gid, this->res] = sum(0);
}

/* NFSv4 CREATE */
::rfs4_op_create:op-create-start
/substr(args[1]->noi_curpath, 0, 1) == "/"/{
    this->t         = args[2]->type;
    this->ts        = walltimestamp;
    this->p         = strjoin(args[1]->noi_curpath, "/");
    /* 
     * Make sure that only actual length of the string in 
     * args[2]->objname.utf8string_val is strjoin(ed) with the
     * remainder of the path. Otherwise we will pick-up crap, because
     * utf8string_val is not properly terminated.
     */
    this->p = strjoin(this->p,
                        substr(args[2]->objname.utf8string_val,
                                0, args[2]->objname.utf8string_len)
                );
}

::rfs4_op_create:op-create-done
/
substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->res = args[2]->status;

    @nfslog[this->ts, createtyp[this->t], NFS4, args[0]->ci_remote, 
        this->p, args[1]->noi_cred->cr_uid,
        args[1]->noi_cred->cr_gid, this->res] = sum(0);
}

/* NFSv4 CREATE (regular files) */
::rfs4_op_open:op-open-start
/args[2]->opentype == OPEN4_CREATE &&
substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->iscreate  = OPEN4_CREATE;
    this->openargs  = args[2];
    this->ts        = walltimestamp;
    this->p         = strjoin(args[1]->noi_curpath, "/");
    /* 
     * Make sure that only actual length of the string in 
     * args[2]->objname.utf8string_val is strjoin(ed) with the
     * remainder of the path. Otherwise we will pick-up crap, because
     * utf8string_val is not properly terminated.
     */
    this->p = strjoin(this->p,
                        substr(args[2]->open_claim4_u.file.utf8string_val,
                                0, args[2]->open_claim4_u.file.utf8string_len)
                );
}

::rfs4_op_open:op-open-done
/ this->iscreate == OPEN4_CREATE &&
substr(args[1]->noi_curpath, 0, 1) == "/"/ {
    this->res = args[2]->status;
    this->iscreate = 0;

    @nfslog[this->ts, createtyp[NF4REG], NFS4, args[0]->ci_remote, 
        this->p, args[1]->noi_cred->cr_uid,
        args[1]->noi_cred->cr_gid, this->res] = sum(0);
}

/* NFSv3 RENAME (start) */
::rfs3_rename:op-rename-start {
    in_rename[curthread] = 1; /* set flag triggering entry into zfs_rename */
    this->ts        = walltimestamp;
    this->newn      = args[2]->to.name;
    this->oldn      = args[2]->from.name;
}

/* NFSv4 RENAME (start) */
::rfs4_op_rename:op-rename-start {
    in_rename[curthread] = 1; /* set flag triggering entry into zfs_rename */
    this->ts        = walltimestamp;
    /* Store pointer and length of old and new value(s) of name */
    this->newval    = args[2]->newname.utf8string_val;
    this->newlen    = args[2]->newname.utf8string_len;
    this->oldval    = args[2]->oldname.utf8string_val;
    this->oldlen    = args[2]->oldname.utf8string_len;

    /* Trim off anything > utf8string_val, to avoid junk in name */
    this->newn      = substr(this->newval, 0, this->newlen);
    this->oldn      = substr(this->oldval, 0, this->oldlen);
}

/* 
 * This probe is triggered when NFS asks the filesystem to rename,
 * and it gives us access to pointers to vnodes, both from and to.
 */
::zfs_rename:entry /in_rename[curthread]/ {
    /* 
     * Set these to 0 later, otherwise we will have issues with
     * dropped dynamic variables after running for some period of time.
     */
    nfsren_from[curthread] = args[0];
    nfsren_to[curthread] = args[2];
}

/* NFSv3 RENAME (done) */
::rfs3_rename:op-rename-done /in_rename[curthread]/ {
    /* 
     * We are expecting that v_path(s) will be valid here, but we experienced
     * instances where the values were not valid, and to protect from that we
     * add an override here in the cases where values are NULL or "".
     */
    this->from      = nfsren_from[curthread]->v_path == NULL ? "<NONE>" : 
                        nfsren_from[curthread]->v_path == "" ? "<NONE>" :
                        nfsren_from[curthread]->v_path;
    this->to        = nfsren_to[curthread]->v_path == NULL ? "<NONE>" :
                        nfsren_to[curthread]->v_path == "" ? "<NONE>" :
                        nfsren_to[curthread]->v_path;
    this->fp_old    = strjoin(this->from, "/");
    this->fp_old    = strjoin(this->fp_old, this->oldn);
    this->fp_new    = strjoin(this->to, "/");
    this->fp_new    = strjoin(this->fp_new, this->newn);
    this->oldnew    = strjoin(this->fp_old, "|");
    this->oldnew    = strjoin(this->oldnew, this->fp_new);

    in_rename[curthread]    = 0;
    nfsren_from[curthread]  = 0;
    nfsren_to[curthread]    = 0;

    this->res = args[2]->status;

    @nfslog[this->ts, opname[probename], NFS3, args[0]->ci_remote, 
    this->oldnew, args[1]->noi_cred->cr_uid,
    args[1]->noi_cred->cr_gid, this->res] = sum(0);
}

/* NFSv4 RENAME (done) */
::rfs4_op_rename:op-rename-done /in_rename[curthread]/ {
    /* 
     * We are expecting that v_path(s) will be valid here, but we experienced
     * instances where the values were not valid, and to protect from that we
     * add an override here in the cases where values are NULL or "".
     */
    this->from      = nfsren_from[curthread]->v_path == NULL ? "<NONE>" : 
                        nfsren_from[curthread]->v_path == "" ? "<NONE>" :
                        nfsren_from[curthread]->v_path;
    this->to        = nfsren_to[curthread]->v_path == NULL ? "<NONE>" :
                        nfsren_to[curthread]->v_path == "" ? "<NONE>" :
                        nfsren_to[curthread]->v_path;
    this->fp_old    = strjoin(this->from, "/");
    this->fp_old    = strjoin(this->fp_old, this->oldn);
    this->fp_new    = strjoin(this->to, "/");
    this->fp_new    = strjoin(this->fp_new, this->newn);
    this->oldnew    = strjoin(this->fp_old, "|");
    this->oldnew    = strjoin(this->oldnew, this->fp_new);

    in_rename[curthread]    = 0;
    nfsren_from[curthread]  = 0;
    nfsren_to[curthread]    = 0;

    this->res = args[2]->status;

    @nfslog[this->ts, opname[probename], NFS4, args[0]->ci_remote, 
        this->oldnew, args[1]->noi_cred->cr_uid,
        args[1]->noi_cred->cr_gid, this->res] = sum(0);
}

/* SMB Read and Write */
::smb_fsop_write:entry,::smb_fsop_read:entry
/substr(args[2]->vp->v_path, 0, 1) == "/"/ {
    this->p         = args[2]->vp->v_path;
    this->realts    = walltimestamp;
    this->ts        = this->realts - this->realts % GRANULARITY;
    this->v         = args[0]->session->dialect;
    this->sess      = args[0]->session;
    this->cr_gid    = args[1]->cr_gid;
    this->cr_uid    = args[1]->cr_uid;
    this->b         = args[3]->uio_resid;
}

::smb_fsop_write:return,::smb_fsop_read:return {
    @smblog[this->ts, opname[probefunc], smbvers[this->v],
        this->sess->ip_addr_str, stringof(this->p), this->cr_uid, this->cr_gid, 
        args[1]] = sum(this->b);
}

/* SMB Create and Remove */
::smb_fsop_create:entry,
::smb_fsop_mkdir:entry
/substr(args[2]->vp->v_path, 0, 1) == "/"/ {
    this->p         = strjoin(args[2]->vp->v_path, "/"); 
    this->p         = strjoin(this->p, args[3]);
    this->ts        = walltimestamp;
    this->v         = args[0]->session->dialect;
    this->sess      = args[0]->session;
    this->cr_gid    = args[1]->cr_gid;
    this->cr_uid    = args[1]->cr_uid; 
}

/*
 * Because smb_fsop_remove and smb_fsop_rmdir have a 0
 * in place of ptr to smb_request_t, we have to brab a handle
 * here and then make sure to zero this out after this function
 * returns. We do not check upon return for existence of value
 * self->reqfree, because it does not really matter, since we
 * are setting to 0 at that point.
 */
::smb_request_free:entry {
    self->reqfree = args[0];
}

::smb_fsop_remove:entry,
::smb_fsop_rmdir:entry
/substr(args[2]->vp->v_path, 0, 1) == "/"/ {
    this->p         = strjoin(args[2]->vp->v_path, "/"); 
    this->p         = strjoin(this->p, args[3]);
    this->ts        = walltimestamp;

    this->v         = self->reqfree->session->dialect;
    this->cr_gid    = args[1]->cr_gid;
    this->cr_uid    = args[1]->cr_uid; 
}

::smb_fsop_remove:return,
::smb_fsop_rmdir:return {
    this->retcode = args[1];
    @smblog[this->ts, opname[probefunc], smbvers[this->v],
        self->reqfree->session->ip_addr_str, stringof(this->p), 
        this->cr_uid, this->cr_gid, this->retcode] = sum(0);
}

::smb_request_free:return {
    self->reqfree = 0;
}

::smb_fsop_mkdir:return,
::smb_fsop_create:return {
    this->retcode = args[1];
    @smblog[this->ts, opname[probefunc], smbvers[this->v], this->sess->ip_addr_str,
        stringof(this->p), this->cr_uid, this->cr_gid, this->retcode] = sum(0);
}

/* SMB Rename */
::smb_fsop_rename:entry
/substr(args[2]->vp->v_path, 0, 1) == "/"/ {
    self->in        = 1;
    this->p         = strjoin(args[2]->vp->v_path, "/"); 
    this->p         = strjoin(this->p, args[3]);
    this->new       = strjoin(args[4]->vp->v_path, "/");
    this->new       = strjoin(this->new, args[5]);
    this->oldnew    = strjoin(this->p, "|");
    this->oldnew    = strjoin(this->oldnew, this->new);
    this->ts        = walltimestamp;
    this->v         = args[0]->session->dialect;
    this->sess      = args[0]->session;
    this->cr_gid    = args[1]->cr_gid;
    this->cr_uid    = args[1]->cr_uid;
}

::smb_fsop_rename:return /self->in/ {
    self->in = 0;
    this->retcode = args[1];

    @smblog[this->ts, opname[probefunc], smbvers[this->v], this->sess->ip_addr_str,
        stringof(this->oldnew), this->cr_uid, this->cr_gid, this->retcode] = sum(0);
}

tick-1sec {
    printa("%d,%s,%d,%s,%s,%d,%d,%d,0x%@x\n", @nfslog);
    printa("%d,%s,%d,%s,%s,%d,%d,%d,0x%@x\n", @smblog);
    trunc(@nfslog); trunc(@smblog);
}