1 /** 2 * SPDX-License-Identifier: BSD-2-Clause-Patent 3 * 4 * SPDX-FileCopyrightText: Copyright (c) 2024 SoftAtHome 5 * 6 * Redistribution and use in source and binary forms, with or 7 * without modification, are permitted provided that the following 8 * conditions 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 * 13 * 2. Redistributions in binary form must reproduce the above 14 * copyright notice, this list of conditions and the following 15 * disclaimer in the documentation and/or other materials provided 16 * with the distribution. 17 * 18 * Subject to the terms and conditions of this license, each 19 * copyright holder and contributor hereby grants to those receiving 20 * rights under this license a perpetual, worldwide, non-exclusive, 21 * no-charge, royalty-free, irrevocable (except for failure to 22 * satisfy the conditions of this license) patent license to make, 23 * have made, use, offer to sell, sell, import, and otherwise 24 * transfer this software, where such license applies only to those 25 * patent claims, already acquired or hereafter acquired, licensable 26 * by such copyright holder or contributor that are necessarily 27 * infringed by: 28 * 29 * (a) their Contribution(s) (the licensed copyrights of copyright 30 * holders and non-copyrightable additions of contributors, in 31 * source or binary form) alone; or 32 * 33 * (b) combination of their Contribution(s) with the work of 34 * authorship to which such Contribution(s) was added by such 35 * copyright holder or contributor, if, at the time the Contribution 36 * is added, such addition causes such combination to be necessarily 37 * infringed. The patent license shall not apply to any other 38 * combinations which include the Contribution. 39 * 40 * Except as expressly stated above, no rights or licenses from any 41 * copyright holder or contributor is granted under this license, 42 * whether expressly, by implication, estoppel or otherwise. 43 * 44 * DISCLAIMER 45 * 46 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 47 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 48 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 49 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 50 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR 51 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 52 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 53 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 54 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 55 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 56 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 57 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 58 * POSSIBILITY OF SUCH DAMAGE. 59 * 60 */ 61 62 #include <arpa/inet.h> 63 #include <inttypes.h> 64 #include <libubox/blobmsg.h> 65 #include <resolv.h> 66 #include <stdio.h> 67 #include <sys/types.h> 68 69 #include "config.h" 70 #include "odhcp6c.h" 71 #include "ubus.h" 72 73 #define CHECK(stmt) \ 74 do { \ 75 int ret = (stmt); \ 76 if (ret != UBUS_STATUS_OK) \ 77 { \ 78 error("%s failed: %s (%d)", #stmt, ubus_strerror(ret), ret); \ 79 return ret; \ 80 } \ 81 } while (0) 82 83 #define CHECK_ALLOC(buf) \ 84 do { \ 85 if (buf == NULL) \ 86 { \ 87 return UBUS_STATUS_NO_MEMORY; \ 88 } \ 89 } while (0) 90 91 enum entry_type { 92 ENTRY_ADDRESS, 93 ENTRY_HOST, 94 ENTRY_ROUTE, 95 ENTRY_PREFIX 96 }; 97 98 enum { 99 RECONFIGURE_DHCP_ATTR_DSCP, 100 RECONFIGURE_DHCP_ATTR_RELEASE, 101 RECONFIGURE_DHCP_ATTR_SOL_TIMEOUT, 102 RECONFIGURE_DHCP_ATTR_SK_PRIORITY, 103 RECONFIGURE_DHCP_ATTR_OPT_REQUESTED, 104 RECONFIGURE_DHCP_ATTR_OPT_STRICT, 105 RECONFIGURE_DHCP_ATTR_OPT_RECONFIGURE, 106 RECONFIGURE_DHCP_ATTR_OPT_FQDN, 107 RECONFIGURE_DHCP_ATTR_OPT_UNICAST, 108 RECONFIGURE_DHCP_ATTR_OPT_SEND, 109 RECONFIGURE_DHCP_ATTR_REQ_ADDRESSES, 110 RECONFIGURE_DHCP_ATTR_REQ_PREFIXES, 111 RECONFIGURE_DHCP_ATTR_STATEFUL, 112 RECONFIGURE_DHCP_ATTR_MSG_SOLICIT, 113 RECONFIGURE_DHCP_ATTR_MSG_REQUEST, 114 RECONFIGURE_DHCP_ATTR_MSG_RENEW, 115 RECONFIGURE_DHCP_ATTR_MSG_REBIND, 116 RECONFIGURE_DHCP_ATTR_MSG_RELEASE, 117 RECONFIGURE_DHCP_ATTR_MSG_DECLINE, 118 RECONFIGURE_DHCP_ATTR_MSG_INFO_REQ, 119 RECONFIGURE_DHCP_ATTR_IRT_DEFAULT, 120 RECONFIGURE_DHCP_ATTR_IRT_MIN, 121 RECONFIGURE_DHCP_ATTR_RAND_FACTOR, 122 RECONFIGURE_DHCP_ATTR_AUTH_PROTO, 123 RECONFIGURE_DHCP_ATTR_AUTH_TOKEN, 124 RECONFIGURE_DHCP_ATTR_MAX, 125 }; 126 127 struct ubus_context *ubus = NULL; 128 static struct blob_buf b; 129 static char ubus_name[24]; 130 131 static int ubus_handle_get_state(struct ubus_context *ctx, struct ubus_object *obj, 132 struct ubus_request_data *req, const char *method, struct blob_attr *msg); 133 static int ubus_handle_get_stats(struct ubus_context *ctx, struct ubus_object *obj, 134 struct ubus_request_data *req, const char *method, struct blob_attr *msg); 135 static int ubus_handle_reset_stats(struct ubus_context *ctx, struct ubus_object *obj, 136 struct ubus_request_data *req, const char *method, struct blob_attr *msg); 137 static int ubus_handle_reconfigure_dhcp(struct ubus_context *ctx, struct ubus_object *obj, 138 struct ubus_request_data *req, const char *method, struct blob_attr *msg); 139 static int ubus_handle_renew(struct ubus_context *ctx, struct ubus_object *obj, 140 struct ubus_request_data *req, const char *method, struct blob_attr *msg); 141 static int ubus_handle_release(struct ubus_context *ctx, struct ubus_object *obj, 142 struct ubus_request_data *req, const char *method, struct blob_attr *msg); 143 144 static const struct blobmsg_policy reconfigure_dhcp_policy[RECONFIGURE_DHCP_ATTR_MAX] = { 145 [RECONFIGURE_DHCP_ATTR_DSCP] = { .name = "dscp", .type = BLOBMSG_TYPE_INT32}, 146 [RECONFIGURE_DHCP_ATTR_RELEASE] = { .name = "release", .type = BLOBMSG_TYPE_BOOL}, 147 [RECONFIGURE_DHCP_ATTR_SOL_TIMEOUT] = { .name = "sol_timeout", .type = BLOBMSG_TYPE_INT32}, 148 [RECONFIGURE_DHCP_ATTR_SK_PRIORITY] = { .name = "sk_prio", .type = BLOBMSG_TYPE_INT32}, 149 [RECONFIGURE_DHCP_ATTR_OPT_REQUESTED] = { .name = "opt_requested", .type = BLOBMSG_TYPE_ARRAY}, 150 [RECONFIGURE_DHCP_ATTR_OPT_STRICT] = { .name = "opt_strict", .type = BLOBMSG_TYPE_BOOL}, 151 [RECONFIGURE_DHCP_ATTR_OPT_RECONFIGURE] = { .name = "opt_reconfigure", .type = BLOBMSG_TYPE_BOOL}, 152 [RECONFIGURE_DHCP_ATTR_OPT_FQDN] = { .name = "opt_fqdn", .type = BLOBMSG_TYPE_BOOL}, 153 [RECONFIGURE_DHCP_ATTR_OPT_UNICAST] = { .name = "opt_unicast", .type = BLOBMSG_TYPE_BOOL}, 154 [RECONFIGURE_DHCP_ATTR_OPT_SEND] = { .name = "opt_send", .type = BLOBMSG_TYPE_ARRAY}, 155 [RECONFIGURE_DHCP_ATTR_REQ_ADDRESSES] = { .name = "req_addresses", .type = BLOBMSG_TYPE_STRING}, 156 [RECONFIGURE_DHCP_ATTR_REQ_PREFIXES] = { .name = "req_prefixes", .type = BLOBMSG_TYPE_INT32}, 157 [RECONFIGURE_DHCP_ATTR_STATEFUL] = { .name = "stateful_only", .type = BLOBMSG_TYPE_BOOL}, 158 [RECONFIGURE_DHCP_ATTR_MSG_SOLICIT] = { .name = "msg_solicit", .type = BLOBMSG_TYPE_TABLE}, 159 [RECONFIGURE_DHCP_ATTR_MSG_REQUEST] = { .name = "msg_request", .type = BLOBMSG_TYPE_TABLE}, 160 [RECONFIGURE_DHCP_ATTR_MSG_RENEW] = { .name = "msg_renew", .type = BLOBMSG_TYPE_TABLE}, 161 [RECONFIGURE_DHCP_ATTR_MSG_REBIND] = { .name = "msg_rebind", .type = BLOBMSG_TYPE_TABLE}, 162 [RECONFIGURE_DHCP_ATTR_MSG_RELEASE] = { .name = "msg_release", .type = BLOBMSG_TYPE_TABLE}, 163 [RECONFIGURE_DHCP_ATTR_MSG_DECLINE] = { .name = "msg_decline", .type = BLOBMSG_TYPE_TABLE}, 164 [RECONFIGURE_DHCP_ATTR_MSG_INFO_REQ] = { .name = "msg_inforeq", .type = BLOBMSG_TYPE_TABLE}, 165 [RECONFIGURE_DHCP_ATTR_IRT_DEFAULT] = { .name = "irt_default", .type = BLOBMSG_TYPE_INT32}, 166 [RECONFIGURE_DHCP_ATTR_IRT_MIN] = { .name = "irt_min", .type = BLOBMSG_TYPE_INT32}, 167 [RECONFIGURE_DHCP_ATTR_RAND_FACTOR] = { .name = "rand_factor", .type = BLOBMSG_TYPE_INT32}, 168 [RECONFIGURE_DHCP_ATTR_AUTH_PROTO] = { .name = "auth_protocol", .type = BLOBMSG_TYPE_STRING}, 169 [RECONFIGURE_DHCP_ATTR_AUTH_TOKEN] = { .name = "auth_token", .type = BLOBMSG_TYPE_STRING}, 170 }; 171 172 static struct ubus_method odhcp6c_object_methods[] = { 173 UBUS_METHOD_NOARG("get_state", ubus_handle_get_state), 174 UBUS_METHOD_NOARG("get_statistics", ubus_handle_get_stats), 175 UBUS_METHOD_NOARG("reset_statistics", ubus_handle_reset_stats), 176 UBUS_METHOD("reconfigure_dhcp", ubus_handle_reconfigure_dhcp, reconfigure_dhcp_policy), 177 UBUS_METHOD_NOARG("renew", ubus_handle_renew), 178 UBUS_METHOD_NOARG("release", ubus_handle_release), 179 }; 180 181 static struct ubus_object_type odhcp6c_object_type = 182 UBUS_OBJECT_TYPE("odhcp6c", odhcp6c_object_methods); 183 184 static struct ubus_object odhcp6c_object = { 185 .name = NULL, 186 .type = &odhcp6c_object_type, 187 .methods = odhcp6c_object_methods, 188 .n_methods = ARRAY_SIZE(odhcp6c_object_methods), 189 }; 190 191 static void ubus_disconnect_cb(struct ubus_context *ubus) 192 { 193 int ret; 194 195 ret = ubus_reconnect(ubus, NULL); 196 if (ret) { 197 error("Cannot reconnect to ubus: %s", ubus_strerror(ret)); 198 ubus_destroy(ubus); 199 } 200 } 201 202 char *ubus_init(const char* interface) 203 { 204 int ret = 0; 205 206 if (!(ubus = ubus_connect(NULL))) 207 return NULL; 208 209 snprintf(ubus_name, 24, "odhcp6c.%s", interface); 210 odhcp6c_object.name = ubus_name; 211 212 ret = ubus_add_object(ubus, &odhcp6c_object); 213 if (ret) { 214 ubus_destroy(ubus); 215 return (char *)ubus_strerror(ret); 216 } 217 218 ubus->connection_lost = ubus_disconnect_cb; 219 return NULL; 220 } 221 222 struct ubus_context *ubus_get_ctx(void) 223 { 224 return ubus; 225 } 226 227 void ubus_destroy(struct ubus_context *ubus) 228 { 229 notice("Disconnecting from ubus"); 230 231 if (ubus != NULL) 232 ubus_free(ubus); 233 ubus = NULL; 234 235 /* Forces re-initialization when we're reusing the same definitions later on. */ 236 odhcp6c_object.id = 0; 237 odhcp6c_object.id = 0; 238 } 239 240 static int ipv6_to_blob(const char *name, 241 const struct in6_addr *addr, size_t cnt) 242 { 243 void *arr = blobmsg_open_array(&b, name); 244 245 for (size_t i = 0; i < cnt; ++i) { 246 char *buf = blobmsg_alloc_string_buffer(&b, NULL, INET6_ADDRSTRLEN); 247 CHECK_ALLOC(buf); 248 inet_ntop(AF_INET6, &addr[i], buf, INET6_ADDRSTRLEN); 249 blobmsg_add_string_buffer(&b); 250 } 251 252 blobmsg_close_array(&b, arr); 253 return UBUS_STATUS_OK; 254 } 255 256 static int fqdn_to_blob(const char *name, const uint8_t *fqdn, size_t len) 257 { 258 size_t buf_size = len > 255 ? 256 : (len + 1); 259 const uint8_t *fqdn_end = fqdn + len; 260 char *buf = NULL; 261 262 void *arr = blobmsg_open_array(&b, name); 263 264 while (fqdn < fqdn_end) { 265 buf = blobmsg_alloc_string_buffer(&b, name, buf_size); 266 CHECK_ALLOC(buf); 267 int l = dn_expand(fqdn, fqdn_end, fqdn, buf, buf_size); 268 if (l < 1) { 269 buf[0] = '\0'; 270 blobmsg_add_string_buffer(&b); 271 break; 272 } 273 buf[l] = '\0'; 274 blobmsg_add_string_buffer(&b); 275 fqdn += l; 276 } 277 278 blobmsg_close_array(&b, arr); 279 return UBUS_STATUS_OK; 280 } 281 282 static int bin_to_blob(uint8_t *opts, size_t len) 283 { 284 uint8_t *oend = opts + len, *odata; 285 uint16_t otype, olen; 286 287 dhcpv6_for_each_option(opts, oend, otype, olen, odata) { 288 char name[14]; 289 char *buf; 290 291 snprintf(name, 14, "OPTION_%hu", otype); 292 buf = blobmsg_alloc_string_buffer(&b, name, olen * 2); 293 CHECK_ALLOC(buf); 294 script_hexlify(buf, odata, olen); 295 blobmsg_add_string_buffer(&b); 296 } 297 return UBUS_STATUS_OK; 298 } 299 300 static int entry_to_blob(const char *name, const void *data, size_t len, enum entry_type type) 301 { 302 const struct odhcp6c_entry *e = data; 303 304 void *arr = blobmsg_open_array(&b, name); 305 306 for (size_t i = 0; i < len / sizeof(*e); ++i) { 307 void *entry = blobmsg_open_table(&b, name); 308 309 /* 310 * The only invalid entries allowed to be passed to the script are prefix entries. 311 * This will allow immediate removal of the old ipv6-prefix-assignment that might 312 * otherwise be kept for up to 2 hours (see L-13 requirement of RFC 7084). 313 */ 314 if (!e[i].valid && type != ENTRY_PREFIX) 315 continue; 316 317 char *buf = blobmsg_alloc_string_buffer(&b, "target", INET6_ADDRSTRLEN); 318 CHECK_ALLOC(buf); 319 inet_ntop(AF_INET6, &e[i].target, buf, INET6_ADDRSTRLEN); 320 blobmsg_add_string_buffer(&b); 321 322 if (type != ENTRY_HOST) { 323 blobmsg_add_u32(&b, "length", e[i].length); 324 if (type == ENTRY_ROUTE) { 325 if (!IN6_IS_ADDR_UNSPECIFIED(&e[i].router)) { 326 buf = blobmsg_alloc_string_buffer(&b, "router", INET6_ADDRSTRLEN); 327 CHECK_ALLOC(buf); 328 inet_ntop(AF_INET6, &e[i].router, buf, INET6_ADDRSTRLEN); 329 blobmsg_add_string_buffer(&b); 330 } 331 332 blobmsg_add_u32(&b, "valid", e[i].valid); 333 blobmsg_add_u32(&b, "priority", e[i].priority); 334 } else { 335 blobmsg_add_u32(&b, "valid", e[i].valid); 336 blobmsg_add_u32(&b, "preferred", e[i].preferred); 337 blobmsg_add_u32(&b, "t1", e[i].t1); 338 blobmsg_add_u32(&b, "t2", e[i].t2); 339 } 340 341 if (type == ENTRY_PREFIX && ntohl(e[i].iaid) != 1) { 342 blobmsg_add_u32(&b, "iaid", ntohl(e[i].iaid)); 343 } 344 345 if (type == ENTRY_PREFIX && e[i].exclusion_length) { 346 buf = blobmsg_alloc_string_buffer(&b, "excluded", INET6_ADDRSTRLEN); 347 CHECK_ALLOC(buf); 348 // '.router' is dual-used: for prefixes it contains the prefix 349 inet_ntop(AF_INET6, &e[i].router, buf, INET6_ADDRSTRLEN); 350 blobmsg_add_string_buffer(&b); 351 blobmsg_add_u32(&b, "excluded_length", e[i].exclusion_length); 352 } 353 } 354 355 blobmsg_close_table(&b, entry); 356 } 357 358 blobmsg_close_array(&b, arr); 359 return UBUS_STATUS_OK; 360 } 361 362 static int search_to_blob(const char *name, const uint8_t *start, size_t len) 363 { 364 void *arr = blobmsg_open_array(&b, name); 365 char *buf = NULL; 366 367 for (struct odhcp6c_entry *e = (struct odhcp6c_entry*)start; 368 (uint8_t*)e < &start[len] && 369 (uint8_t*)odhcp6c_next_entry(e) <= &start[len]; 370 e = odhcp6c_next_entry(e)) { 371 if (!e->valid) 372 continue; 373 374 buf = blobmsg_alloc_string_buffer(&b, NULL, e->auxlen+1); 375 CHECK_ALLOC(buf); 376 buf = mempcpy(buf, e->auxtarget, e->auxlen); 377 *buf = '\0'; 378 blobmsg_add_string_buffer(&b); 379 } 380 381 blobmsg_close_array(&b, arr); 382 return UBUS_STATUS_OK; 383 } 384 385 static int s46_to_blob_portparams(const uint8_t *data, size_t len) 386 { 387 uint8_t *odata; 388 uint16_t otype, olen; 389 390 dhcpv6_for_each_option(data, &data[len], otype, olen, odata) { 391 if (otype == DHCPV6_OPT_S46_PORTPARAMS && 392 olen == sizeof(struct dhcpv6_s46_portparams)) { 393 struct dhcpv6_s46_portparams *params = (void*)odata; 394 blobmsg_add_u32(&b, "offset", params->offset); 395 blobmsg_add_u32(&b, "psidlen", params->psid_len); 396 blobmsg_add_u32(&b, "psid", ntohs(params->psid)); 397 } 398 } 399 return UBUS_STATUS_OK; 400 } 401 402 static int s46_to_blob(enum odhcp6c_state state, const uint8_t *data, size_t len) 403 { 404 const char *name = (state == STATE_S46_MAPE) ? "MAPE" : 405 (state == STATE_S46_MAPT) ? "MAPT" : "LW4O6"; 406 407 if (len == 0) 408 return UBUS_STATUS_OK; 409 410 char *buf = NULL; 411 void *arr = blobmsg_open_array(&b, name); 412 413 const char *type = (state == STATE_S46_MAPE) ? "map-e" : 414 (state == STATE_S46_MAPT) ? "map-t" : "lw4o6"; 415 416 uint8_t *odata; 417 uint16_t otype, olen; 418 419 dhcpv6_for_each_option(data, &data[len], otype, olen, odata) { 420 struct dhcpv6_s46_rule *rule = (struct dhcpv6_s46_rule*)odata; 421 struct dhcpv6_s46_v4v6bind *bind = (struct dhcpv6_s46_v4v6bind*)odata; 422 423 void *option = blobmsg_open_table(&b, NULL); 424 425 if (state != STATE_S46_LW && otype == DHCPV6_OPT_S46_RULE && 426 olen >= sizeof(struct dhcpv6_s46_rule)) { 427 struct in6_addr in6 = IN6ADDR_ANY_INIT; 428 429 size_t prefix6len = rule->prefix6_len; 430 prefix6len = (prefix6len % 8 == 0) ? prefix6len / 8 : prefix6len / 8 + 1; 431 432 if (prefix6len > sizeof(in6) || 433 olen < sizeof(struct dhcpv6_s46_rule) + prefix6len) 434 continue; 435 436 memcpy(&in6, rule->ipv6_prefix, prefix6len); 437 438 buf = blobmsg_alloc_string_buffer(&b, "ipv4prefix", INET_ADDRSTRLEN); 439 CHECK_ALLOC(buf); 440 inet_ntop(AF_INET, &rule->ipv4_prefix, buf, INET_ADDRSTRLEN); 441 blobmsg_add_string_buffer(&b); 442 443 buf = blobmsg_alloc_string_buffer(&b, "ipv6prefix", INET6_ADDRSTRLEN); 444 CHECK_ALLOC(buf); 445 inet_ntop(AF_INET6, &in6, buf, INET6_ADDRSTRLEN); 446 blobmsg_add_string_buffer(&b); 447 448 blobmsg_add_u32(&b, "fmr", rule->flags); 449 blobmsg_add_string(&b, "type", type); 450 blobmsg_add_u32(&b, "ealen", rule->ea_len); 451 blobmsg_add_u32(&b, "prefix4len", rule->prefix4_len); 452 blobmsg_add_u32(&b, "prefix6len", rule->prefix6_len); 453 454 s46_to_blob_portparams(&rule->ipv6_prefix[prefix6len], 455 olen - sizeof(*rule) - prefix6len); 456 457 dhcpv6_for_each_option(data, &data[len], otype, olen, odata) { 458 if (state != STATE_S46_MAPT && otype == DHCPV6_OPT_S46_BR && 459 olen == sizeof(struct in6_addr)) { 460 buf = blobmsg_alloc_string_buffer(&b, "br", INET6_ADDRSTRLEN); 461 CHECK_ALLOC(buf); 462 inet_ntop(AF_INET6, odata, buf, INET6_ADDRSTRLEN); 463 blobmsg_add_string_buffer(&b); 464 } else if (state == STATE_S46_MAPT && otype == DHCPV6_OPT_S46_DMR && 465 olen >= sizeof(struct dhcpv6_s46_dmr)) { 466 struct dhcpv6_s46_dmr *dmr = (struct dhcpv6_s46_dmr*)odata; 467 memset(&in6, 0, sizeof(in6)); 468 size_t prefix6len = dmr->dmr_prefix6_len; 469 prefix6len = (prefix6len % 8 == 0) ? prefix6len / 8 : prefix6len / 8 + 1; 470 471 if (prefix6len > sizeof(in6) || 472 olen < sizeof(struct dhcpv6_s46_dmr) + prefix6len) 473 continue; 474 475 buf = blobmsg_alloc_string_buffer(&b, "dmr", INET6_ADDRSTRLEN); 476 CHECK_ALLOC(buf); 477 inet_ntop(AF_INET6, &in6, buf, INET6_ADDRSTRLEN); 478 blobmsg_add_string_buffer(&b); 479 blobmsg_add_u32(&b, "dmrprefix6len", dmr->dmr_prefix6_len); 480 } 481 } 482 } else if (state == STATE_S46_LW && otype == DHCPV6_OPT_S46_V4V6BIND && 483 olen >= sizeof(struct dhcpv6_s46_v4v6bind)) { 484 struct in6_addr in6 = IN6ADDR_ANY_INIT; 485 486 size_t prefix6len = bind->bindprefix6_len; 487 prefix6len = (prefix6len % 8 == 0) ? prefix6len / 8 : prefix6len / 8 + 1; 488 489 if (prefix6len > sizeof(in6) || 490 olen < sizeof(struct dhcpv6_s46_v4v6bind) + prefix6len) 491 continue; 492 493 memcpy(&in6, bind->bind_ipv6_prefix, prefix6len); 494 495 buf = blobmsg_alloc_string_buffer(&b, "ipv4prefix", INET_ADDRSTRLEN); 496 CHECK_ALLOC(buf); 497 inet_ntop(AF_INET, &bind->ipv4_address, buf, INET_ADDRSTRLEN); 498 blobmsg_add_string_buffer(&b); 499 500 buf = blobmsg_alloc_string_buffer(&b, "ipv6prefix", INET6_ADDRSTRLEN); 501 CHECK_ALLOC(buf); 502 inet_ntop(AF_INET6, &in6, buf, INET6_ADDRSTRLEN); 503 blobmsg_add_string_buffer(&b); 504 505 blobmsg_add_string(&b, "type", type); 506 blobmsg_add_u32(&b, "prefix4len", 32); 507 blobmsg_add_u32(&b, "prefix6len", bind->bindprefix6_len); 508 509 s46_to_blob_portparams(&bind->bind_ipv6_prefix[prefix6len], 510 olen - sizeof(*bind) - prefix6len); 511 512 dhcpv6_for_each_option(data, &data[len], otype, olen, odata) { 513 if (otype == DHCPV6_OPT_S46_BR && olen == sizeof(struct in6_addr)) { 514 buf = blobmsg_alloc_string_buffer(&b, "br", INET6_ADDRSTRLEN); 515 CHECK_ALLOC(buf); 516 inet_ntop(AF_INET6, odata, buf, INET6_ADDRSTRLEN); 517 blobmsg_add_string_buffer(&b); 518 } 519 } 520 } 521 blobmsg_close_table(&b, option); 522 } 523 524 blobmsg_close_array(&b, arr); 525 return UBUS_STATUS_OK; 526 } 527 528 static int states_to_blob(void) 529 { 530 char *buf = NULL; 531 size_t dns_len, search_len, custom_len, sntp_ip_len, ntp_ip_len, ntp_dns_len; 532 size_t sip_ip_len, sip_fqdn_len, aftr_name_len, addr_len; 533 size_t s46_mapt_len, s46_mape_len, s46_lw_len, passthru_len; 534 size_t capt_port_ra_len, capt_port_dhcpv6_len; 535 struct in6_addr *addr = odhcp6c_get_state(STATE_SERVER_ADDR, &addr_len); 536 struct in6_addr *dns = odhcp6c_get_state(STATE_DNS, &dns_len); 537 uint8_t *search = odhcp6c_get_state(STATE_SEARCH, &search_len); 538 uint8_t *custom = odhcp6c_get_state(STATE_CUSTOM_OPTS, &custom_len); 539 struct in6_addr *sntp = odhcp6c_get_state(STATE_SNTP_IP, &sntp_ip_len); 540 struct in6_addr *ntp = odhcp6c_get_state(STATE_NTP_IP, &ntp_ip_len); 541 uint8_t *ntp_dns = odhcp6c_get_state(STATE_NTP_FQDN, &ntp_dns_len); 542 struct in6_addr *sip = odhcp6c_get_state(STATE_SIP_IP, &sip_ip_len); 543 uint8_t *sip_fqdn = odhcp6c_get_state(STATE_SIP_FQDN, &sip_fqdn_len); 544 uint8_t *aftr_name = odhcp6c_get_state(STATE_AFTR_NAME, &aftr_name_len); 545 uint8_t *s46_mapt = odhcp6c_get_state(STATE_S46_MAPT, &s46_mapt_len); 546 uint8_t *s46_mape = odhcp6c_get_state(STATE_S46_MAPE, &s46_mape_len); 547 uint8_t *s46_lw = odhcp6c_get_state(STATE_S46_LW, &s46_lw_len); 548 uint8_t *capt_port_ra = odhcp6c_get_state(STATE_CAPT_PORT_RA, &capt_port_ra_len); 549 uint8_t *capt_port_dhcpv6 = odhcp6c_get_state(STATE_CAPT_PORT_DHCPV6, &capt_port_dhcpv6_len); 550 uint8_t *passthru = odhcp6c_get_state(STATE_PASSTHRU, &passthru_len); 551 552 size_t prefix_len, address_len, ra_pref_len, 553 ra_route_len, ra_dns_len, ra_search_len; 554 uint8_t *prefix = odhcp6c_get_state(STATE_IA_PD, &prefix_len); 555 uint8_t *address = odhcp6c_get_state(STATE_IA_NA, &address_len); 556 uint8_t *ra_pref = odhcp6c_get_state(STATE_RA_PREFIX, &ra_pref_len); 557 uint8_t *ra_route = odhcp6c_get_state(STATE_RA_ROUTE, &ra_route_len); 558 uint8_t *ra_dns = odhcp6c_get_state(STATE_RA_DNS, &ra_dns_len); 559 uint8_t *ra_search = odhcp6c_get_state(STATE_RA_SEARCH, &ra_search_len); 560 561 blob_buf_init(&b, BLOBMSG_TYPE_TABLE); 562 563 /* RFC8910 ยง3 */ 564 if (capt_port_ra_len > 0 && capt_port_dhcpv6_len > 0) { 565 if (capt_port_ra_len != capt_port_dhcpv6_len || 566 !memcmp(capt_port_dhcpv6, capt_port_ra, capt_port_dhcpv6_len)) 567 error( 568 "%s received via different vectors differ: preferring URI from DHCPv6", 569 CAPT_PORT_URI_STR); 570 } 571 572 blobmsg_add_string(&b, "DHCPV6_STATE", dhcpv6_state_to_str(dhcpv6_get_state())); 573 574 CHECK(ipv6_to_blob("SERVER", addr, addr_len / sizeof(*addr))); 575 CHECK(ipv6_to_blob("RDNSS", dns, dns_len / sizeof(*dns))); 576 CHECK(ipv6_to_blob("SNTP_IP", sntp, sntp_ip_len / sizeof(*sntp))); 577 CHECK(ipv6_to_blob("NTP_IP", ntp, ntp_ip_len / sizeof(*ntp))); 578 CHECK(fqdn_to_blob("NTP_FQDN", ntp_dns, ntp_dns_len)); 579 CHECK(ipv6_to_blob("SIP_IP", sip, sip_ip_len / sizeof(*sip))); 580 CHECK(fqdn_to_blob("DOMAINS", search, search_len)); 581 CHECK(fqdn_to_blob("SIP_DOMAIN", sip_fqdn, sip_fqdn_len)); 582 CHECK(fqdn_to_blob("AFTR", aftr_name, aftr_name_len)); 583 CHECK(s46_to_blob(STATE_S46_MAPE, s46_mape, s46_mape_len)); 584 CHECK(s46_to_blob(STATE_S46_MAPT, s46_mapt, s46_mapt_len)); 585 CHECK(s46_to_blob(STATE_S46_LW, s46_lw, s46_lw_len)); 586 if (capt_port_dhcpv6_len > 0) 587 blobmsg_add_string(&b, CAPT_PORT_URI_STR, (char *)capt_port_dhcpv6); 588 else if (capt_port_ra_len > 0) 589 blobmsg_add_string(&b, CAPT_PORT_URI_STR, (char *)capt_port_ra); 590 CHECK(bin_to_blob(custom, custom_len)); 591 592 if (odhcp6c_is_bound()) { 593 CHECK(entry_to_blob("PREFIXES", prefix, prefix_len, ENTRY_PREFIX)); 594 CHECK(entry_to_blob("ADDRESSES", address, address_len, ENTRY_ADDRESS)); 595 } 596 597 CHECK(entry_to_blob("RA_ADDRESSES", ra_pref, ra_pref_len, ENTRY_ADDRESS)); 598 CHECK(entry_to_blob("RA_ROUTES", ra_route, ra_route_len, ENTRY_ROUTE)); 599 CHECK(entry_to_blob("RA_DNS", ra_dns, ra_dns_len, ENTRY_HOST)); 600 CHECK(search_to_blob("RA_DOMAINS", ra_search, ra_search_len)); 601 602 blobmsg_add_u32(&b, "RA_HOPLIMIT", ra_get_hoplimit()); 603 blobmsg_add_u32(&b, "RA_MTU", ra_get_mtu()); 604 blobmsg_add_u32(&b, "RA_REACHABLE", ra_get_reachable()); 605 blobmsg_add_u32(&b, "RA_RETRANSMIT", ra_get_retransmit()); 606 607 buf = blobmsg_alloc_string_buffer(&b, "PASSTHRU", passthru_len * 2); 608 CHECK_ALLOC(buf); 609 script_hexlify(buf, passthru, passthru_len); 610 blobmsg_add_string_buffer(&b); 611 612 return UBUS_STATUS_OK; 613 } 614 615 static int ubus_handle_get_stats(struct ubus_context *ctx, _o_unused struct ubus_object *obj, 616 struct ubus_request_data *req, _o_unused const char *method, 617 _o_unused struct blob_attr *msg) 618 { 619 struct dhcpv6_stats stats = dhcpv6_get_stats(); 620 621 blob_buf_init(&b, BLOBMSG_TYPE_TABLE); 622 blobmsg_add_u64(&b, "dhcp_solicit", stats.solicit); 623 blobmsg_add_u64(&b, "dhcp_advertise", stats.advertise); 624 blobmsg_add_u64(&b, "dhcp_request", stats.request); 625 blobmsg_add_u64(&b, "dhcp_confirm", stats.confirm); 626 blobmsg_add_u64(&b, "dhcp_renew", stats.renew); 627 blobmsg_add_u64(&b, "dhcp_rebind", stats.rebind); 628 blobmsg_add_u64(&b, "dhcp_reply", stats.reply); 629 blobmsg_add_u64(&b, "dhcp_release", stats.release); 630 blobmsg_add_u64(&b, "dhcp_decline", stats.decline); 631 blobmsg_add_u64(&b, "dhcp_reconfigure", stats.reconfigure); 632 blobmsg_add_u64(&b, "dhcp_information_request", stats.information_request); 633 blobmsg_add_u64(&b, "dhcp_discarded_packets", stats.discarded_packets); 634 blobmsg_add_u64(&b, "dhcp_transmit_failures", stats.transmit_failures); 635 636 CHECK(ubus_send_reply(ctx, req, b.head)); 637 blob_buf_free(&b); 638 639 return UBUS_STATUS_OK; 640 } 641 642 static int ubus_handle_reset_stats(_o_unused struct ubus_context *ctx, _o_unused struct ubus_object *obj, 643 _o_unused struct ubus_request_data *req, _o_unused const char *method, 644 _o_unused struct blob_attr *msg) 645 { 646 dhcpv6_reset_stats(); 647 648 return UBUS_STATUS_OK; 649 } 650 651 static int ubus_handle_get_state(struct ubus_context *ctx, _o_unused struct ubus_object *obj, 652 struct ubus_request_data *req, _o_unused const char *method, 653 _o_unused struct blob_attr *msg) 654 { 655 CHECK(states_to_blob()); 656 CHECK(ubus_send_reply(ctx, req, b.head)); 657 blob_buf_free(&b); 658 659 return UBUS_STATUS_OK; 660 } 661 662 static int ubus_handle_reconfigure_dhcp_rtx(enum config_dhcp_msg msg, struct blob_attr* table) 663 { 664 struct blob_attr *cur = NULL; 665 uint32_t value = 0; 666 size_t rem = 0; 667 668 if (msg >= CONFIG_DHCP_MAX || blobmsg_data_len(table) == 0) 669 return UBUS_STATUS_INVALID_ARGUMENT; 670 671 blobmsg_for_each_attr(cur, table, rem) { 672 if (!blobmsg_check_attr(cur, true) || blobmsg_type(cur) != BLOBMSG_TYPE_INT32) 673 return UBUS_STATUS_INVALID_ARGUMENT; 674 675 const char* name = blobmsg_name(cur); 676 if (strcmp("delay_max", name) == 0) { 677 value = blobmsg_get_u32(cur); 678 if (!config_set_rtx_delay_max(msg, value)) 679 return UBUS_STATUS_INVALID_ARGUMENT; 680 } else if (strcmp("timeout_init", name) == 0 ) { 681 value = blobmsg_get_u32(cur); 682 if (!config_set_rtx_timeout_init(msg, value)) 683 return UBUS_STATUS_INVALID_ARGUMENT; 684 } else if (strcmp("timeout_max", name) == 0 ) { 685 value = blobmsg_get_u32(cur); 686 if (!config_set_rtx_timeout_max(msg, value)) 687 return UBUS_STATUS_INVALID_ARGUMENT; 688 } else if (strcmp("rc_max", name) == 0) { 689 value = blobmsg_get_u32(cur); 690 if (!config_set_rtx_rc_max(msg, value)) 691 return UBUS_STATUS_INVALID_ARGUMENT; 692 } else { 693 return UBUS_STATUS_INVALID_ARGUMENT; 694 } 695 } 696 697 return UBUS_STATUS_OK; 698 } 699 700 static int ubus_handle_reconfigure_dhcp(_o_unused struct ubus_context *ctx, _o_unused struct ubus_object *obj, 701 _o_unused struct ubus_request_data *req, _o_unused const char *method, 702 struct blob_attr *msg) 703 { 704 const struct blobmsg_policy *policy = reconfigure_dhcp_policy; 705 struct blob_attr *tb[RECONFIGURE_DHCP_ATTR_MAX]; 706 struct blob_attr *cur = NULL; 707 struct blob_attr *elem = NULL; 708 char *string = NULL; 709 uint32_t value = 0; 710 uint32_t index = 0; 711 bool enabled = false; 712 bool valid_args = false; 713 bool need_reinit = false; 714 715 if (blobmsg_parse(policy, RECONFIGURE_DHCP_ATTR_MAX, tb, blob_data(msg), blob_len(msg))) 716 return UBUS_STATUS_INVALID_ARGUMENT; 717 718 if ((cur = tb[RECONFIGURE_DHCP_ATTR_DSCP])) { 719 value = blobmsg_get_u32(cur); 720 if (!config_set_dscp(value)) 721 return UBUS_STATUS_INVALID_ARGUMENT; 722 need_reinit = true; 723 valid_args = true; 724 } 725 726 if ((cur = tb[RECONFIGURE_DHCP_ATTR_RELEASE])) { 727 enabled = blobmsg_get_bool(cur); 728 config_set_release(enabled); 729 valid_args = true; 730 } 731 732 if ((cur = tb[RECONFIGURE_DHCP_ATTR_SOL_TIMEOUT])) { 733 value = blobmsg_get_u32(cur); 734 if (!config_set_rtx_timeout_max(CONFIG_DHCP_SOLICIT, value)) 735 return UBUS_STATUS_INVALID_ARGUMENT; 736 need_reinit = true; 737 valid_args = true; 738 } 739 740 if ((cur = tb[RECONFIGURE_DHCP_ATTR_SK_PRIORITY])) { 741 value = blobmsg_get_u32(cur); 742 if (!config_set_sk_priority(value)) 743 return UBUS_STATUS_INVALID_ARGUMENT; 744 need_reinit = true; 745 valid_args = true; 746 } 747 748 if ((cur = tb[RECONFIGURE_DHCP_ATTR_OPT_REQUESTED])) { 749 if (blobmsg_type(cur) != BLOBMSG_TYPE_ARRAY || !blobmsg_check_attr(cur, false)) 750 return UBUS_STATUS_INVALID_ARGUMENT; 751 752 config_clear_requested_options(); 753 754 blobmsg_for_each_attr(elem, cur, index) { 755 if (blobmsg_type(elem) != BLOBMSG_TYPE_INT32) 756 return UBUS_STATUS_INVALID_ARGUMENT; 757 758 value = blobmsg_get_u32(elem); 759 if (!config_add_requested_options(value)) 760 return UBUS_STATUS_INVALID_ARGUMENT; 761 } 762 763 need_reinit = true; 764 valid_args = true; 765 } 766 767 if ((cur = tb[RECONFIGURE_DHCP_ATTR_OPT_STRICT])) { 768 enabled = blobmsg_get_bool(cur); 769 config_set_client_options(DHCPV6_STRICT_OPTIONS, enabled); 770 need_reinit = true; 771 valid_args = true; 772 } 773 774 if ((cur = tb[RECONFIGURE_DHCP_ATTR_OPT_RECONFIGURE])) { 775 enabled = blobmsg_get_bool(cur); 776 config_set_client_options(DHCPV6_ACCEPT_RECONFIGURE, enabled); 777 need_reinit = true; 778 valid_args = true; 779 } 780 781 if ((cur = tb[RECONFIGURE_DHCP_ATTR_OPT_FQDN])) { 782 enabled = blobmsg_get_bool(cur); 783 config_set_client_options(DHCPV6_CLIENT_FQDN, enabled); 784 need_reinit = true; 785 valid_args = true; 786 } 787 788 if ((cur = tb[RECONFIGURE_DHCP_ATTR_OPT_UNICAST])) { 789 enabled = blobmsg_get_bool(cur); 790 config_set_client_options(DHCPV6_IGNORE_OPT_UNICAST, enabled); 791 need_reinit = true; 792 valid_args = true; 793 } 794 795 if ((cur = tb[RECONFIGURE_DHCP_ATTR_OPT_SEND])) { 796 if (blobmsg_type(cur) != BLOBMSG_TYPE_ARRAY || !blobmsg_check_attr(cur, false)) 797 return UBUS_STATUS_INVALID_ARGUMENT; 798 799 config_clear_send_options(); 800 801 blobmsg_for_each_attr(elem, cur, index) { 802 string = blobmsg_get_string(elem); 803 if (string == NULL || !config_add_send_options(string)) 804 return UBUS_STATUS_INVALID_ARGUMENT; 805 } 806 807 need_reinit = true; 808 valid_args = true; 809 } 810 811 if ((cur = tb[RECONFIGURE_DHCP_ATTR_REQ_ADDRESSES])) { 812 string = blobmsg_get_string(cur); 813 if (string == NULL || !config_set_request_addresses(string)) 814 return UBUS_STATUS_INVALID_ARGUMENT; 815 816 need_reinit = true; 817 valid_args = true; 818 } 819 820 if ((cur = tb[RECONFIGURE_DHCP_ATTR_REQ_PREFIXES])) { 821 value = blobmsg_get_u32(cur); 822 823 if (!config_set_request_prefix(value, 1)) 824 return UBUS_STATUS_INVALID_ARGUMENT; 825 826 need_reinit = true; 827 valid_args = true; 828 } 829 830 if ((cur = tb[RECONFIGURE_DHCP_ATTR_STATEFUL])) { 831 enabled = blobmsg_get_bool(cur); 832 config_set_stateful_only(enabled); 833 need_reinit = true; 834 valid_args = true; 835 } 836 837 if ((cur = tb[RECONFIGURE_DHCP_ATTR_MSG_SOLICIT])) { 838 if (ubus_handle_reconfigure_dhcp_rtx(CONFIG_DHCP_SOLICIT, cur)) 839 return UBUS_STATUS_INVALID_ARGUMENT; 840 841 need_reinit = true; 842 valid_args = true; 843 } 844 845 if ((cur = tb[RECONFIGURE_DHCP_ATTR_MSG_REQUEST])) { 846 if (ubus_handle_reconfigure_dhcp_rtx(CONFIG_DHCP_REQUEST, cur)) 847 return UBUS_STATUS_INVALID_ARGUMENT; 848 849 need_reinit = true; 850 valid_args = true; 851 } 852 853 if ((cur = tb[RECONFIGURE_DHCP_ATTR_MSG_RENEW])) { 854 if (ubus_handle_reconfigure_dhcp_rtx(CONFIG_DHCP_RENEW, cur)) 855 return UBUS_STATUS_INVALID_ARGUMENT; 856 857 need_reinit = true; 858 valid_args = true; 859 } 860 861 if ((cur = tb[RECONFIGURE_DHCP_ATTR_MSG_REBIND])) { 862 if (ubus_handle_reconfigure_dhcp_rtx(CONFIG_DHCP_REBIND, cur)) 863 return UBUS_STATUS_INVALID_ARGUMENT; 864 865 need_reinit = true; 866 valid_args = true; 867 } 868 869 if ((cur = tb[RECONFIGURE_DHCP_ATTR_MSG_RELEASE])) { 870 if (ubus_handle_reconfigure_dhcp_rtx(CONFIG_DHCP_RELEASE, cur)) 871 return UBUS_STATUS_INVALID_ARGUMENT; 872 873 need_reinit = true; 874 valid_args = true; 875 } 876 877 if ((cur = tb[RECONFIGURE_DHCP_ATTR_MSG_DECLINE])) { 878 if (ubus_handle_reconfigure_dhcp_rtx(CONFIG_DHCP_DECLINE, cur)) 879 return UBUS_STATUS_INVALID_ARGUMENT; 880 881 need_reinit = true; 882 valid_args = true; 883 } 884 885 if ((cur = tb[RECONFIGURE_DHCP_ATTR_MSG_INFO_REQ])) { 886 if (ubus_handle_reconfigure_dhcp_rtx(CONFIG_DHCP_INFO_REQ, cur)) 887 return UBUS_STATUS_INVALID_ARGUMENT; 888 889 need_reinit = true; 890 valid_args = true; 891 } 892 893 if ((cur = tb[RECONFIGURE_DHCP_ATTR_IRT_MIN])) { 894 value = blobmsg_get_u32(cur); 895 896 if (!config_set_irt_min(value)) 897 return UBUS_STATUS_INVALID_ARGUMENT; 898 899 need_reinit = true; 900 valid_args = true; 901 } 902 903 if ((cur = tb[RECONFIGURE_DHCP_ATTR_IRT_DEFAULT])) { 904 value = blobmsg_get_u32(cur); 905 906 if (!config_set_irt_default(value)) 907 return UBUS_STATUS_INVALID_ARGUMENT; 908 909 need_reinit = true; 910 valid_args = true; 911 } 912 913 if ((cur = tb[RECONFIGURE_DHCP_ATTR_RAND_FACTOR])) { 914 value = blobmsg_get_u32(cur); 915 916 if (!config_set_rand_factor(value)) 917 return UBUS_STATUS_INVALID_ARGUMENT; 918 919 need_reinit = true; 920 valid_args = true; 921 } 922 923 if ((cur = tb[RECONFIGURE_DHCP_ATTR_AUTH_PROTO])) { 924 string = blobmsg_get_string(cur); 925 926 if (string == NULL || !config_set_auth_protocol(string)) 927 return UBUS_STATUS_INVALID_ARGUMENT; 928 929 need_reinit = true; 930 valid_args = true; 931 } 932 933 if ((cur = tb[RECONFIGURE_DHCP_ATTR_AUTH_TOKEN])) { 934 string = blobmsg_get_string(cur); 935 936 if (string == NULL || !config_set_auth_token(string)) 937 return UBUS_STATUS_INVALID_ARGUMENT; 938 939 need_reinit = true; 940 valid_args = true; 941 } 942 943 if (need_reinit) 944 raise(SIGUSR2); 945 946 return valid_args ? UBUS_STATUS_OK : UBUS_STATUS_INVALID_ARGUMENT; 947 } 948 949 static int ubus_handle_renew(_o_unused struct ubus_context *ctx, _o_unused struct ubus_object *obj, 950 _o_unused struct ubus_request_data *req, _o_unused const char *method, 951 _o_unused struct blob_attr *msg) 952 { 953 raise(SIGUSR1); 954 return UBUS_STATUS_OK; 955 } 956 957 static int ubus_handle_release(_o_unused struct ubus_context *ctx, _o_unused struct ubus_object *obj, 958 _o_unused struct ubus_request_data *req, _o_unused const char *method, 959 _o_unused struct blob_attr *msg) 960 { 961 raise(SIGUSR2); 962 return UBUS_STATUS_OK; 963 } 964 965 int ubus_dhcp_event(const char *status) 966 { 967 if (!ubus || !odhcp6c_object.has_subscribers) 968 return UBUS_STATUS_UNKNOWN_ERROR; 969 970 CHECK(states_to_blob()); 971 CHECK(ubus_notify(ubus, &odhcp6c_object, status, b.head, -1)); 972 blob_buf_free(&b); 973 974 return UBUS_STATUS_OK; 975 } 976
This page was automatically generated by LXR 0.3.1. • OpenWrt