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-ahcp.c,v 1.6 2023/08/17 20:19:40 christos Exp $");
31#endif
32
33/* \summary: Ad Hoc Configuration Protocol (AHCP) printer */
34
35/* Based on draft-chroboczek-ahcp-00 and source code of ahcpd-0.53 */
36
37#ifdef HAVE_CONFIG_H
38#include <config.h>
39#endif
40
41#include "netdissect-stdinc.h"
42
43#define ND_LONGJMP_FROM_TCHECK
44#include "netdissect.h"
45#include "extract.h"
46#include "addrtoname.h"
47
48
49#define AHCP_MAGIC_NUMBER 43
50#define AHCP_VERSION_1 1
51#define AHCP1_HEADER_FIX_LEN 24
52#define AHCP1_BODY_MIN_LEN 4
53
54#define AHCP1_MSG_DISCOVER 0
55#define AHCP1_MSG_OFFER    1
56#define AHCP1_MSG_REQUEST  2
57#define AHCP1_MSG_ACK      3
58#define AHCP1_MSG_NACK     4
59#define AHCP1_MSG_RELEASE  5
60
61static const struct tok ahcp1_msg_str[] = {
62	{ AHCP1_MSG_DISCOVER, "Discover" },
63	{ AHCP1_MSG_OFFER,    "Offer"    },
64	{ AHCP1_MSG_REQUEST,  "Request"  },
65	{ AHCP1_MSG_ACK,      "Ack"      },
66	{ AHCP1_MSG_NACK,     "Nack"     },
67	{ AHCP1_MSG_RELEASE,  "Release"  },
68	{ 0, NULL }
69};
70
71#define AHCP1_OPT_PAD                     0
72#define AHCP1_OPT_MANDATORY               1
73#define AHCP1_OPT_ORIGIN_TIME             2
74#define AHCP1_OPT_EXPIRES                 3
75#define AHCP1_OPT_MY_IPV6_ADDRESS         4
76#define AHCP1_OPT_MY_IPV4_ADDRESS         5
77#define AHCP1_OPT_IPV6_PREFIX             6
78#define AHCP1_OPT_IPV4_PREFIX             7
79#define AHCP1_OPT_IPV6_ADDRESS            8
80#define AHCP1_OPT_IPV4_ADDRESS            9
81#define AHCP1_OPT_IPV6_PREFIX_DELEGATION 10
82#define AHCP1_OPT_IPV4_PREFIX_DELEGATION 11
83#define AHCP1_OPT_NAME_SERVER            12
84#define AHCP1_OPT_NTP_SERVER             13
85#define AHCP1_OPT_MAX                    13
86
87static const struct tok ahcp1_opt_str[] = {
88	{ AHCP1_OPT_PAD,                    "Pad"                    },
89	{ AHCP1_OPT_MANDATORY,              "Mandatory"              },
90	{ AHCP1_OPT_ORIGIN_TIME,            "Origin Time"            },
91	{ AHCP1_OPT_EXPIRES,                "Expires"                },
92	{ AHCP1_OPT_MY_IPV6_ADDRESS,        "My-IPv6-Address"        },
93	{ AHCP1_OPT_MY_IPV4_ADDRESS,        "My-IPv4-Address"        },
94	{ AHCP1_OPT_IPV6_PREFIX,            "IPv6 Prefix"            },
95	{ AHCP1_OPT_IPV4_PREFIX,            "IPv4 Prefix"            },
96	{ AHCP1_OPT_IPV6_ADDRESS,           "IPv6 Address"           },
97	{ AHCP1_OPT_IPV4_ADDRESS,           "IPv4 Address"           },
98	{ AHCP1_OPT_IPV6_PREFIX_DELEGATION, "IPv6 Prefix Delegation" },
99	{ AHCP1_OPT_IPV4_PREFIX_DELEGATION, "IPv4 Prefix Delegation" },
100	{ AHCP1_OPT_NAME_SERVER,            "Name Server"            },
101	{ AHCP1_OPT_NTP_SERVER,             "NTP Server"             },
102	{ 0, NULL }
103};
104
105static void
106ahcp_time_print(netdissect_options *ndo,
107                const u_char *cp, uint8_t len)
108{
109	time_t t;
110	char buf[sizeof("-yyyyyyyyyy-mm-dd hh:mm:ss UTC")];
111
112	if (len != 4)
113		goto invalid;
114	t = GET_BE_U_4(cp);
115	ND_PRINT(": %s",
116	    nd_format_time(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S UTC",
117	      gmtime(&t)));
118	return;
119
120invalid:
121	nd_print_invalid(ndo);
122	ND_TCHECK_LEN(cp, len);
123}
124
125static void
126ahcp_seconds_print(netdissect_options *ndo,
127                   const u_char *cp, uint8_t len)
128{
129	if (len != 4)
130		goto invalid;
131	ND_PRINT(": %us", GET_BE_U_4(cp));
132	return;
133
134invalid:
135	nd_print_invalid(ndo);
136	ND_TCHECK_LEN(cp, len);
137}
138
139static void
140ahcp_ipv6_addresses_print(netdissect_options *ndo,
141                          const u_char *cp, uint8_t len)
142{
143	const char *sep = ": ";
144
145	while (len) {
146		if (len < 16)
147			goto invalid;
148		ND_PRINT("%s%s", sep, GET_IP6ADDR_STRING(cp));
149		cp += 16;
150		len -= 16;
151		sep = ", ";
152	}
153	return;
154
155invalid:
156	nd_print_invalid(ndo);
157	ND_TCHECK_LEN(cp, len);
158}
159
160static void
161ahcp_ipv4_addresses_print(netdissect_options *ndo,
162                          const u_char *cp, uint8_t len)
163{
164	const char *sep = ": ";
165
166	while (len) {
167		if (len < 4)
168			goto invalid;
169		ND_PRINT("%s%s", sep, GET_IPADDR_STRING(cp));
170		cp += 4;
171		len -= 4;
172		sep = ", ";
173	}
174	return;
175
176invalid:
177	nd_print_invalid(ndo);
178	ND_TCHECK_LEN(cp, len);
179}
180
181static void
182ahcp_ipv6_prefixes_print(netdissect_options *ndo,
183                         const u_char *cp, uint8_t len)
184{
185	const char *sep = ": ";
186
187	while (len) {
188		if (len < 17)
189			goto invalid;
190		ND_PRINT("%s%s/%u", sep, GET_IP6ADDR_STRING(cp), GET_U_1(cp + 16));
191		cp += 17;
192		len -= 17;
193		sep = ", ";
194	}
195	return;
196
197invalid:
198	nd_print_invalid(ndo);
199	ND_TCHECK_LEN(cp, len);
200}
201
202static void
203ahcp_ipv4_prefixes_print(netdissect_options *ndo,
204                         const u_char *cp, uint8_t len)
205{
206	const char *sep = ": ";
207
208	while (len) {
209		if (len < 5)
210			goto invalid;
211		ND_PRINT("%s%s/%u", sep, GET_IPADDR_STRING(cp), GET_U_1(cp + 4));
212		cp += 5;
213		len -= 5;
214		sep = ", ";
215	}
216	return;
217
218invalid:
219	nd_print_invalid(ndo);
220	ND_TCHECK_LEN(cp, len);
221}
222
223static void
224(* const data_decoders[AHCP1_OPT_MAX + 1])(netdissect_options *, const u_char *, uint8_t) = {
225	/* [AHCP1_OPT_PAD]                    = */  NULL,
226	/* [AHCP1_OPT_MANDATORY]              = */  NULL,
227	/* [AHCP1_OPT_ORIGIN_TIME]            = */  ahcp_time_print,
228	/* [AHCP1_OPT_EXPIRES]                = */  ahcp_seconds_print,
229	/* [AHCP1_OPT_MY_IPV6_ADDRESS]        = */  ahcp_ipv6_addresses_print,
230	/* [AHCP1_OPT_MY_IPV4_ADDRESS]        = */  ahcp_ipv4_addresses_print,
231	/* [AHCP1_OPT_IPV6_PREFIX]            = */  ahcp_ipv6_prefixes_print,
232	/* [AHCP1_OPT_IPV4_PREFIX]            = */  NULL,
233	/* [AHCP1_OPT_IPV6_ADDRESS]           = */  ahcp_ipv6_addresses_print,
234	/* [AHCP1_OPT_IPV4_ADDRESS]           = */  ahcp_ipv4_addresses_print,
235	/* [AHCP1_OPT_IPV6_PREFIX_DELEGATION] = */  ahcp_ipv6_prefixes_print,
236	/* [AHCP1_OPT_IPV4_PREFIX_DELEGATION] = */  ahcp_ipv4_prefixes_print,
237	/* [AHCP1_OPT_NAME_SERVER]            = */  ahcp_ipv6_addresses_print,
238	/* [AHCP1_OPT_NTP_SERVER]             = */  ahcp_ipv6_addresses_print,
239};
240
241static void
242ahcp1_options_print(netdissect_options *ndo,
243                    const u_char *cp, uint16_t len)
244{
245	while (len) {
246		uint8_t option_no, option_len;
247
248		/* Option no */
249		option_no = GET_U_1(cp);
250		cp += 1;
251		len -= 1;
252		ND_PRINT("\n\t %s", tok2str(ahcp1_opt_str, "Unknown-%u", option_no));
253		if (option_no == AHCP1_OPT_PAD || option_no == AHCP1_OPT_MANDATORY)
254			continue;
255		/* Length */
256		if (!len)
257			goto invalid;
258		option_len = GET_U_1(cp);
259		cp += 1;
260		len -= 1;
261		if (option_len > len)
262			goto invalid;
263		/* Value */
264		if (option_no <= AHCP1_OPT_MAX && data_decoders[option_no] != NULL) {
265			data_decoders[option_no](ndo, cp, option_len);
266		} else {
267			ND_PRINT(" (Length %u)", option_len);
268			ND_TCHECK_LEN(cp, option_len);
269		}
270		cp += option_len;
271		len -= option_len;
272	}
273	return;
274
275invalid:
276	nd_print_invalid(ndo);
277	ND_TCHECK_LEN(cp, len);
278}
279
280static void
281ahcp1_body_print(netdissect_options *ndo,
282                 const u_char *cp, u_int len)
283{
284	uint8_t type, mbz;
285	uint16_t body_len;
286
287	if (len < AHCP1_BODY_MIN_LEN)
288		goto invalid;
289	/* Type */
290	type = GET_U_1(cp);
291	cp += 1;
292	len -= 1;
293	/* MBZ */
294	mbz = GET_U_1(cp);
295	cp += 1;
296	len -= 1;
297	/* Length */
298	body_len = GET_BE_U_2(cp);
299	cp += 2;
300	len -= 2;
301
302	if (ndo->ndo_vflag) {
303		ND_PRINT("\n\t%s", tok2str(ahcp1_msg_str, "Unknown-%u", type));
304		if (mbz != 0)
305			ND_PRINT(", MBZ %u", mbz);
306		ND_PRINT(", Length %u", body_len);
307	}
308	if (body_len > len)
309		goto invalid;
310
311	/* Options */
312	/* Here use "body_len", not "len" (ignore any extra data). */
313	if (ndo->ndo_vflag >= 2)
314		ahcp1_options_print(ndo, cp, body_len);
315	else
316		ND_TCHECK_LEN(cp, body_len);
317	return;
318
319invalid:
320	nd_print_invalid(ndo);
321	ND_TCHECK_LEN(cp, len);
322
323}
324
325void
326ahcp_print(netdissect_options *ndo,
327           const u_char *cp, u_int len)
328{
329	uint8_t version;
330
331	ndo->ndo_protocol = "ahcp";
332	nd_print_protocol_caps(ndo);
333	if (len < 2)
334		goto invalid;
335	/* Magic */
336	if (GET_U_1(cp) != AHCP_MAGIC_NUMBER)
337		goto invalid;
338	cp += 1;
339	len -= 1;
340	/* Version */
341	version = GET_U_1(cp);
342	cp += 1;
343	len -= 1;
344	switch (version) {
345		case AHCP_VERSION_1: {
346			ND_PRINT(" Version 1");
347			if (len < AHCP1_HEADER_FIX_LEN - 2)
348				goto invalid;
349			if (!ndo->ndo_vflag) {
350				ND_TCHECK_LEN(cp, AHCP1_HEADER_FIX_LEN - 2);
351				cp += AHCP1_HEADER_FIX_LEN - 2;
352				len -= AHCP1_HEADER_FIX_LEN - 2;
353			} else {
354				/* Hopcount */
355				ND_PRINT("\n\tHopcount %u", GET_U_1(cp));
356				cp += 1;
357				len -= 1;
358				/* Original Hopcount */
359				ND_PRINT(", Original Hopcount %u", GET_U_1(cp));
360				cp += 1;
361				len -= 1;
362				/* Nonce */
363				ND_PRINT(", Nonce 0x%08x", GET_BE_U_4(cp));
364				cp += 4;
365				len -= 4;
366				/* Source Id */
367				ND_PRINT(", Source Id %s", GET_LINKADDR_STRING(cp, LINKADDR_OTHER, 8));
368				cp += 8;
369				len -= 8;
370				/* Destination Id */
371				ND_PRINT(", Destination Id %s", GET_LINKADDR_STRING(cp, LINKADDR_OTHER, 8));
372				cp += 8;
373				len -= 8;
374			}
375			/* Body */
376			ahcp1_body_print(ndo, cp, len);
377			break;
378		}
379		default:
380			ND_PRINT(" Version %u (unknown)", version);
381			ND_TCHECK_LEN(cp, len);
382			break;
383	}
384	return;
385
386invalid:
387	nd_print_invalid(ndo);
388	ND_TCHECK_LEN(cp, len);
389}
390