tcpdump/print-ip.c
Guy Harris a4221536d5 ip: set the snapshot length for the payload correctly.
Cut off the snapshot length to the end of the IP payload or the end of
the data in which it's contained, whichever comes first.
2024-10-31 11:28:37 -07:00

531 lines
12 KiB
C

/*
* 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.
*/
/* \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
* or the end of the data in which it's contained, whichever
* comes first.
*/
if (!nd_push_snaplen(ndo, bp, ND_MIN(length, 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))) {
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;
}
}