1/*
2 * Copyright (c) 2013 The TCPDUMP project
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
17 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
18 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
20 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
24 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25 * POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include <sys/cdefs.h>
29#ifndef lint
30__RCSID("$NetBSD: print-zeromq.c,v 1.4 2023/08/17 20:19:40 christos Exp $");
31#endif
32
33/* \summary: ZeroMQ Message Transport Protocol (ZMTP) printer */
34
35#ifdef HAVE_CONFIG_H
36#include <config.h>
37#endif
38
39#include "netdissect-stdinc.h"
40
41#include "netdissect.h"
42#include "extract.h"
43
44
45/* Maximum number of ZMTP/1.0 frame body bytes (without the flags) to dump in
46 * hex and ASCII under a single "-v" flag.
47 */
48#define VBYTES 128
49
50/*
51 * Below is an excerpt from the "13/ZMTP" specification:
52 *
53 * A ZMTP message consists of 1 or more frames.
54 *
55 * A ZMTP frame consists of a length, followed by a flags field and a frame
56 * body of (length - 1) octets. Note: the length includes the flags field, so
57 * an empty frame has a length of 1.
58 *
59 * For frames with a length of 1 to 254 octets, the length SHOULD BE encoded
60 * as a single octet. The minimum valid length of a frame is 1 octet, thus a
61 * length of 0 is invalid and such frames SHOULD be discarded silently.
62 *
63 * For frames with lengths of 255 and greater, the length SHALL BE encoded as
64 * a single octet with the value 255, followed by the length encoded as a
65 * 64-bit unsigned integer in network byte order. For frames with lengths of
66 * 1 to 254 octets this encoding MAY be also used.
67 *
68 * The flags field consists of a single octet containing various control
69 * flags. Bit 0 is the least significant bit.
70 *
71 * - Bit 0 (MORE): More frames to follow. A value of 0 indicates that there
72 *   are no more frames to follow. A value of 1 indicates that more frames
73 *   will follow. On messages consisting of a single frame the MORE flag MUST
74 *   be 0.
75 *
76 * - Bits 1-7: Reserved. Bits 1-7 are reserved for future use and SHOULD be
77 *   zero.
78 */
79
80static const u_char *
81zmtp1_print_frame(netdissect_options *ndo, const u_char *cp, const u_char *ep)
82{
83	uint64_t body_len_declared, body_len_captured, header_len;
84	uint8_t flags;
85
86	ND_PRINT("\n\t");
87
88	if (GET_U_1(cp) != 0xFF) {	/* length/0xFF */
89		header_len = 1; /* length */
90		body_len_declared = GET_U_1(cp);
91		ND_PRINT(" frame flags+body  (8-bit) length %" PRIu64, body_len_declared);
92	} else {
93		header_len = 1 + 8; /* 0xFF, length */
94		ND_PRINT(" frame flags+body (64-bit) length");
95		ND_TCHECK_LEN(cp, header_len); /* 0xFF, length */
96		body_len_declared = GET_BE_U_8(cp + 1);
97		ND_PRINT(" %" PRIu64, body_len_declared);
98	}
99	if (body_len_declared == 0)
100		return cp + header_len; /* skip to the next frame */
101	ND_TCHECK_LEN(cp, header_len + 1); /* ..., flags */
102	flags = GET_U_1(cp + header_len);
103
104	body_len_captured = ep - cp - header_len;
105	if (body_len_declared > body_len_captured)
106		ND_PRINT(" (%" PRIu64 " captured)", body_len_captured);
107	ND_PRINT(", flags 0x%02x", flags);
108
109	if (ndo->ndo_vflag) {
110		uint64_t body_len_printed = ND_MIN(body_len_captured, body_len_declared);
111
112		ND_PRINT(" (%s|%s|%s|%s|%s|%s|%s|%s)",
113			flags & 0x80 ? "MBZ" : "-",
114			flags & 0x40 ? "MBZ" : "-",
115			flags & 0x20 ? "MBZ" : "-",
116			flags & 0x10 ? "MBZ" : "-",
117			flags & 0x08 ? "MBZ" : "-",
118			flags & 0x04 ? "MBZ" : "-",
119			flags & 0x02 ? "MBZ" : "-",
120			flags & 0x01 ? "MORE" : "-");
121
122		if (ndo->ndo_vflag == 1)
123			body_len_printed = ND_MIN(VBYTES + 1, body_len_printed);
124		if (body_len_printed > 1) {
125			ND_PRINT(", first %" PRIu64 " byte(s) of body:", body_len_printed - 1);
126			hex_and_ascii_print(ndo, "\n\t ", cp + header_len + 1, body_len_printed - 1);
127		}
128	}
129
130	/*
131	 * Do not advance cp by the sum of header_len and body_len_declared
132	 * before each offset has successfully passed ND_TCHECK_LEN() as the
133	 * sum can roll over (9 + 0xfffffffffffffff7 = 0) and cause an
134	 * infinite loop.
135	 */
136	cp += header_len;
137	ND_TCHECK_LEN(cp, body_len_declared); /* Next frame within the buffer ? */
138	return cp + body_len_declared;
139
140trunc:
141	nd_trunc_longjmp(ndo);
142}
143
144void
145zmtp1_print(netdissect_options *ndo, const u_char *cp, u_int len)
146{
147	const u_char *ep = ND_MIN(ndo->ndo_snapend, cp + len);
148
149	ndo->ndo_protocol = "zmtp1";
150	ND_PRINT(": ZMTP/1.0");
151	while (cp < ep)
152		cp = zmtp1_print_frame(ndo, cp, ep);
153}
154
155/* The functions below decode a ZeroMQ datagram, supposedly stored in the "Data"
156 * field of an ODATA/RDATA [E]PGM packet. An excerpt from zmq_pgm(7) man page
157 * follows.
158 *
159 * In order for late joining consumers to be able to identify message
160 * boundaries, each PGM datagram payload starts with a 16-bit unsigned integer
161 * in network byte order specifying either the offset of the first message frame
162 * in the datagram or containing the value 0xFFFF if the datagram contains
163 * solely an intermediate part of a larger message.
164 *
165 * Note that offset specifies where the first message begins rather than the
166 * first message part. Thus, if there are trailing message parts at the
167 * beginning of the packet the offset ignores them and points to first initial
168 * message part in the packet.
169 */
170
171static const u_char *
172zmtp1_print_intermediate_part(netdissect_options *ndo, const u_char *cp, const u_int len)
173{
174	u_int frame_offset;
175	u_int remaining_len;
176
177	frame_offset = GET_BE_U_2(cp);
178	ND_PRINT("\n\t frame offset 0x%04x", frame_offset);
179	cp += 2;
180	remaining_len = ND_BYTES_AVAILABLE_AFTER(cp); /* without the frame length */
181
182	if (frame_offset == 0xFFFF)
183		frame_offset = len - 2; /* always within the declared length */
184	else if (2 + frame_offset > len) {
185		ND_PRINT(" (exceeds datagram declared length)");
186		goto trunc;
187	}
188
189	/* offset within declared length of the datagram */
190	if (frame_offset) {
191		ND_PRINT("\n\t frame intermediate part, %u bytes", frame_offset);
192		if (frame_offset > remaining_len)
193			ND_PRINT(" (%u captured)", remaining_len);
194		if (ndo->ndo_vflag) {
195			u_int len_printed = ND_MIN(frame_offset, remaining_len);
196
197			if (ndo->ndo_vflag == 1)
198				len_printed = ND_MIN(VBYTES, len_printed);
199			if (len_printed > 1) {
200				ND_PRINT(", first %u byte(s):", len_printed);
201				hex_and_ascii_print(ndo, "\n\t ", cp, len_printed);
202			}
203		}
204	}
205	return cp + frame_offset;
206
207trunc:
208	nd_trunc_longjmp(ndo);
209}
210
211void
212zmtp1_datagram_print(netdissect_options *ndo, const u_char *cp, const u_int len)
213{
214	const u_char *ep = ND_MIN(ndo->ndo_snapend, cp + len);
215
216	ndo->ndo_protocol = "zmtp1";
217	cp = zmtp1_print_intermediate_part(ndo, cp, len);
218	while (cp < ep)
219		cp = zmtp1_print_frame(ndo, cp, ep);
220}
221