├── .vscode └── settings.json ├── .gitignore ├── .github └── workflows │ ├── scripts │ └── ci_helpers.sh │ ├── formal.yml │ └── ci.yml ├── src ├── dhcpv6-pxe.h ├── statefiles.h ├── dhcpv6-ia.h ├── dhcpv6-pxe.c ├── router.h ├── dhcpv6.h ├── dhcpv4.h ├── ndp.c ├── ubus.c ├── statefiles.c ├── odhcpd.h ├── odhcpd.c ├── netlink.c └── dhcpv6.c ├── scripts └── devel-build.sh ├── CMakeLists.txt ├── README.md └── COPYING /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 8 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .cproject 3 | odhcpd 4 | config.log 5 | CMakeCache.txt 6 | CMakeFiles 7 | CPackConfig.cmake 8 | CPackSourceConfig.cmake 9 | _CPack_Packages 10 | Makefile 11 | cmake_install.cmake 12 | install_manifest.txt 13 | *.deb 14 | build 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 | -------------------------------------------------------------------------------- /src/dhcpv6-pxe.h: -------------------------------------------------------------------------------- 1 | #ifndef _DHCPV6_PXE_H_ 2 | #define _DHCPV6_PXE_H_ 3 | 4 | #include 5 | #include 6 | 7 | // The detail is hidden except for dhcpv6-pxe.c 8 | struct ipv6_pxe_entry; 9 | 10 | const struct ipv6_pxe_entry* ipv6_pxe_entry_new(uint32_t arch, const char* url); 11 | const struct ipv6_pxe_entry* ipv6_pxe_of_arch(uint16_t arch); 12 | void ipv6_pxe_serve_boot_url(uint16_t arch, struct iovec* iov); 13 | void ipv6_pxe_dump(void); 14 | void ipv6_pxe_clear(void); 15 | 16 | #endif /* _DHCPV6_PXE_H_ */ 17 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /src/statefiles.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 David Härdeman 3 | * SPDX-FileCopyrightText: 2025 Álvaro Fernández Rojas 4 | * 5 | * SPDX-License-Identifier: GPL2.0-only 6 | */ 7 | 8 | #ifndef _STATEFILES_H_ 9 | #define _STATEFILES_H_ 10 | 11 | #define ODHCPD_HOSTS_FILE_PREFIX "odhcpd.hosts" 12 | #define ODHCPD_PIO_FILE_PREFIX "odhcpd.pio" 13 | #define ODHCPD_TMP_FILE ".odhcpd.tmp" 14 | 15 | void statefiles_read_prefix_information(struct interface *iface); 16 | 17 | void statefiles_write_prefix_information(struct interface *iface); 18 | 19 | bool statefiles_write(void); 20 | 21 | void statefiles_setup_dirfd(const char *path, int *dirfdp); 22 | 23 | #endif /* _STATEFILES_H_ */ 24 | -------------------------------------------------------------------------------- /src/dhcpv6-ia.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Steven Barth 3 | * Copyright (C) 2016 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 _DHCPV6_IA_H_ 17 | #define _DHCPV6_IA_H_ 18 | 19 | #define ADDR_ENTRY_VALID_IA_ADDR(iface, i, m, addrs) \ 20 | ((iface)->dhcpv6_assignall || \ 21 | (i) == (m) || \ 22 | (addrs)[(i)].prefix_len > 64) 23 | 24 | size_t get_preferred_addr(const struct odhcpd_ipaddr *addrs, const size_t addrlen); 25 | 26 | struct in6_addr in6_from_prefix_and_iid(const struct odhcpd_ipaddr *prefix, uint64_t iid); 27 | 28 | static inline bool valid_prefix_length(const struct dhcpv6_lease *a, const uint8_t prefix_length) 29 | { 30 | return a->length > prefix_length; 31 | } 32 | 33 | static inline bool valid_addr(const struct odhcpd_ipaddr *addr, time_t now) 34 | { 35 | return (addr->prefix_len <= 96 && addr->valid_lt > (uint32_t)now && addr->preferred_lt > (uint32_t)now); 36 | } 37 | 38 | #endif /* _DHCPV6_IA_H_ */ 39 | -------------------------------------------------------------------------------- /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/odhcpd.c" ]; then 9 | echo "odhcpd checkout not found" >&2 10 | exit 1 11 | fi 12 | 13 | if [ $# -eq 0 ]; then 14 | BUILD_ARGS="-DDHCPV4_SUPPORT=ON -DUBUS=ON" 15 | else 16 | BUILD_ARGS="$@" 17 | fi 18 | 19 | # Create build dirs 20 | ODHCPDDIR="$(pwd)" 21 | BUILDDIR="${ODHCPDDIR}/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 "libnl-tiny" ] || git clone https://github.com/openwrt/libnl-tiny.git 30 | [ -e "libubox" ] || git clone https://github.com/openwrt/libubox.git 31 | [ -e "uci" ] || git clone https://github.com/openwrt/uci.git 32 | [ -e "ubus" ] || git clone https://github.com/openwrt/ubus.git 33 | 34 | # Build json-c 35 | cd "${DEPSDIR}/json-c" 36 | cmake \ 37 | -S . \ 38 | -B . \ 39 | -DCMAKE_PREFIX_PATH="${BUILDDIR}" \ 40 | -DBUILD_SHARED_LIBS=ON \ 41 | -DBUILD_STATIC_LIBS=OFF \ 42 | -DDISABLE_EXTRA_LIBS=ON \ 43 | -DBUILD_TESTING=OFF \ 44 | --install-prefix "${BUILDDIR}" 45 | make 46 | make install 47 | 48 | # Build libnl-tiny 49 | cd "${DEPSDIR}/libnl-tiny" 50 | cmake \ 51 | -S . \ 52 | -B . \ 53 | -DCMAKE_PREFIX_PATH="${BUILDDIR}" \ 54 | --install-prefix "${BUILDDIR}" 55 | make 56 | make install 57 | 58 | # Build libubox 59 | cd "${DEPSDIR}/libubox" 60 | cmake \ 61 | -S . \ 62 | -B . \ 63 | -DCMAKE_PREFIX_PATH="${BUILDDIR}" \ 64 | -DBUILD_LUA=OFF \ 65 | -DBUILD_EXAMPLES=OFF \ 66 | --install-prefix "${BUILDDIR}" 67 | make 68 | make install 69 | 70 | # Build ubus 71 | cd "${DEPSDIR}/ubus" 72 | cmake \ 73 | -S . \ 74 | -B . \ 75 | -DCMAKE_PREFIX_PATH="${BUILDDIR}" \ 76 | -DBUILD_LUA=OFF \ 77 | -DBUILD_STATIC=OFF \ 78 | -DBUILD_EXAMPLES=OFF \ 79 | --install-prefix "${BUILDDIR}" 80 | make 81 | make install 82 | 83 | # Build uci 84 | cd "${DEPSDIR}/uci" 85 | cmake \ 86 | -S . \ 87 | -B . \ 88 | -DCMAKE_PREFIX_PATH="${BUILDDIR}" \ 89 | -DBUILD_LUA=OFF \ 90 | -DBUILD_STATIC=OFF \ 91 | --install-prefix "${BUILDDIR}" 92 | make 93 | make install 94 | 95 | # Build odhcpd 96 | cd "${ODHCPDDIR}" 97 | cmake \ 98 | -S . \ 99 | -B "${BUILDDIR}" \ 100 | -DCMAKE_PREFIX_PATH="${BUILDDIR}" \ 101 | ${BUILD_ARGS} 102 | make -C "${BUILDDIR}" 103 | 104 | set +x 105 | echo "✅ Success - the odhcpd binary is available at ${BUILDDIR}/odhcpd" 106 | echo "👷 You can rebuild odhcpd by running 'make -C build'" 107 | 108 | exit 0 109 | -------------------------------------------------------------------------------- /src/dhcpv6-pxe.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include "dhcpv6.h" 7 | #include "dhcpv6-pxe.h" 8 | 9 | struct ipv6_pxe_entry { 10 | struct list_head list; // List head for linking 11 | uint32_t arch; 12 | 13 | // Ready to send 14 | struct _o_packed { 15 | uint16_t type; // In network endianess 16 | uint16_t len; // In network endianess, without /0 17 | char payload[]; // Null-terminated here 18 | } bootfile_url; 19 | }; 20 | 21 | static struct ipv6_pxe_entry* ipv6_pxe_default = NULL; 22 | LIST_HEAD(ipv6_pxe_list); 23 | 24 | const struct ipv6_pxe_entry* ipv6_pxe_entry_new(uint32_t arch, const char* url) { 25 | size_t url_len = strlen(url); 26 | struct ipv6_pxe_entry* ipe = malloc(sizeof(struct ipv6_pxe_entry) + url_len + 1); 27 | if (!ipe) 28 | return NULL; 29 | 30 | memcpy(ipe->bootfile_url.payload, url, url_len + 1); 31 | ipe->bootfile_url.len = htons(url_len); 32 | ipe->bootfile_url.type = htons(DHCPV6_OPT_BOOTFILE_URL); 33 | 34 | if (arch == 0xFFFFFFFF) { 35 | ipv6_pxe_default = ipe; 36 | } 37 | else { 38 | ipe->arch = arch; 39 | list_add(&ipe->list, &ipv6_pxe_list); 40 | } 41 | 42 | return ipe; 43 | } 44 | 45 | const struct ipv6_pxe_entry* ipv6_pxe_of_arch(uint16_t arch) { 46 | struct ipv6_pxe_entry* entry; 47 | list_for_each_entry(entry, &ipv6_pxe_list, list) { 48 | if (arch == entry->arch) 49 | return entry; 50 | } 51 | 52 | return ipv6_pxe_default; 53 | } 54 | 55 | void ipv6_pxe_serve_boot_url(uint16_t arch, struct iovec* iov) { 56 | const struct ipv6_pxe_entry* entry = ipv6_pxe_of_arch(arch); 57 | 58 | if (entry == NULL) { 59 | // No IPv6 PxE bootfile defined 60 | iov->iov_base = NULL; 61 | iov->iov_len = 0; 62 | } 63 | else { 64 | iov->iov_base = (void*)&(entry->bootfile_url); 65 | iov->iov_len = 4 + ntohs(entry->bootfile_url.len); 66 | info("Serve IPv6 PxE, arch = %d, url = %s", arch, entry->bootfile_url.payload); 67 | } 68 | } 69 | 70 | void ipv6_pxe_dump(void) { 71 | struct ipv6_pxe_entry* entry; 72 | int count = 0; 73 | 74 | if (ipv6_pxe_default) 75 | count++; 76 | 77 | list_for_each_entry(entry, &ipv6_pxe_list, list) { 78 | count++; 79 | } 80 | 81 | if (count) { 82 | info("IPv6 PxE URLs:\n"); 83 | 84 | list_for_each_entry(entry, &ipv6_pxe_list, list) 85 | info("\tarch %04d = %s\n", entry->arch, entry->bootfile_url.payload); 86 | 87 | if (ipv6_pxe_default) 88 | info("\tDefault = %s\n", ipv6_pxe_default->bootfile_url.payload); 89 | } 90 | } 91 | 92 | void ipv6_pxe_clear(void) { 93 | struct ipv6_pxe_entry* entry, * temp; 94 | list_for_each_entry_safe(entry, temp, &ipv6_pxe_list, list) { 95 | list_del(&entry->list); 96 | free(entry); 97 | } 98 | 99 | if (ipv6_pxe_default) { 100 | free(ipv6_pxe_default); 101 | ipv6_pxe_default = NULL; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/router.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2012-2013 Steven Barth 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License v2 as published by 6 | * the Free Software Foundation. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | */ 14 | 15 | #ifndef _ROUTER_H_ 16 | #define _ROUTER_H_ 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | struct icmpv6_opt { 23 | uint8_t type; 24 | uint8_t len; 25 | uint8_t data[6]; 26 | }; 27 | 28 | 29 | #define icmpv6_for_each_option(opt, start, end)\ 30 | for (opt = (struct icmpv6_opt*)(start);\ 31 | (void*)(opt + 1) <= (void*)(end) && opt->len > 0 &&\ 32 | (void*)(opt + opt->len) <= (void*)(end); opt += opt->len) 33 | 34 | 35 | #define MaxInitialRtrAdvInterval 16 36 | #define MaxInitialRtAdvs 3 37 | /* RFC8319 §4 38 | * This document updates §4.2 and 6.2.1 of [RFC4861] to change 39 | * the following router configuration variables. 40 | * 41 | * In §6.2.1, inside the paragraph that defines 42 | * MaxRtrAdvInterval, change 1800 to 65535 seconds. 43 | * 44 | * In §6.2.1, inside the paragraph that defines 45 | * AdvDefaultLifetime, change 9000 to 65535 seconds. 46 | */ 47 | #define MaxRtrAdvInterval 65535 48 | #define MinRtrAdvInterval 3 49 | #define AdvDefaultLifetime 65535 50 | /* RFC8319 §4 51 | * This document updates §4.2 and 6.2.1 of [RFC4861] to change 52 | * the following router configuration variables. 53 | * 54 | * In §4.2, inside the paragraph that defines Router Lifetime, 55 | * change 9000 to 65535 seconds. 56 | * 57 | * Note: this is 16 bit Router Lifetime field in RA packets 58 | */ 59 | /* RFC9096 defines recommended option lifetimes configuration values 60 | * ND_PREFERRED_LIMIT 2700 61 | * ND_VALID_LIMIT 5400 62 | * 63 | * RFC9096 §3.4 64 | * CE routers SHOULD set the "Router Lifetime" of Router Advertisement 65 | * (RA) messages to ND_PREFERRED_LIMIT. 66 | * 67 | * Note: while the RFC recommends SHOULD of ND_PREFERRED_LIMIT, this 68 | * define is used to cap values to a sane ceiling, i.e. ND_VALID_LIMIT. 69 | */ 70 | #define RouterLifetime 5400 71 | /* RFC4861 §6.2.1 : AdvReachableTime : 72 | * MUST be no greater than 3,600,000 msec 73 | */ 74 | #define AdvReachableTime 3600000 75 | /* RFC4861 §6.2.1 : AdvCurHopLimit 76 | * The value should be set to the current 77 | * diameter of the Internet. The value zero means 78 | * unspecified (by this router). 79 | * 80 | * Note: this value is an 8 bit int, so max 255. 81 | */ 82 | #define AdvCurHopLimit 255 83 | /* RFC4861 §10 - constants 84 | * Node constants: 85 | * RETRANS_TIMER 1,000 milliseconds 86 | */ 87 | #define RETRANS_TIMER_MAX 60000 88 | /* RFC2460 §5 89 | * IPv6 requires that every link in the internet have an MTU of 1280 90 | * octets or greater. 91 | */ 92 | #define RA_MTU_MIN 1280 93 | #define RA_MTU_MAX 65535 94 | 95 | #define ND_RA_FLAG_PROXY 0x4 96 | #define ND_RA_PREF_HIGH (1 << 3) 97 | #define ND_RA_PREF_LOW (3 << 3) 98 | 99 | /* RFC9762 DHCPv6 PD Availability - Preferred Flag 100 | * use this until it is defined in netinet/icmp6.h 101 | */ 102 | #define ND_OPT_PI_FLAG_PD_PREFERRED 0x10 103 | 104 | #endif /* _ROUTER_H_ */ 105 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | cmake_policy(SET CMP0015 NEW) 3 | 4 | 5 | # Project Definition 6 | project(odhcpd LANGUAGES C) 7 | add_executable(${PROJECT_NAME}) 8 | target_sources(${PROJECT_NAME} PRIVATE 9 | src/odhcpd.c 10 | src/config.c 11 | src/dhcpv6.c 12 | src/dhcpv6-ia.c 13 | src/dhcpv6-pxe.c 14 | src/ndp.c 15 | src/netlink.c 16 | src/statefiles.c 17 | src/router.c 18 | ) 19 | 20 | 21 | # Compiler Options 22 | set_target_properties(${PROJECT_NAME} PROPERTIES C_STANDARD 11) 23 | target_compile_definitions(${PROJECT_NAME} PRIVATE _GNU_SOURCE) 24 | target_compile_options(${PROJECT_NAME} PRIVATE -g3) 25 | target_compile_options(${PROJECT_NAME} PRIVATE -Os) 26 | target_compile_options(${PROJECT_NAME} PRIVATE -Wall) 27 | target_compile_options(${PROJECT_NAME} PRIVATE -Werror) 28 | target_compile_options(${PROJECT_NAME} PRIVATE -Wextra) 29 | target_compile_options(${PROJECT_NAME} PRIVATE -Werror=implicit-function-declaration) 30 | target_compile_options(${PROJECT_NAME} PRIVATE -Wformat) 31 | target_compile_options(${PROJECT_NAME} PRIVATE -Werror=format-security) 32 | target_compile_options(${PROJECT_NAME} PRIVATE -Werror=format-nonliteral) 33 | target_compile_options(${PROJECT_NAME} PRIVATE -Wimplicit-fallthrough=5) 34 | target_compile_options(${PROJECT_NAME} PRIVATE -Wno-shadow=compatible-local) 35 | target_compile_options(${PROJECT_NAME} PRIVATE -Wno-unused-parameter) 36 | target_compile_options(${PROJECT_NAME} PRIVATE -Wmissing-declarations) 37 | target_compile_options(${PROJECT_NAME} PRIVATE -Wshadow=local) 38 | 39 | 40 | # Libraries 41 | target_link_libraries(${PROJECT_NAME} PRIVATE resolv) 42 | 43 | find_path(uci_include_dir uci.h) 44 | target_include_directories(${PROJECT_NAME} PRIVATE ${uci_include_dir}) 45 | find_library(libuci uci) 46 | target_link_libraries(${PROJECT_NAME} PRIVATE ${libuci}) 47 | 48 | find_path(ubox_include_dir uloop.h PATH_SUFFIXES libubox) 49 | target_include_directories(${PROJECT_NAME} PRIVATE ${ubox_include_dir}) 50 | find_library(libubox ubox) 51 | target_link_libraries(${PROJECT_NAME} PRIVATE ${libubox}) 52 | 53 | find_path(libnl-tiny_include_dir netlink-generic.h PATH_SUFFIXES libnl-tiny) 54 | target_include_directories(${PROJECT_NAME} PRIVATE ${libnl-tiny_include_dir}) 55 | find_library(libnl nl-tiny) 56 | target_link_libraries(${PROJECT_NAME} PRIVATE ${libnl}) 57 | 58 | find_path(json_include_dir json.h PATH_SUFFIXES json-c) 59 | target_include_directories(${PROJECT_NAME} PRIVATE ${json_include_dir}) 60 | find_library(libjson json-c) 61 | target_link_libraries(${PROJECT_NAME} PRIVATE ${libjson}) 62 | 63 | 64 | # Optional Features 65 | if(${UBUS}) 66 | target_compile_definitions(${PROJECT_NAME} PRIVATE WITH_UBUS) 67 | target_sources(${PROJECT_NAME} PRIVATE src/ubus.c) 68 | find_path(ubus_include_dir libubus.h) 69 | target_include_directories(${PROJECT_NAME} PRIVATE ${ubus_include_dir}) 70 | find_library(libubus ubus) 71 | target_link_libraries(${PROJECT_NAME} PRIVATE ${libubus}) 72 | endif(${UBUS}) 73 | 74 | if(${DHCPV4_SUPPORT}) 75 | target_compile_definitions(${PROJECT_NAME} PRIVATE DHCPV4_SUPPORT) 76 | target_sources(${PROJECT_NAME} PRIVATE src/dhcpv4.c) 77 | endif(${DHCPV4_SUPPORT}) 78 | 79 | 80 | # Installation 81 | install(TARGETS ${PROJECT_NAME} DESTINATION sbin/) 82 | 83 | 84 | # Packaging Information 85 | set(CPACK_PACKAGE_VERSION "1") 86 | set(CPACK_PACKAGE_CONTACT "Steven Barth ") 87 | set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${PROJECT_NAME}") 88 | set(CPACK_GENERATOR "DEB;RPM;STGZ") 89 | set(CPACK_STRIP_FILES true) 90 | set(CPACK_DEBIAN_PACKAGE_VERSION ${CPACK_PACKAGE_VERSION}) 91 | set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}_${CPACK_DEBIAN_PACKAGE_VERSION}") 92 | include(CPack) 93 | -------------------------------------------------------------------------------- /src/dhcpv6.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2012 Steven Barth 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License version 2 6 | * as published by the Free Software Foundation. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License version 2 for more details. 12 | * 13 | */ 14 | 15 | #ifndef _DHCPV6_H_ 16 | #define _DHCPV6_H_ 17 | 18 | #include "odhcpd.h" 19 | 20 | #define ALL_DHCPV6_RELAYS "ff02::1:2" 21 | 22 | #define ALL_DHCPV6_SERVERS "ff05::1:3" 23 | 24 | #define DHCPV6_CLIENT_PORT 546 25 | #define DHCPV6_SERVER_PORT 547 26 | 27 | /* RFC8415 */ 28 | #define DHCPV6_MSG_SOLICIT 1 29 | #define DHCPV6_MSG_ADVERTISE 2 30 | #define DHCPV6_MSG_REQUEST 3 31 | #define DHCPV6_MSG_CONFIRM 4 32 | #define DHCPV6_MSG_RENEW 5 33 | #define DHCPV6_MSG_REBIND 6 34 | #define DHCPV6_MSG_REPLY 7 35 | #define DHCPV6_MSG_RELEASE 8 36 | #define DHCPV6_MSG_DECLINE 9 37 | #define DHCPV6_MSG_RECONFIGURE 10 38 | #define DHCPV6_MSG_INFORMATION_REQUEST 11 39 | #define DHCPV6_MSG_RELAY_FORW 12 40 | #define DHCPV6_MSG_RELAY_REPL 13 41 | /* RFC7341 */ 42 | #define DHCPV6_MSG_DHCPV4_QUERY 20 43 | #define DHCPV6_MSG_DHCPV4_RESPONSE 21 44 | 45 | #define DHCPV6_OPT_CLIENTID 1 46 | #define DHCPV6_OPT_SERVERID 2 47 | #define DHCPV6_OPT_IA_NA 3 48 | #define DHCPV6_OPT_IA_ADDR 5 49 | #define DHCPV6_OPT_ORO 6 50 | #define DHCPV6_OPT_STATUS 13 51 | #define DHCPV6_OPT_RELAY_MSG 9 52 | #define DHCPV6_OPT_AUTH 11 53 | #define DHCPV6_OPT_RAPID_COMMIT 14 54 | #define DHCPV6_OPT_USER_CLASS 15 55 | #define DHCPV6_OPT_INTERFACE_ID 18 56 | #define DHCPV6_OPT_RECONF_MSG 19 57 | #define DHCPV6_OPT_RECONF_ACCEPT 20 58 | #define DHCPV6_OPT_DNS_SERVERS 23 59 | #define DHCPV6_OPT_DNS_DOMAIN 24 60 | #define DHCPV6_OPT_IA_PD 25 61 | #define DHCPV6_OPT_IA_PREFIX 26 62 | #define DHCPV6_OPT_SNTP_SERVERS 31 63 | #define DHCPV6_OPT_INFO_REFRESH 32 64 | #define DHCPV6_OPT_FQDN 39 65 | /* RFC 4833 */ 66 | #define DHCPV6_OPT_NEW_POSIX_TIMEZONE 41 67 | #define DHCPV6_OPT_NEW_TZDB_TIMEZONE 42 68 | 69 | #define DHCPV6_OPT_NTP_SERVERS 56 70 | #define DHCPV6_OPT_BOOTFILE_URL 59 71 | #define DHCPV6_OPT_BOOTFILE_PARAM 60 72 | #define DHCPV6_OPT_CLIENT_ARCH 61 73 | #define DHCPV6_OPT_SOL_MAX_RT 82 74 | #define DHCPV6_OPT_INF_MAX_RT 83 75 | #define DHCPV6_OPT_DHCPV4_MSG 87 76 | #define DHCPV6_OPT_4O6_SERVER 88 77 | /* RFC8910 */ 78 | #define DHCPV6_OPT_CAPTIVE_PORTAL 103 79 | #define DHCPV6_OPT_DNR 144 80 | 81 | #define DHCPV6_DUID_VENDOR 2 82 | 83 | #define DHCPV6_STATUS_OK 0 84 | #define DHCPV6_STATUS_NOADDRSAVAIL 2 85 | #define DHCPV6_STATUS_NOBINDING 3 86 | #define DHCPV6_STATUS_NOTONLINK 4 87 | #define DHCPV6_STATUS_USEMULTICAST 5 88 | #define DHCPV6_STATUS_NOPREFIXAVAIL 6 89 | 90 | #define DHCPV6_HOP_COUNT_LIMIT 32 91 | 92 | #define DHCPV6_REC_TIMEOUT 2000 /* msec */ 93 | #define DHCPV6_REC_MAX_RC 8 94 | 95 | struct dhcpv6_client_header { 96 | uint8_t msg_type; 97 | uint8_t transaction_id[3]; 98 | } _o_packed; 99 | 100 | struct dhcpv6_relay_header { 101 | uint8_t msg_type; 102 | uint8_t hop_count; 103 | struct in6_addr link_address; 104 | struct in6_addr peer_address; 105 | uint8_t options[]; 106 | } _o_packed; 107 | 108 | struct dhcpv6_relay_forward_envelope { 109 | uint8_t msg_type; 110 | uint8_t hop_count; 111 | struct in6_addr link_address; 112 | struct in6_addr peer_address; 113 | uint16_t interface_id_type; 114 | uint16_t interface_id_len; 115 | uint32_t interface_id_data; 116 | uint16_t relay_message_type; 117 | uint16_t relay_message_len; 118 | } _o_packed; 119 | 120 | struct dhcpv6_auth_reconfigure { 121 | uint16_t type; 122 | uint16_t len; 123 | uint8_t protocol; 124 | uint8_t algorithm; 125 | uint8_t rdm; 126 | uint32_t replay[2]; 127 | uint8_t reconf_type; 128 | uint8_t key[16]; 129 | } _o_packed; 130 | 131 | struct dhcpv6_ia_hdr { 132 | uint16_t type; 133 | uint16_t len; 134 | uint32_t iaid; 135 | uint32_t t1; 136 | uint32_t t2; 137 | } _o_packed; 138 | 139 | struct dhcpv6_ia_prefix { 140 | uint16_t type; 141 | uint16_t len; 142 | uint32_t preferred_lt; 143 | uint32_t valid_lt; 144 | uint8_t prefix_len; 145 | struct in6_addr addr; 146 | } _o_packed; 147 | 148 | struct dhcpv6_ia_addr { 149 | uint16_t type; 150 | uint16_t len; 151 | struct in6_addr addr; 152 | uint32_t preferred_lt; 153 | uint32_t valid_lt; 154 | } _o_packed; 155 | 156 | struct dhcpv6_cer_id { 157 | uint16_t type; 158 | uint16_t len; 159 | uint16_t reserved; 160 | uint16_t auth_type; 161 | uint8_t auth[16]; 162 | struct in6_addr addr; 163 | }; 164 | 165 | #define dhcpv6_for_each_option(start, end, otype, olen, odata) \ 166 | for (uint8_t *_o = (uint8_t*)(start); _o + 4 <= (end) && \ 167 | ((otype) = _o[0] << 8 | _o[1]) && ((odata) = (void*)&_o[4]) && \ 168 | ((olen) = _o[2] << 8 | _o[3]) + (odata) <= (end); \ 169 | _o += 4 + (_o[2] << 8 | _o[3])) 170 | 171 | #define dhcpv6_for_each_sub_option(start, end, otype, olen, odata) \ 172 | for (uint8_t *_so = (uint8_t*)(start); _so + 4 <= (end) && \ 173 | ((otype) = _so[0] << 8 | _so[1]) && ((odata) = (void*)&_so[4]) && \ 174 | ((olen) = _so[2] << 8 | _so[3]) + (odata) <= (end); \ 175 | _so += 4 + (_so[2] << 8 | _so[3])) 176 | 177 | #endif /* _DHCPV6_H_ */ 178 | -------------------------------------------------------------------------------- /src/dhcpv4.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2012 Steven Barth 3 | * Copyright (C) 2016 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 version 2 7 | * as published by 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 version 2 for more details. 13 | * 14 | */ 15 | 16 | #ifndef _DHCPV4_H_ 17 | #define _DHCPV4_H_ 18 | 19 | #define DHCPV4_CLIENT_PORT 68 20 | #define DHCPV4_SERVER_PORT 67 21 | #define DHCPV4_MAX_PREFIX_LEN 28 22 | 23 | #define DHCPV4_FLAG_BROADCAST 0x8000 24 | 25 | // RFC951, §3; RFC1542, §2.1; RFC2131, §2 26 | // draft-ietf-dhc-implementation-02, §4.19.1 27 | #define DHCPV4_MIN_PACKET_SIZE 300 28 | 29 | #define DHCPV4_FR_MIN_DELAY 500 30 | #define DHCPV4_FR_MAX_FUZZ 500 31 | 32 | // RFC8925, §3.4 33 | #define DHCPV4_MIN_V6ONLY_WAIT 300 34 | 35 | // RFC4361, §6.1 36 | #define DHCPV4_CLIENTID_TYPE_DUID_IAID 255 37 | 38 | enum dhcpv4_op { 39 | DHCPV4_OP_BOOTREQUEST = 1, 40 | DHCPV4_OP_BOOTREPLY = 2, 41 | }; 42 | 43 | // https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml#message-type-53 44 | enum dhcpv4_msg { 45 | DHCPV4_MSG_DISCOVER = 1, // RFC2132 46 | DHCPV4_MSG_OFFER = 2, // RFC2132 47 | DHCPV4_MSG_REQUEST = 3, // RFC2132 48 | DHCPV4_MSG_DECLINE = 4, // RFC2132 49 | DHCPV4_MSG_ACK = 5, // RFC2132 50 | DHCPV4_MSG_NAK = 6, // RFC2132 51 | DHCPV4_MSG_RELEASE = 7, // RFC2132 52 | DHCPV4_MSG_INFORM = 8, // RFC2132 53 | DHCPV4_MSG_FORCERENEW = 9, // RFC3203 54 | DHCPV4_MSG_LEASEQUERY = 10, // RFC4388 55 | DHCPV4_MSG_LEASEUNASSIGNED = 11, // RFC4388 56 | DHCPV4_MSG_LEASEUNKNOWN = 12, // RFC4388 57 | DHCPV4_MSG_LEASEACTIVE = 13, // RFC4388 58 | DHCPV4_MSG_BULKLEASEQUERY = 14, // RFC6926 59 | DHCPV4_MSG_LEASEQUERYDONE = 15, // RFC6926 60 | DHCPV4_MSG_ACTIVELEASEQUERY = 16, // RFC7724 61 | DHCPV4_MSG_LEASEQUERYSTATUS = 17, // RFC7724 62 | DHCPV4_MSG_TLS = 18, // RFC7724 63 | }; 64 | 65 | enum dhcpv4_opt { 66 | DHCPV4_OPT_PAD = 0, 67 | DHCPV4_OPT_NETMASK = 1, 68 | DHCPV4_OPT_ROUTER = 3, 69 | DHCPV4_OPT_DNSSERVER = 6, 70 | DHCPV4_OPT_HOSTNAME = 12, 71 | DHCPV4_OPT_DOMAIN = 15, 72 | DHCPV4_OPT_REQUEST = 17, 73 | DHCPV4_OPT_MTU = 26, 74 | DHCPV4_OPT_BROADCAST = 28, 75 | DHCPV4_OPT_NTPSERVER = 42, 76 | DHCPV4_OPT_IPADDRESS = 50, 77 | DHCPV4_OPT_LEASETIME = 51, 78 | DHCPV4_OPT_MESSAGE = 53, 79 | DHCPV4_OPT_SERVERID = 54, 80 | DHCPV4_OPT_REQOPTS = 55, 81 | DHCPV4_OPT_RENEW = 58, 82 | DHCPV4_OPT_REBIND = 59, 83 | DHCPV4_OPT_CLIENTID = 61, 84 | DHCPV4_OPT_USER_CLASS = 77, 85 | DHCPV4_OPT_AUTHENTICATION = 90, 86 | DHCPV4_OPT_IPV6_ONLY_PREFERRED = 108, // RFC8925 87 | DHCPV4_OPT_CAPTIVE_PORTAL = 114, // RFC8910 88 | DHCPV4_OPT_DNS_DOMAIN_SEARCH = 119, 89 | DHCPV4_OPT_FORCERENEW_NONCE_CAPABLE = 145, 90 | DHCPV4_OPT_DNR = 162, 91 | DHCPV4_OPT_END = 255, 92 | }; 93 | 94 | struct dhcpv4_message { 95 | uint8_t op; 96 | uint8_t htype; 97 | uint8_t hlen; 98 | uint8_t hops; 99 | uint32_t xid; 100 | uint16_t secs; 101 | uint16_t flags; 102 | struct in_addr ciaddr; 103 | struct in_addr yiaddr; 104 | struct in_addr siaddr; 105 | struct in_addr giaddr; 106 | uint8_t chaddr[16]; 107 | char sname[64]; 108 | char file[128]; 109 | uint32_t cookie; 110 | uint8_t options[]; 111 | } _o_packed; 112 | 113 | // RFC2131, §3 114 | #define DHCPV4_MAGIC_COOKIE 0x63825363 115 | 116 | // RFC3203, §6; RFC3118, §2; RFC6704, §3.1.2 117 | struct dhcpv4_auth_forcerenew { 118 | uint8_t protocol; 119 | uint8_t algorithm; 120 | uint8_t rdm; 121 | uint32_t replay[2]; 122 | uint8_t type; 123 | uint8_t key[16]; 124 | } _o_packed; 125 | 126 | // https://www.iana.org/assignments/auth-namespaces/auth-namespaces.xhtml#auth-namespaces-1 127 | enum dhcpv4_auth_protocol { 128 | DHCPV4_AUTH_PROTO_CFG_TOKEN = 0, // RFC3118 129 | DHCPV4_AUTH_PROTO_DELAYED = 1, // RFC3118 130 | DHCPV4_AUTH_PROTO_DELAYED_OBS = 2, // RFC8415, Obsolete 131 | DHCPV4_AUTH_PROTO_RKAP = 3, // RFC8415, also RFC6704 132 | DHCPV4_AUTH_PROTO_SPLIT_DNS = 4, // RFC9704 133 | }; 134 | 135 | // https://www.iana.org/assignments/auth-namespaces/auth-namespaces.xhtml#auth-namespaces-2 136 | enum dhcpv4_auth_algorithm { 137 | DHCPV4_AUTH_ALG_CFG_TOKEN = 0, // RFC3118 138 | DHCPV4_AUTH_ALG_HMAC_MD5 = 1, // RFC3118, RFC8415, also RFC6704 139 | }; 140 | 141 | // https://www.iana.org/assignments/auth-namespaces/auth-namespaces.xhtml#auth-namespaces-2 142 | enum dhcpv4_auth_rdm { 143 | DHCPV4_AUTH_RDM_MONOTONIC = 0, // RFC3118, RFC8415, also RFC6704 144 | }; 145 | 146 | // RFC6704, §3.1.2 (for DHCPv6: RFC8415, §20.4) 147 | enum dhcpv4_auth_rkap_ai_type { 148 | DHCPV4_AUTH_RKAP_AI_TYPE_KEY = 1, 149 | DHCPV4_AUTH_RKAP_AI_TYPE_MD5_DIGEST = 2, 150 | }; 151 | 152 | struct dhcpv4_option { 153 | uint8_t code; 154 | uint8_t len; 155 | uint8_t data[]; 156 | }; 157 | 158 | struct dhcpv4_option_u8 { 159 | uint8_t code; 160 | uint8_t len; 161 | uint8_t data; 162 | }; 163 | 164 | struct dhcpv4_option_u16 { 165 | uint8_t code; 166 | uint8_t len; 167 | uint16_t data; 168 | }; 169 | 170 | struct dhcpv4_option_u32 { 171 | uint8_t code; 172 | uint8_t len; 173 | uint32_t data; 174 | } _o_packed; 175 | 176 | /* DNR */ 177 | struct dhcpv4_dnr { 178 | uint16_t len; 179 | uint16_t priority; 180 | uint8_t adn_len; 181 | uint8_t body[]; 182 | }; 183 | 184 | 185 | #define dhcpv4_for_each_option(start, end, opt)\ 186 | for (opt = (struct dhcpv4_option*)(start); \ 187 | &opt[1] <= (struct dhcpv4_option*)(end) && \ 188 | &opt->data[opt->len] <= (end); \ 189 | opt = (struct dhcpv4_option*)&opt->data[opt->len]) 190 | 191 | #endif /* _DHCPV4_H_ */ 192 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # odhcpd - Embedded DHCP/DHCPv6/RA Server & Relay 2 | 3 | ## Abstract 4 | 5 | odhcpd is a daemon for serving and relaying IP management protocols to 6 | configure clients and downstream routers. It tries to follow the RFC 6204 7 | requirements for IPv6 home routers. 8 | 9 | odhcpd provides server services for DHCP, RA, stateless and stateful DHCPv6, 10 | prefix delegation and can be used to relay RA, DHCPv6 and NDP between routed 11 | (non-bridged) interfaces in case no delegated prefixes are available. 12 | 13 | 14 | ## Features 15 | 16 | 1. Router Discovery support (solicitations and advertisements) with 2 modes: 17 | * server: RD server for slave interfaces 18 | * automatic detection of prefixes, delegated prefix and default routes, MTU 19 | * automatic reannouncement when changes to prefixes or routes occur 20 | 21 | * relay: RD relay between master and slave interfaces 22 | * support for rewriting announced DNS-server addresses in relay mode 23 | 24 | 2. DHCPv6-support with 2 modes of operation 25 | * server: stateless, stateful and PD-server mode 26 | * stateless and stateful address assignment 27 | * prefix delegation support 28 | * dynamic reconfiguration in case prefixes change 29 | * hostname detection and hosts-files creation 30 | 31 | * relay: mostly standards-compliant DHCPv6-relay 32 | * support for rewriting announced DNS-server addresses 33 | 34 | 3. DHCPv4-support 35 | * server: stateless and stateful mode 36 | 37 | 4. Proxy for Neighbor Discovery messages (solicitations and advertisements) 38 | * support for auto-learning routes to the local routing table 39 | * support for marking interfaces "external" not proxying NDP for them 40 | and only serving NDP for DAD and for traffic to the router itself 41 | [Warning: you should provide additional firewall rules for security] 42 | 43 | 5. IPv6 PxE Support 44 | 45 | 46 | ## Compiling 47 | 48 | odhcpd uses cmake: 49 | * To prepare a Makefile use: `cmake .` 50 | * To build / install use: `make` / `make install` afterwards. 51 | * To build DEB or RPM packages use: `make package` afterwards. 52 | 53 | 54 | ## Configuration 55 | 56 | odhcpd uses a UCI configuration file in `/etc/config/dhcp` for configuration 57 | and may also receive information from ubus 58 | 59 | 60 | ### Section of type odhcpd 61 | 62 | | Option | Type |Default| Description | 63 | | :------------ | :---- | :---- | :---------- | 64 | | maindhcp | bool | 0 | Use odhcpd as the main DHCPv4 service | 65 | | leasefile | string| | DHCPv4/6 lease file | 66 | | leasetrigger | string| | Lease trigger script | 67 | | hostsdir | string| | DHCPv4/v6 hostfile directory (one file per interface will be created) | 68 | | loglevel |integer| 6 | Syslog level priority (0-7) | 69 | | piodir |string | | Directory to store IPv6 prefix information (to detect stale prefixes, see RFC9096, §3.5) | 70 | | enable_tz |bool | 1 | Toggle whether RFC4833 timezone information is sent to clients, if set in system | 71 | 72 | 73 | ### Sections of type dhcp (configure DHCP / DHCPv6 / RA / NDP service) 74 | 75 | | Option | Type |Default| Description | 76 | | :-------------------- | :---- | :---- | :---------- | 77 | | interface |string |``| logical OpenWrt interface | 78 | | ifname |string |``| physical network interface | 79 | | networkid |string |same as ifname| compat. alias for ifname | 80 | | master |bool | 0 | is a master interface for relaying | 81 | | ra |string |disabled| Router Advert service [disabled\|server\|relay\|hybrid] | 82 | | dhcpv6 |string |disabled| DHCPv6 service [disabled\|server\|relay\|hybrid] | 83 | | dhcpv4 |string |disabled| DHCPv4 service [disabled\|server] | 84 | | ndp |string |disabled| Neighbor Discovery Proxy [disabled\|relay\|hybrid] | 85 | | dynamicdhcp |bool | 1 | Dynamically create leases for DHCPv4 and DHCPv6 | 86 | | dhcpv4_forcereconf |bool | 0 | Force reconfiguration by sending force renew message even if the client did not include the force renew nonce capability option (RFC6704) | 87 | | dhcpv6_assignall |bool | 1 | Assign all viable DHCPv6 addresses in statefull mode; if disabled only the DHCPv6 address having the longest preferred lifetime is assigned | 88 | | dhcpv6_hostidlength |integer| 12 | Host ID length of dynamically created leases, allowed values: 12 - 64 (bits). | 89 | | dhcpv6_na |bool | 1 | DHCPv6 stateful addressing hands out IA_NA - Internet Address - Network Address | 90 | | dhcpv6_pd |bool | 1 | DHCPv6 stateful addressing hands out IA_PD - Internet Address - Prefix Delegation (PD) | 91 | | dhcpv6_pd_preferred |bool | 0 | Set the DHCPv6-PD Preferred (P) flag in outgoing ICMPv6 RA message PIOs (RFC9762); requires `dhcpv6` and `dhcpv6_pd`. | 92 | | dhcpv6_pd_min_len |integer| 62 | Minimum prefix length to delegate with IA_PD (adjusted, if necessary, to be longer than the interface prefix length). Range [1,64] | 93 | | router |list |``| IPv4 addresses of routers on a given subnet (provided via DHCPv4, should be in order of preference) | 94 | | dns |list |``| DNS servers to announce, accepts IPv4 and IPv6 | 95 | | dnr |list |disabled| Encrypted DNS servers to announce, ` [ ...]` | 96 | | dns_service |bool | 1 | Announce the address of interface as DNS service if the list of dns is empty | 97 | | domain |list |``| Search domains to announce | 98 | | leasetime |string | 12h | DHCPv4 address leasetime | 99 | | start |integer| 100 | DHCPv4 pool start | 100 | | limit |integer| 150 | DHCPv4 pool size | 101 | | max_preferred_lifetime|string | 45m | Upper limit for the preferred lifetime for a prefix | 102 | | max_valid_lifetime |string | 90m | Upper limit for the valid lifetime for a prefix | 103 | | ra_default |integer| 0 | Override default route - 0: default, 1: ignore no public address, 2: ignore all | 104 | | ra_flags |list |other-config| List of RA flags to be advertised in RA messages [managed-config\|other-config\|home-agent\|none] | 105 | | ra_slaac |bool | 1 | Advertise that prefixes (which are <= 64 bits long) on this interface can be used for SLAAC (the "A" flag in the PIO, RFC4861, §4.6.2) | 106 | | ra_advrouter |bool | 0 | Advertise the IPv6 address of this router in RA messages (the "R" flag in the PIO, RFC6275, §7.2) | 107 | | ra_offlink |bool | 0 | Announce prefixes off-link | 108 | | ra_preference |string | medium| Route(r) preference [medium\|high\|low] | 109 | | ra_maxinterval |integer| 600 | Maximum time allowed between sending unsolicited RA | 110 | | ra_mininterval |integer| 200 | Minimum time allowed between sending unsolicited RA | 111 | | ra_lifetime |integer| 2700 | Value to be placed in Router Lifetime field of RA. Not recommended to be more than 2700 (RFC9096). | 112 | | ra_reachabletime |integer| 0 | Reachable Time in milliseconds to be advertised in RA messages | 113 | | ra_retranstime |integer| 0 | Retransmit Time in milliseconds to be advertised in RA messages | 114 | | ra_hoplimit |integer| 0 | Current hoplimit to be advertised in RA messages | 115 | | ra_mtu |integer| - | MTU to be advertised in RA messages | 116 | | ra_dns |bool | 1 | Announce DNS configuration in RA messages (RFC8106) | 117 | | ra_pref64 |string | - | Announce PREF64 option for NAT64 prefix (RFC8781) [IPv6 prefix] | 118 | | ndproxy_routing |bool | 1 | Learn routes from NDP | 119 | | ndproxy_slave |bool | 0 | NDProxy external slave | 120 | | ndp_from_link_local |bool | 1 | Use link-local source addresses for NDP operations (RFC 4861, §4.2 compliance) and macOS compatibility | 121 | | prefix_filter |string |`::/0` | Only advertise on-link prefixes within the provided IPv6 prefix; others are filtered out. [IPv6 prefix] | 122 | | ntp |list |``| NTP servers to announce accepts IPv4 and IPv6 | 123 | | upstream |list | - | A list of interfaces which can be used as a source of configuration information (e.g. for NTP servers, if not set explicitly). | 124 | | captive_portal_uri |string | no | The API URI to be sent in RFC8910 captive portal options, via DHCPv4, DHCPv6, and ICMPv6 RA. | 125 | | ipv6_only_preferred |integer| 0 | Indicate that IPv6-only mode is preferred (RFC8925) [V6ONLY_WAIT time in seconds] | 126 | 127 | [//]: # "dhcpv6_raw - string - not documented, may change when generic DHCPv4/DHCPv6 options are added" 128 | 129 | 130 | ### Sections of type host (static leases) 131 | | Option | Type |Default| Description | 132 | | :-------------------- | :---- | :---- | :---------- | 133 | | ip |string |(none) | IPv4 host address or `ignore` to ignore any DHCPv4 request from this host | 134 | | mac |list\|string|(none) | HexadecimalMACaddress(es) | 135 | | duid |list\|string|(none) | Hexadecimal DUID(s), or DUID%IAID(s) | 136 | | hostid |string |(none) | IPv6 tokenised IID or `ignore` to ignore any DHCPv6 request from this host | 137 | | name |string |(none) | Hostname | 138 | | leasetime |string |(none) | DHCPv4/v6leasetime | 139 | 140 | 141 | ### Sections of type boot6 142 | | Option | Type |Required|Description | 143 | | :------------ | :---- | :---- | :---------- | 144 | | url |string | yes | e.g. `tftp://[fd11::1]/pxe.efi` | 145 | | arch |integer| no | the arch code. `07` is EFI. If not present, this boot6 will be the default. | 146 | 147 | odhcpd also uses the UCI configuration file `/etc/config/network` for configuration 148 | of the following options: 149 | 150 | ### Section of type globals 151 | | Option | Type |Required|Description | 152 | | :---------------- | :---- | :---- | :---------- | 153 | | dhcp_default_duid |string | no | The DUID to use to identify the DHCPv6 server to clients. | 154 | 155 | 156 | ### System variables for Timezone options (uci system.system) 157 | | Option | Type |Required|Description | 158 | | :------------ | :---- | :---- | :---------- | 159 | | timezone |string | no | e.g. `EST5EDT4,M3.2.0/02:00,M11.1.0/02:00` | 160 | | zonename |string| no | e.g. `Europe/Zurich` | 161 | 162 | 163 | ## ubus Interface 164 | 165 | odhcpd currently exposes the following methods under the `dhcp` object path: 166 | 167 | | Method | Arguments | Description | 168 | | :------------ | :------------ | :---------- | 169 | | `ipv4leases` | `none` | Lists all currently active DHCPv4 leases per interface | 170 | | `ipv6leases` | `none` | Lists all currently active DHCPv6 leases per interface | 171 | | `ipv6ra` | `none` | Lists announced IPv6 prefixes per interface | 172 | | `add_lease` | options as in the cfg `host` section | Creates a new static lease, the arguments need to be formatted as a valid JSON string | 173 | 174 | These can be called by running e.g. `ubus call dhcp ipv6leases` on your OpenWrt 175 | device. 176 | 177 | odhcpd currently broadcasts the following events via ubus: 178 | 179 | | Name | Parameters | Description | 180 | | :------------ | :---------------------------- | :------------ | 181 | | `dhcp.lease4` | `mac,ip,name,interface` | A new DHCPv4 lease has been created | 182 | | `dhcp.release4`| `mac,ip,name,interface` | A DHCPv4 lease has been released by a client | 183 | | `dhcp.expire4`| `mac,ip,name,interface` | A DHCPv4 lease has expired | 184 | 185 | These can be observed by running e.g. `ubus listen dhcp` on your OpenWrt device. 186 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: odhcpd 2 | 3 | on: 4 | pull_request: 5 | push: 6 | 7 | env: 8 | archs: "aarch64 arm mips mips64 powerpc powerpc64 riscv64 x86_64" 9 | variants: "basic DHCPv4 UBUS full" 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-basic: ${{ steps.basic.outputs.size_aarch64 }} 45 | size-aarch64-dhcpv4: ${{ steps.dhcpv4.outputs.size_aarch64 }} 46 | size-aarch64-ubus: ${{ steps.ubus.outputs.size_aarch64 }} 47 | size-aarch64-full: ${{ steps.full.outputs.size_aarch64 }} 48 | size-arm-basic: ${{ steps.basic.outputs.size_arm }} 49 | size-arm-dhcpv4: ${{ steps.dhcpv4.outputs.size_arm }} 50 | size-arm-ubus: ${{ steps.ubus.outputs.size_arm }} 51 | size-arm-full: ${{ steps.full.outputs.size_arm }} 52 | size-mips-basic: ${{ steps.basic.outputs.size_mips }} 53 | size-mips-dhcpv4: ${{ steps.dhcpv4.outputs.size_mips }} 54 | size-mips-ubus: ${{ steps.ubus.outputs.size_mips }} 55 | size-mips-full: ${{ steps.full.outputs.size_mips }} 56 | size-mips64-basic: ${{ steps.basic.outputs.size_mips64 }} 57 | size-mips64-dhcpv4: ${{ steps.dhcpv4.outputs.size_mips64 }} 58 | size-mips64-ubus: ${{ steps.ubus.outputs.size_mips64 }} 59 | size-mips64-full: ${{ steps.full.outputs.size_mips64 }} 60 | size-powerpc-basic: ${{ steps.basic.outputs.size_powerpc }} 61 | size-powerpc-dhcpv4: ${{ steps.dhcpv4.outputs.size_powerpc }} 62 | size-powerpc-ubus: ${{ steps.ubus.outputs.size_powerpc }} 63 | size-powerpc-full: ${{ steps.full.outputs.size_powerpc }} 64 | size-powerpc64-basic: ${{ steps.basic.outputs.size_powerpc64 }} 65 | size-powerpc64-dhcpv4: ${{ steps.dhcpv4.outputs.size_powerpc64 }} 66 | size-powerpc64-ubus: ${{ steps.ubus.outputs.size_powerpc64 }} 67 | size-powerpc64-full: ${{ steps.full.outputs.size_powerpc64 }} 68 | size-riscv64-basic: ${{ steps.basic.outputs.size_riscv64 }} 69 | size-riscv64-dhcpv4: ${{ steps.dhcpv4.outputs.size_riscv64 }} 70 | size-riscv64-ubus: ${{ steps.ubus.outputs.size_riscv64 }} 71 | size-riscv64-full: ${{ steps.full.outputs.size_riscv64 }} 72 | size-x86_64-basic: ${{ steps.basic.outputs.size_x86_64 }} 73 | size-x86_64-dhcpv4: ${{ steps.dhcpv4.outputs.size_x86_64 }} 74 | size-x86_64-ubus: ${{ steps.ubus.outputs.size_x86_64 }} 75 | size-x86_64-full: ${{ steps.full.outputs.size_x86_64 }} 76 | steps: 77 | - name: Checkout odhcpd 78 | uses: actions/checkout@v5 79 | 80 | - name: Checkout json-c 81 | uses: actions/checkout@v5 82 | with: 83 | repository: json-c/json-c 84 | path: depends/json-c 85 | 86 | - name: Checkout libnl-tiny 87 | uses: actions/checkout@v5 88 | with: 89 | repository: openwrt/libnl-tiny 90 | path: depends/libnl-tiny 91 | 92 | - name: Checkout libubox 93 | uses: actions/checkout@v5 94 | with: 95 | repository: openwrt/libubox 96 | path: depends/libubox 97 | 98 | - name: Checkout ubus 99 | uses: actions/checkout@v5 100 | with: 101 | repository: openwrt/ubus 102 | path: depends/ubus 103 | 104 | - name: Checkout uci 105 | uses: actions/checkout@v5 106 | with: 107 | repository: openwrt/uci 108 | path: depends/uci 109 | 110 | - name: Install dependencies 111 | run: | 112 | sudo apt update 113 | sudo apt install ${{ matrix.packages }} 114 | 115 | - name: Prepare build 116 | run: | 117 | mkdir -p ${GITHUB_WORKSPACE}/build 118 | 119 | - name: Build json-c 120 | working-directory: depends/json-c 121 | run: | 122 | cmake \ 123 | -DCMAKE_C_COMPILER=${{ matrix.gcc }} \ 124 | -DCMAKE_PREFIX_PATH=${GITHUB_WORKSPACE}/build \ 125 | -DBUILD_SHARED_LIBS=ON \ 126 | -DBUILD_STATIC_LIBS=OFF \ 127 | -DDISABLE_EXTRA_LIBS=ON \ 128 | -DBUILD_TESTING=OFF \ 129 | --install-prefix ${GITHUB_WORKSPACE}/build \ 130 | -B . -S . 131 | make 132 | make install 133 | 134 | - name: Build libnl-tiny 135 | working-directory: depends/libnl-tiny 136 | run: | 137 | cmake \ 138 | -DCMAKE_C_COMPILER=${{ matrix.gcc }} \ 139 | -DCMAKE_PREFIX_PATH=${GITHUB_WORKSPACE}/build \ 140 | --install-prefix ${GITHUB_WORKSPACE}/build \ 141 | -B . -S . 142 | make 143 | make install 144 | 145 | - name: Build libubox 146 | working-directory: depends/libubox 147 | run: | 148 | cmake \ 149 | -DCMAKE_C_COMPILER=${{ matrix.gcc }} \ 150 | -DCMAKE_PREFIX_PATH=${GITHUB_WORKSPACE}/build \ 151 | -DBUILD_LUA=OFF \ 152 | -DBUILD_EXAMPLES=OFF \ 153 | --install-prefix ${GITHUB_WORKSPACE}/build \ 154 | -B . -S . 155 | make 156 | make install 157 | 158 | - name: Build ubus 159 | working-directory: depends/ubus 160 | run: | 161 | cmake \ 162 | -DCMAKE_C_COMPILER=${{ matrix.gcc }} \ 163 | -DCMAKE_PREFIX_PATH=${GITHUB_WORKSPACE}/build \ 164 | -DBUILD_LUA=OFF \ 165 | -DBUILD_STATIC=OFF \ 166 | -DBUILD_EXAMPLES=OFF \ 167 | --install-prefix ${GITHUB_WORKSPACE}/build \ 168 | -B . -S . 169 | make 170 | make install 171 | 172 | - name: Build uci 173 | working-directory: depends/uci 174 | run: | 175 | cmake \ 176 | -DCMAKE_C_COMPILER=${{ matrix.gcc }} \ 177 | -DCMAKE_PREFIX_PATH=${GITHUB_WORKSPACE}/build \ 178 | -DBUILD_LUA=OFF \ 179 | -DBUILD_STATIC=OFF \ 180 | --install-prefix ${GITHUB_WORKSPACE}/build \ 181 | -B . -S . 182 | make 183 | make install 184 | 185 | - id: basic 186 | name: Build odhcpd (basic) 187 | env: 188 | BUILD_DIR: build/odhcpd-basic 189 | run: | 190 | cmake \ 191 | -DCMAKE_C_COMPILER=${{ matrix.gcc }} \ 192 | -DCMAKE_PREFIX_PATH=${GITHUB_WORKSPACE}/build \ 193 | -B $BUILD_DIR -S . 194 | make -C $BUILD_DIR 195 | echo "size_${{ matrix.arch }}=$( find $BUILD_DIR -type f -name odhcpd -printf '%s' )" >> $GITHUB_OUTPUT 196 | 197 | - id: dhcpv4 198 | name: Build odhcpd (DHCPv4) 199 | env: 200 | BUILD_DIR: build/odhcpd-dhcpv4 201 | run: | 202 | cmake \ 203 | -DCMAKE_C_COMPILER=${{ matrix.gcc }} \ 204 | -DCMAKE_PREFIX_PATH=${GITHUB_WORKSPACE}/build \ 205 | -DDHCPV4_SUPPORT=ON \ 206 | -B $BUILD_DIR -S . 207 | make -C $BUILD_DIR 208 | echo "size_${{ matrix.arch }}=$( find $BUILD_DIR -type f -name odhcpd -printf '%s' )" >> $GITHUB_OUTPUT 209 | 210 | - id: ubus 211 | name: Build odhcpd (UBUS) 212 | env: 213 | BUILD_DIR: build/odhcpd-ubus 214 | run: | 215 | cmake \ 216 | -DCMAKE_C_COMPILER=${{ matrix.gcc }} \ 217 | -DCMAKE_PREFIX_PATH=${GITHUB_WORKSPACE}/build \ 218 | -DUBUS=ON \ 219 | -B $BUILD_DIR -S . 220 | make -C $BUILD_DIR 221 | echo "size_${{ matrix.arch }}=$( find $BUILD_DIR -type f -name odhcpd -printf '%s' )" >> $GITHUB_OUTPUT 222 | 223 | - id: full 224 | name: Build odhcpd (full) 225 | env: 226 | BUILD_DIR: build/odhcpd-full 227 | run: | 228 | cmake \ 229 | -DCMAKE_C_COMPILER=${{ matrix.gcc }} \ 230 | -DCMAKE_PREFIX_PATH=${GITHUB_WORKSPACE}/build \ 231 | -DDHCPV4_SUPPORT=ON \ 232 | -DUBUS=ON \ 233 | -B $BUILD_DIR -S . 234 | make -C $BUILD_DIR 235 | echo "size_${{ matrix.arch }}=$( find $BUILD_DIR -type f -name odhcpd -printf '%s' )" >> $GITHUB_OUTPUT 236 | 237 | - name: Upload binaries 238 | uses: actions/upload-artifact@v4 239 | with: 240 | name: odhcpd-${{ matrix.arch }}-binaries 241 | path: | 242 | build/odhcpd-*/odhcpd 243 | if-no-files-found: error 244 | 245 | summary: 246 | name: Sizes 247 | needs: [build] 248 | runs-on: ubuntu-latest 249 | steps: 250 | - name: Sizes summary 251 | env: 252 | size_aarch64_basic: ${{needs.build.outputs.size-aarch64-basic}} 253 | size_aarch64_dhcpv4: ${{needs.build.outputs.size-aarch64-dhcpv4}} 254 | size_aarch64_ubus: ${{needs.build.outputs.size-aarch64-ubus}} 255 | size_aarch64_full: ${{needs.build.outputs.size-aarch64-full}} 256 | size_arm_basic: ${{needs.build.outputs.size-arm-basic}} 257 | size_arm_dhcpv4: ${{needs.build.outputs.size-arm-dhcpv4}} 258 | size_arm_ubus: ${{needs.build.outputs.size-arm-ubus}} 259 | size_arm_full: ${{needs.build.outputs.size-arm-full}} 260 | size_mips_basic: ${{needs.build.outputs.size-mips-basic}} 261 | size_mips_dhcpv4: ${{needs.build.outputs.size-mips-dhcpv4}} 262 | size_mips_ubus: ${{needs.build.outputs.size-mips-ubus}} 263 | size_mips_full: ${{needs.build.outputs.size-mips-full}} 264 | size_mips64_basic: ${{needs.build.outputs.size-mips64-basic}} 265 | size_mips64_dhcpv4: ${{needs.build.outputs.size-mips64-dhcpv4}} 266 | size_mips64_ubus: ${{needs.build.outputs.size-mips64-ubus}} 267 | size_mips64_full: ${{needs.build.outputs.size-mips64-full}} 268 | size_powerpc_basic: ${{needs.build.outputs.size-powerpc-basic}} 269 | size_powerpc_dhcpv4: ${{needs.build.outputs.size-powerpc-dhcpv4}} 270 | size_powerpc_ubus: ${{needs.build.outputs.size-powerpc-ubus}} 271 | size_powerpc_full: ${{needs.build.outputs.size-powerpc-full}} 272 | size_powerpc64_basic: ${{needs.build.outputs.size-powerpc64-basic}} 273 | size_powerpc64_dhcpv4: ${{needs.build.outputs.size-powerpc64-dhcpv4}} 274 | size_powerpc64_ubus: ${{needs.build.outputs.size-powerpc64-ubus}} 275 | size_powerpc64_full: ${{needs.build.outputs.size-powerpc64-full}} 276 | size_riscv64_basic: ${{needs.build.outputs.size-riscv64-basic}} 277 | size_riscv64_dhcpv4: ${{needs.build.outputs.size-riscv64-dhcpv4}} 278 | size_riscv64_ubus: ${{needs.build.outputs.size-riscv64-ubus}} 279 | size_riscv64_full: ${{needs.build.outputs.size-riscv64-full}} 280 | size_x86_64_basic: ${{needs.build.outputs.size-x86_64-basic}} 281 | size_x86_64_dhcpv4: ${{needs.build.outputs.size-x86_64-dhcpv4}} 282 | size_x86_64_ubus: ${{needs.build.outputs.size-x86_64-ubus}} 283 | size_x86_64_full: ${{needs.build.outputs.size-x86_64-full}} 284 | run: | 285 | echo "### ${GITHUB_WORKFLOW} sizes :floppy_disk:" >> $GITHUB_STEP_SUMMARY 286 | 287 | header="| arch |" 288 | table="| :---: |" 289 | for variant in ${{ env.variants }}; do 290 | header="$header $variant |" 291 | table="$table :---: |" 292 | done 293 | echo $header >> $GITHUB_STEP_SUMMARY 294 | echo $table >> $GITHUB_STEP_SUMMARY 295 | 296 | for arch in ${{ env.archs }}; do 297 | row="| $arch | " 298 | for variant in $variants; do 299 | value=size_${arch}_$(echo "$variant" | tr "[:upper:]" "[:lower:]") 300 | row="$row ${!value} |" 301 | done 302 | echo $row >> $GITHUB_STEP_SUMMARY 303 | done 304 | -------------------------------------------------------------------------------- /src/ndp.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2012-2013 Steven Barth 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License v2 as published by 6 | * the Free Software Foundation. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | */ 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | 32 | #include "dhcpv6.h" 33 | #include "odhcpd.h" 34 | 35 | 36 | static void ndp_netevent_cb(unsigned long event, struct netevent_handler_info *info); 37 | static void setup_route(struct in6_addr *addr, struct interface *iface, bool add); 38 | static void setup_addr_for_relaying(struct in6_addr *addr, struct interface *iface, bool add); 39 | static void handle_solicit(void *addr, void *data, size_t len, 40 | struct interface *iface, void *dest); 41 | 42 | static struct netevent_handler ndp_netevent_handler = { .cb = ndp_netevent_cb, }; 43 | 44 | /* Initialize NDP-proxy */ 45 | int ndp_init(void) 46 | { 47 | int ret = 0; 48 | 49 | if (netlink_add_netevent_handler(&ndp_netevent_handler) < 0) { 50 | error("Failed to add ndp netevent handler"); 51 | ret = -1; 52 | } 53 | 54 | return ret; 55 | } 56 | 57 | int ndp_setup_interface(struct interface *iface, bool enable) 58 | { 59 | /* Drop everything */ 60 | static const struct sock_filter bpf_drop_filter[] = { 61 | BPF_STMT(BPF_RET | BPF_K, 0), 62 | }; 63 | static const struct sock_fprog bpf_drop = { 64 | .len = ARRAY_SIZE(bpf_drop_filter), 65 | .filter = (struct sock_filter *)bpf_drop_filter, 66 | }; 67 | 68 | /* Filter ICMPv6 messages of type neighbor solicitation */ 69 | static const struct sock_filter bpf[] = { 70 | BPF_STMT(BPF_LD | BPF_B | BPF_ABS, offsetof(struct ip6_hdr, ip6_nxt)), 71 | BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 3), 72 | BPF_STMT(BPF_LD | BPF_B | BPF_ABS, sizeof(struct ip6_hdr) + 73 | offsetof(struct icmp6_hdr, icmp6_type)), 74 | BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_NEIGHBOR_SOLICIT, 0, 1), 75 | BPF_STMT(BPF_RET | BPF_K, 0xffffffff), 76 | BPF_STMT(BPF_RET | BPF_K, 0), 77 | }; 78 | static const struct sock_fprog bpf_prog = { 79 | .len = ARRAY_SIZE(bpf), 80 | .filter = (struct sock_filter *)bpf, 81 | }; 82 | 83 | int ret = 0, procfd; 84 | bool dump_neigh = false; 85 | char procbuf[64]; 86 | 87 | enable = enable && (iface->ndp == MODE_RELAY); 88 | 89 | snprintf(procbuf, sizeof(procbuf), "/proc/sys/net/ipv6/conf/%s/proxy_ndp", iface->ifname); 90 | procfd = open(procbuf, O_WRONLY); 91 | 92 | if (procfd < 0) { 93 | ret = -1; 94 | goto out; 95 | } 96 | 97 | if (iface->ndp_ping_fd >= 0) { 98 | close(iface->ndp_ping_fd); 99 | iface->ndp_ping_fd = -1; 100 | } 101 | 102 | if (iface->ndp_event.uloop.fd >= 0) { 103 | uloop_fd_delete(&iface->ndp_event.uloop); 104 | close(iface->ndp_event.uloop.fd); 105 | iface->ndp_event.uloop.fd = -1; 106 | 107 | if (!enable) 108 | if (write(procfd, "0\n", 2) < 0) {} 109 | 110 | dump_neigh = true; 111 | } 112 | 113 | if (enable) { 114 | struct sockaddr_ll ll; 115 | struct packet_mreq mreq; 116 | struct icmp6_filter filt; 117 | int val = 2; 118 | 119 | if (write(procfd, "1\n", 2) < 0) {} 120 | 121 | /* Open ICMPv6 socket */ 122 | iface->ndp_ping_fd = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC, IPPROTO_ICMPV6); 123 | if (iface->ndp_ping_fd < 0) { 124 | error("socket(AF_INET6): %m"); 125 | ret = -1; 126 | goto out; 127 | } 128 | 129 | if (setsockopt(iface->ndp_ping_fd, SOL_SOCKET, SO_BINDTODEVICE, 130 | iface->ifname, strlen(iface->ifname)) < 0) { 131 | error("setsockopt(SO_BINDTODEVICE): %m"); 132 | ret = -1; 133 | goto out; 134 | } 135 | 136 | if (setsockopt(iface->ndp_ping_fd, IPPROTO_RAW, IPV6_CHECKSUM, 137 | &val, sizeof(val)) < 0) { 138 | error("setsockopt(IPV6_CHECKSUM): %m"); 139 | ret = -1; 140 | goto out; 141 | } 142 | 143 | /* This is required by RFC 4861 */ 144 | val = 255; 145 | if (setsockopt(iface->ndp_ping_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, 146 | &val, sizeof(val)) < 0) { 147 | error("setsockopt(IPV6_MULTICAST_HOPS): %m"); 148 | ret = -1; 149 | goto out; 150 | } 151 | 152 | if (setsockopt(iface->ndp_ping_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, 153 | &val, sizeof(val)) < 0) { 154 | error("setsockopt(IPV6_UNICAST_HOPS): %m"); 155 | ret = -1; 156 | goto out; 157 | } 158 | 159 | /* Filter all packages, we only want to send */ 160 | ICMP6_FILTER_SETBLOCKALL(&filt); 161 | if (setsockopt(iface->ndp_ping_fd, IPPROTO_ICMPV6, ICMP6_FILTER, 162 | &filt, sizeof(filt)) < 0) { 163 | error("setsockopt(ICMP6_FILTER): %m"); 164 | ret = -1; 165 | goto out; 166 | } 167 | 168 | 169 | iface->ndp_event.uloop.fd = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_IPV6)); 170 | if (iface->ndp_event.uloop.fd < 0) { 171 | error("socket(AF_PACKET): %m"); 172 | ret = -1; 173 | goto out; 174 | } 175 | 176 | #ifdef PACKET_RECV_TYPE 177 | int pktt = 1 << PACKET_MULTICAST; 178 | if (setsockopt(iface->ndp_event.uloop.fd, SOL_PACKET, PACKET_RECV_TYPE, 179 | &pktt, sizeof(pktt)) < 0) { 180 | error("setsockopt(PACKET_RECV_TYPE): %m"); 181 | ret = -1; 182 | goto out; 183 | } 184 | #endif 185 | 186 | /* 187 | * AF_PACKET sockets can receive packets as soon as they are 188 | * created, so make sure we don't accept anything... 189 | */ 190 | if (setsockopt(iface->ndp_event.uloop.fd, SOL_SOCKET, SO_ATTACH_FILTER, 191 | &bpf_drop, sizeof(bpf_drop))) { 192 | error("setsockopt(SO_ATTACH_FILTER): %m"); 193 | ret = -1; 194 | goto out; 195 | } 196 | 197 | /* ...and remove stray packets... */ 198 | while (true) { 199 | char null[1]; 200 | if (recv(iface->ndp_event.uloop.fd, null, sizeof(null), MSG_DONTWAIT | MSG_TRUNC) < 0) 201 | break; 202 | } 203 | 204 | /* ...until the real filter is installed */ 205 | if (setsockopt(iface->ndp_event.uloop.fd, SOL_SOCKET, SO_ATTACH_FILTER, 206 | &bpf_prog, sizeof(bpf_prog))) { 207 | error("setsockopt(SO_ATTACH_FILTER): %m"); 208 | ret = -1; 209 | goto out; 210 | } 211 | 212 | memset(&ll, 0, sizeof(ll)); 213 | ll.sll_family = AF_PACKET; 214 | ll.sll_ifindex = iface->ifindex; 215 | ll.sll_protocol = htons(ETH_P_IPV6); 216 | 217 | if (bind(iface->ndp_event.uloop.fd, (struct sockaddr*)&ll, sizeof(ll)) < 0) { 218 | error("bind(): %m"); 219 | ret = -1; 220 | goto out; 221 | } 222 | 223 | memset(&mreq, 0, sizeof(mreq)); 224 | mreq.mr_ifindex = iface->ifindex; 225 | mreq.mr_type = PACKET_MR_ALLMULTI; 226 | mreq.mr_alen = ETH_ALEN; 227 | 228 | if (setsockopt(iface->ndp_event.uloop.fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, 229 | &mreq, sizeof(mreq)) < 0) { 230 | error("setsockopt(PACKET_ADD_MEMBERSHIP): %m"); 231 | ret = -1; 232 | goto out; 233 | } 234 | 235 | iface->ndp_event.handle_dgram = handle_solicit; 236 | odhcpd_register(&iface->ndp_event); 237 | 238 | /* If we already were enabled dump is unnecessary, if not do dump */ 239 | if (!dump_neigh) 240 | netlink_dump_neigh_table(false); 241 | else 242 | dump_neigh = false; 243 | } 244 | 245 | if (dump_neigh) 246 | netlink_dump_neigh_table(true); 247 | 248 | out: 249 | if (ret < 0) { 250 | if (iface->ndp_event.uloop.fd >= 0) { 251 | close(iface->ndp_event.uloop.fd); 252 | iface->ndp_event.uloop.fd = -1; 253 | } 254 | 255 | if (iface->ndp_ping_fd >= 0) { 256 | close(iface->ndp_ping_fd); 257 | iface->ndp_ping_fd = -1; 258 | } 259 | } 260 | 261 | if (procfd >= 0) 262 | close(procfd); 263 | 264 | return ret; 265 | } 266 | 267 | static void ndp_netevent_cb(unsigned long event, struct netevent_handler_info *info) 268 | { 269 | struct interface *iface = info->iface; 270 | bool add = true; 271 | 272 | if (!iface || iface->ndp == MODE_DISABLED) 273 | return; 274 | 275 | switch (event) { 276 | case NETEV_ADDR6_DEL: 277 | add = false; 278 | netlink_dump_neigh_table(false); 279 | _o_fallthrough; 280 | case NETEV_ADDR6_ADD: 281 | setup_addr_for_relaying(&info->addr.in6, iface, add); 282 | break; 283 | case NETEV_NEIGH6_DEL: 284 | add = false; 285 | _o_fallthrough; 286 | case NETEV_NEIGH6_ADD: 287 | if (info->neigh.flags & NTF_PROXY) { 288 | if (add) { 289 | netlink_setup_proxy_neigh(&info->neigh.dst.in6, iface->ifindex, false); 290 | setup_route(&info->neigh.dst.in6, iface, false); 291 | netlink_dump_neigh_table(false); 292 | } 293 | break; 294 | } 295 | 296 | if (add && 297 | !(info->neigh.state & 298 | (NUD_REACHABLE|NUD_STALE|NUD_DELAY|NUD_PROBE|NUD_PERMANENT|NUD_NOARP))) 299 | break; 300 | 301 | setup_addr_for_relaying(&info->neigh.dst.in6, iface, add); 302 | setup_route(&info->neigh.dst.in6, iface, add); 303 | 304 | if (!add) 305 | netlink_dump_neigh_table(false); 306 | break; 307 | default: 308 | break; 309 | } 310 | } 311 | 312 | /* Send an ICMP-ECHO. This is less for actually pinging but for the 313 | * neighbor cache to be kept up-to-date. */ 314 | static void ping6(struct in6_addr *addr, 315 | struct interface *iface) 316 | { 317 | struct sockaddr_in6 dest = { .sin6_family = AF_INET6, .sin6_addr = *addr , }; 318 | struct icmp6_hdr echo = { .icmp6_type = ICMP6_ECHO_REQUEST }; 319 | struct iovec iov = { .iov_base = &echo, .iov_len = sizeof(echo) }; 320 | char ipbuf[INET6_ADDRSTRLEN]; 321 | 322 | inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf)); 323 | debug("Pinging for %s on %s", ipbuf, iface->name); 324 | 325 | netlink_setup_route(addr, 128, iface->ifindex, NULL, 128, true); 326 | 327 | /* Use link-local address as source for RFC 4861 compliance and macOS compatibility */ 328 | odhcpd_try_send_with_src(iface->ndp_ping_fd, &dest, &iov, 1, iface); 329 | 330 | netlink_setup_route(addr, 128, iface->ifindex, NULL, 128, false); 331 | } 332 | 333 | /* Send a Neighbor Advertisement. */ 334 | static void send_na(struct in6_addr *to_addr, 335 | struct interface *iface, struct in6_addr *for_addr, 336 | const uint8_t *mac) 337 | { 338 | struct sockaddr_in6 dest = { .sin6_family = AF_INET6, .sin6_addr = *to_addr }; 339 | char pbuf[sizeof(struct nd_neighbor_advert) + sizeof(struct nd_opt_hdr) + 6]; 340 | struct nd_neighbor_advert *adv = (struct nd_neighbor_advert*)pbuf; 341 | struct nd_opt_hdr *opt = (struct nd_opt_hdr*) &pbuf[sizeof(struct nd_neighbor_advert)]; 342 | struct iovec iov = { .iov_base = &pbuf, .iov_len = sizeof(pbuf) }; 343 | char ipbuf[INET6_ADDRSTRLEN]; 344 | 345 | memset(pbuf, 0, sizeof(pbuf)); 346 | adv->nd_na_hdr = (struct icmp6_hdr) { 347 | .icmp6_type = ND_NEIGHBOR_ADVERT, 348 | .icmp6_dataun.icmp6_un_data32 = { ND_NA_FLAG_SOLICITED } 349 | }; 350 | adv->nd_na_target = *for_addr; 351 | *opt = (struct nd_opt_hdr) { .nd_opt_type = ND_OPT_TARGET_LINKADDR, .nd_opt_len = 1 }; 352 | memcpy(&pbuf[sizeof(struct nd_neighbor_advert) + sizeof(struct nd_opt_hdr)], mac, 6); 353 | 354 | inet_ntop(AF_INET6, to_addr, ipbuf, sizeof(ipbuf)); 355 | debug("Answering NS to %s on %s", ipbuf, iface->ifname); 356 | 357 | /* Use link-local address as source for RFC 4861 compliance and macOS compatibility */ 358 | odhcpd_try_send_with_src(iface->ndp_ping_fd, &dest, &iov, 1, iface); 359 | } 360 | 361 | /* Handle solicitations */ 362 | static void handle_solicit(void *addr, void *data, size_t len, 363 | struct interface *iface, _o_unused void *dest) 364 | { 365 | struct ip6_hdr *ip6 = data; 366 | struct nd_neighbor_solicit *req = (struct nd_neighbor_solicit*)&ip6[1]; 367 | struct sockaddr_ll *ll = addr; 368 | struct interface *c; 369 | char ipbuf[INET6_ADDRSTRLEN]; 370 | uint8_t mac[6]; 371 | bool is_self_sent; 372 | 373 | /* Solicitation is for duplicate address detection */ 374 | bool ns_is_dad = IN6_IS_ADDR_UNSPECIFIED(&ip6->ip6_src); 375 | 376 | /* Don't process solicit messages on non relay interfaces 377 | * Don't forward any non-DAD solicitation for external ifaces 378 | * TODO: check if we should even forward DADs for them */ 379 | if (iface->ndp != MODE_RELAY || (iface->external && !ns_is_dad)) 380 | return; 381 | 382 | if (len < sizeof(*ip6) + sizeof(*req)) 383 | return; // Invalid total length 384 | 385 | if (IN6_IS_ADDR_LINKLOCAL(&req->nd_ns_target) || 386 | IN6_IS_ADDR_LOOPBACK(&req->nd_ns_target) || 387 | IN6_IS_ADDR_MULTICAST(&req->nd_ns_target)) 388 | return; /* Invalid target */ 389 | 390 | inet_ntop(AF_INET6, &req->nd_ns_target, ipbuf, sizeof(ipbuf)); 391 | debug("Got a NS for %s on %s", ipbuf, iface->name); 392 | 393 | odhcpd_get_mac(iface, mac); 394 | is_self_sent = !memcmp(ll->sll_addr, mac, sizeof(mac)); 395 | if (is_self_sent && !iface->master) 396 | return; /* Looped back */ 397 | 398 | avl_for_each_element(&interfaces, c, avl) { 399 | if (iface != c && c->ndp == MODE_RELAY && 400 | (ns_is_dad || !c->external)) 401 | ping6(&req->nd_ns_target, c); 402 | } 403 | 404 | /* Catch global-addressed NS and answer them manually. 405 | * The kernel won't answer these and cannot route them either. */ 406 | if (!IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst) && 407 | IN6_IS_ADDR_LINKLOCAL(&ip6->ip6_src) && !is_self_sent) { 408 | bool is_proxy_neigh = netlink_get_interface_proxy_neigh(iface->ifindex, 409 | &req->nd_ns_target) == 1; 410 | 411 | if (is_proxy_neigh) 412 | send_na(&ip6->ip6_src, iface, &req->nd_ns_target, mac); 413 | } 414 | } 415 | 416 | /* Use rtnetlink to modify kernel routes */ 417 | static void setup_route(struct in6_addr *addr, struct interface *iface, bool add) 418 | { 419 | char ipbuf[INET6_ADDRSTRLEN]; 420 | 421 | inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf)); 422 | debug("%s about %s%s on %s", 423 | (add) ? "Learning" : "Forgetting", 424 | iface->learn_routes ? "proxy routing for " : "", 425 | ipbuf, iface->name); 426 | 427 | if (iface->learn_routes) 428 | netlink_setup_route(addr, 128, iface->ifindex, NULL, 1024, add); 429 | } 430 | 431 | static void setup_addr_for_relaying(struct in6_addr *addr, struct interface *iface, bool add) 432 | { 433 | struct interface *c; 434 | char ipbuf[INET6_ADDRSTRLEN]; 435 | 436 | inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf)); 437 | 438 | avl_for_each_element(&interfaces, c, avl) { 439 | if (iface == c || c->ndp != MODE_RELAY) 440 | continue; 441 | 442 | if (netlink_setup_proxy_neigh(addr, c->ifindex, add)) { 443 | if (add) 444 | error("Failed to add proxy neighbour entry %s on %s", 445 | ipbuf, c->name); 446 | } else 447 | debug("%s proxy neighbour entry %s on %s", 448 | add ? "Added" : "Deleted", ipbuf, c->name); 449 | } 450 | } 451 | -------------------------------------------------------------------------------- /src/ubus.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "odhcpd.h" 10 | #include "dhcpv6.h" 11 | #include "dhcpv4.h" 12 | #include "statefiles.h" 13 | 14 | static struct ubus_context *ubus = NULL; 15 | static struct ubus_subscriber netifd; 16 | static struct blob_buf b; 17 | static struct blob_attr *dump = NULL; 18 | static uint32_t objid = 0; 19 | static struct ubus_request req_dump = { .list = LIST_HEAD_INIT(req_dump.list) }; 20 | 21 | #ifdef DHCPV4_SUPPORT 22 | static int handle_dhcpv4_leases(struct ubus_context *ctx, _o_unused struct ubus_object *obj, 23 | struct ubus_request_data *req, _o_unused const char *method, 24 | _o_unused struct blob_attr *msg) 25 | { 26 | struct interface *iface; 27 | time_t now = odhcpd_time(); 28 | void *a; 29 | 30 | blob_buf_init(&b, 0); 31 | a = blobmsg_open_table(&b, "device"); 32 | 33 | avl_for_each_element(&interfaces, iface, avl) { 34 | if (iface->dhcpv4 != MODE_SERVER) 35 | continue; 36 | 37 | void *i = blobmsg_open_table(&b, iface->ifname); 38 | void *j = blobmsg_open_array(&b, "leases"); 39 | struct dhcpv4_lease *c; 40 | 41 | avl_for_each_element(&iface->dhcpv4_leases, c, iface_avl) { 42 | if (!INFINITE_VALID(c->valid_until) && c->valid_until < now) 43 | continue; 44 | 45 | void *m, *l = blobmsg_open_table(&b, NULL); 46 | char *buf = blobmsg_alloc_string_buffer(&b, "mac", sizeof(c->hwaddr) * 2 + 1); 47 | 48 | odhcpd_hexlify(buf, c->hwaddr, sizeof(c->hwaddr)); 49 | blobmsg_add_string_buffer(&b); 50 | 51 | if (c->duid_len > 0) { 52 | buf = blobmsg_alloc_string_buffer(&b, "duid", DUID_HEXSTRLEN + 1); 53 | odhcpd_hexlify(buf, c->duid, c->duid_len); 54 | blobmsg_add_string_buffer(&b); 55 | blobmsg_add_u32(&b, "iaid", ntohl(c->iaid)); 56 | } 57 | 58 | blobmsg_add_string(&b, "hostname", (c->hostname) ? c->hostname : ""); 59 | blobmsg_add_u8(&b, "accept-reconf", c->accept_fr_nonce); 60 | 61 | m = blobmsg_open_array(&b, "flags"); 62 | if (c->bound) 63 | blobmsg_add_string(&b, NULL, "bound"); 64 | 65 | if (c->lease_cfg) 66 | blobmsg_add_string(&b, NULL, "static"); 67 | 68 | if (!c->hostname_valid) 69 | blobmsg_add_string(&b, NULL, "broken-hostname"); 70 | blobmsg_close_array(&b, m); 71 | 72 | buf = blobmsg_alloc_string_buffer(&b, "address", INET_ADDRSTRLEN); 73 | inet_ntop(AF_INET, &c->ipv4, buf, INET_ADDRSTRLEN); 74 | blobmsg_add_string_buffer(&b); 75 | 76 | blobmsg_add_u32(&b, "valid", INFINITE_VALID(c->valid_until) ? 77 | (uint32_t)-1 : (uint32_t)(c->valid_until - now)); 78 | 79 | blobmsg_close_table(&b, l); 80 | } 81 | 82 | blobmsg_close_array(&b, j); 83 | blobmsg_close_table(&b, i); 84 | } 85 | 86 | blobmsg_close_table(&b, a); 87 | ubus_send_reply(ctx, req, b.head); 88 | 89 | return 0; 90 | } 91 | #endif /* DHCPV4_SUPPORT */ 92 | 93 | static void dhcpv6_blobmsg_ia_addr(_o_unused struct dhcpv6_lease *lease, struct in6_addr *addr, uint8_t prefix_len, 94 | uint32_t pref_lt, uint32_t valid_lt, _o_unused void *arg) 95 | { 96 | void *a = blobmsg_open_table(&b, NULL); 97 | char *buf = blobmsg_alloc_string_buffer(&b, "address", INET6_ADDRSTRLEN); 98 | 99 | inet_ntop(AF_INET6, addr, buf, INET6_ADDRSTRLEN); 100 | blobmsg_add_string_buffer(&b); 101 | blobmsg_add_u32(&b, "preferred-lifetime", 102 | pref_lt == UINT32_MAX ? (uint32_t)-1 : pref_lt); 103 | blobmsg_add_u32(&b, "valid-lifetime", 104 | valid_lt == UINT32_MAX ? (uint32_t)-1 : valid_lt); 105 | 106 | if (prefix_len != 128) 107 | blobmsg_add_u32(&b, "prefix-length", prefix_len); 108 | 109 | blobmsg_close_table(&b, a); 110 | } 111 | 112 | static int handle_dhcpv6_leases(_o_unused struct ubus_context *ctx, _o_unused struct ubus_object *obj, 113 | _o_unused struct ubus_request_data *req, _o_unused const char *method, 114 | _o_unused struct blob_attr *msg) 115 | { 116 | struct interface *iface; 117 | time_t now = odhcpd_time(); 118 | void *dev_tbl; 119 | 120 | blob_buf_init(&b, 0); 121 | dev_tbl = blobmsg_open_table(&b, "device"); 122 | 123 | avl_for_each_element(&interfaces, iface, avl) { 124 | if (iface->dhcpv6 != MODE_SERVER) 125 | continue; 126 | 127 | void *i = blobmsg_open_table(&b, iface->ifname); 128 | void *j = blobmsg_open_array(&b, "leases"); 129 | 130 | struct dhcpv6_lease *a, *border; 131 | 132 | border = list_last_entry(&iface->ia_assignments, struct dhcpv6_lease, head); 133 | 134 | list_for_each_entry(a, &iface->ia_assignments, head) { 135 | if (a == border || (!INFINITE_VALID(a->valid_until) && 136 | a->valid_until < now)) 137 | continue; 138 | 139 | void *m, *l = blobmsg_open_table(&b, NULL); 140 | char *buf = blobmsg_alloc_string_buffer(&b, "duid", DUID_HEXSTRLEN + 1); 141 | 142 | odhcpd_hexlify(buf, a->duid, a->duid_len); 143 | blobmsg_add_string_buffer(&b); 144 | 145 | blobmsg_add_u32(&b, "iaid", ntohl(a->iaid)); 146 | blobmsg_add_string(&b, "hostname", (a->hostname) ? a->hostname : ""); 147 | blobmsg_add_u8(&b, "accept-reconf", a->accept_fr_nonce); 148 | if (a->flags & OAF_DHCPV6_NA) 149 | blobmsg_add_u64(&b, "assigned", a->assigned_host_id); 150 | else 151 | blobmsg_add_u16(&b, "assigned", a->assigned_subnet_id); 152 | 153 | m = blobmsg_open_array(&b, "flags"); 154 | if (a->bound) 155 | blobmsg_add_string(&b, NULL, "bound"); 156 | 157 | if (a->lease_cfg) 158 | blobmsg_add_string(&b, NULL, "static"); 159 | blobmsg_close_array(&b, m); 160 | 161 | m = blobmsg_open_array(&b, a->flags & OAF_DHCPV6_NA ? "ipv6-addr": "ipv6-prefix"); 162 | odhcpd_enum_addr6(iface, a, now, dhcpv6_blobmsg_ia_addr, NULL); 163 | blobmsg_close_array(&b, m); 164 | 165 | blobmsg_add_u32(&b, "valid", INFINITE_VALID(a->valid_until) ? 166 | (uint32_t)-1 : (uint32_t)(a->valid_until - now)); 167 | 168 | blobmsg_close_table(&b, l); 169 | } 170 | 171 | blobmsg_close_array(&b, j); 172 | blobmsg_close_table(&b, i); 173 | } 174 | 175 | blobmsg_close_table(&b, dev_tbl); 176 | ubus_send_reply(ctx, req, b.head); 177 | 178 | return 0; 179 | } 180 | 181 | static int handle_ra_pio(_o_unused struct ubus_context *ctx, _o_unused struct ubus_object *obj, 182 | _o_unused struct ubus_request_data *req, _o_unused const char *method, 183 | _o_unused struct blob_attr *msg) 184 | { 185 | char ipv6_str[INET6_ADDRSTRLEN]; 186 | time_t now = odhcpd_time(); 187 | struct interface *iface; 188 | void *interfaces_blob; 189 | 190 | blob_buf_init(&b, 0); 191 | 192 | interfaces_blob = blobmsg_open_table(&b, "interfaces"); 193 | 194 | avl_for_each_element(&interfaces, iface, avl) { 195 | void *interface_blob; 196 | 197 | if (iface->ra != MODE_SERVER) 198 | continue; 199 | 200 | interface_blob = blobmsg_open_array(&b, iface->ifname); 201 | 202 | for (size_t i = 0; i < iface->pio_cnt; i++) { 203 | struct ra_pio *cur_pio = &iface->pios[i]; 204 | void *cur_pio_blob; 205 | uint32_t pio_lt; 206 | bool pio_stale; 207 | 208 | if (ra_pio_expired(cur_pio, now)) 209 | continue; 210 | 211 | cur_pio_blob = blobmsg_open_table(&b, NULL); 212 | 213 | pio_lt = ra_pio_lifetime(cur_pio, now); 214 | pio_stale = ra_pio_stale(cur_pio); 215 | 216 | inet_ntop(AF_INET6, &cur_pio->prefix, ipv6_str, sizeof(ipv6_str)); 217 | 218 | if (pio_lt) 219 | blobmsg_add_u32(&b, "lifetime", pio_lt); 220 | blobmsg_add_string(&b, "prefix", ipv6_str); 221 | blobmsg_add_u16(&b, "length", cur_pio->length); 222 | blobmsg_add_u8(&b, "stale", pio_stale); 223 | 224 | blobmsg_close_table(&b, cur_pio_blob); 225 | } 226 | 227 | blobmsg_close_array(&b, interface_blob); 228 | } 229 | 230 | blobmsg_close_table(&b, interfaces_blob); 231 | 232 | ubus_send_reply(ctx, req, b.head); 233 | 234 | return 0; 235 | } 236 | 237 | static int handle_add_lease_cfg(_o_unused struct ubus_context *ctx, _o_unused struct ubus_object *obj, 238 | _o_unused struct ubus_request_data *req, _o_unused const char *method, 239 | struct blob_attr *msg) 240 | { 241 | if (!config_set_lease_cfg_from_blobmsg(msg)) 242 | return UBUS_STATUS_OK; 243 | 244 | return UBUS_STATUS_INVALID_ARGUMENT; 245 | } 246 | 247 | static struct ubus_method main_object_methods[] = { 248 | #ifdef DHCPV4_SUPPORT 249 | { .name = "ipv4leases", .handler = handle_dhcpv4_leases }, 250 | #endif /* DHCPV4_SUPPORT */ 251 | { .name = "ipv6leases", .handler = handle_dhcpv6_leases }, 252 | { .name = "ipv6ra", .handler = handle_ra_pio }, 253 | UBUS_METHOD("add_lease", handle_add_lease_cfg, lease_cfg_attrs), 254 | }; 255 | 256 | static struct ubus_object_type main_object_type = 257 | UBUS_OBJECT_TYPE("dhcp", main_object_methods); 258 | 259 | static struct ubus_object main_object = { 260 | .name = "dhcp", 261 | .type = &main_object_type, 262 | .methods = main_object_methods, 263 | .n_methods = ARRAY_SIZE(main_object_methods), 264 | }; 265 | 266 | 267 | enum { 268 | DUMP_ATTR_INTERFACE, 269 | DUMP_ATTR_MAX 270 | }; 271 | 272 | static const struct blobmsg_policy dump_attrs[DUMP_ATTR_MAX] = { 273 | [DUMP_ATTR_INTERFACE] = { .name = "interface", .type = BLOBMSG_TYPE_ARRAY }, 274 | }; 275 | 276 | 277 | enum { 278 | IFACE_ATTR_INTERFACE, 279 | IFACE_ATTR_IFNAME, 280 | IFACE_ATTR_UP, 281 | IFACE_ATTR_DATA, 282 | IFACE_ATTR_PREFIX, 283 | IFACE_ATTR_ADDRESS, 284 | IFACE_ATTR_MAX, 285 | }; 286 | 287 | static const struct blobmsg_policy iface_attrs[IFACE_ATTR_MAX] = { 288 | [IFACE_ATTR_INTERFACE] = { .name = "interface", .type = BLOBMSG_TYPE_STRING }, 289 | [IFACE_ATTR_IFNAME] = { .name = "l3_device", .type = BLOBMSG_TYPE_STRING }, 290 | [IFACE_ATTR_UP] = { .name = "up", .type = BLOBMSG_TYPE_BOOL }, 291 | [IFACE_ATTR_DATA] = { .name = "data", .type = BLOBMSG_TYPE_TABLE }, 292 | [IFACE_ATTR_PREFIX] = { .name = "ipv6-prefix", .type = BLOBMSG_TYPE_ARRAY }, 293 | [IFACE_ATTR_ADDRESS] = { .name = "ipv6-address", .type = BLOBMSG_TYPE_ARRAY }, 294 | }; 295 | 296 | static void handle_dump(_o_unused struct ubus_request *req, _o_unused int type, struct blob_attr *msg) 297 | { 298 | struct blob_attr *tb[DUMP_ATTR_MAX]; 299 | blobmsg_parse(dump_attrs, DUMP_ATTR_MAX, tb, blob_data(msg), blob_len(msg)); 300 | 301 | if (!tb[DUMP_ATTR_INTERFACE]) 302 | return; 303 | 304 | free(dump); 305 | dump = blob_memdup(tb[DUMP_ATTR_INTERFACE]); 306 | odhcpd_reload(); 307 | } 308 | 309 | 310 | static void update_netifd(bool subscribe) 311 | { 312 | if (subscribe) 313 | ubus_subscribe(ubus, &netifd, objid); 314 | 315 | ubus_abort_request(ubus, &req_dump); 316 | blob_buf_init(&b, 0); 317 | 318 | if (!ubus_invoke_async(ubus, objid, "dump", b.head, &req_dump)) { 319 | req_dump.data_cb = handle_dump; 320 | ubus_complete_request_async(ubus, &req_dump); 321 | } 322 | } 323 | 324 | 325 | static int handle_update(_o_unused struct ubus_context *ctx, _o_unused struct ubus_object *obj, 326 | _o_unused struct ubus_request_data *req, _o_unused const char *method, 327 | struct blob_attr *msg) 328 | { 329 | struct blob_attr *tb[IFACE_ATTR_MAX]; 330 | struct interface *c; 331 | bool update = true; 332 | 333 | blobmsg_parse(iface_attrs, IFACE_ATTR_MAX, tb, blob_data(msg), blob_len(msg)); 334 | const char *interface = (tb[IFACE_ATTR_INTERFACE]) ? 335 | blobmsg_get_string(tb[IFACE_ATTR_INTERFACE]) : ""; 336 | 337 | avl_for_each_element(&interfaces, c, avl) { 338 | if (!strcmp(interface, c->name) && c->ignore) { 339 | update = false; 340 | break; 341 | } 342 | } 343 | 344 | if (update) 345 | update_netifd(false); 346 | 347 | return 0; 348 | } 349 | 350 | 351 | void ubus_apply_network(void) 352 | { 353 | struct blob_attr *a; 354 | unsigned rem; 355 | 356 | if (!dump) 357 | return; 358 | 359 | blobmsg_for_each_attr(a, dump, rem) { 360 | struct blob_attr *tb[IFACE_ATTR_MAX]; 361 | blobmsg_parse(iface_attrs, IFACE_ATTR_MAX, tb, blobmsg_data(a), blobmsg_data_len(a)); 362 | 363 | if (!tb[IFACE_ATTR_INTERFACE] || !tb[IFACE_ATTR_DATA]) 364 | continue; 365 | 366 | const char *interface = (tb[IFACE_ATTR_INTERFACE]) ? 367 | blobmsg_get_string(tb[IFACE_ATTR_INTERFACE]) : ""; 368 | 369 | bool matched = false; 370 | struct interface *c, *tmp; 371 | avl_for_each_element_safe(&interfaces, c, avl, tmp) { 372 | char *f = memmem(c->upstream, c->upstream_len, 373 | interface, strlen(interface) + 1); 374 | bool cmatched = !strcmp(interface, c->name); 375 | matched |= cmatched; 376 | 377 | if (!cmatched && (!c->upstream_len || !f || (f != c->upstream && f[-1] != 0))) 378 | continue; 379 | 380 | if (!c->ignore) 381 | config_parse_interface(blobmsg_data(tb[IFACE_ATTR_DATA]), 382 | blobmsg_data_len(tb[IFACE_ATTR_DATA]), c->name, false); 383 | } 384 | 385 | if (!matched) 386 | config_parse_interface(blobmsg_data(tb[IFACE_ATTR_DATA]), 387 | blobmsg_data_len(tb[IFACE_ATTR_DATA]), interface, false); 388 | } 389 | } 390 | 391 | 392 | enum { 393 | OBJ_ATTR_ID, 394 | OBJ_ATTR_PATH, 395 | OBJ_ATTR_MAX 396 | }; 397 | 398 | static const struct blobmsg_policy obj_attrs[OBJ_ATTR_MAX] = { 399 | [OBJ_ATTR_ID] = { .name = "id", .type = BLOBMSG_TYPE_INT32 }, 400 | [OBJ_ATTR_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING }, 401 | }; 402 | 403 | void ubus_bcast_dhcpv4_event(const char *type, const char *iface, 404 | const struct dhcpv4_lease *lease) 405 | { 406 | char ipv4_str[INET_ADDRSTRLEN]; 407 | 408 | if (!ubus || !main_object.has_subscribers || !iface) 409 | return; 410 | 411 | blob_buf_init(&b, 0); 412 | blobmsg_add_string(&b, "interface", iface); 413 | blobmsg_add_string(&b, "ipv4", inet_ntop(AF_INET, &lease->ipv4, ipv4_str, sizeof(ipv4_str))); 414 | blobmsg_add_string(&b, "mac", odhcpd_print_mac(lease->hwaddr, sizeof(lease->hwaddr))); 415 | if (lease->hostname) 416 | blobmsg_add_string(&b, "hostname", lease->hostname); 417 | if (lease->duid_len > 0) { 418 | char *buf = blobmsg_alloc_string_buffer(&b, "duid", DUID_HEXSTRLEN + 1); 419 | odhcpd_hexlify(buf, lease->duid, lease->duid_len); 420 | blobmsg_add_string_buffer(&b); 421 | blobmsg_add_u32(&b, "iaid", lease->iaid); 422 | } 423 | 424 | ubus_notify(ubus, &main_object, type, b.head, -1); 425 | } 426 | 427 | static void handle_event(_o_unused struct ubus_context *ctx, 428 | _o_unused struct ubus_event_handler *ev, 429 | _o_unused const char *type, struct blob_attr *msg) 430 | { 431 | struct blob_attr *tb[OBJ_ATTR_MAX]; 432 | blobmsg_parse(obj_attrs, OBJ_ATTR_MAX, tb, blob_data(msg), blob_len(msg)); 433 | 434 | if (!tb[OBJ_ATTR_ID] || !tb[OBJ_ATTR_PATH]) 435 | return; 436 | 437 | if (strcmp(blobmsg_get_string(tb[OBJ_ATTR_PATH]), "network.interface")) 438 | return; 439 | 440 | objid = blobmsg_get_u32(tb[OBJ_ATTR_ID]); 441 | update_netifd(true); 442 | } 443 | 444 | static struct ubus_event_handler event_handler = { .cb = handle_event }; 445 | 446 | 447 | const char* ubus_get_ifname(const char *name) 448 | { 449 | struct blob_attr *c; 450 | unsigned rem; 451 | 452 | if (!dump) 453 | return NULL; 454 | 455 | blobmsg_for_each_attr(c, dump, rem) { 456 | struct blob_attr *tb[IFACE_ATTR_MAX]; 457 | blobmsg_parse(iface_attrs, IFACE_ATTR_MAX, tb, blobmsg_data(c), blobmsg_data_len(c)); 458 | 459 | if (!tb[IFACE_ATTR_INTERFACE] || strcmp(name, 460 | blobmsg_get_string(tb[IFACE_ATTR_INTERFACE]))) 461 | continue; 462 | 463 | if (tb[IFACE_ATTR_IFNAME]) 464 | return blobmsg_get_string(tb[IFACE_ATTR_IFNAME]); 465 | } 466 | 467 | return NULL; 468 | } 469 | 470 | 471 | bool ubus_has_prefix(const char *name, const char *ifname) 472 | { 473 | struct blob_attr *c, *cur; 474 | unsigned rem; 475 | 476 | if (!dump) 477 | return false; 478 | 479 | blobmsg_for_each_attr(c, dump, rem) { 480 | struct blob_attr *tb[IFACE_ATTR_MAX]; 481 | blobmsg_parse(iface_attrs, IFACE_ATTR_MAX, tb, blobmsg_data(c), blobmsg_data_len(c)); 482 | 483 | if (!tb[IFACE_ATTR_INTERFACE] || !tb[IFACE_ATTR_IFNAME]) 484 | continue; 485 | 486 | if (strcmp(name, blobmsg_get_string(tb[IFACE_ATTR_INTERFACE])) || 487 | strcmp(ifname, blobmsg_get_string(tb[IFACE_ATTR_IFNAME]))) 488 | continue; 489 | 490 | if ((cur = tb[IFACE_ATTR_PREFIX])) { 491 | if (blobmsg_type(cur) != BLOBMSG_TYPE_ARRAY || !blobmsg_check_attr(cur, false)) 492 | continue; 493 | 494 | struct blob_attr *d; 495 | unsigned drem; 496 | blobmsg_for_each_attr(d, cur, drem) { 497 | return true; 498 | } 499 | } 500 | } 501 | 502 | return false; 503 | } 504 | 505 | 506 | int ubus_init(void) 507 | { 508 | if (!(ubus = ubus_connect(NULL))) { 509 | error("Unable to connect to ubus: %m"); 510 | return -1; 511 | } 512 | 513 | netifd.cb = handle_update; 514 | ubus_register_subscriber(ubus, &netifd); 515 | 516 | ubus_add_uloop(ubus); 517 | ubus_add_object(ubus, &main_object); 518 | ubus_register_event_handler(ubus, &event_handler, "ubus.object.add"); 519 | if (!ubus_lookup_id(ubus, "network.interface", &objid)) 520 | update_netifd(true); 521 | 522 | return 0; 523 | } 524 | 525 | -------------------------------------------------------------------------------- /src/statefiles.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2013 Steven Barth 3 | * SPDX-FileCopyrightText: 2013 Hans Dedecker 4 | * SPDX-FileCopyrightText: 2022 Kevin Darbyshire-Bryant 5 | * SPDX-FileCopyrightText: 2024 Paul Donald 6 | * SPDX-FileCopyrightText: 2024 David Härdeman 7 | * SPDX-FileCopyrightText: 2025 Álvaro Fernández Rojas 8 | * 9 | * SPDX-License-Identifier: GPL2.0-only 10 | */ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 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 29 | #include 30 | 31 | #include "odhcpd.h" 32 | #include "dhcpv6-ia.h" 33 | #include "statefiles.h" 34 | 35 | static uint8_t statemd5[16]; 36 | 37 | struct write_ctxt { 38 | FILE *fp; 39 | md5_ctx_t md5; 40 | struct interface *iface; 41 | time_t now; // CLOCK_MONOTONIC 42 | time_t wall_time; 43 | }; 44 | 45 | static FILE *statefiles_open_tmp_file(int dirfd) 46 | { 47 | int fd; 48 | FILE *fp; 49 | 50 | if (dirfd < 0) 51 | return NULL; 52 | 53 | fd = openat(dirfd, ODHCPD_TMP_FILE, O_CREAT | O_WRONLY | O_CLOEXEC, 0644); 54 | if (fd < 0) 55 | goto err; 56 | 57 | if (lockf(fd, F_LOCK, 0) < 0) 58 | goto err; 59 | 60 | if (ftruncate(fd, 0) < 0) 61 | goto err_del; 62 | 63 | fp = fdopen(fd, "w"); 64 | if (!fp) 65 | goto err_del; 66 | 67 | return fp; 68 | 69 | err_del: 70 | unlinkat(dirfd, ODHCPD_TMP_FILE, 0); 71 | err: 72 | close(fd); 73 | error("Failed to create temporary file: %m"); 74 | return NULL; 75 | } 76 | 77 | static void statefiles_finish_tmp_file(int dirfd, FILE **fpp, const char *prefix, const char *suffix) 78 | { 79 | char *filename; 80 | 81 | if (dirfd < 0 || !fpp || !*fpp) 82 | return; 83 | 84 | if (!prefix) { 85 | unlinkat(dirfd, ODHCPD_TMP_FILE, 0); 86 | fclose(*fpp); 87 | *fpp = NULL; 88 | return; 89 | } 90 | 91 | if (fflush(*fpp)) 92 | error("Error flushing tmpfile: %m"); 93 | 94 | if (fsync(fileno(*fpp)) < 0) 95 | error("Error synching tmpfile: %m"); 96 | 97 | fclose(*fpp); 98 | *fpp = NULL; 99 | 100 | if (suffix) { 101 | filename = alloca(strlen(prefix) + strlen(".") + strlen(suffix) + 1); 102 | sprintf(filename, "%s.%s", prefix, suffix); 103 | } else { 104 | filename = alloca(strlen(prefix) + 1); 105 | sprintf(filename, "%s", prefix); 106 | } 107 | 108 | renameat(dirfd, ODHCPD_TMP_FILE, dirfd, filename); 109 | } 110 | 111 | #define JSON_LENGTH "length" 112 | #define JSON_PREFIX "prefix" 113 | #define JSON_SLAAC "slaac" 114 | #define JSON_TIME "time" 115 | 116 | static inline time_t statefiles_time_from_json(time_t json_time) 117 | { 118 | time_t ref, now; 119 | 120 | ref = time(NULL); 121 | now = odhcpd_time(); 122 | 123 | if (now > json_time || ref > json_time) 124 | return 0; 125 | 126 | return json_time + (now - ref); 127 | } 128 | 129 | static inline time_t statefiles_time_to_json(time_t config_time) 130 | { 131 | time_t ref, now; 132 | 133 | ref = time(NULL); 134 | now = odhcpd_time(); 135 | 136 | return config_time + (ref - now); 137 | } 138 | 139 | static inline bool statefiles_ra_pio_enabled(struct interface *iface) 140 | { 141 | return config.ra_piodir_fd >= 0 && iface->ra == MODE_SERVER && !iface->master; 142 | } 143 | 144 | static bool statefiles_ra_pio_time(json_object *slaac_json, time_t *slaac_time) 145 | { 146 | time_t pio_json_time, pio_time; 147 | json_object *time_json; 148 | 149 | time_json = json_object_object_get(slaac_json, JSON_TIME); 150 | if (!time_json) 151 | return true; 152 | 153 | pio_json_time = (time_t) json_object_get_int64(time_json); 154 | if (!pio_json_time) 155 | return true; 156 | 157 | pio_time = statefiles_time_from_json(pio_json_time); 158 | if (!pio_time) 159 | return false; 160 | 161 | *slaac_time = pio_time; 162 | 163 | return true; 164 | } 165 | 166 | static json_object *statefiles_load_ra_pio_json(struct interface *iface) 167 | { 168 | json_object *json; 169 | char filename[strlen(ODHCPD_PIO_FILE_PREFIX) + strlen(".") + strlen(iface->ifname) + 1]; 170 | int fd; 171 | 172 | sprintf(filename, "%s.%s", ODHCPD_PIO_FILE_PREFIX, iface->ifname); 173 | fd = openat(config.ra_piodir_fd, filename, O_RDONLY | O_CLOEXEC); 174 | if (fd < 0) 175 | return NULL; 176 | 177 | json = json_object_from_fd(fd); 178 | 179 | close(fd); 180 | 181 | if (!json) 182 | error("rfc9096: %s: json read error %s", 183 | iface->ifname, 184 | json_util_get_last_err()); 185 | 186 | return json; 187 | } 188 | 189 | void statefiles_read_prefix_information(struct interface *iface) 190 | { 191 | json_object *json, *slaac_json; 192 | struct ra_pio *new_pios; 193 | size_t pio_cnt; 194 | time_t now; 195 | 196 | if (!statefiles_ra_pio_enabled(iface)) 197 | return; 198 | 199 | json = statefiles_load_ra_pio_json(iface); 200 | if (!json) 201 | return; 202 | 203 | slaac_json = json_object_object_get(json, JSON_SLAAC); 204 | if (!slaac_json) { 205 | json_object_put(json); 206 | return; 207 | } 208 | 209 | now = odhcpd_time(); 210 | 211 | pio_cnt = json_object_array_length(slaac_json); 212 | new_pios = realloc(iface->pios, sizeof(struct ra_pio) * pio_cnt); 213 | if (!new_pios) { 214 | json_object_put(json); 215 | return; 216 | } 217 | 218 | iface->pios = new_pios; 219 | iface->pio_cnt = 0; 220 | for (size_t i = 0; i < pio_cnt; i++) { 221 | json_object *cur_pio_json, *length_json, *prefix_json; 222 | const char *pio_str; 223 | time_t pio_lt = 0; 224 | struct ra_pio *pio; 225 | uint8_t pio_len; 226 | 227 | cur_pio_json = json_object_array_get_idx(slaac_json, i); 228 | if (!cur_pio_json) 229 | continue; 230 | 231 | if (!statefiles_ra_pio_time(cur_pio_json, &pio_lt)) 232 | continue; 233 | 234 | length_json = json_object_object_get(cur_pio_json, JSON_LENGTH); 235 | if (!length_json) 236 | continue; 237 | 238 | prefix_json = json_object_object_get(cur_pio_json, JSON_PREFIX); 239 | if (!prefix_json) 240 | continue; 241 | 242 | pio_len = (uint8_t) json_object_get_uint64(length_json); 243 | pio_str = json_object_get_string(prefix_json); 244 | pio = &iface->pios[iface->pio_cnt]; 245 | 246 | inet_pton(AF_INET6, pio_str, &pio->prefix); 247 | pio->length = pio_len; 248 | pio->lifetime = pio_lt; 249 | info("rfc9096: %s: load %s/%u (%u)", 250 | iface->ifname, 251 | pio_str, 252 | pio_len, 253 | ra_pio_lifetime(pio, now)); 254 | 255 | iface->pio_cnt++; 256 | } 257 | 258 | json_object_put(json); 259 | 260 | if (!iface->pio_cnt) { 261 | free(iface->pios); 262 | iface->pios = NULL; 263 | } else if (iface->pio_cnt != pio_cnt) { 264 | struct ra_pio *tmp; 265 | 266 | tmp = realloc(iface->pios, sizeof(struct ra_pio) * iface->pio_cnt); 267 | if (tmp) 268 | iface->pios = tmp; 269 | } 270 | } 271 | 272 | void statefiles_write_prefix_information(struct interface *iface) 273 | { 274 | struct json_object *json, *slaac_json; 275 | char ipv6_str[INET6_ADDRSTRLEN]; 276 | time_t now; 277 | FILE *fp; 278 | 279 | if (!statefiles_ra_pio_enabled(iface)) 280 | return; 281 | 282 | if (!iface->pio_update) 283 | return; 284 | 285 | fp = statefiles_open_tmp_file(config.ra_piodir_fd); 286 | if (!fp) 287 | return; 288 | 289 | now = odhcpd_time(); 290 | 291 | json = json_object_new_object(); 292 | if (!json) 293 | goto out; 294 | 295 | slaac_json = json_object_new_array_ext(iface->pio_cnt); 296 | if (!slaac_json) 297 | goto out; 298 | 299 | json_object_object_add(json, JSON_SLAAC, slaac_json); 300 | 301 | for (size_t i = 0; i < iface->pio_cnt; i++) { 302 | struct json_object *cur_pio_json, *len_json, *pfx_json; 303 | const struct ra_pio *cur_pio = &iface->pios[i]; 304 | 305 | if (ra_pio_expired(cur_pio, now)) 306 | continue; 307 | 308 | cur_pio_json = json_object_new_object(); 309 | if (!cur_pio_json) 310 | continue; 311 | 312 | inet_ntop(AF_INET6, &cur_pio->prefix, ipv6_str, sizeof(ipv6_str)); 313 | 314 | pfx_json = json_object_new_string(ipv6_str); 315 | if (!pfx_json) { 316 | json_object_put(cur_pio_json); 317 | continue; 318 | } 319 | 320 | len_json = json_object_new_uint64(cur_pio->length); 321 | if (!len_json) { 322 | json_object_put(cur_pio_json); 323 | json_object_put(pfx_json); 324 | continue; 325 | } 326 | 327 | json_object_object_add(cur_pio_json, JSON_PREFIX, pfx_json); 328 | json_object_object_add(cur_pio_json, JSON_LENGTH, len_json); 329 | 330 | if (cur_pio->lifetime) { 331 | struct json_object *time_json; 332 | time_t pio_lt; 333 | 334 | pio_lt = statefiles_time_to_json(cur_pio->lifetime); 335 | 336 | time_json = json_object_new_int64(pio_lt); 337 | if (time_json) 338 | json_object_object_add(cur_pio_json, JSON_TIME, time_json); 339 | } 340 | 341 | json_object_array_add(slaac_json, cur_pio_json); 342 | } 343 | 344 | if (json_object_to_fd(fileno(fp), json, JSON_C_TO_STRING_PLAIN)) { 345 | error("rfc9096: %s: json write error %s", 346 | iface->ifname, 347 | json_util_get_last_err()); 348 | goto out; 349 | } 350 | 351 | statefiles_finish_tmp_file(config.ra_piodir_fd, &fp, ODHCPD_PIO_FILE_PREFIX, iface->ifname); 352 | iface->pio_update = false; 353 | warn("rfc9096: %s: piofile updated", iface->ifname); 354 | 355 | out: 356 | json_object_put(json); 357 | statefiles_finish_tmp_file(config.ra_piodir_fd, &fp, NULL, NULL); 358 | } 359 | 360 | static void statefiles_write_host(const char *ipbuf, const char *hostname, struct write_ctxt *ctxt) 361 | { 362 | char exp_dn[DNS_MAX_NAME_LEN]; 363 | 364 | if (dn_expand(ctxt->iface->dns_search, ctxt->iface->dns_search + ctxt->iface->dns_search_len, 365 | ctxt->iface->dns_search, exp_dn, sizeof(exp_dn)) > 0) 366 | fprintf(ctxt->fp, "%s\t%s.%s\t%s\n", ipbuf, hostname, exp_dn, hostname); 367 | else 368 | fprintf(ctxt->fp, "%s\t%s\n", ipbuf, hostname); 369 | } 370 | 371 | static bool statefiles_write_host6(struct write_ctxt *ctxt, struct dhcpv6_lease *lease, 372 | struct in6_addr *addr) 373 | { 374 | char ipbuf[INET6_ADDRSTRLEN]; 375 | 376 | if (!lease->hostname || !lease->hostname_valid || !(lease->flags & OAF_DHCPV6_NA)) 377 | return false; 378 | 379 | if (ctxt->fp) { 380 | inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf)); 381 | statefiles_write_host(ipbuf, lease->hostname, ctxt); 382 | } 383 | 384 | return true; 385 | } 386 | 387 | static void statefiles_write_host6_cb(struct dhcpv6_lease *lease, struct in6_addr *addr, _o_unused uint8_t prefix_len, 388 | _o_unused uint32_t pref_lt, _o_unused uint32_t valid_lt, void *arg) 389 | { 390 | struct write_ctxt *ctxt = (struct write_ctxt *)arg; 391 | 392 | statefiles_write_host6(ctxt, lease, addr); 393 | } 394 | 395 | static bool statefiles_write_host4(struct write_ctxt *ctxt, struct dhcpv4_lease *lease) 396 | { 397 | char ipbuf[INET_ADDRSTRLEN]; 398 | 399 | if (!lease->hostname || !lease->hostname_valid) 400 | return false; 401 | 402 | if (ctxt->fp) { 403 | inet_ntop(AF_INET, &lease->ipv4, ipbuf, sizeof(ipbuf)); 404 | statefiles_write_host(ipbuf, lease->hostname, ctxt); 405 | } 406 | 407 | return true; 408 | } 409 | 410 | static void statefiles_write_hosts(time_t now) 411 | { 412 | struct write_ctxt ctxt; 413 | 414 | if (config.dhcp_hostsdir_fd < 0) 415 | return; 416 | 417 | avl_for_each_element(&interfaces, ctxt.iface, avl) { 418 | if (ctxt.iface->ignore) 419 | continue; 420 | 421 | if (ctxt.iface->dhcpv4 != MODE_SERVER && ctxt.iface->dhcpv6 != MODE_SERVER) 422 | continue; 423 | 424 | ctxt.fp = statefiles_open_tmp_file(config.dhcp_hostsdir_fd); 425 | if (!ctxt.fp) 426 | continue; 427 | 428 | if (ctxt.iface->dhcpv6 == MODE_SERVER) { 429 | struct dhcpv6_lease *lease; 430 | 431 | list_for_each_entry(lease, &ctxt.iface->ia_assignments, head) { 432 | if (!lease->bound) 433 | continue; 434 | 435 | if (!INFINITE_VALID(lease->valid_until) && lease->valid_until <= now) 436 | continue; 437 | 438 | odhcpd_enum_addr6(ctxt.iface, lease, now, 439 | statefiles_write_host6_cb, &ctxt); 440 | } 441 | } 442 | 443 | if (ctxt.iface->dhcpv4 == MODE_SERVER) { 444 | struct dhcpv4_lease *lease; 445 | 446 | avl_for_each_element(&ctxt.iface->dhcpv4_leases, lease, iface_avl) { 447 | if (!lease->bound) 448 | continue; 449 | 450 | if (!INFINITE_VALID(lease->valid_until) && lease->valid_until <= now) 451 | continue; 452 | 453 | statefiles_write_host4(&ctxt, lease); 454 | } 455 | } 456 | 457 | statefiles_finish_tmp_file(config.dhcp_hostsdir_fd, &ctxt.fp, 458 | ODHCPD_HOSTS_FILE_PREFIX, ctxt.iface->name); 459 | } 460 | } 461 | 462 | static void statefiles_write_state6_addr(struct dhcpv6_lease *lease, struct in6_addr *addr, uint8_t prefix_len, 463 | _o_unused uint32_t pref_lt, _o_unused uint32_t valid_lt, void *arg) 464 | { 465 | struct write_ctxt *ctxt = (struct write_ctxt *)arg; 466 | char ipbuf[INET6_ADDRSTRLEN]; 467 | 468 | if (lease->hostname && lease->hostname_valid && lease->flags & OAF_DHCPV6_NA) { 469 | md5_hash(addr, sizeof(*addr), &ctxt->md5); 470 | md5_hash(lease->hostname, strlen(lease->hostname), &ctxt->md5); 471 | } 472 | 473 | if (!ctxt->fp) 474 | return; 475 | 476 | inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf)); 477 | fprintf(ctxt->fp, " %s/%" PRIu8, ipbuf, prefix_len); 478 | } 479 | 480 | static void statefiles_write_state6(struct write_ctxt *ctxt, struct dhcpv6_lease *lease) 481 | { 482 | char duidbuf[DUID_HEXSTRLEN]; 483 | 484 | if (ctxt->fp) { 485 | odhcpd_hexlify(duidbuf, lease->duid, lease->duid_len); 486 | 487 | /* # [ ...] */ 488 | fprintf(ctxt->fp, 489 | "# %s %s %x %s%s %" PRId64 " %" PRIx64 " %" PRIu8, 490 | ctxt->iface->ifname, duidbuf, ntohl(lease->iaid), 491 | lease->hostname && !lease->hostname_valid ? "broken\\x20": "", 492 | lease->hostname ? lease->hostname : "-", 493 | (lease->valid_until > ctxt->now ? 494 | (int64_t)(lease->valid_until - ctxt->now + ctxt->wall_time) : 495 | (INFINITE_VALID(lease->valid_until) ? -1 : 0)), 496 | (lease->flags & OAF_DHCPV6_NA ? 497 | lease->assigned_host_id : 498 | (uint64_t)lease->assigned_subnet_id), 499 | lease->length); 500 | } 501 | 502 | odhcpd_enum_addr6(ctxt->iface, lease, ctxt->now, statefiles_write_state6_addr, ctxt); 503 | 504 | if (ctxt->fp) 505 | putc('\n', ctxt->fp); 506 | } 507 | 508 | static void statefiles_write_state4(struct write_ctxt *ctxt, struct dhcpv4_lease *lease) 509 | { 510 | char ipbuf[INET6_ADDRSTRLEN]; 511 | 512 | if (lease->hostname && lease->hostname_valid) { 513 | md5_hash(&lease->ipv4, sizeof(lease->ipv4), &ctxt->md5); 514 | md5_hash(lease->hostname, strlen(lease->hostname), &ctxt->md5); 515 | } 516 | 517 | if (!ctxt->fp) 518 | return; 519 | 520 | inet_ntop(AF_INET, &lease->ipv4, ipbuf, sizeof(ipbuf)); 521 | 522 | /* # "ipv4" "32" "/32" */ 523 | fprintf(ctxt->fp, 524 | "# %s %s ipv4 %s%s %" PRId64 " %x 32 %s/32\n", 525 | ctxt->iface->ifname, 526 | ether_ntoa((struct ether_addr *)lease->hwaddr), 527 | lease->hostname && !lease->hostname_valid ? "broken\\x20" : "", 528 | lease->hostname ? lease->hostname : "-", 529 | (lease->valid_until > ctxt->now ? 530 | (int64_t)(lease->valid_until - ctxt->now + ctxt->wall_time) : 531 | (INFINITE_VALID(lease->valid_until) ? -1 : 0)), 532 | ntohl(lease->ipv4.s_addr), ipbuf); 533 | } 534 | 535 | /* Returns true if there are changes to be written to the hosts file(s) */ 536 | static bool statefiles_write_state(time_t now) 537 | { 538 | struct write_ctxt ctxt = { 539 | .fp = NULL, 540 | .now = now, 541 | .wall_time = time(NULL), 542 | }; 543 | uint8_t newmd5[16]; 544 | 545 | /* Return value unchecked, continue in order to get the md5 */ 546 | ctxt.fp = statefiles_open_tmp_file(config.dhcp_statedir_fd); 547 | 548 | md5_begin(&ctxt.md5); 549 | 550 | avl_for_each_element(&interfaces, ctxt.iface, avl) { 551 | if (ctxt.iface->dhcpv6 == MODE_SERVER) { 552 | struct dhcpv6_lease *lease; 553 | 554 | list_for_each_entry(lease, &ctxt.iface->ia_assignments, head) { 555 | if (!lease->bound) 556 | continue; 557 | 558 | if (!INFINITE_VALID(lease->valid_until) && lease->valid_until <= now) 559 | continue; 560 | 561 | statefiles_write_state6(&ctxt, lease); 562 | } 563 | } 564 | 565 | if (ctxt.iface->dhcpv4 == MODE_SERVER) { 566 | struct dhcpv4_lease *lease; 567 | 568 | avl_for_each_element(&ctxt.iface->dhcpv4_leases, lease, iface_avl) { 569 | if (!lease->bound) 570 | continue; 571 | 572 | if (!INFINITE_VALID(lease->valid_until) && lease->valid_until <= now) 573 | continue; 574 | 575 | statefiles_write_state4(&ctxt, lease); 576 | } 577 | } 578 | } 579 | 580 | statefiles_finish_tmp_file(config.dhcp_statedir_fd, &ctxt.fp, config.dhcp_statefile, NULL); 581 | 582 | md5_end(newmd5, &ctxt.md5); 583 | if (!memcmp(newmd5, statemd5, sizeof(newmd5))) 584 | return false; 585 | 586 | memcpy(statemd5, newmd5, sizeof(statemd5)); 587 | return true; 588 | } 589 | 590 | bool statefiles_write() 591 | { 592 | time_t now = odhcpd_time(); 593 | 594 | if (!statefiles_write_state(now)) 595 | return false; 596 | 597 | statefiles_write_hosts(now); 598 | 599 | if (config.dhcp_cb) { 600 | char *argv[2] = { config.dhcp_cb, NULL }; 601 | pid_t pid; 602 | 603 | posix_spawn(&pid, argv[0], NULL, NULL, argv, environ); 604 | } 605 | 606 | return true; 607 | } 608 | 609 | void statefiles_setup_dirfd(const char *path, int *dirfd) 610 | { 611 | if (!dirfd) 612 | return; 613 | 614 | if (*dirfd >= 0) { 615 | close(*dirfd); 616 | *dirfd = -1; 617 | } 618 | 619 | if (!path) 620 | return; 621 | 622 | mkdir_p(strdupa(path), 0755); 623 | 624 | *dirfd = open(path, O_PATH | O_DIRECTORY | O_CLOEXEC); 625 | if (*dirfd < 0) 626 | error("Unable to open directory '%s': %m", path); 627 | } 628 | -------------------------------------------------------------------------------- /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/odhcpd.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2012-2013 Steven Barth 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License v2 as published by 6 | * the Free Software Foundation. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | */ 14 | 15 | #ifndef _ODHCPD_H_ 16 | #define _ODHCPD_H_ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #define min(a, b) (((a) < (b)) ? (a) : (b)) 33 | #define max(a, b) (((a) > (b)) ? (a) : (b)) 34 | 35 | /* RFC 1035, §2.3.4, with one extra byte for buffers */ 36 | #define DNS_MAX_NAME_LEN 256 37 | #define DNS_MAX_LABEL_LEN 63 38 | 39 | // RFC 6106 defines this router advertisement option 40 | #define ND_OPT_ROUTE_INFO 24 41 | #define ND_OPT_RECURSIVE_DNS 25 42 | #define ND_OPT_DNS_SEARCH 31 43 | 44 | // RFC 8910 defines captive portal option 45 | #define ND_OPT_CAPTIVE_PORTAL 37 46 | 47 | // RFC 8781 defines PREF64 option 48 | #define ND_OPT_PREF64 38 49 | 50 | // RFC9096 defines recommended option lifetimes configuration values 51 | #define ND_PREFERRED_LIMIT 2700 52 | #define ND_VALID_LIMIT 5400 53 | 54 | // RFC 9463 - Discovery of Network-designated Resolvers (DNR) 55 | #define ND_OPT_DNR 144 56 | 57 | #define INFINITE_VALID(x) ((x) == 0) 58 | 59 | #ifndef _o_fallthrough 60 | #define _o_fallthrough __attribute__((__fallthrough__)) 61 | #endif /* _o_fallthrough */ 62 | 63 | #ifndef _o_packed 64 | #define _o_packed __attribute__((packed)) 65 | #endif /* _o_packed */ 66 | 67 | #ifndef _o_unused 68 | #define _o_unused __attribute__((unused)) 69 | #endif /* _o_unused */ 70 | 71 | #ifndef _o_noreturn 72 | #define _o_noreturn __attribute__((__noreturn__)) 73 | #endif /* _o_noreturn */ 74 | 75 | #define ALL_IPV6_NODES "ff02::1" 76 | #define ALL_IPV6_ROUTERS "ff02::2" 77 | 78 | #define NTP_SUBOPTION_SRV_ADDR 1 79 | #define NTP_SUBOPTION_MC_ADDR 2 80 | #define NTP_SUBOPTION_SRV_FQDN 3 81 | #define IPV6_ADDR_LEN 16 82 | 83 | #define IN6_IS_ADDR_ULA(a) (((a)->s6_addr32[0] & htonl(0xfe000000)) == htonl(0xfc000000)) 84 | 85 | #define ADDR_MATCH_PIO_FILTER(_addr, iface) (odhcpd_bmemcmp(&(_addr)->addr, \ 86 | &(iface)->pio_filter_addr, \ 87 | (iface)->pio_filter_length) != 0 || \ 88 | (_addr)->prefix_len < (iface)->pio_filter_length) 89 | 90 | struct interface; 91 | struct nl_sock; 92 | extern struct config config; 93 | extern struct sys_conf sys_conf; 94 | 95 | void __iflog(int lvl, const char *fmt, ...); 96 | #define debug(fmt, ...) __iflog(LOG_DEBUG, fmt __VA_OPT__(, ) __VA_ARGS__) 97 | #define info(fmt, ...) __iflog(LOG_INFO, fmt __VA_OPT__(, ) __VA_ARGS__) 98 | #define notice(fmt, ...) __iflog(LOG_NOTICE, fmt __VA_OPT__(, ) __VA_ARGS__) 99 | #define warn(fmt, ...) __iflog(LOG_WARNING, fmt __VA_OPT__(, ) __VA_ARGS__) 100 | #define error(fmt, ...) __iflog(LOG_ERR, fmt __VA_OPT__(, ) __VA_ARGS__) 101 | #define critical(fmt, ...) __iflog(LOG_CRIT, fmt __VA_OPT__(, ) __VA_ARGS__) 102 | #define alert(fmt, ...) __iflog(LOG_ALERT, fmt __VA_OPT__(, ) __VA_ARGS__) 103 | #define emergency(fmt, ...) __iflog(LOG_EMERG, fmt __VA_OPT__(, ) __VA_ARGS__) 104 | 105 | 106 | struct odhcpd_event { 107 | struct uloop_fd uloop; 108 | void (*handle_dgram)(void *addr, void *data, size_t len, 109 | struct interface *iface, void *dest_addr); 110 | void (*handle_error)(struct odhcpd_event *e, int error); 111 | void (*recv_msgs)(struct odhcpd_event *e); 112 | }; 113 | 114 | typedef ssize_t (*send_reply_cb_t)(struct iovec *iov, size_t iov_len, 115 | struct sockaddr *dest, socklen_t dest_len, 116 | void *opaque); 117 | 118 | union in46_addr { 119 | struct in_addr in; 120 | struct in6_addr in6; 121 | }; 122 | 123 | struct netevent_handler_info { 124 | struct interface *iface; 125 | union { 126 | struct { 127 | union in46_addr dst; 128 | uint8_t dst_len; 129 | union in46_addr gateway; 130 | } rt; 131 | struct { 132 | union in46_addr dst; 133 | uint16_t state; 134 | uint8_t flags; 135 | } neigh; 136 | struct { 137 | struct odhcpd_ipaddr *addrs; 138 | size_t len; 139 | } addrs_old; 140 | union in46_addr addr; 141 | }; 142 | }; 143 | 144 | enum netevents { 145 | NETEV_IFINDEX_CHANGE, 146 | NETEV_ADDR_ADD, 147 | NETEV_ADDR_DEL, 148 | NETEV_ADDRLIST_CHANGE, 149 | NETEV_ADDR6_ADD, 150 | NETEV_ADDR6_DEL, 151 | NETEV_ADDR6LIST_CHANGE, 152 | NETEV_ROUTE6_ADD, 153 | NETEV_ROUTE6_DEL, 154 | NETEV_NEIGH6_ADD, 155 | NETEV_NEIGH6_DEL, 156 | }; 157 | 158 | struct netevent_handler { 159 | struct list_head head; 160 | void (*cb) (unsigned long event, struct netevent_handler_info *info); 161 | }; 162 | 163 | struct odhcpd_ipaddr { 164 | union in46_addr addr; 165 | uint8_t prefix_len; 166 | uint32_t preferred_lt; 167 | uint32_t valid_lt; 168 | 169 | union { 170 | /* IPv6 only */ 171 | struct { 172 | uint8_t dprefix_len; 173 | bool tentative; 174 | }; 175 | 176 | /* IPv4 only */ 177 | struct { 178 | struct in_addr broadcast; 179 | in_addr_t netmask; 180 | }; 181 | }; 182 | }; 183 | 184 | enum odhcpd_mode { 185 | MODE_DISABLED, 186 | MODE_SERVER, 187 | MODE_RELAY, 188 | MODE_HYBRID 189 | }; 190 | 191 | 192 | enum odhcpd_assignment_flags { 193 | OAF_DHCPV6_NA = (1 << 0), 194 | OAF_DHCPV6_PD = (1 << 1), 195 | }; 196 | 197 | /* 2-byte type + 128-byte DUID, RFC8415, §11.1 */ 198 | #define DUID_MAX_LEN 130 199 | /* In theory, 2 (type only), or 7 (DUID-EN + 1-byte data), but be reasonable */ 200 | #define DUID_MIN_LEN 10 201 | #define DUID_HEXSTRLEN (DUID_MAX_LEN * 2 + 1) 202 | 203 | enum duid_type { 204 | DUID_TYPE_LLT = 1, 205 | DUID_TYPE_EN = 2, 206 | DUID_TYPE_LL = 3, 207 | DUID_TYPE_UUID = 4, 208 | }; 209 | 210 | struct config { 211 | bool enable_tz; 212 | bool main_dhcpv4; 213 | char *dhcp_cb; 214 | bool use_ubus; 215 | 216 | char *dhcp_statefile; 217 | int dhcp_statedir_fd; 218 | char *dhcp_hostsdir; 219 | int dhcp_hostsdir_fd; 220 | 221 | char *ra_piodir; 222 | int ra_piodir_fd; 223 | 224 | char *uci_cfgdir; 225 | int log_level; 226 | bool log_level_cmdline; 227 | bool log_syslog; 228 | 229 | uint8_t default_duid[DUID_MAX_LEN]; 230 | size_t default_duid_len; 231 | }; 232 | 233 | struct sys_conf { 234 | uint8_t *posix_tz; 235 | size_t posix_tz_len; 236 | uint8_t *tzdb_tz; 237 | size_t tzdb_tz_len; 238 | }; 239 | 240 | struct duid { 241 | uint8_t len; 242 | uint8_t id[DUID_MAX_LEN]; 243 | uint32_t iaid; 244 | bool iaid_set; 245 | }; 246 | 247 | struct odhcpd_ref_ip; 248 | 249 | struct dhcpv4_lease { 250 | struct avl_node iface_avl; // struct interface->dhcpv4_leases 251 | 252 | struct interface *iface; // assignment interface, non-null 253 | struct lease_cfg *lease_cfg; // host lease cfg, nullable 254 | 255 | struct in_addr ipv4; // client IPv4 address 256 | bool bound; // the lease has been accepted by the client 257 | time_t valid_until; // CLOCK_MONOTONIC time, 0 = inf 258 | char *hostname; // client hostname 259 | bool hostname_valid; // is the hostname one or more valid DNS labels? 260 | size_t hwaddr_len; // hwaddr length 261 | uint8_t hwaddr[ETH_ALEN]; // hwaddr (only MAC supported) 262 | 263 | // ForceRenew Nonce - RFC6704 §3.1.2 264 | struct uloop_timeout fr_timer; // FR message transmission timer 265 | bool accept_fr_nonce; // FR client support 266 | unsigned fr_cnt; // FR messages sent 267 | uint8_t key[16]; // FR nonce 268 | struct odhcpd_ref_ip *fr_ip; // FR message old serverid/IP 269 | 270 | // RFC4361 271 | uint32_t iaid; 272 | uint8_t duid_len; 273 | uint8_t duid[]; 274 | }; 275 | 276 | struct dhcpv6_lease { 277 | struct list_head head; 278 | struct list_head lease_cfg_list; 279 | 280 | struct interface *iface; 281 | struct lease_cfg *lease_cfg; 282 | 283 | struct sockaddr_in6 peer; 284 | time_t valid_until; 285 | time_t preferred_until; 286 | 287 | // ForceRenew Nonce - RFC8415 §20.4, §21.11 288 | struct uloop_timeout fr_timer; 289 | bool accept_fr_nonce; 290 | int fr_cnt; 291 | uint8_t key[16]; 292 | 293 | union { 294 | uint64_t assigned_host_id; 295 | uint32_t assigned_subnet_id; 296 | }; 297 | uint8_t length; // length == 128 -> IA_NA, length <= 64 -> IA_PD 298 | 299 | unsigned int flags; 300 | bool bound; // the lease has been accepted by the client 301 | uint32_t leasetime; 302 | char *hostname; 303 | bool hostname_valid; // is the hostname one or more valid DNS labels? 304 | 305 | uint32_t iaid; 306 | uint16_t duid_len; 307 | uint8_t duid[]; 308 | }; 309 | 310 | /* This corresponds to a UCI host section, i.e. a static lease cfg */ 311 | struct lease_cfg { 312 | struct vlist_node node; 313 | struct list_head dhcpv6_leases; 314 | struct dhcpv4_lease *dhcpv4_lease; 315 | struct in_addr ipv4; 316 | uint64_t hostid; 317 | size_t mac_count; 318 | struct ether_addr *macs; 319 | size_t duid_count; 320 | struct duid *duids; 321 | uint32_t leasetime; // duration of granted leases, UINT32_MAX = inf 322 | char *hostname; 323 | bool ignore4; 324 | bool ignore6; 325 | }; 326 | 327 | // DNR - RFC9463 328 | struct dnr_options { 329 | uint16_t priority; 330 | 331 | uint32_t lifetime; 332 | bool lifetime_set; 333 | 334 | uint8_t *adn; 335 | uint16_t adn_len; 336 | 337 | struct in_addr *addr4; 338 | size_t addr4_cnt; 339 | struct in6_addr *addr6; 340 | size_t addr6_cnt; 341 | 342 | uint8_t *svc; 343 | uint16_t svc_len; 344 | }; 345 | 346 | 347 | // RA PIO - RFC9096 348 | struct ra_pio { 349 | struct in6_addr prefix; 350 | uint8_t length; 351 | time_t lifetime; 352 | }; 353 | 354 | 355 | struct interface { 356 | struct avl_node avl; 357 | 358 | int ifflags; 359 | int ifindex; 360 | char *ifname; 361 | const char *name; 362 | uint32_t if_mtu; 363 | bool update_statefile; 364 | 365 | // IPv6 runtime data 366 | struct odhcpd_ipaddr *addr6; 367 | size_t addr6_len; 368 | 369 | // RA runtime data 370 | struct odhcpd_event router_event; 371 | struct uloop_timeout timer_rs; 372 | uint32_t ra_sent; 373 | 374 | // DHCPv6 runtime data 375 | struct odhcpd_event dhcpv6_event; 376 | struct list_head ia_assignments; 377 | 378 | // NDP runtime data 379 | struct odhcpd_event ndp_event; 380 | int ndp_ping_fd; 381 | 382 | // IPv4 runtime data 383 | struct odhcpd_ipaddr *oaddrs4; // IPv4 addresses assigned to this interface 384 | size_t oaddrs4_cnt; // Number of IPv4 addresses assigned to this interface 385 | 386 | // DHCPv4 runtime data 387 | struct odhcpd_event dhcpv4_event; 388 | struct avl_tree dhcpv4_leases; 389 | struct list_head dhcpv4_fr_ips; 390 | 391 | /* NOTE: everything from this point is zeroed on odhcpd_reload() */ 392 | 393 | // Services 394 | enum odhcpd_mode ra; 395 | enum odhcpd_mode dhcpv6; 396 | enum odhcpd_mode ndp; 397 | enum odhcpd_mode dhcpv4; 398 | 399 | // Config 400 | bool inuse; 401 | bool external; 402 | bool master; 403 | bool ignore; 404 | bool always_rewrite_dns; 405 | bool dns_service; 406 | 407 | // NDP 408 | int learn_routes; 409 | bool ndp_from_link_local; 410 | struct in6_addr cached_linklocal_addr; 411 | bool cached_linklocal_valid; 412 | 413 | // RA 414 | uint8_t ra_flags; 415 | bool ra_slaac; 416 | bool ra_not_onlink; 417 | bool ra_advrouter; 418 | bool ra_dns; 419 | uint8_t pref64_length; 420 | uint8_t pref64_plc; 421 | uint32_t pref64_prefix[3]; 422 | bool no_dynamic_dhcp; 423 | bool have_link_local; 424 | uint8_t pio_filter_length; 425 | struct in6_addr pio_filter_addr; 426 | int default_router; 427 | int route_preference; 428 | uint32_t ra_maxinterval; 429 | uint32_t ra_mininterval; 430 | uint32_t ra_lifetime; 431 | uint32_t ra_reachabletime; 432 | uint32_t ra_retranstime; 433 | uint32_t ra_hoplimit; 434 | uint32_t ra_mtu; 435 | uint32_t max_preferred_lifetime; 436 | uint32_t max_valid_lifetime; 437 | 438 | // DHCP 439 | uint32_t dhcp_leasetime; 440 | 441 | // DHCPv4 442 | uint32_t dhcpv4_pool_start; // Offset to first dynamic address 443 | uint32_t dhcpv4_pool_end; // Offset to last dynamic address 444 | struct in_addr dhcpv4_start_ip; 445 | struct in_addr dhcpv4_end_ip; 446 | struct odhcpd_ipaddr dhcpv4_own_ip; 447 | struct in_addr *dhcpv4_routers; // IPv4 addresses for routers on this subnet 448 | size_t dhcpv4_routers_cnt; // Count of router addresses 449 | bool dhcpv4_forcereconf; 450 | uint32_t dhcpv4_v6only_wait; // V6ONLY_WAIT for the IPv6-only preferred option (RFC8925) 451 | 452 | // DNS 453 | struct in_addr *dns_addrs4; // IPv4 DNS server addresses to announce 454 | size_t dns_addrs4_cnt; // Count of IPv4 DNS addresses 455 | struct in6_addr *dns_addrs6; // IPv6 DNS server addresses to announce 456 | size_t dns_addrs6_cnt; // Count of IPv6 DNS addresses 457 | uint8_t *dns_search; // DNS domain search list to announce (concatenated) 458 | size_t dns_search_len; // Length of the DNS domain search list (bytes) 459 | 460 | // DHCPV6 461 | void *dhcpv6_raw; 462 | size_t dhcpv6_raw_len; 463 | bool dhcpv6_assignall; 464 | bool dhcpv6_pd; 465 | bool dhcpv6_pd_preferred; 466 | bool dhcpv6_na; 467 | uint32_t dhcpv6_hostid_len; 468 | uint32_t dhcpv6_pd_min_len; // minimum delegated prefix length 469 | 470 | char *upstream; 471 | size_t upstream_len; 472 | 473 | // NTP 474 | struct in_addr *dhcpv4_ntp; 475 | size_t dhcpv4_ntp_cnt; 476 | uint8_t *dhcpv6_ntp; 477 | uint16_t dhcpv6_ntp_len; 478 | size_t dhcpv6_ntp_cnt; 479 | 480 | // SNTP 481 | struct in6_addr *dhcpv6_sntp; 482 | size_t dhcpv6_sntp_cnt; 483 | 484 | // DNR 485 | struct dnr_options *dnr; 486 | size_t dnr_cnt; 487 | 488 | // RA PIO - RFC9096 489 | struct ra_pio *pios; 490 | size_t pio_cnt; 491 | bool pio_update; 492 | 493 | // RFC8910 494 | char *captive_portal_uri; 495 | size_t captive_portal_uri_len; 496 | }; 497 | 498 | extern struct avl_tree interfaces; 499 | 500 | enum { 501 | LEASE_CFG_ATTR_IPV4, 502 | LEASE_CFG_ATTR_MAC, 503 | LEASE_CFG_ATTR_DUID, 504 | LEASE_CFG_ATTR_HOSTID, 505 | LEASE_CFG_ATTR_LEASETIME, 506 | LEASE_CFG_ATTR_NAME, 507 | LEASE_CFG_ATTR_MAX 508 | }; 509 | extern const struct blobmsg_policy lease_cfg_attrs[LEASE_CFG_ATTR_MAX]; 510 | 511 | inline static bool ra_pio_expired(const struct ra_pio *pio, time_t now) 512 | { 513 | return pio->lifetime && (now > pio->lifetime); 514 | } 515 | 516 | inline static uint32_t ra_pio_lifetime(const struct ra_pio *pio, time_t now) 517 | { 518 | if (!pio->lifetime || now > pio->lifetime) 519 | return 0; 520 | 521 | return (uint32_t) (pio->lifetime - now); 522 | } 523 | 524 | inline static bool ra_pio_stale(const struct ra_pio *pio) 525 | { 526 | return !!pio->lifetime; 527 | } 528 | 529 | // Exported main functions 530 | int odhcpd_register(struct odhcpd_event *event); 531 | int odhcpd_deregister(struct odhcpd_event *event); 532 | void odhcpd_process(struct odhcpd_event *event); 533 | 534 | ssize_t odhcpd_send_with_src(int socket, struct sockaddr_in6 *dest, 535 | struct iovec *iov, size_t iov_len, 536 | const struct interface *iface, const struct in6_addr *src_addr); 537 | ssize_t odhcpd_send(int socket, struct sockaddr_in6 *dest, 538 | struct iovec *iov, size_t iov_len, 539 | const struct interface *iface); 540 | ssize_t odhcpd_try_send_with_src(int socket, struct sockaddr_in6 *dest, 541 | struct iovec *iov, size_t iov_len, 542 | struct interface *iface); 543 | int odhcpd_get_interface_dns_addr6(struct interface *iface, 544 | struct in6_addr *dns_addr6); 545 | int odhcpd_get_interface_linklocal_addr(struct interface *iface, 546 | struct in6_addr *addr); 547 | int odhcpd_get_interface_config(const char *ifname, const char *what); 548 | int odhcpd_get_mac(const struct interface *iface, uint8_t mac[6]); 549 | int odhcpd_get_flags(const struct interface *iface); 550 | struct interface* odhcpd_get_interface_by_index(int ifindex); 551 | void odhcpd_urandom(void *data, size_t len); 552 | 553 | int odhcpd_run(void); 554 | time_t odhcpd_time(void); 555 | ssize_t odhcpd_unhexlify(uint8_t *dst, size_t len, const char *src); 556 | void odhcpd_hexlify(char *dst, const uint8_t *src, size_t len); 557 | const char *odhcpd_print_mac(const uint8_t *mac, const size_t len); 558 | 559 | int odhcpd_bmemcmp(const void *av, const void *bv, size_t bits); 560 | void odhcpd_bmemcpy(void *av, const void *bv, size_t bits); 561 | 562 | typedef void (*odhcpd_enum_addr6_cb_t)(struct dhcpv6_lease *lease, 563 | struct in6_addr *addr, uint8_t prefix_len, 564 | uint32_t pref, uint32_t valid, 565 | void *arg); 566 | void odhcpd_enum_addr6(struct interface *iface, struct dhcpv6_lease *lease, 567 | time_t now, odhcpd_enum_addr6_cb_t func, void *arg); 568 | int odhcpd_parse_addr6_prefix(const char *str, struct in6_addr *addr, uint8_t *prefix); 569 | bool odhcpd_hostname_valid(const char *name); 570 | 571 | int config_parse_interface(void *data, size_t len, const char *iname, bool overwrite); 572 | struct lease_cfg *config_find_lease_cfg_by_duid_and_iaid(const uint8_t *duid, 573 | const uint16_t len, 574 | const uint32_t iaid); 575 | struct lease_cfg *config_find_lease_cfg_by_mac(const uint8_t *mac); 576 | struct lease_cfg *config_find_lease_cfg_by_hostid(const uint64_t hostid); 577 | struct lease_cfg *config_find_lease_cfg_by_ipv4(const struct in_addr ipv4); 578 | int config_set_lease_cfg_from_blobmsg(struct blob_attr *ba); 579 | 580 | #ifdef WITH_UBUS 581 | int ubus_init(void); 582 | const char* ubus_get_ifname(const char *name); 583 | void ubus_apply_network(void); 584 | bool ubus_has_prefix(const char *name, const char *ifname); 585 | void ubus_bcast_dhcpv4_event(const char *type, const char *iface, 586 | const struct dhcpv4_lease *lease); 587 | #else 588 | static inline int ubus_init(void) 589 | { 590 | return 0; 591 | } 592 | 593 | static inline const char *ubus_get_ifname(const char *name) 594 | { 595 | return NULL; 596 | } 597 | 598 | static inline void ubus_apply_network(void) 599 | { 600 | return; 601 | } 602 | 603 | static inline bool ubus_has_prefix(const char *name, const char *ifname) 604 | { 605 | return false; 606 | } 607 | 608 | static inline 609 | void ubus_bcast_dhcpv4_event(const char *type, const char *iface, 610 | const struct dhcpv4_lease *lease) 611 | { 612 | return; 613 | } 614 | #endif /* WITH_UBUS */ 615 | 616 | ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *iface, 617 | const struct sockaddr_in6 *addr, const void *data, const uint8_t *end); 618 | int dhcpv6_ia_init(void); 619 | int dhcpv6_ia_setup_interface(struct interface *iface, bool enable); 620 | void dhcpv6_free_lease(struct dhcpv6_lease *lease); 621 | 622 | int netlink_add_netevent_handler(struct netevent_handler *hdlr); 623 | ssize_t netlink_get_interface_addrs(const int ifindex, bool v6, 624 | struct odhcpd_ipaddr **oaddrs); 625 | ssize_t netlink_get_interface_linklocal(int ifindex, struct odhcpd_ipaddr **oaddrs); 626 | int netlink_get_interface_proxy_neigh(int ifindex, const struct in6_addr *addr); 627 | int netlink_setup_route(const struct in6_addr *addr, const int prefixlen, 628 | const int ifindex, const struct in6_addr *gw, 629 | const uint32_t metric, const bool add); 630 | int netlink_setup_proxy_neigh(const struct in6_addr *addr, 631 | const int ifindex, const bool add); 632 | int netlink_setup_addr(struct odhcpd_ipaddr *oaddr, 633 | const int ifindex, const bool v6, const bool add); 634 | void netlink_dump_neigh_table(const bool proxy); 635 | void netlink_dump_addr_table(const bool v6); 636 | 637 | // Exported module initializers 638 | int netlink_init(void); 639 | int router_init(void); 640 | int dhcpv6_init(void); 641 | int ndp_init(void); 642 | 643 | #ifdef DHCPV4_SUPPORT 644 | int dhcpv4_init(void); 645 | void dhcpv4_free_lease(struct dhcpv4_lease *a); 646 | bool dhcpv4_setup_interface(struct interface *iface, bool enable); 647 | void dhcpv4_handle_msg(void *addr, void *data, size_t len, 648 | struct interface *iface, _o_unused void *dest_addr, 649 | send_reply_cb_t send_reply, void *opaque); 650 | #else 651 | static inline bool dhcpv4_setup_interface(struct interface *iface, bool enable) { 652 | return true; 653 | } 654 | 655 | static inline void dhcpv4_free_lease(struct dhcpv4_lease *lease) { 656 | error("Trying to free IPv4 assignment 0x%p", lease); 657 | } 658 | #endif /* DHCPV4_SUPPORT */ 659 | 660 | int router_setup_interface(struct interface *iface, bool enable); 661 | int dhcpv6_setup_interface(struct interface *iface, bool enable); 662 | int ndp_setup_interface(struct interface *iface, bool enable); 663 | void reload_services(struct interface *iface); 664 | 665 | void odhcpd_reload(void); 666 | 667 | #endif /* _ODHCPD_H_ */ 668 | -------------------------------------------------------------------------------- /src/odhcpd.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2012-2013 Steven Barth 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License v2 as published by 6 | * the Free Software Foundation. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | */ 14 | 15 | #include 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 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | #include 44 | 45 | #include "odhcpd.h" 46 | #include "dhcpv6-ia.h" 47 | 48 | static int ioctl_sock = -1; 49 | 50 | void __iflog(int lvl, const char *fmt, ...) 51 | { 52 | va_list ap; 53 | 54 | if (lvl > config.log_level) 55 | return; 56 | 57 | va_start(ap, fmt); 58 | 59 | if (config.log_syslog) { 60 | vsyslog(lvl, fmt, ap); 61 | } else { 62 | vfprintf(stderr, fmt, ap); 63 | fprintf(stderr, "\n"); 64 | } 65 | 66 | va_end(ap); 67 | } 68 | 69 | _o_noreturn static void print_usage(const char *app, int exit_status) 70 | { 71 | printf("== %s Usage ==\n" 72 | "Features: ra ndp dhcpv6" 73 | #ifdef DHCPV4_SUPPORT 74 | " dhcpv4" 75 | #else 76 | " no-dhcpv4" 77 | #endif /* DHCPV4_SUPPORT */ 78 | #ifdef WITH_UBUS 79 | " ubus" 80 | #else 81 | " no-ubus" 82 | #endif /* WITH_UBUS */ 83 | "\n" 84 | "\n" 85 | " -c Read UCI configuration files from \n" 86 | " -l Specify log level 0..7 (default %d)\n" 87 | " -f Log to stderr instead of syslog\n" 88 | #ifdef WITH_UBUS 89 | " -u Disable ubus support\n" 90 | #endif /* WITH_UBUS */ 91 | " -h Print this help text and exit\n", 92 | app, config.log_level); 93 | 94 | exit(exit_status); 95 | } 96 | 97 | static bool ipv6_enabled(void) 98 | { 99 | int fd = socket(AF_INET6, SOCK_DGRAM, 0); 100 | 101 | if (fd < 0) 102 | return false; 103 | 104 | close(fd); 105 | 106 | return true; 107 | } 108 | 109 | int main(int argc, char **argv) 110 | { 111 | int opt; 112 | 113 | while ((opt = getopt(argc, argv, "c:l:fuh")) != -1) { 114 | switch (opt) { 115 | case 'c': 116 | struct stat sb; 117 | char *path; 118 | 119 | free(config.uci_cfgdir); 120 | config.uci_cfgdir = NULL; 121 | 122 | path = realpath(optarg, NULL); 123 | if (!path || stat(path, &sb) || !S_ISDIR(sb.st_mode)) { 124 | fprintf(stderr, "%s is not a directory, ignoring\n", optarg); 125 | free(path); 126 | break; 127 | } 128 | 129 | fprintf(stderr, "Configuration will be read from %s\n", path); 130 | config.uci_cfgdir = path; 131 | break; 132 | 133 | case 'l': 134 | config.log_level = (atoi(optarg) & LOG_PRIMASK); 135 | config.log_level_cmdline = true; 136 | fprintf(stderr, "Log level set to %d\n", config.log_level); 137 | break; 138 | case 'f': 139 | config.log_syslog = false; 140 | fprintf(stderr, "Logging to stderr\n"); 141 | break; 142 | case 'u': 143 | config.use_ubus = false; 144 | fprintf(stderr, "Ubus support disabled\n"); 145 | break; 146 | case 'h': 147 | print_usage(argv[0], EXIT_SUCCESS); 148 | case '?': 149 | default: 150 | print_usage(argv[0], EXIT_FAILURE); 151 | } 152 | } 153 | 154 | if (getuid() != 0) { 155 | error("Must be run as root!"); 156 | return 2; 157 | } 158 | 159 | if (config.log_syslog) { 160 | openlog("odhcpd", LOG_PERROR | LOG_PID, LOG_DAEMON); 161 | setlogmask(LOG_UPTO(config.log_level)); 162 | } 163 | 164 | uloop_init(); 165 | 166 | ioctl_sock = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); 167 | if (ioctl_sock < 0) 168 | return 4; 169 | 170 | if (netlink_init()) 171 | return 4; 172 | 173 | if (ipv6_enabled()) { 174 | if (router_init()) 175 | return 4; 176 | 177 | if (dhcpv6_init()) 178 | return 4; 179 | 180 | if (ndp_init()) 181 | return 4; 182 | } 183 | #ifndef DHCPV4_SUPPORT 184 | else 185 | return 4; 186 | #else 187 | if (dhcpv4_init()) 188 | return 4; 189 | #endif 190 | 191 | return odhcpd_run(); 192 | } 193 | 194 | 195 | /* Read IPv6 MTU for interface */ 196 | int odhcpd_get_interface_config(const char *ifname, const char *what) 197 | { 198 | char buf[64]; 199 | 200 | snprintf(buf, sizeof(buf), "/proc/sys/net/ipv6/conf/%s/%s", ifname, what); 201 | 202 | int fd = open(buf, O_RDONLY); 203 | if (fd < 0) 204 | return -1; 205 | 206 | ssize_t len = read(fd, buf, sizeof(buf) - 1); 207 | close(fd); 208 | 209 | if (len < 0) 210 | return -1; 211 | 212 | buf[len] = 0; 213 | return atoi(buf); 214 | } 215 | 216 | 217 | /* Read IPv6 MAC for interface */ 218 | int odhcpd_get_mac(const struct interface *iface, uint8_t mac[6]) 219 | { 220 | struct ifreq ifr; 221 | 222 | memset(&ifr, 0, sizeof(ifr)); 223 | strncpy(ifr.ifr_name, iface->ifname, sizeof(ifr.ifr_name) - 1); 224 | if (ioctl(ioctl_sock, SIOCGIFHWADDR, &ifr) < 0) 225 | return -1; 226 | 227 | memcpy(mac, ifr.ifr_hwaddr.sa_data, 6); 228 | return 0; 229 | } 230 | 231 | int odhcpd_get_flags(const struct interface *iface) 232 | { 233 | struct ifreq ifr; 234 | 235 | memset(&ifr, 0, sizeof(ifr)); 236 | strncpy(ifr.ifr_name, iface->ifname, sizeof(ifr.ifr_name) - 1); 237 | if (ioctl(ioctl_sock, SIOCGIFFLAGS, &ifr) < 0) 238 | return -1; 239 | 240 | return ifr.ifr_flags; 241 | } 242 | 243 | 244 | /* Forwards a packet on a specific interface with optional source address */ 245 | ssize_t odhcpd_send_with_src(int socket, struct sockaddr_in6 *dest, 246 | struct iovec *iov, size_t iov_len, 247 | const struct interface *iface, const struct in6_addr *src_addr) 248 | { 249 | /* Construct headers */ 250 | uint8_t cmsg_buf[CMSG_SPACE(sizeof(struct in6_pktinfo))] = {0}; 251 | struct msghdr msg = { 252 | .msg_name = (void *) dest, 253 | .msg_namelen = sizeof(*dest), 254 | .msg_iov = iov, 255 | .msg_iovlen = iov_len, 256 | .msg_control = cmsg_buf, 257 | .msg_controllen = sizeof(cmsg_buf), 258 | .msg_flags = 0 259 | }; 260 | 261 | /* Set control data (define destination interface) */ 262 | struct cmsghdr *chdr = CMSG_FIRSTHDR(&msg); 263 | chdr->cmsg_level = IPPROTO_IPV6; 264 | chdr->cmsg_type = IPV6_PKTINFO; 265 | chdr->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); 266 | struct in6_pktinfo *pktinfo = (struct in6_pktinfo*)CMSG_DATA(chdr); 267 | pktinfo->ipi6_ifindex = iface->ifindex; 268 | 269 | /* Set source address if provided */ 270 | if (src_addr) 271 | pktinfo->ipi6_addr = *src_addr; 272 | 273 | /* Also set scope ID if link-local */ 274 | if (IN6_IS_ADDR_LINKLOCAL(&dest->sin6_addr) 275 | || IN6_IS_ADDR_MC_LINKLOCAL(&dest->sin6_addr)) 276 | dest->sin6_scope_id = iface->ifindex; 277 | 278 | char ipbuf[INET6_ADDRSTRLEN]; 279 | inet_ntop(AF_INET6, &dest->sin6_addr, ipbuf, sizeof(ipbuf)); 280 | 281 | ssize_t sent = sendmsg(socket, &msg, MSG_DONTWAIT); 282 | if (sent < 0) 283 | error("Failed to send to %s%%%s@%s (%m)", 284 | ipbuf, iface->name, iface->ifname); 285 | else 286 | debug("Sent %zd bytes to %s%%%s@%s", 287 | sent, ipbuf, iface->name, iface->ifname); 288 | return sent; 289 | } 290 | 291 | /* Forwards a packet on a specific interface */ 292 | ssize_t odhcpd_send(int socket, struct sockaddr_in6 *dest, 293 | struct iovec *iov, size_t iov_len, 294 | const struct interface *iface) 295 | { 296 | return odhcpd_send_with_src(socket, dest, iov, iov_len, iface, NULL); 297 | } 298 | 299 | 300 | int odhcpd_get_interface_linklocal_addr(struct interface *iface, struct in6_addr *addr) 301 | { 302 | /* Return cached address if valid */ 303 | if (iface->cached_linklocal_valid) { 304 | *addr = iface->cached_linklocal_addr; 305 | return 0; 306 | } 307 | 308 | /* First try to get link-local address from interface addresses */ 309 | for (size_t i = 0; i < iface->addr6_len; ++i) { 310 | if (IN6_IS_ADDR_LINKLOCAL(&iface->addr6[i].addr.in6)) { 311 | *addr = iface->addr6[i].addr.in6; 312 | /* Cache the result for future use */ 313 | iface->cached_linklocal_addr = *addr; 314 | iface->cached_linklocal_valid = true; 315 | return 0; 316 | } 317 | } 318 | 319 | /* Fallback to socket-based method */ 320 | struct sockaddr_in6 sockaddr; 321 | socklen_t alen = sizeof(sockaddr); 322 | int sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); 323 | 324 | if (sock >= 0) { 325 | memset(&sockaddr, 0, sizeof(sockaddr)); 326 | sockaddr.sin6_family = AF_INET6; 327 | inet_pton(AF_INET6, ALL_IPV6_ROUTERS, &sockaddr.sin6_addr); 328 | sockaddr.sin6_scope_id = iface->ifindex; 329 | 330 | if (!connect(sock, (struct sockaddr*)&sockaddr, sizeof(sockaddr)) && 331 | !getsockname(sock, (struct sockaddr*)&sockaddr, &alen)) { 332 | *addr = sockaddr.sin6_addr; 333 | /* Cache the result for future use */ 334 | iface->cached_linklocal_addr = *addr; 335 | iface->cached_linklocal_valid = true; 336 | close(sock); 337 | return 0; 338 | } 339 | close(sock); 340 | } 341 | 342 | return -1; 343 | } 344 | 345 | /* Try to send with link-local source address for RFC 4861 compliance and macOS compatibility. 346 | * RFC 4861, §4.2 mandates that Neighbor Advertisement source address MUST be 347 | * the link-local address assigned to the interface from which this message is sent. */ 348 | ssize_t odhcpd_try_send_with_src(int socket, struct sockaddr_in6 *dest, 349 | struct iovec *iov, size_t iov_len, 350 | struct interface *iface) 351 | { 352 | struct in6_addr src_addr; 353 | 354 | if (iface->ndp_from_link_local && odhcpd_get_interface_linklocal_addr(iface, &src_addr) == 0) { 355 | return odhcpd_send_with_src(socket, dest, iov, iov_len, iface, &src_addr); 356 | } else { 357 | /* Fall back to default behavior if no link-local address is available or flag is disabled */ 358 | return odhcpd_send(socket, dest, iov, iov_len, iface); 359 | } 360 | } 361 | 362 | /* 363 | * DNS address selection criteria order : 364 | * - use IPv6 address with valid lifetime if none is yet selected 365 | * - use IPv6 address with a preferred lifetime if the already selected IPv6 address is deprecated 366 | * - use an IPv6 ULA address if the already selected IPv6 address is not an ULA address 367 | * - use the IPv6 address with the longest preferred lifetime 368 | */ 369 | int odhcpd_get_interface_dns_addr6(struct interface *iface, struct in6_addr *dns_addr6) 370 | { 371 | time_t now = odhcpd_time(); 372 | ssize_t m = -1; 373 | 374 | if (!iface->dns_service) 375 | return -1; 376 | 377 | for (size_t i = 0; i < iface->addr6_len; ++i) { 378 | if (iface->addr6[i].valid_lt <= (uint32_t)now) 379 | continue; 380 | 381 | if (m < 0) { 382 | m = i; 383 | continue; 384 | } 385 | 386 | if (iface->addr6[m].preferred_lt >= (uint32_t)now && 387 | iface->addr6[i].preferred_lt < (uint32_t)now) 388 | continue; 389 | 390 | if (IN6_IS_ADDR_ULA(&iface->addr6[i].addr.in6)) { 391 | if (!IN6_IS_ADDR_ULA(&iface->addr6[m].addr.in6)) { 392 | m = i; 393 | continue; 394 | } 395 | } else if (IN6_IS_ADDR_ULA(&iface->addr6[m].addr.in6)) 396 | continue; 397 | 398 | if (iface->addr6[i].preferred_lt > iface->addr6[m].preferred_lt) 399 | m = i; 400 | } 401 | 402 | if (m >= 0) { 403 | *dns_addr6 = iface->addr6[m].addr.in6; 404 | return 0; 405 | } 406 | 407 | return odhcpd_get_interface_linklocal_addr(iface, dns_addr6); 408 | } 409 | 410 | struct interface* odhcpd_get_interface_by_index(int ifindex) 411 | { 412 | struct interface *iface; 413 | 414 | avl_for_each_element(&interfaces, iface, avl) { 415 | if (iface->ifindex == ifindex) 416 | return iface; 417 | } 418 | 419 | return NULL; 420 | } 421 | 422 | /* Convenience function to receive and do basic validation of packets */ 423 | static void odhcpd_receive_packets(struct uloop_fd *u, _o_unused unsigned int events) 424 | { 425 | struct odhcpd_event *e = container_of(u, struct odhcpd_event, uloop); 426 | 427 | uint8_t data_buf[8192], cmsg_buf[128]; 428 | union { 429 | struct sockaddr_in6 in6; 430 | struct sockaddr_in in; 431 | struct sockaddr_ll ll; 432 | struct sockaddr_nl nl; 433 | } addr; 434 | 435 | if (u->error) { 436 | int ret = -1; 437 | socklen_t ret_len = sizeof(ret); 438 | 439 | u->error = false; 440 | if (e->handle_error && getsockopt(u->fd, SOL_SOCKET, SO_ERROR, &ret, &ret_len) == 0) 441 | e->handle_error(e, ret); 442 | } 443 | 444 | if (e->recv_msgs) { 445 | e->recv_msgs(e); 446 | return; 447 | } 448 | 449 | while (true) { 450 | struct iovec iov = {data_buf, sizeof(data_buf)}; 451 | struct msghdr msg = { 452 | .msg_name = (void *) &addr, 453 | .msg_namelen = sizeof(addr), 454 | .msg_iov = &iov, 455 | .msg_iovlen = 1, 456 | .msg_control = cmsg_buf, 457 | .msg_controllen = sizeof(cmsg_buf), 458 | .msg_flags = 0 459 | }; 460 | 461 | ssize_t len = recvmsg(u->fd, &msg, MSG_DONTWAIT); 462 | if (len < 0) { 463 | if (errno == EAGAIN) 464 | break; 465 | else 466 | continue; 467 | } 468 | 469 | 470 | /* Extract destination interface */ 471 | int destiface = 0; 472 | int *hlim = NULL; 473 | void *dest = NULL; 474 | struct in6_pktinfo *pktinfo; 475 | struct in_pktinfo *pkt4info; 476 | for (struct cmsghdr *ch = CMSG_FIRSTHDR(&msg); ch != NULL; ch = CMSG_NXTHDR(&msg, ch)) { 477 | if (ch->cmsg_level == IPPROTO_IPV6 && 478 | ch->cmsg_type == IPV6_PKTINFO) { 479 | pktinfo = (struct in6_pktinfo*)CMSG_DATA(ch); 480 | destiface = pktinfo->ipi6_ifindex; 481 | dest = &pktinfo->ipi6_addr; 482 | } else if (ch->cmsg_level == IPPROTO_IP && 483 | ch->cmsg_type == IP_PKTINFO) { 484 | pkt4info = (struct in_pktinfo*)CMSG_DATA(ch); 485 | destiface = pkt4info->ipi_ifindex; 486 | dest = &pkt4info->ipi_addr; 487 | } else if (ch->cmsg_level == IPPROTO_IPV6 && 488 | ch->cmsg_type == IPV6_HOPLIMIT) { 489 | hlim = (int*)CMSG_DATA(ch); 490 | } 491 | } 492 | 493 | /* Check hoplimit if received */ 494 | if (hlim && *hlim != 255) 495 | continue; 496 | 497 | /* Detect interface for packet sockets */ 498 | if (addr.ll.sll_family == AF_PACKET) 499 | destiface = addr.ll.sll_ifindex; 500 | 501 | char ipbuf[INET6_ADDRSTRLEN] = "kernel"; 502 | if (addr.ll.sll_family == AF_PACKET && 503 | len >= (ssize_t)sizeof(struct ip6_hdr)) 504 | inet_ntop(AF_INET6, &data_buf[8], ipbuf, sizeof(ipbuf)); 505 | else if (addr.in6.sin6_family == AF_INET6) 506 | inet_ntop(AF_INET6, &addr.in6.sin6_addr, ipbuf, sizeof(ipbuf)); 507 | else if (addr.in.sin_family == AF_INET) 508 | inet_ntop(AF_INET, &addr.in.sin_addr, ipbuf, sizeof(ipbuf)); 509 | 510 | /* From netlink */ 511 | if (addr.nl.nl_family == AF_NETLINK) { 512 | debug("Received %zd Bytes from %s%%netlink", len, ipbuf); 513 | e->handle_dgram(&addr, data_buf, len, NULL, dest); 514 | return; 515 | } else if (destiface != 0) { 516 | struct interface *iface; 517 | 518 | avl_for_each_element(&interfaces, iface, avl) { 519 | if (iface->ifindex != destiface) 520 | continue; 521 | 522 | debug("Received %zd Bytes from %s%%%s@%s", len, 523 | ipbuf, iface->name, iface->ifname); 524 | 525 | e->handle_dgram(&addr, data_buf, len, iface, dest); 526 | } 527 | } 528 | 529 | 530 | } 531 | } 532 | 533 | /* Register events for the multiplexer */ 534 | int odhcpd_register(struct odhcpd_event *event) 535 | { 536 | event->uloop.cb = odhcpd_receive_packets; 537 | return uloop_fd_add(&event->uloop, ULOOP_READ | 538 | ((event->handle_error) ? ULOOP_ERROR_CB : 0)); 539 | } 540 | 541 | int odhcpd_deregister(struct odhcpd_event *event) 542 | { 543 | event->uloop.cb = NULL; 544 | return uloop_fd_delete(&event->uloop); 545 | } 546 | 547 | void odhcpd_process(struct odhcpd_event *event) 548 | { 549 | odhcpd_receive_packets(&event->uloop, 0); 550 | } 551 | 552 | void odhcpd_urandom(void *data, size_t len) 553 | { 554 | static bool warned_once = false; 555 | 556 | while (true) { 557 | ssize_t r; 558 | 559 | if (len == 0) 560 | return; 561 | 562 | r = getrandom(data, len, GRND_INSECURE); 563 | if (r < 0) { 564 | if (errno == EINTR) 565 | continue; 566 | 567 | if (!warned_once) { 568 | error("getrandom(): %m"); 569 | warned_once = true; 570 | } 571 | 572 | return; 573 | } 574 | 575 | len -= r; 576 | data = (uint8_t *)data + r; 577 | } 578 | } 579 | 580 | time_t odhcpd_time(void) 581 | { 582 | struct timespec ts; 583 | clock_gettime(CLOCK_MONOTONIC_COARSE, &ts); 584 | return ts.tv_sec; 585 | } 586 | 587 | 588 | static const char hexdigits[] = "0123456789abcdef"; 589 | static const int8_t hexvals[] = { 590 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2, -1, -1, -2, -1, -1, 591 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 592 | -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 593 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, 594 | -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, 595 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 596 | -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, 597 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 598 | }; 599 | 600 | ssize_t odhcpd_unhexlify(uint8_t *dst, size_t len, const char *src) 601 | { 602 | size_t c; 603 | for (c = 0; c < len && src[0] && src[1]; ++c) { 604 | int8_t x = (int8_t)*src++; 605 | int8_t y = (int8_t)*src++; 606 | if (x < 0 || (x = hexvals[x]) < 0 607 | || y < 0 || (y = hexvals[y]) < 0) 608 | return -1; 609 | dst[c] = x << 4 | y; 610 | while (((int8_t)*src) < 0 || 611 | (*src && hexvals[(uint8_t)*src] < 0)) 612 | src++; 613 | } 614 | 615 | return c; 616 | } 617 | 618 | 619 | void odhcpd_hexlify(char *dst, const uint8_t *src, size_t len) 620 | { 621 | for (size_t i = 0; i < len; ++i) { 622 | *dst++ = hexdigits[src[i] >> 4]; 623 | *dst++ = hexdigits[src[i] & 0x0f]; 624 | } 625 | *dst = 0; 626 | } 627 | 628 | const char *odhcpd_print_mac(const uint8_t *mac, const size_t len) 629 | { 630 | static char buf[32]; 631 | 632 | snprintf(buf, sizeof(buf), "%02x", mac[0]); 633 | for (size_t i = 1, j = 2; i < len && j < sizeof(buf); i++, j += 3) 634 | snprintf(buf + j, sizeof(buf) - j, ":%02x", mac[i]); 635 | 636 | return buf; 637 | } 638 | 639 | int odhcpd_bmemcmp(const void *av, const void *bv, size_t bits) 640 | { 641 | const uint8_t *a = av, *b = bv; 642 | size_t bytes = bits / 8; 643 | bits %= 8; 644 | 645 | int res = memcmp(a, b, bytes); 646 | if (res == 0 && bits > 0) 647 | res = (a[bytes] >> (8 - bits)) - (b[bytes] >> (8 - bits)); 648 | 649 | return res; 650 | } 651 | 652 | 653 | void odhcpd_bmemcpy(void *av, const void *bv, size_t bits) 654 | { 655 | uint8_t *a = av; 656 | const uint8_t *b = bv; 657 | 658 | size_t bytes = bits / 8; 659 | bits %= 8; 660 | memcpy(a, b, bytes); 661 | 662 | if (bits > 0) { 663 | uint8_t mask = (1 << (8 - bits)) - 1; 664 | a[bytes] = (a[bytes] & mask) | ((~mask) & b[bytes]); 665 | } 666 | } 667 | 668 | void odhcpd_enum_addr6(struct interface *iface, struct dhcpv6_lease *lease, 669 | time_t now, odhcpd_enum_addr6_cb_t func, void *arg) 670 | { 671 | struct odhcpd_ipaddr *addrs = iface->addr6; 672 | size_t m = get_preferred_addr(addrs, iface->addr6_len); 673 | 674 | for (size_t i = 0; i < iface->addr6_len; ++i) { 675 | struct in6_addr addr; 676 | uint32_t preferred_lt, valid_lt; 677 | int prefix = lease->length; 678 | 679 | if (!valid_addr(&addrs[i], now)) 680 | continue; 681 | 682 | /* Filter Out Prefixes */ 683 | if (ADDR_MATCH_PIO_FILTER(&addrs[i], iface)) { 684 | char addrbuf[INET6_ADDRSTRLEN]; 685 | info("Address %s filtered out on %s", 686 | inet_ntop(AF_INET6, &addrs[i].addr.in6, addrbuf, sizeof(addrbuf)), 687 | iface->name); 688 | continue; 689 | } 690 | 691 | if (lease->flags & OAF_DHCPV6_NA) { 692 | if (!ADDR_ENTRY_VALID_IA_ADDR(iface, i, m, addrs)) 693 | continue; 694 | 695 | addr = in6_from_prefix_and_iid(&addrs[i], lease->assigned_host_id); 696 | } else { 697 | if (!valid_prefix_length(lease, addrs[i].prefix_len)) 698 | continue; 699 | 700 | addr = addrs[i].addr.in6; 701 | addr.s6_addr32[1] |= htonl(lease->assigned_subnet_id); 702 | addr.s6_addr32[2] = addr.s6_addr32[3] = 0; 703 | } 704 | 705 | preferred_lt = addrs[i].preferred_lt; 706 | if (preferred_lt > (uint32_t)lease->preferred_until) 707 | preferred_lt = lease->preferred_until; 708 | 709 | if (preferred_lt > (uint32_t)lease->valid_until) 710 | preferred_lt = lease->valid_until; 711 | 712 | if (preferred_lt != UINT32_MAX) 713 | preferred_lt -= now; 714 | 715 | valid_lt = addrs[i].valid_lt; 716 | if (valid_lt > (uint32_t)lease->valid_until) 717 | valid_lt = lease->valid_until; 718 | 719 | if (valid_lt != UINT32_MAX) 720 | valid_lt -= now; 721 | 722 | func(lease, &addr, prefix, preferred_lt, valid_lt, arg); 723 | } 724 | } 725 | 726 | int odhcpd_parse_addr6_prefix(const char *str, struct in6_addr *addr, uint8_t *prefix) 727 | { 728 | size_t len; 729 | char *delim; 730 | 731 | *prefix = 0; 732 | if (!str) 733 | return -1; 734 | 735 | len = strlen(str); 736 | 737 | char buf[len + 1]; 738 | memcpy(buf, str, len); 739 | buf[len] = '\0'; 740 | 741 | delim = memchr(buf, '/', len); 742 | if (!delim) 743 | return -1; 744 | 745 | *(delim++) = '\0'; 746 | 747 | if (inet_pton(AF_INET6, buf, addr) != 1) 748 | return -1; 749 | 750 | if (sscanf(delim, "%" SCNu8, prefix) != 1 || *prefix > 128) { 751 | *prefix = 0; 752 | return -1; 753 | } 754 | 755 | return 0; 756 | } 757 | 758 | bool odhcpd_hostname_valid(const char *name) 759 | { 760 | const char *c, *label, *label_end; 761 | int label_sz = 0; 762 | 763 | for (c = name, label_sz = 0, label = name, label_end = name + strcspn(name, ".") - 1; 764 | *c && label_sz <= DNS_MAX_LABEL_LEN; c++) { 765 | if ((*c >= '0' && *c <= '9') || 766 | (*c >= 'A' && *c <= 'Z') || 767 | (*c >= 'a' && *c <= 'z')) { 768 | label_sz++; 769 | continue; 770 | } 771 | 772 | /* FIXME: underscore is not allowed in RFC 1035, RFC 1123? */ 773 | if ((*c == '_' || *c == '-') && c != label && c != label_end) { 774 | label_sz++; 775 | continue; 776 | } 777 | 778 | if (*c == '.') { 779 | if (*(c + 1)) { 780 | label = c + 1; 781 | label_end = label + strcspn(label, ".") - 1; 782 | label_sz = 0; 783 | } 784 | continue; 785 | } 786 | 787 | return false; 788 | } 789 | 790 | return (label_sz && label_sz <= DNS_MAX_LABEL_LEN ? true : false); 791 | } 792 | -------------------------------------------------------------------------------- /src/netlink.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2017 Hans Dedecker 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License v2 as published by 6 | * the Free Software Foundation. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | */ 14 | 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | #include "odhcpd.h" 31 | 32 | struct event_socket { 33 | struct odhcpd_event ev; 34 | struct nl_sock *sock; 35 | int sock_bufsize; 36 | }; 37 | 38 | static void handle_rtnl_event(struct odhcpd_event *ev); 39 | static int cb_rtnl_valid(struct nl_msg *msg, void *arg); 40 | static void catch_rtnl_err(struct odhcpd_event *e, int error); 41 | static struct nl_sock *create_socket(int protocol); 42 | 43 | static struct nl_sock *rtnl_socket = NULL; 44 | struct list_head netevent_handler_list = LIST_HEAD_INIT(netevent_handler_list); 45 | static struct event_socket rtnl_event = { 46 | .ev = { 47 | .uloop = {.fd = - 1, }, 48 | .handle_dgram = NULL, 49 | .handle_error = catch_rtnl_err, 50 | .recv_msgs = handle_rtnl_event, 51 | }, 52 | .sock = NULL, 53 | .sock_bufsize = 133120, 54 | }; 55 | 56 | int netlink_init(void) 57 | { 58 | rtnl_socket = create_socket(NETLINK_ROUTE); 59 | if (!rtnl_socket) { 60 | error("Unable to open nl socket: %m"); 61 | goto err; 62 | } 63 | 64 | rtnl_event.sock = create_socket(NETLINK_ROUTE); 65 | if (!rtnl_event.sock) { 66 | error("Unable to open nl event socket: %m"); 67 | goto err; 68 | } 69 | 70 | rtnl_event.ev.uloop.fd = nl_socket_get_fd(rtnl_event.sock); 71 | 72 | if (nl_socket_set_buffer_size(rtnl_event.sock, rtnl_event.sock_bufsize, 0)) 73 | goto err; 74 | 75 | nl_socket_disable_seq_check(rtnl_event.sock); 76 | 77 | nl_socket_modify_cb(rtnl_event.sock, NL_CB_VALID, NL_CB_CUSTOM, 78 | cb_rtnl_valid, NULL); 79 | 80 | /* Receive IPv4 address, IPv6 address, IPv6 routes and neighbor events */ 81 | if (nl_socket_add_memberships(rtnl_event.sock, RTNLGRP_IPV4_IFADDR, 82 | RTNLGRP_IPV6_IFADDR, RTNLGRP_IPV6_ROUTE, 83 | RTNLGRP_NEIGH, RTNLGRP_LINK, 0)) 84 | goto err; 85 | 86 | odhcpd_register(&rtnl_event.ev); 87 | 88 | return 0; 89 | 90 | err: 91 | if (rtnl_socket) { 92 | nl_socket_free(rtnl_socket); 93 | rtnl_socket = NULL; 94 | } 95 | 96 | if (rtnl_event.sock) { 97 | nl_socket_free(rtnl_event.sock); 98 | rtnl_event.sock = NULL; 99 | rtnl_event.ev.uloop.fd = -1; 100 | } 101 | 102 | return -1; 103 | } 104 | 105 | 106 | int netlink_add_netevent_handler(struct netevent_handler *handler) 107 | { 108 | if (!handler->cb) 109 | return -1; 110 | 111 | list_add(&handler->head, &netevent_handler_list); 112 | 113 | return 0; 114 | } 115 | 116 | static void call_netevent_handler_list(unsigned long event, struct netevent_handler_info *info) 117 | { 118 | struct netevent_handler *handler; 119 | 120 | list_for_each_entry(handler, &netevent_handler_list, head) 121 | handler->cb(event, info); 122 | } 123 | 124 | static void handle_rtnl_event(struct odhcpd_event *e) 125 | { 126 | struct event_socket *ev_sock = container_of(e, struct event_socket, ev); 127 | 128 | nl_recvmsgs_default(ev_sock->sock); 129 | } 130 | 131 | static void refresh_iface_addr4(int ifindex) 132 | { 133 | struct odhcpd_ipaddr *oaddrs4 = NULL; 134 | struct interface *iface; 135 | ssize_t oaddrs4_cnt = netlink_get_interface_addrs(ifindex, false, &oaddrs4); 136 | bool change = false; 137 | 138 | if (oaddrs4_cnt < 0) 139 | return; 140 | 141 | avl_for_each_element(&interfaces, iface, avl) { 142 | struct netevent_handler_info event_info; 143 | 144 | if (iface->ifindex != ifindex) 145 | continue; 146 | 147 | memset(&event_info, 0, sizeof(event_info)); 148 | event_info.iface = iface; 149 | event_info.addrs_old.addrs = iface->oaddrs4; 150 | event_info.addrs_old.len = iface->oaddrs4_cnt; 151 | 152 | if (!change) { 153 | change = oaddrs4_cnt != (ssize_t)iface->oaddrs4_cnt; 154 | for (ssize_t i = 0; !change && i < oaddrs4_cnt; i++) { 155 | if (oaddrs4[i].addr.in.s_addr != iface->oaddrs4[i].addr.in.s_addr) 156 | change = true; 157 | } 158 | } 159 | 160 | iface->oaddrs4 = oaddrs4; 161 | iface->oaddrs4_cnt = oaddrs4_cnt; 162 | 163 | if (change) 164 | call_netevent_handler_list(NETEV_ADDRLIST_CHANGE, &event_info); 165 | 166 | free(event_info.addrs_old.addrs); 167 | 168 | if (!oaddrs4_cnt) 169 | continue; 170 | 171 | oaddrs4 = malloc(oaddrs4_cnt * sizeof(*oaddrs4)); 172 | if (!oaddrs4) 173 | break; 174 | 175 | memcpy(oaddrs4, iface->oaddrs4, oaddrs4_cnt * sizeof(*oaddrs4)); 176 | } 177 | 178 | free(oaddrs4); 179 | } 180 | 181 | static void refresh_iface_addr6(int ifindex) 182 | { 183 | struct odhcpd_ipaddr *oaddrs6 = NULL; 184 | struct interface *iface; 185 | ssize_t oaddrs6_cnt = netlink_get_interface_addrs(ifindex, true, &oaddrs6); 186 | time_t now = odhcpd_time(); 187 | bool change = false; 188 | 189 | if (oaddrs6_cnt < 0) 190 | return; 191 | 192 | avl_for_each_element(&interfaces, iface, avl) { 193 | struct netevent_handler_info event_info; 194 | 195 | if (iface->ifindex != ifindex) 196 | continue; 197 | 198 | memset(&event_info, 0, sizeof(event_info)); 199 | event_info.iface = iface; 200 | event_info.addrs_old.addrs = iface->addr6; 201 | event_info.addrs_old.len = iface->addr6_len; 202 | 203 | if (!change) { 204 | change = oaddrs6_cnt != (ssize_t)iface->addr6_len; 205 | for (ssize_t i = 0; !change && i < oaddrs6_cnt; ++i) { 206 | if (!IN6_ARE_ADDR_EQUAL(&oaddrs6[i].addr.in6, &iface->addr6[i].addr.in6) || 207 | oaddrs6[i].prefix_len != iface->addr6[i].prefix_len || 208 | (oaddrs6[i].preferred_lt > (uint32_t)now) != (iface->addr6[i].preferred_lt > (uint32_t)now) || 209 | oaddrs6[i].valid_lt < iface->addr6[i].valid_lt || oaddrs6[i].preferred_lt < iface->addr6[i].preferred_lt) 210 | change = true; 211 | } 212 | } 213 | 214 | iface->addr6 = oaddrs6; 215 | iface->addr6_len = oaddrs6_cnt; 216 | 217 | if (change) 218 | call_netevent_handler_list(NETEV_ADDR6LIST_CHANGE, &event_info); 219 | 220 | free(event_info.addrs_old.addrs); 221 | 222 | if (!oaddrs6_cnt) 223 | continue; 224 | 225 | oaddrs6 = malloc(oaddrs6_cnt * sizeof(*oaddrs6)); 226 | if (!oaddrs6) 227 | break; 228 | 229 | memcpy(oaddrs6, iface->addr6, oaddrs6_cnt * sizeof(*oaddrs6)); 230 | } 231 | 232 | free(oaddrs6); 233 | } 234 | 235 | static int handle_rtm_link(struct nlmsghdr *hdr) 236 | { 237 | struct ifinfomsg *ifi = nlmsg_data(hdr); 238 | struct nlattr *nla[__IFLA_MAX]; 239 | struct interface *iface; 240 | struct netevent_handler_info event_info; 241 | const char *ifname; 242 | 243 | memset(&event_info, 0, sizeof(event_info)); 244 | 245 | if (!nlmsg_valid_hdr(hdr, sizeof(*ifi)) || ifi->ifi_family != AF_UNSPEC) 246 | return NL_SKIP; 247 | 248 | nlmsg_parse(hdr, sizeof(*ifi), nla, __IFLA_MAX - 1, NULL); 249 | if (!nla[IFLA_IFNAME]) 250 | return NL_SKIP; 251 | 252 | ifname = nla_get_string(nla[IFLA_IFNAME]); 253 | 254 | avl_for_each_element(&interfaces, iface, avl) { 255 | if (strcmp(iface->ifname, ifname)) 256 | continue; 257 | 258 | iface->ifflags = ifi->ifi_flags; 259 | 260 | /* store current MTU if available */ 261 | if (nla[IFLA_MTU]) { 262 | iface->if_mtu = nla_get_u32(nla[IFLA_MTU]); 263 | debug("Netlink setting interface '%s' if_mtu MTU to %d", 264 | iface->name, iface->if_mtu); 265 | } 266 | 267 | /* 268 | * Assume for link event of the same index, that link changed 269 | * and reload services to enable or disable them based on the 270 | * RUNNING state of the interface. 271 | */ 272 | if (iface->ifindex == ifi->ifi_index) { 273 | reload_services(iface); 274 | continue; 275 | } 276 | 277 | iface->ifindex = ifi->ifi_index; 278 | event_info.iface = iface; 279 | call_netevent_handler_list(NETEV_IFINDEX_CHANGE, &event_info); 280 | } 281 | 282 | return NL_OK; 283 | } 284 | 285 | static int handle_rtm_route(struct nlmsghdr *hdr, bool add) 286 | { 287 | struct rtmsg *rtm = nlmsg_data(hdr); 288 | struct nlattr *nla[__RTA_MAX]; 289 | struct interface *iface; 290 | struct netevent_handler_info event_info; 291 | int ifindex = 0; 292 | 293 | if (!nlmsg_valid_hdr(hdr, sizeof(*rtm)) || rtm->rtm_family != AF_INET6) 294 | return NL_SKIP; 295 | 296 | nlmsg_parse(hdr, sizeof(*rtm), nla, __RTA_MAX - 1, NULL); 297 | 298 | memset(&event_info, 0, sizeof(event_info)); 299 | event_info.rt.dst_len = rtm->rtm_dst_len; 300 | 301 | if (nla[RTA_DST]) 302 | nla_memcpy(&event_info.rt.dst, nla[RTA_DST], 303 | sizeof(event_info.rt.dst)); 304 | 305 | if (nla[RTA_OIF]) 306 | ifindex = nla_get_u32(nla[RTA_OIF]); 307 | 308 | if (nla[RTA_GATEWAY]) 309 | nla_memcpy(&event_info.rt.gateway, nla[RTA_GATEWAY], 310 | sizeof(event_info.rt.gateway)); 311 | 312 | avl_for_each_element(&interfaces, iface, avl) { 313 | if (ifindex && iface->ifindex != ifindex) 314 | continue; 315 | 316 | event_info.iface = ifindex ? iface : NULL; 317 | call_netevent_handler_list(add ? NETEV_ROUTE6_ADD : NETEV_ROUTE6_DEL, 318 | &event_info); 319 | } 320 | 321 | return NL_OK; 322 | } 323 | 324 | static int handle_rtm_addr(struct nlmsghdr *hdr, bool add) 325 | { 326 | struct ifaddrmsg *ifa = nlmsg_data(hdr); 327 | struct nlattr *nla[__IFA_MAX]; 328 | struct interface *iface; 329 | struct netevent_handler_info event_info; 330 | char buf[INET6_ADDRSTRLEN]; 331 | 332 | if (!nlmsg_valid_hdr(hdr, sizeof(*ifa)) || 333 | (ifa->ifa_family != AF_INET6 && 334 | ifa->ifa_family != AF_INET)) 335 | return NL_SKIP; 336 | 337 | memset(&event_info, 0, sizeof(event_info)); 338 | 339 | nlmsg_parse(hdr, sizeof(*ifa), nla, __IFA_MAX - 1, NULL); 340 | 341 | if (ifa->ifa_family == AF_INET6) { 342 | if (!nla[IFA_ADDRESS]) 343 | return NL_SKIP; 344 | 345 | nla_memcpy(&event_info.addr, nla[IFA_ADDRESS], sizeof(event_info.addr)); 346 | 347 | if (IN6_IS_ADDR_MULTICAST(&event_info.addr)) 348 | return NL_SKIP; 349 | 350 | inet_ntop(AF_INET6, &event_info.addr, buf, sizeof(buf)); 351 | 352 | avl_for_each_element(&interfaces, iface, avl) { 353 | if (iface->ifindex != (int)ifa->ifa_index) 354 | continue; 355 | 356 | if (add && IN6_IS_ADDR_LINKLOCAL(&event_info.addr)) { 357 | iface->have_link_local = true; 358 | return NL_SKIP; 359 | } 360 | 361 | debug("Netlink %s %s on %s", add ? "newaddr" : "deladdr", 362 | buf, iface->name); 363 | 364 | event_info.iface = iface; 365 | call_netevent_handler_list(add ? NETEV_ADDR6_ADD : NETEV_ADDR6_DEL, 366 | &event_info); 367 | } 368 | 369 | refresh_iface_addr6(ifa->ifa_index); 370 | } else { 371 | if (!nla[IFA_LOCAL]) 372 | return NL_SKIP; 373 | 374 | nla_memcpy(&event_info.addr, nla[IFA_LOCAL], sizeof(event_info.addr)); 375 | 376 | inet_ntop(AF_INET, &event_info.addr, buf, sizeof(buf)); 377 | 378 | avl_for_each_element(&interfaces, iface, avl) { 379 | if (iface->ifindex != (int)ifa->ifa_index) 380 | continue; 381 | 382 | debug("Netlink %s %s on %s", add ? "newaddr" : "deladdr", 383 | buf, iface->name); 384 | 385 | event_info.iface = iface; 386 | call_netevent_handler_list(add ? NETEV_ADDR_ADD : NETEV_ADDR_DEL, 387 | &event_info); 388 | } 389 | 390 | refresh_iface_addr4(ifa->ifa_index); 391 | } 392 | 393 | return NL_OK; 394 | } 395 | 396 | static int handle_rtm_neigh(struct nlmsghdr *hdr, bool add) 397 | { 398 | struct ndmsg *ndm = nlmsg_data(hdr); 399 | struct nlattr *nla[__NDA_MAX]; 400 | struct interface *iface; 401 | struct netevent_handler_info event_info; 402 | char buf[INET6_ADDRSTRLEN]; 403 | 404 | if (!nlmsg_valid_hdr(hdr, sizeof(*ndm)) || 405 | ndm->ndm_family != AF_INET6) 406 | return NL_SKIP; 407 | 408 | nlmsg_parse(hdr, sizeof(*ndm), nla, __NDA_MAX - 1, NULL); 409 | if (!nla[NDA_DST]) 410 | return NL_SKIP; 411 | 412 | memset(&event_info, 0, sizeof(event_info)); 413 | 414 | nla_memcpy(&event_info.neigh.dst, nla[NDA_DST], sizeof(event_info.neigh.dst)); 415 | 416 | if (IN6_IS_ADDR_LINKLOCAL(&event_info.neigh.dst) || 417 | IN6_IS_ADDR_MULTICAST(&event_info.neigh.dst)) 418 | return NL_SKIP; 419 | 420 | inet_ntop(AF_INET6, &event_info.neigh.dst, buf, sizeof(buf)); 421 | 422 | avl_for_each_element(&interfaces, iface, avl) { 423 | if (iface->ifindex != ndm->ndm_ifindex) 424 | continue; 425 | 426 | debug("Netlink %s %s on %s", add ? "newneigh" : "delneigh", 427 | buf, iface->name); 428 | 429 | event_info.iface = iface; 430 | event_info.neigh.state = ndm->ndm_state; 431 | event_info.neigh.flags = ndm->ndm_flags; 432 | 433 | call_netevent_handler_list(add ? NETEV_NEIGH6_ADD : NETEV_NEIGH6_DEL, 434 | &event_info); 435 | } 436 | 437 | return NL_OK; 438 | } 439 | 440 | /* Handler for neighbor cache entries from the kernel. This is our source 441 | * to learn and unlearn hosts on interfaces. */ 442 | static int cb_rtnl_valid(struct nl_msg *msg, _o_unused void *arg) 443 | { 444 | struct nlmsghdr *hdr = nlmsg_hdr(msg); 445 | int ret = NL_SKIP; 446 | bool add = false; 447 | 448 | switch (hdr->nlmsg_type) { 449 | case RTM_NEWLINK: 450 | ret = handle_rtm_link(hdr); 451 | break; 452 | 453 | case RTM_NEWROUTE: 454 | add = true; 455 | _o_fallthrough; 456 | case RTM_DELROUTE: 457 | ret = handle_rtm_route(hdr, add); 458 | break; 459 | 460 | case RTM_NEWADDR: 461 | add = true; 462 | _o_fallthrough; 463 | case RTM_DELADDR: 464 | ret = handle_rtm_addr(hdr, add); 465 | break; 466 | 467 | case RTM_NEWNEIGH: 468 | add = true; 469 | _o_fallthrough; 470 | case RTM_DELNEIGH: 471 | ret = handle_rtm_neigh(hdr, add); 472 | break; 473 | 474 | default: 475 | break; 476 | } 477 | 478 | return ret; 479 | } 480 | 481 | static void catch_rtnl_err(struct odhcpd_event *e, int error) 482 | { 483 | struct event_socket *ev_sock = container_of(e, struct event_socket, ev); 484 | 485 | if (error != ENOBUFS) 486 | goto err; 487 | 488 | /* Double netlink event buffer size */ 489 | ev_sock->sock_bufsize *= 2; 490 | 491 | if (nl_socket_set_buffer_size(ev_sock->sock, ev_sock->sock_bufsize, 0)) 492 | goto err; 493 | 494 | netlink_dump_addr_table(true); 495 | return; 496 | 497 | err: 498 | odhcpd_deregister(e); 499 | } 500 | 501 | static struct nl_sock *create_socket(int protocol) 502 | { 503 | struct nl_sock *nl_sock; 504 | 505 | nl_sock = nl_socket_alloc(); 506 | if (!nl_sock) 507 | goto err; 508 | 509 | if (nl_connect(nl_sock, protocol) < 0) 510 | goto err; 511 | 512 | return nl_sock; 513 | 514 | err: 515 | if (nl_sock) 516 | nl_socket_free(nl_sock); 517 | 518 | return NULL; 519 | } 520 | 521 | 522 | struct addr_info { 523 | int ifindex; 524 | int af; 525 | struct odhcpd_ipaddr **oaddrs; 526 | bool pending; 527 | ssize_t ret; 528 | }; 529 | 530 | 531 | static int cb_addr_valid(struct nl_msg *msg, void *arg) 532 | { 533 | struct addr_info *ctxt = (struct addr_info *)arg; 534 | struct odhcpd_ipaddr *oaddrs; 535 | struct nlmsghdr *hdr = nlmsg_hdr(msg); 536 | struct ifaddrmsg *ifa; 537 | struct nlattr *nla[__IFA_MAX], *nla_addr = NULL; 538 | 539 | if (hdr->nlmsg_type != RTM_NEWADDR) 540 | return NL_SKIP; 541 | 542 | ifa = NLMSG_DATA(hdr); 543 | if (ifa->ifa_scope != RT_SCOPE_UNIVERSE || 544 | (ctxt->af != ifa->ifa_family) || 545 | (ctxt->ifindex && ifa->ifa_index != (unsigned)ctxt->ifindex)) 546 | return NL_SKIP; 547 | 548 | nlmsg_parse(hdr, sizeof(*ifa), nla, __IFA_MAX - 1, NULL); 549 | 550 | switch (ifa->ifa_family) { 551 | case AF_INET6: 552 | if (nla[IFA_ADDRESS]) 553 | nla_addr = nla[IFA_ADDRESS]; 554 | break; 555 | 556 | case AF_INET: 557 | if (nla[IFA_LOCAL]) 558 | nla_addr = nla[IFA_LOCAL]; 559 | break; 560 | 561 | default: 562 | break; 563 | } 564 | if (!nla_addr) 565 | return NL_SKIP; 566 | 567 | oaddrs = realloc(*(ctxt->oaddrs), sizeof(*oaddrs) * (ctxt->ret + 1)); 568 | if (!oaddrs) 569 | return NL_SKIP; 570 | 571 | memset(&oaddrs[ctxt->ret], 0, sizeof(oaddrs[ctxt->ret])); 572 | oaddrs[ctxt->ret].prefix_len = ifa->ifa_prefixlen; 573 | 574 | if (ifa->ifa_family == AF_INET) 575 | oaddrs[ctxt->ret].netmask = htonl(~((1U << (32 - ifa->ifa_prefixlen)) - 1)); 576 | 577 | nla_memcpy(&oaddrs[ctxt->ret].addr, nla_addr, sizeof(oaddrs[ctxt->ret].addr)); 578 | 579 | if (nla[IFA_BROADCAST]) 580 | nla_memcpy(&oaddrs[ctxt->ret].broadcast, nla[IFA_BROADCAST], 581 | sizeof(oaddrs[ctxt->ret].broadcast)); 582 | 583 | if (nla[IFA_CACHEINFO]) { 584 | struct ifa_cacheinfo *ifc = nla_data(nla[IFA_CACHEINFO]); 585 | 586 | oaddrs[ctxt->ret].preferred_lt = ifc->ifa_prefered; 587 | oaddrs[ctxt->ret].valid_lt = ifc->ifa_valid; 588 | } 589 | 590 | if (ifa->ifa_flags & IFA_F_DEPRECATED) 591 | oaddrs[ctxt->ret].preferred_lt = 0; 592 | 593 | if (ifa->ifa_family == AF_INET6 && 594 | ifa->ifa_flags & IFA_F_TENTATIVE) 595 | oaddrs[ctxt->ret].tentative = true; 596 | 597 | ctxt->ret++; 598 | *(ctxt->oaddrs) = oaddrs; 599 | 600 | return NL_OK; 601 | } 602 | 603 | 604 | static int cb_addr_finish(_o_unused struct nl_msg *msg, void *arg) 605 | { 606 | struct addr_info *ctxt = (struct addr_info *)arg; 607 | 608 | ctxt->pending = false; 609 | 610 | return NL_STOP; 611 | } 612 | 613 | 614 | static int cb_addr_error(_o_unused struct sockaddr_nl *nla, struct nlmsgerr *err, 615 | void *arg) 616 | { 617 | struct addr_info *ctxt = (struct addr_info *)arg; 618 | 619 | ctxt->pending = false; 620 | ctxt->ret = err->error; 621 | 622 | return NL_STOP; 623 | } 624 | 625 | 626 | static int prefix_cmp(const void *va, const void *vb) 627 | { 628 | const struct odhcpd_ipaddr *a = va, *b = vb; 629 | 630 | if (a->prefix_len != b->prefix_len) 631 | return a->prefix_len < b->prefix_len ? 1 : -1; 632 | 633 | if (ntohl(a->addr.in.s_addr) == ntohl(b->addr.in.s_addr)) 634 | return 0; 635 | 636 | return ntohl(a->addr.in.s_addr) < ntohl(b->addr.in.s_addr) ? 1 : -1; 637 | } 638 | 639 | 640 | /* compare IPv6 prefixes */ 641 | static int prefix6_cmp(const void *va, const void *vb) 642 | { 643 | const struct odhcpd_ipaddr *a = va, *b = vb; 644 | uint32_t a_pref_lt = IN6_IS_ADDR_ULA(&a->addr.in6) ? 1 : a->preferred_lt; 645 | uint32_t b_pref_lt = IN6_IS_ADDR_ULA(&b->addr.in6) ? 1 : b->preferred_lt; 646 | return (a_pref_lt < b_pref_lt) ? 1 : (a_pref_lt > b_pref_lt) ? -1 : 0; 647 | } 648 | 649 | 650 | /* Detect all IPv[46]-addresses currently assigned to the given interface */ 651 | ssize_t netlink_get_interface_addrs(int ifindex, bool v6, struct odhcpd_ipaddr **oaddrs) 652 | { 653 | struct nl_msg *msg; 654 | struct ifaddrmsg ifa = { 655 | .ifa_family = v6? AF_INET6: AF_INET, 656 | .ifa_prefixlen = 0, 657 | .ifa_flags = 0, 658 | .ifa_scope = 0, 659 | .ifa_index = ifindex, 660 | }; 661 | struct nl_cb *cb = nl_cb_alloc(NL_CB_DEFAULT); 662 | struct addr_info ctxt = { 663 | .ifindex = ifindex, 664 | .af = v6? AF_INET6: AF_INET, 665 | .oaddrs = oaddrs, 666 | .ret = 0, 667 | .pending = true, 668 | }; 669 | 670 | if (!cb) { 671 | ctxt.ret = -1; 672 | goto out; 673 | } 674 | 675 | msg = nlmsg_alloc_simple(RTM_GETADDR, NLM_F_REQUEST | NLM_F_DUMP); 676 | 677 | if (!msg) { 678 | ctxt.ret = - 1; 679 | goto out; 680 | } 681 | 682 | nlmsg_append(msg, &ifa, sizeof(ifa), 0); 683 | 684 | nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, cb_addr_valid, &ctxt); 685 | nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, cb_addr_finish, &ctxt); 686 | nl_cb_err(cb, NL_CB_CUSTOM, cb_addr_error, &ctxt); 687 | 688 | ctxt.ret = nl_send_auto_complete(rtnl_socket, msg); 689 | if (ctxt.ret < 0) 690 | goto free; 691 | 692 | ctxt.ret = 0; 693 | while (ctxt.pending) 694 | nl_recvmsgs(rtnl_socket, cb); 695 | 696 | if (ctxt.ret <= 0) 697 | goto free; 698 | 699 | time_t now = odhcpd_time(); 700 | struct odhcpd_ipaddr *oaddr = *oaddrs; 701 | 702 | qsort(oaddr, ctxt.ret, sizeof(*oaddr), v6 ? prefix6_cmp : prefix_cmp); 703 | 704 | for (ssize_t i = 0; i < ctxt.ret; ++i) { 705 | if (oaddr[i].preferred_lt < UINT32_MAX - now) 706 | oaddr[i].preferred_lt += now; 707 | 708 | if (oaddr[i].valid_lt < UINT32_MAX - now) 709 | oaddr[i].valid_lt += now; 710 | } 711 | 712 | free: 713 | nlmsg_free(msg); 714 | out: 715 | nl_cb_put(cb); 716 | 717 | return ctxt.ret; 718 | } 719 | 720 | 721 | static int cb_linklocal_valid(struct nl_msg *msg, void *arg) 722 | { 723 | struct addr_info *ctxt = (struct addr_info *)arg; 724 | struct odhcpd_ipaddr *oaddrs; 725 | struct nlmsghdr *hdr = nlmsg_hdr(msg); 726 | struct ifaddrmsg *ifa; 727 | struct nlattr *nla[__IFA_MAX], *nla_addr = NULL; 728 | struct in6_addr addr; 729 | 730 | if (hdr->nlmsg_type != RTM_NEWADDR) 731 | return NL_SKIP; 732 | 733 | ifa = NLMSG_DATA(hdr); 734 | if (ifa->ifa_scope != RT_SCOPE_LINK || 735 | (ctxt->af != ifa->ifa_family) || 736 | (ctxt->ifindex && ifa->ifa_index != (unsigned)ctxt->ifindex)) 737 | return NL_SKIP; 738 | 739 | nlmsg_parse(hdr, sizeof(*ifa), nla, __IFA_MAX - 1, NULL); 740 | 741 | switch (ifa->ifa_family) { 742 | case AF_INET6: 743 | if (nla[IFA_ADDRESS]) 744 | nla_addr = nla[IFA_ADDRESS]; 745 | break; 746 | 747 | default: 748 | break; 749 | } 750 | if (!nla_addr) 751 | return NL_SKIP; 752 | 753 | nla_memcpy(&addr, nla_addr, sizeof(addr)); 754 | 755 | if (!IN6_IS_ADDR_LINKLOCAL(&addr)) 756 | return NL_SKIP; 757 | 758 | oaddrs = realloc(*(ctxt->oaddrs), sizeof(*oaddrs) * (ctxt->ret + 1)); 759 | if (!oaddrs) 760 | return NL_SKIP; 761 | 762 | memset(&oaddrs[ctxt->ret], 0, sizeof(oaddrs[ctxt->ret])); 763 | memcpy(&oaddrs[ctxt->ret].addr, &addr, sizeof(oaddrs[ctxt->ret].addr)); 764 | 765 | if (ifa->ifa_flags & IFA_F_TENTATIVE) 766 | oaddrs[ctxt->ret].tentative = true; 767 | 768 | ctxt->ret++; 769 | *(ctxt->oaddrs) = oaddrs; 770 | 771 | return NL_OK; 772 | } 773 | 774 | 775 | static int cb_linklocal_finish(_o_unused struct nl_msg *msg, void *arg) 776 | { 777 | struct addr_info *ctxt = (struct addr_info *)arg; 778 | 779 | ctxt->pending = false; 780 | 781 | return NL_STOP; 782 | } 783 | 784 | 785 | static int cb_linklocal_error(_o_unused struct sockaddr_nl *nla, struct nlmsgerr *err, 786 | void *arg) 787 | { 788 | struct addr_info *ctxt = (struct addr_info *)arg; 789 | 790 | ctxt->pending = false; 791 | ctxt->ret = err->error; 792 | 793 | return NL_STOP; 794 | } 795 | 796 | 797 | /* Detect link-local IPv6-addresses currently assigned to the given interface */ 798 | ssize_t netlink_get_interface_linklocal(int ifindex, struct odhcpd_ipaddr **oaddrs) 799 | { 800 | struct nl_msg *msg; 801 | struct ifaddrmsg ifa = { 802 | .ifa_family = AF_INET6, 803 | .ifa_prefixlen = 0, 804 | .ifa_flags = 0, 805 | .ifa_scope = 0, 806 | .ifa_index = ifindex, 807 | }; 808 | struct nl_cb *cb = nl_cb_alloc(NL_CB_DEFAULT); 809 | struct addr_info ctxt = { 810 | .ifindex = ifindex, 811 | .af = AF_INET6, 812 | .oaddrs = oaddrs, 813 | .ret = 0, 814 | .pending = true, 815 | }; 816 | 817 | if (!cb) { 818 | ctxt.ret = -1; 819 | goto out; 820 | } 821 | 822 | msg = nlmsg_alloc_simple(RTM_GETADDR, NLM_F_REQUEST | NLM_F_DUMP); 823 | 824 | if (!msg) { 825 | ctxt.ret = - 1; 826 | goto out; 827 | } 828 | 829 | nlmsg_append(msg, &ifa, sizeof(ifa), 0); 830 | 831 | nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, cb_linklocal_valid, &ctxt); 832 | nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, cb_linklocal_finish, &ctxt); 833 | nl_cb_err(cb, NL_CB_CUSTOM, cb_linklocal_error, &ctxt); 834 | 835 | ctxt.ret = nl_send_auto_complete(rtnl_socket, msg); 836 | if (ctxt.ret < 0) 837 | goto free; 838 | 839 | ctxt.ret = 0; 840 | while (ctxt.pending) 841 | nl_recvmsgs(rtnl_socket, cb); 842 | 843 | if (ctxt.ret <= 0) 844 | goto free; 845 | 846 | free: 847 | nlmsg_free(msg); 848 | out: 849 | nl_cb_put(cb); 850 | 851 | return ctxt.ret; 852 | } 853 | 854 | 855 | struct neigh_info { 856 | int ifindex; 857 | bool pending; 858 | const struct in6_addr *addr; 859 | int ret; 860 | }; 861 | 862 | 863 | static int cb_proxy_neigh_valid(struct nl_msg *msg, void *arg) 864 | { 865 | struct neigh_info *ctxt = (struct neigh_info *)arg; 866 | struct nlmsghdr *hdr = nlmsg_hdr(msg); 867 | struct ndmsg *ndm; 868 | struct nlattr *nla_dst; 869 | 870 | if (hdr->nlmsg_type != RTM_NEWNEIGH) 871 | return NL_SKIP; 872 | 873 | ndm = NLMSG_DATA(hdr); 874 | if (ndm->ndm_family != AF_INET6 || 875 | (ctxt->ifindex && ndm->ndm_ifindex != ctxt->ifindex)) 876 | return NL_SKIP; 877 | 878 | if (!(ndm->ndm_flags & NTF_PROXY)) 879 | return NL_SKIP; 880 | 881 | nla_dst = nlmsg_find_attr(hdr, sizeof(*ndm), NDA_DST); 882 | if (!nla_dst) 883 | return NL_SKIP; 884 | 885 | if (nla_memcmp(nla_dst,ctxt->addr, 16) == 0) 886 | ctxt->ret = 1; 887 | 888 | return NL_OK; 889 | } 890 | 891 | 892 | static int cb_proxy_neigh_finish(_o_unused struct nl_msg *msg, void *arg) 893 | { 894 | struct neigh_info *ctxt = (struct neigh_info *)arg; 895 | 896 | ctxt->pending = false; 897 | 898 | return NL_STOP; 899 | } 900 | 901 | 902 | static int cb_proxy_neigh_error(_o_unused struct sockaddr_nl *nla, struct nlmsgerr *err, 903 | void *arg) 904 | { 905 | struct neigh_info *ctxt = (struct neigh_info *)arg; 906 | 907 | ctxt->pending = false; 908 | ctxt->ret = err->error; 909 | 910 | return NL_STOP; 911 | } 912 | 913 | /* Detect an IPV6-address proxy neighbor for the given interface */ 914 | int netlink_get_interface_proxy_neigh(int ifindex, const struct in6_addr *addr) 915 | { 916 | struct nl_msg *msg; 917 | struct ndmsg ndm = { 918 | .ndm_family = AF_INET6, 919 | .ndm_flags = NTF_PROXY, 920 | .ndm_ifindex = ifindex, 921 | }; 922 | struct nl_cb *cb = nl_cb_alloc(NL_CB_DEFAULT); 923 | struct neigh_info ctxt = { 924 | .ifindex = ifindex, 925 | .addr = addr, 926 | .ret = 0, 927 | .pending = true, 928 | }; 929 | 930 | if (!cb) { 931 | ctxt.ret = -1; 932 | goto out; 933 | } 934 | 935 | msg = nlmsg_alloc_simple(RTM_GETNEIGH, NLM_F_REQUEST | NLM_F_MATCH); 936 | 937 | if (!msg) { 938 | ctxt.ret = -1; 939 | goto out; 940 | } 941 | 942 | nlmsg_append(msg, &ndm, sizeof(ndm), 0); 943 | nla_put(msg, NDA_DST, sizeof(*addr), addr); 944 | 945 | nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, cb_proxy_neigh_valid, &ctxt); 946 | nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, cb_proxy_neigh_finish, &ctxt); 947 | nl_cb_err(cb, NL_CB_CUSTOM, cb_proxy_neigh_error, &ctxt); 948 | 949 | ctxt.ret = nl_send_auto_complete(rtnl_socket, msg); 950 | if (ctxt.ret < 0) 951 | goto free; 952 | 953 | while (ctxt.pending) 954 | nl_recvmsgs(rtnl_socket, cb); 955 | 956 | free: 957 | nlmsg_free(msg); 958 | out: 959 | nl_cb_put(cb); 960 | 961 | return ctxt.ret; 962 | } 963 | 964 | 965 | int netlink_setup_route(const struct in6_addr *addr, const int prefixlen, 966 | const int ifindex, const struct in6_addr *gw, 967 | const uint32_t metric, const bool add) 968 | { 969 | struct nl_msg *msg; 970 | struct rtmsg rtm = { 971 | .rtm_family = AF_INET6, 972 | .rtm_dst_len = prefixlen, 973 | .rtm_src_len = 0, 974 | .rtm_table = RT_TABLE_MAIN, 975 | .rtm_protocol = (add ? RTPROT_STATIC : RTPROT_UNSPEC), 976 | .rtm_scope = (add ? (gw ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK) : RT_SCOPE_NOWHERE), 977 | .rtm_type = (add ? RTN_UNICAST : RTN_UNSPEC), 978 | }; 979 | int ret = 0; 980 | 981 | msg = nlmsg_alloc_simple(add ? RTM_NEWROUTE : RTM_DELROUTE, 982 | add ? NLM_F_CREATE | NLM_F_REPLACE : 0); 983 | if (!msg) 984 | return -1; 985 | 986 | nlmsg_append(msg, &rtm, sizeof(rtm), 0); 987 | 988 | nla_put(msg, RTA_DST, sizeof(*addr), addr); 989 | nla_put_u32(msg, RTA_OIF, ifindex); 990 | nla_put_u32(msg, RTA_PRIORITY, metric); 991 | 992 | if (gw) 993 | nla_put(msg, RTA_GATEWAY, sizeof(*gw), gw); 994 | 995 | ret = nl_send_auto_complete(rtnl_socket, msg); 996 | nlmsg_free(msg); 997 | 998 | if (ret < 0) 999 | return ret; 1000 | 1001 | return nl_wait_for_ack(rtnl_socket); 1002 | } 1003 | 1004 | 1005 | int netlink_setup_proxy_neigh(const struct in6_addr *addr, 1006 | const int ifindex, const bool add) 1007 | { 1008 | struct nl_msg *msg; 1009 | struct ndmsg ndm = { 1010 | .ndm_family = AF_INET6, 1011 | .ndm_flags = NTF_PROXY, 1012 | .ndm_ifindex = ifindex, 1013 | }; 1014 | int ret = 0, flags = NLM_F_REQUEST; 1015 | 1016 | if (add) 1017 | flags |= NLM_F_REPLACE | NLM_F_CREATE; 1018 | 1019 | msg = nlmsg_alloc_simple(add ? RTM_NEWNEIGH : RTM_DELNEIGH, flags); 1020 | if (!msg) 1021 | return -1; 1022 | 1023 | nlmsg_append(msg, &ndm, sizeof(ndm), 0); 1024 | 1025 | nla_put(msg, NDA_DST, sizeof(*addr), addr); 1026 | 1027 | ret = nl_send_auto_complete(rtnl_socket, msg); 1028 | nlmsg_free(msg); 1029 | 1030 | if (ret < 0) 1031 | return ret; 1032 | 1033 | return nl_wait_for_ack(rtnl_socket); 1034 | } 1035 | 1036 | 1037 | int netlink_setup_addr(struct odhcpd_ipaddr *oaddr, 1038 | const int ifindex, const bool v6, const bool add) 1039 | { 1040 | struct nl_msg *msg; 1041 | struct ifaddrmsg ifa = { 1042 | .ifa_family = v6 ? AF_INET6 : AF_INET, 1043 | .ifa_prefixlen = oaddr->prefix_len, 1044 | .ifa_flags = 0, 1045 | .ifa_scope = 0, 1046 | .ifa_index = ifindex, 1047 | }; 1048 | int ret = 0, flags = NLM_F_REQUEST; 1049 | 1050 | if (add) 1051 | flags |= NLM_F_REPLACE | NLM_F_CREATE; 1052 | 1053 | msg = nlmsg_alloc_simple(add ? RTM_NEWADDR : RTM_DELADDR, 0); 1054 | if (!msg) 1055 | return -1; 1056 | 1057 | nlmsg_append(msg, &ifa, sizeof(ifa), flags); 1058 | nla_put(msg, IFA_LOCAL, v6 ? 16 : 4, &oaddr->addr); 1059 | if (v6) { 1060 | struct ifa_cacheinfo cinfo = { 1061 | .ifa_prefered = 0xffffffffU, 1062 | .ifa_valid = 0xffffffffU, 1063 | .cstamp = 0, 1064 | .tstamp = 0, 1065 | }; 1066 | time_t now = odhcpd_time(); 1067 | 1068 | if (oaddr->preferred_lt) { 1069 | int64_t preferred_lt = oaddr->preferred_lt - now; 1070 | if (preferred_lt < 0) 1071 | preferred_lt = 0; 1072 | else if (preferred_lt > UINT32_MAX) 1073 | preferred_lt = UINT32_MAX; 1074 | 1075 | cinfo.ifa_prefered = preferred_lt; 1076 | } 1077 | 1078 | if (oaddr->valid_lt) { 1079 | int64_t valid_lt = oaddr->valid_lt - now; 1080 | if (valid_lt <= 0) { 1081 | nlmsg_free(msg); 1082 | return -1; 1083 | } 1084 | else if (valid_lt > UINT32_MAX) 1085 | valid_lt = UINT32_MAX; 1086 | 1087 | cinfo.ifa_valid = valid_lt; 1088 | } 1089 | 1090 | nla_put(msg, IFA_CACHEINFO, sizeof(cinfo), &cinfo); 1091 | 1092 | nla_put_u32(msg, IFA_FLAGS, IFA_F_NOPREFIXROUTE); 1093 | } else { 1094 | if (oaddr->broadcast.s_addr) 1095 | nla_put_u32(msg, IFA_BROADCAST, oaddr->broadcast.s_addr); 1096 | } 1097 | 1098 | ret = nl_send_auto_complete(rtnl_socket, msg); 1099 | nlmsg_free(msg); 1100 | 1101 | if (ret < 0) 1102 | return ret; 1103 | 1104 | return nl_wait_for_ack(rtnl_socket); 1105 | } 1106 | 1107 | void netlink_dump_neigh_table(const bool proxy) 1108 | { 1109 | struct nl_msg *msg; 1110 | struct ndmsg ndm = { 1111 | .ndm_family = AF_INET6, 1112 | .ndm_flags = proxy ? NTF_PROXY : 0, 1113 | }; 1114 | 1115 | msg = nlmsg_alloc_simple(RTM_GETNEIGH, NLM_F_REQUEST | NLM_F_DUMP); 1116 | if (!msg) 1117 | return; 1118 | 1119 | nlmsg_append(msg, &ndm, sizeof(ndm), 0); 1120 | 1121 | nl_send_auto_complete(rtnl_event.sock, msg); 1122 | 1123 | nlmsg_free(msg); 1124 | } 1125 | 1126 | void netlink_dump_addr_table(const bool v6) 1127 | { 1128 | struct nl_msg *msg; 1129 | struct ifaddrmsg ifa = { 1130 | .ifa_family = v6 ? AF_INET6 : AF_INET, 1131 | }; 1132 | 1133 | msg = nlmsg_alloc_simple(RTM_GETADDR, NLM_F_REQUEST | NLM_F_DUMP); 1134 | if (!msg) 1135 | return; 1136 | 1137 | nlmsg_append(msg, &ifa, sizeof(ifa), 0); 1138 | 1139 | nl_send_auto_complete(rtnl_event.sock, msg); 1140 | 1141 | nlmsg_free(msg); 1142 | } 1143 | -------------------------------------------------------------------------------- /src/dhcpv6.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2012-2013 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 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | 23 | #include "odhcpd.h" 24 | #include "dhcpv6.h" 25 | #include "dhcpv6-pxe.h" 26 | #ifdef DHCPV4_SUPPORT 27 | #include "dhcpv4.h" 28 | #endif 29 | 30 | static void relay_client_request(struct sockaddr_in6 *source, 31 | const void *data, size_t len, struct interface *iface); 32 | static void relay_server_response(uint8_t *data, size_t len); 33 | 34 | static void handle_dhcpv6(void *addr, void *data, size_t len, 35 | struct interface *iface, void *dest); 36 | static void handle_client_request(void *addr, void *data, size_t len, 37 | struct interface *iface, void *dest_addr); 38 | 39 | 40 | /* Create socket and register events */ 41 | int dhcpv6_init(void) 42 | { 43 | return dhcpv6_ia_init(); 44 | } 45 | 46 | int dhcpv6_setup_interface(struct interface *iface, bool enable) 47 | { 48 | int ret = 0; 49 | 50 | enable = enable && (iface->dhcpv6 != MODE_DISABLED); 51 | 52 | if (iface->dhcpv6_event.uloop.fd >= 0) { 53 | uloop_fd_delete(&iface->dhcpv6_event.uloop); 54 | close(iface->dhcpv6_event.uloop.fd); 55 | iface->dhcpv6_event.uloop.fd = -1; 56 | } 57 | 58 | /* Configure multicast settings */ 59 | if (enable) { 60 | struct sockaddr_in6 bind_addr = {AF_INET6, htons(DHCPV6_SERVER_PORT), 61 | 0, IN6ADDR_ANY_INIT, 0}; 62 | struct ipv6_mreq mreq; 63 | int val = 1; 64 | 65 | iface->dhcpv6_event.uloop.fd = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP); 66 | if (iface->dhcpv6_event.uloop.fd < 0) { 67 | error("socket(AF_INET6): %m"); 68 | ret = -1; 69 | goto out; 70 | } 71 | 72 | /* Basic IPv6 configuration */ 73 | if (setsockopt(iface->dhcpv6_event.uloop.fd, SOL_SOCKET, SO_BINDTODEVICE, 74 | iface->ifname, strlen(iface->ifname)) < 0) { 75 | error("setsockopt(SO_BINDTODEVICE): %m"); 76 | ret = -1; 77 | goto out; 78 | } 79 | 80 | if (setsockopt(iface->dhcpv6_event.uloop.fd, IPPROTO_IPV6, IPV6_V6ONLY, 81 | &val, sizeof(val)) < 0) { 82 | error("setsockopt(IPV6_V6ONLY): %m"); 83 | ret = -1; 84 | goto out; 85 | } 86 | 87 | if (setsockopt(iface->dhcpv6_event.uloop.fd, SOL_SOCKET, SO_REUSEADDR, 88 | &val, sizeof(val)) < 0) { 89 | error("setsockopt(SO_REUSEADDR): %m"); 90 | ret = -1; 91 | goto out; 92 | } 93 | 94 | if (setsockopt(iface->dhcpv6_event.uloop.fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, 95 | &val, sizeof(val)) < 0) { 96 | error("setsockopt(IPV6_RECVPKTINFO): %m"); 97 | ret = -1; 98 | goto out; 99 | } 100 | 101 | val = DHCPV6_HOP_COUNT_LIMIT; 102 | if (setsockopt(iface->dhcpv6_event.uloop.fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, 103 | &val, sizeof(val)) < 0) { 104 | error("setsockopt(IPV6_MULTICAST_HOPS): %m"); 105 | ret = -1; 106 | goto out; 107 | } 108 | 109 | val = 0; 110 | if (setsockopt(iface->dhcpv6_event.uloop.fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, 111 | &val, sizeof(val)) < 0) { 112 | error("setsockopt(IPV6_MULTICAST_LOOP): %m"); 113 | ret = -1; 114 | goto out; 115 | } 116 | 117 | if (bind(iface->dhcpv6_event.uloop.fd, (struct sockaddr*)&bind_addr, 118 | sizeof(bind_addr)) < 0) { 119 | error("bind(): %m"); 120 | ret = -1; 121 | goto out; 122 | } 123 | 124 | memset(&mreq, 0, sizeof(mreq)); 125 | inet_pton(AF_INET6, ALL_DHCPV6_RELAYS, &mreq.ipv6mr_multiaddr); 126 | mreq.ipv6mr_interface = iface->ifindex; 127 | 128 | if (setsockopt(iface->dhcpv6_event.uloop.fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, 129 | &mreq, sizeof(mreq)) < 0) { 130 | error("setsockopt(IPV6_ADD_MEMBERSHIP): %m"); 131 | ret = -1; 132 | goto out; 133 | } 134 | 135 | if (iface->dhcpv6 == MODE_SERVER) { 136 | memset(&mreq, 0, sizeof(mreq)); 137 | inet_pton(AF_INET6, ALL_DHCPV6_SERVERS, &mreq.ipv6mr_multiaddr); 138 | mreq.ipv6mr_interface = iface->ifindex; 139 | 140 | if (setsockopt(iface->dhcpv6_event.uloop.fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, 141 | &mreq, sizeof(mreq)) < 0) { 142 | error("setsockopt(IPV6_ADD_MEMBERSHIP): %m"); 143 | ret = -1; 144 | goto out; 145 | } 146 | } 147 | 148 | iface->dhcpv6_event.handle_dgram = handle_dhcpv6; 149 | odhcpd_register(&iface->dhcpv6_event); 150 | } 151 | 152 | ret = dhcpv6_ia_setup_interface(iface, enable); 153 | 154 | out: 155 | if (ret < 0 && iface->dhcpv6_event.uloop.fd >= 0) { 156 | close(iface->dhcpv6_event.uloop.fd); 157 | iface->dhcpv6_event.uloop.fd = -1; 158 | } 159 | 160 | return ret; 161 | } 162 | 163 | enum { 164 | IOV_NESTED = 0, 165 | IOV_DEST, 166 | IOV_CLIENTID, 167 | IOV_MAXRT, 168 | #define IOV_STAT IOV_MAXRT 169 | IOV_RAPID_COMMIT, 170 | IOV_DNS, 171 | IOV_DNS_ADDR, 172 | IOV_SEARCH, 173 | IOV_SEARCH_DOMAIN, 174 | IOV_PDBUF, 175 | #define IOV_REFRESH IOV_PDBUF 176 | IOV_DHCPV6_RAW, 177 | IOV_NTP, 178 | IOV_NTP_ADDR, 179 | IOV_SNTP, 180 | IOV_SNTP_ADDR, 181 | IOV_RELAY_MSG, 182 | IOV_DHCPV4O6_SERVER, 183 | IOV_DNR, 184 | IOV_BOOTFILE_URL, 185 | IOV_POSIX_TZ, 186 | IOV_POSIX_TZ_STR, 187 | IOV_TZDB_TZ, 188 | IOV_TZDB_TZ_STR, 189 | IOV_CAPT_PORTAL, 190 | IOV_CAPT_PORTAL_URI, 191 | IOV_TOTAL 192 | }; 193 | 194 | static void handle_nested_message(uint8_t *data, size_t len, 195 | struct dhcpv6_client_header **c_hdr, uint8_t **opts, 196 | uint8_t **end, struct iovec iov[IOV_TOTAL]) 197 | { 198 | struct dhcpv6_relay_header *r_hdr = (struct dhcpv6_relay_header *)data; 199 | uint16_t otype, olen; 200 | uint8_t *odata; 201 | 202 | if (iov[IOV_NESTED].iov_base == NULL) { 203 | iov[IOV_NESTED].iov_base = data; 204 | iov[IOV_NESTED].iov_len = len; 205 | } 206 | 207 | if (len < sizeof(struct dhcpv6_client_header)) 208 | return; 209 | 210 | if (r_hdr->msg_type != DHCPV6_MSG_RELAY_FORW) { 211 | iov[IOV_NESTED].iov_len = data - (uint8_t *)iov[IOV_NESTED].iov_base; 212 | *c_hdr = (void *)data; 213 | *opts = (uint8_t *)&(*c_hdr)[1]; 214 | *end = data + len; 215 | return; 216 | } 217 | 218 | dhcpv6_for_each_option(r_hdr->options, data + len, otype, olen, odata) { 219 | if (otype == DHCPV6_OPT_RELAY_MSG) { 220 | iov[IOV_RELAY_MSG].iov_base = odata + olen; 221 | iov[IOV_RELAY_MSG].iov_len = (((uint8_t *)iov[IOV_NESTED].iov_base) + 222 | iov[IOV_NESTED].iov_len) - (odata + olen); 223 | handle_nested_message(odata, olen, c_hdr, opts, end, iov); 224 | return; 225 | } 226 | } 227 | } 228 | 229 | 230 | static void update_nested_message(uint8_t *data, size_t len, ssize_t pdiff) 231 | { 232 | struct dhcpv6_relay_header *hdr = (struct dhcpv6_relay_header*)data; 233 | if (hdr->msg_type != DHCPV6_MSG_RELAY_FORW) 234 | return; 235 | 236 | hdr->msg_type = DHCPV6_MSG_RELAY_REPL; 237 | 238 | uint16_t otype, olen; 239 | uint8_t *odata; 240 | dhcpv6_for_each_option(hdr->options, data + len, otype, olen, odata) { 241 | if (otype == DHCPV6_OPT_RELAY_MSG) { 242 | olen += pdiff; 243 | odata[-2] = (olen >> 8) & 0xff; 244 | odata[-1] = olen & 0xff; 245 | update_nested_message(odata, olen - pdiff, pdiff); 246 | return; 247 | } 248 | } 249 | } 250 | 251 | #ifdef DHCPV4_SUPPORT 252 | 253 | struct dhcpv4_msg_data { 254 | uint8_t *msg; 255 | size_t maxsize; 256 | ssize_t len; 257 | }; 258 | 259 | static ssize_t dhcpv6_4o6_send_reply(struct iovec *iov, size_t iov_len, 260 | _o_unused struct sockaddr *dest, 261 | _o_unused socklen_t dest_len, 262 | void *opaque) 263 | { 264 | struct dhcpv4_msg_data *reply = opaque; 265 | size_t len = 0; 266 | 267 | for (size_t i = 0; i < iov_len; i++) 268 | len += iov[i].iov_len; 269 | 270 | if (len > reply->maxsize) { 271 | error("4o6: reply too large, %zu > %zu", len, reply->maxsize); 272 | reply->len = -1; 273 | return -1; 274 | } 275 | 276 | for (size_t i = 0, off = 0; i < iov_len; i++) { 277 | memcpy(reply->msg + off, iov[i].iov_base, iov[i].iov_len); 278 | off += iov[i].iov_len; 279 | } 280 | reply->len = len; 281 | 282 | return len; 283 | } 284 | 285 | static ssize_t dhcpv6_4o6_query(uint8_t *buf, size_t buflen, 286 | struct interface *iface, 287 | const struct sockaddr_in6 *addr, 288 | const void *data, const uint8_t *end) 289 | { 290 | const struct dhcpv6_client_header *hdr = data; 291 | uint16_t otype, olen, msgv4_len = 0; 292 | uint8_t *msgv4_data = NULL; 293 | uint8_t *start = (uint8_t *)&hdr[1], *odata; 294 | struct sockaddr_in addrv4; 295 | struct dhcpv4_msg_data reply = { .msg = buf, .maxsize = buflen, .len = -1 }; 296 | 297 | dhcpv6_for_each_option(start, end, otype, olen, odata) { 298 | if (otype == DHCPV6_OPT_DHCPV4_MSG) { 299 | msgv4_data = odata; 300 | msgv4_len = olen; 301 | } 302 | } 303 | 304 | if (!msgv4_data || msgv4_len == 0) { 305 | error("4o6: missing DHCPv4 message option (%d)", DHCPV6_OPT_DHCPV4_MSG); 306 | return -1; 307 | } 308 | 309 | // Dummy IPv4 address 310 | memset(&addrv4, 0, sizeof(addrv4)); 311 | addrv4.sin_family = AF_INET; 312 | addrv4.sin_addr.s_addr = INADDR_ANY; 313 | addrv4.sin_port = htons(DHCPV4_CLIENT_PORT); 314 | 315 | dhcpv4_handle_msg(&addrv4, msgv4_data, msgv4_len, 316 | iface, NULL, dhcpv6_4o6_send_reply, &reply); 317 | 318 | return reply.len; 319 | } 320 | #endif /* DHCPV4_SUPPORT */ 321 | 322 | /* Simple DHCPv6-server for information requests */ 323 | static void handle_client_request(void *addr, void *data, size_t len, 324 | struct interface *iface, void *dest_addr) 325 | { 326 | struct dhcpv6_client_header *hdr = data; 327 | uint8_t *opts = (uint8_t *)&hdr[1], *opts_end = (uint8_t *)data + len; 328 | bool o_rapid_commit = false; 329 | 330 | if (len < sizeof(*hdr)) 331 | return; 332 | 333 | switch (hdr->msg_type) { 334 | /* Valid message types for clients */ 335 | case DHCPV6_MSG_SOLICIT: 336 | case DHCPV6_MSG_REQUEST: 337 | case DHCPV6_MSG_CONFIRM: 338 | case DHCPV6_MSG_RENEW: 339 | case DHCPV6_MSG_REBIND: 340 | case DHCPV6_MSG_RELEASE: 341 | case DHCPV6_MSG_DECLINE: 342 | case DHCPV6_MSG_INFORMATION_REQUEST: 343 | case DHCPV6_MSG_RELAY_FORW: 344 | #ifdef DHCPV4_SUPPORT 345 | /* if we include DHCPV4 support, handle this message type */ 346 | case DHCPV6_MSG_DHCPV4_QUERY: 347 | #endif 348 | break; 349 | /* Invalid message types for clients i.e. server messages */ 350 | case DHCPV6_MSG_ADVERTISE: 351 | case DHCPV6_MSG_REPLY: 352 | case DHCPV6_MSG_RECONFIGURE: 353 | case DHCPV6_MSG_RELAY_REPL: 354 | #ifndef DHCPV4_SUPPORT 355 | /* if we omit DHCPV4 support, ignore this client message type */ 356 | case DHCPV6_MSG_DHCPV4_QUERY: 357 | #endif 358 | case DHCPV6_MSG_DHCPV4_RESPONSE: 359 | default: 360 | return; 361 | } 362 | 363 | debug("Got a DHCPv6-request on %s", iface->name); 364 | 365 | /* Construct reply message */ 366 | struct _o_packed { 367 | uint8_t msg_type; 368 | uint8_t tr_id[3]; 369 | uint16_t serverid_type; 370 | uint16_t serverid_length; 371 | uint8_t serverid_buf[DUID_MAX_LEN]; 372 | } dest = { 373 | .msg_type = DHCPV6_MSG_REPLY, 374 | .serverid_type = htons(DHCPV6_OPT_SERVERID), 375 | .serverid_length = 0, 376 | .serverid_buf = { 0 }, 377 | }; 378 | 379 | if (config.default_duid_len > 0) { 380 | memcpy(dest.serverid_buf, config.default_duid, config.default_duid_len); 381 | dest.serverid_length = htons(config.default_duid_len); 382 | } else { 383 | uint16_t duid_ll_hdr[] = { htons(DUID_TYPE_LL), htons(ARPHRD_ETHER) }; 384 | memcpy(dest.serverid_buf, duid_ll_hdr, sizeof(duid_ll_hdr)); 385 | odhcpd_get_mac(iface, &dest.serverid_buf[sizeof(duid_ll_hdr)]); 386 | dest.serverid_length = htons(sizeof(duid_ll_hdr) + ETH_ALEN); 387 | } 388 | 389 | struct _o_packed { 390 | uint16_t type; 391 | uint16_t len; 392 | uint8_t buf[DUID_MAX_LEN]; 393 | } clientid = { 394 | .type = htons(DHCPV6_OPT_CLIENTID), 395 | .len = 0, 396 | .buf = { 0 }, 397 | }; 398 | 399 | struct _o_packed { 400 | uint16_t type; 401 | uint16_t len; 402 | uint32_t value; 403 | } maxrt = {htons(DHCPV6_OPT_SOL_MAX_RT), htons(sizeof(maxrt) - 4), 404 | htonl(60)}; 405 | 406 | struct _o_packed { 407 | uint16_t type; 408 | uint16_t len; 409 | } rapid_commit = {htons(DHCPV6_OPT_RAPID_COMMIT), 0}; 410 | 411 | struct _o_packed { 412 | uint16_t type; 413 | uint16_t len; 414 | uint16_t value; 415 | } stat = {htons(DHCPV6_OPT_STATUS), htons(sizeof(stat) - 4), 416 | htons(DHCPV6_STATUS_USEMULTICAST)}; 417 | 418 | struct _o_packed { 419 | uint16_t type; 420 | uint16_t len; 421 | uint32_t value; 422 | } refresh = {htons(DHCPV6_OPT_INFO_REFRESH), htons(sizeof(uint32_t)), 423 | htonl(600)}; 424 | 425 | struct in6_addr *dns_addrs6 = NULL, dns_addr6; 426 | size_t dns_addrs6_cnt = 0; 427 | 428 | if (iface->dns_addrs6_cnt > 0) { 429 | dns_addrs6 = iface->dns_addrs6; 430 | dns_addrs6_cnt = iface->dns_addrs6_cnt; 431 | } else if (!odhcpd_get_interface_dns_addr6(iface, &dns_addr6)) { 432 | dns_addrs6 = &dns_addr6; 433 | dns_addrs6_cnt = 1; 434 | } 435 | 436 | struct { 437 | uint16_t type; 438 | uint16_t len; 439 | } dns_hdr = { htons(DHCPV6_OPT_DNS_SERVERS), htons(dns_addrs6_cnt * sizeof(*dns_addrs6)) }; 440 | 441 | /* SNTP */ 442 | struct in6_addr *sntp_addr_ptr = iface->dhcpv6_sntp; 443 | size_t sntp_cnt = 0; 444 | struct { 445 | uint16_t type; 446 | uint16_t len; 447 | } dhcpv6_sntp; 448 | 449 | /* RFC 4833 - Timezones */ 450 | bool posix_want = false; 451 | uint8_t *posix_ptr = sys_conf.posix_tz; 452 | uint16_t posix_len = sys_conf.posix_tz_len; 453 | /* RFC 4833 - OPTION_NEW_POSIX_TIMEZONE (41) 454 | * e.g. EST5EDT4,M3.2.0/02:00,M11.1.0/02:00 455 | * Variable-length opaque tz_string blob. 456 | */ 457 | struct { 458 | uint16_t type; 459 | uint16_t len; 460 | } posix_tz; 461 | 462 | bool tzdb_want = false; 463 | uint8_t *tzdb_ptr = sys_conf.tzdb_tz; 464 | uint16_t tzdb_len = sys_conf.tzdb_tz_len; 465 | /* RFC 4833 - OPTION_NEW_TZDB_TIMEZONE (42) 466 | * e.g. Europe/Zurich 467 | * Variable-length opaque tz_name blob. 468 | */ 469 | struct { 470 | uint16_t type; 471 | uint16_t len; 472 | } tzdb_tz; 473 | 474 | /* NTP */ 475 | uint8_t *ntp_ptr = iface->dhcpv6_ntp; 476 | uint16_t ntp_len = iface->dhcpv6_ntp_len; 477 | size_t ntp_cnt = 0; 478 | struct { 479 | uint16_t type; 480 | uint16_t len; 481 | } ntp; 482 | 483 | /* DNR */ 484 | struct dhcpv6_dnr { 485 | uint16_t type; 486 | uint16_t len; 487 | uint16_t priority; 488 | uint16_t adn_len; 489 | uint8_t body[]; 490 | }; 491 | struct dhcpv6_dnr *dnrs = NULL; 492 | size_t dnrs_len = 0; 493 | 494 | /* RFC8910 Captive-Portal URI */ 495 | uint8_t *capt_portal_ptr = (uint8_t *)iface->captive_portal_uri; 496 | size_t capt_portal_len = iface->captive_portal_uri_len; 497 | struct { 498 | uint16_t type; 499 | uint16_t len; 500 | } capt_portal; 501 | 502 | /* RFC8910 §2: 503 | * DHCP servers MAY send the Captive Portal option without any explicit request 504 | * If it is configured, send it. 505 | */ 506 | capt_portal.type = htons(DHCPV6_OPT_CAPTIVE_PORTAL); 507 | capt_portal.len = htons(capt_portal_len); 508 | 509 | uint16_t otype, olen; 510 | uint8_t *odata; 511 | uint16_t *reqopts = NULL; 512 | size_t reqopts_cnt = 0; 513 | 514 | /* FIXME: this should be merged with the second loop further down */ 515 | dhcpv6_for_each_option(opts, opts_end, otype, olen, odata) { 516 | /* Requested options, array of uint16_t, RFC 8415 §21.7 */ 517 | if (otype == DHCPV6_OPT_ORO) { 518 | reqopts_cnt = olen / sizeof(uint16_t); 519 | reqopts = (uint16_t *)odata; 520 | break; 521 | } 522 | } 523 | 524 | /* Requested options */ 525 | for (size_t i = 0; i < reqopts_cnt; i++) { 526 | uint16_t opt = ntohs(reqopts[i]); 527 | 528 | switch (opt) { 529 | case DHCPV6_OPT_SNTP_SERVERS: 530 | sntp_cnt = iface->dhcpv6_sntp_cnt; 531 | dhcpv6_sntp.type = htons(DHCPV6_OPT_SNTP_SERVERS); 532 | dhcpv6_sntp.len = htons(sntp_cnt * sizeof(*sntp_addr_ptr)); 533 | break; 534 | 535 | case DHCPV6_OPT_NTP_SERVERS: 536 | ntp_cnt = iface->dhcpv6_ntp_cnt; 537 | ntp.type = htons(DHCPV6_OPT_NTP_SERVERS); 538 | ntp.len = htons(ntp_len); 539 | break; 540 | 541 | case DHCPV6_OPT_NEW_POSIX_TIMEZONE: 542 | posix_want = true; 543 | posix_tz.type = htons(DHCPV6_OPT_NEW_POSIX_TIMEZONE); 544 | posix_tz.len = htons(posix_len); 545 | break; 546 | 547 | case DHCPV6_OPT_NEW_TZDB_TIMEZONE: 548 | tzdb_want = true; 549 | tzdb_tz.type = htons(DHCPV6_OPT_NEW_TZDB_TIMEZONE); 550 | tzdb_tz.len = htons(tzdb_len); 551 | break; 552 | 553 | case DHCPV6_OPT_DNR: 554 | for (size_t j = 0; j < iface->dnr_cnt; j++) { 555 | struct dnr_options *dnr = &iface->dnr[j]; 556 | 557 | if (dnr->addr6_cnt == 0 && dnr->addr4_cnt > 0) 558 | continue; 559 | 560 | dnrs_len += sizeof(struct dhcpv6_dnr); 561 | dnrs_len += dnr->adn_len; 562 | 563 | if (dnr->addr6_cnt > 0 || dnr->svc_len > 0) { 564 | dnrs_len += sizeof(uint16_t); 565 | dnrs_len += dnr->addr6_cnt * sizeof(*dnr->addr6); 566 | dnrs_len += dnr->svc_len; 567 | } 568 | } 569 | 570 | dnrs = alloca(dnrs_len); 571 | uint8_t *pos = (uint8_t *)dnrs; 572 | 573 | for (size_t j = 0; j < iface->dnr_cnt; j++) { 574 | struct dnr_options *dnr = &iface->dnr[j]; 575 | struct dhcpv6_dnr *d6dnr = (struct dhcpv6_dnr *)pos; 576 | uint16_t d6dnr_type_be = htons(DHCPV6_OPT_DNR); 577 | uint16_t d6dnr_len = 2 * sizeof(uint16_t) + dnr->adn_len; 578 | uint16_t d6dnr_len_be; 579 | uint16_t d6dnr_priority_be = htons(dnr->priority); 580 | uint16_t d6dnr_adn_len_be = htons(dnr->adn_len); 581 | 582 | if (dnr->addr6_cnt == 0 && dnr->addr4_cnt > 0) 583 | continue; 584 | 585 | /* memcpy as the struct is unaligned */ 586 | memcpy(&d6dnr->type, &d6dnr_type_be, sizeof(d6dnr_type_be)); 587 | memcpy(&d6dnr->priority, &d6dnr_priority_be, sizeof(d6dnr_priority_be)); 588 | memcpy(&d6dnr->adn_len, &d6dnr_adn_len_be, sizeof(d6dnr_adn_len_be)); 589 | 590 | pos = d6dnr->body; 591 | memcpy(pos, dnr->adn, dnr->adn_len); 592 | pos += dnr->adn_len; 593 | 594 | if (dnr->addr6_cnt > 0 || dnr->svc_len > 0) { 595 | uint16_t addr6_len = dnr->addr6_cnt * sizeof(*dnr->addr6); 596 | uint16_t addr6_len_be = htons(addr6_len); 597 | 598 | memcpy(pos, &addr6_len_be, sizeof(addr6_len_be)); 599 | pos += sizeof(addr6_len_be); 600 | memcpy(pos, dnr->addr6, addr6_len); 601 | pos += addr6_len; 602 | memcpy(pos, dnr->svc, dnr->svc_len); 603 | pos += dnr->svc_len; 604 | 605 | d6dnr_len += sizeof(addr6_len_be) + addr6_len + dnr->svc_len; 606 | } 607 | 608 | d6dnr_len_be = htons(d6dnr_len); 609 | memcpy(&d6dnr->len, &d6dnr_len_be, sizeof(d6dnr_len_be)); 610 | } 611 | break; 612 | } 613 | } 614 | 615 | /* DNS Search options */ 616 | struct { 617 | uint16_t type; 618 | uint16_t len; 619 | } dns_search_hdr = { htons(DHCPV6_OPT_DNS_DOMAIN), htons(iface->dns_search_len) }; 620 | 621 | 622 | struct _o_packed dhcpv4o6_server { 623 | uint16_t type; 624 | uint16_t len; 625 | struct in6_addr addr; 626 | } dhcpv4o6_server = {htons(DHCPV6_OPT_4O6_SERVER), htons(sizeof(struct in6_addr)), 627 | IN6ADDR_ANY_INIT}; 628 | 629 | uint8_t pdbuf[512]; 630 | struct iovec iov[IOV_TOTAL] = { 631 | [IOV_NESTED] = {NULL, 0}, 632 | [IOV_DEST] = {&dest, offsetof(typeof(dest), serverid_buf) + ntohs(dest.serverid_length) }, 633 | [IOV_CLIENTID] = {&clientid, 0}, 634 | [IOV_MAXRT] = {&maxrt, sizeof(maxrt)}, 635 | [IOV_RAPID_COMMIT] = {&rapid_commit, 0}, 636 | [IOV_DNS] = { &dns_hdr, (dns_addrs6_cnt) ? sizeof(dns_hdr) : 0}, 637 | [IOV_DNS_ADDR] = { dns_addrs6, dns_addrs6_cnt * sizeof(*dns_addrs6) }, 638 | [IOV_SEARCH] = { &dns_search_hdr, iface->dns_search_len ? sizeof(dns_search_hdr) : 0 }, 639 | [IOV_SEARCH_DOMAIN] = { iface->dns_search, iface->dns_search_len }, 640 | [IOV_PDBUF] = {pdbuf, 0}, 641 | [IOV_DHCPV6_RAW] = {iface->dhcpv6_raw, iface->dhcpv6_raw_len}, 642 | [IOV_NTP] = {&ntp, (ntp_cnt) ? sizeof(ntp) : 0}, 643 | [IOV_NTP_ADDR] = {ntp_ptr, (ntp_cnt) ? ntp_len : 0}, 644 | [IOV_SNTP] = {&dhcpv6_sntp, (sntp_cnt) ? sizeof(dhcpv6_sntp) : 0}, 645 | [IOV_SNTP_ADDR] = {sntp_addr_ptr, sntp_cnt * sizeof(*sntp_addr_ptr)}, 646 | [IOV_POSIX_TZ] = {&posix_tz, (posix_want) ? sizeof(posix_tz) : 0}, 647 | [IOV_POSIX_TZ_STR] = {posix_ptr, (posix_want) ? posix_len : 0 }, 648 | [IOV_TZDB_TZ] = {&tzdb_tz, (tzdb_want) ? sizeof(tzdb_tz) : 0}, 649 | [IOV_TZDB_TZ_STR] = {tzdb_ptr, (tzdb_want) ? tzdb_len : 0 }, 650 | [IOV_DNR] = {dnrs, dnrs_len}, 651 | [IOV_RELAY_MSG] = {NULL, 0}, 652 | [IOV_DHCPV4O6_SERVER] = {&dhcpv4o6_server, 0}, 653 | [IOV_CAPT_PORTAL] = {&capt_portal, capt_portal_len ? sizeof(capt_portal) : 0}, 654 | [IOV_CAPT_PORTAL_URI] = {capt_portal_ptr, capt_portal_len ? capt_portal_len : 0}, 655 | [IOV_BOOTFILE_URL] = {NULL, 0} 656 | }; 657 | 658 | if (hdr->msg_type == DHCPV6_MSG_RELAY_FORW) 659 | handle_nested_message(data, len, &hdr, &opts, &opts_end, iov); 660 | 661 | if (!IN6_IS_ADDR_MULTICAST((struct in6_addr *)dest_addr) && iov[IOV_NESTED].iov_len == 0 && 662 | (hdr->msg_type == DHCPV6_MSG_SOLICIT || hdr->msg_type == DHCPV6_MSG_CONFIRM || 663 | hdr->msg_type == DHCPV6_MSG_REBIND || hdr->msg_type == DHCPV6_MSG_INFORMATION_REQUEST)) 664 | return; 665 | 666 | memcpy(dest.tr_id, hdr->transaction_id, sizeof(dest.tr_id)); 667 | 668 | /* Go through options and find what we need */ 669 | dhcpv6_for_each_option(opts, opts_end, otype, olen, odata) { 670 | if (otype == DHCPV6_OPT_CLIENTID && olen <= DUID_MAX_LEN) { 671 | clientid.len = htons(olen); 672 | memcpy(clientid.buf, odata, olen); 673 | iov[IOV_CLIENTID].iov_len = offsetof(typeof(clientid), buf) + olen; 674 | } else if (otype == DHCPV6_OPT_SERVERID) { 675 | if (olen != ntohs(dest.serverid_length) || 676 | memcmp(odata, &dest.serverid_buf, olen)) 677 | return; /* Not for us */ 678 | } else if (otype == DHCPV6_OPT_RAPID_COMMIT && hdr->msg_type == DHCPV6_MSG_SOLICIT) { 679 | iov[IOV_RAPID_COMMIT].iov_len = sizeof(rapid_commit); 680 | o_rapid_commit = true; 681 | } else if (otype == DHCPV6_OPT_ORO) { 682 | for (int i=0; i < olen/2; i++) { 683 | uint16_t option = ntohs(((uint16_t *)odata)[i]); 684 | 685 | switch (option) { 686 | #ifdef DHCPV4_SUPPORT 687 | case DHCPV6_OPT_4O6_SERVER: 688 | if (iface->dhcpv4) { 689 | /* According to RFC 7341, 7.2. DHCP 4o6 Server Address Option Format: 690 | * This option may also carry no IPv6 addresses, which instructs the 691 | * client to use the All_DHCP_Relay_Agents_and_Servers multicast address 692 | * as the destination address. 693 | * 694 | * The ISC dhclient logs a missing IPv6 address as an error but seems to 695 | * work anyway: 696 | * dhcp4-o-dhcp6-server: expecting at least 16 bytes; got 0 697 | * 698 | * Include the All_DHCP_Relay_Agents_and_Servers multicast address 699 | * to make it explicit which address to use. */ 700 | struct dhcpv4o6_server *server = iov[IOV_DHCPV4O6_SERVER].iov_base; 701 | 702 | inet_pton(AF_INET6, ALL_DHCPV6_RELAYS, &server->addr); 703 | 704 | iov[IOV_DHCPV4O6_SERVER].iov_len = sizeof(dhcpv4o6_server); 705 | } 706 | break; 707 | #endif /* DHCPV4_SUPPORT */ 708 | default: 709 | break; 710 | } 711 | } 712 | } else if (otype == DHCPV6_OPT_CLIENT_ARCH) { 713 | uint16_t arch_code = ntohs(((uint16_t*)odata)[0]); 714 | ipv6_pxe_serve_boot_url(arch_code, &iov[IOV_BOOTFILE_URL]); 715 | } 716 | } 717 | 718 | if (dest.serverid_length == clientid.len && 719 | !memcmp(clientid.buf, dest.serverid_buf, dest.serverid_length)) { 720 | /* Bail if we are in a network loop where we talk with ourself */ 721 | return; 722 | } 723 | 724 | if (!IN6_IS_ADDR_MULTICAST((struct in6_addr *)dest_addr) && iov[IOV_NESTED].iov_len == 0 && 725 | (hdr->msg_type == DHCPV6_MSG_REQUEST || hdr->msg_type == DHCPV6_MSG_RENEW || 726 | hdr->msg_type == DHCPV6_MSG_RELEASE || hdr->msg_type == DHCPV6_MSG_DECLINE)) { 727 | iov[IOV_STAT].iov_base = &stat; 728 | iov[IOV_STAT].iov_len = sizeof(stat); 729 | 730 | for (ssize_t i = IOV_STAT + 1; i < IOV_TOTAL; ++i) 731 | iov[i].iov_len = 0; 732 | 733 | odhcpd_send(iface->dhcpv6_event.uloop.fd, addr, iov, ARRAY_SIZE(iov), iface); 734 | return; 735 | } 736 | 737 | if (hdr->msg_type == DHCPV6_MSG_SOLICIT && !o_rapid_commit) { 738 | dest.msg_type = DHCPV6_MSG_ADVERTISE; 739 | } else if (hdr->msg_type == DHCPV6_MSG_INFORMATION_REQUEST) { 740 | iov[IOV_REFRESH].iov_base = &refresh; 741 | iov[IOV_REFRESH].iov_len = sizeof(refresh); 742 | 743 | /* Return inf max rt option in reply to information request */ 744 | maxrt.type = htons(DHCPV6_OPT_INF_MAX_RT); 745 | } 746 | 747 | #ifdef DHCPV4_SUPPORT 748 | if (hdr->msg_type == DHCPV6_MSG_DHCPV4_QUERY) { 749 | struct _o_packed dhcpv4_msg_data { 750 | uint16_t type; 751 | uint16_t len; 752 | uint8_t msg[1]; 753 | } *msg_opt = (struct dhcpv4_msg_data*)pdbuf; 754 | ssize_t msglen; 755 | 756 | memset(pdbuf, 0, sizeof(pdbuf)); 757 | 758 | msglen = dhcpv6_4o6_query(msg_opt->msg, sizeof(pdbuf) - sizeof(*msg_opt) + 1, 759 | iface, addr, (const void *)hdr, opts_end); 760 | if (msglen <= 0) { 761 | error("4o6: query failed"); 762 | return; 763 | } 764 | 765 | msg_opt->type = htons(DHCPV6_OPT_DHCPV4_MSG); 766 | msg_opt->len = htons(msglen); 767 | iov[IOV_PDBUF].iov_len = sizeof(*msg_opt) - 1 + msglen; 768 | dest.msg_type = DHCPV6_MSG_DHCPV4_RESPONSE; 769 | } else 770 | #endif /* DHCPV4_SUPPORT */ 771 | 772 | if (hdr->msg_type != DHCPV6_MSG_INFORMATION_REQUEST) { 773 | ssize_t ialen = dhcpv6_ia_handle_IAs(pdbuf, sizeof(pdbuf), iface, addr, (const void *)hdr, opts_end); 774 | 775 | iov[IOV_PDBUF].iov_len = ialen; 776 | if (ialen < 0 || 777 | (ialen == 0 && (hdr->msg_type == DHCPV6_MSG_REBIND || hdr->msg_type == DHCPV6_MSG_CONFIRM))) 778 | return; 779 | } 780 | 781 | if (iov[IOV_NESTED].iov_len > 0) /* Update length */ 782 | update_nested_message(data, len, iov[IOV_DEST].iov_len + iov[IOV_MAXRT].iov_len + 783 | iov[IOV_RAPID_COMMIT].iov_len + iov[IOV_DNS].iov_len + 784 | iov[IOV_DNS_ADDR].iov_len + iov[IOV_SEARCH].iov_len + 785 | iov[IOV_SEARCH_DOMAIN].iov_len + iov[IOV_PDBUF].iov_len + 786 | iov[IOV_DHCPV4O6_SERVER].iov_len + 787 | iov[IOV_DHCPV6_RAW].iov_len + 788 | iov[IOV_NTP].iov_len + iov[IOV_NTP_ADDR].iov_len + 789 | iov[IOV_SNTP].iov_len + iov[IOV_SNTP_ADDR].iov_len + 790 | iov[IOV_POSIX_TZ].iov_len + iov[IOV_POSIX_TZ_STR].iov_len + 791 | iov[IOV_TZDB_TZ].iov_len + iov[IOV_TZDB_TZ_STR].iov_len + 792 | iov[IOV_CAPT_PORTAL].iov_len + iov[IOV_CAPT_PORTAL_URI].iov_len + 793 | iov[IOV_DNR].iov_len + iov[IOV_BOOTFILE_URL].iov_len - 794 | (4 + opts_end - opts)); 795 | 796 | debug("Sending a DHCPv6-%s on %s", iov[IOV_NESTED].iov_len ? "relay-reply" : "reply", iface->name); 797 | 798 | odhcpd_send(iface->dhcpv6_event.uloop.fd, addr, iov, ARRAY_SIZE(iov), iface); 799 | } 800 | 801 | 802 | /* Central DHCPv6-relay handler */ 803 | static void handle_dhcpv6(void *addr, void *data, size_t len, 804 | struct interface *iface, void *dest_addr) 805 | { 806 | if (iface->dhcpv6 == MODE_SERVER) { 807 | handle_client_request(addr, data, len, iface, dest_addr); 808 | } else if (iface->dhcpv6 == MODE_RELAY) { 809 | if (iface->master) 810 | relay_server_response(data, len); 811 | else 812 | relay_client_request(addr, data, len, iface); 813 | } 814 | } 815 | 816 | 817 | /* Relay server response (regular relay server handling) */ 818 | static void relay_server_response(uint8_t *data, size_t len) 819 | { 820 | /* Information we need to gather */ 821 | uint8_t *payload_data = NULL; 822 | size_t payload_len = 0; 823 | int32_t ifaceidx = 0; 824 | struct sockaddr_in6 target = {AF_INET6, htons(DHCPV6_CLIENT_PORT), 825 | 0, IN6ADDR_ANY_INIT, 0}; 826 | uint16_t otype, olen; 827 | uint8_t *odata, *end = data + len; 828 | /* Relay DHCPv6 reply from server to client */ 829 | struct dhcpv6_relay_header *h = (void*)data; 830 | 831 | debug("Got a DHCPv6-relay-reply"); 832 | 833 | if (len < sizeof(*h) || h->msg_type != DHCPV6_MSG_RELAY_REPL) 834 | return; 835 | 836 | memcpy(&target.sin6_addr, &h->peer_address, sizeof(struct in6_addr)); 837 | 838 | /* Go through options and find what we need */ 839 | dhcpv6_for_each_option(h->options, end, otype, olen, odata) { 840 | if (otype == DHCPV6_OPT_INTERFACE_ID 841 | && olen == sizeof(ifaceidx)) { 842 | memcpy(&ifaceidx, odata, sizeof(ifaceidx)); 843 | } else if (otype == DHCPV6_OPT_RELAY_MSG) { 844 | payload_data = odata; 845 | payload_len = olen; 846 | } 847 | } 848 | 849 | /* Invalid interface-id or basic payload */ 850 | struct interface *iface = odhcpd_get_interface_by_index(ifaceidx); 851 | if (!iface || iface->master || !payload_data || payload_len < 4) 852 | return; 853 | 854 | bool is_authenticated = false; 855 | struct in6_addr *dns_addrs6 = NULL; 856 | size_t dns_addrs6_cnt = 0; 857 | 858 | /* If the payload is relay-reply we have to send to the server port */ 859 | if (payload_data[0] == DHCPV6_MSG_RELAY_REPL) { 860 | target.sin6_port = htons(DHCPV6_SERVER_PORT); 861 | } else { /* Go through the payload data */ 862 | struct dhcpv6_client_header *dch = (void*)payload_data; 863 | end = payload_data + payload_len; 864 | 865 | dhcpv6_for_each_option(&dch[1], end, otype, olen, odata) { 866 | if (otype == DHCPV6_OPT_DNS_SERVERS && olen >= sizeof(struct in6_addr)) { 867 | dns_addrs6 = (struct in6_addr*)odata; 868 | dns_addrs6_cnt = olen / sizeof(struct in6_addr); 869 | } else if (otype == DHCPV6_OPT_AUTH) { 870 | is_authenticated = true; 871 | } 872 | } 873 | } 874 | 875 | /* Rewrite DNS servers if requested */ 876 | if (iface->always_rewrite_dns && dns_addrs6 && dns_addrs6_cnt > 0) { 877 | if (is_authenticated) 878 | return; /* Impossible to rewrite */ 879 | 880 | const struct in6_addr *rewrite = iface->dns_addrs6; 881 | struct in6_addr addr; 882 | size_t rewrite_cnt = iface->dns_addrs6_cnt; 883 | 884 | if (rewrite_cnt == 0) { 885 | if (odhcpd_get_interface_dns_addr6(iface, &addr)) 886 | return; /* Unable to get interface address */ 887 | 888 | rewrite = &addr; 889 | rewrite_cnt = 1; 890 | } 891 | 892 | /* Copy over any other addresses */ 893 | for (size_t i = 0; i < dns_addrs6_cnt; ++i) { 894 | size_t j = (i < rewrite_cnt) ? i : rewrite_cnt - 1; 895 | memcpy(&dns_addrs6[i], &rewrite[j], sizeof(*rewrite)); 896 | } 897 | } 898 | 899 | struct iovec iov = {payload_data, payload_len}; 900 | 901 | debug("Sending a DHCPv6-reply on %s", iface->name); 902 | 903 | odhcpd_send(iface->dhcpv6_event.uloop.fd, &target, &iov, 1, iface); 904 | } 905 | 906 | static struct odhcpd_ipaddr *relay_link_address(struct interface *iface) 907 | { 908 | struct odhcpd_ipaddr *addr = NULL; 909 | time_t now = odhcpd_time(); 910 | 911 | for (size_t i = 0; i < iface->addr6_len; i++) { 912 | if (iface->addr6[i].valid_lt <= (uint32_t)now) 913 | continue; 914 | 915 | if (iface->addr6[i].preferred_lt > (uint32_t)now) { 916 | addr = &iface->addr6[i]; 917 | break; 918 | } 919 | 920 | if (!addr || (iface->addr6[i].valid_lt > addr->valid_lt)) 921 | addr = &iface->addr6[i]; 922 | } 923 | 924 | return addr; 925 | } 926 | 927 | /* Relay client request (regular DHCPv6-relay) */ 928 | static void relay_client_request(struct sockaddr_in6 *source, 929 | const void *data, size_t len, struct interface *iface) 930 | { 931 | const struct dhcpv6_relay_header *h = data; 932 | /* Construct our forwarding envelope */ 933 | struct dhcpv6_relay_forward_envelope hdr = { 934 | .msg_type = DHCPV6_MSG_RELAY_FORW, 935 | .hop_count = 0, 936 | .interface_id_type = htons(DHCPV6_OPT_INTERFACE_ID), 937 | .interface_id_len = htons(sizeof(uint32_t)), 938 | .relay_message_type = htons(DHCPV6_OPT_RELAY_MSG), 939 | .relay_message_len = htons(len), 940 | }; 941 | struct iovec iov[2] = {{&hdr, sizeof(hdr)}, {(void *)data, len}}; 942 | struct interface *c; 943 | struct odhcpd_ipaddr *ip; 944 | struct sockaddr_in6 s; 945 | 946 | switch (h->msg_type) { 947 | /* Valid message types from clients */ 948 | case DHCPV6_MSG_SOLICIT: 949 | case DHCPV6_MSG_REQUEST: 950 | case DHCPV6_MSG_CONFIRM: 951 | case DHCPV6_MSG_RENEW: 952 | case DHCPV6_MSG_REBIND: 953 | case DHCPV6_MSG_RELEASE: 954 | case DHCPV6_MSG_DECLINE: 955 | case DHCPV6_MSG_INFORMATION_REQUEST: 956 | case DHCPV6_MSG_RELAY_FORW: 957 | case DHCPV6_MSG_DHCPV4_QUERY: 958 | break; 959 | /* Invalid message types from clients i.e. server messages */ 960 | case DHCPV6_MSG_ADVERTISE: 961 | case DHCPV6_MSG_REPLY: 962 | case DHCPV6_MSG_RECONFIGURE: 963 | case DHCPV6_MSG_RELAY_REPL: 964 | case DHCPV6_MSG_DHCPV4_RESPONSE: 965 | return; 966 | default: 967 | break; 968 | } 969 | 970 | debug("Got a DHCPv6-request on %s", iface->name); 971 | 972 | if (h->msg_type == DHCPV6_MSG_RELAY_FORW) { /* handle relay-forward */ 973 | if (h->hop_count >= DHCPV6_HOP_COUNT_LIMIT) 974 | return; /* Invalid hop count */ 975 | 976 | hdr.hop_count = h->hop_count + 1; 977 | } 978 | 979 | /* use memcpy here as the destination fields are unaligned */ 980 | memcpy(&hdr.peer_address, &source->sin6_addr, sizeof(struct in6_addr)); 981 | memcpy(&hdr.interface_id_data, &iface->ifindex, sizeof(iface->ifindex)); 982 | 983 | /* Detect public IP of slave interface to use as link-address */ 984 | ip = relay_link_address(iface); 985 | if (ip) 986 | memcpy(&hdr.link_address, &ip->addr.in6, sizeof(hdr.link_address)); 987 | 988 | memset(&s, 0, sizeof(s)); 989 | s.sin6_family = AF_INET6; 990 | s.sin6_port = htons(DHCPV6_SERVER_PORT); 991 | inet_pton(AF_INET6, ALL_DHCPV6_SERVERS, &s.sin6_addr); 992 | 993 | avl_for_each_element(&interfaces, c, avl) { 994 | if (!c->master || c->dhcpv6 != MODE_RELAY) 995 | continue; 996 | 997 | if (!ip) { 998 | /* No suitable address! Is the slave not configured yet? 999 | * Detect public IP of master interface and use it instead 1000 | * This is WRONG and probably violates the RFC. However 1001 | * otherwise we have a hen and egg problem because the 1002 | * slave-interface cannot be auto-configured. */ 1003 | ip = relay_link_address(c); 1004 | if (!ip) 1005 | continue; /* Could not obtain a suitable address */ 1006 | 1007 | memcpy(&hdr.link_address, &ip->addr.in6, sizeof(hdr.link_address)); 1008 | ip = NULL; 1009 | } 1010 | 1011 | debug("Sending a DHCPv6-relay-forward on %s", c->name); 1012 | 1013 | odhcpd_send(c->dhcpv6_event.uloop.fd, &s, iov, 2, c); 1014 | } 1015 | } 1016 | --------------------------------------------------------------------------------