1026d7285Schristos /* 2026d7285Schristos * Copyright (c) 2013 The TCPDUMP project 3026d7285Schristos * All rights reserved. 4026d7285Schristos * 5026d7285Schristos * Redistribution and use in source and binary forms, with or without 6026d7285Schristos * modification, are permitted provided that the following conditions 7026d7285Schristos * are met: 8026d7285Schristos * 1. Redistributions of source code must retain the above copyright 9026d7285Schristos * notice, this list of conditions and the following disclaimer. 10026d7285Schristos * 2. Redistributions in binary form must reproduce the above copyright 11026d7285Schristos * notice, this list of conditions and the following disclaimer in the 12026d7285Schristos * documentation and/or other materials provided with the distribution. 13026d7285Schristos * 14026d7285Schristos * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 15026d7285Schristos * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 16026d7285Schristos * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 17026d7285Schristos * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 18026d7285Schristos * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 19026d7285Schristos * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 20026d7285Schristos * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21026d7285Schristos * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22026d7285Schristos * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23026d7285Schristos * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 24026d7285Schristos * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25026d7285Schristos * POSSIBILITY OF SUCH DAMAGE. 26026d7285Schristos */ 27026d7285Schristos 28fdccd7e4Schristos #include <sys/cdefs.h> 29fdccd7e4Schristos #ifndef lint 30*26ba0b50Schristos __RCSID("$NetBSD: print-zeromq.c,v 1.5 2024/09/02 16:15:33 christos Exp $"); 31fdccd7e4Schristos #endif 32fdccd7e4Schristos 33dc860a36Sspz /* \summary: ZeroMQ Message Transport Protocol (ZMTP) printer */ 34*26ba0b50Schristos /* specification: https://rfc.zeromq.org/spec/13/ */ 35dc860a36Sspz 36c74ad251Schristos #include <config.h> 37026d7285Schristos 38c74ad251Schristos #include "netdissect-stdinc.h" 39026d7285Schristos 40784088dfSchristos #include "netdissect.h" 41026d7285Schristos #include "extract.h" 42026d7285Schristos 43c47fd378Schristos 44026d7285Schristos /* Maximum number of ZMTP/1.0 frame body bytes (without the flags) to dump in 45026d7285Schristos * hex and ASCII under a single "-v" flag. 46026d7285Schristos */ 47026d7285Schristos #define VBYTES 128 48026d7285Schristos 49*26ba0b50Schristos static const struct tok flags_bm[] = { 50*26ba0b50Schristos { 0x01, "MORE" }, 51*26ba0b50Schristos { 0x02, "R1" }, 52*26ba0b50Schristos { 0x04, "R2" }, 53*26ba0b50Schristos { 0x08, "R3" }, 54*26ba0b50Schristos { 0x10, "R4" }, 55*26ba0b50Schristos { 0x20, "R5" }, 56*26ba0b50Schristos { 0x40, "R6" }, 57*26ba0b50Schristos { 0x80, "R7" }, 58*26ba0b50Schristos { 0, NULL } 59*26ba0b50Schristos }; 60*26ba0b50Schristos 61026d7285Schristos /* 62026d7285Schristos * Below is an excerpt from the "13/ZMTP" specification: 63026d7285Schristos * 64026d7285Schristos * A ZMTP message consists of 1 or more frames. 65026d7285Schristos * 66026d7285Schristos * A ZMTP frame consists of a length, followed by a flags field and a frame 67026d7285Schristos * body of (length - 1) octets. Note: the length includes the flags field, so 68026d7285Schristos * an empty frame has a length of 1. 69026d7285Schristos * 70026d7285Schristos * For frames with a length of 1 to 254 octets, the length SHOULD BE encoded 71026d7285Schristos * as a single octet. The minimum valid length of a frame is 1 octet, thus a 72026d7285Schristos * length of 0 is invalid and such frames SHOULD be discarded silently. 73026d7285Schristos * 74026d7285Schristos * For frames with lengths of 255 and greater, the length SHALL BE encoded as 75026d7285Schristos * a single octet with the value 255, followed by the length encoded as a 76026d7285Schristos * 64-bit unsigned integer in network byte order. For frames with lengths of 77026d7285Schristos * 1 to 254 octets this encoding MAY be also used. 78026d7285Schristos * 79026d7285Schristos * The flags field consists of a single octet containing various control 80026d7285Schristos * flags. Bit 0 is the least significant bit. 81026d7285Schristos * 82026d7285Schristos * - Bit 0 (MORE): More frames to follow. A value of 0 indicates that there 83026d7285Schristos * are no more frames to follow. A value of 1 indicates that more frames 84026d7285Schristos * will follow. On messages consisting of a single frame the MORE flag MUST 85026d7285Schristos * be 0. 86026d7285Schristos * 87026d7285Schristos * - Bits 1-7: Reserved. Bits 1-7 are reserved for future use and SHOULD be 88026d7285Schristos * zero. 89026d7285Schristos */ 90026d7285Schristos 91026d7285Schristos static const u_char * 923d25ea14Schristos zmtp1_print_frame(netdissect_options *ndo, const u_char *cp, const u_char *ep) 933d25ea14Schristos { 94c47fd378Schristos uint64_t body_len_declared, body_len_captured, header_len; 95c47fd378Schristos uint8_t flags; 96026d7285Schristos 97c74ad251Schristos ND_PRINT("\n\t"); 98026d7285Schristos 99c74ad251Schristos if (GET_U_1(cp) != 0xFF) { /* length/0xFF */ 100026d7285Schristos header_len = 1; /* length */ 101c74ad251Schristos body_len_declared = GET_U_1(cp); 102c74ad251Schristos ND_PRINT(" frame flags+body (8-bit) length %" PRIu64, body_len_declared); 103026d7285Schristos } else { 104026d7285Schristos header_len = 1 + 8; /* 0xFF, length */ 105c74ad251Schristos ND_PRINT(" frame flags+body (64-bit) length"); 106c74ad251Schristos ND_TCHECK_LEN(cp, header_len); /* 0xFF, length */ 107c74ad251Schristos body_len_declared = GET_BE_U_8(cp + 1); 108c74ad251Schristos ND_PRINT(" %" PRIu64, body_len_declared); 109026d7285Schristos } 110784088dfSchristos if (body_len_declared == 0) 111784088dfSchristos return cp + header_len; /* skip to the next frame */ 112c74ad251Schristos ND_TCHECK_LEN(cp, header_len + 1); /* ..., flags */ 113c74ad251Schristos flags = GET_U_1(cp + header_len); 114026d7285Schristos 115026d7285Schristos body_len_captured = ep - cp - header_len; 116026d7285Schristos if (body_len_declared > body_len_captured) 117c74ad251Schristos ND_PRINT(" (%" PRIu64 " captured)", body_len_captured); 118c74ad251Schristos ND_PRINT(", flags 0x%02x", flags); 119026d7285Schristos 120c47fd378Schristos if (ndo->ndo_vflag) { 121c74ad251Schristos uint64_t body_len_printed = ND_MIN(body_len_captured, body_len_declared); 122026d7285Schristos 123*26ba0b50Schristos ND_PRINT(" (%s)", bittok2str(flags_bm, "none", flags)); 124c47fd378Schristos if (ndo->ndo_vflag == 1) 125c74ad251Schristos body_len_printed = ND_MIN(VBYTES + 1, body_len_printed); 126026d7285Schristos if (body_len_printed > 1) { 127c74ad251Schristos ND_PRINT(", first %" PRIu64 " byte(s) of body:", body_len_printed - 1); 128c47fd378Schristos hex_and_ascii_print(ndo, "\n\t ", cp + header_len + 1, body_len_printed - 1); 129026d7285Schristos } 130026d7285Schristos } 131026d7285Schristos 132dc860a36Sspz /* 133dc860a36Sspz * Do not advance cp by the sum of header_len and body_len_declared 134c74ad251Schristos * before each offset has successfully passed ND_TCHECK_LEN() as the 135dc860a36Sspz * sum can roll over (9 + 0xfffffffffffffff7 = 0) and cause an 136dc860a36Sspz * infinite loop. 137dc860a36Sspz */ 138dc860a36Sspz cp += header_len; 139c74ad251Schristos ND_TCHECK_LEN(cp, body_len_declared); /* Next frame within the buffer ? */ 140dc860a36Sspz return cp + body_len_declared; 141026d7285Schristos 142026d7285Schristos trunc: 143c74ad251Schristos nd_trunc_longjmp(ndo); 144026d7285Schristos } 145026d7285Schristos 146026d7285Schristos void 1473d25ea14Schristos zmtp1_print(netdissect_options *ndo, const u_char *cp, u_int len) 1483d25ea14Schristos { 149c74ad251Schristos const u_char *ep = ND_MIN(ndo->ndo_snapend, cp + len); 150026d7285Schristos 151c74ad251Schristos ndo->ndo_protocol = "zmtp1"; 152c74ad251Schristos ND_PRINT(": ZMTP/1.0"); 153026d7285Schristos while (cp < ep) 154c47fd378Schristos cp = zmtp1_print_frame(ndo, cp, ep); 155026d7285Schristos } 156026d7285Schristos 157026d7285Schristos /* The functions below decode a ZeroMQ datagram, supposedly stored in the "Data" 158026d7285Schristos * field of an ODATA/RDATA [E]PGM packet. An excerpt from zmq_pgm(7) man page 159026d7285Schristos * follows. 160026d7285Schristos * 161026d7285Schristos * In order for late joining consumers to be able to identify message 162026d7285Schristos * boundaries, each PGM datagram payload starts with a 16-bit unsigned integer 163026d7285Schristos * in network byte order specifying either the offset of the first message frame 164026d7285Schristos * in the datagram or containing the value 0xFFFF if the datagram contains 165026d7285Schristos * solely an intermediate part of a larger message. 166026d7285Schristos * 167026d7285Schristos * Note that offset specifies where the first message begins rather than the 168026d7285Schristos * first message part. Thus, if there are trailing message parts at the 169026d7285Schristos * beginning of the packet the offset ignores them and points to first initial 170026d7285Schristos * message part in the packet. 171026d7285Schristos */ 172026d7285Schristos 173026d7285Schristos static const u_char * 1743d25ea14Schristos zmtp1_print_intermediate_part(netdissect_options *ndo, const u_char *cp, const u_int len) 1753d25ea14Schristos { 176026d7285Schristos u_int frame_offset; 177c74ad251Schristos u_int remaining_len; 178026d7285Schristos 179c74ad251Schristos frame_offset = GET_BE_U_2(cp); 180c74ad251Schristos ND_PRINT("\n\t frame offset 0x%04x", frame_offset); 181026d7285Schristos cp += 2; 182c74ad251Schristos remaining_len = ND_BYTES_AVAILABLE_AFTER(cp); /* without the frame length */ 183026d7285Schristos 184026d7285Schristos if (frame_offset == 0xFFFF) 185026d7285Schristos frame_offset = len - 2; /* always within the declared length */ 186026d7285Schristos else if (2 + frame_offset > len) { 187c74ad251Schristos ND_PRINT(" (exceeds datagram declared length)"); 188026d7285Schristos goto trunc; 189026d7285Schristos } 190026d7285Schristos 191026d7285Schristos /* offset within declared length of the datagram */ 192026d7285Schristos if (frame_offset) { 193c74ad251Schristos ND_PRINT("\n\t frame intermediate part, %u bytes", frame_offset); 194026d7285Schristos if (frame_offset > remaining_len) 195c74ad251Schristos ND_PRINT(" (%u captured)", remaining_len); 196c47fd378Schristos if (ndo->ndo_vflag) { 197c74ad251Schristos u_int len_printed = ND_MIN(frame_offset, remaining_len); 198026d7285Schristos 199c47fd378Schristos if (ndo->ndo_vflag == 1) 200c74ad251Schristos len_printed = ND_MIN(VBYTES, len_printed); 201026d7285Schristos if (len_printed > 1) { 202c74ad251Schristos ND_PRINT(", first %u byte(s):", len_printed); 203c47fd378Schristos hex_and_ascii_print(ndo, "\n\t ", cp, len_printed); 204026d7285Schristos } 205026d7285Schristos } 206026d7285Schristos } 207026d7285Schristos return cp + frame_offset; 208026d7285Schristos 209026d7285Schristos trunc: 210c74ad251Schristos nd_trunc_longjmp(ndo); 211026d7285Schristos } 212026d7285Schristos 213026d7285Schristos void 214c74ad251Schristos zmtp1_datagram_print(netdissect_options *ndo, const u_char *cp, const u_int len) 2153d25ea14Schristos { 216c74ad251Schristos const u_char *ep = ND_MIN(ndo->ndo_snapend, cp + len); 217026d7285Schristos 218c74ad251Schristos ndo->ndo_protocol = "zmtp1"; 219c47fd378Schristos cp = zmtp1_print_intermediate_part(ndo, cp, len); 220026d7285Schristos while (cp < ep) 221c47fd378Schristos cp = zmtp1_print_frame(ndo, cp, ep); 222026d7285Schristos } 223