/*
* Copyright (c) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997
*      The Regents of the University of California.  All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that: (1) source code distributions
* retain the above copyright notice and this paragraph in its entirety, (2)
* distributions including binary code include the above copyright notice and
* this paragraph in its entirety in the documentation or other materials
* provided with the distribution, and (3) all advertising materials mentioning
* features or use of this software display the following acknowledgement:
* ``This product includes software developed by the University of California,
* Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
* the University nor the names of its contributors may be used to endorse
* or promote products derived from this software without specific prior
* written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/

#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: print-ip.c,v 1.14 2024/09/02 16:15:31 christos Exp $");
#endif

/* \summary: IP printer */

#include <config.h>

#include "netdissect-stdinc.h"

#include "netdissect.h"
#include "addrtoname.h"
#include "extract.h"

#include "ip.h"
#include "ipproto.h"


static const struct tok ip_option_values[] = {
   { IPOPT_EOL, "EOL" },
   { IPOPT_NOP, "NOP" },
   { IPOPT_TS, "timestamp" },
   { IPOPT_SECURITY, "security" },
   { IPOPT_RR, "RR" },
   { IPOPT_SSRR, "SSRR" },
   { IPOPT_LSRR, "LSRR" },
   { IPOPT_RA, "RA" },
   { IPOPT_RFC1393, "traceroute" },
   { 0, NULL }
};

/*
* print the recorded route in an IP RR, LSRR or SSRR option.
*/
static int
ip_printroute(netdissect_options *ndo,
             const u_char *cp, u_int length)
{
       u_int ptr;
       u_int len;

       if (length < 3) {
               ND_PRINT(" [bad length %u]", length);
               return (0);
       }
       if ((length + 1) & 3)
               ND_PRINT(" [bad length %u]", length);
       ptr = GET_U_1(cp + 2) - 1;
       if (ptr < 3 || ((ptr + 1) & 3) || ptr > length + 1)
               ND_PRINT(" [bad ptr %u]", GET_U_1(cp + 2));

       for (len = 3; len < length; len += 4) {
               ND_TCHECK_4(cp + len);  /* Needed to print the IP addresses */
               ND_PRINT(" %s", GET_IPADDR_STRING(cp + len));
               if (ptr > len)
                       ND_PRINT(",");
       }
       return (0);

trunc:
       return (-1);
}

/*
* If source-routing is present and valid, return the final destination.
* Otherwise, return IP destination.
*
* This is used for UDP and TCP pseudo-header in the checksum
* calculation.
*/
static uint32_t
ip_finddst(netdissect_options *ndo,
          const struct ip *ip)
{
       u_int length;
       u_int len;
       const u_char *cp;

       cp = (const u_char *)(ip + 1);
       length = IP_HL(ip) * 4;
       if (length < sizeof(struct ip))
               goto trunc;
       length -= sizeof(struct ip);

       for (; length != 0; cp += len, length -= len) {
               int tt;

               tt = GET_U_1(cp);
               if (tt == IPOPT_EOL)
                       break;
               else if (tt == IPOPT_NOP)
                       len = 1;
               else {
                       len = GET_U_1(cp + 1);
                       if (len < 2)
                               break;
               }
               if (length < len)
                       goto trunc;
               ND_TCHECK_LEN(cp, len);
               switch (tt) {

               case IPOPT_SSRR:
               case IPOPT_LSRR:
                       if (len < 7)
                               break;
                       return (GET_IPV4_TO_NETWORK_ORDER(cp + len - 4));
               }
       }
trunc:
       return (GET_IPV4_TO_NETWORK_ORDER(ip->ip_dst));
}

/*
* Compute a V4-style checksum by building a pseudoheader.
*/
uint16_t
nextproto4_cksum(netdissect_options *ndo,
                const struct ip *ip, const uint8_t *data,
                u_int len, u_int covlen, uint8_t next_proto)
{
       struct phdr {
               uint32_t src;
               uint32_t dst;
               uint8_t mbz;
               uint8_t proto;
               uint16_t len;
       } ph;
       struct cksum_vec vec[2];

       /* pseudo-header.. */
       ph.len = htons((uint16_t)len);
       ph.mbz = 0;
       ph.proto = next_proto;
       ph.src = GET_IPV4_TO_NETWORK_ORDER(ip->ip_src);
       if (IP_HL(ip) == 5)
               ph.dst = GET_IPV4_TO_NETWORK_ORDER(ip->ip_dst);
       else
               ph.dst = ip_finddst(ndo, ip);

       vec[0].ptr = (const uint8_t *)(void *)&ph;
       vec[0].len = sizeof(ph);
       vec[1].ptr = data;
       vec[1].len = covlen;
       return (in_cksum(vec, 2));
}

static int
ip_printts(netdissect_options *ndo,
          const u_char *cp, u_int length)
{
       u_int ptr;
       u_int len;
       u_int hoplen;
       const char *type;

       if (length < 4) {
               ND_PRINT("[bad length %u]", length);
               return (0);
       }
       ND_PRINT(" TS{");
       hoplen = ((GET_U_1(cp + 3) & 0xF) != IPOPT_TS_TSONLY) ? 8 : 4;
       if ((length - 4) & (hoplen-1))
               ND_PRINT("[bad length %u]", length);
       ptr = GET_U_1(cp + 2) - 1;
       len = 0;
       if (ptr < 4 || ((ptr - 4) & (hoplen-1)) || ptr > length + 1)
               ND_PRINT("[bad ptr %u]", GET_U_1(cp + 2));
       switch (GET_U_1(cp + 3)&0xF) {
       case IPOPT_TS_TSONLY:
               ND_PRINT("TSONLY");
               break;
       case IPOPT_TS_TSANDADDR:
               ND_PRINT("TS+ADDR");
               break;
       case IPOPT_TS_PRESPEC:
               ND_PRINT("PRESPEC");
               break;
       default:
               ND_PRINT("[bad ts type %u]", GET_U_1(cp + 3)&0xF);
               goto done;
       }

       type = " ";
       for (len = 4; len < length; len += hoplen) {
               if (ptr == len)
                       type = " ^ ";
               ND_TCHECK_LEN(cp + len, hoplen);
               ND_PRINT("%s%u@%s", type, GET_BE_U_4(cp + len + hoplen - 4),
                         hoplen!=8 ? "" : GET_IPADDR_STRING(cp + len));
               type = " ";
       }

done:
       ND_PRINT("%s", ptr == len ? " ^ " : "");

       if (GET_U_1(cp + 3) >> 4)
               ND_PRINT(" [%u hops not recorded]} ", GET_U_1(cp + 3)>>4);
       else
               ND_PRINT("}");
       return (0);

trunc:
       return (-1);
}

/*
* print IP options.
  If truncated return -1, else 0.
*/
static int
ip_optprint(netdissect_options *ndo,
           const u_char *cp, u_int length)
{
       u_int option_len;
       const char *sep = "";

       for (; length > 0; cp += option_len, length -= option_len) {
               u_int option_code;

               ND_PRINT("%s", sep);
               sep = ",";

               option_code = GET_U_1(cp);

               ND_PRINT("%s",
                         tok2str(ip_option_values,"unknown %u",option_code));

               if (option_code == IPOPT_NOP ||
                   option_code == IPOPT_EOL)
                       option_len = 1;

               else {
                       option_len = GET_U_1(cp + 1);
                       if (option_len < 2) {
                               ND_PRINT(" [bad length %u]", option_len);
                               return 0;
                       }
               }

               if (option_len > length) {
                       ND_PRINT(" [bad length %u]", option_len);
                       return 0;
               }

               ND_TCHECK_LEN(cp, option_len);

               switch (option_code) {
               case IPOPT_EOL:
                       return 0;

               case IPOPT_TS:
                       if (ip_printts(ndo, cp, option_len) == -1)
                               goto trunc;
                       break;

               case IPOPT_RR:       /* fall through */
               case IPOPT_SSRR:
               case IPOPT_LSRR:
                       if (ip_printroute(ndo, cp, option_len) == -1)
                               goto trunc;
                       break;

               case IPOPT_RA:
                       if (option_len < 4) {
                               ND_PRINT(" [bad length %u]", option_len);
                               break;
                       }
                       ND_TCHECK_1(cp + 3);
                       if (GET_BE_U_2(cp + 2) != 0)
                               ND_PRINT(" value %u", GET_BE_U_2(cp + 2));
                       break;

               case IPOPT_NOP:       /* nothing to print - fall through */
               case IPOPT_SECURITY:
               default:
                       break;
               }
       }
       return 0;

trunc:
       return -1;
}

#define IP_RES 0x8000

static const struct tok ip_frag_values[] = {
       { IP_MF,        "+" },
       { IP_DF,        "DF" },
       { IP_RES,       "rsvd" }, /* The RFC3514 evil ;-) bit */
       { 0,            NULL }
};


/*
* print an IP datagram.
*/
void
ip_print(netdissect_options *ndo,
        const u_char *bp,
        const u_int length)
{
       const struct ip *ip;
       u_int off;
       u_int hlen;
       u_int len;
       struct cksum_vec vec[1];
       uint8_t ip_tos, ip_ttl, ip_proto;
       uint16_t sum, ip_sum;
       const char *p_name;
       int truncated = 0;
       int presumed_tso = 0;

       ndo->ndo_protocol = "ip";
       ip = (const struct ip *)bp;

       if (!ndo->ndo_eflag) {
               nd_print_protocol_caps(ndo);
               ND_PRINT(" ");
       }

       ND_ICHECK_ZU(length, <, sizeof (struct ip));
       ND_ICHECKMSG_U("version", IP_V(ip), !=, 4);

       hlen = IP_HL(ip) * 4;
       ND_ICHECKMSG_ZU("header length", hlen, <, sizeof (struct ip));

       len = GET_BE_U_2(ip->ip_len);
       if (len > length) {
               ND_PRINT("[total length %u > length %u]", len, length);
               nd_print_invalid(ndo);
               ND_PRINT(" ");
       }
       if (len == 0) {
               /* we guess that it is a TSO send */
               len = length;
               presumed_tso = 1;
       } else
               ND_ICHECKMSG_U("total length", len, <, hlen);

       ND_TCHECK_SIZE(ip);
       /*
        * Cut off the snapshot length to the end of the IP payload.
        */
       if (!nd_push_snaplen(ndo, bp, len)) {
               (*ndo->ndo_error)(ndo, S_ERR_ND_MEM_ALLOC,
                       "%s: can't push snaplen on buffer stack", __func__);
       }

       len -= hlen;

       off = GET_BE_U_2(ip->ip_off);

       ip_proto = GET_U_1(ip->ip_p);

       if (ndo->ndo_vflag) {
           ip_tos = GET_U_1(ip->ip_tos);
           ND_PRINT("(tos 0x%x", ip_tos);
           /* ECN bits */
           switch (ip_tos & 0x03) {

           case 0:
               break;

           case 1:
               ND_PRINT(",ECT(1)");
               break;

           case 2:
               ND_PRINT(",ECT(0)");
               break;

           case 3:
               ND_PRINT(",CE");
               break;
           }

           ip_ttl = GET_U_1(ip->ip_ttl);
           if (ip_ttl >= 1)
               ND_PRINT(", ttl %u", ip_ttl);

           /*
            * for the firewall guys, print id, offset.
            * On all but the last stick a "+" in the flags portion.
            * For unfragmented datagrams, note the don't fragment flag.
            */
           ND_PRINT(", id %u, offset %u, flags [%s], proto %s (%u)",
                        GET_BE_U_2(ip->ip_id),
                        (off & IP_OFFMASK) * 8,
                        bittok2str(ip_frag_values, "none", off & (IP_RES|IP_DF|IP_MF)),
                        tok2str(ipproto_values, "unknown", ip_proto),
                        ip_proto);

           if (presumed_tso)
               ND_PRINT(", length %u [was 0, presumed TSO]", length);
           else
               ND_PRINT(", length %u", GET_BE_U_2(ip->ip_len));

           if ((hlen - sizeof(struct ip)) > 0) {
               ND_PRINT(", options (");
               if (ip_optprint(ndo, (const u_char *)(ip + 1),
                   hlen - sizeof(struct ip)) == -1) {
                       ND_PRINT(" [truncated-option]");
                       truncated = 1;
               }
               ND_PRINT(")");
           }

           if (!ndo->ndo_Kflag && (const u_char *)ip + hlen <= ndo->ndo_snapend) {
               vec[0].ptr = (const uint8_t *)(const void *)ip;
               vec[0].len = hlen;
               sum = in_cksum(vec, 1);
               if (sum != 0) {
                   ip_sum = GET_BE_U_2(ip->ip_sum);
                   ND_PRINT(", bad cksum %x (->%x)!", ip_sum,
                            in_cksum_shouldbe(ip_sum, sum));
               }
           }

           ND_PRINT(")\n    ");
           if (truncated) {
               ND_PRINT("%s > %s: ",
                        GET_IPADDR_STRING(ip->ip_src),
                        GET_IPADDR_STRING(ip->ip_dst));
               nd_print_trunc(ndo);
               nd_pop_packet_info(ndo);
               return;
           }
       }

       /*
        * If this is fragment zero, hand it to the next higher
        * level protocol.  Let them know whether there are more
        * fragments.
        */
       if ((off & IP_OFFMASK) == 0) {
               uint8_t nh = GET_U_1(ip->ip_p);

               if (nh != IPPROTO_TCP && nh != IPPROTO_UDP &&
                   nh != IPPROTO_SCTP && nh != IPPROTO_DCCP) {
                       ND_PRINT("%s > %s: ",
                                    GET_IPADDR_STRING(ip->ip_src),
                                    GET_IPADDR_STRING(ip->ip_dst));
               }
               /*
                * Do a bounds check before calling ip_demux_print().
                * At least the header data is required.
                */
               if (!ND_TTEST_LEN((const u_char *)ip, hlen)) {
                       ND_PRINT(" [remaining caplen(%u) < header length(%u)]",
                                ND_BYTES_AVAILABLE_AFTER((const u_char *)ip),
                                hlen);
                       nd_trunc_longjmp(ndo);
               }
               ip_demux_print(ndo, (const u_char *)ip + hlen, len, 4,
                              off & IP_MF, GET_U_1(ip->ip_ttl), nh, bp);
       } else {
               /*
                * Ultra quiet now means that all this stuff should be
                * suppressed.
                */
               if (ndo->ndo_qflag > 1) {
                       nd_pop_packet_info(ndo);
                       return;
               }

               /*
                * This isn't the first frag, so we're missing the
                * next level protocol header.  print the ip addr
                * and the protocol.
                */
               ND_PRINT("%s > %s:", GET_IPADDR_STRING(ip->ip_src),
                         GET_IPADDR_STRING(ip->ip_dst));
               if (!ndo->ndo_nflag && (p_name = netdb_protoname(ip_proto)) != NULL)
                       ND_PRINT(" %s", p_name);
               else
                       ND_PRINT(" ip-proto-%u", ip_proto);
       }
       nd_pop_packet_info(ndo);
       return;

trunc:
       nd_print_trunc(ndo);
       return;

invalid:
       nd_print_invalid(ndo);
}

void
ipN_print(netdissect_options *ndo, const u_char *bp, u_int length)
{
       ndo->ndo_protocol = "ipn";
       if (length < 1) {
               ND_PRINT("truncated-ip %u", length);
               return;
       }

       switch (GET_U_1(bp) & 0xF0) {
       case 0x40:
               ip_print(ndo, bp, length);
               break;
       case 0x60:
               ip6_print(ndo, bp, length);
               break;
       default:
               ND_PRINT("unknown ip %u", (GET_U_1(bp) & 0xF0) >> 4);
               break;
       }
}