/* net.c - core analysis suite
 *
 * Copyright (C) 1999, 2000, 2001, 2002 Mission Critical Linux, Inc.
 * Copyright (C) 2002-2016 David Anderson
 * Copyright (C) 2002-2016 Red Hat, Inc. All rights reserved.
 *
 * 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.
 */

#include "defs.h"
#include <netinet/in.h>
#include <netdb.h>
#include <net/if_arp.h>
#include <arpa/inet.h>

/*
 *  Cache values we need that can change based on OS version, or any other
 *  variables static to this file.  These are setup in net_init().  Dump 
 *  the table during runtime via "help -n".
 */
struct net_table {
	ulong flags;
        char *netdevice;    /* name of net device */
	char *dev_name_t;   /* readmem ID's */
        char *dev_type_t;
	char *dev_addr_t;
	long dev_name;
	long dev_next;
	long dev_type;
	long dev_addr_len;
	long dev_ip_ptr;
	long in_device_ifa_list;
	long in_ifaddr_ifa_next;
	long in_ifaddr_ifa_address;
	int net_device_name_index;
} net_table = { 0 };

struct net_table *net = &net_table;

#define NETDEV_INIT       (0x1)
#define STRUCT_DEVICE     (0x2)
#define STRUCT_NET_DEVICE (0x4)
#define SOCK_V1           (0x8)
#define SOCK_V2           (0x10)
#define NO_INET_SOCK      (0x20)

#define	DEV_NAME_MAX	100
struct devinfo {
	char		dev_name[DEV_NAME_MAX];
	unsigned char	dev_addr_len;
	short		dev_type;
};

#define BYTES_IP_ADDR	15	/* bytes to print IP addr (xxx.xxx.xxx.xxx) */
#define BYTES_PORT_NUM	5	/* bytes to print port number */
/* bytes needed for <ip address>:<port> notation */
#define BYTES_IP_TUPLE	(BYTES_IP_ADDR + BYTES_PORT_NUM + 1)

static void show_net_devices(ulong);
static void show_net_devices_v2(ulong);
static void show_net_devices_v3(ulong);
static void print_neighbour_q(ulong, int);
static void get_netdev_info(ulong, struct devinfo *);
static void get_device_name(ulong, char *);
static long get_device_address(ulong, char **, long);
static void get_device_ip6_address(ulong, char **, long);
static void get_sock_info(ulong, char *);
static void dump_arp(void);
static void arp_state_to_flags(unsigned char);
static void dump_ether_hw(unsigned char *, int);
static void dump_sockets(ulong, struct reference *);
static int  sym_socket_dump(ulong, int, int, ulong, struct reference *);
static void dump_hw_addr(unsigned char *, int);
static char *dump_in6_addr_port(uint16_t *, uint16_t, char *, int *);


#define MK_TYPE_T(f,s,m)						\
do {									\
	(f) = malloc(strlen(s) + strlen(m) + 2);			\
	if ((f) == NULL) {						\
		error(WARNING, "malloc fail for type %s.%s", (s), (m));	\
	} else {							\
		sprintf((f), "%s %s", (s), (m));			\
	}								\
} while(0)

void
net_init(void)
{
	/*
	 * Note the order of the following checks.  The device struct was
	 * renamed to net_device in 2.3, but there may be another struct
	 * called 'device' so we check for the new one first.
	 */
	STRUCT_SIZE_INIT(net_device, "net_device");

	if (VALID_STRUCT(net_device)) {
		net->netdevice = "net_device";
		net->dev_next = MEMBER_OFFSET_INIT(net_device_next,
			"net_device", "next");
		net->dev_name = MEMBER_OFFSET_INIT(net_device_name, 
			"net_device", "name");
		net->dev_type = MEMBER_OFFSET_INIT(net_device_type,
			"net_device", "type");
                net->dev_addr_len = MEMBER_OFFSET_INIT(net_device_addr_len,
			"net_device", "addr_len");
		net->dev_ip_ptr = MEMBER_OFFSET_INIT(net_device_ip_ptr,
			"net_device", "ip_ptr");
		MEMBER_OFFSET_INIT(net_device_dev_list, "net_device", "dev_list");
		MEMBER_OFFSET_INIT(net_device_ip6_ptr, "net_device", "ip6_ptr");
		MEMBER_OFFSET_INIT(inet6_dev_addr_list, "inet6_dev", "addr_list");
		MEMBER_OFFSET_INIT(inet6_ifaddr_addr, "inet6_ifaddr", "addr");
		MEMBER_OFFSET_INIT(inet6_ifaddr_if_list, "inet6_ifaddr", "if_list");
		MEMBER_OFFSET_INIT(inet6_ifaddr_if_next, "inet6_ifaddr", "if_next");
		MEMBER_OFFSET_INIT(in6_addr_in6_u, "in6_addr", "in6_u");

		MEMBER_OFFSET_INIT(net_dev_base_head, "net", "dev_base_head");
		ARRAY_LENGTH_INIT(net->net_device_name_index,
			net_device_name, "net_device.name", NULL, sizeof(char));
		net->flags |= (NETDEV_INIT|STRUCT_NET_DEVICE);
	} else {
		STRUCT_SIZE_INIT(device, "device");
		if (VALID_STRUCT(device)) {
			net->netdevice = "device";
			net->dev_next = MEMBER_OFFSET_INIT(device_next, 
				"device", "next");
			net->dev_name = MEMBER_OFFSET_INIT(device_name, 
				"device", "name");
	                net->dev_type = MEMBER_OFFSET_INIT(device_type, 
				"device", "type");
			net->dev_ip_ptr = MEMBER_OFFSET_INIT(device_ip_ptr, 
				"device", "ip_ptr");
	                net->dev_addr_len = MEMBER_OFFSET_INIT(device_addr_len, 
				"device", "addr_len");
			net->flags |= (NETDEV_INIT|STRUCT_DEVICE);
		} else 
			error(WARNING, 
				"net_init: unknown device type for net device");
	}
	if (VALID_MEMBER(task_struct_nsproxy))
		MEMBER_OFFSET_INIT(nsproxy_net_ns, "nsproxy", "net_ns");

	if (net->flags & NETDEV_INIT) {
		MK_TYPE_T(net->dev_name_t, net->netdevice, "name");
		MK_TYPE_T(net->dev_type_t, net->netdevice, "type");
		MK_TYPE_T(net->dev_addr_t, net->netdevice, "addr_len");

		MEMBER_OFFSET_INIT(socket_sk, "socket", "sk");
		MEMBER_OFFSET_INIT(neighbour_next, "neighbour", "next");
        	MEMBER_OFFSET_INIT(neighbour_primary_key,  
			"neighbour", "primary_key");
        	MEMBER_OFFSET_INIT(neighbour_ha, "neighbour", "ha");
        	MEMBER_OFFSET_INIT(neighbour_dev, "neighbour", "dev");
        	MEMBER_OFFSET_INIT(neighbour_nud_state,  
			"neighbour", "nud_state");
		MEMBER_OFFSET_INIT(neigh_table_nht_ptr, "neigh_table", "nht");
		if (VALID_MEMBER(neigh_table_nht_ptr)) {
			MEMBER_OFFSET_INIT(neigh_table_hash_mask,
				"neigh_hash_table", "hash_mask");
			MEMBER_OFFSET_INIT(neigh_table_hash_shift,
				"neigh_hash_table", "hash_shift");
			MEMBER_OFFSET_INIT(neigh_table_hash_buckets,
				"neigh_hash_table", "hash_buckets");
		} else {
			MEMBER_OFFSET_INIT(neigh_table_hash_buckets,
				"neigh_table", "hash_buckets");
			MEMBER_OFFSET_INIT(neigh_table_hash_mask,
				"neigh_table", "hash_mask");
		}
		MEMBER_OFFSET_INIT(neigh_table_key_len,
			"neigh_table", "key_len");

        	MEMBER_OFFSET_INIT(in_device_ifa_list,  
			"in_device", "ifa_list");
        	MEMBER_OFFSET_INIT(in_ifaddr_ifa_next,  
			"in_ifaddr", "ifa_next");
        	MEMBER_OFFSET_INIT(in_ifaddr_ifa_address, 
			"in_ifaddr", "ifa_address");

		STRUCT_SIZE_INIT(sock, "sock");

                MEMBER_OFFSET_INIT(sock_family, "sock", "family");
		if (VALID_MEMBER(sock_family)) {
                	MEMBER_OFFSET_INIT(sock_daddr, "sock", "daddr");
                	MEMBER_OFFSET_INIT(sock_rcv_saddr, "sock", "rcv_saddr");
                	MEMBER_OFFSET_INIT(sock_dport, "sock", "dport");
                	MEMBER_OFFSET_INIT(sock_sport, "sock", "sport");
                	MEMBER_OFFSET_INIT(sock_num, "sock", "num");
                	MEMBER_OFFSET_INIT(sock_type, "sock", "type");
			net->flags |= SOCK_V1;

		} else {
			/*
			 * struct sock {
        		 *	struct sock_common      __sk_common;
			 * #define sk_family __sk_common.skc_family
			 *      ...
			 */
			MEMBER_OFFSET_INIT(sock_common_skc_family,
				"sock_common", "skc_family");
			MEMBER_OFFSET_INIT(sock_sk_type, "sock", "sk_type");
			MEMBER_OFFSET_INIT(sock_sk_common, "sock", "__sk_common");
			MEMBER_OFFSET_INIT(sock_common_skc_v6_daddr, "sock_common", "skc_v6_daddr");
			MEMBER_OFFSET_INIT(sock_common_skc_v6_rcv_saddr, "sock_common", "skc_v6_rcv_saddr");
			/*
			 *  struct inet_sock {
        		 *	struct sock       sk;
        		 *	struct ipv6_pinfo *pinet6;
        		 *	struct inet_opt   inet;
			 *  };
			 */
			STRUCT_SIZE_INIT(inet_sock, "inet_sock");
			STRUCT_SIZE_INIT(socket, "socket");

			if (STRUCT_EXISTS("inet_opt")) {
				MEMBER_OFFSET_INIT(inet_sock_inet, "inet_sock", "inet");
				MEMBER_OFFSET_INIT(inet_opt_daddr, "inet_opt", "daddr");
				MEMBER_OFFSET_INIT(inet_opt_rcv_saddr, "inet_opt", "rcv_saddr");
				MEMBER_OFFSET_INIT(inet_opt_dport, "inet_opt", "dport");
				MEMBER_OFFSET_INIT(inet_opt_sport, "inet_opt", "sport");
				MEMBER_OFFSET_INIT(inet_opt_num, "inet_opt", "num");
			} else {	/* inet_opt moved to inet_sock */
				ASSIGN_OFFSET(inet_sock_inet) = 0;
				if (MEMBER_EXISTS("inet_sock", "daddr")) {
					MEMBER_OFFSET_INIT(inet_opt_daddr, "inet_sock", "daddr");
					MEMBER_OFFSET_INIT(inet_opt_rcv_saddr, "inet_sock", "rcv_saddr");
					MEMBER_OFFSET_INIT(inet_opt_dport, "inet_sock", "dport");
					MEMBER_OFFSET_INIT(inet_opt_sport, "inet_sock", "sport");
					MEMBER_OFFSET_INIT(inet_opt_num, "inet_sock", "num");
				} else if (MEMBER_EXISTS("inet_sock", "inet_daddr")) {
					MEMBER_OFFSET_INIT(inet_opt_daddr, "inet_sock", "inet_daddr");
					MEMBER_OFFSET_INIT(inet_opt_rcv_saddr, "inet_sock", "inet_rcv_saddr");
					MEMBER_OFFSET_INIT(inet_opt_dport, "inet_sock", "inet_dport");
					MEMBER_OFFSET_INIT(inet_opt_sport, "inet_sock", "inet_sport");
					MEMBER_OFFSET_INIT(inet_opt_num, "inet_sock", "inet_num");
				} else if ((MEMBER_OFFSET("inet_sock", "sk") == 0) &&
				    (MEMBER_OFFSET("sock", "__sk_common") == 0)) {
					MEMBER_OFFSET_INIT(inet_opt_daddr, "sock_common", "skc_daddr");
					if (INVALID_MEMBER(inet_opt_daddr))
						ANON_MEMBER_OFFSET_INIT(inet_opt_daddr, "sock_common", 
							"skc_daddr");
					MEMBER_OFFSET_INIT(inet_opt_rcv_saddr, "sock_common", "skc_rcv_saddr");
					if (INVALID_MEMBER(inet_opt_rcv_saddr))
						ANON_MEMBER_OFFSET_INIT(inet_opt_rcv_saddr, "sock_common",
							"skc_rcv_saddr");
					MEMBER_OFFSET_INIT(inet_opt_dport, "inet_sock", "inet_dport");
					if (INVALID_MEMBER(inet_opt_dport)) {
						MEMBER_OFFSET_INIT(inet_opt_dport, "sock_common", 
							"skc_dport");
						if (INVALID_MEMBER(inet_opt_dport))
							ANON_MEMBER_OFFSET_INIT(inet_opt_dport, "sock_common", 
								"skc_dport");
					}
					MEMBER_OFFSET_INIT(inet_opt_sport, "inet_sock", "inet_sport");
					MEMBER_OFFSET_INIT(inet_opt_num, "inet_sock", "inet_num");
					if (INVALID_MEMBER(inet_opt_num)) {
						MEMBER_OFFSET_INIT(inet_opt_num, "sock_common", "skc_num");
						if (INVALID_MEMBER(inet_opt_num))
							ANON_MEMBER_OFFSET_INIT(inet_opt_num, "sock_common", 
							"skc_num");
					}
				}
			}	

			if (VALID_STRUCT(inet_sock) && 
			    INVALID_MEMBER(inet_sock_inet)) {
				/*
				 *  gdb can't seem to figure out the inet_sock
				 *  in later 2.6 kernels, returning this:
				 *
				 *  struct inet_sock {
				 *      <no data fields>
			         *  }
				 *  
				 *  It does know the struct size, so kludge it
			         *  to subtract the size of the inet_opt struct
				 *  from the size of the containing inet_sock.
				 */
				net->flags |= NO_INET_SOCK;
				ASSIGN_OFFSET(inet_sock_inet) = 
				    SIZE(inet_sock) - STRUCT_SIZE("inet_opt");
			}

			/* 
			 *  If necessary, set inet_sock size and inet_sock_inet offset,
			 *  accounting for the configuration-dependent, intervening,
			 *  struct ipv6_pinfo pointer located in between the sock and 
			 *  inet_opt members of the inet_sock.
			 */
			if (!VALID_STRUCT(inet_sock)) 
			{
				if (symbol_exists("tcpv6_protocol") && 
				    symbol_exists("udpv6_protocol")) {
					ASSIGN_SIZE(inet_sock) = SIZE(sock) + 
						sizeof(void *) + STRUCT_SIZE("inet_opt");
					ASSIGN_OFFSET(inet_sock_inet) = SIZE(sock) + 
						sizeof(void *);
				} else {
					ASSIGN_SIZE(inet_sock) = SIZE(sock) + 
						STRUCT_SIZE("inet_opt");
					ASSIGN_OFFSET(inet_sock_inet) = SIZE(sock);
				}
			}

			MEMBER_OFFSET_INIT(ipv6_pinfo_rcv_saddr, "ipv6_pinfo", "rcv_saddr");
			MEMBER_OFFSET_INIT(ipv6_pinfo_daddr, "ipv6_pinfo", "daddr");
			STRUCT_SIZE_INIT(in6_addr, "in6_addr");
			MEMBER_OFFSET_INIT(socket_alloc_vfs_inode, "socket_alloc", "vfs_inode");

			net->flags |= SOCK_V2;
		}
	}	
}

/*
 * The net command...
 */

#define NETOPTS	  "N:asSR:xdn"
#define s_FLAG FOREACH_s_FLAG
#define S_FLAG FOREACH_S_FLAG
#define x_FLAG FOREACH_x_FLAG
#define d_FLAG FOREACH_d_FLAG

#define NET_REF_FOUND             (0x1)
#define NET_REF_HEXNUM            (0x2)
#define NET_REF_DECNUM            (0x4)
#define NET_TASK_HEADER_PRINTED   (0x8)
#define NET_SOCK_HEADER_PRINTED  (0x10)
#define NET_REF_FOUND_ITEM       (0x20)

#define NET_REFERENCE_CHECK(X)   (X)
#define NET_REFERENCE_FOUND(X)   ((X) && ((X)->cmdflags & NET_REF_FOUND))

void
cmd_net(void)
{
	int c;
	ulong sflag, nflag, aflag;
	ulong value;
	ulong task;
	struct task_context *tc = NULL;
	struct in_addr in_addr;
	struct reference reference, *ref;

	if (!(net->flags & NETDEV_INIT)) 
		error(FATAL, "net subsystem not initialized!");

	ref = NULL;
	sflag = nflag = aflag = 0;
	task = pid_to_task(0);

	while ((c = getopt(argcnt, args, NETOPTS)) != EOF) {
		switch (c) {
		case 'R':
			if (ref)
				error(INFO, "only one -R option allowed\n");
			else {
				ref = &reference;
				BZERO(ref, sizeof(struct reference));
				ref->str = optarg;
			}
			break;

		case 'a':
			dump_arp();
			aflag++;
			break;

		case 'N':
			value = stol(optarg, FAULT_ON_ERROR, NULL);
			in_addr.s_addr = (in_addr_t)value;
			fprintf(fp, "%s\n", inet_ntoa(in_addr));
			return;

		case 's':
			if (sflag & S_FLAG)
				error(INFO, 
				    "only one -s or -S option allowed\n");
			else
				sflag |= s_FLAG;
		        break;

		case 'S':
			if (sflag & s_FLAG)
				error(INFO, 
				    "only one -s or -S option allowed\n");
			else
				sflag |= S_FLAG;
            		break;

		case 'x':
			if (sflag & d_FLAG)
				error(FATAL,
					"-d and -x are mutually exclusive\n");
			sflag |= x_FLAG;
			break;

		case 'd':
			if (sflag & x_FLAG)
				error(FATAL,
					"-d and -x are mutually exclusive\n");
			sflag |= d_FLAG;
			break;

		case 'n':
			nflag = 1;
			task = CURRENT_TASK();
			if (args[optind]) {
				switch (str_to_context(args[optind],
					 &value, &tc)) {
				case STR_PID:
				case STR_TASK:
					task = tc->task;
					break;
				case STR_INVALID:
					error(FATAL, "invalid task or pid value: %s\n", args[optind]);
				}
			}
			break;

		default:
			argerrs++;
			break;
		}
	}

	if (argerrs) 
		cmd_usage(pc->curcmd, SYNOPSIS);

	if (sflag & (s_FLAG|S_FLAG))
		dump_sockets(sflag, ref);
	else {
		if ((argcnt == 1) || nflag)
			show_net_devices(task);
		else if (!aflag)
			cmd_usage(pc->curcmd, SYNOPSIS);
	}
}

/*
 *  Just display the address and name of each net device.
 */

static void
show_net_devices(ulong task)
{
	ulong next;
	long flen;
	char *buf;
	long buflen = BUFSIZE;

	if (symbol_exists("dev_base_head")) {
		show_net_devices_v2(task);
		return;
	} else if (symbol_exists("init_net")) {
		show_net_devices_v3(task);
		return;
	}

	if (!symbol_exists("dev_base"))
		error(FATAL, "dev_base, dev_base_head or init_net do not exist!\n");

	get_symbol_data("dev_base", sizeof(void *), &next);

	if (!net->netdevice || !next)
		return;

	buf = GETBUF(buflen);
	flen = MAX(VADDR_PRLEN, strlen(net->netdevice));

	fprintf(fp, "%s  NAME       IP ADDRESS(ES)\n",
		mkstring(upper_case(net->netdevice, buf), 
			flen, CENTER|LJUST, NULL));

	do {
                fprintf(fp, "%s  ", 
                    mkstring(buf, flen, CENTER|RJUST|LONG_HEX, MKSTR(next)));

		get_device_name(next, buf);
		fprintf(fp, "%-10s ", buf);

		get_device_address(next, &buf, buflen);
		get_device_ip6_address(next, &buf, buflen);
		fprintf(fp, "%s\n", buf);

        	readmem(next+net->dev_next, KVADDR, &next, 
			sizeof(void *), "(net_)device.next", FAULT_ON_ERROR);
	} while (next);

	FREEBUF(buf);
}

static void
show_net_devices_v2(ulong task)
{
	struct list_data list_data, *ld;
	char *net_device_buf;
	char *buf;
	long buflen = BUFSIZE;
	int ndevcnt, i;
	long flen;

	if (!net->netdevice) /* initialized in net_init() */
		return;

	buf = GETBUF(buflen);
	flen = MAX(VADDR_PRLEN, strlen(net->netdevice));

	fprintf(fp, "%s  NAME       IP ADDRESS(ES)\n",
		mkstring(upper_case(net->netdevice, buf), 
			flen, CENTER|LJUST, NULL));

	net_device_buf = GETBUF(SIZE(net_device));

	ld =  &list_data;
	BZERO(ld, sizeof(struct list_data));
	ld->flags |= LIST_ALLOCATE;
	get_symbol_data("dev_base_head", sizeof(void *), &ld->start);
	ld->end = symbol_value("dev_base_head");
	ld->list_head_offset = OFFSET(net_device_dev_list);

	ndevcnt = do_list(ld);

	for (i = 0; i < ndevcnt; ++i) {
		readmem(ld->list_ptr[i], KVADDR, net_device_buf,
			SIZE(net_device), "net_device buffer",
			FAULT_ON_ERROR);

                fprintf(fp, "%s  ",
			mkstring(buf, flen, CENTER|RJUST|LONG_HEX,
			MKSTR(ld->list_ptr[i])));

		get_device_name(ld->list_ptr[i], buf);
		fprintf(fp, "%-10s ", buf);

		get_device_address(ld->list_ptr[i], &buf, buflen);
		get_device_ip6_address(ld->list_ptr[i], &buf, buflen);
		fprintf(fp, "%s\n", buf);
	}
	
	FREEBUF(ld->list_ptr);
	FREEBUF(net_device_buf);
	FREEBUF(buf);
}

static void
show_net_devices_v3(ulong task)
{
	ulong nsproxy_p, net_ns_p;
	struct list_data list_data, *ld;
	char *net_device_buf;
	char *buf;
	long buflen = BUFSIZE;
	int ndevcnt, i;
	long flen;

	if (!net->netdevice) /* initialized in net_init() */
		return;

	buf = GETBUF(buflen);
	flen = MAX(VADDR_PRLEN, strlen(net->netdevice));

	fprintf(fp, "%s  NAME       IP ADDRESS(ES)\n",
		mkstring(upper_case(net->netdevice, buf), 
			flen, CENTER|LJUST, NULL));

	net_device_buf = GETBUF(SIZE(net_device));

	ld =  &list_data;
	BZERO(ld, sizeof(struct list_data));
	ld->flags |= LIST_ALLOCATE;
	if (VALID_MEMBER(nsproxy_net_ns)) {
		readmem(task + OFFSET(task_struct_nsproxy), KVADDR, &nsproxy_p,
			sizeof(ulong), "task_struct.nsproxy", FAULT_ON_ERROR);
		if (!readmem(nsproxy_p + OFFSET(nsproxy_net_ns), KVADDR, &net_ns_p,
			sizeof(ulong), "nsproxy.net_ns", RETURN_ON_ERROR|QUIET))
			error(FATAL, "cannot determine net_namespace location!\n");
	} else
		net_ns_p = symbol_value("init_net");
	ld->start = ld->end = net_ns_p + OFFSET(net_dev_base_head);
	ld->list_head_offset = OFFSET(net_device_dev_list);

	ndevcnt = do_list(ld);

	/*
	 *  Skip the first entry (init_net).
	 */
	for (i = 1; i < ndevcnt; ++i) {
		readmem(ld->list_ptr[i], KVADDR, net_device_buf,
			SIZE(net_device), "net_device buffer",
			FAULT_ON_ERROR);

                fprintf(fp, "%s  ",
			mkstring(buf, flen, CENTER|RJUST|LONG_HEX,
			MKSTR(ld->list_ptr[i])));

		get_device_name(ld->list_ptr[i], buf);
		fprintf(fp, "%-10s ", buf);

		get_device_address(ld->list_ptr[i], &buf, buflen);
		get_device_ip6_address(ld->list_ptr[i], &buf, buflen);
		fprintf(fp, "%s\n", buf);
	}
	
	FREEBUF(ld->list_ptr);
	FREEBUF(net_device_buf);
	FREEBUF(buf);
}

/*
 * Perform the actual work of dumping the ARP table...
 */
#define ARP_HEADING \
	"NEIGHBOUR        IP ADDRESS      HW TYPE    HW ADDRESS         DEVICE  STATE"

static void
dump_arp(void)
{
	ulong	arp_tbl;		/* address of arp_tbl */
	ulong	*hash_buckets;
	ulong	hash;
	long	hash_bytes;
	int	nhash_buckets = 0;
	int	key_len;
	int	i;
	int	header_printed = 0;
	int	hash_mask = 0;
	ulong	nht;

	if (!symbol_exists("arp_tbl")) 
		error(FATAL, "arp_tbl does not exist in this kernel\n");

	arp_tbl = symbol_value("arp_tbl");

	/*
	 *  NOTE: 2.6.8 -> 2.6.9 neigh_table struct changed from:
	 *
	 *    struct neighbour *hash_buckets[32];
	 *  to
	 *    struct neighbour **hash_buckets;
	 *
	 *  Use 'hash_mask' as indicator to decide if we're dealing
	 *  with an array or a pointer.
	 *
	 * Around 2.6.37 neigh_hash_table struct has been introduced
	 * and pointer to it has been added to neigh_table.
	 */
	if (VALID_MEMBER(neigh_table_nht_ptr)) {
		readmem(arp_tbl + OFFSET(neigh_table_nht_ptr),
			KVADDR, &nht, sizeof(nht),
			"neigh_table nht", FAULT_ON_ERROR);
		/* NB! Re-use of offsets like neigh_table_hash_mask
		 * with neigh_hash_table structure */
		if (VALID_MEMBER(neigh_table_hash_mask)) {
			readmem(nht + OFFSET(neigh_table_hash_mask),
				KVADDR, &hash_mask, sizeof(hash_mask),
				"neigh_hash_table hash_mask", FAULT_ON_ERROR);

			nhash_buckets = hash_mask + 1;
		} else if (VALID_MEMBER(neigh_table_hash_shift)) {
			readmem(nht + OFFSET(neigh_table_hash_shift),
				KVADDR, &hash_mask, sizeof(hash_mask),
				"neigh_hash_table hash_shift", FAULT_ON_ERROR);

			nhash_buckets = 1U << hash_mask;
		}
	} else if (VALID_MEMBER(neigh_table_hash_mask)) {
		readmem(arp_tbl + OFFSET(neigh_table_hash_mask),
			KVADDR, &hash_mask, sizeof(hash_mask),
			"neigh_table hash_mask", FAULT_ON_ERROR);

		nhash_buckets = hash_mask + 1;
	} else
		nhash_buckets = (i = ARRAY_LENGTH(neigh_table_hash_buckets)) ?
			i : get_array_length("neigh_table.hash_buckets", 
				NULL, sizeof(void *));

	if (nhash_buckets == 0) {
		option_not_supported('a');
		return;
	}

	hash_bytes = nhash_buckets * sizeof(*hash_buckets);

	hash_buckets = (ulong *)GETBUF(hash_bytes);

	readmem(arp_tbl + OFFSET(neigh_table_key_len),
		KVADDR, &key_len, sizeof(key_len),
		"neigh_table key_len", FAULT_ON_ERROR);

	if (VALID_MEMBER(neigh_table_nht_ptr)) {
		readmem(nht + OFFSET(neigh_table_hash_buckets),
			KVADDR, &hash, sizeof(hash),
			"neigh_hash_table hash_buckets ptr", FAULT_ON_ERROR);

		readmem(hash, KVADDR, hash_buckets, hash_bytes,
			"neigh_hash_table hash_buckets", FAULT_ON_ERROR);
	} else if (hash_mask) {
		readmem(arp_tbl + OFFSET(neigh_table_hash_buckets), 
			KVADDR, &hash, sizeof(hash),
			"neigh_table hash_buckets pointer", FAULT_ON_ERROR);
		
		readmem(hash,
			KVADDR, hash_buckets, hash_bytes,
			"neigh_table hash_buckets", FAULT_ON_ERROR);
	} else
		readmem(arp_tbl + OFFSET(neigh_table_hash_buckets), 
			KVADDR, hash_buckets, hash_bytes,
			"neigh_table hash_buckets", FAULT_ON_ERROR);

	for (i = 0; i < nhash_buckets; i++) {
		if (hash_buckets[i] != (ulong)NULL) {
			if (!header_printed) {
				fprintf(fp, "%s\n", ARP_HEADING);
				header_printed = 1;
			}
			print_neighbour_q(hash_buckets[i], key_len);
		}
	}

	fflush(fp);

	FREEBUF(hash_buckets);
}

/*
 * Dump out the relevant information of a neighbour structure for the
 * ARP table.
 */
static void
print_neighbour_q(ulong addr, int key_len)
{
	int i;
	ulong	dev;			/* dev address of this struct */
	unsigned char *ha_buf;		/* buffer for hardware address */
	uint	ha_size;		/* size of HW address */
	uint	ipaddr;			/* hold ipaddr (aka primary_key) */
	struct devinfo dinfo;
	unsigned char state;		/* state of ARP entry */
	struct in_addr in_addr;

	ha_size = (i = ARRAY_LENGTH(neighbour_ha)) ?
		i : get_array_length("neighbour.ha", NULL, sizeof(char));
	ha_buf = (unsigned char *)GETBUF(ha_size);

	while (addr) {
		readmem(addr + OFFSET(neighbour_primary_key), KVADDR, 
			&ipaddr, sizeof(ipaddr), "neighbour primary_key", 
			FAULT_ON_ERROR);

		readmem(addr + OFFSET(neighbour_ha), KVADDR, ha_buf, ha_size,
			"neighbour ha", FAULT_ON_ERROR);

		readmem(addr + OFFSET(neighbour_dev), KVADDR, &dev, sizeof(dev),
			"neighbour dev", FAULT_ON_ERROR);
		get_netdev_info(dev, &dinfo);

		readmem(addr + OFFSET(neighbour_nud_state), KVADDR, 
			&state, sizeof(state), "neighbour nud_state", 
			FAULT_ON_ERROR);

		in_addr.s_addr = ipaddr;
		fprintf(fp, "%-16lx %-16s", addr, inet_ntoa(in_addr));

		switch (dinfo.dev_type) {
		case ARPHRD_ETHER:
			/*
			 * Use the actual HW address size in the device struct
			 * rather than the max size of the array (as was done
			 * during the readmem() call above....
			 */
			fprintf(fp, "%-10s ", "ETHER");
			dump_ether_hw(ha_buf, dinfo.dev_addr_len);
			break;
		case ARPHRD_NETROM:
			fprintf(fp, "%-10s ", "NETROM");
			dump_hw_addr(ha_buf, dinfo.dev_addr_len);
			break;
		case ARPHRD_EETHER:
			fprintf(fp, "%-10s ", "EETHER");
			dump_hw_addr(ha_buf, dinfo.dev_addr_len);
			break;
		case ARPHRD_AX25:
			fprintf(fp, "%-10s ", "AX25");
			dump_hw_addr(ha_buf, dinfo.dev_addr_len);
			break;
		case ARPHRD_PRONET:
			fprintf(fp, "%-10s ", "PRONET");
			dump_hw_addr(ha_buf, dinfo.dev_addr_len);
			break;
		case ARPHRD_CHAOS:
			fprintf(fp, "%-10s ", "CHAOS");
			dump_hw_addr(ha_buf, dinfo.dev_addr_len);
			break;
		case ARPHRD_IEEE802:
			fprintf(fp, "%-10s ", "IEEE802");
			dump_hw_addr(ha_buf, dinfo.dev_addr_len);	
			break;
		case ARPHRD_ARCNET:
			fprintf(fp, "%-10s ", "ARCNET");
			dump_hw_addr(ha_buf, dinfo.dev_addr_len);
			break;
		case ARPHRD_APPLETLK:
			fprintf(fp, "%-10s ", "APPLETLK");
			dump_hw_addr(ha_buf, dinfo.dev_addr_len);
			break;
		case ARPHRD_DLCI:
			fprintf(fp, "%-10s ", "DLCI");
			dump_hw_addr(ha_buf, dinfo.dev_addr_len);
			break;
		case ARPHRD_METRICOM:
			fprintf(fp, "%-10s ", "METRICOM");
			dump_hw_addr(ha_buf, dinfo.dev_addr_len);
			break;
		default:
			fprintf(fp, "%-10s ", "UNKNOWN");
			dump_hw_addr(ha_buf, dinfo.dev_addr_len);
			break;
		}

		fprintf(fp, " %-6s  ", dinfo.dev_name);

		arp_state_to_flags(state);

		readmem(addr + OFFSET(neighbour_next), KVADDR, 
			&addr, sizeof(addr), "neighbour next", FAULT_ON_ERROR);
	}

	FREEBUF(ha_buf);
}

/*
 * read netdevice info.... 
 */
static void
get_netdev_info(ulong devaddr, struct devinfo *dip)
{
	short	dev_type;

	get_device_name(devaddr, dip->dev_name);

	readmem(devaddr + net->dev_type, KVADDR, 
		&dev_type, sizeof(dev_type), net->dev_type_t, FAULT_ON_ERROR);

	dip->dev_type = dev_type;

	readmem(devaddr + net->dev_addr_len, KVADDR,
		&dip->dev_addr_len, sizeof(dip->dev_addr_len), net->dev_addr_t,
		FAULT_ON_ERROR);
}

/*
 *  Get the device name.
 */
static void
get_device_name(ulong devaddr, char *buf)
{
	ulong	name_addr;

	switch (net->flags & (STRUCT_DEVICE|STRUCT_NET_DEVICE))
	{
	case STRUCT_NET_DEVICE:
		if (net->net_device_name_index > 0) {
                	readmem(devaddr + net->dev_name, KVADDR,
                        	buf, net->net_device_name_index, 
				net->dev_name_t, FAULT_ON_ERROR);
			return;
		} 

		/* fallthrough */

        case STRUCT_DEVICE:
                readmem(devaddr + net->dev_name, KVADDR,
                        &name_addr, sizeof(name_addr), net->dev_name_t,
                        FAULT_ON_ERROR);
                read_string(name_addr, buf, DEV_NAME_MAX);
                break;
	}
}

/*
 *  Get the device address.
 *
 *  {net_}device->ip_ptr points to in_device.
 *  in_device->in_ifaddr points to in_ifaddr list.
 *  in_ifaddr->ifa_address contains the address. 
 *  in_ifaddr->ifa_next points to the next in_ifaddr in the list (if any).
 * 
 */
static long
get_device_address(ulong devaddr, char **bufp, long buflen)
{
	ulong ip_ptr, ifa_list;
	struct in_addr ifa_address;
	char *buf;
	char buf2[BUFSIZE];
	long pos = 0;

	buf = *bufp;
	BZERO(buf, buflen);
	BZERO(buf2, BUFSIZE);

        readmem(devaddr + net->dev_ip_ptr, KVADDR,
        	&ip_ptr, sizeof(ulong), "ip_ptr", FAULT_ON_ERROR);

	if (!ip_ptr)
		return buflen;

        readmem(ip_ptr + OFFSET(in_device_ifa_list), KVADDR,
        	&ifa_list, sizeof(ulong), "ifa_list", FAULT_ON_ERROR);

	while (ifa_list) {
        	readmem(ifa_list + OFFSET(in_ifaddr_ifa_address), KVADDR,
        		&ifa_address, sizeof(struct in_addr), "ifa_address", 
			FAULT_ON_ERROR);

		sprintf(buf2, "%s%s", pos ? ", " : "", inet_ntoa(ifa_address));
		if (pos + strlen(buf2) >= buflen) {
			RESIZEBUF(*bufp, buflen, buflen * 2);
			buf = *bufp;
			BZERO(buf + buflen, buflen);
			buflen *= 2;
		}
		BCOPY(buf2, &buf[pos], strlen(buf2));
		pos += strlen(buf2);

        	readmem(ifa_list + OFFSET(in_ifaddr_ifa_next), KVADDR,
        		&ifa_list, sizeof(ulong), "ifa_next", FAULT_ON_ERROR);
	}
	return buflen;
}

static void
get_device_ip6_address(ulong devaddr, char **bufp, long buflen)
{
	ulong ip6_ptr = 0, pos = 0, bufsize = buflen, addr = 0;
	struct in6_addr ip6_addr;
	char *buf;
	char str[INET6_ADDRSTRLEN] = {0};
	char buffer[INET6_ADDRSTRLEN + 2] = {0};
	uint len = 0;

	buf = *bufp;
	pos = strlen(buf);

	readmem(devaddr + OFFSET(net_device_ip6_ptr), KVADDR,
		&ip6_ptr, sizeof(ulong), "ip6_ptr", FAULT_ON_ERROR);

	if (!ip6_ptr)
		return;

	/*
	 * 502a2ffd7376 ("ipv6: convert idev_list to list macros")
	 * v2.6.35-rc1~473^2~733
	 */
	if (VALID_MEMBER(inet6_ifaddr_if_list)) {
		struct list_data list_data, *ld;
		ulong cnt = 0, i;

		ld = &list_data;
		BZERO(ld, sizeof(struct list_data));
		ld->flags |= LIST_ALLOCATE;
		ld->start = ip6_ptr + OFFSET(inet6_dev_addr_list);
		ld->list_head_offset = OFFSET(inet6_ifaddr_if_list);
		cnt = do_list(ld);

		for (i = 1; i < cnt; i++) {

			addr = ld->list_ptr[i] + OFFSET(inet6_ifaddr_addr);
			readmem(addr + OFFSET(in6_addr_in6_u), KVADDR, &ip6_addr,
				sizeof(struct in6_addr), "in6_addr.in6_u", FAULT_ON_ERROR);

			inet_ntop(AF_INET6, (void*)&ip6_addr, str, INET6_ADDRSTRLEN);
			sprintf(buffer, "%s%s", pos ? ", " : "", str);
			len = strlen(buffer);
			if (pos + len >= bufsize) {
				RESIZEBUF(*bufp, bufsize, bufsize + buflen);
				buf = *bufp;
				BZERO(buf + bufsize, buflen);
				bufsize += buflen;
			}
			BCOPY(buffer, &buf[pos], len);
			pos += len;
		}

		FREEBUF(ld->list_ptr);
		return;
	}

	if (INVALID_MEMBER(inet6_ifaddr_if_next))
		return;

	readmem(ip6_ptr + OFFSET(inet6_dev_addr_list), KVADDR,
		&addr, sizeof(void *), "inet6_dev.addr_list", FAULT_ON_ERROR);

	while (addr) {
		readmem(addr + OFFSET(in6_addr_in6_u), KVADDR, &ip6_addr,
			sizeof(struct in6_addr), "in6_addr.in6_u", FAULT_ON_ERROR);
		inet_ntop(AF_INET6, (void*)&ip6_addr, str, INET6_ADDRSTRLEN);
		sprintf(buffer, "%s%s", pos ? ", " : "", str);
		len = strlen(buffer);

		if (pos + len >= bufsize) {
			RESIZEBUF(*bufp, bufsize, bufsize + buflen);
			buf = *bufp;
			BZERO(buf + bufsize, buflen);
			bufsize += buflen;
		}
		BCOPY(buffer, &buf[pos], len);
		pos += len;
		readmem(addr + OFFSET(inet6_ifaddr_if_next), KVADDR, &addr,
			sizeof(void *), "inet6_ifaddr.if_next", FAULT_ON_ERROR);
	}
}

/*
 *  Get the family, type, local and destination address/port pairs.
 */
static void
get_sock_info(ulong sock, char *buf)
{
	uint32_t daddr, rcv_saddr;
	uint16_t dport, sport;
	ushort family, type;
	ushort num ATTRIBUTE_UNUSED;
	char *sockbuf, *inet_sockbuf;
	ulong ipv6_pinfo, ipv6_rcv_saddr, ipv6_daddr;
	uint16_t u6_addr16_src[8];
	uint16_t u6_addr16_dest[8];
	char buf2[BUFSIZE];
	struct in_addr in_addr;
	int len;

	BZERO(buf, BUFSIZE);
	BZERO(buf2, BUFSIZE);
	sockbuf = inet_sockbuf = NULL;
	rcv_saddr = daddr = 0;
	dport = sport = 0;
	family = type = 0;
	ipv6_pinfo = 0;

	switch (net->flags & (SOCK_V1|SOCK_V2))
	{
	case SOCK_V1:
		sockbuf = GETBUF(SIZE(sock));
	        readmem(sock, KVADDR, sockbuf, SIZE(sock), 
			"sock buffer", FAULT_ON_ERROR);
	
		daddr = UINT(sockbuf + OFFSET(sock_daddr));
		rcv_saddr = UINT(sockbuf + OFFSET(sock_rcv_saddr));
		dport = USHORT(sockbuf + OFFSET(sock_dport));
		sport = USHORT(sockbuf + OFFSET(sock_sport));
		num = USHORT(sockbuf + OFFSET(sock_num));
		family = USHORT(sockbuf + OFFSET(sock_family));
		type = USHORT(sockbuf + OFFSET(sock_type));
		break;

	case SOCK_V2:
		inet_sockbuf = GETBUF(SIZE(inet_sock));
	        readmem(sock, KVADDR, inet_sockbuf, SIZE(inet_sock), 
			"inet_sock buffer", FAULT_ON_ERROR);

		daddr = UINT(inet_sockbuf + OFFSET(inet_sock_inet) +
			OFFSET(inet_opt_daddr));
		rcv_saddr = UINT(inet_sockbuf + OFFSET(inet_sock_inet) +
			OFFSET(inet_opt_rcv_saddr));
		dport = USHORT(inet_sockbuf + OFFSET(inet_sock_inet) +
			OFFSET(inet_opt_dport));
		sport = USHORT(inet_sockbuf + OFFSET(inet_sock_inet) +
			OFFSET(inet_opt_sport));
		num = USHORT(inet_sockbuf + OFFSET(inet_sock_inet) +
			OFFSET(inet_opt_num));
		family = USHORT(inet_sockbuf + OFFSET(sock_common_skc_family));
		type = USHORT(inet_sockbuf + OFFSET(sock_sk_type));
		ipv6_pinfo = ULONG(inet_sockbuf + SIZE(sock));
		break;
	}

	switch (family)
	{
	case AF_UNSPEC:
		sprintf(buf, "UNSPEC:"); break;
	case AF_UNIX: 
		sprintf(buf, "UNIX:"); break;
	case AF_INET: 
		sprintf(buf, "INET:"); break;
	case AF_AX25: 
		sprintf(buf, "AX25:"); break;
	case AF_IPX:  
		sprintf(buf, "IPX:"); break;
	case AF_APPLETALK:
		sprintf(buf, "APPLETALK:"); break;
	case AF_NETROM:
		sprintf(buf, "NETROM:"); break;
	case AF_BRIDGE:
		sprintf(buf, "BRIDGE:"); break;
	case AF_ATMPVC:
		sprintf(buf, "ATMPVC:"); break;
	case AF_X25:  
		sprintf(buf, "X25:"); break;
	case AF_INET6:
		sprintf(buf, "INET6:"); break;
	case AF_ROSE: 
		sprintf(buf, "ROSE:"); break;
	case AF_DECnet:
		sprintf(buf, "DECnet:"); break;
	case AF_NETBEUI:
		sprintf(buf, "NETBEUI:"); break;
	case AF_SECURITY: 
		sprintf(buf, "SECURITY/KEY:"); break;
	case AF_NETLINK: 
		sprintf(buf, "NETLINK/ROUTE:"); break;
	case AF_PACKET:  
		sprintf(buf, "PACKET:"); break;
	case AF_ASH:     
		sprintf(buf, "ASH:"); break;
	case AF_ECONET:  
		sprintf(buf, "ECONET:"); break;
	case AF_ATMSVC: 
		sprintf(buf, "ATMSVC:"); break;
	case AF_SNA:    
		sprintf(buf, "SNA:"); break;
	case AF_IRDA:   
		sprintf(buf, "IRDA:"); break;
#ifndef AF_PPPOX
#define AF_PPPOX 24
#endif
	case AF_PPPOX:  
		sprintf(buf, "PPPOX:"); break;
	default:
		sprintf(buf, "%d:", family); break;
	}

	switch (type)
	{
	case SOCK_STREAM:
		sprintf(&buf[strlen(buf)], "STREAM"); break;
	case SOCK_DGRAM:
		sprintf(&buf[strlen(buf)], "DGRAM "); break;
	case SOCK_RAW:
		sprintf(&buf[strlen(buf)], "RAW"); break;
	case SOCK_RDM: 
		sprintf(&buf[strlen(buf)], "RDM"); break;
	case SOCK_SEQPACKET:
		sprintf(&buf[strlen(buf)], "SEQPACKET"); break;
	case SOCK_PACKET:
		sprintf(&buf[strlen(buf)], "PACKET"); break;
	default:
		sprintf(&buf[strlen(buf)], "%d", type); break;
	}

	/* make sure we have room at the end... */
//	sprintf(&buf[strlen(buf)], "%s", space(MINSPACE-1));
	sprintf(&buf[strlen(buf)], " ");
           
	if (family == AF_INET) {
		if (BITS32()) {
			in_addr.s_addr = rcv_saddr;
			sprintf(&buf[strlen(buf)], "%*s-%-*d%s",
				BYTES_IP_ADDR,
				inet_ntoa(in_addr),
				BYTES_PORT_NUM,
				ntohs(sport),
				space(1));
			in_addr.s_addr = daddr;
			sprintf(&buf[strlen(buf)], "%*s-%-*d%s",
				BYTES_IP_ADDR,
				inet_ntoa(in_addr), 
				BYTES_PORT_NUM,
				ntohs(dport),
				space(1));
		} else {
			in_addr.s_addr = rcv_saddr;
	                sprintf(&buf[strlen(buf)], " %s-%d ",
	                        inet_ntoa(in_addr),
	                        ntohs(sport));
			in_addr.s_addr = daddr;
	                sprintf(&buf[strlen(buf)], "%s-%d",
	                        inet_ntoa(in_addr),
	                        ntohs(dport));
		}
	}

	if (sockbuf)
		FREEBUF(sockbuf);
	if (inet_sockbuf)
		FREEBUF(inet_sockbuf);

	if (family != AF_INET6)
		return;

	switch (net->flags & (SOCK_V1|SOCK_V2))
	{
	case SOCK_V1:
		break;

	case SOCK_V2:
		if (VALID_MEMBER(ipv6_pinfo_rcv_saddr) &&
		    VALID_MEMBER(ipv6_pinfo_daddr)) {
			ipv6_rcv_saddr = ipv6_pinfo + OFFSET(ipv6_pinfo_rcv_saddr);
			ipv6_daddr = ipv6_pinfo + OFFSET(ipv6_pinfo_daddr);
		} else if (VALID_MEMBER(sock_sk_common) &&
			   VALID_MEMBER(sock_common_skc_v6_daddr) &&
			   VALID_MEMBER(sock_common_skc_v6_rcv_saddr)) {
			ipv6_rcv_saddr = sock + OFFSET(sock_sk_common) + OFFSET(sock_common_skc_v6_rcv_saddr);
			ipv6_daddr = sock + OFFSET(sock_sk_common) + OFFSET(sock_common_skc_v6_daddr);
		} else {
			sprintf(&buf[strlen(buf)], "%s", "(cannot get IPv6 addresses)");
			break;
		}

		if (!readmem(ipv6_rcv_saddr, KVADDR, u6_addr16_src, SIZE(in6_addr),
                    "ipv6_rcv_saddr buffer", QUIET|RETURN_ON_ERROR))
			break;
                if (!readmem(ipv6_daddr, KVADDR, u6_addr16_dest, SIZE(in6_addr),
                    "ipv6_daddr buffer", QUIET|RETURN_ON_ERROR))
			break;

		sprintf(&buf[strlen(buf)], "%*s ", BITS32() ? 22 : 12,
			dump_in6_addr_port(u6_addr16_src, sport, buf2, &len));
		if (BITS32() && (len > 22))
			len = 1;
		mkstring(dump_in6_addr_port(u6_addr16_dest, dport, buf2, NULL),
			len, CENTER, NULL);
		sprintf(&buf[strlen(buf)], "%s", buf2);

		break;
	}
}

static char *
dump_in6_addr_port(uint16_t *addr, uint16_t port, char *buf, int *len)
{
	sprintf(buf, "%x:%x:%x:%x:%x:%x:%x:%x-%d",
                ntohs(addr[0]),
                ntohs(addr[1]),
                ntohs(addr[2]),
                ntohs(addr[3]),
                ntohs(addr[4]),
                ntohs(addr[5]),
                ntohs(addr[6]),
                ntohs(addr[7]),
                ntohs(port));

	if (len)
		*len = strlen(buf);

	return buf;
}


/*
 *	XXX - copied from neighbour.h !!!!!!
 *
 *      Neighbor Cache Entry States.
 */
#define NUD_INCOMPLETE  0x01
#define NUD_REACHABLE   0x02
#define NUD_STALE       0x04
#define NUD_DELAY       0x08
#define NUD_PROBE       0x10
#define NUD_FAILED      0x20
#define NUD_NOARP       0x40
#define NUD_PERMANENT   0x80

#define FLAGBUF_SIZE 100

#define FILLBUF(s)							\
do {									\
	char *bp;							\
	int blen;							\
	blen=strlen(flag_buffer);					\
	if ((blen + strlen(s)) < FLAGBUF_SIZE-2) {			\
		bp = &flag_buffer[blen];				\
		if (blen != 0) {					\
			sprintf(bp, "|%s", (s));			\
		} else {						\
			sprintf(bp, "%s", (s));				\
		}							\
	}								\
} while(0)

/*
 * Take the state of the ARP entry and print it out the flag associated
 * with the binary state...
 */
static void
arp_state_to_flags(unsigned char state)
{
	char flag_buffer[FLAGBUF_SIZE];
	int had_flags = 0;

	if (!state) { 
		fprintf(fp, "\n");
		return;
	}

	bzero(flag_buffer, FLAGBUF_SIZE);

	if (state & NUD_INCOMPLETE) {
		FILLBUF("INCOMPLETE");
		had_flags = 1;
	}

	if (state & NUD_REACHABLE) {
		FILLBUF("REACHABLE");
		had_flags = 1;
	}

	if (state & NUD_STALE) {
		FILLBUF("STALE");
		had_flags = 1;
	}

	if (state & NUD_DELAY) {
		FILLBUF("DELAY");
		had_flags = 1;
	}

	if (state & NUD_PROBE) {
		FILLBUF("PROBE");
		had_flags = 1;
	}

	if (state & NUD_FAILED) {
		FILLBUF("FAILED");
		had_flags = 1;
	}

	if (state & NUD_NOARP) {
		FILLBUF("NOARP");
		had_flags = 1;
	}

	if (state & NUD_PERMANENT) {
		FILLBUF("PERMANENT");
		had_flags = 1;
	}

	if (had_flags) {
		fprintf(fp, "%s\n", flag_buffer);
		/* fprintf(fp, "%29.29s%s)\n", " ",  flag_buffer); */
	}
}

#undef FILLBUF

/*
 * Print out a formatted ethernet HW address....
 */
static void
dump_ether_hw(unsigned char *ha, int len)
{
	int i;

	for (i = 0; i < len; i++) {
		char sep = ':';
		if (i == (len - 1)) {
			sep = ' ';
		}
		fprintf(fp, "%02x%c", ha[i], sep);
	}
}

/*
 * Catchall routine for dumping out a HA address whose format we
 * don't know about...
 */
static void
dump_hw_addr(unsigned char *ha, int len)
{
	int i;

	for (i = 0; i < len; i++) {
		fprintf(fp, "%02x ", ha[i]);
	}
}

/*
 *  help -N output
 */
void
dump_net_table(void)
{
	int others;

	others = 0;
	fprintf(fp, "              flags: %lx (", net->flags);
	if (net->flags & NETDEV_INIT)
		fprintf(fp, "%sNETDEV_INIT", others++ ? "|" : "");
	if (net->flags & STRUCT_DEVICE)
		fprintf(fp, "%sSTRUCT_DEVICE", others++ ? "|" : "");
	if (net->flags & STRUCT_NET_DEVICE)
		fprintf(fp, "%sSTRUCT_NET_DEVICE", others++ ? "|" : "");
	if (net->flags & NO_INET_SOCK)
		fprintf(fp, "%sNO_INET_SOCK", others++ ? "|" : "");
	if (net->flags & SOCK_V1)
		fprintf(fp, "%sSOCK_V1", others++ ? "|" : "");
	if (net->flags & SOCK_V2)
		fprintf(fp, "%sSOCK_V2", others++ ? "|" : "");
	fprintf(fp, ")\n");

	fprintf(fp, "            netdevice: \"%s\"\n", net->netdevice); 
	fprintf(fp, "           dev_name_t: \"%s\"\n", net->dev_name_t);
	fprintf(fp, "           dev_type_t: \"%s\"\n", net->dev_type_t);
	fprintf(fp, "           dev_addr_t: \"%s\"\n", net->dev_addr_t);
        fprintf(fp, "             dev_name: %ld\n", net->dev_name);
	fprintf(fp, "             dev_next: %ld\n", net->dev_next);
        fprintf(fp, "             dev_type: %ld\n", net->dev_type);
        fprintf(fp, "           dev_ip_ptr: %ld\n", net->dev_ip_ptr);
        fprintf(fp, "         dev_addr_len: %ld\n", net->dev_addr_len);
	fprintf(fp, "net_device_name_index: %d\n", net->net_device_name_index);
}


/*
 * Dump the open sockets for a given PID.
 */
static void
dump_sockets(ulong flag, struct reference *ref)
{
    	struct task_context *tc;
    	ulong value;
    	int subsequent;

    	if (!args[optind]) { 
		if (!NET_REFERENCE_CHECK(ref))
            		print_task_header(fp, CURRENT_CONTEXT(), 0);
        	dump_sockets_workhorse(CURRENT_TASK(), flag, ref);
        	return;
    	}

	subsequent = 0;

	while (args[optind]) {

                switch (str_to_context(args[optind], &value, &tc))
                {
                case STR_PID:
                        for (tc = pid_to_context(value); tc; tc = tc->tc_next) {
                                if (!NET_REFERENCE_CHECK(ref))
                                        print_task_header(fp, tc, subsequent++);
                                dump_sockets_workhorse(tc->task, flag, ref);
                        }
                        break;

                case STR_TASK:
                        if (!NET_REFERENCE_CHECK(ref))
                                print_task_header(fp, tc, subsequent++);
                        dump_sockets_workhorse(tc->task, flag, ref);
                        break;

                case STR_INVALID:
                        error(INFO, "%sinvalid task or pid value: %s\n",
				subsequent++ ? "\n" : "", args[optind]);
                        break;
                }

		optind++;
	}
}

/*
 *  Find all sockets in the designated task and call sym_socket_dump()
 *  to display them.
 */
void
dump_sockets_workhorse(ulong task, ulong flag, struct reference *ref)
{
	ulong files_struct_addr = 0, fdtable_addr = 0;
	int max_fdset = 0;
	int max_fds = 0;
	ulong open_fds_addr = 0;
	ulong *open_fds;
	int open_fds_size;
	ulong fd;
	ulong file;
	int i, j;
	int sockets_found = 0;
	ulong value;

       /* 
        * Steps to getting open sockets:
        *
        * 1)  task->files (struct files_struct)
        * 2)  files->fd   (struct file **)
        * 3)  cycle through from 0 to files->open_fds offset from *fd
        *     i.e.    fd[0], fd[1], fd[2]  are pointers to the first three
        *     open file descriptors.  Thus, we have:
        *         struct file *fd[0], *fd[1], *fd[2],...
        *
        * 4) file->f_dentry (struct dentry)
        * 5) dentry->d_inode (struct inode)
        * 6) S_ISSOCK(inode.mode)
        *      Assuming it _is_ a socket:
        * 7) inode.u (struct socket)   -- offset 0xdc from inode pointer
        */

	readmem(task + OFFSET(task_struct_files), KVADDR, &files_struct_addr,
            sizeof(void *), "task files contents", FAULT_ON_ERROR);

        if (files_struct_addr) {
                if (VALID_MEMBER(files_struct_max_fdset)) {
		 	readmem(files_struct_addr + OFFSET(files_struct_max_fdset),
		          	KVADDR, &max_fdset, sizeof(int),
				"files_struct max_fdset", FAULT_ON_ERROR);
		      	readmem(files_struct_addr + OFFSET(files_struct_max_fds),
        	        	KVADDR, &max_fds, sizeof(int), "files_struct max_fds",
                	   	FAULT_ON_ERROR);
                }
		else if (VALID_MEMBER(files_struct_fdt)) {
			readmem(files_struct_addr + OFFSET(files_struct_fdt), KVADDR,
				&fdtable_addr, sizeof(void *), "fdtable buffer",
				FAULT_ON_ERROR);
			if (VALID_MEMBER(fdtable_max_fdset))
		      		readmem(fdtable_addr + OFFSET(fdtable_max_fdset),
        	         		KVADDR, &max_fdset, sizeof(int),
					"fdtable_struct max_fdset", FAULT_ON_ERROR);
			else
				max_fdset = -1;
		      	readmem(fdtable_addr + OFFSET(fdtable_max_fds),
	      	            	KVADDR, &max_fds, sizeof(int), "fdtable_struct max_fds",
	               	    	FAULT_ON_ERROR);
    		}
	}

	if ((VALID_MEMBER(files_struct_fdt) && !fdtable_addr) ||
	    !files_struct_addr || (max_fdset == 0) || (max_fds == 0)) {
		if (!NET_REFERENCE_CHECK(ref))
			fprintf(fp, "No open sockets.\n");
		return;
	}

	if (VALID_MEMBER(fdtable_open_fds)){
		readmem(fdtable_addr + OFFSET(fdtable_open_fds), KVADDR,
     	  		&open_fds_addr, sizeof(void *), "files_struct open_fds addr",
	            	FAULT_ON_ERROR);
		readmem(fdtable_addr + OFFSET(fdtable_fd), KVADDR, &fd,
           		sizeof(void *), "files_struct fd addr", FAULT_ON_ERROR);
	} else {
		readmem(files_struct_addr + OFFSET(files_struct_open_fds), KVADDR,
            		&open_fds_addr, sizeof(void *), "files_struct open_fds addr",
	          	FAULT_ON_ERROR);
		readmem(files_struct_addr + OFFSET(files_struct_fd), KVADDR, &fd,
            		sizeof(void *), "files_struct fd addr", FAULT_ON_ERROR);
	}

	open_fds_size = MAX(max_fdset, max_fds) / BITS_PER_BYTE;
	open_fds = (ulong *)GETBUF(open_fds_size);
	if (!open_fds)
		return;

	if (open_fds_addr) 
		readmem(open_fds_addr, KVADDR, open_fds, open_fds_size,
	               	"files_struct open_fds", FAULT_ON_ERROR);
    	if (!open_fds_addr || !fd) { 
		if (!NET_REFERENCE_CHECK(ref))
			fprintf(fp, "No open sockets.\n");
		FREEBUF(open_fds);
        	return;
	}

	if (NET_REFERENCE_CHECK(ref)) {
                if (IS_A_NUMBER(ref->str)) {
	                if (hexadecimal_only(ref->str, 0)) {
	                        ref->hexval = htol(ref->str,
	                        	FAULT_ON_ERROR, NULL);
	                        ref->cmdflags |= NET_REF_HEXNUM;
	                } else {
	                        value = dtol(ref->str, FAULT_ON_ERROR, NULL);
	                        if (value <= MAX(max_fdset, max_fds)) {
	                                ref->decval = value;
	                                ref->cmdflags |= NET_REF_DECNUM;
	                        } else {
	                                ref->hexval = htol(ref->str,
						FAULT_ON_ERROR, NULL);
	                                ref->cmdflags |= NET_REF_HEXNUM;
	                        }
	                }
                }
		ref->ref1 = task;
	}

    	j = 0;
    	for (;;) {
	        unsigned long set;
	        i = j * BITS_PER_LONG;
	        if (((max_fdset >= 0) && (i >= max_fdset)) || (i >= max_fds))
	            	break;
	        set = open_fds[j++];
	        while (set) {
	            	if (set & 1) {
		                readmem(fd + i*sizeof(struct file *), KVADDR, 
		                        &file, sizeof(struct file *), 
		                        "fd file", FAULT_ON_ERROR);
		                if (file) {
		                    	if (sym_socket_dump(file, i, 
					    sockets_found, flag, ref)) {
		                        	sockets_found++;
					}
		                }
	            	}
	            	i++;
	            	set >>= 1;
	        }
        }

    	if (!sockets_found && !NET_REFERENCE_CHECK(ref))
        	fprintf(fp, "No open sockets.\n");

	if (NET_REFERENCE_FOUND(ref))
		fprintf(fp, "\n");

	FREEBUF(open_fds);
}


/*
 *  Dump a struct socket symbolically.  Dave makes this _very_ easy.
 *
 *  Return TRUE if we found a socket, FALSE otherwise.
 */

static char *socket_hdr_32 = 
"FD   SOCKET     SOCK    FAMILY:TYPE          SOURCE-PORT      DESTINATION-PORT";
static char *socket_hdr_64 = 
"FD      SOCKET            SOCK       FAMILY:TYPE SOURCE-PORT DESTINATION-PORT";

static int
sym_socket_dump(ulong file, 
		int fd, 
		int sockets_found, 
		ulong flag,
		struct reference *ref)
{
	uint16_t umode16 = 0;
	uint32_t umode32 = 0;
    	uint mode = 0;
    	ulong dentry = 0, inode = 0,
        struct_socket = 0;
	ulong sock = 0;
	char *file_buf, *dentry_buf, *inode_buf, *socket_buf;
	char buf1[BUFSIZE];
	char buf2[BUFSIZE];
	char *socket_hdr = BITS32() ? socket_hdr_32 : socket_hdr_64;
	unsigned int radix;

	file_buf = fill_file_cache(file);
	dentry = ULONG(file_buf + OFFSET(file_f_dentry));

	if (flag & d_FLAG)
		radix = 10;
	else if (flag & x_FLAG)
		radix = 16;
	else
		radix = 0;

    	if (!dentry)
        	return FALSE;

	dentry_buf = fill_dentry_cache(dentry);
	inode = ULONG(dentry_buf + OFFSET(dentry_d_inode));

    	if (!inode)
        	return FALSE; 

	inode_buf = fill_inode_cache(inode);


	switch (SIZE(umode_t))
	{
	case SIZEOF_32BIT:
		umode32 = UINT(inode_buf + OFFSET(inode_i_mode));
		break;

	case SIZEOF_16BIT:
		umode16 = USHORT(inode_buf + OFFSET(inode_i_mode));
		break;
	}

	if (SIZE(umode_t) == SIZEOF_32BIT)
		mode = umode32;
	else
		mode = (uint)umode16;

    	if (!S_ISSOCK(mode))
        	return FALSE;

	/* 
	 * 2.6 (SOCK_V2) -- socket is inode addr minus sizeof(struct socket) 
	 */
	switch (net->flags & (SOCK_V1|SOCK_V2))  
	{
	case SOCK_V1:
    		struct_socket = inode + OFFSET(inode_u);
		sock = ULONG(inode_buf + OFFSET(inode_u) + OFFSET(socket_sk));
		break;

	case SOCK_V2:
		if (!VALID_SIZE(inet_sock)) 
			error(FATAL, 
              	           "cannot determine what an inet_sock structure is\n");
    		struct_socket = inode - OFFSET(socket_alloc_vfs_inode);
		socket_buf = GETBUF(SIZE(socket));
                readmem(struct_socket, KVADDR, socket_buf,
                        SIZE(socket), "socket buffer", FAULT_ON_ERROR);
		sock = ULONG(socket_buf + OFFSET(socket_sk));
		FREEBUF(socket_buf);
		break;
	} 

	if (NET_REFERENCE_CHECK(ref)) {
		if ((ref->cmdflags & NET_REF_HEXNUM) &&
		    ((ref->hexval == sock) || (ref->hexval == struct_socket)))
			ref->cmdflags |= NET_REF_FOUND_ITEM;
		else if ((ref->cmdflags & NET_REF_DECNUM) &&
			(ref->decval == (ulong)fd))
			ref->cmdflags |= NET_REF_FOUND_ITEM;
                else if ((ref->cmdflags & NET_REF_HEXNUM) &&
                        (ref->hexval == (ulong)fd))
                        ref->cmdflags |= NET_REF_FOUND_ITEM;

		if (!(ref->cmdflags & NET_REF_FOUND_ITEM))
			return FALSE;

		ref->cmdflags &= ~NET_REF_FOUND_ITEM;
		ref->cmdflags |= NET_REF_FOUND;

		if (!(ref->cmdflags & NET_TASK_HEADER_PRINTED)) {
			print_task_header(fp, task_to_context(ref->ref1), 0);
			ref->cmdflags |= NET_TASK_HEADER_PRINTED;
		}

		if (!(ref->cmdflags & NET_SOCK_HEADER_PRINTED)) {
			sockets_found = 0;
			ref->cmdflags |= NET_SOCK_HEADER_PRINTED;
		}
	}

	switch (flag & (S_FLAG|s_FLAG))
	{
	case S_FLAG:
		fprintf(fp, "%sFD  %s  %s\n", sockets_found ? "\n" : "",
			mkstring(buf1, VADDR_PRLEN, CENTER|LJUST, "SOCKET"),
			mkstring(buf2, VADDR_PRLEN, CENTER|LJUST, "SOCK"));
		fprintf(fp, "%2d  %s  %s\n\n",
			fd, 
			mkstring(buf1, VADDR_PRLEN, RJUST|LONG_HEX, 
			MKSTR(struct_socket)),
			mkstring(buf2, VADDR_PRLEN, RJUST|LONG_HEX, 
			MKSTR(sock)));

    		dump_struct("socket", struct_socket, radix);
		switch (net->flags & (SOCK_V1|SOCK_V2))  
		{
		case SOCK_V1:
    			dump_struct("sock", sock, radix);
			break;
		case SOCK_V2:
			if (STRUCT_EXISTS("inet_sock") && !(net->flags & NO_INET_SOCK))
				dump_struct("inet_sock", sock, radix);
			else if (STRUCT_EXISTS("sock"))
				dump_struct("sock", sock, radix);
			else
				fprintf(fp, "\nunable to display inet_sock structure\n");
			break;
		}
		break;

	case s_FLAG:
		if (!sockets_found) {
			fprintf(fp, "%s\n", socket_hdr);
		}
		fprintf(fp, "%2d%s%s%s%s%s",
			fd, space(MINSPACE), 
			mkstring(buf1, VADDR_PRLEN, RJUST|LONG_HEX, 
			MKSTR(struct_socket)),
			space(MINSPACE),
			mkstring(buf2, VADDR_PRLEN, RJUST|LONG_HEX,
                        MKSTR(sock)),
		        space(MINSPACE)); 

		buf1[0] = NULLCHAR;
		get_sock_info(sock, buf1);
		fprintf(fp, "%s\n", buf1);

		return TRUE;

	default:
		error(FATAL, "illegal flag: %lx\n", flag);
	}

    	return TRUE;
}
