1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2015-2019 Yandex LLC 5 * Copyright (c) 2015-2019 Andrey V. Elsukov <ae@FreeBSD.org> 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 ``AS IS'' AND ANY EXPRESS OR 18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include <sys/cdefs.h> 30 __FBSDID("$FreeBSD$"); 31 32 #include <sys/types.h> 33 #include <sys/socket.h> 34 35 #include "ipfw2.h" 36 37 #include <ctype.h> 38 #include <err.h> 39 #include <errno.h> 40 #include <inttypes.h> 41 #include <netdb.h> 42 #include <stdio.h> 43 #include <stdlib.h> 44 #include <string.h> 45 #include <sysexits.h> 46 47 #include <net/if.h> 48 #include <netinet/in.h> 49 #include <netinet/ip_fw.h> 50 #include <netinet6/ip_fw_nat64.h> 51 #include <arpa/inet.h> 52 53 typedef int (nat64stl_cb_t)(ipfw_nat64stl_cfg *i, const char *name, 54 uint8_t set); 55 static int nat64stl_foreach(nat64stl_cb_t *f, const char *name, uint8_t set, 56 int sort); 57 58 static void nat64stl_create(const char *name, uint8_t set, int ac, char **av); 59 static void nat64stl_config(const char *name, uint8_t set, int ac, char **av); 60 static void nat64stl_destroy(const char *name, uint8_t set); 61 static void nat64stl_stats(const char *name, uint8_t set); 62 static void nat64stl_reset_stats(const char *name, uint8_t set); 63 static int nat64stl_show_cb(ipfw_nat64stl_cfg *cfg, const char *name, 64 uint8_t set); 65 static int nat64stl_destroy_cb(ipfw_nat64stl_cfg *cfg, const char *name, 66 uint8_t set); 67 68 static struct _s_x nat64cmds[] = { 69 { "create", TOK_CREATE }, 70 { "config", TOK_CONFIG }, 71 { "destroy", TOK_DESTROY }, 72 { "list", TOK_LIST }, 73 { "show", TOK_LIST }, 74 { "stats", TOK_STATS }, 75 { NULL, 0 } 76 }; 77 78 #define IPV6_ADDR_INT32_WKPFX htonl(0x64ff9b) 79 #define IN6_IS_ADDR_WKPFX(a) \ 80 ((a)->__u6_addr.__u6_addr32[0] == IPV6_ADDR_INT32_WKPFX && \ 81 (a)->__u6_addr.__u6_addr32[1] == 0 && \ 82 (a)->__u6_addr.__u6_addr32[2] == 0) 83 int 84 ipfw_check_nat64prefix(const struct in6_addr *prefix, int length) 85 { 86 87 switch (length) { 88 case 32: 89 case 40: 90 case 48: 91 case 56: 92 case 64: 93 /* Well-known prefix has 96 prefix length */ 94 if (IN6_IS_ADDR_WKPFX(prefix)) 95 return (EINVAL); 96 /* FALLTHROUGH */ 97 case 96: 98 /* Bits 64 to 71 must be set to zero */ 99 if (prefix->__u6_addr.__u6_addr8[8] != 0) 100 return (EINVAL); 101 /* XXX: looks incorrect */ 102 if (IN6_IS_ADDR_MULTICAST(prefix) || 103 IN6_IS_ADDR_UNSPECIFIED(prefix) || 104 IN6_IS_ADDR_LOOPBACK(prefix)) 105 return (EINVAL); 106 return (0); 107 } 108 return (EINVAL); 109 } 110 111 static struct _s_x nat64statscmds[] = { 112 { "reset", TOK_RESET }, 113 { NULL, 0 } 114 }; 115 116 /* 117 * This one handles all nat64stl-related commands 118 * ipfw [set N] nat64stl NAME {create | config} ... 119 * ipfw [set N] nat64stl NAME stats [reset] 120 * ipfw [set N] nat64stl {NAME | all} destroy 121 * ipfw [set N] nat64stl {NAME | all} {list | show} 122 */ 123 #define nat64stl_check_name table_check_name 124 void 125 ipfw_nat64stl_handler(int ac, char *av[]) 126 { 127 const char *name; 128 int tcmd; 129 uint8_t set; 130 131 if (g_co.use_set != 0) 132 set = g_co.use_set - 1; 133 else 134 set = 0; 135 ac--; av++; 136 137 NEED1("nat64stl needs instance name"); 138 name = *av; 139 if (nat64stl_check_name(name) != 0) { 140 if (strcmp(name, "all") == 0) 141 name = NULL; 142 else 143 errx(EX_USAGE, "nat64stl instance name %s is invalid", 144 name); 145 } 146 ac--; av++; 147 NEED1("nat64stl needs command"); 148 149 tcmd = get_token(nat64cmds, *av, "nat64stl command"); 150 if (name == NULL && tcmd != TOK_DESTROY && tcmd != TOK_LIST) 151 errx(EX_USAGE, "nat64stl instance name required"); 152 switch (tcmd) { 153 case TOK_CREATE: 154 ac--; av++; 155 nat64stl_create(name, set, ac, av); 156 break; 157 case TOK_CONFIG: 158 ac--; av++; 159 nat64stl_config(name, set, ac, av); 160 break; 161 case TOK_LIST: 162 nat64stl_foreach(nat64stl_show_cb, name, set, 1); 163 break; 164 case TOK_DESTROY: 165 if (name == NULL) 166 nat64stl_foreach(nat64stl_destroy_cb, NULL, set, 0); 167 else 168 nat64stl_destroy(name, set); 169 break; 170 case TOK_STATS: 171 ac--; av++; 172 if (ac == 0) { 173 nat64stl_stats(name, set); 174 break; 175 } 176 tcmd = get_token(nat64statscmds, *av, "stats command"); 177 if (tcmd == TOK_RESET) 178 nat64stl_reset_stats(name, set); 179 } 180 } 181 182 183 static void 184 nat64stl_fill_ntlv(ipfw_obj_ntlv *ntlv, const char *name, uint8_t set) 185 { 186 187 ntlv->head.type = IPFW_TLV_EACTION_NAME(1); /* it doesn't matter */ 188 ntlv->head.length = sizeof(ipfw_obj_ntlv); 189 ntlv->idx = 1; 190 ntlv->set = set; 191 strlcpy(ntlv->name, name, sizeof(ntlv->name)); 192 } 193 194 static struct _s_x nat64newcmds[] = { 195 { "table4", TOK_TABLE4 }, 196 { "table6", TOK_TABLE6 }, 197 { "prefix6", TOK_PREFIX6 }, 198 { "log", TOK_LOG }, 199 { "-log", TOK_LOGOFF }, 200 { "allow_private", TOK_PRIVATE }, 201 { "-allow_private", TOK_PRIVATEOFF }, 202 { NULL, 0 } 203 }; 204 205 /* 206 * Creates new nat64stl instance 207 * ipfw nat64stl <NAME> create table4 <name> table6 <name> [ prefix6 <prefix>] 208 * Request: [ ipfw_obj_lheader ipfw_nat64stl_cfg ] 209 */ 210 #define NAT64STL_HAS_TABLE4 0x01 211 #define NAT64STL_HAS_TABLE6 0x02 212 #define NAT64STL_HAS_PREFIX6 0x04 213 static void 214 nat64stl_create(const char *name, uint8_t set, int ac, char *av[]) 215 { 216 char buf[sizeof(ipfw_obj_lheader) + sizeof(ipfw_nat64stl_cfg)]; 217 ipfw_nat64stl_cfg *cfg; 218 ipfw_obj_lheader *olh; 219 int tcmd, flags; 220 char *p; 221 222 memset(buf, 0, sizeof(buf)); 223 olh = (ipfw_obj_lheader *)buf; 224 cfg = (ipfw_nat64stl_cfg *)(olh + 1); 225 226 /* Some reasonable defaults */ 227 inet_pton(AF_INET6, "64:ff9b::", &cfg->prefix6); 228 cfg->plen6 = 96; 229 cfg->set = set; 230 flags = NAT64STL_HAS_PREFIX6; 231 while (ac > 0) { 232 tcmd = get_token(nat64newcmds, *av, "option"); 233 ac--; av++; 234 235 switch (tcmd) { 236 case TOK_TABLE4: 237 NEED1("table name required"); 238 table_fill_ntlv(&cfg->ntlv4, *av, set, 4); 239 flags |= NAT64STL_HAS_TABLE4; 240 ac--; av++; 241 break; 242 case TOK_TABLE6: 243 NEED1("table name required"); 244 table_fill_ntlv(&cfg->ntlv6, *av, set, 6); 245 flags |= NAT64STL_HAS_TABLE6; 246 ac--; av++; 247 break; 248 case TOK_PREFIX6: 249 NEED1("IPv6 prefix6 required"); 250 if ((p = strchr(*av, '/')) != NULL) 251 *p++ = '\0'; 252 if (inet_pton(AF_INET6, *av, &cfg->prefix6) != 1) 253 errx(EX_USAGE, 254 "Bad prefix: %s", *av); 255 cfg->plen6 = strtol(p, NULL, 10); 256 if (ipfw_check_nat64prefix(&cfg->prefix6, 257 cfg->plen6) != 0) 258 errx(EX_USAGE, 259 "Bad prefix length: %s", p); 260 flags |= NAT64STL_HAS_PREFIX6; 261 ac--; av++; 262 break; 263 case TOK_LOG: 264 cfg->flags |= NAT64_LOG; 265 break; 266 case TOK_LOGOFF: 267 cfg->flags &= ~NAT64_LOG; 268 break; 269 case TOK_PRIVATE: 270 cfg->flags |= NAT64_ALLOW_PRIVATE; 271 break; 272 case TOK_PRIVATEOFF: 273 cfg->flags &= ~NAT64_ALLOW_PRIVATE; 274 break; 275 } 276 } 277 278 /* Check validness */ 279 if ((flags & NAT64STL_HAS_TABLE4) != NAT64STL_HAS_TABLE4) 280 errx(EX_USAGE, "table4 required"); 281 if ((flags & NAT64STL_HAS_TABLE6) != NAT64STL_HAS_TABLE6) 282 errx(EX_USAGE, "table6 required"); 283 if ((flags & NAT64STL_HAS_PREFIX6) != NAT64STL_HAS_PREFIX6) 284 errx(EX_USAGE, "prefix6 required"); 285 286 olh->count = 1; 287 olh->objsize = sizeof(*cfg); 288 olh->size = sizeof(buf); 289 strlcpy(cfg->name, name, sizeof(cfg->name)); 290 if (do_set3(IP_FW_NAT64STL_CREATE, &olh->opheader, sizeof(buf)) != 0) 291 err(EX_OSERR, "nat64stl instance creation failed"); 292 } 293 294 /* 295 * Configures existing nat64stl instance 296 * ipfw nat64stl <NAME> config <options> 297 * Request: [ ipfw_obj_header ipfw_nat64stl_cfg ] 298 */ 299 static void 300 nat64stl_config(const char *name, uint8_t set, int ac, char **av) 301 { 302 char buf[sizeof(ipfw_obj_header) + sizeof(ipfw_nat64stl_cfg)]; 303 ipfw_nat64stl_cfg *cfg; 304 ipfw_obj_header *oh; 305 char *opt; 306 size_t sz; 307 int tcmd; 308 309 if (ac == 0) 310 errx(EX_USAGE, "config options required"); 311 memset(&buf, 0, sizeof(buf)); 312 oh = (ipfw_obj_header *)buf; 313 cfg = (ipfw_nat64stl_cfg *)(oh + 1); 314 sz = sizeof(buf); 315 316 nat64stl_fill_ntlv(&oh->ntlv, name, set); 317 if (do_get3(IP_FW_NAT64STL_CONFIG, &oh->opheader, &sz) != 0) 318 err(EX_OSERR, "failed to get config for instance %s", name); 319 320 while (ac > 0) { 321 tcmd = get_token(nat64newcmds, *av, "option"); 322 opt = *av; 323 ac--; av++; 324 325 switch (tcmd) { 326 #if 0 327 case TOK_TABLE4: 328 NEED1("table name required"); 329 table_fill_ntlv(&cfg->ntlv4, *av, set, 4); 330 ac--; av++; 331 break; 332 case TOK_TABLE6: 333 NEED1("table name required"); 334 table_fill_ntlv(&cfg->ntlv6, *av, set, 6); 335 ac--; av++; 336 break; 337 #endif 338 case TOK_LOG: 339 cfg->flags |= NAT64_LOG; 340 break; 341 case TOK_LOGOFF: 342 cfg->flags &= ~NAT64_LOG; 343 break; 344 case TOK_PRIVATE: 345 cfg->flags |= NAT64_ALLOW_PRIVATE; 346 break; 347 case TOK_PRIVATEOFF: 348 cfg->flags &= ~NAT64_ALLOW_PRIVATE; 349 break; 350 default: 351 errx(EX_USAGE, "Can't change %s option", opt); 352 } 353 } 354 355 if (do_set3(IP_FW_NAT64STL_CONFIG, &oh->opheader, sizeof(buf)) != 0) 356 err(EX_OSERR, "nat64stl instance configuration failed"); 357 } 358 359 /* 360 * Destroys nat64stl instance. 361 * Request: [ ipfw_obj_header ] 362 */ 363 static void 364 nat64stl_destroy(const char *name, uint8_t set) 365 { 366 ipfw_obj_header oh; 367 368 memset(&oh, 0, sizeof(oh)); 369 nat64stl_fill_ntlv(&oh.ntlv, name, set); 370 if (do_set3(IP_FW_NAT64STL_DESTROY, &oh.opheader, sizeof(oh)) != 0) 371 err(EX_OSERR, "failed to destroy nat instance %s", name); 372 } 373 374 /* 375 * Get nat64stl instance statistics. 376 * Request: [ ipfw_obj_header ] 377 * Reply: [ ipfw_obj_header ipfw_obj_ctlv [ uint64_t x N ] ] 378 */ 379 static int 380 nat64stl_get_stats(const char *name, uint8_t set, 381 struct ipfw_nat64stl_stats *stats) 382 { 383 ipfw_obj_header *oh; 384 ipfw_obj_ctlv *oc; 385 size_t sz; 386 387 sz = sizeof(*oh) + sizeof(*oc) + sizeof(*stats); 388 oh = calloc(1, sz); 389 nat64stl_fill_ntlv(&oh->ntlv, name, set); 390 if (do_get3(IP_FW_NAT64STL_STATS, &oh->opheader, &sz) == 0) { 391 oc = (ipfw_obj_ctlv *)(oh + 1); 392 memcpy(stats, oc + 1, sizeof(*stats)); 393 free(oh); 394 return (0); 395 } 396 free(oh); 397 return (-1); 398 } 399 400 static void 401 nat64stl_stats(const char *name, uint8_t set) 402 { 403 struct ipfw_nat64stl_stats stats; 404 405 if (nat64stl_get_stats(name, set, &stats) != 0) 406 err(EX_OSERR, "Error retrieving stats"); 407 408 if (g_co.use_set != 0 || set != 0) 409 printf("set %u ", set); 410 printf("nat64stl %s\n", name); 411 412 printf("\t%ju packets translated from IPv6 to IPv4\n", 413 (uintmax_t)stats.opcnt64); 414 printf("\t%ju packets translated from IPv4 to IPv6\n", 415 (uintmax_t)stats.opcnt46); 416 printf("\t%ju IPv6 fragments created\n", 417 (uintmax_t)stats.ofrags); 418 printf("\t%ju IPv4 fragments received\n", 419 (uintmax_t)stats.ifrags); 420 printf("\t%ju output packets dropped due to no bufs, etc.\n", 421 (uintmax_t)stats.oerrors); 422 printf("\t%ju output packets discarded due to no IPv4 route\n", 423 (uintmax_t)stats.noroute4); 424 printf("\t%ju output packets discarded due to no IPv6 route\n", 425 (uintmax_t)stats.noroute6); 426 printf("\t%ju packets discarded due to unsupported protocol\n", 427 (uintmax_t)stats.noproto); 428 printf("\t%ju packets discarded due to memory allocation problems\n", 429 (uintmax_t)stats.nomem); 430 printf("\t%ju packets discarded due to some errors\n", 431 (uintmax_t)stats.dropped); 432 } 433 434 /* 435 * Reset nat64stl instance statistics specified by @oh->ntlv. 436 * Request: [ ipfw_obj_header ] 437 */ 438 static void 439 nat64stl_reset_stats(const char *name, uint8_t set) 440 { 441 ipfw_obj_header oh; 442 443 memset(&oh, 0, sizeof(oh)); 444 nat64stl_fill_ntlv(&oh.ntlv, name, set); 445 if (do_set3(IP_FW_NAT64STL_RESET_STATS, &oh.opheader, sizeof(oh)) != 0) 446 err(EX_OSERR, "failed to reset stats for instance %s", name); 447 } 448 449 static int 450 nat64stl_show_cb(ipfw_nat64stl_cfg *cfg, const char *name, uint8_t set) 451 { 452 char abuf[INET6_ADDRSTRLEN]; 453 454 if (name != NULL && strcmp(cfg->name, name) != 0) 455 return (ESRCH); 456 457 if (g_co.use_set != 0 && cfg->set != set) 458 return (ESRCH); 459 460 if (g_co.use_set != 0 || cfg->set != 0) 461 printf("set %u ", cfg->set); 462 463 printf("nat64stl %s table4 %s table6 %s", 464 cfg->name, cfg->ntlv4.name, cfg->ntlv6.name); 465 inet_ntop(AF_INET6, &cfg->prefix6, abuf, sizeof(abuf)); 466 printf(" prefix6 %s/%u", abuf, cfg->plen6); 467 if (cfg->flags & NAT64_LOG) 468 printf(" log"); 469 if (cfg->flags & NAT64_ALLOW_PRIVATE) 470 printf(" allow_private"); 471 printf("\n"); 472 return (0); 473 } 474 475 static int 476 nat64stl_destroy_cb(ipfw_nat64stl_cfg *cfg, const char *name __unused, 477 uint8_t set) 478 { 479 480 if (g_co.use_set != 0 && cfg->set != set) 481 return (ESRCH); 482 483 nat64stl_destroy(cfg->name, cfg->set); 484 return (0); 485 } 486 487 488 /* 489 * Compare nat64stl instances names. 490 * Honor number comparison. 491 */ 492 static int 493 nat64name_cmp(const void *a, const void *b) 494 { 495 const ipfw_nat64stl_cfg *ca, *cb; 496 497 ca = (const ipfw_nat64stl_cfg *)a; 498 cb = (const ipfw_nat64stl_cfg *)b; 499 500 if (ca->set > cb->set) 501 return (1); 502 else if (ca->set < cb->set) 503 return (-1); 504 return (stringnum_cmp(ca->name, cb->name)); 505 } 506 507 /* 508 * Retrieves nat64stl instance list from kernel, 509 * optionally sorts it and calls requested function for each instance. 510 * 511 * Request: [ ipfw_obj_lheader ] 512 * Reply: [ ipfw_obj_lheader ipfw_nat64stl_cfg x N ] 513 */ 514 static int 515 nat64stl_foreach(nat64stl_cb_t *f, const char *name, uint8_t set, int sort) 516 { 517 ipfw_obj_lheader *olh; 518 ipfw_nat64stl_cfg *cfg; 519 size_t sz; 520 uint32_t i; 521 int error; 522 523 /* Start with reasonable default */ 524 sz = sizeof(*olh) + 16 * sizeof(*cfg); 525 for (;;) { 526 if ((olh = calloc(1, sz)) == NULL) 527 return (ENOMEM); 528 529 olh->size = sz; 530 if (do_get3(IP_FW_NAT64STL_LIST, &olh->opheader, &sz) != 0) { 531 sz = olh->size; 532 free(olh); 533 if (errno != ENOMEM) 534 return (errno); 535 continue; 536 } 537 538 if (sort != 0) 539 qsort(olh + 1, olh->count, olh->objsize, 540 nat64name_cmp); 541 542 cfg = (ipfw_nat64stl_cfg *)(olh + 1); 543 for (i = 0; i < olh->count; i++) { 544 error = f(cfg, name, set); /* Ignore errors for now */ 545 cfg = (ipfw_nat64stl_cfg *)((caddr_t)cfg + 546 olh->objsize); 547 } 548 free(olh); 549 break; 550 } 551 return (0); 552 } 553 554