Search code examples
macosnetworkingtcpdtrace

Difficulty showing TCP information with dtrace on osx


I want to use dtrace to show information about tcp connect, accepts, and data sends...but i also want to show port and ip information.

Is there a way of doing this on osx? I notice that osx does not have a TCP provider (unlike solaris), but is there another way of doing it?


Solution

  • OSX does have a TCP provider. Here's how to confirm:

    sudo dtrace -l | perl -pe 's/^.*?\S+\s+(\S+?)([0-9]|\s).*/\1/' | sort | uniq
    

    The following DTrace script has been tested on macOS 10.13 High Sierra and 10.12 Sierra.

    Invoke like so:

    sudo ./tcpsnoop.d 2>/dev/null
    
    #!/usr/sbin/dtrace -s
    
    #pragma D option quiet
    #pragma D option switchrate=10hz
    
    dtrace:::BEGIN
    {
        printf("%6s %20s %15s:%-5s      %15s:%-5s %6s %s\n",
            "TIME", "CMD", "LADDR", "PORT", "RADDR", "PORT", "BYTES", "FLAGS");
    }
    
    tcp:::send
    {
        this->length = args[2]->ip_plength - args[4]->tcp_offset;
        printf("%6d %20s %15s:%-5d  ->  %15s:%-5d %6d (",
            timestamp/1000, execname, args[2]->ip_saddr,
            args[4]->tcp_sport, args[2]->ip_daddr, args[4]->tcp_dport,
            this->length);
    }
    
    tcp:::receive
    {
        this->length = args[2]->ip_plength - args[4]->tcp_offset;
        printf("%6d %20s %15s:%-5d  <-  %15s:%-5d %6d (",
            timestamp/1000, execname, args[2]->ip_daddr,
            args[4]->tcp_dport, args[2]->ip_saddr, args[4]->tcp_sport,
            this->length);
    }
    
    tcp:::send,
    tcp:::receive
    {
        printf("%s", args[4]->tcp_flags & TH_FIN ? "FIN|" : "");
        printf("%s", args[4]->tcp_flags & TH_SYN ? "SYN|" : "");
        printf("%s", args[4]->tcp_flags & TH_RST ? "RST|" : "");
        printf("%s", args[4]->tcp_flags & TH_PUSH ? "PUSH|" : "");
        printf("%s", args[4]->tcp_flags & TH_ACK ? "ACK|" : "");
        printf("%s", args[4]->tcp_flags & TH_URG ? "URG|" : "");
        printf("%s", args[4]->tcp_flags & TH_ECE ? "ECE|" : "");
        printf("%s", args[4]->tcp_flags & TH_CWR ? "CWR|" : "");
        printf("%s", args[4]->tcp_flags == 0 ? "null " : "");
        printf("\b)\n");
    }
    

    Output:

      TIME                  CMD           LADDR:PORT                 RADDR:PORT   BYTES FLAGS
    146791177843                 curl   192.168.232.2:56775  ->    151.101.65.69:80     16172 (SYN)
    146791206127                 curl   192.168.232.2:56775  ->    151.101.65.69:80     35052 (PUSH|ACK)
    146791390070                 curl   192.168.232.2:56775  ->    151.101.65.69:80     13292 (FIN|ACK)
    

    Some suggestions for how to extend it:

    Add a filter /execname == $1/ onto each of the three blocks tcp:::send, tcp:::receive, tcp:::send, tcp:::receive.
    This lets you filter by process name:

    sudo ./tcpsnoop.d 2>/dev/null curl
    

    Add a filter /progenyof($target) || pid == $target/ onto each of the three blocks tcp:::send, tcp:::receive, tcp:::send, tcp:::receive.
    This lets you filter by PID:

    pgrep 'pritunl-openvpn' | xargs -n 1 sudo ./tcpsnoop3.d 2>/dev/null -p