1 /*- 2 * Copyright (c) 2014-2015 Sandvine Inc. 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 */ 26 27 #include <sys/cdefs.h> 28 __FBSDID("$FreeBSD$"); 29 30 #include <sys/param.h> 31 #include <sys/iov.h> 32 #include <sys/nv.h> 33 #include <net/ethernet.h> 34 35 #include <err.h> 36 #include <errno.h> 37 #include <fcntl.h> 38 #include <regex.h> 39 #include <stdint.h> 40 #include <stdio.h> 41 #include <stdlib.h> 42 #include <string.h> 43 #include <ucl.h> 44 #include <unistd.h> 45 46 #include "iovctl.h" 47 48 static void 49 report_config_error(const char *key, const ucl_object_t *obj, const char *type) 50 { 51 52 errx(1, "Value '%s' of key '%s' is not of type %s", 53 ucl_object_tostring(obj), key, type); 54 } 55 56 /* 57 * Verifies that the value specified in the config file is a boolean value, and 58 * then adds the value to the configuration. 59 */ 60 static void 61 add_bool_config(const char *key, const ucl_object_t *obj, nvlist_t *config) 62 { 63 bool val; 64 65 if (!ucl_object_toboolean_safe(obj, &val)) 66 report_config_error(key, obj, "bool"); 67 68 nvlist_add_bool(config, key, val); 69 } 70 71 /* 72 * Verifies that the value specified in the config file is a string, and then 73 * adds the value to the configuration. 74 */ 75 static void 76 add_string_config(const char *key, const ucl_object_t *obj, nvlist_t *config) 77 { 78 const char *val; 79 80 if (!ucl_object_tostring_safe(obj, &val)) 81 report_config_error(key, obj, "string"); 82 83 nvlist_add_string(config, key, val); 84 } 85 86 /* 87 * Verifies that the value specified in the config file is a integer value 88 * within the specified range, and then adds the value to the configuration. 89 */ 90 static void 91 add_uint_config(const char *key, const ucl_object_t *obj, nvlist_t *config, 92 const char *type, uint64_t max) 93 { 94 int64_t val; 95 uint64_t uval; 96 97 /* I must use a signed type here as libucl doesn't provide unsigned. */ 98 if (!ucl_object_toint_safe(obj, &val)) 99 report_config_error(key, obj, type); 100 101 if (val < 0) 102 report_config_error(key, obj, type); 103 104 uval = val; 105 if (uval > max) 106 report_config_error(key, obj, type); 107 108 nvlist_add_number(config, key, uval); 109 } 110 111 /* 112 * Verifies that the value specified in the config file is a unicast MAC 113 * address, and then adds the value to the configuration. 114 */ 115 static void 116 add_unicast_mac_config(const char *key, const ucl_object_t *obj, nvlist_t *config) 117 { 118 uint8_t mac[ETHER_ADDR_LEN]; 119 const char *val, *token; 120 char *parse, *orig_parse, *tokpos, *endpos; 121 size_t len; 122 u_long value; 123 int i; 124 125 if (!ucl_object_tostring_safe(obj, &val)) 126 report_config_error(key, obj, "unicast-mac"); 127 128 parse = strdup(val); 129 orig_parse = parse; 130 131 i = 0; 132 while ((token = strtok_r(parse, ":", &tokpos)) != NULL) { 133 parse = NULL; 134 135 len = strlen(token); 136 if (len < 1 || len > 2) 137 report_config_error(key, obj, "unicast-mac"); 138 139 value = strtoul(token, &endpos, 16); 140 141 if (*endpos != '\0') 142 report_config_error(key, obj, "unicast-mac"); 143 144 if (value > UINT8_MAX) 145 report_config_error(key, obj, "unicast-mac"); 146 147 if (i >= ETHER_ADDR_LEN) 148 report_config_error(key, obj, "unicast-mac"); 149 150 mac[i] = value; 151 i++; 152 } 153 154 free(orig_parse); 155 156 if (i != ETHER_ADDR_LEN) 157 report_config_error(key, obj, "unicast-mac"); 158 159 if (ETHER_IS_MULTICAST(mac)) 160 errx(1, "Value '%s' of key '%s' is a multicast address", 161 ucl_object_tostring(obj), key); 162 163 nvlist_add_binary(config, key, mac, ETHER_ADDR_LEN); 164 } 165 166 /* 167 * Validates that the given configuation value has the right type as specified 168 * in the schema, and then adds the value to the configuation node. 169 */ 170 static void 171 add_config(const char *key, const ucl_object_t *obj, nvlist_t *config, 172 const nvlist_t *schema) 173 { 174 const char *type; 175 176 type = nvlist_get_string(schema, TYPE_SCHEMA_NAME); 177 178 if (strcasecmp(type, "bool") == 0) 179 add_bool_config(key, obj, config); 180 else if (strcasecmp(type, "string") == 0) 181 add_string_config(key, obj, config); 182 else if (strcasecmp(type, "uint8_t") == 0) 183 add_uint_config(key, obj, config, type, UINT8_MAX); 184 else if (strcasecmp(type, "uint16_t") == 0) 185 add_uint_config(key, obj, config, type, UINT16_MAX); 186 else if (strcasecmp(type, "uint32_t") == 0) 187 add_uint_config(key, obj, config, type, UINT32_MAX); 188 else if (strcasecmp(type, "uint64_t") == 0) 189 add_uint_config(key, obj, config, type, UINT64_MAX); 190 else if (strcasecmp(type, "unicast-mac") == 0) 191 add_unicast_mac_config(key, obj, config); 192 else 193 errx(1, "Unexpected type '%s' in schema", type); 194 } 195 196 /* 197 * Parses all values specified in a device section in the configuration file, 198 * validates that the key/value pair is valid in the schema, and then adds 199 * the key/value pair to the correct subsystem in the config. 200 */ 201 static void 202 parse_device_config(const ucl_object_t *top, nvlist_t *config, 203 const char *subsystem, const nvlist_t *schema) 204 { 205 ucl_object_iter_t it; 206 const ucl_object_t *obj; 207 nvlist_t *subsystem_config, *driver_config, *iov_config; 208 const nvlist_t *driver_schema, *iov_schema; 209 const char *key; 210 211 if (nvlist_exists(config, subsystem)) 212 errx(1, "Multiple definitions of '%s' in config file", 213 subsystem); 214 215 driver_schema = nvlist_get_nvlist(schema, DRIVER_CONFIG_NAME); 216 iov_schema = nvlist_get_nvlist(schema, IOV_CONFIG_NAME); 217 218 driver_config = nvlist_create(NV_FLAG_IGNORE_CASE); 219 if (driver_config == NULL) 220 err(1, "Could not allocate config nvlist"); 221 222 iov_config = nvlist_create(NV_FLAG_IGNORE_CASE); 223 if (iov_config == NULL) 224 err(1, "Could not allocate config nvlist"); 225 226 subsystem_config = nvlist_create(NV_FLAG_IGNORE_CASE); 227 if (subsystem_config == NULL) 228 err(1, "Could not allocate config nvlist"); 229 230 it = NULL; 231 while ((obj = ucl_iterate_object(top, &it, true)) != NULL) { 232 key = ucl_object_key(obj); 233 234 if (nvlist_exists_nvlist(iov_schema, key)) 235 add_config(key, obj, iov_config, 236 nvlist_get_nvlist(iov_schema, key)); 237 else if (nvlist_exists_nvlist(driver_schema, key)) 238 add_config(key, obj, driver_config, 239 nvlist_get_nvlist(driver_schema, key)); 240 else 241 errx(1, "%s: Invalid config key '%s'", subsystem, key); 242 } 243 244 nvlist_move_nvlist(subsystem_config, DRIVER_CONFIG_NAME, driver_config); 245 nvlist_move_nvlist(subsystem_config, IOV_CONFIG_NAME, iov_config); 246 nvlist_move_nvlist(config, subsystem, subsystem_config); 247 } 248 249 /* 250 * Parses the specified config file using the given schema, and returns an 251 * nvlist containing the configuration specified by the file. 252 * 253 * Exits with a message to stderr and an error if any config validation fails. 254 */ 255 nvlist_t * 256 parse_config_file(const char *filename, const nvlist_t *schema) 257 { 258 ucl_object_iter_t it; 259 struct ucl_parser *parser; 260 ucl_object_t *top; 261 const ucl_object_t *obj; 262 nvlist_t *config; 263 const nvlist_t *pf_schema, *vf_schema; 264 const char *errmsg, *key; 265 regex_t vf_pat; 266 int regex_err, processed_vf; 267 268 regex_err = regcomp(&vf_pat, "^"VF_PREFIX"([1-9][0-9]*|0)$", 269 REG_EXTENDED | REG_ICASE); 270 if (regex_err != 0) 271 errx(1, "Could not compile VF regex"); 272 273 parser = ucl_parser_new(0); 274 if (parser == NULL) 275 err(1, "Could not allocate parser"); 276 277 if (!ucl_parser_add_file(parser, filename)) 278 err(1, "Could not open '%s' for reading", filename); 279 280 errmsg = ucl_parser_get_error(parser); 281 if (errmsg != NULL) 282 errx(1, "Could not parse '%s': %s", filename, errmsg); 283 284 config = nvlist_create(NV_FLAG_IGNORE_CASE); 285 if (config == NULL) 286 err(1, "Could not allocate config nvlist"); 287 288 pf_schema = nvlist_get_nvlist(schema, PF_CONFIG_NAME); 289 vf_schema = nvlist_get_nvlist(schema, VF_SCHEMA_NAME); 290 291 processed_vf = 0; 292 top = ucl_parser_get_object(parser); 293 it = NULL; 294 while ((obj = ucl_iterate_object(top, &it, true)) != NULL) { 295 key = ucl_object_key(obj); 296 297 if (strcasecmp(key, PF_CONFIG_NAME) == 0) 298 parse_device_config(obj, config, key, pf_schema); 299 else if (strcasecmp(key, DEFAULT_SCHEMA_NAME) == 0) { 300 /* 301 * Enforce that the default section must come before all 302 * VF sections. This will hopefully prevent confusing 303 * the user by having a default value apply to a VF 304 * that was declared earlier in the file. 305 * 306 * This also gives us the flexibility to extend the file 307 * format in the future to allow for multiple default 308 * sections that do only apply to subsequent VF 309 * sections. 310 */ 311 if (processed_vf) 312 errx(1, 313 "'default' section must precede all VF sections"); 314 315 parse_device_config(obj, config, key, vf_schema); 316 } else if (regexec(&vf_pat, key, 0, NULL, 0) == 0) { 317 processed_vf = 1; 318 parse_device_config(obj, config, key, vf_schema); 319 } else 320 errx(1, "Unexpected top-level node: %s", key); 321 } 322 323 validate_config(config, schema, &vf_pat); 324 325 ucl_object_unref(top); 326 ucl_parser_free(parser); 327 regfree(&vf_pat); 328 329 return (config); 330 } 331 332 /* 333 * Parse the PF configuration section for and return the value specified for 334 * the device parameter, or NULL if the device is not specified. 335 */ 336 static const char * 337 find_pf_device(const ucl_object_t *pf) 338 { 339 ucl_object_iter_t it; 340 const ucl_object_t *obj; 341 const char *key, *device; 342 343 it = NULL; 344 while ((obj = ucl_iterate_object(pf, &it, true)) != NULL) { 345 key = ucl_object_key(obj); 346 347 if (strcasecmp(key, "device") == 0) { 348 if (!ucl_object_tostring_safe(obj, &device)) 349 err(1, 350 "Config PF.device must be a string"); 351 352 return (device); 353 } 354 } 355 356 return (NULL); 357 } 358 359 /* 360 * Manually parse the config file looking for the name of the PF device. We 361 * have to do this separately because we need the config schema to call the 362 * normal config file parsing code, and we need to know the name of the PF 363 * device so that we can fetch the schema from it. 364 * 365 * This will always exit on failure, so if it returns then it is guaranteed to 366 * have returned a valid device name. 367 */ 368 char * 369 find_device(const char *filename) 370 { 371 char *device; 372 const char *deviceName; 373 ucl_object_iter_t it; 374 struct ucl_parser *parser; 375 ucl_object_t *top; 376 const ucl_object_t *obj; 377 const char *errmsg, *key; 378 int error; 379 380 device = NULL; 381 deviceName = NULL; 382 383 parser = ucl_parser_new(0); 384 if (parser == NULL) 385 err(1, "Could not allocate parser"); 386 387 if (!ucl_parser_add_file(parser, filename)) 388 err(1, "Could not open '%s' for reading", filename); 389 390 errmsg = ucl_parser_get_error(parser); 391 if (errmsg != NULL) 392 errx(1, "Could not parse '%s': %s", filename, errmsg); 393 394 top = ucl_parser_get_object (parser); 395 it = NULL; 396 while ((obj = ucl_iterate_object(top, &it, true)) != NULL) { 397 key = ucl_object_key(obj); 398 399 if (strcasecmp(key, PF_CONFIG_NAME) == 0) { 400 deviceName = find_pf_device(obj); 401 break; 402 } 403 } 404 405 if (deviceName == NULL) 406 errx(1, "Config file does not specify device"); 407 408 error = asprintf(&device, "/dev/iov/%s", deviceName); 409 if (error < 0) 410 err(1, "Could not allocate memory for device"); 411 412 ucl_object_unref(top); 413 ucl_parser_free(parser); 414 415 return (device); 416 } 417