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/param.h> 29 #include <sys/socket.h> 30 31 #include "ipfw2.h" 32 33 #include <ctype.h> 34 #include <err.h> 35 #include <errno.h> 36 #include <inttypes.h> 37 #include <stdio.h> 38 #include <stdlib.h> 39 #include <string.h> 40 #include <sysexits.h> 41 42 #include <net/if.h> 43 #include <netinet/in.h> 44 #include <netinet/ip_fw.h> 45 #include <netinet6/ip_fw_nptv6.h> 46 #include <arpa/inet.h> 47 48 49 typedef int (nptv6_cb_t)(ipfw_nptv6_cfg *i, const char *name, uint8_t set); 50 static int nptv6_foreach(nptv6_cb_t *f, const char *name, uint8_t set, 51 int sort); 52 53 static void nptv6_create(const char *name, uint8_t set, int ac, char **av); 54 static void nptv6_destroy(const char *name, uint8_t set); 55 static void nptv6_stats(const char *name, uint8_t set); 56 static void nptv6_reset_stats(const char *name, uint8_t set); 57 static int nptv6_show_cb(ipfw_nptv6_cfg *cfg, const char *name, uint8_t set); 58 static int nptv6_destroy_cb(ipfw_nptv6_cfg *cfg, const char *name, uint8_t set); 59 60 static struct _s_x nptv6cmds[] = { 61 { "create", TOK_CREATE }, 62 { "destroy", TOK_DESTROY }, 63 { "list", TOK_LIST }, 64 { "show", TOK_LIST }, 65 { "stats", TOK_STATS }, 66 { NULL, 0 } 67 }; 68 69 static struct _s_x nptv6statscmds[] = { 70 { "reset", TOK_RESET }, 71 { NULL, 0 } 72 }; 73 74 /* 75 * This one handles all NPTv6-related commands 76 * ipfw [set N] nptv6 NAME {create | config} ... 77 * ipfw [set N] nptv6 NAME stats [reset] 78 * ipfw [set N] nptv6 {NAME | all} destroy 79 * ipfw [set N] nptv6 {NAME | all} {list | show} 80 */ 81 #define nptv6_check_name table_check_name 82 void 83 ipfw_nptv6_handler(int ac, char *av[]) 84 { 85 const char *name; 86 int tcmd; 87 uint8_t set; 88 89 if (g_co.use_set != 0) 90 set = g_co.use_set - 1; 91 else 92 set = 0; 93 ac--; av++; 94 95 NEED1("nptv6 needs instance name"); 96 name = *av; 97 if (nptv6_check_name(name) != 0) { 98 if (strcmp(name, "all") == 0) { 99 name = NULL; 100 } else 101 errx(EX_USAGE, "nptv6 instance name %s is invalid", 102 name); 103 } 104 ac--; av++; 105 NEED1("nptv6 needs command"); 106 107 tcmd = get_token(nptv6cmds, *av, "nptv6 command"); 108 if (name == NULL && tcmd != TOK_DESTROY && tcmd != TOK_LIST) 109 errx(EX_USAGE, "nptv6 instance name required"); 110 switch (tcmd) { 111 case TOK_CREATE: 112 ac--; av++; 113 nptv6_create(name, set, ac, av); 114 break; 115 case TOK_LIST: 116 nptv6_foreach(nptv6_show_cb, name, set, 1); 117 break; 118 case TOK_DESTROY: 119 if (name == NULL) 120 nptv6_foreach(nptv6_destroy_cb, NULL, set, 0); 121 else 122 nptv6_destroy(name, set); 123 break; 124 case TOK_STATS: 125 ac--; av++; 126 if (ac == 0) { 127 nptv6_stats(name, set); 128 break; 129 } 130 tcmd = get_token(nptv6statscmds, *av, "stats command"); 131 if (tcmd == TOK_RESET) 132 nptv6_reset_stats(name, set); 133 } 134 } 135 136 137 static void 138 nptv6_fill_ntlv(ipfw_obj_ntlv *ntlv, const char *name, uint8_t set) 139 { 140 141 ntlv->head.type = IPFW_TLV_EACTION_NAME(1); /* it doesn't matter */ 142 ntlv->head.length = sizeof(ipfw_obj_ntlv); 143 ntlv->idx = 1; 144 ntlv->set = set; 145 strlcpy(ntlv->name, name, sizeof(ntlv->name)); 146 } 147 148 static struct _s_x nptv6newcmds[] = { 149 { "int_prefix", TOK_INTPREFIX }, 150 { "ext_prefix", TOK_EXTPREFIX }, 151 { "prefixlen", TOK_PREFIXLEN }, 152 { "ext_if", TOK_EXTIF }, 153 { NULL, 0 } 154 }; 155 156 static void 157 nptv6_parse_prefix(const char *arg, struct in6_addr *prefix, int *len) 158 { 159 long plen; 160 char *p, *l; 161 162 p = strdup(arg); 163 if (p == NULL) 164 err(EX_OSERR, NULL); 165 if ((l = strchr(p, '/')) != NULL) 166 *l++ = '\0'; 167 if (inet_pton(AF_INET6, p, prefix) != 1) 168 errx(EX_USAGE, "Bad prefix: %s", p); 169 if (l != NULL) { 170 plen = strtol(l, &l, 10); 171 if (*l != '\0' || plen < 8 || plen > 64) 172 errx(EX_USAGE, "Bad prefix length: %s", arg); 173 *len = plen; 174 } else 175 *len = 0; 176 free(p); 177 } 178 179 /* 180 * Creates new nptv6 instance 181 * ipfw nptv6 <NAME> create int_prefix <prefix> ext_prefix <prefix> 182 * Request: [ ipfw_obj_lheader ipfw_nptv6_cfg ] 183 */ 184 #define NPTV6_HAS_INTPREFIX 0x01 185 #define NPTV6_HAS_EXTPREFIX 0x02 186 #define NPTV6_HAS_PREFIXLEN 0x04 187 static void 188 nptv6_create(const char *name, uint8_t set, int ac, char *av[]) 189 { 190 char buf[sizeof(ipfw_obj_lheader) + sizeof(ipfw_nptv6_cfg)]; 191 struct in6_addr mask; 192 ipfw_nptv6_cfg *cfg; 193 ipfw_obj_lheader *olh; 194 int tcmd, flags, iplen, eplen, pplen; 195 char *p; 196 197 iplen = eplen = pplen = 0; 198 memset(buf, 0, sizeof(buf)); 199 olh = (ipfw_obj_lheader *)buf; 200 cfg = (ipfw_nptv6_cfg *)(olh + 1); 201 cfg->set = set; 202 flags = 0; 203 while (ac > 0) { 204 tcmd = get_token(nptv6newcmds, *av, "option"); 205 ac--; av++; 206 207 switch (tcmd) { 208 case TOK_INTPREFIX: 209 NEED1("IPv6 prefix required"); 210 nptv6_parse_prefix(*av, &cfg->internal, &iplen); 211 flags |= NPTV6_HAS_INTPREFIX; 212 ac--; av++; 213 break; 214 case TOK_EXTPREFIX: 215 if (flags & NPTV6_HAS_EXTPREFIX) 216 errx(EX_USAGE, 217 "Only one ext_prefix or ext_if allowed"); 218 NEED1("IPv6 prefix required"); 219 nptv6_parse_prefix(*av, &cfg->external, &eplen); 220 flags |= NPTV6_HAS_EXTPREFIX; 221 ac--; av++; 222 break; 223 case TOK_EXTIF: 224 if (flags & NPTV6_HAS_EXTPREFIX) 225 errx(EX_USAGE, 226 "Only one ext_prefix or ext_if allowed"); 227 NEED1("Interface name required"); 228 if (strlen(*av) >= sizeof(cfg->if_name)) 229 errx(EX_USAGE, "Invalid interface name"); 230 flags |= NPTV6_HAS_EXTPREFIX; 231 cfg->flags |= NPTV6_DYNAMIC_PREFIX; 232 strncpy(cfg->if_name, *av, sizeof(cfg->if_name)); 233 ac--; av++; 234 break; 235 case TOK_PREFIXLEN: 236 NEED1("IPv6 prefix length required"); 237 pplen = strtol(*av, &p, 10); 238 if (*p != '\0' || pplen < 8 || pplen > 64) 239 errx(EX_USAGE, "wrong prefix length: %s", *av); 240 ac--; av++; 241 break; 242 } 243 } 244 245 /* RFC 6296 Sec. 3.1 */ 246 if (pplen != 0) { 247 if ((eplen != 0 && eplen != pplen) || 248 (iplen != 0 && iplen != pplen)) 249 errx(EX_USAGE, "prefix length mismatch"); 250 cfg->plen = pplen; 251 flags |= NPTV6_HAS_PREFIXLEN; 252 } else if (eplen != 0 || iplen != 0) { 253 if (eplen != 0 && iplen != 0 && eplen != iplen) 254 errx(EX_USAGE, "prefix length mismatch"); 255 warnx("use prefixlen instead"); 256 cfg->plen = eplen ? eplen : iplen; 257 flags |= NPTV6_HAS_PREFIXLEN; 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 424 /* Start with reasonable default */ 425 sz = sizeof(*olh) + 16 * sizeof(*cfg); 426 for (;;) { 427 if ((olh = calloc(1, sz)) == NULL) 428 return (ENOMEM); 429 430 olh->size = sz; 431 if (do_get3(IP_FW_NPTV6_LIST, &olh->opheader, &sz) != 0) { 432 sz = olh->size; 433 free(olh); 434 if (errno != ENOMEM) 435 return (errno); 436 continue; 437 } 438 439 if (sort != 0) 440 qsort(olh + 1, olh->count, olh->objsize, nptv6name_cmp); 441 442 cfg = (ipfw_nptv6_cfg *)(olh + 1); 443 for (i = 0; i < olh->count; i++) { 444 (void)f(cfg, name, set); 445 cfg = (ipfw_nptv6_cfg *)((caddr_t)cfg + olh->objsize); 446 } 447 free(olh); 448 break; 449 } 450 return (0); 451 } 452 453