text: /*
   * mod_print.c
   *
   * Copyright (c) 2001 Dug Song <dugsong@monkey.org>
   *
   * $Id: mod_print.c,v 1.7 2002/04/07 22:55:20 dugsong Exp $
   */
  
  #include "config.h"
  
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  
  #include "mod.h"
  #include "pkt.h"
  
  #ifndef INET6_ADDRSTRLEN
  #define INET6_ADDRSTRLEN 46
  #endif
  
  #define EXTRACT_16BITS(p)	((uint16_t)ntohs(*(uint16_t *)(p)))
  #define EXTRACT_32BITS(p)	((uint32_t)ntohl(*(uint32_t *)(p)))
  
  /* XXX - _print_* routines adapted from tcpdump */
  
  static void
  _print_icmp(u_char *p, int length)
  {
  	struct ip_hdr *ip;
  	struct icmp_hdr *icmp;
  	
  	ip = (struct ip_hdr *)p;
  	icmp = (struct icmp_hdr *)(p + (ip->ip_hl * 4));
  
  	/* XXX - truncation? */
  	printf("%s > %s:", ip_ntoa(&ip->ip_src), ip_ntoa(&ip->ip_dst));
  	printf(" icmp: type %d code %d", icmp->icmp_type, icmp->icmp_code);
  }
  
  static void
  _print_icmp6(u_char *p, int length)
  {
  	struct ip6_hdr *ip6;
  	struct icmp_hdr *icmp;
  
  	ip6 = (struct ip6_hdr *)p;
  	icmp = (struct icmp_hdr *)(p + IP6_HDR_LEN);
  
  	/* XXX - truncation? */
  	printf("%s > %s:", ip6_ntoa(&ip6->ip6_src), ip6_ntoa(&ip6->ip6_dst));
  	printf(" icmp: type %hhu code %hhu", icmp->icmp_type, icmp->icmp_code);
  }
  
  void
  _print_tcp(int family, unsigned char *p, int length)
  {
  	struct tcp_hdr *tcp;
  	u_short sport, dport, win, urp;
  	u_long seq, ack;
  	int len, tcp_hl;
  	register char ch;
  
  	char src[INET6_ADDRSTRLEN], dst[INET6_ADDRSTRLEN];
  
  	if (family == AF_INET6) {
  			struct ip6_hdr *ip6 = (struct ip6_hdr *)p;
  			tcp = (struct tcp_hdr *)(p + IP6_HDR_LEN);
  			len = length;
  
  			ip6_ntop(&ip6->ip6_src, src, sizeof(src));
  			ip6_ntop(&ip6->ip6_dst, dst, sizeof(dst));
  	} else {
  			struct ip_hdr *ip;
  
  			ip = (struct ip_hdr *)p;
  			tcp = (struct tcp_hdr *)(p + (ip->ip_hl * 4));
  			len = length - (ip->ip_hl * 4);
  
  			ip_ntop(&ip->ip_src, src, sizeof(src));
  			ip_ntop(&ip->ip_dst, dst, sizeof(dst));
  	}
  
  	if (len < TCP_HDR_LEN) {
  		printf("truncated-tcp %d", len);
  		return;
  	}
  	sport = ntohs(tcp->th_sport);
  	dport = ntohs(tcp->th_dport);
  	seq = ntohl(tcp->th_seq);
  	ack = ntohl(tcp->th_ack);
  	win = ntohs(tcp->th_win);
  	urp = ntohs(tcp->th_urp);
  	tcp_hl = tcp->th_off * 4;
  
  	printf("%s.%d > %s.%d: ", src, sport, dst, dport);
  
  	if (tcp->th_flags & (TH_SYN|TH_FIN|TH_RST|TH_PUSH)) {
  		if (tcp->th_flags & TH_SYN)	putchar('S');
  		if (tcp->th_flags & TH_FIN)	putchar('F');
  		if (tcp->th_flags & TH_RST)	putchar('R');
  		if (tcp->th_flags & TH_PUSH)	putchar('P');
  	} else
  		putchar('.');
  	
  	if (tcp_hl > len) {
  		printf(" [bad hdr length]");
  		return;
  	}
  	len -= tcp_hl;
  	
  	if (len > 0 || tcp->th_flags & (TH_SYN | TH_FIN | TH_RST))
  		printf(" %lu:%lu(%d)", seq, seq + len, len);
  	
  	if (tcp->th_flags & TH_ACK)
  		printf(" ack %lu", ack);
  	printf(" win %d", win);
  	if (tcp->th_flags & TH_URG)
  		printf(" urg %d", urp);
  	
  	/* Handle options. */
  	if ((tcp_hl -= TCP_HDR_LEN) > 0) {
  		register const u_char *cp;
  		register int i, opt, len, datalen;
  		
  		cp = (const u_char *)tcp + TCP_HDR_LEN;
  		putchar(' ');
  		ch = '<';
  		
  		while (tcp_hl > 0) {
  			putchar(ch);
  			opt = *cp++;
  			if (TCP_OPT_TYPEONLY(opt)) {
  				len = 1;
  			} else {
  				len = *cp++;    /* total including type, len */
  				if (len < 2 || len > tcp_hl)
  					goto bad;
  				--tcp_hl;         /* account for length byte */
  			}
  			--tcp_hl;           /* account for type byte */
  			datalen = 0;
  			
  /* Bail if "l" bytes of data are not left or were not captured  */
  #define LENCHECK(l) { if ((l) > tcp_hl) goto bad; }
  			
  			switch (opt) {
  			case TCP_OPT_MSS:
  				printf("mss");
  				datalen = 2;
  				LENCHECK(datalen);
  				printf(" %u", EXTRACT_16BITS(cp));
  				break;
  			case TCP_OPT_EOL:
  				printf("eol");
  				break;
  			case TCP_OPT_NOP:
  				printf("nop");
  				break;
  			case TCP_OPT_WSCALE:
  				printf("wscale");
  				datalen = 1;
  				LENCHECK(datalen);
  				printf(" %u", *cp);
  				break;
  			case TCP_OPT_SACKOK:
  				printf("sackOK");
  				if (len != 2)
  					printf("[len %d]", len);
  				break;
  			case TCP_OPT_SACK:
  				datalen = len - 2;
  				if ((datalen % 8) != 0 ||
  				    !(tcp->th_flags & TH_ACK)) {
  					printf("malformed sack ");
  					printf("[len %d] ", datalen);
  					break;
  				}
  				printf("sack %d ", datalen / 8);
  				break;
  			case TCP_OPT_ECHO:
  				printf("echo");
  				datalen = 4;
  				LENCHECK(datalen);
  				printf(" %u", EXTRACT_32BITS(cp));
  				break;
  			case TCP_OPT_ECHOREPLY:
  				printf("echoreply");
  				datalen = 4;
  				LENCHECK(datalen);
  				printf(" %u", EXTRACT_32BITS(cp));
  				break;
  			case TCP_OPT_TIMESTAMP:
  				printf("timestamp");
  				datalen = 8;
  				LENCHECK(4);
  				printf(" %u", EXTRACT_32BITS(cp));
  				LENCHECK(datalen);
  				printf(" %u", EXTRACT_32BITS(cp + 4));
  				break;
  			case TCP_OPT_CC:
  				printf("cc");
  				datalen = 4;
  				LENCHECK(datalen);
  				printf(" %u", EXTRACT_32BITS(cp));
  				break;
  			case TCP_OPT_CCNEW:
  				printf("ccnew");
  				datalen = 4;
  				LENCHECK(datalen);
  				printf(" %u", EXTRACT_32BITS(cp));
  				break;
  			case TCP_OPT_CCECHO:
  				printf("ccecho");
  				datalen = 4;
  				LENCHECK(datalen);
  				printf(" %u", EXTRACT_32BITS(cp));
  				break;
  			default:
  				printf("opt-%d:", opt);
  				datalen = len - 2;
  				for (i = 0; i < datalen; ++i) {
  					LENCHECK(i);
  					printf("%02x", cp[i]);
  				}
  				break;
  			}
  			/* Account for data printed */
  			cp += datalen;
  			tcp_hl -= datalen;
  			
  			/* Check specification against observed length */
  			++datalen;                /* option octet */
  			if (!TCP_OPT_TYPEONLY(opt))
  				++datalen;              /* size octet */
  			if (datalen != len)
  				printf("[len %d]", len);
  			ch = ',';
  			if (opt == TCP_OPT_EOL)
  				break;
  		}
  		putchar('>');
  	}
  	return;
   bad:
  	fputs("[bad opt]", stdout);
  	if (ch != '\0')
  		putchar('>');
  	return;
  }
  
  static void
  _print_udp(int family, u_char *p, int length)
  {
  	struct udp_hdr *udp;
  	char src[INET6_ADDRSTRLEN], dst[INET6_ADDRSTRLEN];
  
  	if (family == AF_INET6) {
  			struct ip6_hdr *ip6 = (struct ip6_hdr *)p;
  			udp = (struct udp_hdr *)(p + IP6_HDR_LEN);
  
  			ip6_ntop(&ip6->ip6_src, src, sizeof(src));
  			ip6_ntop(&ip6->ip6_dst, dst, sizeof(dst));
  	} else {
  			struct ip_hdr *ip;
  
  			ip = (struct ip_hdr *)p;
  			udp = (struct udp_hdr *)(p + (ip->ip_hl * 4));
  
  			ip_ntop(&ip->ip_src, src, sizeof(src));
  			ip_ntop(&ip->ip_dst, dst, sizeof(dst));
  	}
  
  	/* XXX - truncation? */
  	printf("%s.%d > %s.%d:", src, ntohs(udp->uh_sport),
  		dst, ntohs(udp->uh_dport));
  	printf(" udp %d", ntohs(udp->uh_ulen) - UDP_HDR_LEN);
  }
  
  static void
  _print_frag6(u_char *p, int length)
  {
  	struct ip6_hdr *ip6;
  	struct ip6_ext_hdr *ext;
  	int off;
  
  	ip6 = (struct ip6_hdr *)p;
  	ext = (struct ip6_ext_hdr *)(p + IP6_HDR_LEN);
  
  	off = htons(ext->ext_data.fragment.offlg & IP6_OFF_MASK);
  
  	printf("%s > %s:", ip6_ntoa(&ip6->ip6_src), ip6_ntoa(&ip6->ip6_dst));
  	printf(" fragment: next %hhu offset %d%s ident 0x%08x",
  		ext->ext_nxt, off,
  		(ext->ext_data.fragment.offlg & IP6_MORE_FRAG) ? " MF" : "", 
  		htonl(ext->ext_data.fragment.ident));
  }
  
  static void
  _print_ip(u_char *p, int length)
  {
  	struct ip_hdr *ip;
  	u_int ip_off, ip_hl, ip_len;
  	
  	ip = (struct ip_hdr *)p;
  	
  	if (length < IP_HDR_LEN) {
  		printf("truncated-ip %d", length);
  		return;
  	}
  	ip_hl = ip->ip_hl * 4;
  	ip_len = ntohs(ip->ip_len);
  	
  	if (length < ip_len) {
  		printf("truncated-ip - %d bytes missing!", ip_len - length);
  		return;
  	}
  	ip_off = ntohs(ip->ip_off);
  	
  	/* Handle first fragment. */
  	if ((ip_off & IP_OFFMASK) == 0) {
  		switch (ip->ip_p) {
  		case IP_PROTO_TCP:
  			_print_tcp(AF_INET, p, ip_len);
  			break;
  		case IP_PROTO_UDP:
  			_print_udp(AF_INET, p, ip_len);
  			break;
  		case IP_PROTO_ICMP:
  			_print_icmp(p, ip_len);
  			break;
  		default:
  			printf("%s > %s:", ip_ntoa(&ip->ip_src),
  			    ip_ntoa(&ip->ip_dst));
  			printf(" ip-proto-%d %d", ip->ip_p, ip_len);
  			break;
  		}
  	}
  	/* Handle more frags. */
  	if (ip_off & (IP_MF|IP_OFFMASK)) {
  		if (ip_off & IP_OFFMASK)
  			printf("%s > %s:", ip_ntoa(&ip->ip_src),
  			    ip_ntoa(&ip->ip_dst));
  		printf(" (frag %d:%d@%d%s)", ntohs(ip->ip_id), ip_len - ip_hl,
  		    (ip_off & IP_OFFMASK) << 3, (ip_off & IP_MF) ? "+" : "");
  	} else if (ip_off & IP_DF)
  		printf(" (DF)");
  	
  	if (ip->ip_tos)
  		printf(" [tos 0x%x]", ip->ip_tos);
  	if (ip->ip_ttl <= 1)
  		printf(" [ttl %d]", ip->ip_ttl);
  }
  
  static void
  _print_ip6(u_char *p, int length)
  {
  	struct ip6_hdr *ip6;
  	int plen;
  
  	ip6 = (struct ip6_hdr *)p;
  
  	if (length < IP6_HDR_LEN) {
  		printf("truncated-ip6 %d", length);
  		return;
  	}
  
  	plen = htons(ip6->ip6_plen);
  
  	switch (ip6->ip6_nxt) {
  		case IP_PROTO_TCP:
  			_print_tcp(AF_INET6, p, plen);
  			break;
  		case IP_PROTO_UDP:
  			_print_udp(AF_INET6, p, plen);
  			break;
  		case IP_PROTO_ICMPV6:
  			_print_icmp6(p, plen);
  			break;
  		case IP_PROTO_FRAGMENT:
  			_print_frag6(p, plen);
  			break;
  		default:
  			printf("%s > %s:", ip6_ntoa(&ip6->ip6_src),
  				ip6_ntoa(&ip6->ip6_dst));
  			printf(" ip-proto-%hhu ttl %hhu  payload len %hu", ip6->ip6_nxt,
  				ip6->ip6_hlim, plen);
  			break;
  	}
  
  	if (ip6->ip6_hlim <= 1)
  		printf(" [ttl %d]", ip6->ip6_hlim);
  }
  
  static void
  _print_eth(struct eth_hdr* e, int length)
  {
  		char d[20], s[20];
  		eth_ntop(&e->eth_dst, &d[0], sizeof(d));
  		eth_ntop(&e->eth_src, &s[0], sizeof(s));
  
  		printf("%s > %s type 0x%04hx length %d", d, s, htons(e->eth_type), length);
  }
  
  static char *
  timerntoa(struct timeval *tv)
  {
  	static char buf[128];
  	uint64_t usec;
  
  	usec = (tv->tv_sec * 1000000) + tv->tv_usec;
  	
  	snprintf(buf, sizeof(buf), "%d.%03d ms",
  	    (int)(usec / 1000), (int)(usec % 1000));
  	
  	return (buf);
  }
  
  int
  print_apply(void *d, struct pktq *pktq, struct rule **next_rule)
  {
  	struct pkt *pkt;
  
  	TAILQ_FOREACH(pkt, pktq, pkt_next) {
  		uint16_t eth_type = htons(pkt->pkt_eth->eth_type);
  
  		if (eth_type == ETH_TYPE_IP)
  		  _print_ip(pkt->pkt_eth_data, pkt->pkt_end - pkt->pkt_eth_data);
  		else if (eth_type == ETH_TYPE_IPV6)
  		  _print_ip6(pkt->pkt_eth_data, pkt->pkt_end - pkt->pkt_eth_data);
  		else
  			_print_eth(pkt->pkt_eth, pkt->pkt_end - pkt->pkt_data);
  		if (timerisset(&pkt->pkt_ts))
  			printf(" [delay %s]", timerntoa(&pkt->pkt_ts));
  		printf("\n");
  	}
  	return (0);
  }
  
  struct mod mod_print = {
  	"print",		/* name */
  	"print",		/* usage */
  	NULL,			/* open */
  	NULL,			/* init */
  	print_apply,		/* apply */
  	NULL			/* close */
  };
  