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