Ethereal-dev: [Ethereal-dev] Process information patch

Note: This archive is from the project's previous web site, ethereal.com. This list is no longer active.

From: Gerald Combs <[email protected]>
Date: Sun, 14 Oct 2001 21:06:29 -0500 (CDT)
A while back Ryan Schneider suggested adding the ability to display
information about the process associated with a particular packet.
Attached is some code that does just that for TCP and UDP packets under
Linux. It only works during live capture, and only when real-time data
display is enabled.

It's currently a proof-of-concept, and is in no way ready for production.
I'm sending it to the list in case anyone wants to play with it.

Index: packet-frame.c
===================================================================
RCS file: /usr/local/cvsroot/ethereal/packet-frame.c,v
retrieving revision 1.9
diff -u -r1.9 packet-frame.c
--- packet-frame.c	2001/09/14 07:10:05	1.9
+++ packet-frame.c	2001/10/15 01:19:43
@@ -34,6 +34,12 @@
 #include "tvbuff.h"
 #include "packet-frame.h"
 
+#if defined (linux)
+# include "conversation.h"
+# include "resolv.h"
+# include "process_info.h"
+#endif
+
 static int proto_frame = -1;
 static int hf_frame_arrival_time = -1;
 static int hf_frame_time_delta = -1;
@@ -42,6 +48,9 @@
 static int hf_frame_packet_len = -1;
 static int hf_frame_capture_len = -1;
 static int hf_frame_p2p_dir = -1;
+static int hf_frame_proc_uid = -1;
+static int hf_frame_proc_pid = -1;
+static int hf_frame_proc_cmd = -1;
 static int proto_short = -1;
 int proto_malformed = -1;
 
@@ -62,6 +71,10 @@
 	proto_item	*ti;
 	nstime_t	ts;
 	int		cap_len, pkt_len;
+#ifdef linux
+	conversation_t *conversation;
+	process_info_t *proc_info;
+#endif
 
 	pinfo->current_proto = "Frame";
 
@@ -143,6 +156,25 @@
 				"[Malformed Frame: %s]", pinfo->current_proto );
 	}
 	ENDTRY;
+
+#ifdef linux
+	if (pinfo->ptype == PT_TCP || pinfo->ptype == PT_UDP) {
+		conversation = find_conversation(&pinfo->src, &pinfo->dst, 
+			pinfo->ptype, pinfo->srcport, pinfo->destport, 0);
+		if (conversation) {
+			proc_info = conversation_get_proto_data(conversation, proto_frame);
+			if (tree && proc_info->found) {
+	  			proto_tree_add_uint(fh_tree, hf_frame_proc_uid, tvb,
+					0, 0, proc_info->uid);
+	  			proto_tree_add_uint(fh_tree, hf_frame_proc_pid, tvb,
+					0, 0, proc_info->pid);
+				proto_tree_add_string(fh_tree, hf_frame_proc_cmd, tvb,
+					0, 0, proc_info->cmd);
+			}
+		}
+	}
+#endif 
+
 }
 
 void
@@ -177,6 +209,18 @@
 
 		{ &hf_frame_p2p_dir,
 		{ "Point-to-Point Direction",	"frame.p2p_dir", FT_UINT8, BASE_DEC, VALS(p2p_dirs), 0x0,
+			"", HFILL }},
+
+		{ &hf_frame_proc_uid,
+		{ "Process UID",	"frame.proc_uid", FT_UINT32, BASE_DEC, NULL, 0x0,
+			"", HFILL }},
+
+		{ &hf_frame_proc_pid,
+		{ "Process PID",	"frame.proc_pid", FT_UINT32, BASE_DEC, NULL, 0x0,
+			"", HFILL }},
+
+		{ &hf_frame_proc_cmd,
+		{ "Process Command",	"frame.proc_cmd", FT_STRING, BASE_NONE, NULL, 0x0,
 			"", HFILL }},
 	};
 	static gint *ett[] = {
Index: packet-tcp.c
===================================================================
RCS file: /usr/local/cvsroot/ethereal/packet-tcp.c,v
retrieving revision 1.111
diff -u -r1.111 packet-tcp.c
--- packet-tcp.c	2001/10/01 08:29:35	1.111
+++ packet-tcp.c	2001/10/15 01:19:44
@@ -44,6 +44,8 @@
 #endif
 
 #include "resolv.h"
+#include "packet_info.h"
+#include "process_info.h"
 #include "ipproto.h"
 #include "follow.h"
 #include "prefs.h"
@@ -1067,7 +1069,14 @@
   pinfo->ptype = PT_TCP;
   pinfo->srcport = th_sport;
   pinfo->destport = th_dport;
+
+  /* Get the proccess information */  
+#ifdef linux
+
+  get_linux_proc_info(pinfo);
   
+#endif
+
   /* Check the packet length to see if there's more data
      (it could be an ACK-only packet) */
   length_remaining = tvb_length_remaining(tvb, offset);
Index: packet-udp.c
===================================================================
RCS file: /usr/local/cvsroot/ethereal/packet-udp.c,v
retrieving revision 1.95
diff -u -r1.95 packet-udp.c
--- packet-udp.c	2001/09/27 10:19:14	1.95
+++ packet-udp.c	2001/10/15 01:19:44
@@ -216,6 +216,12 @@
     }
   }
 
+#ifdef linux
+
+  get_linux_proc_info(pinfo);
+
+#endif
+
   /* Skip over header */
   offset += 8;
 
Index: epan/Makefile.am
===================================================================
RCS file: /usr/local/cvsroot/ethereal/epan/Makefile.am,v
retrieving revision 1.27
diff -u -r1.27 Makefile.am
--- Makefile.am	2001/09/14 07:33:04	1.27
+++ Makefile.am	2001/10/15 01:19:44
@@ -67,6 +67,8 @@
 	pint.h			\
 	plugins.c		\
 	plugins.h		\
+	process_info.c	\
+	process_info.h	\
 	proto.c			\
 	proto.h			\
 	resolv.c		\
/* process_info.c
 * Routines for process information lookup
 *
 * $Id$
 *
 * Ethereal - Network traffic analyzer
 * By Gerald Combs <[email protected]>
 * Copyright 2001 Gerald Combs
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#if defined (linux)

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <stdio.h>

/* General includes */
#include "packet.h"
#include "conversation.h"
#include "packet_info.h"
#include "process_info.h"

/* Linux-specific includes */
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include <ctype.h>



/*
 * Given a pinfo struct with a TCP or UDP socket pair defined, look
 * up the corresponding process information.
 *
 * /proc/net/{tcp|udp} provides the UID associated with each connection, 
 * but not the PID.  To get the process info, we have to inspect each
 * /proc/nnn/fd/mm to find the link to our socket inode.  
 *
 * In order to make things a little more efficient, we skip PID directories
 * that don't match our UID.
 *
 * XXX - Should we also skip the first few (3) file descriptors?
 */

#define MAX_PROC_STR_LEN 32 /* /proc/1234/fd/1234 or socket:[12345] */
#define MAX_PROC_NET_HDR_LEN 150
#define PROC_NET_HDR "  sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode                               \n"
#define PROC_NET_TCP_PATH "/proc/net/tcp"
#define PROC_NET_UDP_PATH "/proc/net/udp"

extern gboolean
get_linux_proc_info(packet_info *pinfo) {
  DIR *dir, *pdir;
  FILE *fp;
  struct dirent *de, *pde;
  struct stat st_buf;
  char big_str[MAX_PROC_NET_HDR_LEN];
  char *d_name, path[MAX_PROC_STR_LEN], fdpath[MAX_PROC_STR_LEN];
  char lbuf[MAX_PROC_STR_LEN], linkstr[MAX_PROC_STR_LEN];
  int len = 0;
  guint32 locaddr, remaddr, uid, inode = 0;
  guint16 locport, remport;
  conversation_t *conversation;
  static int proto_frame = -1;
/*   uid_t uid;
  ino_t inode = 0;
 */  
  static process_info_t *proc_not_found = NULL;
  process_info_t *proc_info;

  /* Find the frame protocol number */
  proto_frame = proto_get_id_by_filter_name("frame");

  /* XXX - Add IPv6 support */
  if (pinfo->net_src.type != AT_IPv4 || pinfo->net_dst.type != AT_IPv4) {
    return FALSE;
  }

  if (pinfo->ptype != PT_TCP && pinfo->ptype != PT_UDP) {
    return FALSE;
  }
  
  /* Find out if the process info has already been looked up.
     If not, create a new conversation. */
   
  conversation = find_conversation(&pinfo->src, &pinfo->dst, pinfo->ptype,
    pinfo->srcport, pinfo->destport, 0);
  if (conversation) {
    proc_info = conversation_get_proto_data(conversation, proto_frame);
    if (proc_info->tries < 1)
      return proc_info->found;
  } else {
    proc_info = g_malloc(sizeof(process_info_t));
    proc_info->found = FALSE;
    proc_info->tries = 4;
    proc_info->uid = 0;
    proc_info->pid = 0;
    proc_info->cmd = NULL;
    conversation = conversation_new(&pinfo->src, &pinfo->dst, pinfo->ptype,
      pinfo->srcport, pinfo->destport, 0);
    conversation_add_proto_data(conversation, proto_frame, (void *) proc_info);
  }    

  /* Initialize proc_not_found if needed. */
  if (!proc_not_found) {
    proc_not_found = g_malloc(sizeof(process_info_t));
    proc_not_found->found = FALSE;
    proc_not_found->tries = 0;
    proc_not_found->uid = 0;
    proc_not_found->pid = 0;
    proc_not_found->cmd = NULL;
  }    

  if (pinfo->ptype == PT_TCP) {
    fp = fopen(PROC_NET_TCP_PATH, "r");
  } else if (pinfo->ptype == PT_UDP) {
    fp = fopen(PROC_NET_UDP_PATH, "r");
  } else {
    return FALSE;
  }
  
  if (fgets(big_str, MAX_PROC_NET_HDR_LEN - 1, fp) == NULL) {
    return FALSE;
  }
  if (strcmp(big_str, PROC_NET_HDR) != 0) {
    return FALSE;
  }
  
  /* From net/ipv4/proc.c in the Linux 2.2.14 sources: 
   *
   * sprintf(tmpbuf, "%4d: %08lX:%04X %08lX:%04X"
   *       " %02X %08X:%08X %02X:%08lX %08X %5d %8d %u"
   *
   * The local and remote addresses that are fed to sprintf are in network byte
   * order.  On little endian systems, this means they are printed in /proc/net/tcp in
   * reverse order.  However, scanning them back in should store them in network byte
   * order.
   */

  while (fgets(big_str, MAX_PROC_NET_HDR_LEN - 1, fp) != NULL) {
    /*
     * Sheesh.  For some reason, fscanf _won't_ scan 32-bit hex values on 
     * my system, but sscanf _will_.  
     * - gcc
     */
    len = sscanf(big_str, "%*d: %8x:%x %8x:%4x"
      " %*2x %*8x:%*8x %*2x:%*8x %*8x %d %*d %u \n", 
      &locaddr, (unsigned int *) &locport, &remaddr, (unsigned int *) &remport, &uid, &inode);
    if (len < 6)
      break;

    if ((memcmp(pinfo->net_src.data, &locaddr, 4) == 0 &&
         memcmp(&pinfo->srcport, &locport, 2)     == 0 &&
         memcmp(pinfo->net_dst.data, &remaddr, 4) == 0 &&
         memcmp(&pinfo->destport, &remport, 2)    == 0) ||
	(memcmp(pinfo->net_dst.data, &locaddr, 4) == 0 &&
         memcmp(&pinfo->destport, &locport, 2)    == 0 &&
         memcmp(pinfo->net_src.data, &remaddr, 4) == 0 &&
	 memcmp(&pinfo->srcport, &remport, 2)     == 0)) {
      break;
    }
    len = 0;
  }
  fclose(fp);

  if (len < 6) {
    goto not_found;
  }
  
  sprintf(linkstr, "socket:[%d]", inode);  /* The point of this exercise */
  dir = opendir("/proc");
  while ((de = readdir(dir)) != NULL) {
    d_name = de->d_name;
    while (*d_name != '\0') {  /* Is the name all digits, i.e. a PID? */
      if (! isdigit(*d_name))
        break;
      d_name++;
    }
    if (*d_name == '\0') {
      snprintf(path, MAX_PROC_STR_LEN, "/proc/%s", de->d_name);
      path[MAX_PROC_STR_LEN - 1] = '\0';
      snprintf(path, MAX_PROC_STR_LEN, "/proc/%s/fd", de->d_name);
      path[MAX_PROC_STR_LEN - 1] = '\0';
      if ((pdir = opendir(path)) != NULL) {
        while ((pde = readdir(pdir)) != NULL) {
          snprintf(fdpath, MAX_PROC_STR_LEN, "%s/%s", path, pde->d_name);
	  fdpath[MAX_PROC_STR_LEN - 1] = '\0';
	  len = readlink(fdpath, lbuf, MAX_PROC_STR_LEN);
	  if (len > 0 && len < MAX_PROC_STR_LEN) {
	    lbuf[len] = '\0';
	    if (strcmp(lbuf, linkstr) == 0) {
	      /* We have a winner */
	      /* For setuid programs, the UID returned by /proc/net/{tcp|udp} shows
	       * the set UID and not the original UID.
	       */
	      snprintf(path, MAX_PROC_STR_LEN, "/proc/%s", de->d_name);
	      path[MAX_PROC_STR_LEN - 1] = '\0';
	      if(stat(path, &st_buf) != 0) {
	        closedir(pdir);
		closedir(dir);
	        goto not_found;
	      }
	      uid = st_buf.st_uid;
	      snprintf(path, MAX_PROC_STR_LEN, "/proc/%s/cmdline", de->d_name);
	      path[MAX_PROC_STR_LEN - 1] = '\0';
	      fp = fopen(path, "r");
	      if (fp == NULL) {
	        closedir(pdir);
		closedir(dir);
	        goto not_found;
	      }
	      if (fgets(big_str, MAX_PROC_NET_HDR_LEN - 1, fp) != NULL) {
	      /* XXX - Parse out the command args.  The buffer read contains 
	         null-separated strings */
		proc_info->found = TRUE;
		proc_info->tries = 0;
		proc_info->uid = uid;
		proc_info->pid = atoi(de->d_name);
		proc_info->cmd = g_strdup(big_str);
		return TRUE;
	      }
	      fclose(fp);
	    }
	  }
	}
	closedir(pdir);
      }
    }
  }
  closedir(dir);

  not_found:

  if (conversation) {
    if (proc_info->tries > 0) {
      proc_info->tries--;
      return FALSE;
    }
    g_free(proc_info);
  }
  conversation = conversation_new(&pinfo->src, &pinfo->dst, pinfo->ptype,
    pinfo->srcport, pinfo->destport, 0);
  conversation_add_proto_data(conversation, proto_frame, (void *) proc_not_found);
  return FALSE;
}

/* XXX - add clear_linux_proc_info, which frees up the command strings. */

#endif
/* process_info.h
 * Definitions for process information lookup
 *
 * $Id$
 *
 * Ethereal - Network traffic analyzer
 * By Gerald Combs <[email protected]>
 * Copyright 2001 Gerald Combs
 *
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#ifndef __PROCESS_INFO_H__
#define __PROCESS_INFO_H__

#ifdef linux

/*
 * For a given populated pinfo struct, find the process and user info 
 * associated with the socket pair.
 */

typedef struct {
  gboolean found;
  gint tries;
  guint32 uid;
  guint32 pid;
  gchar *cmd;  /* XXX - put in a lookup table later. */
} process_info_t;

gboolean get_linux_proc_info(packet_info *pinfo);

#endif

#endif /* __PROCESS_INFO_H__ */