├── .vscode └── settings.json ├── .gitignore ├── .github └── workflows │ ├── scripts │ └── ci_helpers.sh │ ├── formal.yml │ └── ci.yml ├── scripts └── devel-build.sh ├── src ├── ra.h ├── ubus.h ├── config.h ├── odhcp6c.h ├── script.c ├── config.c ├── ra.c ├── ubus.c └── odhcp6c.c ├── CMakeLists.txt ├── odhcp6c-example-script.sh ├── README.md └── COPYING /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 8 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .cproject 3 | build 4 | odhcp6c 5 | config.log 6 | CMakeCache.txt 7 | CMakeFiles 8 | CPackConfig.cmake 9 | CPackSourceConfig.cmake 10 | _CPack_Packages 11 | Makefile 12 | cmake_install.cmake 13 | install_manifest.txt 14 | *.deb 15 | -------------------------------------------------------------------------------- /.github/workflows/scripts/ci_helpers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | color_out() { 4 | printf "\e[0;$1m%s\e[0;0m\n" "$2" 5 | } 6 | 7 | success() { 8 | color_out 32 "$1" 9 | } 10 | 11 | info() { 12 | color_out 36 "$1" 13 | } 14 | 15 | err() { 16 | color_out 31 "$1" 17 | } 18 | 19 | warn() { 20 | color_out 33 "$1" 21 | } 22 | 23 | err_die() { 24 | err "$1" 25 | exit 1 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/formal.yml: -------------------------------------------------------------------------------- 1 | name: Test Formalities 2 | 3 | on: 4 | pull_request: 5 | 6 | permissions: 7 | contents: read 8 | pull-requests: write 9 | 10 | jobs: 11 | build: 12 | name: Test Formalities 13 | uses: openwrt/actions-shared-workflows/.github/workflows/formal.yml@main 14 | # with: 15 | # # Post formality check summaries to the PR. 16 | # # Repo's permissions need to be updated for actions to modify PRs: 17 | # # https://docs.github.com/en/rest/issues/comments?apiVersion=2022-11-28#create-an-issue-comment 18 | # post_comment: true 19 | -------------------------------------------------------------------------------- /scripts/devel-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euxo pipefail 4 | cd "${0%/*}" 5 | cd .. 6 | 7 | # Sanity checks 8 | if [ ! -e "CMakeLists.txt" ] || [ ! -e "src/odhcp6c.c" ]; then 9 | echo "odhcp6c checkout not found" >&2 10 | exit 1 11 | fi 12 | 13 | if [ $# -eq 0 ]; then 14 | BUILD_ARGS="" 15 | else 16 | BUILD_ARGS="$@" 17 | fi 18 | 19 | # Create build dirs 20 | ODHCP6CDIR="$(pwd)" 21 | BUILDDIR="${ODHCP6CDIR}/build" 22 | DEPSDIR="${BUILDDIR}/depends" 23 | [ -e "${BUILDDIR}" ] || mkdir "${BUILDDIR}" 24 | [ -e "${DEPSDIR}" ] || mkdir "${DEPSDIR}" 25 | 26 | # Download deps 27 | cd "${DEPSDIR}" 28 | [ -e "json-c" ] || git clone https://github.com/json-c/json-c.git 29 | [ -e "libubox" ] || git clone https://github.com/openwrt/libubox.git 30 | [ -e "ubus" ] || git clone https://github.com/openwrt/ubus.git 31 | 32 | # Build json-c 33 | cd "${DEPSDIR}/json-c" 34 | cmake \ 35 | -S . \ 36 | -B . \ 37 | -DCMAKE_PREFIX_PATH="${BUILDDIR}" \ 38 | -DBUILD_SHARED_LIBS=OFF \ 39 | -DDISABLE_EXTRA_LIBS=ON \ 40 | -DBUILD_TESTING=OFF \ 41 | --install-prefix "${BUILDDIR}" 42 | make 43 | make install 44 | 45 | # Build libubox 46 | cd "${DEPSDIR}/libubox" 47 | cmake \ 48 | -S . \ 49 | -B . \ 50 | -DCMAKE_PREFIX_PATH="${BUILDDIR}" \ 51 | -DBUILD_LUA=OFF \ 52 | -DBUILD_EXAMPLES=OFF \ 53 | --install-prefix "${BUILDDIR}" 54 | make 55 | make install 56 | 57 | # Build ubus 58 | cd "${DEPSDIR}/ubus" 59 | cmake \ 60 | -S . \ 61 | -B . \ 62 | -DCMAKE_PREFIX_PATH="${BUILDDIR}" \ 63 | -DBUILD_LUA=OFF \ 64 | -DBUILD_EXAMPLES=OFF \ 65 | --install-prefix "${BUILDDIR}" 66 | make 67 | make install 68 | 69 | # Build odhcp6c 70 | cd "${ODHCP6CDIR}" 71 | cmake \ 72 | -S . \ 73 | -B "${BUILDDIR}" \ 74 | -DCMAKE_PREFIX_PATH="${BUILDDIR}" \ 75 | -DUBUS=ON \ 76 | ${BUILD_ARGS} 77 | make -C "${BUILDDIR}" 78 | 79 | set +x 80 | echo "✅ Success - the odhcp6c binary is available at ${BUILDDIR}/odhcp6c" 81 | echo "👷 You can rebuild odhcp6c by running 'make -C build'" 82 | 83 | exit 0 84 | -------------------------------------------------------------------------------- /src/ra.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2012-2014 Steven Barth 3 | * Copyright (C) 2018 Hans Dedecker 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License v2 as published by 7 | * the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | */ 15 | 16 | #ifndef _RA_H_ 17 | #define _RA_H_ 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #define ALL_IPV6_NODES {{{0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ 24 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}}} 25 | 26 | #define ALL_IPV6_ROUTERS {{{0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ 27 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02}}} 28 | 29 | struct icmpv6_opt { 30 | uint8_t type; 31 | uint8_t len; 32 | uint8_t data[6]; 33 | }; 34 | 35 | /* RFC8910 Captive-Portal §2.3 */ 36 | struct icmpv6_opt_captive_portal { 37 | uint8_t type; /* 37 */ 38 | uint8_t len; /* includes the Type and Length fields, in units of 8 bytes */ 39 | uint8_t data[]; /* padded with NUL (0x00) to make length multiple of 8 */ 40 | }; 41 | 42 | struct icmpv6_opt_route_info { 43 | uint8_t type; 44 | uint8_t len; 45 | uint8_t prefix_len; 46 | uint8_t flags; 47 | uint32_t lifetime; 48 | uint8_t prefix[]; 49 | }; 50 | 51 | typedef enum ra_ifid_mode { 52 | RA_IFID_EUI64, 53 | RA_IFID_FIXED, 54 | RA_IFID_RANDOM, 55 | RA_IFID_LLA, 56 | } ra_ifid_mode_t; 57 | 58 | #define ND_OPT_ROUTE_INFORMATION 24 59 | #define ND_OPT_CAPTIVE_PORTAL 37 60 | 61 | 62 | #define icmpv6_for_each_option(opt, start, end)\ 63 | for (opt = (struct icmpv6_opt*)(start);\ 64 | (void*)(opt + 1) <= (void*)(end) && opt->len > 0 &&\ 65 | (void*)(opt + opt->len) <= (void*)(end); opt += opt->len) 66 | 67 | 68 | int ra_init(const char *ifname, const struct in6_addr *ifid, 69 | ra_ifid_mode_t ifid_mode, unsigned int options, 70 | unsigned int holdoff_interval); 71 | bool ra_link_up(void); 72 | bool ra_process(void); 73 | 74 | #endif /* _RA_H_ */ 75 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | cmake_policy(SET CMP0015 NEW) 3 | 4 | 5 | # Project Definition 6 | project(odhcp6c LANGUAGES C) 7 | add_executable(${PROJECT_NAME}) 8 | target_sources(${PROJECT_NAME} PRIVATE 9 | src/config.c 10 | src/dhcpv6.c 11 | src/odhcp6c.c 12 | src/ra.c 13 | src/script.c 14 | ) 15 | 16 | 17 | # Compiler Options 18 | set_target_properties(${PROJECT_NAME} PROPERTIES C_STANDARD 11) 19 | target_compile_definitions(${PROJECT_NAME} PRIVATE _GNU_SOURCE) 20 | target_compile_options(${PROJECT_NAME} PRIVATE -g3) 21 | target_compile_options(${PROJECT_NAME} PRIVATE -Os) 22 | target_compile_options(${PROJECT_NAME} PRIVATE -Wall) 23 | target_compile_options(${PROJECT_NAME} PRIVATE -Werror) 24 | target_compile_options(${PROJECT_NAME} PRIVATE -Wextra) 25 | target_compile_options(${PROJECT_NAME} PRIVATE -Werror=implicit-function-declaration) 26 | target_compile_options(${PROJECT_NAME} PRIVATE -Wformat) 27 | target_compile_options(${PROJECT_NAME} PRIVATE -Werror=format-security) 28 | target_compile_options(${PROJECT_NAME} PRIVATE -Werror=format-nonliteral) 29 | target_compile_options(${PROJECT_NAME} PRIVATE -Wimplicit-fallthrough=5) 30 | target_compile_options(${PROJECT_NAME} PRIVATE -Wno-shadow=compatible-local) 31 | target_compile_options(${PROJECT_NAME} PRIVATE -Wno-unused-parameter) 32 | target_compile_options(${PROJECT_NAME} PRIVATE -Wmissing-declarations) 33 | target_compile_options(${PROJECT_NAME} PRIVATE -Wshadow=local) 34 | 35 | 36 | # Libraries 37 | target_link_libraries(${PROJECT_NAME} PRIVATE resolv) 38 | 39 | find_path(ubox_include_dir libubox/md5.h) 40 | target_include_directories(${PROJECT_NAME} PRIVATE ${ubox_include_dir}) 41 | find_library(libubox ubox) 42 | target_link_libraries(${PROJECT_NAME} PRIVATE ${libubox}) 43 | 44 | if(${UBUS}) 45 | target_compile_definitions(${PROJECT_NAME} PRIVATE WITH_UBUS) 46 | target_sources(${PROJECT_NAME} PRIVATE src/ubus.c) 47 | find_path(ubus_include_dir libubus.h) 48 | target_include_directories(${PROJECT_NAME} PRIVATE ${ubus_include_dir}) 49 | find_library(libubus ubus) 50 | target_link_libraries(${PROJECT_NAME} PRIVATE ${libubus}) 51 | endif(${UBUS}) 52 | 53 | 54 | # Installation 55 | install(TARGETS ${PROJECT_NAME} DESTINATION sbin/) 56 | 57 | 58 | # Packaging information 59 | set(CPACK_PACKAGE_VERSION "1") 60 | set(CPACK_PACKAGE_CONTACT "Steven Barth ") 61 | set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${PROJECT_NAME}") 62 | set(CPACK_GENERATOR "DEB;RPM;STGZ") 63 | set(CPACK_STRIP_FILES true) 64 | SET(CPACK_DEBIAN_PACKAGE_VERSION ${CPACK_PACKAGE_VERSION}) 65 | set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}_${CPACK_DEBIAN_PACKAGE_VERSION}") 66 | include(CPack) 67 | -------------------------------------------------------------------------------- /src/ubus.h: -------------------------------------------------------------------------------- 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 | #ifndef _UBUS_H_ 63 | #define _UBUS_H_ 64 | 65 | #ifdef WITH_UBUS 66 | #include 67 | #endif /* WITH_UBUS */ 68 | 69 | char *ubus_init(const char* interface); 70 | struct ubus_context *ubus_get_ctx(void); 71 | void ubus_destroy(struct ubus_context *ubus); 72 | int ubus_dhcp_event(const char *status); 73 | 74 | #endif /* _UBUS_H_ */ 75 | -------------------------------------------------------------------------------- /odhcp6c-example-script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | [ -z "$2" ] && echo "Error: should be run by odhcpc6c" && exit 1 3 | 4 | update_resolv() { 5 | local device="$1" 6 | local dns="$2" 7 | 8 | ( 9 | flock 9 10 | grep -v "#odhcp6c:$device:" /etc/resolv.conf > /tmp/resolv.conf.tmp 11 | for c in $dns; do 12 | echo "nameserver $c #odhcp6c:$device:" >> /tmp/resolv.conf.tmp 13 | done 14 | mv /tmp/resolv.conf.tmp /etc/resolv.conf 15 | chmod 0644 /etc/resolv.conf 16 | ) 9>/tmp/resolv.conf.lock 17 | rm -f /tmp/resolv.conf.lock /tmp/resolv.conf.tmp 18 | } 19 | 20 | setup_interface () { 21 | local device="$1" 22 | 23 | # Merge RA-DNS 24 | for radns in $RA_DNS; do 25 | local duplicate=0 26 | for dns in $RDNSS; do 27 | [ "$radns" = "$dns" ] && duplicate=1 28 | done 29 | [ "$duplicate" = 0 ] && RDNSS="$RDNSS $radns" 30 | done 31 | 32 | local dnspart="" 33 | for dns in $RDNSS; do 34 | if [ -z "$dnspart" ]; then 35 | dnspart="\"$dns\"" 36 | else 37 | dnspart="$dnspart, \"$dns\"" 38 | fi 39 | done 40 | 41 | update_resolv "$device" "$dns" 42 | 43 | local prefixpart="" 44 | for entry in $PREFIXES; do 45 | local addr="${entry%%,*}" 46 | entry="${entry#*,}" 47 | local preferred="${entry%%,*}" 48 | entry="${entry#*,}" 49 | local valid="${entry%%,*}" 50 | entry="${entry#*,}" 51 | [ "$entry" = "$valid" ] && entry= 52 | 53 | local class="" 54 | local excluded="" 55 | 56 | while [ -n "$entry" ]; do 57 | local key="${entry%%=*}" 58 | entry="${entry#*=}" 59 | local val="${entry%%,*}" 60 | entry="${entry#*,}" 61 | [ "$entry" = "$val" ] && entry= 62 | 63 | if [ "$key" = "class" ]; then 64 | class=", \"class\": $val" 65 | elif [ "$key" = "excluded" ]; then 66 | excluded=", \"excluded\": \"$val\"" 67 | fi 68 | done 69 | 70 | local prefix="{\"address\": \"$addr\", \"preferred\": $preferred, \"valid\": $valid $class $excluded}" 71 | 72 | if [ -z "$prefixpart" ]; then 73 | prefixpart="$prefix" 74 | else 75 | prefixpart="$prefixpart, $prefix" 76 | fi 77 | 78 | # TODO: delete this somehow when the prefix disappears 79 | ip -6 route add unreachable "$addr" 80 | done 81 | 82 | ip -6 route flush dev "$device" 83 | ip -6 address flush dev "$device" scope global 84 | 85 | # Merge addresses 86 | for entry in $RA_ADDRESSES; do 87 | local duplicate=0 88 | local addr="${entry%%/*}" 89 | for dentry in $ADDRESSES; do 90 | local daddr="${dentry%%/*}" 91 | [ "$addr" = "$daddr" ] && duplicate=1 92 | done 93 | [ "$duplicate" = "0" ] && ADDRESSES="$ADDRESSES $entry" 94 | done 95 | 96 | for entry in $ADDRESSES; do 97 | local addr="${entry%%,*}" 98 | entry="${entry#*,}" 99 | local preferred="${entry%%,*}" 100 | entry="${entry#*,}" 101 | local valid="${entry%%,*}" 102 | 103 | ip -6 address add "$addr" dev "$device" preferred_lft "$preferred" valid_lft "$valid" 104 | done 105 | 106 | for entry in $RA_ROUTES; do 107 | local addr="${entry%%,*}" 108 | entry="${entry#*,}" 109 | local gw="${entry%%,*}" 110 | entry="${entry#*,}" 111 | local valid="${entry%%,*}" 112 | entry="${entry#*,}" 113 | local metric="${entry%%,*}" 114 | 115 | if [ -n "$gw" ]; then 116 | ip -6 route add "$addr" via "$gw" metric "$metric" dev "$device" from "::/128" 117 | else 118 | ip -6 route add "$addr" metric "$metric" dev "$device" 119 | fi 120 | 121 | for prefix in $PREFIXES; do 122 | local paddr="${prefix%%,*}" 123 | [ -n "$gw" ] && ip -6 route add "$addr" via "$gw" metric "$metric" dev "$device" from "$paddr" 124 | done 125 | done 126 | } 127 | 128 | teardown_interface() { 129 | local device="$1" 130 | ip -6 route flush dev "$device" 131 | ip -6 address flush dev "$device" scope global 132 | update_resolv "$device" "" 133 | } 134 | 135 | ( 136 | flock 9 137 | case "$2" in 138 | bound) 139 | teardown_interface "$1" 140 | setup_interface "$1" 141 | ;; 142 | informed|updated|rebound|ra-updated) 143 | setup_interface "$1" 144 | ;; 145 | stopped|unbound) 146 | teardown_interface "$1" 147 | ;; 148 | started) 149 | teardown_interface "$1" 150 | ;; 151 | esac 152 | 153 | # user rules 154 | [ -f /etc/odhcp6c.user ] && . /etc/odhcp6c.user 155 | ) 9>/tmp/odhcp6c.lock.$1 156 | rm -f /tmp/odhcp6c.lock.$1 157 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 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 | #ifndef _CONFIG_H_ 63 | #define _CONFIG_H_ 64 | 65 | #include "odhcp6c.h" 66 | 67 | struct config_dhcp_rtx { 68 | uint8_t delay_max; 69 | uint8_t timeout_init; 70 | uint16_t timeout_max; 71 | uint8_t rc_max; 72 | }; 73 | 74 | enum config_dhcp_msg { 75 | CONFIG_DHCP_SOLICIT, 76 | CONFIG_DHCP_REQUEST, 77 | CONFIG_DHCP_RENEW, 78 | CONFIG_DHCP_REBIND, 79 | CONFIG_DHCP_RELEASE, 80 | CONFIG_DHCP_DECLINE, 81 | CONFIG_DHCP_INFO_REQ, 82 | CONFIG_DHCP_MAX 83 | }; 84 | 85 | struct config_dhcp { 86 | bool log_syslog; 87 | bool release; 88 | int log_level; 89 | int dscp; 90 | int sk_prio; 91 | bool stateful_only_mode; 92 | enum odhcp6c_ia_mode ia_na_mode; 93 | enum odhcp6c_ia_mode ia_pd_mode; 94 | unsigned int client_options; 95 | bool allow_slaac_only; 96 | unsigned int oro_user_cnt; 97 | struct config_dhcp_rtx message_rtx[CONFIG_DHCP_MAX]; 98 | uint32_t irt_default; 99 | uint32_t irt_min; 100 | uint16_t rand_factor; 101 | enum odhcp6c_auth_protocol auth_protocol; 102 | char* auth_token; 103 | }; 104 | 105 | struct config_dhcp *config_dhcp_get(void); 106 | void config_dhcp_reset(void); 107 | void config_set_release(bool enable); 108 | bool config_set_dscp(unsigned int value) ; 109 | bool config_set_sk_priority(unsigned int priority); 110 | void config_set_client_options(enum dhcpv6_config option, bool enable); 111 | bool config_set_request_addresses(char *mode); 112 | bool config_set_request_prefix(unsigned int length, unsigned int id); 113 | void config_set_force_prefix(bool enable); 114 | void config_set_stateful_only(bool enable); 115 | void config_set_allow_slaac_only(bool value); 116 | void config_clear_requested_options(void) ; 117 | bool config_add_requested_options(unsigned int option); 118 | void config_clear_send_options(void); 119 | bool config_add_send_options(char *option); 120 | bool config_set_rtx_delay_max(enum config_dhcp_msg msg, unsigned int value); 121 | bool config_set_rtx_timeout_init(enum config_dhcp_msg msg, unsigned int value); 122 | bool config_set_rtx_timeout_max(enum config_dhcp_msg msg, unsigned int value); 123 | bool config_set_rtx_rc_max(enum config_dhcp_msg msg, unsigned int value); 124 | bool config_set_irt_default(unsigned int value); 125 | bool config_set_irt_min(unsigned int value); 126 | bool config_set_rand_factor(unsigned int value); 127 | bool config_set_auth_protocol(const char* protocol); 128 | bool config_set_auth_token(const char* token); 129 | 130 | int config_add_opt(const uint16_t code, const uint8_t *data, const uint16_t len); 131 | int config_parse_opt_data(const char *data, uint8_t **dst, const unsigned int type, const bool array); 132 | int config_parse_opt(const char *opt); 133 | 134 | void config_apply_dhcp_rtx(struct dhcpv6_retx* dhcpv6_retx); 135 | 136 | #endif /* _CONFIG_H_ */ 137 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: odhcp6c 2 | 3 | on: 4 | pull_request: 5 | push: 6 | 7 | env: 8 | archs: "aarch64 arm mips mips64 powerpc powerpc64 riscv64 x86_64" 9 | variants: "without_ubus with_ubus" 10 | 11 | jobs: 12 | build: 13 | name: Build ${{ matrix.arch }} 14 | runs-on: ubuntu-latest 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | include: 19 | - arch: aarch64 20 | gcc: /usr/bin/aarch64-linux-gnu-gcc 21 | packages: gcc-aarch64-linux-gnu 22 | - arch: arm 23 | gcc: /usr/bin/arm-linux-gnueabi-gcc 24 | packages: gcc-arm-linux-gnueabi 25 | - arch: mips 26 | gcc: /usr/bin/mips-linux-gnu-gcc 27 | packages: gcc-mips-linux-gnu 28 | - arch: mips64 29 | gcc: /usr/bin/mips64-linux-gnuabi64-gcc 30 | packages: gcc-mips64-linux-gnuabi64 31 | - arch: powerpc 32 | gcc: /usr/bin/powerpc-linux-gnu-gcc 33 | packages: gcc-powerpc-linux-gnu 34 | - arch: powerpc64 35 | gcc: /usr/bin/powerpc64-linux-gnu-gcc 36 | packages: gcc-powerpc64-linux-gnu 37 | - arch: riscv64 38 | gcc: /usr/bin/riscv64-linux-gnu-gcc 39 | packages: gcc-riscv64-linux-gnu 40 | - arch: x86_64 41 | gcc: /usr/bin/x86_64-linux-gnu-gcc 42 | packages: gcc-x86-64-linux-gnu 43 | outputs: 44 | size-aarch64-without-ubus: ${{ steps.without_ubus.outputs.size_aarch64 }} 45 | size-aarch64-with-ubus: ${{ steps.with_ubus.outputs.size_aarch64 }} 46 | size-arm-without-ubus: ${{ steps.without_ubus.outputs.size_arm }} 47 | size-arm-with-ubus: ${{ steps.with_ubus.outputs.size_arm }} 48 | size-mips-without-ubus: ${{ steps.without_ubus.outputs.size_mips }} 49 | size-mips-with-ubus: ${{ steps.with_ubus.outputs.size_mips }} 50 | size-mips64-without-ubus: ${{ steps.without_ubus.outputs.size_mips64 }} 51 | size-mips64-with-ubus: ${{ steps.with_ubus.outputs.size_mips64 }} 52 | size-powerpc-without-ubus: ${{ steps.without_ubus.outputs.size_powerpc }} 53 | size-powerpc-with-ubus: ${{ steps.with_ubus.outputs.size_powerpc }} 54 | size-powerpc64-without-ubus: ${{ steps.without_ubus.outputs.size_powerpc64 }} 55 | size-powerpc64-with-ubus: ${{ steps.with_ubus.outputs.size_powerpc64 }} 56 | size-riscv64-without-ubus: ${{ steps.without_ubus.outputs.size_riscv64 }} 57 | size-riscv64-with-ubus: ${{ steps.with_ubus.outputs.size_riscv64 }} 58 | size-x86_64-without-ubus: ${{ steps.without_ubus.outputs.size_x86_64 }} 59 | size-x86_64-with-ubus: ${{ steps.with_ubus.outputs.size_x86_64 }} 60 | steps: 61 | - name: Checkout odhcp6c 62 | uses: actions/checkout@v5 63 | 64 | - name: Checkout json-c 65 | uses: actions/checkout@v5 66 | with: 67 | repository: json-c/json-c 68 | path: depends/json-c 69 | 70 | - name: Checkout libubox 71 | uses: actions/checkout@v5 72 | with: 73 | repository: openwrt/libubox 74 | path: depends/libubox 75 | 76 | - name: Checkout ubus 77 | uses: actions/checkout@v5 78 | with: 79 | repository: openwrt/ubus 80 | path: depends/ubus 81 | 82 | - name: Install dependencies 83 | run: | 84 | sudo apt update 85 | sudo apt install ${{ matrix.packages }} 86 | 87 | - name: Prepare build 88 | run: | 89 | mkdir -p ${GITHUB_WORKSPACE}/build 90 | 91 | - name: Build json-c 92 | working-directory: depends/json-c 93 | run: | 94 | cmake \ 95 | -DCMAKE_C_COMPILER=${{ matrix.gcc }} \ 96 | -DCMAKE_PREFIX_PATH=${GITHUB_WORKSPACE}/build \ 97 | -DBUILD_SHARED_LIBS=OFF \ 98 | -DDISABLE_EXTRA_LIBS=ON \ 99 | -DBUILD_TESTING=OFF \ 100 | --install-prefix ${GITHUB_WORKSPACE}/build \ 101 | -B . -S . 102 | make 103 | make install 104 | 105 | - name: Build libubox 106 | working-directory: depends/libubox 107 | run: | 108 | cmake \ 109 | -DCMAKE_C_COMPILER=${{ matrix.gcc }} \ 110 | -DCMAKE_PREFIX_PATH=${GITHUB_WORKSPACE}/build \ 111 | -DBUILD_LUA=OFF \ 112 | -DBUILD_EXAMPLES=OFF \ 113 | --install-prefix ${GITHUB_WORKSPACE}/build \ 114 | -B . -S . 115 | make 116 | make install 117 | 118 | - name: Build ubus 119 | working-directory: depends/ubus 120 | run: | 121 | cmake \ 122 | -DCMAKE_C_COMPILER=${{ matrix.gcc }} \ 123 | -DCMAKE_PREFIX_PATH=${GITHUB_WORKSPACE}/build \ 124 | -DBUILD_LUA=OFF \ 125 | -DBUILD_EXAMPLES=OFF \ 126 | --install-prefix ${GITHUB_WORKSPACE}/build \ 127 | -B . -S . 128 | make 129 | make install 130 | 131 | - id: without_ubus 132 | name: Build odhcp6c (w/o ubus) 133 | env: 134 | BUILD_DIR: build/odhcp6c-without-ubus 135 | run: | 136 | cmake \ 137 | -DCMAKE_C_COMPILER=${{ matrix.gcc }} \ 138 | -DCMAKE_PREFIX_PATH=${GITHUB_WORKSPACE}/build \ 139 | -B $BUILD_DIR -S . 140 | make -C $BUILD_DIR 141 | echo "size_${{ matrix.arch }}=$( find $BUILD_DIR -type f -name odhcp6c -printf '%s' )" >> $GITHUB_OUTPUT 142 | 143 | - id: with_ubus 144 | name: Build odhcp6c (with ubus) 145 | env: 146 | BUILD_DIR: build/odhcp6c-with-ubus 147 | run: | 148 | cmake \ 149 | -DCMAKE_C_COMPILER=${{ matrix.gcc }} \ 150 | -DCMAKE_PREFIX_PATH=${GITHUB_WORKSPACE}/build \ 151 | -DUBUS=ON \ 152 | -B $BUILD_DIR -S . 153 | make -C $BUILD_DIR 154 | echo "size_${{ matrix.arch }}=$( find $BUILD_DIR -type f -name odhcp6c -printf '%s' )" >> $GITHUB_OUTPUT 155 | 156 | - name: Upload binaries 157 | uses: actions/upload-artifact@v4 158 | with: 159 | name: odhcp6c-${{ matrix.arch }}-binaries 160 | path: | 161 | build/odhcp6c-*/odhcp6c 162 | if-no-files-found: error 163 | 164 | summary: 165 | name: Sizes 166 | needs: [build] 167 | runs-on: ubuntu-latest 168 | steps: 169 | - name: Sizes summary 170 | env: 171 | size_aarch64_without_ubus: ${{needs.build.outputs.size-aarch64-without-ubus}} 172 | size_aarch64_with_ubus: ${{needs.build.outputs.size-aarch64-with-ubus}} 173 | size_arm_without_ubus: ${{needs.build.outputs.size-arm-without-ubus}} 174 | size_arm_with_ubus: ${{needs.build.outputs.size-arm-with-ubus}} 175 | size_mips_without_ubus: ${{needs.build.outputs.size-mips-without-ubus}} 176 | size_mips_with_ubus: ${{needs.build.outputs.size-mips-with-ubus}} 177 | size_mips64_without_ubus: ${{needs.build.outputs.size-mips64-without-ubus}} 178 | size_mips64_with_ubus: ${{needs.build.outputs.size-mips64-with-ubus}} 179 | size_powerpc_without_ubus: ${{needs.build.outputs.size-powerpc-without-ubus}} 180 | size_powerpc_with_ubus: ${{needs.build.outputs.size-powerpc-with-ubus}} 181 | size_powerpc64_without_ubus: ${{needs.build.outputs.size-powerpc64-without-ubus}} 182 | size_powerpc64_with_ubus: ${{needs.build.outputs.size-powerpc64-with-ubus}} 183 | size_riscv64_without_ubus: ${{needs.build.outputs.size-riscv64-without-ubus}} 184 | size_riscv64_with_ubus: ${{needs.build.outputs.size-riscv64-with-ubus}} 185 | size_x86_64_without_ubus: ${{needs.build.outputs.size-x86_64-without-ubus}} 186 | size_x86_64_with_ubus: ${{needs.build.outputs.size-x86_64-with-ubus}} 187 | run: | 188 | echo "### ${GITHUB_WORKFLOW} sizes :floppy_disk:" >> $GITHUB_STEP_SUMMARY 189 | 190 | header="| arch |" 191 | table="| :---: |" 192 | for variant in ${{ env.variants }}; do 193 | header="$header $variant |" 194 | table="$table :---: |" 195 | done 196 | echo $header >> $GITHUB_STEP_SUMMARY 197 | echo $table >> $GITHUB_STEP_SUMMARY 198 | 199 | for arch in ${{ env.archs }}; do 200 | row="| $arch | " 201 | for variant in $variants; do 202 | value=size_${arch}_$(echo "$variant" | tr "[:upper:]" "[:lower:]") 203 | row="$row ${!value} |" 204 | done 205 | echo $row >> $GITHUB_STEP_SUMMARY 206 | done 207 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # odhcp6c - Embedded DHCPv6 Client 2 | 3 | 4 | ### Abstract 5 | 6 | odhcp6c is a minimal DHCPv6 and RA-client for use in embedded Linux systems 7 | especially routers. 8 | 9 | 10 | ## Features 11 | 12 | 1. IPv6 bootstrap from different environments with autodetection 13 | * RA only 14 | * RA + stateless DHCPv6 15 | * RA + stateful DHCPv6 (either IA_NA or IA_PD or both) 16 | 17 | 2. Handling of non-temporary addresses (IA_NA) 18 | * handling of valid and preferred lifetimes 19 | * automatic fallback to stateless or PD-only mode 20 | 21 | 3. Support for DHCPv6 extension 22 | * Reconfigure-Messages 23 | * Prefix Delegation (including handling of valid and preferred lifetimes) 24 | * Prefix Exclusion 25 | * DNS Configuration Options 26 | * NTP Options 27 | * SIP Options 28 | * Information-Refresh Options 29 | * Configurable SOL_MAX_RT 30 | * DS-Lite AFTR-Name Option 31 | * Softwire address and port mapped clients (MAP, LW4over6) 32 | * Server unicast Option 33 | 34 | 4. Support for requesting and parsing Router Advertisements 35 | * parsing of prefixes, routes, MTU and RDNSS options 36 | 37 | 38 | ## Compiling 39 | 40 | odhcp6c uses cmake: 41 | * To prepare a Makefile use: `cmake`. 42 | * To build / install use: `make` / `make install` afterwards. 43 | * To build DEB or RPM packages use: `make package` afterwards. 44 | 45 | 46 | ## State Script 47 | 48 | The state script is called whenever the DHCPv6 state changes. 49 | The script is called with the following parameters: `` `` 50 | 51 | | State | Description | 52 | |-----------------------------------|-------------------------------------------------------------------| 53 | | `started` | The DHCPv6 client has been started | 54 | | `bound` | A suitable server was found and addresses or prefixes acquired | 55 | | `informed` | A stateless information request returned updated information | 56 | | `updated` | Updated information was received from the DHCPv6 server | 57 | | `ra-updated` | Updated information was received via Router Advertisements | 58 | | `rebound` | The DHCPv6 client switched to another server | 59 | | `unbound` | The DHCPv6 client lost all DHCPv6 servers and will restart | 60 | | `stopped` | The DHCPv6 client has been stopped | 61 | 62 | 63 | 64 | 65 | | Environment | Description | 66 | |-----------------------------------|-------------------------------------------------------------------| 67 | | `SERVER` | A space-separated list of upstream IPv6 routers | 68 | | `RDNSS` | A space-separated list of recursive DNS servers | 69 | | `DOMAINS` | A space-separated list of DNS search domains | 70 | | `SNTP_IP` | A space-separated list of SNTP server IP addresses | 71 | | `SNTP_FQDN` | A space-separated list of SNTP server FQDNs | 72 | | `SIP_IP` | A space-separated list of SIP servers | 73 | | `SIP_DOMAIN` | A space-separated list of SIP domains | 74 | | `OPTION_` | Custom option received as base-16 | 75 | | | E.g. (Client ID) `OPTION_1` : `000300010badf00dcafe` | 76 | | `PREFIXES` | A space-separated list of prefixes currently assigned | 77 | | | Format: `/,preferred,valid[,excluded=/][,class=]`| 78 | | `ADDRESSES` | A space-separated list of addresses currently assigned | 79 | | | Format: `
/,preferred,valid` | 80 | | `RA_ADDRESSES` | A space-separated list of addresses from RA-prefixes | 81 | | | Format: `
/,preferred,valid` | 82 | | `RA_ROUTES` | A space-separated list of routes from the RA | 83 | | | Format: `
/,gateway,valid,metric` | 84 | | `RA_DNS` | A space-separated list of recursive DNS servers from the RA | 85 | | `RA_DOMAINS` | A space-separated list of DNS search domains from the RA | 86 | | `RA_HOPLIMIT` | Highest hop-limit received in RAs | 87 | | `RA_MTU` | MTU-value received in RA | 88 | | `RA_REACHABLE` | ND Reachability time | 89 | | `RA_RETRANSMIT` | ND Retransmit time | 90 | | `AFTR` | The DS-Lite AFTR domain name | 91 | | `MAPE` / `MAPT` / `LW4O6` | Softwire rules for MAPE, MAPT and LW4O6 | 92 | | `CAPTIVE_PORTAL_URI` | RFC8910 captive portal API URI received from upstream | 93 | | `PASSTHRU` | The content of the last packet relayed | 94 | 95 | 96 | 97 | ## Ubus Integration 98 | 99 | Build with `ENABLE_UBUS` flag to connect odhcp6c to ubus. Object is registered at : `odhcp6c.{ifname}`. 100 | 101 | Events are emitted whenever the DHCPv6 state changes and can replace the use of a state script. The variables are the same as those defined in the State Script section. 102 | 103 | The following RPC methods are available: 104 | 105 | 106 | | Method | I/O | Description | 107 | |-----------------------------------|-----------|-------------------------------------------------------------------| 108 | | `get_state()` | Output | Returns the DHCPv6 state | 109 | | | | OUT : see State Script section | 110 | | `get_statistics()` | Output | Returns the packet statistics | 111 | | | | `dhcp_solicit` : Total number of SOLICIT messages sent | 112 | | | | `dhcp_advertise` : Total number of ADVERTISE messages received | 113 | | | | `dhcp_request` : Total number of REQUEST messages sent | 114 | | | | `dhcp_confirm` : Total number of CONFIRM messages sent | 115 | | | | `dhcp_renew` : Total number of RENEW messages sent | 116 | | | | `dhcp_rebind` : Total number of REBIND messages sent | 117 | | | | `dhcp_reply` : Total number of REPLY messages received | 118 | | | | `dhcp_release` : Total number of RELEASE messages sent | 119 | | | | `dhcp_decline` : Total number of DECLINE messages sent | 120 | | | | `dhcp_reconfigure` : Total number of RECONFIGURE messages received | 121 | | | | `dhcp_information_request` : Total number of INFORMATION-REQUEST messages sent | 122 | | | | `dhcp_discarded_packets` : Total number of discarded DHCP packets | 123 | | | | `dhcp_transmit_failures` : Total number of DHCP messages that failed to be transmitted | 124 | | `reset_statistics()` | Input | Reset packet statistics | 125 | | `reconfigure_dhcp({...})` | Input | Reconfigure DHCP settings | 126 | | | | `dscp` (int) : DSCP value used for DHCP packets | 127 | | | | `release` (bool) : Send a RELEASE message on exit/reset | 128 | | | | `sol_timeout` (int) : Maximum timeout for DHCPv6-SOLICIT | 129 | | | | `sk_prio` (int) : Packet kernel priority | 130 | | | | `opt_requested` (int[]) : Options to be requested | 131 | | | | `opt_strict` (bool) : Do not request any options except those specified | 132 | | | | `opt_reconfigure` (bool) : Send Accept Reconfigure option | 133 | | | | `opt_fqdn` (bool) : Send Client FQDN option | 134 | | | | `opt_unicast` (bool) : Ignore Server Unicast option | 135 | | | | `opt_send` (string[]) : Options to be sent | 136 | | | | `req_addresses` (string{`try|force|none`}) : Request addresses | 137 | | | | `req_prefixes` (int) : Request Prefixes (0 = auto) | 138 | | | | `stateful_only` (bool) : Discard advertisements without any address or prefix proposed | 139 | | | | `irt_default` (int) : Default information refresh time (expressed in seconds) | 140 | | | | `irt_min` (int) : Minimum information refresh time (expressed in seconds) | 141 | | | | `rand_factor` (int) : Randomization factor for retransmission timeout | 142 | | | | `auth_protocol` (string) : Authentication protocol to be used (`None`,`ConfigurationToken`, `ReconfigureKeyAuthentication`)| 143 | | | | `auth_token` (string) : Authentication token to be used when AuthenticationProtocol is set to `ConfigurationToken`| 144 | | | | `msg_solicit` (table) : Retransmission settings for SOLICIT | 145 | | | | `msg_request` (table) : Retransmission settings for REQUEST | 146 | | | | `msg_renew` (table) : Retransmission settings for RENEW | 147 | | | | `msg_rebind` (table) : Retransmission settings for REBIND | 148 | | | | `msg_release` (table) : Retransmission settings for RELEASE | 149 | | | | `msg_decline` (table) : Retransmission settings for DECLINE | 150 | | | | `msg_inforeq` (table) : Retransmission settings for INFORMATION-REQUEST | 151 | | | | | 152 | | | | Input arguments for Retransmission settings : | 153 | | | | `delay_max` (int) : Maximum delay of first message (expressed in seconds) | 154 | | | | `timeout_init` (int) : Initial message timeout (expressed in seconds) | 155 | | | | `timeout_max` (int) : Maximum message timeout (expressed in seconds) | 156 | | | | `rc_max` (int) : Maximum message retry attempts | 157 | | `renew()` | Input | Force transmission of RENEW/INFORMATION-REQUEST messages | 158 | | `release()` | Input | Force transmission of RELEASE message and start new cycle | 159 | 160 | -------------------------------------------------------------------------------- /src/odhcp6c.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2012-2014 Steven Barth 3 | * Copyright (C) 2018 Hans Dedecker 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License v2 as published by 7 | * the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | */ 15 | 16 | #ifndef _ODHCP6C_H_ 17 | #define _ODHCP6C_H_ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #ifndef _o_aligned 25 | #define _o_aligned(n) __attribute__((aligned(n))) 26 | #endif /* _o_aligned */ 27 | 28 | #ifndef _o_fallthrough 29 | #define _o_fallthrough __attribute__((__fallthrough__)) 30 | #endif /* _o_fallthrough */ 31 | 32 | #ifndef _o_packed 33 | #define _o_packed __attribute__((packed)) 34 | #endif /* _o_packed */ 35 | 36 | #ifndef _o_unused 37 | #define _o_unused __attribute__((unused)) 38 | #endif /* _o_unused */ 39 | 40 | #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) 41 | 42 | void __iflog(int lvl, const char *fmt, ...); 43 | #define debug(fmt, ...) __iflog(LOG_DEBUG, fmt __VA_OPT__(, ) __VA_ARGS__) 44 | #define info(fmt, ...) __iflog(LOG_INFO, fmt __VA_OPT__(, ) __VA_ARGS__) 45 | #define notice(fmt, ...) __iflog(LOG_NOTICE, fmt __VA_OPT__(, ) __VA_ARGS__) 46 | #define warn(fmt, ...) __iflog(LOG_WARNING, fmt __VA_OPT__(, ) __VA_ARGS__) 47 | #define error(fmt, ...) __iflog(LOG_ERR, fmt __VA_OPT__(, ) __VA_ARGS__) 48 | #define critical(fmt, ...) __iflog(LOG_CRIT, fmt __VA_OPT__(, ) __VA_ARGS__) 49 | #define alert(fmt, ...) __iflog(LOG_ALERT, fmt __VA_OPT__(, ) __VA_ARGS__) 50 | #define emergency(fmt, ...) __iflog(LOG_EMERG, fmt __VA_OPT__(, ) __VA_ARGS__) 51 | 52 | #define ND_OPT_RECURSIVE_DNS 25 53 | #define ND_OPT_DNSSL 31 54 | 55 | #define DHCPV6_MAX_DELAY 1 56 | #define DHCPV6_IRT_DEFAULT 86400 57 | #define DHCPV6_IRT_MIN 600 58 | #define DHCPV6_RAND_FACTOR 100 59 | 60 | #define DHCPV6_SOL_INIT_RT 1 61 | #define DHCPV6_SOL_MAX_RT 120 62 | 63 | #define DHCPV6_REQ_INIT_RT 1 64 | #define DHCPV6_REQ_MAX_RT 30 65 | #define DHCPV6_REQ_MAX_RC 10 66 | 67 | #define DHCPV6_REN_INIT_RT 10 68 | #define DHCPV6_REN_MAX_RT 600 69 | 70 | #define DHCPV6_REB_INIT_RT 10 71 | #define DHCPV6_REB_MAX_RT 600 72 | 73 | #define DHCPV6_INF_INIT_RT 1 74 | #define DHCPV6_INF_MAX_RT 3600 75 | 76 | #define DHCPV6_REL_INIT_RT 1 77 | #define DHCPV6_REL_MAX_RC 4 78 | 79 | #define DHCPV6_DEC_INIT_RT 1 80 | #define DHCPV6_DEC_MAX_RC 4 81 | 82 | #define DHCPV6_IFACEID_EUI64 "eui64" 83 | #define DHCPV6_IFACEID_RANDOM "random" 84 | 85 | #define RA_MIN_ADV_INTERVAL 3 /* RFC 4861 paragraph 6.2.1 */ 86 | 87 | /* RFC8910 §2 */ 88 | static const uint8_t URN_IETF_CAPT_PORT_UNRESTR[] = "urn:ietf:params:capport:unrestricted"; 89 | #define CAPT_PORT_URI_STR "CAPTIVE_PORTAL_URI" 90 | 91 | enum dhcvp6_opt { 92 | /* RFC8415(bis) */ 93 | DHCPV6_OPT_CLIENTID = 1, 94 | DHCPV6_OPT_SERVERID = 2, 95 | DHCPV6_OPT_IA_NA = 3, 96 | DHCPV6_OPT_IA_TA = 4, 97 | DHCPV6_OPT_IA_ADDR = 5, 98 | DHCPV6_OPT_ORO = 6, 99 | DHCPV6_OPT_PREF = 7, 100 | DHCPV6_OPT_ELAPSED = 8, 101 | DHCPV6_OPT_RELAY_MSG = 9, 102 | DHCPV6_OPT_AUTH = 11, 103 | DHCPV6_OPT_UNICAST = 12, 104 | DHCPV6_OPT_STATUS = 13, 105 | DHCPV6_OPT_RAPID_COMMIT = 14, 106 | DHCPV6_OPT_USER_CLASS = 15, 107 | DHCPV6_OPT_VENDOR_CLASS = 16, 108 | DHCPV6_OPT_INTERFACE_ID = 18, 109 | DHCPV6_OPT_RECONF_MESSAGE = 19, 110 | DHCPV6_OPT_RECONF_ACCEPT = 20, 111 | /* RFC3319 */ 112 | DHCPV6_OPT_SIP_SERVER_D = 21, 113 | DHCPV6_OPT_SIP_SERVER_A = 22, 114 | /* RFC3646 */ 115 | DHCPV6_OPT_DNS_SERVERS = 23, 116 | DHCPV6_OPT_DNS_DOMAIN = 24, 117 | /* RFC8415(bis) */ 118 | DHCPV6_OPT_IA_PD = 25, 119 | DHCPV6_OPT_IA_PREFIX = 26, 120 | /* RFC4075 */ 121 | DHCPV6_OPT_SNTP_SERVERS = 31, 122 | /* RFC4242 */ 123 | DHCPV6_OPT_INFO_REFRESH = 32, 124 | /* RFC4649 */ 125 | DHCPV6_OPT_REMOTE_ID = 37, 126 | /* RFC4580 */ 127 | DHCPV6_OPT_SUBSCRIBER_ID = 38, 128 | /* RFC4704 */ 129 | DHCPV6_OPT_FQDN = 39, 130 | /* RFC4994 */ 131 | DHCPV6_OPT_ERO = 43, 132 | /* RFC5007 */ 133 | DHCPV6_OPT_LQ_QUERY = 44, 134 | DHCPV6_OPT_CLIENT_DATA = 45, 135 | DHCPV6_OPT_CLT_TIME = 46, 136 | DHCPV6_OPT_LQ_RELAY_DATA = 47, 137 | DHCPV6_OPT_LQ_CLIENT_LINK = 48, 138 | /* RFC5460 */ 139 | DHCPV6_OPT_RELAY_ID = 53, 140 | /* RFC5908 */ 141 | DHCPV6_OPT_NTP_SERVER = 56, 142 | /* RFC5970 */ 143 | DHCPV6_OPT_CLIENT_ARCH_TYPE = 61, 144 | /* RFC6334 */ 145 | DHCPV6_OPT_AFTR_NAME = 64, 146 | /* RFC6422 */ 147 | DHCPV6_OPT_RSOO = 66, 148 | /* RFC6603 */ 149 | DHCPV6_OPT_PD_EXCLUDE = 67, 150 | /* RFC6607 */ 151 | DHCPV6_OPT_VSS = 68, 152 | /* RFC6939 */ 153 | DHCPV6_OPT_LINK_LAYER_ADDRESS = 79, 154 | /* RFC6977 */ 155 | DHCPV6_OPT_LINK_ADDRESS = 80, 156 | /* RFC7037 */ 157 | DHCPV6_OPT_RADIUS = 81, 158 | /* RFC8415(bis) */ 159 | DHCPV6_OPT_SOL_MAX_RT = 82, 160 | DHCPV6_OPT_INF_MAX_RT = 83, 161 | /* RFC8415(bis) */ 162 | DHCPV6_OPT_DHCPV4_MSG = 87, 163 | /* RFC7598 */ 164 | DHCPV6_OPT_S46_RULE = 89, 165 | DHCPV6_OPT_S46_BR = 90, /* & RFC8539 */ 166 | DHCPV6_OPT_S46_DMR = 91, 167 | DHCPV6_OPT_S46_V4V6BIND = 92, 168 | DHCPV6_OPT_S46_PORTPARAMS = 93, 169 | DHCPV6_OPT_S46_CONT_MAPE = 94, 170 | DHCPV6_OPT_S46_CONT_MAPT = 95, 171 | DHCPV6_OPT_S46_CONT_LW = 96, 172 | /* RFC7653 */ 173 | DHCPV6_OPT_LQ_BASE_TIME = 100, 174 | DHCPV6_OPT_LQ_START_TIME = 101, 175 | DHCPV6_OPT_LQ_END_TIME = 102, 176 | /* RFC8910 */ 177 | DHCPV6_OPT_CAPTIVE_PORTAL = 103, 178 | /* RFC7839 */ 179 | DHCPV6_OPT_ANI_ATT = 105, 180 | DHCPV6_OPT_ANI_NETWORK_NAME = 106, 181 | DHCPV6_OPT_ANI_AP_NAME = 107, 182 | DHCPV6_OPT_ANI_AP_BSSID = 108, 183 | DHCPV6_OPT_ANI_OPERATOR_ID = 109, 184 | DHCPV6_OPT_ANI_OPERATOR_REALM = 110, 185 | /* RFC8520 */ 186 | DHCPV6_OPT_MUD_URL_V6 = 112, 187 | /* RFC8156 */ 188 | DHCPV6_OPT_F_BINDING_STATUS = 114, 189 | DHCPV6_OPT_F_CONNECT_FLAGS = 115, 190 | DHCPV6_OPT_F_DNS_REMOVAL_INFO = 116, 191 | DHCPV6_OPT_F_DNS_HOST_NAME = 117, 192 | DHCPV6_OPT_F_DNS_ZONE_NAME = 118, 193 | DHCPV6_OPT_F_DNS_FLAGS = 119, 194 | DHCPV6_OPT_F_EXPIRATION_TIME = 120, 195 | DHCPV6_OPT_F_MAX_UNACKED_BNDUPD = 121, 196 | DHCPV6_OPT_F_MCLT = 122, 197 | DHCPV6_OPT_F_PARTNER_LIFETIME = 123, 198 | DHCPV6_OPT_F_PARTNER_LIFETIME_SENT = 124, 199 | DHCPV6_OPT_F_PARTNER_DOWN_TIME = 125, 200 | DHCPV6_OPT_F_PARTNER_RAW_CLT_TIME = 126, 201 | DHCPV6_OPT_F_PROTOCOL_VERSION = 127, 202 | DHCPV6_OPT_F_KEEPALIVE_TIME = 128, 203 | DHCPV6_OPT_F_RECONFIGURE_DATA = 129, 204 | DHCPV6_OPT_F_RELATIONSHIP_NAME = 130, 205 | DHCPV6_OPT_F_SERVER_FLAGS = 131, 206 | DHCPV6_OPT_F_SERVER_STATE = 132, 207 | DHCPV6_OPT_F_START_TIME_OF_STATE = 133, 208 | DHCPV6_OPT_F_STATE_EXPIRATION_TIME = 134, 209 | /* RFC8357 */ 210 | DHCPV6_OPT_RELAY_PORT = 135, 211 | }; 212 | 213 | enum dhcpv6_opt_npt { 214 | NTP_SRV_ADDR = 1, 215 | NTP_MC_ADDR = 2, 216 | NTP_SRV_FQDN = 3 217 | }; 218 | 219 | enum dhcpv6_msg { 220 | /* RFC8415(bis) */ 221 | DHCPV6_MSG_UNKNOWN = 0, 222 | DHCPV6_MSG_SOLICIT = 1, 223 | DHCPV6_MSG_ADVERT = 2, 224 | DHCPV6_MSG_REQUEST = 3, 225 | DHCPV6_MSG_RENEW = 5, 226 | DHCPV6_MSG_REBIND = 6, 227 | DHCPV6_MSG_REPLY = 7, 228 | DHCPV6_MSG_RELEASE = 8, 229 | DHCPV6_MSG_DECLINE = 9, 230 | DHCPV6_MSG_RECONF = 10, 231 | DHCPV6_MSG_INFO_REQ = 11, 232 | _DHCPV6_MSG_MAX 233 | }; 234 | 235 | enum dhcpv6_state { 236 | DHCPV6_INIT, 237 | DHCPV6_SOLICIT, 238 | DHCPV6_SOLICIT_PROCESSING, 239 | DHCPV6_ADVERT, 240 | DHCPV6_REQUEST, 241 | DHCPV6_REQUEST_PROCESSING, 242 | DHCPV6_REPLY, 243 | DHCPV6_BOUND, 244 | DHCPV6_BOUND_PROCESSING, 245 | DHCPV6_BOUND_REPLY, 246 | DHCPV6_RECONF, 247 | DHCPV6_RECONF_PROCESSING, 248 | DHCPV6_RECONF_REPLY, 249 | DHCPV6_RENEW, 250 | DHCPV6_RENEW_PROCESSING, 251 | DHCPV6_RENEW_REPLY, 252 | DHCPV6_REBIND, 253 | DHCPV6_REBIND_PROCESSING, 254 | DHCPV6_REBIND_REPLY, 255 | DHCPV6_INFO, 256 | DHCPV6_INFO_PROCESSING, 257 | DHCPV6_INFO_REPLY, 258 | DHCPV6_EXIT, 259 | DHCPV6_RESET 260 | }; 261 | 262 | enum dhcpv6_status { 263 | /* RFC8415(bis) */ 264 | DHCPV6_Success = 0, 265 | DHCPV6_UnspecFail = 1, 266 | DHCPV6_NoAddrsAvail = 2, 267 | DHCPV6_NoBinding = 3, 268 | DHCPV6_NotOnLink = 4, 269 | DHCPV6_UseMulticast = 5, 270 | DHCPV6_NoPrefixAvail = 6, 271 | _DHCPV6_Status_Max 272 | }; 273 | 274 | enum dhcpv6_config { 275 | DHCPV6_STRICT_OPTIONS = 1, 276 | DHCPV6_CLIENT_FQDN = 2, 277 | DHCPV6_ACCEPT_RECONFIGURE = 4, 278 | DHCPV6_IGNORE_OPT_UNICAST = 8, 279 | }; 280 | 281 | typedef int(reply_handler)(enum dhcpv6_msg orig, const int rc, 282 | const void *opt, const void *end, const struct sockaddr_in6 *from); 283 | 284 | // retransmission strategy 285 | struct dhcpv6_retx { 286 | uint8_t max_delay; // Delay before starting transaction 287 | uint8_t init_timeo; 288 | uint16_t max_timeo; 289 | uint8_t max_rc; // Max Retry Count 290 | char name[8]; 291 | reply_handler *handler_reply; 292 | int(*handler_finish)(void); 293 | bool is_retransmit; 294 | uint64_t timeout; // Maximum duration (in seconds) for the entire DHCPv6 transaction. Varies based on the message type 295 | uint8_t rc; // Retry Count 296 | uint64_t start; // Transaction start time (in milliseconds) 297 | uint8_t tr_id[3]; // Transaction ID 298 | int64_t rto; // Retransmission TimeOut 299 | uint64_t round_start; // the (RTT) time when a request was sent (in milliseconds) 300 | uint64_t round_end; // the (RTT) time when a response was expected to arrive (in milliseconds) 301 | int reply_ret; // Reply handler return value 302 | uint64_t delay_msec; // Delay before starting the transaction 303 | }; 304 | 305 | #define DHCPV6_OPT_HDR_SIZE 4 306 | #define DHCPV6_OPT_HDR_SIZE_U 4U 307 | #define DHCPV6_DUID_MAX_LEN 130 // 2-byte type + 128-byte DUID, RFC8415, §11.1 308 | 309 | // DHCPv6 Protocol Headers 310 | struct dhcpv6_header { 311 | uint8_t msg_type; 312 | uint8_t tr_id[3]; 313 | } _o_packed; 314 | 315 | struct dhcpv6_ia_hdr { 316 | uint16_t type; 317 | uint16_t len; 318 | uint32_t iaid; 319 | uint32_t t1; 320 | uint32_t t2; 321 | } _o_packed; 322 | 323 | struct dhcpv6_ia_addr { 324 | uint16_t type; 325 | uint16_t len; 326 | struct in6_addr addr; 327 | uint32_t preferred; 328 | uint32_t valid; 329 | } _o_packed; 330 | 331 | struct dhcpv6_ia_prefix { 332 | uint16_t type; 333 | uint16_t len; 334 | uint32_t preferred; 335 | uint32_t valid; 336 | uint8_t prefix; 337 | struct in6_addr addr; 338 | } _o_packed; 339 | 340 | struct dhcpv6_duid { 341 | uint16_t type; 342 | uint16_t len; 343 | uint16_t duid_type; 344 | uint8_t data[128]; 345 | } _o_packed; 346 | 347 | struct dhcpv6_auth { 348 | uint16_t type; 349 | uint16_t len; 350 | uint8_t protocol; 351 | uint8_t algorithm; 352 | uint8_t rdm; 353 | uint64_t replay; 354 | uint8_t data[]; 355 | } _o_packed; 356 | 357 | struct dhcpv6_auth_reconfigure { 358 | uint8_t reconf_type; 359 | uint8_t key[16]; 360 | } _o_packed; 361 | 362 | struct dhcpv6_s46_portparams { 363 | uint8_t offset; 364 | uint8_t psid_len; 365 | uint16_t psid; 366 | } _o_packed; 367 | 368 | struct dhcpv6_s46_v4v6bind { 369 | struct in_addr ipv4_address; 370 | uint8_t bindprefix6_len; 371 | uint8_t bind_ipv6_prefix[]; 372 | } _o_packed; 373 | 374 | struct dhcpv6_s46_dmr { 375 | uint8_t dmr_prefix6_len; 376 | uint8_t dmr_ipv6_prefix[]; 377 | } _o_packed; 378 | 379 | struct dhcpv6_s46_rule { 380 | uint8_t flags; 381 | uint8_t ea_len; 382 | uint8_t prefix4_len; 383 | struct in_addr ipv4_prefix; 384 | uint8_t prefix6_len; 385 | uint8_t ipv6_prefix[]; 386 | } _o_packed; 387 | 388 | #define dhcpv6_for_each_option(start, end, otype, olen, odata)\ 389 | for (uint8_t *_o = (uint8_t*)(start); _o + 4 <= (uint8_t*)(end) &&\ 390 | ((otype) = _o[0] << 8 | _o[1]) && ((odata) = (void*)&_o[4]) &&\ 391 | ((olen) = _o[2] << 8 | _o[3]) + (odata) <= (uint8_t*)(end); \ 392 | _o += 4 + (_o[2] << 8 | _o[3])) 393 | 394 | 395 | struct dhcpv6_server_cand { 396 | bool has_noaddravail; 397 | bool wants_reconfigure; 398 | int16_t preference; 399 | uint8_t duid_len; 400 | uint8_t duid[DHCPV6_DUID_MAX_LEN]; 401 | struct in6_addr server_addr; 402 | uint32_t sol_max_rt; 403 | uint32_t inf_max_rt; 404 | void *ia_na; 405 | void *ia_pd; 406 | size_t ia_na_len; 407 | size_t ia_pd_len; 408 | }; 409 | 410 | struct dhcpv6_stats { 411 | uint64_t solicit; 412 | uint64_t advertise; 413 | uint64_t request; 414 | uint64_t confirm; 415 | uint64_t renew; 416 | uint64_t rebind; 417 | uint64_t reply; 418 | uint64_t release; 419 | uint64_t decline; 420 | uint64_t reconfigure; 421 | uint64_t information_request; 422 | uint64_t discarded_packets; 423 | uint64_t transmit_failures; 424 | }; 425 | 426 | enum odhcp6c_state { 427 | STATE_CLIENT_ID, 428 | STATE_SERVER_ID, 429 | STATE_OUR_FQDN, 430 | STATE_SERVER_CAND, 431 | STATE_SERVER_ADDR, 432 | STATE_ORO, 433 | STATE_DNS, 434 | STATE_SEARCH, 435 | STATE_IA_NA, 436 | STATE_IA_PD, 437 | STATE_IA_PD_INIT, 438 | STATE_CUSTOM_OPTS, 439 | STATE_SNTP_IP, 440 | STATE_NTP_IP, 441 | STATE_NTP_FQDN, 442 | STATE_SIP_IP, 443 | STATE_SIP_FQDN, 444 | STATE_RA_ROUTE, 445 | STATE_RA_PREFIX, 446 | STATE_RA_DNS, 447 | STATE_RA_SEARCH, 448 | STATE_AFTR_NAME, 449 | STATE_OPTS, 450 | STATE_S46_MAPT, 451 | STATE_S46_MAPE, 452 | STATE_S46_LW, 453 | STATE_CAPT_PORT_RA, 454 | STATE_CAPT_PORT_DHCPV6, 455 | STATE_PASSTHRU, 456 | _STATE_MAX 457 | }; 458 | 459 | struct icmp6_opt { 460 | uint8_t type; 461 | uint8_t len; 462 | uint8_t data[6]; 463 | }; 464 | 465 | 466 | enum dhcpv6_mode { 467 | DHCPV6_UNKNOWN = -1, 468 | DHCPV6_STATELESS, 469 | DHCPV6_STATEFUL 470 | }; 471 | 472 | enum ra_config { 473 | RA_RDNSS_DEFAULT_LIFETIME = 1, 474 | }; 475 | 476 | enum odhcp6c_ia_mode { 477 | IA_MODE_NONE, 478 | IA_MODE_TRY, 479 | IA_MODE_FORCE, 480 | }; 481 | 482 | enum odhcp6c_auth_protocol { 483 | AUTH_PROT_NONE = -1, 484 | /* RFC3118 */ 485 | AUTH_PROT_TOKEN = 0, 486 | /* RFC8415(bis) */ 487 | AUTH_PROT_RKAP = 3, 488 | }; 489 | 490 | enum odhcp6c_auth_algorithm { 491 | /* RFC3118 */ 492 | AUTH_ALG_TOKEN = 0, 493 | /* RFC8415(bis) */ 494 | AUTH_ALG_HMACMD5 = 1 495 | }; 496 | 497 | enum odhcp6c_rkap_type { 498 | /* RFC8415(bis) */ 499 | RKAP_TYPE_KEY = 1, 500 | RKAP_TYPE_HMACMD5 = 2, 501 | }; 502 | 503 | struct odhcp6c_entry { 504 | struct in6_addr router; 505 | uint8_t auxlen; 506 | uint8_t length; 507 | uint8_t ra_flags; 508 | uint8_t exclusion_length; 509 | struct in6_addr target; 510 | int16_t priority; 511 | uint32_t valid; 512 | uint32_t preferred; 513 | uint32_t t1; 514 | uint32_t t2; 515 | uint32_t iaid; 516 | uint8_t auxtarget[]; 517 | }; 518 | 519 | // Include padding after auxtarget to align the next entry 520 | #define odhcp6c_entry_size(entry) \ 521 | (sizeof(struct odhcp6c_entry) + (((entry)->auxlen + 3) & ~3)) 522 | 523 | #define odhcp6c_next_entry(entry) \ 524 | ((struct odhcp6c_entry *)((uint8_t *)(entry) + odhcp6c_entry_size(entry))) 525 | 526 | 527 | struct odhcp6c_request_prefix { 528 | uint32_t iaid; 529 | uint8_t length; 530 | struct in6_addr addr; 531 | }; 532 | 533 | enum odhcp6c_opt_flags { 534 | OPT_U8 = 0, 535 | OPT_IP6, 536 | OPT_STR, 537 | OPT_DNS_STR, 538 | OPT_USER_CLASS, 539 | OPT_MASK_SIZE = 0x0F, 540 | OPT_ARRAY = 0x10, 541 | OPT_INTERNAL = 0x20, 542 | OPT_NO_PASSTHRU = 0x40, 543 | OPT_ORO = 0x80, 544 | OPT_ORO_STATEFUL = 0x100, 545 | OPT_ORO_STATELESS = 0x200, 546 | OPT_ORO_SOLICIT = 0x400 547 | }; 548 | 549 | struct odhcp6c_opt { 550 | uint16_t code; 551 | uint16_t flags; 552 | const char *str; 553 | }; 554 | 555 | uint32_t hash_ifname(const char *s); 556 | int init_dhcpv6(const char *ifname); 557 | int dhcpv6_get_ia_mode(void); 558 | int dhcpv6_promote_server_cand(void); 559 | int dhcpv6_send_request(enum dhcpv6_msg type); 560 | int dhcpv6_receive_response(enum dhcpv6_msg type); 561 | enum dhcpv6_state dhcpv6_get_state(void); 562 | void dhcpv6_set_state(enum dhcpv6_state state); 563 | int dhcpv6_get_socket(void); 564 | struct dhcpv6_stats dhcpv6_get_stats(void); 565 | void dhcpv6_reset_stats(void); 566 | int dhcpv6_state_processing(enum dhcpv6_msg type); 567 | int dhcpv6_get_state_timeout(void); 568 | void dhcpv6_set_state_timeout(int timeout); 569 | void dhcpv6_reset_state_timeout(void); 570 | const char *dhcpv6_state_to_str(enum dhcpv6_state state); 571 | 572 | int init_rtnetlink(void); 573 | int set_rtnetlink_addr(int ifindex, const struct in6_addr *addr, 574 | uint32_t pref, uint32_t valid); 575 | 576 | int ra_get_hoplimit(void); 577 | int ra_get_mtu(void); 578 | int ra_get_reachable(void); 579 | int ra_get_retransmit(void); 580 | 581 | void notify_state_change(const char *status, int delay, bool resume); 582 | 583 | int script_init(const char *path, const char *ifname); 584 | ssize_t script_unhexlify(uint8_t *dst, size_t len, const char *src); 585 | void script_hexlify(char *dst, const uint8_t *src, size_t len); 586 | void script_call(const char *status, int delay, bool resume); 587 | 588 | bool odhcp6c_signal_process(void); 589 | uint64_t odhcp6c_get_milli_time(void); 590 | int odhcp6c_random(void *buf, size_t len); 591 | bool odhcp6c_is_bound(void); 592 | bool odhcp6c_addr_in_scope(const struct in6_addr *addr); 593 | 594 | // State manipulation 595 | void odhcp6c_clear_state(enum odhcp6c_state state); 596 | int odhcp6c_add_state(enum odhcp6c_state state, const void *data, size_t len); 597 | void odhcp6c_append_state(enum odhcp6c_state state, const void *data, size_t len); 598 | int odhcp6c_insert_state(enum odhcp6c_state state, size_t offset, const void *data, size_t len); 599 | size_t odhcp6c_remove_state(enum odhcp6c_state state, size_t offset, size_t len); 600 | void* odhcp6c_move_state(enum odhcp6c_state state, size_t *len); 601 | void* odhcp6c_get_state(enum odhcp6c_state state, size_t *len); 602 | 603 | // Entry manipulation 604 | bool odhcp6c_update_entry(enum odhcp6c_state state, struct odhcp6c_entry *new, 605 | unsigned int holdoff_interval); 606 | 607 | void odhcp6c_expire(bool expire_ia_pd); 608 | uint32_t odhcp6c_elapsed(void); 609 | struct odhcp6c_opt *odhcp6c_find_opt(const uint16_t code); 610 | struct odhcp6c_opt *odhcp6c_find_opt_by_name(const char *name); 611 | 612 | static inline bool odhcp6c_is_multicast_ether_addr(const uint8_t *addr) 613 | { 614 | return addr[0] & 0x01; 615 | } 616 | 617 | static inline bool odhcp6c_is_zero_ether_addr(const uint8_t *addr) 618 | { 619 | return (addr[0] | addr[1] | addr[2] | 620 | addr[3] | addr[4] | addr[5]) == 0; 621 | } 622 | 623 | static inline bool odhcp6c_is_valid_ether_addr(const uint8_t *addr) 624 | { 625 | return !odhcp6c_is_multicast_ether_addr(addr) && 626 | !odhcp6c_is_zero_ether_addr(addr); 627 | } 628 | 629 | #endif /* _ODHCP6C_H_ */ 630 | -------------------------------------------------------------------------------- /src/script.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2012-2014 Steven Barth 3 | * Copyright (C) 2017-2018 Hans Dedecker 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License v2 as published by 7 | * the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | */ 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "odhcp6c.h" 29 | 30 | static const char hexdigits[] = "0123456789abcdef"; 31 | static const int8_t hexvals[] = { 32 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2, -1, -1, -2, -1, -1, 33 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 34 | -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 35 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, 36 | -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, 37 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 38 | -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, 39 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 40 | }; 41 | 42 | static char action[16] = ""; 43 | static char *argv[4] = {NULL, NULL, action, NULL}; 44 | static volatile pid_t running = 0; 45 | static time_t started; 46 | 47 | static void script_sighandle(int signal) 48 | { 49 | if (signal == SIGCHLD) { 50 | pid_t child; 51 | 52 | while ((child = waitpid(-1, NULL, WNOHANG)) > 0) 53 | if (running == child) 54 | running = 0; 55 | } 56 | } 57 | 58 | int script_init(const char *path, const char *ifname) 59 | { 60 | argv[0] = (char*)path; 61 | argv[1] = (char*)ifname; 62 | signal(SIGCHLD, script_sighandle); 63 | 64 | return 0; 65 | } 66 | 67 | ssize_t script_unhexlify(uint8_t *dst, size_t len, const char *src) 68 | { 69 | size_t c; 70 | 71 | for (c = 0; c < len && src[0] && src[1]; ++c) { 72 | int8_t x = (int8_t)*src++; 73 | int8_t y = (int8_t)*src++; 74 | if (x < 0 || (x = hexvals[x]) < 0 75 | || y < 0 || (y = hexvals[y]) < 0) 76 | return -1; 77 | dst[c] = x << 4 | y; 78 | while (((int8_t)*src) < 0 || 79 | (*src && hexvals[(uint8_t)*src] < 0)) 80 | src++; 81 | } 82 | 83 | return c; 84 | } 85 | 86 | void script_hexlify(char *dst, const uint8_t *src, size_t len) 87 | { 88 | for (size_t i = 0; i < len; ++i) { 89 | *dst++ = hexdigits[src[i] >> 4]; 90 | *dst++ = hexdigits[src[i] & 0x0f]; 91 | } 92 | 93 | *dst = 0; 94 | } 95 | 96 | static void ipv6_to_env(const char *name, 97 | const struct in6_addr *addr, size_t cnt) 98 | { 99 | size_t buf_len = strlen(name); 100 | char *buf = realloc(NULL, cnt * INET6_ADDRSTRLEN + buf_len + 2); 101 | 102 | memcpy(buf, name, buf_len); 103 | buf[buf_len++] = '='; 104 | 105 | for (size_t i = 0; i < cnt; ++i) { 106 | inet_ntop(AF_INET6, &addr[i], &buf[buf_len], INET6_ADDRSTRLEN); 107 | buf_len += strlen(&buf[buf_len]); 108 | buf[buf_len++] = ' '; 109 | } 110 | 111 | if (buf[buf_len - 1] == ' ') 112 | buf_len--; 113 | 114 | buf[buf_len] = '\0'; 115 | putenv(buf); 116 | } 117 | 118 | static void fqdn_to_env(const char *name, const uint8_t *fqdn, size_t len) 119 | { 120 | size_t buf_len = strlen(name); 121 | size_t buf_size = len + buf_len + 2; 122 | const uint8_t *fqdn_end = fqdn + len; 123 | char *buf = realloc(NULL, len + buf_len + 2); 124 | 125 | memcpy(buf, name, buf_len); 126 | buf[buf_len++] = '='; 127 | 128 | while (fqdn < fqdn_end) { 129 | int l = dn_expand(fqdn, fqdn_end, fqdn, &buf[buf_len], buf_size - buf_len); 130 | if (l <= 0) 131 | break; 132 | fqdn += l; 133 | buf_len += strlen(&buf[buf_len]); 134 | buf[buf_len++] = ' '; 135 | } 136 | 137 | if (buf[buf_len - 1] == ' ') 138 | buf_len--; 139 | 140 | buf[buf_len] = '\0'; 141 | putenv(buf); 142 | } 143 | 144 | static void string_to_env(const char *name, const uint8_t *string, size_t len) 145 | { 146 | size_t buf_len = strlen(name); 147 | const uint8_t *string_end = string + len; 148 | char *buf = realloc(NULL, len + buf_len + 2); 149 | 150 | memcpy(buf, name, buf_len); 151 | buf[buf_len++] = '='; 152 | 153 | while (string < string_end) { 154 | int l = strlen((const char *)string); 155 | if (l <= 0) 156 | break; 157 | string += l; 158 | buf_len += strlen(&buf[buf_len]); 159 | buf[buf_len++] = ' '; 160 | } 161 | 162 | if (buf[buf_len - 1] == ' ') 163 | buf_len--; 164 | 165 | buf[buf_len] = '\0'; 166 | putenv(buf); 167 | } 168 | 169 | static void bin_to_env(uint8_t *opts, size_t len) 170 | { 171 | uint8_t *oend = opts + len, *odata; 172 | uint16_t otype, olen; 173 | 174 | dhcpv6_for_each_option(opts, oend, otype, olen, odata) { 175 | char *buf = realloc(NULL, 14 + (olen * 2)); 176 | size_t buf_len = 0; 177 | 178 | snprintf(buf, 14, "OPTION_%hu=", otype); 179 | buf_len += strlen(buf); 180 | 181 | script_hexlify(&buf[buf_len], odata, olen); 182 | putenv(buf); 183 | } 184 | } 185 | 186 | enum entry_type { 187 | ENTRY_ADDRESS, 188 | ENTRY_HOST, 189 | ENTRY_ROUTE, 190 | ENTRY_PREFIX 191 | }; 192 | 193 | static void entry_to_env(const char *name, const void *data, size_t len, enum entry_type type) 194 | { 195 | size_t buf_len = strlen(name); 196 | const struct odhcp6c_entry *e = data; 197 | // Worst case: ENTRY_PREFIX with iaid != 1 and exclusion 198 | const size_t max_entry_len = (INET6_ADDRSTRLEN-1 + 5 + 44 + 15 + 10 + 199 | INET6_ADDRSTRLEN-1 + 11 + 1); 200 | char *buf = realloc(NULL, buf_len + 2 + (len / sizeof(*e)) * max_entry_len); 201 | 202 | memcpy(buf, name, buf_len); 203 | buf[buf_len++] = '='; 204 | 205 | for (size_t i = 0; i < len / sizeof(*e); ++i) { 206 | /* 207 | * The only invalid entries allowed to be passed to the script are prefix and RA 208 | * entries. This will allow immediate removal of the old ipv6-prefix-assignment 209 | * that might otherwise be kept for up to 2 hours (see L-13 requirement of RFC 7084). 210 | * Similarly, a RA with router lifetime set to 0 indicates that the advertising 211 | * router "is not a default router and SHOULD NOT appear on the default router list" 212 | * (see RFC 4861, section 4.2). 213 | */ 214 | if (!e[i].valid && type != ENTRY_PREFIX && type != ENTRY_ROUTE) 215 | continue; 216 | 217 | inet_ntop(AF_INET6, &e[i].target, &buf[buf_len], INET6_ADDRSTRLEN); 218 | buf_len += strlen(&buf[buf_len]); 219 | 220 | if (type != ENTRY_HOST) { 221 | snprintf(&buf[buf_len], 6, "/%"PRIu16, e[i].length); 222 | buf_len += strlen(&buf[buf_len]); 223 | 224 | if (type == ENTRY_ROUTE) { 225 | buf[buf_len++] = ','; 226 | 227 | if (!IN6_IS_ADDR_UNSPECIFIED(&e[i].router)) { 228 | inet_ntop(AF_INET6, &e[i].router, &buf[buf_len], INET6_ADDRSTRLEN); 229 | buf_len += strlen(&buf[buf_len]); 230 | } 231 | 232 | snprintf(&buf[buf_len], 23, ",%u,%u", e[i].valid, e[i].priority); 233 | buf_len += strlen(&buf[buf_len]); 234 | } else { 235 | snprintf(&buf[buf_len], 45, ",%u,%u,%u,%u", e[i].preferred, e[i].valid, e[i].t1, e[i].t2); 236 | buf_len += strlen(&buf[buf_len]); 237 | } 238 | 239 | if (type == ENTRY_PREFIX && ntohl(e[i].iaid) != 1) { 240 | snprintf(&buf[buf_len], 16, ",class=%08x", ntohl(e[i].iaid)); 241 | buf_len += strlen(&buf[buf_len]); 242 | } 243 | 244 | if (type == ENTRY_PREFIX && e[i].exclusion_length) { 245 | snprintf(&buf[buf_len], 11, ",excluded="); 246 | buf_len += strlen(&buf[buf_len]); 247 | // '.router' is dual-used: for prefixes it contains the prefix 248 | inet_ntop(AF_INET6, &e[i].router, &buf[buf_len], INET6_ADDRSTRLEN); 249 | buf_len += strlen(&buf[buf_len]); 250 | snprintf(&buf[buf_len], 12, "/%u", e[i].exclusion_length); 251 | buf_len += strlen(&buf[buf_len]); 252 | } 253 | } 254 | 255 | buf[buf_len++] = ' '; 256 | } 257 | 258 | if (buf[buf_len - 1] == ' ') 259 | buf_len--; 260 | 261 | buf[buf_len] = '\0'; 262 | putenv(buf); 263 | } 264 | 265 | static void search_to_env(const char *name, const uint8_t *start, size_t len) 266 | { 267 | size_t buf_len = strlen(name); 268 | char *buf = realloc(NULL, buf_len + 2 + len); 269 | char *c = mempcpy(buf, name, buf_len); 270 | *c++ = '='; 271 | 272 | for (struct odhcp6c_entry *e = (struct odhcp6c_entry*)start; 273 | (uint8_t*)e < &start[len] && 274 | (uint8_t*)odhcp6c_next_entry(e) <= &start[len]; 275 | e = odhcp6c_next_entry(e)) { 276 | if (!e->valid) 277 | continue; 278 | c = mempcpy(c, e->auxtarget, e->auxlen); 279 | *c++ = ' '; 280 | } 281 | 282 | if (c[-1] == ' ') 283 | c--; 284 | 285 | *c = '\0'; 286 | putenv(buf); 287 | } 288 | 289 | static void int_to_env(const char *name, int value) 290 | { 291 | size_t len = 13 + strlen(name); 292 | char *buf = realloc(NULL, len); 293 | 294 | snprintf(buf, len, "%s=%d", name, value); 295 | putenv(buf); 296 | } 297 | 298 | static void s46_to_env_portparams(const uint8_t *data, size_t len, FILE *fp) 299 | { 300 | uint8_t *odata; 301 | uint16_t otype, olen; 302 | 303 | dhcpv6_for_each_option(data, &data[len], otype, olen, odata) { 304 | if (otype == DHCPV6_OPT_S46_PORTPARAMS && 305 | olen == sizeof(struct dhcpv6_s46_portparams)) { 306 | struct dhcpv6_s46_portparams *params = (void*)odata; 307 | fprintf(fp, "offset=%d,psidlen=%d,psid=%d,", 308 | params->offset, params->psid_len, ntohs(params->psid)); 309 | } 310 | } 311 | } 312 | 313 | static void s46_to_env(enum odhcp6c_state state, const uint8_t *data, size_t len) 314 | { 315 | const char *name = (state == STATE_S46_MAPE) ? "MAPE" : 316 | (state == STATE_S46_MAPT) ? "MAPT" : "LW4O6"; 317 | 318 | if (len == 0) 319 | return; 320 | 321 | char *str; 322 | size_t strsize; 323 | 324 | FILE *fp = open_memstream(&str, &strsize); 325 | fputs(name, fp); 326 | fputc('=', fp); 327 | 328 | const char *type = (state == STATE_S46_MAPE) ? "map-e" : 329 | (state == STATE_S46_MAPT) ? "map-t" : "lw4o6"; 330 | 331 | uint8_t *odata; 332 | uint16_t otype, olen; 333 | 334 | dhcpv6_for_each_option(data, &data[len], otype, olen, odata) { 335 | struct dhcpv6_s46_rule *rule = (struct dhcpv6_s46_rule*)odata; 336 | struct dhcpv6_s46_v4v6bind *bind = (struct dhcpv6_s46_v4v6bind*)odata; 337 | 338 | if (state != STATE_S46_LW && otype == DHCPV6_OPT_S46_RULE && 339 | olen >= sizeof(struct dhcpv6_s46_rule)) { 340 | char buf4[INET_ADDRSTRLEN]; 341 | char buf6[INET6_ADDRSTRLEN]; 342 | struct in6_addr in6 = IN6ADDR_ANY_INIT; 343 | 344 | size_t prefix6len = rule->prefix6_len; 345 | prefix6len = (prefix6len % 8 == 0) ? prefix6len / 8 : prefix6len / 8 + 1; 346 | 347 | if (prefix6len > sizeof(in6) || 348 | olen < sizeof(struct dhcpv6_s46_rule) + prefix6len) 349 | continue; 350 | 351 | memcpy(&in6, rule->ipv6_prefix, prefix6len); 352 | 353 | inet_ntop(AF_INET, &rule->ipv4_prefix, buf4, sizeof(buf4)); 354 | inet_ntop(AF_INET6, &in6, buf6, sizeof(buf6)); 355 | 356 | if (rule->flags & 1) 357 | fputs("fmr,", fp); 358 | 359 | fprintf(fp, "type=%s,ealen=%d,prefix4len=%d,prefix6len=%d,ipv4prefix=%s,ipv6prefix=%s,", 360 | type, rule->ea_len, rule->prefix4_len, rule->prefix6_len, buf4, buf6); 361 | 362 | s46_to_env_portparams(&rule->ipv6_prefix[prefix6len], 363 | olen - sizeof(*rule) - prefix6len, fp); 364 | 365 | dhcpv6_for_each_option(data, &data[len], otype, olen, odata) { 366 | if (state != STATE_S46_MAPT && otype == DHCPV6_OPT_S46_BR && 367 | olen == sizeof(struct in6_addr)) { 368 | inet_ntop(AF_INET6, odata, buf6, sizeof(buf6)); 369 | fprintf(fp, "br=%s,", buf6); 370 | } else if (state == STATE_S46_MAPT && otype == DHCPV6_OPT_S46_DMR && 371 | olen >= sizeof(struct dhcpv6_s46_dmr)) { 372 | struct dhcpv6_s46_dmr *dmr = (struct dhcpv6_s46_dmr*)odata; 373 | memset(&in6, 0, sizeof(in6)); 374 | size_t prefix6len = dmr->dmr_prefix6_len; 375 | prefix6len = (prefix6len % 8 == 0) ? prefix6len / 8 : prefix6len / 8 + 1; 376 | 377 | if (prefix6len > sizeof(in6) || 378 | olen < sizeof(struct dhcpv6_s46_dmr) + prefix6len) 379 | continue; 380 | 381 | memcpy(&in6, dmr->dmr_ipv6_prefix, prefix6len); 382 | inet_ntop(AF_INET6, &in6, buf6, sizeof(buf6)); 383 | fprintf(fp, "dmr=%s/%d,", buf6, dmr->dmr_prefix6_len); 384 | } 385 | } 386 | 387 | fputc(' ', fp); 388 | } else if (state == STATE_S46_LW && otype == DHCPV6_OPT_S46_V4V6BIND && 389 | olen >= sizeof(struct dhcpv6_s46_v4v6bind)) { 390 | char buf4[INET_ADDRSTRLEN]; 391 | char buf6[INET6_ADDRSTRLEN]; 392 | struct in6_addr in6 = IN6ADDR_ANY_INIT; 393 | 394 | size_t prefix6len = bind->bindprefix6_len; 395 | prefix6len = (prefix6len % 8 == 0) ? prefix6len / 8 : prefix6len / 8 + 1; 396 | 397 | if (prefix6len > sizeof(in6) || 398 | olen < sizeof(struct dhcpv6_s46_v4v6bind) + prefix6len) 399 | continue; 400 | 401 | memcpy(&in6, bind->bind_ipv6_prefix, prefix6len); 402 | 403 | inet_ntop(AF_INET, &bind->ipv4_address, buf4, sizeof(buf4)); 404 | inet_ntop(AF_INET6, &in6, buf6, sizeof(buf6)); 405 | 406 | fprintf(fp, "type=%s,prefix4len=32,prefix6len=%d,ipv4prefix=%s,ipv6prefix=%s,", 407 | type, bind->bindprefix6_len, buf4, buf6); 408 | 409 | s46_to_env_portparams(&bind->bind_ipv6_prefix[prefix6len], 410 | olen - sizeof(*bind) - prefix6len, fp); 411 | 412 | dhcpv6_for_each_option(data, &data[len], otype, olen, odata) { 413 | if (otype == DHCPV6_OPT_S46_BR && olen == sizeof(struct in6_addr)) { 414 | inet_ntop(AF_INET6, odata, buf6, sizeof(buf6)); 415 | fprintf(fp, "br=%s,", buf6); 416 | } 417 | } 418 | 419 | fputc(' ', fp); 420 | } 421 | } 422 | 423 | fclose(fp); 424 | putenv(str); 425 | } 426 | 427 | void script_call(const char *status, int delay, bool resume) 428 | { 429 | time_t now = odhcp6c_get_milli_time() / 1000; 430 | bool running_script = false; 431 | 432 | if (!argv[0]) 433 | return; 434 | 435 | if (running) { 436 | time_t diff = now - started; 437 | 438 | kill(running, SIGTERM); 439 | 440 | if (diff > delay) 441 | delay -= diff; 442 | else 443 | delay = 0; 444 | 445 | running_script = true; 446 | } 447 | 448 | if (resume || !running_script || !action[0]) 449 | strncpy(action, status, sizeof(action) - 1); 450 | 451 | pid_t pid = fork(); 452 | 453 | if (pid > 0) { 454 | running = pid; 455 | started = now; 456 | 457 | if (!resume) 458 | action[0] = 0; 459 | 460 | } else if (pid == 0) { 461 | size_t dns_len, search_len, custom_len, sntp_ip_len, ntp_ip_len, ntp_dns_len; 462 | size_t sip_ip_len, sip_fqdn_len, aftr_name_len, addr_len; 463 | size_t s46_mapt_len, s46_mape_len, s46_lw_len, passthru_len; 464 | size_t capt_port_ra_len, capt_port_dhcpv6_len; 465 | 466 | signal(SIGTERM, SIG_DFL); 467 | if (delay > 0) { 468 | sleep(delay); 469 | odhcp6c_expire(false); 470 | } 471 | 472 | struct in6_addr *addr = odhcp6c_get_state(STATE_SERVER_ADDR, &addr_len); 473 | struct in6_addr *dns = odhcp6c_get_state(STATE_DNS, &dns_len); 474 | uint8_t *search = odhcp6c_get_state(STATE_SEARCH, &search_len); 475 | uint8_t *custom = odhcp6c_get_state(STATE_CUSTOM_OPTS, &custom_len); 476 | struct in6_addr *sntp = odhcp6c_get_state(STATE_SNTP_IP, &sntp_ip_len); 477 | struct in6_addr *ntp = odhcp6c_get_state(STATE_NTP_IP, &ntp_ip_len); 478 | uint8_t *ntp_dns = odhcp6c_get_state(STATE_NTP_FQDN, &ntp_dns_len); 479 | struct in6_addr *sip = odhcp6c_get_state(STATE_SIP_IP, &sip_ip_len); 480 | uint8_t *sip_fqdn = odhcp6c_get_state(STATE_SIP_FQDN, &sip_fqdn_len); 481 | uint8_t *aftr_name = odhcp6c_get_state(STATE_AFTR_NAME, &aftr_name_len); 482 | uint8_t *s46_mapt = odhcp6c_get_state(STATE_S46_MAPT, &s46_mapt_len); 483 | uint8_t *s46_mape = odhcp6c_get_state(STATE_S46_MAPE, &s46_mape_len); 484 | uint8_t *s46_lw = odhcp6c_get_state(STATE_S46_LW, &s46_lw_len); 485 | uint8_t *capt_port_ra = odhcp6c_get_state(STATE_CAPT_PORT_RA, &capt_port_ra_len); 486 | uint8_t *capt_port_dhcpv6 = odhcp6c_get_state(STATE_CAPT_PORT_DHCPV6, &capt_port_dhcpv6_len); 487 | uint8_t *passthru = odhcp6c_get_state(STATE_PASSTHRU, &passthru_len); 488 | 489 | size_t prefix_len, address_len, ra_pref_len, 490 | ra_route_len, ra_dns_len, ra_search_len; 491 | uint8_t *prefix = odhcp6c_get_state(STATE_IA_PD, &prefix_len); 492 | uint8_t *address = odhcp6c_get_state(STATE_IA_NA, &address_len); 493 | uint8_t *ra_pref = odhcp6c_get_state(STATE_RA_PREFIX, &ra_pref_len); 494 | uint8_t *ra_route = odhcp6c_get_state(STATE_RA_ROUTE, &ra_route_len); 495 | uint8_t *ra_dns = odhcp6c_get_state(STATE_RA_DNS, &ra_dns_len); 496 | uint8_t *ra_search = odhcp6c_get_state(STATE_RA_SEARCH, &ra_search_len); 497 | 498 | /* RFC8910 §3 */ 499 | if (capt_port_ra_len > 0 && capt_port_dhcpv6_len > 0) { 500 | if (capt_port_ra_len != capt_port_dhcpv6_len || 501 | !memcmp(capt_port_dhcpv6, capt_port_ra, capt_port_dhcpv6_len)) 502 | error( 503 | "%s received via different vectors differ: preferring URI from DHCPv6", 504 | CAPT_PORT_URI_STR); 505 | } 506 | 507 | ipv6_to_env("SERVER", addr, addr_len / sizeof(*addr)); 508 | ipv6_to_env("RDNSS", dns, dns_len / sizeof(*dns)); 509 | ipv6_to_env("SNTP_IP", sntp, sntp_ip_len / sizeof(*sntp)); 510 | ipv6_to_env("NTP_IP", ntp, ntp_ip_len / sizeof(*ntp)); 511 | fqdn_to_env("NTP_FQDN", ntp_dns, ntp_dns_len); 512 | ipv6_to_env("SIP_IP", sip, sip_ip_len / sizeof(*sip)); 513 | fqdn_to_env("DOMAINS", search, search_len); 514 | fqdn_to_env("SIP_DOMAIN", sip_fqdn, sip_fqdn_len); 515 | fqdn_to_env("AFTR", aftr_name, aftr_name_len); 516 | s46_to_env(STATE_S46_MAPE, s46_mape, s46_mape_len); 517 | s46_to_env(STATE_S46_MAPT, s46_mapt, s46_mapt_len); 518 | s46_to_env(STATE_S46_LW, s46_lw, s46_lw_len); 519 | if (capt_port_dhcpv6_len > 0) 520 | string_to_env(CAPT_PORT_URI_STR, capt_port_dhcpv6, capt_port_dhcpv6_len); 521 | else if (capt_port_ra_len > 0) 522 | string_to_env(CAPT_PORT_URI_STR, capt_port_ra, capt_port_ra_len); 523 | bin_to_env(custom, custom_len); 524 | 525 | if (odhcp6c_is_bound()) { 526 | entry_to_env("PREFIXES", prefix, prefix_len, ENTRY_PREFIX); 527 | entry_to_env("ADDRESSES", address, address_len, ENTRY_ADDRESS); 528 | } 529 | 530 | entry_to_env("RA_ADDRESSES", ra_pref, ra_pref_len, ENTRY_ADDRESS); 531 | entry_to_env("RA_ROUTES", ra_route, ra_route_len, ENTRY_ROUTE); 532 | entry_to_env("RA_DNS", ra_dns, ra_dns_len, ENTRY_HOST); 533 | search_to_env("RA_DOMAINS", ra_search, ra_search_len); 534 | 535 | int_to_env("RA_HOPLIMIT", ra_get_hoplimit()); 536 | int_to_env("RA_MTU", ra_get_mtu()); 537 | int_to_env("RA_REACHABLE", ra_get_reachable()); 538 | int_to_env("RA_RETRANSMIT", ra_get_retransmit()); 539 | 540 | char *buf = malloc(10 + passthru_len * 2); 541 | strncpy(buf, "PASSTHRU=", 10); 542 | script_hexlify(&buf[9], passthru, passthru_len); 543 | putenv(buf); 544 | 545 | execv(argv[0], argv); 546 | _exit(128); 547 | } 548 | } 549 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License 307 | along with this program; if not, write to the Free Software 308 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 309 | 310 | 311 | Also add information on how to contact you by electronic and paper mail. 312 | 313 | If the program is interactive, make it output a short notice like this 314 | when it starts in an interactive mode: 315 | 316 | Gnomovision version 69, Copyright (C) year name of author 317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 318 | This is free software, and you are welcome to redistribute it 319 | under certain conditions; type `show c' for details. 320 | 321 | The hypothetical commands `show w' and `show c' should show the appropriate 322 | parts of the General Public License. Of course, the commands you use may 323 | be called something other than `show w' and `show c'; they could even be 324 | mouse-clicks or menu items--whatever suits your program. 325 | 326 | You should also get your employer (if you work as a programmer) or your 327 | school, if any, to sign a "copyright disclaimer" for the program, if 328 | necessary. Here is a sample; alter the names: 329 | 330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 331 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 332 | 333 | , 1 April 1989 334 | Ty Coon, President of Vice 335 | 336 | This General Public License does not permit incorporating your program into 337 | proprietary programs. If your program is a subroutine library, you may 338 | consider it more useful to permit linking proprietary applications with the 339 | library. If this is what you want to do, use the GNU Library General 340 | Public License instead of this License. 341 | -------------------------------------------------------------------------------- /src/config.c: -------------------------------------------------------------------------------- 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 63 | #include 64 | #include 65 | #include 66 | #include 67 | 68 | #include "config.h" 69 | #include "odhcp6c.h" 70 | 71 | #define ARRAY_SEP " ,\t" 72 | 73 | static struct config_dhcp config_dhcp; 74 | 75 | struct config_dhcp *config_dhcp_get(void) { 76 | return &config_dhcp; 77 | } 78 | 79 | void config_dhcp_reset(void) { 80 | config_dhcp.log_level = LOG_WARNING; 81 | config_dhcp.release = true; 82 | config_dhcp.dscp = 0; 83 | config_dhcp.sk_prio = 0; 84 | config_dhcp.stateful_only_mode = false; 85 | config_dhcp.ia_na_mode = IA_MODE_TRY; 86 | config_dhcp.ia_pd_mode = IA_MODE_NONE; 87 | config_dhcp.client_options = DHCPV6_CLIENT_FQDN | DHCPV6_ACCEPT_RECONFIGURE; 88 | config_dhcp.allow_slaac_only = true; 89 | config_dhcp.oro_user_cnt = 0; 90 | memset(config_dhcp.message_rtx, 0, sizeof(config_dhcp.message_rtx)); 91 | config_dhcp.message_rtx[CONFIG_DHCP_SOLICIT].delay_max = DHCPV6_MAX_DELAY; 92 | config_dhcp.message_rtx[CONFIG_DHCP_SOLICIT].timeout_init = DHCPV6_SOL_INIT_RT; 93 | config_dhcp.message_rtx[CONFIG_DHCP_SOLICIT].timeout_max = DHCPV6_SOL_MAX_RT; 94 | config_dhcp.message_rtx[CONFIG_DHCP_REQUEST].timeout_init = DHCPV6_REQ_INIT_RT; 95 | config_dhcp.message_rtx[CONFIG_DHCP_REQUEST].timeout_max = DHCPV6_REQ_MAX_RT; 96 | config_dhcp.message_rtx[CONFIG_DHCP_REQUEST].rc_max = DHCPV6_REQ_MAX_RC; 97 | config_dhcp.message_rtx[CONFIG_DHCP_RENEW].timeout_init = DHCPV6_REN_INIT_RT; 98 | config_dhcp.message_rtx[CONFIG_DHCP_RENEW].timeout_max = DHCPV6_REN_MAX_RT; 99 | config_dhcp.message_rtx[CONFIG_DHCP_REBIND].timeout_init = DHCPV6_REB_INIT_RT; 100 | config_dhcp.message_rtx[CONFIG_DHCP_REBIND].timeout_max = DHCPV6_REB_MAX_RT; 101 | config_dhcp.message_rtx[CONFIG_DHCP_INFO_REQ].delay_max = DHCPV6_MAX_DELAY; 102 | config_dhcp.message_rtx[CONFIG_DHCP_INFO_REQ].timeout_init = DHCPV6_INF_INIT_RT; 103 | config_dhcp.message_rtx[CONFIG_DHCP_INFO_REQ].timeout_max = DHCPV6_INF_MAX_RT; 104 | config_dhcp.message_rtx[CONFIG_DHCP_RELEASE].timeout_init = DHCPV6_REL_INIT_RT; 105 | config_dhcp.message_rtx[CONFIG_DHCP_RELEASE].rc_max = DHCPV6_REL_MAX_RC; 106 | config_dhcp.message_rtx[CONFIG_DHCP_DECLINE].timeout_init = DHCPV6_DEC_INIT_RT; 107 | config_dhcp.message_rtx[CONFIG_DHCP_DECLINE].rc_max = DHCPV6_DEC_MAX_RC; 108 | config_dhcp.irt_default = DHCPV6_IRT_DEFAULT; 109 | config_dhcp.irt_min = DHCPV6_IRT_MIN; 110 | config_dhcp.rand_factor = DHCPV6_RAND_FACTOR; 111 | config_dhcp.auth_protocol = AUTH_PROT_RKAP; 112 | free(config_dhcp.auth_token); 113 | config_dhcp.auth_token = NULL; 114 | } 115 | 116 | void config_set_release(bool enable) { 117 | config_dhcp.release = enable; 118 | } 119 | 120 | bool config_set_dscp(unsigned int value) { 121 | if (value > 63) { 122 | error("Invalid DSCP value"); 123 | return false; 124 | } 125 | config_dhcp.dscp = value; 126 | return true; 127 | } 128 | 129 | bool config_set_sk_priority(unsigned int priority) { 130 | if (priority > 6) { 131 | error("Invalid SK priority value"); 132 | return false; 133 | } 134 | config_dhcp.sk_prio = priority; 135 | return true; 136 | } 137 | 138 | void config_set_client_options(enum dhcpv6_config option, bool enable) { 139 | if (enable) { 140 | config_dhcp.client_options |= option; 141 | } else { 142 | config_dhcp.client_options &= ~option; 143 | } 144 | } 145 | 146 | bool config_set_request_addresses(char* mode) { 147 | if (!strcmp(mode, "force")) { 148 | config_dhcp.ia_na_mode = IA_MODE_FORCE; 149 | config_dhcp.allow_slaac_only = false; 150 | } else if (!strcmp(mode, "none")) { 151 | config_dhcp.ia_na_mode = IA_MODE_NONE; 152 | } else if (!strcmp(mode, "try")) { 153 | config_dhcp.ia_na_mode = IA_MODE_TRY; 154 | } else { 155 | error("Invalid IA_NA Request Addresses mode"); 156 | return false; 157 | } 158 | 159 | return true; 160 | } 161 | 162 | bool config_set_request_prefix(unsigned int length, unsigned int id) { 163 | struct odhcp6c_request_prefix prefix = {0}; 164 | 165 | odhcp6c_clear_state(STATE_IA_PD_INIT); 166 | 167 | if (config_dhcp.ia_pd_mode != IA_MODE_FORCE) 168 | config_dhcp.ia_pd_mode = length > 128 ? IA_MODE_NONE : IA_MODE_TRY; 169 | 170 | if (length <= 128) { 171 | prefix.length = length; 172 | prefix.iaid = htonl(id); 173 | 174 | if (odhcp6c_add_state(STATE_IA_PD_INIT, &prefix, sizeof(prefix))) { 175 | error("Failed to set request IPv6-Prefix"); 176 | return false; 177 | } 178 | } 179 | 180 | return true; 181 | } 182 | 183 | void config_set_force_prefix(bool enable) { 184 | if (enable) { 185 | config_dhcp.allow_slaac_only = false; 186 | config_dhcp.ia_pd_mode = IA_MODE_FORCE; 187 | } else { 188 | config_dhcp.ia_pd_mode = IA_MODE_NONE; 189 | } 190 | } 191 | 192 | void config_set_stateful_only(bool enable) { 193 | config_dhcp.stateful_only_mode = enable; 194 | } 195 | 196 | void config_set_allow_slaac_only(bool value) { 197 | config_dhcp.allow_slaac_only = value; 198 | } 199 | 200 | void config_clear_requested_options(void) { 201 | config_dhcp.oro_user_cnt = 0; 202 | } 203 | 204 | bool config_add_requested_options(unsigned int option) { 205 | if (option > UINT16_MAX) { 206 | error("Invalid requested option"); 207 | return false; 208 | } 209 | 210 | option = htons(option); 211 | if (odhcp6c_insert_state(STATE_ORO, 0, &option, 2)) { 212 | error("Failed to set requested option"); 213 | return false; 214 | } 215 | config_dhcp.oro_user_cnt++; 216 | return true; 217 | } 218 | 219 | void config_clear_send_options(void) { 220 | odhcp6c_clear_state(STATE_OPTS); 221 | } 222 | 223 | bool config_add_send_options(char* option) { 224 | return (config_parse_opt(option) == 0); 225 | } 226 | 227 | bool config_set_rtx_delay_max(enum config_dhcp_msg msg, unsigned int value) 228 | { 229 | if (msg >= CONFIG_DHCP_MAX || value > UINT8_MAX) { 230 | error("Invalid retransmission Maximum Delay value"); 231 | return false; 232 | } 233 | config_dhcp.message_rtx[msg].delay_max = value; 234 | return true; 235 | } 236 | 237 | bool config_set_rtx_timeout_init(enum config_dhcp_msg msg, unsigned int value) 238 | { 239 | if (msg >= CONFIG_DHCP_MAX || value > UINT8_MAX || value == 0) { 240 | error("Invalid retransmission Initial Timeout value"); 241 | return false; 242 | } 243 | config_dhcp.message_rtx[msg].timeout_init = value; 244 | return true; 245 | } 246 | 247 | bool config_set_rtx_timeout_max(enum config_dhcp_msg msg, unsigned int value) 248 | { 249 | if (msg >= CONFIG_DHCP_MAX || value > UINT16_MAX) { 250 | error("Invalid retransmission Maximum Timeout value"); 251 | return false; 252 | } 253 | config_dhcp.message_rtx[msg].timeout_max = value; 254 | return true; 255 | } 256 | 257 | bool config_set_rtx_rc_max(enum config_dhcp_msg msg, unsigned int value) 258 | { 259 | if (msg >= CONFIG_DHCP_MAX || value > UINT8_MAX) { 260 | error("Invalid retransmission Retry Attempt value"); 261 | return false; 262 | } 263 | config_dhcp.message_rtx[msg].rc_max = value; 264 | return true; 265 | } 266 | 267 | bool config_set_irt_default(unsigned int value) 268 | { 269 | if (value == 0) { 270 | error("Invalid Default Information Refresh Time value"); 271 | return false; 272 | } 273 | config_dhcp.irt_default = value; 274 | return true; 275 | } 276 | 277 | bool config_set_irt_min(unsigned int value) 278 | { 279 | if (value == 0) { 280 | error("Invalid Minimum Information Refresh Time value"); 281 | return false; 282 | } 283 | config_dhcp.irt_min = value; 284 | return true; 285 | } 286 | 287 | bool config_set_rand_factor(unsigned int value) 288 | { 289 | if (value > 999 || value < 10) { 290 | error("Invalid Random Factor value"); 291 | return false; 292 | } 293 | config_dhcp.rand_factor = value; 294 | return true; 295 | } 296 | 297 | bool config_set_auth_protocol(const char* protocol) 298 | { 299 | if (!strcmp(protocol, "None")) { 300 | config_dhcp.auth_protocol = AUTH_PROT_NONE; 301 | } else if (!strcmp(protocol, "ConfigurationToken")) { 302 | config_dhcp.auth_protocol = AUTH_PROT_TOKEN; 303 | } else if (!strcmp(protocol, "ReconfigureKeyAuthentication")) { 304 | config_dhcp.auth_protocol = AUTH_PROT_RKAP; 305 | } else { 306 | error("Invalid Authentication protocol"); 307 | return false; 308 | } 309 | 310 | return true; 311 | } 312 | 313 | bool config_set_auth_token(const char* token) 314 | { 315 | free(config_dhcp.auth_token); 316 | 317 | config_dhcp.auth_token = strdup(token); 318 | return config_dhcp.auth_token != NULL; 319 | } 320 | 321 | static int config_parse_opt_u8(const char *src, uint8_t **dst) 322 | { 323 | int len = strlen(src); 324 | 325 | *dst = realloc(*dst, len/2); 326 | if (!*dst) 327 | return -1; 328 | 329 | return script_unhexlify(*dst, len, src); 330 | } 331 | 332 | static int config_parse_opt_string(const char *src, uint8_t **dst, const bool array) 333 | { 334 | int o_len = 0; 335 | char *sep = strpbrk(src, ARRAY_SEP); 336 | 337 | if (sep && !array) 338 | return -1; 339 | 340 | do { 341 | if (sep) { 342 | *sep = 0; 343 | sep++; 344 | } 345 | 346 | int len = strlen(src); 347 | 348 | *dst = realloc(*dst, o_len + len); 349 | if (!*dst) 350 | return -1; 351 | 352 | memcpy(&((*dst)[o_len]), src, len); 353 | 354 | o_len += len; 355 | src = sep; 356 | 357 | if (sep) 358 | sep = strpbrk(src, ARRAY_SEP); 359 | } while (src); 360 | 361 | return o_len; 362 | } 363 | 364 | static int config_parse_opt_dns_string(const char *src, uint8_t **dst, const bool array) 365 | { 366 | int o_len = 0; 367 | char *sep = strpbrk(src, ARRAY_SEP); 368 | 369 | if (sep && !array) 370 | return -1; 371 | 372 | do { 373 | uint8_t tmp[256]; 374 | 375 | if (sep) { 376 | *sep = 0; 377 | sep++; 378 | } 379 | 380 | int len = dn_comp(src, tmp, sizeof(tmp), NULL, NULL); 381 | if (len < 0) 382 | return -1; 383 | 384 | *dst = realloc(*dst, o_len + len); 385 | if (!*dst) 386 | return -1; 387 | 388 | memcpy(&((*dst)[o_len]), tmp, len); 389 | 390 | o_len += len; 391 | src = sep; 392 | 393 | if (sep) 394 | sep = strpbrk(src, ARRAY_SEP); 395 | } while (src); 396 | 397 | return o_len; 398 | } 399 | 400 | static int config_parse_opt_ip6(const char *src, uint8_t **dst, const bool array) 401 | { 402 | int o_len = 0; 403 | char *sep = strpbrk(src, ARRAY_SEP); 404 | 405 | if (sep && !array) 406 | return -1; 407 | 408 | do { 409 | int len = sizeof(struct in6_addr); 410 | 411 | if (sep) { 412 | *sep = 0; 413 | sep++; 414 | } 415 | 416 | *dst = realloc(*dst, o_len + len); 417 | if (!*dst) 418 | return -1; 419 | 420 | if (inet_pton(AF_INET6, src, &((*dst)[o_len])) < 1) 421 | return -1; 422 | 423 | o_len += len; 424 | src = sep; 425 | 426 | if (sep) 427 | sep = strpbrk(src, ARRAY_SEP); 428 | } while (src); 429 | 430 | return o_len; 431 | } 432 | 433 | static int config_parse_opt_user_class(const char *src, uint8_t **dst, const bool array) 434 | { 435 | int o_len = 0; 436 | char *sep = strpbrk(src, ARRAY_SEP); 437 | 438 | if (sep && !array) 439 | return -1; 440 | 441 | do { 442 | if (sep) { 443 | *sep = 0; 444 | sep++; 445 | } 446 | uint16_t str_len = strlen(src); 447 | 448 | *dst = realloc(*dst, o_len + str_len + 2); 449 | if (!*dst) 450 | return -1; 451 | 452 | struct user_class { 453 | uint16_t len; 454 | uint8_t data[]; 455 | } *e = (struct user_class *)&((*dst)[o_len]); 456 | 457 | e->len = ntohs(str_len); 458 | memcpy(e->data, src, str_len); 459 | 460 | o_len += str_len + 2; 461 | src = sep; 462 | 463 | if (sep) 464 | sep = strpbrk(src, ARRAY_SEP); 465 | } while (src); 466 | 467 | return o_len; 468 | } 469 | 470 | static uint8_t *config_state_find_opt(const uint16_t code) 471 | { 472 | size_t opts_len; 473 | uint8_t *odata, *opts = odhcp6c_get_state(STATE_OPTS, &opts_len); 474 | uint16_t otype, olen; 475 | 476 | dhcpv6_for_each_option(opts, &opts[opts_len], otype, olen, odata) { 477 | if (otype == code) 478 | return &odata[-4]; 479 | } 480 | 481 | return NULL; 482 | } 483 | 484 | int config_add_opt(const uint16_t code, const uint8_t *data, const uint16_t len) 485 | { 486 | struct { 487 | uint16_t code; 488 | uint16_t len; 489 | } opt_hdr = { htons(code), htons(len) }; 490 | 491 | if (config_state_find_opt(code)) 492 | return -1; 493 | 494 | if (odhcp6c_add_state(STATE_OPTS, &opt_hdr, sizeof(opt_hdr)) || 495 | odhcp6c_add_state(STATE_OPTS, data, len)) { 496 | error("Failed to add option %hu", code); 497 | return 1; 498 | } 499 | 500 | return 0; 501 | } 502 | 503 | int config_parse_opt_data(const char *data, uint8_t **dst, const unsigned int type, 504 | const bool array) 505 | { 506 | int ret = 0; 507 | 508 | switch (type) { 509 | case OPT_U8: 510 | ret = config_parse_opt_u8(data, dst); 511 | break; 512 | 513 | case OPT_STR: 514 | ret = config_parse_opt_string(data, dst, array); 515 | break; 516 | 517 | case OPT_DNS_STR: 518 | ret = config_parse_opt_dns_string(data, dst, array); 519 | break; 520 | 521 | case OPT_IP6: 522 | ret = config_parse_opt_ip6(data, dst, array); 523 | break; 524 | 525 | case OPT_USER_CLASS: 526 | ret = config_parse_opt_user_class(data, dst, array); 527 | break; 528 | 529 | default: 530 | ret = -1; 531 | break; 532 | } 533 | 534 | return ret; 535 | } 536 | 537 | int config_parse_opt(const char *opt) 538 | { 539 | uint32_t optn; 540 | char *data; 541 | uint8_t *payload = NULL; 542 | int payload_len; 543 | unsigned int type = OPT_U8; 544 | bool array = false; 545 | struct odhcp6c_opt *dopt = NULL; 546 | int ret = -1; 547 | 548 | data = strpbrk(opt, ":"); 549 | if (!data) 550 | return -1; 551 | 552 | *data = '\0'; 553 | data++; 554 | 555 | if (strlen(opt) == 0 || strlen(data) == 0) 556 | return -1; 557 | 558 | dopt = odhcp6c_find_opt_by_name(opt); 559 | if (!dopt) { 560 | char *e; 561 | optn = strtoul(opt, &e, 0); 562 | if (*e || e == opt || optn > USHRT_MAX) 563 | return -1; 564 | 565 | dopt = odhcp6c_find_opt(optn); 566 | } else { 567 | optn = dopt->code; 568 | } 569 | 570 | /* Check if the type for the content is well-known */ 571 | if (dopt) { 572 | /* Refuse internal options */ 573 | if (dopt->flags & OPT_INTERNAL) 574 | return -1; 575 | 576 | type = dopt->flags & OPT_MASK_SIZE; 577 | array = ((dopt->flags & OPT_ARRAY) == OPT_ARRAY) ? true : false; 578 | } else if (data[0] == '"' || data[0] == '\'') { 579 | char *end = strrchr(data + 1, data[0]); 580 | 581 | if (end && (end == (data + strlen(data) - 1))) { 582 | /* Raw option is specified as a string */ 583 | type = OPT_STR; 584 | data++; 585 | *end = '\0'; 586 | } 587 | } 588 | 589 | payload_len = config_parse_opt_data(data, &payload, type, array); 590 | if (payload_len > 0) 591 | ret = config_add_opt(optn, payload, payload_len); 592 | 593 | free(payload); 594 | 595 | return ret; 596 | } 597 | 598 | void config_apply_dhcp_rtx(struct dhcpv6_retx* dhcpv6_retx) 599 | { 600 | dhcpv6_retx[DHCPV6_MSG_SOLICIT].max_delay = config_dhcp.message_rtx[CONFIG_DHCP_SOLICIT].delay_max; 601 | dhcpv6_retx[DHCPV6_MSG_SOLICIT].init_timeo = config_dhcp.message_rtx[CONFIG_DHCP_SOLICIT].timeout_init; 602 | dhcpv6_retx[DHCPV6_MSG_SOLICIT].max_timeo = config_dhcp.message_rtx[CONFIG_DHCP_SOLICIT].timeout_max; 603 | dhcpv6_retx[DHCPV6_MSG_REQUEST].init_timeo = config_dhcp.message_rtx[CONFIG_DHCP_REQUEST].timeout_init; 604 | dhcpv6_retx[DHCPV6_MSG_REQUEST].max_timeo = config_dhcp.message_rtx[CONFIG_DHCP_REQUEST].timeout_max; 605 | dhcpv6_retx[DHCPV6_MSG_REQUEST].max_rc = config_dhcp.message_rtx[CONFIG_DHCP_REQUEST].rc_max; 606 | dhcpv6_retx[DHCPV6_MSG_RENEW].init_timeo = config_dhcp.message_rtx[CONFIG_DHCP_RENEW].timeout_init; 607 | dhcpv6_retx[DHCPV6_MSG_RENEW].max_timeo = config_dhcp.message_rtx[CONFIG_DHCP_RENEW].timeout_max; 608 | dhcpv6_retx[DHCPV6_MSG_REBIND].init_timeo = config_dhcp.message_rtx[CONFIG_DHCP_REBIND].timeout_init; 609 | dhcpv6_retx[DHCPV6_MSG_REBIND].max_timeo = config_dhcp.message_rtx[CONFIG_DHCP_REBIND].timeout_max; 610 | dhcpv6_retx[DHCPV6_MSG_INFO_REQ].max_delay = config_dhcp.message_rtx[CONFIG_DHCP_INFO_REQ].delay_max; 611 | dhcpv6_retx[DHCPV6_MSG_INFO_REQ].init_timeo = config_dhcp.message_rtx[CONFIG_DHCP_INFO_REQ].timeout_init; 612 | dhcpv6_retx[DHCPV6_MSG_INFO_REQ].max_timeo = config_dhcp.message_rtx[CONFIG_DHCP_INFO_REQ].timeout_max; 613 | dhcpv6_retx[DHCPV6_MSG_RELEASE].init_timeo = config_dhcp.message_rtx[CONFIG_DHCP_RELEASE].timeout_init; 614 | dhcpv6_retx[DHCPV6_MSG_RELEASE].max_rc = config_dhcp.message_rtx[CONFIG_DHCP_RELEASE].rc_max; 615 | dhcpv6_retx[DHCPV6_MSG_DECLINE].init_timeo = config_dhcp.message_rtx[CONFIG_DHCP_DECLINE].timeout_init; 616 | dhcpv6_retx[DHCPV6_MSG_DECLINE].max_rc = config_dhcp.message_rtx[CONFIG_DHCP_DECLINE].rc_max; 617 | } 618 | -------------------------------------------------------------------------------- /src/ra.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2012-2014 Steven Barth 3 | * Copyright (C) 2017-2018 Hans Dedecker 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License v2 as published by 7 | * the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | */ 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include "odhcp6c.h" 37 | #include "ra.h" 38 | 39 | #ifndef SOL_NETLINK 40 | #define SOL_NETLINK 270 41 | #endif 42 | 43 | #ifndef NETLINK_ADD_MEMBERSHIP 44 | #define NETLINK_ADD_MEMBERSHIP 1 45 | #endif 46 | 47 | #ifndef IFF_LOWER_UP 48 | #define IFF_LOWER_UP 0x10000 49 | #endif 50 | 51 | static bool nocarrier = false; 52 | static bool ptp_link = false; 53 | 54 | static int sock = -1, rtnl = -1; 55 | static int if_index = 0; 56 | static char if_name[IF_NAMESIZE] = {0}; 57 | static volatile int rs_attempt = 0; 58 | static struct in6_addr ra_addr = IN6ADDR_ANY_INIT; 59 | static unsigned int ra_options = 0; 60 | static unsigned int ra_holdoff_interval = 0; 61 | static ra_ifid_mode_t ra_ifid_mode = RA_IFID_LLA; 62 | static int ra_hoplimit = 0; 63 | static int ra_mtu = 0; 64 | static int ra_reachable = 0; 65 | static int ra_retransmit = 0; 66 | 67 | struct { 68 | struct icmp6_hdr hdr; 69 | struct icmpv6_opt lladdr; 70 | } rs = { 71 | .hdr = {ND_ROUTER_SOLICIT, 0, 0, {{0}}}, 72 | .lladdr = {ND_OPT_SOURCE_LINKADDR, 1, {0}}, 73 | }; 74 | 75 | static void ra_send_rs(_o_unused int signal); 76 | 77 | int ra_init(const char *ifname, const struct in6_addr *ifid, 78 | ra_ifid_mode_t ifid_mode, unsigned int options, 79 | unsigned int holdoff_interval) 80 | { 81 | struct ifreq ifr; 82 | 83 | ra_options = options; 84 | ra_holdoff_interval = holdoff_interval; 85 | ra_ifid_mode = ifid_mode; 86 | 87 | const pid_t ourpid = getpid(); 88 | sock = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC, IPPROTO_ICMPV6); 89 | if (sock < 0) 90 | goto failure; 91 | 92 | memset(&ifr, 0, sizeof(ifr)); 93 | strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name) - 1); 94 | if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) 95 | goto failure; 96 | 97 | ptp_link = !!(ifr.ifr_flags & IFF_POINTOPOINT); 98 | 99 | memset(&ifr, 0, sizeof(ifr)); 100 | strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name) - 1); 101 | if (ioctl(sock, SIOCGIFINDEX, &ifr) < 0) 102 | goto failure; 103 | 104 | strncpy(if_name, ifname, sizeof(if_name) - 1); 105 | if_index = ifr.ifr_ifindex; 106 | ra_addr = *ifid; 107 | 108 | rtnl = socket(AF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_ROUTE); 109 | if (rtnl < 0) 110 | goto failure; 111 | 112 | struct sockaddr_nl rtnl_kernel = { .nl_family = AF_NETLINK }; 113 | if (connect(rtnl, (const struct sockaddr*)&rtnl_kernel, sizeof(rtnl_kernel)) < 0) 114 | goto failure; 115 | 116 | int val = RTNLGRP_LINK; 117 | if (setsockopt(rtnl, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, &val, sizeof(val)) < 0) 118 | goto failure; 119 | 120 | if (fcntl(rtnl, F_SETOWN, ourpid) < 0) 121 | goto failure; 122 | 123 | if (fcntl(rtnl, F_SETFL, fcntl(sock, F_GETFL) | O_ASYNC) < 0) 124 | goto failure; 125 | 126 | struct { 127 | struct nlmsghdr hdr; 128 | struct ifinfomsg ifi; 129 | } req = { 130 | .hdr = {sizeof(req), RTM_GETLINK, NLM_F_REQUEST, 1, 0}, 131 | .ifi = {.ifi_index = if_index} 132 | }; 133 | if (send(rtnl, &req, sizeof(req), 0) < 0) 134 | goto failure; 135 | 136 | ra_link_up(); 137 | 138 | // Filter ICMPv6 package types 139 | struct icmp6_filter filt; 140 | ICMP6_FILTER_SETBLOCKALL(&filt); 141 | ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filt); 142 | if (setsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER, &filt, sizeof(filt)) < 0) 143 | goto failure; 144 | 145 | // Bind to all-nodes 146 | struct ipv6_mreq an = {ALL_IPV6_NODES, if_index}; 147 | if (setsockopt(sock, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &an, sizeof(an)) < 0) 148 | goto failure; 149 | 150 | // Let the kernel compute our checksums 151 | val = 2; 152 | if (setsockopt(sock, IPPROTO_RAW, IPV6_CHECKSUM, &val, sizeof(val)) < 0) 153 | goto failure; 154 | 155 | // This is required by RFC 4861 156 | val = 255; 157 | if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, sizeof(val)) < 0) 158 | goto failure; 159 | 160 | // Receive multicast hops 161 | val = 1; 162 | if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &val, sizeof(val)) < 0) 163 | goto failure; 164 | 165 | // Bind to one device 166 | if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname)) < 0) 167 | goto failure; 168 | 169 | // Add async-mode 170 | if (fcntl(sock, F_SETOWN, ourpid) < 0) 171 | goto failure; 172 | 173 | val = fcntl(sock, F_GETFL); 174 | if (val < 0) 175 | goto failure; 176 | 177 | if (fcntl(sock, F_SETFL, val | O_ASYNC) < 0) 178 | goto failure; 179 | 180 | // flush any received messages that may not have had all the options (particularly the bind) applied to them 181 | uint8_t buf[1500] _o_aligned(4); 182 | union { 183 | struct cmsghdr hdr; 184 | uint8_t buf[CMSG_SPACE(sizeof(int))]; 185 | } cmsg_buf; 186 | 187 | while (true) { 188 | struct sockaddr_in6 from; 189 | struct iovec iov = {buf, sizeof(buf)}; 190 | struct msghdr msg = { 191 | .msg_name = (void *) &from, 192 | .msg_namelen = sizeof(from), 193 | .msg_iov = &iov, 194 | .msg_iovlen = 1, 195 | .msg_control = cmsg_buf.buf, 196 | .msg_controllen = sizeof(cmsg_buf), 197 | .msg_flags = 0 198 | }; 199 | 200 | ssize_t len = recvmsg(sock, &msg, MSG_DONTWAIT); 201 | if (len <= 0) 202 | break; 203 | } 204 | 205 | // Send RS 206 | signal(SIGALRM, ra_send_rs); 207 | ra_send_rs(SIGALRM); 208 | 209 | return 0; 210 | 211 | failure: 212 | if (sock >= 0) 213 | close(sock); 214 | 215 | if (rtnl >= 0) 216 | close(rtnl); 217 | 218 | return -1; 219 | } 220 | 221 | static void ra_send_rs(_o_unused int signal) 222 | { 223 | const struct sockaddr_in6 dest = {AF_INET6, 0, 0, ALL_IPV6_ROUTERS, if_index}; 224 | const struct icmpv6_opt llnull = {ND_OPT_SOURCE_LINKADDR, 1, {0}}; 225 | size_t len; 226 | 227 | if ((rs_attempt % 2 == 0) && memcmp(&rs.lladdr, &llnull, sizeof(llnull))) 228 | len = sizeof(rs); 229 | else 230 | len = sizeof(struct icmp6_hdr); 231 | 232 | if (sendto(sock, &rs, len, MSG_DONTWAIT, (struct sockaddr*)&dest, sizeof(dest)) < 0) 233 | error("Failed to send RS (%s)", strerror(errno)); 234 | 235 | if (++rs_attempt <= 3) 236 | alarm(4); 237 | } 238 | 239 | static int16_t pref_to_priority(uint8_t flags) 240 | { 241 | flags = (flags >> 3) & 0x03; 242 | 243 | return (flags == 0x0) ? 512 : (flags == 0x1) ? 384 : 244 | (flags == 0x3) ? 640 : -1; 245 | } 246 | 247 | bool ra_link_up(void) 248 | { 249 | static bool firstcall = true; 250 | struct { 251 | struct nlmsghdr hdr; 252 | struct ifinfomsg msg; 253 | uint8_t pad[4000]; 254 | } resp; 255 | bool ret = false; 256 | ssize_t read; 257 | 258 | do { 259 | read = recv(rtnl, &resp, sizeof(resp), MSG_DONTWAIT); 260 | 261 | if (read < 0 || !NLMSG_OK(&resp.hdr, (size_t)read) || 262 | resp.hdr.nlmsg_type != RTM_NEWLINK || 263 | resp.msg.ifi_index != if_index) 264 | continue; 265 | 266 | ssize_t alen = NLMSG_PAYLOAD(&resp.hdr, sizeof(resp.msg)); 267 | for (struct rtattr *rta = (struct rtattr*)(resp.pad); 268 | RTA_OK(rta, alen); rta = RTA_NEXT(rta, alen)) { 269 | if (rta->rta_type == IFLA_ADDRESS && 270 | RTA_PAYLOAD(rta) >= sizeof(rs.lladdr.data)) 271 | memcpy(rs.lladdr.data, RTA_DATA(rta), sizeof(rs.lladdr.data)); 272 | } 273 | 274 | bool hascarrier = resp.msg.ifi_flags & IFF_LOWER_UP; 275 | if (!firstcall && nocarrier != !hascarrier) 276 | ret = true; 277 | 278 | nocarrier = !hascarrier; 279 | firstcall = false; 280 | } while (read > 0); 281 | 282 | if (ret) { 283 | notice("carrier => %i event on %s", (int)!nocarrier, if_name); 284 | 285 | rs_attempt = 0; 286 | ra_send_rs(SIGALRM); 287 | } 288 | 289 | return ret; 290 | } 291 | 292 | static bool ra_icmpv6_valid(struct sockaddr_in6 *source, int hlim, uint8_t *data, size_t len) 293 | { 294 | struct icmp6_hdr *hdr = (struct icmp6_hdr*)data; 295 | struct icmpv6_opt *opt, *end = (struct icmpv6_opt*)&data[len]; 296 | 297 | if (hlim != 255 || len < sizeof(*hdr) || hdr->icmp6_code) 298 | return false; 299 | 300 | switch (hdr->icmp6_type) { 301 | case ND_ROUTER_ADVERT: 302 | if (!IN6_IS_ADDR_LINKLOCAL(&source->sin6_addr)) 303 | return false; 304 | 305 | opt = (struct icmpv6_opt*)((struct nd_router_advert*)data + 1); 306 | break; 307 | 308 | default: 309 | return false; 310 | } 311 | 312 | icmpv6_for_each_option(opt, opt, end) 313 | ; 314 | 315 | return opt == end; 316 | } 317 | 318 | static bool ra_set_hoplimit(int val) 319 | { 320 | if (val > 0 && val != ra_hoplimit) { 321 | ra_hoplimit = val; 322 | return true; 323 | } 324 | 325 | return false; 326 | } 327 | 328 | static bool ra_set_mtu(int val) 329 | { 330 | if (val >= 1280 && val <= 65535 && ra_mtu != val) { 331 | ra_mtu = val; 332 | return true; 333 | } 334 | 335 | return false; 336 | } 337 | 338 | static bool ra_set_reachable(int val) 339 | { 340 | if (val > 0 && val <= 3600000 && ra_reachable != val) { 341 | ra_reachable = val; 342 | return true; 343 | } 344 | 345 | return false; 346 | } 347 | 348 | static bool ra_set_retransmit(int val) 349 | { 350 | if (val > 0 && val <= 60000 && ra_retransmit != val) { 351 | ra_retransmit = val; 352 | return true; 353 | } 354 | 355 | return false; 356 | } 357 | 358 | static bool ra_generate_addr_eui64(void) 359 | { 360 | struct ifreq ifr; 361 | int sock; 362 | 363 | sock = socket(AF_INET6, SOCK_DGRAM, 0); 364 | if (sock < 0) { 365 | error( 366 | "%s: error creating EUI64 socket", 367 | if_name); 368 | return false; 369 | } 370 | 371 | memset(&ifr, 0, sizeof(ifr)); 372 | strncpy(ifr.ifr_name, if_name, sizeof(ifr.ifr_name) - 1); 373 | 374 | if (ioctl(sock, SIOCGIFHWADDR, &ifr) != 0) { 375 | error( 376 | "%s: error getting EUI64 HW address", 377 | if_name); 378 | close(sock); 379 | return false; 380 | } 381 | 382 | close(sock); 383 | 384 | if (!odhcp6c_is_valid_ether_addr((uint8_t *) ifr.ifr_hwaddr.sa_data)) { 385 | error( 386 | "%s: invalid EUI64 HW address", 387 | if_name); 388 | return false; 389 | } 390 | 391 | ra_addr.s6_addr[0] = 0xfe; 392 | ra_addr.s6_addr[1] = 0x80; 393 | ra_addr.s6_addr[8] = ifr.ifr_hwaddr.sa_data[0] ^ 0x2; 394 | ra_addr.s6_addr[9] = ifr.ifr_hwaddr.sa_data[1]; 395 | ra_addr.s6_addr[10] = ifr.ifr_hwaddr.sa_data[2]; 396 | ra_addr.s6_addr[11] = 0xff; 397 | ra_addr.s6_addr[12] = 0xfe; 398 | ra_addr.s6_addr[13] = ifr.ifr_hwaddr.sa_data[3]; 399 | ra_addr.s6_addr[14] = ifr.ifr_hwaddr.sa_data[4]; 400 | ra_addr.s6_addr[15] = ifr.ifr_hwaddr.sa_data[5]; 401 | 402 | return true; 403 | } 404 | 405 | static bool ra_generate_addr_ll(void) 406 | { 407 | struct sockaddr_in6 addr = {AF_INET6, 0, 0, ALL_IPV6_ROUTERS, if_index}; 408 | socklen_t alen = sizeof(addr); 409 | int sock; 410 | 411 | sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); 412 | if (sock < 0) { 413 | error( 414 | "%s: error creating LLA socket", 415 | if_name); 416 | return false; 417 | } 418 | 419 | if (connect(sock, (struct sockaddr*) &addr, sizeof(addr)) != 0) { 420 | error( 421 | "%s: error connecting LLA socket", 422 | if_name); 423 | close(sock); 424 | return false; 425 | } 426 | 427 | if (getsockname(sock, (struct sockaddr*) &addr, &alen) != 0) { 428 | error( 429 | "%s: error getting address from LLA socket", 430 | if_name); 431 | close(sock); 432 | return false; 433 | } 434 | 435 | close(sock); 436 | 437 | ra_addr = addr.sin6_addr; 438 | 439 | return true; 440 | } 441 | 442 | static bool ra_generate_addr_rand(void) 443 | { 444 | if (odhcp6c_random(&ra_addr.s6_addr[8], 8) != 8) { 445 | ra_addr.s6_addr32[2] = 0; 446 | ra_addr.s6_addr32[3] = 0; 447 | error( 448 | "%s: error generating random interface address", 449 | if_name); 450 | return false; 451 | } 452 | 453 | ra_addr.s6_addr[0] = 0xfe; 454 | ra_addr.s6_addr[1] = 0x80; 455 | 456 | return true; 457 | } 458 | 459 | static void ra_generate_addr(void) 460 | { 461 | bool addr_lla = false; 462 | 463 | switch (ra_ifid_mode) { 464 | case RA_IFID_EUI64: 465 | addr_lla |= !ra_generate_addr_eui64(); 466 | break; 467 | case RA_IFID_FIXED: 468 | /* nothing to do */ 469 | break; 470 | case RA_IFID_LLA: 471 | addr_lla = true; 472 | break; 473 | case RA_IFID_RANDOM: 474 | addr_lla |= !ra_generate_addr_rand(); 475 | break; 476 | } 477 | 478 | if (addr_lla) 479 | ra_generate_addr_ll(); 480 | } 481 | 482 | int ra_get_hoplimit(void) 483 | { 484 | return ra_hoplimit; 485 | } 486 | 487 | int ra_get_mtu(void) 488 | { 489 | return ra_mtu; 490 | } 491 | 492 | int ra_get_reachable(void) 493 | { 494 | return ra_reachable; 495 | } 496 | 497 | int ra_get_retransmit(void) 498 | { 499 | return ra_retransmit; 500 | } 501 | 502 | bool ra_process(void) 503 | { 504 | bool found = false; 505 | bool changed = false; 506 | uint8_t buf[1500] _o_aligned(4); 507 | union { 508 | struct cmsghdr hdr; 509 | uint8_t buf[CMSG_SPACE(sizeof(int))]; 510 | } cmsg_buf; 511 | struct nd_router_advert *adv = (struct nd_router_advert*)buf; 512 | struct odhcp6c_entry *entry = alloca(sizeof(*entry) + 256); 513 | const struct in6_addr any = IN6ADDR_ANY_INIT; 514 | 515 | memset(entry, 0, sizeof(*entry)); 516 | 517 | if (IN6_IS_ADDR_UNSPECIFIED(&ra_addr)) 518 | ra_generate_addr(); 519 | 520 | while (true) { 521 | struct sockaddr_in6 from; 522 | struct iovec iov = {buf, sizeof(buf)}; 523 | struct msghdr msg = { 524 | .msg_name = (void *) &from, 525 | .msg_namelen = sizeof(from), 526 | .msg_iov = &iov, 527 | .msg_iovlen = 1, 528 | .msg_control = cmsg_buf.buf, 529 | .msg_controllen = sizeof(cmsg_buf), 530 | .msg_flags = 0 531 | }; 532 | struct icmpv6_opt *opt; 533 | uint32_t router_valid; 534 | int hlim = 0; 535 | 536 | ssize_t len = recvmsg(sock, &msg, MSG_DONTWAIT); 537 | if (len <= 0) 538 | break; 539 | 540 | if (IN6_IS_ADDR_UNSPECIFIED(&ra_addr)) 541 | continue; 542 | 543 | for (struct cmsghdr *ch = CMSG_FIRSTHDR(&msg); ch != NULL; 544 | ch = CMSG_NXTHDR(&msg, ch)) 545 | if (ch->cmsg_level == IPPROTO_IPV6 && 546 | ch->cmsg_type == IPV6_HOPLIMIT) 547 | memcpy(&hlim, CMSG_DATA(ch), sizeof(hlim)); 548 | 549 | if (!ra_icmpv6_valid(&from, hlim, buf, len)) 550 | continue; 551 | 552 | if (!found) { 553 | odhcp6c_expire(false); 554 | found = true; 555 | } 556 | 557 | router_valid = ntohs(adv->nd_ra_router_lifetime); 558 | 559 | /* RFC4861 §6.3.7 560 | * Once the host sends a Router Solicitation, and receives a valid 561 | * Router Advertisement with a non-zero Router Lifetime, the host MUST 562 | * desist from sending additional solicitations on that interface 563 | * Moreover, a host SHOULD send at least one solicitation in the case 564 | * where an advertisement is received prior to having sent a solicitation. 565 | */ 566 | if (rs_attempt > 0 && router_valid > 0) { 567 | alarm(0); 568 | rs_attempt = 0; 569 | } 570 | 571 | // Parse default route 572 | entry->target = any; 573 | entry->length = 0; 574 | entry->router = from.sin6_addr; 575 | entry->ra_flags = adv->nd_ra_flags_reserved & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER); 576 | entry->priority = pref_to_priority(adv->nd_ra_flags_reserved); 577 | if (entry->priority < 0) 578 | entry->priority = pref_to_priority(0); 579 | 580 | entry->valid = router_valid; 581 | entry->preferred = entry->valid; 582 | changed |= odhcp6c_update_entry(STATE_RA_ROUTE, entry, 583 | ra_holdoff_interval); 584 | entry->ra_flags = 0; // other STATE_RA_* entries don't have flags 585 | 586 | // Parse hop limit 587 | changed |= ra_set_hoplimit(adv->nd_ra_curhoplimit); 588 | 589 | // Parse ND parameters 590 | changed |= ra_set_reachable(ntohl(adv->nd_ra_reachable)); 591 | changed |= ra_set_retransmit(ntohl(adv->nd_ra_retransmit)); 592 | 593 | // Evaluate options 594 | icmpv6_for_each_option(opt, &adv[1], &buf[len]) { 595 | if (opt->type == ND_OPT_MTU) { 596 | uint32_t *mtu = (uint32_t*)&opt->data[2]; 597 | changed |= ra_set_mtu(ntohl(*mtu)); 598 | } else if (opt->type == ND_OPT_ROUTE_INFORMATION && opt->len <= 3) { 599 | struct icmpv6_opt_route_info *ri = (struct icmpv6_opt_route_info *)opt; 600 | 601 | if (ri->prefix_len > 128) { 602 | continue; 603 | } else if (ri->prefix_len > 64) { 604 | if (ri->len < 2) 605 | continue; 606 | } else if (ri->prefix_len > 0) { 607 | if (ri->len < 1) 608 | continue; 609 | } 610 | 611 | entry->router = from.sin6_addr; 612 | entry->target = any; 613 | entry->priority = pref_to_priority(ri->flags); 614 | entry->length = ri->prefix_len; 615 | entry->valid = ntohl(ri->lifetime); 616 | memcpy(&entry->target, ri->prefix, (ri->len - 1) * 8); 617 | 618 | if (IN6_IS_ADDR_LINKLOCAL(&entry->target) 619 | || IN6_IS_ADDR_LOOPBACK(&entry->target) 620 | || IN6_IS_ADDR_MULTICAST(&entry->target)) 621 | continue; 622 | 623 | if (entry->priority > 0) 624 | changed |= odhcp6c_update_entry(STATE_RA_ROUTE, entry, 625 | ra_holdoff_interval); 626 | } else if (opt->type == ND_OPT_PREFIX_INFORMATION && opt->len == 4) { 627 | /* 628 | * We implement draft-ietf-6man-slaac-renum-11 here: 629 | * https://datatracker.ietf.org/doc/html/draft-ietf-6man-slaac-renum-11#section-5.4 630 | * 631 | * This removes the two hour magic and instead just uses the new 632 | * data. If the lifetime is zero, then the prefix is removed. 633 | * 634 | * An entry with lifetime zero is added. odhcp6c_expire will remove 635 | * it again. odhcp6c_expire is called at the end of this function. 636 | */ 637 | struct nd_opt_prefix_info *pinfo = (struct nd_opt_prefix_info*)opt; 638 | entry->router = any; 639 | entry->target = pinfo->nd_opt_pi_prefix; 640 | entry->priority = 256; 641 | entry->length = pinfo->nd_opt_pi_prefix_len; 642 | entry->valid = ntohl(pinfo->nd_opt_pi_valid_time); 643 | entry->preferred = ntohl(pinfo->nd_opt_pi_preferred_time); 644 | 645 | if (entry->length > 128 || IN6_IS_ADDR_LINKLOCAL(&entry->target) 646 | || IN6_IS_ADDR_LOOPBACK(&entry->target) 647 | || IN6_IS_ADDR_MULTICAST(&entry->target) 648 | || entry->valid < entry->preferred) 649 | continue; 650 | 651 | if ((pinfo->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK) && 652 | !ptp_link) 653 | changed |= odhcp6c_update_entry(STATE_RA_ROUTE, entry, 654 | ra_holdoff_interval); 655 | 656 | if (!(pinfo->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO) || 657 | pinfo->nd_opt_pi_prefix_len != 64) 658 | continue; 659 | 660 | entry->target.s6_addr32[2] = ra_addr.s6_addr32[2]; 661 | entry->target.s6_addr32[3] = ra_addr.s6_addr32[3]; 662 | 663 | changed |= odhcp6c_update_entry(STATE_RA_PREFIX, entry, 664 | ra_holdoff_interval); 665 | } else if (opt->type == ND_OPT_RECURSIVE_DNS && opt->len > 2) { 666 | entry->router = from.sin6_addr; 667 | entry->priority = 0; 668 | entry->length = 128; 669 | uint32_t *valid = (uint32_t*)&opt->data[2]; 670 | entry->valid = ntohl(*valid); 671 | entry->preferred = 0; 672 | 673 | for (ssize_t i = 0; i < (opt->len - 1) / 2; ++i) { 674 | memcpy(&entry->target, &opt->data[6 + i * sizeof(entry->target)], 675 | sizeof(entry->target)); 676 | changed |= odhcp6c_update_entry(STATE_RA_DNS, entry, 677 | ra_holdoff_interval); 678 | } 679 | } else if (opt->type == ND_OPT_DNSSL && opt->len > 1) { 680 | uint32_t *valid = (uint32_t*)&opt->data[2]; 681 | uint8_t *ds_buf = &opt->data[6]; 682 | uint8_t *end = &ds_buf[(opt->len - 1) * 8]; 683 | 684 | entry->router = from.sin6_addr; 685 | entry->valid = ntohl(*valid); 686 | 687 | while (ds_buf < end) { 688 | int ds_len = dn_expand(ds_buf, end, ds_buf, (char*)entry->auxtarget, 256); 689 | if (ds_len < 1) 690 | break; 691 | 692 | ds_buf = &ds_buf[ds_len]; 693 | entry->auxlen = strlen((char*)entry->auxtarget); 694 | 695 | if (entry->auxlen == 0) 696 | continue; 697 | 698 | changed |= odhcp6c_update_entry(STATE_RA_SEARCH, entry, 699 | ra_holdoff_interval); 700 | entry->auxlen = 0; 701 | } 702 | } else if (opt->type == ND_OPT_CAPTIVE_PORTAL) { 703 | /* RFC8910 Captive-Portal §2.3 */ 704 | if (opt->len <= 1) 705 | continue; 706 | 707 | struct icmpv6_opt_captive_portal *capt_port = (struct icmpv6_opt_captive_portal*)opt; 708 | uint8_t *cp_buf = &capt_port->data[0]; 709 | size_t ref_len = sizeof(URN_IETF_CAPT_PORT_UNRESTR) - 1; 710 | 711 | /* RFC8910 §2: 712 | * Networks with no captive portals may explicitly indicate this 713 | * condition by using this option with the IANA-assigned URI for 714 | * this purpose. Clients observing the URI value ... may forego 715 | * time-consuming forms of captive portal detection. */ 716 | if (memcmp(cp_buf, URN_IETF_CAPT_PORT_UNRESTR, ref_len)) { 717 | /* URI are not guaranteed to be \0 terminated if data is unpadded */ 718 | size_t uri_len = (capt_port->len * 8) - 2; 719 | /* Allocate new buffer including room for '\0' */ 720 | uint8_t *copy = malloc(uri_len + 1); 721 | if (!copy) 722 | continue; 723 | 724 | memcpy(copy, cp_buf, uri_len); 725 | copy[uri_len] = '\0'; 726 | odhcp6c_clear_state(STATE_CAPT_PORT_RA); 727 | odhcp6c_add_state(STATE_CAPT_PORT_RA, copy, uri_len); 728 | free(copy); 729 | } 730 | } 731 | } 732 | 733 | if (ra_options & RA_RDNSS_DEFAULT_LIFETIME) { 734 | int states[2] = {STATE_RA_DNS, STATE_RA_SEARCH}; 735 | 736 | for (size_t i = 0; i < 2; ++i) { 737 | size_t ra_dns_len; 738 | uint8_t *start = odhcp6c_get_state(states[i], &ra_dns_len); 739 | 740 | for (struct odhcp6c_entry *c = (struct odhcp6c_entry*)start; 741 | (uint8_t*)c < &start[ra_dns_len] && 742 | (uint8_t*)odhcp6c_next_entry(c) <= &start[ra_dns_len]; 743 | c = odhcp6c_next_entry(c)) { 744 | if (IN6_ARE_ADDR_EQUAL(&c->router, &from.sin6_addr) && 745 | c->valid > router_valid) 746 | c->valid = router_valid; 747 | } 748 | } 749 | } 750 | } 751 | 752 | if (found) 753 | odhcp6c_expire(false); 754 | 755 | return found && changed; 756 | } 757 | -------------------------------------------------------------------------------- /src/ubus.c: -------------------------------------------------------------------------------- 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 63 | #include 64 | #include 65 | #include 66 | #include 67 | #include 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 | -------------------------------------------------------------------------------- /src/odhcp6c.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2012-2014 Steven Barth 3 | * Copyright (C) 2017-2018 Hans Dedecker 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License v2 as published by 7 | * the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | */ 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | #include "config.h" 39 | #include "odhcp6c.h" 40 | #include "ra.h" 41 | #include "ubus.h" 42 | 43 | #define DHCPV6_FD_INDEX 0 44 | #define UBUS_FD_INDEX 1 45 | 46 | #ifndef IN6_IS_ADDR_UNIQUELOCAL 47 | #define IN6_IS_ADDR_UNIQUELOCAL(a) \ 48 | ((((__const uint32_t *) (a))[0] & htonl (0xfe000000)) \ 49 | == htonl (0xfc000000)) 50 | #endif 51 | 52 | static void sighandler(int signal); 53 | static int usage(void); 54 | 55 | static uint8_t *state_data[_STATE_MAX] = {NULL}; 56 | static size_t state_len[_STATE_MAX] = {0}; 57 | 58 | static volatile bool signal_io = false; 59 | static volatile bool signal_usr1 = false; 60 | static volatile bool signal_usr2 = false; 61 | static volatile bool signal_term = false; 62 | 63 | static bool client_id_param = false; 64 | static int urandom_fd = -1; 65 | static bool bound = false, ra = false; 66 | static time_t last_update = 0; 67 | static char *ifname = NULL; 68 | struct config_dhcp *config_dhcp = NULL; 69 | 70 | void __iflog(int lvl, const char *fmt, ...) 71 | { 72 | va_list ap; 73 | 74 | if (lvl > config_dhcp->log_level) 75 | return; 76 | 77 | va_start(ap, fmt); 78 | 79 | if (config_dhcp->log_syslog) { 80 | vsyslog(lvl, fmt, ap); 81 | } else { 82 | vfprintf(stderr, fmt, ap); 83 | fprintf(stderr, "\n"); 84 | } 85 | 86 | va_end(ap); 87 | } 88 | 89 | static unsigned int script_sync_delay = 10; 90 | static unsigned int script_accu_delay = 1; 91 | 92 | static struct odhcp6c_opt opts[] = { 93 | { .code = DHCPV6_OPT_CLIENTID, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 94 | { .code = DHCPV6_OPT_SERVERID, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 95 | { .code = DHCPV6_OPT_IA_NA, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str= NULL }, 96 | { .code = DHCPV6_OPT_IA_TA, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 97 | { .code = DHCPV6_OPT_IA_ADDR, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 98 | { .code = DHCPV6_OPT_ORO, .flags = OPT_INTERNAL, .str = NULL }, 99 | { .code = DHCPV6_OPT_PREF, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 100 | { .code = DHCPV6_OPT_ELAPSED, .flags = OPT_INTERNAL, .str = NULL }, 101 | { .code = DHCPV6_OPT_RELAY_MSG, .flags = OPT_INTERNAL, .str = NULL }, 102 | { .code = DHCPV6_OPT_AUTH, .flags = OPT_U8 | OPT_NO_PASSTHRU, .str = "authentication" }, 103 | { .code = DHCPV6_OPT_UNICAST, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 104 | { .code = DHCPV6_OPT_STATUS, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 105 | { .code = DHCPV6_OPT_RAPID_COMMIT, .flags = OPT_INTERNAL, .str = NULL }, 106 | { .code = DHCPV6_OPT_USER_CLASS, .flags = OPT_USER_CLASS | OPT_ARRAY, .str = "userclass" }, 107 | { .code = DHCPV6_OPT_VENDOR_CLASS, .flags = OPT_U8, .str = "vendorclass" }, 108 | { .code = DHCPV6_OPT_INTERFACE_ID, .flags = OPT_INTERNAL, .str = NULL }, 109 | { .code = DHCPV6_OPT_RECONF_MESSAGE, .flags = OPT_INTERNAL, .str = NULL }, 110 | { .code = DHCPV6_OPT_RECONF_ACCEPT, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 111 | { .code = DHCPV6_OPT_SIP_SERVER_D, .flags = OPT_DNS_STR | OPT_ORO, .str = "sipserver_d" }, 112 | { .code = DHCPV6_OPT_SIP_SERVER_A, .flags = OPT_IP6 | OPT_ARRAY | OPT_ORO, .str = "sipserver_a" }, 113 | { .code = DHCPV6_OPT_DNS_SERVERS, .flags = OPT_IP6 | OPT_ARRAY | OPT_ORO, .str = "dns" }, 114 | { .code = DHCPV6_OPT_DNS_DOMAIN, .flags = OPT_DNS_STR | OPT_ORO, .str = "search" }, 115 | { .code = DHCPV6_OPT_IA_PD, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 116 | { .code = DHCPV6_OPT_IA_PREFIX, .flags = OPT_INTERNAL, .str = NULL }, 117 | { .code = DHCPV6_OPT_SNTP_SERVERS, .flags = OPT_IP6 | OPT_ARRAY | OPT_ORO, .str = "sntpservers" }, 118 | { .code = DHCPV6_OPT_INFO_REFRESH, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU | OPT_ORO | OPT_ORO_STATELESS, .str = NULL }, 119 | { .code = DHCPV6_OPT_REMOTE_ID, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 120 | { .code = DHCPV6_OPT_SUBSCRIBER_ID, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 121 | { .code = DHCPV6_OPT_FQDN, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU | OPT_ORO, .str = NULL }, 122 | { .code = DHCPV6_OPT_ERO, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 123 | { .code = DHCPV6_OPT_LQ_QUERY, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 124 | { .code = DHCPV6_OPT_CLIENT_DATA, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 125 | { .code = DHCPV6_OPT_CLT_TIME, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 126 | { .code = DHCPV6_OPT_LQ_RELAY_DATA, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 127 | { .code = DHCPV6_OPT_LQ_CLIENT_LINK, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 128 | { .code = DHCPV6_OPT_RELAY_ID, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 129 | { .code = DHCPV6_OPT_NTP_SERVER, .flags = OPT_U8 | OPT_ORO, .str = "ntpserver" }, 130 | { .code = DHCPV6_OPT_CLIENT_ARCH_TYPE, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 131 | { .code = DHCPV6_OPT_AFTR_NAME, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU | OPT_ORO, .str = NULL }, 132 | { .code = DHCPV6_OPT_RSOO, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 133 | { .code = DHCPV6_OPT_PD_EXCLUDE, .flags = OPT_INTERNAL | OPT_ORO | OPT_ORO_STATEFUL, .str = NULL }, 134 | { .code = DHCPV6_OPT_VSS, .flags = OPT_U8, .str = "vss" }, 135 | { .code = DHCPV6_OPT_LINK_LAYER_ADDRESS, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 136 | { .code = DHCPV6_OPT_LINK_ADDRESS, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 137 | { .code = DHCPV6_OPT_RADIUS, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 138 | { .code = DHCPV6_OPT_SOL_MAX_RT, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU | OPT_ORO | OPT_ORO_SOLICIT, .str = NULL }, 139 | { .code = DHCPV6_OPT_INF_MAX_RT, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU | OPT_ORO | OPT_ORO_STATELESS, .str = NULL }, 140 | { .code = DHCPV6_OPT_DHCPV4_MSG, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 141 | { .code = DHCPV6_OPT_S46_RULE, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 142 | { .code = DHCPV6_OPT_S46_BR, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 143 | { .code = DHCPV6_OPT_S46_DMR, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 144 | { .code = DHCPV6_OPT_S46_V4V6BIND, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 145 | { .code = DHCPV6_OPT_S46_PORTPARAMS, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 146 | { .code = DHCPV6_OPT_S46_CONT_MAPE, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU | OPT_ORO, .str = NULL }, 147 | { .code = DHCPV6_OPT_S46_CONT_MAPT, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU | OPT_ORO, .str = NULL }, 148 | { .code = DHCPV6_OPT_S46_CONT_LW, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU | OPT_ORO, .str = NULL }, 149 | { .code = DHCPV6_OPT_LQ_BASE_TIME, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 150 | { .code = DHCPV6_OPT_LQ_START_TIME, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 151 | { .code = DHCPV6_OPT_LQ_END_TIME, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 152 | { .code = DHCPV6_OPT_ANI_ATT, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 153 | { .code = DHCPV6_OPT_ANI_NETWORK_NAME, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 154 | { .code = DHCPV6_OPT_ANI_AP_NAME, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 155 | { .code = DHCPV6_OPT_ANI_AP_BSSID, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 156 | { .code = DHCPV6_OPT_ANI_OPERATOR_ID, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 157 | { .code = DHCPV6_OPT_ANI_OPERATOR_REALM, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 158 | { .code = DHCPV6_OPT_MUD_URL_V6, .flags = OPT_STR | OPT_NO_PASSTHRU, .str = "mud_url_v6" }, 159 | { .code = DHCPV6_OPT_F_BINDING_STATUS, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 160 | { .code = DHCPV6_OPT_F_CONNECT_FLAGS, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 161 | { .code = DHCPV6_OPT_F_DNS_REMOVAL_INFO, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 162 | { .code = DHCPV6_OPT_F_DNS_HOST_NAME, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 163 | { .code = DHCPV6_OPT_F_DNS_ZONE_NAME, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 164 | { .code = DHCPV6_OPT_F_DNS_FLAGS, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 165 | { .code = DHCPV6_OPT_F_EXPIRATION_TIME, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 166 | { .code = DHCPV6_OPT_F_MAX_UNACKED_BNDUPD, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 167 | { .code = DHCPV6_OPT_F_MCLT, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 168 | { .code = DHCPV6_OPT_F_PARTNER_LIFETIME, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 169 | { .code = DHCPV6_OPT_F_PARTNER_LIFETIME_SENT, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 170 | { .code = DHCPV6_OPT_F_PARTNER_DOWN_TIME, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 171 | { .code = DHCPV6_OPT_F_PARTNER_RAW_CLT_TIME, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 172 | { .code = DHCPV6_OPT_F_PROTOCOL_VERSION, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 173 | { .code = DHCPV6_OPT_F_KEEPALIVE_TIME, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 174 | { .code = DHCPV6_OPT_F_RECONFIGURE_DATA, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 175 | { .code = DHCPV6_OPT_F_RELATIONSHIP_NAME, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 176 | { .code = DHCPV6_OPT_F_SERVER_FLAGS, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 177 | { .code = DHCPV6_OPT_F_SERVER_STATE, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 178 | { .code = DHCPV6_OPT_F_START_TIME_OF_STATE, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 179 | { .code = DHCPV6_OPT_F_STATE_EXPIRATION_TIME, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 180 | { .code = DHCPV6_OPT_RELAY_PORT, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL }, 181 | { .code = 0, .flags = 0, .str = NULL }, 182 | }; 183 | 184 | int main(_o_unused int argc, char* const argv[]) 185 | { 186 | static struct in6_addr ifid = IN6ADDR_ANY_INIT; 187 | // Allocate resources 188 | const char *pidfile = NULL; 189 | const char *script = "/lib/netifd/dhcpv6.script"; 190 | ssize_t l; 191 | uint8_t buf[134], *o_data; 192 | char *optpos; 193 | uint16_t opttype; 194 | struct odhcp6c_opt *opt; 195 | int ia_pd_iaid_index = 0; 196 | bool help = false, daemonize = false; 197 | int logopt = LOG_PID; 198 | int c; 199 | int res = -1; 200 | unsigned int ra_options = RA_RDNSS_DEFAULT_LIFETIME; 201 | unsigned int ra_holdoff_interval = RA_MIN_ADV_INTERVAL; 202 | ra_ifid_mode_t ra_ifid_mode = RA_IFID_LLA; 203 | bool terminate = false; 204 | bool deprecated_opt = false; 205 | 206 | config_dhcp = config_dhcp_get(); 207 | config_dhcp_reset(); 208 | 209 | while ((c = getopt(argc, argv, "SDN:V:P:FB:c:i:r:Ru:Ux:s:EkK:t:C:m:Lhedp:favl:")) != -1) { 210 | switch (c) { 211 | case 'S': 212 | config_set_allow_slaac_only(false); 213 | break; 214 | 215 | case 'D': 216 | config_set_stateful_only(true); 217 | break; 218 | 219 | case 'N': 220 | if (!config_set_request_addresses(optarg)) 221 | help = true; 222 | break; 223 | 224 | case 'V': 225 | opt = odhcp6c_find_opt(DHCPV6_OPT_VENDOR_CLASS); 226 | if (!opt) { 227 | error("Failed to set vendor-class option"); 228 | return 1; 229 | } 230 | 231 | o_data = NULL; 232 | res = config_parse_opt_data(optarg, &o_data, opt->flags & OPT_MASK_SIZE, 233 | (opt->flags & OPT_ARRAY) == OPT_ARRAY); 234 | if (res > 0) { 235 | res = config_add_opt(opt->code, o_data, res); 236 | if (res) { 237 | if (res > 0) 238 | return 1; 239 | 240 | help = true; 241 | } 242 | } else { 243 | help = true; 244 | } 245 | 246 | free(o_data); 247 | break; 248 | 249 | case 'P': 250 | if (config_dhcp->ia_pd_mode == IA_MODE_NONE) 251 | config_dhcp->ia_pd_mode = IA_MODE_TRY; 252 | 253 | struct odhcp6c_request_prefix prefix = { 0 }; 254 | 255 | optpos = strchr(optarg, '/'); 256 | if (optpos) { 257 | strncpy((char *)buf, optarg, optpos - optarg); 258 | buf[optpos - optarg] = '\0'; 259 | if (inet_pton(AF_INET6, (char *)buf, &prefix.addr) <= 0) { 260 | error("invalid argument: '%s'", optarg); 261 | return 1; 262 | } 263 | optpos++; 264 | } else { 265 | optpos = optarg; 266 | } 267 | 268 | char *iaid_begin; 269 | int iaid_len = 0; 270 | prefix.length = strtoul(optpos, &iaid_begin, 10); 271 | 272 | if (*iaid_begin != '\0' && *iaid_begin != ',' && *iaid_begin != ':') { 273 | error("invalid argument: '%s'", optarg); 274 | return 1; 275 | } 276 | 277 | if (*iaid_begin == ',' && (iaid_len = strlen(iaid_begin)) > 1) 278 | memcpy(&prefix.iaid, iaid_begin + 1, iaid_len > 4 ? 4 : iaid_len); 279 | else if (*iaid_begin == ':') 280 | prefix.iaid = htonl((uint32_t)strtoul(&iaid_begin[1], NULL, 16)); 281 | else 282 | prefix.iaid = htonl(++ia_pd_iaid_index); 283 | 284 | if (odhcp6c_add_state(STATE_IA_PD_INIT, &prefix, sizeof(prefix))) { 285 | error("Failed to set request IPv6-Prefix"); 286 | return 1; 287 | } 288 | break; 289 | 290 | case 'F': 291 | config_set_force_prefix(true); 292 | break; 293 | 294 | case 'c': 295 | l = script_unhexlify(&buf[4], sizeof(buf) - DHCPV6_OPT_HDR_SIZE, optarg); 296 | if (l > 0) { 297 | buf[0] = 0; 298 | buf[1] = DHCPV6_OPT_CLIENTID; 299 | buf[2] = 0; 300 | buf[3] = l; 301 | if (odhcp6c_add_state(STATE_CLIENT_ID, buf, l + 4)) { 302 | error("Failed to override client-ID"); 303 | return 1; 304 | } else { 305 | client_id_param = true; 306 | } 307 | } else { 308 | help = true; 309 | } 310 | break; 311 | 312 | case 'i': 313 | if (!strcmp(optarg, DHCPV6_IFACEID_EUI64)) { 314 | ra_ifid_mode = RA_IFID_EUI64; 315 | } else if (!strcmp(optarg, DHCPV6_IFACEID_RANDOM)) { 316 | ra_ifid_mode = RA_IFID_RANDOM; 317 | } else if (inet_pton(AF_INET6, optarg, &ifid) == 1) { 318 | ra_ifid_mode = RA_IFID_FIXED; 319 | ifid.s6_addr[0] = 0xfe; 320 | ifid.s6_addr[1] = 0x80; 321 | } else { 322 | /* Do not error on bad values; fall back to default */ 323 | error("Invalid interface-ID: %s", optarg); 324 | } 325 | break; 326 | 327 | case 'r': 328 | optpos = optarg; 329 | while (optpos[0]) { 330 | opttype = htons(strtoul(optarg, &optpos, 10)); 331 | if (optpos == optarg) 332 | break; 333 | else if (optpos[0]) 334 | optarg = &optpos[1]; 335 | 336 | if (odhcp6c_add_state(STATE_ORO, &opttype, 2)) { 337 | error("Failed to add requested option"); 338 | return 1; 339 | } 340 | } 341 | break; 342 | 343 | case 'R': 344 | config_set_client_options(DHCPV6_STRICT_OPTIONS, true); 345 | break; 346 | 347 | case 'u': 348 | opt = odhcp6c_find_opt(DHCPV6_OPT_USER_CLASS); 349 | if (!opt) { 350 | error("Failed to set user-class option"); 351 | return 1; 352 | } 353 | 354 | o_data = NULL; 355 | res = config_parse_opt_data(optarg, &o_data, opt->flags & OPT_MASK_SIZE, 356 | (opt->flags & OPT_ARRAY) == OPT_ARRAY); 357 | if (res > 0) { 358 | res = config_add_opt(opt->code, o_data, res); 359 | if (res) { 360 | if (res > 0) 361 | return 1; 362 | 363 | help = true; 364 | } 365 | } else { 366 | help = true; 367 | } 368 | 369 | free(o_data); 370 | break; 371 | 372 | case 'U': 373 | config_set_client_options(DHCPV6_IGNORE_OPT_UNICAST, true); 374 | break; 375 | 376 | case 's': 377 | if (script) 378 | script = optarg; 379 | break; 380 | 381 | case 'E': 382 | #ifndef WITH_UBUS 383 | error("Failed to use ubus event: ENABLE_UBUS compilation flag missing"); 384 | return 1; 385 | #endif /* WITH_UBUS */ 386 | script = NULL; 387 | break; 388 | 389 | case 'k': 390 | config_set_release(false); 391 | break; 392 | 393 | case 'K': 394 | config_set_sk_priority(atoi(optarg)); 395 | break; 396 | 397 | case 't': 398 | config_set_rtx_timeout_max(CONFIG_DHCP_SOLICIT, atoi(optarg)); 399 | break; 400 | 401 | case 'C': 402 | config_set_dscp(atoi(optarg)); 403 | break; 404 | 405 | case 'm': 406 | ra_holdoff_interval = atoi(optarg); 407 | break; 408 | 409 | case 'L': 410 | ra_options &= ~RA_RDNSS_DEFAULT_LIFETIME; 411 | break; 412 | 413 | case 'e': 414 | logopt |= LOG_PERROR; 415 | break; 416 | 417 | case 'd': 418 | daemonize = true; 419 | break; 420 | 421 | case 'p': 422 | pidfile = optarg; 423 | break; 424 | 425 | case 'f': 426 | config_set_client_options(DHCPV6_CLIENT_FQDN, false); 427 | break; 428 | 429 | case 'a': 430 | config_set_client_options(DHCPV6_ACCEPT_RECONFIGURE, false); 431 | break; 432 | 433 | case 'v': 434 | /* deprecated - remove -v options from start scripts first */ 435 | deprecated_opt = true; 436 | break; 437 | 438 | case 'l': 439 | config_dhcp->log_level = (atoi(optarg) & LOG_PRIMASK); 440 | break; 441 | 442 | case 'x': 443 | res = config_parse_opt(optarg); 444 | if (res) { 445 | if (res > 0) 446 | return res; 447 | 448 | help = true; 449 | } 450 | break; 451 | 452 | default: 453 | help = true; 454 | break; 455 | } 456 | } 457 | 458 | openlog("odhcp6c", logopt, LOG_DAEMON); 459 | setlogmask(LOG_UPTO(config_dhcp->log_level)); 460 | 461 | ifname = argv[optind]; 462 | 463 | if (help || !ifname) 464 | return usage(); 465 | 466 | signal(SIGIO, sighandler); 467 | signal(SIGHUP, sighandler); 468 | signal(SIGINT, sighandler); 469 | signal(SIGTERM, sighandler); 470 | signal(SIGUSR1, sighandler); 471 | signal(SIGUSR2, sighandler); 472 | 473 | if (daemonize) { 474 | openlog("odhcp6c", LOG_PID, LOG_DAEMON); // Disable LOG_PERROR 475 | if (daemon(0, 0)) { 476 | error("Failed to daemonize: %s", 477 | strerror(errno)); 478 | return 3; 479 | } 480 | 481 | if (!pidfile) { 482 | snprintf((char*)buf, sizeof(buf), "/var/run/odhcp6c.%s.pid", ifname); 483 | pidfile = (char*)buf; 484 | } 485 | 486 | FILE *fp = fopen(pidfile, "w"); 487 | if (fp) { 488 | fprintf(fp, "%i\n", getpid()); 489 | fclose(fp); 490 | } 491 | } else { 492 | config_dhcp->log_syslog = false; 493 | } 494 | 495 | if (deprecated_opt) 496 | warn("The -v flag is deprecated and will be removed. Use -l[0-7]."); 497 | 498 | if ((urandom_fd = open("/dev/urandom", O_CLOEXEC | O_RDONLY)) < 0 || 499 | ra_init(ifname, &ifid, ra_ifid_mode, ra_options, ra_holdoff_interval) || 500 | script_init(script, ifname)) { 501 | error("failed to initialize: %s", strerror(errno)); 502 | return 4; 503 | } 504 | 505 | struct pollfd fds[2] = {0}; 506 | int nfds = 0; 507 | 508 | int mode = DHCPV6_UNKNOWN; 509 | enum dhcpv6_msg req_msg_type = DHCPV6_MSG_UNKNOWN; 510 | 511 | fds[DHCPV6_FD_INDEX].fd = -1; 512 | fds[DHCPV6_FD_INDEX].events = POLLIN; 513 | nfds++; 514 | 515 | #ifdef WITH_UBUS 516 | char *err = ubus_init(ifname); 517 | if (err) { 518 | error("ubus error: %s", err); 519 | return 1; 520 | } 521 | 522 | struct ubus_context *ubus = ubus_get_ctx(); 523 | int ubus_socket = ubus->sock.fd; 524 | if (ubus_socket < 0) { 525 | error("Invalid ubus file descriptor"); 526 | return 1; 527 | } 528 | fds[UBUS_FD_INDEX].fd = ubus_socket; 529 | fds[UBUS_FD_INDEX].events = POLLIN; 530 | nfds++; 531 | #endif /* WITH_UBUS */ 532 | 533 | notify_state_change("started", 0, false); 534 | 535 | while (!terminate) { // Main logic 536 | int poll_res; 537 | bool signalled = odhcp6c_signal_process(); 538 | 539 | switch (dhcpv6_get_state()) { 540 | case DHCPV6_INIT: 541 | odhcp6c_clear_state(STATE_SERVER_ID); 542 | odhcp6c_clear_state(STATE_SERVER_ADDR); 543 | odhcp6c_clear_state(STATE_IA_NA); 544 | odhcp6c_clear_state(STATE_IA_PD); 545 | odhcp6c_clear_state(STATE_SNTP_IP); 546 | odhcp6c_clear_state(STATE_NTP_IP); 547 | odhcp6c_clear_state(STATE_NTP_FQDN); 548 | odhcp6c_clear_state(STATE_SIP_IP); 549 | odhcp6c_clear_state(STATE_SIP_FQDN); 550 | odhcp6c_clear_state(STATE_CAPT_PORT_DHCPV6); 551 | bound = false; 552 | res = -1; 553 | 554 | size_t oro_len = 0; 555 | odhcp6c_get_state(STATE_ORO, &oro_len); 556 | config_dhcp->oro_user_cnt = oro_len / sizeof(uint16_t); 557 | 558 | if (init_dhcpv6(ifname)) { 559 | error("failed to initialize: %s", strerror(errno)); 560 | return 1; 561 | } 562 | 563 | fds[DHCPV6_FD_INDEX].fd = dhcpv6_get_socket(); 564 | 565 | notice("(re)starting transaction on %s", ifname); 566 | 567 | signal_usr1 = signal_usr2 = false; 568 | 569 | dhcpv6_set_state(DHCPV6_SOLICIT); 570 | break; 571 | 572 | case DHCPV6_SOLICIT: 573 | mode = dhcpv6_get_ia_mode(); 574 | if (mode == DHCPV6_STATELESS) { 575 | dhcpv6_set_state(DHCPV6_REQUEST); 576 | break; 577 | } 578 | 579 | req_msg_type = DHCPV6_MSG_SOLICIT; 580 | dhcpv6_send_request(req_msg_type); 581 | break; 582 | 583 | case DHCPV6_ADVERT: 584 | if (res > 0) { 585 | mode = DHCPV6_STATEFUL; 586 | dhcpv6_set_state(DHCPV6_REQUEST); 587 | } else { 588 | mode = DHCPV6_UNKNOWN; 589 | dhcpv6_set_state(DHCPV6_RESET); 590 | } 591 | break; 592 | 593 | case DHCPV6_REQUEST: 594 | req_msg_type = (mode == DHCPV6_STATELESS) ? DHCPV6_MSG_INFO_REQ : DHCPV6_MSG_REQUEST; 595 | dhcpv6_send_request(req_msg_type); 596 | break; 597 | 598 | case DHCPV6_REPLY: 599 | if ((res > 0) && mode != DHCPV6_UNKNOWN) { 600 | dhcpv6_set_state(DHCPV6_BOUND); 601 | break; 602 | } 603 | 604 | if ((res < 0) && signalled) { 605 | mode = DHCPV6_UNKNOWN; 606 | dhcpv6_set_state(DHCPV6_RESET); 607 | break; 608 | } 609 | 610 | mode = dhcpv6_promote_server_cand(); 611 | dhcpv6_set_state(mode > DHCPV6_UNKNOWN ? DHCPV6_REQUEST : DHCPV6_RESET); 612 | break; 613 | 614 | case DHCPV6_BOUND: 615 | if (!bound) { 616 | bound = true; 617 | if (mode == DHCPV6_STATELESS) { 618 | notice("entering stateless-mode on %s", ifname); 619 | signal_usr1 = false; 620 | notify_state_change("informed", script_sync_delay, true); 621 | } else { 622 | notify_state_change("bound", script_sync_delay, true); 623 | notice("entering stateful-mode on %s", ifname); 624 | } 625 | } 626 | 627 | req_msg_type = DHCPV6_MSG_UNKNOWN; 628 | dhcpv6_send_request(req_msg_type); 629 | break; 630 | 631 | case DHCPV6_BOUND_REPLY: 632 | if (res == DHCPV6_MSG_RENEW || res == DHCPV6_MSG_REBIND || 633 | res == DHCPV6_MSG_INFO_REQ) { 634 | req_msg_type = res; 635 | dhcpv6_set_state(DHCPV6_RECONF); 636 | } else { 637 | dhcpv6_set_state(DHCPV6_RECONF_REPLY); 638 | } 639 | break; 640 | 641 | case DHCPV6_RECONF: 642 | dhcpv6_send_request(req_msg_type); 643 | break; 644 | 645 | case DHCPV6_RECONF_REPLY: 646 | if (res > 0) { 647 | dhcpv6_set_state(DHCPV6_BOUND); 648 | if (mode == DHCPV6_STATEFUL) 649 | notify_state_change("updated", 0, false); 650 | } else { 651 | dhcpv6_set_state(mode == DHCPV6_STATELESS ? DHCPV6_INFO : DHCPV6_RENEW); 652 | } 653 | break; 654 | 655 | case DHCPV6_RENEW: 656 | req_msg_type = DHCPV6_MSG_RENEW; 657 | dhcpv6_send_request(req_msg_type); 658 | break; 659 | 660 | case DHCPV6_RENEW_REPLY: 661 | if (res > 0 ) { 662 | notify_state_change("updated", 0, false); 663 | dhcpv6_set_state(DHCPV6_BOUND); 664 | } else { 665 | dhcpv6_set_state(DHCPV6_REBIND); 666 | } 667 | break; 668 | 669 | case DHCPV6_REBIND: 670 | odhcp6c_clear_state(STATE_SERVER_ID); // Remove binding 671 | odhcp6c_clear_state(STATE_SERVER_ADDR); 672 | 673 | size_t ia_pd_len_r, ia_na_len_r; 674 | odhcp6c_get_state(STATE_IA_PD, &ia_pd_len_r); 675 | odhcp6c_get_state(STATE_IA_NA, &ia_na_len_r); 676 | 677 | // If we have IAs, try rebind otherwise restart 678 | if (ia_pd_len_r == 0 && ia_na_len_r == 0) { 679 | dhcpv6_set_state(DHCPV6_RESET); 680 | break; 681 | } 682 | 683 | req_msg_type = DHCPV6_MSG_REBIND; 684 | dhcpv6_send_request(req_msg_type); 685 | break; 686 | 687 | case DHCPV6_REBIND_REPLY: 688 | if (res < 0) { 689 | dhcpv6_set_state(DHCPV6_RESET); 690 | } else { 691 | notify_state_change("rebound", 0, true); 692 | dhcpv6_set_state(DHCPV6_BOUND); 693 | } 694 | break; 695 | 696 | case DHCPV6_INFO: 697 | req_msg_type = DHCPV6_MSG_INFO_REQ; 698 | dhcpv6_send_request(req_msg_type); 699 | break; 700 | 701 | case DHCPV6_INFO_REPLY: 702 | dhcpv6_set_state(res < 0 ? DHCPV6_RESET : DHCPV6_BOUND); 703 | break; 704 | 705 | case DHCPV6_SOLICIT_PROCESSING: 706 | case DHCPV6_REQUEST_PROCESSING: 707 | res = dhcpv6_state_processing(req_msg_type); 708 | break; 709 | 710 | case DHCPV6_BOUND_PROCESSING: 711 | case DHCPV6_RECONF_PROCESSING: 712 | case DHCPV6_REBIND_PROCESSING: 713 | res = dhcpv6_state_processing(req_msg_type); 714 | 715 | if (signal_usr1) 716 | dhcpv6_set_state(mode == DHCPV6_STATELESS ? DHCPV6_INFO : DHCPV6_RENEW); 717 | break; 718 | 719 | case DHCPV6_RENEW_PROCESSING: 720 | case DHCPV6_INFO_PROCESSING: 721 | res = dhcpv6_state_processing(req_msg_type); 722 | 723 | if (signal_usr1) 724 | signal_usr1 = false; // Acknowledged 725 | break; 726 | 727 | case DHCPV6_EXIT: 728 | odhcp6c_expire(false); 729 | 730 | size_t ia_pd_len, ia_na_len, server_id_len; 731 | odhcp6c_get_state(STATE_IA_PD, &ia_pd_len); 732 | odhcp6c_get_state(STATE_IA_NA, &ia_na_len); 733 | odhcp6c_get_state(STATE_SERVER_ID, &server_id_len); 734 | 735 | // Add all prefixes to lost prefixes 736 | if (bound) { 737 | bound = false; 738 | notify_state_change("unbound", 0, true); 739 | } 740 | 741 | if (server_id_len > 0 && (ia_pd_len > 0 || ia_na_len > 0) && (!signal_term || config_dhcp->release)) 742 | dhcpv6_send_request(DHCPV6_MSG_RELEASE); 743 | 744 | odhcp6c_clear_state(STATE_IA_NA); 745 | odhcp6c_clear_state(STATE_IA_PD); 746 | 747 | if (signal_term) { 748 | terminate = true; 749 | } else { 750 | signal_usr2 = false; 751 | dhcpv6_set_state(DHCPV6_RESET); 752 | } 753 | break; 754 | 755 | case DHCPV6_RESET: 756 | if (!client_id_param) 757 | odhcp6c_clear_state(STATE_CLIENT_ID); 758 | 759 | if (bound) { 760 | bound = false; 761 | notify_state_change("unbound", 0, true); 762 | } 763 | 764 | size_t oro_user_len, oro_total_len; 765 | odhcp6c_get_state(STATE_ORO, &oro_total_len); 766 | oro_user_len = config_dhcp->oro_user_cnt * sizeof(uint16_t); 767 | odhcp6c_remove_state(STATE_ORO, oro_user_len, oro_total_len - oro_user_len); 768 | 769 | close(dhcpv6_get_socket()); 770 | fds[DHCPV6_FD_INDEX].fd = -1; 771 | 772 | dhcpv6_set_state(DHCPV6_INIT); 773 | break; 774 | 775 | default: 776 | break; 777 | } 778 | 779 | if (signal_usr2 || signal_term) 780 | dhcpv6_set_state(DHCPV6_EXIT); 781 | 782 | poll_res = poll(fds, nfds, dhcpv6_get_state_timeout()); 783 | dhcpv6_reset_state_timeout(); 784 | if (poll_res == -1 && (errno == EINTR || errno == EAGAIN)) { 785 | continue; 786 | } 787 | 788 | if (fds[0].revents & POLLIN) 789 | dhcpv6_receive_response(req_msg_type); 790 | 791 | #ifdef WITH_UBUS 792 | if (fds[1].revents & POLLIN) 793 | ubus_handle_event(ubus); 794 | #endif /* WITH_UBUS */ 795 | } 796 | 797 | notify_state_change("stopped", 0, true); 798 | 799 | #ifdef WITH_UBUS 800 | ubus_destroy(ubus); 801 | #endif /* WITH_UBUS */ 802 | 803 | return 0; 804 | } 805 | 806 | static int usage(void) 807 | { 808 | const char buf[] = 809 | "Usage: odhcp6c [options] \n" 810 | "\nFeature options:\n" 811 | " -S Don't allow configuration via SLAAC (RAs) only\n" 812 | " -D Discard advertisements without any address or prefix proposed\n" 813 | " -N Mode for requesting addresses [try|force|none]\n" 814 | " -P <[pfx/]len> Request IPv6-Prefix (0 = auto)\n" 815 | " -F Force IPv6-Prefix\n" 816 | " -V Set vendor-class option (base-16 encoded)\n" 817 | " -u Set user-class option string\n" 818 | " -x : Add option opt (with value val) in sent packets (cumulative)\n" 819 | " Examples of IPv6 address, string and base-16 encoded options:\n" 820 | " -x dns:2001:2001::1,2001:2001::2 - option 23\n" 821 | " -x 15:office - option 15 (userclass)\n" 822 | " -x 0x1f4:ABBA - option 500\n" 823 | " -x 202:'\"file\"' - option 202\n" 824 | " -c Override client-ID (base-16 encoded 16-bit type + value)\n" 825 | " -i Use a custom interface identifier for RA handling\n" 826 | " -r Options to be requested (comma-separated)\n" 827 | " -R Do not request any options except those specified with -r\n" 828 | " -s