xref: /freebsd/lib/libnetmap/nmreq.c (revision 7cc42f6d)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
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  * $FreeBSD$
30  */
31 
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <sys/ioctl.h>
35 #include <sys/mman.h>
36 #include <ctype.h>
37 #include <fcntl.h>
38 #include <inttypes.h>
39 #include <stdlib.h>
40 #include <stdio.h>
41 #include <stdarg.h>
42 #include <string.h>
43 #include <unistd.h>
44 #include <errno.h>
45 
46 //#define NMREQ_DEBUG
47 #ifdef NMREQ_DEBUG
48 #define NETMAP_WITH_LIBS
49 #define ED(...)	D(__VA_ARGS__)
50 #else
51 #define ED(...)
52 /* an identifier is a possibly empty sequence of alphanum characters and
53  * underscores
54  */
55 static int
56 nm_is_identifier(const char *s, const char *e)
57 {
58 	for (; s != e; s++) {
59 		if (!isalnum(*s) && *s != '_') {
60 			return 0;
61 		}
62 	}
63 
64 	return 1;
65 }
66 #endif /* NMREQ_DEBUG */
67 
68 #include <net/netmap_user.h>
69 #define LIBNETMAP_NOTHREADSAFE
70 #include "libnetmap.h"
71 
72 void
73 nmreq_push_option(struct nmreq_header *h, struct nmreq_option *o)
74 {
75 	o->nro_next = h->nr_options;
76 	h->nr_options = (uintptr_t)o;
77 }
78 
79 struct nmreq_prefix {
80 	const char *prefix;		/* the constant part of the prefix */
81 	size_t	    len;		/* its strlen() */
82 	uint32_t    flags;
83 #define	NR_P_ID		(1U << 0)	/* whether an identifier is needed */
84 #define NR_P_SKIP	(1U << 1)	/* whether the scope must be passed to netmap */
85 #define NR_P_EMPTYID	(1U << 2)	/* whether an empty identifier is allowed */
86 };
87 
88 #define declprefix(prefix, flags)	{ (prefix), (sizeof(prefix) - 1), (flags) }
89 
90 static struct nmreq_prefix nmreq_prefixes[] = {
91 	declprefix("netmap", NR_P_SKIP),
92 	declprefix(NM_BDG_NAME,	NR_P_ID|NR_P_EMPTYID),
93 	{ NULL } /* terminate the list */
94 };
95 
96 void
97 nmreq_header_init(struct nmreq_header *h, uint16_t reqtype, void *body)
98 {
99 	memset(h, 0, sizeof(*h));
100 	h->nr_version = NETMAP_API;
101 	h->nr_reqtype = reqtype;
102 	h->nr_body = (uintptr_t)body;
103 }
104 
105 int
106 nmreq_header_decode(const char **pifname, struct nmreq_header *h, struct nmctx *ctx)
107 {
108 	const char *scan = NULL;
109 	const char *vpname = NULL;
110 	const char *pipesep = NULL;
111 	u_int namelen;
112 	const char *ifname = *pifname;
113 	struct nmreq_prefix *p;
114 
115 	scan = ifname;
116 	for (p = nmreq_prefixes; p->prefix != NULL; p++) {
117 		if (!strncmp(scan, p->prefix, p->len))
118 			break;
119 	}
120 	if (p->prefix == NULL) {
121 		nmctx_ferror(ctx, "%s: invalid request, prefix unknown or missing", *pifname);
122 		goto fail;
123 	}
124 	scan += p->len;
125 
126 	vpname = index(scan, ':');
127 	if (vpname == NULL) {
128 		nmctx_ferror(ctx, "%s: missing ':'", ifname);
129 		goto fail;
130 	}
131 	if (vpname != scan) {
132 		/* there is an identifier, can we accept it? */
133 		if (!(p->flags & NR_P_ID)) {
134 			nmctx_ferror(ctx, "%s: no identifier allowed between '%s' and ':'", *pifname, p->prefix);
135 			goto fail;
136 		}
137 
138 		if (!nm_is_identifier(scan, vpname)) {
139 			nmctx_ferror(ctx, "%s: invalid identifier '%.*s'", *pifname, vpname - scan, scan);
140 			goto fail;
141 		}
142 	} else {
143 		if ((p->flags & NR_P_ID) && !(p->flags & NR_P_EMPTYID)) {
144 			nmctx_ferror(ctx, "%s: identifier is missing between '%s' and ':'", *pifname, p->prefix);
145 			goto fail;
146 		}
147 	}
148 	++vpname; /* skip the colon */
149 	if (p->flags & NR_P_SKIP)
150 		ifname = vpname;
151 	scan = vpname;
152 
153 	/* scan for a separator */
154 	for (; *scan && !index("-*^/@", *scan); scan++)
155 		;
156 
157 	/* search for possible pipe indicators */
158 	for (pipesep = vpname; pipesep != scan && !index("{}", *pipesep); pipesep++)
159 		;
160 
161 	if (!nm_is_identifier(vpname, pipesep)) {
162 		nmctx_ferror(ctx, "%s: invalid port name '%.*s'", *pifname,
163 				pipesep - vpname, vpname);
164 		goto fail;
165 	}
166 	if (pipesep != scan) {
167 		pipesep++;
168 		if (*pipesep == '\0') {
169 			nmctx_ferror(ctx, "%s: invalid empty pipe name", *pifname);
170 			goto fail;
171 		}
172 		if (!nm_is_identifier(pipesep, scan)) {
173 			nmctx_ferror(ctx, "%s: invalid pipe name '%.*s'", *pifname, scan - pipesep, pipesep);
174 			goto fail;
175 		}
176 	}
177 
178 	namelen = scan - ifname;
179 	if (namelen >= sizeof(h->nr_name)) {
180 		nmctx_ferror(ctx, "name '%.*s' too long", namelen, ifname);
181 		goto fail;
182 	}
183 	if (namelen == 0) {
184 		nmctx_ferror(ctx, "%s: invalid empty port name", *pifname);
185 		goto fail;
186 	}
187 
188 	/* fill the header */
189 	memcpy(h->nr_name, ifname, namelen);
190 	h->nr_name[namelen] = '\0';
191 	ED("name %s", h->nr_name);
192 
193 	*pifname = scan;
194 
195 	return 0;
196 fail:
197 	errno = EINVAL;
198 	return -1;
199 }
200 
201 
202 /*
203  * 0 not recognized
204  * -1 error
205  *  >= 0 mem_id
206  */
207 int32_t
208 nmreq_get_mem_id(const char **pifname, struct nmctx *ctx)
209 {
210 	int fd = -1;
211 	struct nmreq_header gh;
212 	struct nmreq_port_info_get gb;
213 	const char *ifname;
214 
215 	errno = 0;
216 	ifname = *pifname;
217 
218 	if (ifname == NULL)
219 		goto fail;
220 
221 	/* try to look for a netmap port with this name */
222 	fd = open("/dev/netmap", O_RDWR);
223 	if (fd < 0) {
224 		nmctx_ferror(ctx, "cannot open /dev/netmap: %s", strerror(errno));
225 		goto fail;
226 	}
227 	nmreq_header_init(&gh, NETMAP_REQ_PORT_INFO_GET, &gb);
228 	if (nmreq_header_decode(&ifname, &gh, ctx) < 0) {
229 		goto fail;
230 	}
231 	memset(&gb, 0, sizeof(gb));
232 	if (ioctl(fd, NIOCCTRL, &gh) < 0) {
233 		nmctx_ferror(ctx, "cannot get info for '%s': %s", *pifname, strerror(errno));
234 		goto fail;
235 	}
236 	*pifname = ifname;
237 	close(fd);
238 	return gb.nr_mem_id;
239 
240 fail:
241 	if (fd >= 0)
242 		close(fd);
243 	if (!errno)
244 		errno = EINVAL;
245 	return -1;
246 }
247 
248 
249 int
250 nmreq_register_decode(const char **pifname, struct nmreq_register *r, struct nmctx *ctx)
251 {
252 	enum { P_START, P_RNGSFXOK, P_GETNUM, P_FLAGS, P_FLAGSOK, P_MEMID, P_ONESW } p_state;
253 	long num;
254 	const char *scan = *pifname;
255 	uint32_t nr_mode;
256 	uint16_t nr_mem_id;
257 	uint16_t nr_ringid;
258 	uint64_t nr_flags;
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;
607 
608 	for (o = (struct nmreq_option *)h->nr_options; o != NULL;
609 			o = (struct nmreq_option *)o->nro_next) {
610 		if (o->nro_reqtype == t)
611 			break;
612 	}
613 	return o;
614 }
615 
616 void
617 nmreq_remove_option(struct nmreq_header *h, struct nmreq_option *o)
618 {
619         struct nmreq_option **nmo;
620 
621 	for (nmo = (struct nmreq_option **)&h->nr_options; *nmo != NULL;
622 			nmo = (struct nmreq_option **)&(*nmo)->nro_next) {
623 		if (*nmo == o) {
624 			*((uint64_t *)(*nmo)) = o->nro_next;
625 			o->nro_next = (uint64_t)(uintptr_t)NULL;
626 			break;
627 		}
628 	}
629 }
630 
631 void
632 nmreq_free_options(struct nmreq_header *h)
633 {
634 	struct nmreq_option *o, *next;
635 
636 	for (o = (struct nmreq_option *)h->nr_options; o != NULL; o = next) {
637 		next = (struct nmreq_option *)o->nro_next;
638 		free(o);
639 	}
640 }
641 
642 const char*
643 nmreq_option_name(uint32_t nro_reqtype)
644 {
645 	switch (nro_reqtype) {
646 	case NETMAP_REQ_OPT_EXTMEM:
647 		return "extmem";
648 	case NETMAP_REQ_OPT_SYNC_KLOOP_EVENTFDS:
649 		return "sync-kloop-eventfds";
650 	case NETMAP_REQ_OPT_CSB:
651 		return "csb";
652 	case NETMAP_REQ_OPT_SYNC_KLOOP_MODE:
653 		return "sync-kloop-mode";
654 	default:
655 		return "unknown";
656 	}
657 }
658 
659 #if 0
660 #include <inttypes.h>
661 static void
662 nmreq_dump(struct nmport_d *d)
663 {
664 	printf("header:\n");
665 	printf("   nr_version:  %"PRIu16"\n", d->hdr.nr_version);
666 	printf("   nr_reqtype:  %"PRIu16"\n", d->hdr.nr_reqtype);
667 	printf("   nr_reserved: %"PRIu32"\n", d->hdr.nr_reserved);
668 	printf("   nr_name:     %s\n", d->hdr.nr_name);
669 	printf("   nr_options:  %lx\n", (unsigned long)d->hdr.nr_options);
670 	printf("   nr_body:     %lx\n", (unsigned long)d->hdr.nr_body);
671 	printf("\n");
672 	printf("register (%p):\n", (void *)d->hdr.nr_body);
673 	printf("   nr_mem_id:   %"PRIu16"\n", d->reg.nr_mem_id);
674 	printf("   nr_ringid:   %"PRIu16"\n", d->reg.nr_ringid);
675 	printf("   nr_mode:     %lx\n", (unsigned long)d->reg.nr_mode);
676 	printf("   nr_flags:    %lx\n", (unsigned long)d->reg.nr_flags);
677 	printf("\n");
678 	if (d->hdr.nr_options) {
679 		struct nmreq_opt_extmem *e = (struct nmreq_opt_extmem *)d->hdr.nr_options;
680 		printf("opt_extmem (%p):\n", e);
681 		printf("   nro_opt.nro_next:    %lx\n", (unsigned long)e->nro_opt.nro_next);
682 		printf("   nro_opt.nro_reqtype: %"PRIu32"\n", e->nro_opt.nro_reqtype);
683 		printf("   nro_usrptr:          %lx\n", (unsigned long)e->nro_usrptr);
684 		printf("   nro_info.nr_memsize  %"PRIu64"\n", e->nro_info.nr_memsize);
685 	}
686 	printf("\n");
687 	printf("mem (%p):\n", d->mem);
688 	printf("   refcount:   %d\n", d->mem->refcount);
689 	printf("   mem:        %p\n", d->mem->mem);
690 	printf("   size:       %zu\n", d->mem->size);
691 	printf("\n");
692 	printf("rings:\n");
693 	printf("   tx:   [%d, %d]\n", d->first_tx_ring, d->last_tx_ring);
694 	printf("   rx:   [%d, %d]\n", d->first_rx_ring, d->last_rx_ring);
695 }
696 int
697 main(int argc, char *argv[])
698 {
699 	struct nmport_d *d;
700 
701 	if (argc < 2) {
702 		fprintf(stderr, "usage: %s netmap-expr\n", argv[0]);
703 		return 1;
704 	}
705 
706 	d = nmport_open(argv[1]);
707 	if (d != NULL) {
708 		nmreq_dump(d);
709 		nmport_close(d);
710 	}
711 
712 	return 0;
713 }
714 #endif
715