xref: /freebsd/lib/libnetmap/nmreq.c (revision 2ba1d4970a06a1660b46f6fd99351d154b295683)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (C) 2018 Universita` di Pisa
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  *   1. Redistributions of source code must retain the above copyright
12  *      notice, this list of conditions and the following disclaimer.
13  *   2. Redistributions in binary form must reproduce the above copyright
14  *      notice, this list of conditions and the following disclaimer in the
15  *      documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29 
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <sys/ioctl.h>
33 #include <sys/mman.h>
34 #include <ctype.h>
35 #include <fcntl.h>
36 #include <inttypes.h>
37 #include <stdlib.h>
38 #include <stdio.h>
39 #include <stdarg.h>
40 #include <string.h>
41 #include <unistd.h>
42 #include <errno.h>
43 
44 //#define NMREQ_DEBUG
45 #ifdef NMREQ_DEBUG
46 #define NETMAP_WITH_LIBS
47 #define ED(...)	D(__VA_ARGS__)
48 #else
49 #define ED(...)
50 /* an identifier is a possibly empty sequence of alphanum characters and
51  * underscores
52  */
53 static int
54 nm_is_identifier(const char *s, const char *e)
55 {
56 	for (; s != e; s++) {
57 		if (!isalnum(*s) && *s != '_') {
58 			return 0;
59 		}
60 	}
61 
62 	return 1;
63 }
64 #endif /* NMREQ_DEBUG */
65 
66 #include <net/netmap_user.h>
67 #define LIBNETMAP_NOTHREADSAFE
68 #include "libnetmap.h"
69 
70 void
71 nmreq_push_option(struct nmreq_header *h, struct nmreq_option *o)
72 {
73 	o->nro_next = h->nr_options;
74 	h->nr_options = (uintptr_t)o;
75 }
76 
77 struct nmreq_prefix {
78 	const char *prefix;		/* the constant part of the prefix */
79 	size_t	    len;		/* its strlen() */
80 	uint32_t    flags;
81 #define	NR_P_ID		(1U << 0)	/* whether an identifier is needed */
82 #define NR_P_SKIP	(1U << 1)	/* whether the scope must be passed to netmap */
83 #define NR_P_EMPTYID	(1U << 2)	/* whether an empty identifier is allowed */
84 };
85 
86 #define declprefix(prefix, flags)	{ (prefix), (sizeof(prefix) - 1), (flags) }
87 
88 static struct nmreq_prefix nmreq_prefixes[] = {
89 	declprefix("netmap", NR_P_SKIP),
90 	declprefix(NM_BDG_NAME,	NR_P_ID|NR_P_EMPTYID),
91 	{ NULL } /* terminate the list */
92 };
93 
94 void
95 nmreq_header_init(struct nmreq_header *h, uint16_t reqtype, void *body)
96 {
97 	memset(h, 0, sizeof(*h));
98 	h->nr_version = NETMAP_API;
99 	h->nr_reqtype = reqtype;
100 	h->nr_body = (uintptr_t)body;
101 }
102 
103 int
104 nmreq_header_decode(const char **pifname, struct nmreq_header *h, struct nmctx *ctx)
105 {
106 	const char *scan = NULL;
107 	const char *vpname = NULL;
108 	const char *pipesep = NULL;
109 	u_int namelen;
110 	const char *ifname = *pifname;
111 	struct nmreq_prefix *p;
112 
113 	scan = ifname;
114 	for (p = nmreq_prefixes; p->prefix != NULL; p++) {
115 		if (!strncmp(scan, p->prefix, p->len))
116 			break;
117 	}
118 	if (p->prefix == NULL) {
119 		nmctx_ferror(ctx, "%s: invalid request, prefix unknown or missing", *pifname);
120 		goto fail;
121 	}
122 	scan += p->len;
123 
124 	vpname = index(scan, ':');
125 	if (vpname == NULL) {
126 		nmctx_ferror(ctx, "%s: missing ':'", ifname);
127 		goto fail;
128 	}
129 	if (vpname != scan) {
130 		/* there is an identifier, can we accept it? */
131 		if (!(p->flags & NR_P_ID)) {
132 			nmctx_ferror(ctx, "%s: no identifier allowed between '%s' and ':'", *pifname, p->prefix);
133 			goto fail;
134 		}
135 
136 		if (!nm_is_identifier(scan, vpname)) {
137 			nmctx_ferror(ctx, "%s: invalid identifier '%.*s'", *pifname, vpname - scan, scan);
138 			goto fail;
139 		}
140 	} else {
141 		if ((p->flags & NR_P_ID) && !(p->flags & NR_P_EMPTYID)) {
142 			nmctx_ferror(ctx, "%s: identifier is missing between '%s' and ':'", *pifname, p->prefix);
143 			goto fail;
144 		}
145 	}
146 	++vpname; /* skip the colon */
147 	if (p->flags & NR_P_SKIP)
148 		ifname = vpname;
149 	scan = vpname;
150 
151 	/* scan for a separator */
152 	for (; *scan && !index("-*^/@", *scan); scan++)
153 		;
154 
155 	/* search for possible pipe indicators */
156 	for (pipesep = vpname; pipesep != scan && !index("{}", *pipesep); pipesep++)
157 		;
158 
159 	if (!nm_is_identifier(vpname, pipesep)) {
160 		nmctx_ferror(ctx, "%s: invalid port name '%.*s'", *pifname,
161 				pipesep - vpname, vpname);
162 		goto fail;
163 	}
164 	if (pipesep != scan) {
165 		pipesep++;
166 		if (*pipesep == '\0') {
167 			nmctx_ferror(ctx, "%s: invalid empty pipe name", *pifname);
168 			goto fail;
169 		}
170 		if (!nm_is_identifier(pipesep, scan)) {
171 			nmctx_ferror(ctx, "%s: invalid pipe name '%.*s'", *pifname, scan - pipesep, pipesep);
172 			goto fail;
173 		}
174 	}
175 
176 	namelen = scan - ifname;
177 	if (namelen >= sizeof(h->nr_name)) {
178 		nmctx_ferror(ctx, "name '%.*s' too long", namelen, ifname);
179 		goto fail;
180 	}
181 	if (namelen == 0) {
182 		nmctx_ferror(ctx, "%s: invalid empty port name", *pifname);
183 		goto fail;
184 	}
185 
186 	/* fill the header */
187 	memcpy(h->nr_name, ifname, namelen);
188 	h->nr_name[namelen] = '\0';
189 	ED("name %s", h->nr_name);
190 
191 	*pifname = scan;
192 
193 	return 0;
194 fail:
195 	errno = EINVAL;
196 	return -1;
197 }
198 
199 
200 /*
201  * 0 not recognized
202  * -1 error
203  *  >= 0 mem_id
204  */
205 int32_t
206 nmreq_get_mem_id(const char **pifname, struct nmctx *ctx)
207 {
208 	int fd = -1;
209 	struct nmreq_header gh;
210 	struct nmreq_port_info_get gb;
211 	const char *ifname;
212 
213 	errno = 0;
214 	ifname = *pifname;
215 
216 	if (ifname == NULL)
217 		goto fail;
218 
219 	/* try to look for a netmap port with this name */
220 	fd = open("/dev/netmap", O_RDWR);
221 	if (fd < 0) {
222 		nmctx_ferror(ctx, "cannot open /dev/netmap: %s", strerror(errno));
223 		goto fail;
224 	}
225 	nmreq_header_init(&gh, NETMAP_REQ_PORT_INFO_GET, &gb);
226 	if (nmreq_header_decode(&ifname, &gh, ctx) < 0) {
227 		goto fail;
228 	}
229 	memset(&gb, 0, sizeof(gb));
230 	if (ioctl(fd, NIOCCTRL, &gh) < 0) {
231 		nmctx_ferror(ctx, "cannot get info for '%s': %s", *pifname, strerror(errno));
232 		goto fail;
233 	}
234 	*pifname = ifname;
235 	close(fd);
236 	return gb.nr_mem_id;
237 
238 fail:
239 	if (fd >= 0)
240 		close(fd);
241 	if (!errno)
242 		errno = EINVAL;
243 	return -1;
244 }
245 
246 
247 int
248 nmreq_register_decode(const char **pifname, struct nmreq_register *r, struct nmctx *ctx)
249 {
250 	enum { P_START, P_RNGSFXOK, P_GETNUM, P_FLAGS, P_FLAGSOK, P_MEMID, P_ONESW } p_state;
251 	long num;
252 	const char *scan = *pifname;
253 	uint32_t nr_mode;
254 	uint16_t nr_mem_id;
255 	uint16_t nr_ringid;
256 	uint64_t nr_flags;
257 
258 	errno = 0;
259 
260 	/* fill the request */
261 
262 	p_state = P_START;
263 	/* defaults */
264 	nr_mode = NR_REG_ALL_NIC; /* default for no suffix */
265 	nr_mem_id = r->nr_mem_id; /* if non-zero, further updates are disabled */
266 	nr_ringid = 0;
267 	nr_flags = 0;
268 	while (*scan) {
269 		switch (p_state) {
270 		case P_START:
271 			switch (*scan) {
272 			case '^': /* only SW ring */
273 				nr_mode = NR_REG_SW;
274 				p_state = P_ONESW;
275 				break;
276 			case '*': /* NIC and SW */
277 				nr_mode = NR_REG_NIC_SW;
278 				p_state = P_RNGSFXOK;
279 				break;
280 			case '-': /* one NIC ring pair */
281 				nr_mode = NR_REG_ONE_NIC;
282 				p_state = P_GETNUM;
283 				break;
284 			case '/': /* start of flags */
285 				p_state = P_FLAGS;
286 				break;
287 			case '@': /* start of memid */
288 				p_state = P_MEMID;
289 				break;
290 			default:
291 				nmctx_ferror(ctx, "unknown modifier: '%c'", *scan);
292 				goto fail;
293 			}
294 			scan++;
295 			break;
296 		case P_RNGSFXOK:
297 			switch (*scan) {
298 			case '/':
299 				p_state = P_FLAGS;
300 				break;
301 			case '@':
302 				p_state = P_MEMID;
303 				break;
304 			default:
305 				nmctx_ferror(ctx, "unexpected character: '%c'", *scan);
306 				goto fail;
307 			}
308 			scan++;
309 			break;
310 		case P_GETNUM:
311 			if (!isdigit(*scan)) {
312 				nmctx_ferror(ctx, "got '%s' while expecting a number", scan);
313 				goto fail;
314 			}
315 			num = strtol(scan, (char **)&scan, 10);
316 			if (num < 0 || num >= NETMAP_RING_MASK) {
317 				nmctx_ferror(ctx, "'%ld' out of range [0, %d)",
318 						num, NETMAP_RING_MASK);
319 				goto fail;
320 			}
321 			nr_ringid = num & NETMAP_RING_MASK;
322 			p_state = P_RNGSFXOK;
323 			break;
324 		case P_FLAGS:
325 		case P_FLAGSOK:
326 			switch (*scan) {
327 			case '@':
328 				p_state = P_MEMID;
329 				scan++;
330 				continue;
331 			case 'x':
332 				nr_flags |= NR_EXCLUSIVE;
333 				break;
334 			case 'z':
335 				nr_flags |= NR_ZCOPY_MON;
336 				break;
337 			case 't':
338 				nr_flags |= NR_MONITOR_TX;
339 				break;
340 			case 'r':
341 				nr_flags |= NR_MONITOR_RX;
342 				break;
343 			case 'R':
344 				nr_flags |= NR_RX_RINGS_ONLY;
345 				break;
346 			case 'T':
347 				nr_flags |= NR_TX_RINGS_ONLY;
348 				break;
349 			default:
350 				nmctx_ferror(ctx, "unrecognized flag: '%c'", *scan);
351 				goto fail;
352 			}
353 			scan++;
354 			p_state = P_FLAGSOK;
355 			break;
356 		case P_MEMID:
357 			if (!isdigit(*scan)) {
358 				scan--;	/* escape to options */
359 				goto out;
360 			}
361 			num = strtol(scan, (char **)&scan, 10);
362 			if (num <= 0) {
363 				nmctx_ferror(ctx, "invalid mem_id: '%ld'", num);
364 				goto fail;
365 			}
366 			if (nr_mem_id && nr_mem_id != num) {
367 				nmctx_ferror(ctx, "invalid setting of mem_id to %ld (already set to %"PRIu16")", num, nr_mem_id);
368 				goto fail;
369 			}
370 			nr_mem_id = num;
371 			p_state = P_RNGSFXOK;
372 			break;
373 		case P_ONESW:
374 			if (!isdigit(*scan)) {
375 				p_state = P_RNGSFXOK;
376 			} else {
377 				nr_mode = NR_REG_ONE_SW;
378 				p_state = P_GETNUM;
379 			}
380 			break;
381 		}
382 	}
383 	if (p_state == P_MEMID && !*scan) {
384 		nmctx_ferror(ctx, "invalid empty mem_id");
385 		goto fail;
386 	}
387 	if (p_state != P_START && p_state != P_RNGSFXOK &&
388 	    p_state != P_FLAGSOK && p_state != P_MEMID && p_state != P_ONESW) {
389 		nmctx_ferror(ctx, "unexpected end of request");
390 		goto fail;
391 	}
392 out:
393 	ED("flags: %s %s %s %s %s %s",
394 			(nr_flags & NR_EXCLUSIVE) ? "EXCLUSIVE" : "",
395 			(nr_flags & NR_ZCOPY_MON) ? "ZCOPY_MON" : "",
396 			(nr_flags & NR_MONITOR_TX) ? "MONITOR_TX" : "",
397 			(nr_flags & NR_MONITOR_RX) ? "MONITOR_RX" : "",
398 			(nr_flags & NR_RX_RINGS_ONLY) ? "RX_RINGS_ONLY" : "",
399 			(nr_flags & NR_TX_RINGS_ONLY) ? "TX_RINGS_ONLY" : "");
400 	r->nr_mode = nr_mode;
401 	r->nr_ringid = nr_ringid;
402 	r->nr_flags = nr_flags;
403 	r->nr_mem_id = nr_mem_id;
404 	*pifname = scan;
405 	return 0;
406 
407 fail:
408 	if (!errno)
409 		errno = EINVAL;
410 	return -1;
411 }
412 
413 
414 static int
415 nmreq_option_parsekeys(const char *prefix, char *body, struct nmreq_opt_parser *p,
416 		struct nmreq_parse_ctx *pctx)
417 {
418 	char *scan;
419 	char delim1;
420 	struct nmreq_opt_key *k;
421 
422 	scan = body;
423 	delim1 = *scan;
424 	while (delim1 != '\0') {
425 		char *key, *value;
426 		char delim;
427 		size_t vlen;
428 
429 		key = scan;
430 		for ( scan++; *scan != '\0' && *scan != '=' && *scan != ','; scan++) {
431 			if (*scan == '-')
432 				*scan = '_';
433 		}
434 		delim = *scan;
435 		*scan = '\0';
436 		scan++;
437 		for (k = p->keys; (k - p->keys) < NMREQ_OPT_MAXKEYS && k->key != NULL;
438 				k++) {
439 			if (!strcmp(k->key, key))
440 				goto found;
441 
442 		}
443 		nmctx_ferror(pctx->ctx, "unknown key: '%s'", key);
444 		errno = EINVAL;
445 		return -1;
446 	found:
447 		if (pctx->keys[k->id] != NULL) {
448 			nmctx_ferror(pctx->ctx, "option '%s': duplicate key '%s', already set to '%s'",
449 					prefix, key, pctx->keys[k->id]);
450 			errno = EINVAL;
451 			return -1;
452 		}
453 		value = scan;
454 		for ( ; *scan != '\0' && *scan != ','; scan++)
455 			;
456 		delim1 = *scan;
457 		*scan = '\0';
458 		vlen = scan - value;
459 		scan++;
460 		if (delim == '=') {
461 			pctx->keys[k->id] = (vlen ? value : NULL);
462 		} else {
463 			if (!(k->flags & NMREQ_OPTK_ALLOWEMPTY)) {
464 				nmctx_ferror(pctx->ctx, "option '%s': missing '=value' for key '%s'",
465 						prefix, key);
466 				errno = EINVAL;
467 				return -1;
468 			}
469 			pctx->keys[k->id] = key;
470 		}
471 	}
472 	/* now check that all no-default keys have been assigned */
473 	for (k = p->keys; (k - p->keys) < NMREQ_OPT_MAXKEYS && k->key != NULL; k++) {
474 		if ((k->flags & NMREQ_OPTK_MUSTSET) && pctx->keys[k->id] == NULL) {
475 			nmctx_ferror(pctx->ctx, "option '%s': mandatory key '%s' not assigned",
476 					prefix, k->key);
477 			errno = EINVAL;
478 			return -1;
479 		}
480 	}
481 	return 0;
482 }
483 
484 
485 static int
486 nmreq_option_decode1(char *opt, struct nmreq_opt_parser *parsers,
487 		void *token, struct nmctx *ctx)
488 {
489 	struct nmreq_opt_parser *p;
490 	const char *prefix;
491 	char *scan;
492 	char delim;
493 	struct nmreq_parse_ctx pctx;
494 	int i;
495 
496 	prefix = opt;
497 	/* find the delimiter */
498 	for (scan = opt; *scan != '\0' && *scan != ':' && *scan != '='; scan++)
499 		;
500 	delim = *scan;
501 	*scan = '\0';
502 	scan++;
503 	/* find the prefix */
504 	for (p = parsers; p != NULL; p = p->next) {
505 		if (!strcmp(prefix, p->prefix))
506 			break;
507 	}
508 	if (p == NULL) {
509 		nmctx_ferror(ctx, "unknown option: '%s'", prefix);
510 		errno = EINVAL;
511 		return -1;
512 	}
513 	if (p->flags & NMREQ_OPTF_DISABLED) {
514 		nmctx_ferror(ctx, "option '%s' is not supported", prefix);
515 		errno = EOPNOTSUPP;
516 		return -1;
517 	}
518 	/* prepare the parse context */
519 	pctx.ctx = ctx;
520 	pctx.token = token;
521 	for (i = 0; i < NMREQ_OPT_MAXKEYS; i++)
522 		pctx.keys[i] = NULL;
523 	switch (delim) {
524 	case '\0':
525 		/* no body */
526 		if (!(p->flags & NMREQ_OPTF_ALLOWEMPTY)) {
527 			nmctx_ferror(ctx, "syntax error: missing body after '%s'",
528 					prefix);
529 			errno = EINVAL;
530 			return -1;
531 		}
532 		break;
533 	case '=': /* the body goes to the default option key, if any */
534 		if (p->default_key < 0 || p->default_key >= NMREQ_OPT_MAXKEYS) {
535 			nmctx_ferror(ctx, "syntax error: '=' not valid after '%s'",
536 					prefix);
537 			errno = EINVAL;
538 			return -1;
539 		}
540 		if (*scan == '\0') {
541 			nmctx_ferror(ctx, "missing value for option '%s'", prefix);
542 			errno = EINVAL;
543 			return -1;
544 		}
545 		pctx.keys[p->default_key] = scan;
546 		break;
547 	case ':': /* parse 'key=value' strings */
548 		if (nmreq_option_parsekeys(prefix, scan, p, &pctx) < 0)
549 			return -1;
550 		break;
551 	}
552 	return p->parse(&pctx);
553 }
554 
555 int
556 nmreq_options_decode(const char *opt, struct nmreq_opt_parser parsers[],
557 		void *token, struct nmctx *ctx)
558 {
559 	const char *scan, *opt1;
560 	char *w;
561 	size_t len;
562 	int ret;
563 
564 	if (*opt == '\0')
565 		return 0; /* empty list, OK */
566 
567 	if (*opt != '@') {
568 		nmctx_ferror(ctx, "option list does not start with '@'");
569 		errno = EINVAL;
570 		return -1;
571 	}
572 
573 	scan = opt;
574 	do {
575 		scan++; /* skip the plus */
576 		opt1 = scan; /* start of option */
577 		/* find the end of the option */
578 		for ( ; *scan != '\0' && *scan != '@'; scan++)
579 			;
580 		len = scan - opt1;
581 		if (len == 0) {
582 			nmctx_ferror(ctx, "invalid empty option");
583 			errno = EINVAL;
584 			return -1;
585 		}
586 		w = nmctx_malloc(ctx, len + 1);
587 		if (w == NULL) {
588 			nmctx_ferror(ctx, "out of memory");
589 			errno = ENOMEM;
590 			return -1;
591 		}
592 		memcpy(w, opt1, len);
593 		w[len] = '\0';
594 		ret = nmreq_option_decode1(w, parsers, token, ctx);
595 		nmctx_free(ctx, w);
596 		if (ret < 0)
597 			return -1;
598 	} while (*scan != '\0');
599 
600 	return 0;
601 }
602 
603 struct nmreq_option *
604 nmreq_find_option(struct nmreq_header *h, uint32_t t)
605 {
606 	struct nmreq_option *o = NULL;
607 
608 	nmreq_foreach_option(h, o) {
609 		if (o->nro_reqtype == t)
610 			break;
611 	}
612 	return o;
613 }
614 
615 void
616 nmreq_remove_option(struct nmreq_header *h, struct nmreq_option *o)
617 {
618 	struct nmreq_option **nmo;
619 
620 	for (nmo = (struct nmreq_option **)&h->nr_options; *nmo != NULL;
621 	    nmo = (struct nmreq_option **)&(*nmo)->nro_next) {
622 		if (*nmo == o) {
623 			*((uint64_t *)(*nmo)) = o->nro_next;
624 			o->nro_next = (uint64_t)(uintptr_t)NULL;
625 			break;
626 		}
627 	}
628 }
629 
630 void
631 nmreq_free_options(struct nmreq_header *h)
632 {
633 	struct nmreq_option *o, *next;
634 
635 	/*
636 	 * Note: can't use nmreq_foreach_option() here; it frees the
637 	 * list as it's walking and nmreq_foreach_option() isn't
638 	 * modification-safe.
639 	 */
640 	for (o = (struct nmreq_option *)(uintptr_t)h->nr_options; o != NULL;
641 	    o = next) {
642 		next = (struct nmreq_option *)(uintptr_t)o->nro_next;
643 		free(o);
644 	}
645 }
646 
647 const char*
648 nmreq_option_name(uint32_t nro_reqtype)
649 {
650 	switch (nro_reqtype) {
651 	case NETMAP_REQ_OPT_EXTMEM:
652 		return "extmem";
653 	case NETMAP_REQ_OPT_SYNC_KLOOP_EVENTFDS:
654 		return "sync-kloop-eventfds";
655 	case NETMAP_REQ_OPT_CSB:
656 		return "csb";
657 	case NETMAP_REQ_OPT_SYNC_KLOOP_MODE:
658 		return "sync-kloop-mode";
659 	case NETMAP_REQ_OPT_OFFSETS:
660 		return "offsets";
661 	default:
662 		return "unknown";
663 	}
664 }
665 
666 #if 0
667 #include <inttypes.h>
668 static void
669 nmreq_dump(struct nmport_d *d)
670 {
671 	printf("header:\n");
672 	printf("   nr_version:  %"PRIu16"\n", d->hdr.nr_version);
673 	printf("   nr_reqtype:  %"PRIu16"\n", d->hdr.nr_reqtype);
674 	printf("   nr_reserved: %"PRIu32"\n", d->hdr.nr_reserved);
675 	printf("   nr_name:     %s\n", d->hdr.nr_name);
676 	printf("   nr_options:  %lx\n", (unsigned long)d->hdr.nr_options);
677 	printf("   nr_body:     %lx\n", (unsigned long)d->hdr.nr_body);
678 	printf("\n");
679 	printf("register (%p):\n", (void *)d->hdr.nr_body);
680 	printf("   nr_mem_id:   %"PRIu16"\n", d->reg.nr_mem_id);
681 	printf("   nr_ringid:   %"PRIu16"\n", d->reg.nr_ringid);
682 	printf("   nr_mode:     %lx\n", (unsigned long)d->reg.nr_mode);
683 	printf("   nr_flags:    %lx\n", (unsigned long)d->reg.nr_flags);
684 	printf("\n");
685 	if (d->hdr.nr_options) {
686 		struct nmreq_opt_extmem *e = (struct nmreq_opt_extmem *)d->hdr.nr_options;
687 		printf("opt_extmem (%p):\n", e);
688 		printf("   nro_opt.nro_next:    %lx\n", (unsigned long)e->nro_opt.nro_next);
689 		printf("   nro_opt.nro_reqtype: %"PRIu32"\n", e->nro_opt.nro_reqtype);
690 		printf("   nro_usrptr:          %lx\n", (unsigned long)e->nro_usrptr);
691 		printf("   nro_info.nr_memsize  %"PRIu64"\n", e->nro_info.nr_memsize);
692 	}
693 	printf("\n");
694 	printf("mem (%p):\n", d->mem);
695 	printf("   refcount:   %d\n", d->mem->refcount);
696 	printf("   mem:        %p\n", d->mem->mem);
697 	printf("   size:       %zu\n", d->mem->size);
698 	printf("\n");
699 	printf("rings:\n");
700 	printf("   tx:   [%d, %d]\n", d->first_tx_ring, d->last_tx_ring);
701 	printf("   rx:   [%d, %d]\n", d->first_rx_ring, d->last_rx_ring);
702 }
703 int
704 main(int argc, char *argv[])
705 {
706 	struct nmport_d *d;
707 
708 	if (argc < 2) {
709 		fprintf(stderr, "usage: %s netmap-expr\n", argv[0]);
710 		return 1;
711 	}
712 
713 	d = nmport_open(argv[1]);
714 	if (d != NULL) {
715 		nmreq_dump(d);
716 		nmport_close(d);
717 	}
718 
719 	return 0;
720 }
721 #endif
722