1 /*- 2 * Copyright (c) 2016 Yandex LLC 3 * Copyright (c) 2016 Andrey V. Elsukov <ae@FreeBSD.org> 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 21 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 25 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 #include <sys/cdefs.h> 29 __FBSDID("$FreeBSD$"); 30 31 #include <sys/param.h> 32 #include <sys/socket.h> 33 34 #include "ipfw2.h" 35 36 #include <ctype.h> 37 #include <err.h> 38 #include <errno.h> 39 #include <inttypes.h> 40 #include <stdio.h> 41 #include <stdlib.h> 42 #include <string.h> 43 #include <sysexits.h> 44 45 #include <net/if.h> 46 #include <netinet/in.h> 47 #include <netinet/ip_fw.h> 48 #include <netinet6/ip_fw_nptv6.h> 49 #include <arpa/inet.h> 50 51 52 typedef int (nptv6_cb_t)(ipfw_nptv6_cfg *i, const char *name, uint8_t set); 53 static int nptv6_foreach(nptv6_cb_t *f, const char *name, uint8_t set, 54 int sort); 55 56 static void nptv6_create(const char *name, uint8_t set, int ac, char **av); 57 static void nptv6_destroy(const char *name, uint8_t set); 58 static void nptv6_stats(const char *name, uint8_t set); 59 static void nptv6_reset_stats(const char *name, uint8_t set); 60 static int nptv6_show_cb(ipfw_nptv6_cfg *cfg, const char *name, uint8_t set); 61 static int nptv6_destroy_cb(ipfw_nptv6_cfg *cfg, const char *name, uint8_t set); 62 63 static struct _s_x nptv6cmds[] = { 64 { "create", TOK_CREATE }, 65 { "destroy", TOK_DESTROY }, 66 { "list", TOK_LIST }, 67 { "show", TOK_LIST }, 68 { "stats", TOK_STATS }, 69 { NULL, 0 } 70 }; 71 72 static struct _s_x nptv6statscmds[] = { 73 { "reset", TOK_RESET }, 74 { NULL, 0 } 75 }; 76 77 /* 78 * This one handles all NPTv6-related commands 79 * ipfw [set N] nptv6 NAME {create | config} ... 80 * ipfw [set N] nptv6 NAME stats [reset] 81 * ipfw [set N] nptv6 {NAME | all} destroy 82 * ipfw [set N] nptv6 {NAME | all} {list | show} 83 */ 84 #define nptv6_check_name table_check_name 85 void 86 ipfw_nptv6_handler(int ac, char *av[]) 87 { 88 const char *name; 89 int tcmd; 90 uint8_t set; 91 92 if (g_co.use_set != 0) 93 set = g_co.use_set - 1; 94 else 95 set = 0; 96 ac--; av++; 97 98 NEED1("nptv6 needs instance name"); 99 name = *av; 100 if (nptv6_check_name(name) != 0) { 101 if (strcmp(name, "all") == 0) { 102 name = NULL; 103 } else 104 errx(EX_USAGE, "nptv6 instance name %s is invalid", 105 name); 106 } 107 ac--; av++; 108 NEED1("nptv6 needs command"); 109 110 tcmd = get_token(nptv6cmds, *av, "nptv6 command"); 111 if (name == NULL && tcmd != TOK_DESTROY && tcmd != TOK_LIST) 112 errx(EX_USAGE, "nptv6 instance name required"); 113 switch (tcmd) { 114 case TOK_CREATE: 115 ac--; av++; 116 nptv6_create(name, set, ac, av); 117 break; 118 case TOK_LIST: 119 nptv6_foreach(nptv6_show_cb, name, set, 1); 120 break; 121 case TOK_DESTROY: 122 if (name == NULL) 123 nptv6_foreach(nptv6_destroy_cb, NULL, set, 0); 124 else 125 nptv6_destroy(name, set); 126 break; 127 case TOK_STATS: 128 ac--; av++; 129 if (ac == 0) { 130 nptv6_stats(name, set); 131 break; 132 } 133 tcmd = get_token(nptv6statscmds, *av, "stats command"); 134 if (tcmd == TOK_RESET) 135 nptv6_reset_stats(name, set); 136 } 137 } 138 139 140 static void 141 nptv6_fill_ntlv(ipfw_obj_ntlv *ntlv, const char *name, uint8_t set) 142 { 143 144 ntlv->head.type = IPFW_TLV_EACTION_NAME(1); /* it doesn't matter */ 145 ntlv->head.length = sizeof(ipfw_obj_ntlv); 146 ntlv->idx = 1; 147 ntlv->set = set; 148 strlcpy(ntlv->name, name, sizeof(ntlv->name)); 149 } 150 151 static struct _s_x nptv6newcmds[] = { 152 { "int_prefix", TOK_INTPREFIX }, 153 { "ext_prefix", TOK_EXTPREFIX }, 154 { "prefixlen", TOK_PREFIXLEN }, 155 { "ext_if", TOK_EXTIF }, 156 { NULL, 0 } 157 }; 158 159 160 static void 161 nptv6_parse_prefix(const char *arg, struct in6_addr *prefix, int *len) 162 { 163 char *p, *l; 164 165 p = strdup(arg); 166 if (p == NULL) 167 err(EX_OSERR, NULL); 168 if ((l = strchr(p, '/')) != NULL) 169 *l++ = '\0'; 170 if (inet_pton(AF_INET6, p, prefix) != 1) 171 errx(EX_USAGE, "Bad prefix: %s", p); 172 if (l != NULL) { 173 *len = (int)strtol(l, &l, 10); 174 if (*l != '\0' || *len <= 0 || *len > 64) 175 errx(EX_USAGE, "Bad prefix length: %s", arg); 176 } else 177 *len = 0; 178 free(p); 179 } 180 /* 181 * Creates new nptv6 instance 182 * ipfw nptv6 <NAME> create int_prefix <prefix> ext_prefix <prefix> 183 * Request: [ ipfw_obj_lheader ipfw_nptv6_cfg ] 184 */ 185 #define NPTV6_HAS_INTPREFIX 0x01 186 #define NPTV6_HAS_EXTPREFIX 0x02 187 #define NPTV6_HAS_PREFIXLEN 0x04 188 static void 189 nptv6_create(const char *name, uint8_t set, int ac, char *av[]) 190 { 191 char buf[sizeof(ipfw_obj_lheader) + sizeof(ipfw_nptv6_cfg)]; 192 struct in6_addr mask; 193 ipfw_nptv6_cfg *cfg; 194 ipfw_obj_lheader *olh; 195 int tcmd, flags, plen; 196 char *p; 197 198 plen = 0; 199 memset(buf, 0, sizeof(buf)); 200 olh = (ipfw_obj_lheader *)buf; 201 cfg = (ipfw_nptv6_cfg *)(olh + 1); 202 cfg->set = set; 203 flags = 0; 204 while (ac > 0) { 205 tcmd = get_token(nptv6newcmds, *av, "option"); 206 ac--; av++; 207 208 switch (tcmd) { 209 case TOK_INTPREFIX: 210 NEED1("IPv6 prefix required"); 211 nptv6_parse_prefix(*av, &cfg->internal, &plen); 212 flags |= NPTV6_HAS_INTPREFIX; 213 if (plen > 0) 214 goto check_prefix; 215 ac--; av++; 216 break; 217 case TOK_EXTPREFIX: 218 if (flags & NPTV6_HAS_EXTPREFIX) 219 errx(EX_USAGE, 220 "Only one ext_prefix or ext_if allowed"); 221 NEED1("IPv6 prefix required"); 222 nptv6_parse_prefix(*av, &cfg->external, &plen); 223 flags |= NPTV6_HAS_EXTPREFIX; 224 if (plen > 0) 225 goto check_prefix; 226 ac--; av++; 227 break; 228 case TOK_EXTIF: 229 if (flags & NPTV6_HAS_EXTPREFIX) 230 errx(EX_USAGE, 231 "Only one ext_prefix or ext_if allowed"); 232 NEED1("Interface name required"); 233 if (strlen(*av) >= sizeof(cfg->if_name)) 234 errx(EX_USAGE, "Invalid interface name"); 235 flags |= NPTV6_HAS_EXTPREFIX; 236 cfg->flags |= NPTV6_DYNAMIC_PREFIX; 237 strncpy(cfg->if_name, *av, sizeof(cfg->if_name)); 238 ac--; av++; 239 break; 240 case TOK_PREFIXLEN: 241 NEED1("IPv6 prefix length required"); 242 plen = strtol(*av, &p, 10); 243 check_prefix: 244 if (*p != '\0' || plen < 8 || plen > 64) 245 errx(EX_USAGE, "wrong prefix length: %s", *av); 246 /* RFC 6296 Sec. 3.1 */ 247 if (cfg->plen > 0 && cfg->plen != plen) { 248 warnx("Prefix length mismatch (%d vs %d). " 249 "It was extended up to %d", 250 cfg->plen, plen, MAX(plen, cfg->plen)); 251 plen = MAX(plen, cfg->plen); 252 } 253 cfg->plen = plen; 254 flags |= NPTV6_HAS_PREFIXLEN; 255 ac--; av++; 256 break; 257 } 258 } 259 260 /* Check validness */ 261 if ((flags & NPTV6_HAS_INTPREFIX) != NPTV6_HAS_INTPREFIX) 262 errx(EX_USAGE, "int_prefix required"); 263 if ((flags & NPTV6_HAS_EXTPREFIX) != NPTV6_HAS_EXTPREFIX) 264 errx(EX_USAGE, "ext_prefix or ext_if required"); 265 if ((flags & NPTV6_HAS_PREFIXLEN) != NPTV6_HAS_PREFIXLEN) 266 errx(EX_USAGE, "prefixlen required"); 267 268 n2mask(&mask, cfg->plen); 269 APPLY_MASK(&cfg->internal, &mask); 270 if ((cfg->flags & NPTV6_DYNAMIC_PREFIX) == 0) 271 APPLY_MASK(&cfg->external, &mask); 272 273 olh->count = 1; 274 olh->objsize = sizeof(*cfg); 275 olh->size = sizeof(buf); 276 strlcpy(cfg->name, name, sizeof(cfg->name)); 277 if (do_set3(IP_FW_NPTV6_CREATE, &olh->opheader, sizeof(buf)) != 0) 278 err(EX_OSERR, "nptv6 instance creation failed"); 279 } 280 281 /* 282 * Destroys NPTv6 instance. 283 * Request: [ ipfw_obj_header ] 284 */ 285 static void 286 nptv6_destroy(const char *name, uint8_t set) 287 { 288 ipfw_obj_header oh; 289 290 memset(&oh, 0, sizeof(oh)); 291 nptv6_fill_ntlv(&oh.ntlv, name, set); 292 if (do_set3(IP_FW_NPTV6_DESTROY, &oh.opheader, sizeof(oh)) != 0) 293 err(EX_OSERR, "failed to destroy nat instance %s", name); 294 } 295 296 /* 297 * Get NPTv6 instance statistics. 298 * Request: [ ipfw_obj_header ] 299 * Reply: [ ipfw_obj_header ipfw_obj_ctlv [ uint64_t x N ] ] 300 */ 301 static int 302 nptv6_get_stats(const char *name, uint8_t set, struct ipfw_nptv6_stats *stats) 303 { 304 ipfw_obj_header *oh; 305 ipfw_obj_ctlv *oc; 306 size_t sz; 307 308 sz = sizeof(*oh) + sizeof(*oc) + sizeof(*stats); 309 oh = calloc(1, sz); 310 nptv6_fill_ntlv(&oh->ntlv, name, set); 311 if (do_get3(IP_FW_NPTV6_STATS, &oh->opheader, &sz) == 0) { 312 oc = (ipfw_obj_ctlv *)(oh + 1); 313 memcpy(stats, oc + 1, sizeof(*stats)); 314 free(oh); 315 return (0); 316 } 317 free(oh); 318 return (-1); 319 } 320 321 static void 322 nptv6_stats(const char *name, uint8_t set) 323 { 324 struct ipfw_nptv6_stats stats; 325 326 if (nptv6_get_stats(name, set, &stats) != 0) 327 err(EX_OSERR, "Error retrieving stats"); 328 329 if (g_co.use_set != 0 || set != 0) 330 printf("set %u ", set); 331 printf("nptv6 %s\n", name); 332 printf("\t%ju packets translated (internal to external)\n", 333 (uintmax_t)stats.in2ex); 334 printf("\t%ju packets translated (external to internal)\n", 335 (uintmax_t)stats.ex2in); 336 printf("\t%ju packets dropped due to some error\n", 337 (uintmax_t)stats.dropped); 338 } 339 340 /* 341 * Reset NPTv6 instance statistics specified by @oh->ntlv. 342 * Request: [ ipfw_obj_header ] 343 */ 344 static void 345 nptv6_reset_stats(const char *name, uint8_t set) 346 { 347 ipfw_obj_header oh; 348 349 memset(&oh, 0, sizeof(oh)); 350 nptv6_fill_ntlv(&oh.ntlv, name, set); 351 if (do_set3(IP_FW_NPTV6_RESET_STATS, &oh.opheader, sizeof(oh)) != 0) 352 err(EX_OSERR, "failed to reset stats for instance %s", name); 353 } 354 355 static int 356 nptv6_show_cb(ipfw_nptv6_cfg *cfg, const char *name, uint8_t set) 357 { 358 char abuf[INET6_ADDRSTRLEN]; 359 360 if (name != NULL && strcmp(cfg->name, name) != 0) 361 return (ESRCH); 362 363 if (g_co.use_set != 0 && cfg->set != set) 364 return (ESRCH); 365 366 if (g_co.use_set != 0 || cfg->set != 0) 367 printf("set %u ", cfg->set); 368 inet_ntop(AF_INET6, &cfg->internal, abuf, sizeof(abuf)); 369 printf("nptv6 %s int_prefix %s ", cfg->name, abuf); 370 if (cfg->flags & NPTV6_DYNAMIC_PREFIX) 371 printf("ext_if %s ", cfg->if_name); 372 else { 373 inet_ntop(AF_INET6, &cfg->external, abuf, sizeof(abuf)); 374 printf("ext_prefix %s ", abuf); 375 } 376 printf("prefixlen %u\n", cfg->plen); 377 return (0); 378 } 379 380 static int 381 nptv6_destroy_cb(ipfw_nptv6_cfg *cfg, const char *name __unused, uint8_t set) 382 { 383 384 if (g_co.use_set != 0 && cfg->set != set) 385 return (ESRCH); 386 387 nptv6_destroy(cfg->name, cfg->set); 388 return (0); 389 } 390 391 392 /* 393 * Compare NPTv6 instances names. 394 * Honor number comparison. 395 */ 396 static int 397 nptv6name_cmp(const void *a, const void *b) 398 { 399 const ipfw_nptv6_cfg *ca, *cb; 400 401 ca = (const ipfw_nptv6_cfg *)a; 402 cb = (const ipfw_nptv6_cfg *)b; 403 404 if (ca->set > cb->set) 405 return (1); 406 else if (ca->set < cb->set) 407 return (-1); 408 return (stringnum_cmp(ca->name, cb->name)); 409 } 410 411 /* 412 * Retrieves NPTv6 instance list from kernel, 413 * Request: [ ipfw_obj_lheader ] 414 * Reply: [ ipfw_obj_lheader ipfw_nptv6_cfg x N ] 415 */ 416 static int 417 nptv6_foreach(nptv6_cb_t *f, const char *name, uint8_t set, int sort) 418 { 419 ipfw_obj_lheader *olh; 420 ipfw_nptv6_cfg *cfg; 421 size_t sz; 422 uint32_t i; 423 int error; 424 425 /* Start with reasonable default */ 426 sz = sizeof(*olh) + 16 * sizeof(*cfg); 427 for (;;) { 428 if ((olh = calloc(1, sz)) == NULL) 429 return (ENOMEM); 430 431 olh->size = sz; 432 if (do_get3(IP_FW_NPTV6_LIST, &olh->opheader, &sz) != 0) { 433 sz = olh->size; 434 free(olh); 435 if (errno != ENOMEM) 436 return (errno); 437 continue; 438 } 439 440 if (sort != 0) 441 qsort(olh + 1, olh->count, olh->objsize, nptv6name_cmp); 442 443 cfg = (ipfw_nptv6_cfg *)(olh + 1); 444 for (i = 0; i < olh->count; i++) { 445 error = f(cfg, name, set); 446 cfg = (ipfw_nptv6_cfg *)((caddr_t)cfg + olh->objsize); 447 } 448 free(olh); 449 break; 450 } 451 return (0); 452 } 453 454