1 1.1 rmind /*- 2 1.36 joe * Copyright (c) 2013-2025 The NetBSD Foundation, Inc. 3 1.1 rmind * All rights reserved. 4 1.1 rmind * 5 1.1 rmind * This code is derived from software contributed to The NetBSD Foundation 6 1.1 rmind * by Mindaugas Rasiukevicius. 7 1.1 rmind * 8 1.1 rmind * Redistribution and use in source and binary forms, with or without 9 1.1 rmind * modification, are permitted provided that the following conditions 10 1.1 rmind * are met: 11 1.1 rmind * 1. Redistributions of source code must retain the above copyright 12 1.1 rmind * notice, this list of conditions and the following disclaimer. 13 1.1 rmind * 2. Redistributions in binary form must reproduce the above copyright 14 1.1 rmind * notice, this list of conditions and the following disclaimer in the 15 1.1 rmind * documentation and/or other materials provided with the distribution. 16 1.1 rmind * 17 1.1 rmind * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 18 1.1 rmind * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 1.1 rmind * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 20 1.1 rmind * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 21 1.1 rmind * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 1.1 rmind * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 1.1 rmind * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 1.1 rmind * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 1.1 rmind * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 1.1 rmind * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 1.1 rmind * POSSIBILITY OF SUCH DAMAGE. 28 1.1 rmind */ 29 1.1 rmind 30 1.1 rmind /* 31 1.1 rmind * NPF configuration printing. 32 1.1 rmind * 33 1.1 rmind * Each rule having BPF byte-code has a binary description. 34 1.1 rmind */ 35 1.1 rmind 36 1.1 rmind #include <sys/cdefs.h> 37 1.38 joe __RCSID("$NetBSD: npf_show.c,v 1.38 2025/07/08 14:02:16 joe Exp $"); 38 1.1 rmind 39 1.1 rmind #include <sys/socket.h> 40 1.20 christos #define __FAVOR_BSD 41 1.1 rmind #include <netinet/in.h> 42 1.1 rmind #include <netinet/tcp.h> 43 1.1 rmind #include <net/if.h> 44 1.1 rmind 45 1.1 rmind #include <stdio.h> 46 1.1 rmind #include <stdlib.h> 47 1.1 rmind #include <string.h> 48 1.1 rmind #include <stdbool.h> 49 1.1 rmind #include <inttypes.h> 50 1.1 rmind #include <errno.h> 51 1.1 rmind #include <err.h> 52 1.1 rmind 53 1.1 rmind #include "npfctl.h" 54 1.1 rmind 55 1.32 rmind #define SEEN_PROTO 0x01 56 1.32 rmind 57 1.32 rmind typedef struct { 58 1.32 rmind char ** values; 59 1.32 rmind unsigned count; 60 1.32 rmind } elem_list_t; 61 1.32 rmind 62 1.32 rmind enum { 63 1.32 rmind LIST_PROTO = 0, LIST_SADDR, LIST_DADDR, LIST_SPORT, LIST_DPORT, 64 1.32 rmind LIST_COUNT, 65 1.32 rmind }; 66 1.18 rmind 67 1.37 joe enum { 68 1.37 joe LIST_E_SADDR = 0, LIST_E_DADDR, LIST_ETYPE, 69 1.37 joe LIST_E_COUNT, 70 1.37 joe }; 71 1.37 joe 72 1.1 rmind typedef struct { 73 1.4 rmind nl_config_t * conf; 74 1.32 rmind bool validating; 75 1.32 rmind 76 1.1 rmind FILE * fp; 77 1.1 rmind long fpos; 78 1.32 rmind long fposln; 79 1.32 rmind int glevel; 80 1.32 rmind 81 1.32 rmind unsigned flags; 82 1.18 rmind uint32_t curmark; 83 1.32 rmind uint64_t seen_marks; 84 1.32 rmind elem_list_t list[LIST_COUNT]; 85 1.32 rmind 86 1.1 rmind } npf_conf_info_t; 87 1.1 rmind 88 1.1 rmind static void print_linesep(npf_conf_info_t *); 89 1.1 rmind 90 1.32 rmind static npf_conf_info_t * 91 1.20 christos npfctl_show_init(void) 92 1.20 christos { 93 1.32 rmind static npf_conf_info_t stdout_ctx; 94 1.32 rmind memset(&stdout_ctx, 0, sizeof(npf_conf_info_t)); 95 1.32 rmind stdout_ctx.glevel = -1; 96 1.20 christos stdout_ctx.fp = stdout; 97 1.32 rmind return &stdout_ctx; 98 1.32 rmind } 99 1.32 rmind 100 1.32 rmind static void 101 1.32 rmind list_push(elem_list_t *list, char *val) 102 1.32 rmind { 103 1.32 rmind const unsigned n = list->count; 104 1.32 rmind char **values; 105 1.32 rmind 106 1.32 rmind if ((values = calloc(n + 1, sizeof(char *))) == NULL) { 107 1.32 rmind err(EXIT_FAILURE, "calloc"); 108 1.32 rmind } 109 1.32 rmind for (unsigned i = 0; i < n; i++) { 110 1.32 rmind values[i] = list->values[i]; 111 1.32 rmind } 112 1.32 rmind values[n] = val; 113 1.32 rmind free(list->values); 114 1.32 rmind list->values = values; 115 1.32 rmind list->count++; 116 1.32 rmind } 117 1.32 rmind 118 1.32 rmind static char * 119 1.32 rmind list_join_free(elem_list_t *list, const bool use_br, const char *sep) 120 1.32 rmind { 121 1.32 rmind char *s, buf[2048]; 122 1.32 rmind 123 1.32 rmind if (!join(buf, sizeof(buf), list->count, list->values, sep)) { 124 1.32 rmind errx(EXIT_FAILURE, "out of memory while parsing the rule"); 125 1.32 rmind } 126 1.32 rmind easprintf(&s, (use_br && list->count > 1) ? "{ %s }" : "%s", buf); 127 1.32 rmind for (unsigned i = 0; i < list->count; i++) { 128 1.32 rmind free(list->values[i]); 129 1.32 rmind } 130 1.32 rmind free(list->values); 131 1.32 rmind list->values = NULL; 132 1.32 rmind list->count = 0; 133 1.32 rmind return s; 134 1.20 christos } 135 1.20 christos 136 1.1 rmind /* 137 1.1 rmind * Helper routines to print various pieces of information. 138 1.1 rmind */ 139 1.1 rmind 140 1.1 rmind static void 141 1.27 rmind print_indent(npf_conf_info_t *ctx, unsigned level) 142 1.1 rmind { 143 1.32 rmind if (ctx->glevel >= 0 && level <= (unsigned)ctx->glevel) { 144 1.27 rmind /* 145 1.27 rmind * Level decrease -- end of the group. 146 1.27 rmind * Print the group closing curly bracket. 147 1.27 rmind */ 148 1.32 rmind ctx->fpos += fprintf(ctx->fp, "}\n\n"); 149 1.32 rmind ctx->glevel = -1; 150 1.1 rmind } 151 1.27 rmind while (level--) { 152 1.32 rmind ctx->fpos += fprintf(ctx->fp, "\t"); 153 1.27 rmind } 154 1.1 rmind } 155 1.1 rmind 156 1.1 rmind static void 157 1.1 rmind print_linesep(npf_conf_info_t *ctx) 158 1.1 rmind { 159 1.32 rmind if (ctx->fpos != ctx->fposln) { 160 1.32 rmind ctx->fpos += fprintf(ctx->fp, "\n"); 161 1.32 rmind ctx->fposln = ctx->fpos; 162 1.1 rmind } 163 1.1 rmind } 164 1.1 rmind 165 1.1 rmind static size_t 166 1.32 rmind tcpflags2string(char *buf, unsigned tfl) 167 1.1 rmind { 168 1.32 rmind unsigned i = 0; 169 1.1 rmind 170 1.1 rmind if (tfl & TH_FIN) buf[i++] = 'F'; 171 1.1 rmind if (tfl & TH_SYN) buf[i++] = 'S'; 172 1.1 rmind if (tfl & TH_RST) buf[i++] = 'R'; 173 1.1 rmind if (tfl & TH_PUSH) buf[i++] = 'P'; 174 1.1 rmind if (tfl & TH_ACK) buf[i++] = 'A'; 175 1.1 rmind if (tfl & TH_URG) buf[i++] = 'U'; 176 1.1 rmind if (tfl & TH_ECE) buf[i++] = 'E'; 177 1.30 christos if (tfl & TH_CWR) buf[i++] = 'W'; 178 1.1 rmind buf[i] = '\0'; 179 1.1 rmind return i; 180 1.1 rmind } 181 1.1 rmind 182 1.1 rmind static char * 183 1.26 rmind print_family(npf_conf_info_t *ctx __unused, const uint32_t *words) 184 1.1 rmind { 185 1.1 rmind const int af = words[0]; 186 1.1 rmind 187 1.1 rmind switch (af) { 188 1.1 rmind case AF_INET: 189 1.10 rmind return estrdup("inet4"); 190 1.1 rmind case AF_INET6: 191 1.1 rmind return estrdup("inet6"); 192 1.1 rmind default: 193 1.1 rmind errx(EXIT_FAILURE, "invalid byte-code mark (family)"); 194 1.1 rmind } 195 1.1 rmind return NULL; 196 1.1 rmind } 197 1.1 rmind 198 1.1 rmind static char * 199 1.26 rmind print_address(npf_conf_info_t *ctx __unused, const uint32_t *words) 200 1.1 rmind { 201 1.1 rmind const int af = *words++; 202 1.32 rmind const unsigned mask = *words++; 203 1.1 rmind const npf_addr_t *addr; 204 1.1 rmind int alen = 0; 205 1.1 rmind 206 1.1 rmind switch (af) { 207 1.1 rmind case AF_INET: 208 1.1 rmind alen = 4; 209 1.1 rmind break; 210 1.1 rmind case AF_INET6: 211 1.1 rmind alen = 16; 212 1.1 rmind break; 213 1.1 rmind default: 214 1.1 rmind errx(EXIT_FAILURE, "invalid byte-code mark (address)"); 215 1.1 rmind } 216 1.1 rmind addr = (const npf_addr_t *)words; 217 1.21 christos return npfctl_print_addrmask(alen, "%a", addr, mask); 218 1.1 rmind } 219 1.1 rmind 220 1.1 rmind static char * 221 1.26 rmind print_number(npf_conf_info_t *ctx __unused, const uint32_t *words) 222 1.1 rmind { 223 1.1 rmind char *p; 224 1.1 rmind easprintf(&p, "%u", words[0]); 225 1.1 rmind return p; 226 1.1 rmind } 227 1.1 rmind 228 1.1 rmind static char * 229 1.4 rmind print_table(npf_conf_info_t *ctx, const uint32_t *words) 230 1.4 rmind { 231 1.27 rmind const unsigned tid = words[0]; 232 1.27 rmind const char *tname; 233 1.27 rmind char *s = NULL; 234 1.27 rmind bool ifaddr; 235 1.27 rmind 236 1.27 rmind tname = npfctl_table_getname(ctx->conf, tid, &ifaddr); 237 1.27 rmind easprintf(&s, ifaddr ? "ifaddrs(%s)" : "<%s>", tname); 238 1.27 rmind return s; 239 1.4 rmind } 240 1.4 rmind 241 1.4 rmind static char * 242 1.4 rmind print_proto(npf_conf_info_t *ctx, const uint32_t *words) 243 1.1 rmind { 244 1.32 rmind ctx->flags |= SEEN_PROTO; 245 1.1 rmind switch (words[0]) { 246 1.1 rmind case IPPROTO_TCP: 247 1.1 rmind return estrdup("tcp"); 248 1.1 rmind case IPPROTO_UDP: 249 1.1 rmind return estrdup("udp"); 250 1.1 rmind case IPPROTO_ICMP: 251 1.1 rmind return estrdup("icmp"); 252 1.1 rmind case IPPROTO_ICMPV6: 253 1.1 rmind return estrdup("ipv6-icmp"); 254 1.1 rmind } 255 1.4 rmind return print_number(ctx, words); 256 1.1 rmind } 257 1.1 rmind 258 1.1 rmind static char * 259 1.26 rmind print_tcpflags(npf_conf_info_t *ctx __unused, const uint32_t *words) 260 1.1 rmind { 261 1.32 rmind const unsigned tf = words[0], tf_mask = words[1]; 262 1.32 rmind char buf[32]; 263 1.32 rmind size_t n; 264 1.1 rmind 265 1.32 rmind if ((ctx->flags & SEEN_PROTO) == 0) { 266 1.32 rmind /* 267 1.32 rmind * Note: the TCP flag matching might be without 'proto tcp' 268 1.32 rmind * when using a plain 'stateful' rule. In such case, just 269 1.32 rmind * skip showing of the flags as they are implicit. 270 1.32 rmind */ 271 1.32 rmind return NULL; 272 1.32 rmind } 273 1.32 rmind n = tcpflags2string(buf, tf); 274 1.1 rmind if (tf != tf_mask) { 275 1.1 rmind buf[n++] = '/'; 276 1.1 rmind tcpflags2string(buf + n, tf_mask); 277 1.1 rmind } 278 1.1 rmind return estrdup(buf); 279 1.1 rmind } 280 1.1 rmind 281 1.1 rmind static char * 282 1.29 rmind print_portrange(npf_conf_info_t *ctx __unused, const uint32_t *words) 283 1.1 rmind { 284 1.32 rmind unsigned fport = words[0], tport = words[1]; 285 1.1 rmind char *p; 286 1.1 rmind 287 1.1 rmind if (fport != tport) { 288 1.29 rmind easprintf(&p, "%u-%u", fport, tport); 289 1.1 rmind } else { 290 1.29 rmind easprintf(&p, "%u", fport); 291 1.1 rmind } 292 1.1 rmind return p; 293 1.1 rmind } 294 1.1 rmind 295 1.37 joe static char * 296 1.37 joe print_ether_address(npf_conf_info_t *ctx __unused, const uint32_t *nwords) 297 1.37 joe { 298 1.37 joe const struct ether_addr* addr = (const struct ether_addr *)nwords; 299 1.37 joe char *a; 300 1.37 joe 301 1.37 joe _DIAGASSERT(addr != NULL); 302 1.37 joe 303 1.37 joe easprintf(&a, "%02x:%02x:%02x:%02x:%02x:%02x", 304 1.37 joe addr->ether_addr_octet[0], addr->ether_addr_octet[1], 305 1.37 joe addr->ether_addr_octet[2], addr->ether_addr_octet[3], 306 1.37 joe addr->ether_addr_octet[4], addr->ether_addr_octet[5]); 307 1.37 joe return a; 308 1.37 joe } 309 1.37 joe 310 1.37 joe static char * 311 1.37 joe print_ether_type(npf_conf_info_t *ctx __unused, const uint32_t *words) 312 1.37 joe { 313 1.37 joe const uint8_t *type = (const uint8_t *)words; 314 1.37 joe char *a; 315 1.37 joe 316 1.37 joe _DIAGASSERT(type != NULL); 317 1.37 joe 318 1.37 joe easprintf(&a, "Ex%02x%02x", type[0], type[1]); 319 1.37 joe 320 1.37 joe return a; 321 1.37 joe } 322 1.37 joe 323 1.1 rmind /* 324 1.1 rmind * The main keyword mapping tables defining the syntax: 325 1.1 rmind * - Mapping of rule attributes (flags) to the keywords. 326 1.1 rmind * - Mapping of the byte-code marks to the keywords. 327 1.1 rmind */ 328 1.1 rmind 329 1.1 rmind #define F(name) __CONCAT(NPF_RULE_, name) 330 1.28 rmind #define STATEFUL_ALL (NPF_RULE_STATEFUL | NPF_RULE_GSTATEFUL) 331 1.1 rmind #define NAME_AT 2 332 1.1 rmind 333 1.1 rmind static const struct attr_keyword_mapent { 334 1.1 rmind uint32_t mask; 335 1.1 rmind uint32_t flags; 336 1.1 rmind const char * val; 337 1.1 rmind } attr_keyword_map[] = { 338 1.1 rmind { F(GROUP)|F(DYNAMIC), F(GROUP), "group" }, 339 1.32 rmind { F(GROUP)|F(DYNAMIC), F(GROUP)|F(DYNAMIC), "ruleset" }, 340 1.1 rmind { F(GROUP)|F(PASS), 0, "block" }, 341 1.1 rmind { F(GROUP)|F(PASS), F(PASS), "pass" }, 342 1.37 joe { F(GROUP)|F(PASS)|F(LAYER_2), F(LAYER_2), "ether" }, 343 1.37 joe { F(GROUP)|F(PASS)|F(LAYER_2), F(LAYER_2)|F(PASS), "ether" }, 344 1.1 rmind { F(RETRST)|F(RETICMP), F(RETRST)|F(RETICMP), "return" }, 345 1.1 rmind { F(RETRST)|F(RETICMP), F(RETRST), "return-rst" }, 346 1.1 rmind { F(RETRST)|F(RETICMP), F(RETICMP), "return-icmp" }, 347 1.28 rmind { STATEFUL_ALL, F(STATEFUL), "stateful" }, 348 1.28 rmind { STATEFUL_ALL, STATEFUL_ALL, "stateful-all" }, 349 1.1 rmind { F(DIMASK), F(IN), "in" }, 350 1.1 rmind { F(DIMASK), F(OUT), "out" }, 351 1.1 rmind { F(FINAL), F(FINAL), "final" }, 352 1.1 rmind }; 353 1.1 rmind 354 1.1 rmind static const struct mark_keyword_mapent { 355 1.32 rmind unsigned mark; 356 1.32 rmind const char * format; 357 1.32 rmind int list_id; 358 1.4 rmind char * (*printfn)(npf_conf_info_t *, const uint32_t *); 359 1.32 rmind unsigned fwords; 360 1.1 rmind } mark_keyword_map[] = { 361 1.32 rmind { BM_IPVER, "family %s", LIST_PROTO, print_family, 1 }, 362 1.32 rmind { BM_PROTO, "proto %s", LIST_PROTO, print_proto, 1 }, 363 1.32 rmind { BM_TCPFL, "flags %s", LIST_PROTO, print_tcpflags, 2 }, 364 1.32 rmind { BM_ICMP_TYPE, "icmp-type %s", LIST_PROTO, print_number, 1 }, 365 1.32 rmind { BM_ICMP_CODE, "code %s", LIST_PROTO, print_number, 1 }, 366 1.32 rmind 367 1.32 rmind { BM_SRC_NEG, NULL, -1, NULL, 0 }, 368 1.32 rmind { BM_SRC_CIDR, NULL, LIST_SADDR, print_address, 6 }, 369 1.32 rmind { BM_SRC_TABLE, NULL, LIST_SADDR, print_table, 1 }, 370 1.32 rmind { BM_SRC_PORTS, NULL, LIST_SPORT, print_portrange,2 }, 371 1.32 rmind 372 1.32 rmind { BM_DST_NEG, NULL, -1, NULL, 0 }, 373 1.32 rmind { BM_DST_CIDR, NULL, LIST_DADDR, print_address, 6 }, 374 1.32 rmind { BM_DST_TABLE, NULL, LIST_DADDR, print_table, 1 }, 375 1.32 rmind { BM_DST_PORTS, NULL, LIST_DPORT, print_portrange,2 }, 376 1.37 joe }, mark_keyword_mapl2[] = { 377 1.37 joe {BM_SRC_ENEG, NULL, -1, NULL, 0 }, 378 1.37 joe {BM_SRC_ETHER, NULL, LIST_E_SADDR, print_ether_address, 2 }, 379 1.37 joe 380 1.37 joe {BM_DST_ENEG, NULL, -1, NULL, 0 }, 381 1.37 joe {BM_DST_ETHER, NULL, LIST_E_DADDR, print_ether_address, 2 }, 382 1.37 joe {BM_ETHER_TYPE, "type %s", LIST_ETYPE, print_ether_type,1 } 383 1.1 rmind }; 384 1.1 rmind 385 1.1 rmind static const char * __attribute__((format_arg(2))) 386 1.1 rmind verified_fmt(const char *fmt, const char *t __unused) 387 1.1 rmind { 388 1.1 rmind return fmt; 389 1.1 rmind } 390 1.1 rmind 391 1.32 rmind static void 392 1.1 rmind scan_marks(npf_conf_info_t *ctx, const struct mark_keyword_mapent *mk, 393 1.1 rmind const uint32_t *marks, size_t mlen) 394 1.1 rmind { 395 1.32 rmind elem_list_t sublist, *target_list; 396 1.32 rmind 397 1.32 rmind /* 398 1.32 rmind * If format is used for this mark, then collect multiple elements 399 1.32 rmind * in into the list, merge and re-push the set into the target list. 400 1.32 rmind * 401 1.32 rmind * Currently, this is applicable only for 'proto { tcp, udp }'. 402 1.32 rmind */ 403 1.32 rmind memset(&sublist, 0, sizeof(elem_list_t)); 404 1.32 rmind target_list = mk->format ? &sublist : &ctx->list[mk->list_id]; 405 1.1 rmind 406 1.1 rmind /* Scan for the marks and extract the values. */ 407 1.1 rmind mlen /= sizeof(uint32_t); 408 1.1 rmind while (mlen > 2) { 409 1.1 rmind const uint32_t m = *marks++; 410 1.32 rmind const unsigned nwords = *marks++; 411 1.1 rmind 412 1.1 rmind if ((mlen -= 2) < nwords) { 413 1.1 rmind errx(EXIT_FAILURE, "byte-code marking inconsistency"); 414 1.1 rmind } 415 1.1 rmind if (m == mk->mark) { 416 1.32 rmind /* 417 1.32 rmind * Set the current mark and note it as seen. 418 1.32 rmind * Value is processed by the print function, 419 1.32 rmind * otherwise we just need to note the mark. 420 1.32 rmind */ 421 1.32 rmind ctx->curmark = m; 422 1.32 rmind assert(BM_COUNT < (sizeof(uint64_t) * CHAR_BIT)); 423 1.34 mlelstv ctx->seen_marks |= UINT64_C(1) << m; 424 1.32 rmind assert(mk->fwords == nwords); 425 1.29 rmind 426 1.32 rmind if (mk->printfn) { 427 1.32 rmind char *val; 428 1.18 rmind 429 1.32 rmind if ((val = mk->printfn(ctx, marks)) != NULL) { 430 1.32 rmind list_push(target_list, val); 431 1.32 rmind } 432 1.29 rmind } 433 1.1 rmind } 434 1.1 rmind marks += nwords; 435 1.1 rmind mlen -= nwords; 436 1.1 rmind } 437 1.1 rmind 438 1.32 rmind if (sublist.count) { 439 1.32 rmind char *val, *elements; 440 1.1 rmind 441 1.32 rmind elements = list_join_free(&sublist, true, ", "); 442 1.32 rmind easprintf(&val, verified_fmt(mk->format, "%s"), elements ); 443 1.32 rmind list_push(&ctx->list[mk->list_id], val); 444 1.32 rmind free(elements); 445 1.1 rmind } 446 1.1 rmind } 447 1.1 rmind 448 1.1 rmind static void 449 1.23 christos npfctl_print_id(npf_conf_info_t *ctx, nl_rule_t *rl) 450 1.23 christos { 451 1.32 rmind const uint64_t id = npf_rule_getid(rl); 452 1.32 rmind 453 1.32 rmind if (id) { 454 1.32 rmind ctx->fpos += fprintf(ctx->fp, "# id=\"%" PRIx64 "\" ", id); 455 1.32 rmind } 456 1.23 christos } 457 1.23 christos 458 1.23 christos static void 459 1.37 joe npfctl_print_filter_generic(npf_conf_info_t *ctx, uint32_t plist) 460 1.32 rmind { 461 1.37 joe assert(plist < LIST_COUNT); 462 1.37 joe elem_list_t *list = &ctx->list[plist]; 463 1.32 rmind 464 1.32 rmind if (list->count) { 465 1.32 rmind char *elements = list_join_free(list, false, " "); 466 1.32 rmind ctx->fpos += fprintf(ctx->fp, "%s ", elements); 467 1.32 rmind free(elements); 468 1.32 rmind } 469 1.32 rmind } 470 1.32 rmind 471 1.32 rmind static bool 472 1.32 rmind npfctl_print_filter_seg(npf_conf_info_t *ctx, unsigned which) 473 1.32 rmind { 474 1.32 rmind static const struct { 475 1.32 rmind const char * keyword; 476 1.32 rmind unsigned alist; 477 1.32 rmind unsigned plist; 478 1.32 rmind unsigned negbm; 479 1.32 rmind } refs[] = { 480 1.32 rmind [NPF_SRC] = { 481 1.32 rmind .keyword = "from", 482 1.32 rmind .alist = LIST_SADDR, 483 1.32 rmind .plist = LIST_SPORT, 484 1.32 rmind .negbm = UINT64_C(1) << BM_SRC_NEG, 485 1.32 rmind }, 486 1.32 rmind [NPF_DST] = { 487 1.32 rmind .keyword = "to", 488 1.32 rmind .alist = LIST_DADDR, 489 1.32 rmind .plist = LIST_DPORT, 490 1.32 rmind .negbm = UINT64_C(1) << BM_DST_NEG, 491 1.32 rmind } 492 1.32 rmind }; 493 1.32 rmind const char *neg = !!(ctx->seen_marks & refs[which].negbm) ? "! " : ""; 494 1.32 rmind const char *kwd = refs[which].keyword; 495 1.32 rmind bool seen_filter = false; 496 1.32 rmind elem_list_t *list; 497 1.32 rmind char *elements; 498 1.32 rmind 499 1.32 rmind list = &ctx->list[refs[which].alist]; 500 1.32 rmind if (list->count != 0) { 501 1.32 rmind seen_filter = true; 502 1.32 rmind elements = list_join_free(list, true, ", "); 503 1.32 rmind ctx->fpos += fprintf(ctx->fp, "%s %s%s ", kwd, neg, elements); 504 1.32 rmind free(elements); 505 1.32 rmind } 506 1.32 rmind 507 1.32 rmind list = &ctx->list[refs[which].plist]; 508 1.32 rmind if (list->count != 0) { 509 1.32 rmind if (!seen_filter) { 510 1.32 rmind ctx->fpos += fprintf(ctx->fp, "%s any ", kwd); 511 1.32 rmind seen_filter = true; 512 1.32 rmind } 513 1.32 rmind elements = list_join_free(list, true, ", "); 514 1.32 rmind ctx->fpos += fprintf(ctx->fp, "port %s ", elements); 515 1.32 rmind free(elements); 516 1.32 rmind } 517 1.32 rmind return seen_filter; 518 1.32 rmind } 519 1.32 rmind 520 1.32 rmind static bool 521 1.37 joe npfctl_print_l2filter_seg(npf_conf_info_t *ctx, unsigned which) 522 1.37 joe { 523 1.37 joe static const struct { 524 1.37 joe const char * keyword; 525 1.37 joe unsigned alist; 526 1.37 joe unsigned negbm; 527 1.37 joe } refs[] = { 528 1.37 joe [NPF_SRC] = { 529 1.37 joe .keyword = "from", 530 1.37 joe .alist = LIST_E_SADDR, 531 1.37 joe .negbm = UINT64_C(1) << BM_SRC_ENEG, 532 1.37 joe }, 533 1.37 joe [NPF_DST] = { 534 1.37 joe .keyword = "to", 535 1.37 joe .alist = LIST_E_DADDR, 536 1.37 joe .negbm = UINT64_C(1) << BM_DST_ENEG, 537 1.37 joe } 538 1.37 joe }; 539 1.37 joe const char *neg = !!(ctx->seen_marks & refs[which].negbm) ? "! " : ""; 540 1.37 joe const char *kwd = refs[which].keyword; 541 1.37 joe bool seen_filter = false; 542 1.37 joe elem_list_t *list; 543 1.37 joe char *elements; 544 1.37 joe 545 1.37 joe list = &ctx->list[refs[which].alist]; 546 1.37 joe if (list->count != 0) { 547 1.37 joe seen_filter = true; 548 1.37 joe elements = list_join_free(list, true, ", "); 549 1.37 joe ctx->fpos += fprintf(ctx->fp, "%s %s%s ", kwd, neg, elements); 550 1.37 joe free(elements); 551 1.37 joe } 552 1.37 joe 553 1.37 joe return seen_filter; 554 1.37 joe } 555 1.37 joe 556 1.37 joe static bool 557 1.37 joe npfctl_print_filter(npf_conf_info_t *ctx, nl_rule_t *rl, uint32_t attr) 558 1.1 rmind { 559 1.1 rmind const void *marks; 560 1.16 rmind size_t mlen, len; 561 1.16 rmind const void *code; 562 1.32 rmind bool seenf = false; 563 1.16 rmind int type; 564 1.1 rmind 565 1.1 rmind marks = npf_rule_getinfo(rl, &mlen); 566 1.16 rmind if (!marks && (code = npf_rule_getcode(rl, &type, &len)) != NULL) { 567 1.16 rmind /* 568 1.16 rmind * No marks, but the byte-code is present. This must 569 1.16 rmind * have been filled by libpcap(3) or possibly an unknown 570 1.16 rmind * to us byte-code. 571 1.16 rmind */ 572 1.32 rmind ctx->fpos += fprintf(ctx->fp, "%s ", type == NPF_CODE_BPF ? 573 1.16 rmind "pcap-filter \"...\"" : "unrecognized-bytecode"); 574 1.32 rmind return true; 575 1.16 rmind } 576 1.19 rmind ctx->flags = 0; 577 1.16 rmind 578 1.16 rmind /* 579 1.16 rmind * BPF filter criteria described by the byte-code marks. 580 1.16 rmind */ 581 1.34 mlelstv ctx->seen_marks = 0; 582 1.37 joe if (attr & NPF_RULE_LAYER_2) { 583 1.37 joe for (unsigned i = 0; i < __arraycount(mark_keyword_mapl2); i++) { 584 1.37 joe const struct mark_keyword_mapent *mk = &mark_keyword_mapl2[i]; 585 1.37 joe scan_marks(ctx, mk, marks, mlen); 586 1.37 joe } 587 1.37 joe seenf |= npfctl_print_l2filter_seg(ctx, NPF_SRC); 588 1.37 joe seenf |= npfctl_print_l2filter_seg(ctx, NPF_DST); 589 1.37 joe npfctl_print_filter_generic(ctx, LIST_ETYPE); 590 1.37 joe } else if (attr & NPF_RULE_LAYER_3) { 591 1.37 joe for (unsigned i = 0; i < __arraycount(mark_keyword_map); i++) { 592 1.37 joe const struct mark_keyword_mapent *mk = &mark_keyword_map[i]; 593 1.37 joe scan_marks(ctx, mk, marks, mlen); 594 1.37 joe } 595 1.37 joe npfctl_print_filter_generic(ctx, LIST_PROTO); 596 1.37 joe seenf |= npfctl_print_filter_seg(ctx, NPF_SRC); 597 1.37 joe seenf |= npfctl_print_filter_seg(ctx, NPF_DST); 598 1.37 joe } else { 599 1.37 joe yyerror("%s: layer not supported", __func__); 600 1.37 joe } 601 1.32 rmind return seenf; 602 1.1 rmind } 603 1.1 rmind 604 1.35 joe static char * 605 1.35 joe print_guid(char *buf, struct r_id id, int size) 606 1.35 joe { 607 1.35 joe if (id.op == NPF_OP_XRG) { 608 1.35 joe snprintf(buf, size, "%u <> %u", id.id[0], id.id[1]); 609 1.35 joe } else if (id.op == NPF_OP_IRG) { 610 1.35 joe snprintf(buf, size, "%u >< %u", id.id[0], id.id[1]); 611 1.35 joe } else if (id.op == NPF_OP_EQ ) { 612 1.35 joe snprintf(buf, size, "%u", id.id[0]); 613 1.35 joe } else if (id.op == NPF_OP_NE) { 614 1.35 joe snprintf(buf, size, "!= %u", id.id[0]); 615 1.35 joe } else if (id.op == NPF_OP_LE) { 616 1.35 joe snprintf(buf, size, "<= %u", id.id[0]); 617 1.35 joe } else if (id.op == NPF_OP_LT) { 618 1.35 joe snprintf(buf, size, "< %u", id.id[0]); 619 1.35 joe } else if (id.op == NPF_OP_GE) { 620 1.35 joe snprintf(buf, size, ">= %u", id.id[0]); 621 1.35 joe } else if (id.op == NPF_OP_GT) { 622 1.35 joe snprintf(buf, size, "> %u", id.id[0]); 623 1.35 joe } else { 624 1.35 joe return NULL; 625 1.35 joe } 626 1.35 joe return buf; 627 1.35 joe } 628 1.35 joe #define BUF_SIZE 40 629 1.1 rmind static void 630 1.32 rmind npfctl_print_rule(npf_conf_info_t *ctx, nl_rule_t *rl, unsigned level) 631 1.1 rmind { 632 1.1 rmind const uint32_t attr = npf_rule_getattr(rl); 633 1.3 rmind const char *rproc, *ifname, *name; 634 1.32 rmind bool dyn_ruleset; 635 1.35 joe struct r_id rid; 636 1.35 joe char buf[BUF_SIZE]; 637 1.1 rmind 638 1.1 rmind /* Rule attributes/flags. */ 639 1.32 rmind for (unsigned i = 0; i < __arraycount(attr_keyword_map); i++) { 640 1.1 rmind const struct attr_keyword_mapent *ak = &attr_keyword_map[i]; 641 1.1 rmind 642 1.1 rmind if (i == NAME_AT && (name = npf_rule_getname(rl)) != NULL) { 643 1.32 rmind ctx->fpos += fprintf(ctx->fp, "\"%s\" ", name); 644 1.1 rmind } 645 1.1 rmind if ((attr & ak->mask) == ak->flags) { 646 1.32 rmind ctx->fpos += fprintf(ctx->fp, "%s ", ak->val); 647 1.1 rmind } 648 1.1 rmind } 649 1.3 rmind if ((ifname = npf_rule_getinterface(rl)) != NULL) { 650 1.32 rmind ctx->fpos += fprintf(ctx->fp, "on %s ", ifname); 651 1.1 rmind } 652 1.37 joe if (attr == (NPF_RULE_GROUP | NPF_RULE_IN | NPF_RULE_OUT | NPF_RULE_LAYER_3) && !ifname) { 653 1.27 rmind /* The default group is a special case. */ 654 1.32 rmind ctx->fpos += fprintf(ctx->fp, "default "); 655 1.27 rmind } 656 1.37 joe if (attr == (NPF_RULE_GROUP | NPF_RULE_IN | NPF_RULE_OUT | NPF_RULE_LAYER_2) && !ifname) { 657 1.37 joe /* The default group is a special case. */ 658 1.37 joe ctx->fpos += fprintf(ctx->fp, "default layer-2 "); 659 1.37 joe } 660 1.37 joe if (attr == (NPF_RULE_GROUP | NPF_RULE_IN | NPF_RULE_LAYER_2)) { 661 1.37 joe ctx->fpos += fprintf(ctx->fp, "layer-2 "); 662 1.37 joe } 663 1.37 joe if (attr == (NPF_RULE_GROUP | NPF_RULE_OUT | NPF_RULE_LAYER_2)) { 664 1.37 joe ctx->fpos += fprintf(ctx->fp, "layer-2 "); 665 1.37 joe } 666 1.16 rmind if ((attr & NPF_DYNAMIC_GROUP) == NPF_RULE_GROUP) { 667 1.1 rmind /* Group; done. */ 668 1.32 rmind ctx->fpos += fprintf(ctx->fp, "{ "); 669 1.32 rmind ctx->glevel = level; 670 1.23 christos goto out; 671 1.1 rmind } 672 1.1 rmind 673 1.1 rmind /* Print filter criteria. */ 674 1.32 rmind dyn_ruleset = (attr & NPF_DYNAMIC_GROUP) == NPF_DYNAMIC_GROUP; 675 1.37 joe if (!npfctl_print_filter(ctx, rl, attr) && !dyn_ruleset) { 676 1.32 rmind ctx->fpos += fprintf(ctx->fp, "all "); 677 1.32 rmind } 678 1.1 rmind 679 1.35 joe if (!npf_rule_getrid(&rid, rl, "r_user")) { 680 1.35 joe ctx->fpos += fprintf(ctx->fp, "user %s ", print_guid(buf, rid, BUF_SIZE)); 681 1.35 joe } 682 1.35 joe 683 1.35 joe if (!npf_rule_getrid(&rid, rl, "r_group")) { 684 1.35 joe ctx->fpos += fprintf(ctx->fp, "group %s ", print_guid(buf, rid, BUF_SIZE)); 685 1.35 joe } 686 1.35 joe 687 1.1 rmind /* Rule procedure. */ 688 1.1 rmind if ((rproc = npf_rule_getproc(rl)) != NULL) { 689 1.32 rmind ctx->fpos += fprintf(ctx->fp, "apply \"%s\" ", rproc); 690 1.16 rmind } 691 1.23 christos out: 692 1.23 christos npfctl_print_id(ctx, rl); 693 1.32 rmind ctx->fpos += fprintf(ctx->fp, "\n"); 694 1.1 rmind } 695 1.1 rmind 696 1.1 rmind static void 697 1.1 rmind npfctl_print_nat(npf_conf_info_t *ctx, nl_nat_t *nt) 698 1.1 rmind { 699 1.28 rmind const unsigned dynamic_natset = NPF_RULE_GROUP | NPF_RULE_DYNAMIC; 700 1.1 rmind nl_rule_t *rl = (nl_nat_t *)nt; 701 1.27 rmind const char *ifname, *algo, *seg1, *seg2, *arrow; 702 1.27 rmind const npf_addr_t *addr; 703 1.27 rmind npf_netmask_t mask; 704 1.1 rmind in_port_t port; 705 1.3 rmind size_t alen; 706 1.28 rmind unsigned flags; 707 1.3 rmind char *seg; 708 1.38 joe uint32_t attr = npf_rule_getattr(rl); 709 1.1 rmind 710 1.28 rmind /* Get flags and the interface. */ 711 1.28 rmind flags = npf_nat_getflags(nt); 712 1.3 rmind ifname = npf_rule_getinterface(rl); 713 1.3 rmind assert(ifname != NULL); 714 1.1 rmind 715 1.38 joe if ((attr & dynamic_natset) == dynamic_natset) { 716 1.28 rmind const char *name = npf_rule_getname(rl); 717 1.32 rmind ctx->fpos += fprintf(ctx->fp, 718 1.32 rmind "map ruleset \"%s\" on %s\n", name, ifname); 719 1.28 rmind return; 720 1.28 rmind } 721 1.28 rmind 722 1.27 rmind /* Get the translation address or table (and port, if used). */ 723 1.27 rmind addr = npf_nat_getaddr(nt, &alen, &mask); 724 1.27 rmind if (addr) { 725 1.27 rmind seg = npfctl_print_addrmask(alen, "%a", addr, mask); 726 1.27 rmind } else { 727 1.27 rmind const unsigned tid = npf_nat_gettable(nt); 728 1.27 rmind const char *tname; 729 1.27 rmind bool ifaddr; 730 1.27 rmind 731 1.27 rmind tname = npfctl_table_getname(ctx->conf, tid, &ifaddr); 732 1.27 rmind easprintf(&seg, ifaddr ? "ifaddrs(%s)" : "<%s>", tname); 733 1.27 rmind } 734 1.27 rmind 735 1.27 rmind if ((port = npf_nat_getport(nt)) != 0) { 736 1.1 rmind char *p; 737 1.12 rmind easprintf(&p, "%s port %u", seg, ntohs(port)); 738 1.1 rmind free(seg), seg = p; 739 1.1 rmind } 740 1.1 rmind seg1 = seg2 = "any"; 741 1.1 rmind 742 1.1 rmind /* Get the NAT type and determine the translation segment. */ 743 1.1 rmind switch (npf_nat_gettype(nt)) { 744 1.1 rmind case NPF_NATIN: 745 1.1 rmind arrow = "<-"; 746 1.1 rmind seg1 = seg; 747 1.1 rmind break; 748 1.1 rmind case NPF_NATOUT: 749 1.1 rmind arrow = "->"; 750 1.1 rmind seg2 = seg; 751 1.1 rmind break; 752 1.1 rmind default: 753 1.9 rmind abort(); 754 1.1 rmind } 755 1.1 rmind 756 1.27 rmind /* NAT algorithm. */ 757 1.27 rmind switch (npf_nat_getalgo(nt)) { 758 1.27 rmind case NPF_ALGO_NETMAP: 759 1.27 rmind algo = "algo netmap "; 760 1.27 rmind break; 761 1.27 rmind case NPF_ALGO_IPHASH: 762 1.27 rmind algo = "algo ip-hash "; 763 1.27 rmind break; 764 1.27 rmind case NPF_ALGO_RR: 765 1.27 rmind algo = "algo round-robin "; 766 1.27 rmind break; 767 1.27 rmind case NPF_ALGO_NPT66: 768 1.32 rmind algo = "algo npt66 "; 769 1.27 rmind break; 770 1.27 rmind default: 771 1.27 rmind algo = ""; 772 1.27 rmind break; 773 1.27 rmind } 774 1.27 rmind 775 1.32 rmind /* XXX also handle "any" */ 776 1.27 rmind 777 1.1 rmind /* Print out the NAT policy with the filter criteria. */ 778 1.32 rmind ctx->fpos += fprintf(ctx->fp, "map %s %s %s%s%s %s %s pass ", 779 1.9 rmind ifname, (flags & NPF_NAT_STATIC) ? "static" : "dynamic", 780 1.27 rmind algo, (flags & NPF_NAT_PORTS) ? "" : "no-ports ", 781 1.9 rmind seg1, arrow, seg2); 782 1.38 joe npfctl_print_filter(ctx, rl, attr); 783 1.23 christos npfctl_print_id(ctx, rl); 784 1.32 rmind ctx->fpos += fprintf(ctx->fp, "\n"); 785 1.1 rmind free(seg); 786 1.1 rmind } 787 1.1 rmind 788 1.1 rmind static void 789 1.1 rmind npfctl_print_table(npf_conf_info_t *ctx, nl_table_t *tl) 790 1.1 rmind { 791 1.4 rmind const char *name = npf_table_getname(tl); 792 1.11 rmind const unsigned type = npf_table_gettype(tl); 793 1.11 rmind const char *table_types[] = { 794 1.27 rmind [NPF_TABLE_IPSET] = "ipset", 795 1.27 rmind [NPF_TABLE_LPM] = "lpm", 796 1.27 rmind [NPF_TABLE_CONST] = "const", 797 1.11 rmind }; 798 1.1 rmind 799 1.5 rmind if (name[0] == '.') { 800 1.5 rmind /* Internal tables use dot and are hidden. */ 801 1.5 rmind return; 802 1.5 rmind } 803 1.11 rmind assert(type < __arraycount(table_types)); 804 1.32 rmind ctx->fpos += fprintf(ctx->fp, 805 1.32 rmind "table <%s> type %s\n", name, table_types[type]); 806 1.32 rmind } 807 1.32 rmind 808 1.32 rmind static void 809 1.32 rmind npfctl_print_params(npf_conf_info_t *ctx, nl_config_t *ncf) 810 1.32 rmind { 811 1.32 rmind nl_iter_t i = NPF_ITER_BEGIN; 812 1.32 rmind int val, defval, *dval; 813 1.32 rmind const char *name; 814 1.32 rmind 815 1.32 rmind dval = ctx->validating ? NULL : &defval; 816 1.32 rmind while ((name = npf_param_iterate(ncf, &i, &val, dval)) != NULL) { 817 1.32 rmind if (dval && val == *dval) { 818 1.32 rmind continue; 819 1.32 rmind } 820 1.32 rmind ctx->fpos += fprintf(ctx->fp, "set %s %d\n", name, val); 821 1.32 rmind } 822 1.32 rmind print_linesep(ctx); 823 1.1 rmind } 824 1.1 rmind 825 1.1 rmind int 826 1.1 rmind npfctl_config_show(int fd) 827 1.1 rmind { 828 1.32 rmind npf_conf_info_t *ctx = npfctl_show_init(); 829 1.1 rmind nl_config_t *ncf; 830 1.20 christos bool loaded; 831 1.1 rmind 832 1.1 rmind if (fd) { 833 1.20 christos ncf = npf_config_retrieve(fd); 834 1.1 rmind if (ncf == NULL) { 835 1.1 rmind return errno; 836 1.1 rmind } 837 1.20 christos loaded = npf_config_loaded_p(ncf); 838 1.32 rmind ctx->validating = false; 839 1.32 rmind ctx->fpos += fprintf(ctx->fp, 840 1.32 rmind "# filtering:\t%s\n# config:\t%s\n", 841 1.20 christos npf_config_active_p(ncf) ? "active" : "inactive", 842 1.1 rmind loaded ? "loaded" : "empty"); 843 1.1 rmind print_linesep(ctx); 844 1.1 rmind } else { 845 1.1 rmind ncf = npfctl_config_ref(); 846 1.31 rmind npfctl_config_build(); 847 1.32 rmind ctx->validating = true; 848 1.1 rmind loaded = true; 849 1.1 rmind } 850 1.4 rmind ctx->conf = ncf; 851 1.1 rmind 852 1.1 rmind if (loaded) { 853 1.1 rmind nl_rule_t *rl; 854 1.1 rmind nl_rproc_t *rp; 855 1.1 rmind nl_nat_t *nt; 856 1.1 rmind nl_table_t *tl; 857 1.28 rmind nl_iter_t i; 858 1.27 rmind unsigned level; 859 1.1 rmind 860 1.32 rmind npfctl_print_params(ctx, ncf); 861 1.32 rmind 862 1.28 rmind i = NPF_ITER_BEGIN; 863 1.28 rmind while ((tl = npf_table_iterate(ncf, &i)) != NULL) { 864 1.1 rmind npfctl_print_table(ctx, tl); 865 1.1 rmind } 866 1.1 rmind print_linesep(ctx); 867 1.1 rmind 868 1.28 rmind i = NPF_ITER_BEGIN; 869 1.28 rmind while ((rp = npf_rproc_iterate(ncf, &i)) != NULL) { 870 1.1 rmind const char *rpname = npf_rproc_getname(rp); 871 1.32 rmind ctx->fpos += fprintf(ctx->fp, 872 1.32 rmind "procedure \"%s\"\n", rpname); 873 1.1 rmind } 874 1.1 rmind print_linesep(ctx); 875 1.1 rmind 876 1.28 rmind i = NPF_ITER_BEGIN; 877 1.28 rmind while ((nt = npf_nat_iterate(ncf, &i)) != NULL) { 878 1.1 rmind npfctl_print_nat(ctx, nt); 879 1.1 rmind } 880 1.1 rmind print_linesep(ctx); 881 1.1 rmind 882 1.28 rmind i = NPF_ITER_BEGIN; 883 1.28 rmind while ((rl = npf_rule_iterate(ncf, &i, &level)) != NULL) { 884 1.1 rmind print_indent(ctx, level); 885 1.32 rmind npfctl_print_rule(ctx, rl, level); 886 1.1 rmind } 887 1.27 rmind print_indent(ctx, 0); 888 1.1 rmind } 889 1.1 rmind npf_config_destroy(ncf); 890 1.1 rmind return 0; 891 1.1 rmind } 892 1.1 rmind 893 1.1 rmind int 894 1.1 rmind npfctl_ruleset_show(int fd, const char *ruleset_name) 895 1.1 rmind { 896 1.32 rmind npf_conf_info_t *ctx = npfctl_show_init(); 897 1.1 rmind nl_config_t *ncf; 898 1.1 rmind nl_rule_t *rl; 899 1.27 rmind unsigned level; 900 1.28 rmind nl_iter_t i; 901 1.1 rmind int error; 902 1.1 rmind 903 1.1 rmind ncf = npf_config_create(); 904 1.4 rmind ctx->conf = ncf; 905 1.4 rmind 906 1.1 rmind if ((error = _npf_ruleset_list(fd, ruleset_name, ncf)) != 0) { 907 1.1 rmind return error; 908 1.1 rmind } 909 1.28 rmind i = NPF_ITER_BEGIN; 910 1.28 rmind while ((rl = npf_rule_iterate(ncf, &i, &level)) != NULL) { 911 1.32 rmind npfctl_print_rule(ctx, rl, 0); 912 1.1 rmind } 913 1.1 rmind npf_config_destroy(ncf); 914 1.1 rmind return error; 915 1.1 rmind } 916