/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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 http://www.opensolaris.org/os/licensing.
 * 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) 2001 by Sun Microsystems, Inc.
 * All rights reserved.
 */

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/sysmacros.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/pppoe.h>
#include "snoop.h"

/*
 * These two macros extract the version and type fields respectively from
 * the first byte of the PPPoE header.
 */
#define	POE_VERS(x)	(((x) >> 4) & 0x0f)
#define	POE_TYPE(x)	((x) & 0x0f)

typedef void interpret_func_t(uint8_t *, uint16_t);

typedef struct taginfo {
	char *tag_name;
	uint16_t tag_type;
	interpret_func_t *interpret_tagvalue;
} taginfo_t;


static char *pppoe_codetoname(int, boolean_t);
static taginfo_t *pppoe_gettaginfo(uint16_t);
static void print_hexdata(char *, uint8_t *, uint16_t);
static void print_utf8string(char *, char *, uint16_t);
static char *print_linetag(char *);
static interpret_func_t interpret_tags;
static interpret_func_t interpret_hexdata;
static interpret_func_t interpret_service;
static interpret_func_t interpret_access;
static interpret_func_t interpret_cookie;
static interpret_func_t interpret_vendor;
static interpret_func_t interpret_relay;
static interpret_func_t interpret_error;
static interpret_func_t interpret_hurl;
static interpret_func_t interpret_motm;
static interpret_func_t interpret_rteadd;


static taginfo_t taginfo_array[] = {
	{ "End-Of-List",	POETT_END,	interpret_hexdata },
	{ "Service-Name",	POETT_SERVICE,	interpret_service },
	{ "AC-Name",		POETT_ACCESS,	interpret_access },
	{ "Host-Uniq",		POETT_UNIQ,	interpret_hexdata },
	{ "AC-Cookie",		POETT_COOKIE,	interpret_cookie },
	{ "Vendor-Specific",	POETT_VENDOR,	interpret_vendor },
	{ "Relay-Session-Id",	POETT_RELAY,	interpret_relay },
	{ "Service-Name-Error",	POETT_NAMERR,	interpret_error },
	{ "AC-System-Error",	POETT_SYSERR,	interpret_error },
	{ "Generic-Error",	POETT_GENERR,	interpret_error },
	{ "Multicast-Capable",	POETT_MULTI,	interpret_hexdata },
	{ "Host-URL",		POETT_HURL,	interpret_hurl },
	{ "Message-Of-The-Minute", POETT_MOTM,	interpret_motm },
	{ "IP-Route-Add",	POETT_RTEADD,	interpret_rteadd },
	{ "Unknown TAG",	0,		NULL }
};


int
interpret_pppoe(int flags, poep_t *poep, int len)
{
	uint8_t code = poep->poep_code;
	uint8_t *payload;

	if (len < sizeof (poep_t))
		return (len);

	payload = (uint8_t *)poep + sizeof (poep_t);

	if (flags & F_SUM) {
		(void) sprintf(get_sum_line(), "PPPoE %s",
		    pppoe_codetoname(code, B_FALSE));
	} else { /* flags & F_DTAIL */
		show_header("PPPoE:  ", "PPP Over Ethernet", len);
		show_space();

		(void) sprintf(get_line(0, 0),
		    "Version = %d", POE_VERS(poep->poep_version_type));

		(void) sprintf(get_line(0, 0),
		    "Type = %d", POE_TYPE(poep->poep_version_type));

		(void) sprintf(get_line(0, 0),
		    "Code = %d (%s)", code, pppoe_codetoname(code, B_TRUE));

		(void) sprintf(get_line(0, 0),
		    "Session Id = %d", ntohs(poep->poep_session_id));

		(void) sprintf(get_line(0, 0),
		    "Length = %d bytes", ntohs(poep->poep_length));

		show_space();

		len -= sizeof (poep_t);
		len = MIN(len, ntohs(poep->poep_length));

		if (poep->poep_code != 0 && poep->poep_length > 0) {
			interpret_tags(payload, len);
		}
	}

	if (poep->poep_code == 0) {
		return (interpret_ppp(flags, payload, len));
	}
	return (len);
}


/*
 * interpret_tags() prints PPPoE Discovery Stage TAGs in detail.
 */
static void
interpret_tags(uint8_t *payload, uint16_t length)
{
	uint8_t *tagptr = payload;
	uint16_t tag_length;
	uint16_t tag_type;
	uint8_t *tag_value;
	taginfo_t *tinfo;

	while (length >= POET_HDRLEN) {
		tag_type = POET_GET_TYPE(tagptr);
		tag_length = POET_GET_LENG(tagptr);

		tinfo = pppoe_gettaginfo(tag_type);

		show_header("PPPoE:  ", tinfo->tag_name,
		    tag_length + POET_HDRLEN);

		(void) sprintf(get_line(0, 0),
		    "Tag Type = %d", tag_type);

		(void) sprintf(get_line(0, 0),
		    "Tag Length = %d bytes", tag_length);

		length -= POET_HDRLEN;
		if (tag_length > length) {
			(void) sprintf(get_line(0, 0),
			    "Warning: Truncated Packet");
			show_space();
			break;
		}

		/*
		 * unknown tags or tags which should always have 0 length
		 * are not interpreted any further.
		 */
		tag_value = POET_DATA(tagptr);
		if (tag_length != 0 && tinfo->interpret_tagvalue != NULL)
			tinfo->interpret_tagvalue(tag_value, tag_length);

		show_space();
		length -= tag_length;
		tagptr = POET_NEXT(tagptr);
	}
}

static char *
pppoe_codetoname(int code, boolean_t verbose)
{
	char *name;

	switch (code) {
	case POECODE_DATA:
		name = "Session";
		break;
	case POECODE_PADO:
		if (verbose)
			name = "Active Discovery Offer";
		else
			name = "PADO";
		break;
	case POECODE_PADI:
		if (verbose)
			name = "Active Discovery Initiation";
		else
			name = "PADI";
		break;
	case POECODE_PADR:
		if (verbose)
			name = "Active Discovery Request";
		else
			name = "PADR";
		break;
	case POECODE_PADS:
		if (verbose)
			name = "Active Discovery Session-Confirmation";
		else
			name = "PADS";
		break;
	case POECODE_PADT:
		if (verbose)
			name = "Active Discovery Terminate";
		else
			name = "PADT";
		break;
	case POECODE_PADM:
		if (verbose)
			name = "Active Discovery Message";
		else
			name = "PADM";
		break;
	case POECODE_PADN:
		if (verbose)
			name = "Active Discovery Network";
		else
			name = "PADN";
		break;
	default:
		name = "Unknown Code";
	}

	return (name);
}

static taginfo_t *
pppoe_gettaginfo(uint16_t type)
{
	taginfo_t *taginfo_ptr = &taginfo_array[0];
	int i = 0;

	while (taginfo_ptr->tag_type != type &&
	    taginfo_ptr->interpret_tagvalue != NULL) {
		taginfo_ptr = &taginfo_array[++i];
	}

	return (taginfo_ptr);
}

static void
interpret_hexdata(uint8_t *tag_value, uint16_t tag_length)
{
	char *endofline;

	endofline = print_linetag("Data = ");
	print_hexdata(endofline, tag_value, tag_length);
}

static void
interpret_service(uint8_t *tag_value, uint16_t tag_length)
{
	char *endofline;

	endofline = print_linetag("Service Name = ");
	print_utf8string(endofline, (char *)tag_value, tag_length);
}

static void
interpret_access(uint8_t *tag_value, uint16_t tag_length)
{
	char *endofline;

	endofline = print_linetag("AC Name = ");
	print_utf8string(endofline, (char *)tag_value, tag_length);
}

static void
interpret_cookie(uint8_t *tag_value, uint16_t tag_length)
{
	char *endofline;

	endofline = print_linetag("Cookie = ");
	print_hexdata(endofline, tag_value, tag_length);
}

static void
interpret_vendor(uint8_t *tag_value, uint16_t tag_length)
{
	uint8_t *vendor_data;
	uint32_t vendorid;
	char *endofline;

	vendorid = ntohl(*(uint32_t *)tag_value);
	(void) sprintf(get_line(0, 0),
	    "Vendor ID = %d", vendorid);

	if (tag_length > 4) {
		vendor_data = tag_value + 4;
		endofline = print_linetag("Vendor Data = ");
		print_hexdata(endofline, vendor_data, tag_length - 4);
	}
}

static void
interpret_relay(uint8_t *tag_value, uint16_t tag_length)
{
	char *endofline;

	endofline = print_linetag("ID = ");
	print_hexdata(endofline, tag_value, tag_length);
}

static void
interpret_error(uint8_t *tag_value, uint16_t tag_length)
{
	char *endofline;

	endofline = print_linetag("Error = ");
	print_utf8string(endofline, (char *)tag_value, tag_length);
}

static void
interpret_hurl(uint8_t *tag_value, uint16_t tag_length)
{
	char *endofline;

	endofline = print_linetag("URL = ");
	print_utf8string(endofline, (char *)tag_value, tag_length);
}

static void
interpret_motm(uint8_t *tag_value, uint16_t tag_length)
{
	char *endofline;

	endofline = print_linetag("Message = ");
	print_utf8string(endofline, (char *)tag_value, tag_length);
}

static void
interpret_rteadd(uint8_t *tag_value, uint16_t tag_length)
{
	char dest[INET_ADDRSTRLEN];
	char mask[INET_ADDRSTRLEN];
	char gateway[INET_ADDRSTRLEN];
	uint32_t metric;

	if (tag_length == 16) {
		(void) inet_ntop(AF_INET, tag_value, dest,
		    INET_ADDRSTRLEN);
		(void) inet_ntop(AF_INET, &tag_value[4], mask,
		    INET_ADDRSTRLEN);
		(void) inet_ntop(AF_INET, &tag_value[8], gateway,
		    INET_ADDRSTRLEN);
		metric = ntohl(*(uint32_t *)&tag_value[12]);
		sprintf(get_line(0, 0),
		    "Destination\tNetmask\tGateway\tMetric");
		sprintf(get_line(0, 0),
		    "%s\t%s\t%s\t%d", dest, mask, gateway, metric);
	}
}

static void
print_hexdata(char *line, uint8_t *data, uint16_t length)
{
	uint16_t index = 0;

	line += sprintf(line, "0x");

	while (index < length) {
		line += sprintf(line, "%02x", data[index++]);
	}
}

static void
print_utf8string(char *firstline, char *string, uint16_t length)
{
	(void) sprintf(firstline, "%.*s", length, string);
}

static char *
print_linetag(char *string)
{
	char *line = get_line(0, 0);
	return (line + sprintf(line, string));
}
