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 52typedef int (nptv6_cb_t)(ipfw_nptv6_cfg *i, const char *name, uint8_t set); 53static int nptv6_foreach(nptv6_cb_t *f, const char *name, uint8_t set, 54 int sort); 55 56static void nptv6_create(const char *name, uint8_t set, int ac, char **av); 57static void nptv6_destroy(const char *name, uint8_t set); 58static void nptv6_stats(const char *name, uint8_t set); 59static void nptv6_reset_stats(const char *name, uint8_t set); 60static int nptv6_show_cb(ipfw_nptv6_cfg *cfg, const char *name, uint8_t set); 61static int nptv6_destroy_cb(ipfw_nptv6_cfg *cfg, const char *name, uint8_t set); 62 63static 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 72static 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 85void 86ipfw_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 140static void 141nptv6_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 151static 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 160static void 161nptv6_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 188static void 189nptv6_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); 243check_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 */ 285static void 286nptv6_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 */ 301static int 302nptv6_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 321static void 322nptv6_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 */ 344static void 345nptv6_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 355static int 356nptv6_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 380static int 381nptv6_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 */ 396static int 397nptv6name_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 */ 416static int 417nptv6_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